12752 lines
449 KiB
C#
12752 lines
449 KiB
C#
using Server.Accounting;
|
|
using Server.Commands;
|
|
using Server.Commands.Generic;
|
|
using Server.ContextMenus;
|
|
using Server.Items;
|
|
using Server.Network;
|
|
using Server.Targeting;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Xml;
|
|
using Server.Engines.Spawners;
|
|
|
|
namespace Server.Mobiles;
|
|
|
|
public class XmlSpawner : Item, ISpawner
|
|
{
|
|
public enum TODModeType { Realtime, Gametime }
|
|
|
|
public enum SpawnPositionType { Random, RowFill, ColFill, Perimeter, Player, Waypoint, RelXY, DeltaLocation, Location, Wet, Tiles, NoTiles, ItemID, NoItemID }
|
|
|
|
public class SpawnPositionInfo
|
|
{
|
|
public SpawnPositionType positionType;
|
|
public Mobile trigMob;
|
|
public string[] positionArgs;
|
|
|
|
public SpawnPositionInfo(SpawnPositionType positiontype, Mobile trigmob, string[] positionargs)
|
|
{
|
|
positionType = positiontype;
|
|
trigMob = trigmob;
|
|
positionArgs = positionargs;
|
|
}
|
|
}
|
|
|
|
public class MovementInfo
|
|
{
|
|
public Mobile trigMob;
|
|
public Point3D trigLocation = Point3D.Zero;
|
|
|
|
public MovementInfo(Mobile m)
|
|
{
|
|
trigMob = m;
|
|
if (m != null)
|
|
{
|
|
trigLocation = m.Location;
|
|
}
|
|
}
|
|
}
|
|
|
|
public const string Version = "5.0"; // RUADUCK's EDIT
|
|
public const byte MaxLoops = 10; //maximum number of recursive calls from spawner to itself. this is to prevent stack overflow from xmlspawner scripting
|
|
private const int ShowBoundsItemId = 14089; // 14089 Fire Column // 3555 Campfire // 8708 Skull Pole
|
|
private const string SpawnDataSetName = "Spawns";
|
|
private const string SpawnTablePointName = "Points";
|
|
private const int SpawnFitSize = 16; // Normal wall/door height for a mobile is 20 to walk through
|
|
private static int BaseItemId = 0x1F1C; // Purple Magic Crystal
|
|
private static int ShowItemId = 0x3E57; // ships mast
|
|
private static int defaultTriggerSound = 0x1F4; // click and sparkle sound by default (0x1F4) , click sound (0x3A4)
|
|
public static string XmlSpawnDir = "XmlSpawner"; // default directory for saving/loading .xml files with [xmlload [xmlsave
|
|
private const int MaxSmartSectorListSize = 1024; // maximum sector list size for use in smart spawning. This gives a 512x512 tile range.
|
|
|
|
private static string defwaypointname; // default waypoint name will get assigned in Initialize
|
|
private const string XmlTableName = "Properties";
|
|
private const string XmlDataSetName = "XmlSpawner";
|
|
public static AccessLevel DiskAccessLevel = AccessLevel.Administrator; // minimum access level required by commands that can access the disk such as XmlLoad, XmlSave, and the Save function of XmlEdit
|
|
#if (RESTRICTConstructible)
|
|
public static AccessLevel ConstructibleAccessLevel = AccessLevel.GameMaster; // only allow spawning of objects that have Constructible access restrictions at this level or lower. Must define RESTRICTConstructible to enable this.
|
|
#endif
|
|
private static int MaxMoveCheck = 10; // limit number of players that can be checked for triggering in a single OnMovement tick
|
|
|
|
// specifies the level at which smartspawning will be triggered. Players with AccessLevel above this will not trigger smartspawning unless unhidden.
|
|
public static AccessLevel SmartSpawnAccessLevel = AccessLevel.Player;
|
|
|
|
// define the default values used in making spawners
|
|
private static TimeSpan defMinDelay = TimeSpan.FromMinutes(5);
|
|
private static TimeSpan defMaxDelay = TimeSpan.FromMinutes(10);
|
|
private static TimeSpan defMinRefractory = TimeSpan.FromMinutes(0);
|
|
private static TimeSpan defMaxRefractory = TimeSpan.FromMinutes(0);
|
|
private static TimeSpan defTODStart = TimeSpan.FromMinutes(0);
|
|
private static TimeSpan defTODEnd = TimeSpan.FromMinutes(0);
|
|
private static TimeSpan defDuration = TimeSpan.FromMinutes(0);
|
|
private static TimeSpan defDespawnTime = TimeSpan.FromHours(0);
|
|
private static bool defIsGroup;
|
|
private static int defTeam;
|
|
private static int defProximityTriggerSound = defaultTriggerSound;
|
|
private static int defAmount = 1;
|
|
private static bool defRelativeHome = true;
|
|
private static int defSpawnRange = 5;
|
|
private static int defHomeRange = 5;
|
|
private static double defTriggerProbability = 1;
|
|
private static int defProximityRange = -1;
|
|
private static readonly int defKillReset = 1;
|
|
private static TODModeType defTODMode = TODModeType.Realtime;
|
|
|
|
private static Timer m_GlobalSectorTimer;
|
|
private static bool SmartSpawningSystemEnabled;
|
|
|
|
private static WarnTimer2 m_WarnTimer;
|
|
|
|
// hash table for optimizing HoldSmartSpawning method invocation
|
|
private static Dictionary<Type, PropertyInfo> holdSmartSpawningHash;
|
|
|
|
public static int seccount;
|
|
|
|
// sector hashtable for each map
|
|
private static readonly Dictionary<Sector, List<XmlSpawner>>[] GlobalSectorTable = new Dictionary<Sector, List<XmlSpawner>>[6];
|
|
|
|
private string m_Name = string.Empty;
|
|
private string m_UniqueId = string.Empty;
|
|
private bool m_PlayerCreated;
|
|
private bool m_HomeRangeIsRelative;
|
|
private int m_Team;
|
|
private int m_HomeRange;
|
|
// added a amount parameter for stacked item spawns
|
|
private int m_StackAmount;
|
|
// this is actually redundant with the width height spec for spawning area
|
|
// just an easier way of specifying it
|
|
private int m_SpawnRange;
|
|
private int m_Count;
|
|
private TimeSpan m_MinDelay;
|
|
private TimeSpan m_MaxDelay;
|
|
// added a duration parameter for time-limited spawns
|
|
private TimeSpan m_Duration;
|
|
public List<SpawnObject> m_SpawnObjects = new(); // List of objects to spawn
|
|
private DateTime m_End;
|
|
private DateTime m_RefractEnd;
|
|
private DateTime m_DurEnd;
|
|
private SpawnerTimer m_Timer;
|
|
private InternalTimer m_DurTimer;
|
|
private InternalTimer3 m_RefractoryTimer;
|
|
private bool m_Running;
|
|
private bool m_Group;
|
|
private int m_X;
|
|
private int m_Y;
|
|
private int m_Width;
|
|
private int m_Height;
|
|
private WayPoint m_WayPoint;
|
|
|
|
private Static m_ShowContainerStatic;
|
|
private bool m_proximityActivated;
|
|
private bool m_refractActivated;
|
|
private bool m_durActivated;
|
|
private TimeSpan m_TODStart;
|
|
private TimeSpan m_TODEnd;
|
|
// time after proximity activation when the spawn cannot be reactivated
|
|
private TimeSpan m_MinRefractory;
|
|
private TimeSpan m_MaxRefractory;
|
|
private string m_ItemTriggerName;
|
|
private string m_NoItemTriggerName;
|
|
private Item m_ObjectPropertyItem;
|
|
private string m_ObjectPropertyName;
|
|
public string status_str;
|
|
public int m_killcount;
|
|
// added proximity range sensor
|
|
private int m_ProximityRange;
|
|
// sound played when a proximity triggered spawner is tripped by a player
|
|
// set this to zero if you dont want to hear anything
|
|
private int m_ProximityTriggerSound;
|
|
private string m_ProximityTriggerMessage;
|
|
private string m_SpeechTrigger;
|
|
private bool m_speechTriggerActivated;
|
|
private string m_MobPropertyName;
|
|
private string m_MobTriggerName;
|
|
private string m_PlayerPropertyName;
|
|
private double m_TriggerProbability = defTriggerProbability;
|
|
private Mobile m_mob_who_triggered;
|
|
private Item m_SetPropertyItem;
|
|
|
|
private bool m_skipped;
|
|
private int m_KillReset = defKillReset; // number of spawn ticks that pass without kills before killcount gets reset to zero
|
|
private int m_spawncheck;
|
|
private TODModeType m_TODMode = TODModeType.Realtime;
|
|
private string m_GumpState;
|
|
private bool m_ExternalTriggering;
|
|
private bool m_ExternalTrigger;
|
|
private int m_SequentialSpawning = -1; // off by default
|
|
private DateTime m_SeqEnd;
|
|
private Region m_Region; // 2004.02.08 :: Omega Red
|
|
private string m_RegionName = string.Empty; // 2004.02.08 :: Omega Red
|
|
private AccessLevel m_TriggerAccessLevel = AccessLevel.Player;
|
|
|
|
public List<XmlTextEntryBook> m_TextEntryBook;
|
|
private XmlSpawnerGump m_SpawnerGump;
|
|
|
|
private bool m_AllowGhostTriggering;
|
|
private bool m_AllowNPCTriggering;
|
|
private string m_ConfigFile;
|
|
private bool m_OnHold;
|
|
private bool m_HoldSequence;
|
|
private bool m_SpawnOnTrigger;
|
|
|
|
private List<MovementInfo> m_MovementList;
|
|
private MovementTimer m_MovementTimer;
|
|
internal List<BaseXmlSpawner.KeywordTag> m_KeywordTagList = new();
|
|
|
|
public List<XmlSpawner> RecentSpawnerSearchList = null;
|
|
public List<Item> RecentItemSearchList = null;
|
|
public List<Mobile> RecentMobileSearchList = null;
|
|
private TimeSpan m_DespawnTime;
|
|
|
|
private string m_SkillTrigger;
|
|
private SkillName m_skill_that_triggered;
|
|
private bool m_FreeRun; // override for all other triggering modes
|
|
|
|
private Map currentmap;
|
|
|
|
public bool m_IsInactivated;
|
|
private bool m_SmartSpawning;
|
|
private SectorTimer m_SectorTimer;
|
|
|
|
private List<Static> m_ShowBoundsItems = new();
|
|
|
|
public List<BaseXmlSpawner.TypeInfo> PropertyInfoList = null; // used to optimize property info lookup used by set and get property methods.
|
|
|
|
private Dictionary<string, List<Item>> spawnPositionWayTable; // used to optimize #waypoint lookup
|
|
|
|
private bool inrespawn;
|
|
|
|
private List<Sector> sectorList;
|
|
|
|
private bool m_DisableGlobalAutoReset;
|
|
|
|
private Point3D mostRecentSpawnPosition = Point3D.Zero;
|
|
|
|
// does not decay
|
|
public override bool Decays => false;
|
|
// is not counted in the normal item count
|
|
public override bool IsVirtualItem => true;
|
|
|
|
// private bool m_skillTriggerActivated;
|
|
// private SkillName m_SkillTriggerName;
|
|
// private double m_SkillTriggerMin;
|
|
// private double m_SkillTriggerMax;
|
|
// private int m_SkillTriggerSuccess;
|
|
|
|
public bool DebugThis { get; set; } = false;
|
|
|
|
public int MovingPlayerCount { get; set; }
|
|
|
|
public int FastestPlayerSpeed { get; set; }
|
|
|
|
public int NearbyPlayerCount
|
|
{
|
|
get
|
|
{
|
|
int count = 0;
|
|
if (ProximityRange >= 0)
|
|
{
|
|
IPooledEnumerable eable = GetMobilesInRange(ProximityRange);
|
|
foreach (Mobile m in eable)
|
|
{
|
|
if (m != null && m.Player)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
eable.Free();
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
|
|
public Point3D MostRecentSpawnPosition
|
|
{
|
|
get => mostRecentSpawnPosition;
|
|
set => mostRecentSpawnPosition = value;
|
|
}
|
|
|
|
public TimeSpan GameTOD
|
|
{
|
|
get
|
|
{
|
|
int hours;
|
|
int minutes;
|
|
|
|
Clock.GetTime(Map, Location.X, Location.Y, out hours, out minutes);
|
|
return new DateTime(Core.Now.Year, Core.Now.Month, Core.Now.Day, hours, minutes, 0).TimeOfDay;
|
|
}
|
|
}
|
|
|
|
public TimeSpan RealTOD => Core.Now.TimeOfDay;
|
|
|
|
public int RealDay => Core.Now.Day;
|
|
|
|
public int RealMonth => Core.Now.Month;
|
|
|
|
public DayOfWeek RealDayOfWeek => Core.Now.DayOfWeek;
|
|
|
|
public MoonPhase MoonPhase => Clock.GetMoonPhase(Map, Location.X, Location.Y);
|
|
|
|
public XmlSpawnerGump SpawnerGump
|
|
{
|
|
get => m_SpawnerGump;
|
|
set => m_SpawnerGump = value;
|
|
}
|
|
|
|
public bool DisableGlobalAutoReset { get => m_DisableGlobalAutoReset;
|
|
set => m_DisableGlobalAutoReset = value;
|
|
}
|
|
|
|
public bool DoDefrag
|
|
{
|
|
get => false;
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
Defrag(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly bool sectorIsActive = false;
|
|
private bool UseSectorActivate;
|
|
|
|
public bool SingleSector => UseSectorActivate;
|
|
|
|
public bool InActivationRange(Sector s1, Sector s2)
|
|
{
|
|
// check to see if the sectors are within +- 2 of one another
|
|
if (s1 == null || s2 == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return Math.Abs(s1.X - s2.X) < 3 && Math.Abs(s1.Y - s2.Y) < 3;
|
|
}
|
|
|
|
public bool HasDamagedOrDistantSpawns
|
|
{
|
|
get
|
|
{
|
|
Sector ssec = Map.GetSector(Location);
|
|
// go through the spawn lists
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
|
|
if (o is BaseCreature creature)
|
|
{
|
|
// if the mob is damaged or outside of smartspawning detection range then return true
|
|
if (creature.Hits < creature.HitsMax || creature.Mana < creature.ManaMax || creature.Stam < creature.StamMax || creature.Map != Map)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if the spawn moves into a sector that is not activatable from a sector on the sector list then dont smartspawn
|
|
if (creature.Map != null && creature.Map != Map.Internal)
|
|
{
|
|
Sector bsec = creature.Map.GetSector(creature.Location);
|
|
|
|
if (UseSectorActivate)
|
|
{
|
|
// is it in activatable range of the sector the spawner is in
|
|
if (!InActivationRange(bsec, ssec))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool outofsec = true;
|
|
|
|
if (sectorList != null)
|
|
{
|
|
foreach (Sector s in sectorList)
|
|
{
|
|
// is the creatures sector within activation range of any of the sectors in the list
|
|
if (InActivationRange(bsec, s))
|
|
{
|
|
outofsec = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (outofsec)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static int totalSectorsMonitored;
|
|
|
|
public bool HasActiveSectors
|
|
{
|
|
get
|
|
{
|
|
if (!SmartSpawning || Map == null || Map == Map.Internal)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// is this a region spawner?
|
|
if (m_Region != null)
|
|
{
|
|
var players = m_Region.GetPlayers();
|
|
|
|
if (players == null || players.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// confirm that players with the proper access level are present
|
|
foreach (Mobile m in players)
|
|
{
|
|
if (m != null && (m.AccessLevel <= SmartSpawnAccessLevel || !m.Hidden))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// is this a single sector spawner?
|
|
if (UseSectorActivate)
|
|
{
|
|
return sectorIsActive;
|
|
}
|
|
|
|
// if there is no sector list made for this spawner then create one.
|
|
if (sectorList == null)
|
|
{
|
|
Point3D loc = Location;
|
|
sectorList = new List<Sector>();
|
|
|
|
// is this container held?
|
|
if (Parent != null)
|
|
{
|
|
if (RootParent is Mobile mobile)
|
|
{
|
|
loc = ((Mobile)RootParent).Location;
|
|
}
|
|
else
|
|
if (RootParent is Item item)
|
|
{
|
|
loc = ((Item)RootParent).Location;
|
|
}
|
|
}
|
|
|
|
// find the max detection range by examining both spawnrange
|
|
// note, sectors will activate when within +-2 sectors
|
|
int bufferzone = 2 * Map.SectorSize;
|
|
int x1 = m_X - bufferzone;
|
|
int width = m_Width + 2 * bufferzone;
|
|
int y1 = m_Y - bufferzone;
|
|
int height = m_Height + 2 * bufferzone;
|
|
|
|
// go through all of the sectors within the SpawnRange of the spawner to see if any are active
|
|
for (int x = x1; x <= x1 + width; x += Map.SectorSize)
|
|
{
|
|
for (int y = y1; y <= y1 + height; y += Map.SectorSize)
|
|
{
|
|
Sector s = Map.GetSector(new Point3D(x, y, loc.Z));
|
|
|
|
if (s == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// dont add any redundant sectors
|
|
bool duplicate = false;
|
|
foreach (Sector olds in sectorList)
|
|
{
|
|
if (olds == s)
|
|
{
|
|
duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!duplicate)
|
|
{
|
|
sectorList.Add(s);
|
|
|
|
if (GlobalSectorTable[Map.MapID] == null)
|
|
{
|
|
GlobalSectorTable[Map.MapID] = new Dictionary<Sector, List<XmlSpawner>>();
|
|
}
|
|
|
|
// add this sector and the spawner associated with it to the global sector table
|
|
List<XmlSpawner> spawnerlist;
|
|
if (GlobalSectorTable[Map.MapID].TryGetValue(s, out spawnerlist)) //.Contains(s))
|
|
{
|
|
//List<XmlSpawner> spawnerlist = GlobalSectorTable[Map.MapID][s];
|
|
if (spawnerlist == null)
|
|
{
|
|
//GlobalSectorTable[Map.MapID].Remove(s);
|
|
spawnerlist = new List<XmlSpawner>();
|
|
//GlobalSectorTable[Map.MapID].Add(s, spawnerlist);
|
|
GlobalSectorTable[Map.MapID][s] = spawnerlist;
|
|
}
|
|
|
|
if (!spawnerlist.Contains(this))
|
|
{
|
|
spawnerlist.Add(this);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spawnerlist = new List<XmlSpawner>();
|
|
spawnerlist.Add(this);
|
|
// add a new entry to the table
|
|
GlobalSectorTable[Map.MapID][s] = spawnerlist;
|
|
}
|
|
|
|
totalSectorsMonitored++;
|
|
|
|
// add some sanity checking here
|
|
if (sectorList.Count > MaxSmartSectorListSize)
|
|
{
|
|
SmartSpawning = false;
|
|
|
|
// log it
|
|
try
|
|
{
|
|
Console.WriteLine("SmartSpawning disabled at {0} {1} : Range too large.", loc, Map);
|
|
|
|
using (StreamWriter op = new StreamWriter("badspawn.log", true))
|
|
{
|
|
op.WriteLine("{0} SmartSpawning disabled at {1} {2} : Range too large.", Core.Now, loc, Map);
|
|
op.WriteLine();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UseSectorActivate = false;
|
|
}
|
|
|
|
_TraceStart(2);
|
|
// go through the sectorlist and see if any of the sectors are active
|
|
|
|
foreach (Sector s in sectorList)
|
|
{
|
|
if (s != null && s.Active && s.Clients != null && s.Clients.Count > 0)
|
|
{
|
|
// confirm that players with the proper access level are present
|
|
foreach (NetState ns in s.Clients)
|
|
{
|
|
var m = ns.Mobile;
|
|
if (m != null && (m.AccessLevel <= SmartSpawnAccessLevel || !m.Hidden))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
_TraceEnd(2);
|
|
}
|
|
seccount++;
|
|
}
|
|
_TraceEnd(2);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public int SecCount => seccount;
|
|
|
|
public bool IsInactivated
|
|
{
|
|
get => m_IsInactivated;
|
|
set => m_IsInactivated = value;
|
|
}
|
|
|
|
public int ActiveSectorCount
|
|
{
|
|
get
|
|
{
|
|
if (sectorList != null)
|
|
{
|
|
return sectorList.Count;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public bool PlayerCreated
|
|
{
|
|
get => m_PlayerCreated;
|
|
set => m_PlayerCreated = value;
|
|
}
|
|
|
|
public bool OnHold
|
|
{
|
|
get
|
|
{
|
|
if (m_OnHold)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// determine whether there are any keywordtags with the hold flag
|
|
if (m_KeywordTagList == null || m_KeywordTagList.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (BaseXmlSpawner.KeywordTag sot in m_KeywordTagList)
|
|
{
|
|
// check for any keyword tag with the holdspawn flag
|
|
if (sot != null && !sot.Deleted && (sot.Flags & BaseXmlSpawner.KeywordFlags.HoldSpawn) != 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// no hold flags were set
|
|
return false;
|
|
}
|
|
set => m_OnHold = value;
|
|
}
|
|
|
|
public string AddSpawn
|
|
{
|
|
get => null;
|
|
set
|
|
{
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
string str = value.Trim();
|
|
string typestr = BaseXmlSpawner.ParseObjectType(str);
|
|
|
|
Type type = AssemblyHandler.FindTypeByName(typestr);
|
|
|
|
if (type != null)
|
|
{
|
|
m_SpawnObjects.Add(new SpawnObject(str, 1));
|
|
}
|
|
else
|
|
{
|
|
// check for special keywords
|
|
if (typestr != null && (BaseXmlSpawner.IsTypeOrItemKeyword(typestr) || typestr.IndexOf("{") != -1 || typestr.StartsWith("*") || typestr.StartsWith("#")))
|
|
{
|
|
m_SpawnObjects.Add(new SpawnObject(str, 1));
|
|
}
|
|
else
|
|
{
|
|
status_str = $"{str} is not a valid type name.";
|
|
}
|
|
}
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
}
|
|
|
|
public string UniqueId => m_UniqueId;
|
|
|
|
// does not perform a defrag, so less accurate but can be used while looping through world object enums
|
|
public int SafeCurrentCount => SafeTotalSpawnedObjects;
|
|
|
|
public bool FreeRun
|
|
{
|
|
get => m_FreeRun;
|
|
set => m_FreeRun = value;
|
|
}
|
|
|
|
public bool CanFreeSpawn
|
|
{
|
|
get
|
|
{
|
|
// allow free spawning if proximity sensing is off and if all of the potential free-spawning triggers are disabled
|
|
if (Running && m_ProximityRange == -1 &&
|
|
string.IsNullOrEmpty(m_ObjectPropertyName) &&
|
|
(string.IsNullOrEmpty(m_MobPropertyName) ||
|
|
m_MobTriggerName == null || m_MobTriggerName.Length == 0) &&
|
|
!m_ExternalTriggering)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public SpawnObject[] SpawnObjects
|
|
{
|
|
get => m_SpawnObjects.ToArray();
|
|
set
|
|
{
|
|
if (value != null && value.Length > 0)
|
|
{
|
|
|
|
foreach (SpawnObject so in value)
|
|
{
|
|
if (so == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool AlreadyInList = false;
|
|
|
|
// Check if the new array has an existing spawn object
|
|
foreach (SpawnObject TheSpawn in m_SpawnObjects)
|
|
{
|
|
if (TheSpawn.TypeName.ToUpper() == so.TypeName.ToUpper())
|
|
{
|
|
AlreadyInList = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Does this item need to be added
|
|
if (!AlreadyInList)
|
|
{
|
|
// This is a new spawn object so add it to the array (deep copy)
|
|
m_SpawnObjects.Add(new SpawnObject(so.TypeName, so.ActualMaxCount, so.SubGroup, so.SequentialResetTime, so.SequentialResetTo, so.KillsNeeded,
|
|
so.RestrictKillsToSubgroup, so.ClearOnAdvance, so.MinDelay, so.MaxDelay, so.SpawnsPerTick, so.PackRange));
|
|
}
|
|
}
|
|
|
|
if (SpawnObjects.Length < 1)
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool HoldSequence
|
|
{
|
|
get
|
|
{
|
|
// check to see if any keyword tags have the holdsequence flag set, or whether the spawner holdsequence flag is set
|
|
if (m_HoldSequence)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// determine whether there are any keywordtags with the hold flag
|
|
if (m_KeywordTagList == null || m_KeywordTagList.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (BaseXmlSpawner.KeywordTag sot in m_KeywordTagList)
|
|
{
|
|
// check for any keyword tag with the holdsequence flag
|
|
if (sot != null && !sot.Deleted && (sot.Flags & BaseXmlSpawner.KeywordFlags.HoldSequence) != 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// no hold flags were set
|
|
return false;
|
|
}
|
|
|
|
set => m_HoldSequence = value;
|
|
}
|
|
|
|
public bool CanSpawn
|
|
{
|
|
get
|
|
{
|
|
if (OnHold)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (m_Group)
|
|
{
|
|
if (TotalSpawnedObjects <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (IsFull)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// test for a full spawner
|
|
public bool IsFull
|
|
{
|
|
get
|
|
{
|
|
int nobj = TotalSpawnedObjects;
|
|
|
|
return nobj >= m_Count || nobj >= TotalSpawnObjectCount;
|
|
}
|
|
}
|
|
|
|
// this can be used in loops over world objects since it will not defrag and potentially modify the world object lists
|
|
public int SafeTotalSpawnedObjects
|
|
{
|
|
get
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int count = 0;
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
count += so.SpawnedObjects.Count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
}
|
|
|
|
public int TotalSpawnedObjects
|
|
{
|
|
get
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// defrag so that accurately reflects currently active spawns
|
|
Defrag(true);
|
|
|
|
int count = 0;
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
count += so.SpawnedObjects.Count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
}
|
|
|
|
public bool isEmpty()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
if (so.SpawnedObjects != null && so.SpawnedObjects.Count > 0)
|
|
{
|
|
if (so.SpawnedObjects[0] is Mobile)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public int TotalSpawnObjectCount
|
|
{
|
|
get
|
|
{
|
|
int count = 0;
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
count += so.MaxCount;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool GumpReset
|
|
{
|
|
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
m_SpawnerGump = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Region SpawnRegion
|
|
{
|
|
get => m_Region;
|
|
set
|
|
{
|
|
// force a re-update of the smart spawning sector list the next time it is accessed
|
|
ResetSectorList();
|
|
|
|
m_Region = value;
|
|
|
|
m_RegionName = m_Region?.Name;
|
|
}
|
|
}
|
|
|
|
// 2004.02.08 :: Omega Red
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string RegionName
|
|
{
|
|
get => m_RegionName;
|
|
set
|
|
{
|
|
// force a re-update of the smart spawning sector list the next time it is accessed
|
|
ResetSectorList();
|
|
|
|
m_RegionName = value;
|
|
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
m_Region = null;
|
|
return;
|
|
}
|
|
|
|
if (Region.Regions.Count == 0) // after world load, before region load
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (Region region in Region.Regions)
|
|
{
|
|
if (string.Compare(region.Name, m_RegionName, true) == 0)
|
|
{
|
|
m_Region = region;
|
|
m_RegionName = region.Name;
|
|
//InvalidateProperties();
|
|
return;
|
|
}
|
|
}
|
|
status_str = $"invalid region: {value}";
|
|
m_Region = null;
|
|
}
|
|
}
|
|
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D X1_Y1
|
|
{
|
|
get => new(m_X, m_Y, Z);
|
|
set
|
|
{
|
|
// X1 and Y1 will initiate region specification
|
|
m_Width = 0;
|
|
m_Height = 0;
|
|
m_X = value.X;
|
|
m_Y = value.Y;
|
|
|
|
// reset the sector list
|
|
ResetSectorList();
|
|
|
|
m_SpawnRange = 0;
|
|
|
|
if (ShowBounds)
|
|
{
|
|
ShowBounds = false;
|
|
ShowBounds = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Point3D X2_Y2
|
|
{
|
|
get => new(m_X + m_Width, m_Y + m_Height, Z);
|
|
set
|
|
{
|
|
int X2;
|
|
int Y2;
|
|
|
|
int OriginalX2 = m_X + m_Width;
|
|
int OriginalY2 = m_Y + m_Height;
|
|
|
|
// reset the sector list
|
|
ResetSectorList();
|
|
|
|
// now determine based upon the entered coordinate values what the lower left corner is
|
|
// lower left will be the min x and min y
|
|
// upper right will be max x max y
|
|
if (value.X < OriginalX2)
|
|
{
|
|
// ok, this is the proper x value for the lower left
|
|
m_X = value.X;
|
|
X2 = OriginalX2;
|
|
}
|
|
else
|
|
{
|
|
m_X = OriginalX2;
|
|
X2 = value.X;
|
|
}
|
|
|
|
if (value.Y < OriginalY2)
|
|
{
|
|
// ok, this is the proper y value for the lower left
|
|
m_Y = value.Y;
|
|
Y2 = OriginalY2;
|
|
}
|
|
else
|
|
{
|
|
m_Y = OriginalY2;
|
|
Y2 = value.Y;
|
|
}
|
|
|
|
m_Width = X2 - m_X;
|
|
m_Height = Y2 - m_Y;
|
|
|
|
if (m_Width == m_Height)
|
|
{
|
|
m_SpawnRange = m_Width / 2;
|
|
}
|
|
else
|
|
{
|
|
m_SpawnRange = -1;
|
|
}
|
|
|
|
if (m_HomeRangeIsRelative == false)
|
|
{
|
|
int NewHomeRange = m_Width > m_Height ? m_Height : m_Width;
|
|
m_HomeRange = NewHomeRange > 0 ? NewHomeRange : 0;
|
|
}
|
|
|
|
//original test was for less than 1, changed it to less than zero (zero is a valid width, its the default in fact)
|
|
// Stop the spawner if the width or height is less than 1
|
|
if (m_Width < 0 || m_Height < 0)
|
|
{
|
|
Running = false;
|
|
}
|
|
|
|
InvalidateProperties();
|
|
|
|
if (ShowBounds)
|
|
{
|
|
ShowBounds = false;
|
|
ShowBounds = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// added the spawnrange property. It sets both the XY and width/height parameters automatically.
|
|
// also doesnt mess with homerange like XY does
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int SpawnRange
|
|
{
|
|
get => m_SpawnRange;
|
|
set
|
|
{
|
|
if (value < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// reset the sector list
|
|
ResetSectorList();
|
|
|
|
m_SpawnRange = value;
|
|
m_Width = m_SpawnRange * 2;
|
|
m_Height = m_SpawnRange * 2;
|
|
|
|
// dont set the bounding box locations if the initial location is 0,0 since this occurs when the item is just being made
|
|
// because m_X and m_Y are restored on loading, it creates problems with OnLocationChange which has to avoid applying translational
|
|
// adjustments to newly placed spawners (because the actual m_X and m_Y is associated with the original location, not the 0,0 location)
|
|
// basically, before placement, dont set m_X or m_Y to anything that needs to be adjusted later on
|
|
|
|
if (Location.X == 0 && Location.Y == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_X = Location.X - m_SpawnRange;
|
|
m_Y = Location.Y - m_SpawnRange;
|
|
|
|
if (ShowBounds)
|
|
{
|
|
ShowBounds = false;
|
|
ShowBounds = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool ShowBounds
|
|
{
|
|
get => m_ShowBoundsItems != null && m_ShowBoundsItems.Count > 0;
|
|
set
|
|
{
|
|
if (value && ShowBounds == false)
|
|
{
|
|
if (m_ShowBoundsItems == null)
|
|
{
|
|
m_ShowBoundsItems = new List<Static>();
|
|
}
|
|
|
|
// Boundary lines
|
|
int ValidX1 = m_X;
|
|
int ValidX2 = m_X + m_Width;
|
|
int ValidY1 = m_Y;
|
|
int ValidY2 = m_Y + m_Height;
|
|
|
|
for (int x = 0; x <= m_Width; x++)
|
|
{
|
|
int NewX = m_X + x;
|
|
for (int y = 0; y <= m_Height; y++)
|
|
{
|
|
int NewY = m_Y + y;
|
|
|
|
if (NewX == ValidX1 || NewX == ValidX2 || NewX == ValidY1 || NewX == ValidY2 || NewY == ValidX1 || NewY == ValidX2 || NewY == ValidY1 || NewY == ValidY2)
|
|
{
|
|
// Add an object to show the spawn area
|
|
Static s = new Static(ShowBoundsItemId)
|
|
{
|
|
Visible = false
|
|
};
|
|
s.MoveToWorld(new Point3D(NewX, NewY, Z), Map);
|
|
m_ShowBoundsItems.Add(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (value == false && m_ShowBoundsItems != null)
|
|
{
|
|
// Remove all of the items from the array
|
|
foreach (Static s in m_ShowBoundsItems)
|
|
{
|
|
s.Delete();
|
|
}
|
|
|
|
m_ShowBoundsItems.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int MaxCount
|
|
{
|
|
get => m_Count;
|
|
set
|
|
{
|
|
m_Count = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int CurrentCount => TotalSpawnedObjects;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public WayPoint WayPoint
|
|
{
|
|
get => m_WayPoint;
|
|
set => m_WayPoint = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool ExternalTriggering
|
|
{
|
|
get => m_ExternalTriggering;
|
|
set => m_ExternalTriggering = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool ExtTrigState
|
|
{
|
|
get => m_ExternalTrigger;
|
|
set => m_ExternalTrigger = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Running
|
|
{
|
|
get => m_Running;
|
|
set
|
|
{
|
|
// Don't start the spawner unless the height and width are valid
|
|
if (value && m_Width >= 0 && m_Height >= 0)
|
|
{
|
|
Start();
|
|
}
|
|
else
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HomeRange
|
|
{
|
|
get => m_HomeRange;
|
|
set { m_HomeRange = value; InvalidateProperties(); }
|
|
}
|
|
|
|
public Region Region { get; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool HomeRangeIsRelative
|
|
{
|
|
get => m_HomeRangeIsRelative;
|
|
set => m_HomeRangeIsRelative = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Team
|
|
{
|
|
get => m_Team;
|
|
set { m_Team = value; InvalidateProperties(); }
|
|
}
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int StackAmount
|
|
{
|
|
get => m_StackAmount;
|
|
set => m_StackAmount = value;
|
|
}
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan MinDelay
|
|
{
|
|
get => m_MinDelay;
|
|
set
|
|
{
|
|
m_MinDelay = value;
|
|
// reset the spawn timer
|
|
DoTimer();
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan MaxDelay
|
|
{
|
|
get => m_MaxDelay;
|
|
set
|
|
{
|
|
m_MaxDelay = value;
|
|
// reset the spawn timer
|
|
DoTimer();
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int KillCount
|
|
{
|
|
get => m_killcount;
|
|
set => m_killcount = value;
|
|
}
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int KillReset
|
|
{
|
|
get => m_KillReset;
|
|
set => m_KillReset = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public double TriggerProbability
|
|
{
|
|
get => m_TriggerProbability;
|
|
set => m_TriggerProbability = value;
|
|
}
|
|
|
|
//added refractory period support
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan RefractMin
|
|
{
|
|
get => m_MinRefractory;
|
|
set => m_MinRefractory = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan RefractMax
|
|
{
|
|
get => m_MaxRefractory;
|
|
set => m_MaxRefractory = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan RefractoryOver
|
|
{
|
|
get
|
|
{
|
|
if (m_refractActivated)
|
|
{
|
|
return m_RefractEnd - Core.Now;
|
|
}
|
|
|
|
return TimeSpan.FromSeconds(0);
|
|
}
|
|
set => DoTimer3(value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string SetItemName
|
|
{
|
|
get
|
|
{
|
|
if (m_SetPropertyItem == null || m_SetPropertyItem.Deleted)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return m_SetPropertyItem.Name;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Item SetItem
|
|
{
|
|
get => m_SetPropertyItem;
|
|
set => m_SetPropertyItem = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string MobTriggerProp
|
|
{
|
|
get => m_MobPropertyName;
|
|
set => m_MobPropertyName = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string MobTriggerName
|
|
{
|
|
get => m_MobTriggerName;
|
|
set => m_MobTriggerName = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Mobile MobTriggerId
|
|
{
|
|
get
|
|
{
|
|
if (m_MobTriggerName == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// try to parse out the type information if it has also been saved
|
|
string[] typeargs = m_MobTriggerName.Split(",".ToCharArray(), 2);
|
|
string typestr = null;
|
|
string namestr = m_MobTriggerName;
|
|
|
|
if (typeargs.Length > 1)
|
|
{
|
|
namestr = typeargs[0];
|
|
typestr = typeargs[1];
|
|
}
|
|
return BaseXmlSpawner.FindMobileByName(this, namestr, typestr);
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string PlayerTriggerProp
|
|
{
|
|
get => m_PlayerPropertyName;
|
|
set => m_PlayerPropertyName = value;
|
|
}
|
|
|
|
// time of day activation
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan TODStart
|
|
{
|
|
get => m_TODStart;
|
|
set => m_TODStart = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan TODEnd
|
|
{
|
|
get => m_TODEnd;
|
|
set => m_TODEnd = value;
|
|
}
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan TOD
|
|
{
|
|
get
|
|
{
|
|
if (m_TODMode == TODModeType.Gametime)
|
|
{
|
|
int hours;
|
|
int minutes;
|
|
Clock.GetTime(Map, Location.X, Location.Y, out hours, out minutes);
|
|
return new DateTime(Core.Now.Year, Core.Now.Month, Core.Now.Day, hours, minutes, 0).TimeOfDay;
|
|
}
|
|
|
|
return Core.Now.TimeOfDay;
|
|
}
|
|
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TODModeType TODMode
|
|
{
|
|
get => m_TODMode;
|
|
set => m_TODMode = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool TODInRange
|
|
{
|
|
get
|
|
{
|
|
if (m_TODStart == m_TODEnd)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
DateTime now;
|
|
|
|
if (m_TODMode == TODModeType.Gametime)
|
|
{
|
|
int hours;
|
|
int minutes;
|
|
Clock.GetTime(Map, Location.X, Location.Y, out hours, out minutes);
|
|
now = new DateTime(Core.Now.Year, Core.Now.Month, Core.Now.Day, hours, minutes, 0);
|
|
}
|
|
else
|
|
{
|
|
// calculate the time window
|
|
now = Core.Now;
|
|
}
|
|
var day_start = new DateTime(now.Year, now.Month, now.Day);
|
|
// calculate the starting TOD window by adding the TODStart to day_start
|
|
var TOD_start = day_start + m_TODStart;
|
|
var TOD_end = day_start + m_TODEnd;
|
|
|
|
// handle the case when TODstart is before midnight and end is after
|
|
|
|
if (TOD_start > TOD_end)
|
|
{
|
|
if (now > TOD_start || now < TOD_end)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (now > TOD_start && now < TOD_end)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan DespawnTime
|
|
{
|
|
get => m_DespawnTime;
|
|
set => m_DespawnTime = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan Duration
|
|
{
|
|
get => m_Duration;
|
|
set
|
|
{
|
|
m_Duration = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan DurationOver
|
|
{
|
|
get
|
|
{
|
|
if (m_durActivated)
|
|
{
|
|
return m_DurEnd - Core.Now;
|
|
}
|
|
|
|
return TimeSpan.FromSeconds(0);
|
|
}
|
|
set => DoTimer2(value);
|
|
}
|
|
// proximity range parameter
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ProximityRange
|
|
{
|
|
get => m_ProximityRange;
|
|
set
|
|
{
|
|
m_ProximityRange = value;
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
|
|
// proximity range activated?
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool ProximityActivated
|
|
{
|
|
get => m_proximityActivated;
|
|
set
|
|
{
|
|
|
|
if (AllowTriggering)
|
|
{
|
|
ActivateTrigger();
|
|
}
|
|
|
|
m_proximityActivated = value;
|
|
|
|
}
|
|
}
|
|
|
|
// proximity trigger sound parameter
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ProximitySound
|
|
{
|
|
get => m_ProximityTriggerSound;
|
|
set => m_ProximityTriggerSound = value;
|
|
}
|
|
|
|
// proximity trigger message parameter
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string ProximityMsg
|
|
{
|
|
get => m_ProximityTriggerMessage;
|
|
set => m_ProximityTriggerMessage = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string SpeechTrigger
|
|
{
|
|
get => m_SpeechTrigger;
|
|
set => m_SpeechTrigger = value;
|
|
}
|
|
|
|
public string SkillTrigger
|
|
{
|
|
get => m_SkillTrigger;
|
|
set => m_SkillTrigger = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan NextSpawn
|
|
{
|
|
get
|
|
{
|
|
if (m_Running)
|
|
{
|
|
return m_End - Core.Now;
|
|
}
|
|
|
|
return TimeSpan.FromSeconds(0);
|
|
}
|
|
set
|
|
{
|
|
Start();
|
|
DoTimer(value);
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool SpawnOnTrigger
|
|
{
|
|
get => m_SpawnOnTrigger;
|
|
set => m_SpawnOnTrigger = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Group
|
|
{
|
|
get => m_Group;
|
|
set { m_Group = value; InvalidateProperties(); }
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string GumpState
|
|
{
|
|
get => m_GumpState;
|
|
set => m_GumpState = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int SequentialSpawn
|
|
{
|
|
get => m_SequentialSpawning;
|
|
set => m_SequentialSpawning = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan NextSeqReset
|
|
{
|
|
get
|
|
{
|
|
if (m_Running && m_SeqEnd - Core.Now > TimeSpan.Zero)
|
|
{
|
|
return m_SeqEnd - Core.Now;
|
|
}
|
|
|
|
return TimeSpan.FromSeconds(0);
|
|
}
|
|
set => m_SeqEnd = Core.Now + value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public AccessLevel TriggerAccessLevel
|
|
{
|
|
get => m_TriggerAccessLevel;
|
|
set => m_TriggerAccessLevel = value;
|
|
}
|
|
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool DoRespawn
|
|
{
|
|
get => false;
|
|
set
|
|
{
|
|
// need to determine whether this is being set by the spawner during processing of a respawn entry
|
|
// if so then dont do it, otherwise you will infinitely recurse and crash with a stack overflow
|
|
if (value && !inrespawn)
|
|
{
|
|
TryRespawn();
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool DoReset
|
|
{
|
|
get => false;
|
|
set { if (value)
|
|
{
|
|
Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool AllowGhostTrig
|
|
{
|
|
get => m_AllowGhostTriggering;
|
|
set => m_AllowGhostTriggering = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool AllowNPCTrig
|
|
{
|
|
get => m_AllowNPCTriggering;
|
|
set => m_AllowNPCTriggering = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public string ConfigFile
|
|
{
|
|
get => m_ConfigFile;
|
|
set => m_ConfigFile = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool LoadConfig
|
|
{
|
|
get => false;
|
|
set { if (value)
|
|
{
|
|
LoadXmlConfig(ConfigFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Mobile TriggerMob
|
|
{
|
|
get => m_mob_who_triggered;
|
|
set => m_mob_who_triggered = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool SmartSpawning
|
|
{
|
|
get => m_SmartSpawning;
|
|
set
|
|
{
|
|
m_SmartSpawning = value;
|
|
|
|
if (m_SmartSpawning)
|
|
{
|
|
// if any spawner is smartspawning, then the smartspawning system is enabled
|
|
SmartSpawningSystemEnabled = true;
|
|
// check to see if the global sector timer is running
|
|
if (m_GlobalSectorTimer == null || !m_GlobalSectorTimer.Running)
|
|
{
|
|
// start the global smartspawning timer
|
|
DoGlobalSectorTimer(TimeSpan.FromSeconds(1));
|
|
}
|
|
}
|
|
|
|
//IsInactivated = false;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool IsEmpty => isEmpty();
|
|
|
|
public Guid Guid { get; }
|
|
public bool UnlinkOnTaming => true;
|
|
|
|
[CommandProperty(AccessLevel.Developer)]
|
|
public bool ReturnOnDeactivate { get; set; }
|
|
|
|
public Point3D HomeLocation => Location;
|
|
public int Range => HomeRange;
|
|
|
|
public virtual void GetSpawnProperties(ISpawnable spawn, IPropertyList list)
|
|
{ }
|
|
|
|
public virtual void GetSpawnContextEntries(ISpawnable spawn, Mobile user, List<ContextMenuEntry> list)
|
|
{ }
|
|
|
|
public void Remove(ISpawnable spawn)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
if (so.SpawnedObjects[i] == spawn)
|
|
{
|
|
so.SpawnedObjects.Remove(spawn);
|
|
if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup)
|
|
{
|
|
if (so.SubGroup == SequentialSpawn)
|
|
{
|
|
m_killcount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_killcount++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RestoreISpawner()
|
|
{
|
|
// restore the Spawner assignments to all spawned objects
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
object o = so.SpawnedObjects[i];
|
|
if (o is Item item)
|
|
{
|
|
item.Spawner = this;
|
|
}
|
|
else if (o is Mobile mobile)
|
|
{
|
|
mobile.Spawner = this;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnAfterDuped(Item newItem)
|
|
{
|
|
((XmlSpawner)newItem).Running = false; // automatically turn off duped spawners
|
|
|
|
base.OnAfterDuped(newItem);
|
|
}
|
|
|
|
public override void OnMapChange()
|
|
{
|
|
base.OnMapChange();
|
|
|
|
currentmap = Map;
|
|
|
|
ResetSectorList(); // reset the sector list for smart spawning
|
|
}
|
|
|
|
public override void OnDoubleClick(Mobile from)
|
|
{
|
|
if (from == null || from.Deleted || from.AccessLevel < AccessLevel.GameMaster || m_SpawnerGump != null && SomeOneHasGumpOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeleteTextEntryBook(); // clear any text entry books that might still be around
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
Account acct = from.Account as Account; // read the text entries for default values
|
|
|
|
if (acct != null)
|
|
{
|
|
XmlSpawnerDefaults.DefaultEntry defs = XmlSpawnerDefaults.GetDefaults(acct.ToString(), from.Name);
|
|
if (defs != null)
|
|
{
|
|
x = defs.SpawnerGumpX;
|
|
y = defs.SpawnerGumpY;
|
|
}
|
|
}
|
|
|
|
XmlSpawnerGump g = new XmlSpawnerGump(this, x, y, 0, 0, 0);
|
|
from.SendGump(g);
|
|
}
|
|
|
|
public override void GetProperties(IPropertyList list)
|
|
{
|
|
base.GetProperties(list);
|
|
|
|
list.Add(m_Running ? 1060742 : 1060743); // Active - Inactive
|
|
|
|
// add whitespace to the beginning to avoid any problem with names that begin with # and are interpreted as cliloc ids
|
|
list.Add(1042971, $" {Name}"); // ~1_val~
|
|
list.Add(1060656, m_Count.ToString()); // amount to make: ~1_val~
|
|
list.Add(1061169, m_HomeRange.ToString()); // range ~1_val~
|
|
|
|
int nlist_items = 6;
|
|
|
|
if (m_Group)
|
|
{
|
|
list.Add(1060658 + 6 - nlist_items, $"{"group"}\t{m_Group}"); // ~1_val~: ~2_val~
|
|
nlist_items--;
|
|
}
|
|
|
|
if (m_Team != 0)
|
|
{
|
|
list.Add(1060658 + 6 - nlist_items, $"{"team"}\t{m_Team}"); // ~1_val~: ~2_val~
|
|
nlist_items--;
|
|
}
|
|
|
|
list.Add(1060658 + 6 - nlist_items, $"{"speed"}\t{m_MinDelay} to {m_MaxDelay}"); // ~1_val~: ~2_val~
|
|
nlist_items--;
|
|
|
|
// display the duration parameter in the prop gump if it is non-zero
|
|
if (m_Duration > TimeSpan.FromMinutes(0))
|
|
{
|
|
list.Add(1060658 + 6 - nlist_items, $"{"Duration"}\t{m_Duration}");
|
|
nlist_items--;
|
|
}
|
|
|
|
// display the proximity range parameter in the prop gump if it is active
|
|
if (m_ProximityRange != -1)
|
|
{
|
|
list.Add(1060658 + 6 - nlist_items, $"{"ProximityRange"}\t{m_ProximityRange}");
|
|
nlist_items--;
|
|
}
|
|
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
for (int i = 0; i < nlist_items && i < m_SpawnObjects.Count; ++i)
|
|
{
|
|
string typename = m_SpawnObjects[i].TypeName;
|
|
if (typename != null && typename.Length > 20)
|
|
{
|
|
typename = typename[..20];
|
|
}
|
|
|
|
typename = $" {typename}";
|
|
|
|
list.Add(1060658 + (6 - nlist_items) + i, $"{typename}\t{m_SpawnObjects[i].SpawnedObjects.Count}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnDelete()
|
|
{
|
|
base.OnDelete();
|
|
|
|
if (ShowBounds)
|
|
{
|
|
ShowBounds = false;
|
|
}
|
|
|
|
RemoveSpawnObjects();
|
|
|
|
// remove any text entry books that might still be attached to the spawner
|
|
DeleteTextEntryBook();
|
|
|
|
if (m_Timer != null)
|
|
{
|
|
m_Timer.Stop();
|
|
}
|
|
|
|
if (m_DurTimer != null)
|
|
{
|
|
m_DurTimer.Stop();
|
|
}
|
|
|
|
if (m_RefractoryTimer != null)
|
|
{
|
|
m_RefractoryTimer.Stop();
|
|
}
|
|
|
|
// if statics were added for marking container held spawners, delete them
|
|
if (m_ShowContainerStatic != null && !m_ShowContainerStatic.Deleted)
|
|
{
|
|
m_ShowContainerStatic.Delete();
|
|
}
|
|
}
|
|
|
|
static bool IgnoreLocationChange;
|
|
public override void OnLocationChange(Point3D oldLocation)
|
|
{
|
|
if (IgnoreLocationChange)
|
|
{
|
|
IgnoreLocationChange = false;
|
|
return;
|
|
}
|
|
|
|
|
|
// calculate the positional shift
|
|
if (oldLocation.X > 0 && oldLocation.Y > 0)
|
|
{
|
|
int diffx = X - oldLocation.X;
|
|
int diffy = Y - oldLocation.Y;
|
|
m_X += diffx;
|
|
m_Y += diffy;
|
|
}
|
|
else
|
|
{
|
|
// Keep the original dimensions the same (Width, Height),
|
|
// just recalculate the new top left corner
|
|
m_X = X - m_Width / 2;
|
|
m_Y = Y - m_Height / 2;
|
|
}
|
|
|
|
// reset the sector list for smart spawning
|
|
ResetSectorList();
|
|
|
|
// Check if the spawner is showing its bounds
|
|
if (ShowBounds)
|
|
{
|
|
ShowBounds = false;
|
|
ShowBounds = true;
|
|
}
|
|
}
|
|
|
|
public bool SomeOneHasGumpOpen
|
|
{
|
|
get
|
|
{
|
|
// go through all online mobiles and see if any have xmlspawner gumps open
|
|
var states = TcpServer.Instances;
|
|
|
|
foreach (var ns in states)
|
|
{
|
|
var m = ns.Mobile;
|
|
if (m != null && m.HasGump<XmlSpawnerGump>())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
public static void SpawnerGumpCallback(Mobile from, object invoker, string response)
|
|
{
|
|
// assign the response to the gumpstate
|
|
if (invoker is XmlSpawner xs)
|
|
{
|
|
xs.GumpState = response;
|
|
}
|
|
}
|
|
|
|
public void DeleteTextEntryBook()
|
|
{
|
|
if (m_TextEntryBook != null)
|
|
{
|
|
foreach (XmlTextEntryBook s in m_TextEntryBook)
|
|
{
|
|
s.Delete();
|
|
}
|
|
|
|
m_TextEntryBook = null;
|
|
}
|
|
}
|
|
|
|
private static bool IsConstructible(ConstructorInfo ctor) => ctor.IsDefined(typeof(ConstructibleAttribute), false);
|
|
|
|
public static int ConvertToInt(string value)
|
|
{
|
|
if (value.StartsWith("0x"))
|
|
{
|
|
return Convert.ToInt32(value.Substring(2), 16);
|
|
}
|
|
|
|
return Convert.ToInt32(value);
|
|
}
|
|
|
|
public static void ExecuteAction(object attachedto, Mobile trigmob, string action)
|
|
{
|
|
Point3D loc = Point3D.Zero;
|
|
Map map = null;
|
|
if (attachedto is IEntity entity)
|
|
{
|
|
loc = entity.Location;
|
|
map = entity.Map;
|
|
}
|
|
|
|
if (action == null || action.Length <= 0 || attachedto == null || map == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SpawnObject TheSpawn = new SpawnObject(null, 0)
|
|
{
|
|
TypeName = action
|
|
};
|
|
string substitutedtypeName = BaseXmlSpawner.ApplySubstitution(null, attachedto, action);
|
|
string typeName = BaseXmlSpawner.ParseObjectType(substitutedtypeName);
|
|
|
|
|
|
string status_str;
|
|
if (BaseXmlSpawner.IsTypeOrItemKeyword(typeName))
|
|
{
|
|
BaseXmlSpawner.SpawnTypeKeyword(attachedto, TheSpawn, typeName, substitutedtypeName, trigmob, map, out status_str);
|
|
}
|
|
else
|
|
{
|
|
// its a regular type descriptor so find out what it is
|
|
Type type = AssemblyHandler.FindTypeByName(typeName);
|
|
try
|
|
{
|
|
string[] arglist = BaseXmlSpawner.ParseString(substitutedtypeName, 3, "/");
|
|
object o = CreateObject(type, arglist[0]);
|
|
|
|
if (o == null)
|
|
{
|
|
status_str = $"invalid type specification: {arglist[0]}";
|
|
}
|
|
else
|
|
if (o is Mobile mobile)
|
|
{
|
|
if (mobile is BaseCreature creature)
|
|
{
|
|
creature.Home = loc; // Spawners location is the home point
|
|
}
|
|
|
|
mobile.Location = loc;
|
|
mobile.Map = map;
|
|
|
|
BaseXmlSpawner.ApplyObjectStringProperties(null, substitutedtypeName, mobile, trigmob, attachedto, out status_str);
|
|
}
|
|
else
|
|
if (o is Item item)
|
|
{
|
|
BaseXmlSpawner.AddSpawnItem(null, attachedto, TheSpawn, item, loc, map, trigmob, false, substitutedtypeName, out status_str);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void RemoveFromSectorTable(Sector s, XmlSpawner spawner)
|
|
{
|
|
if (s == null || s.Owner == null || s.Owner == Map.Internal || GlobalSectorTable[s.Owner.MapID] == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// find the sector
|
|
List<XmlSpawner> spawnerlist;
|
|
if (GlobalSectorTable[s.Owner.MapID].TryGetValue(s, out spawnerlist) && spawnerlist != null)
|
|
{
|
|
//List<XmlSpawner> spawnerlist = GlobalSectorTable[s.Owner.MapID][s];
|
|
if (spawnerlist.Contains(spawner))
|
|
{
|
|
spawnerlist.Remove(spawner);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResetSectorList()
|
|
{
|
|
// remove the global sector entries
|
|
if (sectorList != null)
|
|
{
|
|
foreach (Sector s in sectorList)
|
|
{
|
|
RemoveFromSectorTable(s, this);
|
|
|
|
}
|
|
}
|
|
sectorList = null;
|
|
UseSectorActivate = false;
|
|
|
|
// force an update of the sector list
|
|
bool sectorrefresh = HasActiveSectors;
|
|
}
|
|
|
|
public void LoadXmlConfig(string filename)
|
|
{
|
|
if (filename == null || filename.Length <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if the file exists
|
|
if (File.Exists(filename))
|
|
{
|
|
FileStream fs = null;
|
|
try
|
|
{
|
|
fs = File.Open(filename, FileMode.Open, FileAccess.Read);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
|
|
if (fs == null)
|
|
{
|
|
status_str = $"Unable to open {filename} for loading";
|
|
return;
|
|
}
|
|
|
|
// Create the data set
|
|
DataSet ds = new DataSet(XmlDataSetName);
|
|
|
|
// Read in the file
|
|
bool fileerror = false;
|
|
try
|
|
{
|
|
ds.ReadXml(fs);
|
|
}
|
|
catch { fileerror = true; }
|
|
// close the file
|
|
fs.Close();
|
|
if (fileerror)
|
|
{
|
|
Console.WriteLine("XmlSpawner: Error in XML config file '{0}'", filename);
|
|
return;
|
|
}
|
|
|
|
// Check that at least a single table was loaded
|
|
if (ds.Tables.Count > 0)
|
|
{
|
|
if (ds.Tables[XmlTableName] != null && ds.Tables[XmlTableName].Rows.Count > 0)
|
|
{
|
|
foreach (DataRow dr in ds.Tables[XmlTableName].Rows)
|
|
{
|
|
string strEntry = null;
|
|
bool boolEntry = true;
|
|
double doubleEntry = 0;
|
|
int intEntry = 0;
|
|
|
|
var valid_entry = true;
|
|
try { strEntry = (string)dr["Name"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { Name = strEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["X"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_X = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["Y"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_Y = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["Width"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_Width = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["Height"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_Height = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["CentreX"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { X = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["CentreY"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { Y = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["CentreZ"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { Z = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["SequentialSpawning"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_SequentialSpawning = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["ProximityRange"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_ProximityRange = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["ProximityTriggerMessage"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_ProximityTriggerMessage = strEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["SpeechTrigger"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_SpeechTrigger = strEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["SkillTrigger"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_SkillTrigger = strEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["ProximityTriggerSound"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_ProximityTriggerSound = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["ItemTriggerName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_ItemTriggerName = strEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["NoItemTriggerName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_NoItemTriggerName = strEntry; }
|
|
|
|
// check for the delayinsec entry
|
|
bool delayinsec = false;
|
|
try { delayinsec = bool.Parse((string)dr["DelayInSec"]); }
|
|
catch { }
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["MinDelay"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
m_MinDelay = delayinsec ? TimeSpan.FromSeconds(doubleEntry) : TimeSpan.FromMinutes(doubleEntry);
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["MaxDelay"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
m_MaxDelay = delayinsec ? TimeSpan.FromSeconds(doubleEntry) : TimeSpan.FromMinutes(doubleEntry);
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["Duration"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_Duration = TimeSpan.FromMinutes(doubleEntry); }
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["DespawnTime"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_DespawnTime = TimeSpan.FromHours(doubleEntry); }
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["MinRefractory"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_MinRefractory = TimeSpan.FromMinutes(doubleEntry); }
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["MaxRefractory"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_MaxRefractory = TimeSpan.FromMinutes(doubleEntry); }
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["TODStart"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_TODStart = TimeSpan.FromMinutes(doubleEntry); }
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["TODEnd"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_TODEnd = TimeSpan.FromMinutes(doubleEntry); }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["TODMode"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_TODMode = (TODModeType)intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["Amount"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_StackAmount = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["MaxCount"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_Count = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["Range"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_HomeRange = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["Team"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_Team = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["WayPoint"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_WayPoint = GetWaypoint(strEntry); }
|
|
|
|
valid_entry = true;
|
|
try { intEntry = int.Parse((string)dr["KillReset"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_KillReset = intEntry; }
|
|
|
|
valid_entry = true;
|
|
try { doubleEntry = double.Parse((string)dr["TriggerProbability"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_TriggerProbability = doubleEntry; }
|
|
|
|
valid_entry = true;
|
|
try { boolEntry = bool.Parse((string)dr["ExternalTriggering"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_ExternalTriggering = boolEntry; }
|
|
|
|
valid_entry = true;
|
|
try { boolEntry = bool.Parse((string)dr["IsGroup"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_Group = boolEntry; }
|
|
|
|
valid_entry = true;
|
|
try { boolEntry = bool.Parse((string)dr["IsHomeRangeRelative"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_HomeRangeIsRelative = boolEntry; }
|
|
|
|
valid_entry = true;
|
|
try { boolEntry = bool.Parse((string)dr["AllowGhostTriggering"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_AllowGhostTriggering = boolEntry; }
|
|
|
|
valid_entry = true;
|
|
try { boolEntry = bool.Parse((string)dr["AllowNPCTriggering"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_AllowNPCTriggering = boolEntry; }
|
|
|
|
valid_entry = true;
|
|
try { boolEntry = bool.Parse((string)dr["SpawnOnTrigger"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_SpawnOnTrigger = boolEntry; }
|
|
|
|
valid_entry = true;
|
|
try { boolEntry = bool.Parse((string)dr["SmartSpawning"]); }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { m_SmartSpawning = boolEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["RegionName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
RegionName = strEntry;
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["PlayerPropertyName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
m_PlayerPropertyName = strEntry;
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["MobPropertyName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
m_MobPropertyName = strEntry;
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["MobTriggerName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
m_MobTriggerName = strEntry;
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["ObjectPropertyName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
m_ObjectPropertyName = strEntry;
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["ObjectPropertyItemName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
string[] typeargs = strEntry.Split(",".ToCharArray(), 2);
|
|
string typestr = null;
|
|
string namestr = strEntry;
|
|
|
|
if (typeargs.Length > 1)
|
|
{
|
|
namestr = typeargs[0];
|
|
typestr = typeargs[1];
|
|
}
|
|
m_ObjectPropertyItem = BaseXmlSpawner.FindItemByName(this, namestr, typestr);
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["SetPropertyItemName"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
string[] typeargs = strEntry.Split(",".ToCharArray(), 2);
|
|
string typestr = null;
|
|
string namestr = strEntry;
|
|
|
|
if (typeargs.Length > 1)
|
|
{
|
|
namestr = typeargs[0];
|
|
typestr = typeargs[1];
|
|
}
|
|
m_SetPropertyItem = BaseXmlSpawner.FindItemByName(this, namestr, typestr);
|
|
}
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["Name"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry) { Name = strEntry; }
|
|
|
|
valid_entry = true;
|
|
try { strEntry = (string)dr["Map"]; }
|
|
catch { valid_entry = false; }
|
|
if (valid_entry)
|
|
{
|
|
// Convert the xml map value to a real map object
|
|
try
|
|
{
|
|
Map = Map.Parse(strEntry);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
}
|
|
|
|
// try loading the new spawn specifications first
|
|
SpawnObject[] Spawns = new SpawnObject[0];
|
|
bool havenew = true;
|
|
valid_entry = true;
|
|
try { Spawns = SpawnObject.LoadSpawnObjectsFromString2((string)dr["Objects2"]); }
|
|
catch { havenew = false; }
|
|
if (!havenew)
|
|
{
|
|
// try loading the new spawn specifications
|
|
try { Spawns = SpawnObject.LoadSpawnObjectsFromString((string)dr["Objects"]); }
|
|
catch { valid_entry = false; }
|
|
// can only have one of these defined
|
|
}
|
|
if (valid_entry)
|
|
{
|
|
|
|
// clear existing spawns
|
|
RemoveSpawnObjects();
|
|
|
|
// Create the new array of spawned objects
|
|
m_SpawnObjects = new List<SpawnObject>();
|
|
|
|
// Assign the list of objects to spawn
|
|
SpawnObjects = Spawns;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ReportStatus()
|
|
{
|
|
if (PropertyInfoList != null)
|
|
{
|
|
Console.WriteLine("PropertyInfoList: {0}", PropertyInfoList.Count);
|
|
foreach (BaseXmlSpawner.TypeInfo to in PropertyInfoList)
|
|
{
|
|
Console.WriteLine("\t{0}", to.t);
|
|
foreach (PropertyInfo p in to.plist)
|
|
{
|
|
Console.WriteLine("\t\t{0}", p);
|
|
}
|
|
}
|
|
}
|
|
|
|
ShowTagList(this);
|
|
int count = 0;
|
|
Console.WriteLine("Registered SkillsTotal = {0}", count);
|
|
}
|
|
|
|
#if (TRACE)
|
|
|
|
readonly string setname1 = _traceName[1] = "XmlFind";
|
|
readonly string setname2 = _traceName[2] = "HasSector";
|
|
readonly string setname4 = _traceName[4] = "AttachSpeech";
|
|
readonly string setname5 = _traceName[5] = "HasHold";
|
|
readonly string setname8 = _traceName[8] = "OnTick";
|
|
readonly string setname9 = _traceName[9] = "Defrag";
|
|
readonly string setname10 = _traceName[10] = "Respawn";
|
|
readonly string setname11 = _traceName[11] = "SetProp";
|
|
readonly string setname12 = _traceName[12] = "AttachMovement";
|
|
readonly string setname13 = _traceName[13] = "ActiveSector";
|
|
readonly string setname15 = _traceName[15] = "DistroTick";
|
|
readonly string setname16 = _traceName[16] = "GetScaledFaction";
|
|
readonly string setname17 = _traceName[17] = "FactionOnKill";
|
|
readonly string setname18 = _traceName[18] = "CheckAcquire";
|
|
|
|
|
|
private const int MaxTraces = 20;
|
|
private static readonly DateTime[] _traceStart = new DateTime[MaxTraces];
|
|
public static TimeSpan[] _traceTotal = new TimeSpan[MaxTraces];
|
|
public static string[] _traceName = new string[MaxTraces];
|
|
public static int[] _traceCount = new int[MaxTraces];
|
|
private static DateTime _traceStartTime = Core.Now;
|
|
private static double _startProcessTime;
|
|
|
|
public static void _TraceStart(int index)
|
|
{
|
|
if (index < MaxTraces)
|
|
{
|
|
_traceStart[index] = Core.Now;
|
|
//_traceStart[index] = Process.GetCurrentProcess().UserProcessorTime;
|
|
}
|
|
}
|
|
public static void _TraceEnd(int index)
|
|
{
|
|
if (index < MaxTraces)
|
|
{
|
|
_traceTotal[index] = _traceTotal[index].Add(Core.Now - _traceStart[index]);
|
|
//XmlSpawner._traceTotal[index] = XmlSpawner._traceTotal[index].Add(Process.GetCurrentProcess().UserProcessorTime - _traceStart[index]);
|
|
_traceCount[index]++;
|
|
}
|
|
}
|
|
#else
|
|
public static void _TraceStart(int index) { }
|
|
public static void _TraceEnd(int index) { }
|
|
#endif
|
|
|
|
private bool ValidPlayerTrig(Mobile m)
|
|
{
|
|
if (m == null || m.Deleted)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (m.Player || m_AllowNPCTriggering) && m.AccessLevel <= TriggerAccessLevel && (!m.Body.IsGhost && !m_AllowGhostTriggering || m.Body.IsGhost && m_AllowGhostTriggering);
|
|
}
|
|
|
|
private bool AllowTriggering => m_Running && !m_refractActivated && TODInRange && CanSpawn;
|
|
|
|
private void ActivateTrigger()
|
|
{
|
|
DoTimer(); // reset the timer
|
|
|
|
// start the refractory timer to set proximity activated to false, thus enabling another activation
|
|
if (m_MaxRefractory > TimeSpan.FromMinutes(0))
|
|
{
|
|
int minSeconds = (int)m_MinRefractory.TotalSeconds;
|
|
int maxSeconds = (int)m_MaxRefractory.TotalSeconds;
|
|
|
|
DoTimer3(TimeSpan.FromSeconds(Utility.RandomMinMax(minSeconds, maxSeconds)));
|
|
}
|
|
|
|
// if the spawnontrigger flag is set, then spawn immediately
|
|
if (m_SpawnOnTrigger)
|
|
{
|
|
NextSpawn = TimeSpan.Zero;
|
|
ResetNextSpawnTimes();
|
|
}
|
|
|
|
// reset speech triggering if it was set
|
|
m_speechTriggerActivated = false;
|
|
}
|
|
|
|
public void CheckTriggers(Mobile m, Skill s, bool hasproximity)
|
|
{
|
|
if (AllowTriggering && !m_proximityActivated) // only proximity trigger when no spawns have already been triggered
|
|
{
|
|
bool needs_speech_trigger = false;
|
|
bool needs_player_trigger = false;
|
|
bool has_player_trigger = false;
|
|
|
|
m_skipped = false;
|
|
|
|
// test for the various triggering options in the order of increasing computational demand. No point checking a high demand test
|
|
// if a low demand one has already failed.
|
|
|
|
// check for external triggering
|
|
if (m_ExternalTriggering && !m_ExternalTrigger)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if speech triggering is set then test for successful activation
|
|
if (!string.IsNullOrEmpty(m_SpeechTrigger))
|
|
{
|
|
needs_speech_trigger = true;
|
|
}
|
|
// check to see if we have to continue
|
|
if (needs_speech_trigger && !m_speechTriggerActivated)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if player property triggering is set then look for the mob and test properties
|
|
if (!string.IsNullOrEmpty(m_PlayerPropertyName))
|
|
{
|
|
needs_player_trigger = true;
|
|
string status_str;
|
|
|
|
if (BaseXmlSpawner.TestMobProperty(this, m, m_PlayerPropertyName, out status_str))
|
|
{
|
|
has_player_trigger = true;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(status_str))
|
|
{
|
|
this.status_str = status_str;
|
|
}
|
|
}
|
|
|
|
// check to see if we have to continue
|
|
if (needs_player_trigger && !has_player_trigger)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if this was called without being proximity triggered then check to see that the non-movement triggers were enabled.
|
|
if (!hasproximity && !m_ExternalTriggering)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// all of the necessary trigger conditions have been met so go ahead and trigger
|
|
// after you make the probability check
|
|
|
|
if (Utility.RandomDouble() < m_TriggerProbability)
|
|
{
|
|
// play a sound indicating the spawner has been triggered
|
|
if (m_ProximityTriggerSound > 0 && m != null && !m.Deleted)
|
|
{
|
|
m.PlaySound(m_ProximityTriggerSound);
|
|
}
|
|
|
|
// display the trigger message
|
|
if (!string.IsNullOrEmpty(m_ProximityTriggerMessage) && m != null && !m.Deleted)
|
|
{
|
|
m.PublicOverheadMessage(MessageType.Regular, 0x3B2, false, m_ProximityTriggerMessage);
|
|
}
|
|
|
|
// enable spawning at the next ontick
|
|
// this will also start the refractory timer and send the triggering indicators
|
|
ProximityActivated = true;
|
|
|
|
// keep track of who triggered this
|
|
m_mob_who_triggered = m;
|
|
}
|
|
else
|
|
{
|
|
m_skipped = true;
|
|
|
|
// reset speech triggering if it was set
|
|
m_speechTriggerActivated = false;
|
|
}
|
|
}
|
|
}
|
|
public bool HandlesOnSkillUse => m_Running && m_SkillTrigger != null && m_SkillTrigger.Length > 0;
|
|
|
|
// this is the handler for skill use
|
|
public void OnSkillUse(Mobile m, Skill skill, bool success)
|
|
{
|
|
|
|
if (m_Running && m_ProximityRange >= 0 && ValidPlayerTrig(m) && CanSpawn && !m_refractActivated && TODInRange)
|
|
{
|
|
|
|
if (!Utility.InRange(m.Location, Location, m_ProximityRange))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// m_skillTriggerActivated = false;
|
|
|
|
// check the skill trigger conditions, Skillname[+/-][,min,max]
|
|
// if (m_SkillTrigger != null && skill.SkillName == m_SkillTriggerName &&
|
|
// (m_SkillTriggerMin < 0 || skill.Value >= m_SkillTriggerMin) &&
|
|
// (m_SkillTriggerMax < 0 || skill.Value <= m_SkillTriggerMax) &&
|
|
// (m_SkillTriggerSuccess == 3 || m_SkillTriggerSuccess == 1 && success || m_SkillTriggerSuccess == 2 && !success))
|
|
// {
|
|
// // have a skill trigger so flag it and test it
|
|
// m_skillTriggerActivated = true;
|
|
//
|
|
// CheckTriggers(m, skill, true);
|
|
// }
|
|
}
|
|
}
|
|
public override bool HandlesOnSpeech => m_Running && !string.IsNullOrEmpty(m_SpeechTrigger);
|
|
|
|
public override void OnSpeech(SpeechEventArgs e)
|
|
{
|
|
if (m_Running && m_ProximityRange >= 0 && ValidPlayerTrig(e.Mobile) && CanSpawn && !m_refractActivated && TODInRange)
|
|
{
|
|
m_speechTriggerActivated = false;
|
|
|
|
if (!Utility.InRange(e.Mobile.Location, Location, m_ProximityRange))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_SpeechTrigger != null && e.Speech.ToLower().IndexOf(m_SpeechTrigger.ToLower()) >= 0)
|
|
{
|
|
e.Handled = true;
|
|
|
|
// found the speech trigger so flag it for testing in the onmovement handler where the other proximity features are tested
|
|
m_speechTriggerActivated = true;
|
|
|
|
CheckTriggers(e.Mobile, null, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override bool HandlesOnMovement => m_Running && m_ProximityRange >= 0;
|
|
|
|
public void AddToMovementList(Mobile m)
|
|
{
|
|
// go through the list and check for redundancy
|
|
if (m_MovementList == null)
|
|
{
|
|
m_MovementList = new List<MovementInfo>();
|
|
}
|
|
|
|
// check to see if the movement timer is running
|
|
if (m_MovementTimer == null || !m_MovementTimer.Running)
|
|
{
|
|
DoMovementTimer(TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
bool add = true;
|
|
|
|
foreach (MovementInfo moveinfo in m_MovementList)
|
|
{
|
|
Mobile mtrig = moveinfo.trigMob;
|
|
if (mtrig == m)
|
|
{
|
|
add = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// wasnt on the list so add it
|
|
if (add)
|
|
{
|
|
|
|
// is the list at max throttling length?
|
|
if (m_MovementList.Count > MaxMoveCheck)
|
|
{
|
|
// replace a random entry in the current list with this one
|
|
m_MovementList[Utility.Random(m_MovementList.Count)] = new MovementInfo(m);
|
|
}
|
|
else
|
|
{
|
|
|
|
m_MovementList.Add(new MovementInfo(m));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DoMovementTimer(TimeSpan delay)
|
|
{
|
|
if (m_MovementTimer != null)
|
|
{
|
|
m_MovementTimer.Stop();
|
|
}
|
|
|
|
m_MovementTimer = new MovementTimer(this, delay);
|
|
|
|
m_MovementTimer.Start();
|
|
}
|
|
|
|
private class MovementTimer : Timer
|
|
{
|
|
private readonly XmlSpawner m_Spawner;
|
|
|
|
public MovementTimer(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_Spawner = spawner;
|
|
|
|
protected override void OnTick()
|
|
{
|
|
// check everyone on the movement list then clear the list
|
|
if (m_Spawner != null && !m_Spawner.Deleted)
|
|
{
|
|
if (m_Spawner.m_Running && !m_Spawner.m_proximityActivated && !m_Spawner.m_refractActivated && m_Spawner.TODInRange && m_Spawner.CanSpawn)
|
|
{
|
|
int count = 0;
|
|
int maxspeed = 0;
|
|
foreach (MovementInfo moveinfo in m_Spawner.m_MovementList)
|
|
{
|
|
Mobile m = moveinfo.trigMob;
|
|
if (m == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// additional throttling in here by limiting number of mobs that can be checked in a single ontick
|
|
count++;
|
|
if (count > MaxMoveCheck)
|
|
{
|
|
break;
|
|
}
|
|
|
|
int speed = (int)GetDistance(m.Location, moveinfo.trigLocation);
|
|
if (speed > maxspeed)
|
|
{
|
|
maxspeed = speed;
|
|
}
|
|
|
|
m_Spawner.CheckTriggers(m, null, true);
|
|
}
|
|
|
|
m_Spawner.MovingPlayerCount = m_Spawner.m_MovementList.Count;
|
|
m_Spawner.FastestPlayerSpeed = maxspeed;
|
|
|
|
}
|
|
m_Spawner.m_MovementList.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static double GetDistance(Point3D p1, Point3D p2)
|
|
{
|
|
int xDelta = p1.X - p2.X;
|
|
int yDelta = p1.Y - p2.Y;
|
|
|
|
return Math.Sqrt(xDelta * xDelta + yDelta * yDelta);
|
|
}
|
|
|
|
public override void OnMovement(Mobile m, Point3D oldLocation)
|
|
{
|
|
if (m_Running && m_ProximityRange >= 0 && ValidPlayerTrig(m) && CanSpawn)
|
|
{
|
|
// check to see if player is within range of the spawner
|
|
if (Parent == null && Utility.InRange(m.Location, Location, m_ProximityRange))
|
|
{
|
|
// add some throttling code here.
|
|
// add the player to a list that gets cleared every few seconds, checking for redundancy then trigger off of the list instead of off of
|
|
// the actual movement stream
|
|
|
|
AddToMovementList(m);
|
|
}
|
|
else
|
|
{
|
|
// clear any speech triggering
|
|
m_speechTriggerActivated = false;
|
|
}
|
|
}
|
|
base.OnMovement(m, oldLocation);
|
|
}
|
|
|
|
public static bool AssignSettings(string argname, string value)
|
|
{
|
|
switch (argname)
|
|
{
|
|
case "XmlSpawnDir":
|
|
{
|
|
XmlSpawnDir = value;
|
|
break;
|
|
}
|
|
case "DiskAccessLevel":
|
|
{
|
|
DiskAccessLevel = (AccessLevel)Enum.Parse(typeof(AccessLevel), value, true);
|
|
break;
|
|
}
|
|
case "SmartSpawnAccessLevel":
|
|
{
|
|
SmartSpawnAccessLevel = (AccessLevel)Enum.Parse(typeof(AccessLevel), value, true);
|
|
break;
|
|
}
|
|
case "defaultTriggerSound":
|
|
{
|
|
defaultTriggerSound = ConvertToInt(value);
|
|
defProximityTriggerSound = defaultTriggerSound;
|
|
break;
|
|
}
|
|
case "BaseItemId":
|
|
{
|
|
BaseItemId = ConvertToInt(value);
|
|
break;
|
|
}
|
|
case "ShowItemId":
|
|
{
|
|
ShowItemId = ConvertToInt(value);
|
|
break;
|
|
}
|
|
case "MaxMoveCheck":
|
|
{
|
|
MaxMoveCheck = ConvertToInt(value);
|
|
break;
|
|
}
|
|
case "defMinDelay":
|
|
{
|
|
defMinDelay = TimeSpan.FromMinutes(ConvertToInt(value));
|
|
break;
|
|
}
|
|
case "defMaxDelay":
|
|
{
|
|
defMaxDelay = TimeSpan.FromMinutes(ConvertToInt(value));
|
|
break;
|
|
}
|
|
case "defRelativeHome":
|
|
{
|
|
defRelativeHome = bool.Parse(value);
|
|
break;
|
|
}
|
|
case "defSpawnRange":
|
|
{
|
|
defSpawnRange = ConvertToInt(value);
|
|
break;
|
|
}
|
|
case "defHomeRange":
|
|
{
|
|
defHomeRange = ConvertToInt(value);
|
|
break;
|
|
}
|
|
case "BlockKeyword":
|
|
{
|
|
// parse the keyword list and remove them from the keyword hashtables
|
|
string[] keywordlist = value.Split(',');
|
|
|
|
if (keywordlist.Length > 0)
|
|
{
|
|
for (int i = 0; i < keywordlist.Length; i++)
|
|
{
|
|
BaseXmlSpawner.RemoveKeyword(keywordlist[i]);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "BlockCommand":
|
|
case "ChangeCommand":
|
|
{
|
|
// delay processing of these settings until after all commands have been registered in their Initialize methods
|
|
Timer.DelayCall(TimeSpan.Zero, DelayedAssignSettings, argname, value);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void DelayedAssignSettings(string argname, string value)
|
|
{
|
|
switch (argname)
|
|
{
|
|
case "BlockCommand":
|
|
{
|
|
// delay processing of this until after all commands have been registered in their Initialize methods
|
|
// parse the command list and remove them from the command hashtables
|
|
// the syntax is "commandname, commandname, etc."
|
|
string[] keywordlist = value.Split(',');
|
|
|
|
if (keywordlist.Length > 0)
|
|
{
|
|
for (int i = 0; i < keywordlist.Length; i++)
|
|
{
|
|
string commandname = keywordlist[i].Trim().ToLower();
|
|
try
|
|
{
|
|
CommandSystem.Entries.Remove(commandname);
|
|
}
|
|
catch
|
|
{
|
|
Console.WriteLine("{0}: invalid command {1}", argname, commandname);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "ChangeCommand":
|
|
{
|
|
// delay processing of this until after all commands have been registered in their Initialize methods
|
|
// parse the command list and rehash them into the command hashtables
|
|
// the syntax is "oldname:newname[:accesslevel], oldname:newname[:accesslevel], etc."
|
|
string[] keywordlist = value.Split(',');
|
|
|
|
if (keywordlist.Length > 0)
|
|
{
|
|
for (int i = 0; i < keywordlist.Length; i++)
|
|
{
|
|
string[] namelist = keywordlist[i].Split(':');
|
|
if (namelist.Length > 1)
|
|
{
|
|
string oldname = namelist[0].Trim().ToLower();
|
|
string newname = namelist[1].Trim();
|
|
|
|
if (newname.Length == 0)
|
|
{
|
|
newname = oldname;
|
|
}
|
|
|
|
AccessLevel access = AccessLevel.Player;
|
|
bool validaccess = false;
|
|
if (namelist.Length > 2)
|
|
{
|
|
// get the new accesslevel
|
|
try
|
|
{
|
|
access = (AccessLevel)Enum.Parse(typeof(AccessLevel), namelist[2].Trim(), true);
|
|
validaccess = true;
|
|
}
|
|
catch
|
|
{
|
|
Console.WriteLine("{0}: invalid accesslevel {1} for {2}", argname, namelist[2], newname);
|
|
}
|
|
}
|
|
// find the command entry for the old name
|
|
CommandEntry e = null;
|
|
try
|
|
{
|
|
e = CommandSystem.Entries[oldname];
|
|
}
|
|
catch
|
|
{
|
|
Console.WriteLine("{0}: invalid command {1}", argname, oldname);
|
|
}
|
|
if (e != null)
|
|
{
|
|
if (!validaccess)
|
|
{
|
|
// use the old accesslevel
|
|
access = e.AccessLevel;
|
|
}
|
|
// remove the old command entry
|
|
CommandSystem.Entries.Remove(oldname);
|
|
// register the new command using the old handler
|
|
CommandSystem.Register(newname, access, e.Handler);
|
|
}
|
|
|
|
// also look in the targetcommands list and adjust name and accesslevel there
|
|
foreach (BaseCommand b in TargetCommands.AllCommands)
|
|
{
|
|
if (b.Commands != null)
|
|
{
|
|
for (int j = 0; j < b.Commands.Length; j++)
|
|
{
|
|
string commandname = b.Commands[j];
|
|
if (commandname.ToLower() == oldname)
|
|
{
|
|
// modify the basecommand with the new name and access
|
|
b.Commands[j] = newname;
|
|
if (validaccess)
|
|
{
|
|
b.AccessLevel = access;
|
|
}
|
|
|
|
// re-register it in the implementors hashtable
|
|
List<BaseCommandImplementor> impls = BaseCommandImplementor.Implementors;
|
|
|
|
for (int k = 0; k < impls.Count; ++k)
|
|
{
|
|
BaseCommandImplementor impl = impls[k];
|
|
|
|
if ((b.Supports & impl.SupportRequirement) != 0)
|
|
{
|
|
try
|
|
{
|
|
impl.Commands.Remove(commandname);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(ex);
|
|
}
|
|
impl.Register(b);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public delegate bool AssignSettingsHandler(string argname, string value);
|
|
|
|
// load in settings from the xmlspawner2.cfg file in the Data directory
|
|
public static void LoadSettings(AssignSettingsHandler settingshandler, string section)
|
|
{
|
|
// Check if the file exists
|
|
string path = Path.Combine(Core.BaseDirectory, "Data/xmlspawner.cfg");
|
|
|
|
if (!File.Exists(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Console.WriteLine("Loading {0} configuration", section);
|
|
using (StreamReader ip = new StreamReader(path))
|
|
{
|
|
string line;
|
|
string currentsection = null;
|
|
int nsettings = 0;
|
|
|
|
while ((line = ip.ReadLine()) != null)
|
|
{
|
|
line = line.Trim();
|
|
|
|
// skip comments
|
|
if (line.Length == 0 || line.StartsWith("#"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (line.StartsWith("["))
|
|
{
|
|
// parse the section name
|
|
string[] args = line.Split("[]".ToCharArray(), 3);
|
|
if (args.Length > 2)
|
|
{
|
|
currentsection = args[1].Trim();
|
|
}
|
|
}
|
|
|
|
// only process the matching classname section
|
|
if (currentsection != section)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string[] split = line.Split('=');
|
|
|
|
if (split.Length >= 2)
|
|
{
|
|
string argname = split[0].Trim();
|
|
string value = split[1].Trim();
|
|
|
|
if (argname.Length == 0 || value.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (settingshandler(argname, value))
|
|
{
|
|
nsettings++;
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("'{0}' setting is invalid in section [{1}]", argname, currentsection);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine("Config error '{0}'='{1}'", argname, value);
|
|
Console.WriteLine("Error: {0}", e.Message);
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nsettings > 0)
|
|
{
|
|
Console.WriteLine("{0} settings processed", nsettings);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void Initialize()
|
|
{
|
|
LoadSettings(AssignSettings, "XmlSpawner");
|
|
|
|
// initialize the default waypoint name
|
|
WayPoint tmpwaypoint = new WayPoint();
|
|
defwaypointname = tmpwaypoint.Name;
|
|
tmpwaypoint.Delete();
|
|
|
|
int count = 0;
|
|
int regional = 0;
|
|
|
|
foreach (Item item in World.Items.Values)
|
|
{
|
|
if (item is XmlSpawner spawner)
|
|
{
|
|
count++;
|
|
|
|
if (!string.IsNullOrEmpty(spawner.RegionName))
|
|
{
|
|
spawner.RegionName = spawner.RegionName; // invoke set(RegionName)
|
|
regional++;
|
|
}
|
|
|
|
// check for smart spawning and restart timers after deser if needed
|
|
// note, HasActiveSectors will recalculate the sector list and UseSectorActivate property
|
|
bool recalc_sectors = spawner.HasActiveSectors;
|
|
|
|
spawner.RestoreISpawner();
|
|
}
|
|
}
|
|
|
|
// start the global smartspawning timer
|
|
if (SmartSpawningSystemEnabled)
|
|
{
|
|
DoGlobalSectorTimer(TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
// standard commands
|
|
CommandSystem.Register("XmlSpawnerShowAll", AccessLevel.Administrator, ShowSpawnPoints_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerHideAll", AccessLevel.Administrator, HideSpawnPoints_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerWipe", AccessLevel.Administrator, Wipe_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerWipeAll", AccessLevel.Administrator, WipeAll_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerLoad", DiskAccessLevel, Load_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerSave", DiskAccessLevel, Save_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerSaveAll", DiskAccessLevel, SaveAll_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerRespawn", AccessLevel.Seer, Respawn_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerRespawnAll", AccessLevel.Seer, RespawnAll_OnCommand);
|
|
|
|
CommandSystem.Register("XmlShow", AccessLevel.Administrator, ShowSpawnPoints_OnCommand);
|
|
CommandSystem.Register("XmlHide", AccessLevel.Administrator, HideSpawnPoints_OnCommand);
|
|
CommandSystem.Register("XmlHome", AccessLevel.GameMaster, XmlHome_OnCommand);
|
|
CommandSystem.Register("XmlUnLoad", DiskAccessLevel, UnLoad_OnCommand);
|
|
CommandSystem.Register("XmlSpawnerUnLoad", DiskAccessLevel, UnLoad_OnCommand);
|
|
CommandSystem.Register("XmlLoad", DiskAccessLevel, Load_OnCommand);
|
|
CommandSystem.Register("XmlLoadHere", DiskAccessLevel, LoadHere_OnCommand);
|
|
CommandSystem.Register("XmlNewLoad", DiskAccessLevel, NewLoad_OnCommand);
|
|
CommandSystem.Register("XmlNewLoadHere", DiskAccessLevel, NewLoadHere_OnCommand);
|
|
CommandSystem.Register("XmlSave", DiskAccessLevel, Save_OnCommand);
|
|
CommandSystem.Register("XmlSaveAll", DiskAccessLevel, SaveAll_OnCommand);
|
|
CommandSystem.Register("XmlSaveOld", DiskAccessLevel, SaveOld_OnCommand);
|
|
CommandSystem.Register("XmlImportSpawners", DiskAccessLevel, XmlImportSpawners_OnCommand);
|
|
CommandSystem.Register("XmlImportMSF", DiskAccessLevel, XmlImportMSF_OnCommand);
|
|
CommandSystem.Register("XmlImportMap", DiskAccessLevel, XmlImportMap_OnCommand);
|
|
CommandSystem.Register("XmlDefaults", AccessLevel.Administrator, XmlDefaults_OnCommand);
|
|
CommandSystem.Register("XmlGet", AccessLevel.GameMaster, XmlGetValue_OnCommand);
|
|
CommandSystem.Register("OptimalSmartSpawning", AccessLevel.Administrator, OptimalSmartSpawning_OnCommand);
|
|
CommandSystem.Register("SmartStat", AccessLevel.GameMaster, SmartStat_OnCommand);
|
|
CommandSystem.Register("XmlGo", AccessLevel.GameMaster, SpawnEditorGo_OnCommand);
|
|
|
|
TargetCommands.Register(new XmlSetCommand());
|
|
TargetCommands.Register(new XmlSaveSingle());
|
|
|
|
#if (TRACE)
|
|
CommandSystem.Register("XmlMake", AccessLevel.Administrator, XmlMake_OnCommand);
|
|
CommandSystem.Register("XmlTrace", AccessLevel.Administrator, XmlTrace_OnCommand);
|
|
CommandSystem.Register("XmlResetTrace", AccessLevel.Administrator, XmlResetTrace_OnCommand);
|
|
#endif
|
|
|
|
}
|
|
|
|
[Usage("XmlGet property")]
|
|
[Description("Returns value of the property on the targeted object.")]
|
|
public static void XmlGetValue_OnCommand(CommandEventArgs e)
|
|
{
|
|
e.Mobile.Target = new GetValueTarget(e);
|
|
}
|
|
private class GetValueTarget : Target
|
|
{
|
|
private readonly CommandEventArgs m_e;
|
|
public GetValueTarget(CommandEventArgs e)
|
|
: base(30, false, TargetFlags.None) =>
|
|
m_e = e;
|
|
|
|
protected override void OnTarget(Mobile from, object targeted)
|
|
{
|
|
string pname = m_e.GetString(0);
|
|
Type ptype;
|
|
string result = BaseXmlSpawner.GetPropertyValue(null, targeted, pname, out ptype);
|
|
|
|
// see if it was successful
|
|
if (ptype == null)
|
|
{
|
|
return;
|
|
}
|
|
from.SendMessage($"{result}");
|
|
|
|
}
|
|
}
|
|
|
|
public class XmlSetCommand : BaseCommand
|
|
{
|
|
public XmlSetCommand()
|
|
{
|
|
AccessLevel = AccessLevel.Administrator;
|
|
Supports = CommandSupport.All;
|
|
Commands = new[] { "XmlSet" };
|
|
ObjectTypes = ObjectTypes.Both;
|
|
Usage = "XmlSet <propertyName> <value>";
|
|
Description = "Sets a property value by name of a targeted object. Provides access to all public properties.";
|
|
}
|
|
|
|
public override void Execute(CommandEventArgs e, object obj)
|
|
{
|
|
if (e.Length >= 2)
|
|
{
|
|
string result = BaseXmlSpawner.SetPropertyValue(null, obj, e.GetString(0), e.GetString(1));
|
|
|
|
if (result == "Property has been set.")
|
|
{
|
|
AddResponse(result);
|
|
}
|
|
else
|
|
{
|
|
LogFailure(result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogFailure("Format: XmlSet <propertyName> <value>");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Usage("TagList property")]
|
|
[Description("Lists the keyword taglist for a spawner")]
|
|
public static void ShowTagList_OnCommand(CommandEventArgs e)
|
|
{
|
|
e.Mobile.Target = new TagListTarget(e);
|
|
}
|
|
|
|
private class TagListTarget : Target
|
|
{
|
|
private readonly CommandEventArgs m_e;
|
|
|
|
public TagListTarget(CommandEventArgs e)
|
|
: base(30, false, TargetFlags.None) =>
|
|
m_e = e;
|
|
|
|
protected override void OnTarget(Mobile from, object targeted)
|
|
{
|
|
if (targeted is XmlSpawner spawner)
|
|
{
|
|
spawner.ShowTagList(spawner);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ShowTagList(XmlSpawner spawner)
|
|
{
|
|
int count = 0;
|
|
Console.WriteLine("{0} tags", spawner.m_KeywordTagList.Count);
|
|
foreach (BaseXmlSpawner.KeywordTag tag in spawner.m_KeywordTagList)
|
|
{
|
|
count++;
|
|
Console.WriteLine("tag {0} : {1}", count, BaseXmlSpawner.TagInfo(tag));
|
|
}
|
|
}
|
|
|
|
|
|
// added in targeting for the [xmlhome command
|
|
private class XmlHomeTarget : Target
|
|
{
|
|
private readonly CommandEventArgs m_e;
|
|
public XmlHomeTarget(CommandEventArgs e)
|
|
: base(30, false, TargetFlags.None) =>
|
|
m_e = e;
|
|
|
|
protected override void OnTarget(Mobile from, object targeted)
|
|
{
|
|
XmlSpawner spawner = null;
|
|
|
|
if (targeted is XmlSpawner xmlSpawner)
|
|
{
|
|
if (m_e.GetString(0) == "status")
|
|
{
|
|
xmlSpawner.ReportStatus();
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if (targeted is Mobile mobile)
|
|
{
|
|
spawner = mobile.Spawner as XmlSpawner;
|
|
}
|
|
else
|
|
if (targeted is Item item)
|
|
{
|
|
spawner = item.Spawner as XmlSpawner;
|
|
}
|
|
|
|
if (spawner == null)
|
|
{
|
|
from.SendMessage("Unable to find spawner for this object");
|
|
return;
|
|
}
|
|
|
|
// check to make sure it is still on the spawner
|
|
foreach (SpawnObject so in spawner.m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
|
|
if (o == targeted)
|
|
{
|
|
from.SendMessage($"{spawner.Location}");
|
|
|
|
if (m_e.GetString(0) == "go")
|
|
{
|
|
// make sure the spawner is not in a container.
|
|
if (spawner.Parent == null)
|
|
{
|
|
from.Location = new Point3D(spawner.Location);
|
|
from.Map = spawner.Map;
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage("Spawner is in a container");
|
|
}
|
|
}
|
|
else
|
|
if (m_e.GetString(0) == "send")
|
|
{
|
|
// make sure the spawner is not in a container.
|
|
if (spawner.Parent == null)
|
|
{
|
|
if (o is Item item)
|
|
{
|
|
item.Location = new Point3D(spawner.Location);
|
|
item.Map = spawner.Map;
|
|
}
|
|
if (o is Mobile mobile1)
|
|
{
|
|
mobile1.Location = new Point3D(spawner.Location);
|
|
mobile1.Map = spawner.Map;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage("Spawner is in a container");
|
|
}
|
|
}
|
|
else if (m_e.GetString(0) == "gump")
|
|
{
|
|
spawner.OnDoubleClick(from);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Usage("XmlHome [go][gump][send]")]
|
|
[Description("Returns the coordinates of the spawner for the targeted object. Args: 'go' teleports to spawner, 'gump' opens spawner gump, 'send' sends mob home")]
|
|
public static void XmlHome_OnCommand(CommandEventArgs e)
|
|
{
|
|
e.Mobile.Target = new XmlHomeTarget(e);
|
|
}
|
|
|
|
private static void XmlSaveDefaults(string filePath, Mobile m)
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(filePath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
using (StreamWriter op = new StreamWriter(filePath))
|
|
{
|
|
XmlTextWriter xml = new XmlTextWriter(op)
|
|
{
|
|
Formatting = Formatting.Indented,
|
|
IndentChar = '\t',
|
|
Indentation = 1
|
|
};
|
|
|
|
xml.WriteStartDocument(true);
|
|
|
|
xml.WriteStartElement("XmlDefaults");
|
|
|
|
xml.WriteStartElement("defProximityRange");
|
|
xml.WriteString(defProximityRange.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defTriggerProbability");
|
|
xml.WriteString(defTriggerProbability.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defProximityTriggerSound");
|
|
xml.WriteString(defProximityTriggerSound.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defMinRefractory");
|
|
xml.WriteString(defMinRefractory.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defMaxRefractory");
|
|
xml.WriteString(defMaxRefractory.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defTODStart");
|
|
xml.WriteString(defTODStart.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defTODEnd");
|
|
xml.WriteString(defTODEnd.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defStackAmount");
|
|
xml.WriteString(defAmount.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defDuration");
|
|
xml.WriteString(defDuration.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defIsGroup");
|
|
xml.WriteString(defIsGroup.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defTeam");
|
|
xml.WriteString(defTeam.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defRelativeHome");
|
|
xml.WriteString(defRelativeHome.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defSpawnRange");
|
|
xml.WriteString(defSpawnRange.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defHomeRange");
|
|
xml.WriteString(defHomeRange.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defMinDelay");
|
|
xml.WriteString(defMinDelay.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defMaxDelay");
|
|
xml.WriteString(defMaxDelay.ToString());
|
|
xml.WriteEndElement();
|
|
xml.WriteStartElement("defTODMode");
|
|
xml.WriteString(defTODMode.ToString());
|
|
xml.WriteEndElement();
|
|
|
|
xml.WriteEndElement();
|
|
|
|
xml.Close();
|
|
}
|
|
m.SendMessage($"defaults saved to {filePath}");
|
|
}
|
|
|
|
public static void XmlLoadDefaults(string filePath, Mobile m)
|
|
{
|
|
if (m == null || m.Deleted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(filePath))
|
|
{
|
|
|
|
if (File.Exists(filePath))
|
|
{
|
|
XmlDocument doc = new XmlDocument();
|
|
doc.Load(filePath);
|
|
|
|
XmlElement root = doc["XmlDefaults"];
|
|
LoadDefaults(root);
|
|
m.SendMessage($"defaults loaded successfully from {filePath}");
|
|
}
|
|
else
|
|
{
|
|
m.SendMessage($"File {filePath} does not exist.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void LoadDefaults(XmlElement node)
|
|
{
|
|
|
|
try { defProximityRange = int.Parse(node["defProximityRange"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defTriggerProbability = double.Parse(node["defTriggerProbability"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defProximityTriggerSound = int.Parse(node["defProximityTriggerSound"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defMinRefractory = TimeSpan.Parse(node["defMinRefractory"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defMaxRefractory = TimeSpan.Parse(node["defMaxRefractory"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defTODStart = TimeSpan.Parse(node["defTODStart"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defTODEnd = TimeSpan.Parse(node["defTODEnd"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defAmount = int.Parse(node["defStackAmount"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defDuration = TimeSpan.Parse(node["defDuration"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defIsGroup = bool.Parse(node["defIsGroup"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defTeam = int.Parse(node["defTeam"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defRelativeHome = bool.Parse(node["defRelativeHome"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defSpawnRange = int.Parse(node["defSpawnRange"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defHomeRange = int.Parse(node["defHomeRange"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defMinDelay = TimeSpan.Parse(node["defMinDelay"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
try { defMaxDelay = TimeSpan.Parse(node["defMaxDelay"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
int todmode = 0;
|
|
try { todmode = int.Parse(node["defTODMode"].InnerText); }
|
|
catch (Exception e)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(e);
|
|
}
|
|
switch (todmode)
|
|
{
|
|
case (int)TODModeType.Realtime:
|
|
{
|
|
defTODMode = TODModeType.Realtime;
|
|
break;
|
|
}
|
|
case (int)TODModeType.Gametime:
|
|
{
|
|
defTODMode = TODModeType.Gametime;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Usage("XmlDefaults [defaultpropertyname value]")]
|
|
[Description("Returns or changes the default settings of the spawner.")]
|
|
public static void XmlDefaults_OnCommand(CommandEventArgs e)
|
|
{
|
|
Mobile m = e.Mobile;
|
|
if (m == null || m.Deleted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
// leave open the possibility of just requesting display of a single property
|
|
if (e.Arguments.Length == 2)
|
|
{
|
|
if (e.Arguments[0].ToLower() == "save")
|
|
{
|
|
XmlSaveDefaults(e.Arguments[1], m);
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "load")
|
|
{
|
|
XmlLoadDefaults(e.Arguments[1], m);
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "maxdelay")
|
|
{
|
|
try
|
|
{
|
|
defMaxDelay = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
|
|
m.SendMessage($"MaxDelay = {defMaxDelay}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "mindelay")
|
|
{
|
|
try
|
|
{
|
|
defMinDelay = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
|
|
m.SendMessage($"MinDelay = {defMinDelay}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "spawnrange")
|
|
{
|
|
try
|
|
{
|
|
defSpawnRange = Convert.ToInt32(e.Arguments[1]);
|
|
m.SendMessage($"SpawnRange = {defSpawnRange}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "homerange")
|
|
{
|
|
try
|
|
{
|
|
defHomeRange = Convert.ToInt32(e.Arguments[1]);
|
|
m.SendMessage($"HomeRange = {defHomeRange}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "relativehome")
|
|
{
|
|
try
|
|
{
|
|
defRelativeHome = Convert.ToBoolean(e.Arguments[1]);
|
|
m.SendMessage($"RelativeHome = {defRelativeHome}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "proximitytriggersound")
|
|
{
|
|
try
|
|
{
|
|
defProximityTriggerSound = Convert.ToInt32(e.Arguments[1]);
|
|
m.SendMessage($"ProximityTriggerSound = {defProximityTriggerSound}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "proximityrange")
|
|
{
|
|
try
|
|
{
|
|
defProximityRange = Convert.ToInt32(e.Arguments[1]);
|
|
m.SendMessage($"ProximityRange = {defProximityRange}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "triggerprobability")
|
|
{
|
|
try
|
|
{
|
|
defTriggerProbability = Convert.ToDouble(e.Arguments[1]);
|
|
m.SendMessage($"TriggerProbability = {defTriggerProbability}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "todstart")
|
|
{
|
|
try
|
|
{
|
|
defTODStart = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
|
|
m.SendMessage($"TODStart = {defTODStart}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "todend")
|
|
{
|
|
try
|
|
{
|
|
defTODEnd = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
|
|
m.SendMessage($"TODEnd = {defTODEnd}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "stackamount")
|
|
{
|
|
try
|
|
{
|
|
defAmount = Convert.ToInt32(e.Arguments[1]);
|
|
m.SendMessage($"StackAmount = {defAmount}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "duration")
|
|
{
|
|
try
|
|
{
|
|
defDuration = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
|
|
m.SendMessage($"Duration = {defDuration}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "group")
|
|
{
|
|
try
|
|
{
|
|
defIsGroup = Convert.ToBoolean(e.Arguments[1]);
|
|
m.SendMessage($"Group = {defIsGroup}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "team")
|
|
{
|
|
try
|
|
{
|
|
defTeam = Convert.ToInt32(e.Arguments[1]);
|
|
m.SendMessage($"Team = {defTeam}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "todmode")
|
|
{
|
|
try
|
|
{
|
|
int todmode = Convert.ToInt32(e.Arguments[1]);
|
|
switch (todmode)
|
|
{
|
|
case (int)TODModeType.Gametime:
|
|
{
|
|
defTODMode = TODModeType.Gametime;
|
|
break;
|
|
}
|
|
case (int)TODModeType.Realtime:
|
|
{
|
|
defTODMode = TODModeType.Realtime;
|
|
break;
|
|
}
|
|
}
|
|
m.SendMessage($"TODMode = {defTODMode}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "maxrefractory")
|
|
{
|
|
try
|
|
{
|
|
defMaxRefractory = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
|
|
m.SendMessage($"MaxRefractory = {defMaxRefractory}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else if (e.Arguments[0].ToLower() == "minrefractory")
|
|
{
|
|
try
|
|
{
|
|
defMinRefractory = TimeSpan.FromMinutes(Convert.ToDouble(e.Arguments[1]));
|
|
m.SendMessage($"MinRefractory = {defMinRefractory}");
|
|
}
|
|
catch { m.SendMessage($"invalid value : {e.Arguments[1]}"); }
|
|
}
|
|
else
|
|
{
|
|
m.SendMessage($"{e.Arguments[0]} : no such default value.");
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// just display the values
|
|
m.SendMessage($"TriggerProbability = {defTriggerProbability}");
|
|
m.SendMessage($"ProximityRange = {defProximityRange}");
|
|
m.SendMessage($"ProximityTriggerSound = {defProximityTriggerSound}");
|
|
m.SendMessage($"MinRefractory = {defMinRefractory}");
|
|
m.SendMessage($"MaxRefractory = {defMaxRefractory}");
|
|
m.SendMessage($"TODStart = {defTODStart}");
|
|
m.SendMessage($"TODEnd = {defTODEnd}");
|
|
m.SendMessage($"TODMode = {defTODMode}");
|
|
m.SendMessage($"StackAmount = {defAmount}");
|
|
m.SendMessage($"Duration = {defDuration}");
|
|
m.SendMessage($"Group = {defIsGroup}");
|
|
m.SendMessage($"Team = {defTeam}");
|
|
m.SendMessage($"RelativeHome = {defRelativeHome}");
|
|
m.SendMessage($"SpawnRange = {defSpawnRange}");
|
|
m.SendMessage($"HomeRange = {defHomeRange}");
|
|
m.SendMessage($"MinDelay = {defMinDelay}");
|
|
m.SendMessage($"MaxDelay = {defMaxDelay}");
|
|
}
|
|
}
|
|
|
|
[Usage("XmlSpawnerShowAll")]
|
|
[Aliases("XmlShow")]
|
|
[Description("Makes all XmlSpawner objects movable and also changes the item id to a blue ships mast for easy identification.")]
|
|
public static void ShowSpawnPoints_OnCommand(CommandEventArgs e)
|
|
{
|
|
List<Item> ToShow = new List<Item>();
|
|
foreach (Item item in World.Items.Values)
|
|
{
|
|
if (item is XmlSpawner)
|
|
{
|
|
//turned off visibility. Admins will still see masts but players will not.
|
|
item.Visible = false; // set the spawn item visibility
|
|
item.Movable = false; // Make the spawn item movable
|
|
item.Hue = 88; // Bright blue colour so its easy to spot
|
|
item.ItemID = ShowItemId; // Ship Mast (Very tall, easy to see if beneath other objects)
|
|
|
|
// find container-held spawners to be marked with an external static
|
|
if (item.Parent != null && item.RootParent is Container)
|
|
{
|
|
ToShow.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// place the statics
|
|
foreach (XmlSpawner xml_item in ToShow)
|
|
{
|
|
// does the spawner already have a static attached to it? could happen if two showall commands are issued in a row.
|
|
// if so then dont add another
|
|
if ((xml_item.m_ShowContainerStatic == null || xml_item.m_ShowContainerStatic.Deleted) && xml_item.RootParent is Container rootItem)
|
|
{
|
|
// calculate a world location for the static. Position it just above the container
|
|
int x = rootItem.Location.X;
|
|
int y = rootItem.Location.Y;
|
|
int z = rootItem.Location.Z + 10;
|
|
|
|
Static s = new Static(ShowItemId)
|
|
{
|
|
Visible = false
|
|
};
|
|
s.MoveToWorld(new Point3D(x, y, z), rootItem.Map);
|
|
|
|
xml_item.m_ShowContainerStatic = s;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
[Usage("XmlSpawnerHideAll")]
|
|
[Aliases("XmlHide")]
|
|
[Description("Makes all XmlSpawner objects invisible and unmovable returns the object id to the default.")]
|
|
public static void HideSpawnPoints_OnCommand(CommandEventArgs e)
|
|
{
|
|
List<Item> ToDelete = new List<Item>();
|
|
foreach (Item item in World.Items.Values)
|
|
{
|
|
if (item is XmlSpawner xmlItem)
|
|
{
|
|
xmlItem.Visible = false;
|
|
xmlItem.Movable = false;
|
|
xmlItem.Hue = 0;
|
|
xmlItem.ItemID = BaseItemId;
|
|
|
|
// get rid of the external static marker for container-held spawners
|
|
// check anything that might have been tagged with a container static
|
|
if (xmlItem.m_ShowContainerStatic != null && !xmlItem.m_ShowContainerStatic.Deleted)
|
|
{
|
|
ToDelete.Add(xmlItem);
|
|
}
|
|
}
|
|
}
|
|
foreach (XmlSpawner xml_item in ToDelete)
|
|
{
|
|
if (xml_item.m_ShowContainerStatic != null && !xml_item.m_ShowContainerStatic.Deleted)
|
|
{
|
|
xml_item.m_ShowContainerStatic.Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Usage("XmlGo <map> | <map> <x> <y> [z]")]
|
|
[Description("Go command used with spawn editor, takes the name of the map as the first parameter.")]
|
|
private static void SpawnEditorGo_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Mobile from = e.Mobile;
|
|
|
|
// Make sure a map name was given at least
|
|
if (from != null && e.Length >= 1)
|
|
{
|
|
string MapName = e.Arguments[0];
|
|
|
|
// Get the map
|
|
Map NewMap;
|
|
// Convert the xml map value to a real map object
|
|
if (string.Compare(MapName, Map.Trammel.Name, true) == 0)
|
|
{
|
|
NewMap = Map.Trammel;
|
|
}
|
|
else if (string.Compare(MapName, Map.Felucca.Name, true) == 0)
|
|
{
|
|
NewMap = Map.Felucca;
|
|
}
|
|
else if (string.Compare(MapName, Map.Ilshenar.Name, true) == 0)
|
|
{
|
|
NewMap = Map.Ilshenar;
|
|
}
|
|
else if (string.Compare(MapName, Map.Malas.Name, true) == 0)
|
|
{
|
|
NewMap = Map.Malas;
|
|
}
|
|
else if (string.Compare(MapName, Map.Tokuno.Name, true) == 0)
|
|
{
|
|
NewMap = Map.Tokuno;
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage($"Map '{MapName}' does not exist!");
|
|
return;
|
|
}
|
|
|
|
// Now that the map has been determined, continue
|
|
// Check if the request is to simply change maps
|
|
if (e.Length == 1)
|
|
{
|
|
// Map Change ONLY
|
|
from.Map = NewMap;
|
|
}
|
|
else if (e.Length == 3)
|
|
{
|
|
// Map & X Y ONLY
|
|
if (NewMap != null)
|
|
{
|
|
int x = e.GetInt32(1);
|
|
int y = e.GetInt32(2);
|
|
int z = NewMap.GetAverageZ(x, y);
|
|
from.Map = NewMap;
|
|
from.Location = new Point3D(x, y, z);
|
|
}
|
|
}
|
|
else if (e.Length == 4)
|
|
{
|
|
// Map & X Y Z
|
|
from.Map = NewMap;
|
|
from.Location = new Point3D(e.GetInt32(1), e.GetInt32(2), e.GetInt32(3));
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage("Format: XmlGo <map> | <map> <x> <y> [z]");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Usage("SmartStat [accesslevel Player/Counselor/GameMaster/Seer/Administrator]")]
|
|
[Description("Returns the spawn reduction due to SmartSpawning.")]
|
|
public static void SmartStat_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e == null || e.Mobile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.Arguments.Length > 1 && e.Arguments[0].ToLower() == "accesslevel" && e.Mobile.AccessLevel >= AccessLevel.Administrator)
|
|
{
|
|
try
|
|
{
|
|
SmartSpawnAccessLevel = (AccessLevel)Enum.Parse(typeof(AccessLevel), e.Arguments[1], true);
|
|
}
|
|
catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); }
|
|
}
|
|
// handle the
|
|
// number of spawners
|
|
int count = 0;
|
|
// number of actual spawns
|
|
int currentcount = 0;
|
|
int smartcount = 0;
|
|
int inactivecount = 0;
|
|
// maximum possible spawns
|
|
int totalcount = 0;
|
|
int maxcount = 0;
|
|
// maximum possible of spawns that are currently inactivated
|
|
int savings = 0;
|
|
foreach (Item item in World.Items.Values)
|
|
{
|
|
if (item is XmlSpawner spawner)
|
|
{
|
|
if (spawner.Deleted)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
totalcount += spawner.MaxCount;
|
|
// get the current count without defragging
|
|
currentcount += spawner.SafeCurrentCount;
|
|
count++;
|
|
|
|
// check to see if smartspawning is set
|
|
if (spawner.SmartSpawning)
|
|
{
|
|
smartcount++;
|
|
maxcount += spawner.MaxCount;
|
|
}
|
|
|
|
if (spawner.IsInactivated)
|
|
{
|
|
inactivecount++;
|
|
savings += spawner.MaxCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
int percent = 0;
|
|
|
|
int maxpercent = 0;
|
|
if (totalcount > 0)
|
|
{
|
|
percent = 100 * savings / totalcount;
|
|
maxpercent = 100 * maxcount / totalcount;
|
|
}
|
|
|
|
e.Mobile.SendMessage($"Smartspawning access level is {SmartSpawnAccessLevel}");
|
|
e.Mobile.SendMessage($"--------------------------------");
|
|
e.Mobile.SendMessage($"{count} XmlSpawners");
|
|
e.Mobile.SendMessage($"{smartcount} are configured for SmartSpawning\n");
|
|
e.Mobile.SendMessage($"{inactivecount} are currently inactivated");
|
|
e.Mobile.SendMessage($"{totalSectorsMonitored} sectors being monitored\n");
|
|
e.Mobile.SendMessage($"Maximum possible spawn count is {totalcount}");
|
|
e.Mobile.SendMessage($"Maximum possible spawn reduction is {maxcount}\n");
|
|
e.Mobile.SendMessage($"Current spawn count is {currentcount}");
|
|
e.Mobile.SendMessage($"Current spawn reduction is {savings}");
|
|
e.Mobile.SendMessage($"Maximum possible savings is {maxpercent}%");
|
|
e.Mobile.SendMessage($"Current savings is {percent}%");
|
|
}
|
|
|
|
[Usage("OptimalSmartSpawning [max spawn/homerange diff]")]
|
|
[Description("Activates SmartSpawning on XmlSpawners that are well-suited for use of this feature.")]
|
|
public static void OptimalSmartSpawning_OnCommand(CommandEventArgs e)
|
|
{
|
|
int maxdiff = 1;
|
|
if (e.Arguments.Length > 0)
|
|
{
|
|
try
|
|
{
|
|
maxdiff = int.Parse(e.Arguments[0]);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Diagnostics.ExceptionLogging.LogException(ex);
|
|
}
|
|
}
|
|
int count = 0;
|
|
int maxcount = 0;
|
|
foreach (Item item in World.Items.Values)
|
|
{
|
|
if (item is XmlSpawner spawner)
|
|
{
|
|
// determine whether this spawner is a good candidate
|
|
|
|
if (spawner.Deleted)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// ignore spawners in towns
|
|
//if (Region.Find(spawner.Location, spawner.Map) is Regions.TownRegion) continue;
|
|
|
|
// dont bother setting it on triggered spawners
|
|
if (spawner.ProximityRange >= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// check the relative spawnrange and homerange. Dont set it on spawners with a larger homerange than spawnrange
|
|
int width = spawner.m_Width;
|
|
int height = spawner.m_Height;
|
|
|
|
if (spawner.HomeRange * 2 > width + maxdiff * 2 || spawner.HomeRange * 2 > height + maxdiff * 2 && spawner.m_Region != null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int nso = 0;
|
|
|
|
if (spawner.m_SpawnObjects != null)
|
|
{
|
|
nso = spawner.m_SpawnObjects.Count;
|
|
}
|
|
|
|
// empty spawner so skip it
|
|
if (nso == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool skipit = false;
|
|
|
|
// check the spawn types
|
|
for (int i = 0; i < nso; ++i)
|
|
{
|
|
SpawnObject so = spawner.m_SpawnObjects[i];
|
|
|
|
if (so == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string typestr = so.TypeName;
|
|
|
|
Type type = AssemblyHandler.FindTypeByName(typestr);
|
|
|
|
// if it has basevendors on it or invalid types, then skip it
|
|
if (typestr == null || type != null && (type == typeof(BaseVendor) || type.IsSubclassOf(typeof(BaseVendor))) ||
|
|
type == null && !BaseXmlSpawner.IsTypeOrItemKeyword(typestr) && typestr.IndexOf('{') == -1 && !typestr.StartsWith("*") && !typestr.StartsWith("#"))
|
|
{
|
|
skipit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!skipit)
|
|
{
|
|
count++;
|
|
spawner.SmartSpawning = true;
|
|
maxcount += spawner.MaxCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
e.Mobile.SendMessage($"Configured {count} XmlSpawners for SmartSpawning using maxdiff of {maxdiff}");
|
|
e.Mobile.SendMessage($"Estimated item/mob reduction is {maxcount}");
|
|
}
|
|
|
|
[Usage("XmlSpawnerWipe [SpawnerPrefixFilter]")]
|
|
[Description("Removes all XmlSpawner objects from the current map.")]
|
|
public static void Wipe_OnCommand(CommandEventArgs e)
|
|
{
|
|
WipeSpawners(e, false);
|
|
}
|
|
|
|
[Usage("XmlSpawnerWipeAll [SpawnerPrefixFilter]")]
|
|
[Description("Removes all XmlSpawner objects from the entire world.")]
|
|
public static void WipeAll_OnCommand(CommandEventArgs e)
|
|
{
|
|
WipeSpawners(e, true);
|
|
}
|
|
|
|
public static void XmlUnLoadFromFile(string filename, string SpawnerPrefix, Mobile from, out int processedmaps, out int processedspawners)
|
|
{
|
|
|
|
processedmaps = 0;
|
|
processedspawners = 0;
|
|
if (filename == null || filename.Length <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int total_processed_maps = 0;
|
|
int total_processed_spawners = 0;
|
|
|
|
// Check if the file exists
|
|
if (File.Exists(filename))
|
|
{
|
|
FileStream fs = null;
|
|
try
|
|
{
|
|
fs = File.Open(filename, FileMode.Open, FileAccess.Read);
|
|
}
|
|
catch { }
|
|
|
|
if (fs == null)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"Unable to open {filename} for unloading");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
XmlUnLoadFromStream(fs, filename, SpawnerPrefix, from, out processedmaps, out processedspawners);
|
|
|
|
}
|
|
else
|
|
// check to see if it is a directory
|
|
if (Directory.Exists(filename))
|
|
{
|
|
// if so then import all of the .xml files in the directory
|
|
string[] files = null;
|
|
try
|
|
{
|
|
files = Directory.GetFiles(filename, "*.xml");
|
|
}
|
|
catch { }
|
|
if (files != null && files.Length > 0)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"UnLoading {files.Length} .xml files from directory {filename}");
|
|
}
|
|
|
|
foreach (string file in files)
|
|
{
|
|
XmlUnLoadFromFile(file, SpawnerPrefix, from, out processedmaps, out processedspawners);
|
|
total_processed_maps += processedmaps;
|
|
total_processed_spawners += processedspawners;
|
|
}
|
|
}
|
|
// recursively search subdirectories for more .xml files
|
|
string[] dirs = null;
|
|
try
|
|
{
|
|
dirs = Directory.GetDirectories(filename);
|
|
}
|
|
catch { }
|
|
if (dirs != null && dirs.Length > 0)
|
|
{
|
|
foreach (string dir in dirs)
|
|
{
|
|
XmlUnLoadFromFile(dir, SpawnerPrefix, from, out processedmaps, out processedspawners);
|
|
total_processed_maps += processedmaps;
|
|
total_processed_spawners += processedspawners;
|
|
}
|
|
}
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"UnLoaded a total of {total_processed_maps} .xml files and {total_processed_spawners} spawners from directory {filename}");
|
|
}
|
|
|
|
processedmaps = total_processed_maps;
|
|
processedspawners = total_processed_spawners;
|
|
}
|
|
else
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"{filename} does not exist");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public static void XmlUnLoadFromStream(Stream fs, string filename, string SpawnerPrefix, Mobile from, out int processedmaps, out int processedspawners)
|
|
{
|
|
processedmaps = 0;
|
|
processedspawners = 0;
|
|
|
|
if (fs == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int TotalCount = 0;
|
|
int TrammelCount = 0;
|
|
int FeluccaCount = 0;
|
|
int IlshenarCount = 0;
|
|
int MalasCount = 0;
|
|
int TokunoCount = 0;
|
|
int OtherCount = 0;
|
|
int bad_spawner_count = 0;
|
|
int spawners_deleted = 0;
|
|
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(
|
|
$"UnLoading {"XmlSpawner"} objects{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)} from file {filename}."
|
|
);
|
|
}
|
|
|
|
// Create the data set
|
|
DataSet ds = new DataSet(SpawnDataSetName);
|
|
|
|
// Read in the file
|
|
//ds.ReadXml(e.Arguments[0].ToString());
|
|
bool fileerror = false;
|
|
try
|
|
{
|
|
ds.ReadXml(fs);
|
|
}
|
|
catch
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Error reading xml file {filename}");
|
|
}
|
|
|
|
fileerror = true;
|
|
}
|
|
// close the file
|
|
fs.Close();
|
|
if (fileerror)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check that at least a single table was loaded
|
|
if (ds.Tables.Count > 0)
|
|
{
|
|
// Add each spawn point to the current map
|
|
if (ds.Tables[SpawnTablePointName] != null && ds.Tables[SpawnTablePointName].Rows.Count > 0)
|
|
{
|
|
foreach (DataRow dr in ds.Tables[SpawnTablePointName].Rows)
|
|
{
|
|
// load in the spawner info. Certain fields are required and therefore cannot be ignored
|
|
// the exception handler for those will flag bad_spawner and the result will be logged
|
|
|
|
// Each row makes up a single spawner
|
|
string SpawnName = "Spawner";
|
|
try { SpawnName = (string)dr["Name"]; }
|
|
catch { }
|
|
|
|
// Check if there is any spawner name criteria specified on the unload
|
|
if (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || SpawnName.StartsWith(SpawnerPrefix))
|
|
{
|
|
bool bad_spawner = false;
|
|
// Try load the GUID (might not work so create a new GUID)
|
|
Guid SpawnId = Guid.NewGuid();
|
|
try { SpawnId = new Guid((string)dr["UniqueId"]); }
|
|
catch { bad_spawner = true; }
|
|
// have to have a GUID or no point in continuing
|
|
if (bad_spawner)
|
|
{
|
|
bad_spawner_count++;
|
|
continue;
|
|
}
|
|
// Get the map (default to the mobiles map)
|
|
Map SpawnMap = Map.Internal;
|
|
string XmlMapName = SpawnMap.Name;
|
|
|
|
// Try to get the "map" field, but in case it doesn't exist, catch and discard the exception
|
|
try { XmlMapName = (string)dr["Map"]; }
|
|
catch { }
|
|
|
|
// Convert the xml map value to a real map object
|
|
if (string.Compare(XmlMapName, Map.Trammel.Name, true) == 0 || XmlMapName == "Trammel")
|
|
{
|
|
SpawnMap = Map.Trammel;
|
|
TrammelCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Felucca.Name, true) == 0 || XmlMapName == "Felucca")
|
|
{
|
|
SpawnMap = Map.Felucca;
|
|
FeluccaCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Ilshenar.Name, true) == 0 || XmlMapName == "Ilshenar")
|
|
{
|
|
SpawnMap = Map.Ilshenar;
|
|
IlshenarCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Malas.Name, true) == 0 || XmlMapName == "Malas")
|
|
{
|
|
SpawnMap = Map.Malas;
|
|
MalasCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Tokuno.Name, true) == 0 || XmlMapName == "Tokuno")
|
|
{
|
|
SpawnMap = Map.Tokuno;
|
|
TokunoCount++;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
SpawnMap = Map.Parse(XmlMapName);
|
|
}
|
|
catch { }
|
|
OtherCount++;
|
|
}
|
|
|
|
// Check if this spawner already exists
|
|
XmlSpawner OldSpawner = null;
|
|
foreach (Item i in World.Items.Values)
|
|
{
|
|
if (i is XmlSpawner checkXmlSpawner)
|
|
{
|
|
// Check if the spawners GUID is the same as the one being unloaded
|
|
// and that the spawners map is the same as the one being unloaded
|
|
if (checkXmlSpawner.UniqueId == SpawnId.ToString()
|
|
/*&& (CheckXmlSpawner.Map == SpawnMap)*/)
|
|
{
|
|
OldSpawner = checkXmlSpawner;
|
|
if (OldSpawner != null)
|
|
{
|
|
spawners_deleted++;
|
|
OldSpawner.Delete();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TotalCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
fs.Close();
|
|
}
|
|
catch { }
|
|
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(
|
|
$"{spawners_deleted}/{TotalCount} spawner(s) were unloaded using file {filename} [Trammel={TrammelCount}, Felucca={FeluccaCount}, Ilshenar={IlshenarCount}, Malas={MalasCount}, Tokuno={TokunoCount}, Other={OtherCount}]."
|
|
);
|
|
}
|
|
|
|
if (bad_spawner_count > 0)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"{bad_spawner_count} bad spawners detected.");
|
|
}
|
|
}
|
|
|
|
processedmaps = 1;
|
|
processedspawners = TotalCount;
|
|
}
|
|
|
|
[Usage("XmlSpawnerUnLoad <SpawnFile or directory> [SpawnerPrefixFilter]")]
|
|
[Aliases("XmlUnload")]
|
|
[Description("UnLoads XmlSpawner objects from the proper map as defined in the file supplied.")]
|
|
public static void UnLoad_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e.Mobile.AccessLevel >= DiskAccessLevel)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
// Spawner unload criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
|
|
// Check if there is an argument provided (load criteria)
|
|
if (e.Arguments.Length > 1)
|
|
{
|
|
SpawnerPrefix = e.Arguments[1];
|
|
}
|
|
|
|
string filename = LocateFile(e.Arguments[0]);
|
|
XmlUnLoadFromFile(filename, SpawnerPrefix, e.Mobile, out _, out _);
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile or directory>");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
[Usage("XmlImportMap <mapfile or directory>")]
|
|
[Description("Loads spawner definitions from a .map file")]
|
|
public static void XmlImportMap_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e.Mobile.AccessLevel >= DiskAccessLevel)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
string filename = e.Arguments[0];
|
|
|
|
XmlImportMap(filename, e.Mobile, out _, out _);
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <MapFile>");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
public static void XmlImportMap(string filename, Mobile from, out int processedmaps, out int processedspawners)
|
|
{
|
|
processedmaps = 0;
|
|
processedspawners = 0;
|
|
int total_processed_maps = 0;
|
|
int total_processed_spawners = 0;
|
|
if (filename == null || filename.Length <= 0 || from == null || from.Deleted)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if the file exists
|
|
if (File.Exists(filename))
|
|
{
|
|
int spawnercount = 0;
|
|
int badspawnercount = 0;
|
|
int linenumber = 0;
|
|
// default is no map override, use the map spec from each spawn line
|
|
int overridemap = -1;
|
|
double overridemintime = -1;
|
|
double overridemaxtime = -1;
|
|
bool newformat = false;
|
|
try
|
|
{
|
|
// Create an instance of StreamReader to read from a file.
|
|
// The using statement also closes the StreamReader.
|
|
using (StreamReader sr = new StreamReader(filename))
|
|
{
|
|
string line;
|
|
// Read and display lines from the file until the end of
|
|
// the file is reached.
|
|
while ((line = sr.ReadLine()) != null)
|
|
{
|
|
// the old format of each .map line is * Dragon:Wyvern 5209 965 -40 2 2 10 50 30 1
|
|
// * typename:typename:... x y z map mindelay maxdelay homerange spawnrange maxcount
|
|
// * | typename:typename:... |s1 |s2 |s3 |s4 |s5 | x | y | z | map | mindelay maxdelay homerange spawnrange spawnid maxcount | maxcount1 | maxcount2 | maxcount3 | maxcount4 | maxcount5
|
|
// where s1-5 are additional spawn type entries with their own maxcounts
|
|
// the new format of each .map line is * |Dragon:Wyvern| spawns:spawns| | | | | 5209 | 965 | -40 | 2 | 2 | 10 | 50 | 30 | 1
|
|
|
|
linenumber++;
|
|
// is this the new format?
|
|
string[] args;
|
|
if (line.IndexOf('|') >= 0)
|
|
{
|
|
args = line.Trim().Split('|');
|
|
newformat = true;
|
|
}
|
|
else
|
|
{
|
|
args = line.Trim().Split(' ');
|
|
}
|
|
|
|
// determine the format of this line and parse accordingly
|
|
if (newformat)
|
|
{
|
|
ParseNewMapFormat(from, filename, line, args, linenumber, ref spawnercount, ref badspawnercount, ref overridemap, ref overridemintime, ref overridemaxtime);
|
|
}
|
|
else
|
|
{
|
|
ParseOldMapFormat(from, filename, line, args, linenumber, ref spawnercount, ref badspawnercount, ref overridemap, ref overridemintime, ref overridemaxtime);
|
|
}
|
|
|
|
}
|
|
sr.Close();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Let the user know what went wrong.
|
|
from.SendMessage($"The file could not be read: {e.Message}");
|
|
}
|
|
from.SendMessage($"Imported {spawnercount} spawners from {filename}");
|
|
from.SendMessage($"{badspawnercount} bad spawners detected");
|
|
processedmaps = 1;
|
|
processedspawners = spawnercount;
|
|
}
|
|
else
|
|
// check to see if it is a directory
|
|
if (Directory.Exists(filename))
|
|
{
|
|
// if so then import all of the .map files in the directory
|
|
string[] files = null;
|
|
try
|
|
{
|
|
files = Directory.GetFiles(filename, "*.map");
|
|
}
|
|
catch { }
|
|
if (files != null && files.Length > 0)
|
|
{
|
|
from.SendMessage($"Importing {files.Length} .map files from directory {filename}");
|
|
foreach (string file in files)
|
|
{
|
|
XmlImportMap(file, from, out processedmaps, out processedspawners);
|
|
total_processed_maps += processedmaps;
|
|
total_processed_spawners += processedspawners;
|
|
}
|
|
}
|
|
// recursively search subdirectories for more .map files
|
|
string[] dirs = null;
|
|
try
|
|
{
|
|
dirs = Directory.GetDirectories(filename);
|
|
}
|
|
catch { }
|
|
if (dirs != null && dirs.Length > 0)
|
|
{
|
|
foreach (string dir in dirs)
|
|
{
|
|
XmlImportMap(dir, from, out processedmaps, out processedspawners);
|
|
total_processed_maps += processedmaps;
|
|
total_processed_spawners += processedspawners;
|
|
}
|
|
}
|
|
from.SendMessage(
|
|
$"Imported a total of {total_processed_maps} .map files and {filename} spawners from directory {total_processed_spawners}"
|
|
);
|
|
processedmaps = total_processed_maps;
|
|
processedspawners = total_processed_spawners;
|
|
}
|
|
else
|
|
{
|
|
from.SendMessage($"{filename} does not exist");
|
|
}
|
|
}
|
|
|
|
private static void ParseNewMapFormat(Mobile from, string filename, string line, string[] args, int linenumber, ref int spawnercount, ref int badspawnercount, ref int overridemap, ref double overridemintime, ref double overridemaxtime)
|
|
{
|
|
// format of each .map line is * Dragon:Wyvern 5209 965 -40 2 2 10 50 30 1
|
|
// * typename:typename:... x y z map mindelay maxdelay homerange spawnrange maxcount
|
|
// or
|
|
// * typename:typename:... x y z map mindelay maxdelay homerange spawnrange spawnid maxcount
|
|
// ## are comments
|
|
// overridemap mapnumber
|
|
// map 0 is tram+fel
|
|
// map 1 is fel
|
|
// map 2 is tram
|
|
// map 3 is ilsh
|
|
// map 4 is mal
|
|
// map 5 is tokuno
|
|
//
|
|
// * | typename:typename:... | | | | | | x | y | z | map | mindelay maxdelay homerange spawnrange spawnid maxcount1 | maxcount2 | maxcount2 | maxcount3 | maxcount4 | maxcount5 | maxcount6
|
|
// the new format of each .map line is * |Dragon:Wyvern| spawns:spawns| | | | | 5209 | 965 | -40 | 2 | 2 | 10 | 50 | 30 | 1
|
|
|
|
if (args == null || from == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// look for the override keyword
|
|
if (args.Length == 2 && args[0].ToLower() == "overridemap")
|
|
{
|
|
try
|
|
{
|
|
overridemap = int.Parse(args[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
if (args.Length == 2 && args[0].ToLower() == "overridemintime")
|
|
{
|
|
try
|
|
{
|
|
overridemintime = double.Parse(args[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
if (args.Length == 2 && args[0].ToLower() == "overridemaxtime")
|
|
{
|
|
try
|
|
{
|
|
overridemaxtime = double.Parse(args[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
// look for a spawn spec line
|
|
if (args.Length > 0 && args[0] == "*")
|
|
{
|
|
|
|
bool badspawn = false;
|
|
int x = 0;
|
|
int y = 0;
|
|
int z = 0;
|
|
int map = 0;
|
|
double mindelay = 0;
|
|
double maxdelay = 0;
|
|
int homerange = 0;
|
|
int spawnrange = 0;
|
|
string[][] typenames = new string[6][];
|
|
|
|
int[] maxcount = new int[6];
|
|
|
|
// parse the main args
|
|
|
|
try
|
|
{
|
|
// get the list of spawns
|
|
for (int k = 0; k < 6; k++)
|
|
{
|
|
typenames[k] = args[k + 1].Split(':');
|
|
}
|
|
|
|
x = int.Parse(args[7]);
|
|
y = int.Parse(args[8]);
|
|
z = int.Parse(args[9]);
|
|
map = int.Parse(args[10]);
|
|
mindelay = double.Parse(args[11]);
|
|
maxdelay = double.Parse(args[12]);
|
|
homerange = int.Parse(args[13]);
|
|
spawnrange = int.Parse(args[14]);
|
|
int spawnid = int.Parse(args[15]);
|
|
|
|
for (int k = 0; k < 6; k++)
|
|
{
|
|
maxcount[k] = int.Parse(args[k + 16]);
|
|
}
|
|
}
|
|
catch { from.SendMessage($"Parsing error at line {linenumber}"); badspawn = true; }
|
|
|
|
// compute the total number of spawns
|
|
int totalspawns = 0;
|
|
int totalmaxcount = 0;
|
|
|
|
for (int k = 0; k < 6; k++)
|
|
{
|
|
if (typenames[k] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int i = 0; i < typenames[k].Length; i++)
|
|
{
|
|
if (typenames[k][i] == null || typenames[k][i].Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
totalspawns++;
|
|
}
|
|
|
|
totalmaxcount += maxcount[k];
|
|
}
|
|
|
|
// apply min/maxdelay overrides
|
|
if (overridemintime != -1)
|
|
{
|
|
mindelay = overridemintime;
|
|
}
|
|
if (overridemaxtime != -1)
|
|
{
|
|
maxdelay = overridemaxtime;
|
|
}
|
|
if (mindelay > maxdelay)
|
|
{
|
|
maxdelay = mindelay;
|
|
}
|
|
|
|
if (!badspawn && totalspawns > 0)
|
|
{
|
|
// everything seems ok so go ahead and make the spawner
|
|
// check for map override
|
|
if (overridemap >= 0)
|
|
{
|
|
map = overridemap;
|
|
}
|
|
|
|
Map spawnmap = Map.Internal;
|
|
switch (map)
|
|
{
|
|
case 0:
|
|
{
|
|
spawnmap = Map.Felucca;
|
|
// note it also does trammel
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
spawnmap = Map.Felucca;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
spawnmap = Map.Trammel;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
spawnmap = Map.Ilshenar;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
spawnmap = Map.Malas;
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
spawnmap = Map.Tokuno;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!IsValidMapLocation(x, y, spawnmap))
|
|
{
|
|
// invalid so dont spawn it
|
|
badspawnercount++;
|
|
from.SendMessage($"Invalid map/location at line {linenumber}");
|
|
from.SendMessage($"Bad spawn at line {line}: {line}");
|
|
return;
|
|
}
|
|
|
|
// allow it to make an xmlspawner instead
|
|
// first add all of the creatures on the list
|
|
SpawnObject[] so = new SpawnObject[totalspawns];
|
|
int count = 0;
|
|
bool hasvendor = true;
|
|
for (int k = 0; k < 6; k++)
|
|
{
|
|
if (typenames[k] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int i = 0; i < typenames[k].Length; i++)
|
|
{
|
|
if (typenames[k][i] == null || typenames[k][i].Length == 0 || count > totalspawns)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
so[count++] = new SpawnObject(typenames[k][i], maxcount[k]);
|
|
|
|
// check the type to see if there are vendors on it
|
|
Type type = AssemblyHandler.FindTypeByName(typenames[k][i]);
|
|
|
|
// check for vendor-only spawners which get special spawnrange treatment
|
|
if (type != null && type != typeof(BaseVendor) && !type.IsSubclassOf(typeof(BaseVendor)))
|
|
{
|
|
hasvendor = false;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// assign it a unique id
|
|
Guid SpawnId = Guid.NewGuid();
|
|
|
|
// and give it a name based on the spawner count and file
|
|
string spawnername = $"{Path.GetFileNameWithoutExtension(filename)}#{spawnercount}";
|
|
|
|
// Create the new xml spawner
|
|
XmlSpawner spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, totalmaxcount,
|
|
TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1,
|
|
0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0),
|
|
TimeSpan.FromMinutes(0), null, null, null, null, null,
|
|
null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null,
|
|
TimeSpan.FromHours(0), null, false, null);
|
|
|
|
spawner.SpawnRange = hasvendor ? 0 : spawnrange;
|
|
|
|
spawner.m_PlayerCreated = true;
|
|
|
|
spawner.MoveToWorld(new Point3D(x, y, z), spawnmap);
|
|
if (spawner.Map == Map.Internal)
|
|
{
|
|
badspawnercount++;
|
|
spawner.Delete();
|
|
from.SendMessage($"Invalid map at line {linenumber}");
|
|
from.SendMessage($"Bad spawn at line {linenumber}: {line}");
|
|
return;
|
|
}
|
|
spawnercount++;
|
|
// handle the special case of map 0 that also needs to do trammel
|
|
if (map == 0)
|
|
{
|
|
spawnmap = Map.Trammel;
|
|
// assign it a unique id
|
|
SpawnId = Guid.NewGuid();
|
|
// Create the new xml spawner
|
|
spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, totalmaxcount,
|
|
TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1,
|
|
0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0),
|
|
TimeSpan.FromMinutes(0), null, null, null, null, null,
|
|
null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null,
|
|
TimeSpan.FromHours(0), null, false, null)
|
|
{
|
|
SpawnRange = spawnrange,
|
|
m_PlayerCreated = true
|
|
};
|
|
|
|
spawner.MoveToWorld(new Point3D(x, y, z), spawnmap);
|
|
if (spawner.Map == Map.Internal)
|
|
{
|
|
badspawnercount++;
|
|
spawner.Delete();
|
|
from.SendMessage($"Bad spawn at line {linenumber}: {line}");
|
|
return;
|
|
}
|
|
spawnercount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
badspawnercount++;
|
|
from.SendMessage($"Bad spawn at line {linenumber}: {line}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ParseOldMapFormat(Mobile from, string filename, string line, string[] args, int linenumber, ref int spawnercount, ref int badspawnercount, ref int overridemap, ref double overridemintime, ref double overridemaxtime)
|
|
{
|
|
// format of each .map line is * Dragon:Wyvern 5209 965 -40 2 2 10 50 30 1
|
|
// * typename:typename:... x y z map mindelay maxdelay homerange spawnrange maxcount
|
|
// or
|
|
// * typename:typename:... x y z map mindelay maxdelay homerange spawnrange spawnid maxcount
|
|
// ## are comments
|
|
// overridemap mapnumber
|
|
// map 0 is tram+fel
|
|
// map 1 is fel
|
|
// map 2 is tram
|
|
// map 3 is ilsh
|
|
// map 4 is mal
|
|
// map 5 is tokuno
|
|
//
|
|
// * | typename:typename:... | | | | | | x | y | z | map | mindelay maxdelay homerange spawnrange spawnid maxcount | maxcount2 | maxcount2 | maxcount3 | maxcount4 | maxcount5
|
|
// the new format of each .map line is * |Dragon:Wyvern| spawns:spawns| | | | | 5209 | 965 | -40 | 2 | 2 | 10 | 50 | 30 | 1
|
|
|
|
if (args == null || from == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// look for the override keyword
|
|
if (args.Length == 2 && args[0].ToLower() == "overridemap")
|
|
{
|
|
try
|
|
{
|
|
overridemap = int.Parse(args[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
if (args.Length == 2 && args[0].ToLower() == "overridemintime")
|
|
{
|
|
try
|
|
{
|
|
overridemintime = double.Parse(args[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
if (args.Length == 2 && args[0].ToLower() == "overridemaxtime")
|
|
{
|
|
try
|
|
{
|
|
overridemaxtime = double.Parse(args[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
// look for a spawn spec line
|
|
if (args.Length > 0 && args[0] == "*")
|
|
{
|
|
bool badspawn = false;
|
|
int x = 0;
|
|
int y = 0;
|
|
int z = 0;
|
|
int map = 0;
|
|
double mindelay = 0;
|
|
double maxdelay = 0;
|
|
int homerange = 0;
|
|
int spawnrange = 0;
|
|
int maxcount = 0;
|
|
string[] typenames = null;
|
|
if (args.Length != 11 && args.Length != 12)
|
|
{
|
|
badspawn = true;
|
|
from.SendMessage($"Invalid arg count {args.Length} at line {linenumber}");
|
|
}
|
|
else
|
|
{
|
|
// get the list of spawns
|
|
typenames = args[1].Split(':');
|
|
// parse the rest of the args
|
|
|
|
if (args.Length == 11)
|
|
{
|
|
|
|
try
|
|
{
|
|
x = int.Parse(args[2]);
|
|
y = int.Parse(args[3]);
|
|
z = int.Parse(args[4]);
|
|
map = int.Parse(args[5]);
|
|
mindelay = double.Parse(args[6]);
|
|
maxdelay = double.Parse(args[7]);
|
|
homerange = int.Parse(args[8]);
|
|
spawnrange = int.Parse(args[9]);
|
|
maxcount = int.Parse(args[10]);
|
|
|
|
}
|
|
catch { from.SendMessage($"Parsing error at line {linenumber}"); badspawn = true; }
|
|
}
|
|
else
|
|
if (args.Length == 12)
|
|
{
|
|
|
|
try
|
|
{
|
|
x = int.Parse(args[2]);
|
|
y = int.Parse(args[3]);
|
|
z = int.Parse(args[4]);
|
|
map = int.Parse(args[5]);
|
|
mindelay = double.Parse(args[6]);
|
|
maxdelay = double.Parse(args[7]);
|
|
homerange = int.Parse(args[8]);
|
|
spawnrange = int.Parse(args[9]);
|
|
int spawnid = int.Parse(args[10]);
|
|
maxcount = int.Parse(args[11]);
|
|
|
|
}
|
|
catch { from.SendMessage($"Parsing error at line {linenumber}"); badspawn = true; }
|
|
}
|
|
}
|
|
|
|
|
|
// apply mi/maxdelay overrides
|
|
if (overridemintime != -1)
|
|
{
|
|
mindelay = overridemintime;
|
|
}
|
|
if (overridemaxtime != -1)
|
|
{
|
|
maxdelay = overridemaxtime;
|
|
}
|
|
if (mindelay > maxdelay)
|
|
{
|
|
maxdelay = mindelay;
|
|
}
|
|
|
|
if (!badspawn && typenames.Length > 0)
|
|
{
|
|
// everything seems ok so go ahead and make the spawner
|
|
// check for map override
|
|
if (overridemap >= 0)
|
|
{
|
|
map = overridemap;
|
|
}
|
|
|
|
Map spawnmap = Map.Internal;
|
|
switch (map)
|
|
{
|
|
case 0:
|
|
{
|
|
spawnmap = Map.Felucca;
|
|
// note it also does trammel
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
spawnmap = Map.Felucca;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
spawnmap = Map.Trammel;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
spawnmap = Map.Ilshenar;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
spawnmap = Map.Malas;
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
spawnmap = Map.Tokuno;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!IsValidMapLocation(x, y, spawnmap))
|
|
{
|
|
// invalid so dont spawn it
|
|
badspawnercount++;
|
|
from.SendMessage($"Invalid map/location at line {linenumber}");
|
|
from.SendMessage($"Bad spawn at line {linenumber}: {line}");
|
|
return;
|
|
}
|
|
|
|
// allow it to make an xmlspawner instead
|
|
// first add all of the creatures on the list
|
|
SpawnObject[] so = new SpawnObject[typenames.Length];
|
|
|
|
bool hasvendor = true;
|
|
for (int i = 0; i < typenames.Length; i++)
|
|
{
|
|
so[i] = new SpawnObject(typenames[i], maxcount);
|
|
|
|
// check the type to see if there are vendors on it
|
|
Type type = AssemblyHandler.FindTypeByName(typenames[i]);
|
|
|
|
// check for vendor-only spawners which get special spawnrange treatment
|
|
if (type != null && type != typeof(BaseVendor) && !type.IsSubclassOf(typeof(BaseVendor)))
|
|
{
|
|
hasvendor = false;
|
|
}
|
|
|
|
}
|
|
|
|
// assign it a unique id
|
|
Guid SpawnId = Guid.NewGuid();
|
|
|
|
// and give it a name based on the spawner count and file
|
|
string spawnername = $"{Path.GetFileNameWithoutExtension(filename)}#{spawnercount}";
|
|
|
|
// Create the new xml spawner
|
|
XmlSpawner spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, maxcount,
|
|
TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1,
|
|
0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0),
|
|
TimeSpan.FromMinutes(0), null, null, null, null, null,
|
|
null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null,
|
|
TimeSpan.FromHours(0), null, false, null);
|
|
|
|
spawner.SpawnRange = hasvendor ? 0 : spawnrange;
|
|
|
|
spawner.m_PlayerCreated = true;
|
|
|
|
spawner.MoveToWorld(new Point3D(x, y, z), spawnmap);
|
|
if (spawner.Map == Map.Internal)
|
|
{
|
|
badspawnercount++;
|
|
spawner.Delete();
|
|
from.SendMessage($"Invalid map at line {linenumber}");
|
|
from.SendMessage($"Bad spawn at line {linenumber}: {line}");
|
|
return;
|
|
}
|
|
spawnercount++;
|
|
// handle the special case of map 0 that also needs to do trammel
|
|
if (map == 0)
|
|
{
|
|
spawnmap = Map.Trammel;
|
|
// assign it a unique id
|
|
SpawnId = Guid.NewGuid();
|
|
// Create the new xml spawner
|
|
spawner = new XmlSpawner(SpawnId, x, y, 0, 0, spawnername, maxcount,
|
|
TimeSpan.FromMinutes(mindelay), TimeSpan.FromMinutes(maxdelay), TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1,
|
|
0, homerange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0),
|
|
TimeSpan.FromMinutes(0), null, null, null, null, null,
|
|
null, null, null, null, 1, null, false, defTODMode, defKillReset, false, -1, null, false, false, false, null,
|
|
TimeSpan.FromHours(0), null, false, null)
|
|
{
|
|
SpawnRange = spawnrange,
|
|
m_PlayerCreated = true
|
|
};
|
|
|
|
spawner.MoveToWorld(new Point3D(x, y, z), spawnmap);
|
|
if (spawner.Map == Map.Internal)
|
|
{
|
|
badspawnercount++;
|
|
spawner.Delete();
|
|
from.SendMessage($"Bad spawn at line {linenumber}: {line}");
|
|
return;
|
|
}
|
|
spawnercount++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
badspawnercount++;
|
|
from.SendMessage($"Bad spawn at line {linenumber}: {line}");
|
|
}
|
|
}
|
|
}
|
|
|
|
[Usage("XmlImportSpawners filename")]
|
|
[Description("Loads xml files created by Sno's xml exporter as xmlspawners.")]
|
|
public static void XmlImportSpawners_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
string filename = e.GetString(0);
|
|
string filePath = Path.Combine("Saves/Spawners", filename);
|
|
if (File.Exists(filePath))
|
|
{
|
|
XmlDocument doc = new XmlDocument();
|
|
try
|
|
{
|
|
doc.Load(filePath);
|
|
}
|
|
catch
|
|
{
|
|
e.Mobile.SendMessage($"unable to load file {filePath}.");
|
|
return;
|
|
}
|
|
|
|
XmlElement root = doc["spawners"];
|
|
int successes = 0, failures = 0;
|
|
if (root?.GetElementsByTagName("spawner") != null)
|
|
{
|
|
foreach (XmlElement spawner in root.GetElementsByTagName("spawner"))
|
|
{
|
|
try
|
|
{
|
|
ImportSpawner(spawner, e.Mobile);
|
|
successes++;
|
|
}
|
|
catch (Exception ex) { e.Mobile.SendMessage(33, $"{ex.Message} {spawner.InnerText}"); failures++; }
|
|
}
|
|
}
|
|
e.Mobile.SendMessage($"{successes} spawners loaded successfully from {filePath}, {failures} failures.");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"File {filePath} does not exist.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("Usage: [XmlImportSpawners <filename>");
|
|
}
|
|
}
|
|
|
|
private static string GetText(XmlElement node, string defaultValue)
|
|
{
|
|
if (node == null)
|
|
{
|
|
return defaultValue;
|
|
}
|
|
|
|
return node.InnerText;
|
|
}
|
|
|
|
private static void ImportSpawner(XmlElement node, Mobile from)
|
|
{
|
|
int count = int.Parse(GetText(node["count"], "1"));
|
|
int homeRange = int.Parse(GetText(node["homerange"], "4"));
|
|
int walkingRange = int.Parse(GetText(node["walkingrange"], "-1"));
|
|
// width of the spawning area
|
|
int spawnwidth = homeRange * 2;
|
|
if (walkingRange >= 0)
|
|
{
|
|
spawnwidth = walkingRange * 2;
|
|
}
|
|
|
|
int team = int.Parse(GetText(node["team"], "0"));
|
|
bool group = bool.Parse(GetText(node["group"], "False"));
|
|
TimeSpan maxDelay = TimeSpan.Parse(GetText(node["maxdelay"], "10:00"));
|
|
TimeSpan minDelay = TimeSpan.Parse(GetText(node["mindelay"], "05:00"));
|
|
List<string> creaturesName = LoadCreaturesName(node["creaturesname"]);
|
|
string name = GetText(node["name"], "Spawner");
|
|
Point3D location = Point3D.Parse(GetText(node["location"], "Error"));
|
|
Map map = Map.Parse(GetText(node["map"], "Error"));
|
|
|
|
// allow it to make an xmlspawner instead
|
|
// first add all of the creatures on the list
|
|
SpawnObject[] so = new SpawnObject[creaturesName.Count];
|
|
|
|
bool hasvendor = false;
|
|
|
|
for (int i = 0; i < creaturesName.Count; i++)
|
|
{
|
|
so[i] = new SpawnObject(creaturesName[i], count);
|
|
// check the type to see if there are vendors on it
|
|
Type type = AssemblyHandler.FindTypeByName(creaturesName[i]);
|
|
|
|
// if it has basevendors on it or invalid types, then skip it
|
|
if (type != null && (type == typeof(BaseVendor) || type.IsSubclassOf(typeof(BaseVendor))))
|
|
{
|
|
hasvendor = true;
|
|
}
|
|
}
|
|
|
|
// assign it a unique id
|
|
Guid SpawnId = Guid.NewGuid();
|
|
|
|
// Create the new xml spawner
|
|
XmlSpawner spawner = new XmlSpawner(SpawnId, location.X, location.Y, spawnwidth, spawnwidth, name, count,
|
|
minDelay, maxDelay, TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1,
|
|
team, homeRange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0),
|
|
TimeSpan.FromMinutes(0), null, null, null, null, null,
|
|
null, null, null, null, 1, null, group, defTODMode, defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null);
|
|
|
|
spawner.SpawnRange = hasvendor ? 0 : homeRange;
|
|
spawner.m_PlayerCreated = true;
|
|
|
|
spawner.MoveToWorld(location, map);
|
|
if (!IsValidMapLocation(location, spawner.Map))
|
|
{
|
|
spawner.Delete();
|
|
throw new Exception("Invalid spawner location.");
|
|
}
|
|
}
|
|
|
|
private static List<string> LoadCreaturesName(XmlElement node)
|
|
{
|
|
List<string> names = new List<string>();
|
|
|
|
if (node != null)
|
|
{
|
|
foreach (XmlElement ele in node.GetElementsByTagName("creaturename"))
|
|
{
|
|
if (ele != null)
|
|
{
|
|
names.Add(ele.InnerText);
|
|
}
|
|
}
|
|
}
|
|
return names;
|
|
}
|
|
|
|
[Usage("XmlImportMSF filename")]
|
|
[Description("Loads msf files created by Morxeton's megaspawner as xmlspawners.")]
|
|
public static void XmlImportMSF_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
/*
|
|
// I'm not sure what the default location for .msf files is
|
|
string filename = e.GetString(0);
|
|
string filePath = Path.Combine("Data/Megaspawner", filename);
|
|
*/
|
|
string filePath = e.GetString(0);
|
|
if (File.Exists(filePath))
|
|
{
|
|
XmlDocument doc = new XmlDocument();
|
|
doc.Load(filePath);
|
|
XmlElement root = doc["MegaSpawners"];
|
|
if (root != null)
|
|
{
|
|
int successes = 0, failures = 0;
|
|
foreach (XmlElement spawner in root.GetElementsByTagName("MegaSpawner"))
|
|
{
|
|
|
|
try
|
|
{
|
|
ImportMegaSpawner(e.Mobile, spawner);
|
|
successes++;
|
|
}
|
|
catch (Exception ex) { e.Mobile.SendMessage(33, $"{ex.Message} {spawner.InnerText}"); failures++; }
|
|
}
|
|
e.Mobile.SendMessage($"{successes} megaspawners loaded successfully from {filePath}, {failures} failures.");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("Invalid .msf file. No MegaSpawners node found");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"File {filePath} does not exist.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("Usage: [XmlImportMSF <filename>");
|
|
}
|
|
}
|
|
|
|
private static void ImportMegaSpawner(Mobile from, XmlElement node)
|
|
{
|
|
string name = GetText(node["Name"], "MegaSpawner");
|
|
bool running = bool.Parse(GetText(node["Active"], "True"));
|
|
Point3D location = Point3D.Parse(GetText(node["Location"], "Error"));
|
|
Map map = Map.Parse(GetText(node["Map"], "Error"));
|
|
|
|
|
|
int team = 0;
|
|
bool group = false;
|
|
int maxcount = 0; // default maxcount of the spawner
|
|
int homeRange = 4; // default homerange
|
|
int spawnRange = 4; // default homerange
|
|
TimeSpan maxDelay = TimeSpan.FromMinutes(10);
|
|
TimeSpan minDelay = TimeSpan.FromMinutes(5);
|
|
|
|
XmlElement listnode = node["EntryLists"];
|
|
|
|
int nentries = 0;
|
|
SpawnObject[] so = null;
|
|
|
|
|
|
if (listnode != null)
|
|
{
|
|
// get the number of entries
|
|
if (listnode.HasAttributes)
|
|
{
|
|
XmlAttributeCollection attr = listnode.Attributes;
|
|
|
|
nentries = int.Parse(attr.GetNamedItem("count").Value);
|
|
}
|
|
if (nentries > 0)
|
|
{
|
|
so = new SpawnObject[nentries];
|
|
|
|
int entrycount = 0;
|
|
bool diff = false;
|
|
foreach (XmlElement entrynode in listnode.GetElementsByTagName("EntryList"))
|
|
{
|
|
// go through each entry and add a spawn object for it
|
|
if (entrynode != null)
|
|
{
|
|
if (entrycount == 0)
|
|
{
|
|
// get the spawner defaults from the first entry
|
|
// dont handle the individually specified entry attributes
|
|
group = bool.Parse(GetText(entrynode["GroupSpawn"], "False"));
|
|
maxDelay = TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MaxDelay"], "10:00")));
|
|
minDelay = TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MinDelay"], "05:00")));
|
|
homeRange = int.Parse(GetText(entrynode["WalkRange"], "10"));
|
|
spawnRange = int.Parse(GetText(entrynode["SpawnRange"], "4"));
|
|
}
|
|
else
|
|
{
|
|
// just check for consistency with other entries and report discrepancies
|
|
if (group != bool.Parse(GetText(entrynode["GroupSpawn"], "False")))
|
|
{
|
|
diff = true;
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badimport.log", true))
|
|
{
|
|
op.WriteLine("MSFimport : individual group entry difference: {0} vs {1}",
|
|
GetText(entrynode["GroupSpawn"], "False"), group);
|
|
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
if (minDelay != TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MinDelay"], "05:00"))))
|
|
{
|
|
diff = true;
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badimport.log", true))
|
|
{
|
|
op.WriteLine("MSFimport : individual mindelay entry difference: {0} vs {1}",
|
|
GetText(entrynode["MinDelay"], "05:00"), minDelay);
|
|
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
if (maxDelay != TimeSpan.FromSeconds(int.Parse(GetText(entrynode["MaxDelay"], "10:00"))))
|
|
{
|
|
diff = true;
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badimport.log", true))
|
|
{
|
|
op.WriteLine("MSFimport : individual maxdelay entry difference: {0} vs {1}",
|
|
GetText(entrynode["MaxDelay"], "10:00"), maxDelay);
|
|
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
if (homeRange != int.Parse(GetText(entrynode["WalkRange"], "10")))
|
|
{
|
|
diff = true;
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badimport.log", true))
|
|
{
|
|
op.WriteLine("MSFimport : individual homerange entry difference: {0} vs {1}",
|
|
GetText(entrynode["WalkRange"], "10"), homeRange);
|
|
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
if (spawnRange != int.Parse(GetText(entrynode["SpawnRange"], "4")))
|
|
{
|
|
diff = true;
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badimport.log", true))
|
|
{
|
|
op.WriteLine("MSFimport : individual spawnrange entry difference: {0} vs {1}",
|
|
GetText(entrynode["SpawnRange"], "4"), spawnRange);
|
|
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
// these apply to individual entries
|
|
int amount = int.Parse(GetText(entrynode["Amount"], "1"));
|
|
string entryname = GetText(entrynode["EntryType"], "");
|
|
|
|
// keep track of the maxcount for the spawner by adding the individual amounts
|
|
maxcount += amount;
|
|
|
|
// add the creature entry
|
|
so[entrycount] = new SpawnObject(entryname, amount);
|
|
|
|
entrycount++;
|
|
if (entrycount > nentries)
|
|
{
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badimport.log", true))
|
|
{
|
|
op.WriteLine($"{Core.Now} MSFImport Error; inconsistent entry count {location} {map}");
|
|
op.WriteLine();
|
|
}
|
|
}
|
|
catch { }
|
|
from.SendMessage($"Inconsistent entry count detected at {location} {map}.");
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
if (diff)
|
|
{
|
|
from.SendMessage($"Individual entry setting detected at {location} {map}.");
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badimport.log", true))
|
|
{
|
|
op.WriteLine($"{Core.Now} MSFImport: Individual entry setting differences listed above from spawner at {location} {map}");
|
|
op.WriteLine();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
|
|
// assign it a unique id
|
|
Guid SpawnId = Guid.NewGuid();
|
|
// Create the new xml spawner
|
|
XmlSpawner spawner = new XmlSpawner(SpawnId, location.X, location.Y, 0, 0, name, maxcount,
|
|
minDelay, maxDelay, TimeSpan.FromMinutes(0), -1, defaultTriggerSound, 1,
|
|
team, homeRange, false, so, TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(0),
|
|
TimeSpan.FromMinutes(0), null, null, null, null, null,
|
|
null, null, null, null, 1, null, group, defTODMode, defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null)
|
|
{
|
|
SpawnRange = spawnRange,
|
|
m_PlayerCreated = true
|
|
};
|
|
|
|
// Try to find a valid Z height if required (Z == -999)
|
|
|
|
if (location.Z == -999)
|
|
{
|
|
int NewZ = map.GetAverageZ(location.X, location.Y);
|
|
|
|
if (map.CanFit(location.X, location.Y, NewZ, SpawnFitSize) == false)
|
|
{
|
|
for (int x = 1; x <= 39; x++)
|
|
{
|
|
if (map.CanFit(location.X, location.Y, NewZ + x, SpawnFitSize))
|
|
{
|
|
NewZ += x;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
location.Z = NewZ;
|
|
}
|
|
|
|
spawner.MoveToWorld(location, map);
|
|
|
|
if (!IsValidMapLocation(location, spawner.Map))
|
|
{
|
|
spawner.Delete();
|
|
throw new Exception("Invalid spawner location.");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public static void XmlLoadFromFile(string filename, string SpawnerPrefix, Mobile from, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners)
|
|
{
|
|
processedmaps = 0;
|
|
processedspawners = 0;
|
|
int total_processed_maps = 0;
|
|
int total_processed_spawners = 0;
|
|
|
|
if (filename == null || filename.Length <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check if the file exists
|
|
if (File.Exists(filename))
|
|
{
|
|
FileStream fs = null;
|
|
try
|
|
{
|
|
fs = File.Open(filename, FileMode.Open, FileAccess.Read);
|
|
}
|
|
catch { }
|
|
|
|
if (fs == null)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"Unable to open {filename} for loading");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// load the file
|
|
XmlLoadFromStream(fs, filename, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners);
|
|
|
|
}
|
|
else if (Directory.Exists(filename))
|
|
{
|
|
// if so then load all of the .xml files in the directory
|
|
string[] files = null;
|
|
try
|
|
{
|
|
files = Directory.GetFiles(filename, "*.xml");
|
|
}
|
|
catch { }
|
|
if (files != null && files.Length > 0)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"Loading {files.Length} .xml files from directory {filename}");
|
|
}
|
|
|
|
foreach (string file in files)
|
|
{
|
|
XmlLoadFromFile(file, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners);
|
|
total_processed_maps += processedmaps;
|
|
total_processed_spawners += processedspawners;
|
|
}
|
|
}
|
|
// recursively search subdirectories for more .xml files
|
|
string[] dirs = null;
|
|
try
|
|
{
|
|
dirs = Directory.GetDirectories(filename);
|
|
}
|
|
catch { }
|
|
if (dirs != null && dirs.Length > 0)
|
|
{
|
|
foreach (string dir in dirs)
|
|
{
|
|
XmlLoadFromFile(dir, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners);
|
|
total_processed_maps += processedmaps;
|
|
total_processed_spawners += processedspawners;
|
|
}
|
|
}
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"Loaded a total of {total_processed_maps} .xml files and {filename} spawners from directory {total_processed_spawners}");
|
|
}
|
|
|
|
processedmaps = total_processed_maps;
|
|
processedspawners = total_processed_spawners;
|
|
}
|
|
else
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"{filename} does not exist");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public static void XmlLoadFromFile(string filename, string SpawnerPrefix, Mobile from, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners)
|
|
{
|
|
processedmaps = 0;
|
|
processedspawners = 0;
|
|
|
|
if (from == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
XmlLoadFromFile(filename, SpawnerPrefix, from, from.Location, from.Map, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners);
|
|
}
|
|
|
|
public static void XmlLoadFromFile(string filename, string SpawnerPrefix, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners)
|
|
{
|
|
XmlLoadFromFile(filename, SpawnerPrefix, null, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners);
|
|
|
|
}
|
|
|
|
public static void XmlLoadFromFile(string filename, string SpawnerPrefix, bool loadnew, out int processedmaps, out int processedspawners)
|
|
{
|
|
XmlLoadFromFile(filename, SpawnerPrefix, null, Point3D.Zero, Map.Internal, false, 0, loadnew, out processedmaps, out processedspawners);
|
|
|
|
}
|
|
|
|
|
|
public static void XmlLoadFromStream(Stream fs, string filename, string SpawnerPrefix, Mobile from, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners)
|
|
{
|
|
XmlLoadFromStream(fs, filename, SpawnerPrefix, from, fromloc, frommap, loadrelative, maxrange, loadnew, out processedmaps, out processedspawners, false);
|
|
}
|
|
|
|
public static void XmlLoadFromStream(Stream fs, string filename, string SpawnerPrefix, Mobile from, Point3D fromloc, Map frommap, bool loadrelative, int maxrange, bool loadnew, out int processedmaps, out int processedspawners, bool verbose)
|
|
{
|
|
processedmaps = 0;
|
|
processedspawners = 0;
|
|
|
|
if (fs == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// assign an id that will be used to distinguish the newly loaded spawners by appending it to their name
|
|
Guid newloadid = Guid.NewGuid();
|
|
|
|
int TotalCount = 0;
|
|
int TrammelCount = 0;
|
|
int FeluccaCount = 0;
|
|
int IlshenarCount = 0;
|
|
int MalasCount = 0;
|
|
int TokunoCount = 0;
|
|
int OtherCount = 0;
|
|
bool questionable_spawner = false;
|
|
bool bad_spawner = false;
|
|
int badcount = 0;
|
|
int questionablecount = 0;
|
|
|
|
int failedobjectitemcount = 0;
|
|
int failedsetitemcount = 0;
|
|
int relativex = -1;
|
|
int relativey = -1;
|
|
int relativez = 0;
|
|
Map relativemap = null;
|
|
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"Loading {"XmlSpawner"} objects{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)} from file {filename}.");
|
|
}
|
|
|
|
// Create the data set
|
|
DataSet ds = new DataSet(SpawnDataSetName);
|
|
|
|
// Read in the file
|
|
bool fileerror = false;
|
|
try
|
|
{
|
|
ds.ReadXml(fs);
|
|
}
|
|
catch
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Error reading xml file {filename}");
|
|
}
|
|
|
|
fileerror = true;
|
|
}
|
|
// close the file
|
|
fs.Close();
|
|
if (fileerror)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check that at least a single table was loaded
|
|
if (ds.Tables.Count > 0)
|
|
{
|
|
// Add each spawn point to the current map
|
|
if (ds.Tables[SpawnTablePointName] != null && ds.Tables[SpawnTablePointName].Rows.Count > 0)
|
|
{
|
|
foreach (DataRow dr in ds.Tables[SpawnTablePointName].Rows)
|
|
{
|
|
// load in the spawner info. Certain fields are required and therefore cannot be ignored
|
|
// the exception handler for those will flag bad_spawner and the result will be logged
|
|
|
|
// Each row makes up a single spawner
|
|
string SpawnName = "Spawner";
|
|
try { SpawnName = (string)dr["Name"]; }
|
|
catch { questionable_spawner = true; }
|
|
|
|
if (loadnew)
|
|
{
|
|
// append the new id to the name
|
|
SpawnName = $"{SpawnName}-{newloadid}";
|
|
}
|
|
|
|
// Check if there is any spawner name criteria specified on the load
|
|
if (string.IsNullOrEmpty(SpawnerPrefix) || SpawnName.StartsWith(SpawnerPrefix))
|
|
{
|
|
// Try load the GUID (might not work so create a new GUID)
|
|
Guid SpawnId = Guid.NewGuid();
|
|
if (!loadnew)
|
|
{
|
|
try { SpawnId = new Guid((string)dr["UniqueId"]); }
|
|
catch { }
|
|
}
|
|
else
|
|
{
|
|
// change the dataset guid to the newly created one when new loading
|
|
try
|
|
{
|
|
dr["UniqueId"] = SpawnId;
|
|
}
|
|
catch { Console.WriteLine("unable to set UniqueId"); }
|
|
}
|
|
|
|
int SpawnCentreX = fromloc.X;
|
|
int SpawnCentreY = fromloc.Y;
|
|
int SpawnCentreZ = fromloc.Z;
|
|
|
|
try { SpawnCentreX = int.Parse((string)dr["CentreX"]); }
|
|
catch { bad_spawner = true; }
|
|
try { SpawnCentreY = int.Parse((string)dr["CentreY"]); }
|
|
catch { bad_spawner = true; }
|
|
try { SpawnCentreZ = int.Parse((string)dr["CentreZ"]); }
|
|
catch { bad_spawner = true; }
|
|
|
|
int SpawnX = SpawnCentreX;
|
|
int SpawnY = SpawnCentreY;
|
|
int SpawnWidth = 0;
|
|
int SpawnHeight = 0;
|
|
try { SpawnX = int.Parse((string)dr["X"]); }
|
|
catch { questionable_spawner = true; }
|
|
try { SpawnY = int.Parse((string)dr["Y"]); }
|
|
catch { questionable_spawner = true; }
|
|
try { SpawnWidth = int.Parse((string)dr["Width"]); }
|
|
catch { questionable_spawner = true; }
|
|
try { SpawnHeight = int.Parse((string)dr["Height"]); }
|
|
catch { questionable_spawner = true; }
|
|
|
|
// Try load the InContainer (default to false)
|
|
bool InContainer = false;
|
|
int ContainerX = 0;
|
|
int ContainerY = 0;
|
|
int ContainerZ = 0;
|
|
try { InContainer = bool.Parse((string)dr["InContainer"]); }
|
|
catch { }
|
|
if (InContainer)
|
|
{
|
|
try { ContainerX = int.Parse((string)dr["ContainerX"]); }
|
|
catch { }
|
|
try { ContainerY = int.Parse((string)dr["ContainerY"]); }
|
|
catch { }
|
|
try { ContainerZ = int.Parse((string)dr["ContainerZ"]); }
|
|
catch { }
|
|
}
|
|
|
|
// Get the map (default to the mobiles map) if the relative distance is too great, then use the defined map
|
|
|
|
Map SpawnMap = frommap;
|
|
|
|
string XmlMapName = frommap.Name;
|
|
|
|
//if (!loadrelative && !loadnew)
|
|
{
|
|
// Try to get the "map" field, but in case it doesn't exist, catch and discard the exception
|
|
try { XmlMapName = (string)dr["Map"]; }
|
|
catch { questionable_spawner = true; }
|
|
|
|
// Convert the xml map value to a real map object
|
|
if (string.Compare(XmlMapName, Map.Trammel.Name, true) == 0 || XmlMapName == "Trammel")
|
|
{
|
|
SpawnMap = Map.Trammel;
|
|
TrammelCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Felucca.Name, true) == 0 || XmlMapName == "Felucca")
|
|
{
|
|
SpawnMap = Map.Felucca;
|
|
FeluccaCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Ilshenar.Name, true) == 0 || XmlMapName == "Ilshenar")
|
|
{
|
|
SpawnMap = Map.Ilshenar;
|
|
IlshenarCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Malas.Name, true) == 0 || XmlMapName == "Malas")
|
|
{
|
|
SpawnMap = Map.Malas;
|
|
MalasCount++;
|
|
}
|
|
else if (string.Compare(XmlMapName, Map.Tokuno.Name, true) == 0 || XmlMapName == "Tokuno")
|
|
{
|
|
SpawnMap = Map.Tokuno;
|
|
TokunoCount++;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
SpawnMap = Map.Parse(XmlMapName);
|
|
}
|
|
catch { }
|
|
OtherCount++;
|
|
}
|
|
}
|
|
|
|
// test to see whether the distance between the relative center point and the spawner is too great. If so then dont do relative
|
|
if (relativex == -1 && relativey == -1)
|
|
{
|
|
// the first xml entry in the file will determine the origin
|
|
relativex = SpawnCentreX;
|
|
relativey = SpawnCentreY;
|
|
relativez = SpawnCentreZ;
|
|
|
|
// and also the relative map to relocate from
|
|
relativemap = SpawnMap;
|
|
}
|
|
|
|
int SpawnRelZ = 0;
|
|
int OrigZ = SpawnCentreZ;
|
|
if (loadrelative && Math.Abs(relativex - SpawnCentreX) <= maxrange && Math.Abs(relativey - SpawnCentreY) <= maxrange
|
|
&& SpawnMap == relativemap)
|
|
{
|
|
// its within range so shift it
|
|
SpawnCentreX -= relativex - fromloc.X;
|
|
SpawnCentreY -= relativey - fromloc.Y;
|
|
SpawnX -= relativex - fromloc.X;
|
|
SpawnY -= relativey - fromloc.Y;
|
|
// force it to autosearch for Z when it places it but hold onto relative Z info just in case it can be placed there
|
|
SpawnRelZ = relativez - fromloc.Z;
|
|
SpawnCentreZ = short.MinValue;
|
|
}
|
|
|
|
// if relative loading has been specified, see if the loaded map is the same as the relativemap and relocate.
|
|
// if it doesnt match then just leave it
|
|
if (loadrelative && relativemap == SpawnMap)
|
|
{
|
|
SpawnMap = frommap;
|
|
}
|
|
|
|
|
|
if (SpawnMap == Map.Internal)
|
|
{
|
|
bad_spawner = true;
|
|
}
|
|
|
|
// Try load the IsRelativeHomeRange (default to true)
|
|
bool SpawnIsRelativeHomeRange = true;
|
|
try { SpawnIsRelativeHomeRange = bool.Parse((string)dr["IsHomeRangeRelative"]); }
|
|
catch { }
|
|
|
|
|
|
int SpawnHomeRange = 5;
|
|
try { SpawnHomeRange = int.Parse((string)dr["Range"]); }
|
|
catch { questionable_spawner = true; }
|
|
int SpawnMaxCount = 1;
|
|
try { SpawnMaxCount = int.Parse((string)dr["MaxCount"]); }
|
|
catch { questionable_spawner = true; }
|
|
|
|
//deal with double format for delay. default is the old minute format
|
|
bool delay_in_sec = false;
|
|
try { delay_in_sec = bool.Parse((string)dr["DelayInSec"]); }
|
|
catch { }
|
|
TimeSpan SpawnMinDelay = TimeSpan.FromMinutes(5);
|
|
TimeSpan SpawnMaxDelay = TimeSpan.FromMinutes(10);
|
|
|
|
|
|
if (delay_in_sec)
|
|
{
|
|
try { SpawnMinDelay = TimeSpan.FromSeconds(int.Parse((string)dr["MinDelay"])); }
|
|
catch { }
|
|
try { SpawnMaxDelay = TimeSpan.FromSeconds(int.Parse((string)dr["MaxDelay"])); }
|
|
catch { }
|
|
}
|
|
else
|
|
{
|
|
try { SpawnMinDelay = TimeSpan.FromMinutes(int.Parse((string)dr["MinDelay"])); }
|
|
catch { }
|
|
try { SpawnMaxDelay = TimeSpan.FromMinutes(int.Parse((string)dr["MaxDelay"])); }
|
|
catch { }
|
|
}
|
|
TimeSpan SpawnMinRefractory = TimeSpan.FromMinutes(0);
|
|
try { SpawnMinRefractory = TimeSpan.FromMinutes(double.Parse((string)dr["MinRefractory"])); }
|
|
catch { }
|
|
|
|
TimeSpan SpawnMaxRefractory = TimeSpan.FromMinutes(0);
|
|
try { SpawnMaxRefractory = TimeSpan.FromMinutes(double.Parse((string)dr["MaxRefractory"])); }
|
|
catch { }
|
|
|
|
TimeSpan SpawnTODStart = TimeSpan.FromMinutes(0);
|
|
try { SpawnTODStart = TimeSpan.FromMinutes(double.Parse((string)dr["TODStart"])); }
|
|
catch { }
|
|
|
|
TimeSpan SpawnTODEnd = TimeSpan.FromMinutes(0);
|
|
try { SpawnTODEnd = TimeSpan.FromMinutes(double.Parse((string)dr["TODEnd"])); }
|
|
catch { }
|
|
|
|
int todmode = (int)TODModeType.Realtime;
|
|
TODModeType SpawnTODMode = TODModeType.Realtime;
|
|
try { todmode = int.Parse((string)dr["TODMode"]); }
|
|
catch { }
|
|
switch (todmode)
|
|
{
|
|
case (int)TODModeType.Gametime:
|
|
{
|
|
SpawnTODMode = TODModeType.Gametime;
|
|
break;
|
|
}
|
|
case (int)TODModeType.Realtime:
|
|
{
|
|
SpawnTODMode = TODModeType.Realtime;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int SpawnKillReset = defKillReset;
|
|
try { SpawnKillReset = int.Parse((string)dr["KillReset"]); }
|
|
catch { }
|
|
|
|
string SpawnProximityMessage = null;
|
|
// proximity message
|
|
try { SpawnProximityMessage = (string)dr["ProximityTriggerMessage"]; }
|
|
catch { }
|
|
|
|
string SpawnItemTriggerName = null;
|
|
try { SpawnItemTriggerName = (string)dr["ItemTriggerName"]; }
|
|
catch { }
|
|
|
|
string SpawnNoItemTriggerName = null;
|
|
try { SpawnNoItemTriggerName = (string)dr["NoItemTriggerName"]; }
|
|
catch { }
|
|
|
|
string SpawnSpeechTrigger = null;
|
|
try { SpawnSpeechTrigger = (string)dr["SpeechTrigger"]; }
|
|
catch { }
|
|
|
|
string SpawnSkillTrigger = null;
|
|
try { SpawnSkillTrigger = (string)dr["SkillTrigger"]; }
|
|
catch { }
|
|
|
|
string SpawnMobTriggerName = null;
|
|
try { SpawnMobTriggerName = (string)dr["MobTriggerName"]; }
|
|
catch { }
|
|
string SpawnMobPropertyName = null;
|
|
try { SpawnMobPropertyName = (string)dr["MobPropertyName"]; }
|
|
catch { }
|
|
string SpawnPlayerPropertyName = null;
|
|
try { SpawnPlayerPropertyName = (string)dr["PlayerPropertyName"]; }
|
|
catch { }
|
|
|
|
double SpawnTriggerProbability = 1;
|
|
try { SpawnTriggerProbability = double.Parse((string)dr["TriggerProbability"]); }
|
|
catch { }
|
|
|
|
int SpawnSequentialSpawning = -1;
|
|
try { SpawnSequentialSpawning = int.Parse((string)dr["SequentialSpawning"]); }
|
|
catch { }
|
|
|
|
string SpawnRegionName = null;
|
|
try { SpawnRegionName = (string)dr["RegionName"]; }
|
|
catch { }
|
|
|
|
string SpawnConfigFile = null;
|
|
try { SpawnConfigFile = (string)dr["ConfigFile"]; }
|
|
catch { }
|
|
|
|
bool SpawnAllowGhost = false;
|
|
try { SpawnAllowGhost = bool.Parse((string)dr["AllowGhostTriggering"]); }
|
|
catch { }
|
|
|
|
bool SpawnAllowNPC = false;
|
|
try { SpawnAllowNPC = bool.Parse((string)dr["AllowNPCTriggering"]); }
|
|
catch { }
|
|
|
|
bool SpawnSpawnOnTrigger = false;
|
|
try { SpawnSpawnOnTrigger = bool.Parse((string)dr["SpawnOnTrigger"]); }
|
|
catch { }
|
|
|
|
bool SpawnSmartSpawning = false;
|
|
try { SpawnSmartSpawning = bool.Parse((string)dr["SmartSpawning"]); }
|
|
catch { }
|
|
|
|
bool TickReset = false;
|
|
try { TickReset = bool.Parse((string)dr["TickReset"]); }
|
|
catch { }
|
|
|
|
string SpawnObjectPropertyName = null;
|
|
try { SpawnObjectPropertyName = (string)dr["ObjectPropertyName"]; }
|
|
catch { }
|
|
|
|
// we will assign this during the self-reference resolution pass
|
|
Item SpawnSetPropertyItem = null;
|
|
|
|
// we will assign this during the self-reference resolution pass
|
|
Item SpawnObjectPropertyItem = null;
|
|
|
|
// read the duration parameter from the xml file
|
|
// but older files wont have it so deal with that condition and set it to the default of "0", i.e. infinite duration
|
|
// Try to get the "Duration" field, but in case it doesn't exist, catch and discard the exception
|
|
TimeSpan SpawnDuration = TimeSpan.FromMinutes(0);
|
|
try { SpawnDuration = TimeSpan.FromMinutes(double.Parse((string)dr["Duration"])); }
|
|
catch { }
|
|
|
|
TimeSpan SpawnDespawnTime = TimeSpan.FromHours(0);
|
|
try { SpawnDespawnTime = TimeSpan.FromHours(double.Parse((string)dr["DespawnTime"])); }
|
|
catch { }
|
|
int SpawnProximityRange = -1;
|
|
// Try to get the "ProximityRange" field, but in case it doesn't exist, catch and discard the exception
|
|
try { SpawnProximityRange = int.Parse((string)dr["ProximityRange"]); }
|
|
catch { }
|
|
|
|
int SpawnProximityTriggerSound = 0;
|
|
// Try to get the "ProximityTriggerSound" field, but in case it doesn't exist, catch and discard the exception
|
|
try { SpawnProximityTriggerSound = int.Parse((string)dr["ProximityTriggerSound"]); }
|
|
catch { }
|
|
|
|
int SpawnAmount = 1;
|
|
try { SpawnAmount = int.Parse((string)dr["Amount"]); }
|
|
catch { }
|
|
|
|
bool SpawnExternalTriggering = false;
|
|
try { SpawnExternalTriggering = bool.Parse((string)dr["ExternalTriggering"]); }
|
|
catch { }
|
|
|
|
string waypointstr = null;
|
|
try { waypointstr = (string)dr["Waypoint"]; }
|
|
catch { }
|
|
|
|
WayPoint SpawnWaypoint = GetWaypoint(waypointstr);
|
|
|
|
int SpawnTeam = 0;
|
|
try { SpawnTeam = int.Parse((string)dr["Team"]); }
|
|
catch { questionable_spawner = true; }
|
|
bool SpawnIsGroup = false;
|
|
try { SpawnIsGroup = bool.Parse((string)dr["IsGroup"]); }
|
|
catch { questionable_spawner = true; }
|
|
bool SpawnIsRunning = false;
|
|
try { SpawnIsRunning = bool.Parse((string)dr["IsRunning"]); }
|
|
catch { questionable_spawner = true; }
|
|
// try loading the new spawn specifications first
|
|
SpawnObject[] Spawns = new SpawnObject[0];
|
|
bool havenew = true;
|
|
try { Spawns = SpawnObject.LoadSpawnObjectsFromString2((string)dr["Objects2"]); }
|
|
catch { havenew = false; }
|
|
if (!havenew)
|
|
{
|
|
// try loading the new spawn specifications
|
|
try { Spawns = SpawnObject.LoadSpawnObjectsFromString((string)dr["Objects"]); }
|
|
catch { questionable_spawner = true; }
|
|
// can only have one of these defined
|
|
}
|
|
|
|
// do a check on the location of the spawner
|
|
if (!IsValidMapLocation(SpawnCentreX, SpawnCentreY, SpawnMap))
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Invalid location '{SpawnName}' at [{SpawnCentreX} {SpawnCentreY}] in {XmlMapName}");
|
|
}
|
|
|
|
bad_spawner = true;
|
|
}
|
|
|
|
// Check if this spawner already exists
|
|
XmlSpawner OldSpawner = null;
|
|
bool found_container = false;
|
|
bool found_spawner = false;
|
|
Container spawn_container = null;
|
|
if (!bad_spawner)
|
|
{
|
|
foreach (Item i in World.Items.Values)
|
|
{
|
|
if (i is XmlSpawner checkXmlSpawner)
|
|
{
|
|
// Check if the spawners GUID is the same as the one being loaded
|
|
// and that the spawners map is the same as the one being loaded
|
|
if (checkXmlSpawner.UniqueId == SpawnId.ToString()
|
|
/* && (CheckXmlSpawner.Map == SpawnMap || loadrelative)*/)
|
|
{
|
|
OldSpawner = checkXmlSpawner;
|
|
found_spawner = true;
|
|
}
|
|
}
|
|
|
|
//look for containers with the spawn coordinates if the incontainer flag is set
|
|
if (InContainer && !found_container && i is Container container && SpawnCentreX == container.Location.X && SpawnCentreY == container.Location.Y &&
|
|
(SpawnCentreZ == container.Location.Z || SpawnCentreZ == short.MinValue))
|
|
{
|
|
// assume this is the container that the spawner was in
|
|
found_container = true;
|
|
spawn_container = container;
|
|
}
|
|
// ok we can break if we have handled both the spawner and any containers
|
|
if (found_spawner && (found_container || !InContainer))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// test to see whether the spawner specification was valid, bad, or questionable
|
|
if (bad_spawner)
|
|
{
|
|
badcount++;
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, "Invalid spawner");
|
|
}
|
|
|
|
// log it
|
|
long fileposition = -1;
|
|
try { fileposition = fs.Position; }
|
|
catch { }
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badxml.log", true))
|
|
{
|
|
op.WriteLine("# Invalid spawner : {0}: Fileposition {1} {2}", Core.Now, fileposition, filename);
|
|
op.WriteLine();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
if (questionable_spawner)
|
|
{
|
|
questionablecount++;
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Questionable spawner '{SpawnName}' at [{SpawnCentreX} {SpawnCentreY}] in {XmlMapName}");
|
|
}
|
|
|
|
// log it
|
|
long fileposition = -1;
|
|
try { fileposition = fs.Position; }
|
|
catch { }
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badxml.log", true))
|
|
{
|
|
op.WriteLine("# Questionable spawner : {0}: Format: X Y Z Map SpawnerName Fileposition Xmlfile", Core.Now);
|
|
op.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}", SpawnCentreX, SpawnCentreY, SpawnCentreZ, XmlMapName, SpawnName, fileposition, filename);
|
|
op.WriteLine();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
if (!bad_spawner)
|
|
{
|
|
// Delete the old spawner if it exists
|
|
if (OldSpawner != null)
|
|
{
|
|
OldSpawner.Delete();
|
|
}
|
|
|
|
// Create the new spawner
|
|
XmlSpawner TheSpawn = new XmlSpawner(SpawnId, SpawnX, SpawnY, SpawnWidth, SpawnHeight, SpawnName, SpawnMaxCount,
|
|
SpawnMinDelay, SpawnMaxDelay, SpawnDuration, SpawnProximityRange, SpawnProximityTriggerSound, SpawnAmount,
|
|
SpawnTeam, SpawnHomeRange, SpawnIsRelativeHomeRange, Spawns, SpawnMinRefractory, SpawnMaxRefractory, SpawnTODStart,
|
|
SpawnTODEnd, SpawnObjectPropertyItem, SpawnObjectPropertyName, SpawnProximityMessage, SpawnItemTriggerName, SpawnNoItemTriggerName,
|
|
SpawnSpeechTrigger, SpawnMobTriggerName, SpawnMobPropertyName, SpawnPlayerPropertyName, SpawnTriggerProbability,
|
|
SpawnSetPropertyItem, SpawnIsGroup, SpawnTODMode, SpawnKillReset, SpawnExternalTriggering, SpawnSequentialSpawning,
|
|
SpawnRegionName, SpawnAllowGhost, SpawnAllowNPC, SpawnSpawnOnTrigger, SpawnConfigFile, SpawnDespawnTime, SpawnSkillTrigger, SpawnSmartSpawning, SpawnWaypoint)
|
|
{
|
|
m_DisableGlobalAutoReset = TickReset
|
|
};
|
|
|
|
// Try to find a valid Z height if required (SpawnCentreZ = short.MinValue)
|
|
int NewZ = 0;
|
|
|
|
// Check if relative loading is set. If so then try loading at the z-offset position first with no surface requirement, then try auto
|
|
/*if (loadrelative && SpawnMap.CanFit(SpawnCentreX, SpawnCentreY, OrigZ - SpawnRelZ, SpawnFitSize,true, false,false)) */
|
|
|
|
if (loadrelative && HasTileSurface(SpawnMap, SpawnCentreX, SpawnCentreY, OrigZ - SpawnRelZ))
|
|
{
|
|
NewZ = OrigZ - SpawnRelZ;
|
|
}
|
|
else if (SpawnCentreZ == short.MinValue)
|
|
{
|
|
NewZ = SpawnMap.GetAverageZ(SpawnCentreX, SpawnCentreY);
|
|
|
|
if (SpawnMap.CanFit(SpawnCentreX, SpawnCentreY, NewZ, SpawnFitSize) == false)
|
|
{
|
|
for (int x = 1; x <= 39; x++)
|
|
{
|
|
if (SpawnMap.CanFit(SpawnCentreX, SpawnCentreY, NewZ + x, SpawnFitSize))
|
|
{
|
|
NewZ += x;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This spawn point already has a defined Z location, so use it
|
|
NewZ = SpawnCentreZ;
|
|
}
|
|
|
|
// if this is a container held spawner, drop it in the container
|
|
if (found_container && spawn_container != null && !spawn_container.Deleted)
|
|
{
|
|
TheSpawn.Location = new Point3D(ContainerX, ContainerY, ContainerZ);
|
|
spawn_container.AddItem(TheSpawn);
|
|
}
|
|
else
|
|
{
|
|
// disable the X_Y adjustments in OnLocationChange
|
|
IgnoreLocationChange = true;
|
|
TheSpawn.MoveToWorld(new Point3D(SpawnCentreX, SpawnCentreY, NewZ), SpawnMap);
|
|
}
|
|
|
|
// reset the spawner
|
|
TheSpawn.Reset();
|
|
TheSpawn.Running = SpawnIsRunning;
|
|
|
|
// update subgroup-specific next spawn times
|
|
TheSpawn.NextSpawn = TimeSpan.Zero;
|
|
TheSpawn.ResetNextSpawnTimes();
|
|
|
|
|
|
// Send a message to the client that the spawner is created
|
|
if (from != null && verbose)
|
|
{
|
|
from.SendMessage(188, $"Created '{TheSpawn.Name}' in {TheSpawn.Map.Name} at {TheSpawn.Location}");
|
|
}
|
|
|
|
// Do a total respawn
|
|
//TheSpawn.Respawn();
|
|
|
|
// Increment the count
|
|
TotalCount++;
|
|
}
|
|
bad_spawner = false;
|
|
questionable_spawner = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (from != null)
|
|
{
|
|
from.SendMessage("Resolving spawner self references");
|
|
}
|
|
|
|
if (ds.Tables[SpawnTablePointName] != null && ds.Tables[SpawnTablePointName].Rows.Count > 0)
|
|
{
|
|
foreach (DataRow dr in ds.Tables[SpawnTablePointName].Rows)
|
|
{
|
|
// Try load the GUID
|
|
bool badid = false;
|
|
Guid SpawnId = Guid.NewGuid();
|
|
try { SpawnId = new Guid((string)dr["UniqueId"]); }
|
|
catch { badid = true; }
|
|
if (badid)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Get the map
|
|
Map SpawnMap = frommap;
|
|
string XmlMapName = frommap.Name;
|
|
|
|
if (!loadrelative)
|
|
{
|
|
try { XmlMapName = (string)dr["Map"]; }
|
|
catch { }
|
|
|
|
// Convert the xml map value to a real map object
|
|
try
|
|
{
|
|
SpawnMap = Map.Parse(XmlMapName);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
bool found_spawner = false;
|
|
XmlSpawner OldSpawner = null;
|
|
foreach (Item i in World.Items.Values)
|
|
{
|
|
if (i is XmlSpawner checkXmlSpawner)
|
|
{
|
|
// Check if the spawners GUID is the same as the one being loaded
|
|
// and that the spawners map is the same as the one being loaded
|
|
if (checkXmlSpawner.UniqueId == SpawnId.ToString()
|
|
/* && (CheckXmlSpawner.Map == SpawnMap || loadrelative) */)
|
|
{
|
|
OldSpawner = checkXmlSpawner;
|
|
found_spawner = true;
|
|
}
|
|
}
|
|
|
|
if (found_spawner)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found_spawner && OldSpawner != null && !OldSpawner.Deleted)
|
|
{
|
|
// resolve item name references since they may have referred to spawners that were just created
|
|
string setObjectName = null;
|
|
try { setObjectName = (string)dr["SetPropertyItemName"]; }
|
|
catch { }
|
|
if (!string.IsNullOrEmpty(setObjectName))
|
|
{
|
|
// try to parse out the type information if it has also been saved
|
|
string[] typeargs = setObjectName.Split(",".ToCharArray(), 2);
|
|
string typestr = null;
|
|
string namestr = setObjectName;
|
|
|
|
if (typeargs.Length > 1)
|
|
{
|
|
namestr = typeargs[0];
|
|
typestr = typeargs[1];
|
|
}
|
|
|
|
// if this is a new load then assume that it will be referring to another newly loaded object so append the newloadid
|
|
if (loadnew)
|
|
{
|
|
string tmpsetObjectName = $"{namestr}-{newloadid}";
|
|
OldSpawner.m_SetPropertyItem = BaseXmlSpawner.FindItemByName(null, tmpsetObjectName, typestr);
|
|
}
|
|
// if this fails then try the original
|
|
if (OldSpawner.m_SetPropertyItem == null)
|
|
{
|
|
OldSpawner.m_SetPropertyItem = BaseXmlSpawner.FindItemByName(null, namestr, typestr);
|
|
}
|
|
if (OldSpawner.m_SetPropertyItem == null)
|
|
{
|
|
failedsetitemcount++;
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Failed to initialize SetItemProperty Object '{setObjectName}' on ' '{OldSpawner.Name}' at [{OldSpawner.Location.X} {OldSpawner.Location.Y}] in {OldSpawner.Map}");
|
|
}
|
|
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badxml.log", true))
|
|
{
|
|
op.WriteLine("# Failed SetItemProperty Object initialization : {0}: Format: ObjectName X Y Z Map SpawnerName Xmlfile",
|
|
Core.Now);
|
|
op.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}",
|
|
setObjectName, OldSpawner.Location.X, OldSpawner.Location.Y, OldSpawner.Location.Z, OldSpawner.Map, OldSpawner.Name, filename);
|
|
op.WriteLine();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
string triggerObjectName = null;
|
|
try { triggerObjectName = (string)dr["ObjectPropertyItemName"]; }
|
|
catch { }
|
|
|
|
if (!string.IsNullOrEmpty(triggerObjectName))
|
|
{
|
|
string[] typeargs = triggerObjectName.Split(",".ToCharArray(), 2);
|
|
string typestr = null;
|
|
string namestr = triggerObjectName;
|
|
|
|
if (typeargs.Length > 1)
|
|
{
|
|
namestr = typeargs[0];
|
|
typestr = typeargs[1];
|
|
}
|
|
|
|
// if this is a new load then assume that it will be referring to another newly loaded object so append the newloadid
|
|
if (loadnew)
|
|
{
|
|
string tmptriggerObjectName = $"{namestr}-{newloadid}";
|
|
OldSpawner.m_ObjectPropertyItem = BaseXmlSpawner.FindItemByName(null, tmptriggerObjectName, typestr);
|
|
}
|
|
// if this fails then try the original
|
|
if (OldSpawner.m_ObjectPropertyItem == null)
|
|
{
|
|
OldSpawner.m_ObjectPropertyItem = BaseXmlSpawner.FindItemByName(null, namestr, typestr);
|
|
}
|
|
if (OldSpawner.m_ObjectPropertyItem == null)
|
|
{
|
|
failedobjectitemcount++;
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Failed to initialize TriggerObject '{triggerObjectName}' on ' '{OldSpawner.Name}' at [{OldSpawner.Location.X} {OldSpawner.Location.Y}] in {OldSpawner.Map}");
|
|
}
|
|
|
|
// log it
|
|
try
|
|
{
|
|
using (StreamWriter op = new StreamWriter("badxml.log", true))
|
|
{
|
|
op.WriteLine("# Failed TriggerObject initialization : {0}: Format: ObjectName X Y Z Map SpawnerName Xmlfile",
|
|
Core.Now);
|
|
op.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}",
|
|
triggerObjectName, OldSpawner.Location.X, OldSpawner.Location.Y, OldSpawner.Location.Z, OldSpawner.Map, OldSpawner.Name, filename);
|
|
op.WriteLine();
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// close the file
|
|
try
|
|
{
|
|
fs.Close();
|
|
}
|
|
catch { }
|
|
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"{TotalCount} spawner(s) were created from file {filename} [Trammel={TrammelCount}, Felucca={FeluccaCount}, Ilshenar={IlshenarCount}, Malas={MalasCount}, Tokuno={TokunoCount} Other={OtherCount}].");
|
|
}
|
|
|
|
if (failedobjectitemcount > 0)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Failed to initialize TriggerObjects in {failedobjectitemcount} spawners. Saved to 'badxml.log'");
|
|
}
|
|
}
|
|
if (failedsetitemcount > 0)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"Failed to initialize SetItemProperty Objects in {failedsetitemcount} spawners. Saved to 'badxml.log'");
|
|
}
|
|
}
|
|
if (badcount > 0)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"{badcount} bad spawners detected. Saved to 'badxml.log'");
|
|
}
|
|
}
|
|
if (questionablecount > 0)
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage(33, $"{questionablecount} questionable spawners detected. Saved to 'badxml.log'");
|
|
}
|
|
}
|
|
processedmaps = 1;
|
|
processedspawners = TotalCount;
|
|
|
|
}
|
|
|
|
public static string LocateFile(string filename)
|
|
{
|
|
bool found = false;
|
|
|
|
string dirname = null;
|
|
|
|
if (Directory.Exists(XmlSpawnDir))
|
|
{
|
|
// get it from the defaults directory if it exists
|
|
dirname = $"{XmlSpawnDir}/{filename}";
|
|
found = File.Exists(dirname) || Directory.Exists(dirname);
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
// otherwise just get it from the main installation dir
|
|
dirname = filename;
|
|
}
|
|
|
|
return dirname;
|
|
}
|
|
|
|
[Usage("XmlNewLoad <SpawnFile or directory> [SpawnerPrefixFilter]")]
|
|
[Description("Loads new XmlSpawner objects with new GUIDs (no replacement) into the current map of the player.")]
|
|
public static void NewLoad_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e.Mobile.AccessLevel >= DiskAccessLevel)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
string filename = LocateFile(e.Arguments[0]);
|
|
|
|
// Spawner load criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
|
|
// Check if there is an argument provided (load criteria)
|
|
if (e.Arguments.Length > 1)
|
|
{
|
|
SpawnerPrefix = e.Arguments[1];
|
|
}
|
|
|
|
XmlLoadFromFile(filename, SpawnerPrefix, e.Mobile, false, 0, true, out _, out _);
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile or directory> [SpawnerPrefixFilter]");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
[Usage("XmlLoad <SpawnFile or directory> [SpawnerPrefixFilter]")]
|
|
[Description("Loads XmlSpawner objects (replacing existing spawners with matching GUIDs) into the proper map as defined in the file supplied.")]
|
|
public static void Load_OnCommand(CommandEventArgs e)
|
|
{
|
|
var m = e.Mobile;
|
|
|
|
if (m == null || m.AccessLevel >= DiskAccessLevel)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
string filename = LocateFile(e.Arguments[0]);
|
|
|
|
// Spawner load criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
|
|
// Check if there is an argument provided (load criteria)
|
|
if (e.Arguments.Length > 1)
|
|
{
|
|
SpawnerPrefix = e.Arguments[1];
|
|
}
|
|
|
|
XmlLoadFromFile(filename, SpawnerPrefix, m, false, 0, false, out _, out _);
|
|
}
|
|
else if (m != null)
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile or directory> [SpawnerPrefixFilter]");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
[Usage("XmlNewLoadHere <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]")]
|
|
[Description("Loads new XmlSpawner objects with new GUIDs (no replacement) to the current map and location of the player. Spawners beyond maxrange (default=48 tiles) are not moved relative to the player")]
|
|
public static void NewLoadHere_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e.Mobile.AccessLevel >= DiskAccessLevel)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
string filename = LocateFile(e.Arguments[0]);
|
|
|
|
// Spawner load criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
bool badargs = false;
|
|
int maxrange = 48;
|
|
|
|
// Check if there is an argument provided (load criteria)
|
|
try
|
|
{
|
|
// Check if there is an argument provided (load criteria)
|
|
for (int nxtarg = 1; nxtarg < e.Arguments.Length; nxtarg++)
|
|
{
|
|
// is it a maxrange option?
|
|
if (e.Arguments[nxtarg].ToLower() == "-maxrange")
|
|
{
|
|
maxrange = int.Parse(e.Arguments[++nxtarg]);
|
|
}
|
|
else
|
|
{
|
|
SpawnerPrefix = e.Arguments[nxtarg];
|
|
}
|
|
}
|
|
}
|
|
catch { e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]"); badargs = true; }
|
|
|
|
if (!badargs)
|
|
{
|
|
XmlLoadFromFile(filename, SpawnerPrefix, e.Mobile, true, maxrange, true, out _, out _);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
[Usage("XmlLoadHere <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]")]
|
|
[Description("Loads XmlSpawner objects to the current map and location of the player. Spawners beyond maxrange (default=48 tiles) are not moved relative to the player")]
|
|
public static void LoadHere_OnCommand(CommandEventArgs e)
|
|
{
|
|
if (e.Mobile.AccessLevel >= DiskAccessLevel)
|
|
{
|
|
if (e.Arguments.Length >= 1)
|
|
{
|
|
string filename = LocateFile(e.Arguments[0]);
|
|
|
|
// Spawner load criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
bool badargs = false;
|
|
int maxrange = 48;
|
|
|
|
try
|
|
{
|
|
// Check if there is an argument provided (load criteria)
|
|
for (int nxtarg = 1; nxtarg < e.Arguments.Length; nxtarg++)
|
|
{
|
|
// is it a maxrange option?
|
|
if (e.Arguments[nxtarg].ToLower() == "-maxrange")
|
|
{
|
|
maxrange = int.Parse(e.Arguments[++nxtarg]);
|
|
}
|
|
else
|
|
{
|
|
SpawnerPrefix = e.Arguments[nxtarg];
|
|
}
|
|
}
|
|
}
|
|
catch { e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]"); badargs = true; }
|
|
|
|
if (!badargs)
|
|
{
|
|
XmlLoadFromFile(filename, SpawnerPrefix, e.Mobile, true, maxrange, false, out _, out _);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile or directory> [SpawnerPrefixFilter][-maxrange range]");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
[Usage("XmlSaveOld <SpawnFile> [SpawnerPrefixFilter]")]
|
|
[Description("Saves all XmlSpawner objects from the current map into the file supplied in the old xmlspawner format.")]
|
|
public static void SaveOld_OnCommand(CommandEventArgs e)
|
|
{
|
|
SaveSpawns(e, false, true);
|
|
}
|
|
|
|
[Usage("XmlSpawnerSave <SpawnFile> [SpawnerPrefixFilter]")]
|
|
[Description("Saves all XmlSpawner objects from the current map into the file supplied.")]
|
|
public static void Save_OnCommand(CommandEventArgs e)
|
|
{
|
|
SaveSpawns(e, false, false);
|
|
}
|
|
|
|
[Usage("XmlSpawnerSaveAll <SpawnFile> [SpawnerPrefixFilter]")]
|
|
[Description("Saves ALL XmlSpawner objects from the entire world into the file supplied.")]
|
|
public static void SaveAll_OnCommand(CommandEventArgs e)
|
|
{
|
|
SaveSpawns(e, true, false);
|
|
}
|
|
|
|
public class XmlSaveSingle : BaseCommand
|
|
{
|
|
public XmlSaveSingle()
|
|
{
|
|
AccessLevel = DiskAccessLevel;
|
|
Supports = CommandSupport.Single;
|
|
Commands = new[] { "XmlSaveSingle" };
|
|
ObjectTypes = ObjectTypes.Items;
|
|
Usage = "XmlSaveSingle <filename>";
|
|
Description = "Saves single xmlspawner to specified file.";
|
|
}
|
|
|
|
public override void Execute(CommandEventArgs e, object obj)
|
|
{
|
|
if (e == null || e.Mobile == null || e.Arguments == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.Arguments.Length < 1)
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile> (without spaces!!)");
|
|
return;
|
|
}
|
|
|
|
string filename = e.Arguments[0];
|
|
|
|
XmlSpawner xmlspawner = obj as XmlSpawner;
|
|
|
|
if (xmlspawner == null)
|
|
{
|
|
e.Mobile.SendMessage("You can select only XmlSpawner objects!");
|
|
return;
|
|
}
|
|
|
|
Mobile m = e.Mobile;
|
|
|
|
CommandLogging.WriteLine(m, $"{m.AccessLevel} {CommandLogging.Format(m)} Saving XmlSpawner {CommandLogging.Format(xmlspawner)} on file {CommandLogging.Format(filename)}");
|
|
SaveSpawns(m, xmlspawner, filename);
|
|
}
|
|
}
|
|
|
|
private static void SaveSpawns(Mobile m, XmlSpawner xmlspawner, string filename)
|
|
{
|
|
if (m.AccessLevel < DiskAccessLevel)
|
|
{
|
|
m.SendMessage("You do not have rights to perform this command.");
|
|
return;
|
|
}
|
|
|
|
string dirname;
|
|
|
|
if (Directory.Exists(XmlSpawnDir) && filename != null && !filename.StartsWith("/") && !filename.StartsWith("\\"))
|
|
{
|
|
// put it in the defaults directory if it exists
|
|
dirname = $"{XmlSpawnDir}/{filename}";
|
|
}
|
|
else
|
|
{
|
|
// otherwise just put it in the main installation dir
|
|
dirname = filename;
|
|
}
|
|
|
|
m.SendMessage($"Saving object in folder {dirname} - file {filename} - spawner {xmlspawner}.");
|
|
|
|
List<XmlSpawner> saveslist = new List<XmlSpawner>(1);
|
|
saveslist.Add(xmlspawner);
|
|
SaveSpawnList(m, saveslist, dirname, false, true);
|
|
}
|
|
|
|
private static void SaveSpawns(CommandEventArgs e, bool SaveAllMaps, bool oldformat)
|
|
{
|
|
if (e == null || e.Mobile == null || e.Arguments == null || e.Arguments.Length < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.Mobile.AccessLevel < DiskAccessLevel)
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
return;
|
|
}
|
|
|
|
if (e.Arguments != null && e.Arguments.Length < 1)
|
|
{
|
|
e.Mobile.SendMessage($"Usage: {e.Command} <SpawnFile> [SpawnerPrefixFilter]");
|
|
return;
|
|
}
|
|
|
|
// Spawner save criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
|
|
// Check if there is an argument provided (save criteria)
|
|
if (e.Arguments.Length > 1)
|
|
{
|
|
SpawnerPrefix = e.Arguments[1];
|
|
}
|
|
|
|
string filename = e.Arguments[0];
|
|
|
|
string dirname;
|
|
if (Directory.Exists(XmlSpawnDir) && filename != null && !filename.StartsWith("/") && !filename.StartsWith("\\"))
|
|
{
|
|
// put it in the defaults directory if it exists
|
|
dirname = $"{XmlSpawnDir}/{filename}";
|
|
}
|
|
else
|
|
{
|
|
// otherwise just put it in the main installation dir
|
|
dirname = filename;
|
|
}
|
|
|
|
if (SaveAllMaps)
|
|
{
|
|
e.Mobile.SendMessage(
|
|
$"Saving XmlSpawner objects{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)} to file {dirname} from {e.Mobile.Map}."
|
|
);
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage(
|
|
$"Saving XmlSpawner obejcts{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)} to file {dirname} from the entire world."
|
|
);
|
|
}
|
|
|
|
|
|
List<XmlSpawner> saveslist = new List<XmlSpawner>();
|
|
|
|
// Add each spawn point to the list
|
|
foreach (Item i in World.Items.Values)
|
|
{
|
|
if (i is XmlSpawner spawner && !spawner.Deleted && (SaveAllMaps || spawner.Map == e.Mobile.Map)
|
|
//check for mob carried spawners and ignore them
|
|
&& !(spawner.RootParent is Mobile)
|
|
&& (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || spawner.Name != null && spawner.Name.StartsWith(SpawnerPrefix)))
|
|
{
|
|
saveslist.Add(spawner);
|
|
}
|
|
}
|
|
|
|
// save the list
|
|
SaveSpawnList(e.Mobile, saveslist, dirname, oldformat, true);
|
|
}
|
|
|
|
public static bool SaveSpawnList(List<XmlSpawner> savelist, Stream stream) => SaveSpawnList(null, savelist, null, stream, false, false);
|
|
|
|
public static bool SaveSpawnList(Mobile from, List<XmlSpawner> savelist, string dirname, bool oldformat, bool verbose)
|
|
{
|
|
if (string.IsNullOrEmpty(dirname))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
|
|
bool save_ok = true;
|
|
FileStream fs = null;
|
|
|
|
try
|
|
{
|
|
// Create the FileStream to write with.
|
|
fs = new FileStream(dirname, FileMode.Create);
|
|
}
|
|
catch
|
|
{
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"Error creating file {dirname}");
|
|
}
|
|
|
|
save_ok = false;
|
|
}
|
|
|
|
// so far so good
|
|
if (save_ok)
|
|
{
|
|
save_ok = SaveSpawnList(from, savelist, dirname, fs, oldformat, verbose);
|
|
}
|
|
|
|
if (!save_ok && from != null)
|
|
{
|
|
from.SendMessage("Unable to complete save operation.");
|
|
}
|
|
|
|
return save_ok;
|
|
}
|
|
|
|
|
|
public static bool SaveSpawnList(Mobile from, List<XmlSpawner> savelist, string dirname, Stream stream, bool oldformat, bool verbose)
|
|
{
|
|
if (savelist == null || stream == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int TotalCount = 0;
|
|
int TrammelCount = 0;
|
|
int FeluccaCount = 0;
|
|
int IlshenarCount = 0;
|
|
int MalasCount = 0;
|
|
int TokunoCount = 0;
|
|
int OtherCount = 0;
|
|
|
|
|
|
// Create the data set
|
|
DataSet ds = new DataSet(SpawnDataSetName);
|
|
|
|
// Load the data set up
|
|
ds.Tables.Add(SpawnTablePointName);
|
|
|
|
// Create spawn point schema
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Name");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("UniqueId");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Map");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("X");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Y");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Width");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Height");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("CentreX");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("CentreY");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("CentreZ");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Range");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("MaxCount");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("MinDelay");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("MaxDelay");
|
|
// deal with the double format for delay. old format stored them as minutes in int format. that meant that short delays were lost
|
|
// proper solution would simply be to store as doubles, but older progs still assume int format (like spawneditor)
|
|
// so this is the solution. add a flag and do it both ways.
|
|
ds.Tables[SpawnTablePointName].Columns.Add("DelayInSec");
|
|
|
|
// add the duration and proximity range and sound parameters, and in container flag and coords inside the container
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Duration");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("DespawnTime");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ProximityRange");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ProximityTriggerSound");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ProximityTriggerMessage");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ObjectPropertyName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ObjectPropertyItemName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("SetPropertyItemName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ItemTriggerName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("NoItemTriggerName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("MobTriggerName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("MobPropertyName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("PlayerPropertyName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("TriggerProbability");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("SpeechTrigger");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("SkillTrigger");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("InContainer");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ContainerX");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ContainerY");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ContainerZ");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("MinRefractory");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("MaxRefractory");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("TODStart");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("TODEnd");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("TODMode");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("KillReset");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ExternalTriggering");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("SequentialSpawning");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("RegionName");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("AllowGhostTriggering");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("AllowNPCTriggering");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("SpawnOnTrigger");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("ConfigFile");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("SmartSpawning");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("TickReset");
|
|
|
|
ds.Tables[SpawnTablePointName].Columns.Add("WayPoint");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Team");
|
|
// amount for stacked item spawns
|
|
ds.Tables[SpawnTablePointName].Columns.Add("Amount");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("IsGroup");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("IsRunning");
|
|
ds.Tables[SpawnTablePointName].Columns.Add("IsHomeRangeRelative");
|
|
ds.Tables[SpawnTablePointName].Columns.Add(oldformat ? "Objects" : "Objects2");
|
|
|
|
// Always export sorted by UUID to help diffs
|
|
savelist.Sort((a, b) =>
|
|
{
|
|
return a.UniqueId.CompareTo(b.UniqueId);
|
|
});
|
|
|
|
// Add each spawn point to the new table
|
|
foreach (XmlSpawner sp in savelist)
|
|
{
|
|
if (sp == null || sp.Map == null || sp.Deleted)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (verbose && from != null)
|
|
// Send a message to the client that the spawner is being saved
|
|
{
|
|
from.SendMessage(68, $"Saving '{sp.Name}' in {sp.Map.Name} at {sp.Location}");
|
|
}
|
|
|
|
// Create a new data row
|
|
DataRow dr = ds.Tables[SpawnTablePointName].NewRow();
|
|
|
|
// Populate the data
|
|
dr["Name"] = sp.Name;
|
|
|
|
// Set the unqiue id
|
|
dr["UniqueId"] = sp.m_UniqueId;
|
|
|
|
// Get the map name
|
|
dr["Map"] = sp.Map.Name;
|
|
|
|
// Convert the xml map value to a real map object
|
|
if (string.Compare(sp.Map.Name, Map.Trammel.Name, true) == 0)
|
|
{
|
|
TrammelCount++;
|
|
}
|
|
else if (string.Compare(sp.Map.Name, Map.Felucca.Name, true) == 0)
|
|
{
|
|
FeluccaCount++;
|
|
}
|
|
else if (string.Compare(sp.Map.Name, Map.Ilshenar.Name, true) == 0)
|
|
{
|
|
IlshenarCount++;
|
|
}
|
|
else if (string.Compare(sp.Map.Name, Map.Malas.Name, true) == 0)
|
|
{
|
|
MalasCount++;
|
|
}
|
|
else if (string.Compare(sp.Map.Name, Map.Tokuno.Name, true) == 0)
|
|
{
|
|
TokunoCount++;
|
|
}
|
|
else
|
|
{
|
|
OtherCount++;
|
|
}
|
|
|
|
dr["X"] = sp.m_X;
|
|
dr["Y"] = sp.m_Y;
|
|
dr["Width"] = sp.m_Width;
|
|
dr["Height"] = sp.m_Height;
|
|
|
|
// check to see if this is in a container
|
|
if (sp.RootParent is Container container)
|
|
{
|
|
dr["CentreX"] = container.Location.X;
|
|
dr["CentreY"] = container.Location.Y;
|
|
dr["CentreZ"] = container.Location.Z;
|
|
dr["ContainerX"] = sp.Location.X;
|
|
dr["ContainerY"] = sp.Location.Y;
|
|
dr["ContainerZ"] = sp.Location.Z;
|
|
dr["InContainer"] = true;
|
|
}
|
|
else
|
|
{
|
|
dr["CentreX"] = sp.Location.X;
|
|
dr["CentreY"] = sp.Location.Y;
|
|
dr["CentreZ"] = sp.Location.Z;
|
|
//dr["ContainerX"] = 0;
|
|
//dr["ContainerY"] = 0;
|
|
//dr["ContainerZ"] = 0;
|
|
dr["InContainer"] = false;
|
|
}
|
|
dr["Range"] = sp.m_HomeRange;
|
|
dr["MaxCount"] = sp.m_Count;
|
|
|
|
// need to deal with the fact that the old xmlspawner xml format only saved delays in minutes as ints, so shorter spawn times
|
|
// are lost
|
|
// flag it then on reading it can be properly handled and still
|
|
// maintain backward compatibility with older xml files
|
|
if ((int)sp.m_MinDelay.TotalSeconds - 60 * (int)sp.m_MinDelay.TotalMinutes > 0 ||
|
|
(int)sp.m_MaxDelay.TotalSeconds - 60 * (int)sp.m_MaxDelay.TotalMinutes > 0)
|
|
{
|
|
dr["DelayInSec"] = true;
|
|
dr["MinDelay"] = (int)sp.m_MinDelay.TotalSeconds;
|
|
dr["MaxDelay"] = (int)sp.m_MaxDelay.TotalSeconds;
|
|
}
|
|
else
|
|
{
|
|
dr["DelayInSec"] = false;
|
|
dr["MinDelay"] = (int)sp.m_MinDelay.TotalMinutes;
|
|
dr["MaxDelay"] = (int)sp.m_MaxDelay.TotalMinutes;
|
|
}
|
|
|
|
// additional parameters
|
|
dr["TODStart"] = sp.m_TODStart.TotalMinutes;
|
|
dr["TODEnd"] = sp.m_TODEnd.TotalMinutes;
|
|
dr["TODMode"] = (int)sp.m_TODMode;
|
|
dr["KillReset"] = sp.m_KillReset;
|
|
dr["MinRefractory"] = sp.m_MinRefractory.TotalMinutes;
|
|
dr["MaxRefractory"] = sp.m_MaxRefractory.TotalMinutes;
|
|
dr["Duration"] = sp.m_Duration.TotalMinutes;
|
|
dr["DespawnTime"] = sp.m_DespawnTime.TotalHours;
|
|
dr["ExternalTriggering"] = sp.m_ExternalTriggering;
|
|
|
|
dr["ProximityRange"] = sp.m_ProximityRange;
|
|
dr["ProximityTriggerSound"] = sp.m_ProximityTriggerSound;
|
|
dr["ProximityTriggerMessage"] = sp.m_ProximityTriggerMessage;
|
|
if (sp.m_ObjectPropertyItem != null && !sp.m_ObjectPropertyItem.Deleted)
|
|
{
|
|
dr["ObjectPropertyItemName"] = $"{sp.m_ObjectPropertyItem.Name},{sp.m_ObjectPropertyItem.GetType().Name}";
|
|
}
|
|
else
|
|
{
|
|
dr["ObjectPropertyItemName"] = null;
|
|
}
|
|
|
|
dr["ObjectPropertyName"] = sp.m_ObjectPropertyName;
|
|
if (sp.m_SetPropertyItem != null && !sp.m_SetPropertyItem.Deleted)
|
|
{
|
|
dr["SetPropertyItemName"] = $"{sp.m_SetPropertyItem.Name},{sp.m_SetPropertyItem.GetType().Name}";
|
|
}
|
|
else
|
|
{
|
|
dr["SetPropertyItemName"] = null;
|
|
}
|
|
|
|
dr["ItemTriggerName"] = sp.m_ItemTriggerName;
|
|
dr["NoItemTriggerName"] = sp.m_NoItemTriggerName;
|
|
dr["MobTriggerName"] = sp.m_MobTriggerName;
|
|
dr["MobPropertyName"] = sp.m_MobPropertyName;
|
|
dr["PlayerPropertyName"] = sp.m_PlayerPropertyName;
|
|
dr["TriggerProbability"] = sp.m_TriggerProbability;
|
|
dr["SequentialSpawning"] = sp.m_SequentialSpawning;
|
|
dr["RegionName"] = sp.m_RegionName;
|
|
dr["AllowGhostTriggering"] = sp.m_AllowGhostTriggering;
|
|
dr["AllowNPCTriggering"] = sp.m_AllowNPCTriggering;
|
|
dr["SpawnOnTrigger"] = sp.m_SpawnOnTrigger;
|
|
dr["ConfigFile"] = sp.m_ConfigFile;
|
|
dr["SmartSpawning"] = sp.m_SmartSpawning;
|
|
dr["TickReset"] = sp.m_DisableGlobalAutoReset;
|
|
|
|
dr["SpeechTrigger"] = sp.m_SpeechTrigger;
|
|
dr["SkillTrigger"] = sp.m_SkillTrigger;
|
|
dr["Amount"] = sp.m_StackAmount;
|
|
dr["Team"] = sp.m_Team;
|
|
|
|
// assign the waypoint based on the waypoint name if it deviates from the default waypoint name, otherwise do it by serial
|
|
string waystr = null;
|
|
if (sp.m_WayPoint != null)
|
|
{
|
|
if (sp.m_WayPoint.Name != defwaypointname && !string.IsNullOrEmpty(sp.m_WayPoint.Name))
|
|
{
|
|
waystr = sp.m_WayPoint.Name;
|
|
}
|
|
else
|
|
{
|
|
waystr = $"SERIAL,{sp.m_WayPoint.Serial}";
|
|
}
|
|
}
|
|
dr["WayPoint"] = waystr;
|
|
|
|
dr["IsGroup"] = sp.m_Group;
|
|
dr["IsRunning"] = sp.m_Running;
|
|
dr["IsHomeRangeRelative"] = sp.m_HomeRangeIsRelative;
|
|
if (oldformat)
|
|
{
|
|
dr["Objects"] = sp.GetSerializedObjectList();
|
|
}
|
|
else
|
|
{
|
|
dr["Objects2"] = sp.GetSerializedObjectList2();
|
|
}
|
|
|
|
// Add the row the the table
|
|
ds.Tables[SpawnTablePointName].Rows.Add(dr);
|
|
|
|
// Increment the count
|
|
TotalCount++;
|
|
}
|
|
|
|
// Write out the file
|
|
bool file_error = false;
|
|
if (TotalCount > 0)
|
|
{
|
|
try
|
|
{
|
|
ds.WriteXml(stream);
|
|
}
|
|
catch { file_error = true; }
|
|
|
|
if (file_error)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
stream.Close();
|
|
}
|
|
catch { }
|
|
// Indicate how many spawners were written
|
|
if (from != null)
|
|
{
|
|
from.SendMessage($"{TotalCount} spawner(s) were saved to file {dirname} [Trammel={TrammelCount}, Felucca={FeluccaCount}, Ilshenar={IlshenarCount}, Malas={MalasCount}, Tokuno={TokunoCount}, Other={OtherCount}].");
|
|
}
|
|
return true;
|
|
|
|
}
|
|
|
|
private static void WipeSpawners(CommandEventArgs e, bool WipeAll)
|
|
{
|
|
if (e == null || e.Mobile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.Mobile.AccessLevel >= AccessLevel.Administrator)
|
|
{
|
|
// Spawner delete criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
|
|
// Check if there is an argument provided (delete criteria)
|
|
if (e.Arguments != null && e.Arguments.Length > 0)
|
|
{
|
|
SpawnerPrefix = e.Arguments[0];
|
|
}
|
|
|
|
if (WipeAll)
|
|
{
|
|
e.Mobile.SendMessage($"Removing ALL XmlSpawner objects from the world{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)}.");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Removing ALL XmlSpawner objects from {e.Mobile.Map}{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)}.");
|
|
}
|
|
|
|
// Delete Xml spawner's in the world based on the mobiles current map
|
|
int Count = 0;
|
|
List<Item> ToDelete = new List<Item>();
|
|
foreach (Item i in World.Items.Values)
|
|
{
|
|
if (i is XmlSpawner && (WipeAll || i.Map == e.Mobile.Map) && i.Deleted == false)
|
|
{
|
|
// Check if there is a delete condition
|
|
if (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || i.Name.StartsWith(SpawnerPrefix))
|
|
{
|
|
// Send a message to the client that the spawner is being deleted
|
|
//e.Mobile.SendMessage(33, "Removing '{0}' in {1} at {2}", i.Name, i.Map.Name, i.Location.ToString());
|
|
|
|
ToDelete.Add(i);
|
|
Count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete the items in the array list
|
|
foreach (Item i in ToDelete)
|
|
{
|
|
i.Delete();
|
|
}
|
|
|
|
if (WipeAll)
|
|
{
|
|
e.Mobile.SendMessage($"Removed {Count} XmlSpawner objects from the world.");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Removed {Count} XmlSpawner objects from {e.Mobile.Map}.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
|
|
[Usage("XmlSpawnerRespawn [SpawnerPrefixFilter]")]
|
|
[Description("Respawns all XmlSpawner objects from the current map.")]
|
|
public static void Respawn_OnCommand(CommandEventArgs e)
|
|
{
|
|
RespawnSpawners(e, false);
|
|
}
|
|
|
|
[Usage("XmlSpawnerRespawnAll [SpawnerPrefixFilter]")]
|
|
[Description("Respawns all XmlSpawner objects from the entire world.")]
|
|
public static void RespawnAll_OnCommand(CommandEventArgs e)
|
|
{
|
|
RespawnSpawners(e, true);
|
|
}
|
|
|
|
private static void RespawnSpawners(CommandEventArgs e, bool RespawnAll)
|
|
{
|
|
if (e == null || e.Mobile == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.Mobile.AccessLevel >= AccessLevel.Administrator)
|
|
{
|
|
// Spawner Respawn criteria (if any)
|
|
string SpawnerPrefix = string.Empty;
|
|
|
|
// Check if there is an argument provided (respawn criteria)
|
|
if (e.Arguments != null && e.Arguments.Length > 0)
|
|
{
|
|
SpawnerPrefix = e.Arguments[0];
|
|
}
|
|
|
|
if (RespawnAll)
|
|
{
|
|
e.Mobile.SendMessage($"Respawning ALL XmlSpawner objects from the world{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)}.");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Respawning ALL XmlSpawner objects from {e.Mobile.Map}{(!string.IsNullOrEmpty(SpawnerPrefix) ? $" beginning with {SpawnerPrefix}" : string.Empty)}.");
|
|
}
|
|
|
|
// Respawn Xml spawner's in the world based on the mobiles current map
|
|
int Count = 0;
|
|
List<Item> ToRespawn = new List<Item>();
|
|
foreach (Item i in World.Items.Values)
|
|
{
|
|
try
|
|
{
|
|
if (i is XmlSpawner && (RespawnAll || i.Map == e.Mobile.Map) && i.Deleted == false)
|
|
{
|
|
// Check if there is a respawn condition
|
|
if (SpawnerPrefix == null || SpawnerPrefix.Length == 0 || i.Name != null && i.Name.StartsWith(SpawnerPrefix))
|
|
{
|
|
ToRespawn.Add(i);
|
|
Count++;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex) { Console.WriteLine("Error attempting to add {0}, {1}", i, ex.Message); }
|
|
}
|
|
// Respawn the items in the array list
|
|
foreach (Item i in ToRespawn)
|
|
{
|
|
|
|
// Send a message to the client that the spawner is being respawned
|
|
e.Mobile.SendMessage(33, $"Respawning '{i.Name}' in {i.Map.Name} at {i.Location}");
|
|
XmlSpawner CheckXmlSpawner = (XmlSpawner)i;
|
|
CheckXmlSpawner.TryRespawn();
|
|
}
|
|
|
|
if (RespawnAll)
|
|
{
|
|
e.Mobile.SendMessage($"Respawned {Count} XmlSpawner objects from the world.");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Respawned {Count} XmlSpawner objects from {e.Mobile.Map}.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage("You do not have rights to perform this command.");
|
|
}
|
|
}
|
|
|
|
#if (TRACE)
|
|
public static void XmlMake_OnCommand(CommandEventArgs e)
|
|
{
|
|
|
|
if (e.Arguments.Length > 0)
|
|
{
|
|
int count = 0;
|
|
try
|
|
{
|
|
count = Convert.ToInt32(e.Arguments[0], 10);
|
|
}
|
|
catch (Exception ex) { Diagnostics.ExceptionLogging.LogException(ex); }
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (e.Arguments.Length > 2)
|
|
{
|
|
Spawner x = new Spawner(10, 1, 1, 0, 2, e.Arguments[1]);
|
|
x.Location = new Point3D(5400 + Utility.Random(700), 1090 + Utility.Random(180), 0);
|
|
x.Map = Map.Trammel;
|
|
}
|
|
else
|
|
if (e.Arguments.Length > 1)
|
|
{
|
|
XmlSpawner x = new XmlSpawner(10, 1, 1, 0, 2, e.Arguments[1]);
|
|
x.Location = new Point3D(5400 + Utility.Random(700), 1090 + Utility.Random(180), 0);
|
|
x.Map = Map.Trammel;
|
|
//x.MinDelay = TimeSpan.FromSeconds(1);
|
|
//x.MaxDelay = TimeSpan.FromSeconds(1);
|
|
//x.ProximityRange = 0;
|
|
}
|
|
}
|
|
if (e.Arguments.Length > 2)
|
|
{
|
|
e.Mobile.SendMessage($"Created {count} Spawner objects.");
|
|
}
|
|
else
|
|
{
|
|
e.Mobile.SendMessage($"Created {count} XmlSpawner objects.");
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
public static void XmlTrace_OnCommand()
|
|
{
|
|
XmlTrace_OnCommand(null);
|
|
}
|
|
|
|
public static void XmlTrace_OnCommand(CommandEventArgs e)
|
|
{
|
|
Process currentprocess = Process.GetCurrentProcess();
|
|
TimeSpan runningtime = Core.Now - _traceStartTime;
|
|
double processtime = currentprocess.UserProcessorTime.TotalMilliseconds - _startProcessTime;
|
|
double sysload = 0;
|
|
|
|
if (runningtime.TotalMilliseconds > 0)
|
|
{
|
|
sysload = processtime / runningtime.TotalMilliseconds;
|
|
}
|
|
|
|
Console.WriteLine("______________");
|
|
Console.WriteLine("Active Traces:");
|
|
Console.WriteLine("Running Time = {0}", runningtime);
|
|
Console.WriteLine("Adjusted Process Time = {0:####.####} secs", processtime / 1000);
|
|
Console.WriteLine("Processor Time = {0} ({1:p3} avg sys load)", currentprocess.UserProcessorTime, sysload);
|
|
|
|
for (int i = 0; i < MaxTraces; i++)
|
|
{
|
|
if (_traceCount[i] > 0)
|
|
{
|
|
double load = 0;
|
|
if (processtime > 0)
|
|
{
|
|
load = _traceTotal[i].TotalMilliseconds / processtime;
|
|
}
|
|
Console.WriteLine("{0} ({4}) {1,21} / {2} calls = {3:####.####} ms/call, {5:p3}",
|
|
i, _traceTotal[i], _traceCount[i], _traceTotal[i].TotalMilliseconds / _traceCount[i],
|
|
_traceName[i], load);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void XmlResetTrace_OnCommand(CommandEventArgs e)
|
|
{
|
|
|
|
if (e.Arguments.Length >= 0)
|
|
{
|
|
for (int i = 0; i < MaxTraces; i++)
|
|
{
|
|
_traceCount[i] = 0;
|
|
_traceTotal[i] = TimeSpan.Zero;
|
|
}
|
|
_traceStartTime = Core.Now;
|
|
|
|
Process currentprocess = Process.GetCurrentProcess();
|
|
_startProcessTime = currentprocess.UserProcessorTime.TotalMilliseconds;
|
|
|
|
Console.WriteLine("Traces reset");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
[Constructible]
|
|
public XmlSpawner()
|
|
: base(BaseItemId)
|
|
{
|
|
m_PlayerCreated = true;
|
|
m_UniqueId = Guid.NewGuid().ToString();
|
|
SpawnRange = defSpawnRange;
|
|
|
|
InitSpawn(0, 0, m_Width, m_Height, string.Empty, 0, defMinDelay, defMaxDelay, defDuration,
|
|
defProximityRange, defProximityTriggerSound, defAmount, defTeam, defHomeRange, defRelativeHome, new SpawnObject[0], defMinRefractory, defMaxRefractory,
|
|
defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode,
|
|
defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null);
|
|
}
|
|
|
|
[Constructible]
|
|
public XmlSpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, string creatureName)
|
|
: base(BaseItemId)
|
|
{
|
|
m_PlayerCreated = true;
|
|
m_UniqueId = Guid.NewGuid().ToString();
|
|
SpawnRange = homeRange;
|
|
SpawnObject[] so = new SpawnObject[1];
|
|
so[0] = new SpawnObject(creatureName, amount);
|
|
|
|
InitSpawn(0, 0, m_Width, m_Height, string.Empty, amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay), defDuration,
|
|
defProximityRange, defProximityTriggerSound, defAmount, team, homeRange, defRelativeHome, so, defMinRefractory, defMaxRefractory,
|
|
defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode,
|
|
defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null);
|
|
}
|
|
|
|
[Constructible]
|
|
public XmlSpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, int spawnRange, string creatureName)
|
|
: base(BaseItemId)
|
|
{
|
|
m_PlayerCreated = true;
|
|
m_UniqueId = Guid.NewGuid().ToString();
|
|
SpawnRange = spawnRange;
|
|
SpawnObject[] so = new SpawnObject[1];
|
|
so[0] = new SpawnObject(creatureName, amount);
|
|
|
|
InitSpawn(0, 0, m_Width, m_Height, string.Empty, amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay), defDuration,
|
|
defProximityRange, defProximityTriggerSound, defAmount, team, homeRange, defRelativeHome, so, defMinRefractory, defMaxRefractory,
|
|
defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode,
|
|
defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null);
|
|
}
|
|
|
|
[Constructible]
|
|
public XmlSpawner(string creatureName)
|
|
: base(BaseItemId)
|
|
{
|
|
m_PlayerCreated = true;
|
|
m_UniqueId = Guid.NewGuid().ToString();
|
|
SpawnObject[] so = new SpawnObject[1];
|
|
so[0] = new SpawnObject(creatureName, 1);
|
|
SpawnRange = defSpawnRange;
|
|
|
|
InitSpawn(0, 0, m_Width, m_Height, string.Empty, 1, defMinDelay, defMaxDelay, defDuration,
|
|
defProximityRange, defProximityTriggerSound, defAmount, defTeam, defHomeRange, defRelativeHome, so, defMinRefractory, defMaxRefractory,
|
|
defTODStart, defTODEnd, null, null, null, null, null, null, null, null, null, defTriggerProbability, null, defIsGroup, defTODMode,
|
|
defKillReset, false, -1, null, false, false, false, null, defDespawnTime, null, false, null);
|
|
}
|
|
|
|
public XmlSpawner(Guid uniqueId, int x, int y, int width, int height, string name, int maxCount, TimeSpan minDelay, TimeSpan maxDelay, TimeSpan duration,
|
|
int proximityRange, int proximityTriggerSound, int amount, int team, int homeRange, bool isRelativeHomeRange, SpawnObject[] spawnObjects,
|
|
TimeSpan minRefractory, TimeSpan maxRefractory, TimeSpan todstart, TimeSpan todend, Item objectPropertyItem, string objectPropertyName, string proximityMessage,
|
|
string itemTriggerName, string noitemTriggerName, string speechTrigger, string mobTriggerName, string mobPropertyName, string playerPropertyName, double triggerProbability,
|
|
Item setPropertyItem, bool isGroup, TODModeType todMode, int killReset, bool externalTriggering, int sequentialSpawning, string regionName,
|
|
bool allowghost, bool allownpc, bool spawnontrigger, string configfile, TimeSpan despawnTime, string skillTrigger, bool smartSpawning, WayPoint wayPoint)
|
|
: base(BaseItemId)
|
|
{
|
|
m_UniqueId = uniqueId.ToString();
|
|
InitSpawn(x, y, width, height, name, maxCount, minDelay, maxDelay, duration,
|
|
proximityRange, proximityTriggerSound, amount, team, homeRange, isRelativeHomeRange, spawnObjects, minRefractory, maxRefractory, todstart, todend,
|
|
objectPropertyItem, objectPropertyName, proximityMessage, itemTriggerName, noitemTriggerName, speechTrigger, mobTriggerName, mobPropertyName, playerPropertyName,
|
|
triggerProbability, setPropertyItem, isGroup, todMode, killReset, externalTriggering, sequentialSpawning, regionName, allowghost, allownpc, spawnontrigger, configfile,
|
|
despawnTime, skillTrigger, smartSpawning, wayPoint);
|
|
}
|
|
|
|
|
|
public void InitSpawn(int x, int y, int width, int height, string name, int maxCount, TimeSpan minDelay, TimeSpan maxDelay, TimeSpan duration,
|
|
int proximityRange, int proximityTriggerSound, int amount, int team, int homeRange, bool isRelativeHomeRange, SpawnObject[] objectsToSpawn,
|
|
TimeSpan minRefractory, TimeSpan maxRefractory, TimeSpan todstart, TimeSpan todend, Item objectPropertyItem, string objectPropertyName, string proximityMessage,
|
|
string itemTriggerName, string noitemTriggerName, string speechTrigger, string mobTriggerName, string mobPropertyName, string playerPropertyName, double triggerProbability,
|
|
Item setPropertyItem, bool isGroup, TODModeType todMode, int killReset, bool externalTriggering, int sequentialSpawning, string regionName, bool allowghost, bool allownpc, bool spawnontrigger,
|
|
string configfile, TimeSpan despawnTime, string skillTrigger, bool smartSpawning, WayPoint wayPoint)
|
|
{
|
|
|
|
Visible = false;
|
|
Movable = false;
|
|
m_X = x;
|
|
m_Y = y;
|
|
m_Width = width;
|
|
m_Height = height;
|
|
|
|
// init spawn range if compatible
|
|
if (width == height)
|
|
{
|
|
m_SpawnRange = width / 2;
|
|
}
|
|
else
|
|
{
|
|
m_SpawnRange = -1;
|
|
}
|
|
|
|
m_Running = true;
|
|
m_Group = isGroup;
|
|
|
|
Name = !string.IsNullOrEmpty(name) ? name : "Spawner";
|
|
|
|
m_MinDelay = minDelay;
|
|
m_MaxDelay = maxDelay;
|
|
|
|
// duration and proximity range parameter
|
|
m_MinRefractory = minRefractory;
|
|
m_MaxRefractory = maxRefractory;
|
|
m_TODStart = todstart;
|
|
m_TODEnd = todend;
|
|
m_TODMode = todMode;
|
|
m_KillReset = killReset;
|
|
m_Duration = duration;
|
|
m_DespawnTime = despawnTime;
|
|
m_ProximityRange = proximityRange;
|
|
m_ProximityTriggerSound = proximityTriggerSound;
|
|
m_proximityActivated = false;
|
|
m_durActivated = false;
|
|
m_refractActivated = false;
|
|
m_Count = maxCount;
|
|
m_Team = team;
|
|
m_StackAmount = amount;
|
|
m_HomeRange = homeRange;
|
|
m_HomeRangeIsRelative = isRelativeHomeRange;
|
|
m_ObjectPropertyItem = objectPropertyItem;
|
|
m_ObjectPropertyName = objectPropertyName;
|
|
m_ProximityTriggerMessage = proximityMessage;
|
|
m_ItemTriggerName = itemTriggerName;
|
|
m_NoItemTriggerName = noitemTriggerName;
|
|
m_SpeechTrigger = speechTrigger;
|
|
SkillTrigger = skillTrigger; // note this will register the skill as well
|
|
m_MobTriggerName = mobTriggerName;
|
|
m_MobPropertyName = mobPropertyName;
|
|
m_PlayerPropertyName = playerPropertyName;
|
|
m_TriggerProbability = triggerProbability;
|
|
m_SetPropertyItem = setPropertyItem;
|
|
m_ExternalTriggering = externalTriggering;
|
|
m_ExternalTrigger = false;
|
|
m_SequentialSpawning = sequentialSpawning;
|
|
RegionName = regionName;
|
|
m_AllowGhostTriggering = allowghost;
|
|
m_AllowNPCTriggering = allownpc;
|
|
m_SpawnOnTrigger = spawnontrigger;
|
|
m_SmartSpawning = smartSpawning;
|
|
ConfigFile = configfile;
|
|
m_WayPoint = wayPoint;
|
|
|
|
// set the totalitem property to -1 so that it doesnt show up in the item count of containers
|
|
//TotalItems = -1;
|
|
//UpdateTotal(this, TotalType.Items, -1);
|
|
|
|
// Create the array of spawned objects
|
|
m_SpawnObjects = new List<SpawnObject>();
|
|
|
|
// Assign the list of objects to spawn
|
|
SpawnObjects = objectsToSpawn;
|
|
|
|
// Kick off the process
|
|
DoTimer(TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
public XmlSpawner(Serial serial)
|
|
: base(serial)
|
|
{
|
|
}
|
|
|
|
public void Defrag(bool killtest)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool removed = false;
|
|
int total_removed = 0;
|
|
|
|
List<Item> deleteilist = new List<Item>();
|
|
List<Mobile> deletemlist = new List<Mobile>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
|
|
if (o is Item item)
|
|
{
|
|
bool despawned = false;
|
|
// check to see if the despawn time has elapsed. If so, then delete it if it hasnt been picked up or stolen.
|
|
if (DespawnTime.TotalHours > 0 && !item.Deleted && item.LastMoved < Core.Now - DespawnTime && item.Parent == Parent
|
|
&& (!ItemFlags.GetTaken(item) || item.Parent != null && item.Parent == Parent)) // can despawn if just moved within the same container
|
|
{
|
|
//item.Delete();
|
|
deleteilist.Add(item);
|
|
despawned = true;
|
|
}
|
|
|
|
// Check if the items has been deleted or
|
|
// if something else now owns the item (picked it up for example)
|
|
// also check the stolen/placed in container flag. If any of those are true then the spawner doesnt own it any more so take it off the list.
|
|
// the stolen/container flag prevents spawns from being left on the list when players take them and lock them back down on the ground.
|
|
// If you have made the changes to stealing.cs and container.cs described in xmlspawner2.txt then just uncomment the line below to
|
|
// enable this check
|
|
if (item.Deleted || despawned || item.Parent != Parent // different container
|
|
|| ItemFlags.GetTaken(item) && (item.Parent == null || item.Parent != Parent)) // taken and in the world, or a different container
|
|
{
|
|
so.SpawnedObjects.Remove(item);
|
|
x--;
|
|
removed = true;
|
|
// if sequential spawning is active and the RestrictKillsToSubgroup flag is set, then check to see if
|
|
// the object is in the current subgroup before adding to the total
|
|
if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup)
|
|
{
|
|
if (so.SubGroup == SequentialSpawn)
|
|
{
|
|
total_removed++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// just add it
|
|
total_removed++;
|
|
}
|
|
}
|
|
}
|
|
else if (o is Mobile mobile)
|
|
{
|
|
bool despawned = false;
|
|
// check to see if the despawn time has elapsed. If so, and the sector is not active then delete it.
|
|
if (DespawnTime.TotalHours > 0 && !mobile.Deleted && mobile.Created < Core.Now - DespawnTime
|
|
&& mobile.Map != null && mobile.Map != Map.Internal && !mobile.Map.GetSector(mobile.Location).Active)
|
|
{
|
|
//m.Delete();
|
|
deletemlist.Add(mobile);
|
|
despawned = true;
|
|
}
|
|
|
|
if (mobile.Deleted || despawned)
|
|
{
|
|
// Remove the delete mobile from the list
|
|
so.SpawnedObjects.Remove(mobile);
|
|
x--;
|
|
removed = true;
|
|
// if sequential spawning is active and the RestrictKillsToSubgroup flag is set, then check to see if
|
|
// the object is in the current subgroup before adding to the total
|
|
if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup)
|
|
{
|
|
if (so.SubGroup == SequentialSpawn)
|
|
{
|
|
total_removed++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// just add it
|
|
total_removed++;
|
|
}
|
|
}
|
|
else if (mobile is BaseCreature creature)
|
|
{
|
|
// Check if the creature has been tamed or previously tamed and released
|
|
// and if it is, remove it from the list of spawns
|
|
if (creature.Controlled || creature.IsStabled || creature.Owners != null && creature.Owners.Count > 0)
|
|
{
|
|
so.SpawnedObjects.Remove(mobile);
|
|
x--;
|
|
removed = true;
|
|
// if sequential spawning is active and the RestrictKillsToSubgroup flag is set, then check to see if
|
|
// the object is in the current subgroup before adding to the total
|
|
if (SequentialSpawn >= 0 && so.RestrictKillsToSubgroup)
|
|
{
|
|
if (so.SubGroup == SequentialSpawn)
|
|
{
|
|
total_removed++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// just add it
|
|
total_removed++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if (o is BaseXmlSpawner.KeywordTag tag)
|
|
{
|
|
if (tag.Deleted)
|
|
{
|
|
so.SpawnedObjects.Remove(o);
|
|
x--;
|
|
removed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Don't know what this is, so remove it
|
|
Console.WriteLine("removing unknown {0} from spawnlist", so);
|
|
so.SpawnedObjects.Remove(o);
|
|
x--;
|
|
removed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteFromList(deleteilist, deletemlist);
|
|
|
|
// Check if anything has been removed
|
|
if (removed)
|
|
{
|
|
InvalidateProperties();
|
|
}
|
|
|
|
// increment the killcount based upon the number of items that were removed from the spawnlist (i.e. were spawned but now are gone, presumed killed)
|
|
if (killtest)
|
|
{
|
|
m_killcount += total_removed;
|
|
}
|
|
}
|
|
|
|
// special defrag pass to remove GOTO keyword tags
|
|
public void ClearGOTOTags()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<BaseXmlSpawner.KeywordTag> ToDelete = new List<BaseXmlSpawner.KeywordTag>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
if (o is BaseXmlSpawner.KeywordTag sot)
|
|
{
|
|
// clear the tags except for gump and delay tags
|
|
if (sot.Type == 2)
|
|
{
|
|
ToDelete.Add(sot);
|
|
so.SpawnedObjects.Remove(o);
|
|
x--;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int x = ToDelete.Count - 1; x >= 0; --x) //BaseXmlSpawner.KeywordTag i in ToDelete)
|
|
{
|
|
BaseXmlSpawner.KeywordTag i = ToDelete[x];
|
|
if (i != null && !i.Deleted)
|
|
{
|
|
i.Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
// special defrag pass to remove spawn object tags, which are placeholders for the special keyword spawn spec entries
|
|
public void ClearTags(bool all)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool removed = false;
|
|
List<BaseXmlSpawner.KeywordTag> ToDelete = new List<BaseXmlSpawner.KeywordTag>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
if (o is BaseXmlSpawner.KeywordTag sot)
|
|
{
|
|
// clear the tags except for gump and delay tags
|
|
if (all || (sot.Flags & BaseXmlSpawner.KeywordFlags.Defrag) != 0)
|
|
{
|
|
ToDelete.Add(sot);
|
|
so.SpawnedObjects.Remove(o);
|
|
x--;
|
|
removed = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int x = ToDelete.Count - 1; x >= 0; --x) //each (BaseXmlSpawner.KeywordTag i in ToDelete)
|
|
{
|
|
BaseXmlSpawner.KeywordTag i = ToDelete[x];
|
|
if (i != null && !i.Deleted)
|
|
{
|
|
i.Delete();
|
|
}
|
|
}
|
|
|
|
// full clear of the taglist
|
|
if (all)
|
|
{
|
|
m_KeywordTagList.Clear();
|
|
}
|
|
|
|
// Check if anything has been removed
|
|
if (removed)
|
|
{
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
public void DeleteGumpTags()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool removed = false;
|
|
List<BaseXmlSpawner.KeywordTag> ToDelete = new List<BaseXmlSpawner.KeywordTag>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
if (o is BaseXmlSpawner.KeywordTag sot)
|
|
{
|
|
// clear the gump tags
|
|
if (sot.Type == 1)
|
|
{
|
|
ToDelete.Add(sot);
|
|
so.SpawnedObjects.Remove(o);
|
|
x--;
|
|
removed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int x = ToDelete.Count - 1; x >= 0; --x) //BaseXmlSpawner.KeywordTag i in ToDelete)
|
|
{
|
|
BaseXmlSpawner.KeywordTag i = ToDelete[x];
|
|
if (i != null && !i.Deleted)
|
|
{
|
|
i.Delete();
|
|
}
|
|
}
|
|
|
|
// Check if anything has been removed
|
|
if (removed)
|
|
{
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
public void DeleteTag(BaseXmlSpawner.KeywordTag tag)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool removed = false;
|
|
List<BaseXmlSpawner.KeywordTag> ToDelete = new List<BaseXmlSpawner.KeywordTag>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
if (o is BaseXmlSpawner.KeywordTag sot)
|
|
{
|
|
// clear the matching tags
|
|
if (sot == tag)
|
|
{
|
|
ToDelete.Add(sot);
|
|
so.SpawnedObjects.Remove(o);
|
|
x--;
|
|
removed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int x = ToDelete.Count - 1; x >= 0; --x) //BaseXmlSpawner.KeywordTag i in ToDelete)
|
|
{
|
|
BaseXmlSpawner.KeywordTag i = ToDelete[x];
|
|
if (i != null && !i.Deleted)
|
|
{
|
|
i.Delete();
|
|
}
|
|
}
|
|
|
|
// Check if anything has been removed
|
|
if (removed)
|
|
{
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
private int SubGroupCount(int sgroup)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int nsub = 0;
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[i];
|
|
|
|
if (s.SubGroup == sgroup)
|
|
{
|
|
nsub++;
|
|
}
|
|
}
|
|
|
|
return nsub;
|
|
}
|
|
|
|
private int RandomAvailableSpawnIndex() =>
|
|
// get spawn indices randomly from all available spawns independent of group
|
|
RandomAvailableSpawnIndex(-1);
|
|
|
|
// get spawn indices randomly from all available spawns of a group
|
|
private int RandomAvailableSpawnIndex(int sgroup)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int maxrange = 0;
|
|
List<int> sgrouplist = null;
|
|
int totalcount = 0;
|
|
// make a pass to determine which subgroups are available for spawning
|
|
// by finding any subgroups that do not have available spawns
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[i];
|
|
if (s.SubGroup > 0 && (s.Ignore || s.Disabled))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
totalcount += s.SpawnedObjects.Count;
|
|
if (s.SubGroup > 0 && s.SpawnedObjects.Count >= s.MaxCount)
|
|
{
|
|
// this subgroup is not available so add it to the list
|
|
if (sgrouplist == null)
|
|
{
|
|
sgrouplist = new List<int>();
|
|
}
|
|
sgrouplist.Add(s.SubGroup);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[i];
|
|
|
|
if (s.SubGroup > 0 && (s.Ignore || s.Disabled))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (s.MaxCount > s.SpawnedObjects.Count && (sgroup < 0 || sgroup == s.SubGroup)
|
|
&& (sgrouplist == null || !sgrouplist.Contains(s.SubGroup)) && (s.SubGroup <= 0 || SubGroupCount(s.SubGroup) + totalcount <= MaxCount))
|
|
{
|
|
// keep track of the number of spawn objects that are not at max (hence available for spawning)
|
|
// this will be used to compute the probabilistic weighting function based on the relative
|
|
// maxcounts of each entry
|
|
maxrange += s.MaxCount;
|
|
s.Available = true;
|
|
}
|
|
else
|
|
{
|
|
s.Available = false;
|
|
}
|
|
}
|
|
// now generate a random number over the available spawnobjects
|
|
// but only if the entire subgroup is available for spawning
|
|
// note, subgroup zero is exempt from this check.
|
|
if (maxrange > 0)
|
|
{
|
|
int randindex = Utility.Random(maxrange);
|
|
|
|
// and map it into the avail spawns
|
|
int currentrange = 0;
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[i];
|
|
if (s.SubGroup > 0 && (s.Ignore || s.Disabled))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// keep track of the number of spawn objects that are not at max (hence available for spawning)
|
|
if (s.Available)
|
|
{
|
|
// check to see if the random value maps into the range of the current index
|
|
if (randindex >= currentrange && randindex < currentrange + s.MaxCount)
|
|
{
|
|
return i;
|
|
}
|
|
|
|
currentrange += s.MaxCount;
|
|
}
|
|
}
|
|
|
|
// should never get here
|
|
return -1;
|
|
}
|
|
|
|
// no spawns are available
|
|
return -1;
|
|
}
|
|
|
|
// get spawn indices randomly from all available spawns of a group
|
|
private int RandomSpawnIndex(int sgroup)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int avail = 0;
|
|
int maxrange = 0;
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[i];
|
|
|
|
// keep track of the number of spawn objects that are not at max (hence available for spawning)
|
|
if (sgroup < 0 || sgroup == s.SubGroup)
|
|
{
|
|
avail++;
|
|
maxrange += s.MaxCount;
|
|
}
|
|
}
|
|
// now generate a random number over the available spawnobjects
|
|
if (avail > 0 && maxrange > 0)
|
|
{
|
|
int randindex = Utility.Random(maxrange);
|
|
|
|
// and map it into the avail spawns
|
|
int currentrange = 0;
|
|
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[i];
|
|
|
|
// keep track of the number of spawn objects that are not at max (hence available for spawning)
|
|
if (sgroup < 0 || sgroup == s.SubGroup)
|
|
{
|
|
if (randindex >= currentrange && randindex < currentrange + s.MaxCount)
|
|
{
|
|
return i;
|
|
}
|
|
|
|
currentrange += s.MaxCount;
|
|
}
|
|
}
|
|
|
|
// should never get here
|
|
return -1;
|
|
}
|
|
|
|
// no spawns are available
|
|
return -1;
|
|
}
|
|
|
|
// return the next subgroup in the sequence.
|
|
public int NextSequentialIndex(int sgroup)
|
|
{
|
|
if (m_SpawnObjects == null || m_SpawnObjects.Count == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int finddirection = 1;
|
|
int largergroup = -1;
|
|
|
|
//find the next subgroup that is greater than the current one
|
|
for (int j = 0; j < m_SpawnObjects.Count; j++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[j];
|
|
if (s.SubGroup > 0 && (s.Ignore || s.Disabled))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int thisgroup = s.SubGroup;
|
|
|
|
// start off by finding a subgroup that is larger
|
|
if (finddirection == 1)
|
|
{
|
|
if (thisgroup > sgroup)
|
|
{
|
|
largergroup = thisgroup;
|
|
|
|
// then work backward to find the group that is less than this but still larger than the current
|
|
finddirection = -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (thisgroup > sgroup && thisgroup < largergroup)
|
|
{
|
|
largergroup = thisgroup;
|
|
|
|
finddirection = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if couldnt find one larger, then it is time to wraparound
|
|
if (largergroup < 0 && sgroup >= 0)
|
|
{
|
|
return NextSequentialIndex(-1);
|
|
}
|
|
|
|
return largergroup;
|
|
}
|
|
|
|
// returns the spawn index of a spawn entry in the current sequential subgroup
|
|
public int GetCurrentAvailableSequentialSpawnIndex(int sgroup)
|
|
{
|
|
if (sgroup < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (sgroup == 0)
|
|
{
|
|
return RandomAvailableSpawnIndex(0);
|
|
}
|
|
|
|
//return the first instance of a spawn object that is an available member of the requested subgroup
|
|
for (int j = 0; j < m_SpawnObjects.Count; j++)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[j];
|
|
|
|
if (s.SubGroup == sgroup && s.MaxCount > s.SpawnedObjects.Count)
|
|
{
|
|
return j;
|
|
}
|
|
}
|
|
// failed to find any spawn entry of the requested subgroup
|
|
return -1;
|
|
}
|
|
|
|
// returns the spawn index of a spawn entry in the current sequential subgroup
|
|
public int GetCurrentSequentialSpawnIndex(int sgroup)
|
|
{
|
|
if (sgroup < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (sgroup == 0)
|
|
{
|
|
return RandomSpawnIndex(0);
|
|
}
|
|
|
|
//return the first instance of a spawn object that is an available member of the requested subgroup
|
|
for (int j = 0; j < m_SpawnObjects.Count; j++)
|
|
{
|
|
if (m_SpawnObjects[j].SubGroup == sgroup)
|
|
{
|
|
return j;
|
|
}
|
|
}
|
|
// failed to find any spawn entry of the requested subgroup
|
|
return -1;
|
|
}
|
|
|
|
private void SeqResetTo(int sgroup)
|
|
{
|
|
// check the SequentialResetTo on the subgroup
|
|
// cant do resets on subgroup 0
|
|
if (sgroup == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// this will get the index of the first spawn entry in the subgroup
|
|
// it will have the subgroup timer settings
|
|
int spawnindex = GetCurrentSequentialSpawnIndex(sgroup);
|
|
|
|
if (spawnindex >= 0)
|
|
{
|
|
// if it is greater than zero then initiate reset
|
|
SpawnObject s = m_SpawnObjects[spawnindex];
|
|
m_SequentialSpawning = s.SequentialResetTo;
|
|
|
|
InitiateSequentialReset(sgroup);
|
|
|
|
// clear the spawns
|
|
//RemoveSpawnObjects();
|
|
ClearSubgroup(s.SubGroup);
|
|
|
|
// and reset the kill count
|
|
KillCount = 0;
|
|
}
|
|
}
|
|
|
|
private bool CheckForSequentialReset()
|
|
{
|
|
// check the SequentialResetTime on the subgroup
|
|
// cant do resets on subgroup 0
|
|
if (m_SequentialSpawning == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// this will get the index of the first spawn entry in the subgroup
|
|
// it will have the subgroup timer settings
|
|
int spawnindex = GetCurrentSequentialSpawnIndex(m_SequentialSpawning);
|
|
|
|
if (spawnindex >= 0)
|
|
{
|
|
// check the reset time on it
|
|
SpawnObject s = m_SpawnObjects[spawnindex];
|
|
// if it is greater than zero then resetting is possible
|
|
if (s.SequentialResetTime > 0)
|
|
{
|
|
// so check the reset timer
|
|
if (NextSeqReset <= TimeSpan.Zero)
|
|
{
|
|
// it has expired so time to reset
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void InitiateSequentialReset(int sgroup)
|
|
{
|
|
// check the SequentialResetTime on the subgroup
|
|
// cant do resets on subgroup 0
|
|
if (sgroup == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// this will get the index of the first spawn entry in the subgroup
|
|
// it will have the subgroup timer settings
|
|
int spawnindex = GetCurrentSequentialSpawnIndex(sgroup);
|
|
|
|
if (spawnindex >= 0)
|
|
{
|
|
// if it is greater than zero then initiate reset
|
|
SpawnObject s = m_SpawnObjects[spawnindex];
|
|
NextSeqReset = TimeSpan.FromMinutes(s.SequentialResetTime);
|
|
}
|
|
}
|
|
|
|
|
|
public void ResetSequential()
|
|
{
|
|
// go back to the lowest level
|
|
if (m_SequentialSpawning >= 0)
|
|
{
|
|
m_SequentialSpawning = NextSequentialIndex(-1);
|
|
}
|
|
|
|
// reset the nextspawn times
|
|
ResetNextSpawnTimes();
|
|
|
|
// and reset the kill count
|
|
KillCount = 0;
|
|
}
|
|
|
|
public bool AdvanceSequential()
|
|
{
|
|
// check for a sequence hold
|
|
|
|
if (HoldSequence)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check for triggering
|
|
if (!((m_proximityActivated || CanFreeSpawn) && TODInRange))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if kills needed is greater than zero then check the killcount as well
|
|
int spawnindex = GetCurrentSequentialSpawnIndex(m_SequentialSpawning);
|
|
|
|
int killsneeded = 0;
|
|
int subgroup = -1;
|
|
bool clearedobjects = false;
|
|
|
|
if (spawnindex >= 0)
|
|
{
|
|
SpawnObject s = m_SpawnObjects[spawnindex];
|
|
subgroup = s.SubGroup;
|
|
killsneeded = s.KillsNeeded;
|
|
}
|
|
|
|
// advance the sequential spawn index if it is enabled and kills needed have been satisfied
|
|
if (m_SequentialSpawning >= 0 && (killsneeded == 0 || KillCount >= killsneeded))
|
|
{
|
|
m_SequentialSpawning = NextSequentialIndex(m_SequentialSpawning);
|
|
|
|
// set the sequential reset based on the current sequence state
|
|
// this will be checked in the spawner OnTick to determine whether to Reset the sequential state
|
|
InitiateSequentialReset(m_SequentialSpawning);
|
|
|
|
// clear the spawns if there is a killcount on the level
|
|
if (killsneeded >= 0)
|
|
{
|
|
ClearSubgroup(subgroup);
|
|
clearedobjects = true;
|
|
}
|
|
|
|
// and reset the kill count
|
|
KillCount = 0;
|
|
}
|
|
|
|
// returning true will indicate that all spawns have been cleared and therefore a new spawn can be initiated in the same OnTick
|
|
return clearedobjects;
|
|
}
|
|
|
|
int killcount_held;
|
|
|
|
public void OnTick()
|
|
{
|
|
_TraceStart(8);
|
|
// start up the timer again for the next Ontick
|
|
DoTimer();
|
|
|
|
// reset the protection against runaway looping
|
|
ClearSpawnedThisTick = true;
|
|
|
|
// if regional spawning is enabled, update the region in case new regions were added after the initialization pass
|
|
CheckRegionAssignment = true;
|
|
|
|
// reset the killcount whenever a spawntick goes by in which it could have spawned, ie the spawner is full, or proximity triggered
|
|
// spawns were not activated. Note that killcount gets incremented within Defrag whenever a spawn is that had been generated is removed from the active list.
|
|
// Check the count before and then after the spawn passes.
|
|
// if the spawner is still refractory then dont do a reset of the killcount.
|
|
//int startcount = this.m_killcount;
|
|
int startcount = killcount_held;
|
|
if (!m_skipped)
|
|
{
|
|
killcount_held = m_killcount;
|
|
}
|
|
|
|
// killcount will be updated in Defrag
|
|
Defrag(true);
|
|
|
|
// remove any keyword tags that were made
|
|
// note, tags only last a single ontick except for WAIT type
|
|
ClearTags(false);
|
|
|
|
if (!m_DisableGlobalAutoReset && startcount == m_killcount && !m_refractActivated && !m_skipped)
|
|
{
|
|
m_spawncheck--;
|
|
}
|
|
m_skipped = false;
|
|
|
|
// allow for some slack in the killcount reset by resetting after a certain number of spawn ticks without kills pass
|
|
if (m_spawncheck <= 0)
|
|
{
|
|
m_killcount = 0;
|
|
m_spawncheck = m_KillReset; // wait for 1 spawn ticks to pass before resetting. This can be set to anything you like
|
|
}
|
|
|
|
// check for smart spawning
|
|
if (SmartSpawning && IsFull && !HasActiveSectors && !HasDamagedOrDistantSpawns /*&& !HasHoldSmartSpawning */)
|
|
{
|
|
IsInactivated = true;
|
|
// for multiple sector spawning ranges use the sector timer, otherwise just rely on OnSectorActivate to detect sector activation
|
|
//if (!UseSectorActivate)
|
|
//DoSectorTimer(TimeSpan.FromSeconds(1));
|
|
|
|
SmartRemoveSpawnObjects();
|
|
|
|
}
|
|
|
|
// dont process spawn ticks while inactivated if smart spawning is enabled
|
|
if (SmartSpawning && IsInactivated)
|
|
{
|
|
_TraceEnd(8);
|
|
return;
|
|
}
|
|
|
|
IsInactivated = false;
|
|
|
|
// check to see if spawning is on hold due to a WAIT keyword
|
|
if (!OnHold)
|
|
{
|
|
// look for triggers that are not player activated.
|
|
if (m_ProximityRange == -1 && CanSpawn)
|
|
{
|
|
CheckTriggers(null, null, false);
|
|
}
|
|
|
|
// check for proximity triggers without movement activation
|
|
if (m_ProximityRange >= 0 && CanSpawn)
|
|
{
|
|
// check all nearby players
|
|
IPooledEnumerable eable = GetMobilesInRange(m_ProximityRange);
|
|
foreach (Mobile p in eable)
|
|
{
|
|
if (ValidPlayerTrig(p))
|
|
{
|
|
CheckTriggers(p, null, true);
|
|
}
|
|
}
|
|
|
|
eable.Free();
|
|
}
|
|
|
|
if (m_Group)
|
|
{
|
|
// check the seq reset time on the current subgroup
|
|
// if the reset time is greater than zero then check the timer
|
|
// if it has expired then reset the sequential subgroup
|
|
// only do this if it can actually spawn
|
|
if (CheckForSequentialReset())
|
|
{
|
|
// it has expired so reset the sequential spawn level
|
|
SeqResetTo(m_SequentialSpawning);
|
|
|
|
bool triedtospawn = TryRespawn();
|
|
|
|
if (triedtospawn)
|
|
{
|
|
ClearGOTOTags();
|
|
}
|
|
|
|
// dont advance if the spawn isnt triggered after resetting
|
|
if (!triedtospawn)
|
|
{
|
|
HoldSequence = true;
|
|
}
|
|
}
|
|
else if (TotalSpawnedObjects <= 0)
|
|
{
|
|
|
|
// advance the sequential spawn index if it is enabled
|
|
AdvanceSequential();
|
|
|
|
//bool hadhold = HoldSequence;
|
|
|
|
//HoldSequence = false;
|
|
|
|
bool triedtospawn = TryRespawn();
|
|
|
|
if (triedtospawn)
|
|
{
|
|
ClearGOTOTags();
|
|
}
|
|
|
|
//if (!triedtospawn) HoldSequence = hadhold;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (CheckForSequentialReset())
|
|
{
|
|
// it has expired so reset the sequential spawn level
|
|
SeqResetTo(m_SequentialSpawning);
|
|
|
|
// dont advance if the spawn isnt triggered after resetting
|
|
HoldSequence = true;
|
|
}
|
|
else
|
|
{
|
|
// advance the sequence before spawning
|
|
AdvanceSequential();
|
|
}
|
|
|
|
// keep track of the hold flag before trying to spawn in case no spawn attempt is made
|
|
//bool hadhold = HoldSequence;
|
|
|
|
// clear the hold flag to see if any of the spawned entries try to set it
|
|
//HoldSequence = false;
|
|
|
|
// try to spawn. If spawning conditions such as triggering or TOD are not met, then it returns false
|
|
bool triedtospawn = Spawn(false, 0);
|
|
|
|
if (triedtospawn)
|
|
{
|
|
ClearGOTOTags();
|
|
}
|
|
// this will maintain any sequential holds if spawning was suppressed due to triggering
|
|
|
|
if (!FreeRun)
|
|
{
|
|
m_mob_who_triggered = null;
|
|
}
|
|
|
|
}
|
|
|
|
// remove any keyword tags that were made except for WAIT type
|
|
ClearTags(false);
|
|
|
|
|
|
// and clear triggering flags
|
|
if (!OnHold && !FreeRun)
|
|
{
|
|
m_proximityActivated = false;
|
|
}
|
|
}
|
|
|
|
if (FreeRun && SpawnOnTrigger && m_proximityActivated)
|
|
{
|
|
// if it is in free run and was triggered, then just keep spawning as though it was triggered immediately
|
|
NextSpawn = TimeSpan.Zero;
|
|
ResetNextSpawnTimes();
|
|
}
|
|
|
|
|
|
//this.m_ExternalTrigger = false;
|
|
// if it is out of the TOD range then delete the spawns
|
|
if (!TODInRange)
|
|
{
|
|
RemoveSpawnObjects();
|
|
|
|
ResetAllFlags();
|
|
}
|
|
_TraceEnd(8);
|
|
|
|
}
|
|
|
|
public bool ClearSpawnedThisTick
|
|
{
|
|
set
|
|
{
|
|
if (m_SpawnObjects == null || value == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject sobj = m_SpawnObjects[i];
|
|
if (sobj != null)
|
|
{
|
|
sobj.SpawnedThisTick = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// select and spawn something
|
|
// return false if it cannot spawn, e.g. there is nothing to spawn or it is a triggerable spawner and has not been triggered
|
|
public bool Spawn(bool smartspawn, byte loops)
|
|
{
|
|
if (m_SpawnObjects != null && m_SpawnObjects.Count > 0 && (m_proximityActivated || CanFreeSpawn) && TODInRange)
|
|
{
|
|
m_HoldSequence = false;
|
|
|
|
// if the spawner is full then dont bother
|
|
if (IsFull)
|
|
{
|
|
ResetProximityActivated();
|
|
return true;
|
|
}
|
|
|
|
// Pick a spawn object to spawn
|
|
int SpawnIndex;
|
|
|
|
// see if sequential spawning has been selected
|
|
SpawnIndex = m_SequentialSpawning >= 0 ? GetCurrentAvailableSequentialSpawnIndex(m_SequentialSpawning) : RandomAvailableSpawnIndex();
|
|
|
|
// no spawns are available so no point in continuing
|
|
if (SpawnIndex < 0)
|
|
{
|
|
ResetProximityActivated();
|
|
return true;
|
|
}
|
|
|
|
SpawnObject sobj = m_SpawnObjects[SpawnIndex];
|
|
int sgroup = sobj.SubGroup;
|
|
|
|
// if this is part of a non-zero group, then spawn all of the group members as well
|
|
if (sgroup != 0)
|
|
{
|
|
SpawnSubGroup(sgroup, smartspawn, loops);
|
|
}
|
|
else
|
|
{
|
|
// Found a valid spawn object so spawn it and see if it successful
|
|
if (Spawn(SpawnIndex, smartspawn, sobj.SpawnsPerTick, loops))
|
|
{
|
|
if (!smartspawn)
|
|
{
|
|
RefreshNextSpawnTime(sobj);
|
|
}
|
|
}
|
|
}
|
|
|
|
ResetProximityActivated();
|
|
return true;
|
|
}
|
|
|
|
ResetProximityActivated();
|
|
return false;
|
|
}
|
|
|
|
// spawn an individual entry by index up to count times
|
|
public bool Spawn(int index, bool smartspawn, int count, int packrange, Point3D packcoord, bool ignoreloopprotection, byte loops)
|
|
{
|
|
if (m_SpawnObjects == null || index >= m_SpawnObjects.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool didspawn = false;
|
|
|
|
SpawnObject so = m_SpawnObjects[index];
|
|
|
|
if (so == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Defrag(false);
|
|
|
|
// make sure you dont go over the individual entry maxcount
|
|
int somax = so.MaxCount;
|
|
int socnt = so.SpawnedObjects.Count;
|
|
int nspawn = so.SpawnsPerTick;
|
|
int scnt = SafeCurrentCount;
|
|
|
|
for (int k = 0; k < nspawn && k + socnt < somax && k + scnt < MaxCount; k++)
|
|
{
|
|
if (packrange >= 0 && so.SubGroup > 0 && packcoord == Point3D.Zero)
|
|
{
|
|
packcoord = GetPackCoord(so.SubGroup);
|
|
}
|
|
if (Spawn(index, smartspawn, packrange, packcoord, ignoreloopprotection, loops))
|
|
{
|
|
// if any of the attempts were successful then flag it as having spawned
|
|
didspawn = true;
|
|
}
|
|
}
|
|
|
|
return didspawn;
|
|
}
|
|
|
|
// spawn an individual entry by index up to count times
|
|
public bool Spawn(int index, bool smartspawn, int count, byte loops) => Spawn(index, smartspawn, count, false, loops);
|
|
|
|
// spawn an individual entry by index up to count times
|
|
public bool Spawn(int index, bool smartspawn, int count, bool ignoreloopprotection, byte loops) => Spawn(index, smartspawn, count, -1, Point3D.Zero, ignoreloopprotection, loops);
|
|
|
|
// spawn an individual entry by spawn object
|
|
public void Spawn(string SpawnObjectTypeName, bool smartspawn, int packrange, Point3D packcoord, byte loops)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
if (m_SpawnObjects[i].TypeName.ToUpper() == SpawnObjectTypeName.ToUpper())
|
|
{
|
|
|
|
if (Spawn(i, smartspawn, packrange, packcoord, loops))
|
|
{
|
|
RefreshNextSpawnTime(m_SpawnObjects[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// spawn an individual entry by index
|
|
public void Spawn(string SpawnObjectTypeName, bool smartspawn, byte loops)
|
|
{
|
|
Spawn(SpawnObjectTypeName, smartspawn, -1, Point3D.Zero, loops);
|
|
}
|
|
|
|
// spawn an individual entry by index
|
|
public bool Spawn(int index, bool smartspawn, int packrange, Point3D packcoord, byte loops) => Spawn(index, smartspawn, packrange, packcoord, false, loops);
|
|
|
|
// spawn an individual entry by index
|
|
public bool Spawn(int index, bool smartspawn, int packrange, Point3D packcoord, bool ignoreloopprotection, byte loops)
|
|
{
|
|
Map map = Map;
|
|
|
|
// Make sure everything is ok to spawn an object
|
|
if (map == null ||
|
|
map == Map.Internal ||
|
|
m_SpawnObjects == null ||
|
|
m_SpawnObjects.Count == 0 ||
|
|
index < 0 ||
|
|
index >= m_SpawnObjects.Count
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Remove any spawns that don't belong to the spawner any more.
|
|
Defrag(false);
|
|
|
|
// Get the spawn object at the required index
|
|
SpawnObject TheSpawn = m_SpawnObjects[index];
|
|
|
|
// Check if the object retrieved is a valid SpawnObject
|
|
if (TheSpawn != null)
|
|
{
|
|
// dont allow an entry to be spawned more than once per tick
|
|
// this protects against runaway recursive looping
|
|
if (TheSpawn.SpawnedThisTick && !ignoreloopprotection)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check the nextspawn time to see if it is available
|
|
if (TheSpawn.NextSpawn > Core.Now)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int CurrentCreatureMax = TheSpawn.MaxCount;
|
|
int CurrentCreatureCount = TheSpawn.SpawnedObjects.Count;
|
|
|
|
// Check that the current object to be spawned has not reached its maximum allowed
|
|
// and make sure that the maximum spawner count has not been exceeded as well
|
|
if (CurrentCreatureCount >= CurrentCreatureMax ||
|
|
TotalSpawnedObjects >= m_Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check for string substitions
|
|
string substitutedtypeName = BaseXmlSpawner.ApplySubstitution(this, this, TheSpawn.TypeName);
|
|
|
|
// random positioning is the default
|
|
List<SpawnPositionInfo> spawnpositioning = null;
|
|
|
|
// require valid surfaces by default
|
|
bool requiresurface = true;
|
|
|
|
// parse the # function specification for the entry
|
|
while (substitutedtypeName.StartsWith("#"))
|
|
{
|
|
string[] args = BaseXmlSpawner.ParseSemicolonArgs(substitutedtypeName, 2);
|
|
|
|
if (args.Length > 0)
|
|
{
|
|
if (spawnpositioning == null)
|
|
{
|
|
spawnpositioning = new List<SpawnPositionInfo>();
|
|
}
|
|
// parse any comma args
|
|
string[] keyvalueargs = BaseXmlSpawner.ParseCommaArgs(args[0], 10);
|
|
|
|
if (keyvalueargs.Length > 0)
|
|
{
|
|
|
|
switch (keyvalueargs[0])
|
|
{
|
|
case "#NOITEMID":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.NoItemID, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#ITEMID":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.ItemID, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#NOTILES":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.NoTiles, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#TILES":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Tiles, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#WET":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Wet, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#XFILL":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.RowFill, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#YFILL":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.ColFill, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#EDGE":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Perimeter, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#PLAYER":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Player, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#WAYPOINT":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Waypoint, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#RELXY":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.RelXY, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#DXY":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.DeltaLocation, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#XY":
|
|
{
|
|
spawnpositioning.Add(new SpawnPositionInfo(SpawnPositionType.Location, m_mob_who_triggered, keyvalueargs));
|
|
break;
|
|
}
|
|
case "#CONDITION":
|
|
{
|
|
// test the specified condition string
|
|
// syntax is #CONDITION,proptest
|
|
// reparse with only one arg after the comma, this allows property tests that use commas as well
|
|
string[] ckeyvalueargs = BaseXmlSpawner.ParseCommaArgs(args[0], 2);
|
|
if (ckeyvalueargs.Length > 1)
|
|
{
|
|
// dont spawn if it fails the test
|
|
if (!BaseXmlSpawner.CheckPropertyString(this, this, ckeyvalueargs[1], out status_str))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
status_str = $"invalid #CONDITION specification: {args[0]}";
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
status_str = $"invalid # specification: {args[0]}";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the rest of the spawn entry
|
|
substitutedtypeName = args.Length > 1 ? args[1].Trim() : string.Empty;
|
|
}
|
|
|
|
|
|
if (substitutedtypeName.StartsWith("*"))
|
|
{
|
|
requiresurface = false;
|
|
substitutedtypeName = substitutedtypeName.TrimStart('*');
|
|
}
|
|
|
|
TheSpawn.RequireSurface = requiresurface;
|
|
|
|
string typeName = BaseXmlSpawner.ParseObjectType(substitutedtypeName);
|
|
|
|
if (BaseXmlSpawner.IsTypeOrItemKeyword(typeName))
|
|
{
|
|
string status_str = null;
|
|
|
|
bool completedtypespawn = BaseXmlSpawner.SpawnTypeKeyword(this, TheSpawn, typeName, substitutedtypeName,
|
|
m_mob_who_triggered, Map, out status_str, loops);
|
|
|
|
if (status_str != null)
|
|
{
|
|
this.status_str = status_str;
|
|
}
|
|
|
|
if (completedtypespawn)
|
|
{
|
|
// successfully spawned the keyword
|
|
// note that returning true means that Spawn will assume that it worked and will not try to respawn something else
|
|
// added the duration timer that begins on spawning
|
|
DoTimer2(m_Duration);
|
|
|
|
InvalidateProperties();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// its a regular type descriptor so find out what it is
|
|
Type type = AssemblyHandler.FindTypeByName(typeName);
|
|
|
|
// dont try to spawn invalid types, or Mobile type spawns in containers
|
|
if (type != null && !(Parent != null && (type == typeof(Mobile) || type.IsSubclassOf(typeof(Mobile)))))
|
|
{
|
|
|
|
string[] arglist = BaseXmlSpawner.ParseString(substitutedtypeName, 3, "/");
|
|
|
|
object o = CreateObject(type, arglist[0]);
|
|
|
|
if (o == null)
|
|
{
|
|
status_str = $"invalid type specification: {arglist[0]}";
|
|
return true;
|
|
}
|
|
try
|
|
{
|
|
if (o is Mobile mob)
|
|
{
|
|
// if this is in any container such as a pack the xyz values are invalid as map coords so dont spawn the mob
|
|
if (Parent is Container)
|
|
{
|
|
mob.Delete();
|
|
|
|
return true;
|
|
}
|
|
|
|
// add the mobile to the spawned list
|
|
TheSpawn.SpawnedObjects.Add(mob);
|
|
|
|
mob.Spawner = this;
|
|
|
|
var loc = GetSpawnPosition(requiresurface, packrange, packcoord, spawnpositioning, mob);
|
|
|
|
if (!smartspawn)
|
|
{
|
|
mob.OnBeforeSpawn(loc, map);
|
|
}
|
|
|
|
mob.MoveToWorld(loc, map);
|
|
|
|
if (mob is BaseCreature mobile)
|
|
{
|
|
mobile.RangeHome = m_HomeRange;
|
|
mobile.CurrentWayPoint = m_WayPoint;
|
|
|
|
if (m_Team > 0)
|
|
{
|
|
mobile.Team = m_Team;
|
|
}
|
|
|
|
// Check if this spawner uses absolute (from spawnER location)
|
|
// or relative (from spawnED location) as the mobiles home point
|
|
mobile.Home = m_HomeRangeIsRelative ? mobile.Location : Location;
|
|
}
|
|
|
|
// if the object has an OnSpawned method, then invoke it
|
|
if (!smartspawn)
|
|
{
|
|
mob.OnAfterSpawn();
|
|
}
|
|
|
|
// apply the parsed arguments from the typestring using setcommand
|
|
// be sure to do this after setting map and location so that errors dont place the mob on the internal map
|
|
string status_str;
|
|
|
|
BaseXmlSpawner.ApplyObjectStringProperties(this, substitutedtypeName, mob, m_mob_who_triggered, this, out status_str);
|
|
|
|
if (status_str != null)
|
|
{
|
|
this.status_str = status_str;
|
|
}
|
|
|
|
InvalidateProperties();
|
|
|
|
// added the duration timer that begins on spawning
|
|
DoTimer2(m_Duration);
|
|
|
|
return true;
|
|
}
|
|
|
|
if (o is Item item)
|
|
{
|
|
string status_str;
|
|
|
|
BaseXmlSpawner.AddSpawnItem(this, TheSpawn, item, Location, map, m_mob_who_triggered, requiresurface, spawnpositioning, substitutedtypeName, smartspawn, out status_str);
|
|
|
|
if (status_str != null)
|
|
{
|
|
this.status_str = status_str;
|
|
}
|
|
|
|
InvalidateProperties();
|
|
|
|
// added the duration timer that begins on spawning
|
|
DoTimer2(m_Duration);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception ex) { Console.WriteLine("When spawning {0}, {1}", o, ex); }
|
|
}
|
|
else
|
|
{
|
|
status_str = $"invalid type specification: {typeName}";
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool SpawnSubGroup(int sgroup, byte loops) => SpawnSubGroup(sgroup, false, loops);
|
|
|
|
public bool SpawnSubGroup(int sgroup, bool smartspawn, byte loops) => SpawnSubGroup(sgroup, false, false, loops);
|
|
|
|
public bool SpawnSubGroup(int sgroup, bool smartspawn, bool ignoreloopprotection, byte loops)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (sgroup >= 0)
|
|
{
|
|
bool didspawn = false;
|
|
Point3D packcoord = Point3D.Zero;
|
|
|
|
for (int j = 0; j < m_SpawnObjects.Count; j++)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[j];
|
|
|
|
if (so != null && so.SubGroup == sgroup)
|
|
{
|
|
// find the first subgroup spawn to determine the packspawning reference coordinates
|
|
if (so.PackRange >= 0 && packcoord == Point3D.Zero)
|
|
{
|
|
packcoord = GetPackCoord(sgroup);
|
|
}
|
|
|
|
// get the SpawnsPerTick count and spawn up to that number
|
|
bool success = Spawn(j, smartspawn, so.SpawnsPerTick, so.PackRange, packcoord, ignoreloopprotection, loops);
|
|
|
|
if (success)
|
|
{
|
|
didspawn = true;
|
|
}
|
|
|
|
if (success && !smartspawn)
|
|
{
|
|
RefreshNextSpawnTime(so);
|
|
}
|
|
}
|
|
}
|
|
|
|
// success if any of the subgroup spawned
|
|
if (didspawn)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public Point3D GetPackCoord(int sgroup)
|
|
{
|
|
for (int j = 0; j < m_SpawnObjects.Count; j++)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[j];
|
|
|
|
if (so != null && so.SubGroup == sgroup && so.SpawnedObjects.Count > 0 && so.PackRange >= 0)
|
|
{
|
|
// if pack spawning is enabled for this subgroup, then get the
|
|
// the origin for pack spawning using the first existing pack spawn
|
|
// in the subgroup
|
|
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
object o = so.SpawnedObjects[i];
|
|
if (o is Item item)
|
|
{
|
|
return item.Location;
|
|
}
|
|
|
|
if (o is Mobile mobile)
|
|
{
|
|
return mobile.Location;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Point3D.Zero;
|
|
}
|
|
|
|
|
|
//used by the reset button in the gump
|
|
public void ResetAllFlags()
|
|
{
|
|
m_proximityActivated = false;
|
|
m_ExternalTrigger = false;
|
|
m_durActivated = false;
|
|
m_refractActivated = false;
|
|
m_mob_who_triggered = null;
|
|
m_killcount = 0;
|
|
m_GumpState = null;
|
|
FreeRun = false;
|
|
}
|
|
|
|
public bool BringHome
|
|
{
|
|
set { if (value)
|
|
{
|
|
BringToHome();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void BringToHome()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Defrag(false);
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
object o = so.SpawnedObjects[i];
|
|
|
|
if (o is Mobile mobile)
|
|
{
|
|
mobile.Map = Map;
|
|
mobile.Location = new Point3D(Location);
|
|
}
|
|
else if (o is Item item)
|
|
{
|
|
item.MoveToWorld(Location, Map);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool CheckRegionAssignment
|
|
{
|
|
get => false;
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
// see if a region definition needs updating
|
|
if (m_Region == null && m_RegionName != null && RegionName != string.Empty)
|
|
{
|
|
RegionName = RegionName;
|
|
|
|
if (SpawnRegion != null)
|
|
{
|
|
// clear the status if successful
|
|
status_str = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
if (m_Running == false)
|
|
{
|
|
if (m_SpawnObjects != null && m_SpawnObjects.Count > 0)
|
|
{
|
|
m_Running = true;
|
|
DoTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (m_Running)
|
|
{
|
|
// turn off all timers
|
|
if (m_Timer != null)
|
|
{
|
|
m_Timer.Stop();
|
|
}
|
|
|
|
if (m_DurTimer != null)
|
|
{
|
|
m_DurTimer.Stop();
|
|
}
|
|
|
|
if (m_RefractoryTimer != null)
|
|
{
|
|
m_RefractoryTimer.Stop();
|
|
}
|
|
|
|
m_Running = false;
|
|
m_proximityActivated = false;
|
|
m_ExternalTrigger = false;
|
|
m_mob_who_triggered = null;
|
|
}
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
Stop();
|
|
// reset the protection against runaway looping
|
|
ClearSpawnedThisTick = true;
|
|
RemoveSpawnObjects();
|
|
ClearTags(true);
|
|
ResetAllFlags();
|
|
status_str = "";
|
|
m_killcount = 0;
|
|
OnHold = false;
|
|
mostRecentSpawnPosition = Point3D.Zero;
|
|
spawnPositionWayTable = null;
|
|
// dont advance before the next spawn
|
|
HoldSequence = true;
|
|
IsInactivated = false;
|
|
ResetSequential();
|
|
}
|
|
|
|
public void Respawn()
|
|
{
|
|
TryRespawn();
|
|
}
|
|
|
|
public bool TryRespawn()
|
|
{
|
|
inrespawn = true;
|
|
IsInactivated = false;
|
|
|
|
// reset the protection against runaway looping
|
|
ClearSpawnedThisTick = true;
|
|
|
|
// Delete all currently spawned objects
|
|
RemoveSpawnObjects();
|
|
|
|
// added the explicit start. Previously it relied on the automatic start that occurred when the spawnobject list was updated.
|
|
Start();
|
|
|
|
ResetNextSpawnTimes();
|
|
|
|
// Respawn all objects up to the spawners current maximum allowed
|
|
// note that by default, for proximity sensing, the spawner will only trigger once, but for respawns allow them all
|
|
bool keepProximityActivated = m_proximityActivated;
|
|
|
|
bool triedtospawn = false;
|
|
|
|
// attempt to spawn up to the MaxCount of the spawner
|
|
for (int x = 0; x < m_Count; x++)
|
|
{
|
|
triedtospawn = Spawn(false, 0);
|
|
|
|
if (x < m_Count - 1 || OnHold)
|
|
{
|
|
m_proximityActivated = keepProximityActivated;
|
|
}
|
|
}
|
|
if (!FreeRun)
|
|
{
|
|
m_mob_who_triggered = null;
|
|
}
|
|
|
|
ClearTags(true);
|
|
|
|
inrespawn = false;
|
|
|
|
return triedtospawn;
|
|
}
|
|
|
|
// used to optimize smartspawning use of hasholdsmartspawning
|
|
public void SmartRespawn()
|
|
{
|
|
inrespawn = true;
|
|
IsInactivated = false;
|
|
|
|
// reset the protection against runaway looping
|
|
ClearSpawnedThisTick = true;
|
|
|
|
// Delete all currently spawned objects
|
|
SmartRemoveSpawnObjects();
|
|
|
|
// added the explicit start. Previously it relied on the automatic start that occurred when the spawnobject list was updated.
|
|
Start();
|
|
|
|
ResetNextSpawnTimes();
|
|
|
|
// Respawn all objects up to the spawners current maximum allowed
|
|
// note that by default, for proximity sensing, the spawner will only trigger once, but for respawns allow them all
|
|
bool keepProximityActivated = m_proximityActivated;
|
|
|
|
// attempt to spawn up to the MaxCount of the spawner
|
|
for (int x = 0; x < m_Count; x++)
|
|
{
|
|
Spawn(true, 0);
|
|
|
|
if (x < m_Count - 1 || OnHold)
|
|
{
|
|
m_proximityActivated = keepProximityActivated;
|
|
}
|
|
}
|
|
|
|
if (!FreeRun)
|
|
{
|
|
m_mob_who_triggered = null;
|
|
}
|
|
|
|
ClearTags(true);
|
|
|
|
inrespawn = false;
|
|
}
|
|
|
|
|
|
public void SortSpawns()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// establish the entry order
|
|
int count = 0;
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
so.EntryOrder = count++;
|
|
}
|
|
|
|
m_SpawnObjects.Sort(new SubgroupSorter());
|
|
}
|
|
|
|
private class SubgroupSorter : IComparer<SpawnObject>
|
|
{
|
|
public int Compare(SpawnObject a, SpawnObject b)
|
|
{
|
|
if (a.SubGroup == b.SubGroup)
|
|
{
|
|
// use the entry order as the secondary sort factor
|
|
return a.EntryOrder - b.EntryOrder;
|
|
}
|
|
|
|
return a.SubGroup - b.SubGroup;
|
|
}
|
|
}
|
|
|
|
public static SpawnObject GetSpawnObject(XmlSpawner spawner, int sgroup)
|
|
{
|
|
if (spawner == null || spawner.m_SpawnObjects == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (int i = 0; i < spawner.m_SpawnObjects.Count; i++)
|
|
{
|
|
// find the first entry with matching subgroup id
|
|
if (spawner.m_SpawnObjects[i].SubGroup == sgroup)
|
|
{
|
|
return spawner.m_SpawnObjects[i];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static object GetSpawned(XmlSpawner spawner, int sgroup)
|
|
{
|
|
if (spawner == null || spawner.m_SpawnObjects == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (int i = 0; i < spawner.m_SpawnObjects.Count; i++)
|
|
{
|
|
// find the first entry with matching subgroup id
|
|
if (spawner.m_SpawnObjects[i].SubGroup == sgroup)
|
|
{
|
|
// find the first spawned object in the entry
|
|
if (spawner.m_SpawnObjects[i].SpawnedObjects.Count > 0)
|
|
{
|
|
return spawner.m_SpawnObjects[i].SpawnedObjects[0];
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static List<object> GetSpawnedList(XmlSpawner spawner, int sgroup)
|
|
{
|
|
List<object> newlist = new List<object>();
|
|
|
|
if (spawner == null || spawner.m_SpawnObjects == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (int i = 0; i < spawner.m_SpawnObjects.Count; i++)
|
|
{
|
|
// find the first entry with matching subgroup id
|
|
if (spawner.m_SpawnObjects[i].SubGroup == sgroup)
|
|
{
|
|
// find the first spawned object in the entry
|
|
|
|
if (spawner.m_SpawnObjects[i].SpawnedObjects.Count > 0)
|
|
{
|
|
for (int j = 0; j < spawner.m_SpawnObjects[i].SpawnedObjects.Count; j++)
|
|
{
|
|
newlist.Add(spawner.m_SpawnObjects[i].SpawnedObjects[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return newlist;
|
|
}
|
|
|
|
public bool HasSubGroups()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int j = 0; j < m_SpawnObjects.Count; j++)
|
|
{
|
|
if (m_SpawnObjects[j].SubGroup > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void ResetProximityActivated()
|
|
{
|
|
// dont reset triggering if free run mode has been selected
|
|
if (!FreeRun)
|
|
{
|
|
m_proximityActivated = false;
|
|
}
|
|
}
|
|
|
|
public bool HasIndividualSpawnTimes()
|
|
{
|
|
|
|
if (m_SpawnObjects != null && m_SpawnObjects.Count > 0)
|
|
{
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[i];
|
|
|
|
if (so.MinDelay != -1 || so.MaxDelay != -1)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void ResetNextSpawnTimes()
|
|
{
|
|
|
|
if (m_SpawnObjects != null && m_SpawnObjects.Count > 0)
|
|
{
|
|
for (int i = 0; i < m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[i];
|
|
|
|
so.NextSpawn = Core.Now;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RefreshNextSpawnTime(SpawnObject so)
|
|
{
|
|
if (so == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int mind = (int)(so.MinDelay * 60);
|
|
int maxd = (int)(so.MaxDelay * 60);
|
|
if (mind < 0 || maxd < 0)
|
|
{
|
|
so.NextSpawn = Core.Now;
|
|
}
|
|
else
|
|
{
|
|
|
|
TimeSpan delay = TimeSpan.FromSeconds(Utility.RandomMinMax(mind, maxd));
|
|
|
|
so.NextSpawn = Core.Now + delay;
|
|
}
|
|
|
|
}
|
|
|
|
public static bool IsValidMapLocation(int X, int Y, Map map)
|
|
{
|
|
if (map == null || map == Map.Internal)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check the location relative to the current map to make sure it is valid
|
|
if (X < 0 || X > map.Width || Y < 0 || Y > map.Height)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
public static bool IsValidMapLocation(Point3D location, Map map)
|
|
{
|
|
if (map == null || map == Map.Internal)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check the location relative to the current map to make sure it is valid
|
|
if (location.X < 0 || location.X > map.Width || location.Y < 0 || location.Y > map.Height)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
public static bool IsValidMapLocation(Point2D location, Map map)
|
|
{
|
|
if (map == null || map == Map.Internal)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// check the location relative to the current map to make sure it is valid
|
|
if (location.X < 0 || location.X > map.Width || location.Y < 0 || location.Y > map.Height)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static WayPoint GetWaypoint(string waypointstr)
|
|
{
|
|
WayPoint waypoint = null;
|
|
|
|
// try parsing the waypoint name to determine the waypoint. object syntax is "SERIAL,sernumber" or "waypointname"
|
|
if (!string.IsNullOrEmpty(waypointstr))
|
|
{
|
|
string[] wayargs = BaseXmlSpawner.ParseString(waypointstr, 2, ",");
|
|
if (wayargs != null && wayargs.Length > 0)
|
|
{
|
|
// is this a SERIAL specification?
|
|
if (wayargs[0] == "SERIAL")
|
|
{
|
|
|
|
// look it up by serial
|
|
if (wayargs.Length > 1)
|
|
{
|
|
uint sernum;
|
|
try
|
|
{
|
|
sernum = (uint)Convert.ToUInt64(wayargs[1][2..], 16);
|
|
IEntity e = World.FindEntity((Serial)sernum);
|
|
|
|
if (e is WayPoint point)
|
|
{
|
|
waypoint = point;
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// just look it up by name
|
|
Item wayitem = BaseXmlSpawner.FindItemByName(null, wayargs[0], "WayPoint");
|
|
if (wayitem is WayPoint point)
|
|
{
|
|
waypoint = point;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return waypoint;
|
|
}
|
|
|
|
private static bool HasTileSurface(Map map, int X, int Y, int Z)
|
|
{
|
|
if (map == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StaticTile[] tiles = map.Tiles.GetStaticTiles(X, Y, true);
|
|
|
|
if (tiles == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// go through the tiles and see if any are at the Z location
|
|
foreach (StaticTile o in tiles)
|
|
{
|
|
StaticTile i = o;
|
|
|
|
if (i.Z + i.Height == Z)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool CheckHoldSmartSpawning(object o)
|
|
{
|
|
if (o == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// try looking this up in the lookup table
|
|
if (holdSmartSpawningHash == null)
|
|
{
|
|
holdSmartSpawningHash = new Dictionary<Type, PropertyInfo>();
|
|
}
|
|
PropertyInfo prop;
|
|
if (!holdSmartSpawningHash.TryGetValue(o.GetType(), out prop))
|
|
{
|
|
prop = o.GetType().GetProperty("HoldSmartSpawning");
|
|
// check to make sure the HoldSmartSpawning property for this object has the right type
|
|
if (prop != null && (!prop.CanRead || prop.PropertyType != typeof(bool)))
|
|
{
|
|
prop = null;
|
|
}
|
|
|
|
holdSmartSpawningHash[o.GetType()] = prop;
|
|
}
|
|
|
|
if (prop != null)
|
|
{
|
|
try
|
|
{
|
|
return (bool)prop.GetValue(o, null);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool HasHoldSmartSpawning
|
|
{
|
|
get
|
|
{
|
|
// go through the spawn lists
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int x = 0; x < so.SpawnedObjects.Count; x++)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
if (CheckHoldSmartSpawning(o))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// if a non-null mob argument is passed, then check the canswim and cantwalk props to determine valid placement
|
|
public bool CanFit(int x, int y, int z, int height, bool checkBlocksFit, bool checkMobiles, bool requireSurface, Mobile mob)
|
|
{
|
|
Map map = Map;
|
|
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("CanFit mob {0}, map={1}", mob, map);
|
|
}
|
|
if (map == null || map == Map.Internal)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (x < 0 || y < 0 || x >= map.Width || y >= map.Height)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool hasSurface = false;
|
|
bool checkmob = false;
|
|
bool canswim = false;
|
|
bool cantwalk = false;
|
|
|
|
if (mob != null)
|
|
{
|
|
checkmob = true;
|
|
canswim = mob.CanSwim;
|
|
cantwalk = mob.CantWalk;
|
|
}
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("fitting mob {0} checkmob={1} swim={2} walk={3}", mob, checkmob, canswim, cantwalk);
|
|
}
|
|
LandTile lt = map.Tiles.GetLandTile(x, y);
|
|
|
|
bool surface;
|
|
bool wet = false;
|
|
|
|
map.GetAverageZ(x, y, out var lowZ, out var avgZ, out var topZ);
|
|
TileFlag landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags;
|
|
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("landtile at {0},{1},{2} lowZ={3} avgZ={4} topZ={5}", x, y, z, lowZ, avgZ, topZ);
|
|
}
|
|
|
|
var impassable = (landFlags & TileFlag.Impassable) != 0;
|
|
if (checkmob)
|
|
{
|
|
wet = (landFlags & TileFlag.Wet) != 0;
|
|
// dont allow wateronly creatures on land
|
|
if (cantwalk && !wet)
|
|
{
|
|
impassable = true;
|
|
}
|
|
|
|
// allow water creatures on water
|
|
if (canswim && wet)
|
|
{
|
|
impassable = false;
|
|
}
|
|
}
|
|
|
|
if (impassable && avgZ > z && z + height > lowZ)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!impassable && z == avgZ && !lt.Ignored)
|
|
{
|
|
hasSurface = true;
|
|
}
|
|
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("landtile at {0},{1},{2} wet={3} impassable={4} hassurface={5}", x, y, z, wet, impassable, hasSurface);
|
|
}
|
|
|
|
StaticTile[] staticTiles = map.Tiles.GetStaticTiles(x, y, true);
|
|
|
|
for (int i = 0; i < staticTiles.Length; ++i)
|
|
{
|
|
ItemData id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue];
|
|
surface = id.Surface;
|
|
impassable = id.Impassable;
|
|
if (checkmob)
|
|
{
|
|
wet = (id.Flags & TileFlag.Wet) != 0;
|
|
// dont allow wateronly creatures on land
|
|
if (cantwalk && !wet)
|
|
{
|
|
impassable = true;
|
|
}
|
|
|
|
// allow water creatures on water
|
|
if (canswim && wet)
|
|
{
|
|
surface = true;
|
|
impassable = false;
|
|
}
|
|
}
|
|
|
|
if ((surface || impassable) && staticTiles[i].Z + id.CalcHeight > z && z + height > staticTiles[i].Z)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (surface && !impassable && z == staticTiles[i].Z + id.CalcHeight)
|
|
{
|
|
hasSurface = true;
|
|
}
|
|
}
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("statics hassurface={0}", hasSurface);
|
|
}
|
|
|
|
Sector sector = map.GetSector(x, y);
|
|
List<Item> items = sector.Items;
|
|
List<Mobile> mobs = sector.Mobiles;
|
|
|
|
for (int i = 0; i < items.Count; ++i)
|
|
{
|
|
Item item = items[i];
|
|
|
|
if (item.ItemID < 0x4000 && item.AtWorldPoint(x, y))
|
|
{
|
|
ItemData id = item.ItemData;
|
|
surface = id.Surface;
|
|
impassable = id.Impassable;
|
|
if (checkmob)
|
|
{
|
|
wet = (id.Flags & TileFlag.Wet) != 0;
|
|
// dont allow wateronly creatures on land
|
|
if (cantwalk && !wet)
|
|
{
|
|
impassable = true;
|
|
}
|
|
|
|
// allow water creatures on water
|
|
if (canswim && wet)
|
|
{
|
|
surface = true;
|
|
impassable = false;
|
|
}
|
|
}
|
|
|
|
if ((surface || impassable || checkBlocksFit && item.BlocksFit) && item.Z + id.CalcHeight > z && z + height > item.Z)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (surface && !impassable && !item.Movable && z == item.Z + id.CalcHeight)
|
|
{
|
|
hasSurface = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("items hassurface={0}", hasSurface);
|
|
}
|
|
|
|
if (checkMobiles)
|
|
{
|
|
for (int i = 0; i < mobs.Count; ++i)
|
|
{
|
|
Mobile m = mobs[i];
|
|
|
|
if (m.Location.X == x && m.Location.Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden))
|
|
{
|
|
if (m.Z + 16 > z && z + height > m.Z)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("return requiresurface={0} hassurface={1}", requireSurface, hasSurface);
|
|
}
|
|
|
|
return !requireSurface || hasSurface;
|
|
}
|
|
|
|
public bool CanSpawnMobile(int x, int y, int z, Mobile mob)
|
|
{
|
|
if (DebugThis)
|
|
{
|
|
Console.WriteLine("CanSpawnMobile mob {0}", mob);
|
|
}
|
|
|
|
return Region.Find(new Point3D(x, y, z), Map).AllowSpawn() && Map.CanFit(x, y, z, 16);
|
|
}
|
|
|
|
public static bool HasRegionPoints(Region r) => r != null && r.Area.Length > 0;
|
|
|
|
public Rectangle2D SpawnerBounds => new(m_X, m_Y, m_Width + 1, m_Height + 1);
|
|
|
|
private void FindTileLocations(ref List<Point3D> locations, Map map, int startx, int starty, int width, int height, List<int> includetilelist, List<int> excludetilelist, TileFlag tileflag, bool checkitems, int spawnerZ)
|
|
{
|
|
if (width < 0 || height < 0 || map == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (locations == null)
|
|
{
|
|
locations = new List<Point3D>();
|
|
}
|
|
|
|
bool includetile;
|
|
bool excludetile;
|
|
|
|
for (int x = startx; x <= startx + width; x++)
|
|
{
|
|
for (int y = starty; y <= starty + height; y++)
|
|
{
|
|
bool allok = false;
|
|
Point3D p = Point3D.Zero;
|
|
// go through all of the tiles at the location and find those that are in the allowed tiles list
|
|
LandTile ltile = map.Tiles.GetLandTile(x, y);
|
|
TileFlag lflags = TileData.LandTable[ltile.ID & TileData.MaxLandValue].Flags;
|
|
|
|
// check the land tile
|
|
if (includetilelist != null && includetilelist.Count > 0)
|
|
{
|
|
includetile = includetilelist.Contains(ltile.ID & TileData.MaxLandValue);
|
|
}
|
|
else
|
|
{
|
|
includetile = true;
|
|
}
|
|
|
|
// non-excluded tiles must also be passable
|
|
if (excludetilelist != null && excludetilelist.Count > 0)
|
|
{
|
|
// also require the tile to be passable
|
|
excludetile = (lflags & TileFlag.Impassable) != 0 || excludetilelist.Contains(ltile.ID & TileData.MaxLandValue);
|
|
}
|
|
else
|
|
{
|
|
excludetile = false;
|
|
}
|
|
|
|
if (includetile && !excludetile && (lflags & tileflag) == tileflag)
|
|
{
|
|
p = new Point3D(x, y, ltile.Z + ltile.Height);
|
|
allok = true;
|
|
}
|
|
|
|
StaticTile[] statictiles = map.Tiles.GetStaticTiles(x, y, true);
|
|
|
|
// check the static tiles
|
|
for (int i = 0; i < statictiles.Length; ++i)
|
|
{
|
|
StaticTile stile = statictiles[i];
|
|
TileFlag sflags = TileData.ItemTable[stile.ID & TileData.MaxItemValue].Flags;
|
|
|
|
if (includetilelist != null && includetilelist.Count > 0)
|
|
{
|
|
includetile = includetilelist.Contains(stile.ID & TileData.MaxItemValue);
|
|
}
|
|
else
|
|
{
|
|
includetile = true;
|
|
}
|
|
|
|
// non-excluded tiles must also be passable
|
|
if (excludetilelist != null && excludetilelist.Count > 0)
|
|
{
|
|
excludetile = (sflags & TileFlag.Impassable) != 0 || excludetilelist.Contains(stile.ID & TileData.MaxItemValue);
|
|
}
|
|
else
|
|
{
|
|
excludetile = false;
|
|
}
|
|
|
|
if (includetile && !excludetile && (sflags & tileflag) == tileflag)
|
|
{
|
|
//Console.WriteLine("found statictile {0}/{1} at {2},{3},{4}", stile.ID, stile.ID & 0x3fff, x, y, stile.Z + stile.Height);
|
|
if (p == Point3D.Zero)
|
|
{
|
|
p = new Point3D(x, y, stile.Z + stile.Height);
|
|
}
|
|
else if (!allok && p.Z - spawnerZ > Math.Abs(stile.Z - spawnerZ))
|
|
{
|
|
p = new Point3D(x, y, stile.Z + stile.Height);
|
|
}
|
|
else if (Math.Abs(ltile.Z - spawnerZ) > Math.Abs(stile.Z - spawnerZ)) //maggiore distanza rispetto allo statico dallo spawner
|
|
{
|
|
p = new Point3D(x, y, stile.Z + stile.Height);
|
|
}
|
|
|
|
allok = true;
|
|
//locations.Add(new Point3D(x, y, stile.Z + stile.Height));
|
|
//break;
|
|
}
|
|
}
|
|
|
|
if (checkitems)
|
|
{
|
|
IPooledEnumerable itemslist = map.GetItemsInRange(new Point3D(x, y, 0), 0);
|
|
|
|
// check the itemsid
|
|
foreach (Item i in itemslist)
|
|
{
|
|
if (i.ItemData.Impassable)
|
|
{
|
|
excludetile = true;
|
|
}
|
|
|
|
TileFlag iflags = TileData.ItemTable[i.ItemID & TileData.MaxItemValue].Flags;
|
|
if (includetilelist != null && includetilelist.Count > 0)
|
|
{
|
|
includetile = includetilelist.Contains(i.ItemID & TileData.MaxItemValue);
|
|
}
|
|
else
|
|
{
|
|
includetile = true;
|
|
}
|
|
|
|
if (excludetilelist != null && excludetilelist.Count > 0)
|
|
{
|
|
excludetile = excludetilelist.Contains(i.ItemID & TileData.MaxItemValue);
|
|
}
|
|
else
|
|
{
|
|
excludetile = false;
|
|
}
|
|
|
|
if (includetile && !excludetile && (iflags & tileflag) == tileflag)
|
|
{
|
|
p = new Point3D(x, y, i.Z + i.ItemData.Height);
|
|
allok = true;
|
|
}
|
|
}
|
|
|
|
itemslist.Free();
|
|
}
|
|
|
|
if (allok && !excludetile)
|
|
{
|
|
locations.Add(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FindRegionTileLocations(ref List<Point3D> locations, Region r, List<int> includetilelist, List<int> excludetilelist, TileFlag tileflag, bool checkitems, int spawnerZ)
|
|
{
|
|
if (r == null || r.Area == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int count = r.Area.Length;
|
|
|
|
if (locations == null)
|
|
{
|
|
locations = new List<Point3D>();
|
|
}
|
|
|
|
// calculate fields of all rectangles (for probability calculating)
|
|
for (int n = 0; n < count; n++)
|
|
{
|
|
Rectangle3D ra = r.Area[n];
|
|
int sx = ra.Start.X;
|
|
int sy = ra.Start.Y;
|
|
int w = ra.Width;
|
|
int h = ra.Height;
|
|
|
|
// find all of the valid tile locations in the area
|
|
FindTileLocations(ref locations, r.Map, sx, sy, w, h, includetilelist, excludetilelist, tileflag, checkitems, spawnerZ);
|
|
}
|
|
}
|
|
|
|
public Point2D GetRandomRegionPoint(Region r)
|
|
{
|
|
int count = r.Area.Length;
|
|
|
|
int[] FieldArray = new int[count];
|
|
int total = 0;
|
|
|
|
// calculate fields of all rectangles (for probability calculating)
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Rectangle3D ra = r.Area[i];
|
|
total += FieldArray[i] = ra.Width * ra.Height;
|
|
}
|
|
|
|
int sum = 0;
|
|
int rnd = 0;
|
|
if (total > 0)
|
|
{
|
|
rnd = Utility.Random(total);
|
|
}
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
sum += FieldArray[i];
|
|
if (sum > rnd)
|
|
{
|
|
Rectangle3D r3d = r.Area[i];
|
|
if (r3d.Width >= 0)
|
|
{
|
|
x = r3d.Start.X + Utility.Random(r3d.Width);
|
|
}
|
|
|
|
if (r3d.Height >= 0)
|
|
{
|
|
y = r3d.Start.Y + Utility.Random(r3d.Height);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new Point2D(x, y);
|
|
}
|
|
|
|
public Point3D GetSpawnPosition(ISpawnable spawned, Map map) => GetSpawnPosition(true, spawned as Mobile);
|
|
|
|
// used for getting non-mobile spawn positions
|
|
public Point3D GetSpawnPosition(bool requiresurface) =>
|
|
// no pack spawning
|
|
GetSpawnPosition(requiresurface, -1, Point3D.Zero, null, null);
|
|
|
|
// used for getting mobile spawn positions
|
|
public Point3D GetSpawnPosition(bool requiresurface, Mobile mob) =>
|
|
// no pack spawning
|
|
GetSpawnPosition(requiresurface, -1, Point3D.Zero, null, mob);
|
|
|
|
// used for getting non-mobile spawn positions
|
|
public Point3D GetSpawnPosition(
|
|
bool requiresurface,
|
|
int packrange,
|
|
Point3D packcoord,
|
|
List<SpawnPositionInfo> spawnpositioning
|
|
) => GetSpawnPosition(requiresurface, packrange, packcoord, spawnpositioning, null);
|
|
|
|
public Point3D GetSpawnPosition(bool requiresurface, int packrange, Point3D packcoord, List<SpawnPositionInfo> spawnpositioning, Mobile mob)
|
|
{
|
|
Map map = Map;
|
|
|
|
if (map == null)
|
|
{
|
|
return Location;
|
|
}
|
|
|
|
// random positioning by default
|
|
SpawnPositionType positioning = SpawnPositionType.Random;
|
|
Mobile trigmob = null;
|
|
List<int> includetilelist = null;
|
|
List<int> excludetilelist = null;
|
|
bool checkitems = false;
|
|
// restrictions on tile flags
|
|
TileFlag tileflag = TileFlag.None;
|
|
List<Point3D> locations = null;
|
|
|
|
int fillinc = 1;
|
|
int positionrange = 0;
|
|
string prefix = null;
|
|
List<Item> WayList = null;
|
|
int xinc = 0;
|
|
int yinc = 0;
|
|
int zinc = 0;
|
|
if (spawnpositioning != null)
|
|
{
|
|
foreach (SpawnPositionInfo s in spawnpositioning)
|
|
{
|
|
if (s == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
trigmob = s.trigMob;
|
|
string[] positionargs = s.positionArgs;
|
|
|
|
// parse the possible args to the spawn position control keywords
|
|
switch (s.positionType)
|
|
{
|
|
case SpawnPositionType.Wet:
|
|
{
|
|
// syntax Wet
|
|
// find all of the wet tiles
|
|
tileflag |= TileFlag.Wet;
|
|
requiresurface = false;
|
|
break;
|
|
}
|
|
case SpawnPositionType.ItemID:
|
|
{
|
|
checkitems = true;
|
|
goto case SpawnPositionType.Tiles;
|
|
}
|
|
case SpawnPositionType.NoItemID:
|
|
{
|
|
checkitems = true;
|
|
goto case SpawnPositionType.NoTiles;
|
|
}
|
|
case SpawnPositionType.Tiles:
|
|
{
|
|
// syntax Tiles,start[,end]
|
|
// get the tiles in the range
|
|
requiresurface = false;
|
|
int start = -1;
|
|
int end = -1;
|
|
if (positionargs != null && positionargs.Length > 1)
|
|
{
|
|
try
|
|
{
|
|
start = int.Parse(positionargs[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
if (positionargs != null && positionargs.Length > 2)
|
|
{
|
|
try
|
|
{
|
|
end = int.Parse(positionargs[2]);
|
|
}
|
|
catch { }
|
|
}
|
|
if (includetilelist == null)
|
|
{
|
|
includetilelist = new List<int>();
|
|
}
|
|
|
|
// add the tiles to the list
|
|
if (start > -1 && end < 0)
|
|
{
|
|
includetilelist.Add(start);
|
|
}
|
|
else
|
|
if (start > -1 && end > -1)
|
|
{
|
|
for (int j = start; j <= end; j++)
|
|
{
|
|
includetilelist.Add(j);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SpawnPositionType.NoTiles:
|
|
{
|
|
// syntax Tiles,start[,end]
|
|
// get the tiles in the range
|
|
requiresurface = false;
|
|
int start = -1;
|
|
int end = -1;
|
|
if (positionargs != null && positionargs.Length > 1)
|
|
{
|
|
try
|
|
{
|
|
start = int.Parse(positionargs[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
if (positionargs != null && positionargs.Length > 2)
|
|
{
|
|
try
|
|
{
|
|
end = int.Parse(positionargs[2]);
|
|
}
|
|
catch { }
|
|
}
|
|
if (excludetilelist == null)
|
|
{
|
|
excludetilelist = new List<int>();
|
|
}
|
|
|
|
// add the tiles to the list
|
|
if (start > -1 && end < 0)
|
|
{
|
|
excludetilelist.Add(start);
|
|
}
|
|
else
|
|
if (start > -1 && end > -1)
|
|
{
|
|
for (int j = start; j <= end; j++)
|
|
{
|
|
excludetilelist.Add(j);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SpawnPositionType.RowFill:
|
|
case SpawnPositionType.ColFill:
|
|
case SpawnPositionType.Perimeter:
|
|
{
|
|
// syntax XFILL[,inc]
|
|
// syntax YFILL[,inc]
|
|
// syntax EDGE[,inc]
|
|
positioning = s.positionType;
|
|
if (positionargs != null && positionargs.Length > 1)
|
|
{
|
|
try
|
|
{
|
|
fillinc = int.Parse(positionargs[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
break;
|
|
}
|
|
case SpawnPositionType.RelXY:
|
|
case SpawnPositionType.DeltaLocation:
|
|
case SpawnPositionType.Location:
|
|
{
|
|
// syntax RELXY,xinc,yinc[,zinc]
|
|
// syntax XY,x,y[,z]
|
|
// syntax DXY,dx,dy[,dz]
|
|
positioning = s.positionType;
|
|
if (positionargs != null && positionargs.Length > 2)
|
|
{
|
|
try
|
|
{
|
|
xinc = int.Parse(positionargs[1]);
|
|
yinc = int.Parse(positionargs[2]);
|
|
}
|
|
catch { }
|
|
}
|
|
if (positionargs != null && positionargs.Length > 3)
|
|
{
|
|
try
|
|
{
|
|
zinc = int.Parse(positionargs[3]);
|
|
}
|
|
catch { }
|
|
}
|
|
break;
|
|
}
|
|
case SpawnPositionType.Waypoint:
|
|
{
|
|
// syntax WAYPOINT,prefix[,range]
|
|
positioning = s.positionType;
|
|
if (positionargs != null && positionargs.Length > 1)
|
|
{
|
|
prefix = positionargs[1];
|
|
}
|
|
|
|
if (positionargs != null && positionargs.Length > 2)
|
|
{
|
|
try
|
|
{
|
|
positionrange = int.Parse(positionargs[2]);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// find a list of items that match the waypoint prefix
|
|
if (prefix != null)
|
|
{
|
|
// see if there is an existing hashtable for the waypoint lists
|
|
if (spawnPositionWayTable == null)
|
|
{
|
|
spawnPositionWayTable = new Dictionary<string, List<Item>>();
|
|
}
|
|
|
|
// no existing list so create a new one
|
|
if (!spawnPositionWayTable.TryGetValue(prefix, out WayList) || WayList == null)
|
|
{
|
|
WayList = new List<Item>();
|
|
|
|
foreach (Item i in World.Items.Values)
|
|
{
|
|
if (i is WayPoint && !string.IsNullOrEmpty(i.Name) && i.Map == Map && i.Name == prefix)
|
|
{
|
|
// add it to the list of items
|
|
WayList.Add(i);
|
|
}
|
|
}
|
|
// add the new list to the local table
|
|
spawnPositionWayTable[prefix] = WayList;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SpawnPositionType.Player:
|
|
{
|
|
// syntax PLAYER[,range]
|
|
positioning = s.positionType;
|
|
if (positionargs != null && positionargs.Length > 1)
|
|
{
|
|
try
|
|
{
|
|
positionrange = int.Parse(positionargs[1]);
|
|
}
|
|
catch { }
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// precalculate tile locations if they have been specified
|
|
if (includetilelist != null || excludetilelist != null || tileflag != TileFlag.None)
|
|
{
|
|
if (m_Region != null && HasRegionPoints(m_Region))
|
|
{
|
|
FindRegionTileLocations(ref locations, m_Region, includetilelist, excludetilelist, tileflag, checkitems, Z);
|
|
}
|
|
else if (positioning == SpawnPositionType.Random)
|
|
{
|
|
FindTileLocations(ref locations, Map, m_X, m_Y, m_Width, m_Height, includetilelist, excludetilelist, tileflag, checkitems, Z);
|
|
}
|
|
}
|
|
|
|
// Try 10 times to find a Spawnable location.
|
|
// trace profiling indicates that this is a major bottleneck
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
int x = X;
|
|
int y = Y;
|
|
int z = Z;
|
|
|
|
int defaultZ = Z;
|
|
if (packrange >= 0 && packcoord != Point3D.Zero)
|
|
{
|
|
defaultZ = packcoord.Z;
|
|
}
|
|
|
|
if (packrange >= 0 && packcoord != Point3D.Zero)
|
|
{
|
|
// find a random coord relative to the packcoord
|
|
x = packcoord.X - packrange + Utility.Random(packrange * 2 + 1);
|
|
y = packcoord.Y - packrange + Utility.Random(packrange * 2 + 1);
|
|
}
|
|
else if (m_Region != null && HasRegionPoints(m_Region))
|
|
{
|
|
// if region spawning is selected then use that to find an x,y loc instead of the spawn box
|
|
|
|
if (includetilelist != null || excludetilelist != null || tileflag != TileFlag.None)
|
|
{
|
|
// use the precalculated tile locations
|
|
if (locations != null && locations.Count > 0)
|
|
{
|
|
Point3D p = locations[Utility.Random(locations.Count)];
|
|
x = p.X;
|
|
y = p.Y;
|
|
defaultZ = p.Z;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Point2D p = GetRandomRegionPoint(m_Region);
|
|
x = p.X;
|
|
y = p.Y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (positioning)
|
|
{
|
|
case SpawnPositionType.Random:
|
|
{
|
|
if (includetilelist != null || excludetilelist != null || tileflag != TileFlag.None)
|
|
{
|
|
|
|
if (locations != null && locations.Count > 0)
|
|
{
|
|
Point3D p = locations[Utility.Random(locations.Count)];
|
|
x = p.X;
|
|
y = p.Y;
|
|
defaultZ = p.Z;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
if (m_Width > 0)
|
|
{
|
|
x = m_X + Utility.Random(m_Width + 1);
|
|
}
|
|
|
|
if (m_Height > 0)
|
|
{
|
|
y = m_Y + Utility.Random(m_Height + 1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.RelXY:
|
|
{
|
|
x = mostRecentSpawnPosition.X + xinc;
|
|
y = mostRecentSpawnPosition.Y + yinc;
|
|
defaultZ = mostRecentSpawnPosition.Z + zinc;
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.DeltaLocation:
|
|
{
|
|
x = X + xinc;
|
|
y = Y + yinc;
|
|
defaultZ = Z + zinc;
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.Location:
|
|
{
|
|
x = xinc;
|
|
y = yinc;
|
|
defaultZ = zinc;
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.RowFill:
|
|
{
|
|
x = mostRecentSpawnPosition.X + fillinc;
|
|
y = mostRecentSpawnPosition.Y;
|
|
|
|
if (x < m_X)
|
|
{
|
|
x = m_X;
|
|
}
|
|
|
|
if (y < m_Y)
|
|
{
|
|
y = m_Y;
|
|
}
|
|
|
|
if (x > m_X + m_Width)
|
|
{
|
|
x = m_X + (x - m_X - m_Width - 1);
|
|
y++;
|
|
}
|
|
|
|
if (y > m_Y + m_Height)
|
|
{
|
|
y = m_Y;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.ColFill:
|
|
{
|
|
x = mostRecentSpawnPosition.X;
|
|
y = mostRecentSpawnPosition.Y + fillinc;
|
|
|
|
if (x < m_X)
|
|
{
|
|
x = m_X;
|
|
}
|
|
|
|
if (y < m_Y)
|
|
{
|
|
y = m_Y;
|
|
}
|
|
|
|
if (y > m_Y + m_Height)
|
|
{
|
|
y = m_Y + (y - m_Y - m_Height - 1);
|
|
x++;
|
|
}
|
|
|
|
if (x > m_X + m_Width)
|
|
{
|
|
x = m_X;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.Perimeter:
|
|
{
|
|
x = mostRecentSpawnPosition.X;
|
|
y = mostRecentSpawnPosition.Y;
|
|
|
|
// if the point is not on the perimeter, reset it to the corner
|
|
if (x != m_X && x != m_X + m_Width && y != m_Y && y != m_Y + m_Height)
|
|
{
|
|
x = m_X;
|
|
y = m_Y;
|
|
}
|
|
|
|
if (y == m_Y && x < m_X + m_Width)
|
|
{
|
|
x += fillinc;
|
|
}
|
|
else
|
|
if (y == m_Y + m_Height && x > m_X)
|
|
{
|
|
x -= fillinc;
|
|
}
|
|
else
|
|
if (x == m_X && y > m_Y)
|
|
{
|
|
y -= fillinc;
|
|
}
|
|
else
|
|
if (x == m_X + m_Width && y < m_Y + m_Height)
|
|
{
|
|
y += fillinc;
|
|
}
|
|
|
|
if (x > m_X + m_Width)
|
|
{
|
|
x = m_X + m_Width;
|
|
}
|
|
|
|
if (y > m_Y + m_Height)
|
|
{
|
|
y = m_Y + m_Height;
|
|
}
|
|
|
|
if (x < m_X)
|
|
{
|
|
x = m_X;
|
|
}
|
|
|
|
if (y < m_Y)
|
|
{
|
|
y = m_Y;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.Player:
|
|
{
|
|
if (trigmob != null)
|
|
{
|
|
x = trigmob.Location.X;
|
|
y = trigmob.Location.Y;
|
|
if (positionrange > 0)
|
|
{
|
|
x += Utility.Random(positionrange * 2 + 1) - positionrange;
|
|
y += Utility.Random(positionrange * 2 + 1) - positionrange;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SpawnPositionType.Waypoint:
|
|
{
|
|
// pick an item randomly from the waylist
|
|
if (WayList != null && WayList.Count > 0)
|
|
{
|
|
int index = Utility.Random(WayList.Count);
|
|
Item waypoint = WayList[index];
|
|
if (waypoint != null)
|
|
{
|
|
x = waypoint.Location.X;
|
|
y = waypoint.Location.Y;
|
|
defaultZ = waypoint.Location.Z;
|
|
if (positionrange > 0)
|
|
{
|
|
x += Utility.Random(positionrange * 2 + 1) - positionrange;
|
|
y += Utility.Random(positionrange * 2 + 1) - positionrange;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
mostRecentSpawnPosition = new Point3D(x, y, defaultZ);
|
|
}
|
|
|
|
// skip invalid points
|
|
if (x < 0 || y < 0 || x == 0 && y == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// try to find a valid spawn location using the z coord of the spawner
|
|
// relax the normal surface requirement for mobiles if the flag is set
|
|
var fit = requiresurface ? CanSpawnMobile(x, y, defaultZ, mob) : Map.CanFit(x, y, defaultZ, SpawnFitSize, true, false, false);
|
|
|
|
// if that fails then try to find a valid z coord
|
|
if (fit)
|
|
{
|
|
return new Point3D(x, y, defaultZ);
|
|
}
|
|
|
|
z = Map.GetAverageZ(x, y);
|
|
|
|
fit = requiresurface ? CanSpawnMobile(x, y, z, mob) : Map.CanFit(x, y, z, SpawnFitSize, true, false, false);
|
|
|
|
if (fit)
|
|
{
|
|
return new Point3D(x, y, z);
|
|
}
|
|
}
|
|
|
|
if (packrange >= 0 && packcoord != Point3D.Zero)
|
|
{
|
|
return packcoord;
|
|
}
|
|
|
|
return Location;
|
|
}
|
|
|
|
public int GetCreatureMax(int index)
|
|
{
|
|
Defrag(false);
|
|
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return m_SpawnObjects[index].MaxCount;
|
|
}
|
|
|
|
private void DeleteFromList(List<object> list)
|
|
{
|
|
if (list == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (object o in list)
|
|
{
|
|
if (o is Item item)
|
|
{
|
|
item.Delete();
|
|
}
|
|
else if (o is Mobile mobile)
|
|
{
|
|
mobile.Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DeleteFromList(List<Item> listi, List<Mobile> listm)
|
|
{
|
|
if (listi != null)
|
|
{
|
|
int i = listi.Count;
|
|
|
|
while (--i >= 0)
|
|
{
|
|
if (i < listi.Count && listi[i] != null)
|
|
{
|
|
try
|
|
{
|
|
listi[i].Delete();
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
|
|
listi.Clear();
|
|
}
|
|
|
|
if (listm != null)
|
|
{
|
|
int i = listm.Count;
|
|
|
|
while (--i >= 0)
|
|
{
|
|
if (i < listm.Count && listm[i] != null)
|
|
{
|
|
try
|
|
{
|
|
listm[i].Delete();
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
|
|
listm.Clear();
|
|
}
|
|
}
|
|
|
|
public void RemoveSpawnObjects()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Defrag(false);
|
|
|
|
ClearTags(true);
|
|
List<object> deletelist = new List<object>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
object o = so.SpawnedObjects[i];
|
|
|
|
if (o is Item || o is Mobile)
|
|
{
|
|
deletelist.Add(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteFromList(deletelist);
|
|
|
|
// Defrag again
|
|
Defrag(false);
|
|
}
|
|
|
|
|
|
public void RemoveSpawnObjects(SpawnObject so)
|
|
{
|
|
if (so == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Defrag(false);
|
|
|
|
List<object> deletelist = new List<object>();
|
|
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
object o = so.SpawnedObjects[i];
|
|
|
|
if (o is Item || o is Mobile)
|
|
{
|
|
deletelist.Add(o);
|
|
}
|
|
}
|
|
|
|
DeleteFromList(deletelist);
|
|
|
|
// Defrag again
|
|
Defrag(false);
|
|
}
|
|
|
|
public void ClearSubgroup(int subgroup)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Defrag(false);
|
|
|
|
ClearTags(true);
|
|
List<object> deletelist = new List<object>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
if (so.SubGroup != subgroup || !so.ClearOnAdvance)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
object o = so.SpawnedObjects[i];
|
|
|
|
if (o is Item || o is Mobile)
|
|
{
|
|
deletelist.Add(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteFromList(deletelist);
|
|
|
|
// Defrag again
|
|
Defrag(false);
|
|
}
|
|
|
|
|
|
// used to optimize smart spawning by removing all objects except those that have hold smartspawning
|
|
public void SmartRemoveSpawnObjects()
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Defrag(false);
|
|
|
|
ClearTags(true);
|
|
List<object> deletelist = new List<object>();
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
for (int i = 0; i < so.SpawnedObjects.Count; ++i)
|
|
{
|
|
object o = so.SpawnedObjects[i];
|
|
|
|
// new optimization for smart spawning to remove all objects except those with hold smartspawning enabled
|
|
if (CheckHoldSmartSpawning(o))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (o is Item || o is Mobile)
|
|
{
|
|
deletelist.Add(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteFromList(deletelist);
|
|
|
|
// Defrag again
|
|
Defrag(false);
|
|
}
|
|
|
|
public void AddSpawnObject(string SpawnObjectName)
|
|
{
|
|
if (m_SpawnObjects == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Defrag(false);
|
|
|
|
// Find the spawn object and increment its count by one
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
if (so.TypeName.ToUpper() == SpawnObjectName.ToUpper())
|
|
{
|
|
// Add one to the total count
|
|
m_Count++;
|
|
|
|
// Increment the max count for the current creature
|
|
so.ActualMaxCount++;
|
|
|
|
//only spawn them immediately if the spawner is running
|
|
if (Running)
|
|
{
|
|
Spawn(SpawnObjectName, false, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
InvalidateProperties();
|
|
}
|
|
|
|
public void DeleteSpawnObject(Mobile from, string SpawnObjectName)
|
|
{
|
|
bool WasRunning = m_Running;
|
|
|
|
try
|
|
{
|
|
// Stop spawning for a moment
|
|
Stop();
|
|
|
|
// Clean up any spawns marked as deleted
|
|
Defrag(false);
|
|
|
|
// Keep a reference to the spawn object
|
|
SpawnObject TheSpawn = null;
|
|
|
|
// Find the spawn object and increment its count by one
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
if (so.TypeName.ToUpper() == SpawnObjectName.ToUpper())
|
|
{
|
|
// Set the spawn
|
|
TheSpawn = so;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Was the spawn object found
|
|
if (TheSpawn != null)
|
|
{
|
|
bool delete_this_entry = false;
|
|
|
|
// Decrement the max count for the current creature
|
|
TheSpawn.ActualMaxCount--;
|
|
|
|
// Make sure the spawn count does not go negative
|
|
if (TheSpawn.MaxCount < 0)
|
|
{
|
|
TheSpawn.MaxCount = 0;
|
|
delete_this_entry = true;
|
|
}
|
|
|
|
if (!delete_this_entry)
|
|
{
|
|
// Subtract one to the total count
|
|
m_Count--;
|
|
}
|
|
|
|
// Make sure the count does not go negative
|
|
if (m_Count < 0)
|
|
{
|
|
m_Count = 0;
|
|
|
|
}
|
|
|
|
List<object> deletelist = new List<object>();
|
|
|
|
// Remove any spawns over the count
|
|
while (TheSpawn.SpawnedObjects != null && TheSpawn.SpawnedObjects.Count > 0 && TheSpawn.SpawnedObjects.Count > TheSpawn.MaxCount)
|
|
{
|
|
object o = TheSpawn.SpawnedObjects[0];
|
|
|
|
// Delete the object
|
|
if (o is Item || o is Mobile)
|
|
{
|
|
deletelist.Add(o);
|
|
}
|
|
|
|
TheSpawn.SpawnedObjects.Remove(o);
|
|
}
|
|
|
|
DeleteFromList(deletelist);
|
|
|
|
// Check if the spawn object should be removed
|
|
if (delete_this_entry)
|
|
{
|
|
m_SpawnObjects.Remove(TheSpawn);
|
|
if (from != null)
|
|
{
|
|
var loc = GetWorldLocation();
|
|
CommandLogging.WriteLine(from, $"{from.AccessLevel} {CommandLogging.Format(from)} removed from XmlSpawner {Serial} '{Name}' [{loc.X}, {loc.Y}] ({Map}) : {SpawnObjectName}");
|
|
}
|
|
}
|
|
}
|
|
|
|
InvalidateProperties();
|
|
}
|
|
finally
|
|
{
|
|
if (WasRunning)
|
|
{
|
|
Start();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveSpawnObject(SpawnObject so)
|
|
{
|
|
if (m_SpawnObjects.Contains(so))
|
|
{
|
|
m_SpawnObjects.Remove(so);
|
|
}
|
|
}
|
|
|
|
public static object CreateObject(Type type, string itemtypestring) => CreateObject(type, itemtypestring, true);
|
|
|
|
public static object CreateObject(Type type, string itemtypestring, bool requireConstructible)
|
|
{
|
|
// look for constructor arguments to be passed to it with the syntax type,arg1,arg2,.../
|
|
string[] typewordargs = BaseXmlSpawner.ParseObjectArgs(itemtypestring);
|
|
|
|
return CreateObject(type, typewordargs, requireConstructible);
|
|
}
|
|
|
|
public static object CreateObject(Type type, string[] typewordargs, bool requireConstructible)
|
|
{
|
|
if (type == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
object o = null;
|
|
|
|
int typearglen = 0;
|
|
if (typewordargs != null)
|
|
{
|
|
typearglen = typewordargs.Length;
|
|
}
|
|
|
|
// ok, there are args in the typename, so we need to invoke the proper constructor
|
|
ConstructorInfo[] ctors = type.GetConstructors();
|
|
|
|
// go through all the constructors for this type
|
|
for (int i = 0; i < ctors.Length; ++i)
|
|
{
|
|
ConstructorInfo ctor = ctors[i];
|
|
|
|
// if requireConstructible is true, then allow either condition
|
|
#if (RESTRICTConstructible)
|
|
if (!(requireConstructible && Add.IsConstructible(ctor,requester)))
|
|
continue;
|
|
#else
|
|
if (!(requireConstructible && IsConstructible(ctor)))
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
// check the parameter list of the constructor
|
|
ParameterInfo[] paramList = ctor.GetParameters();
|
|
|
|
// and compare with the argument list provided
|
|
if (typearglen == paramList.Length)
|
|
{
|
|
// this is a constructor that takes args and matches the number of args passed in to CreateObject
|
|
if (paramList.Length > 0)
|
|
{
|
|
object[] paramValues = null;
|
|
|
|
try
|
|
{
|
|
paramValues = Add.ParseValues(paramList, typewordargs);
|
|
}
|
|
catch { }
|
|
|
|
if (paramValues == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// ok, have a match on args, so try to construct it
|
|
try
|
|
{
|
|
o = Activator.CreateInstance(type, paramValues);
|
|
}
|
|
catch { }
|
|
}
|
|
else
|
|
{
|
|
// zero argument constructor
|
|
try
|
|
{
|
|
o = Activator.CreateInstance(type);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// successfully constructed the object, otherwise try another matching constructor
|
|
if (o != null)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
private static void DoGlobalSectorTimer(TimeSpan delay)
|
|
{
|
|
if (m_GlobalSectorTimer != null)
|
|
{
|
|
m_GlobalSectorTimer.Stop();
|
|
}
|
|
|
|
m_GlobalSectorTimer = new GlobalSectorTimer(delay);
|
|
|
|
m_GlobalSectorTimer.Start();
|
|
}
|
|
|
|
private class GlobalSectorTimer : Timer
|
|
{
|
|
|
|
public GlobalSectorTimer(TimeSpan delay) : base(delay, delay)
|
|
{
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
// check the sectors
|
|
|
|
// check all active players
|
|
foreach (NetState state in TcpServer.Instances)
|
|
{
|
|
Mobile m = state.Mobile;
|
|
|
|
if (m != null && (m.AccessLevel <= SmartSpawnAccessLevel || !m.Hidden))
|
|
{
|
|
// activate any spawner in the sector they are in
|
|
if (m.Map != null && m.Map != Map.Internal)
|
|
{
|
|
Sector s = m.Map.GetSector(m.Location);
|
|
|
|
if (s != null && GlobalSectorTable[m.Map.MapID] != null)
|
|
{
|
|
|
|
List<XmlSpawner> spawnerlist; // = GlobalSectorTable[m.Map.MapID][s];
|
|
if (GlobalSectorTable[m.Map.MapID].TryGetValue(s, out spawnerlist) && spawnerlist != null)
|
|
{
|
|
foreach (XmlSpawner spawner in spawnerlist)
|
|
{
|
|
|
|
if (spawner != null && !spawner.Deleted && spawner.Running && spawner.SmartSpawning && spawner.IsInactivated)
|
|
{
|
|
spawner.SmartRespawn();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DoSectorTimer(TimeSpan delay)
|
|
{
|
|
if (m_SectorTimer != null)
|
|
{
|
|
m_SectorTimer.Stop();
|
|
}
|
|
|
|
m_SectorTimer = new SectorTimer(this, delay);
|
|
|
|
m_SectorTimer.Start();
|
|
}
|
|
|
|
private class SectorTimer : Timer
|
|
{
|
|
private readonly XmlSpawner m_Spawner;
|
|
|
|
public SectorTimer(XmlSpawner spawner, TimeSpan delay) : base(delay, delay) => m_Spawner = spawner;
|
|
|
|
protected override void OnTick()
|
|
{
|
|
// check the sectors
|
|
if (m_Spawner != null && !m_Spawner.Deleted && m_Spawner.Running && m_Spawner.IsInactivated)
|
|
{
|
|
if (m_Spawner.SmartSpawning)
|
|
{
|
|
if (m_Spawner.HasActiveSectors)
|
|
{
|
|
Stop();
|
|
|
|
m_Spawner.SmartRespawn();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Stop();
|
|
|
|
m_Spawner.IsInactivated = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Stop();
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
private class WarnTimer2 : Timer
|
|
{
|
|
private readonly List<WarnEntry2> m_List;
|
|
|
|
private class WarnEntry2
|
|
{
|
|
public Point3D m_Point;
|
|
public Map m_Map;
|
|
public string m_Name;
|
|
|
|
public WarnEntry2(Point3D p, Map map, string name)
|
|
{
|
|
m_Point = p;
|
|
m_Map = map;
|
|
m_Name = name;
|
|
}
|
|
}
|
|
|
|
public WarnTimer2()
|
|
: base(TimeSpan.FromSeconds(1.0))
|
|
{
|
|
m_List = new List<WarnEntry2>();
|
|
Start();
|
|
}
|
|
|
|
public void Add(Point3D p, Map map, string name)
|
|
{
|
|
m_List.Add(new WarnEntry2(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}", Core.Now);
|
|
op.WriteLine("# Format: X Y Z F Name");
|
|
op.WriteLine();
|
|
|
|
foreach (WarnEntry2 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
|
|
{ }
|
|
}
|
|
}
|
|
|
|
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 void DoTimer(TimeSpan delay)
|
|
{
|
|
if (!m_Running)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_End = Core.Now + delay;
|
|
|
|
if (m_Timer != null)
|
|
{
|
|
m_Timer.Stop();
|
|
}
|
|
|
|
m_Timer = new SpawnerTimer(this, delay);
|
|
m_Timer.Start();
|
|
}
|
|
|
|
public void DoTimer2(TimeSpan delay)
|
|
{
|
|
m_DurEnd = Core.Now + delay;
|
|
if (m_Duration > TimeSpan.FromMinutes(0) || m_durActivated)
|
|
{
|
|
if (m_DurTimer != null)
|
|
{
|
|
m_DurTimer.Stop();
|
|
}
|
|
|
|
m_DurTimer = new InternalTimer(this, delay);
|
|
m_DurTimer.Start();
|
|
m_durActivated = true;
|
|
}
|
|
}
|
|
|
|
public void DoTimer3(TimeSpan delay)
|
|
{
|
|
m_RefractEnd = Core.Now + delay;
|
|
m_refractActivated = true;
|
|
|
|
if (m_RefractoryTimer != null)
|
|
{
|
|
m_RefractoryTimer.Stop();
|
|
}
|
|
|
|
m_RefractoryTimer = new InternalTimer3(this, delay);
|
|
m_RefractoryTimer.Start();
|
|
}
|
|
|
|
// added the duration timer that begins on spawning
|
|
private class InternalTimer : Timer
|
|
{
|
|
private readonly XmlSpawner m_spawner;
|
|
|
|
public InternalTimer(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_spawner = spawner;
|
|
|
|
protected override void OnTick()
|
|
{
|
|
if (m_spawner != null && !m_spawner.Deleted)
|
|
{
|
|
m_spawner.RemoveSpawnObjects();
|
|
m_spawner.m_durActivated = false;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private class SpawnerTimer : Timer
|
|
{
|
|
private readonly XmlSpawner m_Spawner;
|
|
|
|
public SpawnerTimer(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_Spawner = spawner;
|
|
|
|
protected override void OnTick()
|
|
{
|
|
if (m_Spawner != null && !m_Spawner.Deleted)
|
|
{
|
|
m_Spawner.OnTick();
|
|
}
|
|
}
|
|
}
|
|
|
|
// added the refractory timer that begins on proximity triggering
|
|
private class InternalTimer3 : Timer
|
|
{
|
|
private readonly XmlSpawner m_spawner;
|
|
|
|
public InternalTimer3(XmlSpawner spawner, TimeSpan delay) : base(delay) => m_spawner = spawner;
|
|
|
|
protected override void OnTick()
|
|
{
|
|
if (m_spawner != null && !m_spawner.Deleted)
|
|
{
|
|
// reenable triggering
|
|
m_spawner.m_refractActivated = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Serialize(IGenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
|
|
writer.Write(32); // version
|
|
// version 31
|
|
writer.Write(m_DisableGlobalAutoReset);
|
|
// Version 30
|
|
writer.Write(m_AllowNPCTriggering);
|
|
|
|
// Version 29
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
writer.Write(m_SpawnObjects.Count);
|
|
for (int i = 0; i < m_SpawnObjects.Count; ++i)
|
|
{
|
|
// Write the spawns per tick value
|
|
writer.Write(m_SpawnObjects[i].SpawnsPerTick);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// empty spawner
|
|
writer.Write(0);
|
|
}
|
|
|
|
// Version 28
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
for (int i = 0; i < m_SpawnObjects.Count; ++i)
|
|
{
|
|
// Write the pack range value
|
|
writer.Write(m_SpawnObjects[i].PackRange);
|
|
}
|
|
}
|
|
|
|
|
|
// Version 27
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
for (int i = 0; i < m_SpawnObjects.Count; ++i)
|
|
{
|
|
// Write the disable spawn flag
|
|
writer.Write(m_SpawnObjects[i].Disabled);
|
|
}
|
|
}
|
|
|
|
// Version 26
|
|
writer.Write(m_SpawnOnTrigger);
|
|
|
|
// Version 24
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
for (int i = 0; i < m_SpawnObjects.Count; ++i)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[i];
|
|
// Write the restrict kills flag
|
|
writer.Write(so.RestrictKillsToSubgroup);
|
|
// Write the clear on advance flag
|
|
writer.Write(so.ClearOnAdvance);
|
|
// Write the mindelay
|
|
writer.Write(so.MinDelay);
|
|
// Write the maxdelay
|
|
writer.Write(so.MaxDelay);
|
|
// write the next spawn time for the subgrop
|
|
writer.WriteDeltaTime(so.NextSpawn);
|
|
|
|
}
|
|
}
|
|
|
|
if (m_ShowBoundsItems != null && m_ShowBoundsItems.Count > 0)
|
|
{
|
|
writer.Write(true);
|
|
writer.Write(m_ShowBoundsItems);
|
|
}
|
|
else
|
|
{
|
|
// empty showbounds item list
|
|
writer.Write(false);
|
|
}
|
|
|
|
// Version 23
|
|
writer.Write(IsInactivated);
|
|
writer.Write(m_SmartSpawning);
|
|
// Version 22
|
|
writer.Write(m_SkillTrigger);
|
|
writer.Write((int)m_skill_that_triggered);
|
|
writer.Write(m_FreeRun);
|
|
writer.Write(m_mob_who_triggered);
|
|
// Version 21
|
|
writer.Write(m_DespawnTime);
|
|
// Version 20
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
for (int i = 0; i < m_SpawnObjects.Count; ++i)
|
|
{
|
|
// Write the requiresurface flag
|
|
writer.Write(m_SpawnObjects[i].RequireSurface);
|
|
}
|
|
}
|
|
// Version 19
|
|
writer.Write(m_ConfigFile);
|
|
writer.Write(m_OnHold);
|
|
writer.Write(m_HoldSequence);
|
|
// compute the number of tags to save
|
|
int tagcount = 0;
|
|
for (int i = 0; i < m_KeywordTagList.Count; i++)
|
|
{
|
|
// only save WAIT type keywords or other keywords that have the save flag set
|
|
if ((m_KeywordTagList[i].Flags & BaseXmlSpawner.KeywordFlags.Serialize) != 0)
|
|
{
|
|
tagcount++;
|
|
}
|
|
}
|
|
writer.Write(tagcount);
|
|
// and write them out
|
|
for (int i = 0; i < m_KeywordTagList.Count; i++)
|
|
{
|
|
if ((m_KeywordTagList[i].Flags & BaseXmlSpawner.KeywordFlags.Serialize) != 0)
|
|
{
|
|
m_KeywordTagList[i].Serialize(writer);
|
|
}
|
|
}
|
|
// Version 18
|
|
writer.Write(m_AllowGhostTriggering);
|
|
// Version 17
|
|
// removed in version 25
|
|
//writer.Write(m_TextEntryBook);
|
|
// Version 16
|
|
writer.Write(m_SequentialSpawning);
|
|
// write out the remaining time until sequential reset
|
|
writer.Write(NextSeqReset);
|
|
// Write the spawn object list
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
for (int i = 0; i < m_SpawnObjects.Count; ++i)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[i];
|
|
// Write the subgroup and sequential reset time
|
|
writer.Write(so.SubGroup);
|
|
writer.Write(so.SequentialResetTime);
|
|
writer.Write(so.SequentialResetTo);
|
|
writer.Write(so.KillsNeeded);
|
|
}
|
|
}
|
|
writer.Write(m_RegionName);
|
|
|
|
// Version 15
|
|
writer.Write(m_ExternalTriggering);
|
|
writer.Write(m_ExternalTrigger);
|
|
|
|
// Version 14
|
|
writer.Write(m_NoItemTriggerName);
|
|
|
|
// Version 13
|
|
writer.Write(m_GumpState);
|
|
|
|
// Version 12
|
|
int todtype = (int)m_TODMode;
|
|
writer.Write(todtype);
|
|
|
|
// Version 11
|
|
writer.Write(m_KillReset);
|
|
writer.Write(m_skipped);
|
|
writer.Write(m_spawncheck);
|
|
|
|
// Version 10
|
|
writer.Write(m_SetPropertyItem);
|
|
|
|
// Version 9
|
|
writer.Write(m_TriggerProbability);
|
|
|
|
// Version 8
|
|
writer.Write(m_MobPropertyName);
|
|
writer.Write(m_MobTriggerName);
|
|
writer.Write(m_PlayerPropertyName);
|
|
|
|
// Version 7
|
|
writer.Write(m_SpeechTrigger);
|
|
|
|
// Version 6
|
|
writer.Write(m_ItemTriggerName);
|
|
|
|
// Version 5
|
|
writer.Write(m_ProximityTriggerMessage);
|
|
writer.Write(m_ObjectPropertyItem);
|
|
writer.Write(m_ObjectPropertyName);
|
|
writer.Write(m_killcount);
|
|
|
|
// Version 4
|
|
writer.Write(m_ProximityRange);
|
|
writer.Write(m_ProximityTriggerSound);
|
|
writer.Write(m_proximityActivated);
|
|
writer.Write(m_durActivated);
|
|
writer.Write(m_refractActivated);
|
|
writer.Write(m_StackAmount);
|
|
writer.Write(m_TODStart);
|
|
writer.Write(m_TODEnd);
|
|
writer.Write(m_MinRefractory);
|
|
writer.Write(m_MaxRefractory);
|
|
if (m_refractActivated)
|
|
{
|
|
writer.Write(m_RefractEnd - Core.Now);
|
|
}
|
|
|
|
if (m_durActivated)
|
|
{
|
|
writer.Write(m_DurEnd - Core.Now);
|
|
}
|
|
|
|
// Version 3
|
|
writer.Write(m_ShowContainerStatic);
|
|
// Version 2
|
|
writer.Write(m_Duration);
|
|
|
|
// Version 1
|
|
writer.Write(m_UniqueId);
|
|
writer.Write(m_HomeRangeIsRelative);
|
|
|
|
// Version 0
|
|
writer.Write(m_Name);
|
|
writer.Write(m_X);
|
|
writer.Write(m_Y);
|
|
writer.Write(m_Width);
|
|
writer.Write(m_Height);
|
|
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.Write(m_End - Core.Now);
|
|
}
|
|
|
|
// Write the spawn object list
|
|
int nso = 0;
|
|
if (m_SpawnObjects != null)
|
|
{
|
|
nso = m_SpawnObjects.Count;
|
|
}
|
|
|
|
writer.Write(nso);
|
|
for (int i = 0; i < nso; ++i)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[i];
|
|
|
|
// Write the type and maximum count
|
|
writer.Write(so.TypeName);
|
|
writer.Write(so.ActualMaxCount);
|
|
|
|
// Write the spawned object information
|
|
writer.Write(so.SpawnedObjects.Count);
|
|
for (int x = 0; x < so.SpawnedObjects.Count; ++x)
|
|
{
|
|
object o = so.SpawnedObjects[x];
|
|
|
|
if (o is Item item)
|
|
{
|
|
writer.Write(item);
|
|
}
|
|
else if (o is Mobile mobile)
|
|
{
|
|
writer.Write(mobile);
|
|
}
|
|
else
|
|
{
|
|
// if this is a keyword tag then add some more info
|
|
if (o is BaseXmlSpawner.KeywordTag tag)
|
|
{
|
|
writer.Write(-1 * tag.Serial - 2);
|
|
}
|
|
else
|
|
{
|
|
writer.Write(Serial.MinusOne);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Deserialize(IGenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
|
|
int version = reader.ReadInt();
|
|
bool haveproximityrange = false;
|
|
bool hasnewobjectinfo = false;
|
|
int tmpSpawnListSize = 0;
|
|
List<int> tmpSubGroup = null;
|
|
List<double> tmpSequentialResetTime = null;
|
|
List<int> tmpSequentialResetTo = null;
|
|
List<int> tmpKillsNeeded = null;
|
|
List<bool> tmpRequireSurface = null;
|
|
List<bool> tmpRestrictKillsToSubgroup = null;
|
|
List<bool> tmpClearOnAdvance = null;
|
|
List<double> tmpMinDelay = null;
|
|
List<double> tmpMaxDelay = null;
|
|
List<DateTime> tmpNextSpawn = null;
|
|
List<bool> tmpDisableSpawn = null;
|
|
List<int> tmpPackRange = null;
|
|
List<int> tmpSpawnsPer = null;
|
|
|
|
switch (version)
|
|
{
|
|
case 32:
|
|
case 31:
|
|
{
|
|
m_DisableGlobalAutoReset = reader.ReadBool();
|
|
goto case 30;
|
|
}
|
|
case 30:
|
|
{
|
|
m_AllowNPCTriggering = reader.ReadBool();
|
|
goto case 29;
|
|
}
|
|
case 29:
|
|
{
|
|
tmpSpawnListSize = reader.ReadInt();
|
|
tmpSpawnsPer = new List<int>(tmpSpawnListSize);
|
|
for (int i = 0; i < tmpSpawnListSize; ++i)
|
|
{
|
|
int spawnsper = reader.ReadInt();
|
|
|
|
tmpSpawnsPer.Add(spawnsper);
|
|
|
|
}
|
|
goto case 28;
|
|
}
|
|
case 28:
|
|
{
|
|
tmpPackRange = new List<int>(tmpSpawnListSize);
|
|
for (int i = 0; i < tmpSpawnListSize; ++i)
|
|
{
|
|
int packrange = reader.ReadInt();
|
|
|
|
tmpPackRange.Add(packrange);
|
|
|
|
}
|
|
goto case 27;
|
|
}
|
|
case 27:
|
|
{
|
|
tmpDisableSpawn = new List<bool>(tmpSpawnListSize);
|
|
for (int i = 0; i < tmpSpawnListSize; ++i)
|
|
{
|
|
bool disablespawn = reader.ReadBool();
|
|
|
|
tmpDisableSpawn.Add(disablespawn);
|
|
|
|
}
|
|
goto case 26;
|
|
}
|
|
case 26:
|
|
{
|
|
m_SpawnOnTrigger = reader.ReadBool();
|
|
|
|
if (version < 32)
|
|
{
|
|
// Delete First & Last Modified
|
|
reader.ReadDateTime();
|
|
reader.ReadDateTime();
|
|
}
|
|
goto case 25;
|
|
}
|
|
case 25:
|
|
{
|
|
goto case 24;
|
|
}
|
|
case 24:
|
|
{
|
|
tmpRestrictKillsToSubgroup = new List<bool>(tmpSpawnListSize);
|
|
tmpClearOnAdvance = new List<bool>(tmpSpawnListSize);
|
|
tmpMinDelay = new List<double>(tmpSpawnListSize);
|
|
tmpMaxDelay = new List<double>(tmpSpawnListSize);
|
|
tmpNextSpawn = new List<DateTime>(tmpSpawnListSize);
|
|
for (int i = 0; i < tmpSpawnListSize; ++i)
|
|
{
|
|
bool restrictkills = reader.ReadBool();
|
|
bool clearadvance = reader.ReadBool();
|
|
double mind = reader.ReadDouble();
|
|
double maxd = reader.ReadDouble();
|
|
DateTime nextspawn = reader.ReadDeltaTime();
|
|
|
|
tmpRestrictKillsToSubgroup.Add(restrictkills);
|
|
tmpClearOnAdvance.Add(clearadvance);
|
|
tmpMinDelay.Add(mind);
|
|
tmpMaxDelay.Add(maxd);
|
|
tmpNextSpawn.Add(nextspawn);
|
|
}
|
|
|
|
bool hasitems = reader.ReadBool();
|
|
|
|
if (hasitems)
|
|
{
|
|
m_ShowBoundsItems = reader.ReadEntityList<Static>();
|
|
}
|
|
goto case 23;
|
|
}
|
|
case 23:
|
|
{
|
|
IsInactivated = reader.ReadBool();
|
|
SmartSpawning = reader.ReadBool();
|
|
|
|
goto case 22;
|
|
}
|
|
case 22:
|
|
{
|
|
SkillTrigger = reader.ReadString(); // note this will also register the skill
|
|
m_skill_that_triggered = (SkillName)reader.ReadInt();
|
|
m_FreeRun = reader.ReadBool();
|
|
m_mob_who_triggered = reader.ReadEntity<Mobile>();
|
|
goto case 21;
|
|
}
|
|
case 21:
|
|
{
|
|
m_DespawnTime = reader.ReadTimeSpan();
|
|
goto case 20;
|
|
}
|
|
case 20:
|
|
{
|
|
tmpRequireSurface = new List<bool>(tmpSpawnListSize);
|
|
for (int i = 0; i < tmpSpawnListSize; ++i)
|
|
{
|
|
bool requiresurface = reader.ReadBool();
|
|
tmpRequireSurface.Add(requiresurface);
|
|
}
|
|
goto case 19;
|
|
}
|
|
case 19:
|
|
{
|
|
m_ConfigFile = reader.ReadString();
|
|
m_OnHold = reader.ReadBool();
|
|
m_HoldSequence = reader.ReadBool();
|
|
|
|
if (version < 32)
|
|
{
|
|
// // Delete First & Last Modified By
|
|
// // Delete First & Last Modified By
|
|
reader.ReadString();
|
|
reader.ReadString();
|
|
}
|
|
|
|
// deserialize the keyword tag list
|
|
int tagcount = reader.ReadInt();
|
|
m_KeywordTagList = new List<BaseXmlSpawner.KeywordTag>(tagcount);
|
|
for (int i = 0; i < tagcount; i++)
|
|
{
|
|
BaseXmlSpawner.KeywordTag tag = new BaseXmlSpawner.KeywordTag(null, this);
|
|
tag.Deserialize(reader);
|
|
}
|
|
goto case 18;
|
|
}
|
|
case 18:
|
|
{
|
|
m_AllowGhostTriggering = reader.ReadBool();
|
|
goto case 17;
|
|
}
|
|
case 17:
|
|
{
|
|
goto case 16;
|
|
}
|
|
case 16:
|
|
{
|
|
hasnewobjectinfo = true;
|
|
m_SequentialSpawning = reader.ReadInt();
|
|
TimeSpan seqdelay = reader.ReadTimeSpan();
|
|
m_SeqEnd = Core.Now + seqdelay;
|
|
|
|
tmpSubGroup = new List<int>(tmpSpawnListSize);
|
|
tmpSequentialResetTime = new List<double>(tmpSpawnListSize);
|
|
tmpSequentialResetTo = new List<int>(tmpSpawnListSize);
|
|
tmpKillsNeeded = new List<int>(tmpSpawnListSize);
|
|
for (int i = 0; i < tmpSpawnListSize; ++i)
|
|
{
|
|
int subgroup = reader.ReadInt();
|
|
double resettime = reader.ReadDouble();
|
|
int resetto = reader.ReadInt();
|
|
int killsneeded = reader.ReadInt();
|
|
tmpSubGroup.Add(subgroup);
|
|
tmpSequentialResetTime.Add(resettime);
|
|
tmpSequentialResetTo.Add(resetto);
|
|
tmpKillsNeeded.Add(killsneeded);
|
|
}
|
|
m_RegionName = reader.ReadString();
|
|
goto case 15;
|
|
}
|
|
case 15:
|
|
{
|
|
m_ExternalTriggering = reader.ReadBool();
|
|
m_ExternalTrigger = reader.ReadBool();
|
|
goto case 14;
|
|
}
|
|
case 14:
|
|
{
|
|
m_NoItemTriggerName = reader.ReadString();
|
|
goto case 13;
|
|
}
|
|
case 13:
|
|
{
|
|
m_GumpState = reader.ReadString();
|
|
goto case 12;
|
|
}
|
|
case 12:
|
|
{
|
|
int todtype = reader.ReadInt();
|
|
switch (todtype)
|
|
{
|
|
case (int)TODModeType.Gametime:
|
|
{
|
|
m_TODMode = TODModeType.Gametime;
|
|
break;
|
|
}
|
|
case (int)TODModeType.Realtime:
|
|
{
|
|
m_TODMode = TODModeType.Realtime;
|
|
break;
|
|
}
|
|
}
|
|
goto case 11;
|
|
}
|
|
case 11:
|
|
{
|
|
m_KillReset = reader.ReadInt();
|
|
m_skipped = reader.ReadBool();
|
|
m_spawncheck = reader.ReadInt();
|
|
goto case 10;
|
|
}
|
|
case 10:
|
|
{
|
|
m_SetPropertyItem = reader.ReadEntity<Item>();
|
|
goto case 9;
|
|
}
|
|
case 9:
|
|
{
|
|
m_TriggerProbability = reader.ReadDouble();
|
|
goto case 8;
|
|
}
|
|
case 8:
|
|
{
|
|
m_MobPropertyName = reader.ReadString();
|
|
m_MobTriggerName = reader.ReadString();
|
|
m_PlayerPropertyName = reader.ReadString();
|
|
goto case 7;
|
|
}
|
|
case 7:
|
|
{
|
|
m_SpeechTrigger = reader.ReadString();
|
|
goto case 6;
|
|
}
|
|
case 6:
|
|
{
|
|
m_ItemTriggerName = reader.ReadString();
|
|
goto case 5;
|
|
}
|
|
case 5:
|
|
{
|
|
m_ProximityTriggerMessage = reader.ReadString();
|
|
m_ObjectPropertyItem = reader.ReadEntity<Item>();
|
|
m_ObjectPropertyName = reader.ReadString();
|
|
m_killcount = reader.ReadInt();
|
|
goto case 4;
|
|
}
|
|
case 4:
|
|
{
|
|
haveproximityrange = true;
|
|
m_ProximityRange = reader.ReadInt();
|
|
m_ProximityTriggerSound = reader.ReadInt();
|
|
m_proximityActivated = reader.ReadBool();
|
|
m_durActivated = reader.ReadBool();
|
|
m_refractActivated = reader.ReadBool();
|
|
m_StackAmount = reader.ReadInt();
|
|
m_TODStart = reader.ReadTimeSpan();
|
|
m_TODEnd = reader.ReadTimeSpan();
|
|
m_MinRefractory = reader.ReadTimeSpan();
|
|
m_MaxRefractory = reader.ReadTimeSpan();
|
|
if (m_refractActivated)
|
|
{
|
|
TimeSpan delay = reader.ReadTimeSpan();
|
|
DoTimer3(delay);
|
|
}
|
|
if (m_durActivated)
|
|
{
|
|
TimeSpan delay = reader.ReadTimeSpan();
|
|
DoTimer2(delay);
|
|
}
|
|
goto case 3;
|
|
}
|
|
case 3:
|
|
{
|
|
m_ShowContainerStatic = reader.ReadEntity<Static>() ;
|
|
goto case 2;
|
|
}
|
|
case 2:
|
|
{
|
|
m_Duration = reader.ReadTimeSpan();
|
|
goto case 1;
|
|
}
|
|
case 1:
|
|
{
|
|
m_UniqueId = reader.ReadString();
|
|
m_HomeRangeIsRelative = reader.ReadBool();
|
|
goto case 0;
|
|
}
|
|
case 0:
|
|
{
|
|
m_Name = reader.ReadString();
|
|
// backward compatibility with old name storage
|
|
if (!string.IsNullOrEmpty(m_Name))
|
|
{
|
|
Name = m_Name;
|
|
}
|
|
|
|
m_X = reader.ReadInt();
|
|
m_Y = reader.ReadInt();
|
|
m_Width = reader.ReadInt();
|
|
m_Height = reader.ReadInt();
|
|
//we HAVE to check if the area is even or if coordinates point to the original spawner, otherwise it's custom area!
|
|
if (m_Width == m_Height && m_Width % 2 == 0 && m_X + m_Width / 2 == X && m_Y + m_Height / 2 == Y)
|
|
{
|
|
m_SpawnRange = m_Width / 2;
|
|
}
|
|
else
|
|
{
|
|
m_SpawnRange = -1;
|
|
}
|
|
|
|
if (!haveproximityrange)
|
|
{
|
|
m_ProximityRange = -1;
|
|
}
|
|
m_WayPoint = reader.ReadEntity<WayPoint>();
|
|
m_Group = reader.ReadBool();
|
|
m_MinDelay = reader.ReadTimeSpan();
|
|
m_MaxDelay = reader.ReadTimeSpan();
|
|
m_Count = reader.ReadInt();
|
|
m_Team = reader.ReadInt();
|
|
m_HomeRange = reader.ReadInt();
|
|
m_Running = reader.ReadBool();
|
|
|
|
if (m_Running)
|
|
{
|
|
TimeSpan delay = reader.ReadTimeSpan();
|
|
DoTimer(delay);
|
|
}
|
|
|
|
// Read in the size of the spawn object list
|
|
int SpawnListSize = reader.ReadInt();
|
|
m_SpawnObjects = new List<SpawnObject>(SpawnListSize);
|
|
for (int i = 0; i < SpawnListSize; ++i)
|
|
{
|
|
string TypeName = reader.ReadString();
|
|
int TypeMaxCount = reader.ReadInt();
|
|
|
|
SpawnObject TheSpawnObject = new SpawnObject(TypeName, TypeMaxCount);
|
|
|
|
m_SpawnObjects.Add(TheSpawnObject);
|
|
|
|
string typeName = BaseXmlSpawner.ParseObjectType(TypeName);
|
|
|
|
if (typeName == null || AssemblyHandler.FindTypeByName(typeName) == null &&
|
|
!BaseXmlSpawner.IsTypeOrItemKeyword(typeName) && typeName.IndexOf('{') == -1 && !typeName.StartsWith("*") && !typeName.StartsWith("#"))
|
|
{
|
|
if (m_WarnTimer == null)
|
|
{
|
|
m_WarnTimer = new WarnTimer2();
|
|
}
|
|
|
|
m_WarnTimer.Add(Location, Map, TypeName);
|
|
|
|
status_str = $"invalid type: {typeName}";
|
|
}
|
|
|
|
// Read in the number of spawns already
|
|
int SpawnedCount = reader.ReadInt();
|
|
|
|
TheSpawnObject.SpawnedObjects = new List<object>(SpawnedCount);
|
|
|
|
for (int x = 0; x < SpawnedCount; ++x)
|
|
{
|
|
int serial = reader.ReadInt();
|
|
if (serial < -1)
|
|
{
|
|
// minusone is reserved for unknown types by default
|
|
// minustwo on is used for referencing keyword tags
|
|
int tagserial = -1 * (serial + 2);
|
|
// get the tag with that serial and add it
|
|
BaseXmlSpawner.KeywordTag t = BaseXmlSpawner.GetFromTagList(this, tagserial);
|
|
if (t != null)
|
|
{
|
|
TheSpawnObject.SpawnedObjects.Add(t);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IEntity e = World.FindEntity((Serial)(uint)serial);
|
|
|
|
if (e != null)
|
|
{
|
|
TheSpawnObject.SpawnedObjects.Add(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// now have to reintegrate the later version spawnobject information into the earlier version desered objects
|
|
if (hasnewobjectinfo && tmpSpawnListSize == SpawnListSize)
|
|
{
|
|
for (int i = 0; i < SpawnListSize; ++i)
|
|
{
|
|
SpawnObject so = m_SpawnObjects[i];
|
|
|
|
so.SubGroup = tmpSubGroup[i];
|
|
so.SequentialResetTime = tmpSequentialResetTime[i];
|
|
so.SequentialResetTo = tmpSequentialResetTo[i];
|
|
so.KillsNeeded = tmpKillsNeeded[i];
|
|
if (version > 19)
|
|
{
|
|
so.RequireSurface = tmpRequireSurface[i];
|
|
}
|
|
|
|
bool restrictkills = false;
|
|
bool clearadvance = true;
|
|
double mind = -1;
|
|
double maxd = -1;
|
|
DateTime nextspawn = DateTime.MinValue;
|
|
if (version > 23)
|
|
{
|
|
restrictkills = tmpRestrictKillsToSubgroup[i];
|
|
clearadvance = tmpClearOnAdvance[i];
|
|
mind = tmpMinDelay[i];
|
|
maxd = tmpMaxDelay[i];
|
|
nextspawn = tmpNextSpawn[i];
|
|
}
|
|
so.RestrictKillsToSubgroup = restrictkills;
|
|
so.ClearOnAdvance = clearadvance;
|
|
so.MinDelay = mind;
|
|
so.MaxDelay = maxd;
|
|
so.NextSpawn = nextspawn;
|
|
|
|
bool disablespawn = false;
|
|
if (version > 26)
|
|
{
|
|
disablespawn = tmpDisableSpawn[i];
|
|
}
|
|
so.Disabled = disablespawn;
|
|
|
|
int packrange = -1;
|
|
if (version > 27)
|
|
{
|
|
packrange = tmpPackRange[i];
|
|
}
|
|
so.PackRange = packrange;
|
|
|
|
int spawnsper = 1;
|
|
if (version > 28)
|
|
{
|
|
spawnsper = tmpSpawnsPer[i];
|
|
}
|
|
so.SpawnsPerTick = spawnsper;
|
|
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
if (m_RegionName != null)
|
|
{
|
|
Timer.DelayCall(delegate { if (!Deleted && m_RegionName != null)
|
|
{
|
|
RegionName = m_RegionName;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
internal string GetSerializedObjectList()
|
|
{
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
if (sb.Length > 0)
|
|
{
|
|
sb.Append(':'); // ':' Separates multiple object types
|
|
}
|
|
|
|
sb.AppendFormat("{0}={1}", so.TypeName, so.ActualMaxCount); // '=' separates object name from maximum amount
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
internal string GetSerializedObjectList2()
|
|
{
|
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
|
|
|
foreach (SpawnObject so in m_SpawnObjects)
|
|
{
|
|
if (sb.Length > 0)
|
|
{
|
|
sb.Append(":OBJ="); // Separates multiple object types
|
|
}
|
|
|
|
sb.AppendFormat("{0}:MX={1}:SB={2}:RT={3}:TO={4}:KL={5}:RK={6}:CA={7}:DN={8}:DX={9}:SP={10}:PR={11}",
|
|
so.TypeName, so.ActualMaxCount, so.SubGroup, so.SequentialResetTime, so.SequentialResetTo, so.KillsNeeded,
|
|
so.RestrictKillsToSubgroup ? 1 : 0, so.ClearOnAdvance ? 1 : 0, so.MinDelay, so.MaxDelay, so.SpawnsPerTick, so.PackRange);
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
public class SpawnObject
|
|
{
|
|
private int m_MaxCount;
|
|
|
|
// temporary variable used to calculate weighted spawn probabilities
|
|
public bool Available;
|
|
|
|
public List<object> SpawnedObjects;
|
|
public string[] PropertyArgs;
|
|
public double SequentialResetTime;
|
|
public int EntryOrder; // used for sorting
|
|
public bool RequireSurface = true;
|
|
public DateTime NextSpawn;
|
|
public bool SpawnedThisTick;
|
|
|
|
// these are externally accessible to the SETONSPAWNENTRY keyword
|
|
public string TypeName { get; set; }
|
|
|
|
public int MaxCount
|
|
{
|
|
get
|
|
{
|
|
if (Disabled)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return m_MaxCount;
|
|
}
|
|
set => m_MaxCount = value;
|
|
}
|
|
public int ActualMaxCount
|
|
{
|
|
get => m_MaxCount;
|
|
set => m_MaxCount = value;
|
|
}
|
|
public int SubGroup { get; set; }
|
|
public int SpawnsPerTick { get; set; } = 1;
|
|
public int SequentialResetTo { get; set; }
|
|
public int KillsNeeded { get; set; }
|
|
public bool RestrictKillsToSubgroup { get; set; }
|
|
public bool ClearOnAdvance { get; set; } = true;
|
|
public double MinDelay { get; set; } = -1;
|
|
public double MaxDelay { get; set; } = -1;
|
|
public bool Disabled { get; set; }
|
|
public bool Ignore { get; set; } = false;
|
|
public int PackRange { get; set; } = -1;
|
|
|
|
// command loggable constructor
|
|
public SpawnObject(Mobile from, XmlSpawner spawner, string name, int maxamount)
|
|
{
|
|
|
|
if (from != null && spawner != null)
|
|
{
|
|
bool found = false;
|
|
// go through the current spawner objects and see if this is a new entry
|
|
if (spawner.m_SpawnObjects != null)
|
|
{
|
|
for (int i = 0; i < spawner.m_SpawnObjects.Count; i++)
|
|
{
|
|
SpawnObject s = spawner.m_SpawnObjects[i];
|
|
if (s != null && s.TypeName == name)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
var loc = spawner.GetWorldLocation();
|
|
CommandLogging.WriteLine(from, $"{from.AccessLevel} {CommandLogging.Format(from)} added to XmlSpawner {spawner.Serial} '{spawner.Name}' [{loc.X}, {loc.Y}] ({spawner.Map}) : {name}");
|
|
}
|
|
}
|
|
|
|
TypeName = name;
|
|
MaxCount = maxamount;
|
|
SubGroup = 0;
|
|
SequentialResetTime = 0;
|
|
SequentialResetTo = 0;
|
|
KillsNeeded = 0;
|
|
RestrictKillsToSubgroup = false;
|
|
ClearOnAdvance = true;
|
|
SpawnedObjects = new List<object>();
|
|
}
|
|
|
|
public SpawnObject(string name, int maxamount)
|
|
{
|
|
TypeName = name;
|
|
MaxCount = maxamount;
|
|
SubGroup = 0;
|
|
SequentialResetTime = 0;
|
|
SequentialResetTo = 0;
|
|
KillsNeeded = 0;
|
|
RestrictKillsToSubgroup = false;
|
|
ClearOnAdvance = true;
|
|
SpawnedObjects = new List<object>();
|
|
}
|
|
|
|
public SpawnObject(string name, int maxamount, int subgroup, double sequentialresettime, int sequentialresetto, int killsneeded,
|
|
bool restrictkills, bool clearadvance, double mindelay, double maxdelay, int spawnsper, int packrange)
|
|
{
|
|
TypeName = name;
|
|
MaxCount = maxamount;
|
|
SubGroup = subgroup;
|
|
SequentialResetTime = sequentialresettime;
|
|
SequentialResetTo = sequentialresetto;
|
|
KillsNeeded = killsneeded;
|
|
RestrictKillsToSubgroup = restrictkills;
|
|
ClearOnAdvance = clearadvance;
|
|
MinDelay = mindelay;
|
|
MaxDelay = maxdelay;
|
|
SpawnsPerTick = spawnsper;
|
|
PackRange = packrange;
|
|
SpawnedObjects = new List<object>();
|
|
}
|
|
|
|
internal static string GetParm(string str, string separator)
|
|
{
|
|
// find the parm separator in the string
|
|
// then look for the termination at the ':' or end of string
|
|
// and return the stuff between
|
|
string[] arg = BaseXmlSpawner.SplitString(str, separator);
|
|
//should be 2 args
|
|
if (arg.Length > 1)
|
|
{
|
|
// look for the end of parm terminator (could also be eol)
|
|
string[] parm = arg[1].Split(':');
|
|
if (parm.Length > 0)
|
|
{
|
|
return parm[0];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
internal static SpawnObject[] LoadSpawnObjectsFromString(string ObjectList)
|
|
{
|
|
// Clear the spawn object list
|
|
List<SpawnObject> NewSpawnObjects = new List<SpawnObject>();
|
|
|
|
if (!string.IsNullOrEmpty(ObjectList))
|
|
{
|
|
// Split the string based on the object separator first ':'
|
|
string[] SpawnObjectList = ObjectList.Split(':');
|
|
|
|
// Parse each item in the array
|
|
foreach (string s in SpawnObjectList)
|
|
{
|
|
// Split the single spawn object item by the max count '='
|
|
string[] SpawnObjectDetails = s.Split('=');
|
|
|
|
// Should be two entries
|
|
if (SpawnObjectDetails.Length == 2)
|
|
{
|
|
// Validate the information
|
|
|
|
// Make sure the spawn object name part has a valid length
|
|
if (SpawnObjectDetails[0].Length > 0)
|
|
{
|
|
// Make sure the max count part has a valid length
|
|
if (SpawnObjectDetails[1].Length > 0)
|
|
{
|
|
int maxCount = 1;
|
|
|
|
try
|
|
{
|
|
maxCount = int.Parse(SpawnObjectDetails[1]);
|
|
}
|
|
catch (Exception)
|
|
{ // Something went wrong, leave the default amount }
|
|
}
|
|
|
|
// Create the spawn object and store it in the array list
|
|
SpawnObject so = new SpawnObject(SpawnObjectDetails[0], maxCount);
|
|
NewSpawnObjects.Add(so);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NewSpawnObjects.ToArray();
|
|
}
|
|
|
|
internal static SpawnObject[] LoadSpawnObjectsFromString2(string ObjectList)
|
|
{
|
|
// Clear the spawn object list
|
|
List<SpawnObject> NewSpawnObjects = new List<SpawnObject>();
|
|
|
|
// spawn object definitions will take the form typestring:MX=int:SB=int:RT=double:TO=int:KL=int
|
|
// or typestring:MX=int:SB=int:RT=double:TO=int:KL=int:OBJ=typestring...
|
|
if (!string.IsNullOrEmpty(ObjectList))
|
|
{
|
|
string[] SpawnObjectList = BaseXmlSpawner.SplitString(ObjectList, ":OBJ=");
|
|
|
|
// Parse each item in the array
|
|
foreach (string s in SpawnObjectList)
|
|
{
|
|
// at this point each spawn string will take the form typestring:MX=int:SB=int:RT=double:TO=int:KL=int
|
|
// Split the single spawn object item by the max count to get the typename and the remaining parms
|
|
string[] SpawnObjectDetails = BaseXmlSpawner.SplitString(s, ":MX=");
|
|
|
|
// Should be two entries
|
|
if (SpawnObjectDetails.Length == 2)
|
|
{
|
|
// Validate the information
|
|
|
|
// Make sure the spawn object name part has a valid length
|
|
if (SpawnObjectDetails[0].Length > 0)
|
|
{
|
|
// Make sure the parm part has a valid length
|
|
if (SpawnObjectDetails[1].Length > 0)
|
|
{
|
|
// now parse out the parms
|
|
// MaxCount
|
|
string parmstr = GetParm(s, ":MX=");
|
|
int maxCount = 1;
|
|
try { maxCount = int.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// SubGroup
|
|
parmstr = GetParm(s, ":SB=");
|
|
|
|
int subGroup = 0;
|
|
try { subGroup = int.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// SequentialSpawnResetTime
|
|
parmstr = GetParm(s, ":RT=");
|
|
double resetTime = 0;
|
|
try { resetTime = double.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// SequentialSpawnResetTo
|
|
parmstr = GetParm(s, ":TO=");
|
|
int resetTo = 0;
|
|
try { resetTo = int.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// KillsNeeded
|
|
parmstr = GetParm(s, ":KL=");
|
|
int killsNeeded = 0;
|
|
try { killsNeeded = int.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// RestrictKills
|
|
parmstr = GetParm(s, ":RK=");
|
|
bool restrictKills = false;
|
|
if (parmstr != null)
|
|
{
|
|
try { restrictKills = int.Parse(parmstr) == 1; }
|
|
catch { }
|
|
}
|
|
|
|
// ClearOnAdvance
|
|
parmstr = GetParm(s, ":CA=");
|
|
bool clearAdvance = true;
|
|
// if kills needed is zero, then set CA to false by default. This maintains consistency with the
|
|
// previous default behavior for old spawn specs that havent specified CA
|
|
if (killsNeeded == 0)
|
|
{
|
|
clearAdvance = false;
|
|
}
|
|
|
|
if (parmstr != null)
|
|
{
|
|
try { clearAdvance = int.Parse(parmstr) == 1; }
|
|
catch { }
|
|
}
|
|
|
|
// MinDelay
|
|
parmstr = GetParm(s, ":DN=");
|
|
double minD = -1;
|
|
try { minD = double.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// MaxDelay
|
|
parmstr = GetParm(s, ":DX=");
|
|
double maxD = -1;
|
|
try { maxD = double.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// SpawnsPerTick
|
|
parmstr = GetParm(s, ":SP=");
|
|
int spawnsPer = 1;
|
|
try { spawnsPer = int.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// PackRange
|
|
parmstr = GetParm(s, ":PR=");
|
|
int packRange = -1;
|
|
try { packRange = int.Parse(parmstr); }
|
|
catch { }
|
|
|
|
// Create the spawn object and store it in the array list
|
|
SpawnObject so = new SpawnObject(SpawnObjectDetails[0], maxCount, subGroup, resetTime, resetTo, killsNeeded,
|
|
restrictKills, clearAdvance, minD, maxD, spawnsPer, packRange);
|
|
|
|
NewSpawnObjects.Add(so);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NewSpawnObjects.ToArray();
|
|
}
|
|
}
|
|
}
|