#W# Tradesman: Added citizens that stand round and 'work'
This commit is contained in:
parent
7de553e357
commit
740d06a524
27 changed files with 610 additions and 459 deletions
|
|
@ -1,14 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
1855 2013 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1823 1981 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1798 2045 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1854 1967 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1771 2001 0
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
3352 2402 1
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3374 2390 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3398 2372 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3374 2345 5
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3357 2347 1
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3334 2370 1
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
1421 1188 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1449 1191 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1482 1175 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1527 1170 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1559 1158 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1551 1139 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1572 1123 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1612 1147 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1653 1146 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1612 1167 2
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
1312 1758 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1340 1718 5
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1333 1682 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1416 1697 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1445 1689 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1388 1731 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1411 1754 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1445 1766 0
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
2249 2835 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
2258 2809 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
2294 2805 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
2331 2779 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
2354 2797 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
2352 2826 0
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
1797 1408 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1779 1400 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1813 1370 2
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
3351 3633 1
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3378 3614 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3362 3686 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3444 3650 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3453 3694 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3419 3670 0
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
358 1264 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
362 1231 1
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
340 1291 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
392 1283 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
450 1302 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
425 1304 0
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
1575 2895 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1574 2920 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1530 2930 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1545 2997 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1574 2976 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1599 2968 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1549 2927 2
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
756 1225 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
775 1269 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
806 1304 0
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
412 1856 1
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
454 1835 1
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
393 1899 1
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
381 1886 1
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
1211 1691 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1244 1758 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1138 1751 0
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1271 1707 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1270 1770 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
1275 1747 1
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
TownGatheringSpot 0xFF8D
|
||||
3501 403 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3521 393 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3510 358 2
|
||||
|
||||
TownGatheringSpot 0xFF8D
|
||||
3485 369 2
|
||||
|
|
@ -512,6 +512,25 @@ namespace Server.Commands
|
|||
light.ItemID = m_ItemID;
|
||||
}
|
||||
}
|
||||
else if (item is TradesmanNode node)
|
||||
{
|
||||
for (var i = 0; i < m_Params.Length; ++i)
|
||||
{
|
||||
if (m_Params[i].StartsWithOrdinal("NodeType"))
|
||||
{
|
||||
var indexOf = m_Params[i].IndexOfOrdinal('=');
|
||||
|
||||
if (indexOf >= 0)
|
||||
{
|
||||
node.NodeType = (TradesmanNodeType)Enum.Parse(
|
||||
typeof(TradesmanNodeType),
|
||||
m_Params[i][++indexOf..],
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (item is BaseSpawner sp)
|
||||
{
|
||||
sp.NextSpawn = TimeSpan.Zero;
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
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,144 +0,0 @@
|
|||
using System;
|
||||
using Server;
|
||||
using Server.Mobiles;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
public class TownGatheringSpot : Item
|
||||
{
|
||||
private List<Mobile> _Attendees = new List<Mobile>();
|
||||
|
||||
[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 = RandomAttendee();
|
||||
Mobile south = RandomAttendee();
|
||||
Mobile west = RandomAttendee();
|
||||
Mobile north = RandomAttendee();
|
||||
|
||||
PlaceAndFace(east, Location.X + 2, Location.Y, Location.Z, Direction.West);
|
||||
PlaceAndFace(west, Location.X - 2, Location.Y, Location.Z, Direction.East);
|
||||
PlaceAndFace(south, Location.X, Location.Y + 2, Location.Z, Direction.North);
|
||||
PlaceAndFace(north, Location.X, Location.Y - 2, Location.Z, Direction.South);
|
||||
}
|
||||
|
||||
private Mobile RandomAttendee()
|
||||
{
|
||||
if (Utility.RandomBool())
|
||||
{
|
||||
return new GatheringCitizen();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new GatheringMonster();
|
||||
}
|
||||
}
|
||||
|
||||
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<Mobile>();
|
||||
|
||||
if (m != null && !m.Deleted)
|
||||
{
|
||||
_Attendees.Add(m);
|
||||
}
|
||||
}
|
||||
|
||||
Timer.DelayCall(TimeSpan.FromSeconds(1.0), CheckAndSpawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using Server;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class ForgeHit : Item
|
||||
{
|
||||
[SerializableField(0)]
|
||||
private Item _smeltedItem;
|
||||
|
||||
[Constructible]
|
||||
public ForgeHit() : base(0xFB1)
|
||||
{
|
||||
Name = "forge";
|
||||
Movable = false;
|
||||
}
|
||||
|
||||
public void Smelt(Mobile from)
|
||||
{
|
||||
if (from == null || from.Deleted) return;
|
||||
|
||||
// Snap the NPC to face the forge directly
|
||||
from.Direction = from.GetDirectionTo(Location);
|
||||
|
||||
// Play the blunt swing animation
|
||||
from.Animate(11, 5, 1, true, false, 0);
|
||||
|
||||
if (_smeltedItem == null || _smeltedItem.Deleted)
|
||||
{
|
||||
// Create Ore on the first strike
|
||||
_smeltedItem = new Item(0x19B8);
|
||||
_smeltedItem.Movable = false;
|
||||
_smeltedItem.Name = "smelting ore";
|
||||
|
||||
// Elevate it so it sits on top of the forge
|
||||
_smeltedItem.MoveToWorld(new Point3D(X, Y, Z + 5), Map);
|
||||
from.PlaySound(0x2B); // Metal clink
|
||||
}
|
||||
else
|
||||
{
|
||||
// Toggle between Ore and Ingots
|
||||
if (_smeltedItem.ItemID == 0x19B8)
|
||||
{
|
||||
_smeltedItem.ItemID = 0x1BF3;
|
||||
_smeltedItem.Name = "fresh ingots";
|
||||
from.PlaySound(0x208); // Fire sizzle/quench sound
|
||||
}
|
||||
else
|
||||
{
|
||||
_smeltedItem.ItemID = 0x19B8;
|
||||
_smeltedItem.Name = "smelting ore";
|
||||
from.PlaySound(0x2B); // Metal clink
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup: If a GM deletes the forge, delete the visual ore
|
||||
public override void OnDelete()
|
||||
{
|
||||
if (_smeltedItem != null && !_smeltedItem.Deleted)
|
||||
{
|
||||
_smeltedItem.Delete();
|
||||
}
|
||||
base.OnDelete();
|
||||
}
|
||||
|
||||
// QoL: If a GM moves the forge, bring the visual ore with it
|
||||
public override void OnLocationChange(Point3D oldLocation)
|
||||
{
|
||||
base.OnLocationChange(oldLocation);
|
||||
if (_smeltedItem != null && !_smeltedItem.Deleted)
|
||||
{
|
||||
_smeltedItem.Location = new Point3D(X, Y, Z + 5);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapChange()
|
||||
{
|
||||
base.OnMapChange();
|
||||
if (_smeltedItem != null && !_smeltedItem.Deleted)
|
||||
{
|
||||
_smeltedItem.Map = Map;
|
||||
}
|
||||
}
|
||||
|
||||
public ForgeHit(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using Server;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Items
|
||||
{
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class AnvilHit : Item
|
||||
{
|
||||
// Tells ModernUO to automatically save our forged item reference during server saves
|
||||
[SerializableField(0)]
|
||||
private Item _ForgedItem;
|
||||
|
||||
// A registry of item graphics to cycle through on the anvil
|
||||
private static readonly int[] _ForgedGraphics =
|
||||
{
|
||||
0x1BEF, // Iron Ingot
|
||||
0x1B73, // Buckler
|
||||
0x1BEF, // Iron Ingot
|
||||
0x13B9, // Viking Sword
|
||||
0x1BEF,
|
||||
0x1401, // Kryss
|
||||
0x1BEF,
|
||||
0x1B76 // Heater Shield
|
||||
};
|
||||
|
||||
[Constructible]
|
||||
public AnvilHit() : base(0xFAF)
|
||||
{
|
||||
Name = "anvil";
|
||||
Movable = false;
|
||||
}
|
||||
|
||||
public void Strike(Mobile from)
|
||||
{
|
||||
if (from == null || from.Deleted) return;
|
||||
|
||||
// Snap the NPC to face the anvil directly
|
||||
from.Direction = from.GetDirectionTo(Location);
|
||||
|
||||
// Action 11 is the human animation for swinging a 1-handed blunt weapon
|
||||
from.Animate(11, 5, 1, true, false, 0);
|
||||
from.PlaySound(0x2A);
|
||||
|
||||
// Handle the visual forged item on top of the anvil
|
||||
if (_ForgedItem == null || _ForgedItem.Deleted)
|
||||
{
|
||||
// Create it on the first strike
|
||||
_ForgedItem = new Item(_ForgedGraphics[Utility.Random(_ForgedGraphics.Length)]);
|
||||
_ForgedItem.Movable = false;
|
||||
_ForgedItem.Name = "a forged item";
|
||||
|
||||
// Elevate it by 5 Z-levels so it sits on top of the anvil mesh
|
||||
_ForgedItem.MoveToWorld(new Point3D(X, Y, Z + 5), Map);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cycle to a new random graphic with each subsequent strike
|
||||
_ForgedItem.ItemID = _ForgedGraphics[Utility.Random(_ForgedGraphics.Length)];
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup: If a GM deletes the anvil, delete the forged weapon too
|
||||
public override void OnDelete()
|
||||
{
|
||||
if (_ForgedItem != null && !_ForgedItem.Deleted)
|
||||
{
|
||||
_ForgedItem.Delete();
|
||||
}
|
||||
base.OnDelete();
|
||||
}
|
||||
|
||||
// QoL: If a GM moves the anvil, bring the forged weapon with it
|
||||
public override void OnLocationChange(Point3D oldLocation)
|
||||
{
|
||||
base.OnLocationChange(oldLocation);
|
||||
if (_ForgedItem != null && !_ForgedItem.Deleted)
|
||||
{
|
||||
_ForgedItem.Location = new Point3D(X, Y, Z + 5);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnMapChange()
|
||||
{
|
||||
base.OnMapChange();
|
||||
if (_ForgedItem != null && !_ForgedItem.Deleted)
|
||||
{
|
||||
_ForgedItem.Map = Map;
|
||||
}
|
||||
}
|
||||
|
||||
public AnvilHit(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using Server;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Mobiles
|
||||
{
|
||||
[SerializationGenerator(0, false)]
|
||||
public abstract partial class TradesmanCitizen : BaseCreature
|
||||
{
|
||||
private DateTime _NextChatter;
|
||||
|
||||
public override bool InitialInnocent => true;
|
||||
public override bool CanTeach => false;
|
||||
public override bool ClickTitle => true;
|
||||
|
||||
[Constructible]
|
||||
public TradesmanCitizen() : base(AIType.AI_Animal, FightMode.None)
|
||||
{
|
||||
SpeechHue = 48;
|
||||
Hue = Race.Human.RandomSkinHue();
|
||||
|
||||
if (Utility.RandomBool())
|
||||
{
|
||||
Body = 0x191; // Female
|
||||
Name = NameList.RandomName("female");
|
||||
}
|
||||
else
|
||||
{
|
||||
Body = 0x190; // Male
|
||||
Name = NameList.RandomName("male");
|
||||
Utility.AssignRandomFacialHair(this, HairHue);
|
||||
}
|
||||
|
||||
Utility.AssignRandomHair(this);
|
||||
Title = "the tradesman";
|
||||
|
||||
SetStr(100);
|
||||
SetDex(100);
|
||||
SetInt(100);
|
||||
SetHits(200, 300);
|
||||
|
||||
DressTradesman();
|
||||
|
||||
CantWalk = false; // Allow them to wander
|
||||
_NextChatter = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(10, 25));
|
||||
}
|
||||
|
||||
public virtual void DressTradesman()
|
||||
{
|
||||
AddItem(new Shirt(Utility.RandomNeutralHue()));
|
||||
AddItem(new LongPants(Utility.RandomNeutralHue()));
|
||||
AddItem(new Boots(Utility.RandomNeutralHue()));
|
||||
}
|
||||
|
||||
public override void OnMovement(Mobile m, Point3D oldLocation)
|
||||
{
|
||||
base.OnMovement(m, oldLocation);
|
||||
|
||||
if (m.Player && m.Alive && !m.Hidden && DateTime.UtcNow >= _NextChatter && InRange(m, 8))
|
||||
{
|
||||
if (Utility.RandomBool())
|
||||
{
|
||||
SayRandomChatter();
|
||||
}
|
||||
|
||||
_NextChatter = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(30, 60));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SayRandomChatter()
|
||||
{
|
||||
string[] lines = {
|
||||
"Lovely weather today.",
|
||||
"Always so much work to do...",
|
||||
"Have you heard any news from the capital?",
|
||||
"I need to visit the market later."
|
||||
};
|
||||
Say(lines[Utility.Random(lines.Length)]);
|
||||
}
|
||||
|
||||
public TradesmanCitizen(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using Server;
|
||||
using Server.Items;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Mobiles
|
||||
{
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class TradesmanSmelter : TradesmanCitizen
|
||||
{
|
||||
private DateTime _nextWork;
|
||||
private ForgeHit _myForge;
|
||||
|
||||
[Constructible]
|
||||
public TradesmanSmelter()
|
||||
{
|
||||
Title = "the smelter";
|
||||
CantWalk = true;
|
||||
_nextWork = DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||
}
|
||||
|
||||
public override void DressTradesman()
|
||||
{
|
||||
AddItem(new Shirt(Utility.RandomNeutralHue()));
|
||||
AddItem(new ShortPants(Utility.RandomNeutralHue()));
|
||||
AddItem(new Boots());
|
||||
|
||||
// Full apron for heat protection
|
||||
AddItem(new FullApron(Utility.RandomList(0x906, 0x8A5, 0x89A)));
|
||||
|
||||
// Tongs instead of a hammer
|
||||
Item hammer = new SmithHammer();
|
||||
hammer.Movable = false;
|
||||
AddItem(hammer);
|
||||
}
|
||||
|
||||
public override void SayRandomChatter()
|
||||
{
|
||||
string[] lines = {
|
||||
"Hot enough to melt dragon scales today.",
|
||||
"More ore, always more ore.",
|
||||
"Careful, don't burn yourself.",
|
||||
"Nothing beats the glow of fresh ingots."
|
||||
};
|
||||
Say(lines[Utility.Random(lines.Length)]);
|
||||
}
|
||||
|
||||
public override void OnThink()
|
||||
{
|
||||
base.OnThink();
|
||||
|
||||
if (DateTime.UtcNow >= _nextWork)
|
||||
{
|
||||
if (_myForge == null || _myForge.Deleted)
|
||||
{
|
||||
foreach (Item item in GetItemsInRange(1))
|
||||
{
|
||||
if (item is ForgeHit forge)
|
||||
{
|
||||
_myForge = forge;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_myForge != null && !_myForge.Deleted)
|
||||
{
|
||||
_myForge.Smelt(this);
|
||||
}
|
||||
|
||||
// Randomize the pause between smelts
|
||||
_nextWork = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(3, 6));
|
||||
}
|
||||
}
|
||||
|
||||
public TradesmanSmelter(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using Server;
|
||||
using Server.Items;
|
||||
using ModernUO.Serialization;
|
||||
|
||||
namespace Server.Mobiles
|
||||
{
|
||||
[SerializationGenerator(0, false)]
|
||||
public partial class TradesmanSmith : TradesmanCitizen
|
||||
{
|
||||
private DateTime _NextWork;
|
||||
private AnvilHit _MyAnvil;
|
||||
|
||||
[Constructible]
|
||||
public TradesmanSmith()
|
||||
{
|
||||
Title = "the blacksmith";
|
||||
CantWalk = true;
|
||||
|
||||
_NextWork = DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||
}
|
||||
|
||||
public override void DressTradesman()
|
||||
{
|
||||
AddItem(new LongPants(Utility.RandomNeutralHue()));
|
||||
AddItem(new Boots());
|
||||
|
||||
AddItem(new HalfApron(Utility.RandomList(0x906, 0x8A5, 0x89A)));
|
||||
|
||||
Item hammer = new SmithHammer();
|
||||
hammer.Movable = false;
|
||||
AddItem(hammer);
|
||||
}
|
||||
|
||||
public override void SayRandomChatter()
|
||||
{
|
||||
string[] lines = {
|
||||
"This iron is stubborn today.",
|
||||
"A good blade takes time and patience.",
|
||||
"Hot work, but someone has to do it.",
|
||||
"Need your armor patched up?"
|
||||
};
|
||||
Say(lines[Utility.Random(lines.Length)]);
|
||||
}
|
||||
|
||||
public override void OnThink()
|
||||
{
|
||||
base.OnThink();
|
||||
|
||||
if (DateTime.UtcNow >= _NextWork)
|
||||
{
|
||||
if (_MyAnvil == null || _MyAnvil.Deleted)
|
||||
{
|
||||
foreach (Item item in GetItemsInRange(1))
|
||||
{
|
||||
if (item is AnvilHit anvil)
|
||||
{
|
||||
_MyAnvil = anvil;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_MyAnvil != null && !_MyAnvil.Deleted)
|
||||
{
|
||||
_MyAnvil.Strike(this);
|
||||
}
|
||||
|
||||
_NextWork = DateTime.UtcNow + TimeSpan.FromSeconds(Utility.RandomMinMax(2, 5));
|
||||
}
|
||||
}
|
||||
|
||||
public TradesmanSmith(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue