From 55aada83d4a3f8865667b02c0d9c13350f2705c4 Mon Sep 17 00:00:00 2001 From: WarrentyExpired Date: Sun, 1 Nov 2026 10:53:29 -0500 Subject: [PATCH] #W# Added: Town gathering spots for citizens. --- Projects/UOContent/Misc/RandomThings.cs | 127 +++++++++++++++++ .../Townfolk/{ => Citizens}/Citizens.cs | 48 ++++++- .../Townfolk/Citizens/FriendlyMonster.cs | 65 +++++++++ .../Townfolk/Citizens/GatheringCitizen.cs | 53 +++++++ .../Townfolk/Citizens/GatheringMonster.cs | 53 +++++++ .../Townfolk/Citizens/TownGatheringSpot.cs | 132 ++++++++++++++++++ 6 files changed, 475 insertions(+), 3 deletions(-) create mode 100644 Projects/UOContent/Misc/RandomThings.cs rename Projects/UOContent/Mobiles/Townfolk/{ => Citizens}/Citizens.cs (76%) create mode 100644 Projects/UOContent/Mobiles/Townfolk/Citizens/FriendlyMonster.cs create mode 100644 Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringCitizen.cs create mode 100644 Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringMonster.cs create mode 100644 Projects/UOContent/Mobiles/Townfolk/Citizens/TownGatheringSpot.cs diff --git a/Projects/UOContent/Misc/RandomThings.cs b/Projects/UOContent/Misc/RandomThings.cs new file mode 100644 index 000000000..be4070d51 --- /dev/null +++ b/Projects/UOContent/Misc/RandomThings.cs @@ -0,0 +1,127 @@ +using System; +using Server; +using System.Collections; +using Server.Misc; +using Server.Network; +using Server.Commands; +using Server.Commands.Generic; +using Server.Items; +using Server.Regions; + +namespace Server.Misc +{ + class RandomThings + { + public static string GetRandomRealDungeon() + { + string rDungeon = "Unknown"; + switch( Utility.RandomMinMax( 0, 8 )) + { + case 0: rDungeon = "Dungeon Covetous"; break; + case 1: rDungeon = "Dungeon Despice"; break; + case 2: rDungeon = "Dungeon Deceit"; break; + case 3: rDungeon = "Dungeon Destard"; break; + case 4: rDungeon = "Dungeon Despise"; break; + case 5: rDungeon = "Dungeon Shame"; break; + case 6: rDungeon = "Dungeon Hythloth"; break; + case 7: rDungeon = "Dungeon Terathan Keep"; break; + case 8: rDungeon = "Dungeon of Ice"; break; + } + return rDungeon; + } + + public static string GetRandomRealTown() + { + string rTown = "Unknown"; + switch( Utility.RandomMinMax( 0, 12 )) + { + case 0: rTown = "Bastion"; break; + case 1: rTown = "Mireport"; break; + case 2: rTown = "Myrish"; break; + case 3: rTown = "Teareek"; break; + case 4: rTown = "Landow"; break; + case 5: rTown = "Rivermouth"; break; + case 6: rTown = "Wharf's End"; break; + case 7: rTown = "Coldbarrow"; break; + case 8: rTown = "Fairwinds"; break; + case 9: rTown = "Stonehollow"; break; + case 10: rTown = "Cresthaven"; break; + case 11: rTown = "Port Grim"; break; + case 12: rTown = "Maerid"; break; + } + return rTown; + } + + public static string GetRandomFakeDungeon() + { + string fDungeon = "Unknown"; + switch( Utility.RandomMinMax( 0, 2)) + { + case 0: fDungeon = "Scourge Keep"; break; + case 1: fDungeon = "Eliffs Cave"; break; + case 2: fDungeon = "Crypts Elbow"; break; + } + return fDungeon; + } + + public static string GetRandomFakeTown() + { + string fTown = "Unknown"; + switch( Utility.RandomMinMax( 0, 2)) + { + case 0: fTown = "Klydeport"; break; + case 1: fTown = "Longshore"; break; + case 2: fTown = "Tessburg"; break; + } + return fTown; + } + + public static string GetRandomAdjective() + { + string adj = "an odd"; + switch( Utility.RandomMinMax( 0, 6 )) + { + case 0: adj = "an odd"; break; + case 1: adj = "a weirdly crafted"; break; + case 2: adj = "a suspiciously shiny"; break; + case 3: adj = "a blood-stained"; break; + case 4: adj = "a masterfully forged"; break; + case 5: adj = "an ancient, cursed"; break; + case 6: adj = "a rusted but reliable"; break; + } + return adj; + } + + public static string GetRandomName() + { + string name = "John"; + switch( Utility.RandomMinMax( 0, 7 )) + { + case 0: name = "Balthazar"; break; + case 1: name = "Kaelen"; break; + case 2: name = "Elias"; break; + case 3: name = "Gwendolyn"; break; + case 4: name = "Darius"; break; + case 5: name = "Seraphina"; break; + case 6: name = "Vander"; break; + case 7: name = "Isolde"; break; + } + return name; + } + + public static string GetRandomTitle() + { + string title = "King"; + switch( Utility.RandomMinMax( 0, 5 )) + { + case 0: title = "King"; break; + case 1: title = "Queen"; break; + case 2: title = "Baron"; break; + case 3: title = "Duke"; break; + case 4: title = "Lord"; break; + case 5: title = "Lady"; break; + } + return title; + } + } +} diff --git a/Projects/UOContent/Mobiles/Townfolk/Citizens.cs b/Projects/UOContent/Mobiles/Townfolk/Citizens/Citizens.cs similarity index 76% rename from Projects/UOContent/Mobiles/Townfolk/Citizens.cs rename to Projects/UOContent/Mobiles/Townfolk/Citizens/Citizens.cs index 7fef8fc54..21319df1c 100644 --- a/Projects/UOContent/Mobiles/Townfolk/Citizens.cs +++ b/Projects/UOContent/Mobiles/Townfolk/Citizens/Citizens.cs @@ -2,6 +2,8 @@ using System; using Server; using Server.Items; using ModernUO.Serialization; +using Server.Misc; +using Server.Custom; namespace Server.Mobiles { @@ -12,12 +14,29 @@ namespace Server.Mobiles private static readonly string[] _ChatterLines = new string[] { + // General "Lovely weather we're having today.", - "I heard the dungeons are more dangerous than usual...", - "Just another quiet day in town.", "I need to visit the provisioner soon.", "Have you seen my lost dog?", - "The guards seem on edge lately." + "The guards seem on edge lately.", + "Just another quiet day in town.", + + // Real Dungeon + "I heard the {rdungeon} is more dangerous than usual...", + "Stay away from {rdungeon}, I lost a good friend there.", + + // Real Town + "Did you hear the recent news from {rtown}?", + + // Fake Dungeon + "Did you hear that {fdungeon} has been destroyed?", + + // Fake Town + "I'm thinking of packing up and moving to {ftown}.", + + // Active Dungeon of the Day + "I saw a huge group of adventurers heading toward {activedungeon} this morning.", + "They say the magical energies are shifting around {activedungeon} today." }; // Add your custom titles here! @@ -148,6 +167,29 @@ namespace Server.Mobiles if (playerNearby) { string line = _ChatterLines[Utility.Random(_ChatterLines.Length)]; + + if (line.Contains("{rdungeon}")) + line = line.Replace("{rdungeon}", Server.Misc.RandomThings.GetRandomRealDungeon()); + + if (line.Contains("{rtown}")) + line = line.Replace("{rtown}", Server.Misc.RandomThings.GetRandomRealTown()); + + if (line.Contains("{fdungeon}")) + line = line.Replace("{fdungeon}", Server.Misc.RandomThings.GetRandomFakeDungeon()); + + if (line.Contains("{ftown}")) + line = line.Replace("{ftown}", Server.Misc.RandomThings.GetRandomFakeTown()); + + if (line.Contains("{activedungeon}")) + { + string dotd = Server.Custom.DailyDungeonManager.ActiveDungeon; + + if (string.IsNullOrEmpty(dotd)) + dotd = "one of the local dungeons"; + + line = line.Replace("{activedungeon}", dotd); + } + Say(line); } diff --git a/Projects/UOContent/Mobiles/Townfolk/Citizens/FriendlyMonster.cs b/Projects/UOContent/Mobiles/Townfolk/Citizens/FriendlyMonster.cs new file mode 100644 index 000000000..120449b98 --- /dev/null +++ b/Projects/UOContent/Mobiles/Townfolk/Citizens/FriendlyMonster.cs @@ -0,0 +1,65 @@ +using System; +using Server; +using Server.Mobiles; +using ModernUO.Serialization; + +namespace Server.Mobiles +{ + [SerializationGenerator(0, false)] + public partial class FriendlyTownMonster : BaseCreature + { + private DateTime _NextChatter; + public override bool InitialInnocent => true; + + [Constructible] + public FriendlyTownMonster() : base(AIType.AI_Animal, FightMode.None) + { + Name = NameList.RandomName("orc"); + Title = "the civilized"; + Body = Utility.RandomList(17, 1075, 1072); + BaseSoundID = 0x45A; + + Karma = 1000; + Fame = 1000; + _NextChatter = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(10, 20)); + } + + public override void OnThink() + { + base.OnThink(); + if (Deleted) return; + + if (DateTime.UtcNow > _NextChatter) + { + bool playerNearby = false; + foreach (Mobile m in GetMobilesInRange(8)) + { + if (m != null && m.Player && m.Alive) + { + playerNearby = true; + break; + } + } + + if (playerNearby) + { + string[] lines = { + "*grunts amicably*", + "Me like town. No heroes trying to stab me.", + "Guards no chase me here.", + "You have shiny coins for trading?" + }; + Say(lines[Utility.Random(lines.Length)]); + } + + _NextChatter = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(30, 60)); + } + } + + public override bool IsHarmfulCriminal(Mobile target) => true; + + public FriendlyTownMonster(Serial serial) : base(serial) + { + } + } +} diff --git a/Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringCitizen.cs b/Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringCitizen.cs new file mode 100644 index 000000000..78859c69f --- /dev/null +++ b/Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringCitizen.cs @@ -0,0 +1,53 @@ +using System; +using Server; + +namespace Server.Mobiles +{ + public class GatheringCitizen : Citizen + { + private Direction _GatherDirection; + + [CommandProperty(AccessLevel.GameMaster)] + public Direction GatherDirection + { + get => _GatherDirection; + set => _GatherDirection = value; + } + + [Constructible] + public GatheringCitizen() : base() + { + CantWalk = true; + } + + public override void OnThink() + { + base.OnThink(); + + if (Direction != _GatherDirection) + { + Direction = _GatherDirection; + } + } + + public GatheringCitizen(Serial serial) : base(serial) + { + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + writer.Write(0); + + writer.Write((int)_GatherDirection); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + int version = reader.ReadInt(); + + _GatherDirection = (Direction)reader.ReadInt(); + } + } +} diff --git a/Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringMonster.cs b/Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringMonster.cs new file mode 100644 index 000000000..79bda0ef5 --- /dev/null +++ b/Projects/UOContent/Mobiles/Townfolk/Citizens/GatheringMonster.cs @@ -0,0 +1,53 @@ +using System; +using Server; + +namespace Server.Mobiles +{ + public class GatheringMonster : FriendlyTownMonster + { + private Direction _GatherDirection; + + [CommandProperty(AccessLevel.GameMaster)] + public Direction GatherDirection + { + get => _GatherDirection; + set => _GatherDirection = value; + } + + [Constructible] + public GatheringMonster() : base() + { + CantWalk = true; + } + + public override void OnThink() + { + base.OnThink(); + + if (Direction != _GatherDirection) + { + Direction = _GatherDirection; + } + } + + public GatheringMonster(Serial serial) : base(serial) + { + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + writer.Write(0); + + writer.Write((int)_GatherDirection); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + int version = reader.ReadInt(); + + _GatherDirection = (Direction)reader.ReadInt(); + } + } +} diff --git a/Projects/UOContent/Mobiles/Townfolk/Citizens/TownGatheringSpot.cs b/Projects/UOContent/Mobiles/Townfolk/Citizens/TownGatheringSpot.cs new file mode 100644 index 000000000..5aafadf09 --- /dev/null +++ b/Projects/UOContent/Mobiles/Townfolk/Citizens/TownGatheringSpot.cs @@ -0,0 +1,132 @@ +using System; +using Server; +using Server.Mobiles; +using System.Collections.Generic; + +namespace Server.Items +{ + public class TownGatheringSpot : Item + { + private List _Attendees = new List(); + + [Constructible] + public TownGatheringSpot() : base(0xFF8D) + { + Name = "Town Gathering Spot"; + Visible = true; + Movable = false; + + Timer.DelayCall(TimeSpan.FromSeconds(1.0), CheckAndSpawn); + } + + private void CheckAndSpawn() + { + if (Deleted || Map == null || Map == Map.Internal) + return; + + _Attendees.RemoveAll(m => m == null || m.Deleted || !m.Alive); + + if (_Attendees.Count == 0) + { + SpawnGathering(); + } + } + + public override void OnDoubleClick(Mobile from) + { + if (from.AccessLevel >= AccessLevel.GameMaster) + { + SpawnGathering(); + from.SendMessage("Gathering forcefully respawned and reset!"); + } + } + + public void SpawnGathering() + { + foreach(Mobile m in _Attendees) + { + if (m != null && !m.Deleted) + m.Delete(); + } + _Attendees.Clear(); + + Mobile east = new GatheringCitizen(); + Mobile south = new GatheringCitizen(); + Mobile west = new GatheringMonster(); + Mobile north = new GatheringMonster(); + + PlaceAndFace(east, Location.X + 3, Location.Y, Location.Z, Direction.West); + PlaceAndFace(west, Location.X - 3, Location.Y, Location.Z, Direction.East); + PlaceAndFace(south, Location.X, Location.Y + 3, Location.Z, Direction.North); + PlaceAndFace(north, Location.X, Location.Y - 3, Location.Z, Direction.South); + } + + private void PlaceAndFace(Mobile m, int x, int y, int z, Direction facing) + { + m.MoveToWorld(new Point3D(x, y, z), Map); + m.CantWalk = true; + + if (m is GatheringCitizen gc) + { + gc.GatherDirection = facing; + } + else if (m is GatheringMonster gm) + { + gm.GatherDirection = facing; + } + + m.Direction = facing; + + _Attendees.Add(m); + } + + public override void OnDelete() + { + foreach (Mobile m in _Attendees) + { + if (m != null && !m.Deleted) + { + m.Delete(); + } + } + + _Attendees.Clear(); + base.OnDelete(); + } + + public TownGatheringSpot(Serial serial) : base(serial) + { + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + writer.Write(0); + + writer.Write(_Attendees.Count); + foreach (Mobile m in _Attendees) + { + writer.Write(m); + } + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + int version = reader.ReadInt(); + + int count = reader.ReadInt(); + for (int i = 0; i < count; i++) + { + Mobile m = reader.ReadEntity(); + + if (m != null && !m.Deleted) + { + _Attendees.Add(m); + } + } + + Timer.DelayCall(TimeSpan.FromSeconds(1.0), CheckAndSpawn); + } + } +}