From 4a20c34c0d1966fda55f6e59df3e644d84956759 Mon Sep 17 00:00:00 2001 From: WarrentyExpired Date: Fri, 9 Oct 2026 20:00:00 -0400 Subject: [PATCH] #W# Bounty: The bounty system is working. Its a standalone system not MLQuest. Need to fill it in and add rewards. --- .gitignore | 1 + .../UOContent/Engines/Bounties/BountyBoard.cs | 177 +++++++++++++ .../UOContent/Engines/Bounties/BountyCore.cs | 245 ++++++++++++++++++ .../Engines/Bounties/BountyRegistry.cs | 108 ++++++++ .../Items => Bounties}/BountyToken.cs | 0 .../Mobiles => Bounties}/BountyVendor.cs | 0 .../Definitions/DungeonBountyBoard.cs | 109 -------- Projects/UOContent/Mobiles/BaseCreature.cs | 2 +- 8 files changed, 532 insertions(+), 110 deletions(-) create mode 100644 Projects/UOContent/Engines/Bounties/BountyBoard.cs create mode 100644 Projects/UOContent/Engines/Bounties/BountyCore.cs create mode 100644 Projects/UOContent/Engines/Bounties/BountyRegistry.cs rename Projects/UOContent/Engines/{ML Quests/Items => Bounties}/BountyToken.cs (100%) rename Projects/UOContent/Engines/{ML Quests/Mobiles => Bounties}/BountyVendor.cs (100%) delete mode 100644 Projects/UOContent/Engines/ML Quests/Definitions/DungeonBountyBoard.cs diff --git a/.gitignore b/.gitignore index 4d9b383a8..63e61391b 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ # Ignore everything under tools/ except the operator scripts checked in below. /tools/* !/tools/Export-IpBlocklist.ps1 +Distribution/Data/DungeonBounties.json diff --git a/Projects/UOContent/Engines/Bounties/BountyBoard.cs b/Projects/UOContent/Engines/Bounties/BountyBoard.cs new file mode 100644 index 000000000..3291e67c6 --- /dev/null +++ b/Projects/UOContent/Engines/Bounties/BountyBoard.cs @@ -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)); + } +} diff --git a/Projects/UOContent/Engines/Bounties/BountyCore.cs b/Projects/UOContent/Engines/Bounties/BountyCore.cs new file mode 100644 index 000000000..a795ef1c3 --- /dev/null +++ b/Projects/UOContent/Engines/Bounties/BountyCore.cs @@ -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 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(); + 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(); + 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>(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"); + } +} diff --git a/Projects/UOContent/Engines/Bounties/BountyRegistry.cs b/Projects/UOContent/Engines/Bounties/BountyRegistry.cs new file mode 100644 index 000000000..1bbf3db13 --- /dev/null +++ b/Projects/UOContent/Engines/Bounties/BountyRegistry.cs @@ -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> Tiers { get; private set; } = new(); + + // A master dictionary to look up any bounty by ID + public static Dictionary 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(); + } + + 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)]; + } +} diff --git a/Projects/UOContent/Engines/ML Quests/Items/BountyToken.cs b/Projects/UOContent/Engines/Bounties/BountyToken.cs similarity index 100% rename from Projects/UOContent/Engines/ML Quests/Items/BountyToken.cs rename to Projects/UOContent/Engines/Bounties/BountyToken.cs diff --git a/Projects/UOContent/Engines/ML Quests/Mobiles/BountyVendor.cs b/Projects/UOContent/Engines/Bounties/BountyVendor.cs similarity index 100% rename from Projects/UOContent/Engines/ML Quests/Mobiles/BountyVendor.cs rename to Projects/UOContent/Engines/Bounties/BountyVendor.cs diff --git a/Projects/UOContent/Engines/ML Quests/Definitions/DungeonBountyBoard.cs b/Projects/UOContent/Engines/ML Quests/Definitions/DungeonBountyBoard.cs deleted file mode 100644 index f669c1266..000000000 --- a/Projects/UOContent/Engines/ML Quests/Definitions/DungeonBountyBoard.cs +++ /dev/null @@ -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 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)); - } -} diff --git a/Projects/UOContent/Mobiles/BaseCreature.cs b/Projects/UOContent/Mobiles/BaseCreature.cs index 0833a6e45..a5a2b143d 100644 --- a/Projects/UOContent/Mobiles/BaseCreature.cs +++ b/Projects/UOContent/Mobiles/BaseCreature.cs @@ -3720,7 +3720,7 @@ namespace Server.Mobiles { c.Delete(); } - + Server.Engines.Bounties.BountySystem.OnCreatureDeath(this); CreatureEvents.CreatureDeathEvent(this); }