#W# Citizens: Reworked Citizens, working citizens, and meeting spot citizens
This commit is contained in:
parent
740d06a524
commit
54e6d4c978
10 changed files with 337 additions and 166 deletions
|
|
@ -3,8 +3,8 @@ Citizens
|
|||
- They only spawn on specific tiles (dirt/gravel/stone) to keep them from spawning
|
||||
in houses and businesses. They also spawn slower so the town fills up slowly.
|
||||
- They also say unique things. Come false some true.
|
||||
Town Gathering Spots
|
||||
- Same as citizens but the stand at a camp fire.
|
||||
- Tradesman Citizens are npc that look like they are working. Standing at an
|
||||
anvil making armor/weapons etc.
|
||||
CrystalOfKnowlege/Fortitude/Insight
|
||||
- These act as power scrolls, but only raise skill/skill total/stat cap 1 point at a time.
|
||||
Portal Gates
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ Spawned Treasure Chests
|
|||
The new DungeonLootCrates fill the void for now.
|
||||
|
||||
Citizens
|
||||
- Town citizens that have there own personalitys
|
||||
- Need to add the Tradesmen spawn nodes to towns.
|
||||
- Need to add more Tradesmen.
|
||||
|
||||
Hoard Piles
|
||||
- Treausre hoard pile.
|
||||
|
|
|
|||
|
|
@ -512,7 +512,7 @@ namespace Server.Commands
|
|||
light.ItemID = m_ItemID;
|
||||
}
|
||||
}
|
||||
else if (item is TradesmanNode node)
|
||||
else if (item is CitizenNode node)
|
||||
{
|
||||
for (var i = 0; i < m_Params.Length; ++i)
|
||||
{
|
||||
|
|
@ -522,8 +522,8 @@ namespace Server.Commands
|
|||
|
||||
if (indexOf >= 0)
|
||||
{
|
||||
node.NodeType = (TradesmanNodeType)Enum.Parse(
|
||||
typeof(TradesmanNodeType),
|
||||
node.NodeType = (CitizenNodeType)Enum.Parse(
|
||||
typeof(CitizenNodeType),
|
||||
m_Params[i][++indexOf..],
|
||||
true
|
||||
);
|
||||
|
|
|
|||
201
Projects/UOContent/Mobiles/Townfolk/Citizens/CitizenNode.cs
Normal file
201
Projects/UOContent/Mobiles/Townfolk/Citizens/CitizenNode.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server;
|
||||
using Server.Mobiles;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
public enum CitizenNodeType
|
||||
{
|
||||
Smith,
|
||||
Smelter,
|
||||
MeetingSpot
|
||||
}
|
||||
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class CitizenNode : Item
|
||||
{
|
||||
[SerializableField(0, fieldChanged: nameof(OnNodeTypeChanged))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private CitizenNodeType _nodeType;
|
||||
|
||||
[SerializableField(1)]
|
||||
private List<Mobile> _workers = new();
|
||||
|
||||
[SerializableField(2)]
|
||||
private Item _workstation;
|
||||
|
||||
[SerializableField(3)]
|
||||
private DateTime _nextRespawn;
|
||||
|
||||
public override bool HandlesOnMovement => true;
|
||||
|
||||
private void OnNodeTypeChanged(CitizenNodeType oldValue, CitizenNodeType newValue)
|
||||
{
|
||||
ClearNode();
|
||||
}
|
||||
|
||||
[Constructible]
|
||||
public CitizenNode() : this(CitizenNodeType.Smith)
|
||||
{
|
||||
}
|
||||
|
||||
[Constructible]
|
||||
public CitizenNode(CitizenNodeType type) : base(0x1BCE)
|
||||
{
|
||||
Name = "citizen node";
|
||||
Movable = false;
|
||||
Visible = false;
|
||||
_nodeType = type;
|
||||
_nextRespawn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public override void OnMovement(Mobile m, Point3D oldLocation)
|
||||
{
|
||||
if (Deleted || m == null || !m.Player || m.Hidden || m.Map != Map)
|
||||
return;
|
||||
|
||||
if (Utility.InRange(m.Location, Location, 15))
|
||||
{
|
||||
CheckAndSpawn();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAndSpawn()
|
||||
{
|
||||
if (Deleted || Map == null || Map == Map.Internal)
|
||||
return;
|
||||
|
||||
_workers.RemoveAll(w => w == null || w.Deleted || !w.Alive);
|
||||
|
||||
bool workersMissing = _workers.Count == 0;
|
||||
bool stationMissing = (_workstation == null || _workstation.Deleted);
|
||||
|
||||
if ((workersMissing || stationMissing) && DateTime.UtcNow >= _nextRespawn)
|
||||
{
|
||||
SpawnNode();
|
||||
_nextRespawn = DateTime.UtcNow + TimeSpan.FromMinutes(10);
|
||||
}
|
||||
}
|
||||
|
||||
public void SpawnNode()
|
||||
{
|
||||
ClearNode();
|
||||
|
||||
switch (_nodeType)
|
||||
{
|
||||
case CitizenNodeType.Smith:
|
||||
_workstation = new AnvilHit();
|
||||
AddWorker(new TradesmanSmith(), GetSingleWorkerSpot());
|
||||
break;
|
||||
|
||||
case CitizenNodeType.Smelter:
|
||||
_workstation = new ForgeHit();
|
||||
AddWorker(new TradesmanSmelter(), GetSingleWorkerSpot());
|
||||
break;
|
||||
|
||||
case CitizenNodeType.MeetingSpot:
|
||||
_workstation = new CampfireHit();
|
||||
SpawnMeetingSpotCitizens();
|
||||
break;
|
||||
}
|
||||
|
||||
if (_workstation != null)
|
||||
{
|
||||
_workstation.MoveToWorld(Location, Map);
|
||||
}
|
||||
}
|
||||
|
||||
private (Point3D loc, Direction dir) GetSingleWorkerSpot()
|
||||
{
|
||||
if (Utility.RandomBool())
|
||||
{
|
||||
return (new Point3D(X - 1, Y, Z), Direction.East);
|
||||
}
|
||||
return (new Point3D(X, Y - 1, Z), Direction.South);
|
||||
}
|
||||
|
||||
private void AddWorker(Mobile worker, (Point3D loc, Direction dir) spawnData)
|
||||
{
|
||||
if (worker == null) return;
|
||||
|
||||
worker.MoveToWorld(spawnData.loc, Map);
|
||||
worker.Direction = spawnData.dir;
|
||||
_workers.Add(worker);
|
||||
}
|
||||
|
||||
private void SpawnMeetingSpotCitizens()
|
||||
{
|
||||
(Point3D loc, Direction dir)[] potentialSpots = new[]
|
||||
{
|
||||
(new Point3D(X - 2, Y, Z), Direction.East), // West spot -> Face East
|
||||
(new Point3D(X + 2, Y, Z), Direction.West), // East spot -> Face West
|
||||
(new Point3D(X, Y - 2, Z), Direction.South), // North spot -> Face South
|
||||
(new Point3D(X, Y + 2, Z), Direction.North) // South spot -> Face North
|
||||
};
|
||||
|
||||
Utility.Shuffle(potentialSpots);
|
||||
|
||||
int countToSpawn = Utility.RandomMinMax(2, 4);
|
||||
|
||||
for (int i = 0; i < countToSpawn && i < potentialSpots.Length; i++)
|
||||
{
|
||||
var spot = potentialSpots[i];
|
||||
Mobile attendee;
|
||||
|
||||
if (Utility.RandomBool())
|
||||
{
|
||||
var gc = new GatheringCitizen();
|
||||
gc.GatherDirection = spot.dir;
|
||||
attendee = gc;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gm = new GatheringMonster();
|
||||
gm.GatherDirection = spot.dir;
|
||||
attendee = gm;
|
||||
}
|
||||
|
||||
AddWorker(attendee, spot);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearNode()
|
||||
{
|
||||
foreach (var worker in _workers)
|
||||
{
|
||||
if (worker != null && !worker.Deleted)
|
||||
{
|
||||
worker.Delete();
|
||||
}
|
||||
}
|
||||
_workers.Clear();
|
||||
|
||||
if (_workstation != null && !_workstation.Deleted)
|
||||
{
|
||||
_workstation.Delete();
|
||||
}
|
||||
_workstation = null;
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (from.AccessLevel >= AccessLevel.GameMaster)
|
||||
{
|
||||
SpawnNode();
|
||||
from.SendMessage("Citizen node forcefully respawned.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
ClearNode();
|
||||
base.OnDelete();
|
||||
}
|
||||
|
||||
public CitizenNode(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ namespace Server.Mobiles
|
|||
BaseSoundID = profile.SoundID;
|
||||
Karma = 1000;
|
||||
Fame = 1000;
|
||||
_NextChatter = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(10, 20));
|
||||
_NextChatter = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(5, 15));
|
||||
}
|
||||
|
||||
public override void OnThink()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using Server;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class CampfireHit : Item
|
||||
{
|
||||
[Constructible]
|
||||
public CampfireHit() : base(0xFF8D)
|
||||
{
|
||||
Name = "a campfire";
|
||||
Movable = false;
|
||||
Light = LightType.Circle225;
|
||||
}
|
||||
|
||||
public CampfireHit(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using System;
|
||||
using Server;
|
||||
|
||||
namespace Server.Mobiles
|
||||
{
|
||||
public class GatheringMonster : MonsterCitizen
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
using System;
|
||||
using Server;
|
||||
using Server.Mobiles;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
public enum TradesmanNodeType
|
||||
{
|
||||
Smith,
|
||||
Smelter
|
||||
}
|
||||
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class TradesmanNode : Item
|
||||
{
|
||||
// The generator automatically creates 'public TradesmanNodeType NodeType { get; set; }'
|
||||
// and triggers OnNodeTypeChanged whenever a GM changes it via the [Props gump.
|
||||
[SerializableField(0, fieldChanged: nameof(OnNodeTypeChanged))]
|
||||
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
||||
private TradesmanNodeType _nodeType;
|
||||
|
||||
[SerializableField(1)]
|
||||
private Mobile _worker;
|
||||
|
||||
[SerializableField(2)]
|
||||
private Item _workstation;
|
||||
|
||||
[SerializableField(3)]
|
||||
private DateTime _nextRespawn;
|
||||
|
||||
public override bool HandlesOnMovement => true;
|
||||
|
||||
private void OnNodeTypeChanged(TradesmanNodeType oldValue, TradesmanNodeType newValue)
|
||||
{
|
||||
ClearNode(); // Instantly reset the scene if a GM changes the type
|
||||
}
|
||||
|
||||
[Constructible]
|
||||
public TradesmanNode() : this(TradesmanNodeType.Smith)
|
||||
{
|
||||
}
|
||||
|
||||
[Constructible]
|
||||
public TradesmanNode(TradesmanNodeType type) : base(0x1BCE)
|
||||
{
|
||||
Name = "tradesman node";
|
||||
Movable = false;
|
||||
Visible = false; // Completely invisible to players
|
||||
_nodeType = type;
|
||||
_nextRespawn = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public override void OnMovement(Mobile m, Point3D oldLocation)
|
||||
{
|
||||
// Ignore deleted items, NPCs, hidden staff, or players on other maps
|
||||
if (Deleted || m == null || !m.Player || m.Hidden || m.Map != Map)
|
||||
return;
|
||||
|
||||
// Trigger the spawn if a player walks within 15 tiles
|
||||
if (Utility.InRange(m.Location, Location, 15))
|
||||
{
|
||||
CheckAndSpawn();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAndSpawn()
|
||||
{
|
||||
if (Deleted || Map == null || Map == Map.Internal)
|
||||
return;
|
||||
|
||||
bool workerMissing = (_worker == null || _worker.Deleted || !_worker.Alive);
|
||||
bool stationMissing = (_workstation == null || _workstation.Deleted);
|
||||
|
||||
// Only spawn if the scene is missing pieces AND the cooldown is finished
|
||||
if ((workerMissing || stationMissing) && DateTime.UtcNow >= _nextRespawn)
|
||||
{
|
||||
SpawnNode();
|
||||
|
||||
// 10-minute cooldown so players can't spam-farm the NPC if they kill them
|
||||
_nextRespawn = DateTime.UtcNow + TimeSpan.FromMinutes(10);
|
||||
}
|
||||
}
|
||||
|
||||
public void SpawnNode()
|
||||
{
|
||||
ClearNode(); // Clean up any lingering pieces before spawning fresh
|
||||
|
||||
switch (_nodeType)
|
||||
{
|
||||
case TradesmanNodeType.Smith:
|
||||
_workstation = new AnvilHit();
|
||||
_worker = new TradesmanSmith();
|
||||
break;
|
||||
case TradesmanNodeType.Smelter:
|
||||
_workstation = new ForgeHit();
|
||||
_worker = new TradesmanSmelter();
|
||||
break;
|
||||
}
|
||||
|
||||
if (_workstation != null)
|
||||
{
|
||||
// Place workstation exactly on the node
|
||||
_workstation.MoveToWorld(Location, Map);
|
||||
}
|
||||
|
||||
if (_worker != null)
|
||||
{
|
||||
if (Utility.RandomBool())
|
||||
{
|
||||
// Place West, face East
|
||||
_worker.MoveToWorld(new Point3D(X - 1, Y, Z), Map);
|
||||
_worker.Direction = Direction.East;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Place North, face South
|
||||
_worker.MoveToWorld(new Point3D(X, Y - 1, Z), Map);
|
||||
_worker.Direction = Direction.South;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearNode()
|
||||
{
|
||||
if (_worker != null && !_worker.Deleted)
|
||||
{
|
||||
_worker.Delete();
|
||||
}
|
||||
_worker = null;
|
||||
|
||||
if (_workstation != null && !_workstation.Deleted)
|
||||
{
|
||||
_workstation.Delete();
|
||||
}
|
||||
_workstation = null;
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (from.AccessLevel >= AccessLevel.GameMaster)
|
||||
{
|
||||
SpawnNode();
|
||||
from.SendMessage("Citizen node forcefully respawned.");
|
||||
}
|
||||
}
|
||||
|
||||
// Automatic Garbage Collection: Deleting the node deletes the NPC and workstation
|
||||
public override void OnDelete()
|
||||
{
|
||||
ClearNode();
|
||||
base.OnDelete();
|
||||
}
|
||||
|
||||
public TradesmanNode(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue