#W# Bounty: The bounty system is working. Its a standalone system not MLQuest. Need to fill it in and add rewards.
This commit is contained in:
parent
b4e25a2713
commit
4a20c34c0d
8 changed files with 532 additions and 110 deletions
177
Projects/UOContent/Engines/Bounties/BountyBoard.cs
Normal file
177
Projects/UOContent/Engines/Bounties/BountyBoard.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
using System;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Engines.Bounties;
|
||||
using Server.Gumps;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Items;
|
||||
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class DungeonBountyBoard : Item
|
||||
{
|
||||
[Constructible]
|
||||
public DungeonBountyBoard() : base(0x1E5E)
|
||||
{
|
||||
Name = "Dungeon Bounty Board";
|
||||
Movable = false;
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (from is PlayerMobile pm)
|
||||
{
|
||||
if (from.InRange(Location, 3))
|
||||
pm.SendGump(new BountyBoardGump(pm));
|
||||
else
|
||||
from.SendLocalizedMessage(500446);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BountyBoardGump : Gump
|
||||
{
|
||||
private readonly PlayerMobile _player;
|
||||
|
||||
public BountyBoardGump(PlayerMobile player) : base(50, 50)
|
||||
{
|
||||
_player = player;
|
||||
BountyProfile profile = BountySystem.GetProfile(player);
|
||||
|
||||
AddPage(0);
|
||||
AddBackground(0, 0, 550, 480, 9270);
|
||||
AddAlphaRegion(10, 10, 530, 460);
|
||||
AddLabel(180, 15, 1152, "DUNGEON BOUNTY BOARD");
|
||||
|
||||
// --- ACTIVE BOUNTY SECTION ---
|
||||
AddLabel(20, 50, 0x3F, "--- Your Active Bounty ---");
|
||||
|
||||
if (profile.HasActiveBounty && BountyRegistry.AllBounties.TryGetValue(profile.ActiveBountyID, out var activeBounty))
|
||||
{
|
||||
AddLabel(20, 75, 1152, activeBounty.Title);
|
||||
AddLabel(350, 75, 2100, $"{profile.CurrentKills} / {activeBounty.RequiredKills} Slain");
|
||||
|
||||
if (profile.CurrentKills >= activeBounty.RequiredKills)
|
||||
{
|
||||
AddButton(20, 100, 4005, 4007, 1000, GumpButtonType.Reply, 0);
|
||||
AddLabel(55, 100, 0x44, "CLAIM REWARD");
|
||||
}
|
||||
else
|
||||
{
|
||||
int penaltyCost = activeBounty.RewardGold; // Cancellation costs the reward amount
|
||||
AddButton(20, 100, 4017, 4019, 1001, GumpButtonType.Reply, 0);
|
||||
AddLabel(55, 100, 33, $"Refuse Bounty (Cost: {penaltyCost} Gold)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLabel(20, 75, 1152, "You have no active bounties.");
|
||||
}
|
||||
|
||||
// --- AVAILABLE BOUNTIES SECTION ---
|
||||
AddLabel(20, 150, 0x3F, "--- Available Bounties ---");
|
||||
|
||||
if (profile.IsOnCooldown)
|
||||
{
|
||||
TimeSpan wait = profile.NextAvailableTime - DateTime.UtcNow;
|
||||
AddLabel(20, 180, 33, $"The board is empty. Next bounties arrive in {(int)wait.TotalMinutes} minutes.");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLabel(20, 170, 2100, $"Your Current Fame: {player.Fame}");
|
||||
|
||||
int y = 200;
|
||||
for (int tier = 1; tier <= 5; tier++)
|
||||
{
|
||||
// Grab an example from the tier to check fame requirements and rewards
|
||||
var example = BountyRegistry.GetRandomBounty(tier);
|
||||
if (example == null) continue;
|
||||
|
||||
if (player.Fame >= example.RequiredFame)
|
||||
{
|
||||
if (!profile.HasActiveBounty)
|
||||
{
|
||||
// Assigning Button IDs 101 to 105 for Tiers 1-5
|
||||
AddButton(20, y, 4005, 4007, 100 + tier, GumpButtonType.Reply, 0);
|
||||
}
|
||||
AddLabel(55, y, 1152, $"Request Tier {tier} Bounty");
|
||||
AddLabel(320, y, 0x3F, $"Earn {example.RewardTokens} Tokens");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLabel(55, y, 33, $"Tier {tier} Bounty (Requires {example.RequiredFame} Fame)");
|
||||
}
|
||||
y += 35;
|
||||
}
|
||||
}
|
||||
|
||||
AddButton(20, 440, 4017, 4019, 0, GumpButtonType.Reply, 0);
|
||||
AddLabel(55, 440, 1152, "Close Board");
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, in RelayInfo info)
|
||||
{
|
||||
if (info.ButtonID == 0) return;
|
||||
|
||||
BountyProfile profile = BountySystem.GetProfile(_player);
|
||||
|
||||
if (info.ButtonID == 1000) // CLAIM REWARD
|
||||
{
|
||||
if (profile.HasActiveBounty && BountyRegistry.AllBounties.TryGetValue(profile.ActiveBountyID, out var activeBounty))
|
||||
{
|
||||
if (profile.CurrentKills >= activeBounty.RequiredKills)
|
||||
{
|
||||
_player.AddToBackpack(new BountyToken(activeBounty.RewardTokens));
|
||||
_player.AddToBackpack(new Gold(activeBounty.RewardGold));
|
||||
_player.SendMessage(0x44, $"Complete! Received {activeBounty.RewardTokens} Tokens and {activeBounty.RewardGold} Gold.");
|
||||
_player.PlaySound(0x2E6);
|
||||
|
||||
// Reset Profile and trigger the 60 minute cooldown
|
||||
profile.ActiveBountyID = -1;
|
||||
profile.CurrentKills = 0;
|
||||
profile.NextAvailableTime = DateTime.UtcNow + TimeSpan.FromMinutes(60);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (info.ButtonID == 1001) // CANCEL BOUNTY (Refusal Fee)
|
||||
{
|
||||
if (BountyRegistry.AllBounties.TryGetValue(profile.ActiveBountyID, out var activeBounty))
|
||||
{
|
||||
int cost = activeBounty.RewardGold;
|
||||
|
||||
// Attempt to consume gold from their backpack
|
||||
if (_player.Backpack != null && _player.Backpack.ConsumeTotal(typeof(Gold), cost))
|
||||
{
|
||||
profile.ActiveBountyID = -1;
|
||||
profile.CurrentKills = 0;
|
||||
profile.NextAvailableTime = DateTime.UtcNow + TimeSpan.FromMinutes(60);
|
||||
_player.SendMessage(33, $"You paid the {cost} gold refusal fee and abandoned the bounty.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_player.SendMessage(33, $"You need {cost} gold in your backpack to refuse this bounty!");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (info.ButtonID > 100 && info.ButtonID <= 105) // ACCEPT RANDOM BOUNTY BY TIER
|
||||
{
|
||||
int selectedTier = info.ButtonID - 100;
|
||||
|
||||
if (!profile.HasActiveBounty && !profile.IsOnCooldown)
|
||||
{
|
||||
var newBounty = BountyRegistry.GetRandomBounty(selectedTier);
|
||||
|
||||
if (newBounty != null && _player.Fame >= newBounty.RequiredFame)
|
||||
{
|
||||
profile.ActiveBountyID = newBounty.ID;
|
||||
profile.CurrentKills = 0;
|
||||
_player.SendMessage(0x3F, $"You have been assigned: {newBounty.Title}");
|
||||
_player.PlaySound(0x04A);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_player.SendGump(new BountyBoardGump(_player));
|
||||
}
|
||||
}
|
||||
245
Projects/UOContent/Engines/Bounties/BountyCore.cs
Normal file
245
Projects/UOContent/Engines/Bounties/BountyCore.cs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using Server;
|
||||
using Server.Commands;
|
||||
using Server.Gumps;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Engines.Bounties;
|
||||
|
||||
// Moved BountyDefinition to BountyRegistry.cs
|
||||
|
||||
// ==========================================
|
||||
// 1. PLAYER BOUNTY PROFILE
|
||||
// ==========================================
|
||||
public class BountyProfile
|
||||
{
|
||||
public int ActiveBountyID { get; set; } = -1;
|
||||
public int CurrentKills { get; set; } = 0;
|
||||
|
||||
// Time-Gate Tracking
|
||||
public DateTime NextAvailableTime { get; set; } = DateTime.MinValue;
|
||||
|
||||
public bool HasActiveBounty => ActiveBountyID != -1;
|
||||
public bool IsOnCooldown => DateTime.UtcNow < NextAvailableTime;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. JSON SAVE DATA STRUCTURE
|
||||
// ==========================================
|
||||
public class BountySaveData
|
||||
{
|
||||
public uint MobileSerial { get; set; }
|
||||
public int ActiveBountyID { get; set; }
|
||||
public int CurrentKills { get; set; }
|
||||
public long NextAvailableTicks { get; set; } // Save DateTime as ticks
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. BOUNTY SYSTEM ENGINE
|
||||
// ==========================================
|
||||
public static class BountySystem
|
||||
{
|
||||
private static Dictionary<Mobile, BountyProfile> Profiles = new();
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
EventSink.WorldSave += delegate { OnSave(); };
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Bounties", AccessLevel.Player, new CommandEventHandler(Bounties_OnCommand));
|
||||
|
||||
// Initialize the new external registry
|
||||
BountyRegistry.Initialize();
|
||||
Load();
|
||||
}
|
||||
|
||||
[Usage("Bounties")]
|
||||
[Description("Checks your current active dungeon bounty.")]
|
||||
private static void Bounties_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Mobile is not PlayerMobile pm) return;
|
||||
BountyProfile profile = GetProfile(pm);
|
||||
pm.CloseGump<ActiveBountyGump>();
|
||||
pm.SendGump(new ActiveBountyGump(pm, profile));
|
||||
}
|
||||
|
||||
public static BountyProfile GetProfile(Mobile pm)
|
||||
{
|
||||
if (!Profiles.TryGetValue(pm, out var profile))
|
||||
{
|
||||
profile = new BountyProfile();
|
||||
Profiles[pm] = profile;
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
// --- BULLETPROOF REGION CHECKER ---
|
||||
private static bool IsInValidRegion(Mobile creature, Mobile killer, string requiredRegion)
|
||||
{
|
||||
if (CheckRegionTree(creature.Region, requiredRegion)) return true;
|
||||
if (CheckRegionTree(killer.Region, requiredRegion)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool CheckRegionTree(Region region, string requiredRegion)
|
||||
{
|
||||
Region current = region;
|
||||
while (current != null)
|
||||
{
|
||||
if (current.Name != null && current.Name.IndexOf(requiredRegion, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return true;
|
||||
current = current.Parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- KILL TRACKING HOOK ---
|
||||
public static void OnCreatureDeath(BaseCreature creature)
|
||||
{
|
||||
if (creature == null) return;
|
||||
Mobile source = creature.LastKiller ?? creature.FindMostRecentDamager(true);
|
||||
|
||||
if (source is BaseCreature pet && pet.Controlled && pet.ControlMaster != null)
|
||||
source = pet.ControlMaster;
|
||||
|
||||
if (source is not PlayerMobile killer) return;
|
||||
|
||||
BountyProfile profile = GetProfile(killer);
|
||||
|
||||
if (profile.HasActiveBounty && BountyRegistry.AllBounties.TryGetValue(profile.ActiveBountyID, out BountyDefinition bounty))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(bounty.RequiredRegion))
|
||||
{
|
||||
if (!IsInValidRegion(creature, killer, bounty.RequiredRegion)) return;
|
||||
}
|
||||
|
||||
if (profile.CurrentKills < bounty.RequiredKills && bounty.Matches(creature.GetType()))
|
||||
{
|
||||
profile.CurrentKills++;
|
||||
if (profile.CurrentKills >= bounty.RequiredKills)
|
||||
{
|
||||
killer.SendMessage(0x3F, $"[Bounty Complete]: You have slain {profile.CurrentKills}/{bounty.RequiredKills} {bounty.Title}!");
|
||||
killer.PlaySound(0x5B5);
|
||||
}
|
||||
else
|
||||
{
|
||||
killer.SendMessage(0x35, $"[Bounty Progress]: {creature.Name} slain. ({profile.CurrentKills}/{bounty.RequiredKills})");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- DATA PERSISTENCE (JSON) ---
|
||||
private static string FilePath => Path.Combine(Core.BaseDirectory, "Data", "DungeonBounties.json");
|
||||
|
||||
private static void OnSave()
|
||||
{
|
||||
try
|
||||
{
|
||||
string dir = Path.GetDirectoryName(FilePath);
|
||||
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
|
||||
|
||||
var list = new List<BountySaveData>();
|
||||
foreach (var kvp in Profiles)
|
||||
{
|
||||
if (kvp.Key != null && !kvp.Key.Deleted && (kvp.Value.HasActiveBounty || kvp.Value.IsOnCooldown))
|
||||
{
|
||||
list.Add(new BountySaveData
|
||||
{
|
||||
MobileSerial = kvp.Key.Serial.Value,
|
||||
ActiveBountyID = kvp.Value.ActiveBountyID,
|
||||
CurrentKills = kvp.Value.CurrentKills,
|
||||
NextAvailableTicks = kvp.Value.NextAvailableTime.Ticks
|
||||
});
|
||||
}
|
||||
}
|
||||
File.WriteAllText(FilePath, JsonSerializer.Serialize(list, new JsonSerializerOptions { WriteIndented = true }));
|
||||
}
|
||||
catch (Exception e) { Console.WriteLine($"Error saving Bounties: {e.Message}"); }
|
||||
}
|
||||
|
||||
private static void Load()
|
||||
{
|
||||
if (!File.Exists(FilePath)) return;
|
||||
try
|
||||
{
|
||||
var list = JsonSerializer.Deserialize<List<BountySaveData>>(File.ReadAllText(FilePath));
|
||||
if (list == null) return;
|
||||
|
||||
foreach (var data in list)
|
||||
{
|
||||
Mobile m = World.FindMobile((Serial)data.MobileSerial);
|
||||
if (m != null && !m.Deleted)
|
||||
{
|
||||
Profiles[m] = new BountyProfile
|
||||
{
|
||||
ActiveBountyID = data.ActiveBountyID,
|
||||
CurrentKills = data.CurrentKills,
|
||||
NextAvailableTime = new DateTime(data.NextAvailableTicks, DateTimeKind.Utc)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) { Console.WriteLine($"Error loading Bounties: {e.Message}"); }
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 4. PLAYER STATUS GUMP ([Bounties Command)
|
||||
// ==========================================
|
||||
public class ActiveBountyGump : Gump
|
||||
{
|
||||
public ActiveBountyGump(PlayerMobile pm, BountyProfile profile) : base(100, 100)
|
||||
{
|
||||
AddPage(0);
|
||||
AddBackground(0, 0, 350, 220, 9270);
|
||||
AddAlphaRegion(10, 10, 330, 200);
|
||||
AddLabel(110, 15, 1152, "ACTIVE BOUNTY STATUS");
|
||||
|
||||
if (profile.HasActiveBounty && BountyRegistry.AllBounties.TryGetValue(profile.ActiveBountyID, out BountyDefinition bounty))
|
||||
{
|
||||
AddLabel(20, 50, 0x3F, "Target:");
|
||||
AddLabel(90, 50, 1152, bounty.Title);
|
||||
|
||||
if (!string.IsNullOrEmpty(bounty.RequiredRegion))
|
||||
{
|
||||
AddLabel(20, 75, 0x3F, "Location:");
|
||||
AddLabel(90, 75, 1152, bounty.RequiredRegion);
|
||||
}
|
||||
|
||||
AddLabel(20, 100, 0x3F, "Progress:");
|
||||
|
||||
if (profile.CurrentKills >= bounty.RequiredKills)
|
||||
{
|
||||
AddLabel(90, 100, 0x44, $"{profile.CurrentKills} / {bounty.RequiredKills} - COMPLETE!");
|
||||
AddLabel(20, 125, 0x44, "Return to a Bounty Board to claim!");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLabel(90, 100, 2100, $"{profile.CurrentKills} / {bounty.RequiredKills} Slain");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLabel(20, 75, 33, "You do not have an active bounty.");
|
||||
|
||||
if (profile.IsOnCooldown)
|
||||
{
|
||||
TimeSpan wait = profile.NextAvailableTime - DateTime.UtcNow;
|
||||
AddLabel(20, 95, 2100, $"New bounties in: {(int)wait.TotalMinutes} minutes.");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLabel(20, 95, 1152, "Visit a Bounty Board to accept one.");
|
||||
}
|
||||
}
|
||||
|
||||
AddButton(20, 175, 4017, 4019, 0, GumpButtonType.Reply, 0);
|
||||
AddLabel(55, 175, 1152, "Close");
|
||||
}
|
||||
}
|
||||
108
Projects/UOContent/Engines/Bounties/BountyRegistry.cs
Normal file
108
Projects/UOContent/Engines/Bounties/BountyRegistry.cs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Engines.Bounties;
|
||||
|
||||
// ==========================================
|
||||
// 1. BOUNTY DEFINITION
|
||||
// ==========================================
|
||||
public class BountyDefinition
|
||||
{
|
||||
public int ID { get; }
|
||||
public string Title { get; }
|
||||
public Type[] TargetTypes { get; }
|
||||
public int RequiredKills { get; }
|
||||
public int RequiredFame { get; }
|
||||
public int RewardTokens { get; }
|
||||
public int RewardGold { get; }
|
||||
public string RequiredRegion { get; }
|
||||
|
||||
public BountyDefinition(int id, string title, Type[] targetTypes, int requiredKills, int requiredFame, int rewardTokens, int rewardGold, string requiredRegion = null)
|
||||
{
|
||||
ID = id;
|
||||
Title = title;
|
||||
TargetTypes = targetTypes;
|
||||
RequiredKills = requiredKills;
|
||||
RequiredFame = requiredFame;
|
||||
RewardTokens = rewardTokens;
|
||||
RewardGold = rewardGold;
|
||||
RequiredRegion = requiredRegion;
|
||||
}
|
||||
|
||||
public bool Matches(Type type)
|
||||
{
|
||||
foreach (var t in TargetTypes)
|
||||
{
|
||||
if (t == type || t.IsAssignableFrom(type))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. TIERED BOUNTY REGISTRY
|
||||
// ==========================================
|
||||
public static class BountyRegistry
|
||||
{
|
||||
// Lists mapped by Tier (1 through 5)
|
||||
public static Dictionary<int, List<BountyDefinition>> Tiers { get; private set; } = new();
|
||||
|
||||
// A master dictionary to look up any bounty by ID
|
||||
public static Dictionary<int, BountyDefinition> AllBounties { get; private set; } = new();
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// ==========================================
|
||||
// TIER 1: Local Adventurer (Fame 0) - Rewards: 5 Tokens, 500g
|
||||
// ==========================================
|
||||
Add(1, new BountyDefinition(1, "Covetous Harpies", new[] { typeof(Harpy) }, 15, 0, 5, 500, "Covetous"));
|
||||
Add(1, new BountyDefinition(2, "Shame Earth Elementals", new[] { typeof(EarthElemental) }, 10, 0, 5, 500, "Shame"));
|
||||
Add(1, new BountyDefinition(3, "Despise Lizardmen", new[] { typeof(Lizardman) }, 15, 0, 5, 500, "Despise"));
|
||||
|
||||
// ==========================================
|
||||
// TIER 2: Estimable Hunter (Fame 1250) - Rewards: 15 Tokens, 1200g
|
||||
// ==========================================
|
||||
Add(2, new BountyDefinition(4, "Deceit Skeletal Knights", new[] { typeof(SkeletalKnight) }, 10, 1250, 15, 1200, "Deceit"));
|
||||
Add(2, new BountyDefinition(5, "Destard Drakes", new[] { typeof(Drake) }, 10, 1250, 15, 1200, "Destard"));
|
||||
|
||||
// ==========================================
|
||||
// TIER 3: Great Slayer (Fame 2500) - Rewards: 30 Tokens, 2500g
|
||||
// ==========================================
|
||||
Add(3, new BountyDefinition(6, "Despise Titans", new[] { typeof(Titan) }, 8, 2500, 30, 2500, "Despise"));
|
||||
|
||||
// ==========================================
|
||||
// TIER 4: Illustrious Champion (Fame 5000) - Rewards: 60 Tokens, 5000g
|
||||
// ==========================================
|
||||
Add(4, new BountyDefinition(7, "Destard Dragons", new[] { typeof(Dragon) }, 3, 5000, 60, 5000, "Destard"));
|
||||
|
||||
// ==========================================
|
||||
// TIER 5: Glorious Legend (Fame 10000) - Rewards: 100 Tokens, 10000g
|
||||
// ==========================================
|
||||
Add(5, new BountyDefinition(8, "Hythloth Balrons", new[] { typeof(Balron) }, 2, 10000, 100, 10000, "Hythloth"));
|
||||
}
|
||||
|
||||
private static void Add(int tier, BountyDefinition bounty)
|
||||
{
|
||||
if (!Tiers.ContainsKey(tier))
|
||||
{
|
||||
Tiers[tier] = new List<BountyDefinition>();
|
||||
}
|
||||
|
||||
Tiers[tier].Add(bounty);
|
||||
AllBounties[bounty.ID] = bounty;
|
||||
}
|
||||
|
||||
// Pulls a random bounty from a specific tier pool
|
||||
public static BountyDefinition GetRandomBounty(int tier)
|
||||
{
|
||||
if (!Tiers.ContainsKey(tier)) return null;
|
||||
|
||||
var list = Tiers[tier];
|
||||
return list[Utility.Random(list.Count)];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Engines.MLQuests;
|
||||
using Server.Engines.MLQuests.Objectives;
|
||||
using Server.Engines.MLQuests.Rewards;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Engines.MLQuests.Definitions;
|
||||
|
||||
// ==========================================
|
||||
// 1. BOUNTY BOARD ITEM
|
||||
// ==========================================
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class DungeonBountyBoard : Item, IQuestGiver
|
||||
{
|
||||
// Implementation for IQuestGiver
|
||||
public List<MLQuest> MLQuests => MLQuestSystem.FindQuestList(GetType());
|
||||
|
||||
// Automatically registers all bounties when the server boots up
|
||||
public static void Initialize()
|
||||
{
|
||||
MLQuestSystem.Register(new DestardDrakeBounty(), typeof(DungeonBountyBoard));
|
||||
MLQuestSystem.Register(new DeceitSkeletalKnightBounty(), typeof(DungeonBountyBoard));
|
||||
}
|
||||
|
||||
[Constructible]
|
||||
public DungeonBountyBoard() : base(0x1E5E) // Bulletin board Graphic ID
|
||||
{
|
||||
Name = "Dungeon Bounty Board";
|
||||
Movable = false;
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (from is PlayerMobile pm)
|
||||
{
|
||||
if (from.InRange(GetWorldLocation(), 3))
|
||||
{
|
||||
MLQuestSystem.OnDoubleClick(this, pm);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(500446); // That is too far away.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. DESTARD BOUNTY: DRAKES
|
||||
// ==========================================
|
||||
public class DestardDrakeBounty : MLQuest
|
||||
{
|
||||
public DestardDrakeBounty()
|
||||
{
|
||||
Activated = true;
|
||||
Title = "Bounty: Destard Drakes";
|
||||
|
||||
Description = "Notice to all adventurers: Drake activity in the caverns of Destard has surged! Slay 10 Drakes to thin their numbers and keep the surrounding passes safe.";
|
||||
RefusalMessage = "The bounty remains posted if you change your mind.";
|
||||
InProgressMessage = "You haven't slain enough Drakes yet. Return once you've dispatched 10 of them in Destard.";
|
||||
CompletionMessage = "Excellent work! The realm thanks you for your service. Here is your gold reward.";
|
||||
CompletionNotice = CompletionNoticeShort;
|
||||
|
||||
// Objective: Slay 10 Drakes
|
||||
Objectives.Add(new KillObjective(10, new[] { typeof(Drake) }, "Drakes"));
|
||||
|
||||
// Reward: 1,200 Gold
|
||||
Rewards.Add(new ItemReward("1,200 Bounty Tokens", typeof(BountyToken), 1200));
|
||||
}
|
||||
|
||||
public override void Generate()
|
||||
{
|
||||
base.Generate();
|
||||
MLQuestSystem.Register(this, typeof(DungeonBountyBoard));
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. DECEIT BOUNTY: SKELETAL KNIGHTS
|
||||
// ==========================================
|
||||
public class DeceitSkeletalKnightBounty : MLQuest
|
||||
{
|
||||
public DeceitSkeletalKnightBounty()
|
||||
{
|
||||
Activated = true;
|
||||
Title = "Bounty: Deceit Skeletal Knights";
|
||||
|
||||
Description = "Undead forces are amassing within the halls of Deceit. Slay 10 Skeletal Knights and report back to claim your reward.";
|
||||
RefusalMessage = "The bounty remains posted on the board.";
|
||||
InProgressMessage = "The skeletal knights still roam Deceit. Return when you have destroyed 10 of them.";
|
||||
CompletionMessage = "The undead threat has been pushed back! Take your gold reward.";
|
||||
CompletionNotice = CompletionNoticeShort;
|
||||
|
||||
// Objective: Slay 10 Skeletal Knights
|
||||
Objectives.Add(new KillObjective(10, new[] { typeof(SkeletalKnight) }, "Skeletal Knights"));
|
||||
|
||||
// Reward: 1,500 Gold
|
||||
Rewards.Add(new ItemReward("1,500 Bounty Tokens", typeof(BountyToken), 1500));
|
||||
}
|
||||
|
||||
public override void Generate()
|
||||
{
|
||||
base.Generate();
|
||||
MLQuestSystem.Register(this, typeof(DungeonBountyBoard));
|
||||
}
|
||||
}
|
||||
|
|
@ -3720,7 +3720,7 @@ namespace Server.Mobiles
|
|||
{
|
||||
c.Delete();
|
||||
}
|
||||
|
||||
Server.Engines.Bounties.BountySystem.OnCreatureDeath(this);
|
||||
CreatureEvents.CreatureDeathEvent(this);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue