using System; using System.IO; using System.Reflection; using System.Collections.Generic; using Server.Items; using Server.Commands; using CPA = Server.CommandPropertyAttribute; namespace Server.Mobiles { public class Spawner : Item, ISpawner { private int m_Team; private int m_HomeRange; private int m_Count; private int m_WalkingRange = -1; private TimeSpan m_MinDelay; private TimeSpan m_MaxDelay; private List m_Entries; private Dictionary m_Spawned; private DateTime m_End; private InternalTimer m_Timer; private bool m_Running; private bool m_Group; private WayPoint m_WayPoint; public override string DefaultName => "Spawner"; public bool IsFull => ( m_Spawned != null && m_Spawned.Count >= m_Count ); public bool IsEmpty => ( m_Spawned != null && m_Spawned.Count == 0 ); public Point3D HomeLocation => Location; public bool UnlinkOnTaming => true; public DateTime End{ get => m_End; set => m_End = value; } public List Entries => m_Entries; public Dictionary Spawned => m_Spawned; public override void OnAfterDuped( Item newItem ) { if ( newItem is Spawner newSpawner ) { for ( int i = 0; i < m_Entries.Count; i++ ) newSpawner.AddEntry( m_Entries[i].SpawnedName, m_Entries[i].SpawnedProbability, m_Entries[i].SpawnedMaxCount, false ); } } [CommandProperty( AccessLevel.Developer )] public int Count { get => m_Count; set { m_Count = value; if ( m_Timer != null ) { if ( ( !IsFull && !m_Timer.Running ) || IsFull && m_Timer.Running ) DoTimer(); } InvalidateProperties(); } } [CommandProperty( AccessLevel.Developer )] public WayPoint WayPoint{ get => m_WayPoint; set => m_WayPoint = value; } [CommandProperty( AccessLevel.Developer )] public bool Running { get => m_Running; set { if ( value ) Start(); else Stop(); InvalidateProperties(); } } [CommandProperty( AccessLevel.Developer )] public int HomeRange{ get => m_HomeRange; set { m_HomeRange = value; InvalidateProperties(); } } [CommandProperty( AccessLevel.Developer )] public int WalkingRange{ get => m_WalkingRange; set { m_WalkingRange = value; InvalidateProperties(); } } [CommandProperty( AccessLevel.Developer )] public int Team{ get => m_Team; set { m_Team = value; InvalidateProperties(); } } [CommandProperty( AccessLevel.Developer )] public TimeSpan MinDelay{ get => m_MinDelay; set { m_MinDelay = value; InvalidateProperties(); } } [CommandProperty( AccessLevel.Developer )] public TimeSpan MaxDelay{ get => m_MaxDelay; set { m_MaxDelay = value; InvalidateProperties(); } } [CommandProperty( AccessLevel.Developer )] public TimeSpan NextSpawn { get { if ( m_Running && m_Timer != null && m_Timer.Running ) return m_End - DateTime.UtcNow; else return TimeSpan.FromSeconds( 0 ); } set { Start(); DoTimer( value ); } } [CommandProperty( AccessLevel.Developer )] public bool Group { get => m_Group; set { m_Group = value; InvalidateProperties(); } } Region ISpawner.Region => Region.Find( Location, Map ); // [Constructible] public Spawner( int amount, int minDelay, int maxDelay, int team, int homeRange, string spawnedNames ) : base( 0x1f13 ) { InitSpawn( amount, TimeSpan.FromMinutes( minDelay ), TimeSpan.FromMinutes( maxDelay ), team, homeRange ); AddEntry( spawnedNames, 100, amount, false ); } // [Constructible] public Spawner( string spawnedName ) : base( 0x1f13 ) { InitSpawn( 1, TimeSpan.FromMinutes( 5 ), TimeSpan.FromMinutes( 10 ), 0, 4 ); AddEntry( spawnedName, 100, 1, false ); } [Constructible( AccessLevel.Developer )] public Spawner() : base( 0x1f13 ) { InitSpawn( 1, TimeSpan.FromMinutes( 5 ), TimeSpan.FromMinutes( 10 ), 0, 4 ); } public Spawner( int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, params string[] spawnedNames ) : base( 0x1f13 ) { InitSpawn( amount, minDelay, maxDelay, team, homeRange ); for ( int i = 0;i < spawnedNames.Length; i++ ) AddEntry( spawnedNames[i], 100, amount, false ); } public SpawnerEntry AddEntry( string creaturename, int probability, int amount ) { return AddEntry( creaturename, probability, amount, true ); } public SpawnerEntry AddEntry( string creaturename, int probability, int amount, bool dotimer ) { SpawnerEntry entry = new SpawnerEntry( creaturename, probability, amount ); m_Entries.Add( entry ); if ( dotimer ) DoTimer( TimeSpan.FromSeconds( 1 ) ); return entry; } public void InitSpawn( int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange ) { Visible = false; Movable = false; m_Running = true; m_Group = false; m_MinDelay = minDelay; m_MaxDelay = maxDelay; m_Count = amount; m_Team = team; m_HomeRange = homeRange; m_Entries = new List(); m_Spawned = new Dictionary(); DoTimer( TimeSpan.FromSeconds( 1 ) ); } public Spawner( Serial serial ) : base( serial ) { } public override void OnDoubleClick( Mobile from ) { if ( from.AccessLevel >= AccessLevel.Developer ) from.SendGump( new SpawnerGump( this ) ); } public override void GetProperties( ObjectPropertyList list ) { base.GetProperties( list ); if ( m_Running ) { list.Add( 1060742 ); // active list.Add( 1060656, m_Count.ToString() ); // amount to make: ~1_val~ list.Add( 1061169, m_HomeRange.ToString() ); // range ~1_val~ list.Add( 1060658, "walking range\t{0}", m_WalkingRange ); // ~1_val~: ~2_val~ list.Add( 1060658, "group\t{0}", m_Group ); // ~1_val~: ~2_val~ list.Add( 1060659, "team\t{0}", m_Team ); // ~1_val~: ~2_val~ list.Add( 1060660, "speed\t{0} to {1}", m_MinDelay, m_MaxDelay ); // ~1_val~: ~2_val~ for ( int i = 0; i < 3 && i < m_Entries.Count; ++i ) list.Add( 1060661 + i, "{0}\t{1}", m_Entries[i].SpawnedName, CountSpawns( m_Entries[i] ) ); } else list.Add( 1060743 ); // inactive } public override void OnSingleClick( Mobile from ) { base.OnSingleClick( from ); if ( m_Running ) LabelTo( from, "[Running]" ); else LabelTo( from, "[Off]" ); } public void Start() { if ( !m_Running ) { if ( m_Entries.Count > 0 ) { m_Running = true; DoTimer(); } } } public void Stop() { if ( m_Running ) { if ( m_Timer != null ) m_Timer.Stop(); m_Running = false; } } public void Defrag() { if ( m_Entries == null ) m_Entries = new List(); for ( int i = 0; i < m_Entries.Count; ++i ) m_Entries[i].Defrag( this ); } public void OnTick() { // DoTimer( m_Spawned.Count >= m_Count ); if ( m_Group ) { Defrag(); if ( m_Spawned.Count > 0 ) return; Respawn(); } else Spawn(); /* if ( m_Running && m_Timer != null ) { if ( m_Spawned.Count >= m_Count && m_Timer.Running ) DoTimer( true ); else if ( m_Spawned.Count < m_Count && !m_Timer.Running ) DoTimer( false ); } */ DoTimer(); } public virtual void Respawn() { RemoveSpawns(); for ( int i = 0; i < m_Count; i++ ) Spawn(); DoTimer(); //Turn off the timer! } public virtual void Spawn() { Defrag(); if ( m_Entries.Count > 0 && !IsFull ) { int probsum = 0; for ( int i = 0; i < m_Entries.Count; i++ ) if ( !m_Entries[i].IsFull ) probsum += m_Entries[i].SpawnedProbability; if ( probsum > 0 ) { int rand = Utility.RandomMinMax( 1, probsum ); for ( int i = 0; i < m_Entries.Count; i++ ) { SpawnerEntry entry = m_Entries[i]; if ( !entry.IsFull ) { bool success = true; if ( rand <= entry.SpawnedProbability ) { EntryFlags flags; success = Spawn( entry, out flags ); entry.Valid = flags; return; } if ( success ) rand -= entry.SpawnedProbability; } } } } } private static string[,] FormatProperties( string[] args ) { string[,] props = null; int remains = args.Length; if ( remains >= 2 ) { props = new string[remains / 2, 2]; remains /= 2; for ( int j = 0; j < remains; ++j ) { props[j, 0] = args[j * 2]; props[j, 1] = args[(j * 2) + 1]; } } else props = new string[0,0]; return props; } private static PropertyInfo[] GetTypeProperties( Type type, string[,] props ) { PropertyInfo[] realProps = null; if ( props != null ) { realProps = new PropertyInfo[props.GetLength( 0 )]; PropertyInfo[] allProps = type.GetProperties( BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public ); for ( int i = 0; i < realProps.Length; ++i ) { PropertyInfo thisProp = null; string propName = props[i, 0]; for ( int j = 0; thisProp == null && j < allProps.Length; ++j ) { if ( Insensitive.Equals( propName, allProps[j].Name ) ) thisProp = allProps[j]; } if ( thisProp == null ) return null; else { CPA attr = Properties.GetCPA( thisProp ); if ( attr == null || AccessLevel.Developer < attr.WriteLevel || !thisProp.CanWrite || attr.ReadOnly ) return null; else realProps[i] = thisProp; } } } return realProps; } public bool Spawn( int index, out EntryFlags flags ) { if ( index >= 0 && index < m_Entries.Count ) return Spawn( m_Entries[index], out flags ); else { flags = EntryFlags.InvalidEntry; return false; } } public bool Spawn( SpawnerEntry entry, out EntryFlags flags ) { Map map = GetSpawnMap(); flags = EntryFlags.None; if ( map == null || map == Map.Internal || Parent != null ) return false; //Defrag taken care of in Spawn(), beforehand //Count check taken care of in Spawn(), beforehand Type type = SpawnerType.GetType( entry.SpawnedName ); if ( type != null ) { try { object o = null; string[] paramargs; string[] propargs; if ( string.IsNullOrEmpty( entry.Properties ) ) propargs = new string[0]; else propargs = CommandSystem.Split( entry.Properties.Trim() ); string[,] props = FormatProperties( propargs ); PropertyInfo[] realProps = GetTypeProperties( type, props ); if ( realProps == null ) { flags = EntryFlags.InvalidProps; return false; } if ( string.IsNullOrEmpty( entry.Parameters ) ) paramargs = new string[0]; else paramargs = entry.Parameters.Trim().Split( ' ' ); ConstructorInfo[] ctors = type.GetConstructors(); for ( int i = 0; i < ctors.Length; ++i ) { ConstructorInfo ctor = ctors[i]; if ( Add.IsConstructible( ctor, AccessLevel.Developer ) ) { ParameterInfo[] paramList = ctor.GetParameters(); if ( paramargs.Length == paramList.Length ) { object[] paramValues = Add.ParseValues( paramList, paramargs ); if ( paramValues != null ) { o = ctor.Invoke( paramValues ); for ( int j = 0; j < realProps.Length; j++ ) { if ( realProps[j] != null ) { object toSet = null; string result = Properties.ConstructFromString( realProps[j].PropertyType, o, props[j, 1], ref toSet ); if ( result == null ) realProps[j].SetValue( o, toSet, null ); else { flags = EntryFlags.InvalidProps; if ( o is ISpawnable spawnable ) spawnable.Delete(); return false; } } } break; } } } } if ( o is Mobile m ) { m_Spawned.Add( m, entry ); entry.Spawned.Add( m ); Point3D loc = ( m is BaseVendor ? Location : GetSpawnPosition( m, map ) ); m.OnBeforeSpawn( loc, map ); InvalidateProperties(); m.MoveToWorld( loc, map ); if ( m is BaseCreature c ) { int walkrange = GetWalkingRange(); if ( walkrange >= 0 ) c.RangeHome = walkrange; else c.RangeHome = m_HomeRange; c.CurrentWayPoint = GetWayPoint(); if ( m_Team > 0 ) c.Team = m_Team; c.Home = Location; c.HomeMap = Map; } m.Spawner = this; m.OnAfterSpawn(); } else if ( o is Item item ) { m_Spawned.Add( item, entry ); entry.Spawned.Add( item ); Point3D loc = GetSpawnPosition( item, map ); item.OnBeforeSpawn( loc, map ); item.MoveToWorld( loc, map ); item.Spawner = this; item.OnAfterSpawn(); } else { flags = EntryFlags.InvalidType | EntryFlags.InvalidParams; return false; } } catch ( Exception e ) { Console.WriteLine( "EXCEPTION CAUGHT: {0:X}", Serial ); Console.WriteLine( e ); return false; } InvalidateProperties(); return true; } flags = EntryFlags.InvalidType; return false; } public virtual int GetWalkingRange() { return m_WalkingRange; } public virtual WayPoint GetWayPoint() { return m_WayPoint; } public virtual Point3D GetSpawnPosition( ISpawnable spawned, Map map ) { if ( map == null || map == Map.Internal ) return Location; bool waterMob, waterOnlyMob; if ( spawned is Mobile mob ) { waterMob = mob.CanSwim; waterOnlyMob = ( mob.CanSwim && mob.CantWalk ); } else { waterMob = false; waterOnlyMob = false; } // Try 10 times to find a Spawnable location. for ( int i = 0; i < 10; i++ ) { int x = Location.X + (Utility.Random( (m_HomeRange * 2) + 1 ) - m_HomeRange); int y = Location.Y + (Utility.Random( (m_HomeRange * 2) + 1 ) - m_HomeRange); int z = Map.GetAverageZ( x, y ); int mapZ = map.GetAverageZ( x, y ); if ( waterMob ) { if ( IsValidWater( map, x, y, Z ) ) return new Point3D( x, y, Z ); else if ( IsValidWater( map, x, y, mapZ ) ) return new Point3D( x, y, mapZ ); } if ( !waterOnlyMob ) { if ( map.CanSpawnMobile( x, y, Z ) ) return new Point3D( x, y, Z ); else if ( map.CanSpawnMobile( x, y, mapZ ) ) return new Point3D( x, y, mapZ ); } } return Location; } public static bool IsValidWater( Map map, int x, int y, int z ) { if ( !Region.Find( new Point3D( x, y, z ), map ).AllowSpawn() || !map.CanFit( x, y, z, 16, false, true, false ) ) return false; LandTile landTile = map.Tiles.GetLandTile( x, y ); if ( landTile.Z == z && ( TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Wet ) != 0 ) return true; StaticTile[] staticTiles = map.Tiles.GetStaticTiles( x, y, true ); for ( int i = 0; i < staticTiles.Length; ++i ) { StaticTile staticTile = staticTiles[i]; if ( staticTile.Z == z && ( TileData.ItemTable[staticTile.ID & TileData.MaxItemValue].Flags & TileFlag.Wet ) != 0 ) return true; } return false; } public virtual Map GetSpawnMap() { return Map; } public void DoTimer() { if ( !m_Running ) return; int minSeconds = (int)m_MinDelay.TotalSeconds; int maxSeconds = (int)m_MaxDelay.TotalSeconds; TimeSpan delay = TimeSpan.FromSeconds( Utility.RandomMinMax( minSeconds, maxSeconds ) ); DoTimer( delay ); } public virtual void DoTimer( TimeSpan delay ) { if ( !m_Running ) return; m_End = DateTime.UtcNow + delay; m_Timer?.Stop(); m_Timer = new InternalTimer( this, delay ); if ( !IsFull ) m_Timer.Start(); } private class InternalTimer : Timer { private Spawner m_Spawner; public InternalTimer( Spawner spawner, TimeSpan delay ) : base( delay ) { if ( spawner.IsFull ) Priority = TimerPriority.FiveSeconds; else Priority = TimerPriority.OneSecond; m_Spawner = spawner; } protected override void OnTick() { if ( m_Spawner != null ) if ( !m_Spawner.Deleted ) m_Spawner.OnTick(); } } public int CountSpawns( SpawnerEntry entry ) { Defrag(); return entry.Spawned.Count; } public void RemoveEntry( SpawnerEntry entry ) { Defrag(); for ( int i = entry.Spawned.Count-1; i >= 0; i-- ) { ISpawnable e = entry.Spawned[i]; entry.Spawned.RemoveAt( i ); if ( e != null ) e.Delete(); } m_Entries.Remove( entry ); if ( m_Running && !IsFull && m_Timer != null && !m_Timer.Running ) DoTimer(); InvalidateProperties(); } public void Remove( ISpawnable spawn ) { Defrag(); if ( spawn != null ) { m_Spawned.TryGetValue( spawn, out SpawnerEntry entry ); if ( entry != null ) entry.Spawned.Remove( spawn ); m_Spawned.Remove( spawn ); } if ( m_Running && !IsFull && m_Timer != null && !m_Timer.Running ) DoTimer(); } public void RemoveSpawn( int index ) //Entry { if ( index >= 0 && index < m_Entries.Count ) RemoveSpawn( m_Entries[index] ); } public void RemoveSpawn( SpawnerEntry entry ) { for ( int i = entry.Spawned.Count-1; i >= 0; i-- ) { ISpawnable e = entry.Spawned[i]; if ( e != null ) { entry.Spawned.RemoveAt( i ); m_Spawned.Remove( e ); e.Delete(); } } } public void RemoveSpawns() { Defrag(); for ( int i = 0;i < m_Entries.Count; i++ ) { SpawnerEntry entry = m_Entries[i]; for ( int j = entry.Spawned.Count - 1; j >= 0; j-- ) { ISpawnable e = entry.Spawned[j]; if ( e != null ) { m_Spawned.Remove( e ); entry.Spawned.RemoveAt( j ); e.Delete(); } } } if ( m_Running && !IsFull && m_Timer != null && !m_Timer.Running ) DoTimer(); InvalidateProperties(); } public void BringToHome() { Defrag(); foreach( ISpawnable e in m_Spawned.Keys ) if ( e != null ) e.MoveToWorld( Location, Map ); } public override void OnDelete() { base.OnDelete(); Stop(); RemoveSpawns(); } public override void Serialize( GenericWriter writer ) { base.Serialize( writer ); writer.Write( (int) 7 ); // version writer.Write( m_Entries.Count ); for ( int i = 0; i < m_Entries.Count; ++i ) m_Entries[i].Serialize( writer ); writer.Write( m_WalkingRange ); writer.Write( m_WayPoint ); writer.Write( m_Group ); writer.Write( m_MinDelay ); writer.Write( m_MaxDelay ); writer.Write( m_Count ); writer.Write( m_Team ); writer.Write( m_HomeRange ); writer.Write( m_Running ); if ( m_Running ) writer.WriteDeltaTime( m_End ); } private static WarnTimer m_WarnTimer; public override void Deserialize( GenericReader reader ) { base.Deserialize( reader ); int version = reader.ReadInt(); m_Spawned = new Dictionary(); if ( version < 7 ) m_Entries = new List(); switch ( version ) { case 7: { int size = reader.ReadInt(); m_Entries = new List( size ); for ( int i = 0; i < size; ++i ) m_Entries.Add( new SpawnerEntry( this, reader ) ); goto case 4; //Skip the other crap } case 6: { int size = reader.ReadInt(); bool addentries = m_Entries.Count == 0; for ( int i = 0; i < size; ++i ) if ( addentries ) m_Entries.Add( new SpawnerEntry( string.Empty, 100, reader.ReadInt() ) ); else m_Entries[i].SpawnedMaxCount = reader.ReadInt(); goto case 5; } case 5: { int size = reader.ReadInt(); bool addentries = m_Entries.Count == 0; for ( int i = 0; i < size; ++i ) if ( addentries ) m_Entries.Add( new SpawnerEntry( string.Empty, reader.ReadInt(), 1 ) ); else m_Entries[i].SpawnedProbability = reader.ReadInt(); goto case 4; } case 4: { m_WalkingRange = reader.ReadInt(); goto case 3; } case 3: case 2: { m_WayPoint = reader.ReadItem() as WayPoint; goto case 1; } case 1: { m_Group = reader.ReadBool(); goto case 0; } case 0: { m_MinDelay = reader.ReadTimeSpan(); m_MaxDelay = reader.ReadTimeSpan(); m_Count = reader.ReadInt(); m_Team = reader.ReadInt(); m_HomeRange = reader.ReadInt(); m_Running = reader.ReadBool(); TimeSpan ts = TimeSpan.Zero; if ( m_Running ) ts = reader.ReadDeltaTime() - DateTime.UtcNow; if ( version < 7 ) { int size = reader.ReadInt(); bool addentries = m_Entries.Count == 0; for ( int i = 0; i < size; ++i ) { string typeName = reader.ReadString(); if ( addentries ) m_Entries.Add( new SpawnerEntry( typeName, 100, 1 ) ); else m_Entries[i].SpawnedName = typeName; if ( SpawnerType.GetType( typeName ) == null ) { if ( m_WarnTimer == null ) m_WarnTimer = new WarnTimer(); m_WarnTimer.Add( Location, Map, typeName ); } } int count = reader.ReadInt(); for ( int i = 0; i < count; ++i ) { if ( reader.ReadEntity() is ISpawnable e ) { if ( e is BaseCreature creature ) creature.RemoveIfUntamed = true; e.Spawner = this; for ( int j = 0;j < m_Entries.Count; j++ ) { if ( SpawnerType.GetType( m_Entries[j].SpawnedName ) == e.GetType() ) { m_Entries[j].Spawned.Add( e ); m_Spawned.Add( e, m_Entries[j] ); break; } } } } } DoTimer( ts ); break; } } if ( version < 4 ) m_WalkingRange = m_HomeRange; } public static string ConvertTypes( string type ) { type = type.ToLower(); switch ( type ) { case "wheat": return "WheatSheaf"; case "noxxiousmage": return "NoxiousMage"; case "noxxiousarcher": return "NoxiousArcher"; case "noxxiouswarrior": return "NoxiousWarrior"; case "noxxiouswarlord": return "NoxiousWarlord"; case "obsidian": return "obsidianstatue"; case "adeepwaterelemental": return "deepwaterelemental"; case "noxskeleton": return "poisonskeleton"; case "earthcaller": return "earthsummoner"; case "bonedemon": return "bonedaemon"; } return type; } private class WarnTimer : Timer { private List m_List; private class WarnEntry { public Point3D m_Point; public Map m_Map; public string m_Name; public WarnEntry( Point3D p, Map map, string name ) { m_Point = p; m_Map = map; m_Name = name; } } public WarnTimer() : base( TimeSpan.FromSeconds( 1.0 ) ) { m_List = new List(); Start(); } public void Add( Point3D p, Map map, string name ) { m_List.Add( new WarnEntry( p, map, name ) ); } protected override void OnTick() { try { Console.WriteLine( "Warning: {0} bad spawns detected, logged: 'badspawn.log'", m_List.Count ); using ( StreamWriter op = new StreamWriter( "badspawn.log", true ) ) { op.WriteLine( "# Bad spawns : {0}", DateTime.Now ); op.WriteLine( "# Format: X Y Z F Name" ); op.WriteLine(); foreach ( WarnEntry e in m_List ) op.WriteLine( "{0}\t{1}\t{2}\t{3}\t{4}", e.m_Point.X, e.m_Point.Y, e.m_Point.Z, e.m_Map, e.m_Name ); op.WriteLine(); op.WriteLine(); } } catch { } } } } [Flags] public enum EntryFlags { None = 0x000, InvalidType = 0x001, InvalidParams = 0x002, InvalidProps = 0x004, InvalidEntry = 0x008 } public class SpawnerEntry { private string m_SpawnedName; private int m_SpawnedProbability; private List m_Spawned; private int m_SpawnedMaxCount; private string m_Properties; private string m_Parameters; private EntryFlags m_Valid; public int SpawnedProbability { get => m_SpawnedProbability; set => m_SpawnedProbability = value; } public int SpawnedMaxCount { get => m_SpawnedMaxCount; set => m_SpawnedMaxCount = value; } public string SpawnedName { get => m_SpawnedName; set => m_SpawnedName = value; } public string Properties { get => m_Properties; set => m_Properties = value; } public string Parameters { get => m_Parameters; set => m_Parameters = value; } public EntryFlags Valid{ get => m_Valid; set => m_Valid = value; } public List Spawned => m_Spawned; public bool IsFull => m_Spawned.Count >= m_SpawnedMaxCount; public SpawnerEntry( string name, int probability, int maxcount ) { m_SpawnedName = name; m_SpawnedProbability = probability; m_SpawnedMaxCount = maxcount; m_Spawned = new List(); } public void Serialize( GenericWriter writer ) { writer.Write( (int) 0 ); // version writer.Write( m_SpawnedName ); writer.Write( m_SpawnedProbability ); writer.Write( m_SpawnedMaxCount ); writer.Write( m_Properties ); writer.Write( m_Parameters ); writer.Write( m_Spawned.Count ); for ( int i = 0; i < m_Spawned.Count; ++i ) { object o = m_Spawned[i]; if ( o is Item item ) writer.Write( item ); else if ( o is Mobile mobile ) writer.Write( mobile ); else writer.Write( Serial.MinusOne ); } } public SpawnerEntry( Spawner parent, GenericReader reader ) { int version = reader.ReadInt(); m_SpawnedName = reader.ReadString(); m_SpawnedProbability = reader.ReadInt(); m_SpawnedMaxCount = reader.ReadInt(); m_Properties = reader.ReadString(); m_Parameters = reader.ReadString(); int count = reader.ReadInt(); m_Spawned = new List( count ); for ( int i = 0; i < count; ++i ) { //IEntity e = World.FindEntity( reader.ReadInt() ); if ( reader.ReadEntity() is ISpawnable e ) { e.Spawner = parent; if ( e is BaseCreature creature ) creature.RemoveIfUntamed = true; m_Spawned.Add( e ); if ( !parent.Spawned.ContainsKey( e ) ) parent.Spawned.Add( e, this ); } } } public void Defrag( Spawner parent ) { for ( int i = 0; i < m_Spawned.Count; ++i ) { ISpawnable e = m_Spawned[i]; bool remove = false; if ( e is Item item ) { if ( item.Deleted || item.RootParent is Mobile || item.IsLockedDown || item.IsSecure || item.Spawner == null ) remove = true; } else if ( e is Mobile m ) { if ( m.Deleted ) remove = true; else if ( m is BaseCreature c ) { if ( c.Controlled || c.IsStabled ) remove = true; /* else if ( c.Combatant == null && ( c.GetDistanceToSqrt( Location ) > (c.RangeHome * 4) ) ) { //m_Spawned[i].Delete(); m_Spawned.RemoveAt( i ); --i; c.Delete(); remove = true; } */ } else if ( m.Spawner == null ) remove = true; } else remove = true; if ( remove ) { m_Spawned.RemoveAt( i-- ); if ( parent.Spawned.ContainsKey( e ) ) parent.Spawned.Remove( e ); } } } } }