### Summary - Fixes issues with non-_ISerializable_ objects having bad serialization (Skill/Stat. Mods) - Fixes issues with MarkDirty and namespaces: https://github.com/modernuo/SerializationGenerator/issues/29 - Fixes missing dirty tracking for some data structures. - Fixes cascading MarkDirty for non-serializable types that have owners that are also non-serializable types. ### New Codegenned API When a data structure (Array, List, Dictionary, HashSet, etc) is source generated for serialization, new methods are added which will handle dirty tracking. Use these instead of the built in methods for that data structure. _All Data Structures_ ```cs public void Clear<PropertyName>(); ``` _Lists and Sets_ ```cs public void AddTo<PropertyName>(V value); public void RemoveFrom<PropertyName>(V value); ``` _Lists_ ```cs public void InsertInto<PropertyName>(int index, V value); public void RemoveFrom<PropertyName>At(int index); ``` _Dictionaries_ ```cs public void AddTo<PropertyName>(T key, V value); public void RemoveFrom<PropertyName>(T key); public void ReplaceIn<PropertyName>(T key, V value); ``` Over time, they will be cleaned up and more variants added such as `bool RemoveFrom<PropertyName>(T key, out V value)`
430 lines
9.4 KiB
C#
430 lines
9.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using ModernUO.Serialization;
|
|
using Server.Items;
|
|
using Server.Utilities;
|
|
|
|
namespace Server.Engines.Doom;
|
|
|
|
public enum GauntletSpawnerState
|
|
{
|
|
InSequence,
|
|
InProgress,
|
|
Completed
|
|
}
|
|
|
|
[SerializationGenerator(2, false)]
|
|
public partial class GauntletSpawner : Item
|
|
{
|
|
public const int PlayersPerSpawn = 5;
|
|
|
|
public const int InSequenceItemHue = 0x000;
|
|
public const int InProgressItemHue = 0x676;
|
|
public const int CompletedItemHue = 0x455;
|
|
|
|
[SerializableField(0)]
|
|
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
|
private Rectangle2D _regionBounds;
|
|
|
|
[SerializableField(1)]
|
|
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
|
private List<BaseTrap> _traps;
|
|
|
|
[SerializableField(2)]
|
|
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
|
private List<Mobile> _creatures;
|
|
|
|
[SerializableField(3)]
|
|
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
|
private string _typeName;
|
|
|
|
[SerializableField(4)]
|
|
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
|
private BaseDoor _door;
|
|
|
|
[SerializableField(5)]
|
|
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
|
private BaseAddon _addon;
|
|
|
|
[SerializableField(6)]
|
|
[SerializedCommandProperty(AccessLevel.GameMaster)]
|
|
private GauntletSpawner _sequence;
|
|
|
|
private TimerExecutionToken _timerToken;
|
|
|
|
[Constructible]
|
|
public GauntletSpawner(string typeName = null) : base(0x36FE)
|
|
{
|
|
Visible = false;
|
|
Movable = false;
|
|
|
|
TypeName = typeName;
|
|
Creatures = new List<Mobile>();
|
|
Traps = new List<BaseTrap>();
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool HasCompleted
|
|
{
|
|
get
|
|
{
|
|
if (Creatures.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < Creatures.Count; ++i)
|
|
{
|
|
var mob = Creatures[i];
|
|
|
|
if (!mob.Deleted)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
[SerializableProperty(7)]
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public GauntletSpawnerState State
|
|
{
|
|
get => _state;
|
|
set
|
|
{
|
|
if (_state != value)
|
|
{
|
|
_state = value;
|
|
Update();
|
|
}
|
|
|
|
this.MarkDirty();
|
|
}
|
|
}
|
|
|
|
public Region Region { get; set; }
|
|
|
|
private void Update(bool shouldSpawn = true)
|
|
{
|
|
var lockDoors = _state == GauntletSpawnerState.InProgress;
|
|
|
|
var hue = _state switch
|
|
{
|
|
GauntletSpawnerState.InSequence => InSequenceItemHue,
|
|
GauntletSpawnerState.InProgress => InProgressItemHue,
|
|
GauntletSpawnerState.Completed => CompletedItemHue,
|
|
_ => 0
|
|
};
|
|
|
|
if (Door != null)
|
|
{
|
|
Door.Hue = hue;
|
|
Door.Locked = lockDoors;
|
|
|
|
if (lockDoors)
|
|
{
|
|
Door.KeyValue = Key.RandomValue();
|
|
Door.Open = false;
|
|
}
|
|
|
|
if (Door.Link?.Deleted == false)
|
|
{
|
|
Door.Link.Hue = hue;
|
|
Door.Link.Locked = lockDoors;
|
|
|
|
if (lockDoors)
|
|
{
|
|
Door.Link.KeyValue = Key.RandomValue();
|
|
Door.Open = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Addon != null)
|
|
{
|
|
Addon.Hue = hue;
|
|
}
|
|
|
|
if (_state == GauntletSpawnerState.InProgress)
|
|
{
|
|
CreateRegion();
|
|
|
|
if (shouldSpawn)
|
|
{
|
|
FullSpawn();
|
|
}
|
|
|
|
Timer.StartTimer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0), Slice, out _timerToken);
|
|
}
|
|
else
|
|
{
|
|
Stop();
|
|
}
|
|
}
|
|
|
|
public override string DefaultName => "doom spawner";
|
|
|
|
public virtual void CreateRegion()
|
|
{
|
|
if (Region != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var map = Map;
|
|
|
|
if (map == null || map == Map.Internal)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Region = new GauntletRegion(this, map);
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
ClearCreatures();
|
|
ClearTraps();
|
|
DestroyRegion();
|
|
|
|
_timerToken.Cancel();
|
|
}
|
|
|
|
public override void OnDelete()
|
|
{
|
|
base.OnDelete();
|
|
Stop();
|
|
|
|
Door?.Link?.Delete();
|
|
Door?.Delete();
|
|
Addon?.Delete();
|
|
}
|
|
|
|
public virtual void DestroyRegion()
|
|
{
|
|
Region?.Unregister();
|
|
|
|
Region = null;
|
|
}
|
|
|
|
public virtual int ComputeTrapCount()
|
|
{
|
|
var area = RegionBounds.Width * RegionBounds.Height;
|
|
|
|
return area / 100;
|
|
}
|
|
|
|
public virtual void RemoveAllTraps()
|
|
{
|
|
for (var i = 0; i < Traps.Count; ++i)
|
|
{
|
|
Traps[i].Delete();
|
|
}
|
|
|
|
ClearTraps();
|
|
}
|
|
|
|
public virtual void SpawnTrap()
|
|
{
|
|
var map = Map;
|
|
|
|
if (map == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var random = Utility.Random(100);
|
|
|
|
BaseTrap trap = random switch
|
|
{
|
|
< 22 => new SawTrap(Utility.RandomBool() ? SawTrapType.WestFloor : SawTrapType.NorthFloor),
|
|
< 44 => new SpikeTrap(Utility.RandomBool() ? SpikeTrapType.WestFloor : SpikeTrapType.NorthFloor),
|
|
< 66 => new GasTrap(Utility.RandomBool() ? GasTrapType.NorthWall : GasTrapType.WestWall),
|
|
< 88 => new FireColumnTrap(),
|
|
_ => new MushroomTrap()
|
|
};
|
|
|
|
if (trap is FireColumnTrap or MushroomTrap)
|
|
{
|
|
trap.Hue = 0x451;
|
|
}
|
|
|
|
// try 10 times to find a valid location
|
|
for (var i = 0; i < 10; ++i)
|
|
{
|
|
var x = Utility.Random(RegionBounds.X, RegionBounds.Width);
|
|
var y = Utility.Random(RegionBounds.Y, RegionBounds.Height);
|
|
var z = Z;
|
|
|
|
if (!map.CanFit(x, y, z, 16, false, false))
|
|
{
|
|
z = map.GetAverageZ(x, y);
|
|
}
|
|
|
|
if (!map.CanFit(x, y, z, 16, false, false))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
trap.MoveToWorld(new Point3D(x, y, z), map);
|
|
Traps.Add(trap);
|
|
|
|
return;
|
|
}
|
|
|
|
trap.Delete();
|
|
}
|
|
|
|
public virtual int ComputeSpawnCount()
|
|
{
|
|
var playerCount = 0;
|
|
|
|
var map = Map;
|
|
|
|
if (map != null)
|
|
{
|
|
var loc = GetWorldLocation();
|
|
|
|
var reg = Region.Find(loc, map).GetRegion("Doom Gauntlet");
|
|
|
|
if (reg != null)
|
|
{
|
|
playerCount = reg.GetPlayerCount();
|
|
}
|
|
}
|
|
|
|
if (playerCount == 0 && Region != null)
|
|
{
|
|
playerCount = Region.GetPlayerCount();
|
|
}
|
|
|
|
return Math.Max((playerCount + PlayersPerSpawn - 1) / PlayersPerSpawn, 1);
|
|
}
|
|
|
|
public virtual void RemoveAllCreatures()
|
|
{
|
|
for (var i = 0; i < Creatures.Count; ++i)
|
|
{
|
|
Creatures[i].Delete();
|
|
}
|
|
|
|
ClearCreatures();
|
|
}
|
|
|
|
public virtual void FullSpawn()
|
|
{
|
|
RemoveAllCreatures();
|
|
|
|
var count = ComputeSpawnCount();
|
|
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
Spawn();
|
|
}
|
|
|
|
RemoveAllTraps();
|
|
|
|
count = ComputeTrapCount();
|
|
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
SpawnTrap();
|
|
}
|
|
}
|
|
|
|
public virtual void Spawn()
|
|
{
|
|
try
|
|
{
|
|
if (TypeName == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var type = AssemblyHandler.FindTypeByName(TypeName);
|
|
|
|
if (type == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var mob = type.CreateEntityInstance<Mobile>();
|
|
|
|
if (mob != null)
|
|
{
|
|
mob.MoveToWorld(GetWorldLocation(), Map);
|
|
Creatures.Add(mob);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
|
|
public virtual void RecurseReset()
|
|
{
|
|
if (_state != GauntletSpawnerState.InSequence)
|
|
{
|
|
State = GauntletSpawnerState.InSequence;
|
|
|
|
if (Sequence?.Deleted == false)
|
|
{
|
|
Sequence.RecurseReset();
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void Slice()
|
|
{
|
|
if (_state != GauntletSpawnerState.InProgress)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var count = ComputeSpawnCount();
|
|
|
|
for (var i = Creatures.Count; i < count; ++i)
|
|
{
|
|
Spawn();
|
|
}
|
|
|
|
if (HasCompleted)
|
|
{
|
|
State = GauntletSpawnerState.Completed;
|
|
|
|
if (Sequence?.Deleted == false)
|
|
{
|
|
if (Sequence.State == GauntletSpawnerState.Completed)
|
|
{
|
|
RecurseReset();
|
|
}
|
|
|
|
Sequence.State = GauntletSpawnerState.InProgress;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Deserialize(IGenericReader reader, int version)
|
|
{
|
|
RegionBounds = reader.ReadRect2D();
|
|
Traps = reader.ReadEntityList<BaseTrap>();
|
|
|
|
Creatures = reader.ReadEntityList<Mobile>();
|
|
|
|
TypeName = reader.ReadString();
|
|
Door = reader.ReadEntity<BaseDoor>();
|
|
Addon = reader.ReadEntity<BaseAddon>();
|
|
Sequence = reader.ReadEntity<GauntletSpawner>();
|
|
|
|
_state = (GauntletSpawnerState)reader.ReadInt();
|
|
}
|
|
|
|
[AfterDeserialization(false)]
|
|
private void AfterDeserialization()
|
|
{
|
|
Update(false);
|
|
}
|
|
}
|