diff --git a/Projects/Server/AggressorInfo.cs b/Projects/Server/AggressorInfo.cs index 15c212dbb..7b89d3081 100644 --- a/Projects/Server/AggressorInfo.cs +++ b/Projects/Server/AggressorInfo.cs @@ -25,178 +25,179 @@ using System.IO; namespace Server { - public class AggressorInfo { - private static readonly Queue m_Pool = new Queue(); - private Mobile m_Attacker, m_Defender; - private bool m_CanReportMurder; - private bool m_CriminalAggression; - private DateTime m_LastCombatTime; - - private bool m_Queued; - private bool m_Reported; - - private AggressorInfo(Mobile attacker, Mobile defender, bool criminal) + public class AggressorInfo { - m_Attacker = attacker; - m_Defender = defender; + private static readonly Queue m_Pool = new Queue(); + private Mobile m_Attacker, m_Defender; + private bool m_CanReportMurder; + private bool m_CriminalAggression; + private DateTime m_LastCombatTime; - m_CanReportMurder = criminal; - m_CriminalAggression = criminal; + private bool m_Queued; + private bool m_Reported; - Refresh(); + private AggressorInfo(Mobile attacker, Mobile defender, bool criminal) + { + m_Attacker = attacker; + m_Defender = defender; + + m_CanReportMurder = criminal; + m_CriminalAggression = criminal; + + Refresh(); + } + + public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); + + public bool Expired + { + get + { + if (m_Queued) + DumpAccess(); + + return m_Attacker.Deleted || m_Defender.Deleted || DateTime.UtcNow >= m_LastCombatTime + ExpireDelay; + } + } + + public bool CriminalAggression + { + get + { + if (m_Queued) + DumpAccess(); + + return m_CriminalAggression; + } + set + { + if (m_Queued) + DumpAccess(); + + m_CriminalAggression = value; + } + } + + public Mobile Attacker + { + get + { + if (m_Queued) + DumpAccess(); + + return m_Attacker; + } + } + + public Mobile Defender + { + get + { + if (m_Queued) + DumpAccess(); + + return m_Defender; + } + } + + public DateTime LastCombatTime + { + get + { + if (m_Queued) + DumpAccess(); + + return m_LastCombatTime; + } + } + + public bool Reported + { + get + { + if (m_Queued) + DumpAccess(); + + return m_Reported; + } + set + { + if (m_Queued) + DumpAccess(); + + m_Reported = value; + } + } + + public bool CanReportMurder + { + get + { + if (m_Queued) + DumpAccess(); + + return m_CanReportMurder; + } + set + { + if (m_Queued) + DumpAccess(); + + m_CanReportMurder = value; + } + } + + public static AggressorInfo Create(Mobile attacker, Mobile defender, bool criminal) + { + AggressorInfo info; + + if (m_Pool.Count > 0) + { + info = m_Pool.Dequeue(); + + info.m_Attacker = attacker; + info.m_Defender = defender; + + info.m_CanReportMurder = criminal; + info.m_CriminalAggression = criminal; + + info.m_Queued = false; + + info.Refresh(); + } + else + { + info = new AggressorInfo(attacker, defender, criminal); + } + + return info; + } + + public void Free() + { + if (m_Queued) + return; + + m_Queued = true; + m_Pool.Enqueue(this); + } + + public static void DumpAccess() + { + using var op = new StreamWriter("warnings.log", true); + op.WriteLine("Warning: Access to queued AggressorInfo:"); + op.WriteLine(new StackTrace()); + op.WriteLine(); + op.WriteLine(); + } + + public void Refresh() + { + if (m_Queued) + DumpAccess(); + + m_LastCombatTime = DateTime.UtcNow; + m_Reported = false; + } } - - public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); - - public bool Expired - { - get - { - if (m_Queued) - DumpAccess(); - - return m_Attacker.Deleted || m_Defender.Deleted || DateTime.UtcNow >= m_LastCombatTime + ExpireDelay; - } - } - - public bool CriminalAggression - { - get - { - if (m_Queued) - DumpAccess(); - - return m_CriminalAggression; - } - set - { - if (m_Queued) - DumpAccess(); - - m_CriminalAggression = value; - } - } - - public Mobile Attacker - { - get - { - if (m_Queued) - DumpAccess(); - - return m_Attacker; - } - } - - public Mobile Defender - { - get - { - if (m_Queued) - DumpAccess(); - - return m_Defender; - } - } - - public DateTime LastCombatTime - { - get - { - if (m_Queued) - DumpAccess(); - - return m_LastCombatTime; - } - } - - public bool Reported - { - get - { - if (m_Queued) - DumpAccess(); - - return m_Reported; - } - set - { - if (m_Queued) - DumpAccess(); - - m_Reported = value; - } - } - - public bool CanReportMurder - { - get - { - if (m_Queued) - DumpAccess(); - - return m_CanReportMurder; - } - set - { - if (m_Queued) - DumpAccess(); - - m_CanReportMurder = value; - } - } - - public static AggressorInfo Create(Mobile attacker, Mobile defender, bool criminal) - { - AggressorInfo info; - - if (m_Pool.Count > 0) - { - info = m_Pool.Dequeue(); - - info.m_Attacker = attacker; - info.m_Defender = defender; - - info.m_CanReportMurder = criminal; - info.m_CriminalAggression = criminal; - - info.m_Queued = false; - - info.Refresh(); - } - else - { - info = new AggressorInfo(attacker, defender, criminal); - } - - return info; - } - - public void Free() - { - if (m_Queued) - return; - - m_Queued = true; - m_Pool.Enqueue(this); - } - - public static void DumpAccess() - { - using var op = new StreamWriter("warnings.log", true); - op.WriteLine("Warning: Access to queued AggressorInfo:"); - op.WriteLine(new StackTrace()); - op.WriteLine(); - op.WriteLine(); - } - - public void Refresh() - { - if (m_Queued) - DumpAccess(); - - m_LastCombatTime = DateTime.UtcNow; - m_Reported = false; - } - } } diff --git a/Projects/Server/AssemblyHandler.cs b/Projects/Server/AssemblyHandler.cs index 1360aaafc..230fc9593 100644 --- a/Projects/Server/AssemblyHandler.cs +++ b/Projects/Server/AssemblyHandler.cs @@ -1,169 +1,177 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: AssemblyHandler.cs - Created: 2019/08/02 - Updated: 2020/05/09 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.Loader; - -namespace Server -{ - public static class AssemblyHandler - { - private static readonly Dictionary m_TypeCaches = new Dictionary(); - private static TypeCache m_NullCache; - public static Assembly[] Assemblies { get; set; } - - public static void LoadScripts(string[] files) - { - var assemblies = new Assembly[files.Length]; - - for (int i = 0; i < files.Length; i++) assemblies[i] = AssemblyLoadContext.Default.LoadFromAssemblyPath(files[i]); - - Assemblies = assemblies; - } - - public static void Invoke(string method) - { - var invoke = new List(); - - for (var a = 0; a < Assemblies.Length; ++a) - invoke.AddRange(Assemblies[a].GetTypes() - .Select(t => t.GetMethod(method, BindingFlags.Static | BindingFlags.Public)).Where(m => m != null)); - - invoke.Sort(new CallPriorityComparer()); - - for (var i = 0; i < invoke.Count; ++i) - invoke[i].Invoke(null, null); - } - - public static TypeCache GetTypeCache(Assembly asm) - { - if (asm == null) - return m_NullCache ??= new TypeCache(null); - - if (m_TypeCaches.TryGetValue(asm, out var c)) - return c; - - return m_TypeCaches[asm] = new TypeCache(asm); - } - - public static Type FindFirstTypeForName(string name, bool ignoreCase = false, Func predicate = null) - { - if (string.IsNullOrWhiteSpace(name)) return null; - - var types = FindTypesByName(name, ignoreCase).ToList(); - if (types.Count == 0) - return null; - - if (predicate != null) - return types.FirstOrDefault(predicate); - if (types.Count == 1) - return types[0]; - // Try to find the closest match if there is no predicate. - // Check for exact match of the FullName or Name - // Then check for case-insensitive match of FullName or Name - // Otherwise just return the first entry - var stringComparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - - return types.FirstOrDefault(x => - stringComparer.Equals(x.FullName, name) || stringComparer.Equals(x.Name, name)) ?? - types[0]; - } - - public static List FindTypesByName(string name, bool ignoreCase = false) - { - var types = new List(); - - if (ignoreCase) - name = name.ToLower(); - - for (var i = 0; i < Assemblies.Length; i++) - types.AddRange(GetTypeCache(Assemblies[i])[name]); - - if (types.Count == 0) - types.AddRange(GetTypeCache(Core.Assembly)[name]); - - return types; - } - - public static string EnsureDirectory(string dir) - { - var path = Path.Combine(Core.BaseDirectory, dir); - - if (!Directory.Exists(path)) - Directory.CreateDirectory(path); - - return path; - } - } - - public class TypeCache - { - private readonly Dictionary m_NameMap = new Dictionary(); - private readonly Type[] m_Types; - public IEnumerable Types => m_Types; - public IEnumerable Names => m_NameMap.Keys; - - public IEnumerable this[string name] => - m_NameMap.TryGetValue(name, out var value) ? value.Select(x => m_Types[x]) : Array.Empty(); - - public TypeCache(Assembly asm) - { - m_Types = asm?.GetTypes() ?? Type.EmptyTypes; - - var nameMap = new Dictionary>(); - HashSet refs; - Action addToRefs = (index, key) => - { - if (nameMap.TryGetValue(key, out refs)) - { - refs.Add(index); - } - else - { - refs = new HashSet { index }; - nameMap.Add(key, refs); - } - }; - - var aliasType = typeof(TypeAliasAttribute); - for (var i = 0; i < m_Types.Length; i++) - { - var current = m_Types[i]; - addToRefs(i, current.Name); - addToRefs(i, current.Name.ToLower()); - addToRefs(i, current.FullName); - addToRefs(i, current.FullName?.ToLower()); - if (current.GetCustomAttribute(aliasType, false) is TypeAliasAttribute alias) - for (var j = 0; j < alias.Aliases.Length; j++) - { - addToRefs(i, alias.Aliases[j]); - addToRefs(i, alias.Aliases[j].ToLower()); - } - } - - foreach (var (key, value) in nameMap) - m_NameMap[key] = value.ToArray(); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AssemblyHandler.cs - Created: 2019/08/02 - Updated: 2020/05/09 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; + +namespace Server +{ + public static class AssemblyHandler + { + private static readonly Dictionary m_TypeCaches = new Dictionary(); + private static TypeCache m_NullCache; + public static Assembly[] Assemblies { get; set; } + + public static void LoadScripts(string[] files) + { + var assemblies = new Assembly[files.Length]; + + for (var i = 0; i < files.Length; i++) + assemblies[i] = AssemblyLoadContext.Default.LoadFromAssemblyPath(files[i]); + + Assemblies = assemblies; + } + + public static void Invoke(string method) + { + var invoke = new List(); + + for (var a = 0; a < Assemblies.Length; ++a) + invoke.AddRange( + Assemblies[a] + .GetTypes() + .Select(t => t.GetMethod(method, BindingFlags.Static | BindingFlags.Public)) + .Where(m => m != null) + ); + + invoke.Sort(new CallPriorityComparer()); + + for (var i = 0; i < invoke.Count; ++i) + invoke[i].Invoke(null, null); + } + + public static TypeCache GetTypeCache(Assembly asm) + { + if (asm == null) + return m_NullCache ??= new TypeCache(null); + + if (m_TypeCaches.TryGetValue(asm, out var c)) + return c; + + return m_TypeCaches[asm] = new TypeCache(asm); + } + + public static Type FindFirstTypeForName(string name, bool ignoreCase = false, Func predicate = null) + { + if (string.IsNullOrWhiteSpace(name)) return null; + + var types = FindTypesByName(name, ignoreCase).ToList(); + if (types.Count == 0) + return null; + + if (predicate != null) + return types.FirstOrDefault(predicate); + if (types.Count == 1) + return types[0]; + // Try to find the closest match if there is no predicate. + // Check for exact match of the FullName or Name + // Then check for case-insensitive match of FullName or Name + // Otherwise just return the first entry + var stringComparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + + return types.FirstOrDefault( + x => + stringComparer.Equals(x.FullName, name) || stringComparer.Equals(x.Name, name) + ) ?? + types[0]; + } + + public static List FindTypesByName(string name, bool ignoreCase = false) + { + var types = new List(); + + if (ignoreCase) + name = name.ToLower(); + + for (var i = 0; i < Assemblies.Length; i++) + types.AddRange(GetTypeCache(Assemblies[i])[name]); + + if (types.Count == 0) + types.AddRange(GetTypeCache(Core.Assembly)[name]); + + return types; + } + + public static string EnsureDirectory(string dir) + { + var path = Path.Combine(Core.BaseDirectory, dir); + + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + + return path; + } + } + + public class TypeCache + { + private readonly Dictionary m_NameMap = new Dictionary(); + private readonly Type[] m_Types; + + public TypeCache(Assembly asm) + { + m_Types = asm?.GetTypes() ?? Type.EmptyTypes; + + var nameMap = new Dictionary>(); + HashSet refs; + Action addToRefs = (index, key) => + { + if (nameMap.TryGetValue(key, out refs)) + { + refs.Add(index); + } + else + { + refs = new HashSet { index }; + nameMap.Add(key, refs); + } + }; + + var aliasType = typeof(TypeAliasAttribute); + for (var i = 0; i < m_Types.Length; i++) + { + var current = m_Types[i]; + addToRefs(i, current.Name); + addToRefs(i, current.Name.ToLower()); + addToRefs(i, current.FullName); + addToRefs(i, current.FullName?.ToLower()); + if (current.GetCustomAttribute(aliasType, false) is TypeAliasAttribute alias) + for (var j = 0; j < alias.Aliases.Length; j++) + { + addToRefs(i, alias.Aliases[j]); + addToRefs(i, alias.Aliases[j].ToLower()); + } + } + + foreach (var (key, value) in nameMap) + m_NameMap[key] = value.ToArray(); + } + + public IEnumerable Types => m_Types; + public IEnumerable Names => m_NameMap.Keys; + + public IEnumerable this[string name] => + m_NameMap.TryGetValue(name, out var value) ? value.Select(x => m_Types[x]) : Array.Empty(); + } +} diff --git a/Projects/Server/Attributes.cs b/Projects/Server/Attributes.cs index 5f37b0bd5..a5967466a 100644 --- a/Projects/Server/Attributes.cs +++ b/Projects/Server/Attributes.cs @@ -1,152 +1,153 @@ -/*************************************************************************** - * Attributes.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace Server -{ - [AttributeUsage(AttributeTargets.Property)] - public class HueAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class BodyAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] - public class PropertyObjectAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] - public class NoSortAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method)] - public class CallPriorityAttribute : Attribute - { - public CallPriorityAttribute(int priority) => Priority = priority; - - public int Priority { get; set; } - } - - public class CallPriorityComparer : IComparer - { - public int Compare(MethodInfo x, MethodInfo y) - { - if (x == null && y == null) - return 0; - - if (x == null) - return 1; - - if (y == null) - return -1; - - var xPriority = GetPriority(x); - var yPriority = GetPriority(y); - - if (xPriority > yPriority) - return 1; - - if (xPriority < yPriority) - return -1; - - return 0; - } - - private int GetPriority(MethodInfo mi) - { - var objs = mi.GetCustomAttributes(typeof(CallPriorityAttribute), true); - - if (objs.Length == 0) - return 0; - - if (!(objs[0] is CallPriorityAttribute attr)) - return 0; - - return attr.Priority; - } - } - - [AttributeUsage(AttributeTargets.Class)] - public class TypeAliasAttribute : Attribute - { - public TypeAliasAttribute(params string[] aliases) => Aliases = aliases; - - public string[] Aliases { get; } - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] - public class ParsableAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] - public class CustomEnumAttribute : Attribute - { - public CustomEnumAttribute(string[] names) => Names = names; - - public string[] Names { get; } - } - - [AttributeUsage(AttributeTargets.Constructor)] - public class ConstructibleAttribute : Attribute - { - public ConstructibleAttribute() : this(AccessLevel.Player) // Lowest accesslevel for current functionality (Level determined by access to [add) - { - } - - public ConstructibleAttribute(AccessLevel accessLevel) => AccessLevel = accessLevel; - - public AccessLevel AccessLevel { get; set; } - } - - [AttributeUsage(AttributeTargets.Property)] - public class CommandPropertyAttribute : Attribute - { - public CommandPropertyAttribute(AccessLevel level, bool readOnly) - { - ReadLevel = level; - ReadOnly = readOnly; - } - - public CommandPropertyAttribute(AccessLevel level) : this(level, level) - { - } - - public CommandPropertyAttribute(AccessLevel readLevel, AccessLevel writeLevel) - { - ReadLevel = readLevel; - WriteLevel = writeLevel; - } - - public AccessLevel ReadLevel { get; } - - public AccessLevel WriteLevel { get; } - - public bool ReadOnly { get; } - } -} +/*************************************************************************** + * Attributes.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Server +{ + [AttributeUsage(AttributeTargets.Property)] + public class HueAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Property)] + public class BodyAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + public class PropertyObjectAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + public class NoSortAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Method)] + public class CallPriorityAttribute : Attribute + { + public CallPriorityAttribute(int priority) => Priority = priority; + + public int Priority { get; set; } + } + + public class CallPriorityComparer : IComparer + { + public int Compare(MethodInfo x, MethodInfo y) + { + if (x == null && y == null) + return 0; + + if (x == null) + return 1; + + if (y == null) + return -1; + + var xPriority = GetPriority(x); + var yPriority = GetPriority(y); + + if (xPriority > yPriority) + return 1; + + if (xPriority < yPriority) + return -1; + + return 0; + } + + private int GetPriority(MethodInfo mi) + { + var objs = mi.GetCustomAttributes(typeof(CallPriorityAttribute), true); + + if (objs.Length == 0) + return 0; + + if (!(objs[0] is CallPriorityAttribute attr)) + return 0; + + return attr.Priority; + } + } + + [AttributeUsage(AttributeTargets.Class)] + public class TypeAliasAttribute : Attribute + { + public TypeAliasAttribute(params string[] aliases) => Aliases = aliases; + + public string[] Aliases { get; } + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + public class ParsableAttribute : Attribute + { + } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] + public class CustomEnumAttribute : Attribute + { + public CustomEnumAttribute(string[] names) => Names = names; + + public string[] Names { get; } + } + + [AttributeUsage(AttributeTargets.Constructor)] + public class ConstructibleAttribute : Attribute + { + public ConstructibleAttribute() : + this(AccessLevel.Player) // Lowest accesslevel for current functionality (Level determined by access to [add) + { + } + + public ConstructibleAttribute(AccessLevel accessLevel) => AccessLevel = accessLevel; + + public AccessLevel AccessLevel { get; set; } + } + + [AttributeUsage(AttributeTargets.Property)] + public class CommandPropertyAttribute : Attribute + { + public CommandPropertyAttribute(AccessLevel level, bool readOnly) + { + ReadLevel = level; + ReadOnly = readOnly; + } + + public CommandPropertyAttribute(AccessLevel level) : this(level, level) + { + } + + public CommandPropertyAttribute(AccessLevel readLevel, AccessLevel writeLevel) + { + ReadLevel = readLevel; + WriteLevel = writeLevel; + } + + public AccessLevel ReadLevel { get; } + + public AccessLevel WriteLevel { get; } + + public bool ReadOnly { get; } + } +} diff --git a/Projects/Server/BaseVendor.cs b/Projects/Server/BaseVendor.cs index eb36e3dd0..c90698510 100644 --- a/Projects/Server/BaseVendor.cs +++ b/Projects/Server/BaseVendor.cs @@ -1,106 +1,106 @@ -/*************************************************************************** - * BaseVendor.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.Collections.Generic; - -namespace Server -{ - public class BuyItemStateComparer : IComparer - { - public int Compare(BuyItemState l, BuyItemState r) - { - if (l == null && r == null) return 0; - if (l == null) return -1; - if (r == null) return 1; - - return l.MySerial.CompareTo(r.MySerial); - } - } - - public class BuyItemResponse - { - public BuyItemResponse(Serial serial, int amount) - { - Serial = serial; - Amount = amount; - } - - public Serial Serial { get; } - - public int Amount { get; } - } - - public class SellItemResponse - { - public SellItemResponse(Item i, int amount) - { - Item = i; - Amount = amount; - } - - public Item Item { get; } - - public int Amount { get; } - } - - public class SellItemState - { - public SellItemState(Item item, int price, string name) - { - Item = item; - Price = price; - Name = name; - } - - public Item Item { get; } - - public int Price { get; } - - public string Name { get; } - } - - public class BuyItemState - { - public BuyItemState(string name, Serial cont, Serial serial, int price, int amount, int itemID, int hue) - { - Description = name; - ContainerSerial = cont; - MySerial = serial; - Price = price; - Amount = amount; - ItemID = itemID; - Hue = hue; - } - - public int Price { get; } - - public Serial MySerial { get; } - - public Serial ContainerSerial { get; } - - public int ItemID { get; } - - public int Amount { get; } - - public int Hue { get; } - - public string Description { get; } - } -} +/*************************************************************************** + * BaseVendor.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.Collections.Generic; + +namespace Server +{ + public class BuyItemStateComparer : IComparer + { + public int Compare(BuyItemState l, BuyItemState r) + { + if (l == null && r == null) return 0; + if (l == null) return -1; + if (r == null) return 1; + + return l.MySerial.CompareTo(r.MySerial); + } + } + + public class BuyItemResponse + { + public BuyItemResponse(Serial serial, int amount) + { + Serial = serial; + Amount = amount; + } + + public Serial Serial { get; } + + public int Amount { get; } + } + + public class SellItemResponse + { + public SellItemResponse(Item i, int amount) + { + Item = i; + Amount = amount; + } + + public Item Item { get; } + + public int Amount { get; } + } + + public class SellItemState + { + public SellItemState(Item item, int price, string name) + { + Item = item; + Price = price; + Name = name; + } + + public Item Item { get; } + + public int Price { get; } + + public string Name { get; } + } + + public class BuyItemState + { + public BuyItemState(string name, Serial cont, Serial serial, int price, int amount, int itemID, int hue) + { + Description = name; + ContainerSerial = cont; + MySerial = serial; + Price = price; + Amount = amount; + ItemID = itemID; + Hue = hue; + } + + public int Price { get; } + + public Serial MySerial { get; } + + public Serial ContainerSerial { get; } + + public int ItemID { get; } + + public int Amount { get; } + + public int Hue { get; } + + public string Description { get; } + } +} diff --git a/Projects/Server/Body.cs b/Projects/Server/Body.cs index e4a391294..fdf3ea297 100644 --- a/Projects/Server/Body.cs +++ b/Projects/Server/Body.cs @@ -1,170 +1,171 @@ -/*************************************************************************** - * Body.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.IO; - -namespace Server -{ - public enum BodyType : byte - { - Empty, - Monster, - Sea, - Animal, - Human, - Equipment - } - - public readonly struct Body : IEquatable, IEquatable, IEquatable - { - private static readonly BodyType[] m_Types = Array.Empty(); - - static Body() - { - if (File.Exists("Data/bodyTable.cfg")) - { - using var ip = new StreamReader("Data/bodyTable.cfg"); - m_Types = new BodyType[0x1000]; - - string line; - - while ((line = ip.ReadLine()) != null) - { - if (line.Length == 0 || line.StartsWith("#")) - continue; - - var split = line.Split('\t'); - - if (int.TryParse(split[0], out var bodyID) && Enum.TryParse(split[1], true, out BodyType type) && bodyID >= 0 && - bodyID < m_Types.Length) - { - m_Types[bodyID] = type; - } - else - { - Console.WriteLine("Warning: Invalid bodyTable entry:"); - Console.WriteLine(line); - } - } - } - else - { - Console.WriteLine("Warning: Data/bodyTable.cfg does not exist"); - } - } - - public Body(int bodyID) => BodyID = bodyID; - - public BodyType Type => BodyID >= 0 && BodyID < m_Types.Length ? m_Types[BodyID] : BodyType.Empty; - - public bool IsHuman => BodyID >= 0 - && BodyID < m_Types.Length - && m_Types[BodyID] == BodyType.Human - && BodyID != 402 - && BodyID != 403 - && BodyID != 607 - && BodyID != 608 - && BodyID != 694 - && BodyID != 695 - && BodyID != 970; - - public bool IsGargoyle => BodyID == 666 - || BodyID == 667 - || BodyID == 694 - || BodyID == 695; - - public bool IsMale => BodyID == 183 - || BodyID == 185 - || BodyID == 400 - || BodyID == 402 - || BodyID == 605 - || BodyID == 607 - || BodyID == 666 - || BodyID == 694 - || BodyID == 750; - - public bool IsFemale => BodyID == 184 - || BodyID == 186 - || BodyID == 401 - || BodyID == 403 - || BodyID == 606 - || BodyID == 608 - || BodyID == 667 - || BodyID == 695 - || BodyID == 751; - - public bool IsGhost => BodyID == 402 - || BodyID == 403 - || BodyID == 607 - || BodyID == 608 - || BodyID == 694 - || BodyID == 695 - || BodyID == 970; - - public bool IsMonster => BodyID >= 0 - && BodyID < m_Types.Length - && m_Types[BodyID] == BodyType.Monster; - - public bool IsAnimal => BodyID >= 0 - && BodyID < m_Types.Length - && m_Types[BodyID] == BodyType.Animal; - - public bool IsEmpty => BodyID >= 0 - && BodyID < m_Types.Length - && m_Types[BodyID] == BodyType.Empty; - - public bool IsSea => BodyID >= 0 - && BodyID < m_Types.Length - && m_Types[BodyID] == BodyType.Sea; - - public bool IsEquipment => BodyID >= 0 - && BodyID < m_Types.Length - && m_Types[BodyID] == BodyType.Equipment; - - public int BodyID { get; } - - public static implicit operator int(Body a) => a.BodyID; - - public static implicit operator Body(int a) => new Body(a); - - public override string ToString() => $"0x{BodyID:X}"; - - public override int GetHashCode() => BodyID.GetHashCode(); - - public override bool Equals(object o) => o is Body b && b.BodyID == BodyID; - - public bool Equals(Body b) => b.BodyID == BodyID; - - public bool Equals(int number) => number == BodyID; - - public static bool operator ==(Body l, Body r) => l.BodyID == r.BodyID; - - public static bool operator !=(Body l, Body r) => l.BodyID != r.BodyID; - - public static bool operator >(Body l, Body r) => l.BodyID > r.BodyID; - - public static bool operator >=(Body l, Body r) => l.BodyID >= r.BodyID; - - public static bool operator <(Body l, Body r) => l.BodyID < r.BodyID; - - public static bool operator <=(Body l, Body r) => l.BodyID <= r.BodyID; - } -} +/*************************************************************************** + * Body.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.IO; + +namespace Server +{ + public enum BodyType : byte + { + Empty, + Monster, + Sea, + Animal, + Human, + Equipment + } + + public readonly struct Body : IEquatable, IEquatable, IEquatable + { + private static readonly BodyType[] m_Types = Array.Empty(); + + static Body() + { + if (File.Exists("Data/bodyTable.cfg")) + { + using var ip = new StreamReader("Data/bodyTable.cfg"); + m_Types = new BodyType[0x1000]; + + string line; + + while ((line = ip.ReadLine()) != null) + { + if (line.Length == 0 || line.StartsWith("#")) + continue; + + var split = line.Split('\t'); + + if (int.TryParse(split[0], out var bodyID) && Enum.TryParse(split[1], true, out BodyType type) && + bodyID >= 0 && + bodyID < m_Types.Length) + { + m_Types[bodyID] = type; + } + else + { + Console.WriteLine("Warning: Invalid bodyTable entry:"); + Console.WriteLine(line); + } + } + } + else + { + Console.WriteLine("Warning: Data/bodyTable.cfg does not exist"); + } + } + + public Body(int bodyID) => BodyID = bodyID; + + public BodyType Type => BodyID >= 0 && BodyID < m_Types.Length ? m_Types[BodyID] : BodyType.Empty; + + public bool IsHuman => BodyID >= 0 + && BodyID < m_Types.Length + && m_Types[BodyID] == BodyType.Human + && BodyID != 402 + && BodyID != 403 + && BodyID != 607 + && BodyID != 608 + && BodyID != 694 + && BodyID != 695 + && BodyID != 970; + + public bool IsGargoyle => BodyID == 666 + || BodyID == 667 + || BodyID == 694 + || BodyID == 695; + + public bool IsMale => BodyID == 183 + || BodyID == 185 + || BodyID == 400 + || BodyID == 402 + || BodyID == 605 + || BodyID == 607 + || BodyID == 666 + || BodyID == 694 + || BodyID == 750; + + public bool IsFemale => BodyID == 184 + || BodyID == 186 + || BodyID == 401 + || BodyID == 403 + || BodyID == 606 + || BodyID == 608 + || BodyID == 667 + || BodyID == 695 + || BodyID == 751; + + public bool IsGhost => BodyID == 402 + || BodyID == 403 + || BodyID == 607 + || BodyID == 608 + || BodyID == 694 + || BodyID == 695 + || BodyID == 970; + + public bool IsMonster => BodyID >= 0 + && BodyID < m_Types.Length + && m_Types[BodyID] == BodyType.Monster; + + public bool IsAnimal => BodyID >= 0 + && BodyID < m_Types.Length + && m_Types[BodyID] == BodyType.Animal; + + public bool IsEmpty => BodyID >= 0 + && BodyID < m_Types.Length + && m_Types[BodyID] == BodyType.Empty; + + public bool IsSea => BodyID >= 0 + && BodyID < m_Types.Length + && m_Types[BodyID] == BodyType.Sea; + + public bool IsEquipment => BodyID >= 0 + && BodyID < m_Types.Length + && m_Types[BodyID] == BodyType.Equipment; + + public int BodyID { get; } + + public static implicit operator int(Body a) => a.BodyID; + + public static implicit operator Body(int a) => new Body(a); + + public override string ToString() => $"0x{BodyID:X}"; + + public override int GetHashCode() => BodyID.GetHashCode(); + + public override bool Equals(object o) => o is Body b && b.BodyID == BodyID; + + public bool Equals(Body b) => b.BodyID == BodyID; + + public bool Equals(int number) => number == BodyID; + + public static bool operator ==(Body l, Body r) => l.BodyID == r.BodyID; + + public static bool operator !=(Body l, Body r) => l.BodyID != r.BodyID; + + public static bool operator >(Body l, Body r) => l.BodyID > r.BodyID; + + public static bool operator >=(Body l, Body r) => l.BodyID >= r.BodyID; + + public static bool operator <(Body l, Body r) => l.BodyID < r.BodyID; + + public static bool operator <=(Body l, Body r) => l.BodyID <= r.BodyID; + } +} diff --git a/Projects/Server/Buffers/BufferReader.cs b/Projects/Server/Buffers/BufferReader.cs index 673af7d20..c654c6d29 100644 --- a/Projects/Server/Buffers/BufferReader.cs +++ b/Projects/Server/Buffers/BufferReader.cs @@ -1,306 +1,309 @@ -// Copyright (c) Harry Pierson. All rights reserved. -// Licensed under the MIT license. -// See LICENSE file in the project root for full license information. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Threading; - -namespace System.Buffers -{ - public ref struct BufferReader - { - private readonly bool usingSequence; - private readonly ReadOnlySequence sequence; - private SequencePosition currentPosition; - private SequencePosition nextPosition; - private bool moreData; - private readonly long length; - - public BufferReader(ReadOnlySpan span) - { - usingSequence = false; - CurrentSpanIndex = 0; - Consumed = 0; - sequence = default; - currentPosition = default; - length = span.Length; - - CurrentSpan = span; - nextPosition = default; - moreData = span.Length > 0; - } - - public BufferReader(in ReadOnlySequence sequence) - { - usingSequence = true; - CurrentSpanIndex = 0; - Consumed = 0; - this.sequence = sequence; - currentPosition = sequence.Start; - length = -1; - - var first = sequence.First.Span; - CurrentSpan = first; - nextPosition = sequence.GetPosition(first.Length); - moreData = first.Length > 0; - - if (!moreData && !sequence.IsSingleSegment) - { - moreData = true; - GetNextSpan(); - } - } - - public readonly bool End => !moreData; - - public ReadOnlySpan CurrentSpan { get; private set; } - - public int CurrentSpanIndex { get; private set; } - - public readonly ReadOnlySpan UnreadSpan - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => CurrentSpan.Slice(CurrentSpanIndex); - } - - public long Consumed { get; private set; } - - public readonly long Remaining => Length - Consumed; - - public readonly long Length - { - get - { - if (length < 0) - // Cast-away readonly to initialize lazy field - Volatile.Write(ref Unsafe.AsRef(length), sequence.Length); - - return length; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool TryPeek([MaybeNullWhen(false)] out T value) - { - if (moreData) - { - value = CurrentSpan[CurrentSpanIndex]; - return true; - } - - value = default!; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryRead([MaybeNullWhen(false)] out T value) - { - if (End) - { - value = default!; - return false; - } - - value = CurrentSpan[CurrentSpanIndex]; - CurrentSpanIndex++; - Consumed++; - - if (CurrentSpanIndex >= CurrentSpan.Length) - { - if (usingSequence) - GetNextSpan(); - else - moreData = false; - } - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Rewind(long count) - { - if ((ulong)count > (ulong)Consumed) throw new ArgumentOutOfRangeException(nameof(count)); - - Consumed -= count; - - if (CurrentSpanIndex >= count) - { - CurrentSpanIndex -= (int)count; - moreData = true; - } - else if (usingSequence) - { - // Current segment doesn't have enough data, scan backward through segments - RetreatToPreviousSpan(Consumed); - } - else - { - throw new ArgumentOutOfRangeException(nameof(count), $"Rewind went past the start of the memory by {count}."); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void RetreatToPreviousSpan(long consumed) - { - ResetReader(); - Advance(consumed); - } - - private void ResetReader() - { - CurrentSpanIndex = 0; - Consumed = 0; - currentPosition = sequence.Start; - nextPosition = currentPosition; - - if (sequence.TryGet(ref nextPosition, out var memory)) - { - moreData = true; - - if (memory.Length == 0) - { - CurrentSpan = default; - // No data in the first span, move to one with data - GetNextSpan(); - } - else - { - CurrentSpan = memory.Span; - } - } - else - { - // No data in any spans and at end of sequence - moreData = false; - CurrentSpan = default; - } - } - - private void GetNextSpan() - { - if (!sequence.IsSingleSegment) - { - var previousNextPosition = nextPosition; - while (sequence.TryGet(ref nextPosition, out var memory)) - { - currentPosition = previousNextPosition; - if (memory.Length > 0) - { - CurrentSpan = memory.Span; - CurrentSpanIndex = 0; - return; - } - - CurrentSpan = default; - CurrentSpanIndex = 0; - previousNextPosition = nextPosition; - } - } - - moreData = false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(long count) - { - const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000); - if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count) - { - CurrentSpanIndex += (int)count; - Consumed += count; - } - else if (usingSequence) - { - // Can't satisfy from the current span - AdvanceToNextSpan(count); - } - else if (CurrentSpan.Length - CurrentSpanIndex == (int)count) - { - CurrentSpanIndex += (int)count; - Consumed += count; - moreData = false; - } - else - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - } - - private void AdvanceToNextSpan(long count) - { - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - - Consumed += count; - while (moreData) - { - var remaining = CurrentSpan.Length - CurrentSpanIndex; - - if (remaining > count) - { - CurrentSpanIndex += (int)count; - count = 0; - break; - } - - // As there may not be any further segments we need to - // push the current index to the end of the span. - CurrentSpanIndex += remaining; - count -= remaining; - - GetNextSpan(); - - if (count == 0) break; - } - - if (count != 0) - { - // Not enough data left- adjust for where we actually ended and throw - Consumed -= count; - throw new ArgumentOutOfRangeException(nameof(count)); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly bool TryCopyTo(Span destination) - { - // This API doesn't advance to facilitate conditional advancement based on the data returned. - // We don't provide an advance option to allow easier utilizing of stack allocated destination spans. - // (Because we can make this method readonly we can guarantee that we won't capture the span.) - - var firstSpan = UnreadSpan; - if (firstSpan.Length >= destination.Length) - { - firstSpan.Slice(0, destination.Length).CopyTo(destination); - return true; - } - - // Not enough in the current span to satisfy the request, fall through to the slow path - return TryCopyMultisegment(destination); - } - - internal readonly bool TryCopyMultisegment(Span destination) - { - // If we don't have enough to fill the requested buffer, return false - if (Remaining < destination.Length) - return false; - - var firstSpan = UnreadSpan; - firstSpan.CopyTo(destination); - var copied = firstSpan.Length; - - var next = nextPosition; - while (sequence.TryGet(ref next, out var nextSegment)) - if (nextSegment.Length > 0) - { - var nextSpan = nextSegment.Span; - var toCopy = Math.Min(nextSpan.Length, destination.Length - copied); - nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied)); - copied += toCopy; - if (copied >= destination.Length) break; - } - - return true; - } - } -} +// Copyright (c) Harry Pierson. All rights reserved. +// Licensed under the MIT license. +// See LICENSE file in the project root for full license information. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace System.Buffers +{ + public ref struct BufferReader + { + private readonly bool usingSequence; + private readonly ReadOnlySequence sequence; + private SequencePosition currentPosition; + private SequencePosition nextPosition; + private bool moreData; + private readonly long length; + + public BufferReader(ReadOnlySpan span) + { + usingSequence = false; + CurrentSpanIndex = 0; + Consumed = 0; + sequence = default; + currentPosition = default; + length = span.Length; + + CurrentSpan = span; + nextPosition = default; + moreData = span.Length > 0; + } + + public BufferReader(in ReadOnlySequence sequence) + { + usingSequence = true; + CurrentSpanIndex = 0; + Consumed = 0; + this.sequence = sequence; + currentPosition = sequence.Start; + length = -1; + + var first = sequence.First.Span; + CurrentSpan = first; + nextPosition = sequence.GetPosition(first.Length); + moreData = first.Length > 0; + + if (!moreData && !sequence.IsSingleSegment) + { + moreData = true; + GetNextSpan(); + } + } + + public readonly bool End => !moreData; + + public ReadOnlySpan CurrentSpan { get; private set; } + + public int CurrentSpanIndex { get; private set; } + + public readonly ReadOnlySpan UnreadSpan + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => CurrentSpan.Slice(CurrentSpanIndex); + } + + public long Consumed { get; private set; } + + public readonly long Remaining => Length - Consumed; + + public readonly long Length + { + get + { + if (length < 0) + // Cast-away readonly to initialize lazy field + Volatile.Write(ref Unsafe.AsRef(length), sequence.Length); + + return length; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryPeek([MaybeNullWhen(false)] out T value) + { + if (moreData) + { + value = CurrentSpan[CurrentSpanIndex]; + return true; + } + + value = default!; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryRead([MaybeNullWhen(false)] out T value) + { + if (End) + { + value = default!; + return false; + } + + value = CurrentSpan[CurrentSpanIndex]; + CurrentSpanIndex++; + Consumed++; + + if (CurrentSpanIndex >= CurrentSpan.Length) + { + if (usingSequence) + GetNextSpan(); + else + moreData = false; + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Rewind(long count) + { + if ((ulong)count > (ulong)Consumed) throw new ArgumentOutOfRangeException(nameof(count)); + + Consumed -= count; + + if (CurrentSpanIndex >= count) + { + CurrentSpanIndex -= (int)count; + moreData = true; + } + else if (usingSequence) + { + // Current segment doesn't have enough data, scan backward through segments + RetreatToPreviousSpan(Consumed); + } + else + { + throw new ArgumentOutOfRangeException( + nameof(count), + $"Rewind went past the start of the memory by {count}." + ); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void RetreatToPreviousSpan(long consumed) + { + ResetReader(); + Advance(consumed); + } + + private void ResetReader() + { + CurrentSpanIndex = 0; + Consumed = 0; + currentPosition = sequence.Start; + nextPosition = currentPosition; + + if (sequence.TryGet(ref nextPosition, out var memory)) + { + moreData = true; + + if (memory.Length == 0) + { + CurrentSpan = default; + // No data in the first span, move to one with data + GetNextSpan(); + } + else + { + CurrentSpan = memory.Span; + } + } + else + { + // No data in any spans and at end of sequence + moreData = false; + CurrentSpan = default; + } + } + + private void GetNextSpan() + { + if (!sequence.IsSingleSegment) + { + var previousNextPosition = nextPosition; + while (sequence.TryGet(ref nextPosition, out var memory)) + { + currentPosition = previousNextPosition; + if (memory.Length > 0) + { + CurrentSpan = memory.Span; + CurrentSpanIndex = 0; + return; + } + + CurrentSpan = default; + CurrentSpanIndex = 0; + previousNextPosition = nextPosition; + } + } + + moreData = false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(long count) + { + const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000); + if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count) + { + CurrentSpanIndex += (int)count; + Consumed += count; + } + else if (usingSequence) + { + // Can't satisfy from the current span + AdvanceToNextSpan(count); + } + else if (CurrentSpan.Length - CurrentSpanIndex == (int)count) + { + CurrentSpanIndex += (int)count; + Consumed += count; + moreData = false; + } + else + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + private void AdvanceToNextSpan(long count) + { + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + + Consumed += count; + while (moreData) + { + var remaining = CurrentSpan.Length - CurrentSpanIndex; + + if (remaining > count) + { + CurrentSpanIndex += (int)count; + count = 0; + break; + } + + // As there may not be any further segments we need to + // push the current index to the end of the span. + CurrentSpanIndex += remaining; + count -= remaining; + + GetNextSpan(); + + if (count == 0) break; + } + + if (count != 0) + { + // Not enough data left- adjust for where we actually ended and throw + Consumed -= count; + throw new ArgumentOutOfRangeException(nameof(count)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool TryCopyTo(Span destination) + { + // This API doesn't advance to facilitate conditional advancement based on the data returned. + // We don't provide an advance option to allow easier utilizing of stack allocated destination spans. + // (Because we can make this method readonly we can guarantee that we won't capture the span.) + + var firstSpan = UnreadSpan; + if (firstSpan.Length >= destination.Length) + { + firstSpan.Slice(0, destination.Length).CopyTo(destination); + return true; + } + + // Not enough in the current span to satisfy the request, fall through to the slow path + return TryCopyMultisegment(destination); + } + + internal readonly bool TryCopyMultisegment(Span destination) + { + // If we don't have enough to fill the requested buffer, return false + if (Remaining < destination.Length) + return false; + + var firstSpan = UnreadSpan; + firstSpan.CopyTo(destination); + var copied = firstSpan.Length; + + var next = nextPosition; + while (sequence.TryGet(ref next, out var nextSegment)) + if (nextSegment.Length > 0) + { + var nextSpan = nextSegment.Span; + var toCopy = Math.Min(nextSpan.Length, destination.Length - copied); + nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied)); + copied += toCopy; + if (copied >= destination.Length) break; + } + + return true; + } + } +} diff --git a/Projects/Server/Buffers/BufferReaderExtensions.cs b/Projects/Server/Buffers/BufferReaderExtensions.cs index 00226e2da..fd42765a4 100644 --- a/Projects/Server/Buffers/BufferReaderExtensions.cs +++ b/Projects/Server/Buffers/BufferReaderExtensions.cs @@ -1,177 +1,177 @@ -// Copyright (c) Harry Pierson. All rights reserved. -// Licensed under the MIT license. -// See LICENSE file in the project root for full license information. - -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Buffers -{ - public static class BufferReaderExtensions - { - private static unsafe bool TryRead(ref this BufferReader reader, out T value) - where T : unmanaged - { - var span = reader.UnreadSpan; - if (span.Length < sizeof(T)) return TryReadMultisegment(ref reader, out value); - - value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); - reader.Advance(sizeof(T)); - return true; - } - - private static unsafe bool TryReadMultisegment(ref BufferReader reader, out T value) - where T : unmanaged - { - // Not enough data in the current segment, try to peek for the data we need. - T buffer = default; - var tempSpan = new Span(&buffer, sizeof(T)); - - if (!reader.TryCopyTo(tempSpan)) - { - value = default; - return false; - } - - value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); - reader.Advance(sizeof(T)); - return true; - } - - public static bool TryRead(ref this BufferReader reader, out sbyte value) - { - if (TryRead(ref reader, out byte byteValue)) - { - value = unchecked((sbyte)byteValue); - return true; - } - - value = default; - return false; - } - - public static bool TryReadLittleEndian(ref this BufferReader reader, out short value) => - BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); - - public static bool TryReadBigEndian(ref this BufferReader reader, out short value) => - !BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); - - private static bool TryReadReverseEndianness(ref BufferReader reader, out short value) - { - if (reader.TryRead(out value)) - { - value = BinaryPrimitives.ReverseEndianness(value); - return true; - } - - return false; - } - - public static bool TryReadLittleEndian(ref this BufferReader reader, out ushort value) - { - if (TryReadLittleEndian(ref reader, out short signedvalue)) - { - value = unchecked((ushort)signedvalue); - return true; - } - - value = default; - return false; - } - - public static bool TryReadBigEndian(ref this BufferReader reader, out ushort value) - { - if (TryReadBigEndian(ref reader, out short signedvalue)) - { - value = unchecked((ushort)signedvalue); - return true; - } - - value = default; - return false; - } - - public static bool TryReadLittleEndian(ref this BufferReader reader, out int value) => - BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); - - public static bool TryReadBigEndian(ref this BufferReader reader, out int value) => - !BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); - - private static bool TryReadReverseEndianness(ref BufferReader reader, out int value) - { - if (reader.TryRead(out value)) - { - value = BinaryPrimitives.ReverseEndianness(value); - return true; - } - - return false; - } - - public static bool TryReadLittleEndian(ref this BufferReader reader, out uint value) - { - if (TryReadLittleEndian(ref reader, out int signedvalue)) - { - value = unchecked((uint)signedvalue); - return true; - } - - value = default; - return false; - } - - public static bool TryReadBigEndian(ref this BufferReader reader, out uint value) - { - if (TryReadBigEndian(ref reader, out int signedvalue)) - { - value = unchecked((uint)signedvalue); - return true; - } - - value = default; - return false; - } - - public static bool TryReadLittleEndian(ref this BufferReader reader, out long value) => - BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); - - public static bool TryReadBigEndian(ref this BufferReader reader, out long value) => - !BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); - - private static bool TryReadReverseEndianness(ref BufferReader reader, out long value) - { - if (reader.TryRead(out value)) - { - value = BinaryPrimitives.ReverseEndianness(value); - return true; - } - - return false; - } - - public static bool TryReadLittleEndian(ref this BufferReader reader, out ulong value) - { - if (TryReadLittleEndian(ref reader, out long signedvalue)) - { - value = unchecked((ulong)signedvalue); - return true; - } - - value = default; - return false; - } - - public static bool TryReadBigEndian(ref this BufferReader reader, out ulong value) - { - if (TryReadBigEndian(ref reader, out long signedvalue)) - { - value = unchecked((ulong)signedvalue); - return true; - } - - value = default; - return false; - } - } -} +// Copyright (c) Harry Pierson. All rights reserved. +// Licensed under the MIT license. +// See LICENSE file in the project root for full license information. + +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + public static class BufferReaderExtensions + { + private static unsafe bool TryRead(ref this BufferReader reader, out T value) + where T : unmanaged + { + var span = reader.UnreadSpan; + if (span.Length < sizeof(T)) return TryReadMultisegment(ref reader, out value); + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span)); + reader.Advance(sizeof(T)); + return true; + } + + private static unsafe bool TryReadMultisegment(ref BufferReader reader, out T value) + where T : unmanaged + { + // Not enough data in the current segment, try to peek for the data we need. + T buffer = default; + var tempSpan = new Span(&buffer, sizeof(T)); + + if (!reader.TryCopyTo(tempSpan)) + { + value = default; + return false; + } + + value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan)); + reader.Advance(sizeof(T)); + return true; + } + + public static bool TryRead(ref this BufferReader reader, out sbyte value) + { + if (TryRead(ref reader, out byte byteValue)) + { + value = unchecked((sbyte)byteValue); + return true; + } + + value = default; + return false; + } + + public static bool TryReadLittleEndian(ref this BufferReader reader, out short value) => + BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); + + public static bool TryReadBigEndian(ref this BufferReader reader, out short value) => + !BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); + + private static bool TryReadReverseEndianness(ref BufferReader reader, out short value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + public static bool TryReadLittleEndian(ref this BufferReader reader, out ushort value) + { + if (TryReadLittleEndian(ref reader, out short signedvalue)) + { + value = unchecked((ushort)signedvalue); + return true; + } + + value = default; + return false; + } + + public static bool TryReadBigEndian(ref this BufferReader reader, out ushort value) + { + if (TryReadBigEndian(ref reader, out short signedvalue)) + { + value = unchecked((ushort)signedvalue); + return true; + } + + value = default; + return false; + } + + public static bool TryReadLittleEndian(ref this BufferReader reader, out int value) => + BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); + + public static bool TryReadBigEndian(ref this BufferReader reader, out int value) => + !BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); + + private static bool TryReadReverseEndianness(ref BufferReader reader, out int value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + public static bool TryReadLittleEndian(ref this BufferReader reader, out uint value) + { + if (TryReadLittleEndian(ref reader, out int signedvalue)) + { + value = unchecked((uint)signedvalue); + return true; + } + + value = default; + return false; + } + + public static bool TryReadBigEndian(ref this BufferReader reader, out uint value) + { + if (TryReadBigEndian(ref reader, out int signedvalue)) + { + value = unchecked((uint)signedvalue); + return true; + } + + value = default; + return false; + } + + public static bool TryReadLittleEndian(ref this BufferReader reader, out long value) => + BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); + + public static bool TryReadBigEndian(ref this BufferReader reader, out long value) => + !BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value); + + private static bool TryReadReverseEndianness(ref BufferReader reader, out long value) + { + if (reader.TryRead(out value)) + { + value = BinaryPrimitives.ReverseEndianness(value); + return true; + } + + return false; + } + + public static bool TryReadLittleEndian(ref this BufferReader reader, out ulong value) + { + if (TryReadLittleEndian(ref reader, out long signedvalue)) + { + value = unchecked((ulong)signedvalue); + return true; + } + + value = default; + return false; + } + + public static bool TryReadBigEndian(ref this BufferReader reader, out ulong value) + { + if (TryReadBigEndian(ref reader, out long signedvalue)) + { + value = unchecked((ulong)signedvalue); + return true; + } + + value = default; + return false; + } + } +} diff --git a/Projects/Server/Buffers/BufferWriter.cs b/Projects/Server/Buffers/BufferWriter.cs index c534a238f..f4fbd01c8 100644 --- a/Projects/Server/Buffers/BufferWriter.cs +++ b/Projects/Server/Buffers/BufferWriter.cs @@ -1,143 +1,145 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Runtime.CompilerServices; - -namespace System.Buffers -{ - /// - /// A fast access struct that wraps . - /// - /// The type of element to be written. - internal ref struct BufferWriter where T : IBufferWriter - { - /// - /// The underlying . - /// - private T _output; - - /// - /// The result of the last call to , less any bytes already "consumed" with . - /// Backing field for the property. - /// - private Span _span; - - /// - /// The number of uncommitted bytes (all the calls to since the last call to ). - /// - private int _buffered; - - /// - /// The total number of bytes written with this writer. - /// Backing field for the property. - /// - private long _bytesCommitted; - - /// - /// Initializes a new instance of the struct. - /// - /// The to be wrapped. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferWriter(T output) - { - _buffered = 0; - _bytesCommitted = 0; - _output = output; - _span = output.GetSpan(); - } - - /// - /// Gets the result of the last call to . - /// - public Span Span => _span; - - /// - /// Gets the total number of bytes written with this writer. - /// - public long BytesCommitted => _bytesCommitted; - - /// - /// Calls on the underlying writer - /// with the number of uncommitted bytes. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Commit() - { - var buffered = _buffered; - if (buffered > 0) - { - _bytesCommitted += buffered; - _buffered = 0; - _output.Advance(buffered); - } - } - - /// - /// Used to indicate that part of the buffer has been written to. - /// - /// The number of bytes written to. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int count) - { - _buffered += count; - _span = _span.Slice(count); - } - - /// - /// Copies the caller's buffer into this writer and calls with the length of the source buffer. - /// - /// The buffer to copy in. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Write(ReadOnlySpan source) - { - if (_span.Length >= source.Length) - { - source.CopyTo(_span); - Advance(source.Length); - } - else - { - WriteMultiBuffer(source); - } - } - - /// - /// Acquires a new buffer if necessary to ensure that some given number of bytes can be written to a single buffer. - /// - /// The number of bytes that must be allocated in a single buffer. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Ensure(int count = 1) - { - if (_span.Length < count) EnsureMore(count); - } - - /// - /// Gets a fresh span to write to, with an optional minimum size. - /// - /// The minimum size for the next requested buffer. - [MethodImpl(MethodImplOptions.NoInlining)] - private void EnsureMore(int count = 0) - { - if (_buffered > 0) Commit(); - - _span = _output.GetSpan(count); - } - - /// - /// Copies the caller's buffer into this writer, potentially across multiple buffers from the underlying writer. - /// - /// The buffer to copy into this writer. - private void WriteMultiBuffer(ReadOnlySpan source) - { - while (source.Length > 0) - { - if (_span.Length == 0) EnsureMore(); - - var writable = Math.Min(source.Length, _span.Length); - source.Slice(0, writable).CopyTo(_span); - source = source.Slice(writable); - Advance(writable); - } - } - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + /// + /// A fast access struct that wraps . + /// + /// The type of element to be written. + internal ref struct BufferWriter where T : IBufferWriter + { + /// + /// The underlying . + /// + private T _output; + + /// + /// The result of the last call to , less any bytes already "consumed" with + /// . + /// Backing field for the property. + /// + private Span _span; + + /// + /// The number of uncommitted bytes (all the calls to since the last call to + /// ). + /// + private int _buffered; + + /// + /// The total number of bytes written with this writer. + /// Backing field for the property. + /// + private long _bytesCommitted; + + /// + /// Initializes a new instance of the struct. + /// + /// The to be wrapped. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferWriter(T output) + { + _buffered = 0; + _bytesCommitted = 0; + _output = output; + _span = output.GetSpan(); + } + + /// + /// Gets the result of the last call to . + /// + public Span Span => _span; + + /// + /// Gets the total number of bytes written with this writer. + /// + public long BytesCommitted => _bytesCommitted; + + /// + /// Calls on the underlying writer + /// with the number of uncommitted bytes. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Commit() + { + var buffered = _buffered; + if (buffered > 0) + { + _bytesCommitted += buffered; + _buffered = 0; + _output.Advance(buffered); + } + } + + /// + /// Used to indicate that part of the buffer has been written to. + /// + /// The number of bytes written to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Advance(int count) + { + _buffered += count; + _span = _span.Slice(count); + } + + /// + /// Copies the caller's buffer into this writer and calls with the length of the source buffer. + /// + /// The buffer to copy in. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan source) + { + if (_span.Length >= source.Length) + { + source.CopyTo(_span); + Advance(source.Length); + } + else + { + WriteMultiBuffer(source); + } + } + + /// + /// Acquires a new buffer if necessary to ensure that some given number of bytes can be written to a single buffer. + /// + /// The number of bytes that must be allocated in a single buffer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Ensure(int count = 1) + { + if (_span.Length < count) EnsureMore(count); + } + + /// + /// Gets a fresh span to write to, with an optional minimum size. + /// + /// The minimum size for the next requested buffer. + [MethodImpl(MethodImplOptions.NoInlining)] + private void EnsureMore(int count = 0) + { + if (_buffered > 0) Commit(); + + _span = _output.GetSpan(count); + } + + /// + /// Copies the caller's buffer into this writer, potentially across multiple buffers from the underlying writer. + /// + /// The buffer to copy into this writer. + private void WriteMultiBuffer(ReadOnlySpan source) + { + while (source.Length > 0) + { + if (_span.Length == 0) EnsureMore(); + + var writable = Math.Min(source.Length, _span.Length); + source.Slice(0, writable).CopyTo(_span); + source = source.Slice(writable); + Advance(writable); + } + } + } +} diff --git a/Projects/Server/Buffers/MemoryPoolBlock.cs b/Projects/Server/Buffers/MemoryPoolBlock.cs index 2f1b87e4c..fa69341af 100644 --- a/Projects/Server/Buffers/MemoryPoolBlock.cs +++ b/Projects/Server/Buffers/MemoryPoolBlock.cs @@ -1,57 +1,58 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.InteropServices; - -namespace System.Buffers -{ - /// - /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The - /// individual blocks are then treated as independent array segments. - /// - public sealed class MemoryPoolBlock : IMemoryOwner - { - private readonly int _offset; - private readonly int _length; - - /// - /// This object cannot be instantiated outside of the static Create method - /// - internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length) - { - _offset = offset; - _length = length; - - Pool = pool; - Slab = slab; - - Memory = MemoryMarshal.CreateFromPinnedArray(slab.Array, _offset, _length); - } - - /// - /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool. - /// - public SlabMemoryPool Pool { get; } - - /// - /// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory. - /// - public MemoryPoolSlab Slab { get; } - - public Memory Memory { get; } - - ~MemoryPoolBlock() - { - Pool.RefreshBlock(Slab, _offset, _length); - } - - public void Dispose() - { - Pool.Return(this); - } - - public void Lease() - { - } - } -} +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + /// + /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller + /// blocks. The + /// individual blocks are then treated as independent array segments. + /// + public sealed class MemoryPoolBlock : IMemoryOwner + { + private readonly int _length; + private readonly int _offset; + + /// + /// This object cannot be instantiated outside of the static Create method + /// + internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length) + { + _offset = offset; + _length = length; + + Pool = pool; + Slab = slab; + + Memory = MemoryMarshal.CreateFromPinnedArray(slab.Array, _offset, _length); + } + + /// + /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool. + /// + public SlabMemoryPool Pool { get; } + + /// + /// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory. + /// + public MemoryPoolSlab Slab { get; } + + public Memory Memory { get; } + + public void Dispose() + { + Pool.Return(this); + } + + ~MemoryPoolBlock() + { + Pool.RefreshBlock(Slab, _offset, _length); + } + + public void Lease() + { + } + } +} diff --git a/Projects/Server/Buffers/MemoryPoolFactory.cs b/Projects/Server/Buffers/MemoryPoolFactory.cs index b8eb87903..64b931880 100644 --- a/Projects/Server/Buffers/MemoryPoolFactory.cs +++ b/Projects/Server/Buffers/MemoryPoolFactory.cs @@ -1,11 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace System.Buffers -{ - public static class SlabMemoryPoolFactory - { - public static MemoryPool Create() => CreateSlabMemoryPool(); - public static MemoryPool CreateSlabMemoryPool() => new SlabMemoryPool(); - } -} +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace System.Buffers +{ + public static class SlabMemoryPoolFactory + { + public static MemoryPool Create() => CreateSlabMemoryPool(); + public static MemoryPool CreateSlabMemoryPool() => new SlabMemoryPool(); + } +} diff --git a/Projects/Server/Buffers/MemoryPoolSlab.cs b/Projects/Server/Buffers/MemoryPoolSlab.cs index c79899c1c..82168ee91 100644 --- a/Projects/Server/Buffers/MemoryPoolSlab.cs +++ b/Projects/Server/Buffers/MemoryPoolSlab.cs @@ -1,75 +1,76 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.InteropServices; - -namespace System.Buffers -{ - /// - /// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The - /// individual blocks are then treated as independent array segments. - /// - public class MemoryPoolSlab : IDisposable - { - /// - /// This handle pins the managed array in memory until the slab is disposed. This prevents it from being - /// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls. - /// - private GCHandle _gcHandle; - - private bool _isDisposed; - - public MemoryPoolSlab(byte[] data) - { - Array = data; - _gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); - NativePointer = _gcHandle.AddrOfPinnedObject(); - } - - /// - /// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the - /// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the - /// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will - /// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage - /// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will - /// be unpinned by the slab's Dispose. - /// - public bool IsActive => !_isDisposed; - - public IntPtr NativePointer { get; private set; } - - public byte[] Array { get; private set; } - - public static MemoryPoolSlab Create(int length) - { - // allocate and pin requested memory length - var array = new byte[length]; - - // allocate and return slab tracking object - return new MemoryPoolSlab(array); - } - - protected void Dispose(bool disposing) - { - if (_isDisposed) return; - - _isDisposed = true; - - Array = null; - NativePointer = IntPtr.Zero; - - if (_gcHandle.IsAllocated) _gcHandle.Free(); - } - - ~MemoryPoolSlab() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.InteropServices; + +namespace System.Buffers +{ + /// + /// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller + /// blocks. The + /// individual blocks are then treated as independent array segments. + /// + public class MemoryPoolSlab : IDisposable + { + /// + /// This handle pins the managed array in memory until the slab is disposed. This prevents it from being + /// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls. + /// + private GCHandle _gcHandle; + + private bool _isDisposed; + + public MemoryPoolSlab(byte[] data) + { + Array = data; + _gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); + NativePointer = _gcHandle.AddrOfPinnedObject(); + } + + /// + /// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the + /// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the + /// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will + /// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage + /// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will + /// be unpinned by the slab's Dispose. + /// + public bool IsActive => !_isDisposed; + + public IntPtr NativePointer { get; private set; } + + public byte[] Array { get; private set; } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public static MemoryPoolSlab Create(int length) + { + // allocate and pin requested memory length + var array = new byte[length]; + + // allocate and return slab tracking object + return new MemoryPoolSlab(array); + } + + protected void Dispose(bool disposing) + { + if (_isDisposed) return; + + _isDisposed = true; + + Array = null; + NativePointer = IntPtr.Zero; + + if (_gcHandle.IsAllocated) _gcHandle.Free(); + } + + ~MemoryPoolSlab() + { + Dispose(false); + } + } +} diff --git a/Projects/Server/Buffers/MemoryPoolThrowHelper.cs b/Projects/Server/Buffers/MemoryPoolThrowHelper.cs index 06d23a835..2a62bab70 100644 --- a/Projects/Server/Buffers/MemoryPoolThrowHelper.cs +++ b/Projects/Server/Buffers/MemoryPoolThrowHelper.cs @@ -1,51 +1,53 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.CompilerServices; - -namespace System.Buffers -{ - public static class MemoryPoolThrowHelper - { - public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset) - { - throw GetArgumentOutOfRangeException(sourceLength, offset); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset) => - (uint)offset > (uint)sourceLength - ? new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset)) - : new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.length)); - - public static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize) - { - throw GetArgumentOutOfRangeException_BufferRequestTooLarge(maxSize); - } - - public static void ThrowObjectDisposedException(ExceptionArgument argument) - { - throw GetObjectDisposedException(argument); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static ArgumentOutOfRangeException GetArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize) => - new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.size), - $"Cannot allocate more than {maxSize} bytes in a single buffer"); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static ObjectDisposedException GetObjectDisposedException(ExceptionArgument argument) => - new ObjectDisposedException(GetArgumentName(argument)); - - private static string GetArgumentName(ExceptionArgument argument) => argument.ToString(); - - public enum ExceptionArgument - { - size, - offset, - length, - MemoryPoolBlock, - MemoryPool - } - } -} +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace System.Buffers +{ + public static class MemoryPoolThrowHelper + { + public enum ExceptionArgument + { + size, + offset, + length, + MemoryPoolBlock, + MemoryPool + } + + public static void ThrowArgumentOutOfRangeException(int sourceLength, int offset) + { + throw GetArgumentOutOfRangeException(sourceLength, offset); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(int sourceLength, int offset) => + (uint)offset > (uint)sourceLength + ? new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.offset)) + : new ArgumentOutOfRangeException(GetArgumentName(ExceptionArgument.length)); + + public static void ThrowArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize) + { + throw GetArgumentOutOfRangeException_BufferRequestTooLarge(maxSize); + } + + public static void ThrowObjectDisposedException(ExceptionArgument argument) + { + throw GetObjectDisposedException(argument); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ArgumentOutOfRangeException GetArgumentOutOfRangeException_BufferRequestTooLarge(int maxSize) => + new ArgumentOutOfRangeException( + GetArgumentName(ExceptionArgument.size), + $"Cannot allocate more than {maxSize} bytes in a single buffer" + ); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ObjectDisposedException GetObjectDisposedException(ExceptionArgument argument) => + new ObjectDisposedException(GetArgumentName(argument)); + + private static string GetArgumentName(ExceptionArgument argument) => argument.ToString(); + } +} diff --git a/Projects/Server/Buffers/SlabMemoryPool.cs b/Projects/Server/Buffers/SlabMemoryPool.cs index f0f4969cb..e0e91a131 100644 --- a/Projects/Server/Buffers/SlabMemoryPool.cs +++ b/Projects/Server/Buffers/SlabMemoryPool.cs @@ -1,179 +1,187 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Concurrent; -using System.Threading; - -namespace System.Buffers -{ - /// - /// Used to allocate and distribute re-usable blocks of memory. - /// - public sealed class SlabMemoryPool : MemoryPool - { - /// - /// The size of a block. 4096 is chosen because most operating systems use 4k pages. - /// - private const int _blockSize = 4096; - - /// - /// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the memory - /// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not negatively - /// affect memory management's compactification. - /// - private const int _blockCount = 32; - - /// - /// Max allocation block size for pooled blocks, - /// larger values can be leased but they will be disposed after use rather than returned to the pool. - /// - public override int MaxBufferSize { get; } = _blockSize; - - /// - /// The size of a block. 4096 is chosen because most operating systems use 4k pages. - /// - public static int BlockSize => _blockSize; - - /// - /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab - /// - private static readonly int _slabLength = _blockSize * _blockCount; - - /// - /// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects - /// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is re-added. - /// - private readonly ConcurrentQueue _blocks = new ConcurrentQueue(); - - /// - /// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive, - /// the blocks will be added to _blocks when returned. - /// - private readonly ConcurrentStack _slabs = new ConcurrentStack(); - - /// - /// This is part of implementing the IDisposable pattern. - /// - private bool _isDisposed; // To detect redundant calls - - private int _totalAllocatedBlocks; - - private readonly object _disposeSync = new object(); - - /// - /// This default value passed in to Rent to use the default value for the pool. - /// - private const int AnySize = -1; - - public override IMemoryOwner Rent(int size = AnySize) - { - if (size > _blockSize) MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize); - - var block = Lease(); - return block; - } - - /// - /// Called to take a block from the pool. - /// - /// The block that is reserved for the called. It must be passed to Return when it is no longer being used. - private MemoryPoolBlock Lease() - { - if (_isDisposed) - MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool); - - if (_blocks.TryDequeue(out var block)) - { - // block successfully taken from the stack - return it - - block.Lease(); - return block; - } - - // no blocks available - grow the pool - block = AllocateSlab(); - block.Lease(); - return block; - } - - /// - /// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the - /// block tracking objects, and adds them all to the pool. - /// - private MemoryPoolBlock AllocateSlab() - { -#pragma warning disable CA2000 // Dispose objects before losing scope - var slab = MemoryPoolSlab.Create(_slabLength); -#pragma warning restore CA2000 // Dispose objects before losing scope - _slabs.Push(slab); - - var basePtr = slab.NativePointer; - // Page align the blocks - var offset = (int)((((ulong)basePtr + _blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr); - - var blockCount = (_slabLength - offset) / _blockSize; - Interlocked.Add(ref _totalAllocatedBlocks, blockCount); - - MemoryPoolBlock block = null; - - for (var i = 0; i < blockCount; i++) - { - block = new MemoryPoolBlock(this, slab, offset, _blockSize); - - if (i != blockCount - 1) // last block - Return(block); - - offset += _blockSize; - } - - return block; - } - - /// - /// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and - /// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the - /// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and return - /// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to avoid - /// leaving "dead zones" in the slab due to lost block tracking objects. - /// - /// The block to return. It must have been acquired by calling Lease on the same memory pool instance. - internal void Return(MemoryPoolBlock block) - { - if (!_isDisposed) - _blocks.Enqueue(block); - else - GC.SuppressFinalize(block); - } - - // This method can ONLY be called from the finalizer of MemoryPoolBlock - internal void RefreshBlock(MemoryPoolSlab slab, int offset, int length) - { - lock (_disposeSync) - { - if (!_isDisposed && slab?.IsActive == true) - // Need to make a new object because this one is being finalized - // Note, this must be called within the _disposeSync lock because the block - // could be disposed at the same time as the finalizer. - Return(new MemoryPoolBlock(this, slab, offset, length)); - } - } - - protected override void Dispose(bool disposing) - { - if (_isDisposed) return; - - lock (_disposeSync) - { - _isDisposed = true; - - if (disposing) - while (_slabs.TryPop(out var slab)) - // dispose managed state (managed objects). - slab.Dispose(); - - // Discard blocks in pool - while (_blocks.TryDequeue(out var block)) GC.SuppressFinalize(block); - } - } - } -} +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Concurrent; +using System.Threading; + +namespace System.Buffers +{ + /// + /// Used to allocate and distribute re-usable blocks of memory. + /// + public sealed class SlabMemoryPool : MemoryPool + { + /// + /// The size of a block. 4096 is chosen because most operating systems use 4k pages. + /// + private const int _blockSize = 4096; + + /// + /// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the + /// memory + /// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not + /// negatively + /// affect memory management's compactification. + /// + private const int _blockCount = 32; + + /// + /// This default value passed in to Rent to use the default value for the pool. + /// + private const int AnySize = -1; + + /// + /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab + /// + private static readonly int _slabLength = _blockSize * _blockCount; + + /// + /// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking + /// objects + /// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is + /// re-added. + /// + private readonly ConcurrentQueue _blocks = new ConcurrentQueue(); + + private readonly object _disposeSync = new object(); + + /// + /// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and + /// slab.IsActive, + /// the blocks will be added to _blocks when returned. + /// + private readonly ConcurrentStack _slabs = new ConcurrentStack(); + + /// + /// This is part of implementing the IDisposable pattern. + /// + private bool _isDisposed; // To detect redundant calls + + private int _totalAllocatedBlocks; + + /// + /// Max allocation block size for pooled blocks, + /// larger values can be leased but they will be disposed after use rather than returned to the pool. + /// + public override int MaxBufferSize { get; } = _blockSize; + + /// + /// The size of a block. 4096 is chosen because most operating systems use 4k pages. + /// + public static int BlockSize => _blockSize; + + public override IMemoryOwner Rent(int size = AnySize) + { + if (size > _blockSize) MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize); + + var block = Lease(); + return block; + } + + /// + /// Called to take a block from the pool. + /// + /// The block that is reserved for the called. It must be passed to Return when it is no longer being used. + private MemoryPoolBlock Lease() + { + if (_isDisposed) + MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool); + + if (_blocks.TryDequeue(out var block)) + { + // block successfully taken from the stack - return it + + block.Lease(); + return block; + } + + // no blocks available - grow the pool + block = AllocateSlab(); + block.Lease(); + return block; + } + + /// + /// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of + /// the + /// block tracking objects, and adds them all to the pool. + /// + private MemoryPoolBlock AllocateSlab() + { +#pragma warning disable CA2000 // Dispose objects before losing scope + var slab = MemoryPoolSlab.Create(_slabLength); +#pragma warning restore CA2000 // Dispose objects before losing scope + _slabs.Push(slab); + + var basePtr = slab.NativePointer; + // Page align the blocks + var offset = (int)((((ulong)basePtr + _blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr); + + var blockCount = (_slabLength - offset) / _blockSize; + Interlocked.Add(ref _totalAllocatedBlocks, blockCount); + + MemoryPoolBlock block = null; + + for (var i = 0; i < blockCount; i++) + { + block = new MemoryPoolBlock(this, slab, offset, _blockSize); + + if (i != blockCount - 1) // last block + Return(block); + + offset += _blockSize; + } + + return block; + } + + /// + /// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and + /// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the + /// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and + /// return + /// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to + /// avoid + /// leaving "dead zones" in the slab due to lost block tracking objects. + /// + /// The block to return. It must have been acquired by calling Lease on the same memory pool instance. + internal void Return(MemoryPoolBlock block) + { + if (!_isDisposed) + _blocks.Enqueue(block); + else + GC.SuppressFinalize(block); + } + + // This method can ONLY be called from the finalizer of MemoryPoolBlock + internal void RefreshBlock(MemoryPoolSlab slab, int offset, int length) + { + lock (_disposeSync) + { + if (!_isDisposed && slab?.IsActive == true) + // Need to make a new object because this one is being finalized + // Note, this must be called within the _disposeSync lock because the block + // could be disposed at the same time as the finalizer. + Return(new MemoryPoolBlock(this, slab, offset, length)); + } + } + + protected override void Dispose(bool disposing) + { + if (_isDisposed) return; + + lock (_disposeSync) + { + _isDisposed = true; + + if (disposing) + while (_slabs.TryPop(out var slab)) + // dispose managed state (managed objects). + slab.Dispose(); + + // Discard blocks in pool + while (_blocks.TryDequeue(out var block)) GC.SuppressFinalize(block); + } + } + } +} diff --git a/Projects/Server/Buffers/SpanExtensions.cs b/Projects/Server/Buffers/SpanExtensions.cs index 52fa6a7f9..0bc613d80 100644 --- a/Projects/Server/Buffers/SpanExtensions.cs +++ b/Projects/Server/Buffers/SpanExtensions.cs @@ -1,214 +1,214 @@ -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using System.Text; -using Server; - -namespace System.Buffers -{ - public static class SpanExtensions - { - // Extensions - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ushort value) => BinaryPrimitives.WriteUInt16BigEndian(span, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, short value) => BinaryPrimitives.WriteInt16BigEndian(span, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, uint value) => BinaryPrimitives.WriteUInt32BigEndian(span, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, int value) => BinaryPrimitives.WriteInt32BigEndian(span, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, byte value) => span[0] = value; - - // Ref Extensions - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, ReadOnlySpan data) - { - data.CopyTo(span.Slice(pos, data.Length)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, bool value) - { - span[pos++] = value ? (byte)1 : (byte)0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, byte value) => span[pos++] = value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, ushort value) - { - BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), value); - pos += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, short value) - { - BinaryPrimitives.WriteInt16BigEndian(span.Slice(pos, 2), value); - pos += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, uint value) - { - BinaryPrimitives.WriteUInt32BigEndian(span.Slice(pos, 4), value); - pos += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteLE(this Span span, ref int pos, uint value) - { - BinaryPrimitives.WriteUInt32LittleEndian(span.Slice(pos, 4), value); - pos += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, int value) - { - BinaryPrimitives.WriteInt32BigEndian(span.Slice(pos, 4), value); - pos += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteLE(this Span span, ref int pos, int value) - { - BinaryPrimitives.WriteInt32LittleEndian(span.Slice(pos, 4), value); - pos += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, Serial value) - { - BinaryPrimitives.WriteUInt32BigEndian(span.Slice(pos, 4), value); - pos += 4; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, ulong value) - { - BinaryPrimitives.WriteUInt64BigEndian(span.Slice(pos, 8), value); - pos += 8; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, long value) - { - BinaryPrimitives.WriteInt64BigEndian(span.Slice(pos, 8), value); - pos += 8; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteLE(this Span span, ref int pos, ulong value) - { - BinaryPrimitives.WriteUInt64LittleEndian(span.Slice(pos, 8), value); - pos += 8; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteLE(this Span span, ref int pos, long value) - { - BinaryPrimitives.WriteInt64LittleEndian(span.Slice(pos, 8), value); - pos += 8; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteAscii(this Span span, ref int pos, string value) - { - int length = value.Length; - pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteAscii(this Span span, ref int pos, string value, int max) - { - int length = value.Length <= max ? value.Length : max; - - pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteAsciiNull(this Span span, ref int pos, string value) - { - int length = value.Length; - pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); -#if NO_LOCAL_INIT - span[pos] = 0; // Null terminator -#endif - pos++; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteAsciiNull(this Span span, ref int pos, string value, int max) - { - var length = value.Length < max ? value.Length : max - 1; - - pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); -#if NO_LOCAL_INIT - span[pos] = 0; // Null terminator -#endif - pos++; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteAsciiFixed(this Span span, ref int pos, string value, int amount) - { - var length = value.Length <= amount ? value.Length : amount; -#if NO_LOCAL_INIT - int bytesWritten = Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); - - if (bytesWritten < amount) - span.Slice(pos + bytesWritten, amount - bytesWritten).Clear(); -#else - Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); -#endif - - pos += amount; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteBigUni(this Span span, ref int pos, string value) - { - pos += Encoding.BigEndianUnicode.GetBytes(value, span.Slice(pos)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteLittleUni(this Span span, ref int pos, string value) - { - pos += Encoding.Unicode.GetBytes(value, span.Slice(pos)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteBigUniNull(this Span span, ref int pos, string value) - { - pos += Encoding.BigEndianUnicode.GetBytes(value, span.Slice(pos)); -#if NO_LOCAL_INIT - BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator -#endif - pos += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteLittleUniNull(this Span span, ref int pos, string value) - { - pos += Encoding.Unicode.GetBytes(value, span.Slice(pos)); -#if NO_LOCAL_INIT - BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator -#endif - pos += 2; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Write(this Span span, ref int pos, Point3D p) - { - BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), (ushort)p.X); - BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos + 2, 2), (ushort)p.Y); - span[pos + 4] = (byte)p.Z; - pos += 5; - } - } -} +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Text; +using Server; + +namespace System.Buffers +{ + public static class SpanExtensions + { + // Extensions + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ushort value) => BinaryPrimitives.WriteUInt16BigEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, short value) => BinaryPrimitives.WriteInt16BigEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, uint value) => BinaryPrimitives.WriteUInt32BigEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, int value) => BinaryPrimitives.WriteInt32BigEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, byte value) => span[0] = value; + + // Ref Extensions + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, ReadOnlySpan data) + { + data.CopyTo(span.Slice(pos, data.Length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, bool value) + { + span[pos++] = value ? (byte)1 : (byte)0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, byte value) => span[pos++] = value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, ushort value) + { + BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), value); + pos += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, short value) + { + BinaryPrimitives.WriteInt16BigEndian(span.Slice(pos, 2), value); + pos += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, uint value) + { + BinaryPrimitives.WriteUInt32BigEndian(span.Slice(pos, 4), value); + pos += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLE(this Span span, ref int pos, uint value) + { + BinaryPrimitives.WriteUInt32LittleEndian(span.Slice(pos, 4), value); + pos += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, int value) + { + BinaryPrimitives.WriteInt32BigEndian(span.Slice(pos, 4), value); + pos += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLE(this Span span, ref int pos, int value) + { + BinaryPrimitives.WriteInt32LittleEndian(span.Slice(pos, 4), value); + pos += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, Serial value) + { + BinaryPrimitives.WriteUInt32BigEndian(span.Slice(pos, 4), value); + pos += 4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, ulong value) + { + BinaryPrimitives.WriteUInt64BigEndian(span.Slice(pos, 8), value); + pos += 8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, long value) + { + BinaryPrimitives.WriteInt64BigEndian(span.Slice(pos, 8), value); + pos += 8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLE(this Span span, ref int pos, ulong value) + { + BinaryPrimitives.WriteUInt64LittleEndian(span.Slice(pos, 8), value); + pos += 8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLE(this Span span, ref int pos, long value) + { + BinaryPrimitives.WriteInt64LittleEndian(span.Slice(pos, 8), value); + pos += 8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteAscii(this Span span, ref int pos, string value) + { + var length = value.Length; + pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteAscii(this Span span, ref int pos, string value, int max) + { + var length = value.Length <= max ? value.Length : max; + + pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteAsciiNull(this Span span, ref int pos, string value) + { + var length = value.Length; + pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); +#if NO_LOCAL_INIT + span[pos] = 0; // Null terminator +#endif + pos++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteAsciiNull(this Span span, ref int pos, string value, int max) + { + var length = value.Length < max ? value.Length : max - 1; + + pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); +#if NO_LOCAL_INIT + span[pos] = 0; // Null terminator +#endif + pos++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteAsciiFixed(this Span span, ref int pos, string value, int amount) + { + var length = value.Length <= amount ? value.Length : amount; +#if NO_LOCAL_INIT + int bytesWritten = Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); + + if (bytesWritten < amount) + span.Slice(pos + bytesWritten, amount - bytesWritten).Clear(); +#else + Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length)); +#endif + + pos += amount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteBigUni(this Span span, ref int pos, string value) + { + pos += Encoding.BigEndianUnicode.GetBytes(value, span.Slice(pos)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLittleUni(this Span span, ref int pos, string value) + { + pos += Encoding.Unicode.GetBytes(value, span.Slice(pos)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteBigUniNull(this Span span, ref int pos, string value) + { + pos += Encoding.BigEndianUnicode.GetBytes(value, span.Slice(pos)); +#if NO_LOCAL_INIT + BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator +#endif + pos += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteLittleUniNull(this Span span, ref int pos, string value) + { + pos += Encoding.Unicode.GetBytes(value, span.Slice(pos)); +#if NO_LOCAL_INIT + BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator +#endif + pos += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(this Span span, ref int pos, Point3D p) + { + BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), (ushort)p.X); + BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos + 2, 2), (ushort)p.Y); + span[pos + 4] = (byte)p.Z; + pos += 5; + } + } +} diff --git a/Projects/Server/CityInfo.cs b/Projects/Server/CityInfo.cs index c88b80a28..9981050fc 100644 --- a/Projects/Server/CityInfo.cs +++ b/Projects/Server/CityInfo.cs @@ -1,81 +1,88 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: CityInfo.cs - Created: 2019/10/04 - Updated: 2020/01/19 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server -{ - public sealed class CityInfo - { - private Point3D m_Location; - - public CityInfo(string city, string building, int description, int x, int y, int z, Map m) - { - City = city; - Building = building; - Description = description; - Location = new Point3D(x, y, z); - Map = m; - } - - public CityInfo(string city, string building, int x, int y, int z, Map m) : this(city, building, 0, x, y, z, m) - { - } - - public CityInfo(string city, string building, int description, int x, int y, int z) : this(city, building, - description, x, y, z, Map.Trammel) - { - } - - public CityInfo(string city, string building, int x, int y, int z) : this(city, building, 0, x, y, z, Map.Trammel) - { - } - - public string City { get; set; } - - public string Building { get; set; } - - public int Description { get; set; } - - public int X - { - get => m_Location.X; - set => m_Location.X = value; - } - - public int Y - { - get => m_Location.Y; - set => m_Location.Y = value; - } - - public int Z - { - get => m_Location.Z; - set => m_Location.Z = value; - } - - public Point3D Location - { - get => m_Location; - set => m_Location = value; - } - - public Map Map { get; set; } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CityInfo.cs - Created: 2019/10/04 - Updated: 2020/01/19 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server +{ + public sealed class CityInfo + { + private Point3D m_Location; + + public CityInfo(string city, string building, int description, int x, int y, int z, Map m) + { + City = city; + Building = building; + Description = description; + Location = new Point3D(x, y, z); + Map = m; + } + + public CityInfo(string city, string building, int x, int y, int z, Map m) : this(city, building, 0, x, y, z, m) + { + } + + public CityInfo(string city, string building, int description, int x, int y, int z) : this( + city, + building, + description, + x, + y, + z, + Map.Trammel + ) + { + } + + public CityInfo(string city, string building, int x, int y, int z) : this(city, building, 0, x, y, z, Map.Trammel) + { + } + + public string City { get; set; } + + public string Building { get; set; } + + public int Description { get; set; } + + public int X + { + get => m_Location.X; + set => m_Location.X = value; + } + + public int Y + { + get => m_Location.Y; + set => m_Location.Y = value; + } + + public int Z + { + get => m_Location.Z; + set => m_Location.Z = value; + } + + public Point3D Location + { + get => m_Location; + set => m_Location = value; + } + + public Map Map { get; set; } + } +} diff --git a/Projects/Server/ClientVersion.cs b/Projects/Server/ClientVersion.cs index 81e22d7fd..551904cad 100644 --- a/Projects/Server/ClientVersion.cs +++ b/Projects/Server/ClientVersion.cs @@ -1,207 +1,207 @@ -/*************************************************************************** - * ClientVersion.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Text; - -namespace Server -{ - public enum ClientType - { - Regular, - UOTD, - God, - SA - } - - public class ClientVersion : IComparable, IComparer - { - public ClientVersion(int maj, int min, int rev, int pat, ClientType type = ClientType.Regular) - { - Major = maj; - Minor = min; - Revision = rev; - Patch = pat; - Type = type; - - SourceString = ToStringImpl(); - } - - public ClientVersion(string fmt) - { - SourceString = fmt; - - try - { - fmt = fmt.ToLower(); - - var br1 = fmt.IndexOf('.'); - var br2 = fmt.IndexOf('.', br1 + 1); - - var br3 = br2 + 1; - while (br3 < fmt.Length && char.IsDigit(fmt, br3)) - br3++; - - Major = Utility.ToInt32(fmt.Substring(0, br1)); - Minor = Utility.ToInt32(fmt.Substring(br1 + 1, br2 - br1 - 1)); - Revision = Utility.ToInt32(fmt.Substring(br2 + 1, br3 - br2 - 1)); - - if (br3 < fmt.Length) - { - if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7 - { - if (!char.IsWhiteSpace(fmt, br3)) - Patch = fmt[br3] - 'a' + 1; - } - else - { - Patch = Utility.ToInt32(fmt.Substring(br3 + 1, fmt.Length - br3 - 1)); - } - } - - if (fmt.IndexOf("god") >= 0 || fmt.IndexOf("gq") >= 0) - Type = ClientType.God; - else if (fmt.IndexOf("third dawn") >= 0 || fmt.IndexOf("uo:td") >= 0 || fmt.IndexOf("uotd") >= 0 || - fmt.IndexOf("uo3d") >= 0 || fmt.IndexOf("uo:3d") >= 0) - Type = ClientType.UOTD; - else - Type = ClientType.Regular; - } - catch - { - Major = 0; - Minor = 0; - Revision = 0; - Patch = 0; - Type = ClientType.Regular; - } - } - - public int Major { get; } - - public int Minor { get; } - - public int Revision { get; } - - public int Patch { get; } - - public ClientType Type { get; } - - public string SourceString { get; } - - public int CompareTo(ClientVersion o) - { - if (o == null) - return 1; - - if (Major > o.Major) - return 1; - if (Major < o.Major) - return -1; - if (Minor > o.Minor) - return 1; - if (Minor < o.Minor) - return -1; - if (Revision > o.Revision) - return 1; - if (Revision < o.Revision) - return -1; - if (Patch > o.Patch) - return 1; - if (Patch < o.Patch) - return -1; - return 0; - } - - public static bool operator ==(ClientVersion l, ClientVersion r) => Compare(l, r) == 0; - - public static bool operator !=(ClientVersion l, ClientVersion r) => Compare(l, r) != 0; - - public static bool operator >=(ClientVersion l, ClientVersion r) => Compare(l, r) >= 0; - - public static bool operator >(ClientVersion l, ClientVersion r) => Compare(l, r) > 0; - - public static bool operator <=(ClientVersion l, ClientVersion r) => Compare(l, r) <= 0; - - public static bool operator <(ClientVersion l, ClientVersion r) => Compare(l, r) < 0; - - public override int GetHashCode() => Major ^ Minor ^ Revision ^ Patch ^ (int)Type; - - int IComparer.Compare(ClientVersion x, ClientVersion y) => Compare(x, y); - - public override bool Equals(object obj) - { - var v = obj as ClientVersion; - - return Major == v?.Major - && Minor == v.Minor - && Revision == v.Revision - && Patch == v.Patch - && Type == v.Type; - } - - private string ToStringImpl() - { - var builder = new StringBuilder(16); - - builder.Append(Major); - builder.Append('.'); - builder.Append(Minor); - builder.Append('.'); - builder.Append(Revision); - - if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7 - { - if (Patch > 0) - builder.Append((char)('a' + (Patch - 1))); - } - else - { - builder.Append('.'); - builder.Append(Patch); - } - - if (Type != ClientType.Regular) - { - builder.Append(' '); - builder.Append(Type.ToString()); - } - - return builder.ToString(); - } - - public override string ToString() => ToStringImpl(); - - public static bool IsNull(object x) => ReferenceEquals(x, null); - - public static int Compare(ClientVersion a, ClientVersion b) - { - if (IsNull(a) && IsNull(b)) - return 0; - if (IsNull(a)) - return -1; - if (IsNull(b)) - return 1; - - return a.CompareTo(b); - } - } -} +/*************************************************************************** + * ClientVersion.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Server +{ + public enum ClientType + { + Regular, + UOTD, + God, + SA + } + + public class ClientVersion : IComparable, IComparer + { + public ClientVersion(int maj, int min, int rev, int pat, ClientType type = ClientType.Regular) + { + Major = maj; + Minor = min; + Revision = rev; + Patch = pat; + Type = type; + + SourceString = ToStringImpl(); + } + + public ClientVersion(string fmt) + { + SourceString = fmt; + + try + { + fmt = fmt.ToLower(); + + var br1 = fmt.IndexOf('.'); + var br2 = fmt.IndexOf('.', br1 + 1); + + var br3 = br2 + 1; + while (br3 < fmt.Length && char.IsDigit(fmt, br3)) + br3++; + + Major = Utility.ToInt32(fmt.Substring(0, br1)); + Minor = Utility.ToInt32(fmt.Substring(br1 + 1, br2 - br1 - 1)); + Revision = Utility.ToInt32(fmt.Substring(br2 + 1, br3 - br2 - 1)); + + if (br3 < fmt.Length) + { + if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7 + { + if (!char.IsWhiteSpace(fmt, br3)) + Patch = fmt[br3] - 'a' + 1; + } + else + { + Patch = Utility.ToInt32(fmt.Substring(br3 + 1, fmt.Length - br3 - 1)); + } + } + + if (fmt.IndexOf("god") >= 0 || fmt.IndexOf("gq") >= 0) + Type = ClientType.God; + else if (fmt.IndexOf("third dawn") >= 0 || fmt.IndexOf("uo:td") >= 0 || fmt.IndexOf("uotd") >= 0 || + fmt.IndexOf("uo3d") >= 0 || fmt.IndexOf("uo:3d") >= 0) + Type = ClientType.UOTD; + else + Type = ClientType.Regular; + } + catch + { + Major = 0; + Minor = 0; + Revision = 0; + Patch = 0; + Type = ClientType.Regular; + } + } + + public int Major { get; } + + public int Minor { get; } + + public int Revision { get; } + + public int Patch { get; } + + public ClientType Type { get; } + + public string SourceString { get; } + + public int CompareTo(ClientVersion o) + { + if (o == null) + return 1; + + if (Major > o.Major) + return 1; + if (Major < o.Major) + return -1; + if (Minor > o.Minor) + return 1; + if (Minor < o.Minor) + return -1; + if (Revision > o.Revision) + return 1; + if (Revision < o.Revision) + return -1; + if (Patch > o.Patch) + return 1; + if (Patch < o.Patch) + return -1; + return 0; + } + + int IComparer.Compare(ClientVersion x, ClientVersion y) => Compare(x, y); + + public static bool operator ==(ClientVersion l, ClientVersion r) => Compare(l, r) == 0; + + public static bool operator !=(ClientVersion l, ClientVersion r) => Compare(l, r) != 0; + + public static bool operator >=(ClientVersion l, ClientVersion r) => Compare(l, r) >= 0; + + public static bool operator >(ClientVersion l, ClientVersion r) => Compare(l, r) > 0; + + public static bool operator <=(ClientVersion l, ClientVersion r) => Compare(l, r) <= 0; + + public static bool operator <(ClientVersion l, ClientVersion r) => Compare(l, r) < 0; + + public override int GetHashCode() => Major ^ Minor ^ Revision ^ Patch ^ (int)Type; + + public override bool Equals(object obj) + { + var v = obj as ClientVersion; + + return Major == v?.Major + && Minor == v.Minor + && Revision == v.Revision + && Patch == v.Patch + && Type == v.Type; + } + + private string ToStringImpl() + { + var builder = new StringBuilder(16); + + builder.Append(Major); + builder.Append('.'); + builder.Append(Minor); + builder.Append('.'); + builder.Append(Revision); + + if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7 + { + if (Patch > 0) + builder.Append((char)('a' + (Patch - 1))); + } + else + { + builder.Append('.'); + builder.Append(Patch); + } + + if (Type != ClientType.Regular) + { + builder.Append(' '); + builder.Append(Type.ToString()); + } + + return builder.ToString(); + } + + public override string ToString() => ToStringImpl(); + + public static bool IsNull(object x) => ReferenceEquals(x, null); + + public static int Compare(ClientVersion a, ClientVersion b) + { + if (IsNull(a) && IsNull(b)) + return 0; + if (IsNull(a)) + return -1; + if (IsNull(b)) + return 1; + + return a.CompareTo(b); + } + } +} diff --git a/Projects/Server/Collections/ArraySet.cs b/Projects/Server/Collections/ArraySet.cs index 4d32c2edf..b6952b5e1 100644 --- a/Projects/Server/Collections/ArraySet.cs +++ b/Projects/Server/Collections/ArraySet.cs @@ -1,75 +1,76 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ArraySet.cs - Created: 2019/10/04 - Updated: 2019/12/30 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Server.Collections -{ - public class ArraySet : IList - { - private readonly List m_List = new List(); - - public T this[int index] - { - get => m_List[index]; - set => m_List[index] = value; - } - - public int Count => m_List.Count; - - public bool IsReadOnly => false; - - public int Add(T item) - { - var indexOf = m_List.IndexOf(item); - - if (indexOf >= 0) return indexOf; - - m_List.Add(item); - return m_List.Count - 1; - } - - public void Clear() => m_List.Clear(); - - public bool Contains(T item) => m_List.Contains(item); - - public void CopyTo(T[] array) => m_List.CopyTo(array); - - public void CopyTo(T[] array, int arrayIndex) => m_List.CopyTo(array, arrayIndex); - - public void CopyTo(int index, T[] array, int arrayIndex, int count) => m_List.CopyTo(index, array, arrayIndex, count); - - public IEnumerator GetEnumerator() => m_List.GetEnumerator(); - - public int IndexOf(T item) => m_List.IndexOf(item); - - public void Insert(int index, T item) => throw new NotImplementedException(); - - public bool Remove(T item) => throw new NotImplementedException(); - - public void RemoveAt(int index) => throw new NotImplementedException(); - - void ICollection.Add(T item) => m_List.Add(item); - - IEnumerator IEnumerable.GetEnumerator() => m_List.GetEnumerator(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ArraySet.cs - Created: 2019/10/04 - Updated: 2019/12/30 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Server.Collections +{ + public class ArraySet : IList + { + private readonly List m_List = new List(); + + public T this[int index] + { + get => m_List[index]; + set => m_List[index] = value; + } + + public int Count => m_List.Count; + + public bool IsReadOnly => false; + + public void Clear() => m_List.Clear(); + + public bool Contains(T item) => m_List.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) => m_List.CopyTo(array, arrayIndex); + + public IEnumerator GetEnumerator() => m_List.GetEnumerator(); + + public int IndexOf(T item) => m_List.IndexOf(item); + + public void Insert(int index, T item) => throw new NotImplementedException(); + + public bool Remove(T item) => throw new NotImplementedException(); + + public void RemoveAt(int index) => throw new NotImplementedException(); + + void ICollection.Add(T item) => m_List.Add(item); + + IEnumerator IEnumerable.GetEnumerator() => m_List.GetEnumerator(); + + public int Add(T item) + { + var indexOf = m_List.IndexOf(item); + + if (indexOf >= 0) return indexOf; + + m_List.Add(item); + return m_List.Count - 1; + } + + public void CopyTo(T[] array) => m_List.CopyTo(array); + + public void CopyTo(int index, T[] array, int arrayIndex, int count) => + m_List.CopyTo(index, array, arrayIndex, count); + } +} diff --git a/Projects/Server/Commands.cs b/Projects/Server/Commands.cs index 4f89ac476..190b4b296 100644 --- a/Projects/Server/Commands.cs +++ b/Projects/Server/Commands.cs @@ -1,245 +1,245 @@ -/*************************************************************************** - * Commands.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using Server.Network; - -namespace Server -{ - public delegate void CommandEventHandler(CommandEventArgs e); - - public class CommandEventArgs : EventArgs - { - public CommandEventArgs(Mobile mobile, string command, string argString, string[] arguments) - { - Mobile = mobile; - Command = command; - ArgString = argString; - Arguments = arguments; - } - - public Mobile Mobile { get; } - - public string Command { get; } - - public string ArgString { get; } - - public string[] Arguments { get; } - - public int Length => Arguments.Length; - - public string GetString(int index) - { - if (index < 0 || index >= Arguments.Length) - return ""; - - return Arguments[index]; - } - - public int GetInt32(int index) - { - if (index < 0 || index >= Arguments.Length) - return 0; - - return Utility.ToInt32(Arguments[index]); - } - - public uint GetUInt32(int index) - { - if (index < 0 || index >= Arguments.Length) - return 0; - - return Utility.ToUInt32(Arguments[index]); - } - - public bool GetBoolean(int index) - { - if (index < 0 || index >= Arguments.Length) - return false; - - return Utility.ToBoolean(Arguments[index]); - } - - public double GetDouble(int index) - { - if (index < 0 || index >= Arguments.Length) - return 0.0; - - return Utility.ToDouble(Arguments[index]); - } - - public TimeSpan GetTimeSpan(int index) - { - if (index < 0 || index >= Arguments.Length) - return TimeSpan.Zero; - - return Utility.ToTimeSpan(Arguments[index]); - } - } - - public static partial class EventSink - { - public static event Action Command; - public static void InvokeCommand(CommandEventArgs e) => Command?.Invoke(e); - } - - public class CommandEntry : IComparable - { - public CommandEntry(string command, CommandEventHandler handler, AccessLevel accessLevel) - { - Command = command; - Handler = handler; - AccessLevel = accessLevel; - } - - public string Command { get; } - - public CommandEventHandler Handler { get; } - - public AccessLevel AccessLevel { get; } - - public int CompareTo(CommandEntry e) => e == null ? 1 : Command.CompareTo(e.Command); - } - - public static class CommandSystem - { - public static string Prefix { get; set; } = "["; - - public static Dictionary Entries { get; } = - new Dictionary(StringComparer.OrdinalIgnoreCase); - - public static AccessLevel BadCommandIgnoreLevel { get; set; } = AccessLevel.Player; - - public static string[] Split(string value) - { - var array = value.ToCharArray(); - var list = new List(); - - var start = 0; - - while (start < array.Length) - { - var c = array[start]; - - if (c == '"') - { - ++start; - var end = start; - - while (end < array.Length) - if (array[end] != '"' || array[end - 1] == '\\') - ++end; - else - break; - - list.Add(value.Substring(start, end - start)); - - start = end + 2; - } - else if (c != ' ') - { - var end = start; - - while (end < array.Length) - if (array[end] != ' ') - ++end; - else - break; - - list.Add(value.Substring(start, end - start)); - - start = end + 1; - } - else - { - ++start; - } - } - - return list.ToArray(); - } - - public static void Register(string command, AccessLevel access, CommandEventHandler handler) - { - Entries[command] = new CommandEntry(command, handler, access); - } - - public static bool Handle(Mobile from, string text, MessageType type = MessageType.Regular) - { - if (!text.StartsWith(Prefix) && type != MessageType.Command) - return false; - - if (type != MessageType.Command) - text = text.Substring(Prefix.Length); - - var indexOf = text.IndexOf(' '); - - string command; - string[] args; - string argString; - - if (indexOf >= 0) - { - argString = text.Substring(indexOf + 1); - - command = text.Substring(0, indexOf); - args = Split(argString); - } - else - { - argString = ""; - command = text.ToLower(); - args = Array.Empty(); - } - - Entries.TryGetValue(command, out var entry); - - if (entry != null) - { - if (from.AccessLevel >= entry.AccessLevel) - { - if (entry.Handler != null) - { - var e = new CommandEventArgs(from, command, argString, args); - entry.Handler(e); - EventSink.InvokeCommand(e); - } - } - else - { - if (from.AccessLevel <= BadCommandIgnoreLevel) - return false; - - from.SendMessage("You do not have access to that command."); - } - } - else - { - if (from.AccessLevel <= BadCommandIgnoreLevel) - return false; - - from.SendMessage("That is not a valid command."); - } - - return true; - } - } -} +/*************************************************************************** + * Commands.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using Server.Network; + +namespace Server +{ + public delegate void CommandEventHandler(CommandEventArgs e); + + public class CommandEventArgs : EventArgs + { + public CommandEventArgs(Mobile mobile, string command, string argString, string[] arguments) + { + Mobile = mobile; + Command = command; + ArgString = argString; + Arguments = arguments; + } + + public Mobile Mobile { get; } + + public string Command { get; } + + public string ArgString { get; } + + public string[] Arguments { get; } + + public int Length => Arguments.Length; + + public string GetString(int index) + { + if (index < 0 || index >= Arguments.Length) + return ""; + + return Arguments[index]; + } + + public int GetInt32(int index) + { + if (index < 0 || index >= Arguments.Length) + return 0; + + return Utility.ToInt32(Arguments[index]); + } + + public uint GetUInt32(int index) + { + if (index < 0 || index >= Arguments.Length) + return 0; + + return Utility.ToUInt32(Arguments[index]); + } + + public bool GetBoolean(int index) + { + if (index < 0 || index >= Arguments.Length) + return false; + + return Utility.ToBoolean(Arguments[index]); + } + + public double GetDouble(int index) + { + if (index < 0 || index >= Arguments.Length) + return 0.0; + + return Utility.ToDouble(Arguments[index]); + } + + public TimeSpan GetTimeSpan(int index) + { + if (index < 0 || index >= Arguments.Length) + return TimeSpan.Zero; + + return Utility.ToTimeSpan(Arguments[index]); + } + } + + public static partial class EventSink + { + public static event Action Command; + public static void InvokeCommand(CommandEventArgs e) => Command?.Invoke(e); + } + + public class CommandEntry : IComparable + { + public CommandEntry(string command, CommandEventHandler handler, AccessLevel accessLevel) + { + Command = command; + Handler = handler; + AccessLevel = accessLevel; + } + + public string Command { get; } + + public CommandEventHandler Handler { get; } + + public AccessLevel AccessLevel { get; } + + public int CompareTo(CommandEntry e) => e == null ? 1 : Command.CompareTo(e.Command); + } + + public static class CommandSystem + { + public static string Prefix { get; set; } = "["; + + public static Dictionary Entries { get; } = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + public static AccessLevel BadCommandIgnoreLevel { get; set; } = AccessLevel.Player; + + public static string[] Split(string value) + { + var array = value.ToCharArray(); + var list = new List(); + + var start = 0; + + while (start < array.Length) + { + var c = array[start]; + + if (c == '"') + { + ++start; + var end = start; + + while (end < array.Length) + if (array[end] != '"' || array[end - 1] == '\\') + ++end; + else + break; + + list.Add(value.Substring(start, end - start)); + + start = end + 2; + } + else if (c != ' ') + { + var end = start; + + while (end < array.Length) + if (array[end] != ' ') + ++end; + else + break; + + list.Add(value.Substring(start, end - start)); + + start = end + 1; + } + else + { + ++start; + } + } + + return list.ToArray(); + } + + public static void Register(string command, AccessLevel access, CommandEventHandler handler) + { + Entries[command] = new CommandEntry(command, handler, access); + } + + public static bool Handle(Mobile from, string text, MessageType type = MessageType.Regular) + { + if (!text.StartsWith(Prefix) && type != MessageType.Command) + return false; + + if (type != MessageType.Command) + text = text.Substring(Prefix.Length); + + var indexOf = text.IndexOf(' '); + + string command; + string[] args; + string argString; + + if (indexOf >= 0) + { + argString = text.Substring(indexOf + 1); + + command = text.Substring(0, indexOf); + args = Split(argString); + } + else + { + argString = ""; + command = text.ToLower(); + args = Array.Empty(); + } + + Entries.TryGetValue(command, out var entry); + + if (entry != null) + { + if (from.AccessLevel >= entry.AccessLevel) + { + if (entry.Handler != null) + { + var e = new CommandEventArgs(from, command, argString, args); + entry.Handler(e); + EventSink.InvokeCommand(e); + } + } + else + { + if (from.AccessLevel <= BadCommandIgnoreLevel) + return false; + + from.SendMessage("You do not have access to that command."); + } + } + else + { + if (from.AccessLevel <= BadCommandIgnoreLevel) + return false; + + from.SendMessage("That is not a valid command."); + } + + return true; + } + } +} diff --git a/Projects/Server/Configuration/ServerConfiguration.cs b/Projects/Server/Configuration/ServerConfiguration.cs index 04fbe7db3..a9fd403c2 100644 --- a/Projects/Server/Configuration/ServerConfiguration.cs +++ b/Projects/Server/Configuration/ServerConfiguration.cs @@ -1,278 +1,277 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ServerConfiguration.cs * - * Created: 2019/10/04 - Updated: 2020/07/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text.Json.Serialization; -using Server.Json; - -namespace Server -{ - public static class ServerConfiguration - { - private const string m_RelPath = "Configuration/modernuo.json"; - private static readonly string m_FilePath = Path.Join(Core.BaseDirectory, m_RelPath); - private static ServerSettings m_Settings; - private static bool m_Mocked; - - public static List DataDirectories => m_Settings.dataDirectories; - - public static List Listeners => m_Settings.listeners; - - public static string GetSetting(string key, string defaultValue) - { - m_Settings.settings.TryGetValue(key, out string value); - return value == "(-null-)" ? null : value ?? defaultValue; - } - - public static int GetSetting(string key, int defaultValue) - { - m_Settings.settings.TryGetValue(key, out string strValue); - return int.TryParse(strValue, out int value) ? value : defaultValue; - } - - public static bool GetSetting(string key, bool defaultValue) - { - m_Settings.settings.TryGetValue(key, out string strValue); - return bool.TryParse(strValue, out bool value) ? value : defaultValue; - } - - public static T GetSetting(string key, T defaultValue) where T : struct, Enum - { - m_Settings.settings.TryGetValue(key, out string strValue); - return Enum.TryParse(strValue, out T value) ? value : defaultValue; - } - - public static string GetOrUpdateSetting(string key, string defaultValue) - { - if (m_Settings.settings.TryGetValue(key, out string value)) - return value; - - SetSetting(key, value = defaultValue); - return value; - } - - public static int GetOrUpdateSetting(string key, int defaultValue) - { - int value; - - if (m_Settings.settings.TryGetValue(key, out string strValue)) - value = int.TryParse(strValue, out value) ? value : defaultValue; - else - SetSetting(key, (value = defaultValue).ToString()); - - return value; - } - - public static bool GetOrUpdateSetting(string key, bool defaultValue) - { - bool value; - - if (m_Settings.settings.TryGetValue(key, out string strValue)) - value = bool.TryParse(strValue, out value) ? value : defaultValue; - else - SetSetting(key, (value = defaultValue).ToString()); - - return value; - } - - public static TimeSpan GetOrUpdateSetting(string key, TimeSpan defaultValue) - { - TimeSpan value; - - if (m_Settings.settings.TryGetValue(key, out string strValue)) - value = TimeSpan.TryParse(strValue, out value) ? value : defaultValue; - else - SetSetting(key, (value = defaultValue).ToString()); - - return value; - } - - public static T GetOrUpdateSetting(string key, T defaultValue) where T : struct, Enum - { - T value; - - if (m_Settings.settings.TryGetValue(key, out string strValue)) - value = Enum.TryParse(strValue, out value) ? value : defaultValue; - else - SetSetting(key, (value = defaultValue).ToString()); - - return value; - } - - public static void SetSetting(string key, string value) - { - m_Settings.settings[key] = value; - Save(); - } - - public static T GetMetadata(string key) where T : class - { - m_Settings.metadata.TryGetValue(key, out object value); - return value as T; - } - - public static void SetMetadata(string key, object value) - { - m_Settings.metadata[key] = value; - } - - // If mock is enabled we skip the console readline. - public static void Load(bool mocked = false) - { - m_Mocked = mocked; - bool updated = false; - - if (File.Exists(m_FilePath)) - { - Console.Write($"Core: Reading configuration from {m_RelPath}..."); - m_Settings = JsonConfig.Deserialize(m_FilePath); - - if (m_Settings == null) - { - Utility.PushColor(ConsoleColor.Red); - Console.WriteLine("failed"); - Utility.PopColor(); - throw new Exception("Core: Server configuration failed to deserialize."); - } - - Console.WriteLine("done"); - } - else - { - updated = true; - m_Settings = new ServerSettings(); - } - - if (mocked) - return; - - if (m_Settings.dataDirectories.Count == 0) - { - updated = true; - Utility.PushColor(ConsoleColor.DarkYellow); - Console.WriteLine("Core: Server configuration is missing data directories."); - Utility.PopColor(); - m_Settings.dataDirectories.AddRange(GetDataDirectories()); - } - - if (m_Settings.listeners.Count == 0) - { - updated = true; - Utility.PushColor(ConsoleColor.DarkYellow); - Console.WriteLine("Core: Server is missing socket listener IP addresses."); - Utility.PopColor(); - m_Settings.listeners.AddRange(GetListeners()); - } - - if (updated) - { - Save(); - Utility.PushColor(ConsoleColor.Green); - Console.WriteLine($"Core: Configuration saved to {m_RelPath}."); - Utility.PopColor(); - } - } - - internal class ServerSettings - { - [JsonPropertyName("dataDirectories")] - public List dataDirectories { get; set; } = new List(); - - [JsonPropertyName("listeners")] - public List listeners { get; set; } = new List(); - - [JsonPropertyName("settings")] - public Dictionary settings { get; set; } = new Dictionary(); - - [JsonExtensionData] - public Dictionary metadata { get; set; } = new Dictionary(); - } - - private static List GetDataDirectories() - { - Console.WriteLine("Please enter the absolute path to the Ultima Online data:"); - - List directories = new List(); - - do - { - Console.Write("{0}> ", directories.Count > 0 ? "[finish] " : " "); - var directory = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(directory)) break; - - if (Directory.Exists(directory)) - { - directories.Add(directory); - Console.WriteLine("Core: Path {0} added.", directory); - } - else - Console.WriteLine("Core: Path does not exist. ({0})"); - - } while (true); - - return directories; - } - - private static List GetListeners() - { - Console.WriteLine("Please enter the IP and ports to listen:"); - Console.WriteLine(" - Only enter IP addresses directly bound to this machine"); - Console.WriteLine(" - To listen to all IP addresses enter 0.0.0.0"); - - List ips = new List(); - - do - { // IP:Port? - Console.Write("[{0}]> ", ips.Count > 0 ? "finish" : "0.0.0.0:2593"); - var ipStr = Console.ReadLine(); - if (string.IsNullOrWhiteSpace(ipStr)) break; - - if (ipStr.IndexOf(":", StringComparison.Ordinal) == -1) - ipStr += ":2593"; - - if (IPEndPoint.TryParse(ipStr, out var ip)) - { - ips.Add(ip); - Console.WriteLine("Core: {0} added.", ipStr); - } - else - { - Console.WriteLine("Core: {0} is not a valid IP or port"); - } - } while (true); - - if (ips.Count == 0) - ips.Add(new IPEndPoint(IPAddress.Any, 2593)); - - return ips; - } - - public static void Save() - { - if (m_Mocked) return; - - JsonConfig.Serialize(m_FilePath, m_Settings); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ServerConfiguration.cs * + * Created: 2019/10/04 - Updated: 2020/07/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text.Json.Serialization; +using Server.Json; + +namespace Server +{ + public static class ServerConfiguration + { + private const string m_RelPath = "Configuration/modernuo.json"; + private static readonly string m_FilePath = Path.Join(Core.BaseDirectory, m_RelPath); + private static ServerSettings m_Settings; + private static bool m_Mocked; + + public static List DataDirectories => m_Settings.dataDirectories; + + public static List Listeners => m_Settings.listeners; + + public static string GetSetting(string key, string defaultValue) + { + m_Settings.settings.TryGetValue(key, out var value); + return value == "(-null-)" ? null : value ?? defaultValue; + } + + public static int GetSetting(string key, int defaultValue) + { + m_Settings.settings.TryGetValue(key, out var strValue); + return int.TryParse(strValue, out var value) ? value : defaultValue; + } + + public static bool GetSetting(string key, bool defaultValue) + { + m_Settings.settings.TryGetValue(key, out var strValue); + return bool.TryParse(strValue, out var value) ? value : defaultValue; + } + + public static T GetSetting(string key, T defaultValue) where T : struct, Enum + { + m_Settings.settings.TryGetValue(key, out var strValue); + return Enum.TryParse(strValue, out T value) ? value : defaultValue; + } + + public static string GetOrUpdateSetting(string key, string defaultValue) + { + if (m_Settings.settings.TryGetValue(key, out var value)) + return value; + + SetSetting(key, value = defaultValue); + return value; + } + + public static int GetOrUpdateSetting(string key, int defaultValue) + { + int value; + + if (m_Settings.settings.TryGetValue(key, out var strValue)) + value = int.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static bool GetOrUpdateSetting(string key, bool defaultValue) + { + bool value; + + if (m_Settings.settings.TryGetValue(key, out var strValue)) + value = bool.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static TimeSpan GetOrUpdateSetting(string key, TimeSpan defaultValue) + { + TimeSpan value; + + if (m_Settings.settings.TryGetValue(key, out var strValue)) + value = TimeSpan.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static T GetOrUpdateSetting(string key, T defaultValue) where T : struct, Enum + { + T value; + + if (m_Settings.settings.TryGetValue(key, out var strValue)) + value = Enum.TryParse(strValue, out value) ? value : defaultValue; + else + SetSetting(key, (value = defaultValue).ToString()); + + return value; + } + + public static void SetSetting(string key, string value) + { + m_Settings.settings[key] = value; + Save(); + } + + public static T GetMetadata(string key) where T : class + { + m_Settings.metadata.TryGetValue(key, out var value); + return value as T; + } + + public static void SetMetadata(string key, object value) + { + m_Settings.metadata[key] = value; + } + + // If mock is enabled we skip the console readline. + public static void Load(bool mocked = false) + { + m_Mocked = mocked; + var updated = false; + + if (File.Exists(m_FilePath)) + { + Console.Write($"Core: Reading configuration from {m_RelPath}..."); + m_Settings = JsonConfig.Deserialize(m_FilePath); + + if (m_Settings == null) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine("failed"); + Utility.PopColor(); + throw new Exception("Core: Server configuration failed to deserialize."); + } + + Console.WriteLine("done"); + } + else + { + updated = true; + m_Settings = new ServerSettings(); + } + + if (mocked) + return; + + if (m_Settings.dataDirectories.Count == 0) + { + updated = true; + Utility.PushColor(ConsoleColor.DarkYellow); + Console.WriteLine("Core: Server configuration is missing data directories."); + Utility.PopColor(); + m_Settings.dataDirectories.AddRange(GetDataDirectories()); + } + + if (m_Settings.listeners.Count == 0) + { + updated = true; + Utility.PushColor(ConsoleColor.DarkYellow); + Console.WriteLine("Core: Server is missing socket listener IP addresses."); + Utility.PopColor(); + m_Settings.listeners.AddRange(GetListeners()); + } + + if (updated) + { + Save(); + Utility.PushColor(ConsoleColor.Green); + Console.WriteLine($"Core: Configuration saved to {m_RelPath}."); + Utility.PopColor(); + } + } + + private static List GetDataDirectories() + { + Console.WriteLine("Please enter the absolute path to the Ultima Online data:"); + + var directories = new List(); + + do + { + Console.Write("{0}> ", directories.Count > 0 ? "[finish] " : " "); + var directory = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(directory)) break; + + if (Directory.Exists(directory)) + { + directories.Add(directory); + Console.WriteLine("Core: Path {0} added.", directory); + } + else + { + Console.WriteLine("Core: Path does not exist. ({0})"); + } + } while (true); + + return directories; + } + + private static List GetListeners() + { + Console.WriteLine("Please enter the IP and ports to listen:"); + Console.WriteLine(" - Only enter IP addresses directly bound to this machine"); + Console.WriteLine(" - To listen to all IP addresses enter 0.0.0.0"); + + var ips = new List(); + + do + { + // IP:Port? + Console.Write("[{0}]> ", ips.Count > 0 ? "finish" : "0.0.0.0:2593"); + var ipStr = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(ipStr)) break; + + if (ipStr.IndexOf(":", StringComparison.Ordinal) == -1) + ipStr += ":2593"; + + if (IPEndPoint.TryParse(ipStr, out var ip)) + { + ips.Add(ip); + Console.WriteLine("Core: {0} added.", ipStr); + } + else + { + Console.WriteLine("Core: {0} is not a valid IP or port"); + } + } while (true); + + if (ips.Count == 0) + ips.Add(new IPEndPoint(IPAddress.Any, 2593)); + + return ips; + } + + public static void Save() + { + if (m_Mocked) return; + + JsonConfig.Serialize(m_FilePath, m_Settings); + } + + internal class ServerSettings + { + [JsonPropertyName("dataDirectories")] public List dataDirectories { get; set; } = new List(); + + [JsonPropertyName("listeners")] public List listeners { get; set; } = new List(); + + [JsonPropertyName("settings")] + public Dictionary settings { get; set; } = new Dictionary(); + + [JsonExtensionData] public Dictionary metadata { get; set; } = new Dictionary(); + } + } +} diff --git a/Projects/Server/ContextMenus/ContextMenu.cs b/Projects/Server/ContextMenus/ContextMenu.cs index 9e8184b65..b4ab93f7e 100644 --- a/Projects/Server/ContextMenus/ContextMenu.cs +++ b/Projects/Server/ContextMenus/ContextMenu.cs @@ -1,83 +1,84 @@ -/*************************************************************************** - * ContextMenu.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.Collections.Generic; -using System.Linq; - -namespace Server.ContextMenus -{ - /// - /// Represents the state of an active context menu. This includes who opened the menu, the menu's focus object, and a list of - /// entries that the menu is composed of. - /// - /// - public class ContextMenu - { - /// - /// Instantiates a new ContextMenu instance. - /// - /// - /// The who opened this ContextMenu. - /// - /// - /// - /// The or for which this ContextMenu is on. - /// - /// - public ContextMenu(Mobile from, IEntity target) - { - From = from; - Target = target; - - var list = new List(); - - if (target is Mobile mobile) - mobile.GetContextMenuEntries(from, list); - else if (target is Item item) - item.GetContextMenuEntries(from, list); - - Entries = list.ToArray(); - - for (var i = 0; i < Entries.Length; ++i) - Entries[i].Owner = this; - } - - /// - /// Gets the who opened this ContextMenu. - /// - public Mobile From { get; } - - /// - /// Gets an object of the or for which this ContextMenu is on. - /// - public IEntity Target { get; } - - /// - /// Gets the list of entries contained in this ContextMenu. - /// - public ContextMenuEntry[] Entries { get; } - - /// - /// Returns true if this ContextMenu requires packet version 2. - /// - public bool RequiresNewPacket => - Entries.Any(t => t.Number < 3000000 || t.Number > 3032767); - } -} +/*************************************************************************** + * ContextMenu.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.Collections.Generic; +using System.Linq; + +namespace Server.ContextMenus +{ + /// + /// Represents the state of an active context menu. This includes who opened the menu, the menu's focus object, and a list + /// of + /// entries that the menu is composed of. + /// + /// + public class ContextMenu + { + /// + /// Instantiates a new ContextMenu instance. + /// + /// + /// The who opened this ContextMenu. + /// + /// + /// + /// The or for which this ContextMenu is on. + /// + /// + public ContextMenu(Mobile from, IEntity target) + { + From = from; + Target = target; + + var list = new List(); + + if (target is Mobile mobile) + mobile.GetContextMenuEntries(from, list); + else if (target is Item item) + item.GetContextMenuEntries(from, list); + + Entries = list.ToArray(); + + for (var i = 0; i < Entries.Length; ++i) + Entries[i].Owner = this; + } + + /// + /// Gets the who opened this ContextMenu. + /// + public Mobile From { get; } + + /// + /// Gets an object of the or for which this ContextMenu is on. + /// + public IEntity Target { get; } + + /// + /// Gets the list of entries contained in this ContextMenu. + /// + public ContextMenuEntry[] Entries { get; } + + /// + /// Returns true if this ContextMenu requires packet version 2. + /// + public bool RequiresNewPacket => + Entries.Any(t => t.Number < 3000000 || t.Number > 3032767); + } +} diff --git a/Projects/Server/ContextMenus/ContextMenuEntry.cs b/Projects/Server/ContextMenus/ContextMenuEntry.cs index 76c73f4d7..da9bfdd43 100644 --- a/Projects/Server/ContextMenus/ContextMenuEntry.cs +++ b/Projects/Server/ContextMenus/ContextMenuEntry.cs @@ -1,98 +1,99 @@ -/*************************************************************************** - * ContextMenuEntry.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.ContextMenus -{ - /// - /// Represents a single entry of a context menu. - /// - /// - public class ContextMenuEntry - { - /// - /// Instantiates a new ContextMenuEntry with a given localization number () - /// and maximum range (). - /// - /// - /// The localization number containing the name of this entry. - /// - /// - /// - /// The maximum range at which this entry can be used. - /// - /// - public ContextMenuEntry(int number, int range = -1) - { - if (number <= 0x7FFF) // Legacy code support - Number = 3000000 + number; - else - Number = number; - - Range = range; - Enabled = true; - Color = 0xFFFF; - } - - /// - /// Gets or sets additional flags used in client communication. - /// - public CMEFlags Flags { get; set; } - - /// - /// Gets or sets the that owns this entry. - /// - public ContextMenu Owner { get; set; } - - /// - /// Gets or sets the localization number containing the name of this entry. - /// - public int Number { get; set; } - - /// - /// Gets or sets the maximum range at which this entry may be used, in tiles. A value of -1 signifies no maximum range. - /// - public int Range { get; set; } - - /// - /// Gets or sets the color for this entry. Format is A1-R5-G5-B5. - /// - public int Color { get; set; } - - /// - /// Gets or sets whether this entry is enabled. When false, the entry will appear in a gray hue and - /// will never be invoked. - /// - public bool Enabled { get; set; } - - /// - /// Gets a value indicating if non local use of this entry is permitted. - /// - public virtual bool NonLocalUse => false; - - /// - /// Overridable. Virtual event invoked when the entry is clicked. - /// - public virtual void OnClick() - { - } - } -} +/*************************************************************************** + * ContextMenuEntry.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.ContextMenus +{ + /// + /// Represents a single entry of a context menu. + /// + /// + public class ContextMenuEntry + { + /// + /// Instantiates a new ContextMenuEntry with a given localization number ( + /// ) + /// and maximum range (). + /// + /// + /// The localization number containing the name of this entry. + /// + /// + /// + /// The maximum range at which this entry can be used. + /// + /// + public ContextMenuEntry(int number, int range = -1) + { + if (number <= 0x7FFF) // Legacy code support + Number = 3000000 + number; + else + Number = number; + + Range = range; + Enabled = true; + Color = 0xFFFF; + } + + /// + /// Gets or sets additional flags used in client communication. + /// + public CMEFlags Flags { get; set; } + + /// + /// Gets or sets the that owns this entry. + /// + public ContextMenu Owner { get; set; } + + /// + /// Gets or sets the localization number containing the name of this entry. + /// + public int Number { get; set; } + + /// + /// Gets or sets the maximum range at which this entry may be used, in tiles. A value of -1 signifies no maximum range. + /// + public int Range { get; set; } + + /// + /// Gets or sets the color for this entry. Format is A1-R5-G5-B5. + /// + public int Color { get; set; } + + /// + /// Gets or sets whether this entry is enabled. When false, the entry will appear in a gray hue and + /// will never be invoked. + /// + public bool Enabled { get; set; } + + /// + /// Gets a value indicating if non local use of this entry is permitted. + /// + public virtual bool NonLocalUse => false; + + /// + /// Overridable. Virtual event invoked when the entry is clicked. + /// + public virtual void OnClick() + { + } + } +} diff --git a/Projects/Server/ContextMenus/OpenBackpackEntry.cs b/Projects/Server/ContextMenus/OpenBackpackEntry.cs index f72677908..58ca41d71 100644 --- a/Projects/Server/ContextMenus/OpenBackpackEntry.cs +++ b/Projects/Server/ContextMenus/OpenBackpackEntry.cs @@ -1,34 +1,34 @@ -/*************************************************************************** - * OpenBackpackEntry.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.ContextMenus -{ - public class OpenBackpackEntry : ContextMenuEntry - { - private readonly Mobile m_Mobile; - - public OpenBackpackEntry(Mobile m) : base(6145) => m_Mobile = m; - - public override void OnClick() - { - m_Mobile.Use(m_Mobile.Backpack); - } - } -} +/*************************************************************************** + * OpenBackpackEntry.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.ContextMenus +{ + public class OpenBackpackEntry : ContextMenuEntry + { + private readonly Mobile m_Mobile; + + public OpenBackpackEntry(Mobile m) : base(6145) => m_Mobile = m; + + public override void OnClick() + { + m_Mobile.Use(m_Mobile.Backpack); + } + } +} diff --git a/Projects/Server/ContextMenus/PaperdollEntry.cs b/Projects/Server/ContextMenus/PaperdollEntry.cs index de048b565..e1bffde8a 100644 --- a/Projects/Server/ContextMenus/PaperdollEntry.cs +++ b/Projects/Server/ContextMenus/PaperdollEntry.cs @@ -1,35 +1,35 @@ -/*************************************************************************** - * PaperdollEntry.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.ContextMenus -{ - public class PaperdollEntry : ContextMenuEntry - { - private readonly Mobile m_Mobile; - - public PaperdollEntry(Mobile m) : base(6123, 18) => m_Mobile = m; - - public override void OnClick() - { - if (m_Mobile.CanPaperdollBeOpenedBy(Owner.From)) - m_Mobile.DisplayPaperdollTo(Owner.From); - } - } -} +/*************************************************************************** + * PaperdollEntry.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.ContextMenus +{ + public class PaperdollEntry : ContextMenuEntry + { + private readonly Mobile m_Mobile; + + public PaperdollEntry(Mobile m) : base(6123, 18) => m_Mobile = m; + + public override void OnClick() + { + if (m_Mobile.CanPaperdollBeOpenedBy(Owner.From)) + m_Mobile.DisplayPaperdollTo(Owner.From); + } + } +} diff --git a/Projects/Server/Diagnostics/BaseProfile.cs b/Projects/Server/Diagnostics/BaseProfile.cs index 40d5e6b51..28b680a5e 100644 --- a/Projects/Server/Diagnostics/BaseProfile.cs +++ b/Projects/Server/Diagnostics/BaseProfile.cs @@ -1,88 +1,94 @@ -/*************************************************************************** - * PacketProfile.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; - -namespace Server.Diagnostics -{ - public abstract class BaseProfile - { - private readonly Stopwatch _stopwatch; - - protected BaseProfile(string name) - { - Name = name; - - _stopwatch = new Stopwatch(); - } - - public string Name { get; } - - public long Count { get; private set; } - - public TimeSpan AverageTime => TimeSpan.FromTicks(TotalTime.Ticks / Math.Max(Count, 1)); - - public TimeSpan PeakTime { get; private set; } - - public TimeSpan TotalTime { get; private set; } - - public static void WriteAll(TextWriter op, IEnumerable profiles) where T : BaseProfile - { - var list = new List(profiles); - - list.Sort((a, b) => -a.TotalTime.CompareTo(b.TotalTime)); - - foreach (var prof in list) - { - prof.WriteTo(op); - op.WriteLine(); - } - } - - public virtual void Start() - { - if (_stopwatch.IsRunning) _stopwatch.Reset(); - - _stopwatch.Start(); - } - - public virtual void Finish() - { - var elapsed = _stopwatch.Elapsed; - - TotalTime += elapsed; - - if (elapsed > PeakTime) PeakTime = elapsed; - - Count++; - - _stopwatch.Reset(); - } - - public virtual void WriteTo(TextWriter op) - { - op.Write("{0,-100} {1,12:N0} {2,12:F5} {3,-12:F5} {4,12:F5}", Name, Count, AverageTime.TotalSeconds, - PeakTime.TotalSeconds, TotalTime.TotalSeconds); - } - } -} +/*************************************************************************** + * PacketProfile.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace Server.Diagnostics +{ + public abstract class BaseProfile + { + private readonly Stopwatch _stopwatch; + + protected BaseProfile(string name) + { + Name = name; + + _stopwatch = new Stopwatch(); + } + + public string Name { get; } + + public long Count { get; private set; } + + public TimeSpan AverageTime => TimeSpan.FromTicks(TotalTime.Ticks / Math.Max(Count, 1)); + + public TimeSpan PeakTime { get; private set; } + + public TimeSpan TotalTime { get; private set; } + + public static void WriteAll(TextWriter op, IEnumerable profiles) where T : BaseProfile + { + var list = new List(profiles); + + list.Sort((a, b) => -a.TotalTime.CompareTo(b.TotalTime)); + + foreach (var prof in list) + { + prof.WriteTo(op); + op.WriteLine(); + } + } + + public virtual void Start() + { + if (_stopwatch.IsRunning) _stopwatch.Reset(); + + _stopwatch.Start(); + } + + public virtual void Finish() + { + var elapsed = _stopwatch.Elapsed; + + TotalTime += elapsed; + + if (elapsed > PeakTime) PeakTime = elapsed; + + Count++; + + _stopwatch.Reset(); + } + + public virtual void WriteTo(TextWriter op) + { + op.Write( + "{0,-100} {1,12:N0} {2,12:F5} {3,-12:F5} {4,12:F5}", + Name, + Count, + AverageTime.TotalSeconds, + PeakTime.TotalSeconds, + TotalTime.TotalSeconds + ); + } + } +} diff --git a/Projects/Server/Diagnostics/GumpProfile.cs b/Projects/Server/Diagnostics/GumpProfile.cs index 3687cc307..deaedef86 100644 --- a/Projects/Server/Diagnostics/GumpProfile.cs +++ b/Projects/Server/Diagnostics/GumpProfile.cs @@ -1,47 +1,47 @@ -/*************************************************************************** - * PacketProfile.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server.Diagnostics -{ - public class GumpProfile : BaseProfile - { - private static readonly Dictionary _profiles = new Dictionary(); - - public GumpProfile(Type type) : base(type.FullName) - { - } - - public static IEnumerable Profiles => _profiles.Values; - - public static GumpProfile Acquire(Type type) - { - if (!Core.Profiling) - return null; - - if (!_profiles.TryGetValue(type, out var prof)) - _profiles.Add(type, prof = new GumpProfile(type)); - - return prof; - } - } -} +/*************************************************************************** + * PacketProfile.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server.Diagnostics +{ + public class GumpProfile : BaseProfile + { + private static readonly Dictionary _profiles = new Dictionary(); + + public GumpProfile(Type type) : base(type.FullName) + { + } + + public static IEnumerable Profiles => _profiles.Values; + + public static GumpProfile Acquire(Type type) + { + if (!Core.Profiling) + return null; + + if (!_profiles.TryGetValue(type, out var prof)) + _profiles.Add(type, prof = new GumpProfile(type)); + + return prof; + } + } +} diff --git a/Projects/Server/Diagnostics/PacketProfile.cs b/Projects/Server/Diagnostics/PacketProfile.cs index 1678a6986..0b122ad83 100644 --- a/Projects/Server/Diagnostics/PacketProfile.cs +++ b/Projects/Server/Diagnostics/PacketProfile.cs @@ -1,108 +1,109 @@ -/*************************************************************************** - * PacketProfile.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; -using System.Threading; - -namespace Server.Diagnostics -{ - public abstract class BasePacketProfile : BaseProfile - { - protected BasePacketProfile(string name) : base(name) - { - } - - public long TotalLength { get; private set; } - - public double AverageLength => (double)TotalLength / Math.Max(Count, 1); - - public void Finish(long length) - { - Finish(); - - TotalLength += length; - } - - public override void WriteTo(TextWriter op) - { - base.WriteTo(op); - - op.Write("\t{0,12:F2} {1,-12:N0}", AverageLength, TotalLength); - } - } - - public class PacketSendProfile : BasePacketProfile - { - private static readonly Dictionary _profiles = new Dictionary(); - - private long _created; - - public PacketSendProfile(Type type) : base(type.FullName) - { - } - - public static IEnumerable Profiles => _profiles.Values; - - [MethodImpl(MethodImplOptions.Synchronized)] - public static PacketSendProfile Acquire(Type type) - { - if (!_profiles.TryGetValue(type, out var prof)) - _profiles.Add(type, prof = new PacketSendProfile(type)); - - return prof; - } - - public void Increment() - { - Interlocked.Increment(ref _created); - } - - public override void WriteTo(TextWriter op) - { - base.WriteTo(op); - - op.Write("\t{0,12:N0}", _created); - } - } - - public class PacketReceiveProfile : BasePacketProfile - { - private static readonly Dictionary _profiles = new Dictionary(); - - public PacketReceiveProfile(int packetId) - : base($"0x{packetId:X2}") - { - } - - public static IEnumerable Profiles => _profiles.Values; - - [MethodImpl(MethodImplOptions.Synchronized)] - public static PacketReceiveProfile Acquire(int packetId) - { - if (!_profiles.TryGetValue(packetId, out var prof)) - _profiles.Add(packetId, prof = new PacketReceiveProfile(packetId)); - - return prof; - } - } -} +/*************************************************************************** + * PacketProfile.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Server.Diagnostics +{ + public abstract class BasePacketProfile : BaseProfile + { + protected BasePacketProfile(string name) : base(name) + { + } + + public long TotalLength { get; private set; } + + public double AverageLength => (double)TotalLength / Math.Max(Count, 1); + + public void Finish(long length) + { + Finish(); + + TotalLength += length; + } + + public override void WriteTo(TextWriter op) + { + base.WriteTo(op); + + op.Write("\t{0,12:F2} {1,-12:N0}", AverageLength, TotalLength); + } + } + + public class PacketSendProfile : BasePacketProfile + { + private static readonly Dictionary _profiles = new Dictionary(); + + private long _created; + + public PacketSendProfile(Type type) : base(type.FullName) + { + } + + public static IEnumerable Profiles => _profiles.Values; + + [MethodImpl(MethodImplOptions.Synchronized)] + public static PacketSendProfile Acquire(Type type) + { + if (!_profiles.TryGetValue(type, out var prof)) + _profiles.Add(type, prof = new PacketSendProfile(type)); + + return prof; + } + + public void Increment() + { + Interlocked.Increment(ref _created); + } + + public override void WriteTo(TextWriter op) + { + base.WriteTo(op); + + op.Write("\t{0,12:N0}", _created); + } + } + + public class PacketReceiveProfile : BasePacketProfile + { + private static readonly Dictionary + _profiles = new Dictionary(); + + public PacketReceiveProfile(int packetId) + : base($"0x{packetId:X2}") + { + } + + public static IEnumerable Profiles => _profiles.Values; + + [MethodImpl(MethodImplOptions.Synchronized)] + public static PacketReceiveProfile Acquire(int packetId) + { + if (!_profiles.TryGetValue(packetId, out var prof)) + _profiles.Add(packetId, prof = new PacketReceiveProfile(packetId)); + + return prof; + } + } +} diff --git a/Projects/Server/Diagnostics/TargetProfile.cs b/Projects/Server/Diagnostics/TargetProfile.cs index 67bca020e..e7f1e93b5 100644 --- a/Projects/Server/Diagnostics/TargetProfile.cs +++ b/Projects/Server/Diagnostics/TargetProfile.cs @@ -1,48 +1,48 @@ -/*************************************************************************** - * PacketProfile.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server.Diagnostics -{ - public class TargetProfile : BaseProfile - { - private static readonly Dictionary _profiles = new Dictionary(); - - public TargetProfile(Type type) - : base(type.FullName) - { - } - - public static IEnumerable Profiles => _profiles.Values; - - public static TargetProfile Acquire(Type type) - { - if (!Core.Profiling) - return null; - - if (!_profiles.TryGetValue(type, out var prof)) - _profiles.Add(type, prof = new TargetProfile(type)); - - return prof; - } - } -} +/*************************************************************************** + * PacketProfile.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server.Diagnostics +{ + public class TargetProfile : BaseProfile + { + private static readonly Dictionary _profiles = new Dictionary(); + + public TargetProfile(Type type) + : base(type.FullName) + { + } + + public static IEnumerable Profiles => _profiles.Values; + + public static TargetProfile Acquire(Type type) + { + if (!Core.Profiling) + return null; + + if (!_profiles.TryGetValue(type, out var prof)) + _profiles.Add(type, prof = new TargetProfile(type)); + + return prof; + } + } +} diff --git a/Projects/Server/Diagnostics/TimerProfile.cs b/Projects/Server/Diagnostics/TimerProfile.cs index 9f1780e02..f2e4d05be 100644 --- a/Projects/Server/Diagnostics/TimerProfile.cs +++ b/Projects/Server/Diagnostics/TimerProfile.cs @@ -1,61 +1,61 @@ -/*************************************************************************** - * PacketProfile.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.Collections.Generic; -using System.IO; - -namespace Server.Diagnostics -{ - public class TimerProfile : BaseProfile - { - private static readonly Dictionary _profiles = new Dictionary(); - - public TimerProfile(string name) - : base(name) - { - } - - public static IEnumerable Profiles => _profiles.Values; - - public long Created { get; set; } - - public long Started { get; set; } - - public long Stopped { get; set; } - - public static TimerProfile Acquire(string name) - { - if (!Core.Profiling) - return null; - - if (!_profiles.TryGetValue(name, out var prof)) - _profiles.Add(name, prof = new TimerProfile(name)); - - return prof; - } - - public override void WriteTo(TextWriter op) - { - base.WriteTo(op); - - op.Write("\t{0,12:N0} {1,12:N0} {2,-12:N0}", Created, Started, Stopped); - } - } -} +/*************************************************************************** + * PacketProfile.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.Collections.Generic; +using System.IO; + +namespace Server.Diagnostics +{ + public class TimerProfile : BaseProfile + { + private static readonly Dictionary _profiles = new Dictionary(); + + public TimerProfile(string name) + : base(name) + { + } + + public static IEnumerable Profiles => _profiles.Values; + + public long Created { get; set; } + + public long Started { get; set; } + + public long Stopped { get; set; } + + public static TimerProfile Acquire(string name) + { + if (!Core.Profiling) + return null; + + if (!_profiles.TryGetValue(name, out var prof)) + _profiles.Add(name, prof = new TimerProfile(name)); + + return prof; + } + + public override void WriteTo(TextWriter op) + { + base.WriteTo(op); + + op.Write("\t{0,12:N0} {1,12:N0} {2,-12:N0}", Created, Started, Stopped); + } + } +} diff --git a/Projects/Server/Effects.cs b/Projects/Server/Effects.cs index 929f774e7..dcc9046af 100644 --- a/Projects/Server/Effects.cs +++ b/Projects/Server/Effects.cs @@ -1,398 +1,501 @@ -/*************************************************************************** - * Effects.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server -{ - public enum EffectLayer - { - Head = 0, - RightHand = 1, - LeftHand = 2, - Waist = 3, - LeftFoot = 4, - RightFoot = 5, - CenterFeet = 7 - } - - public enum ParticleSupportType - { - Full, - Detect, - None - } - - public static class Effects - { - public static ParticleSupportType ParticleSupportType { get; set; } = ParticleSupportType.Detect; - - public static bool SendParticlesTo(NetState state) => - ParticleSupportType == ParticleSupportType.Full || - (ParticleSupportType == ParticleSupportType.Detect && state.IsUOTDClient); - - public static void PlaySound(IPoint3D p, Map map, int soundID) - { - if (soundID <= -1) - return; - - if (map != null) - { - Packet playSound = null; - - var eable = map.GetClientsInRange(new Point3D(p)); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - - playSound ??= Packet.Acquire(new PlaySound(soundID, p)); - - state.Send(playSound); - } - - Packet.Release(playSound); - - eable.Free(); - } - } - - public static void SendBoltEffect(IEntity e) - { - SendBoltEffect(e, true, 0); - } - - public static void SendBoltEffect(IEntity e, bool sound) - { - SendBoltEffect(e, sound, 0); - } - - public static void SendBoltEffect(IEntity e, bool sound, int hue) - { - var map = e.Map; - - if (map == null) - return; - - e.ProcessDelta(); - - Packet preEffect = null, boltEffect = null, playSound = null; - - var eable = map.GetClientsInRange(e.Location); - - foreach (var state in eable) - if (state.Mobile.CanSee(e)) - { - if (SendParticlesTo(state)) - { - preEffect ??= Packet.Acquire(new TargetParticleEffect(e, 0, 10, 5, 0, 0, 5031, 3, 0)); - - state.Send(preEffect); - } - - boltEffect ??= Packet.Acquire(new BoltEffect(e, hue)); - - state.Send(boltEffect); - - if (sound) - { - playSound ??= Packet.Acquire(new PlaySound(0x29, e)); - - state.Send(playSound); - } - } - - Packet.Release(preEffect); - Packet.Release(boltEffect); - Packet.Release(playSound); - - eable.Free(); - } - - public static void SendLocationEffect(IPoint3D p, Map map, int itemID, int duration) - { - SendLocationEffect(p, map, itemID, duration, 10, 0, 0); - } - - public static void SendLocationEffect(IPoint3D p, Map map, int itemID, int duration, int speed) - { - SendLocationEffect(p, map, itemID, duration, speed, 0, 0); - } - - public static void SendLocationEffect(IPoint3D p, Map map, int itemID, int duration, int hue, int renderMode) - { - SendLocationEffect(p, map, itemID, duration, 10, hue, renderMode); - } - - public static void SendLocationEffect(IPoint3D p, Map map, int itemID, int duration, int speed, int hue, - int renderMode) - { - SendPacket(p, map, new LocationEffect(p, itemID, speed, duration, hue, renderMode)); - } - - public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect) - { - SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, 0); - } - - public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect, int unknown) - { - SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, unknown); - } - - public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int hue, int renderMode, - int effect, int unknown) - { - var map = e.Map; - - if (map != null) - { - Packet particles = null, regular = null; - - var eable = map.GetClientsInRange(e.Location); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - - if (SendParticlesTo(state)) - { - particles ??= - Packet.Acquire(new LocationParticleEffect(e, itemID, speed, duration, hue, renderMode, effect, unknown)); - - state.Send(particles); - } - else if (itemID != 0) - { - regular ??= Packet.Acquire(new LocationEffect(e, itemID, speed, duration, hue, renderMode)); - - state.Send(regular); - } - } - - Packet.Release(particles); - Packet.Release(regular); - - eable.Free(); - } - - // SendPacket( e.Location, e.Map, new LocationParticleEffect( e, itemID, speed, duration, hue, renderMode, effect, unknown ) ); - } - - public static void SendTargetEffect(IEntity target, int itemID, int duration) - { - SendTargetEffect(target, itemID, duration, 0, 0); - } - - public static void SendTargetEffect(IEntity target, int itemID, int speed, int duration) - { - SendTargetEffect(target, itemID, speed, duration, 0, 0); - } - - public static void SendTargetEffect(IEntity target, int itemID, int duration, int hue, int renderMode) - { - SendTargetEffect(target, itemID, 10, duration, hue, renderMode); - } - - public static void SendTargetEffect(IEntity target, int itemID, int speed, int duration, int hue, int renderMode) - { - if (target is Mobile mobile) - mobile.ProcessDelta(); - - SendPacket(target.Location, target.Map, new TargetEffect(target, itemID, speed, duration, hue, renderMode)); - } - - public static void SendTargetParticles(IEntity target, int itemID, int speed, int duration, int effect, - EffectLayer layer) - { - SendTargetParticles(target, itemID, speed, duration, 0, 0, effect, layer, 0); - } - - public static void SendTargetParticles(IEntity target, int itemID, int speed, int duration, int effect, - EffectLayer layer, int unknown) - { - SendTargetParticles(target, itemID, speed, duration, 0, 0, effect, layer, unknown); - } - - public static void SendTargetParticles(IEntity target, int itemID, int speed, int duration, int hue, int renderMode, - int effect, EffectLayer layer, int unknown) - { - if (target is Mobile mobile) - mobile.ProcessDelta(); - - var map = target.Map; - - if (map != null) - { - Packet particles = null, regular = null; - - var eable = map.GetClientsInRange(target.Location); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - - if (SendParticlesTo(state)) - { - particles ??= Packet.Acquire(new TargetParticleEffect(target, itemID, speed, duration, hue, renderMode, effect, - (int)layer, unknown)); - - state.Send(particles); - } - else if (itemID != 0) - { - regular ??= Packet.Acquire(new TargetEffect(target, itemID, speed, duration, hue, renderMode)); - - state.Send(regular); - } - } - - Packet.Release(particles); - Packet.Release(regular); - - eable.Free(); - } - - // SendPacket( target.Location, target.Map, new TargetParticleEffect( target, itemID, speed, duration, hue, renderMode, effect, (int)layer, unknown ) ); - } - - public static void SendMovingEffect(IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int hue = 0, int renderMode = 0) - { - if (from is Mobile mobile) - mobile.ProcessDelta(); - - if (to is Mobile mobile1) - mobile1.ProcessDelta(); - - SendPacket(from.Location, from.Map, - new MovingEffect(from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode)); - } - - public static void SendMovingParticles(IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound) - { - SendMovingParticles(from, to, itemID, speed, duration, fixedDirection, explodes, 0, 0, effect, explodeEffect, - explodeSound, 0); - } - - public static void SendMovingParticles(IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound, int unknown) - { - SendMovingParticles(from, to, itemID, speed, duration, fixedDirection, explodes, 0, 0, effect, explodeEffect, - explodeSound, unknown); - } - - public static void SendMovingParticles(IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, - int unknown) - { - SendMovingParticles(from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, - explodeEffect, explodeSound, (EffectLayer)255, unknown); - } - - public static void SendMovingParticles(IEntity from, IEntity to, int itemID, int speed, int duration, - bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, - EffectLayer layer, int unknown) - { - if (from is Mobile fromMob) - fromMob.ProcessDelta(); - - if (to is Mobile toMob) - toMob.ProcessDelta(); - - var map = from.Map; - - if (map != null) - { - Packet particles = null, regular = null; - - var eable = map.GetClientsInRange(from.Location); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - - if (SendParticlesTo(state)) - { - particles ??= Packet.Acquire(new MovingParticleEffect(from, to, itemID, speed, duration, - fixedDirection, explodes, hue, renderMode, effect, explodeEffect, explodeSound, layer, unknown)); - - state.Send(particles); - } - else if (itemID > 1) - { - regular ??= Packet.Acquire(new MovingEffect(from, to, itemID, speed, duration, fixedDirection, explodes, hue, - renderMode)); - - state.Send(regular); - } - } - - Packet.Release(particles); - Packet.Release(regular); - - eable.Free(); - } - - // SendPacket( from.Location, from.Map, new MovingParticleEffect( from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, explodeEffect, explodeSound, unknown ) ); - } - - public static void SendPacket(Point3D origin, Map map, Packet p) - { - if (map == null) - return; - - var eable = map.GetClientsInRange(origin); - - p.Acquire(); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - state.Send(p); - } - - p.Release(); - - eable.Free(); - } - - public static void SendPacket(IPoint3D origin, Map map, Packet p) - { - if (map == null) - return; - - var eable = map.GetClientsInRange(new Point3D(origin)); - - p.Acquire(); - - foreach (var state in eable) - { - state.Mobile.ProcessDelta(); - state.Send(p); - } - - p.Release(); - - eable.Free(); - } - } -} +/*************************************************************************** + * Effects.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server +{ + public enum EffectLayer + { + Head = 0, + RightHand = 1, + LeftHand = 2, + Waist = 3, + LeftFoot = 4, + RightFoot = 5, + CenterFeet = 7 + } + + public enum ParticleSupportType + { + Full, + Detect, + None + } + + public static class Effects + { + public static ParticleSupportType ParticleSupportType { get; set; } = ParticleSupportType.Detect; + + public static bool SendParticlesTo(NetState state) => + ParticleSupportType == ParticleSupportType.Full || + ParticleSupportType == ParticleSupportType.Detect && state.IsUOTDClient; + + public static void PlaySound(IPoint3D p, Map map, int soundID) + { + if (soundID <= -1) + return; + + if (map != null) + { + Packet playSound = null; + + var eable = map.GetClientsInRange(new Point3D(p)); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + + playSound ??= Packet.Acquire(new PlaySound(soundID, p)); + + state.Send(playSound); + } + + Packet.Release(playSound); + + eable.Free(); + } + } + + public static void SendBoltEffect(IEntity e) + { + SendBoltEffect(e, true, 0); + } + + public static void SendBoltEffect(IEntity e, bool sound) + { + SendBoltEffect(e, sound, 0); + } + + public static void SendBoltEffect(IEntity e, bool sound, int hue) + { + var map = e.Map; + + if (map == null) + return; + + e.ProcessDelta(); + + Packet preEffect = null, boltEffect = null, playSound = null; + + var eable = map.GetClientsInRange(e.Location); + + foreach (var state in eable) + if (state.Mobile.CanSee(e)) + { + if (SendParticlesTo(state)) + { + preEffect ??= Packet.Acquire(new TargetParticleEffect(e, 0, 10, 5, 0, 0, 5031, 3, 0)); + + state.Send(preEffect); + } + + boltEffect ??= Packet.Acquire(new BoltEffect(e, hue)); + + state.Send(boltEffect); + + if (sound) + { + playSound ??= Packet.Acquire(new PlaySound(0x29, e)); + + state.Send(playSound); + } + } + + Packet.Release(preEffect); + Packet.Release(boltEffect); + Packet.Release(playSound); + + eable.Free(); + } + + public static void SendLocationEffect(IPoint3D p, Map map, int itemID, int duration) + { + SendLocationEffect(p, map, itemID, duration, 10, 0, 0); + } + + public static void SendLocationEffect(IPoint3D p, Map map, int itemID, int duration, int speed) + { + SendLocationEffect(p, map, itemID, duration, speed, 0, 0); + } + + public static void SendLocationEffect(IPoint3D p, Map map, int itemID, int duration, int hue, int renderMode) + { + SendLocationEffect(p, map, itemID, duration, 10, hue, renderMode); + } + + public static void SendLocationEffect( + IPoint3D p, Map map, int itemID, int duration, int speed, int hue, + int renderMode + ) + { + SendPacket(p, map, new LocationEffect(p, itemID, speed, duration, hue, renderMode)); + } + + public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect) + { + SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, 0); + } + + public static void SendLocationParticles(IEntity e, int itemID, int speed, int duration, int effect, int unknown) + { + SendLocationParticles(e, itemID, speed, duration, 0, 0, effect, unknown); + } + + public static void SendLocationParticles( + IEntity e, int itemID, int speed, int duration, int hue, int renderMode, + int effect, int unknown + ) + { + var map = e.Map; + + if (map != null) + { + Packet particles = null, regular = null; + + var eable = map.GetClientsInRange(e.Location); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + + if (SendParticlesTo(state)) + { + particles ??= + Packet.Acquire( + new LocationParticleEffect(e, itemID, speed, duration, hue, renderMode, effect, unknown) + ); + + state.Send(particles); + } + else if (itemID != 0) + { + regular ??= Packet.Acquire(new LocationEffect(e, itemID, speed, duration, hue, renderMode)); + + state.Send(regular); + } + } + + Packet.Release(particles); + Packet.Release(regular); + + eable.Free(); + } + + // SendPacket( e.Location, e.Map, new LocationParticleEffect( e, itemID, speed, duration, hue, renderMode, effect, unknown ) ); + } + + public static void SendTargetEffect(IEntity target, int itemID, int duration) + { + SendTargetEffect(target, itemID, duration, 0, 0); + } + + public static void SendTargetEffect(IEntity target, int itemID, int speed, int duration) + { + SendTargetEffect(target, itemID, speed, duration, 0, 0); + } + + public static void SendTargetEffect(IEntity target, int itemID, int duration, int hue, int renderMode) + { + SendTargetEffect(target, itemID, 10, duration, hue, renderMode); + } + + public static void SendTargetEffect(IEntity target, int itemID, int speed, int duration, int hue, int renderMode) + { + if (target is Mobile mobile) + mobile.ProcessDelta(); + + SendPacket(target.Location, target.Map, new TargetEffect(target, itemID, speed, duration, hue, renderMode)); + } + + public static void SendTargetParticles( + IEntity target, int itemID, int speed, int duration, int effect, + EffectLayer layer + ) + { + SendTargetParticles(target, itemID, speed, duration, 0, 0, effect, layer, 0); + } + + public static void SendTargetParticles( + IEntity target, int itemID, int speed, int duration, int effect, + EffectLayer layer, int unknown + ) + { + SendTargetParticles(target, itemID, speed, duration, 0, 0, effect, layer, unknown); + } + + public static void SendTargetParticles( + IEntity target, int itemID, int speed, int duration, int hue, int renderMode, + int effect, EffectLayer layer, int unknown + ) + { + if (target is Mobile mobile) + mobile.ProcessDelta(); + + var map = target.Map; + + if (map != null) + { + Packet particles = null, regular = null; + + var eable = map.GetClientsInRange(target.Location); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + + if (SendParticlesTo(state)) + { + particles ??= Packet.Acquire( + new TargetParticleEffect( + target, + itemID, + speed, + duration, + hue, + renderMode, + effect, + (int)layer, + unknown + ) + ); + + state.Send(particles); + } + else if (itemID != 0) + { + regular ??= Packet.Acquire(new TargetEffect(target, itemID, speed, duration, hue, renderMode)); + + state.Send(regular); + } + } + + Packet.Release(particles); + Packet.Release(regular); + + eable.Free(); + } + + // SendPacket( target.Location, target.Map, new TargetParticleEffect( target, itemID, speed, duration, hue, renderMode, effect, (int)layer, unknown ) ); + } + + public static void SendMovingEffect( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int hue = 0, int renderMode = 0 + ) + { + if (from is Mobile mobile) + mobile.ProcessDelta(); + + if (to is Mobile mobile1) + mobile1.ProcessDelta(); + + SendPacket( + from.Location, + from.Map, + new MovingEffect(from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode) + ); + } + + public static void SendMovingParticles( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound + ) + { + SendMovingParticles( + from, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + 0, + 0, + effect, + explodeEffect, + explodeSound, + 0 + ); + } + + public static void SendMovingParticles( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int effect, int explodeEffect, int explodeSound, int unknown + ) + { + SendMovingParticles( + from, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + 0, + 0, + effect, + explodeEffect, + explodeSound, + unknown + ); + } + + public static void SendMovingParticles( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, + int unknown + ) + { + SendMovingParticles( + from, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + (EffectLayer)255, + unknown + ); + } + + public static void SendMovingParticles( + IEntity from, IEntity to, int itemID, int speed, int duration, + bool fixedDirection, bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, + EffectLayer layer, int unknown + ) + { + if (from is Mobile fromMob) + fromMob.ProcessDelta(); + + if (to is Mobile toMob) + toMob.ProcessDelta(); + + var map = from.Map; + + if (map != null) + { + Packet particles = null, regular = null; + + var eable = map.GetClientsInRange(from.Location); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + + if (SendParticlesTo(state)) + { + particles ??= Packet.Acquire( + new MovingParticleEffect( + from, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + layer, + unknown + ) + ); + + state.Send(particles); + } + else if (itemID > 1) + { + regular ??= Packet.Acquire( + new MovingEffect( + from, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode + ) + ); + + state.Send(regular); + } + } + + Packet.Release(particles); + Packet.Release(regular); + + eable.Free(); + } + + // SendPacket( from.Location, from.Map, new MovingParticleEffect( from, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, explodeEffect, explodeSound, unknown ) ); + } + + public static void SendPacket(Point3D origin, Map map, Packet p) + { + if (map == null) + return; + + var eable = map.GetClientsInRange(origin); + + p.Acquire(); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + state.Send(p); + } + + p.Release(); + + eable.Free(); + } + + public static void SendPacket(IPoint3D origin, Map map, Packet p) + { + if (map == null) + return; + + var eable = map.GetClientsInRange(new Point3D(origin)); + + p.Acquire(); + + foreach (var state in eable) + { + state.Mobile.ProcessDelta(); + state.Send(p); + } + + p.Release(); + + eable.Free(); + } + } +} diff --git a/Projects/Server/Events/AccountLoginEvent.cs b/Projects/Server/Events/AccountLoginEvent.cs index b18603080..90892e496 100644 --- a/Projects/Server/Events/AccountLoginEvent.cs +++ b/Projects/Server/Events/AccountLoginEvent.cs @@ -1,52 +1,52 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: AccountLoginEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Server.Network; - -namespace Server -{ - public class AccountLoginEventArgs : EventArgs - { - public AccountLoginEventArgs(NetState state, string username, string password) - { - State = state; - Username = username; - Password = password; - } - - public NetState State { get; } - - public string Username { get; } - - public string Password { get; } - - public bool Accepted { get; set; } - - public ALRReason RejectReason { get; set; } - } - - public static partial class EventSink - { - public static event Action AccountLogin; - public static void InvokeAccountLogin(AccountLoginEventArgs e) => AccountLogin?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AccountLoginEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.Network; + +namespace Server +{ + public class AccountLoginEventArgs : EventArgs + { + public AccountLoginEventArgs(NetState state, string username, string password) + { + State = state; + Username = username; + Password = password; + } + + public NetState State { get; } + + public string Username { get; } + + public string Password { get; } + + public bool Accepted { get; set; } + + public ALRReason RejectReason { get; set; } + } + + public static partial class EventSink + { + public static event Action AccountLogin; + public static void InvokeAccountLogin(AccountLoginEventArgs e) => AccountLogin?.Invoke(e); + } +} diff --git a/Projects/Server/Events/AggressiveActionEvent.cs b/Projects/Server/Events/AggressiveActionEvent.cs index 9baf886c7..05b80a302 100644 --- a/Projects/Server/Events/AggressiveActionEvent.cs +++ b/Projects/Server/Events/AggressiveActionEvent.cs @@ -1,75 +1,75 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: AggressiveActionEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server -{ - public class AggressiveActionEventArgs : EventArgs - { - private static readonly Queue m_Pool = new Queue(); - - private AggressiveActionEventArgs(Mobile aggressed, Mobile aggressor, bool criminal) - { - Aggressed = aggressed; - Aggressor = aggressor; - Criminal = criminal; - } - - public Mobile Aggressed { get; private set; } - - public Mobile Aggressor { get; private set; } - - public bool Criminal { get; private set; } - - public static AggressiveActionEventArgs Create(Mobile aggressed, Mobile aggressor, bool criminal) - { - AggressiveActionEventArgs args; - - if (m_Pool.Count > 0) - { - args = m_Pool.Dequeue(); - - args.Aggressed = aggressed; - args.Aggressor = aggressor; - args.Criminal = criminal; - } - else - { - args = new AggressiveActionEventArgs(aggressed, aggressor, criminal); - } - - return args; - } - - public void Free() - { - m_Pool.Enqueue(this); - } - } - - public static partial class EventSink - { - public static event Action AggressiveAction; - public static void InvokeAggressiveAction(AggressiveActionEventArgs e) => AggressiveAction?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AggressiveActionEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server +{ + public class AggressiveActionEventArgs : EventArgs + { + private static readonly Queue m_Pool = new Queue(); + + private AggressiveActionEventArgs(Mobile aggressed, Mobile aggressor, bool criminal) + { + Aggressed = aggressed; + Aggressor = aggressor; + Criminal = criminal; + } + + public Mobile Aggressed { get; private set; } + + public Mobile Aggressor { get; private set; } + + public bool Criminal { get; private set; } + + public static AggressiveActionEventArgs Create(Mobile aggressed, Mobile aggressor, bool criminal) + { + AggressiveActionEventArgs args; + + if (m_Pool.Count > 0) + { + args = m_Pool.Dequeue(); + + args.Aggressed = aggressed; + args.Aggressor = aggressor; + args.Criminal = criminal; + } + else + { + args = new AggressiveActionEventArgs(aggressed, aggressor, criminal); + } + + return args; + } + + public void Free() + { + m_Pool.Enqueue(this); + } + } + + public static partial class EventSink + { + public static event Action AggressiveAction; + public static void InvokeAggressiveAction(AggressiveActionEventArgs e) => AggressiveAction?.Invoke(e); + } +} diff --git a/Projects/Server/Events/CharacterCreatedEvent.cs b/Projects/Server/Events/CharacterCreatedEvent.cs index f3f53db35..e65c7efa5 100644 --- a/Projects/Server/Events/CharacterCreatedEvent.cs +++ b/Projects/Server/Events/CharacterCreatedEvent.cs @@ -1,111 +1,113 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: CharacterCreatedEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Server.Accounting; -using Server.Network; - -namespace Server -{ - public class CharacterCreatedEventArgs : EventArgs - { - public CharacterCreatedEventArgs(NetState state, IAccount a, string name, bool female, int hue, int str, int dex, - int intel, CityInfo city, SkillNameValue[] skills, int shirtHue, int pantsHue, int hairID, int hairHue, - int beardID, int beardHue, int profession, Race race) - { - State = state; - Account = a; - Name = name; - Female = female; - Hue = hue; - Str = str; - Dex = dex; - Int = intel; - City = city; - Skills = skills; - ShirtHue = shirtHue; - PantsHue = pantsHue; - HairID = hairID; - HairHue = hairHue; - BeardID = beardID; - BeardHue = beardHue; - Profession = profession; - Race = race; - } - - public NetState State { get; } - - public IAccount Account { get; } - - public Mobile Mobile { get; set; } - - public string Name { get; } - - public bool Female { get; } - - public int Hue { get; } - - public int Str { get; } - - public int Dex { get; } - - public int Int { get; } - - public CityInfo City { get; } - - public SkillNameValue[] Skills { get; } - - public int ShirtHue { get; } - - public int PantsHue { get; } - - public int HairID { get; } - - public int HairHue { get; } - - public int BeardID { get; } - - public int BeardHue { get; } - - public int Profession { get; set; } - - public Race Race { get; } - } - - public struct SkillNameValue - { - public SkillName Name { get; } - - public int Value { get; } - - public SkillNameValue(SkillName name, int value) - { - Name = name; - Value = value; - } - } - - public static partial class EventSink - { - public static event Action CharacterCreated; - public static void InvokeCharacterCreated(CharacterCreatedEventArgs e) => CharacterCreated?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CharacterCreatedEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.Accounting; +using Server.Network; + +namespace Server +{ + public class CharacterCreatedEventArgs : EventArgs + { + public CharacterCreatedEventArgs( + NetState state, IAccount a, string name, bool female, int hue, int str, int dex, + int intel, CityInfo city, SkillNameValue[] skills, int shirtHue, int pantsHue, int hairID, int hairHue, + int beardID, int beardHue, int profession, Race race + ) + { + State = state; + Account = a; + Name = name; + Female = female; + Hue = hue; + Str = str; + Dex = dex; + Int = intel; + City = city; + Skills = skills; + ShirtHue = shirtHue; + PantsHue = pantsHue; + HairID = hairID; + HairHue = hairHue; + BeardID = beardID; + BeardHue = beardHue; + Profession = profession; + Race = race; + } + + public NetState State { get; } + + public IAccount Account { get; } + + public Mobile Mobile { get; set; } + + public string Name { get; } + + public bool Female { get; } + + public int Hue { get; } + + public int Str { get; } + + public int Dex { get; } + + public int Int { get; } + + public CityInfo City { get; } + + public SkillNameValue[] Skills { get; } + + public int ShirtHue { get; } + + public int PantsHue { get; } + + public int HairID { get; } + + public int HairHue { get; } + + public int BeardID { get; } + + public int BeardHue { get; } + + public int Profession { get; set; } + + public Race Race { get; } + } + + public struct SkillNameValue + { + public SkillName Name { get; } + + public int Value { get; } + + public SkillNameValue(SkillName name, int value) + { + Name = name; + Value = value; + } + } + + public static partial class EventSink + { + public static event Action CharacterCreated; + public static void InvokeCharacterCreated(CharacterCreatedEventArgs e) => CharacterCreated?.Invoke(e); + } +} diff --git a/Projects/Server/Events/CreateGuildEvent.cs b/Projects/Server/Events/CreateGuildEvent.cs index fe1e53080..d06729f90 100644 --- a/Projects/Server/Events/CreateGuildEvent.cs +++ b/Projects/Server/Events/CreateGuildEvent.cs @@ -1,41 +1,41 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: CreateGuildEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Server.Guilds; - -namespace Server -{ - public class CreateGuildEventArgs : EventArgs - { - public CreateGuildEventArgs(uint id) => Id = id; - - public uint Id { get; set; } - - public BaseGuild Guild { get; set; } - } - - public static partial class EventSink - { - public static event Action CreateGuild; - public static void InvokeCreateGuild(CreateGuildEventArgs e) => CreateGuild?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CreateGuildEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.Guilds; + +namespace Server +{ + public class CreateGuildEventArgs : EventArgs + { + public CreateGuildEventArgs(uint id) => Id = id; + + public uint Id { get; set; } + + public BaseGuild Guild { get; set; } + } + + public static partial class EventSink + { + public static event Action CreateGuild; + public static void InvokeCreateGuild(CreateGuildEventArgs e) => CreateGuild?.Invoke(e); + } +} diff --git a/Projects/Server/Events/EventSink.cs b/Projects/Server/Events/EventSink.cs index 6d43d88cf..6cc52f9e0 100644 --- a/Projects/Server/Events/EventSink.cs +++ b/Projects/Server/Events/EventSink.cs @@ -1,168 +1,172 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: EventSink.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using Server.Network; - -namespace Server -{ - public static partial class EventSink - { - public static event Action OpenDoorMacroUsed; - public static void InvokeOpenDoorMacroUsed(Mobile m) => OpenDoorMacroUsed?.Invoke(m); - - public static event Action Login; - public static void InvokeLogin(Mobile m) => Login?.Invoke(m); - - public static event Action HungerChanged; - public static void InvokeHungerChanged(Mobile mobile, int oldValue) => HungerChanged?.Invoke(mobile, oldValue); - - public static event Action Shutdown; - public static void InvokeShutdown() => Shutdown?.Invoke(); - - public static event Action HelpRequest; - public static void InvokeHelpRequest(Mobile m) => HelpRequest?.Invoke(m); - - public static event Action DisarmRequest; - public static void InvokeDisarmRequest(Mobile m) => DisarmRequest?.Invoke(m); - - public static event Action StunRequest; - public static void InvokeStunRequest(Mobile m) => StunRequest?.Invoke(m); - - public static event Action OpenSpellbookRequest; - public static void InvokeOpenSpellbookRequest(Mobile m, int type) => OpenSpellbookRequest?.Invoke(m, type); - - public static event Action CastSpellRequest; - - public static void InvokeCastSpellRequest(Mobile m, int spellID, Item book) => - CastSpellRequest?.Invoke(m, spellID, book); - - public static event Action BandageTargetRequest; - - public static void InvokeBandageTargetRequest(Mobile m, Item bandage, Mobile target) => - BandageTargetRequest?.Invoke(m, bandage, target); - - public static event Action AnimateRequest; - public static void InvokeAnimateRequest(Mobile m, string action) => AnimateRequest?.Invoke(m, action); - - public static event Action Logout; - public static void InvokeLogout(Mobile m) => Logout?.Invoke(m); - - public static event Action Connected; - public static void InvokeConnected(Mobile m) => Connected?.Invoke(m); - - public static event Action Disconnected; - public static void InvokeDisconnected(Mobile m) => Disconnected?.Invoke(m); - - public static event Action RenameRequest; - - public static void InvokeRenameRequest(Mobile from, Mobile target, string name) => - RenameRequest?.Invoke(from, target, name); - - public static event Action PlayerDeath; - public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m); - - public static event Action VirtueGumpRequest; - - public static void InvokeVirtueGumpRequest(Mobile beholder, Mobile beheld) => - VirtueGumpRequest?.Invoke(beholder, beheld); - - public static event Action VirtueItemRequest; - - public static void InvokeVirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) => - VirtueItemRequest?.Invoke(beholder, beheld, gumpID); - - public static event Action VirtueMacroRequest; - public static void InvokeVirtueMacroRequest(Mobile mobile, int virtueID) => VirtueMacroRequest?.Invoke(mobile, virtueID); - - public static event Action ChatRequest; - public static void InvokeChatRequest(Mobile m) => ChatRequest?.Invoke(m); - - public static event Action PaperdollRequest; - public static void InvokePaperdollRequest(Mobile beholder, Mobile beheld) => PaperdollRequest?.Invoke(beholder, beheld); - - public static event Action ProfileRequest; - public static void InvokeProfileRequest(Mobile beholder, Mobile beheld) => ProfileRequest?.Invoke(beholder, beheld); - - public static event Action ChangeProfileRequest; - - public static void InvokeChangeProfileRequest(Mobile beholder, Mobile beheld, string text) => - ChangeProfileRequest?.Invoke(beholder, beheld, text); - - public static event Action DeleteRequest; - public static void InvokeDeleteRequest(NetState state, int index) => DeleteRequest?.Invoke(state, index); - - public static event Action WorldLoad; - public static void InvokeWorldLoad() => WorldLoad?.Invoke(); - - public static event Action WorldSave; - public static void InvokeWorldSave(bool sendMessage) => WorldSave?.Invoke(sendMessage); - - public static event Action SetAbility; - public static void InvokeSetAbility(Mobile mobile, int index) => SetAbility?.Invoke(mobile, index); - - public static event Action ServerStarted; - public static void InvokeServerStarted() => ServerStarted?.Invoke(); - - public static event Action GuildGumpRequest; - public static void InvokeGuildGumpRequest(Mobile m) => GuildGumpRequest?.Invoke(m); - - public static event Action QuestGumpRequest; - public static void InvokeQuestGumpRequest(Mobile m) => QuestGumpRequest?.Invoke(m); - - public static event Action ClientVersionReceived; - - public static void InvokeClientVersionReceived(NetState state, ClientVersion cv) => - ClientVersionReceived?.Invoke(state, cv); - - public static event Action> EquipMacro; - - public static void InvokeEquipMacro(Mobile m, List list) - { - if (list?.Count > 0) - EquipMacro?.Invoke(m, list); - } - - public static event Action> UnequipMacro; - - public static void InvokeUnequipMacro(Mobile m, List layers) - { - if (layers?.Count > 0) - UnequipMacro?.Invoke(m, layers); - } - - public static event Action TargetedSpell; - - public static void InvokeTargetedSpell(Mobile m, IEntity target, int spellId) => - TargetedSpell?.Invoke(m, target, spellId); - - public static event Action TargetedSkillUse; - - public static void InvokeTargetedSkillUse(Mobile m, IEntity target, int skillId) => - TargetedSkillUse?.Invoke(m, target, skillId); - - public static event Action TargetByResourceMacro; - - public static void InvokeTargetByResourceMacro(Mobile m, Item item, short resourceType) => - TargetByResourceMacro?.Invoke(m, item, resourceType); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EventSink.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using Server.Network; + +namespace Server +{ + public static partial class EventSink + { + public static event Action OpenDoorMacroUsed; + public static void InvokeOpenDoorMacroUsed(Mobile m) => OpenDoorMacroUsed?.Invoke(m); + + public static event Action Login; + public static void InvokeLogin(Mobile m) => Login?.Invoke(m); + + public static event Action HungerChanged; + public static void InvokeHungerChanged(Mobile mobile, int oldValue) => HungerChanged?.Invoke(mobile, oldValue); + + public static event Action Shutdown; + public static void InvokeShutdown() => Shutdown?.Invoke(); + + public static event Action HelpRequest; + public static void InvokeHelpRequest(Mobile m) => HelpRequest?.Invoke(m); + + public static event Action DisarmRequest; + public static void InvokeDisarmRequest(Mobile m) => DisarmRequest?.Invoke(m); + + public static event Action StunRequest; + public static void InvokeStunRequest(Mobile m) => StunRequest?.Invoke(m); + + public static event Action OpenSpellbookRequest; + public static void InvokeOpenSpellbookRequest(Mobile m, int type) => OpenSpellbookRequest?.Invoke(m, type); + + public static event Action CastSpellRequest; + + public static void InvokeCastSpellRequest(Mobile m, int spellID, Item book) => + CastSpellRequest?.Invoke(m, spellID, book); + + public static event Action BandageTargetRequest; + + public static void InvokeBandageTargetRequest(Mobile m, Item bandage, Mobile target) => + BandageTargetRequest?.Invoke(m, bandage, target); + + public static event Action AnimateRequest; + public static void InvokeAnimateRequest(Mobile m, string action) => AnimateRequest?.Invoke(m, action); + + public static event Action Logout; + public static void InvokeLogout(Mobile m) => Logout?.Invoke(m); + + public static event Action Connected; + public static void InvokeConnected(Mobile m) => Connected?.Invoke(m); + + public static event Action Disconnected; + public static void InvokeDisconnected(Mobile m) => Disconnected?.Invoke(m); + + public static event Action RenameRequest; + + public static void InvokeRenameRequest(Mobile from, Mobile target, string name) => + RenameRequest?.Invoke(from, target, name); + + public static event Action PlayerDeath; + public static void InvokePlayerDeath(Mobile m) => PlayerDeath?.Invoke(m); + + public static event Action VirtueGumpRequest; + + public static void InvokeVirtueGumpRequest(Mobile beholder, Mobile beheld) => + VirtueGumpRequest?.Invoke(beholder, beheld); + + public static event Action VirtueItemRequest; + + public static void InvokeVirtueItemRequest(Mobile beholder, Mobile beheld, int gumpID) => + VirtueItemRequest?.Invoke(beholder, beheld, gumpID); + + public static event Action VirtueMacroRequest; + + public static void InvokeVirtueMacroRequest(Mobile mobile, int virtueID) => + VirtueMacroRequest?.Invoke(mobile, virtueID); + + public static event Action ChatRequest; + public static void InvokeChatRequest(Mobile m) => ChatRequest?.Invoke(m); + + public static event Action PaperdollRequest; + + public static void InvokePaperdollRequest(Mobile beholder, Mobile beheld) => + PaperdollRequest?.Invoke(beholder, beheld); + + public static event Action ProfileRequest; + public static void InvokeProfileRequest(Mobile beholder, Mobile beheld) => ProfileRequest?.Invoke(beholder, beheld); + + public static event Action ChangeProfileRequest; + + public static void InvokeChangeProfileRequest(Mobile beholder, Mobile beheld, string text) => + ChangeProfileRequest?.Invoke(beholder, beheld, text); + + public static event Action DeleteRequest; + public static void InvokeDeleteRequest(NetState state, int index) => DeleteRequest?.Invoke(state, index); + + public static event Action WorldLoad; + public static void InvokeWorldLoad() => WorldLoad?.Invoke(); + + public static event Action WorldSave; + public static void InvokeWorldSave(bool sendMessage) => WorldSave?.Invoke(sendMessage); + + public static event Action SetAbility; + public static void InvokeSetAbility(Mobile mobile, int index) => SetAbility?.Invoke(mobile, index); + + public static event Action ServerStarted; + public static void InvokeServerStarted() => ServerStarted?.Invoke(); + + public static event Action GuildGumpRequest; + public static void InvokeGuildGumpRequest(Mobile m) => GuildGumpRequest?.Invoke(m); + + public static event Action QuestGumpRequest; + public static void InvokeQuestGumpRequest(Mobile m) => QuestGumpRequest?.Invoke(m); + + public static event Action ClientVersionReceived; + + public static void InvokeClientVersionReceived(NetState state, ClientVersion cv) => + ClientVersionReceived?.Invoke(state, cv); + + public static event Action> EquipMacro; + + public static void InvokeEquipMacro(Mobile m, List list) + { + if (list?.Count > 0) + EquipMacro?.Invoke(m, list); + } + + public static event Action> UnequipMacro; + + public static void InvokeUnequipMacro(Mobile m, List layers) + { + if (layers?.Count > 0) + UnequipMacro?.Invoke(m, layers); + } + + public static event Action TargetedSpell; + + public static void InvokeTargetedSpell(Mobile m, IEntity target, int spellId) => + TargetedSpell?.Invoke(m, target, spellId); + + public static event Action TargetedSkillUse; + + public static void InvokeTargetedSkillUse(Mobile m, IEntity target, int skillId) => + TargetedSkillUse?.Invoke(m, target, skillId); + + public static event Action TargetByResourceMacro; + + public static void InvokeTargetByResourceMacro(Mobile m, Item item, short resourceType) => + TargetByResourceMacro?.Invoke(m, item, resourceType); + } +} diff --git a/Projects/Server/Events/FastwalkEvent.cs b/Projects/Server/Events/FastwalkEvent.cs index c99dd301a..b14a1da10 100644 --- a/Projects/Server/Events/FastwalkEvent.cs +++ b/Projects/Server/Events/FastwalkEvent.cs @@ -1,45 +1,45 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: FastWalkEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Server.Network; - -namespace Server -{ - public class FastWalkEventArgs : EventArgs - { - public FastWalkEventArgs(NetState state) - { - NetState = state; - Blocked = false; - } - - public NetState NetState { get; } - - public bool Blocked { get; set; } - } - - public static partial class EventSink - { - public static event Action FastWalk; - public static void InvokeFastWalk(FastWalkEventArgs e) => FastWalk?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: FastWalkEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.Network; + +namespace Server +{ + public class FastWalkEventArgs : EventArgs + { + public FastWalkEventArgs(NetState state) + { + NetState = state; + Blocked = false; + } + + public NetState NetState { get; } + + public bool Blocked { get; set; } + } + + public static partial class EventSink + { + public static event Action FastWalk; + public static void InvokeFastWalk(FastWalkEventArgs e) => FastWalk?.Invoke(e); + } +} diff --git a/Projects/Server/Events/GameLoginEvent.cs b/Projects/Server/Events/GameLoginEvent.cs index 698291286..570d6477b 100644 --- a/Projects/Server/Events/GameLoginEvent.cs +++ b/Projects/Server/Events/GameLoginEvent.cs @@ -1,52 +1,52 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: GameLoginEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Server.Network; - -namespace Server -{ - public class GameLoginEventArgs : EventArgs - { - public GameLoginEventArgs(NetState state, string un, string pw) - { - State = state; - Username = un; - Password = pw; - } - - public NetState State { get; } - - public string Username { get; } - - public string Password { get; } - - public bool Accepted { get; set; } - - public CityInfo[] CityInfo { get; set; } - } - - public static partial class EventSink - { - public static event Action GameLogin; - public static void InvokeGameLogin(GameLoginEventArgs e) => GameLogin?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GameLoginEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.Network; + +namespace Server +{ + public class GameLoginEventArgs : EventArgs + { + public GameLoginEventArgs(NetState state, string un, string pw) + { + State = state; + Username = un; + Password = pw; + } + + public NetState State { get; } + + public string Username { get; } + + public string Password { get; } + + public bool Accepted { get; set; } + + public CityInfo[] CityInfo { get; set; } + } + + public static partial class EventSink + { + public static event Action GameLogin; + public static void InvokeGameLogin(GameLoginEventArgs e) => GameLogin?.Invoke(e); + } +} diff --git a/Projects/Server/Events/MovementEvent.cs b/Projects/Server/Events/MovementEvent.cs index 68e29bd56..9fbae577a 100644 --- a/Projects/Server/Events/MovementEvent.cs +++ b/Projects/Server/Events/MovementEvent.cs @@ -1,74 +1,74 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MovementEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server -{ - public class MovementEventArgs : EventArgs - { - private static readonly Queue m_Pool = new Queue(); - - public MovementEventArgs(Mobile mobile, Direction dir) - { - Mobile = mobile; - Direction = dir; - } - - public Mobile Mobile { get; private set; } - - public Direction Direction { get; private set; } - - public bool Blocked { get; set; } - - public static MovementEventArgs Create(Mobile mobile, Direction dir) - { - MovementEventArgs args; - - if (m_Pool.Count > 0) - { - args = m_Pool.Dequeue(); - - args.Mobile = mobile; - args.Direction = dir; - args.Blocked = false; - } - else - { - args = new MovementEventArgs(mobile, dir); - } - - return args; - } - - public void Free() - { - m_Pool.Enqueue(this); - } - } - - public static partial class EventSink - { - public static event Action Movement; - public static void InvokeMovement(MovementEventArgs e) => Movement?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MovementEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server +{ + public class MovementEventArgs : EventArgs + { + private static readonly Queue m_Pool = new Queue(); + + public MovementEventArgs(Mobile mobile, Direction dir) + { + Mobile = mobile; + Direction = dir; + } + + public Mobile Mobile { get; private set; } + + public Direction Direction { get; private set; } + + public bool Blocked { get; set; } + + public static MovementEventArgs Create(Mobile mobile, Direction dir) + { + MovementEventArgs args; + + if (m_Pool.Count > 0) + { + args = m_Pool.Dequeue(); + + args.Mobile = mobile; + args.Direction = dir; + args.Blocked = false; + } + else + { + args = new MovementEventArgs(mobile, dir); + } + + return args; + } + + public void Free() + { + m_Pool.Enqueue(this); + } + } + + public static partial class EventSink + { + public static event Action Movement; + public static void InvokeMovement(MovementEventArgs e) => Movement?.Invoke(e); + } +} diff --git a/Projects/Server/Events/ServerCrashedEvent.cs b/Projects/Server/Events/ServerCrashedEvent.cs index a5568810d..146970b86 100644 --- a/Projects/Server/Events/ServerCrashedEvent.cs +++ b/Projects/Server/Events/ServerCrashedEvent.cs @@ -1,40 +1,40 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ServerCrashedEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server -{ - public class ServerCrashedEventArgs : EventArgs - { - public ServerCrashedEventArgs(Exception e) => Exception = e; - - public Exception Exception { get; } - - public bool Close { get; set; } - } - - public static partial class EventSink - { - public static event Action ServerCrashed; - public static void InvokeServerCrashed(ServerCrashedEventArgs e) => ServerCrashed?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ServerCrashedEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server +{ + public class ServerCrashedEventArgs : EventArgs + { + public ServerCrashedEventArgs(Exception e) => Exception = e; + + public Exception Exception { get; } + + public bool Close { get; set; } + } + + public static partial class EventSink + { + public static event Action ServerCrashed; + public static void InvokeServerCrashed(ServerCrashedEventArgs e) => ServerCrashed?.Invoke(e); + } +} diff --git a/Projects/Server/Events/ServerListEvent.cs b/Projects/Server/Events/ServerListEvent.cs index 0e0044b11..a2d0cc9aa 100644 --- a/Projects/Server/Events/ServerListEvent.cs +++ b/Projects/Server/Events/ServerListEvent.cs @@ -1,63 +1,63 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ServerListEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Net; -using Server.Accounting; -using Server.Network; - -namespace Server -{ - public class ServerListEventArgs : EventArgs - { - public ServerListEventArgs(NetState state, IAccount account) - { - State = state; - Account = account; - Servers = new List(); - } - - public NetState State { get; } - - public IAccount Account { get; } - - public bool Rejected { get; set; } - - public List Servers { get; } - - public void AddServer(string name, IPEndPoint address) - { - AddServer(name, 0, TimeZoneInfo.Local, address); - } - - public void AddServer(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address) - { - Servers.Add(new ServerInfo(name, fullPercent, tz, address)); - } - } - - public static partial class EventSink - { - public static event Action ServerList; - public static void InvokeServerList(ServerListEventArgs e) => ServerList?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ServerListEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Net; +using Server.Accounting; +using Server.Network; + +namespace Server +{ + public class ServerListEventArgs : EventArgs + { + public ServerListEventArgs(NetState state, IAccount account) + { + State = state; + Account = account; + Servers = new List(); + } + + public NetState State { get; } + + public IAccount Account { get; } + + public bool Rejected { get; set; } + + public List Servers { get; } + + public void AddServer(string name, IPEndPoint address) + { + AddServer(name, 0, TimeZoneInfo.Local, address); + } + + public void AddServer(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address) + { + Servers.Add(new ServerInfo(name, fullPercent, tz, address)); + } + } + + public static partial class EventSink + { + public static event Action ServerList; + public static void InvokeServerList(ServerListEventArgs e) => ServerList?.Invoke(e); + } +} diff --git a/Projects/Server/Events/SocketConnectionEvent.cs b/Projects/Server/Events/SocketConnectionEvent.cs index 870e4152c..58cf4a784 100644 --- a/Projects/Server/Events/SocketConnectionEvent.cs +++ b/Projects/Server/Events/SocketConnectionEvent.cs @@ -1,45 +1,45 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: SocketConnectionEvent.cs * - * Created: 2020/04/11 - Updated: 2020/04/11 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Microsoft.AspNetCore.Connections; - -namespace Server -{ - public class SocketConnectEventArgs : EventArgs - { - public SocketConnectEventArgs(ConnectionContext c) - { - Context = c; - AllowConnection = true; - } - - public ConnectionContext Context { get; } - - public bool AllowConnection { get; set; } - } - - public static partial class EventSink - { - public static event Action SocketConnect; - public static void InvokeSocketConnect(SocketConnectEventArgs e) => SocketConnect?.Invoke(e); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SocketConnectionEvent.cs * + * Created: 2020/04/11 - Updated: 2020/04/11 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Microsoft.AspNetCore.Connections; + +namespace Server +{ + public class SocketConnectEventArgs : EventArgs + { + public SocketConnectEventArgs(ConnectionContext c) + { + Context = c; + AllowConnection = true; + } + + public ConnectionContext Context { get; } + + public bool AllowConnection { get; set; } + } + + public static partial class EventSink + { + public static event Action SocketConnect; + public static void InvokeSocketConnect(SocketConnectEventArgs e) => SocketConnect?.Invoke(e); + } +} diff --git a/Projects/Server/Events/SpeechEvent.cs b/Projects/Server/Events/SpeechEvent.cs index 9b1468343..8de6fec0e 100644 --- a/Projects/Server/Events/SpeechEvent.cs +++ b/Projects/Server/Events/SpeechEvent.cs @@ -1,46 +1,46 @@ -using System; -using Server.Network; - -namespace Server -{ - public class SpeechEventArgs : EventArgs - { - public SpeechEventArgs(Mobile mobile, string speech, MessageType type, int hue, int[] keywords) - { - Mobile = mobile; - Speech = speech; - Type = type; - Hue = hue; - Keywords = keywords; - } - - public Mobile Mobile { get; } - - public string Speech { get; set; } - - public MessageType Type { get; } - - public int Hue { get; } - - public int[] Keywords { get; } - - public bool Handled { get; set; } - - public bool Blocked { get; set; } - - public bool HasKeyword(int keyword) - { - for (var i = 0; i < Keywords.Length; ++i) - if (Keywords[i] == keyword) - return true; - - return false; - } - } - - public static partial class EventSink - { - public static event Action Speech; - public static void InvokeSpeech(SpeechEventArgs e) => Speech?.Invoke(e); - } -} +using System; +using Server.Network; + +namespace Server +{ + public class SpeechEventArgs : EventArgs + { + public SpeechEventArgs(Mobile mobile, string speech, MessageType type, int hue, int[] keywords) + { + Mobile = mobile; + Speech = speech; + Type = type; + Hue = hue; + Keywords = keywords; + } + + public Mobile Mobile { get; } + + public string Speech { get; set; } + + public MessageType Type { get; } + + public int Hue { get; } + + public int[] Keywords { get; } + + public bool Handled { get; set; } + + public bool Blocked { get; set; } + + public bool HasKeyword(int keyword) + { + for (var i = 0; i < Keywords.Length; ++i) + if (Keywords[i] == keyword) + return true; + + return false; + } + } + + public static partial class EventSink + { + public static event Action Speech; + public static void InvokeSpeech(SpeechEventArgs e) => Speech?.Invoke(e); + } +} diff --git a/Projects/Server/ExpansionInfo.cs b/Projects/Server/ExpansionInfo.cs index 450e010a7..1265bed47 100644 --- a/Projects/Server/ExpansionInfo.cs +++ b/Projects/Server/ExpansionInfo.cs @@ -1,338 +1,353 @@ -/*************************************************************************** - * ExpansionInfo.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server -{ - public enum Expansion - { - None, - T2A, - UOR, - UOTD, - LBR, - AOS, - SE, - ML, - SA, - HS, - TOL, - EJ - } - - [Flags] - public enum ClientFlags - { - None = 0x00000000, - Felucca = 0x00000001, - Trammel = 0x00000002, - Ilshenar = 0x00000004, - Malas = 0x00000008, - Tokuno = 0x00000010, - TerMur = 0x00000020, - Unk1 = 0x00000040, - Unk2 = 0x00000080, - UOTD = 0x00000100 - } - - [Flags] - public enum FeatureFlags - { - None = 0x00000000, - T2A = 0x00000001, - UOR = 0x00000002, - UOTD = 0x00000004, - LBR = 0x00000008, - AOS = 0x00000010, - SixthCharacterSlot = 0x00000020, - SE = 0x00000040, - ML = 0x00000080, - EigthAge = 0x00000100, - NinthAge = 0x00000200, /* Crystal/Shadow Custom House Tiles */ - TenthAge = 0x00000400, - IncreasedStorage = 0x00000800, /* Increased Housing/Bank Storage */ - SeventhCharacterSlot = 0x00001000, - RoleplayFaces = 0x00002000, - TrialAccount = 0x00004000, - LiveAccount = 0x00008000, - SA = 0x00010000, - HS = 0x00020000, - Gothic = 0x00040000, - Rustic = 0x00080000, - Jungle = 0x00100000, - Shadowguard = 0x00200000, - TOL = 0x00400000, - EJ = 0x00800000, - - ExpansionNone = None, - ExpansionT2A = T2A, - ExpansionUOR = ExpansionT2A | UOR, - ExpansionUOTD = ExpansionUOR | UOTD, - ExpansionLBR = ExpansionUOTD | LBR, - ExpansionAOS = ExpansionLBR | AOS | LiveAccount, - ExpansionSE = ExpansionAOS | SE, - ExpansionML = ExpansionSE | ML | NinthAge, - ExpansionSA = ExpansionML | SA | Gothic | Rustic, - ExpansionHS = ExpansionSA | HS, - ExpansionTOL = ExpansionHS | TOL | Jungle | Shadowguard, - ExpansionEJ = ExpansionTOL | EJ - } - - [Flags] - public enum CharacterListFlags - { - None = 0x00000000, - Unk1 = 0x00000001, - OverwriteConfigButton = 0x00000002, - OneCharacterSlot = 0x00000004, - ContextMenus = 0x00000008, - SlotLimit = 0x00000010, - AOS = 0x00000020, - SixthCharacterSlot = 0x00000040, - SE = 0x00000080, - ML = 0x00000100, - Unk2 = 0x00000200, - UO3DClientType = 0x00000400, - Unk3 = 0x00000800, - SeventhCharacterSlot = 0x00001000, - Unk4 = 0x00002000, - NewMovementSystem = 0x00004000, - NewFeluccaAreas = 0x00008000, - - ExpansionNone = ContextMenus, - ExpansionT2A = ContextMenus, - ExpansionUOR = ContextMenus, - ExpansionUOTD = ContextMenus, - ExpansionLBR = ContextMenus, - ExpansionAOS = ContextMenus | AOS, - ExpansionSE = ExpansionAOS | SE, - ExpansionML = ExpansionSE | ML, - ExpansionSA = ExpansionML, - ExpansionHS = ExpansionSA, - ExpansionTOL = ExpansionHS, - ExpansionEJ = ExpansionTOL - } - - [Flags] - public enum HousingFlags - { - None = 0x0, - AOS = 0x10, - SE = 0x40, - ML = 0x80, - Crystal = 0x200, - SA = 0x10000, - HS = 0x20000, - Gothic = 0x40000, - Rustic = 0x80000, - Jungle = 0x100000, - Shadowguard = 0x200000, - TOL = 0x400000, - EJ = 0x800000, - - HousingAOS = AOS, - HousingSE = HousingAOS | SE, - HousingML = HousingSE | ML | Crystal, - HousingSA = HousingML | SA | Gothic | Rustic, - HousingHS = HousingSA | HS, - HousingTOL = HousingHS | TOL | Jungle | Shadowguard, - HousingEJ = HousingTOL | EJ - } - - public class ExpansionInfo - { - static ExpansionInfo() - { - Table = new[] - { - new ExpansionInfo( - 0, - "None", - ClientFlags.None, - FeatureFlags.ExpansionNone, - CharacterListFlags.ExpansionNone, - HousingFlags.None), - new ExpansionInfo( - 1, - "The Second Age", - ClientFlags.Felucca, - FeatureFlags.ExpansionT2A, - CharacterListFlags.ExpansionT2A, - HousingFlags.None), - new ExpansionInfo( - 2, - "Renaissance", - ClientFlags.Trammel, - FeatureFlags.ExpansionUOR, - CharacterListFlags.ExpansionUOR, - HousingFlags.None), - new ExpansionInfo( - 3, - "Third Dawn", - ClientFlags.Ilshenar, - FeatureFlags.ExpansionUOTD, - CharacterListFlags.ExpansionUOTD, - HousingFlags.None), - new ExpansionInfo( - 4, - "Blackthorn's Revenge", - ClientFlags.Ilshenar, - FeatureFlags.ExpansionLBR, - CharacterListFlags.ExpansionLBR, - HousingFlags.None), - new ExpansionInfo( - 5, - "Age of Shadows", - ClientFlags.Malas, - FeatureFlags.ExpansionAOS, - CharacterListFlags.ExpansionAOS, - HousingFlags.HousingAOS), - new ExpansionInfo( - 6, - "Samurai Empire", - ClientFlags.Tokuno, - FeatureFlags.ExpansionSE, - CharacterListFlags.ExpansionSE, - HousingFlags.HousingSE), - new ExpansionInfo( - 7, - "Mondain's Legacy", - new ClientVersion("5.0.0a"), - FeatureFlags.ExpansionML, - CharacterListFlags.ExpansionML, - HousingFlags.HousingML), - new ExpansionInfo( - 8, - "Stygian Abyss", - ClientFlags.TerMur, - FeatureFlags.ExpansionSA, - CharacterListFlags.ExpansionSA, - HousingFlags.HousingSA), - new ExpansionInfo( - 9, - "High Seas", - new ClientVersion("7.0.9.0"), - FeatureFlags.ExpansionHS, - CharacterListFlags.ExpansionHS, - HousingFlags.HousingHS), - new ExpansionInfo( - 10, - "Time of Legends", - new ClientVersion("7.0.45.65"), - FeatureFlags.ExpansionTOL, - CharacterListFlags.ExpansionTOL, - HousingFlags.HousingTOL), - new ExpansionInfo( - 11, - "Endless Journey", - new ClientVersion("7.0.61.0"), - FeatureFlags.ExpansionEJ, - CharacterListFlags.ExpansionEJ, - HousingFlags.HousingEJ) - }; - } - - public ExpansionInfo( - int id, - string name, - ClientFlags clientFlags, - FeatureFlags supportedFeatures, - CharacterListFlags charListFlags, - HousingFlags customHousingFlag) - : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => - ClientFlags = clientFlags; - - public ExpansionInfo( - int id, - string name, - ClientVersion requiredClient, - FeatureFlags supportedFeatures, - CharacterListFlags charListFlags, - HousingFlags customHousingFlag) - : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => - RequiredClient = requiredClient; - - private ExpansionInfo( - int id, - string name, - FeatureFlags supportedFeatures, - CharacterListFlags charListFlags, - HousingFlags customHousingFlag) - { - ID = id; - Name = name; - - SupportedFeatures = supportedFeatures; - CharacterListFlags = charListFlags; - CustomHousingFlag = customHousingFlag; - } - - public static ExpansionInfo CoreExpansion => GetInfo(Core.Expansion); - - public static ExpansionInfo[] Table { get; } - - public int ID { get; } - public string Name { get; set; } - - public ClientFlags ClientFlags { get; set; } - public FeatureFlags SupportedFeatures { get; set; } - public CharacterListFlags CharacterListFlags { get; set; } - public ClientVersion RequiredClient { get; set; } - public HousingFlags CustomHousingFlag { get; set; } - - public static FeatureFlags GetFeatures(Expansion ex) - { - var info = GetInfo(ex); - - if (info != null) return info.SupportedFeatures; - - return ex switch - { - Expansion.None => FeatureFlags.ExpansionNone, - Expansion.T2A => FeatureFlags.ExpansionT2A, - Expansion.UOR => FeatureFlags.ExpansionUOR, - Expansion.UOTD => FeatureFlags.ExpansionUOTD, - Expansion.LBR => FeatureFlags.ExpansionLBR, - Expansion.AOS => FeatureFlags.ExpansionAOS, - Expansion.SE => FeatureFlags.ExpansionSE, - Expansion.ML => FeatureFlags.ExpansionML, - Expansion.SA => FeatureFlags.ExpansionSA, - Expansion.HS => FeatureFlags.ExpansionHS, - Expansion.TOL => FeatureFlags.ExpansionTOL, - Expansion.EJ => FeatureFlags.EJ, - _ => FeatureFlags.ExpansionNone - }; - } - - public static ExpansionInfo GetInfo(Expansion ex) => GetInfo((int)ex); - - public static ExpansionInfo GetInfo(int ex) - { - var v = ex; - - if (v < 0 || v >= Table.Length) v = 0; - - return Table[v]; - } - - public override string ToString() => Name; - } -} +/*************************************************************************** + * ExpansionInfo.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server +{ + public enum Expansion + { + None, + T2A, + UOR, + UOTD, + LBR, + AOS, + SE, + ML, + SA, + HS, + TOL, + EJ + } + + [Flags] + public enum ClientFlags + { + None = 0x00000000, + Felucca = 0x00000001, + Trammel = 0x00000002, + Ilshenar = 0x00000004, + Malas = 0x00000008, + Tokuno = 0x00000010, + TerMur = 0x00000020, + Unk1 = 0x00000040, + Unk2 = 0x00000080, + UOTD = 0x00000100 + } + + [Flags] + public enum FeatureFlags + { + None = 0x00000000, + T2A = 0x00000001, + UOR = 0x00000002, + UOTD = 0x00000004, + LBR = 0x00000008, + AOS = 0x00000010, + SixthCharacterSlot = 0x00000020, + SE = 0x00000040, + ML = 0x00000080, + EigthAge = 0x00000100, + NinthAge = 0x00000200, /* Crystal/Shadow Custom House Tiles */ + TenthAge = 0x00000400, + IncreasedStorage = 0x00000800, /* Increased Housing/Bank Storage */ + SeventhCharacterSlot = 0x00001000, + RoleplayFaces = 0x00002000, + TrialAccount = 0x00004000, + LiveAccount = 0x00008000, + SA = 0x00010000, + HS = 0x00020000, + Gothic = 0x00040000, + Rustic = 0x00080000, + Jungle = 0x00100000, + Shadowguard = 0x00200000, + TOL = 0x00400000, + EJ = 0x00800000, + + ExpansionNone = None, + ExpansionT2A = T2A, + ExpansionUOR = ExpansionT2A | UOR, + ExpansionUOTD = ExpansionUOR | UOTD, + ExpansionLBR = ExpansionUOTD | LBR, + ExpansionAOS = ExpansionLBR | AOS | LiveAccount, + ExpansionSE = ExpansionAOS | SE, + ExpansionML = ExpansionSE | ML | NinthAge, + ExpansionSA = ExpansionML | SA | Gothic | Rustic, + ExpansionHS = ExpansionSA | HS, + ExpansionTOL = ExpansionHS | TOL | Jungle | Shadowguard, + ExpansionEJ = ExpansionTOL | EJ + } + + [Flags] + public enum CharacterListFlags + { + None = 0x00000000, + Unk1 = 0x00000001, + OverwriteConfigButton = 0x00000002, + OneCharacterSlot = 0x00000004, + ContextMenus = 0x00000008, + SlotLimit = 0x00000010, + AOS = 0x00000020, + SixthCharacterSlot = 0x00000040, + SE = 0x00000080, + ML = 0x00000100, + Unk2 = 0x00000200, + UO3DClientType = 0x00000400, + Unk3 = 0x00000800, + SeventhCharacterSlot = 0x00001000, + Unk4 = 0x00002000, + NewMovementSystem = 0x00004000, + NewFeluccaAreas = 0x00008000, + + ExpansionNone = ContextMenus, + ExpansionT2A = ContextMenus, + ExpansionUOR = ContextMenus, + ExpansionUOTD = ContextMenus, + ExpansionLBR = ContextMenus, + ExpansionAOS = ContextMenus | AOS, + ExpansionSE = ExpansionAOS | SE, + ExpansionML = ExpansionSE | ML, + ExpansionSA = ExpansionML, + ExpansionHS = ExpansionSA, + ExpansionTOL = ExpansionHS, + ExpansionEJ = ExpansionTOL + } + + [Flags] + public enum HousingFlags + { + None = 0x0, + AOS = 0x10, + SE = 0x40, + ML = 0x80, + Crystal = 0x200, + SA = 0x10000, + HS = 0x20000, + Gothic = 0x40000, + Rustic = 0x80000, + Jungle = 0x100000, + Shadowguard = 0x200000, + TOL = 0x400000, + EJ = 0x800000, + + HousingAOS = AOS, + HousingSE = HousingAOS | SE, + HousingML = HousingSE | ML | Crystal, + HousingSA = HousingML | SA | Gothic | Rustic, + HousingHS = HousingSA | HS, + HousingTOL = HousingHS | TOL | Jungle | Shadowguard, + HousingEJ = HousingTOL | EJ + } + + public class ExpansionInfo + { + static ExpansionInfo() + { + Table = new[] + { + new ExpansionInfo( + 0, + "None", + ClientFlags.None, + FeatureFlags.ExpansionNone, + CharacterListFlags.ExpansionNone, + HousingFlags.None + ), + new ExpansionInfo( + 1, + "The Second Age", + ClientFlags.Felucca, + FeatureFlags.ExpansionT2A, + CharacterListFlags.ExpansionT2A, + HousingFlags.None + ), + new ExpansionInfo( + 2, + "Renaissance", + ClientFlags.Trammel, + FeatureFlags.ExpansionUOR, + CharacterListFlags.ExpansionUOR, + HousingFlags.None + ), + new ExpansionInfo( + 3, + "Third Dawn", + ClientFlags.Ilshenar, + FeatureFlags.ExpansionUOTD, + CharacterListFlags.ExpansionUOTD, + HousingFlags.None + ), + new ExpansionInfo( + 4, + "Blackthorn's Revenge", + ClientFlags.Ilshenar, + FeatureFlags.ExpansionLBR, + CharacterListFlags.ExpansionLBR, + HousingFlags.None + ), + new ExpansionInfo( + 5, + "Age of Shadows", + ClientFlags.Malas, + FeatureFlags.ExpansionAOS, + CharacterListFlags.ExpansionAOS, + HousingFlags.HousingAOS + ), + new ExpansionInfo( + 6, + "Samurai Empire", + ClientFlags.Tokuno, + FeatureFlags.ExpansionSE, + CharacterListFlags.ExpansionSE, + HousingFlags.HousingSE + ), + new ExpansionInfo( + 7, + "Mondain's Legacy", + new ClientVersion("5.0.0a"), + FeatureFlags.ExpansionML, + CharacterListFlags.ExpansionML, + HousingFlags.HousingML + ), + new ExpansionInfo( + 8, + "Stygian Abyss", + ClientFlags.TerMur, + FeatureFlags.ExpansionSA, + CharacterListFlags.ExpansionSA, + HousingFlags.HousingSA + ), + new ExpansionInfo( + 9, + "High Seas", + new ClientVersion("7.0.9.0"), + FeatureFlags.ExpansionHS, + CharacterListFlags.ExpansionHS, + HousingFlags.HousingHS + ), + new ExpansionInfo( + 10, + "Time of Legends", + new ClientVersion("7.0.45.65"), + FeatureFlags.ExpansionTOL, + CharacterListFlags.ExpansionTOL, + HousingFlags.HousingTOL + ), + new ExpansionInfo( + 11, + "Endless Journey", + new ClientVersion("7.0.61.0"), + FeatureFlags.ExpansionEJ, + CharacterListFlags.ExpansionEJ, + HousingFlags.HousingEJ + ) + }; + } + + public ExpansionInfo( + int id, + string name, + ClientFlags clientFlags, + FeatureFlags supportedFeatures, + CharacterListFlags charListFlags, + HousingFlags customHousingFlag + ) + : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => + ClientFlags = clientFlags; + + public ExpansionInfo( + int id, + string name, + ClientVersion requiredClient, + FeatureFlags supportedFeatures, + CharacterListFlags charListFlags, + HousingFlags customHousingFlag + ) + : this(id, name, supportedFeatures, charListFlags, customHousingFlag) => + RequiredClient = requiredClient; + + private ExpansionInfo( + int id, + string name, + FeatureFlags supportedFeatures, + CharacterListFlags charListFlags, + HousingFlags customHousingFlag + ) + { + ID = id; + Name = name; + + SupportedFeatures = supportedFeatures; + CharacterListFlags = charListFlags; + CustomHousingFlag = customHousingFlag; + } + + public static ExpansionInfo CoreExpansion => GetInfo(Core.Expansion); + + public static ExpansionInfo[] Table { get; } + + public int ID { get; } + public string Name { get; set; } + + public ClientFlags ClientFlags { get; set; } + public FeatureFlags SupportedFeatures { get; set; } + public CharacterListFlags CharacterListFlags { get; set; } + public ClientVersion RequiredClient { get; set; } + public HousingFlags CustomHousingFlag { get; set; } + + public static FeatureFlags GetFeatures(Expansion ex) + { + var info = GetInfo(ex); + + if (info != null) return info.SupportedFeatures; + + return ex switch + { + Expansion.None => FeatureFlags.ExpansionNone, + Expansion.T2A => FeatureFlags.ExpansionT2A, + Expansion.UOR => FeatureFlags.ExpansionUOR, + Expansion.UOTD => FeatureFlags.ExpansionUOTD, + Expansion.LBR => FeatureFlags.ExpansionLBR, + Expansion.AOS => FeatureFlags.ExpansionAOS, + Expansion.SE => FeatureFlags.ExpansionSE, + Expansion.ML => FeatureFlags.ExpansionML, + Expansion.SA => FeatureFlags.ExpansionSA, + Expansion.HS => FeatureFlags.ExpansionHS, + Expansion.TOL => FeatureFlags.ExpansionTOL, + Expansion.EJ => FeatureFlags.EJ, + _ => FeatureFlags.ExpansionNone + }; + } + + public static ExpansionInfo GetInfo(Expansion ex) => GetInfo((int)ex); + + public static ExpansionInfo GetInfo(int ex) + { + var v = ex; + + if (v < 0 || v >= Table.Length) v = 0; + + return Table[v]; + } + + public override string ToString() => Name; + } +} diff --git a/Projects/Server/Geometry/Point2D.cs b/Projects/Server/Geometry/Point2D.cs index 49857029e..538eb426a 100644 --- a/Projects/Server/Geometry/Point2D.cs +++ b/Projects/Server/Geometry/Point2D.cs @@ -1,122 +1,123 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Point2D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server -{ - [Parsable] - public struct Point2D : - IPoint2D, IComparable, IComparable, IEquatable, IEquatable, IEquatable - { - internal int m_X; - internal int m_Y; - - public static readonly Point2D Zero = new Point2D(0, 0); - - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_X; - set => m_X = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Y; - set => m_Y = value; - } - - public Point2D(int x, int y) - { - m_X = x; - m_Y = y; - } - - public Point2D(IPoint2D p) : this(p.X, p.Y) - { - } - - public override string ToString() => $"({m_X}, {m_Y})"; - - public static Point2D Parse(string value) - { - var start = value.IndexOf('('); - var end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int x); - - start = end; - end = value.IndexOf(')', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int y); - - return new Point2D(x, y); - } - - public bool Equals(Point2D other) => m_X == other.m_X && m_Y == other.m_Y; - - public bool Equals(IPoint2D other) => - !ReferenceEquals(other, null) && m_X == other.X && m_Y == other.Y; - - public override bool Equals(object obj) => obj is Point2D other && Equals(other); - - public override int GetHashCode() => HashCode.Combine(m_X, m_Y); - - public static bool operator ==(Point2D l, Point2D r) => l.m_X == r.m_X && l.m_Y == r.m_Y; - - public static bool operator !=(Point2D l, Point2D r) => l.m_X != r.m_X || l.m_Y != r.m_Y; - - public static bool operator ==(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y; - - public static bool operator !=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y); - - public static bool operator >(Point2D l, Point2D r) => l.m_X > r.m_X && l.m_Y > r.m_Y; - - public static bool operator >(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y; - - public static bool operator <(Point2D l, Point2D r) => l.m_X < r.m_X && l.m_Y < r.m_Y; - - public static bool operator <(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y; - - public static bool operator >=(Point2D l, Point2D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y; - - public static bool operator >=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y; - - public static bool operator <=(Point2D l, Point2D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y; - - public static bool operator <=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y; - - public int CompareTo(Point2D other) - { - var xComparison = m_X.CompareTo(other.m_X); - if (xComparison != 0) return xComparison; - return m_Y.CompareTo(other.m_Y); - } - - public int CompareTo(IPoint2D other) - { - var xComparison = m_X.CompareTo(other.X); - if (xComparison != 0) return xComparison; - return m_Y.CompareTo(other.Y); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Point2D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server +{ + [Parsable] + public struct Point2D + : IPoint2D, IComparable, IComparable, IEquatable, IEquatable, + IEquatable + { + internal int m_X; + internal int m_Y; + + public static readonly Point2D Zero = new Point2D(0, 0); + + [CommandProperty(AccessLevel.Counselor)] + public int X + { + get => m_X; + set => m_X = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Y; + set => m_Y = value; + } + + public Point2D(int x, int y) + { + m_X = x; + m_Y = y; + } + + public Point2D(IPoint2D p) : this(p.X, p.Y) + { + } + + public override string ToString() => $"({m_X}, {m_Y})"; + + public static Point2D Parse(string value) + { + var start = value.IndexOf('('); + var end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x); + + start = end; + end = value.IndexOf(')', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var y); + + return new Point2D(x, y); + } + + public bool Equals(Point2D other) => m_X == other.m_X && m_Y == other.m_Y; + + public bool Equals(IPoint2D other) => + !ReferenceEquals(other, null) && m_X == other.X && m_Y == other.Y; + + public override bool Equals(object obj) => obj is Point2D other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(m_X, m_Y); + + public static bool operator ==(Point2D l, Point2D r) => l.m_X == r.m_X && l.m_Y == r.m_Y; + + public static bool operator !=(Point2D l, Point2D r) => l.m_X != r.m_X || l.m_Y != r.m_Y; + + public static bool operator ==(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y; + + public static bool operator !=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y); + + public static bool operator >(Point2D l, Point2D r) => l.m_X > r.m_X && l.m_Y > r.m_Y; + + public static bool operator >(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y; + + public static bool operator <(Point2D l, Point2D r) => l.m_X < r.m_X && l.m_Y < r.m_Y; + + public static bool operator <(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y; + + public static bool operator >=(Point2D l, Point2D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y; + + public static bool operator >=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y; + + public static bool operator <=(Point2D l, Point2D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y; + + public static bool operator <=(Point2D l, IPoint2D r) => !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y; + + public int CompareTo(Point2D other) + { + var xComparison = m_X.CompareTo(other.m_X); + if (xComparison != 0) return xComparison; + return m_Y.CompareTo(other.m_Y); + } + + public int CompareTo(IPoint2D other) + { + var xComparison = m_X.CompareTo(other.X); + if (xComparison != 0) return xComparison; + return m_Y.CompareTo(other.Y); + } + } +} diff --git a/Projects/Server/Geometry/Point3D.cs b/Projects/Server/Geometry/Point3D.cs index 7864c6c89..bf524be54 100644 --- a/Projects/Server/Geometry/Point3D.cs +++ b/Projects/Server/Geometry/Point3D.cs @@ -1,150 +1,151 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Point3D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server -{ - [Parsable] - public struct Point3D : - IPoint3D, IComparable, IComparable, IEquatable, IEquatable, IEquatable - { - internal int m_X; - internal int m_Y; - internal int m_Z; - - public static readonly Point3D Zero = new Point3D(0, 0, 0); - - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_X; - set => m_X = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Y; - set => m_Y = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Z - { - get => m_Z; - set => m_Z = value; - } - - public Point3D(IPoint3D p) : this(p.X, p.Y, p.Z) - { - } - - public Point3D(IPoint2D p, int z) : this(p.X, p.Y, z) - { - } - - public Point3D(int x, int y, int z) - { - m_X = x; - m_Y = y; - m_Z = z; - } - - public override string ToString() => $"({m_X}, {m_Y}, {m_Z})"; - - public bool Equals(Point3D other) => m_X == other.m_X && m_Y == other.m_Y && m_Z == other.m_Z; - - public bool Equals(IPoint3D other) => - !ReferenceEquals(other, null) && m_X == other.X && m_Y == other.Y && m_Z == other.Z; - - public override bool Equals(object obj) => obj is Point3D other && Equals(other); - - public override int GetHashCode() => HashCode.Combine(m_X, m_Y, m_Z); - - public static Point3D Parse(string value) - { - var start = value.IndexOf('('); - var end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int x); - - start = end; - end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int y); - - start = end; - end = value.IndexOf(')', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int z); - - return new Point3D(x, y, z); - } - - public static bool operator ==(Point3D l, Point3D r) => l.m_X == r.m_X && l.m_Y == r.m_Y && l.m_Z == r.m_Z; - - public static bool operator ==(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y && l.m_Z == r.Z; - - public static bool operator !=(Point3D l, Point3D r) => l.m_X != r.m_X || l.m_Y != r.m_Y || l.m_Z != r.m_Z; - - public static bool operator !=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y || l.m_Z != r.Z); - - public static bool operator >(Point3D l, Point3D r) => l.m_X > r.m_X && l.m_Y > r.m_Y && l.m_Z > r.m_Z; - - public static bool operator >(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y && l.m_Z > r.Z; - - public static bool operator <(Point3D l, Point3D r) => l.m_X < r.m_X && l.m_Y < r.m_Y && l.m_Z > r.m_Z; - - public static bool operator <(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y && l.m_Z > r.Z; - - public static bool operator >=(Point3D l, Point3D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y && l.m_Z > r.m_Z; - - public static bool operator >=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y && l.m_Z > r.Z; - - public static bool operator <=(Point3D l, Point3D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y && l.m_Z > r.m_Z; - - public static bool operator <=(Point3D l, IPoint3D r) => - !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y && l.m_Z > r.Z; - - public int CompareTo(Point3D other) - { - var xComparison = m_X.CompareTo(other.m_X); - if (xComparison != 0) return xComparison; - var yComparison = m_Y.CompareTo(other.m_Y); - if (yComparison != 0) return yComparison; - return m_Z.CompareTo(other.m_Z); - } - - public int CompareTo(IPoint3D other) - { - var xComparison = m_X.CompareTo(other.X); - if (xComparison != 0) return xComparison; - var yComparison = m_Y.CompareTo(other.Y); - if (yComparison != 0) return yComparison; - return m_Z.CompareTo(other.Z); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Point3D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server +{ + [Parsable] + public struct Point3D + : IPoint3D, IComparable, IComparable, IEquatable, IEquatable, + IEquatable + { + internal int m_X; + internal int m_Y; + internal int m_Z; + + public static readonly Point3D Zero = new Point3D(0, 0, 0); + + [CommandProperty(AccessLevel.Counselor)] + public int X + { + get => m_X; + set => m_X = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Y; + set => m_Y = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Z + { + get => m_Z; + set => m_Z = value; + } + + public Point3D(IPoint3D p) : this(p.X, p.Y, p.Z) + { + } + + public Point3D(IPoint2D p, int z) : this(p.X, p.Y, z) + { + } + + public Point3D(int x, int y, int z) + { + m_X = x; + m_Y = y; + m_Z = z; + } + + public override string ToString() => $"({m_X}, {m_Y}, {m_Z})"; + + public bool Equals(Point3D other) => m_X == other.m_X && m_Y == other.m_Y && m_Z == other.m_Z; + + public bool Equals(IPoint3D other) => + !ReferenceEquals(other, null) && m_X == other.X && m_Y == other.Y && m_Z == other.Z; + + public override bool Equals(object obj) => obj is Point3D other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(m_X, m_Y, m_Z); + + public static Point3D Parse(string value) + { + var start = value.IndexOf('('); + var end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x); + + start = end; + end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var y); + + start = end; + end = value.IndexOf(')', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var z); + + return new Point3D(x, y, z); + } + + public static bool operator ==(Point3D l, Point3D r) => l.m_X == r.m_X && l.m_Y == r.m_Y && l.m_Z == r.m_Z; + + public static bool operator ==(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X == r.X && l.m_Y == r.Y && l.m_Z == r.Z; + + public static bool operator !=(Point3D l, Point3D r) => l.m_X != r.m_X || l.m_Y != r.m_Y || l.m_Z != r.m_Z; + + public static bool operator !=(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && (l.m_X != r.X || l.m_Y != r.Y || l.m_Z != r.Z); + + public static bool operator >(Point3D l, Point3D r) => l.m_X > r.m_X && l.m_Y > r.m_Y && l.m_Z > r.m_Z; + + public static bool operator >(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X > r.X && l.m_Y > r.Y && l.m_Z > r.Z; + + public static bool operator <(Point3D l, Point3D r) => l.m_X < r.m_X && l.m_Y < r.m_Y && l.m_Z > r.m_Z; + + public static bool operator <(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X < r.X && l.m_Y < r.Y && l.m_Z > r.Z; + + public static bool operator >=(Point3D l, Point3D r) => l.m_X >= r.m_X && l.m_Y >= r.m_Y && l.m_Z > r.m_Z; + + public static bool operator >=(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X >= r.X && l.m_Y >= r.Y && l.m_Z > r.Z; + + public static bool operator <=(Point3D l, Point3D r) => l.m_X <= r.m_X && l.m_Y <= r.m_Y && l.m_Z > r.m_Z; + + public static bool operator <=(Point3D l, IPoint3D r) => + !ReferenceEquals(r, null) && l.m_X <= r.X && l.m_Y <= r.Y && l.m_Z > r.Z; + + public int CompareTo(Point3D other) + { + var xComparison = m_X.CompareTo(other.m_X); + if (xComparison != 0) return xComparison; + var yComparison = m_Y.CompareTo(other.m_Y); + if (yComparison != 0) return yComparison; + return m_Z.CompareTo(other.m_Z); + } + + public int CompareTo(IPoint3D other) + { + var xComparison = m_X.CompareTo(other.X); + if (xComparison != 0) return xComparison; + var yComparison = m_Y.CompareTo(other.Y); + if (yComparison != 0) return yComparison; + return m_Z.CompareTo(other.Z); + } + } +} diff --git a/Projects/Server/Geometry/Point3DList.cs b/Projects/Server/Geometry/Point3DList.cs index 4525f75be..784290ec0 100644 --- a/Projects/Server/Geometry/Point3DList.cs +++ b/Projects/Server/Geometry/Point3DList.cs @@ -1,94 +1,96 @@ -/*************************************************************************** - * Point3DList.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server -{ - public class Point3DList - { - private static readonly Point3D[] m_EmptyList = System.Array.Empty(); - private Point3D[] m_List; - - public Point3DList() - { - m_List = new Point3D[8]; - Count = 0; - } - - public int Count { get; private set; } - - public Point3D Last => m_List[Count - 1]; - - public Point3D this[int index] => m_List[index]; - - public void Clear() - { - Count = 0; - } - - public void Add(int x, int y, int z) - { - if (Count + 1 > m_List.Length) - { - var old = m_List; - m_List = new Point3D[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - m_List[i] = old[i]; - } - - m_List[Count].m_X = x; - m_List[Count].m_Y = y; - m_List[Count].m_Z = z; - ++Count; - } - - public void Add(Point3D p) - { - if (Count + 1 > m_List.Length) - { - var old = m_List; - m_List = new Point3D[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - m_List[i] = old[i]; - } - - m_List[Count].m_X = p.m_X; - m_List[Count].m_Y = p.m_Y; - m_List[Count].m_Z = p.m_Z; - ++Count; - } - - public Point3D[] ToArray() - { - if (Count == 0) - return m_EmptyList; - - var list = new Point3D[Count]; - - for (var i = 0; i < Count; ++i) - list[i] = m_List[i]; - - Count = 0; - - return list; - } - } -} +/*************************************************************************** + * Point3DList.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server +{ + public class Point3DList + { + private static readonly Point3D[] m_EmptyList = Array.Empty(); + private Point3D[] m_List; + + public Point3DList() + { + m_List = new Point3D[8]; + Count = 0; + } + + public int Count { get; private set; } + + public Point3D Last => m_List[Count - 1]; + + public Point3D this[int index] => m_List[index]; + + public void Clear() + { + Count = 0; + } + + public void Add(int x, int y, int z) + { + if (Count + 1 > m_List.Length) + { + var old = m_List; + m_List = new Point3D[old.Length * 2]; + + for (var i = 0; i < old.Length; ++i) + m_List[i] = old[i]; + } + + m_List[Count].m_X = x; + m_List[Count].m_Y = y; + m_List[Count].m_Z = z; + ++Count; + } + + public void Add(Point3D p) + { + if (Count + 1 > m_List.Length) + { + var old = m_List; + m_List = new Point3D[old.Length * 2]; + + for (var i = 0; i < old.Length; ++i) + m_List[i] = old[i]; + } + + m_List[Count].m_X = p.m_X; + m_List[Count].m_Y = p.m_Y; + m_List[Count].m_Z = p.m_Z; + ++Count; + } + + public Point3D[] ToArray() + { + if (Count == 0) + return m_EmptyList; + + var list = new Point3D[Count]; + + for (var i = 0; i < Count; ++i) + list[i] = m_List[i]; + + Count = 0; + + return list; + } + } +} diff --git a/Projects/Server/Geometry/Rectangle2D.cs b/Projects/Server/Geometry/Rectangle2D.cs index d73bec620..dc380c964 100644 --- a/Projects/Server/Geometry/Rectangle2D.cs +++ b/Projects/Server/Geometry/Rectangle2D.cs @@ -1,141 +1,141 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Rectangle2D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server -{ - [NoSort] - [Parsable] - [PropertyObject] - public struct Rectangle2D - { - private Point2D m_Start; - private Point2D m_End; - - public Rectangle2D(IPoint2D start, IPoint2D end) - { - m_Start = new Point2D(start); - m_End = new Point2D(end); - } - - public Rectangle2D(int x, int y, int width, int height) - { - m_Start = new Point2D(x, y); - m_End = new Point2D(x + width, y + height); - } - - public void Set(int x, int y, int width, int height) - { - m_Start = new Point2D(x, y); - m_End = new Point2D(x + width, y + height); - } - - public static Rectangle2D Parse(string value) - { - var start = value.IndexOf('('); - var end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int x); - - start = end; - end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int y); - - start = end; - end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int w); - - start = end; - end = value.IndexOf(')', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int h); - - return new Rectangle2D(x, y, w, h); - } - - [CommandProperty(AccessLevel.Counselor)] - public Point2D Start - { - get => m_Start; - set => m_Start = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public Point2D End - { - get => m_End; - set => m_End = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_Start.m_X; - set => m_Start.m_X = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Start.m_Y; - set => m_Start.m_Y = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Width - { - get => m_End.m_X - m_Start.m_X; - set => m_End.m_X = m_Start.m_X + value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Height - { - get => m_End.m_Y - m_Start.m_Y; - set => m_End.m_Y = m_Start.m_Y + value; - } - - public void MakeHold(Rectangle2D r) - { - if (r.m_Start.m_X < m_Start.m_X) - m_Start.m_X = r.m_Start.m_X; - - if (r.m_Start.m_Y < m_Start.m_Y) - m_Start.m_Y = r.m_Start.m_Y; - - if (r.m_End.m_X > m_End.m_X) - m_End.m_X = r.m_End.m_X; - - if (r.m_End.m_Y > m_End.m_Y) - m_End.m_Y = r.m_End.m_Y; - } - - public bool Contains(Point3D p) => - m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y; - - public bool Contains(Point2D p) => - m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y; - - public bool Contains(IPoint2D p) => m_Start <= p && m_End > p; - - public override string ToString() => $"({X}, {Y})+({Width}, {Height})"; - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Rectangle2D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server +{ + [NoSort] + [Parsable] + [PropertyObject] + public struct Rectangle2D + { + private Point2D m_Start; + private Point2D m_End; + + public Rectangle2D(IPoint2D start, IPoint2D end) + { + m_Start = new Point2D(start); + m_End = new Point2D(end); + } + + public Rectangle2D(int x, int y, int width, int height) + { + m_Start = new Point2D(x, y); + m_End = new Point2D(x + width, y + height); + } + + public void Set(int x, int y, int width, int height) + { + m_Start = new Point2D(x, y); + m_End = new Point2D(x + width, y + height); + } + + public static Rectangle2D Parse(string value) + { + var start = value.IndexOf('('); + var end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x); + + start = end; + end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var y); + + start = end; + end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var w); + + start = end; + end = value.IndexOf(')', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var h); + + return new Rectangle2D(x, y, w, h); + } + + [CommandProperty(AccessLevel.Counselor)] + public Point2D Start + { + get => m_Start; + set => m_Start = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public Point2D End + { + get => m_End; + set => m_End = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int X + { + get => m_Start.m_X; + set => m_Start.m_X = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Start.m_Y; + set => m_Start.m_Y = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Width + { + get => m_End.m_X - m_Start.m_X; + set => m_End.m_X = m_Start.m_X + value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Height + { + get => m_End.m_Y - m_Start.m_Y; + set => m_End.m_Y = m_Start.m_Y + value; + } + + public void MakeHold(Rectangle2D r) + { + if (r.m_Start.m_X < m_Start.m_X) + m_Start.m_X = r.m_Start.m_X; + + if (r.m_Start.m_Y < m_Start.m_Y) + m_Start.m_Y = r.m_Start.m_Y; + + if (r.m_End.m_X > m_End.m_X) + m_End.m_X = r.m_End.m_X; + + if (r.m_End.m_Y > m_End.m_Y) + m_End.m_Y = r.m_End.m_Y; + } + + public bool Contains(Point3D p) => + m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y; + + public bool Contains(Point2D p) => + m_Start.m_X <= p.m_X && m_Start.m_Y <= p.m_Y && m_End.m_X > p.m_X && m_End.m_Y > p.m_Y; + + public bool Contains(IPoint2D p) => m_Start <= p && m_End > p; + + public override string ToString() => $"({X}, {Y})+({Width}, {Height})"; + } +} diff --git a/Projects/Server/Geometry/Rectangle3D.cs b/Projects/Server/Geometry/Rectangle3D.cs index db9b42572..445db7c10 100644 --- a/Projects/Server/Geometry/Rectangle3D.cs +++ b/Projects/Server/Geometry/Rectangle3D.cs @@ -1,123 +1,123 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Rectangle3D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server -{ - [NoSort] - [PropertyObject] - public struct Rectangle3D - { - private Point3D m_Start; - private Point3D m_End; - - public Rectangle3D(Point3D start, Point3D end) - { - m_Start = start; - m_End = end; - } - - public Rectangle3D(int x, int y, int z, int width, int height, int depth) - { - m_Start = new Point3D(x, y, z); - m_End = new Point3D(x + width, y + height, z + depth); - } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D Start - { - get => m_Start; - set => m_Start = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D End - { - get => m_End; - set => m_End = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_Start.m_X; - set => m_Start.m_X = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Start.m_Y; - set => m_Start.m_Y = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Z - { - get => m_Start.m_Z; - set => m_Start.m_Z = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Width => m_End.X - m_Start.X; - - [CommandProperty(AccessLevel.Counselor)] - public int Height => m_End.Y - m_Start.Y; - - [CommandProperty(AccessLevel.Counselor)] - public int Depth => m_End.Z - m_Start.Z; - - public void MakeHold(Rectangle3D r) - { - if (r.m_Start.m_X < m_Start.m_X) - m_Start.m_X = r.m_Start.m_X; - - if (r.m_Start.m_Y < m_Start.m_Y) - m_Start.m_Y = r.m_Start.m_Y; - - if (r.m_Start.m_Z < m_Start.m_Z) - m_Start.m_Z = r.m_Start.m_Z; - - if (r.m_End.m_X > m_End.m_X) - m_End.m_X = r.m_End.m_X; - - if (r.m_End.m_Y > m_End.m_Y) - m_End.m_Y = r.m_End.m_Y; - - if (r.m_End.m_Z < m_End.m_Z) - m_End.m_Z = r.m_End.m_Z; - } - - public bool Contains(Point3D p) => - p.m_X >= m_Start.m_X - && p.m_X < m_End.m_X - && p.m_Y >= m_Start.m_Y - && p.m_Y < m_End.m_Y - && p.m_Z >= m_Start.m_Z - && p.m_Z < m_End.m_Z; - - public bool Contains(IPoint3D p) => - p.X >= m_Start.m_X - && p.X < m_End.m_X - && p.Y >= m_Start.m_Y - && p.Y < m_End.m_Y - && p.Z >= m_Start.m_Z - && p.Z < m_End.m_Z; - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Rectangle3D.cs - Created: 2020/05/31 - Updated: 2020/05/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server +{ + [NoSort] + [PropertyObject] + public struct Rectangle3D + { + private Point3D m_Start; + private Point3D m_End; + + public Rectangle3D(Point3D start, Point3D end) + { + m_Start = start; + m_End = end; + } + + public Rectangle3D(int x, int y, int z, int width, int height, int depth) + { + m_Start = new Point3D(x, y, z); + m_End = new Point3D(x + width, y + height, z + depth); + } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Start + { + get => m_Start; + set => m_Start = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D End + { + get => m_End; + set => m_End = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int X + { + get => m_Start.m_X; + set => m_Start.m_X = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Start.m_Y; + set => m_Start.m_Y = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Z + { + get => m_Start.m_Z; + set => m_Start.m_Z = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Width => m_End.X - m_Start.X; + + [CommandProperty(AccessLevel.Counselor)] + public int Height => m_End.Y - m_Start.Y; + + [CommandProperty(AccessLevel.Counselor)] + public int Depth => m_End.Z - m_Start.Z; + + public void MakeHold(Rectangle3D r) + { + if (r.m_Start.m_X < m_Start.m_X) + m_Start.m_X = r.m_Start.m_X; + + if (r.m_Start.m_Y < m_Start.m_Y) + m_Start.m_Y = r.m_Start.m_Y; + + if (r.m_Start.m_Z < m_Start.m_Z) + m_Start.m_Z = r.m_Start.m_Z; + + if (r.m_End.m_X > m_End.m_X) + m_End.m_X = r.m_End.m_X; + + if (r.m_End.m_Y > m_End.m_Y) + m_End.m_Y = r.m_End.m_Y; + + if (r.m_End.m_Z < m_End.m_Z) + m_End.m_Z = r.m_End.m_Z; + } + + public bool Contains(Point3D p) => + p.m_X >= m_Start.m_X + && p.m_X < m_End.m_X + && p.m_Y >= m_Start.m_Y + && p.m_Y < m_End.m_Y + && p.m_Z >= m_Start.m_Z + && p.m_Z < m_End.m_Z; + + public bool Contains(IPoint3D p) => + p.X >= m_Start.m_X + && p.X < m_End.m_X + && p.Y >= m_Start.m_Y + && p.Y < m_End.m_Y + && p.Z >= m_Start.m_Z + && p.Z < m_End.m_Z; + } +} diff --git a/Projects/Server/Geometry/WorldLocation.cs b/Projects/Server/Geometry/WorldLocation.cs index e80ba23ff..e68ed916c 100644 --- a/Projects/Server/Geometry/WorldLocation.cs +++ b/Projects/Server/Geometry/WorldLocation.cs @@ -1,173 +1,174 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: WorldLocation.cs - Created: 2020/05/31 - Updated: 2020/05/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server -{ - [Parsable] - public struct WorldLocation : IPoint3D, IComparable, IEquatable, IEquatable, IEquatable - { - internal Point3D m_Loc; - internal Map m_Map; - - public static readonly WorldLocation Zero = new WorldLocation(0, 0, 0, Map.Internal); - - [CommandProperty(AccessLevel.Counselor)] - public Point3D Location - { - get => m_Loc; - set => m_Loc = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int X - { - get => m_Loc.m_X; - set => m_Loc.m_X = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Y - { - get => m_Loc.m_Y; - set => m_Loc.m_Y = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public int Z - { - get => m_Loc.m_Z; - set => m_Loc.m_Z = value; - } - - [CommandProperty(AccessLevel.Counselor)] - public Map Map - { - get => m_Map; - set => m_Map = value; - } - - public WorldLocation(IEntity e) : this(e.Location.X, e.Location.Y, e.Location.Z, e.Map) - { - } - - public WorldLocation(IPoint2D p, Map map) : this(p.X, p.Y, 0, map) - { - } - - public WorldLocation(int x, int y, Map map) : this(x, y, 0, map) - { - } - - public WorldLocation(IPoint3D p, Map map) : this(p.X, p.Y, p.Z, map) - { - } - - public WorldLocation(int x, int y, int z, Map map) - { - m_Loc.m_X = x; - m_Loc.m_Y = y; - m_Loc.m_Z = z; - m_Map = map; - } - - public override string ToString() => - $"({m_Loc.m_X}, {m_Loc.m_Y}, {m_Loc.m_Z}, {m_Map?.ToString() ?? "(-null-)"})"; - - public bool Equals(WorldLocation other) => - m_Loc.Equals(other.m_Loc) && m_Map.MapID == other.m_Map.MapID; - - public bool Equals(IEntity other) => - !ReferenceEquals(other, null) && m_Loc == other.Location && - m_Map.MapID == other.Map.MapID; - - public override bool Equals(object obj) => - obj is WorldLocation other && Equals(other); - - public override int GetHashCode() => HashCode.Combine(m_Loc, m_Map); - - public int CompareTo(WorldLocation other) - { - var locComparison = m_Loc.CompareTo(other.m_Loc); - if (locComparison != 0) return locComparison; - return Comparer.Default.Compare(m_Map, other.m_Map); - } - - public static implicit operator Point3D(WorldLocation worldLocation) => worldLocation.Location; - - public static bool operator ==(WorldLocation l, WorldLocation r) => - l.m_Loc == r.m_Loc && l.m_Map == r.m_Map; - - public static bool operator ==(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc == r.Location && l.m_Map == r.Map; - - public static bool operator !=(WorldLocation l, WorldLocation r) => l.m_Loc != r.m_Loc && l.m_Map != r.m_Map; - - public static bool operator !=(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc != r.Location && l.m_Map != r.Map; - - public static bool operator >(WorldLocation l, WorldLocation r) => l.m_Loc > r.m_Loc && l.m_Map == r.m_Map; - - public static bool operator >(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc > r.Location && l.m_Map == r.Map; - - public static bool operator <(WorldLocation l, WorldLocation r) => l.m_Loc < r.m_Loc && l.m_Map == r.m_Map; - - public static bool operator <(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc < r.Location && l.m_Map == r.Map; - - public static bool operator >=(WorldLocation l, WorldLocation r) => l.m_Loc >= r.m_Loc && l.m_Map == r.m_Map; - - public static bool operator >=(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc >= r.Location && l.m_Map == r.Map; - - public static bool operator <=(WorldLocation l, WorldLocation r) => l.m_Loc <= r.m_Loc && l.m_Map == r.m_Map; - - public static bool operator <=(WorldLocation l, IEntity r) => - !ReferenceEquals(r, null) && l.m_Loc <= r.Location && l.m_Map == r.Map; - - public static WorldLocation Parse(string value) - { - var start = value.IndexOf('('); - var end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int x); - - start = end; - end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int y); - - start = end; - end = value.IndexOf(',', start + 1); - - Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out int z); - - start = end; - end = value.IndexOf(')', start + 1); - - var map = Map.Parse(value.Substring(start + 1, end - (start + 1)).Trim()); - - return new WorldLocation(x, y, z, map); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: WorldLocation.cs - Created: 2020/05/31 - Updated: 2020/05/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server +{ + [Parsable] + public struct WorldLocation + : IPoint3D, IComparable, IEquatable, IEquatable, IEquatable + { + internal Point3D m_Loc; + internal Map m_Map; + + public static readonly WorldLocation Zero = new WorldLocation(0, 0, 0, Map.Internal); + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Location + { + get => m_Loc; + set => m_Loc = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int X + { + get => m_Loc.m_X; + set => m_Loc.m_X = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Y + { + get => m_Loc.m_Y; + set => m_Loc.m_Y = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public int Z + { + get => m_Loc.m_Z; + set => m_Loc.m_Z = value; + } + + [CommandProperty(AccessLevel.Counselor)] + public Map Map + { + get => m_Map; + set => m_Map = value; + } + + public WorldLocation(IEntity e) : this(e.Location.X, e.Location.Y, e.Location.Z, e.Map) + { + } + + public WorldLocation(IPoint2D p, Map map) : this(p.X, p.Y, 0, map) + { + } + + public WorldLocation(int x, int y, Map map) : this(x, y, 0, map) + { + } + + public WorldLocation(IPoint3D p, Map map) : this(p.X, p.Y, p.Z, map) + { + } + + public WorldLocation(int x, int y, int z, Map map) + { + m_Loc.m_X = x; + m_Loc.m_Y = y; + m_Loc.m_Z = z; + m_Map = map; + } + + public override string ToString() => + $"({m_Loc.m_X}, {m_Loc.m_Y}, {m_Loc.m_Z}, {m_Map?.ToString() ?? "(-null-)"})"; + + public bool Equals(WorldLocation other) => + m_Loc.Equals(other.m_Loc) && m_Map.MapID == other.m_Map.MapID; + + public bool Equals(IEntity other) => + !ReferenceEquals(other, null) && m_Loc == other.Location && + m_Map.MapID == other.Map.MapID; + + public override bool Equals(object obj) => + obj is WorldLocation other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(m_Loc, m_Map); + + public int CompareTo(WorldLocation other) + { + var locComparison = m_Loc.CompareTo(other.m_Loc); + if (locComparison != 0) return locComparison; + return Comparer.Default.Compare(m_Map, other.m_Map); + } + + public static implicit operator Point3D(WorldLocation worldLocation) => worldLocation.Location; + + public static bool operator ==(WorldLocation l, WorldLocation r) => + l.m_Loc == r.m_Loc && l.m_Map == r.m_Map; + + public static bool operator ==(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc == r.Location && l.m_Map == r.Map; + + public static bool operator !=(WorldLocation l, WorldLocation r) => l.m_Loc != r.m_Loc && l.m_Map != r.m_Map; + + public static bool operator !=(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc != r.Location && l.m_Map != r.Map; + + public static bool operator >(WorldLocation l, WorldLocation r) => l.m_Loc > r.m_Loc && l.m_Map == r.m_Map; + + public static bool operator >(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc > r.Location && l.m_Map == r.Map; + + public static bool operator <(WorldLocation l, WorldLocation r) => l.m_Loc < r.m_Loc && l.m_Map == r.m_Map; + + public static bool operator <(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc < r.Location && l.m_Map == r.Map; + + public static bool operator >=(WorldLocation l, WorldLocation r) => l.m_Loc >= r.m_Loc && l.m_Map == r.m_Map; + + public static bool operator >=(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc >= r.Location && l.m_Map == r.Map; + + public static bool operator <=(WorldLocation l, WorldLocation r) => l.m_Loc <= r.m_Loc && l.m_Map == r.m_Map; + + public static bool operator <=(WorldLocation l, IEntity r) => + !ReferenceEquals(r, null) && l.m_Loc <= r.Location && l.m_Map == r.Map; + + public static WorldLocation Parse(string value) + { + var start = value.IndexOf('('); + var end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x); + + start = end; + end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var y); + + start = end; + end = value.IndexOf(',', start + 1); + + Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var z); + + start = end; + end = value.IndexOf(')', start + 1); + + var map = Map.Parse(value.Substring(start + 1, end - (start + 1)).Trim()); + + return new WorldLocation(x, y, z, map); + } + } +} diff --git a/Projects/Server/Guild.cs b/Projects/Server/Guild.cs index 09ae92439..b80878950 100644 --- a/Projects/Server/Guild.cs +++ b/Projects/Server/Guild.cs @@ -1,108 +1,106 @@ -/*************************************************************************** - * Guild.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.Collections.Generic; -using System.Linq; - -namespace Server.Guilds -{ - public enum GuildType - { - Regular, - Chaos, - Order - } - - public abstract class BaseGuild : ISerializable - { - private readonly BufferWriter m_SaveBuffer; - public BufferWriter SaveBuffer => m_SaveBuffer; - - private static Serial m_NextID = 1; - - protected BaseGuild(uint id) // serialization ctor - { - Serial = id; - List.Add(Serial, this); - if (Serial + 1 > m_NextID) - m_NextID = Serial + 1; - m_SaveBuffer = new BufferWriter(true); - } - - protected BaseGuild() - { - Serial = m_NextID++; - List.Add(Serial, this); - m_SaveBuffer = new BufferWriter(true); - } - - [CommandProperty(AccessLevel.Counselor)] - public Serial Serial { get; } - - public abstract string Abbreviation { get; set; } - public abstract string Name { get; set; } - public abstract GuildType Type { get; set; } - public abstract bool Disbanded { get; } - - public static Dictionary List { get; } = new Dictionary(); - - public int TypeRef => 0; - - public void Serialize() - { - SaveBuffer.Flush(); - Serialize(SaveBuffer); - } - - public abstract void Serialize(IGenericWriter writer); - - public abstract void Deserialize(IGenericReader reader); - public abstract void OnDelete(Mobile mob); - - public static BaseGuild Find(uint id) - { - List.TryGetValue(id, out var g); - - return g; - } - - public static BaseGuild FindByName(string name) => List.Values.FirstOrDefault(g => g.Name == name); - - public static BaseGuild FindByAbbrev(string abbr) => List.Values.FirstOrDefault(g => g.Abbreviation == abbr); - - public static List Search(string find) - { - var words = find.ToLower().Split(' '); - var results = new List(); - - foreach (var g in List.Values) - { - var name = g.Name.ToLower(); - - if (words.All(t => name.IndexOf(t) != -1)) - results.Add(g); - } - - return results; - } - - public override string ToString() => $"0x{Serial:X} \"{Name} [{Abbreviation}]\""; - } -} +/*************************************************************************** + * Guild.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.Collections.Generic; +using System.Linq; + +namespace Server.Guilds +{ + public enum GuildType + { + Regular, + Chaos, + Order + } + + public abstract class BaseGuild : ISerializable + { + private static Serial m_NextID = 1; + + protected BaseGuild(uint id) // serialization ctor + { + Serial = id; + List.Add(Serial, this); + if (Serial + 1 > m_NextID) + m_NextID = Serial + 1; + SaveBuffer = new BufferWriter(true); + } + + protected BaseGuild() + { + Serial = m_NextID++; + List.Add(Serial, this); + SaveBuffer = new BufferWriter(true); + } + + public abstract string Abbreviation { get; set; } + public abstract string Name { get; set; } + public abstract GuildType Type { get; set; } + public abstract bool Disbanded { get; } + + public static Dictionary List { get; } = new Dictionary(); + public BufferWriter SaveBuffer { get; } + + [CommandProperty(AccessLevel.Counselor)] + public Serial Serial { get; } + + public int TypeRef => 0; + + public void Serialize() + { + SaveBuffer.Flush(); + Serialize(SaveBuffer); + } + + public abstract void Serialize(IGenericWriter writer); + + public abstract void Deserialize(IGenericReader reader); + public abstract void OnDelete(Mobile mob); + + public static BaseGuild Find(uint id) + { + List.TryGetValue(id, out var g); + + return g; + } + + public static BaseGuild FindByName(string name) => List.Values.FirstOrDefault(g => g.Name == name); + + public static BaseGuild FindByAbbrev(string abbr) => List.Values.FirstOrDefault(g => g.Abbreviation == abbr); + + public static List Search(string find) + { + var words = find.ToLower().Split(' '); + var results = new List(); + + foreach (var g in List.Values) + { + var name = g.Name.ToLower(); + + if (words.All(t => name.IndexOf(t) != -1)) + results.Add(g); + } + + return results; + } + + public override string ToString() => $"0x{Serial:X} \"{Name} [{Abbreviation}]\""; + } +} diff --git a/Projects/Server/Gumps/Gump.cs b/Projects/Server/Gumps/Gump.cs index d060a9e03..9916492fa 100644 --- a/Projects/Server/Gumps/Gump.cs +++ b/Projects/Server/Gumps/Gump.cs @@ -1,291 +1,317 @@ -/*************************************************************************** - * Gump.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Text; -using Server.Network; - -namespace Server.Gumps -{ - public class Gump - { - private static uint m_NextSerial = 1; - - private static readonly byte[] m_BeginLayout = StringToBuffer("{ "); - private static readonly byte[] m_EndLayout = StringToBuffer(" }"); - - private static readonly byte[] m_NoMove = StringToBuffer("{ nomove }"); - private static readonly byte[] m_NoClose = StringToBuffer("{ noclose }"); - private static readonly byte[] m_NoDispose = StringToBuffer("{ nodispose }"); - private static readonly byte[] m_NoResize = StringToBuffer("{ noresize }"); - - public List Strings { get; } - - internal int m_TextEntries, m_Switches; - - public Gump(int x, int y) - { - do - { - Serial = m_NextSerial++; - } while (Serial == 0); // standard client apparently doesn't send a gump response packet if serial == 0 - - X = x; - Y = y; - - TypeID = GetTypeID(GetType()); - - Entries = new List(); - Strings = new List(); - } - - public int TypeID { get; } - - public List Entries { get; } - - public uint Serial { get; set; } - - public int X { get; set; } - - public int Y { get; set; } - - public bool Disposable { get; set; } = true; - - public bool Resizable { get; set; } = true; - - public bool Draggable { get; set; } = true; - - public bool Closable { get; set; } = true; - - public static int GetTypeID(Type type) => type?.FullName?.GetHashCode() ?? -1; - - public void AddPage(int page) - { - Add(new GumpPage(page)); - } - - public void AddAlphaRegion(int x, int y, int width, int height) - { - Add(new GumpAlphaRegion(x, y, width, height)); - } - - public void AddBackground(int x, int y, int width, int height, int gumpID) - { - Add(new GumpBackground(x, y, width, height, gumpID)); - } - - public void AddButton(int x, int y, int normalID, int pressedID, int buttonID, - GumpButtonType type = GumpButtonType.Reply, int param = 0) - { - Add(new GumpButton(x, y, normalID, pressedID, buttonID, type, param)); - } - - public void AddCheck(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) - { - Add(new GumpCheck(x, y, inactiveID, activeID, initialState, switchID)); - } - - public void AddGroup(int group) - { - Add(new GumpGroup(group)); - } - - public void AddTooltip(int number, string args = null) - { - Add(new GumpTooltip(number, args)); - } - - public void AddHtml(int x, int y, int width, int height, string text, bool background = false, bool scrollbar = false) - { - Add(new GumpHtml(x, y, width, height, text, background, scrollbar)); - } - - public void AddHtmlLocalized(int x, int y, int width, int height, int number, bool background = false, - bool scrollbar = false) - { - Add(new GumpHtmlLocalized(x, y, width, height, number, background, scrollbar)); - } - - public void AddHtmlLocalized(int x, int y, int width, int height, int number, int color, bool background = false, - bool scrollbar = false) - { - Add(new GumpHtmlLocalized(x, y, width, height, number, color, background, scrollbar)); - } - - public void AddHtmlLocalized(int x, int y, int width, int height, int number, string args, int color, - bool background = false, bool scrollbar = false) - { - Add(new GumpHtmlLocalized(x, y, width, height, number, args, color, background, scrollbar)); - } - - public void AddImage(int x, int y, int gumpID, int hue = 0) - { - Add(new GumpImage(x, y, gumpID, hue)); - } - - public void AddImageTiled(int x, int y, int width, int height, int gumpID) - { - Add(new GumpImageTiled(x, y, width, height, gumpID)); - } - - public void AddImageTiledButton(int x, int y, int normalID, int pressedID, int buttonID, GumpButtonType type, - int param, int itemID, int hue, int width, int height, int localizedTooltip = -1) - { - Add(new GumpImageTileButton(x, y, normalID, pressedID, buttonID, type, param, itemID, hue, width, height, - localizedTooltip)); - } - - public void AddItem(int x, int y, int itemID, int hue = 0) - { - Add(new GumpItem(x, y, itemID, hue)); - } - - public void AddLabel(int x, int y, int hue, string text) - { - Add(new GumpLabel(x, y, hue, text)); - } - - public void AddLabelCropped(int x, int y, int width, int height, int hue, string text) - { - Add(new GumpLabelCropped(x, y, width, height, hue, text)); - } - - public void AddRadio(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) - { - Add(new GumpRadio(x, y, inactiveID, activeID, initialState, switchID)); - } - - public void AddTextEntry(int x, int y, int width, int height, int hue, int entryID, string initialText) - { - Add(new GumpTextEntry(x, y, width, height, hue, entryID, initialText)); - } - - public void AddTextEntry(int x, int y, int width, int height, int hue, int entryID, string initialText, int size) - { - Add(new GumpTextEntryLimited(x, y, width, height, hue, entryID, initialText, size)); - } - - public void AddItemProperty(uint serial) - { - Add(new GumpItemProperty(serial)); - } - - public void AddSpriteImage(int x, int y, int gumpID, int width, int height, int sx, int sy) - { - Add(new GumpSpriteImage(x, y, gumpID, width, height, sx, sy)); - } - - public void AddECHandleInput() - { - Add(new GumpECHandleInput()); - } - - public void AddGumpIDOverride(int gumpID) - { - Add(new GumpMasterGump(gumpID)); - } - - public void Add(GumpEntry g) - { - if (g.Parent != this) - g.Parent = this; - else if (!Entries.Contains(g)) - Entries.Add(g); - } - - public void Remove(GumpEntry g) - { - if (g == null || !Entries.Contains(g)) - return; - - Entries.Remove(g); - g.Parent = null; - } - - public int Intern(string value) - { - var indexOf = Strings.IndexOf(value); - - if (indexOf >= 0) return indexOf; - - Strings.Add(value); - return Strings.Count - 1; - } - - public void SendTo(NetState state) - { - state.AddGump(this); - state.Send(Compile(state)); - } - - public static byte[] StringToBuffer(string str) => Encoding.ASCII.GetBytes(str); - - public Packet Compile(NetState ns = null) - { - IGumpWriter disp; - - if (ns?.Unpack == true) - disp = new DisplayGumpPacked(this); - else - disp = new DisplayGumpFast(this); - - if (!Draggable) - disp.AppendLayout(m_NoMove); - - if (!Closable) - disp.AppendLayout(m_NoClose); - - if (!Disposable) - disp.AppendLayout(m_NoDispose); - - if (!Resizable) - disp.AppendLayout(m_NoResize); - - var count = Entries.Count; - - for (var i = 0; i < count; ++i) - { - var e = Entries[i]; - - disp.AppendLayout(m_BeginLayout); - e.AppendTo(ns, disp); - disp.AppendLayout(m_EndLayout); - } - - disp.WriteStrings(Strings); - - disp.Flush(); - - m_TextEntries = disp.TextEntries; - m_Switches = disp.Switches; - - return (Packet)disp; - } - - public virtual void OnResponse(NetState sender, RelayInfo info) - { - } - - public virtual void OnServerClose(NetState owner) - { - } - } -} +/*************************************************************************** + * Gump.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text; +using Server.Network; + +namespace Server.Gumps +{ + public class Gump + { + private static uint m_NextSerial = 1; + + private static readonly byte[] m_BeginLayout = StringToBuffer("{ "); + private static readonly byte[] m_EndLayout = StringToBuffer(" }"); + + private static readonly byte[] m_NoMove = StringToBuffer("{ nomove }"); + private static readonly byte[] m_NoClose = StringToBuffer("{ noclose }"); + private static readonly byte[] m_NoDispose = StringToBuffer("{ nodispose }"); + private static readonly byte[] m_NoResize = StringToBuffer("{ noresize }"); + + internal int m_TextEntries, m_Switches; + + public Gump(int x, int y) + { + do + { + Serial = m_NextSerial++; + } while (Serial == 0); // standard client apparently doesn't send a gump response packet if serial == 0 + + X = x; + Y = y; + + TypeID = GetTypeID(GetType()); + + Entries = new List(); + Strings = new List(); + } + + public List Strings { get; } + + public int TypeID { get; } + + public List Entries { get; } + + public uint Serial { get; set; } + + public int X { get; set; } + + public int Y { get; set; } + + public bool Disposable { get; set; } = true; + + public bool Resizable { get; set; } = true; + + public bool Draggable { get; set; } = true; + + public bool Closable { get; set; } = true; + + public static int GetTypeID(Type type) => type?.FullName?.GetHashCode() ?? -1; + + public void AddPage(int page) + { + Add(new GumpPage(page)); + } + + public void AddAlphaRegion(int x, int y, int width, int height) + { + Add(new GumpAlphaRegion(x, y, width, height)); + } + + public void AddBackground(int x, int y, int width, int height, int gumpID) + { + Add(new GumpBackground(x, y, width, height, gumpID)); + } + + public void AddButton( + int x, int y, int normalID, int pressedID, int buttonID, + GumpButtonType type = GumpButtonType.Reply, int param = 0 + ) + { + Add(new GumpButton(x, y, normalID, pressedID, buttonID, type, param)); + } + + public void AddCheck(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) + { + Add(new GumpCheck(x, y, inactiveID, activeID, initialState, switchID)); + } + + public void AddGroup(int group) + { + Add(new GumpGroup(group)); + } + + public void AddTooltip(int number, string args = null) + { + Add(new GumpTooltip(number, args)); + } + + public void AddHtml( + int x, int y, int width, int height, string text, bool background = false, bool scrollbar = false + ) + { + Add(new GumpHtml(x, y, width, height, text, background, scrollbar)); + } + + public void AddHtmlLocalized( + int x, int y, int width, int height, int number, bool background = false, + bool scrollbar = false + ) + { + Add(new GumpHtmlLocalized(x, y, width, height, number, background, scrollbar)); + } + + public void AddHtmlLocalized( + int x, int y, int width, int height, int number, int color, bool background = false, + bool scrollbar = false + ) + { + Add(new GumpHtmlLocalized(x, y, width, height, number, color, background, scrollbar)); + } + + public void AddHtmlLocalized( + int x, int y, int width, int height, int number, string args, int color, + bool background = false, bool scrollbar = false + ) + { + Add(new GumpHtmlLocalized(x, y, width, height, number, args, color, background, scrollbar)); + } + + public void AddImage(int x, int y, int gumpID, int hue = 0) + { + Add(new GumpImage(x, y, gumpID, hue)); + } + + public void AddImageTiled(int x, int y, int width, int height, int gumpID) + { + Add(new GumpImageTiled(x, y, width, height, gumpID)); + } + + public void AddImageTiledButton( + int x, int y, int normalID, int pressedID, int buttonID, GumpButtonType type, + int param, int itemID, int hue, int width, int height, int localizedTooltip = -1 + ) + { + Add( + new GumpImageTileButton( + x, + y, + normalID, + pressedID, + buttonID, + type, + param, + itemID, + hue, + width, + height, + localizedTooltip + ) + ); + } + + public void AddItem(int x, int y, int itemID, int hue = 0) + { + Add(new GumpItem(x, y, itemID, hue)); + } + + public void AddLabel(int x, int y, int hue, string text) + { + Add(new GumpLabel(x, y, hue, text)); + } + + public void AddLabelCropped(int x, int y, int width, int height, int hue, string text) + { + Add(new GumpLabelCropped(x, y, width, height, hue, text)); + } + + public void AddRadio(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) + { + Add(new GumpRadio(x, y, inactiveID, activeID, initialState, switchID)); + } + + public void AddTextEntry(int x, int y, int width, int height, int hue, int entryID, string initialText) + { + Add(new GumpTextEntry(x, y, width, height, hue, entryID, initialText)); + } + + public void AddTextEntry(int x, int y, int width, int height, int hue, int entryID, string initialText, int size) + { + Add(new GumpTextEntryLimited(x, y, width, height, hue, entryID, initialText, size)); + } + + public void AddItemProperty(uint serial) + { + Add(new GumpItemProperty(serial)); + } + + public void AddSpriteImage(int x, int y, int gumpID, int width, int height, int sx, int sy) + { + Add(new GumpSpriteImage(x, y, gumpID, width, height, sx, sy)); + } + + public void AddECHandleInput() + { + Add(new GumpECHandleInput()); + } + + public void AddGumpIDOverride(int gumpID) + { + Add(new GumpMasterGump(gumpID)); + } + + public void Add(GumpEntry g) + { + if (g.Parent != this) + g.Parent = this; + else if (!Entries.Contains(g)) + Entries.Add(g); + } + + public void Remove(GumpEntry g) + { + if (g == null || !Entries.Contains(g)) + return; + + Entries.Remove(g); + g.Parent = null; + } + + public int Intern(string value) + { + var indexOf = Strings.IndexOf(value); + + if (indexOf >= 0) return indexOf; + + Strings.Add(value); + return Strings.Count - 1; + } + + public void SendTo(NetState state) + { + state.AddGump(this); + state.Send(Compile(state)); + } + + public static byte[] StringToBuffer(string str) => Encoding.ASCII.GetBytes(str); + + public Packet Compile(NetState ns = null) + { + IGumpWriter disp; + + if (ns?.Unpack == true) + disp = new DisplayGumpPacked(this); + else + disp = new DisplayGumpFast(this); + + if (!Draggable) + disp.AppendLayout(m_NoMove); + + if (!Closable) + disp.AppendLayout(m_NoClose); + + if (!Disposable) + disp.AppendLayout(m_NoDispose); + + if (!Resizable) + disp.AppendLayout(m_NoResize); + + var count = Entries.Count; + + for (var i = 0; i < count; ++i) + { + var e = Entries[i]; + + disp.AppendLayout(m_BeginLayout); + e.AppendTo(ns, disp); + disp.AppendLayout(m_EndLayout); + } + + disp.WriteStrings(Strings); + + disp.Flush(); + + m_TextEntries = disp.TextEntries; + m_Switches = disp.Switches; + + return (Packet)disp; + } + + public virtual void OnResponse(NetState sender, RelayInfo info) + { + } + + public virtual void OnServerClose(NetState owner) + { + } + } +} diff --git a/Projects/Server/Gumps/GumpAlphaRegion.cs b/Projects/Server/Gumps/GumpAlphaRegion.cs index 62b2bc066..6e30c9b6e 100644 --- a/Projects/Server/Gumps/GumpAlphaRegion.cs +++ b/Projects/Server/Gumps/GumpAlphaRegion.cs @@ -1,56 +1,56 @@ -/*************************************************************************** - * GumpAlphaRegion.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpAlphaRegion : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("checkertrans"); - - public GumpAlphaRegion(int x, int y, int width, int height) - { - X = x; - Y = y; - Width = width; - Height = height; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public override string Compile(NetState ns) => $"{{ checkertrans {X} {Y} {Width} {Height} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - } - } -} +/*************************************************************************** + * GumpAlphaRegion.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpAlphaRegion : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("checkertrans"); + + public GumpAlphaRegion(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public override string Compile(NetState ns) => $"{{ checkertrans {X} {Y} {Width} {Height} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + } + } +} diff --git a/Projects/Server/Gumps/GumpBackground.cs b/Projects/Server/Gumps/GumpBackground.cs index 1c861e5c9..0d180d301 100644 --- a/Projects/Server/Gumps/GumpBackground.cs +++ b/Projects/Server/Gumps/GumpBackground.cs @@ -1,60 +1,60 @@ -/*************************************************************************** - * GumpBackground.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpBackground : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("resizepic"); - - public GumpBackground(int x, int y, int width, int height, int gumpID) - { - X = x; - Y = y; - Width = width; - Height = height; - GumpID = gumpID; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int GumpID { get; set; } - - public override string Compile(NetState ns) => $"{{ resizepic {X} {Y} {GumpID} {Width} {Height} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(GumpID); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - } - } -} +/*************************************************************************** + * GumpBackground.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpBackground : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("resizepic"); + + public GumpBackground(int x, int y, int width, int height, int gumpID) + { + X = x; + Y = y; + Width = width; + Height = height; + GumpID = gumpID; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int GumpID { get; set; } + + public override string Compile(NetState ns) => $"{{ resizepic {X} {Y} {GumpID} {Width} {Height} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(GumpID); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + } + } +} diff --git a/Projects/Server/Gumps/GumpButton.cs b/Projects/Server/Gumps/GumpButton.cs index cd2a74905..49f59a87b 100644 --- a/Projects/Server/Gumps/GumpButton.cs +++ b/Projects/Server/Gumps/GumpButton.cs @@ -1,76 +1,78 @@ -/*************************************************************************** - * GumpButton.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public enum GumpButtonType - { - Page = 0, - Reply = 1 - } - - public class GumpButton : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("button"); - - public GumpButton(int x, int y, int normalID, int pressedID, int buttonID, - GumpButtonType type = GumpButtonType.Reply, int param = 0) - { - X = x; - Y = y; - NormalID = normalID; - PressedID = pressedID; - ButtonID = buttonID; - Type = type; - Param = param; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int NormalID { get; set; } - - public int PressedID { get; set; } - - public int ButtonID { get; set; } - - public GumpButtonType Type { get; set; } - - public int Param { get; set; } - - public override string Compile(NetState ns) => - $"{{ button {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(NormalID); - disp.AppendLayout(PressedID); - disp.AppendLayout((int)Type); - disp.AppendLayout(Param); - disp.AppendLayout(ButtonID); - } - } -} +/*************************************************************************** + * GumpButton.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public enum GumpButtonType + { + Page = 0, + Reply = 1 + } + + public class GumpButton : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("button"); + + public GumpButton( + int x, int y, int normalID, int pressedID, int buttonID, + GumpButtonType type = GumpButtonType.Reply, int param = 0 + ) + { + X = x; + Y = y; + NormalID = normalID; + PressedID = pressedID; + ButtonID = buttonID; + Type = type; + Param = param; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int NormalID { get; set; } + + public int PressedID { get; set; } + + public int ButtonID { get; set; } + + public GumpButtonType Type { get; set; } + + public int Param { get; set; } + + public override string Compile(NetState ns) => + $"{{ button {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(NormalID); + disp.AppendLayout(PressedID); + disp.AppendLayout((int)Type); + disp.AppendLayout(Param); + disp.AppendLayout(ButtonID); + } + } +} diff --git a/Projects/Server/Gumps/GumpCheck.cs b/Projects/Server/Gumps/GumpCheck.cs index 2820245ae..91113b903 100644 --- a/Projects/Server/Gumps/GumpCheck.cs +++ b/Projects/Server/Gumps/GumpCheck.cs @@ -1,67 +1,67 @@ -/*************************************************************************** - * GumpCheck.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpCheck : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("checkbox"); - - public GumpCheck(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) - { - X = x; - Y = y; - InactiveID = inactiveID; - ActiveID = activeID; - InitialState = initialState; - SwitchID = switchID; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int InactiveID { get; set; } - - public int ActiveID { get; set; } - - public bool InitialState { get; set; } - - public int SwitchID { get; set; } - - public override string Compile(NetState ns) => - $"{{ checkbox {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(InactiveID); - disp.AppendLayout(ActiveID); - disp.AppendLayout(InitialState); - disp.AppendLayout(SwitchID); - - disp.Switches++; - } - } -} +/*************************************************************************** + * GumpCheck.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpCheck : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("checkbox"); + + public GumpCheck(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) + { + X = x; + Y = y; + InactiveID = inactiveID; + ActiveID = activeID; + InitialState = initialState; + SwitchID = switchID; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int InactiveID { get; set; } + + public int ActiveID { get; set; } + + public bool InitialState { get; set; } + + public int SwitchID { get; set; } + + public override string Compile(NetState ns) => + $"{{ checkbox {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(InactiveID); + disp.AppendLayout(ActiveID); + disp.AppendLayout(InitialState); + disp.AppendLayout(SwitchID); + + disp.Switches++; + } + } +} diff --git a/Projects/Server/Gumps/GumpECHandleInput.cs b/Projects/Server/Gumps/GumpECHandleInput.cs index a652775ee..4e3447642 100644 --- a/Projects/Server/Gumps/GumpECHandleInput.cs +++ b/Projects/Server/Gumps/GumpECHandleInput.cs @@ -1,37 +1,36 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: GumpECHandleInput.cs * - * Created: 2020/04/24 - Updated: 2020/04/24 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpECHandleInput : GumpEntry - { - public override string Compile(NetState ns) => "{ echandleinput }"; - - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("echandleinput"); - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GumpECHandleInput.cs * + * Created: 2020/04/24 - Updated: 2020/04/24 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpECHandleInput : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("echandleinput"); + public override string Compile(NetState ns) => "{ echandleinput }"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + } + } +} diff --git a/Projects/Server/Gumps/GumpEntry.cs b/Projects/Server/Gumps/GumpEntry.cs index cbea208c9..a1c045ed5 100644 --- a/Projects/Server/Gumps/GumpEntry.cs +++ b/Projects/Server/Gumps/GumpEntry.cs @@ -1,48 +1,48 @@ -/*************************************************************************** - * GumpEntry.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public abstract class GumpEntry - { - private Gump m_Parent; - - public Gump Parent - { - get => m_Parent; - set - { - if (m_Parent != value) - { - m_Parent?.Remove(this); - - m_Parent = value; - - m_Parent?.Add(this); - } - } - } - - public abstract string Compile(NetState ns); - public abstract void AppendTo(NetState ns, IGumpWriter disp); - } -} +/*************************************************************************** + * GumpEntry.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public abstract class GumpEntry + { + private Gump m_Parent; + + public Gump Parent + { + get => m_Parent; + set + { + if (m_Parent != value) + { + m_Parent?.Remove(this); + + m_Parent = value; + + m_Parent?.Add(this); + } + } + } + + public abstract string Compile(NetState ns); + public abstract void AppendTo(NetState ns, IGumpWriter disp); + } +} diff --git a/Projects/Server/Gumps/GumpGroup.cs b/Projects/Server/Gumps/GumpGroup.cs index c30d499af..abbc64579 100644 --- a/Projects/Server/Gumps/GumpGroup.cs +++ b/Projects/Server/Gumps/GumpGroup.cs @@ -1,41 +1,41 @@ -/*************************************************************************** - * GumpGroup.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpGroup : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("group"); - - public GumpGroup(int group) => Group = group; - - public int Group { get; set; } - - public override string Compile(NetState ns) => $"{{ group {Group} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(Group); - } - } -} +/*************************************************************************** + * GumpGroup.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpGroup : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("group"); + + public GumpGroup(int group) => Group = group; + + public int Group { get; set; } + + public override string Compile(NetState ns) => $"{{ group {Group} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(Group); + } + } +} diff --git a/Projects/Server/Gumps/GumpHtml.cs b/Projects/Server/Gumps/GumpHtml.cs index f55888dd9..943064ed6 100644 --- a/Projects/Server/Gumps/GumpHtml.cs +++ b/Projects/Server/Gumps/GumpHtml.cs @@ -1,69 +1,69 @@ -/*************************************************************************** - * GumpHtml.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpHtml : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("htmlgump"); - - public GumpHtml(int x, int y, int width, int height, string text, bool background, bool scrollbar) - { - X = x; - Y = y; - Width = width; - Height = height; - Text = text; - Background = background; - Scrollbar = scrollbar; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public string Text { get; set; } - - public bool Background { get; set; } - - public bool Scrollbar { get; set; } - - public override string Compile(NetState ns) => - $"{{ htmlgump {X} {Y} {Width} {Height} {Parent.Intern(Text)} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(Parent.Intern(Text)); - disp.AppendLayout(Background); - disp.AppendLayout(Scrollbar); - } - } -} +/*************************************************************************** + * GumpHtml.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpHtml : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("htmlgump"); + + public GumpHtml(int x, int y, int width, int height, string text, bool background, bool scrollbar) + { + X = x; + Y = y; + Width = width; + Height = height; + Text = text; + Background = background; + Scrollbar = scrollbar; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public string Text { get; set; } + + public bool Background { get; set; } + + public bool Scrollbar { get; set; } + + public override string Compile(NetState ns) => + $"{{ htmlgump {X} {Y} {Width} {Height} {Parent.Intern(Text)} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(Parent.Intern(Text)); + disp.AppendLayout(Background); + disp.AppendLayout(Scrollbar); + } + } +} diff --git a/Projects/Server/Gumps/GumpHtmlLocalized.cs b/Projects/Server/Gumps/GumpHtmlLocalized.cs index ae122fe7d..2a08719f2 100644 --- a/Projects/Server/Gumps/GumpHtmlLocalized.cs +++ b/Projects/Server/Gumps/GumpHtmlLocalized.cs @@ -1,172 +1,178 @@ -/*************************************************************************** - * GumpHtmlLocalized.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public enum GumpHtmlLocalizedType - { - Plain, - Color, - Args - } - - public class GumpHtmlLocalized : GumpEntry - { - private static readonly byte[] m_LayoutNamePlain = Gump.StringToBuffer("xmfhtmlgump"); - private static readonly byte[] m_LayoutNameColor = Gump.StringToBuffer("xmfhtmlgumpcolor"); - private static readonly byte[] m_LayoutNameArgs = Gump.StringToBuffer("xmfhtmltok"); - - public GumpHtmlLocalized(int x, int y, int width, int height, int number, - bool background = false, bool scrollbar = false) - { - X = x; - Y = y; - Width = width; - Height = height; - Number = number; - Background = background; - Scrollbar = scrollbar; - - Type = GumpHtmlLocalizedType.Plain; - } - - public GumpHtmlLocalized(int x, int y, int width, int height, int number, int color, - bool background = false, bool scrollbar = false) - { - X = x; - Y = y; - Width = width; - Height = height; - Number = number; - Color = color; - Background = background; - Scrollbar = scrollbar; - - Type = GumpHtmlLocalizedType.Color; - } - - public GumpHtmlLocalized(int x, int y, int width, int height, int number, string args, int color, - bool background = false, bool scrollbar = false) - { - // Are multiple arguments unsupported? And what about non ASCII arguments? - - X = x; - Y = y; - Width = width; - Height = height; - Number = number; - Args = args; - Color = color; - Background = background; - Scrollbar = scrollbar; - - Type = GumpHtmlLocalizedType.Args; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int Number { get; set; } - - public string Args { get; set; } - - public int Color { get; set; } - - public bool Background { get; set; } - - public bool Scrollbar { get; set; } - - public GumpHtmlLocalizedType Type { get; set; } - - public override string Compile(NetState ns) - { - return Type switch - { - GumpHtmlLocalizedType.Plain => - $"{{ xmfhtmlgump {X} {Y} {Width} {Height} {Number} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} }}", - GumpHtmlLocalizedType.Color => - $"{{ xmfhtmlgumpcolor {X} {Y} {Width} {Height} {Number} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} {Color} }}", - _ => - $"{{ xmfhtmltok {X} {Y} {Width} {Height} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} {Color} {Number} @{Args}@ }}" - }; - } - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - switch (Type) - { - case GumpHtmlLocalizedType.Plain: - { - disp.AppendLayout(m_LayoutNamePlain); - - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(Number); - disp.AppendLayout(Background); - disp.AppendLayout(Scrollbar); - - break; - } - - case GumpHtmlLocalizedType.Color: - { - disp.AppendLayout(m_LayoutNameColor); - - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(Number); - disp.AppendLayout(Background); - disp.AppendLayout(Scrollbar); - disp.AppendLayout(Color); - - break; - } - - case GumpHtmlLocalizedType.Args: - { - disp.AppendLayout(m_LayoutNameArgs); - - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(Background); - disp.AppendLayout(Scrollbar); - disp.AppendLayout(Color); - disp.AppendLayout(Number); - disp.AppendLayout(Args); - - break; - } - } - } - } -} +/*************************************************************************** + * GumpHtmlLocalized.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public enum GumpHtmlLocalizedType + { + Plain, + Color, + Args + } + + public class GumpHtmlLocalized : GumpEntry + { + private static readonly byte[] m_LayoutNamePlain = Gump.StringToBuffer("xmfhtmlgump"); + private static readonly byte[] m_LayoutNameColor = Gump.StringToBuffer("xmfhtmlgumpcolor"); + private static readonly byte[] m_LayoutNameArgs = Gump.StringToBuffer("xmfhtmltok"); + + public GumpHtmlLocalized( + int x, int y, int width, int height, int number, + bool background = false, bool scrollbar = false + ) + { + X = x; + Y = y; + Width = width; + Height = height; + Number = number; + Background = background; + Scrollbar = scrollbar; + + Type = GumpHtmlLocalizedType.Plain; + } + + public GumpHtmlLocalized( + int x, int y, int width, int height, int number, int color, + bool background = false, bool scrollbar = false + ) + { + X = x; + Y = y; + Width = width; + Height = height; + Number = number; + Color = color; + Background = background; + Scrollbar = scrollbar; + + Type = GumpHtmlLocalizedType.Color; + } + + public GumpHtmlLocalized( + int x, int y, int width, int height, int number, string args, int color, + bool background = false, bool scrollbar = false + ) + { + // Are multiple arguments unsupported? And what about non ASCII arguments? + + X = x; + Y = y; + Width = width; + Height = height; + Number = number; + Args = args; + Color = color; + Background = background; + Scrollbar = scrollbar; + + Type = GumpHtmlLocalizedType.Args; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int Number { get; set; } + + public string Args { get; set; } + + public int Color { get; set; } + + public bool Background { get; set; } + + public bool Scrollbar { get; set; } + + public GumpHtmlLocalizedType Type { get; set; } + + public override string Compile(NetState ns) + { + return Type switch + { + GumpHtmlLocalizedType.Plain => + $"{{ xmfhtmlgump {X} {Y} {Width} {Height} {Number} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} }}", + GumpHtmlLocalizedType.Color => + $"{{ xmfhtmlgumpcolor {X} {Y} {Width} {Height} {Number} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} {Color} }}", + _ => + $"{{ xmfhtmltok {X} {Y} {Width} {Height} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} {Color} {Number} @{Args}@ }}" + }; + } + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + switch (Type) + { + case GumpHtmlLocalizedType.Plain: + { + disp.AppendLayout(m_LayoutNamePlain); + + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(Number); + disp.AppendLayout(Background); + disp.AppendLayout(Scrollbar); + + break; + } + + case GumpHtmlLocalizedType.Color: + { + disp.AppendLayout(m_LayoutNameColor); + + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(Number); + disp.AppendLayout(Background); + disp.AppendLayout(Scrollbar); + disp.AppendLayout(Color); + + break; + } + + case GumpHtmlLocalizedType.Args: + { + disp.AppendLayout(m_LayoutNameArgs); + + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(Background); + disp.AppendLayout(Scrollbar); + disp.AppendLayout(Color); + disp.AppendLayout(Number); + disp.AppendLayout(Args); + + break; + } + } + } + } +} diff --git a/Projects/Server/Gumps/GumpImage.cs b/Projects/Server/Gumps/GumpImage.cs index 17782c7e9..42d2f4c32 100644 --- a/Projects/Server/Gumps/GumpImage.cs +++ b/Projects/Server/Gumps/GumpImage.cs @@ -1,63 +1,63 @@ -/*************************************************************************** - * GumpImage.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpImage : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("gumppic"); - private static readonly byte[] m_HueEquals = Gump.StringToBuffer(" hue="); - - public GumpImage(int x, int y, int gumpID, int hue = 0) - { - X = x; - Y = y; - GumpID = gumpID; - Hue = hue; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int GumpID { get; set; } - - public int Hue { get; set; } - - public override string Compile(NetState ns) => - Hue == 0 ? $"{{ gumppic {X} {Y} {GumpID} }}" : $"{{ gumppic {X} {Y} {GumpID} hue={Hue} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(GumpID); - - if (Hue != 0) - { - disp.AppendLayout(m_HueEquals); - disp.AppendLayoutNS(Hue); - } - } - } -} +/*************************************************************************** + * GumpImage.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpImage : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("gumppic"); + private static readonly byte[] m_HueEquals = Gump.StringToBuffer(" hue="); + + public GumpImage(int x, int y, int gumpID, int hue = 0) + { + X = x; + Y = y; + GumpID = gumpID; + Hue = hue; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int GumpID { get; set; } + + public int Hue { get; set; } + + public override string Compile(NetState ns) => + Hue == 0 ? $"{{ gumppic {X} {Y} {GumpID} }}" : $"{{ gumppic {X} {Y} {GumpID} hue={Hue} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(GumpID); + + if (Hue != 0) + { + disp.AppendLayout(m_HueEquals); + disp.AppendLayoutNS(Hue); + } + } + } +} diff --git a/Projects/Server/Gumps/GumpImageTileButton.cs b/Projects/Server/Gumps/GumpImageTileButton.cs index 2bcfdb801..62b8cc7e9 100644 --- a/Projects/Server/Gumps/GumpImageTileButton.cs +++ b/Projects/Server/Gumps/GumpImageTileButton.cs @@ -1,121 +1,123 @@ -/*************************************************************************** - * GumpImageTileButton.cs - * ------------------- - * begin : April 26, 2005 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpImageTileButton : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("buttontileart"); - private static readonly byte[] m_LayoutTooltip = Gump.StringToBuffer(" }{ tooltip"); - - private GumpButtonType m_Type; - - // Note, on OSI, The tooltip supports ONLY clilocs as far as I can figure out, and the tooltip ONLY works after the buttonTileArt (as far as I can tell from testing) - - public GumpImageTileButton(int x, int y, int normalID, int pressedID, int buttonID, GumpButtonType type, int param, - int itemID, int hue, int width, int height, int localizedTooltip = -1) - { - X = x; - Y = y; - NormalID = normalID; - PressedID = pressedID; - ButtonID = buttonID; - m_Type = type; - Param = param; - - ItemID = itemID; - Hue = hue; - Width = width; - Height = height; - - LocalizedTooltip = localizedTooltip; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int NormalID { get; set; } - - public int PressedID { get; set; } - - public int ButtonID { get; set; } - - public GumpButtonType Type - { - get => m_Type; - set - { - if (m_Type != value) - { - m_Type = value; - - var parent = Parent; - } - } - } - - public int Param { get; set; } - - public int ItemID { get; set; } - - public int Hue { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int LocalizedTooltip { get; set; } - - public override string Compile(NetState ns) - { - if (LocalizedTooltip > 0) - return - $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)m_Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}{{ tooltip {LocalizedTooltip} }}"; - return - $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)m_Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}"; - } - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(NormalID); - disp.AppendLayout(PressedID); - disp.AppendLayout((int)m_Type); - disp.AppendLayout(Param); - disp.AppendLayout(ButtonID); - - disp.AppendLayout(ItemID); - disp.AppendLayout(Hue); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - - if (LocalizedTooltip > 0) - { - disp.AppendLayout(m_LayoutTooltip); - disp.AppendLayout(LocalizedTooltip); - } - } - } -} +/*************************************************************************** + * GumpImageTileButton.cs + * ------------------- + * begin : April 26, 2005 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpImageTileButton : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("buttontileart"); + private static readonly byte[] m_LayoutTooltip = Gump.StringToBuffer(" }{ tooltip"); + + private GumpButtonType m_Type; + + // Note, on OSI, The tooltip supports ONLY clilocs as far as I can figure out, and the tooltip ONLY works after the buttonTileArt (as far as I can tell from testing) + + public GumpImageTileButton( + int x, int y, int normalID, int pressedID, int buttonID, GumpButtonType type, int param, + int itemID, int hue, int width, int height, int localizedTooltip = -1 + ) + { + X = x; + Y = y; + NormalID = normalID; + PressedID = pressedID; + ButtonID = buttonID; + m_Type = type; + Param = param; + + ItemID = itemID; + Hue = hue; + Width = width; + Height = height; + + LocalizedTooltip = localizedTooltip; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int NormalID { get; set; } + + public int PressedID { get; set; } + + public int ButtonID { get; set; } + + public GumpButtonType Type + { + get => m_Type; + set + { + if (m_Type != value) + { + m_Type = value; + + var parent = Parent; + } + } + } + + public int Param { get; set; } + + public int ItemID { get; set; } + + public int Hue { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int LocalizedTooltip { get; set; } + + public override string Compile(NetState ns) + { + if (LocalizedTooltip > 0) + return + $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)m_Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}{{ tooltip {LocalizedTooltip} }}"; + return + $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)m_Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}"; + } + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(NormalID); + disp.AppendLayout(PressedID); + disp.AppendLayout((int)m_Type); + disp.AppendLayout(Param); + disp.AppendLayout(ButtonID); + + disp.AppendLayout(ItemID); + disp.AppendLayout(Hue); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + + if (LocalizedTooltip > 0) + { + disp.AppendLayout(m_LayoutTooltip); + disp.AppendLayout(LocalizedTooltip); + } + } + } +} diff --git a/Projects/Server/Gumps/GumpImageTiled.cs b/Projects/Server/Gumps/GumpImageTiled.cs index ac16248f6..7955a3e43 100644 --- a/Projects/Server/Gumps/GumpImageTiled.cs +++ b/Projects/Server/Gumps/GumpImageTiled.cs @@ -1,60 +1,60 @@ -/*************************************************************************** - * GumpImageTiled.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpImageTiled : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("gumppictiled"); - - public GumpImageTiled(int x, int y, int width, int height, int gumpID) - { - X = x; - Y = y; - Width = width; - Height = height; - GumpID = gumpID; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int GumpID { get; set; } - - public override string Compile(NetState ns) => $"{{ gumppictiled {X} {Y} {Width} {Height} {GumpID} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(GumpID); - } - } -} +/*************************************************************************** + * GumpImageTiled.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpImageTiled : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("gumppictiled"); + + public GumpImageTiled(int x, int y, int width, int height, int gumpID) + { + X = x; + Y = y; + Width = width; + Height = height; + GumpID = gumpID; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int GumpID { get; set; } + + public override string Compile(NetState ns) => $"{{ gumppictiled {X} {Y} {Width} {Height} {GumpID} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(GumpID); + } + } +} diff --git a/Projects/Server/Gumps/GumpItem.cs b/Projects/Server/Gumps/GumpItem.cs index b0cd9ae6f..a88d9ad01 100644 --- a/Projects/Server/Gumps/GumpItem.cs +++ b/Projects/Server/Gumps/GumpItem.cs @@ -1,60 +1,60 @@ -/*************************************************************************** - * GumpItem.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpItem : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("tilepic"); - private static readonly byte[] m_LayoutNameHue = Gump.StringToBuffer("tilepichue"); - - public GumpItem(int x, int y, int itemID, int hue = 0) - { - X = x; - Y = y; - ItemID = itemID; - Hue = hue; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int ItemID { get; set; } - - public int Hue { get; set; } - - public override string Compile(NetState ns) => - Hue == 0 ? $"{{ tilepic {X} {Y} {ItemID} }}" : $"{{ tilepichue {X} {Y} {ItemID} {Hue} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(Hue == 0 ? m_LayoutName : m_LayoutNameHue); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(ItemID); - - if (Hue != 0) - disp.AppendLayout(Hue); - } - } -} +/*************************************************************************** + * GumpItem.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpItem : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("tilepic"); + private static readonly byte[] m_LayoutNameHue = Gump.StringToBuffer("tilepichue"); + + public GumpItem(int x, int y, int itemID, int hue = 0) + { + X = x; + Y = y; + ItemID = itemID; + Hue = hue; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int ItemID { get; set; } + + public int Hue { get; set; } + + public override string Compile(NetState ns) => + Hue == 0 ? $"{{ tilepic {X} {Y} {ItemID} }}" : $"{{ tilepichue {X} {Y} {ItemID} {Hue} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(Hue == 0 ? m_LayoutName : m_LayoutNameHue); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(ItemID); + + if (Hue != 0) + disp.AppendLayout(Hue); + } + } +} diff --git a/Projects/Server/Gumps/GumpItemProperty.cs b/Projects/Server/Gumps/GumpItemProperty.cs index 0ef3458a8..5f004d85e 100644 --- a/Projects/Server/Gumps/GumpItemProperty.cs +++ b/Projects/Server/Gumps/GumpItemProperty.cs @@ -1,41 +1,41 @@ -/*************************************************************************** - * GumpItemProperty.cs - * ------------------- - * begin : May 26, 2013 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpItemProperty : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("itemproperty"); - - public GumpItemProperty(uint serial) => Serial = serial; - - public uint Serial { get; set; } - - public override string Compile(NetState ns) => $"{{ itemproperty {Serial} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(Serial); - } - } -} +/*************************************************************************** + * GumpItemProperty.cs + * ------------------- + * begin : May 26, 2013 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpItemProperty : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("itemproperty"); + + public GumpItemProperty(uint serial) => Serial = serial; + + public uint Serial { get; set; } + + public override string Compile(NetState ns) => $"{{ itemproperty {Serial} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(Serial); + } + } +} diff --git a/Projects/Server/Gumps/GumpLabel.cs b/Projects/Server/Gumps/GumpLabel.cs index 150a575ee..fe58a0b0d 100644 --- a/Projects/Server/Gumps/GumpLabel.cs +++ b/Projects/Server/Gumps/GumpLabel.cs @@ -1,56 +1,56 @@ -/*************************************************************************** - * GumpLabel.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpLabel : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("text"); - - public GumpLabel(int x, int y, int hue, string text) - { - X = x; - Y = y; - Hue = hue; - Text = text; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Hue { get; set; } - - public string Text { get; set; } - - public override string Compile(NetState ns) => $"{{ text {X} {Y} {Hue} {Parent.Intern(Text)} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Hue); - disp.AppendLayout(Parent.Intern(Text)); - } - } -} +/*************************************************************************** + * GumpLabel.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpLabel : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("text"); + + public GumpLabel(int x, int y, int hue, string text) + { + X = x; + Y = y; + Hue = hue; + Text = text; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Hue { get; set; } + + public string Text { get; set; } + + public override string Compile(NetState ns) => $"{{ text {X} {Y} {Hue} {Parent.Intern(Text)} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Hue); + disp.AppendLayout(Parent.Intern(Text)); + } + } +} diff --git a/Projects/Server/Gumps/GumpLabelCropped.cs b/Projects/Server/Gumps/GumpLabelCropped.cs index e2fb3a7be..dae0129b2 100644 --- a/Projects/Server/Gumps/GumpLabelCropped.cs +++ b/Projects/Server/Gumps/GumpLabelCropped.cs @@ -1,65 +1,65 @@ -/*************************************************************************** - * GumpLabelCropped.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpLabelCropped : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("croppedtext"); - - public GumpLabelCropped(int x, int y, int width, int height, int hue, string text) - { - X = x; - Y = y; - Width = width; - Height = height; - Hue = hue; - Text = text; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int Hue { get; set; } - - public string Text { get; set; } - - public override string Compile(NetState ns) => - $"{{ croppedtext {X} {Y} {Width} {Height} {Hue} {Parent.Intern(Text)} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(Hue); - disp.AppendLayout(Parent.Intern(Text)); - } - } -} +/*************************************************************************** + * GumpLabelCropped.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpLabelCropped : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("croppedtext"); + + public GumpLabelCropped(int x, int y, int width, int height, int hue, string text) + { + X = x; + Y = y; + Width = width; + Height = height; + Hue = hue; + Text = text; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int Hue { get; set; } + + public string Text { get; set; } + + public override string Compile(NetState ns) => + $"{{ croppedtext {X} {Y} {Width} {Height} {Hue} {Parent.Intern(Text)} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(Hue); + disp.AppendLayout(Parent.Intern(Text)); + } + } +} diff --git a/Projects/Server/Gumps/GumpMasterGump.cs b/Projects/Server/Gumps/GumpMasterGump.cs index 8db1ae08a..d69bf06d8 100644 --- a/Projects/Server/Gumps/GumpMasterGump.cs +++ b/Projects/Server/Gumps/GumpMasterGump.cs @@ -1,42 +1,42 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: GumpMasterGump.cs * - * Created: 2020/04/24 - Updated: 2020/04/24 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpMasterGump : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("mastergump"); - - public GumpMasterGump(int gumpID) => GumpID = gumpID; - - public int GumpID { get; set; } - - public override string Compile(NetState ns) => $"{{ mastergump {GumpID} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(GumpID); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GumpMasterGump.cs * + * Created: 2020/04/24 - Updated: 2020/04/24 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpMasterGump : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("mastergump"); + + public GumpMasterGump(int gumpID) => GumpID = gumpID; + + public int GumpID { get; set; } + + public override string Compile(NetState ns) => $"{{ mastergump {GumpID} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(GumpID); + } + } +} diff --git a/Projects/Server/Gumps/GumpPage.cs b/Projects/Server/Gumps/GumpPage.cs index 33e313b4b..81ad30cd4 100644 --- a/Projects/Server/Gumps/GumpPage.cs +++ b/Projects/Server/Gumps/GumpPage.cs @@ -1,41 +1,41 @@ -/*************************************************************************** - * GumpPage.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpPage : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("page"); - - public GumpPage(int page) => Page = page; - - public int Page { get; set; } - - public override string Compile(NetState ns) => $"{{ page {Page} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(Page); - } - } -} +/*************************************************************************** + * GumpPage.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpPage : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("page"); + + public GumpPage(int page) => Page = page; + + public int Page { get; set; } + + public override string Compile(NetState ns) => $"{{ page {Page} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(Page); + } + } +} diff --git a/Projects/Server/Gumps/GumpRadio.cs b/Projects/Server/Gumps/GumpRadio.cs index 688036460..69b93ddee 100644 --- a/Projects/Server/Gumps/GumpRadio.cs +++ b/Projects/Server/Gumps/GumpRadio.cs @@ -1,67 +1,67 @@ -/*************************************************************************** - * GumpRadio.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpRadio : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("radio"); - - public GumpRadio(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) - { - X = x; - Y = y; - InactiveID = inactiveID; - ActiveID = activeID; - InitialState = initialState; - SwitchID = switchID; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int InactiveID { get; set; } - - public int ActiveID { get; set; } - - public bool InitialState { get; set; } - - public int SwitchID { get; set; } - - public override string Compile(NetState ns) => - $"{{ radio {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(InactiveID); - disp.AppendLayout(ActiveID); - disp.AppendLayout(InitialState); - disp.AppendLayout(SwitchID); - - disp.Switches++; - } - } -} +/*************************************************************************** + * GumpRadio.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpRadio : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("radio"); + + public GumpRadio(int x, int y, int inactiveID, int activeID, bool initialState, int switchID) + { + X = x; + Y = y; + InactiveID = inactiveID; + ActiveID = activeID; + InitialState = initialState; + SwitchID = switchID; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int InactiveID { get; set; } + + public int ActiveID { get; set; } + + public bool InitialState { get; set; } + + public int SwitchID { get; set; } + + public override string Compile(NetState ns) => + $"{{ radio {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(InactiveID); + disp.AppendLayout(ActiveID); + disp.AppendLayout(InitialState); + disp.AppendLayout(SwitchID); + + disp.Switches++; + } + } +} diff --git a/Projects/Server/Gumps/GumpSpriteImage.cs b/Projects/Server/Gumps/GumpSpriteImage.cs index 5b98d8192..6bcce5bcb 100644 --- a/Projects/Server/Gumps/GumpSpriteImage.cs +++ b/Projects/Server/Gumps/GumpSpriteImage.cs @@ -1,48 +1,48 @@ -using Server.Network; - -namespace Server.Gumps -{ - public class GumpSpriteImage : GumpEntry - { - public GumpSpriteImage(int x, int y, int gumpID, int width, int height, int sx, int sy) - { - X = x; - Y = y; - GumpID = gumpID; - Width = width; - Height = height; - SX = sx; - SY = sy; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int GumpID { get; set; } - - public int SX { get; set; } - - public int SY { get; set; } - - public override string Compile(NetState ns) => $"{{ picinpic {X} {Y} {GumpID} {Width} {Height} {SX} {SY} }}"; - - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("picinpic"); - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(GumpID); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(SX); - disp.AppendLayout(SY); - } - } -} +using Server.Network; + +namespace Server.Gumps +{ + public class GumpSpriteImage : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("picinpic"); + + public GumpSpriteImage(int x, int y, int gumpID, int width, int height, int sx, int sy) + { + X = x; + Y = y; + GumpID = gumpID; + Width = width; + Height = height; + SX = sx; + SY = sy; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int GumpID { get; set; } + + public int SX { get; set; } + + public int SY { get; set; } + + public override string Compile(NetState ns) => $"{{ picinpic {X} {Y} {GumpID} {Width} {Height} {SX} {SY} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(GumpID); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(SX); + disp.AppendLayout(SY); + } + } +} diff --git a/Projects/Server/Gumps/GumpTextEntry.cs b/Projects/Server/Gumps/GumpTextEntry.cs index de570b730..b4a0f0847 100644 --- a/Projects/Server/Gumps/GumpTextEntry.cs +++ b/Projects/Server/Gumps/GumpTextEntry.cs @@ -1,71 +1,71 @@ -/*************************************************************************** - * GumpTextEntry.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpTextEntry : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("textentry"); - - public GumpTextEntry(int x, int y, int width, int height, int hue, int entryID, string initialText) - { - X = x; - Y = y; - Width = width; - Height = height; - Hue = hue; - EntryID = entryID; - InitialText = initialText; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int Hue { get; set; } - - public int EntryID { get; set; } - - public string InitialText { get; set; } - - public override string Compile(NetState ns) => - $"{{ textentry {X} {Y} {Width} {Height} {Hue} {EntryID} {Parent.Intern(InitialText)} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(Hue); - disp.AppendLayout(EntryID); - disp.AppendLayout(Parent.Intern(InitialText)); - - disp.TextEntries++; - } - } -} +/*************************************************************************** + * GumpTextEntry.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpTextEntry : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("textentry"); + + public GumpTextEntry(int x, int y, int width, int height, int hue, int entryID, string initialText) + { + X = x; + Y = y; + Width = width; + Height = height; + Hue = hue; + EntryID = entryID; + InitialText = initialText; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int Hue { get; set; } + + public int EntryID { get; set; } + + public string InitialText { get; set; } + + public override string Compile(NetState ns) => + $"{{ textentry {X} {Y} {Width} {Height} {Hue} {EntryID} {Parent.Intern(InitialText)} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(Hue); + disp.AppendLayout(EntryID); + disp.AppendLayout(Parent.Intern(InitialText)); + + disp.TextEntries++; + } + } +} diff --git a/Projects/Server/Gumps/GumpTextEntryLimited.cs b/Projects/Server/Gumps/GumpTextEntryLimited.cs index 077cc2b31..c85d6a318 100644 --- a/Projects/Server/Gumps/GumpTextEntryLimited.cs +++ b/Projects/Server/Gumps/GumpTextEntryLimited.cs @@ -1,75 +1,77 @@ -/*************************************************************************** - * GumpTextEntryLimited.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpTextEntryLimited : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("textentrylimited"); - - public GumpTextEntryLimited(int x, int y, int width, int height, int hue, int entryID, string initialText, int size = 0) - { - X = x; - Y = y; - Width = width; - Height = height; - Hue = hue; - EntryID = entryID; - InitialText = initialText; - Size = size; - } - - public int X { get; set; } - - public int Y { get; set; } - - public int Width { get; set; } - - public int Height { get; set; } - - public int Hue { get; set; } - - public int EntryID { get; set; } - - public string InitialText { get; set; } - - public int Size { get; set; } - - public override string Compile(NetState ns) => - $"{{ textentrylimited {X} {Y} {Width} {Height} {Hue} {EntryID} {Parent.Intern(InitialText)} {Size} }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(X); - disp.AppendLayout(Y); - disp.AppendLayout(Width); - disp.AppendLayout(Height); - disp.AppendLayout(Hue); - disp.AppendLayout(EntryID); - disp.AppendLayout(Parent.Intern(InitialText)); - disp.AppendLayout(Size); - - disp.TextEntries++; - } - } -} +/*************************************************************************** + * GumpTextEntryLimited.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpTextEntryLimited : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("textentrylimited"); + + public GumpTextEntryLimited( + int x, int y, int width, int height, int hue, int entryID, string initialText, int size = 0 + ) + { + X = x; + Y = y; + Width = width; + Height = height; + Hue = hue; + EntryID = entryID; + InitialText = initialText; + Size = size; + } + + public int X { get; set; } + + public int Y { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + + public int Hue { get; set; } + + public int EntryID { get; set; } + + public string InitialText { get; set; } + + public int Size { get; set; } + + public override string Compile(NetState ns) => + $"{{ textentrylimited {X} {Y} {Width} {Height} {Hue} {EntryID} {Parent.Intern(InitialText)} {Size} }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(X); + disp.AppendLayout(Y); + disp.AppendLayout(Width); + disp.AppendLayout(Height); + disp.AppendLayout(Hue); + disp.AppendLayout(EntryID); + disp.AppendLayout(Parent.Intern(InitialText)); + disp.AppendLayout(Size); + + disp.TextEntries++; + } + } +} diff --git a/Projects/Server/Gumps/GumpTooltip.cs b/Projects/Server/Gumps/GumpTooltip.cs index 3a32702b9..2bfb00df9 100644 --- a/Projects/Server/Gumps/GumpTooltip.cs +++ b/Projects/Server/Gumps/GumpTooltip.cs @@ -1,48 +1,48 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: GumpTooltip.cs - Created: 2020/04/24 - Updated: 2020/04/24 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using Server.Network; - -namespace Server.Gumps -{ - public class GumpTooltip : GumpEntry - { - private static readonly byte[] m_LayoutName = Gump.StringToBuffer("tooltip"); - - public GumpTooltip(int number, string args) - { - Number = number; - Args = args; - } - - public int Number { get; set; } - - public string Args { get; set; } - - public override string Compile(NetState ns) => $"{{ tooltip {Number} @{Args}@ }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - disp.AppendLayout(m_LayoutName); - disp.AppendLayout(Number); - disp.AppendLayout(Args); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GumpTooltip.cs - Created: 2020/04/24 - Updated: 2020/04/24 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Server.Network; + +namespace Server.Gumps +{ + public class GumpTooltip : GumpEntry + { + private static readonly byte[] m_LayoutName = Gump.StringToBuffer("tooltip"); + + public GumpTooltip(int number, string args) + { + Number = number; + Args = args; + } + + public int Number { get; set; } + + public string Args { get; set; } + + public override string Compile(NetState ns) => $"{{ tooltip {Number} @{Args}@ }}"; + + public override void AppendTo(NetState ns, IGumpWriter disp) + { + disp.AppendLayout(m_LayoutName); + disp.AppendLayout(Number); + disp.AppendLayout(Args); + } + } +} diff --git a/Projects/Server/Gumps/RelayInfo.cs b/Projects/Server/Gumps/RelayInfo.cs index 796c4c6cb..50bf37e3b 100644 --- a/Projects/Server/Gumps/RelayInfo.cs +++ b/Projects/Server/Gumps/RelayInfo.cs @@ -1,69 +1,69 @@ -/*************************************************************************** - * RelayInfo.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Gumps -{ - public class TextRelay - { - public TextRelay(int entryID, string text) - { - EntryID = entryID; - Text = text; - } - - public int EntryID { get; } - - public string Text { get; } - } - - public class RelayInfo - { - public RelayInfo(int buttonID, int[] switches, TextRelay[] textEntries) - { - ButtonID = buttonID; - Switches = switches; - TextEntries = textEntries; - } - - public int ButtonID { get; } - - public int[] Switches { get; } - - public TextRelay[] TextEntries { get; } - - public bool IsSwitched(int switchID) - { - for (var i = 0; i < Switches.Length; ++i) - if (Switches[i] == switchID) - return true; - - return false; - } - - public TextRelay GetTextEntry(int entryID) - { - for (var i = 0; i < TextEntries.Length; ++i) - if (TextEntries[i].EntryID == entryID) - return TextEntries[i]; - - return null; - } - } -} +/*************************************************************************** + * RelayInfo.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Gumps +{ + public class TextRelay + { + public TextRelay(int entryID, string text) + { + EntryID = entryID; + Text = text; + } + + public int EntryID { get; } + + public string Text { get; } + } + + public class RelayInfo + { + public RelayInfo(int buttonID, int[] switches, TextRelay[] textEntries) + { + ButtonID = buttonID; + Switches = switches; + TextEntries = textEntries; + } + + public int ButtonID { get; } + + public int[] Switches { get; } + + public TextRelay[] TextEntries { get; } + + public bool IsSwitched(int switchID) + { + for (var i = 0; i < Switches.Length; ++i) + if (Switches[i] == switchID) + return true; + + return false; + } + + public TextRelay GetTextEntry(int entryID) + { + for (var i = 0; i < TextEntries.Length; ++i) + if (TextEntries[i].EntryID == entryID) + return TextEntries[i]; + + return null; + } + } +} diff --git a/Projects/Server/HuePicker.cs b/Projects/Server/HuePicker.cs index 2f62e2f93..658347a9e 100644 --- a/Projects/Server/HuePicker.cs +++ b/Projects/Server/HuePicker.cs @@ -1,53 +1,53 @@ -/*************************************************************************** - * HuePicker.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.HuePickers -{ - public class HuePicker - { - private static int m_NextSerial = 1; - - public HuePicker(int itemID) - { - do - { - Serial = m_NextSerial++; - } while (Serial == 0); - - ItemID = itemID; - } - - public int Serial { get; } - - public int ItemID { get; } - - public virtual void OnResponse(int hue) - { - } - - public void SendTo(NetState state) - { - state.Send(new DisplayHuePicker(this)); - state.AddHuePicker(this); - } - } -} +/*************************************************************************** + * HuePicker.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.HuePickers +{ + public class HuePicker + { + private static int m_NextSerial = 1; + + public HuePicker(int itemID) + { + do + { + Serial = m_NextSerial++; + } while (Serial == 0); + + ItemID = itemID; + } + + public int Serial { get; } + + public int ItemID { get; } + + public virtual void OnResponse(int hue) + { + } + + public void SendTo(NetState state) + { + state.Send(new DisplayHuePicker(this)); + state.AddHuePicker(this); + } + } +} diff --git a/Projects/Server/IAccount.cs b/Projects/Server/IAccount.cs index eb8afa949..3dacc1076 100644 --- a/Projects/Server/IAccount.cs +++ b/Projects/Server/IAccount.cs @@ -1,126 +1,126 @@ -/*************************************************************************** - * IAccount.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server.Accounting -{ - public static class AccountGold - { - public static bool Enabled = false; - - /// - /// This amount specifies the value at which point Gold turns to Platinum. - /// By default, when 1,000,000,000 Gold is accumulated, it will transform - /// into 1 Platinum. - /// !!! WARNING !!! - /// The client is designed to perceive the currency threashold at 1,000,000,000 - /// if you change this, it may cause unexpected results when using secure trading. - /// - public static int CurrencyThreshold = 1000000000; - - /// - /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency - /// when they are added to a bank box container. - /// - public static bool ConvertOnBank = true; - - /// - /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency - /// when they are added to a secure trade container. - /// - public static bool ConvertOnTrade = false; - } - - public interface IGoldAccount - { - /// - /// This amount represents the current amount of Gold owned by the player. - /// The value does not include the value of Platinum and ranges from - /// 0 to 999,999,999 by default. - /// - [CommandProperty(AccessLevel.Administrator)] - int TotalGold { get; } - - /// - /// This amount represents the current amount of Platinum owned by the player. - /// The value does not include the value of Gold and ranges from - /// 0 to 2,147,483,647 by default. - /// One Platinum represents the value of CurrencyThreshold in Gold. - /// - [CommandProperty(AccessLevel.Administrator)] - int TotalPlat { get; } - - /// - /// Attempts to deposit the given amount of Gold into this account. - /// If the given amount is greater than the CurrencyThreshold, - /// Platinum will be deposited to offset the difference. - /// - /// Amount to deposit. - /// True if successful, false if amount given is less than or equal to zero. - bool DepositGold(int amount); - - /// - /// Attempts to deposit the given amount of Platinum into this account. - /// - /// Amount to deposit. - /// True if successful, false if amount given is less than or equal to zero. - bool DepositPlat(int amount); - - /// - /// Attempts to withdraw the given amount of Gold from this account. - /// If the given amount is greater than the CurrencyThreshold, - /// Platinum will be withdrawn to offset the difference. - /// - /// Amount to withdraw. - /// True if successful, false if balance was too low. - bool WithdrawGold(int amount); - - /// - /// Attempts to withdraw the given amount of Platinum from this account. - /// - /// Amount to withdraw. - /// True if successful, false if balance was too low. - bool WithdrawPlat(int amount); - - /// - /// Returns total gold inclusive of platinum, capped to Int32. - /// This is strictly for backwards compatibility - /// - /// Total gold, capped at Int32.MaxValue - long GetTotalGold(); - } - - public interface IAccount : IGoldAccount, IComparable - { - string Username { get; set; } - string Email { get; set; } - AccessLevel AccessLevel { get; set; } - - int Length { get; } - int Limit { get; } - int Count { get; } - Mobile this[int index] { get; set; } - - void Delete(); - void SetPassword(string password); - bool CheckPassword(string password); - } -} +/*************************************************************************** + * IAccount.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server.Accounting +{ + public static class AccountGold + { + public static bool Enabled = false; + + /// + /// This amount specifies the value at which point Gold turns to Platinum. + /// By default, when 1,000,000,000 Gold is accumulated, it will transform + /// into 1 Platinum. + /// !!! WARNING !!! + /// The client is designed to perceive the currency threashold at 1,000,000,000 + /// if you change this, it may cause unexpected results when using secure trading. + /// + public static int CurrencyThreshold = 1000000000; + + /// + /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency + /// when they are added to a bank box container. + /// + public static bool ConvertOnBank = true; + + /// + /// Enables or Disables automatic conversion of Gold and Checks to Bank Currency + /// when they are added to a secure trade container. + /// + public static bool ConvertOnTrade = false; + } + + public interface IGoldAccount + { + /// + /// This amount represents the current amount of Gold owned by the player. + /// The value does not include the value of Platinum and ranges from + /// 0 to 999,999,999 by default. + /// + [CommandProperty(AccessLevel.Administrator)] + int TotalGold { get; } + + /// + /// This amount represents the current amount of Platinum owned by the player. + /// The value does not include the value of Gold and ranges from + /// 0 to 2,147,483,647 by default. + /// One Platinum represents the value of CurrencyThreshold in Gold. + /// + [CommandProperty(AccessLevel.Administrator)] + int TotalPlat { get; } + + /// + /// Attempts to deposit the given amount of Gold into this account. + /// If the given amount is greater than the CurrencyThreshold, + /// Platinum will be deposited to offset the difference. + /// + /// Amount to deposit. + /// True if successful, false if amount given is less than or equal to zero. + bool DepositGold(int amount); + + /// + /// Attempts to deposit the given amount of Platinum into this account. + /// + /// Amount to deposit. + /// True if successful, false if amount given is less than or equal to zero. + bool DepositPlat(int amount); + + /// + /// Attempts to withdraw the given amount of Gold from this account. + /// If the given amount is greater than the CurrencyThreshold, + /// Platinum will be withdrawn to offset the difference. + /// + /// Amount to withdraw. + /// True if successful, false if balance was too low. + bool WithdrawGold(int amount); + + /// + /// Attempts to withdraw the given amount of Platinum from this account. + /// + /// Amount to withdraw. + /// True if successful, false if balance was too low. + bool WithdrawPlat(int amount); + + /// + /// Returns total gold inclusive of platinum, capped to Int32. + /// This is strictly for backwards compatibility + /// + /// Total gold, capped at Int32.MaxValue + long GetTotalGold(); + } + + public interface IAccount : IGoldAccount, IComparable + { + string Username { get; set; } + string Email { get; set; } + AccessLevel AccessLevel { get; set; } + + int Length { get; } + int Limit { get; } + int Count { get; } + Mobile this[int index] { get; set; } + + void Delete(); + void SetPassword(string password); + bool CheckPassword(string password); + } +} diff --git a/Projects/Server/IEntity.cs b/Projects/Server/IEntity.cs index 6ee5e6a13..c3795a8c8 100644 --- a/Projects/Server/IEntity.cs +++ b/Projects/Server/IEntity.cs @@ -1,103 +1,103 @@ -/*************************************************************************** - * IEntity.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server -{ - public interface IEntity : IPoint3D, IComparable - { - Serial Serial { get; } - Point3D Location { get; } - Map Map { get; } - bool Deleted { get; } - void MoveToWorld(Point3D location, Map map); - - void Delete(); - void ProcessDelta(); - - bool InRange(Point2D p, int range); - - bool InRange(Point3D p, int range); - - bool InRange(IPoint2D p, int range); - } - - public class Entity : IEntity, IComparable - { - public Entity(Serial serial, Point3D loc, Map map) - { - Serial = serial; - Location = loc; - Map = map; - Deleted = false; - } - - public int CompareTo(Entity other) => CompareTo((IEntity)other); - - public int CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); - - public Serial Serial { get; } - - public Point3D Location { get; private set; } - - public int X => Location.X; - - public int Y => Location.Y; - - public int Z => Location.Z; - - public Map Map { get; private set; } - - public virtual void MoveToWorld(Point3D newLocation, Map map) - { - Location = newLocation; - Map = map; - } - - public bool Deleted { get; } - - public void Delete() - { - } - - public void ProcessDelta() - { - } - - public bool InRange(Point2D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public bool InRange(Point3D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public bool InRange(IPoint2D p, int range) => - p.X >= Location.m_X - range - && p.X <= Location.m_X + range - && p.Y >= Location.m_Y - range - && p.Y <= Location.m_Y + range; - } -} +/*************************************************************************** + * IEntity.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server +{ + public interface IEntity : IPoint3D, IComparable + { + Serial Serial { get; } + Point3D Location { get; } + Map Map { get; } + bool Deleted { get; } + void MoveToWorld(Point3D location, Map map); + + void Delete(); + void ProcessDelta(); + + bool InRange(Point2D p, int range); + + bool InRange(Point3D p, int range); + + bool InRange(IPoint2D p, int range); + } + + public class Entity : IEntity, IComparable + { + public Entity(Serial serial, Point3D loc, Map map) + { + Serial = serial; + Location = loc; + Map = map; + Deleted = false; + } + + public int CompareTo(Entity other) => CompareTo((IEntity)other); + + public int CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); + + public Serial Serial { get; } + + public Point3D Location { get; private set; } + + public int X => Location.X; + + public int Y => Location.Y; + + public int Z => Location.Z; + + public Map Map { get; private set; } + + public virtual void MoveToWorld(Point3D newLocation, Map map) + { + Location = newLocation; + Map = map; + } + + public bool Deleted { get; } + + public void Delete() + { + } + + public void ProcessDelta() + { + } + + public bool InRange(Point2D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public bool InRange(Point3D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public bool InRange(IPoint2D p, int range) => + p.X >= Location.m_X - range + && p.X <= Location.m_X + range + && p.Y >= Location.m_Y - range + && p.Y <= Location.m_Y + range; + } +} diff --git a/Projects/Server/Insensitive.cs b/Projects/Server/Insensitive.cs index 8b4c54632..1bb022df9 100644 --- a/Projects/Server/Insensitive.cs +++ b/Projects/Server/Insensitive.cs @@ -1,44 +1,44 @@ -/*************************************************************************** - * Insensitive.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server -{ - public static class Insensitive - { - public static IComparer Comparer { get; } = StringComparer.OrdinalIgnoreCase; - - public static int Compare(string a, string b) => Comparer.Compare(a, b); - - public static bool Equals(string a, string b) => - (a == null && b == null) || (a != null && b != null && a.Length == b.Length && Comparer.Compare(a, b) == 0); - - public static bool StartsWith(string a, string b) => - a != null && b != null && a.Length >= b.Length && Comparer.Compare(a.Substring(0, b.Length), b) == 0; - - public static bool EndsWith(string a, string b) => - a != null && b != null && a.Length >= b.Length && Comparer.Compare(a.Substring(a.Length - b.Length), b) == 0; - - public static bool Contains(string a, string b) => - a != null && b != null && a.Length >= b.Length && a.IndexOf(b, StringComparison.OrdinalIgnoreCase) >= 0; - } -} +/*************************************************************************** + * Insensitive.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server +{ + public static class Insensitive + { + public static IComparer Comparer { get; } = StringComparer.OrdinalIgnoreCase; + + public static int Compare(string a, string b) => Comparer.Compare(a, b); + + public static bool Equals(string a, string b) => + a == null && b == null || a != null && b != null && a.Length == b.Length && Comparer.Compare(a, b) == 0; + + public static bool StartsWith(string a, string b) => + a != null && b != null && a.Length >= b.Length && Comparer.Compare(a.Substring(0, b.Length), b) == 0; + + public static bool EndsWith(string a, string b) => + a != null && b != null && a.Length >= b.Length && Comparer.Compare(a.Substring(a.Length - b.Length), b) == 0; + + public static bool Contains(string a, string b) => + a != null && b != null && a.Length >= b.Length && a.IndexOf(b, StringComparison.OrdinalIgnoreCase) >= 0; + } +} diff --git a/Projects/Server/Interfaces.cs b/Projects/Server/Interfaces.cs index c2d60a8d4..1cb6ad885 100644 --- a/Projects/Server/Interfaces.cs +++ b/Projects/Server/Interfaces.cs @@ -1,95 +1,95 @@ -/*************************************************************************** - * Interfaces.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server -{ - public interface IPoint2D - { - int X { get; } - int Y { get; } - } - - public interface IPoint3D : IPoint2D - { - int Z { get; } - } - - public interface ICarvable - { - void Carve(Mobile from, Item item); - } - - public interface IWeapon - { - int MaxRange { get; } - void OnBeforeSwing(Mobile attacker, Mobile defender); - TimeSpan OnSwing(Mobile attacker, Mobile defender); - TimeSpan OnSwing(Mobile attacker, Mobile defender, double damageBonus); - void GetStatusDamage(Mobile from, out int min, out int max); - } - - public interface IHued - { - int HuedItemID { get; } - } - - public interface ISpell - { - bool IsCasting { get; } - void OnCasterHurt(); - void OnCasterKilled(); - void OnConnectionChanged(); - bool OnCasterMoving(Direction d); - bool OnCasterEquipping(Item item); - bool OnCasterUsingObject(IEntity entity); - bool OnCastInTown(Region r); - void FinishSequence(); - } - - public interface IParty - { - void OnStamChanged(Mobile m); - void OnManaChanged(Mobile m); - void OnStatsQuery(Mobile beholder, Mobile beheld); - } - - // TODO: Add SpawnMap and change Spawner.Map to use it - public interface ISpawner : IEntity - { - bool UnlinkOnTaming { get; } - Point3D HomeLocation { get; } - int HomeRange { get; } - Region Region { get; } - bool ReturnOnDeactivate { get; } - - void Remove(ISpawnable spawn); - Point3D GetSpawnPosition(ISpawnable spawned, Map map); - void Respawn(); - } - - public interface ISpawnable : IEntity - { - ISpawner Spawner { get; set; } - void OnBeforeSpawn(Point3D location, Map map); - void OnAfterSpawn(); - } -} +/*************************************************************************** + * Interfaces.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server +{ + public interface IPoint2D + { + int X { get; } + int Y { get; } + } + + public interface IPoint3D : IPoint2D + { + int Z { get; } + } + + public interface ICarvable + { + void Carve(Mobile from, Item item); + } + + public interface IWeapon + { + int MaxRange { get; } + void OnBeforeSwing(Mobile attacker, Mobile defender); + TimeSpan OnSwing(Mobile attacker, Mobile defender); + TimeSpan OnSwing(Mobile attacker, Mobile defender, double damageBonus); + void GetStatusDamage(Mobile from, out int min, out int max); + } + + public interface IHued + { + int HuedItemID { get; } + } + + public interface ISpell + { + bool IsCasting { get; } + void OnCasterHurt(); + void OnCasterKilled(); + void OnConnectionChanged(); + bool OnCasterMoving(Direction d); + bool OnCasterEquipping(Item item); + bool OnCasterUsingObject(IEntity entity); + bool OnCastInTown(Region r); + void FinishSequence(); + } + + public interface IParty + { + void OnStamChanged(Mobile m); + void OnManaChanged(Mobile m); + void OnStatsQuery(Mobile beholder, Mobile beheld); + } + + // TODO: Add SpawnMap and change Spawner.Map to use it + public interface ISpawner : IEntity + { + bool UnlinkOnTaming { get; } + Point3D HomeLocation { get; } + int HomeRange { get; } + Region Region { get; } + bool ReturnOnDeactivate { get; } + + void Remove(ISpawnable spawn); + Point3D GetSpawnPosition(ISpawnable spawned, Map map); + void Respawn(); + } + + public interface ISpawnable : IEntity + { + ISpawner Spawner { get; set; } + void OnBeforeSpawn(Point3D location, Map map); + void OnAfterSpawn(); + } +} diff --git a/Projects/Server/Item.cs b/Projects/Server/Item.cs index 36f45c5a5..ce4b842d0 100644 --- a/Projects/Server/Item.cs +++ b/Projects/Server/Item.cs @@ -1,3720 +1,3789 @@ -/*************************************************************************** - * Item.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Server.ContextMenus; -using Server.Items; -using Server.Network; -using Server.Targeting; - -namespace Server -{ - /// - /// Internal flags used to signal how the item should be updated and resent to nearby clients. - /// - [Flags] - public enum ItemDelta - { - /// - /// Nothing. - /// - None = 0x00000000, - - /// - /// Resend the item. - /// - Update = 0x00000001, - - /// - /// Resend the item only if it is equipped. - /// - EquipOnly = 0x00000002, - - /// - /// Resend the item's properties. - /// - Properties = 0x00000004 - } - - /// - /// Enumeration containing possible ways to handle item ownership on death. - /// - public enum DeathMoveResult - { - /// - /// The item should be placed onto the corpse. - /// - MoveToCorpse, - - /// - /// The item should remain equipped. - /// - RemainEquipped, - - /// - /// The item should be placed into the owners backpack. - /// - MoveToBackpack - } - - /// - /// Enumeration of an item's loot and steal state. - /// - public enum LootType : byte - { - /// - /// Stealable. Lootable. - /// - Regular = 0, - - /// - /// Unstealable. Unlootable, unless owned by a murderer. - /// - Newbied = 1, - - /// - /// Unstealable. Unlootable, always. - /// - Blessed = 2, - - /// - /// Stealable. Lootable, always. - /// - Cursed = 3 - } - - public class BounceInfo - { - public Point3D Location { get; set; } - public Point3D WorldLoc { get; set; } - public Map Map { get; set; } - public IEntity Parent { get; set; } - - public BounceInfo(Item item) - { - Map = item.Map; - Location = item.Location; - WorldLoc = item.GetWorldLocation(); - Parent = item.Parent; - } - - private BounceInfo(Map map, Point3D loc, Point3D worldLoc, IEntity parent) - { - Map = map; - Location = loc; - WorldLoc = worldLoc; - Parent = parent; - } - - public static BounceInfo Deserialize(IGenericReader reader) - { - if (reader.ReadBool()) - { - var map = reader.ReadMap(); - var loc = reader.ReadPoint3D(); - var worldLoc = reader.ReadPoint3D(); - - IEntity parent; - - Serial serial = reader.ReadUInt(); - - if (serial.IsItem) - parent = World.FindItem(serial); - else if (serial.IsMobile) - parent = World.FindMobile(serial); - else - parent = null; - - return new BounceInfo(map, loc, worldLoc, parent); - } - - return null; - } - - public static void Serialize(BounceInfo info, IGenericWriter writer) - { - if (info == null) - { - writer.Write(false); - } - else - { - writer.Write(true); - - writer.Write(info.Map); - writer.Write(info.Location); - writer.Write(info.WorldLoc); - - if (info.Parent is Mobile mobile) - writer.Write(mobile); - else if (info.Parent is Item item) - writer.Write(item); - else - writer.Write((Serial)0); - } - } - } - - public enum TotalType - { - Gold, - Items, - Weight - } - - [Flags] - public enum ExpandFlag - { - None = 0x000, - - Name = 0x001, - Items = 0x002, - Bounce = 0x004, - Holder = 0x008, - Blessed = 0x010, - TempFlag = 0x020, - SaveFlag = 0x040, - Weight = 0x080, - Spawner = 0x100 - } - - public class Item : IHued, IComparable, ISerializable, ISpawnable, IPropertyListObject - { - private readonly BufferWriter m_SaveBuffer; - public BufferWriter SaveBuffer => m_SaveBuffer; - - public const int QuestItemHue = 0x4EA; // Hmmmm... "for EA"? - public static readonly List EmptyItems = new List(); - - private static readonly List m_DeltaQueue = new List(); - - private static bool _processing; - - private static int m_OpenSlots; - - private CompactInfo m_CompactInfo; - - private ItemDelta m_DeltaFlags; - private ImplFlag m_Flags; - - [Constructible] - public Item(int itemID = 0) - { - m_ItemID = itemID; - Serial = Serial.NewItem; - - // m_Items = new ArrayList( 1 ); - Visible = true; - Movable = true; - Amount = 1; - m_Map = Map.Internal; - - SetLastMoved(); - - World.AddItem(this); - - var ourType = GetType(); - TypeRef = World.m_ItemTypes.IndexOf(ourType); - - if (TypeRef == -1) - { - World.m_ItemTypes.Add(ourType); - TypeRef = World.m_ItemTypes.Count - 1; - } - - m_SaveBuffer = new BufferWriter(true); - } - - public Item(Serial serial) - { - Serial = serial; - - var ourType = GetType(); - TypeRef = World.m_ItemTypes.IndexOf(ourType); - - if (TypeRef == -1) - { - World.m_ItemTypes.Add(ourType); - TypeRef = World.m_ItemTypes.Count - 1; - } - - m_SaveBuffer = new BufferWriter(true); - } - - public int TempFlags - { - get => LookupCompactInfo()?.m_TempFlags ?? 0; - set - { - var info = AcquireCompactInfo(); - - info.m_TempFlags = value; - - if (info.m_TempFlags == 0) - VerifyCompactInfo(); - } - } - - public int SavedFlags - { - get => LookupCompactInfo()?.m_SavedFlags ?? 0; - set - { - var info = AcquireCompactInfo(); - - info.m_SavedFlags = value; - - if (info.m_SavedFlags == 0) - VerifyCompactInfo(); - } - } - - /// - /// The who is currently holding this item. - /// - public Mobile HeldBy - { - get => LookupCompactInfo()?.m_HeldBy; - set - { - var info = AcquireCompactInfo(); - - info.m_HeldBy = value; - - if (info.m_HeldBy == null) - VerifyCompactInfo(); - } - } - - /// - /// Overridable. Determines whether the item will show . - /// - public virtual bool DisplayWeight => Core.ML && (Movable || IsLockedDown || IsSecure || ItemData.Weight != 255); - - [CommandProperty(AccessLevel.GameMaster)] - public LootType LootType - { - get => m_LootType; - set - { - if (m_LootType != value) - { - m_LootType = value; - - if (DisplayLootType) - InvalidateProperties(); - } - } - } - - public static TimeSpan DefaultDecayTime { get; set; } = TimeSpan.FromHours(1.0); - - [CommandProperty(AccessLevel.GameMaster)] - public virtual TimeSpan DecayTime => DefaultDecayTime; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual bool Decays => Movable && Visible; - - public DateTime LastMoved { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Stackable - { - get => GetFlag(ImplFlag.Stackable); - set => SetFlag(ImplFlag.Stackable, value); - } - - public Packet RemovePacket => StaticPacketHandlers.GetRemoveEntityPacket(this); - public OPLInfo OPLPacket => StaticPacketHandlers.GetOPLInfoPacket(this); - - private ObjectPropertyList m_PropertyList; - public ObjectPropertyList PropertyList => m_PropertyList ??= NewObjectPropertyList(); - - public void ReleaseOPLPacket() - { - if (m_PropertyList == null) - return; - - Packet.Release(m_PropertyList); - m_PropertyList = null; - } - - // World packets need to be invalidated when any of the following changes: - // - ItemID - // - Amount - // - Location - // - Hue - // - Packet Flags - // - Direction - public Packet WorldPacket => StaticPacketHandlers.GetWorldItemPacket(this); - public Packet WorldPacketSA => StaticPacketHandlers.GetWorldItemSAPacket(this); - public Packet WorldPacketHS => StaticPacketHandlers.GetWorldItemHSPacket(this); - - [CommandProperty(AccessLevel.GameMaster)] - public bool Visible - { - get => GetFlag(ImplFlag.Visible); - set - { - if (GetFlag(ImplFlag.Visible) != value) - { - SetFlag(ImplFlag.Visible, value); - ReleaseWorldPackets(); - - if (m_Map != null) - { - var worldLoc = GetWorldLocation(); - - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (!m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) - state.Send(RemovePacket); - } - - eable.Free(); - } - - Delta(ItemDelta.Update); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Movable - { - get => GetFlag(ImplFlag.Movable); - set - { - if (GetFlag(ImplFlag.Movable) != value) - { - SetFlag(ImplFlag.Movable, value); - ReleaseWorldPackets(); - Delta(ItemDelta.Update); - } - } - } - - public virtual bool ForceShowProperties => false; - - public virtual bool HandlesOnMovement => false; - - public static int LockedDownFlag { get; set; } - - public static int SecureFlag { get; set; } - - public bool IsLockedDown - { - get => GetTempFlag(LockedDownFlag); - set - { - SetTempFlag(LockedDownFlag, value); - InvalidateProperties(); - } - } - - public bool IsSecure - { - get => GetTempFlag(SecureFlag); - set - { - SetTempFlag(SecureFlag, value); - InvalidateProperties(); - } - } - - public virtual bool IsVirtualItem => false; - - public virtual int LabelNumber - { - get - { - if (m_ItemID < 0x4000) - return 1020000 + m_ItemID; - - return 1078872 + m_ItemID; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalGold => GetTotal(TotalType.Gold); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalItems => GetTotal(TotalType.Items); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalWeight => GetTotal(TotalType.Weight); - - public virtual double DefaultWeight - { - get - { - if (m_ItemID < 0 || m_ItemID > TileData.MaxItemValue || this is BaseMulti) - return 0; - - var weight = TileData.ItemTable[m_ItemID].Weight; - - if (weight == 255 || weight == 0) - weight = 1; - - return weight; - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public double Weight - { - get - { - var info = LookupCompactInfo(); - - return info != null && info.m_Weight != -1 ? info.m_Weight : DefaultWeight; - } - set - { - if (Weight != value) - { - var info = AcquireCompactInfo(); - - var oldPileWeight = PileWeight; - - info.m_Weight = value; - - if (info.m_Weight == -1) - VerifyCompactInfo(); - - var newPileWeight = PileWeight; - - UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); - - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int PileWeight => (int)Math.Ceiling(Weight * Amount); - - [Hue] - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Hue - { - get => m_Hue; - set - { - if (m_Hue != value) - { - m_Hue = value; - ReleaseWorldPackets(); - - Delta(ItemDelta.Update); - } - } - } - - public virtual bool Nontransferable => QuestItem; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual Layer Layer - { - get => m_Layer; - set - { - if (m_Layer != value) - { - m_Layer = value; - - Delta(ItemDelta.EquipOnly); - } - } - } - - public List Items => LookupItems() ?? EmptyItems; - - [CommandProperty(AccessLevel.GameMaster)] - public IEntity RootParent - { - get - { - var p = m_Parent; - - while (p is Item item) - { - if (item.m_Parent == null) break; - - p = item.m_Parent; - } - - return p; - } - } - - public bool NoMoveHS { get; set; } - - public virtual int PhysicalResistance => 0; - public virtual int FireResistance => 0; - public virtual int ColdResistance => 0; - public virtual int PoisonResistance => 0; - public virtual int EnergyResistance => 0; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int ItemID - { - get => m_ItemID; - set - { - if (m_ItemID != value) - { - var oldPileWeight = PileWeight; - - m_ItemID = value; - ReleaseWorldPackets(); - - var newPileWeight = PileWeight; - - UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); - - InvalidateProperties(); - Delta(ItemDelta.Update); - } - } - } - - public virtual string DefaultName => null; - - [CommandProperty(AccessLevel.GameMaster)] - public string Name - { - get => LookupCompactInfo()?.m_Name ?? DefaultName; - set - { - if (value == null || value != DefaultName) - { - var info = AcquireCompactInfo(); - - info.m_Name = value; - - if (info.m_Name == null) - VerifyCompactInfo(); - - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Developer)] - public IEntity Parent - { - get => m_Parent; - set - { - if (m_Parent == value) - return; - - var oldParent = m_Parent; - - m_Parent = value; - - if (m_Map != null) - { - if (oldParent != null && m_Parent == null) - m_Map.OnEnter(this); - else if (m_Parent != null) - m_Map.OnLeave(this); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public LightType Light - { - get => (LightType)m_Direction; - set - { - if ((LightType)m_Direction != value) - { - m_Direction = (Direction)value; - ReleaseWorldPackets(); - - Delta(ItemDelta.Update); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Direction Direction - { - get => m_Direction; - set - { - if (m_Direction != value) - { - m_Direction = value; - ReleaseWorldPackets(); - - Delta(ItemDelta.Update); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Amount - { - get => m_Amount; - set - { - var oldValue = m_Amount; - - if (oldValue != value) - { - var oldPileWeight = PileWeight; - - m_Amount = value; - ReleaseWorldPackets(); - - var newPileWeight = PileWeight; - - UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); - - OnAmountChange(oldValue); - - Delta(ItemDelta.Update); - - if (oldValue > 1 || value > 1) - InvalidateProperties(); - - if (!Stackable && m_Amount > 1) - Console.WriteLine("Warning: 0x{0:X}: Amount changed for non-stackable item '{2}'. ({1})", - Serial.Value, m_Amount, GetType().Name); - } - } - } - - public virtual bool HandlesOnSpeech => false; - - public virtual bool BlocksFit => false; - - public bool InSecureTrade => GetSecureTradeCont() != null; - - public ItemData ItemData => TileData.ItemTable[m_ItemID & TileData.MaxItemValue]; - - public virtual bool CanTarget => true; - public virtual bool DisplayLootType => true; - - public static bool ScissorCopyLootType { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool QuestItem - { - get => GetFlag(ImplFlag.QuestItem); - set - { - SetFlag(ImplFlag.QuestItem, value); - - InvalidateProperties(); - - ReleaseWorldPackets(); - - Delta(ItemDelta.Update); - } - } - - public bool Insured - { - get => GetFlag(ImplFlag.Insured); - set - { - SetFlag(ImplFlag.Insured, value); - InvalidateProperties(); - } - } - - public bool PaidInsurance - { - get => GetFlag(ImplFlag.PaidInsurance); - set => SetFlag(ImplFlag.PaidInsurance, value); - } - - public Mobile BlessedFor - { - get => LookupCompactInfo()?.m_BlessedFor; - set - { - var info = AcquireCompactInfo(); - - info.m_BlessedFor = value; - - if (info.m_BlessedFor == null) - VerifyCompactInfo(); - - InvalidateProperties(); - } - } - - int IComparable.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); - - public int CompareTo(Item other) => other == null ? -1 : Serial.CompareTo(other.Serial); - - /// - /// Moves the Item to a given and . - /// - public void MoveToWorld(Point3D location, Map map) - { - if (Deleted) - return; - - var oldLocation = GetWorldLocation(); - var oldRealLocation = m_Location; - - SetLastMoved(); - - if (Parent is Mobile mobile) - mobile.RemoveItem(this); - else if (Parent is Item item) - item.RemoveItem(this); - - if (m_Map != map) - { - var old = m_Map; - - if (m_Map != null) - { - m_Map.OnLeave(this); - - if (oldLocation.m_X != 0) - { - var eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.InRange(oldLocation, GetUpdateRange(m))) - state.Send(RemovePacket); - } - - eable.Free(); - } - } - - m_Location = location; - OnLocationChange(oldRealLocation); - - ReleaseWorldPackets(); - - var items = LookupItems(); - - if (items != null) - for (var i = 0; i < items.Count; ++i) - items[i].Map = map; - - m_Map = map; - m_Map?.OnEnter(this); - - OnMapChange(); - - if (m_Map != null) - { - var eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) - SendInfoTo(state); - } - - eable.Free(); - } - - RemDelta(ItemDelta.Update); - - if (old == null || old == Map.Internal) - InvalidateProperties(); - } - else if (m_Map != null) - { - IPooledEnumerable eable; - - if (oldLocation.m_X != 0) - { - eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (!m.InRange(location, GetUpdateRange(m))) state.Send(RemovePacket); - } - - eable.Free(); - } - - var oldInternalLocation = m_Location; - - m_Location = location; - OnLocationChange(oldRealLocation); - - ReleaseWorldPackets(); - - eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) - SendInfoTo(state); - } - - eable.Free(); - - m_Map.OnMove(oldInternalLocation, this); - - RemDelta(ItemDelta.Update); - } - else - { - Map = map; - Location = location; - } - } - - /// - /// Has the item been deleted? - /// - public bool Deleted => GetFlag(ImplFlag.Deleted); - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Map Map - { - get => m_Map; - set - { - if (m_Map != value) - { - var old = m_Map; - - if (m_Map != null && m_Parent == null) - { - m_Map.OnLeave(this); - SendRemovePacket(); - } - - var items = LookupItems(); - - if (items != null) - for (var i = 0; i < items.Count; ++i) - items[i].Map = value; - - m_Map = value; - - if (m_Parent == null) - m_Map?.OnEnter(this); - - Delta(ItemDelta.Update); - - OnMapChange(); - - if (old == null || old == Map.Internal) - InvalidateProperties(); - } - } - } - - public virtual void ProcessDelta() - { - var flags = m_DeltaFlags; - - SetFlag(ImplFlag.InQueue, false); - m_DeltaFlags = ItemDelta.None; - - var map = m_Map; - - if (map == null || Deleted) - return; - - var worldLoc = GetWorldLocation(); - var update = (flags & ItemDelta.Update) != 0; - - if (update && m_Parent is Container contParent && !contParent.IsPublicContainer) - { - var rootParent = contParent.RootParent as Mobile; - Mobile tradeRecip = null; - - if (rootParent != null) - { - var ns = rootParent.NetState; - - if (ns != null && rootParent.CanSee(this) && rootParent.InRange(worldLoc, GetUpdateRange(rootParent))) - { - if (ns.ContainerGridLines) - ns.Send(new ContainerContentUpdate6017(this)); - else - ns.Send(new ContainerContentUpdate(this)); - - if (ObjectPropertyList.Enabled) - ns.Send(OPLPacket); - } - } - - var st = GetSecureTradeCont()?.Trade; - - if (st != null) - { - var test = st.From.Mobile; - - if (test != null && test != rootParent) - tradeRecip = test; - - test = st.To.Mobile; - - if (test != null && test != rootParent) - tradeRecip = test; - - var ns = tradeRecip?.NetState; - - if (ns != null && tradeRecip.CanSee(this) && tradeRecip.InRange(worldLoc, GetUpdateRange(tradeRecip))) - { - if (ns.ContainerGridLines) - ns.Send(new ContainerContentUpdate6017(this)); - else - ns.Send(new ContainerContentUpdate(this)); - - if (ObjectPropertyList.Enabled) - ns.Send(OPLPacket); - } - } - - var openers = contParent.Openers; - - if (openers != null) - lock (openers) - { - for (var i = 0; i < openers.Count; ++i) - { - var mob = openers[i]; - - var range = GetUpdateRange(mob); - - if (mob.Map != map || !mob.InRange(worldLoc, range)) - { - openers.RemoveAt(i--); - } - else - { - if (mob == rootParent || mob == tradeRecip) - continue; - - var ns = mob.NetState; - - if (ns != null && mob.CanSee(this)) - { - if (ns.ContainerGridLines) - ns.Send(new ContainerContentUpdate6017(this)); - else - ns.Send(new ContainerContentUpdate(this)); - - if (ObjectPropertyList.Enabled) - ns.Send(OPLPacket); - } - } - } - - if (openers.Count == 0) - contParent.Openers = null; - } - - return; - } - - Packet p = null; - - var eable = map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (!m.CanSee(this) || !m.InRange(worldLoc, GetUpdateRange(m))) continue; - - if (update) - { - if (m_Parent == null) - { - SendInfoTo(state, ObjectPropertyList.Enabled); - } - else - { - if (p != null) - { - state.Send(p); - } - else if (m_Parent is Item) - { - if (state.ContainerGridLines) - state.Send(new ContainerContentUpdate6017(this)); - else - state.Send(new ContainerContentUpdate(this)); - } - else if (m_Parent is Mobile) - { - p = new EquipUpdate(this); - p.Acquire(); - - state.Send(p); - } - - if (ObjectPropertyList.Enabled) - state.Send(OPLPacket); - } - } - else if ((flags & ItemDelta.EquipOnly) != 0 && m_Parent is Mobile) - { - state.Send(p ??= Packet.Acquire(new EquipUpdate(this))); - - if (ObjectPropertyList.Enabled) - state.Send(OPLPacket); - } - else if (ObjectPropertyList.Enabled && (flags & ItemDelta.Properties) != 0) - { - state.Send(OPLPacket); - } - } - - Packet.Release(p); - eable.Free(); - } - - public virtual void Delete() - { - if (Deleted || !World.OnDelete(this)) - return; - - OnDelete(); - - var items = LookupItems(); - - if (items != null) - for (var i = items.Count - 1; i >= 0; --i) - if (i < items.Count) - items[i].OnParentDeleted(this); - - SendRemovePacket(); - - SetFlag(ImplFlag.Deleted, true); - - if (Parent is Mobile mobile) - mobile.RemoveItem(this); - else if (Parent is Item item) - item.RemoveItem(this); - - ClearBounce(); - - if (m_Map != null) - { - if (m_Parent == null) - m_Map.OnLeave(this); - m_Map = null; - } - - World.RemoveItem(this); - - OnAfterDelete(); - - FreeCache(); - } - - [CommandProperty(AccessLevel.Counselor)] - public Serial Serial { get; } - - public virtual int HuedItemID => m_ItemID; - - public int TypeRef { get; } - public void Serialize() - { - SaveBuffer.Flush(); - Serialize(SaveBuffer); - } - - public virtual void Serialize(IGenericWriter writer) - { - writer.Write(9); // version - - var flags = SaveFlag.None; - - int x = m_Location.m_X, y = m_Location.m_Y, z = m_Location.m_Z; - - if (x != 0 || y != 0 || z != 0) - { - if (x >= short.MinValue && x <= short.MaxValue && y >= short.MinValue && y <= short.MaxValue && - z >= sbyte.MinValue && z <= sbyte.MaxValue) - { - if (x != 0 || y != 0) - { - if (x >= byte.MinValue && x <= byte.MaxValue && y >= byte.MinValue && y <= byte.MaxValue) - flags |= SaveFlag.LocationByteXY; - else - flags |= SaveFlag.LocationShortXY; - } - - if (z != 0) - flags |= SaveFlag.LocationSByteZ; - } - else - { - flags |= SaveFlag.LocationFull; - } - } - - var info = LookupCompactInfo(); - var items = LookupItems(); - - if (m_Direction != Direction.North) - flags |= SaveFlag.Direction; - if (info?.m_Bounce != null) - flags |= SaveFlag.Bounce; - if (m_LootType != LootType.Regular) - flags |= SaveFlag.LootType; - if (m_ItemID != 0) - flags |= SaveFlag.ItemID; - if (m_Hue != 0) - flags |= SaveFlag.Hue; - if (m_Amount != 1) - flags |= SaveFlag.Amount; - if (m_Layer != Layer.Invalid) - flags |= SaveFlag.Layer; - if (info?.m_Name != null) - flags |= SaveFlag.Name; - if (m_Parent != null) - flags |= SaveFlag.Parent; - if (items != null && items.Count > 0) - flags |= SaveFlag.Items; - if (m_Map != Map.Internal) - flags |= SaveFlag.Map; - // if (m_InsuredFor != null && !m_InsuredFor.Deleted) - // flags |= SaveFlag.InsuredFor; - - if (info != null) - { - if (info.m_BlessedFor?.Deleted == false) - flags |= SaveFlag.BlessedFor; - if (info.m_HeldBy?.Deleted == false) - flags |= SaveFlag.HeldBy; - if (info.m_SavedFlags != 0) - flags |= SaveFlag.SavedFlags; - } - - if (info == null || info.m_Weight == -1.0) - { - flags |= SaveFlag.NullWeight; - } - else - { - if (info.m_Weight == 0.0) - { - flags |= SaveFlag.WeightIs0; - } - else if (info.m_Weight != 1.0) - { - if (info.m_Weight == (int)info.m_Weight) - flags |= SaveFlag.IntWeight; - else - flags |= SaveFlag.WeightNot1or0; - } - } - - var implFlags = m_Flags & (ImplFlag.Visible | ImplFlag.Movable | ImplFlag.Stackable | ImplFlag.Insured | - ImplFlag.PaidInsurance | ImplFlag.QuestItem); - - if (implFlags != (ImplFlag.Visible | ImplFlag.Movable)) - flags |= SaveFlag.ImplFlags; - - writer.Write((int)flags); - - /* begin last moved time optimization */ - var ticks = LastMoved.Ticks; - var now = DateTime.UtcNow.Ticks; - - var minutes = new TimeSpan(now - ticks).TotalMinutes; - - writer.WriteEncodedInt((int)Math.Clamp(minutes, int.MinValue, int.MaxValue)); - /* end */ - - if (GetSaveFlag(flags, SaveFlag.Direction)) - writer.Write((byte)m_Direction); - - if (GetSaveFlag(flags, SaveFlag.Bounce)) - BounceInfo.Serialize(info?.m_Bounce, writer); - - if (GetSaveFlag(flags, SaveFlag.LootType)) - writer.Write((byte)m_LootType); - - if (GetSaveFlag(flags, SaveFlag.LocationFull)) - { - writer.WriteEncodedInt(x); - writer.WriteEncodedInt(y); - writer.WriteEncodedInt(z); - } - else - { - if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) - { - writer.Write((byte)x); - writer.Write((byte)y); - } - else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) - { - writer.Write((short)x); - writer.Write((short)y); - } - - if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) - writer.Write((sbyte)z); - } - - if (GetSaveFlag(flags, SaveFlag.ItemID)) - writer.WriteEncodedInt(m_ItemID); - - if (GetSaveFlag(flags, SaveFlag.Hue)) - writer.WriteEncodedInt(m_Hue); - - if (GetSaveFlag(flags, SaveFlag.Amount)) - writer.WriteEncodedInt(m_Amount); - - if (GetSaveFlag(flags, SaveFlag.Layer)) - writer.Write((byte)m_Layer); - - if (GetSaveFlag(flags, SaveFlag.Name)) - writer.Write(info.m_Name); - - if (GetSaveFlag(flags, SaveFlag.Parent)) - { - if (m_Parent?.Deleted == false) - writer.Write(m_Parent.Serial); - else - writer.Write(Serial.MinusOne); - } - - if (GetSaveFlag(flags, SaveFlag.Items)) - writer.Write(items, false); - - if (GetSaveFlag(flags, SaveFlag.IntWeight)) - writer.WriteEncodedInt((int)info.m_Weight); - else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) - writer.Write(info.m_Weight); - - if (GetSaveFlag(flags, SaveFlag.Map)) - writer.Write(m_Map); - - if (GetSaveFlag(flags, SaveFlag.ImplFlags)) - writer.WriteEncodedInt((int)implFlags); - - if (GetSaveFlag(flags, SaveFlag.InsuredFor)) - writer.Write((Mobile)null); - - if (GetSaveFlag(flags, SaveFlag.BlessedFor)) - writer.Write(info.m_BlessedFor); - - if (GetSaveFlag(flags, SaveFlag.HeldBy)) - writer.Write(info.m_HeldBy); - - if (GetSaveFlag(flags, SaveFlag.SavedFlags)) - writer.WriteEncodedInt(info.m_SavedFlags); - } - - public ISpawner Spawner - { - get => LookupCompactInfo()?.m_Spawner; - set - { - var info = AcquireCompactInfo(); - - info.m_Spawner = value; - - if (info.m_Spawner == null) - VerifyCompactInfo(); - } - } - - public virtual void OnBeforeSpawn(Point3D location, Map m) - { - } - - public virtual void OnAfterSpawn() - { - } - - public ExpandFlag GetExpandFlags() - { - var info = LookupCompactInfo(); - - ExpandFlag flags = 0; - - if (info != null) - { - if (info.m_BlessedFor != null) - flags |= ExpandFlag.Blessed; - - if (info.m_Bounce != null) - flags |= ExpandFlag.Bounce; - - if (info.m_HeldBy != null) - flags |= ExpandFlag.Holder; - - if (info.m_Items != null) - flags |= ExpandFlag.Items; - - if (info.m_Name != null) - flags |= ExpandFlag.Name; - - if (info.m_Spawner != null) - flags |= ExpandFlag.Spawner; - - if (info.m_SavedFlags != 0) - flags |= ExpandFlag.SaveFlag; - - if (info.m_TempFlags != 0) - flags |= ExpandFlag.TempFlag; - - if (info.m_Weight != -1) - flags |= ExpandFlag.Weight; - } - - return flags; - } - - private CompactInfo LookupCompactInfo() => m_CompactInfo; - - private CompactInfo AcquireCompactInfo() => m_CompactInfo ??= new CompactInfo(); - - private void ReleaseCompactInfo() - { - m_CompactInfo = null; - } - - private void VerifyCompactInfo() - { - var info = m_CompactInfo; - - if (info == null) - return; - - var isValid = info.m_Name != null - || info.m_Items != null - || info.m_Bounce != null - || info.m_HeldBy != null - || info.m_BlessedFor != null - || info.m_Spawner != null - || info.m_TempFlags != 0 - || info.m_SavedFlags != 0 - || info.m_Weight != -1; - - if (!isValid) - ReleaseCompactInfo(); - } - - public List LookupItems() - { - if (this is Container container) - return container.m_Items; - - return LookupCompactInfo()?.m_Items; - } - - public List AcquireItems() - { - if (this is Container cont) - return cont.m_Items ?? (cont.m_Items = new List()); - - var info = AcquireCompactInfo(); - return info.m_Items ?? (info.m_Items = new List()); - } - - private void SetFlag(ImplFlag flag, bool value) - { - if (value) - m_Flags |= flag; - else - m_Flags &= ~flag; - } - - private bool GetFlag(ImplFlag flag) => (m_Flags & flag) != 0; - - public BounceInfo GetBounce() => LookupCompactInfo()?.m_Bounce; - - public void RecordBounce() - { - AcquireCompactInfo().m_Bounce = new BounceInfo(this); - } - - public void ClearBounce() - { - var info = LookupCompactInfo(); - - var bounce = info?.m_Bounce; - - if (bounce == null) - return; - - info.m_Bounce = null; - - if (bounce.Parent is Item parentItem) - { - if (!parentItem.Deleted) - parentItem.OnItemBounceCleared(this); - } - else if (bounce.Parent is Mobile parentMobile) - { - if (!parentMobile.Deleted) - parentMobile.OnItemBounceCleared(this); - } - - VerifyCompactInfo(); - } - - /// - /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Item. - /// Seemingly no longer functional in newer clients. - /// - public virtual void OnHelpRequest(Mobile from) - { - } - - /// - /// Overridable. Method checked to see if the item can be traded. - /// - /// True if the trade is allowed, false if not. - public virtual bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) => true; - - /// - /// Overridable. Virtual event invoked when a trade has completed, either successfully or not. - /// - public virtual void OnSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) - { - } - - /// - /// Overridable. Method checked to see if the elemental resistances of this Item conflict with another Item on the - /// . - /// - /// - /// - /// - /// True - /// - /// There is a conflict. The elemental resistance bonuses of this Item should not be applied to the - /// - /// - /// - /// - /// False - /// There is no conflict. The bonuses should be applied. - /// - /// - /// - public virtual bool CheckPropertyConflict(Mobile m) => false; - - /// - /// Overridable. Sends the object property list to . - /// - public virtual void SendPropertiesTo(Mobile from) - { - from.Send(PropertyList); - } - - /// - /// Overridable. Adds the name of this item to the given . This method should be overridden - /// if the item requires a complex naming format. - /// - public virtual void AddNameProperty(ObjectPropertyList list) - { - var name = Name; - - if (name == null) - { - if (m_Amount <= 1) - list.Add(LabelNumber); - else - list.Add(1050039, "{0}\t#{1}", m_Amount, LabelNumber); // ~1_NUMBER~ ~2_ITEMNAME~ - } - else - { - if (m_Amount <= 1) - list.Add(name); - else - list.Add(1050039, "{0}\t{1}", m_Amount, Name); // ~1_NUMBER~ ~2_ITEMNAME~ - } - } - - /// - /// Overridable. Adds the loot type of this item to the given . By default, this will be - /// either 'blessed', 'cursed', or 'insured'. - /// - public virtual void AddLootTypeProperty(ObjectPropertyList list) - { - if (m_LootType == LootType.Blessed) - list.Add(1038021); // blessed - else if (m_LootType == LootType.Cursed) - list.Add(1049643); // cursed - else if (Insured) - list.Add(1061682); // insured - } - - /// - /// Overridable. Adds any elemental resistances of this item to the given . - /// - public virtual void AddResistanceProperties(ObjectPropertyList list) - { - var v = PhysicalResistance; - - if (v != 0) - list.Add(1060448, v.ToString()); // physical resist ~1_val~% - - v = FireResistance; - - if (v != 0) - list.Add(1060447, v.ToString()); // fire resist ~1_val~% - - v = ColdResistance; - - if (v != 0) - list.Add(1060445, v.ToString()); // cold resist ~1_val~% - - v = PoisonResistance; - - if (v != 0) - list.Add(1060449, v.ToString()); // poison resist ~1_val~% - - v = EnergyResistance; - - if (v != 0) - list.Add(1060446, v.ToString()); // energy resist ~1_val~% - } - - /// - /// Overridable. Displays cliloc 1072788-1072789. - /// - public virtual void AddWeightProperty(ObjectPropertyList list) - { - var weight = PileWeight + TotalWeight; - - if (weight == 1) - list.Add(1072788, weight.ToString()); // Weight: ~1_WEIGHT~ stone - else - list.Add(1072789, weight.ToString()); // Weight: ~1_WEIGHT~ stones - } - - /// - /// Overridable. Adds header properties. By default, this invokes , - /// (if applicable), and (if - /// ). - /// - public virtual void AddNameProperties(ObjectPropertyList list) - { - AddNameProperty(list); - - if (IsSecure) - AddSecureProperty(list); - else if (IsLockedDown) - AddLockedDownProperty(list); - - var blessedFor = BlessedFor; - - if (blessedFor?.Deleted == false) - AddBlessedForProperty(list, blessedFor); - - if (DisplayLootType) - AddLootTypeProperty(list); - - if (DisplayWeight) - AddWeightProperty(list); - - if (QuestItem) - AddQuestItemProperty(list); - - AppendChildNameProperties(list); - } - - /// - /// Overridable. Adds the "Quest Item" property to the given . - /// - public virtual void AddQuestItemProperty(ObjectPropertyList list) - { - list.Add(1072351); // Quest Item - } - - /// - /// Overridable. Adds the "Locked Down & Secure" property to the given . - /// - public virtual void AddSecureProperty(ObjectPropertyList list) - { - list.Add(501644); // locked down & secure - } - - /// - /// Overridable. Adds the "Locked Down" property to the given . - /// - public virtual void AddLockedDownProperty(ObjectPropertyList list) - { - list.Add(501643); // locked down - } - - /// - /// Overridable. Adds the "Blessed for ~1_NAME~" property to the given . - /// - public virtual void AddBlessedForProperty(ObjectPropertyList list, Mobile m) - { - list.Add(1062203, "{0}", m.Name); // Blessed for ~1_NAME~ - } - - /// - /// Overridable. Fills an with everything applicable. By default, this invokes - /// , then Item.GetChildProperties or - /// Mobile.GetChildProperties. This method should be overridden to add any custom - /// properties. - /// - public virtual void GetProperties(ObjectPropertyList list) - { - AddNameProperties(list); - } - - /// - /// Overridable. Event invoked when a child () is building it's . - /// Recursively calls Item.GetChildProperties or - /// Mobile.GetChildProperties. - /// - public virtual void GetChildProperties(ObjectPropertyList list, Item item) - { - if (m_Parent is Item parentItem) - parentItem.GetChildProperties(list, item); - else if (m_Parent is Mobile parentMobile) - parentMobile.GetChildProperties(list, item); - } - - /// - /// Overridable. Event invoked when a child () is building it's Name - /// . Recursively calls Item.GetChildNameProperties or - /// Mobile.GetChildNameProperties. - /// - public virtual void GetChildNameProperties(ObjectPropertyList list, Item item) - { - if (m_Parent is Item parentItem) - parentItem.GetChildNameProperties(list, item); - else if (m_Parent is Mobile parentMobile) - parentMobile.GetChildNameProperties(list, item); - } - - public virtual bool IsChildVisibleTo(Mobile m, Item child) => true; - - public void Bounce(Mobile from) - { - if (m_Parent is Item item) - item.RemoveItem(this); - else if (m_Parent is Mobile mobile) - mobile.RemoveItem(this); - - m_Parent = null; - - var bounce = GetBounce(); - - if (bounce != null) - { - var parent = bounce.Parent; - - if (parent?.Deleted != false) - { - MoveToWorld(bounce.WorldLoc, bounce.Map); - } - else if (parent is Item p) - { - var root = p.RootParent; - - if (p.IsAccessibleTo(from) && - (!(root is Mobile mobileRoot) || mobileRoot.CheckNonlocalDrop(from, this, p))) - { - Location = bounce.Location; - p.AddItem(this); - } - else - { - MoveToWorld(from.Location, from.Map); - } - } - else if (parent is Mobile parentMobile) - { - if (!parentMobile.EquipItem(this)) - MoveToWorld(bounce.WorldLoc, bounce.Map); - } - else - { - MoveToWorld(bounce.WorldLoc, bounce.Map); - } - - ClearBounce(); - } - else - { - MoveToWorld(from.Location, from.Map); - } - } - - /// - /// Overridable. Method checked to see if this item may be equipped while casting a spell. By default, this returns false. It - /// is overridden on spellbook and spell channeling weapons or shields. - /// - /// True if it may, false if not. - /// - /// - /// public override bool AllowEquippedCast( Mobile from ) - /// { - /// if (from.Int >= 100) - /// return true; - /// - /// return base.AllowEquippedCast( from ); - /// } - /// When placed in an Item script, the item may be cast when equipped if the has 100 or more - /// intelligence. Otherwise, it will drop to their backpack. - /// - public virtual bool AllowEquippedCast(Mobile from) => false; - - public virtual bool CheckConflictingLayer(Mobile m, Item item, Layer layer) => m_Layer == layer; - - public virtual bool CanEquip(Mobile m) => m_Layer != Layer.Invalid && m.FindItemOnLayer(m_Layer) == null; - - public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) - { - if (m_Parent is Item parentItem) - parentItem.GetChildContextMenuEntries(from, list, item); - else if (m_Parent is Mobile parentMobile) - parentMobile.GetChildContextMenuEntries(from, list, item); - } - - public virtual void GetContextMenuEntries(Mobile from, List list) - { - if (m_Parent is Item item) - item.GetChildContextMenuEntries(from, list, this); - else if (m_Parent is Mobile mobile) - mobile.GetChildContextMenuEntries(from, list, this); - } - - public virtual bool VerifyMove(Mobile from) => Movable; - - public virtual DeathMoveResult OnParentDeath(Mobile parent) - { - if (!Movable) - return DeathMoveResult.RemainEquipped; - if (parent.KeepsItemsOnDeath) - return DeathMoveResult.MoveToBackpack; - if (CheckBlessed(parent)) - return DeathMoveResult.MoveToBackpack; - if (CheckNewbied() && parent.Kills < 5) - return DeathMoveResult.MoveToBackpack; - if (parent.Player && Nontransferable) - return DeathMoveResult.MoveToBackpack; - - return DeathMoveResult.MoveToCorpse; - } - - public virtual DeathMoveResult OnInventoryDeath(Mobile parent) - { - if (!Movable) - return DeathMoveResult.MoveToBackpack; - if (parent.KeepsItemsOnDeath) - return DeathMoveResult.MoveToBackpack; - if (CheckBlessed(parent)) - return DeathMoveResult.MoveToBackpack; - if (CheckNewbied() && parent.Kills < 5) - return DeathMoveResult.MoveToBackpack; - if (parent.Player && Nontransferable) - return DeathMoveResult.MoveToBackpack; - - return DeathMoveResult.MoveToCorpse; - } - - /// - /// Moves the Item to . The Item does not change maps. - /// - public virtual void MoveToWorld(Point3D location) - { - MoveToWorld(location, m_Map); - } - - public void LabelTo(Mobile to, int number) - { - to.Send(new MessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", "")); - } - - public void LabelTo(Mobile to, int number, string args) - { - to.Send(new MessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", args)); - } - - public void LabelTo(Mobile to, string text) - { - to.Send(new UnicodeMessage(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, "ENU", "", text)); - } - - public void LabelTo(Mobile to, string format, params object[] args) - { - LabelTo(to, string.Format(format, args)); - } - - public void LabelToAffix(Mobile to, int number, AffixType type, string affix) - { - to.Send(new MessageLocalizedAffix(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", type, affix, "")); - } - - public void LabelToAffix(Mobile to, int number, AffixType type, string affix, string args) - { - to.Send(new MessageLocalizedAffix(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", type, affix, args)); - } - - public virtual void LabelLootTypeTo(Mobile to) - { - if (m_LootType == LootType.Blessed) - LabelTo(to, 1041362); // (blessed) - else if (m_LootType == LootType.Cursed) - LabelTo(to, "(cursed)"); - } - - public bool AtWorldPoint(int x, int y) => m_Parent == null && m_Location.m_X == x && m_Location.m_Y == y; - - public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; - - public virtual bool OnDecay() => - Decays && Parent == null && Map != Map.Internal && Region.Find(Location, Map).OnDecay(this); - - public void SetLastMoved() - { - LastMoved = DateTime.UtcNow; - } - - public virtual bool CanStackWith(Item dropped) => - dropped.Stackable && Stackable && dropped.GetType() == GetType() && dropped.ItemID == ItemID && - dropped.Hue == Hue && dropped.Name == Name && dropped.Amount + Amount <= 60000 && dropped != this; - - public bool StackWith(Mobile from, Item dropped) => StackWith(from, dropped, true); - - public virtual bool StackWith(Mobile from, Item dropped, bool playSound) - { - if (CanStackWith(dropped)) - { - if (m_LootType != dropped.m_LootType) - m_LootType = LootType.Regular; - - Amount += dropped.Amount; - dropped.Delete(); - - if (playSound && from != null) - { - var soundID = GetDropSound(); - - if (soundID == -1) - soundID = 0x42; - - from.SendSound(soundID, GetWorldLocation()); - } - - return true; - } - - return false; - } - - public virtual bool OnDragDrop(Mobile from, Item dropped) - { - var success = Parent is Container container && container.OnStackAttempt(from, this, dropped) || - StackWith(from, dropped); - - if (success && Spawner != null) - { - Spawner.Remove(this); - Spawner = null; - } - - return success; - } - - public Rectangle2D GetGraphicBounds() - { - var itemID = m_ItemID; - var doubled = m_Amount > 1; - - if (itemID >= 0xEEA && itemID <= 0xEF2) // Are we coins? - { - var coinBase = (itemID - 0xEEA) / 3; - coinBase *= 3; - coinBase += 0xEEA; - - doubled = false; - - if (m_Amount <= 1) - itemID = coinBase; - else if (m_Amount <= 5) - itemID = coinBase + 1; - else // m_Amount > 5 - itemID = coinBase + 2; - } - - var bounds = ItemBounds.Table[itemID & 0x3FFF]; - - if (doubled) bounds.Set(bounds.X, bounds.Y, bounds.Width + 5, bounds.Height + 5); - - return bounds; - } - - public virtual void AppendChildProperties(ObjectPropertyList list) - { - if (m_Parent is Item item) - item.GetChildProperties(list, this); - else if (m_Parent is Mobile mobile) - mobile.GetChildProperties(list, this); - } - - public virtual void AppendChildNameProperties(ObjectPropertyList list) - { - if (m_Parent is Item item) - item.GetChildNameProperties(list, this); - else if (m_Parent is Mobile mobile) - mobile.GetChildNameProperties(list, this); - } - - public ObjectPropertyList NewObjectPropertyList() - { - var list = new ObjectPropertyList(this); - - GetProperties(list); - AppendChildProperties(list); - - list.Terminate(); - list.SetStatic(); - return list; - } - - public void ClearProperties() - { - ReleaseOPLPacket(); - StaticPacketHandlers.FreeOPLInfoPacket(this); - } - - public void InvalidateProperties() - { - if (!ObjectPropertyList.Enabled) - return; - - if (m_Map != null && m_Map != Map.Internal && !World.Loading) - { - var oldList = m_PropertyList; - m_PropertyList = null; - - if (oldList != null && oldList.Hash != PropertyList.Hash) - { - StaticPacketHandlers.FreeOPLInfoPacket(this); - Delta(ItemDelta.Properties); - } - } - else - { - ClearProperties(); - } - } - - public void ReleaseWorldPackets() - { - StaticPacketHandlers.FreeWorldItemPackets(this); - } - - public virtual int GetPacketFlags() - { - var flags = 0; - - if (!Visible) - flags |= 0x80; - - if (Movable || ForceShowProperties) - flags |= 0x20; - - return flags; - } - - public virtual bool OnMoveOff(Mobile m) => true; - - public virtual bool OnMoveOver(Mobile m) => true; - - public virtual void OnMovement(Mobile m, Point3D oldLocation) - { - } - - public void Internalize() - { - MoveToWorld(Point3D.Zero, Map.Internal); - } - - public virtual void OnMapChange() - { - } - - public virtual void OnRemoved(IEntity parent) - { - } - - public virtual void OnAdded(IEntity parent) - { - } - - private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) - { - if (setIf) - flags |= toSet; - } - - private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; - - public IPooledEnumerable GetObjectsInRange(int range) - { - var map = m_Map; - - return map == null ? Map.NullEnumerable.Instance : map.GetObjectsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); - } - - public IPooledEnumerable GetItemsInRange(int range) - { - var map = m_Map; - - return map?.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? Map.NullEnumerable.Instance; - } - - public IPooledEnumerable GetMobilesInRange(int range) - { - var map = m_Map; - - return map?.GetMobilesInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? Map.NullEnumerable.Instance; - } - - public IPooledEnumerable GetClientsInRange(int range) - { - var map = m_Map; - - return map.GetClientsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) - ?? Map.NullEnumerable.Instance; - } - - public bool GetTempFlag(int flag) => ((LookupCompactInfo()?.m_TempFlags ?? 0) & flag) != 0; - - public void SetTempFlag(int flag, bool value) - { - var info = AcquireCompactInfo(); - - if (value) - info.m_TempFlags |= flag; - else - info.m_TempFlags &= ~flag; - - if (info.m_TempFlags == 0) - VerifyCompactInfo(); - } - - public bool GetSavedFlag(int flag) => ((LookupCompactInfo()?.m_SavedFlags ?? 0) & flag) != 0; - - public void SetSavedFlag(int flag, bool value) - { - var info = AcquireCompactInfo(); - - if (value) - info.m_SavedFlags |= flag; - else - info.m_SavedFlags &= ~flag; - - if (info.m_SavedFlags == 0) - VerifyCompactInfo(); - } - - public virtual void Deserialize(IGenericReader reader) - { - var version = reader.ReadInt(); - - SetLastMoved(); - - switch (version) - { - case 9: - case 8: - case 7: - case 6: - { - var flags = (SaveFlag)reader.ReadInt(); - - if (version < 7) - { - LastMoved = reader.ReadDeltaTime(); - } - else - { - var minutes = reader.ReadEncodedInt(); - - try - { - LastMoved = DateTime.UtcNow - TimeSpan.FromMinutes(minutes); - } - catch - { - LastMoved = DateTime.UtcNow; - } - } - - if (GetSaveFlag(flags, SaveFlag.Direction)) - m_Direction = (Direction)reader.ReadByte(); - - if (GetSaveFlag(flags, SaveFlag.Bounce)) - AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); - - if (GetSaveFlag(flags, SaveFlag.LootType)) - m_LootType = (LootType)reader.ReadByte(); - - int x = 0, y = 0, z = 0; - - if (GetSaveFlag(flags, SaveFlag.LocationFull)) - { - x = reader.ReadEncodedInt(); - y = reader.ReadEncodedInt(); - z = reader.ReadEncodedInt(); - } - else - { - if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) - { - x = reader.ReadByte(); - y = reader.ReadByte(); - } - else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) - { - x = reader.ReadShort(); - y = reader.ReadShort(); - } - - if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) - z = reader.ReadSByte(); - } - - m_Location = new Point3D(x, y, z); - - if (GetSaveFlag(flags, SaveFlag.ItemID)) - m_ItemID = reader.ReadEncodedInt(); - - if (GetSaveFlag(flags, SaveFlag.Hue)) - m_Hue = reader.ReadEncodedInt(); - - m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadEncodedInt() : 1; - - if (GetSaveFlag(flags, SaveFlag.Layer)) - m_Layer = (Layer)reader.ReadByte(); - - if (GetSaveFlag(flags, SaveFlag.Name)) - { - var name = reader.ReadString(); - - if (name != DefaultName) - AcquireCompactInfo().m_Name = name; - } - - if (GetSaveFlag(flags, SaveFlag.Parent)) - { - Serial parent = reader.ReadUInt(); - - if (parent.IsMobile) - m_Parent = World.FindMobile(parent); - else if (parent.IsItem) - m_Parent = World.FindItem(parent); - else - m_Parent = null; - - if (m_Parent == null && (parent.IsMobile || parent.IsItem)) - Delete(); - } - - if (GetSaveFlag(flags, SaveFlag.Items)) - { - var items = reader.ReadStrongItemList(); - - if (this is Container) - (this as Container).m_Items = items; - else - AcquireCompactInfo().m_Items = items; - } - - if (version < 8 || !GetSaveFlag(flags, SaveFlag.NullWeight)) - { - double weight; - - if (GetSaveFlag(flags, SaveFlag.IntWeight)) - weight = reader.ReadEncodedInt(); - else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) - weight = reader.ReadDouble(); - else if (GetSaveFlag(flags, SaveFlag.WeightIs0)) - weight = 0.0; - else - weight = 1.0; - - if (weight != DefaultWeight) - AcquireCompactInfo().m_Weight = weight; - } - - m_Map = GetSaveFlag(flags, SaveFlag.Map) ? reader.ReadMap() : Map.Internal; - - SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); - - SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); - - if (GetSaveFlag(flags, SaveFlag.Stackable)) - SetFlag(ImplFlag.Stackable, reader.ReadBool()); - - if (GetSaveFlag(flags, SaveFlag.ImplFlags)) - m_Flags = (ImplFlag)reader.ReadEncodedInt(); - - if (GetSaveFlag(flags, SaveFlag.InsuredFor)) - /*m_InsuredFor = */ - reader.ReadMobile(); - - if (GetSaveFlag(flags, SaveFlag.BlessedFor)) - AcquireCompactInfo().m_BlessedFor = reader.ReadMobile(); - - if (GetSaveFlag(flags, SaveFlag.HeldBy)) - AcquireCompactInfo().m_HeldBy = reader.ReadMobile(); - - if (GetSaveFlag(flags, SaveFlag.SavedFlags)) - AcquireCompactInfo().m_SavedFlags = reader.ReadEncodedInt(); - - if (m_Map != null && m_Parent == null) - m_Map.OnEnter(this); - - break; - } - case 5: - { - var flags = (SaveFlag)reader.ReadInt(); - - LastMoved = reader.ReadDeltaTime(); - - if (GetSaveFlag(flags, SaveFlag.Direction)) - m_Direction = (Direction)reader.ReadByte(); - - if (GetSaveFlag(flags, SaveFlag.Bounce)) - AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); - - if (GetSaveFlag(flags, SaveFlag.LootType)) - m_LootType = (LootType)reader.ReadByte(); - - if (GetSaveFlag(flags, SaveFlag.LocationFull)) - m_Location = reader.ReadPoint3D(); - - if (GetSaveFlag(flags, SaveFlag.ItemID)) - m_ItemID = reader.ReadInt(); - - if (GetSaveFlag(flags, SaveFlag.Hue)) - m_Hue = reader.ReadInt(); - - m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadInt() : 1; - - if (GetSaveFlag(flags, SaveFlag.Layer)) - m_Layer = (Layer)reader.ReadByte(); - - if (GetSaveFlag(flags, SaveFlag.Name)) - { - var name = reader.ReadString(); - - if (name != DefaultName) - AcquireCompactInfo().m_Name = name; - } - - if (GetSaveFlag(flags, SaveFlag.Parent)) - { - Serial parent = reader.ReadUInt(); - - if (parent.IsMobile) - m_Parent = World.FindMobile(parent); - else if (parent.IsItem) - m_Parent = World.FindItem(parent); - else - m_Parent = null; - - if (m_Parent == null && (parent.IsMobile || parent.IsItem)) - Delete(); - } - - if (GetSaveFlag(flags, SaveFlag.Items)) - { - var items = reader.ReadStrongItemList(); - - if (this is Container cont) - cont.m_Items = items; - else - AcquireCompactInfo().m_Items = items; - } - - double weight; - - if (GetSaveFlag(flags, SaveFlag.IntWeight)) - weight = reader.ReadEncodedInt(); - else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) - weight = reader.ReadDouble(); - else if (GetSaveFlag(flags, SaveFlag.WeightIs0)) - weight = 0.0; - else - weight = 1.0; - - if (weight != DefaultWeight) - AcquireCompactInfo().m_Weight = weight; - - if (GetSaveFlag(flags, SaveFlag.Map)) - m_Map = reader.ReadMap(); - else - m_Map = Map.Internal; - - SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); - SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); - - if (GetSaveFlag(flags, SaveFlag.Stackable)) - SetFlag(ImplFlag.Stackable, reader.ReadBool()); - - if (m_Map != null && m_Parent == null) - m_Map.OnEnter(this); - - break; - } - case 4: // Just removed variables - case 3: - { - m_Direction = (Direction)reader.ReadInt(); - - goto case 2; - } - case 2: - { - AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); - LastMoved = reader.ReadDeltaTime(); - - goto case 1; - } - case 1: - { - m_LootType = (LootType)reader.ReadByte(); // m_Newbied = reader.ReadBool(); - - goto case 0; - } - case 0: - { - m_Location = reader.ReadPoint3D(); - m_ItemID = reader.ReadInt(); - m_Hue = reader.ReadInt(); - m_Amount = reader.ReadInt(); - m_Layer = (Layer)reader.ReadByte(); - - var name = reader.ReadString(); - - if (name != DefaultName) - AcquireCompactInfo().m_Name = name; - - Serial parent = reader.ReadUInt(); - - if (parent.IsMobile) - m_Parent = World.FindMobile(parent); - else if (parent.IsItem) - m_Parent = World.FindItem(parent); - else - m_Parent = null; - - if (m_Parent == null && (parent.IsMobile || parent.IsItem)) - Delete(); - - var count = reader.ReadInt(); - - if (count > 0) - { - var items = new List(count); - - for (var i = 0; i < count; ++i) - { - var item = reader.ReadItem(); - - if (item != null) - items.Add(item); - } - - if (this is Container cont) - cont.m_Items = items; - else - AcquireCompactInfo().m_Items = items; - } - - var weight = reader.ReadDouble(); - - if (weight != DefaultWeight) - AcquireCompactInfo().m_Weight = weight; - - if (version <= 3) - { - reader.ReadInt(); - reader.ReadInt(); - reader.ReadInt(); - } - - m_Map = reader.ReadMap(); - SetFlag(ImplFlag.Visible, reader.ReadBool()); - SetFlag(ImplFlag.Movable, reader.ReadBool()); - - if (version <= 3) - /*m_Deleted =*/ - reader.ReadBool(); - - Stackable = reader.ReadBool(); - - if (m_Map != null && m_Parent == null) - m_Map.OnEnter(this); - - break; - } - } - - if (HeldBy != null) - Timer.DelayCall(FixHolding_Sandbox); - - // if (version < 9) - VerifyCompactInfo(); - } - - private void FixHolding_Sandbox() - { - var heldBy = HeldBy; - - if (heldBy != null) - { - if (GetBounce() != null) - { - Bounce(heldBy); - } - else - { - heldBy.Holding = null; - heldBy.AddToBackpack(this); - ClearBounce(); - } - } - } - - public virtual int GetMaxUpdateRange() => 18; - - public virtual int GetUpdateRange(Mobile m) => 18; - - public void SendInfoTo(NetState state) - { - SendInfoTo(state, ObjectPropertyList.Enabled); - } - - public virtual void SendInfoTo(NetState state, bool sendOplPacket) - { - state.Send(GetWorldPacketFor(state)); - - if (sendOplPacket) state.Send(OPLPacket); - } - - protected virtual Packet GetWorldPacketFor(NetState state) - { - if (state.HighSeas) - return WorldPacketHS; - if (state.StygianAbyss) - return WorldPacketSA; - return WorldPacket; - } - - public virtual int GetTotal(TotalType type) => 0; - - public virtual void UpdateTotal(Item sender, TotalType type, int delta) - { - if (!IsVirtualItem) - { - if (m_Parent is Item item) - item.UpdateTotal(sender, type, delta); - else if (m_Parent is Mobile mobile) - mobile.UpdateTotal(sender, type, delta); - else - HeldBy?.UpdateTotal(sender, type, delta); - } - } - - public virtual void UpdateTotals() - { - } - - public virtual void HandleInvalidTransfer(Mobile from) - { - // OSI sends 1074769, bug! - if (QuestItem) - from.SendLocalizedMessage( - 1049343); // You can only drop quest items into the top-most level of your backpack while you still need them for your quest. - } - - public bool ParentsContain() where T : Item - { - var p = m_Parent; - - while (p is Item item) - { - if (item is T) - return true; - - if (item.m_Parent == null) break; - - p = item.m_Parent; - } - - return false; - } - - public virtual void AddItem(Item item) - { - if (item?.Deleted != false || item.m_Parent == this) return; - - if (item == this) - { - Console.WriteLine("Warning: Adding item to itself: [0x{0:X} {1}].AddItem( [0x{2:X} {3}] )", Serial.Value, - GetType().Name, item.Serial.Value, item.GetType().Name); - Console.WriteLine(new StackTrace()); - return; - } - - if (IsChildOf(item)) - { - Console.WriteLine("Warning: Adding parent item to child: [0x{0:X} {1}].AddItem( [0x{2:X} {3}] )", - Serial.Value, GetType().Name, item.Serial.Value, item.GetType().Name); - Console.WriteLine(new StackTrace()); - return; - } - - if (item.m_Parent is Mobile parentMobile) - parentMobile.RemoveItem(item); - else if (item.m_Parent is Item parentItem) - parentItem.RemoveItem(item); - else - item.SendRemovePacket(); - - item.Parent = this; - item.Map = m_Map; - - var items = AcquireItems(); - - items.Add(item); - - if (!item.IsVirtualItem) - { - UpdateTotal(item, TotalType.Gold, item.TotalGold); - UpdateTotal(item, TotalType.Items, item.TotalItems + 1); - UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); - } - - item.Delta(ItemDelta.Update); - - item.OnAdded(this); - OnItemAdded(item); - } - - public void Delta(ItemDelta flags) - { - if (m_Map == null || m_Map == Map.Internal) - return; - - m_DeltaFlags |= flags; - - if (!GetFlag(ImplFlag.InQueue)) - { - SetFlag(ImplFlag.InQueue, true); - - if (_processing) - try - { - using var op = new StreamWriter("delta-recursion.log", true); - op.WriteLine("# {0}", DateTime.UtcNow); - op.WriteLine(new StackTrace()); - op.WriteLine(); - } - catch - { - // ignored - } - else - m_DeltaQueue.Add(this); - } - - Core.Set(); - } - - public void RemDelta(ItemDelta flags) - { - m_DeltaFlags &= ~flags; - - if (GetFlag(ImplFlag.InQueue) && m_DeltaFlags == ItemDelta.None) - { - SetFlag(ImplFlag.InQueue, false); - - if (_processing) - try - { - using var op = new StreamWriter("delta-recursion.log", true); - op.WriteLine("# {0}", DateTime.UtcNow); - op.WriteLine(new StackTrace()); - op.WriteLine(); - } - catch - { - // ignored - } - else - m_DeltaQueue.Remove(this); - } - } - - public static void ProcessDeltaQueue() - { - _processing = true; - - if (m_DeltaQueue.Count >= 512) - Parallel.ForEach(m_DeltaQueue, i => i.ProcessDelta()); - else - for (var i = 0; i < m_DeltaQueue.Count; i++) - m_DeltaQueue[i].ProcessDelta(); - - m_DeltaQueue.Clear(); - - _processing = false; - } - - public virtual void OnDelete() - { - if (Spawner != null) - { - Spawner.Remove(this); - Spawner = null; - } - } - - public virtual void OnParentDeleted(IEntity parent) - { - Delete(); - } - - public virtual void FreeCache() - { - ReleaseWorldPackets(); - StaticPacketHandlers.FreeRemoveItemPacket(this); - StaticPacketHandlers.FreeOPLInfoPacket(this); - ReleaseOPLPacket(); - } - - public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text) - { - if (m_Map == null) - return; - - Packet p = null; - var worldLoc = GetWorldLocation(); - - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) - { - if (p == null) - { - if (ascii) - p = new AsciiMessage(Serial, m_ItemID, type, hue, 3, Name, text); - else - p = new UnicodeMessage(Serial, m_ItemID, type, hue, 3, "ENU", Name, text); - - p.Acquire(); - } - - state.Send(p); - } - } - - Packet.Release(p); - - eable.Free(); - } - - public void PublicOverheadMessage(MessageType type, int hue, int number) - { - PublicOverheadMessage(type, hue, number, ""); - } - - public void PublicOverheadMessage(MessageType type, int hue, int number, string args) - { - if (m_Map == null) - return; - - Packet p = null; - var worldLoc = GetWorldLocation(); - - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) - { - p ??= Packet.Acquire(new MessageLocalized(Serial, m_ItemID, type, hue, 3, number, Name, args)); - - state.Send(p); - } - } - - Packet.Release(p); - - eable.Free(); - } - - public virtual void OnAfterDelete() - { - } - - public virtual void RemoveItem(Item item) - { - var items = LookupItems(); - - if (items?.Contains(item) == true) - { - item.SendRemovePacket(); - - items.Remove(item); - - if (!item.IsVirtualItem) - { - UpdateTotal(item, TotalType.Gold, -item.TotalGold); - UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); - UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); - } - - item.Parent = null; - - item.OnRemoved(this); - OnItemRemoved(item); - } - } - - public virtual void OnAfterDuped(Item newItem) - { - } - - public virtual bool OnDragLift(Mobile from) => true; - - public virtual bool OnEquip(Mobile from) => true; - - protected virtual void OnAmountChange(int oldValue) - { - } - - public virtual void OnSpeech(SpeechEventArgs e) - { - } - - public virtual bool OnDroppedToMobile(Mobile from, Mobile target) - { - if (Nontransferable && from.Player) - { - HandleInvalidTransfer(from); - return false; - } - - return true; - } - - public virtual bool DropToMobile(Mobile from, Mobile target, Point3D p) => - !(Deleted || from.Deleted || target.Deleted) && from.Map == target.Map && from.Map != null && - target.Map != null && (from.AccessLevel >= AccessLevel.GameMaster || from.InRange(target.Location, 2)) && - from.CanSee(target) && from.InLOS(target) && from.OnDroppedItemToMobile(this, target) && - OnDroppedToMobile(from, target) && target.OnDragDrop(from, this); - - public virtual bool OnDroppedInto(Mobile from, Container target, Point3D p) - { - if (!from.OnDroppedItemInto(this, target, p)) - return false; - - if (Nontransferable && from.Player && target != from.Backpack) - { - HandleInvalidTransfer(from); - return false; - } - - return target.OnDragDropInto(from, this, p); - } - - public virtual bool OnDroppedOnto(Mobile from, Item target) - { - if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || - target.Map == null) - return false; - if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) - return false; - if (!from.CanSee(target) || !from.InLOS(target)) - return false; - if (!target.IsAccessibleTo(from)) - return false; - if (!from.OnDroppedItemOnto(this, target)) - return false; - if (Nontransferable && from.Player && target != from.Backpack) - { - HandleInvalidTransfer(from); - return false; - } - - return target.OnDragDrop(from, this); - } - - public virtual bool DropToItem(Mobile from, Item target, Point3D p) - { - if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || - target.Map == null) - return false; - - if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) - return false; - if (!from.CanSee(target) || !from.InLOS(target)) - return false; - if (!target.IsAccessibleTo(from)) - return false; - if (target.RootParent is Mobile mobile && !mobile.CheckNonlocalDrop(from, this, target)) - return false; - if (!from.OnDroppedItemToItem(this, target, p)) - return false; - if (target is Container container && p.m_X != -1 && p.m_Y != -1) - return OnDroppedInto(from, container, p); - - return OnDroppedOnto(from, target); - } - - public virtual bool OnDroppedToWorld(Mobile from, Point3D p) - { - if (Nontransferable && from.Player) - { - HandleInvalidTransfer(from); - return false; - } - - return true; - } - - public virtual int GetLiftSound(Mobile from) => 0x57; - - public virtual bool DropToWorld(Mobile from, Point3D p) - { - if (Deleted || from.Deleted || from.Map == null) - return false; - - if (!from.InRange(p, 2)) - return false; - - var map = from.Map; - - if (map == null) - return false; - - int x = p.m_X, y = p.m_Y; - var z = int.MinValue; - - var maxZ = from.Z + 16; - - var landTile = map.Tiles.GetLandTile(x, y); - var landFlags = TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags; - - int landZ = 0, landAvg = 0, landTop = 0; - map.GetAverageZ(x, y, ref landZ, ref landAvg, ref landTop); - - if (!landTile.Ignored && (landFlags & TileFlag.Impassable) == 0) - if (landAvg <= maxZ) - z = landAvg; - - var tiles = map.Tiles.GetStaticTiles(x, y, true); - - for (var i = 0; i < tiles.Length; ++i) - { - var tile = tiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - if (!id.Surface) - continue; - - var top = tile.Z + id.CalcHeight; - - if (top > maxZ || top < z) - continue; - - z = top; - } - - var eable = map.GetItemsInRange(p, 0); - - var items = eable.Where(item => - { - if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) - return false; - - var id = item.ItemData; - - if (id.Surface) - { - var top = item.Z + id.CalcHeight; - if (top <= maxZ && top >= z) - z = top; - } - - return true; - }).ToList(); - - eable.Free(); - - if (z == int.MinValue) - return false; - - if (z > maxZ) - return false; - - m_OpenSlots = (1 << 20) - 1; - - var surfaceZ = z; - - for (var i = 0; i < tiles.Length; ++i) - { - var tile = tiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - var checkZ = tile.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - ++checkTop; - - var zStart = Math.Max(checkZ - z, 0); - var zEnd = Math.Min(checkTop - z, 19); - - if (zStart >= 20 || zEnd < 0) - continue; - - var bitCount = zEnd - zStart; - - m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); - } - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - var id = item.ItemData; - - var checkZ = item.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - ++checkTop; - - var zStart = Math.Max(checkZ - z, 0); - var zEnd = Math.Min(checkTop - z, 19); - - if (zStart >= 20 || zEnd < 0) - continue; - - var bitCount = zEnd - zStart; - - m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); - } - - var height = ItemData.Height; - - if (height == 0) - ++height; - - if (height > 30) - height = 30; - - var match = (1 << height) - 1; - var okay = false; - - for (var i = 0; i < 20; ++i) - { - if (i + height > 20) - match >>= 1; - - okay = ((m_OpenSlots >> i) & match) == match; - - if (okay) - { - z += i; - break; - } - } - - if (!okay) - return false; - - height = ItemData.Height; - - if (height == 0) - ++height; - - if (landAvg > z && z + height > landZ) - return false; - - if ((landFlags & TileFlag.Impassable) != 0 && landAvg > surfaceZ && z + height > landZ) - return false; - - for (var i = 0; i < tiles.Length; ++i) - { - var tile = tiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - var checkZ = tile.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop > z && z + height > checkZ) - return false; - - if ((id.Surface || id.Impassable) && checkTop > surfaceZ && z + height > checkZ) - return false; - } - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - var id = item.ItemData; - - // int checkZ = item.Z; - // int checkTop = checkZ + id.CalcHeight; - - if (item.Z + id.CalcHeight > z && z + height > item.Z) - return false; - } - - p = new Point3D(x, y, z); - - if (!from.InLOS(new Point3D(x, y, z + 1))) - return false; - if (!from.OnDroppedItemToWorld(this, p)) - return false; - if (!OnDroppedToWorld(from, p)) - return false; - - var soundID = GetDropSound(); - - MoveToWorld(p, from.Map); - - from.SendSound(soundID == -1 ? 0x42 : soundID, GetWorldLocation()); - - return true; - } - - public void SendRemovePacket() - { - if (Deleted || m_Map == null) - return; - var worldLoc = GetWorldLocation(); - - var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.InRange(worldLoc, GetUpdateRange(m))) state.Send(RemovePacket); - } - - eable.Free(); - } - - public virtual int GetDropSound() => -1; - - public Point3D GetWorldLocation() - { - var root = RootParent; - - if (root == null) - return m_Location; - return root.Location; - - // return root == null ? m_Location : new Point3D( (IPoint3D) root ); - } - - public Point3D GetSurfaceTop() - { - var root = RootParent; - - if (root == null) - return new Point3D(m_Location.m_X, m_Location.m_Y, - m_Location.m_Z + (ItemData.Surface ? ItemData.CalcHeight : 0)); - - return root.Location; - } - - public Point3D GetWorldTop() => RootParent?.Location ?? - new Point3D(m_Location.m_X, m_Location.m_Y, m_Location.m_Z + ItemData.CalcHeight); - - public void SendLocalizedMessageTo(Mobile to, int number) - { - if (Deleted || !to.CanSee(this)) - return; - - to.Send(new MessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", "")); - } - - public void SendLocalizedMessageTo(Mobile to, int number, string args) - { - if (Deleted || !to.CanSee(this)) - return; - - to.Send(new MessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", args)); - } - - public void SendLocalizedMessageTo(Mobile to, int number, AffixType affixType, string affix, string args) - { - if (Deleted || !to.CanSee(this)) - return; - - to.Send(new MessageLocalizedAffix(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", affixType, affix, - args)); - } - - public virtual void OnSnoop(Mobile from) - { - } - - public SecureTradeContainer GetSecureTradeCont() - { - object p = this; - - while (p is Item item) - { - if (item is SecureTradeContainer container) - return container; - - p = item.m_Parent; - } - - return null; - } - - public virtual void OnItemAdded(Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnSubItemAdded(item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnSubItemAdded(item); - } - - public virtual void OnItemRemoved(Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnSubItemRemoved(item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnSubItemRemoved(item); - } - - public virtual void OnSubItemAdded(Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnSubItemAdded(item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnSubItemAdded(item); - } - - public virtual void OnSubItemRemoved(Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnSubItemRemoved(item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnSubItemRemoved(item); - } - - public virtual void OnItemBounceCleared(Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnSubItemBounceCleared(item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnSubItemBounceCleared(item); - } - - public virtual void OnSubItemBounceCleared(Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnSubItemBounceCleared(item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnSubItemBounceCleared(item); - } - - public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => - m_Parent switch - { - Item item => item.CheckTarget(from, targ, targeted), - Mobile mobile => mobile.CheckTarget(from, targ, targeted), - _ => true - }; - - public virtual bool IsAccessibleTo(Mobile check) - { - if (m_Parent is Item item) - return item.IsAccessibleTo(check); - - var reg = Region.Find(GetWorldLocation(), m_Map); - - return reg.CheckAccessibility(this, check); - - /*SecureTradeContainer cont = GetSecureTradeCont(); - - if (cont != null && !cont.IsChildOf( check )) - return false; - - return true;*/ - } - - public bool IsChildOf(IEntity o) => IsChildOf(o, false); - - public bool IsChildOf(IEntity o, bool allowNull) - { - var p = m_Parent; - - if ((p == null || o == null) && !allowNull) - return false; - - if (p == o) - return true; - - while (p is Item item) - { - if (item.m_Parent == null) - break; - - p = item.m_Parent; - - if (p == o) - return true; - } - - return false; - } - - public virtual void OnItemUsed(Mobile from, Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnItemUsed(from, item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnItemUsed(from, item); - } - - public bool CheckItemUse(Mobile from) => CheckItemUse(from, this); - - public virtual bool CheckItemUse(Mobile from, Item item) => - m_Parent switch - { - Item parentItem => parentItem.CheckItemUse(from, item), - Mobile parentMobile => parentMobile.CheckItemUse(from, item), - _ => true - }; - - public virtual void OnItemLifted(Mobile from, Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnItemLifted(from, item); - else if (m_Parent is Mobile parentMobile) - parentMobile.OnItemLifted(from, item); - } - - public bool CheckLift(Mobile from) - { - var reject = LRReason.Inspecific; - - return CheckLift(from, this, ref reject); - } - - public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => - m_Parent switch - { - Item parentItem => parentItem.CheckLift(from, item, ref reject), - Mobile parentMobile => parentMobile.CheckLift(from, item, ref reject), - _ => true - }; - - public virtual void OnSingleClickContained(Mobile from, Item item) - { - if (m_Parent is Item parentItem) - parentItem.OnSingleClickContained(from, item); - } - - public virtual void OnAosSingleClick(Mobile from) - { - var opl = PropertyList; - - if (opl.Header > 0) - from.Send(new MessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, opl.Header, Name, - opl.HeaderArgs)); - } - - public virtual void OnSingleClick(Mobile from) - { - if (Deleted || !from.CanSee(this)) - return; - - if (DisplayLootType) - LabelLootTypeTo(from); - - var ns = from.NetState; - - if (ns == null) - return; - - if (Name == null) - { - if (m_Amount <= 1) - ns.Send(new MessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, LabelNumber, "", "")); - else - ns.Send(new MessageLocalizedAffix(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, LabelNumber, "", - AffixType.Append, - $" : {m_Amount}", "")); - } - else - { - ns.Send(new UnicodeMessage(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, "ENU", "", - Name + (m_Amount > 1 ? $" : {m_Amount}" : ""))); - } - } - - public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem) - { - ScissorHelper(from, newItem, amountPerOldItem, true); - } - - public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem, bool carryHue) - { - // let's not go over 60000 - var amount = Math.Min(Amount, 60000 / amountPerOldItem); - - Amount -= amount; - - var ourHue = Hue; - var thisMap = Map; - var thisParent = m_Parent; - var worldLoc = GetWorldLocation(); - var type = LootType; - - if (Amount == 0) - Delete(); - - newItem.Amount = amount * amountPerOldItem; - - if (carryHue) - newItem.Hue = ourHue; - - if (ScissorCopyLootType) - newItem.LootType = type; - - if ((thisParent as Container)?.TryDropItem(from, newItem, false) != true) - newItem.MoveToWorld(worldLoc, thisMap); - } - - public virtual void Consume() - { - Consume(1); - } - - public virtual void Consume(int amount) - { - Amount -= amount; - - if (Amount <= 0) - Delete(); - } - - public virtual void ReplaceWith(Item newItem) - { - if (m_Parent is Container container) - { - container.AddItem(newItem); - newItem.Location = m_Location; - } - else - { - newItem.MoveToWorld(GetWorldLocation(), m_Map); - } - - Delete(); - } - - public virtual bool CheckBlessed(Mobile m) => - m_LootType == LootType.Blessed || Mobile.InsuranceEnabled && Insured || m != null && m == BlessedFor; - - public virtual bool CheckNewbied() => m_LootType == LootType.Newbied; - - public virtual bool IsStandardLoot() => - (!Mobile.InsuranceEnabled || !Insured) && BlessedFor == null && m_LootType == LootType.Regular; - - public override string ToString() => $"0x{Serial.Value:X} \"{GetType().Name}\""; - - public virtual void OnSectorActivate() - { - } - - public virtual void OnSectorDeactivate() - { - } - - [Flags] - private enum ImplFlag : byte - { - None = 0x00, - Visible = 0x01, - Movable = 0x02, - Deleted = 0x04, - Stackable = 0x08, - InQueue = 0x10, - Insured = 0x20, - PaidInsurance = 0x40, - QuestItem = 0x80 - } - - private class CompactInfo - { - public Mobile m_BlessedFor; - public BounceInfo m_Bounce; - - public Mobile m_HeldBy; - - public List m_Items; - public string m_Name; - public int m_SavedFlags; - - public ISpawner m_Spawner; - - public int m_TempFlags; - - public double m_Weight = -1; - } - - [Flags] - private enum SaveFlag : uint - { - None = 0x00000000, - Direction = 0x00000001, - Bounce = 0x00000002, - LootType = 0x00000004, - LocationFull = 0x00000008, - ItemID = 0x00000010, - Hue = 0x00000020, - Amount = 0x00000040, - Layer = 0x00000080, - Name = 0x00000100, - Parent = 0x00000200, - Items = 0x00000400, - WeightNot1or0 = 0x00000800, - Map = 0x00001000, - Visible = 0x00002000, - Movable = 0x00004000, - Stackable = 0x00008000, - WeightIs0 = 0x00010000, - LocationSByteZ = 0x00020000, - LocationShortXY = 0x00040000, - LocationByteXY = 0x00080000, - ImplFlags = 0x00100000, - InsuredFor = 0x00200000, - BlessedFor = 0x00400000, - HeldBy = 0x00800000, - IntWeight = 0x01000000, - SavedFlags = 0x02000000, - NullWeight = 0x04000000 - } - - private Point3D m_Location; - private int m_ItemID; - private int m_Hue; - private int m_Amount; - private Layer m_Layer; - private IEntity m_Parent; // Mobile, Item, or null=World - private Map m_Map; - private LootType m_LootType; - private Direction m_Direction; - - public virtual void OnLocationChange(Point3D oldLocation) - { - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public virtual Point3D Location - { - get => m_Location; - set - { - var oldLocation = m_Location; - - if (oldLocation == value) - return; - if (m_Map != null) - { - if (m_Parent == null) - { - IPooledEnumerable eable; - - if (m_Location.m_X != 0) - { - eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (!m.InRange(value, GetUpdateRange(m))) state.Send(RemovePacket); - } - - eable.Free(); - } - - var oldLoc = m_Location; - m_Location = value; - ReleaseWorldPackets(); - - SetLastMoved(); - - eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); - - foreach (var state in eable) - { - var m = state.Mobile; - - if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m)) && - (!state.HighSeas || !NoMoveHS || (m_DeltaFlags & ItemDelta.Update) != 0 || - !m.InRange(oldLoc, GetUpdateRange(m)))) - SendInfoTo(state); - } - - eable.Free(); - - RemDelta(ItemDelta.Update); - } - else if (m_Parent is Item) - { - m_Location = value; - ReleaseWorldPackets(); - - Delta(ItemDelta.Update); - } - else - { - m_Location = value; - ReleaseWorldPackets(); - } - - if (m_Parent == null) - m_Map.OnMove(oldLocation, this); - } - else - { - m_Location = value; - ReleaseWorldPackets(); - } - - OnLocationChange(oldLocation); - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int X - { - get => m_Location.m_X; - set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Y - { - get => m_Location.m_Y; - set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Z - { - get => m_Location.m_Z; - set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); - } - - public virtual bool InRange(Point2D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public virtual bool InRange(Point3D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public virtual bool InRange(IPoint2D p, int range) => - p.X >= Location.m_X - range - && p.X <= Location.m_X + range - && p.Y >= Location.m_Y - range - && p.Y <= Location.m_Y + range; - - public virtual void OnDoubleClick(Mobile from) - { - } - - public virtual void OnDoubleClickOutOfRange(Mobile from) - { - } - - public virtual void OnDoubleClickCantSee(Mobile from) - { - } - - public virtual void OnDoubleClickDead(Mobile from) - { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. - } - - public virtual void OnDoubleClickNotAccessible(Mobile from) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - public virtual void OnDoubleClickSecureTrade(Mobile from) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - } -} +/*************************************************************************** + * Item.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Server.ContextMenus; +using Server.Items; +using Server.Network; +using Server.Targeting; + +namespace Server +{ + /// + /// Internal flags used to signal how the item should be updated and resent to nearby clients. + /// + [Flags] + public enum ItemDelta + { + /// + /// Nothing. + /// + None = 0x00000000, + + /// + /// Resend the item. + /// + Update = 0x00000001, + + /// + /// Resend the item only if it is equipped. + /// + EquipOnly = 0x00000002, + + /// + /// Resend the item's properties. + /// + Properties = 0x00000004 + } + + /// + /// Enumeration containing possible ways to handle item ownership on death. + /// + public enum DeathMoveResult + { + /// + /// The item should be placed onto the corpse. + /// + MoveToCorpse, + + /// + /// The item should remain equipped. + /// + RemainEquipped, + + /// + /// The item should be placed into the owners backpack. + /// + MoveToBackpack + } + + /// + /// Enumeration of an item's loot and steal state. + /// + public enum LootType : byte + { + /// + /// Stealable. Lootable. + /// + Regular = 0, + + /// + /// Unstealable. Unlootable, unless owned by a murderer. + /// + Newbied = 1, + + /// + /// Unstealable. Unlootable, always. + /// + Blessed = 2, + + /// + /// Stealable. Lootable, always. + /// + Cursed = 3 + } + + public class BounceInfo + { + public BounceInfo(Item item) + { + Map = item.Map; + Location = item.Location; + WorldLoc = item.GetWorldLocation(); + Parent = item.Parent; + } + + private BounceInfo(Map map, Point3D loc, Point3D worldLoc, IEntity parent) + { + Map = map; + Location = loc; + WorldLoc = worldLoc; + Parent = parent; + } + + public Point3D Location { get; set; } + public Point3D WorldLoc { get; set; } + public Map Map { get; set; } + public IEntity Parent { get; set; } + + public static BounceInfo Deserialize(IGenericReader reader) + { + if (reader.ReadBool()) + { + var map = reader.ReadMap(); + var loc = reader.ReadPoint3D(); + var worldLoc = reader.ReadPoint3D(); + + IEntity parent; + + Serial serial = reader.ReadUInt(); + + if (serial.IsItem) + parent = World.FindItem(serial); + else if (serial.IsMobile) + parent = World.FindMobile(serial); + else + parent = null; + + return new BounceInfo(map, loc, worldLoc, parent); + } + + return null; + } + + public static void Serialize(BounceInfo info, IGenericWriter writer) + { + if (info == null) + { + writer.Write(false); + } + else + { + writer.Write(true); + + writer.Write(info.Map); + writer.Write(info.Location); + writer.Write(info.WorldLoc); + + if (info.Parent is Mobile mobile) + writer.Write(mobile); + else if (info.Parent is Item item) + writer.Write(item); + else + writer.Write((Serial)0); + } + } + } + + public enum TotalType + { + Gold, + Items, + Weight + } + + [Flags] + public enum ExpandFlag + { + None = 0x000, + + Name = 0x001, + Items = 0x002, + Bounce = 0x004, + Holder = 0x008, + Blessed = 0x010, + TempFlag = 0x020, + SaveFlag = 0x040, + Weight = 0x080, + Spawner = 0x100 + } + + public class Item : IHued, IComparable, ISerializable, ISpawnable, IPropertyListObject + { + public const int QuestItemHue = 0x4EA; // Hmmmm... "for EA"? + public static readonly List EmptyItems = new List(); + + private static readonly List m_DeltaQueue = new List(); + + private static bool _processing; + + private static int m_OpenSlots; + private int m_Amount; + + private CompactInfo m_CompactInfo; + + private ItemDelta m_DeltaFlags; + private Direction m_Direction; + private ImplFlag m_Flags; + private int m_Hue; + private int m_ItemID; + private Layer m_Layer; + + private Point3D m_Location; + private LootType m_LootType; + private Map m_Map; + private IEntity m_Parent; // Mobile, Item, or null=World + + private ObjectPropertyList m_PropertyList; + + [Constructible] + public Item(int itemID = 0) + { + m_ItemID = itemID; + Serial = Serial.NewItem; + + // m_Items = new ArrayList( 1 ); + Visible = true; + Movable = true; + Amount = 1; + m_Map = Map.Internal; + + SetLastMoved(); + + World.AddItem(this); + + var ourType = GetType(); + TypeRef = World.m_ItemTypes.IndexOf(ourType); + + if (TypeRef == -1) + { + World.m_ItemTypes.Add(ourType); + TypeRef = World.m_ItemTypes.Count - 1; + } + + SaveBuffer = new BufferWriter(true); + } + + public Item(Serial serial) + { + Serial = serial; + + var ourType = GetType(); + TypeRef = World.m_ItemTypes.IndexOf(ourType); + + if (TypeRef == -1) + { + World.m_ItemTypes.Add(ourType); + TypeRef = World.m_ItemTypes.Count - 1; + } + + SaveBuffer = new BufferWriter(true); + } + + public int TempFlags + { + get => LookupCompactInfo()?.m_TempFlags ?? 0; + set + { + var info = AcquireCompactInfo(); + + info.m_TempFlags = value; + + if (info.m_TempFlags == 0) + VerifyCompactInfo(); + } + } + + public int SavedFlags + { + get => LookupCompactInfo()?.m_SavedFlags ?? 0; + set + { + var info = AcquireCompactInfo(); + + info.m_SavedFlags = value; + + if (info.m_SavedFlags == 0) + VerifyCompactInfo(); + } + } + + /// + /// The who is currently holding this item. + /// + public Mobile HeldBy + { + get => LookupCompactInfo()?.m_HeldBy; + set + { + var info = AcquireCompactInfo(); + + info.m_HeldBy = value; + + if (info.m_HeldBy == null) + VerifyCompactInfo(); + } + } + + /// + /// Overridable. Determines whether the item will show . + /// + public virtual bool DisplayWeight => Core.ML && (Movable || IsLockedDown || IsSecure || ItemData.Weight != 255); + + [CommandProperty(AccessLevel.GameMaster)] + public LootType LootType + { + get => m_LootType; + set + { + if (m_LootType != value) + { + m_LootType = value; + + if (DisplayLootType) + InvalidateProperties(); + } + } + } + + public static TimeSpan DefaultDecayTime { get; set; } = TimeSpan.FromHours(1.0); + + [CommandProperty(AccessLevel.GameMaster)] + public virtual TimeSpan DecayTime => DefaultDecayTime; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual bool Decays => Movable && Visible; + + public DateTime LastMoved { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Stackable + { + get => GetFlag(ImplFlag.Stackable); + set => SetFlag(ImplFlag.Stackable, value); + } + + public Packet RemovePacket => StaticPacketHandlers.GetRemoveEntityPacket(this); + + // World packets need to be invalidated when any of the following changes: + // - ItemID + // - Amount + // - Location + // - Hue + // - Packet Flags + // - Direction + public Packet WorldPacket => StaticPacketHandlers.GetWorldItemPacket(this); + public Packet WorldPacketSA => StaticPacketHandlers.GetWorldItemSAPacket(this); + public Packet WorldPacketHS => StaticPacketHandlers.GetWorldItemHSPacket(this); + + [CommandProperty(AccessLevel.GameMaster)] + public bool Visible + { + get => GetFlag(ImplFlag.Visible); + set + { + if (GetFlag(ImplFlag.Visible) != value) + { + SetFlag(ImplFlag.Visible, value); + ReleaseWorldPackets(); + + if (m_Map != null) + { + var worldLoc = GetWorldLocation(); + + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (!m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) + state.Send(RemovePacket); + } + + eable.Free(); + } + + Delta(ItemDelta.Update); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Movable + { + get => GetFlag(ImplFlag.Movable); + set + { + if (GetFlag(ImplFlag.Movable) != value) + { + SetFlag(ImplFlag.Movable, value); + ReleaseWorldPackets(); + Delta(ItemDelta.Update); + } + } + } + + public virtual bool ForceShowProperties => false; + + public virtual bool HandlesOnMovement => false; + + public static int LockedDownFlag { get; set; } + + public static int SecureFlag { get; set; } + + public bool IsLockedDown + { + get => GetTempFlag(LockedDownFlag); + set + { + SetTempFlag(LockedDownFlag, value); + InvalidateProperties(); + } + } + + public bool IsSecure + { + get => GetTempFlag(SecureFlag); + set + { + SetTempFlag(SecureFlag, value); + InvalidateProperties(); + } + } + + public virtual bool IsVirtualItem => false; + + public virtual int LabelNumber + { + get + { + if (m_ItemID < 0x4000) + return 1020000 + m_ItemID; + + return 1078872 + m_ItemID; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalGold => GetTotal(TotalType.Gold); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalItems => GetTotal(TotalType.Items); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalWeight => GetTotal(TotalType.Weight); + + public virtual double DefaultWeight + { + get + { + if (m_ItemID < 0 || m_ItemID > TileData.MaxItemValue || this is BaseMulti) + return 0; + + var weight = TileData.ItemTable[m_ItemID].Weight; + + if (weight == 255 || weight == 0) + weight = 1; + + return weight; + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public double Weight + { + get + { + var info = LookupCompactInfo(); + + return info != null && info.m_Weight != -1 ? info.m_Weight : DefaultWeight; + } + set + { + if (Weight != value) + { + var info = AcquireCompactInfo(); + + var oldPileWeight = PileWeight; + + info.m_Weight = value; + + if (info.m_Weight == -1) + VerifyCompactInfo(); + + var newPileWeight = PileWeight; + + UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); + + InvalidateProperties(); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int PileWeight => (int)Math.Ceiling(Weight * Amount); + + [Hue] + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Hue + { + get => m_Hue; + set + { + if (m_Hue != value) + { + m_Hue = value; + ReleaseWorldPackets(); + + Delta(ItemDelta.Update); + } + } + } + + public virtual bool Nontransferable => QuestItem; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual Layer Layer + { + get => m_Layer; + set + { + if (m_Layer != value) + { + m_Layer = value; + + Delta(ItemDelta.EquipOnly); + } + } + } + + public List Items => LookupItems() ?? EmptyItems; + + [CommandProperty(AccessLevel.GameMaster)] + public IEntity RootParent + { + get + { + var p = m_Parent; + + while (p is Item item) + { + if (item.m_Parent == null) break; + + p = item.m_Parent; + } + + return p; + } + } + + public bool NoMoveHS { get; set; } + + public virtual int PhysicalResistance => 0; + public virtual int FireResistance => 0; + public virtual int ColdResistance => 0; + public virtual int PoisonResistance => 0; + public virtual int EnergyResistance => 0; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int ItemID + { + get => m_ItemID; + set + { + if (m_ItemID != value) + { + var oldPileWeight = PileWeight; + + m_ItemID = value; + ReleaseWorldPackets(); + + var newPileWeight = PileWeight; + + UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); + + InvalidateProperties(); + Delta(ItemDelta.Update); + } + } + } + + public virtual string DefaultName => null; + + [CommandProperty(AccessLevel.GameMaster)] + public string Name + { + get => LookupCompactInfo()?.m_Name ?? DefaultName; + set + { + if (value == null || value != DefaultName) + { + var info = AcquireCompactInfo(); + + info.m_Name = value; + + if (info.m_Name == null) + VerifyCompactInfo(); + + InvalidateProperties(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Developer)] + public IEntity Parent + { + get => m_Parent; + set + { + if (m_Parent == value) + return; + + var oldParent = m_Parent; + + m_Parent = value; + + if (m_Map != null) + { + if (oldParent != null && m_Parent == null) + m_Map.OnEnter(this); + else if (m_Parent != null) + m_Map.OnLeave(this); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public LightType Light + { + get => (LightType)m_Direction; + set + { + if ((LightType)m_Direction != value) + { + m_Direction = (Direction)value; + ReleaseWorldPackets(); + + Delta(ItemDelta.Update); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Direction Direction + { + get => m_Direction; + set + { + if (m_Direction != value) + { + m_Direction = value; + ReleaseWorldPackets(); + + Delta(ItemDelta.Update); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Amount + { + get => m_Amount; + set + { + var oldValue = m_Amount; + + if (oldValue != value) + { + var oldPileWeight = PileWeight; + + m_Amount = value; + ReleaseWorldPackets(); + + var newPileWeight = PileWeight; + + UpdateTotal(this, TotalType.Weight, newPileWeight - oldPileWeight); + + OnAmountChange(oldValue); + + Delta(ItemDelta.Update); + + if (oldValue > 1 || value > 1) + InvalidateProperties(); + + if (!Stackable && m_Amount > 1) + Console.WriteLine( + "Warning: 0x{0:X}: Amount changed for non-stackable item '{2}'. ({1})", + Serial.Value, + m_Amount, + GetType().Name + ); + } + } + } + + public virtual bool HandlesOnSpeech => false; + + public virtual bool BlocksFit => false; + + public bool InSecureTrade => GetSecureTradeCont() != null; + + public ItemData ItemData => TileData.ItemTable[m_ItemID & TileData.MaxItemValue]; + + public virtual bool CanTarget => true; + public virtual bool DisplayLootType => true; + + public static bool ScissorCopyLootType { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool QuestItem + { + get => GetFlag(ImplFlag.QuestItem); + set + { + SetFlag(ImplFlag.QuestItem, value); + + InvalidateProperties(); + + ReleaseWorldPackets(); + + Delta(ItemDelta.Update); + } + } + + public bool Insured + { + get => GetFlag(ImplFlag.Insured); + set + { + SetFlag(ImplFlag.Insured, value); + InvalidateProperties(); + } + } + + public bool PaidInsurance + { + get => GetFlag(ImplFlag.PaidInsurance); + set => SetFlag(ImplFlag.PaidInsurance, value); + } + + public Mobile BlessedFor + { + get => LookupCompactInfo()?.m_BlessedFor; + set + { + var info = AcquireCompactInfo(); + + info.m_BlessedFor = value; + + if (info.m_BlessedFor == null) + VerifyCompactInfo(); + + InvalidateProperties(); + } + } + + public int CompareTo(Item other) => other == null ? -1 : Serial.CompareTo(other.Serial); + + public virtual int HuedItemID => m_ItemID; + public OPLInfo OPLPacket => StaticPacketHandlers.GetOPLInfoPacket(this); + public ObjectPropertyList PropertyList => m_PropertyList ??= NewObjectPropertyList(); + + /// + /// Overridable. Fills an with everything applicable. By default, this invokes + /// , then Item.GetChildProperties or + /// Mobile.GetChildProperties. This method should be overridden to add any + /// custom + /// properties. + /// + public virtual void GetProperties(ObjectPropertyList list) + { + AddNameProperties(list); + } + + public BufferWriter SaveBuffer { get; } + + [CommandProperty(AccessLevel.Counselor)] + public Serial Serial { get; } + + public int TypeRef { get; } + + public void Serialize() + { + SaveBuffer.Flush(); + Serialize(SaveBuffer); + } + + public virtual void Serialize(IGenericWriter writer) + { + writer.Write(9); // version + + var flags = SaveFlag.None; + + int x = m_Location.m_X, y = m_Location.m_Y, z = m_Location.m_Z; + + if (x != 0 || y != 0 || z != 0) + { + if (x >= short.MinValue && x <= short.MaxValue && y >= short.MinValue && y <= short.MaxValue && + z >= sbyte.MinValue && z <= sbyte.MaxValue) + { + if (x != 0 || y != 0) + { + if (x >= byte.MinValue && x <= byte.MaxValue && y >= byte.MinValue && y <= byte.MaxValue) + flags |= SaveFlag.LocationByteXY; + else + flags |= SaveFlag.LocationShortXY; + } + + if (z != 0) + flags |= SaveFlag.LocationSByteZ; + } + else + { + flags |= SaveFlag.LocationFull; + } + } + + var info = LookupCompactInfo(); + var items = LookupItems(); + + if (m_Direction != Direction.North) + flags |= SaveFlag.Direction; + if (info?.m_Bounce != null) + flags |= SaveFlag.Bounce; + if (m_LootType != LootType.Regular) + flags |= SaveFlag.LootType; + if (m_ItemID != 0) + flags |= SaveFlag.ItemID; + if (m_Hue != 0) + flags |= SaveFlag.Hue; + if (m_Amount != 1) + flags |= SaveFlag.Amount; + if (m_Layer != Layer.Invalid) + flags |= SaveFlag.Layer; + if (info?.m_Name != null) + flags |= SaveFlag.Name; + if (m_Parent != null) + flags |= SaveFlag.Parent; + if (items != null && items.Count > 0) + flags |= SaveFlag.Items; + if (m_Map != Map.Internal) + flags |= SaveFlag.Map; + // if (m_InsuredFor != null && !m_InsuredFor.Deleted) + // flags |= SaveFlag.InsuredFor; + + if (info != null) + { + if (info.m_BlessedFor?.Deleted == false) + flags |= SaveFlag.BlessedFor; + if (info.m_HeldBy?.Deleted == false) + flags |= SaveFlag.HeldBy; + if (info.m_SavedFlags != 0) + flags |= SaveFlag.SavedFlags; + } + + if (info == null || info.m_Weight == -1.0) + { + flags |= SaveFlag.NullWeight; + } + else + { + if (info.m_Weight == 0.0) + { + flags |= SaveFlag.WeightIs0; + } + else if (info.m_Weight != 1.0) + { + if (info.m_Weight == (int)info.m_Weight) + flags |= SaveFlag.IntWeight; + else + flags |= SaveFlag.WeightNot1or0; + } + } + + var implFlags = m_Flags & (ImplFlag.Visible | ImplFlag.Movable | ImplFlag.Stackable | ImplFlag.Insured | + ImplFlag.PaidInsurance | ImplFlag.QuestItem); + + if (implFlags != (ImplFlag.Visible | ImplFlag.Movable)) + flags |= SaveFlag.ImplFlags; + + writer.Write((int)flags); + + /* begin last moved time optimization */ + var ticks = LastMoved.Ticks; + var now = DateTime.UtcNow.Ticks; + + var minutes = new TimeSpan(now - ticks).TotalMinutes; + + writer.WriteEncodedInt((int)Math.Clamp(minutes, int.MinValue, int.MaxValue)); + /* end */ + + if (GetSaveFlag(flags, SaveFlag.Direction)) + writer.Write((byte)m_Direction); + + if (GetSaveFlag(flags, SaveFlag.Bounce)) + BounceInfo.Serialize(info?.m_Bounce, writer); + + if (GetSaveFlag(flags, SaveFlag.LootType)) + writer.Write((byte)m_LootType); + + if (GetSaveFlag(flags, SaveFlag.LocationFull)) + { + writer.WriteEncodedInt(x); + writer.WriteEncodedInt(y); + writer.WriteEncodedInt(z); + } + else + { + if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) + { + writer.Write((byte)x); + writer.Write((byte)y); + } + else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) + { + writer.Write((short)x); + writer.Write((short)y); + } + + if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) + writer.Write((sbyte)z); + } + + if (GetSaveFlag(flags, SaveFlag.ItemID)) + writer.WriteEncodedInt(m_ItemID); + + if (GetSaveFlag(flags, SaveFlag.Hue)) + writer.WriteEncodedInt(m_Hue); + + if (GetSaveFlag(flags, SaveFlag.Amount)) + writer.WriteEncodedInt(m_Amount); + + if (GetSaveFlag(flags, SaveFlag.Layer)) + writer.Write((byte)m_Layer); + + if (GetSaveFlag(flags, SaveFlag.Name)) + writer.Write(info.m_Name); + + if (GetSaveFlag(flags, SaveFlag.Parent)) + { + if (m_Parent?.Deleted == false) + writer.Write(m_Parent.Serial); + else + writer.Write(Serial.MinusOne); + } + + if (GetSaveFlag(flags, SaveFlag.Items)) + writer.Write(items, false); + + if (GetSaveFlag(flags, SaveFlag.IntWeight)) + writer.WriteEncodedInt((int)info.m_Weight); + else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) + writer.Write(info.m_Weight); + + if (GetSaveFlag(flags, SaveFlag.Map)) + writer.Write(m_Map); + + if (GetSaveFlag(flags, SaveFlag.ImplFlags)) + writer.WriteEncodedInt((int)implFlags); + + if (GetSaveFlag(flags, SaveFlag.InsuredFor)) + writer.Write((Mobile)null); + + if (GetSaveFlag(flags, SaveFlag.BlessedFor)) + writer.Write(info.m_BlessedFor); + + if (GetSaveFlag(flags, SaveFlag.HeldBy)) + writer.Write(info.m_HeldBy); + + if (GetSaveFlag(flags, SaveFlag.SavedFlags)) + writer.WriteEncodedInt(info.m_SavedFlags); + } + + int IComparable.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); + + /// + /// Moves the Item to a given and . + /// + public void MoveToWorld(Point3D location, Map map) + { + if (Deleted) + return; + + var oldLocation = GetWorldLocation(); + var oldRealLocation = m_Location; + + SetLastMoved(); + + if (Parent is Mobile mobile) + mobile.RemoveItem(this); + else if (Parent is Item item) + item.RemoveItem(this); + + if (m_Map != map) + { + var old = m_Map; + + if (m_Map != null) + { + m_Map.OnLeave(this); + + if (oldLocation.m_X != 0) + { + var eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.InRange(oldLocation, GetUpdateRange(m))) + state.Send(RemovePacket); + } + + eable.Free(); + } + } + + m_Location = location; + OnLocationChange(oldRealLocation); + + ReleaseWorldPackets(); + + var items = LookupItems(); + + if (items != null) + for (var i = 0; i < items.Count; ++i) + items[i].Map = map; + + m_Map = map; + m_Map?.OnEnter(this); + + OnMapChange(); + + if (m_Map != null) + { + var eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) + SendInfoTo(state); + } + + eable.Free(); + } + + RemDelta(ItemDelta.Update); + + if (old == null || old == Map.Internal) + InvalidateProperties(); + } + else if (m_Map != null) + { + IPooledEnumerable eable; + + if (oldLocation.m_X != 0) + { + eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (!m.InRange(location, GetUpdateRange(m))) state.Send(RemovePacket); + } + + eable.Free(); + } + + var oldInternalLocation = m_Location; + + m_Location = location; + OnLocationChange(oldRealLocation); + + ReleaseWorldPackets(); + + eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m))) + SendInfoTo(state); + } + + eable.Free(); + + m_Map.OnMove(oldInternalLocation, this); + + RemDelta(ItemDelta.Update); + } + else + { + Map = map; + Location = location; + } + } + + /// + /// Has the item been deleted? + /// + public bool Deleted => GetFlag(ImplFlag.Deleted); + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Map Map + { + get => m_Map; + set + { + if (m_Map != value) + { + var old = m_Map; + + if (m_Map != null && m_Parent == null) + { + m_Map.OnLeave(this); + SendRemovePacket(); + } + + var items = LookupItems(); + + if (items != null) + for (var i = 0; i < items.Count; ++i) + items[i].Map = value; + + m_Map = value; + + if (m_Parent == null) + m_Map?.OnEnter(this); + + Delta(ItemDelta.Update); + + OnMapChange(); + + if (old == null || old == Map.Internal) + InvalidateProperties(); + } + } + } + + public virtual void ProcessDelta() + { + var flags = m_DeltaFlags; + + SetFlag(ImplFlag.InQueue, false); + m_DeltaFlags = ItemDelta.None; + + var map = m_Map; + + if (map == null || Deleted) + return; + + var worldLoc = GetWorldLocation(); + var update = (flags & ItemDelta.Update) != 0; + + if (update && m_Parent is Container contParent && !contParent.IsPublicContainer) + { + var rootParent = contParent.RootParent as Mobile; + Mobile tradeRecip = null; + + if (rootParent != null) + { + var ns = rootParent.NetState; + + if (ns != null && rootParent.CanSee(this) && rootParent.InRange(worldLoc, GetUpdateRange(rootParent))) + { + if (ns.ContainerGridLines) + ns.Send(new ContainerContentUpdate6017(this)); + else + ns.Send(new ContainerContentUpdate(this)); + + if (ObjectPropertyList.Enabled) + ns.Send(OPLPacket); + } + } + + var st = GetSecureTradeCont()?.Trade; + + if (st != null) + { + var test = st.From.Mobile; + + if (test != null && test != rootParent) + tradeRecip = test; + + test = st.To.Mobile; + + if (test != null && test != rootParent) + tradeRecip = test; + + var ns = tradeRecip?.NetState; + + if (ns != null && tradeRecip.CanSee(this) && tradeRecip.InRange(worldLoc, GetUpdateRange(tradeRecip))) + { + if (ns.ContainerGridLines) + ns.Send(new ContainerContentUpdate6017(this)); + else + ns.Send(new ContainerContentUpdate(this)); + + if (ObjectPropertyList.Enabled) + ns.Send(OPLPacket); + } + } + + var openers = contParent.Openers; + + if (openers != null) + lock (openers) + { + for (var i = 0; i < openers.Count; ++i) + { + var mob = openers[i]; + + var range = GetUpdateRange(mob); + + if (mob.Map != map || !mob.InRange(worldLoc, range)) + { + openers.RemoveAt(i--); + } + else + { + if (mob == rootParent || mob == tradeRecip) + continue; + + var ns = mob.NetState; + + if (ns != null && mob.CanSee(this)) + { + if (ns.ContainerGridLines) + ns.Send(new ContainerContentUpdate6017(this)); + else + ns.Send(new ContainerContentUpdate(this)); + + if (ObjectPropertyList.Enabled) + ns.Send(OPLPacket); + } + } + } + + if (openers.Count == 0) + contParent.Openers = null; + } + + return; + } + + Packet p = null; + + var eable = map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (!m.CanSee(this) || !m.InRange(worldLoc, GetUpdateRange(m))) continue; + + if (update) + { + if (m_Parent == null) + { + SendInfoTo(state, ObjectPropertyList.Enabled); + } + else + { + if (p != null) + { + state.Send(p); + } + else if (m_Parent is Item) + { + if (state.ContainerGridLines) + state.Send(new ContainerContentUpdate6017(this)); + else + state.Send(new ContainerContentUpdate(this)); + } + else if (m_Parent is Mobile) + { + p = new EquipUpdate(this); + p.Acquire(); + + state.Send(p); + } + + if (ObjectPropertyList.Enabled) + state.Send(OPLPacket); + } + } + else if ((flags & ItemDelta.EquipOnly) != 0 && m_Parent is Mobile) + { + state.Send(p ??= Packet.Acquire(new EquipUpdate(this))); + + if (ObjectPropertyList.Enabled) + state.Send(OPLPacket); + } + else if (ObjectPropertyList.Enabled && (flags & ItemDelta.Properties) != 0) + { + state.Send(OPLPacket); + } + } + + Packet.Release(p); + eable.Free(); + } + + public virtual void Delete() + { + if (Deleted || !World.OnDelete(this)) + return; + + OnDelete(); + + var items = LookupItems(); + + if (items != null) + for (var i = items.Count - 1; i >= 0; --i) + if (i < items.Count) + items[i].OnParentDeleted(this); + + SendRemovePacket(); + + SetFlag(ImplFlag.Deleted, true); + + if (Parent is Mobile mobile) + mobile.RemoveItem(this); + else if (Parent is Item item) + item.RemoveItem(this); + + ClearBounce(); + + if (m_Map != null) + { + if (m_Parent == null) + m_Map.OnLeave(this); + m_Map = null; + } + + World.RemoveItem(this); + + OnAfterDelete(); + + FreeCache(); + } + + public ISpawner Spawner + { + get => LookupCompactInfo()?.m_Spawner; + set + { + var info = AcquireCompactInfo(); + + info.m_Spawner = value; + + if (info.m_Spawner == null) + VerifyCompactInfo(); + } + } + + public virtual void OnBeforeSpawn(Point3D location, Map m) + { + } + + public virtual void OnAfterSpawn() + { + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public virtual Point3D Location + { + get => m_Location; + set + { + var oldLocation = m_Location; + + if (oldLocation == value) + return; + if (m_Map != null) + { + if (m_Parent == null) + { + IPooledEnumerable eable; + + if (m_Location.m_X != 0) + { + eable = m_Map.GetClientsInRange(oldLocation, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (!m.InRange(value, GetUpdateRange(m))) state.Send(RemovePacket); + } + + eable.Free(); + } + + var oldLoc = m_Location; + m_Location = value; + ReleaseWorldPackets(); + + SetLastMoved(); + + eable = m_Map.GetClientsInRange(m_Location, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(m_Location, GetUpdateRange(m)) && + (!state.HighSeas || !NoMoveHS || (m_DeltaFlags & ItemDelta.Update) != 0 || + !m.InRange(oldLoc, GetUpdateRange(m)))) + SendInfoTo(state); + } + + eable.Free(); + + RemDelta(ItemDelta.Update); + } + else if (m_Parent is Item) + { + m_Location = value; + ReleaseWorldPackets(); + + Delta(ItemDelta.Update); + } + else + { + m_Location = value; + ReleaseWorldPackets(); + } + + if (m_Parent == null) + m_Map.OnMove(oldLocation, this); + } + else + { + m_Location = value; + ReleaseWorldPackets(); + } + + OnLocationChange(oldLocation); + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int X + { + get => m_Location.m_X; + set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Y + { + get => m_Location.m_Y; + set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Z + { + get => m_Location.m_Z; + set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); + } + + public virtual bool InRange(Point2D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public virtual bool InRange(Point3D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public virtual bool InRange(IPoint2D p, int range) => + p.X >= Location.m_X - range + && p.X <= Location.m_X + range + && p.Y >= Location.m_Y - range + && p.Y <= Location.m_Y + range; + + public void ReleaseOPLPacket() + { + if (m_PropertyList == null) + return; + + Packet.Release(m_PropertyList); + m_PropertyList = null; + } + + public ExpandFlag GetExpandFlags() + { + var info = LookupCompactInfo(); + + ExpandFlag flags = 0; + + if (info != null) + { + if (info.m_BlessedFor != null) + flags |= ExpandFlag.Blessed; + + if (info.m_Bounce != null) + flags |= ExpandFlag.Bounce; + + if (info.m_HeldBy != null) + flags |= ExpandFlag.Holder; + + if (info.m_Items != null) + flags |= ExpandFlag.Items; + + if (info.m_Name != null) + flags |= ExpandFlag.Name; + + if (info.m_Spawner != null) + flags |= ExpandFlag.Spawner; + + if (info.m_SavedFlags != 0) + flags |= ExpandFlag.SaveFlag; + + if (info.m_TempFlags != 0) + flags |= ExpandFlag.TempFlag; + + if (info.m_Weight != -1) + flags |= ExpandFlag.Weight; + } + + return flags; + } + + private CompactInfo LookupCompactInfo() => m_CompactInfo; + + private CompactInfo AcquireCompactInfo() => m_CompactInfo ??= new CompactInfo(); + + private void ReleaseCompactInfo() + { + m_CompactInfo = null; + } + + private void VerifyCompactInfo() + { + var info = m_CompactInfo; + + if (info == null) + return; + + var isValid = info.m_Name != null + || info.m_Items != null + || info.m_Bounce != null + || info.m_HeldBy != null + || info.m_BlessedFor != null + || info.m_Spawner != null + || info.m_TempFlags != 0 + || info.m_SavedFlags != 0 + || info.m_Weight != -1; + + if (!isValid) + ReleaseCompactInfo(); + } + + public List LookupItems() + { + if (this is Container container) + return container.m_Items; + + return LookupCompactInfo()?.m_Items; + } + + public List AcquireItems() + { + if (this is Container cont) + return cont.m_Items ?? (cont.m_Items = new List()); + + var info = AcquireCompactInfo(); + return info.m_Items ?? (info.m_Items = new List()); + } + + private void SetFlag(ImplFlag flag, bool value) + { + if (value) + m_Flags |= flag; + else + m_Flags &= ~flag; + } + + private bool GetFlag(ImplFlag flag) => (m_Flags & flag) != 0; + + public BounceInfo GetBounce() => LookupCompactInfo()?.m_Bounce; + + public void RecordBounce() + { + AcquireCompactInfo().m_Bounce = new BounceInfo(this); + } + + public void ClearBounce() + { + var info = LookupCompactInfo(); + + var bounce = info?.m_Bounce; + + if (bounce == null) + return; + + info.m_Bounce = null; + + if (bounce.Parent is Item parentItem) + { + if (!parentItem.Deleted) + parentItem.OnItemBounceCleared(this); + } + else if (bounce.Parent is Mobile parentMobile) + { + if (!parentMobile.Deleted) + parentMobile.OnItemBounceCleared(this); + } + + VerifyCompactInfo(); + } + + /// + /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Item. + /// Seemingly no longer functional in newer clients. + /// + public virtual void OnHelpRequest(Mobile from) + { + } + + /// + /// Overridable. Method checked to see if the item can be traded. + /// + /// True if the trade is allowed, false if not. + public virtual bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) => true; + + /// + /// Overridable. Virtual event invoked when a trade has completed, either successfully or not. + /// + public virtual void OnSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) + { + } + + /// + /// Overridable. Method checked to see if the elemental resistances of this Item conflict with another Item on the + /// . + /// + /// + /// + /// + /// True + /// + /// There is a conflict. The elemental resistance bonuses of this Item should not be applied to the + /// + /// + /// + /// + /// False + /// There is no conflict. The bonuses should be applied. + /// + /// + /// + public virtual bool CheckPropertyConflict(Mobile m) => false; + + /// + /// Overridable. Sends the object property list to . + /// + public virtual void SendPropertiesTo(Mobile from) + { + from.Send(PropertyList); + } + + /// + /// Overridable. Adds the name of this item to the given . This method should be overridden + /// if the item requires a complex naming format. + /// + public virtual void AddNameProperty(ObjectPropertyList list) + { + var name = Name; + + if (name == null) + { + if (m_Amount <= 1) + list.Add(LabelNumber); + else + list.Add(1050039, "{0}\t#{1}", m_Amount, LabelNumber); // ~1_NUMBER~ ~2_ITEMNAME~ + } + else + { + if (m_Amount <= 1) + list.Add(name); + else + list.Add(1050039, "{0}\t{1}", m_Amount, Name); // ~1_NUMBER~ ~2_ITEMNAME~ + } + } + + /// + /// Overridable. Adds the loot type of this item to the given . By default, this will be + /// either 'blessed', 'cursed', or 'insured'. + /// + public virtual void AddLootTypeProperty(ObjectPropertyList list) + { + if (m_LootType == LootType.Blessed) + list.Add(1038021); // blessed + else if (m_LootType == LootType.Cursed) + list.Add(1049643); // cursed + else if (Insured) + list.Add(1061682); // insured + } + + /// + /// Overridable. Adds any elemental resistances of this item to the given . + /// + public virtual void AddResistanceProperties(ObjectPropertyList list) + { + var v = PhysicalResistance; + + if (v != 0) + list.Add(1060448, v.ToString()); // physical resist ~1_val~% + + v = FireResistance; + + if (v != 0) + list.Add(1060447, v.ToString()); // fire resist ~1_val~% + + v = ColdResistance; + + if (v != 0) + list.Add(1060445, v.ToString()); // cold resist ~1_val~% + + v = PoisonResistance; + + if (v != 0) + list.Add(1060449, v.ToString()); // poison resist ~1_val~% + + v = EnergyResistance; + + if (v != 0) + list.Add(1060446, v.ToString()); // energy resist ~1_val~% + } + + /// + /// Overridable. Displays cliloc 1072788-1072789. + /// + public virtual void AddWeightProperty(ObjectPropertyList list) + { + var weight = PileWeight + TotalWeight; + + if (weight == 1) + list.Add(1072788, weight.ToString()); // Weight: ~1_WEIGHT~ stone + else + list.Add(1072789, weight.ToString()); // Weight: ~1_WEIGHT~ stones + } + + /// + /// Overridable. Adds header properties. By default, this invokes , + /// (if applicable), and (if + /// ). + /// + public virtual void AddNameProperties(ObjectPropertyList list) + { + AddNameProperty(list); + + if (IsSecure) + AddSecureProperty(list); + else if (IsLockedDown) + AddLockedDownProperty(list); + + var blessedFor = BlessedFor; + + if (blessedFor?.Deleted == false) + AddBlessedForProperty(list, blessedFor); + + if (DisplayLootType) + AddLootTypeProperty(list); + + if (DisplayWeight) + AddWeightProperty(list); + + if (QuestItem) + AddQuestItemProperty(list); + + AppendChildNameProperties(list); + } + + /// + /// Overridable. Adds the "Quest Item" property to the given . + /// + public virtual void AddQuestItemProperty(ObjectPropertyList list) + { + list.Add(1072351); // Quest Item + } + + /// + /// Overridable. Adds the "Locked Down & Secure" property to the given . + /// + public virtual void AddSecureProperty(ObjectPropertyList list) + { + list.Add(501644); // locked down & secure + } + + /// + /// Overridable. Adds the "Locked Down" property to the given . + /// + public virtual void AddLockedDownProperty(ObjectPropertyList list) + { + list.Add(501643); // locked down + } + + /// + /// Overridable. Adds the "Blessed for ~1_NAME~" property to the given . + /// + public virtual void AddBlessedForProperty(ObjectPropertyList list, Mobile m) + { + list.Add(1062203, "{0}", m.Name); // Blessed for ~1_NAME~ + } + + /// + /// Overridable. Event invoked when a child () is building it's . + /// Recursively calls Item.GetChildProperties or + /// Mobile.GetChildProperties. + /// + public virtual void GetChildProperties(ObjectPropertyList list, Item item) + { + if (m_Parent is Item parentItem) + parentItem.GetChildProperties(list, item); + else if (m_Parent is Mobile parentMobile) + parentMobile.GetChildProperties(list, item); + } + + /// + /// Overridable. Event invoked when a child () is building it's Name + /// + /// . Recursively calls Item.GetChildNameProperties or + /// Mobile.GetChildNameProperties. + /// + public virtual void GetChildNameProperties(ObjectPropertyList list, Item item) + { + if (m_Parent is Item parentItem) + parentItem.GetChildNameProperties(list, item); + else if (m_Parent is Mobile parentMobile) + parentMobile.GetChildNameProperties(list, item); + } + + public virtual bool IsChildVisibleTo(Mobile m, Item child) => true; + + public void Bounce(Mobile from) + { + if (m_Parent is Item item) + item.RemoveItem(this); + else if (m_Parent is Mobile mobile) + mobile.RemoveItem(this); + + m_Parent = null; + + var bounce = GetBounce(); + + if (bounce != null) + { + var parent = bounce.Parent; + + if (parent?.Deleted != false) + { + MoveToWorld(bounce.WorldLoc, bounce.Map); + } + else if (parent is Item p) + { + var root = p.RootParent; + + if (p.IsAccessibleTo(from) && + (!(root is Mobile mobileRoot) || mobileRoot.CheckNonlocalDrop(from, this, p))) + { + Location = bounce.Location; + p.AddItem(this); + } + else + { + MoveToWorld(from.Location, from.Map); + } + } + else if (parent is Mobile parentMobile) + { + if (!parentMobile.EquipItem(this)) + MoveToWorld(bounce.WorldLoc, bounce.Map); + } + else + { + MoveToWorld(bounce.WorldLoc, bounce.Map); + } + + ClearBounce(); + } + else + { + MoveToWorld(from.Location, from.Map); + } + } + + /// + /// Overridable. Method checked to see if this item may be equipped while casting a spell. By default, this returns false. + /// It + /// is overridden on spellbook and spell channeling weapons or shields. + /// + /// True if it may, false if not. + /// + /// + /// public override bool AllowEquippedCast( Mobile from ) + /// { + /// if (from.Int >= 100) + /// return true; + /// + /// return base.AllowEquippedCast( from ); + /// } + /// When placed in an Item script, the item may be cast when equipped if the has 100 or more + /// intelligence. Otherwise, it will drop to their backpack. + /// + public virtual bool AllowEquippedCast(Mobile from) => false; + + public virtual bool CheckConflictingLayer(Mobile m, Item item, Layer layer) => m_Layer == layer; + + public virtual bool CanEquip(Mobile m) => m_Layer != Layer.Invalid && m.FindItemOnLayer(m_Layer) == null; + + public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) + { + if (m_Parent is Item parentItem) + parentItem.GetChildContextMenuEntries(from, list, item); + else if (m_Parent is Mobile parentMobile) + parentMobile.GetChildContextMenuEntries(from, list, item); + } + + public virtual void GetContextMenuEntries(Mobile from, List list) + { + if (m_Parent is Item item) + item.GetChildContextMenuEntries(from, list, this); + else if (m_Parent is Mobile mobile) + mobile.GetChildContextMenuEntries(from, list, this); + } + + public virtual bool VerifyMove(Mobile from) => Movable; + + public virtual DeathMoveResult OnParentDeath(Mobile parent) + { + if (!Movable) + return DeathMoveResult.RemainEquipped; + if (parent.KeepsItemsOnDeath) + return DeathMoveResult.MoveToBackpack; + if (CheckBlessed(parent)) + return DeathMoveResult.MoveToBackpack; + if (CheckNewbied() && parent.Kills < 5) + return DeathMoveResult.MoveToBackpack; + if (parent.Player && Nontransferable) + return DeathMoveResult.MoveToBackpack; + + return DeathMoveResult.MoveToCorpse; + } + + public virtual DeathMoveResult OnInventoryDeath(Mobile parent) + { + if (!Movable) + return DeathMoveResult.MoveToBackpack; + if (parent.KeepsItemsOnDeath) + return DeathMoveResult.MoveToBackpack; + if (CheckBlessed(parent)) + return DeathMoveResult.MoveToBackpack; + if (CheckNewbied() && parent.Kills < 5) + return DeathMoveResult.MoveToBackpack; + if (parent.Player && Nontransferable) + return DeathMoveResult.MoveToBackpack; + + return DeathMoveResult.MoveToCorpse; + } + + /// + /// Moves the Item to . The Item does not change maps. + /// + public virtual void MoveToWorld(Point3D location) + { + MoveToWorld(location, m_Map); + } + + public void LabelTo(Mobile to, int number) + { + to.Send(new MessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", "")); + } + + public void LabelTo(Mobile to, int number, string args) + { + to.Send(new MessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", args)); + } + + public void LabelTo(Mobile to, string text) + { + to.Send(new UnicodeMessage(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, "ENU", "", text)); + } + + public void LabelTo(Mobile to, string format, params object[] args) + { + LabelTo(to, string.Format(format, args)); + } + + public void LabelToAffix(Mobile to, int number, AffixType type, string affix) + { + to.Send(new MessageLocalizedAffix(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", type, affix, "")); + } + + public void LabelToAffix(Mobile to, int number, AffixType type, string affix, string args) + { + to.Send(new MessageLocalizedAffix(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, number, "", type, affix, args)); + } + + public virtual void LabelLootTypeTo(Mobile to) + { + if (m_LootType == LootType.Blessed) + LabelTo(to, 1041362); // (blessed) + else if (m_LootType == LootType.Cursed) + LabelTo(to, "(cursed)"); + } + + public bool AtWorldPoint(int x, int y) => m_Parent == null && m_Location.m_X == x && m_Location.m_Y == y; + + public bool AtPoint(int x, int y) => m_Location.m_X == x && m_Location.m_Y == y; + + public virtual bool OnDecay() => + Decays && Parent == null && Map != Map.Internal && Region.Find(Location, Map).OnDecay(this); + + public void SetLastMoved() + { + LastMoved = DateTime.UtcNow; + } + + public virtual bool CanStackWith(Item dropped) => + dropped.Stackable && Stackable && dropped.GetType() == GetType() && dropped.ItemID == ItemID && + dropped.Hue == Hue && dropped.Name == Name && dropped.Amount + Amount <= 60000 && dropped != this; + + public bool StackWith(Mobile from, Item dropped) => StackWith(from, dropped, true); + + public virtual bool StackWith(Mobile from, Item dropped, bool playSound) + { + if (CanStackWith(dropped)) + { + if (m_LootType != dropped.m_LootType) + m_LootType = LootType.Regular; + + Amount += dropped.Amount; + dropped.Delete(); + + if (playSound && from != null) + { + var soundID = GetDropSound(); + + if (soundID == -1) + soundID = 0x42; + + from.SendSound(soundID, GetWorldLocation()); + } + + return true; + } + + return false; + } + + public virtual bool OnDragDrop(Mobile from, Item dropped) + { + var success = Parent is Container container && container.OnStackAttempt(from, this, dropped) || + StackWith(from, dropped); + + if (success && Spawner != null) + { + Spawner.Remove(this); + Spawner = null; + } + + return success; + } + + public Rectangle2D GetGraphicBounds() + { + var itemID = m_ItemID; + var doubled = m_Amount > 1; + + if (itemID >= 0xEEA && itemID <= 0xEF2) // Are we coins? + { + var coinBase = (itemID - 0xEEA) / 3; + coinBase *= 3; + coinBase += 0xEEA; + + doubled = false; + + if (m_Amount <= 1) + itemID = coinBase; + else if (m_Amount <= 5) + itemID = coinBase + 1; + else // m_Amount > 5 + itemID = coinBase + 2; + } + + var bounds = ItemBounds.Table[itemID & 0x3FFF]; + + if (doubled) bounds.Set(bounds.X, bounds.Y, bounds.Width + 5, bounds.Height + 5); + + return bounds; + } + + public virtual void AppendChildProperties(ObjectPropertyList list) + { + if (m_Parent is Item item) + item.GetChildProperties(list, this); + else if (m_Parent is Mobile mobile) + mobile.GetChildProperties(list, this); + } + + public virtual void AppendChildNameProperties(ObjectPropertyList list) + { + if (m_Parent is Item item) + item.GetChildNameProperties(list, this); + else if (m_Parent is Mobile mobile) + mobile.GetChildNameProperties(list, this); + } + + public ObjectPropertyList NewObjectPropertyList() + { + var list = new ObjectPropertyList(this); + + GetProperties(list); + AppendChildProperties(list); + + list.Terminate(); + list.SetStatic(); + return list; + } + + public void ClearProperties() + { + ReleaseOPLPacket(); + StaticPacketHandlers.FreeOPLInfoPacket(this); + } + + public void InvalidateProperties() + { + if (!ObjectPropertyList.Enabled) + return; + + if (m_Map != null && m_Map != Map.Internal && !World.Loading) + { + var oldList = m_PropertyList; + m_PropertyList = null; + + if (oldList != null && oldList.Hash != PropertyList.Hash) + { + StaticPacketHandlers.FreeOPLInfoPacket(this); + Delta(ItemDelta.Properties); + } + } + else + { + ClearProperties(); + } + } + + public void ReleaseWorldPackets() + { + StaticPacketHandlers.FreeWorldItemPackets(this); + } + + public virtual int GetPacketFlags() + { + var flags = 0; + + if (!Visible) + flags |= 0x80; + + if (Movable || ForceShowProperties) + flags |= 0x20; + + return flags; + } + + public virtual bool OnMoveOff(Mobile m) => true; + + public virtual bool OnMoveOver(Mobile m) => true; + + public virtual void OnMovement(Mobile m, Point3D oldLocation) + { + } + + public void Internalize() + { + MoveToWorld(Point3D.Zero, Map.Internal); + } + + public virtual void OnMapChange() + { + } + + public virtual void OnRemoved(IEntity parent) + { + } + + public virtual void OnAdded(IEntity parent) + { + } + + private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) + { + if (setIf) + flags |= toSet; + } + + private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; + + public IPooledEnumerable GetObjectsInRange(int range) + { + var map = m_Map; + + return map == null + ? Map.NullEnumerable.Instance + : map.GetObjectsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range); + } + + public IPooledEnumerable GetItemsInRange(int range) + { + var map = m_Map; + + return map?.GetItemsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) + ?? Map.NullEnumerable.Instance; + } + + public IPooledEnumerable GetMobilesInRange(int range) + { + var map = m_Map; + + return map?.GetMobilesInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) + ?? Map.NullEnumerable.Instance; + } + + public IPooledEnumerable GetClientsInRange(int range) + { + var map = m_Map; + + return map.GetClientsInRange(m_Parent == null ? m_Location : GetWorldLocation(), range) + ?? Map.NullEnumerable.Instance; + } + + public bool GetTempFlag(int flag) => ((LookupCompactInfo()?.m_TempFlags ?? 0) & flag) != 0; + + public void SetTempFlag(int flag, bool value) + { + var info = AcquireCompactInfo(); + + if (value) + info.m_TempFlags |= flag; + else + info.m_TempFlags &= ~flag; + + if (info.m_TempFlags == 0) + VerifyCompactInfo(); + } + + public bool GetSavedFlag(int flag) => ((LookupCompactInfo()?.m_SavedFlags ?? 0) & flag) != 0; + + public void SetSavedFlag(int flag, bool value) + { + var info = AcquireCompactInfo(); + + if (value) + info.m_SavedFlags |= flag; + else + info.m_SavedFlags &= ~flag; + + if (info.m_SavedFlags == 0) + VerifyCompactInfo(); + } + + public virtual void Deserialize(IGenericReader reader) + { + var version = reader.ReadInt(); + + SetLastMoved(); + + switch (version) + { + case 9: + case 8: + case 7: + case 6: + { + var flags = (SaveFlag)reader.ReadInt(); + + if (version < 7) + { + LastMoved = reader.ReadDeltaTime(); + } + else + { + var minutes = reader.ReadEncodedInt(); + + try + { + LastMoved = DateTime.UtcNow - TimeSpan.FromMinutes(minutes); + } + catch + { + LastMoved = DateTime.UtcNow; + } + } + + if (GetSaveFlag(flags, SaveFlag.Direction)) + m_Direction = (Direction)reader.ReadByte(); + + if (GetSaveFlag(flags, SaveFlag.Bounce)) + AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); + + if (GetSaveFlag(flags, SaveFlag.LootType)) + m_LootType = (LootType)reader.ReadByte(); + + int x = 0, y = 0, z = 0; + + if (GetSaveFlag(flags, SaveFlag.LocationFull)) + { + x = reader.ReadEncodedInt(); + y = reader.ReadEncodedInt(); + z = reader.ReadEncodedInt(); + } + else + { + if (GetSaveFlag(flags, SaveFlag.LocationByteXY)) + { + x = reader.ReadByte(); + y = reader.ReadByte(); + } + else if (GetSaveFlag(flags, SaveFlag.LocationShortXY)) + { + x = reader.ReadShort(); + y = reader.ReadShort(); + } + + if (GetSaveFlag(flags, SaveFlag.LocationSByteZ)) + z = reader.ReadSByte(); + } + + m_Location = new Point3D(x, y, z); + + if (GetSaveFlag(flags, SaveFlag.ItemID)) + m_ItemID = reader.ReadEncodedInt(); + + if (GetSaveFlag(flags, SaveFlag.Hue)) + m_Hue = reader.ReadEncodedInt(); + + m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadEncodedInt() : 1; + + if (GetSaveFlag(flags, SaveFlag.Layer)) + m_Layer = (Layer)reader.ReadByte(); + + if (GetSaveFlag(flags, SaveFlag.Name)) + { + var name = reader.ReadString(); + + if (name != DefaultName) + AcquireCompactInfo().m_Name = name; + } + + if (GetSaveFlag(flags, SaveFlag.Parent)) + { + Serial parent = reader.ReadUInt(); + + if (parent.IsMobile) + m_Parent = World.FindMobile(parent); + else if (parent.IsItem) + m_Parent = World.FindItem(parent); + else + m_Parent = null; + + if (m_Parent == null && (parent.IsMobile || parent.IsItem)) + Delete(); + } + + if (GetSaveFlag(flags, SaveFlag.Items)) + { + var items = reader.ReadStrongItemList(); + + if (this is Container) + (this as Container).m_Items = items; + else + AcquireCompactInfo().m_Items = items; + } + + if (version < 8 || !GetSaveFlag(flags, SaveFlag.NullWeight)) + { + double weight; + + if (GetSaveFlag(flags, SaveFlag.IntWeight)) + weight = reader.ReadEncodedInt(); + else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) + weight = reader.ReadDouble(); + else if (GetSaveFlag(flags, SaveFlag.WeightIs0)) + weight = 0.0; + else + weight = 1.0; + + if (weight != DefaultWeight) + AcquireCompactInfo().m_Weight = weight; + } + + m_Map = GetSaveFlag(flags, SaveFlag.Map) ? reader.ReadMap() : Map.Internal; + + SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); + + SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); + + if (GetSaveFlag(flags, SaveFlag.Stackable)) + SetFlag(ImplFlag.Stackable, reader.ReadBool()); + + if (GetSaveFlag(flags, SaveFlag.ImplFlags)) + m_Flags = (ImplFlag)reader.ReadEncodedInt(); + + if (GetSaveFlag(flags, SaveFlag.InsuredFor)) + /*m_InsuredFor = */ + reader.ReadMobile(); + + if (GetSaveFlag(flags, SaveFlag.BlessedFor)) + AcquireCompactInfo().m_BlessedFor = reader.ReadMobile(); + + if (GetSaveFlag(flags, SaveFlag.HeldBy)) + AcquireCompactInfo().m_HeldBy = reader.ReadMobile(); + + if (GetSaveFlag(flags, SaveFlag.SavedFlags)) + AcquireCompactInfo().m_SavedFlags = reader.ReadEncodedInt(); + + if (m_Map != null && m_Parent == null) + m_Map.OnEnter(this); + + break; + } + case 5: + { + var flags = (SaveFlag)reader.ReadInt(); + + LastMoved = reader.ReadDeltaTime(); + + if (GetSaveFlag(flags, SaveFlag.Direction)) + m_Direction = (Direction)reader.ReadByte(); + + if (GetSaveFlag(flags, SaveFlag.Bounce)) + AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); + + if (GetSaveFlag(flags, SaveFlag.LootType)) + m_LootType = (LootType)reader.ReadByte(); + + if (GetSaveFlag(flags, SaveFlag.LocationFull)) + m_Location = reader.ReadPoint3D(); + + if (GetSaveFlag(flags, SaveFlag.ItemID)) + m_ItemID = reader.ReadInt(); + + if (GetSaveFlag(flags, SaveFlag.Hue)) + m_Hue = reader.ReadInt(); + + m_Amount = GetSaveFlag(flags, SaveFlag.Amount) ? reader.ReadInt() : 1; + + if (GetSaveFlag(flags, SaveFlag.Layer)) + m_Layer = (Layer)reader.ReadByte(); + + if (GetSaveFlag(flags, SaveFlag.Name)) + { + var name = reader.ReadString(); + + if (name != DefaultName) + AcquireCompactInfo().m_Name = name; + } + + if (GetSaveFlag(flags, SaveFlag.Parent)) + { + Serial parent = reader.ReadUInt(); + + if (parent.IsMobile) + m_Parent = World.FindMobile(parent); + else if (parent.IsItem) + m_Parent = World.FindItem(parent); + else + m_Parent = null; + + if (m_Parent == null && (parent.IsMobile || parent.IsItem)) + Delete(); + } + + if (GetSaveFlag(flags, SaveFlag.Items)) + { + var items = reader.ReadStrongItemList(); + + if (this is Container cont) + cont.m_Items = items; + else + AcquireCompactInfo().m_Items = items; + } + + double weight; + + if (GetSaveFlag(flags, SaveFlag.IntWeight)) + weight = reader.ReadEncodedInt(); + else if (GetSaveFlag(flags, SaveFlag.WeightNot1or0)) + weight = reader.ReadDouble(); + else if (GetSaveFlag(flags, SaveFlag.WeightIs0)) + weight = 0.0; + else + weight = 1.0; + + if (weight != DefaultWeight) + AcquireCompactInfo().m_Weight = weight; + + if (GetSaveFlag(flags, SaveFlag.Map)) + m_Map = reader.ReadMap(); + else + m_Map = Map.Internal; + + SetFlag(ImplFlag.Visible, !GetSaveFlag(flags, SaveFlag.Visible) || reader.ReadBool()); + SetFlag(ImplFlag.Movable, !GetSaveFlag(flags, SaveFlag.Movable) || reader.ReadBool()); + + if (GetSaveFlag(flags, SaveFlag.Stackable)) + SetFlag(ImplFlag.Stackable, reader.ReadBool()); + + if (m_Map != null && m_Parent == null) + m_Map.OnEnter(this); + + break; + } + case 4: // Just removed variables + case 3: + { + m_Direction = (Direction)reader.ReadInt(); + + goto case 2; + } + case 2: + { + AcquireCompactInfo().m_Bounce = BounceInfo.Deserialize(reader); + LastMoved = reader.ReadDeltaTime(); + + goto case 1; + } + case 1: + { + m_LootType = (LootType)reader.ReadByte(); // m_Newbied = reader.ReadBool(); + + goto case 0; + } + case 0: + { + m_Location = reader.ReadPoint3D(); + m_ItemID = reader.ReadInt(); + m_Hue = reader.ReadInt(); + m_Amount = reader.ReadInt(); + m_Layer = (Layer)reader.ReadByte(); + + var name = reader.ReadString(); + + if (name != DefaultName) + AcquireCompactInfo().m_Name = name; + + Serial parent = reader.ReadUInt(); + + if (parent.IsMobile) + m_Parent = World.FindMobile(parent); + else if (parent.IsItem) + m_Parent = World.FindItem(parent); + else + m_Parent = null; + + if (m_Parent == null && (parent.IsMobile || parent.IsItem)) + Delete(); + + var count = reader.ReadInt(); + + if (count > 0) + { + var items = new List(count); + + for (var i = 0; i < count; ++i) + { + var item = reader.ReadItem(); + + if (item != null) + items.Add(item); + } + + if (this is Container cont) + cont.m_Items = items; + else + AcquireCompactInfo().m_Items = items; + } + + var weight = reader.ReadDouble(); + + if (weight != DefaultWeight) + AcquireCompactInfo().m_Weight = weight; + + if (version <= 3) + { + reader.ReadInt(); + reader.ReadInt(); + reader.ReadInt(); + } + + m_Map = reader.ReadMap(); + SetFlag(ImplFlag.Visible, reader.ReadBool()); + SetFlag(ImplFlag.Movable, reader.ReadBool()); + + if (version <= 3) + /*m_Deleted =*/ + reader.ReadBool(); + + Stackable = reader.ReadBool(); + + if (m_Map != null && m_Parent == null) + m_Map.OnEnter(this); + + break; + } + } + + if (HeldBy != null) + Timer.DelayCall(FixHolding_Sandbox); + + // if (version < 9) + VerifyCompactInfo(); + } + + private void FixHolding_Sandbox() + { + var heldBy = HeldBy; + + if (heldBy != null) + { + if (GetBounce() != null) + { + Bounce(heldBy); + } + else + { + heldBy.Holding = null; + heldBy.AddToBackpack(this); + ClearBounce(); + } + } + } + + public virtual int GetMaxUpdateRange() => 18; + + public virtual int GetUpdateRange(Mobile m) => 18; + + public void SendInfoTo(NetState state) + { + SendInfoTo(state, ObjectPropertyList.Enabled); + } + + public virtual void SendInfoTo(NetState state, bool sendOplPacket) + { + state.Send(GetWorldPacketFor(state)); + + if (sendOplPacket) state.Send(OPLPacket); + } + + protected virtual Packet GetWorldPacketFor(NetState state) + { + if (state.HighSeas) + return WorldPacketHS; + if (state.StygianAbyss) + return WorldPacketSA; + return WorldPacket; + } + + public virtual int GetTotal(TotalType type) => 0; + + public virtual void UpdateTotal(Item sender, TotalType type, int delta) + { + if (!IsVirtualItem) + { + if (m_Parent is Item item) + item.UpdateTotal(sender, type, delta); + else if (m_Parent is Mobile mobile) + mobile.UpdateTotal(sender, type, delta); + else + HeldBy?.UpdateTotal(sender, type, delta); + } + } + + public virtual void UpdateTotals() + { + } + + public virtual void HandleInvalidTransfer(Mobile from) + { + // OSI sends 1074769, bug! + if (QuestItem) + from.SendLocalizedMessage( + 1049343 + ); // You can only drop quest items into the top-most level of your backpack while you still need them for your quest. + } + + public bool ParentsContain() where T : Item + { + var p = m_Parent; + + while (p is Item item) + { + if (item is T) + return true; + + if (item.m_Parent == null) break; + + p = item.m_Parent; + } + + return false; + } + + public virtual void AddItem(Item item) + { + if (item?.Deleted != false || item.m_Parent == this) return; + + if (item == this) + { + Console.WriteLine( + "Warning: Adding item to itself: [0x{0:X} {1}].AddItem( [0x{2:X} {3}] )", + Serial.Value, + GetType().Name, + item.Serial.Value, + item.GetType().Name + ); + Console.WriteLine(new StackTrace()); + return; + } + + if (IsChildOf(item)) + { + Console.WriteLine( + "Warning: Adding parent item to child: [0x{0:X} {1}].AddItem( [0x{2:X} {3}] )", + Serial.Value, + GetType().Name, + item.Serial.Value, + item.GetType().Name + ); + Console.WriteLine(new StackTrace()); + return; + } + + if (item.m_Parent is Mobile parentMobile) + parentMobile.RemoveItem(item); + else if (item.m_Parent is Item parentItem) + parentItem.RemoveItem(item); + else + item.SendRemovePacket(); + + item.Parent = this; + item.Map = m_Map; + + var items = AcquireItems(); + + items.Add(item); + + if (!item.IsVirtualItem) + { + UpdateTotal(item, TotalType.Gold, item.TotalGold); + UpdateTotal(item, TotalType.Items, item.TotalItems + 1); + UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); + } + + item.Delta(ItemDelta.Update); + + item.OnAdded(this); + OnItemAdded(item); + } + + public void Delta(ItemDelta flags) + { + if (m_Map == null || m_Map == Map.Internal) + return; + + m_DeltaFlags |= flags; + + if (!GetFlag(ImplFlag.InQueue)) + { + SetFlag(ImplFlag.InQueue, true); + + if (_processing) + try + { + using var op = new StreamWriter("delta-recursion.log", true); + op.WriteLine("# {0}", DateTime.UtcNow); + op.WriteLine(new StackTrace()); + op.WriteLine(); + } + catch + { + // ignored + } + else + m_DeltaQueue.Add(this); + } + + Core.Set(); + } + + public void RemDelta(ItemDelta flags) + { + m_DeltaFlags &= ~flags; + + if (GetFlag(ImplFlag.InQueue) && m_DeltaFlags == ItemDelta.None) + { + SetFlag(ImplFlag.InQueue, false); + + if (_processing) + try + { + using var op = new StreamWriter("delta-recursion.log", true); + op.WriteLine("# {0}", DateTime.UtcNow); + op.WriteLine(new StackTrace()); + op.WriteLine(); + } + catch + { + // ignored + } + else + m_DeltaQueue.Remove(this); + } + } + + public static void ProcessDeltaQueue() + { + _processing = true; + + if (m_DeltaQueue.Count >= 512) + Parallel.ForEach(m_DeltaQueue, i => i.ProcessDelta()); + else + for (var i = 0; i < m_DeltaQueue.Count; i++) + m_DeltaQueue[i].ProcessDelta(); + + m_DeltaQueue.Clear(); + + _processing = false; + } + + public virtual void OnDelete() + { + if (Spawner != null) + { + Spawner.Remove(this); + Spawner = null; + } + } + + public virtual void OnParentDeleted(IEntity parent) + { + Delete(); + } + + public virtual void FreeCache() + { + ReleaseWorldPackets(); + StaticPacketHandlers.FreeRemoveItemPacket(this); + StaticPacketHandlers.FreeOPLInfoPacket(this); + ReleaseOPLPacket(); + } + + public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text) + { + if (m_Map == null) + return; + + Packet p = null; + var worldLoc = GetWorldLocation(); + + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) + { + if (p == null) + { + if (ascii) + p = new AsciiMessage(Serial, m_ItemID, type, hue, 3, Name, text); + else + p = new UnicodeMessage(Serial, m_ItemID, type, hue, 3, "ENU", Name, text); + + p.Acquire(); + } + + state.Send(p); + } + } + + Packet.Release(p); + + eable.Free(); + } + + public void PublicOverheadMessage(MessageType type, int hue, int number) + { + PublicOverheadMessage(type, hue, number, ""); + } + + public void PublicOverheadMessage(MessageType type, int hue, int number, string args) + { + if (m_Map == null) + return; + + Packet p = null; + var worldLoc = GetWorldLocation(); + + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.CanSee(this) && m.InRange(worldLoc, GetUpdateRange(m))) + { + p ??= Packet.Acquire(new MessageLocalized(Serial, m_ItemID, type, hue, 3, number, Name, args)); + + state.Send(p); + } + } + + Packet.Release(p); + + eable.Free(); + } + + public virtual void OnAfterDelete() + { + } + + public virtual void RemoveItem(Item item) + { + var items = LookupItems(); + + if (items?.Contains(item) == true) + { + item.SendRemovePacket(); + + items.Remove(item); + + if (!item.IsVirtualItem) + { + UpdateTotal(item, TotalType.Gold, -item.TotalGold); + UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); + UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); + } + + item.Parent = null; + + item.OnRemoved(this); + OnItemRemoved(item); + } + } + + public virtual void OnAfterDuped(Item newItem) + { + } + + public virtual bool OnDragLift(Mobile from) => true; + + public virtual bool OnEquip(Mobile from) => true; + + protected virtual void OnAmountChange(int oldValue) + { + } + + public virtual void OnSpeech(SpeechEventArgs e) + { + } + + public virtual bool OnDroppedToMobile(Mobile from, Mobile target) + { + if (Nontransferable && from.Player) + { + HandleInvalidTransfer(from); + return false; + } + + return true; + } + + public virtual bool DropToMobile(Mobile from, Mobile target, Point3D p) => + !(Deleted || from.Deleted || target.Deleted) && from.Map == target.Map && from.Map != null && + target.Map != null && (from.AccessLevel >= AccessLevel.GameMaster || from.InRange(target.Location, 2)) && + from.CanSee(target) && from.InLOS(target) && from.OnDroppedItemToMobile(this, target) && + OnDroppedToMobile(from, target) && target.OnDragDrop(from, this); + + public virtual bool OnDroppedInto(Mobile from, Container target, Point3D p) + { + if (!from.OnDroppedItemInto(this, target, p)) + return false; + + if (Nontransferable && from.Player && target != from.Backpack) + { + HandleInvalidTransfer(from); + return false; + } + + return target.OnDragDropInto(from, this, p); + } + + public virtual bool OnDroppedOnto(Mobile from, Item target) + { + if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || + target.Map == null) + return false; + if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) + return false; + if (!from.CanSee(target) || !from.InLOS(target)) + return false; + if (!target.IsAccessibleTo(from)) + return false; + if (!from.OnDroppedItemOnto(this, target)) + return false; + if (Nontransferable && from.Player && target != from.Backpack) + { + HandleInvalidTransfer(from); + return false; + } + + return target.OnDragDrop(from, this); + } + + public virtual bool DropToItem(Mobile from, Item target, Point3D p) + { + if (Deleted || from.Deleted || target.Deleted || from.Map != target.Map || from.Map == null || + target.Map == null) + return false; + + if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(target.GetWorldLocation(), 2)) + return false; + if (!from.CanSee(target) || !from.InLOS(target)) + return false; + if (!target.IsAccessibleTo(from)) + return false; + if (target.RootParent is Mobile mobile && !mobile.CheckNonlocalDrop(from, this, target)) + return false; + if (!from.OnDroppedItemToItem(this, target, p)) + return false; + if (target is Container container && p.m_X != -1 && p.m_Y != -1) + return OnDroppedInto(from, container, p); + + return OnDroppedOnto(from, target); + } + + public virtual bool OnDroppedToWorld(Mobile from, Point3D p) + { + if (Nontransferable && from.Player) + { + HandleInvalidTransfer(from); + return false; + } + + return true; + } + + public virtual int GetLiftSound(Mobile from) => 0x57; + + public virtual bool DropToWorld(Mobile from, Point3D p) + { + if (Deleted || from.Deleted || from.Map == null) + return false; + + if (!from.InRange(p, 2)) + return false; + + var map = from.Map; + + if (map == null) + return false; + + int x = p.m_X, y = p.m_Y; + var z = int.MinValue; + + var maxZ = from.Z + 16; + + var landTile = map.Tiles.GetLandTile(x, y); + var landFlags = TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags; + + int landZ = 0, landAvg = 0, landTop = 0; + map.GetAverageZ(x, y, ref landZ, ref landAvg, ref landTop); + + if (!landTile.Ignored && (landFlags & TileFlag.Impassable) == 0) + if (landAvg <= maxZ) + z = landAvg; + + var tiles = map.Tiles.GetStaticTiles(x, y, true); + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + if (!id.Surface) + continue; + + var top = tile.Z + id.CalcHeight; + + if (top > maxZ || top < z) + continue; + + z = top; + } + + var eable = map.GetItemsInRange(p, 0); + + var items = eable.Where( + item => + { + if (item is BaseMulti || item.ItemID > TileData.MaxItemValue) + return false; + + var id = item.ItemData; + + if (id.Surface) + { + var top = item.Z + id.CalcHeight; + if (top <= maxZ && top >= z) + z = top; + } + + return true; + } + ) + .ToList(); + + eable.Free(); + + if (z == int.MinValue) + return false; + + if (z > maxZ) + return false; + + m_OpenSlots = (1 << 20) - 1; + + var surfaceZ = z; + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + var checkZ = tile.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + ++checkTop; + + var zStart = Math.Max(checkZ - z, 0); + var zEnd = Math.Min(checkTop - z, 19); + + if (zStart >= 20 || zEnd < 0) + continue; + + var bitCount = zEnd - zStart; + + m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); + } + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + var id = item.ItemData; + + var checkZ = item.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + ++checkTop; + + var zStart = Math.Max(checkZ - z, 0); + var zEnd = Math.Min(checkTop - z, 19); + + if (zStart >= 20 || zEnd < 0) + continue; + + var bitCount = zEnd - zStart; + + m_OpenSlots &= ~(((1 << bitCount) - 1) << zStart); + } + + var height = ItemData.Height; + + if (height == 0) + ++height; + + if (height > 30) + height = 30; + + var match = (1 << height) - 1; + var okay = false; + + for (var i = 0; i < 20; ++i) + { + if (i + height > 20) + match >>= 1; + + okay = ((m_OpenSlots >> i) & match) == match; + + if (okay) + { + z += i; + break; + } + } + + if (!okay) + return false; + + height = ItemData.Height; + + if (height == 0) + ++height; + + if (landAvg > z && z + height > landZ) + return false; + + if ((landFlags & TileFlag.Impassable) != 0 && landAvg > surfaceZ && z + height > landZ) + return false; + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + var checkZ = tile.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop > z && z + height > checkZ) + return false; + + if ((id.Surface || id.Impassable) && checkTop > surfaceZ && z + height > checkZ) + return false; + } + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + var id = item.ItemData; + + // int checkZ = item.Z; + // int checkTop = checkZ + id.CalcHeight; + + if (item.Z + id.CalcHeight > z && z + height > item.Z) + return false; + } + + p = new Point3D(x, y, z); + + if (!from.InLOS(new Point3D(x, y, z + 1))) + return false; + if (!from.OnDroppedItemToWorld(this, p)) + return false; + if (!OnDroppedToWorld(from, p)) + return false; + + var soundID = GetDropSound(); + + MoveToWorld(p, from.Map); + + from.SendSound(soundID == -1 ? 0x42 : soundID, GetWorldLocation()); + + return true; + } + + public void SendRemovePacket() + { + if (Deleted || m_Map == null) + return; + var worldLoc = GetWorldLocation(); + + var eable = m_Map.GetClientsInRange(worldLoc, GetMaxUpdateRange()); + + foreach (var state in eable) + { + var m = state.Mobile; + + if (m.InRange(worldLoc, GetUpdateRange(m))) state.Send(RemovePacket); + } + + eable.Free(); + } + + public virtual int GetDropSound() => -1; + + public Point3D GetWorldLocation() + { + var root = RootParent; + + if (root == null) + return m_Location; + return root.Location; + + // return root == null ? m_Location : new Point3D( (IPoint3D) root ); + } + + public Point3D GetSurfaceTop() + { + var root = RootParent; + + if (root == null) + return new Point3D( + m_Location.m_X, + m_Location.m_Y, + m_Location.m_Z + (ItemData.Surface ? ItemData.CalcHeight : 0) + ); + + return root.Location; + } + + public Point3D GetWorldTop() => RootParent?.Location ?? + new Point3D(m_Location.m_X, m_Location.m_Y, m_Location.m_Z + ItemData.CalcHeight); + + public void SendLocalizedMessageTo(Mobile to, int number) + { + if (Deleted || !to.CanSee(this)) + return; + + to.Send(new MessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", "")); + } + + public void SendLocalizedMessageTo(Mobile to, int number, string args) + { + if (Deleted || !to.CanSee(this)) + return; + + to.Send(new MessageLocalized(Serial, ItemID, MessageType.Regular, 0x3B2, 3, number, "", args)); + } + + public void SendLocalizedMessageTo(Mobile to, int number, AffixType affixType, string affix, string args) + { + if (Deleted || !to.CanSee(this)) + return; + + to.Send( + new MessageLocalizedAffix( + Serial, + ItemID, + MessageType.Regular, + 0x3B2, + 3, + number, + "", + affixType, + affix, + args + ) + ); + } + + public virtual void OnSnoop(Mobile from) + { + } + + public SecureTradeContainer GetSecureTradeCont() + { + object p = this; + + while (p is Item item) + { + if (item is SecureTradeContainer container) + return container; + + p = item.m_Parent; + } + + return null; + } + + public virtual void OnItemAdded(Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnSubItemAdded(item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnSubItemAdded(item); + } + + public virtual void OnItemRemoved(Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnSubItemRemoved(item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnSubItemRemoved(item); + } + + public virtual void OnSubItemAdded(Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnSubItemAdded(item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnSubItemAdded(item); + } + + public virtual void OnSubItemRemoved(Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnSubItemRemoved(item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnSubItemRemoved(item); + } + + public virtual void OnItemBounceCleared(Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnSubItemBounceCleared(item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnSubItemBounceCleared(item); + } + + public virtual void OnSubItemBounceCleared(Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnSubItemBounceCleared(item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnSubItemBounceCleared(item); + } + + public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => + m_Parent switch + { + Item item => item.CheckTarget(from, targ, targeted), + Mobile mobile => mobile.CheckTarget(from, targ, targeted), + _ => true + }; + + public virtual bool IsAccessibleTo(Mobile check) + { + if (m_Parent is Item item) + return item.IsAccessibleTo(check); + + var reg = Region.Find(GetWorldLocation(), m_Map); + + return reg.CheckAccessibility(this, check); + + /*SecureTradeContainer cont = GetSecureTradeCont(); + + if (cont != null && !cont.IsChildOf( check )) + return false; + + return true;*/ + } + + public bool IsChildOf(IEntity o) => IsChildOf(o, false); + + public bool IsChildOf(IEntity o, bool allowNull) + { + var p = m_Parent; + + if ((p == null || o == null) && !allowNull) + return false; + + if (p == o) + return true; + + while (p is Item item) + { + if (item.m_Parent == null) + break; + + p = item.m_Parent; + + if (p == o) + return true; + } + + return false; + } + + public virtual void OnItemUsed(Mobile from, Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnItemUsed(from, item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnItemUsed(from, item); + } + + public bool CheckItemUse(Mobile from) => CheckItemUse(from, this); + + public virtual bool CheckItemUse(Mobile from, Item item) => + m_Parent switch + { + Item parentItem => parentItem.CheckItemUse(from, item), + Mobile parentMobile => parentMobile.CheckItemUse(from, item), + _ => true + }; + + public virtual void OnItemLifted(Mobile from, Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnItemLifted(from, item); + else if (m_Parent is Mobile parentMobile) + parentMobile.OnItemLifted(from, item); + } + + public bool CheckLift(Mobile from) + { + var reject = LRReason.Inspecific; + + return CheckLift(from, this, ref reject); + } + + public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => + m_Parent switch + { + Item parentItem => parentItem.CheckLift(from, item, ref reject), + Mobile parentMobile => parentMobile.CheckLift(from, item, ref reject), + _ => true + }; + + public virtual void OnSingleClickContained(Mobile from, Item item) + { + if (m_Parent is Item parentItem) + parentItem.OnSingleClickContained(from, item); + } + + public virtual void OnAosSingleClick(Mobile from) + { + var opl = PropertyList; + + if (opl.Header > 0) + from.Send( + new MessageLocalized( + Serial, + m_ItemID, + MessageType.Label, + 0x3B2, + 3, + opl.Header, + Name, + opl.HeaderArgs + ) + ); + } + + public virtual void OnSingleClick(Mobile from) + { + if (Deleted || !from.CanSee(this)) + return; + + if (DisplayLootType) + LabelLootTypeTo(from); + + var ns = from.NetState; + + if (ns == null) + return; + + if (Name == null) + { + if (m_Amount <= 1) + ns.Send(new MessageLocalized(Serial, m_ItemID, MessageType.Label, 0x3B2, 3, LabelNumber, "", "")); + else + ns.Send( + new MessageLocalizedAffix( + Serial, + m_ItemID, + MessageType.Label, + 0x3B2, + 3, + LabelNumber, + "", + AffixType.Append, + $" : {m_Amount}", + "" + ) + ); + } + else + { + ns.Send( + new UnicodeMessage( + Serial, + m_ItemID, + MessageType.Label, + 0x3B2, + 3, + "ENU", + "", + Name + (m_Amount > 1 ? $" : {m_Amount}" : "") + ) + ); + } + } + + public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem) + { + ScissorHelper(from, newItem, amountPerOldItem, true); + } + + public virtual void ScissorHelper(Mobile from, Item newItem, int amountPerOldItem, bool carryHue) + { + // let's not go over 60000 + var amount = Math.Min(Amount, 60000 / amountPerOldItem); + + Amount -= amount; + + var ourHue = Hue; + var thisMap = Map; + var thisParent = m_Parent; + var worldLoc = GetWorldLocation(); + var type = LootType; + + if (Amount == 0) + Delete(); + + newItem.Amount = amount * amountPerOldItem; + + if (carryHue) + newItem.Hue = ourHue; + + if (ScissorCopyLootType) + newItem.LootType = type; + + if ((thisParent as Container)?.TryDropItem(from, newItem, false) != true) + newItem.MoveToWorld(worldLoc, thisMap); + } + + public virtual void Consume() + { + Consume(1); + } + + public virtual void Consume(int amount) + { + Amount -= amount; + + if (Amount <= 0) + Delete(); + } + + public virtual void ReplaceWith(Item newItem) + { + if (m_Parent is Container container) + { + container.AddItem(newItem); + newItem.Location = m_Location; + } + else + { + newItem.MoveToWorld(GetWorldLocation(), m_Map); + } + + Delete(); + } + + public virtual bool CheckBlessed(Mobile m) => + m_LootType == LootType.Blessed || Mobile.InsuranceEnabled && Insured || m != null && m == BlessedFor; + + public virtual bool CheckNewbied() => m_LootType == LootType.Newbied; + + public virtual bool IsStandardLoot() => + (!Mobile.InsuranceEnabled || !Insured) && BlessedFor == null && m_LootType == LootType.Regular; + + public override string ToString() => $"0x{Serial.Value:X} \"{GetType().Name}\""; + + public virtual void OnSectorActivate() + { + } + + public virtual void OnSectorDeactivate() + { + } + + public virtual void OnLocationChange(Point3D oldLocation) + { + } + + public virtual void OnDoubleClick(Mobile from) + { + } + + public virtual void OnDoubleClickOutOfRange(Mobile from) + { + } + + public virtual void OnDoubleClickCantSee(Mobile from) + { + } + + public virtual void OnDoubleClickDead(Mobile from) + { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. + } + + public virtual void OnDoubleClickNotAccessible(Mobile from) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + public virtual void OnDoubleClickSecureTrade(Mobile from) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + [Flags] + private enum ImplFlag : byte + { + None = 0x00, + Visible = 0x01, + Movable = 0x02, + Deleted = 0x04, + Stackable = 0x08, + InQueue = 0x10, + Insured = 0x20, + PaidInsurance = 0x40, + QuestItem = 0x80 + } + + private class CompactInfo + { + public Mobile m_BlessedFor; + public BounceInfo m_Bounce; + + public Mobile m_HeldBy; + + public List m_Items; + public string m_Name; + public int m_SavedFlags; + + public ISpawner m_Spawner; + + public int m_TempFlags; + + public double m_Weight = -1; + } + + [Flags] + private enum SaveFlag : uint + { + None = 0x00000000, + Direction = 0x00000001, + Bounce = 0x00000002, + LootType = 0x00000004, + LocationFull = 0x00000008, + ItemID = 0x00000010, + Hue = 0x00000020, + Amount = 0x00000040, + Layer = 0x00000080, + Name = 0x00000100, + Parent = 0x00000200, + Items = 0x00000400, + WeightNot1or0 = 0x00000800, + Map = 0x00001000, + Visible = 0x00002000, + Movable = 0x00004000, + Stackable = 0x00008000, + WeightIs0 = 0x00010000, + LocationSByteZ = 0x00020000, + LocationShortXY = 0x00040000, + LocationByteXY = 0x00080000, + ImplFlags = 0x00100000, + InsuredFor = 0x00200000, + BlessedFor = 0x00400000, + HeldBy = 0x00800000, + IntWeight = 0x01000000, + SavedFlags = 0x02000000, + NullWeight = 0x04000000 + } + } +} diff --git a/Projects/Server/ItemBounds.cs b/Projects/Server/ItemBounds.cs index f1d5bdbe6..77c0ecb4e 100644 --- a/Projects/Server/ItemBounds.cs +++ b/Projects/Server/ItemBounds.cs @@ -1,60 +1,64 @@ -/*************************************************************************** - * ItemBounds.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.IO; - -namespace Server -{ - public static class ItemBounds - { - static ItemBounds() - { - Table = new Rectangle2D[TileData.ItemTable.Length]; - - if (File.Exists("Data/Binary/Bounds.bin")) - { - using var fs = new FileStream("Data/Binary/Bounds.bin", FileMode.Open, FileAccess.Read, - FileShare.Read); - var bin = new BinaryReader(fs); - - var count = Math.Min(Table.Length, (int)(fs.Length / 8)); - - for (var i = 0; i < count; ++i) - { - int xMin = bin.ReadInt16(); - int yMin = bin.ReadInt16(); - int xMax = bin.ReadInt16(); - int yMax = bin.ReadInt16(); - - Table[i].Set(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1); - } - - bin.Close(); - } - else - { - Console.WriteLine("Warning: Data/Binary/Bounds.bin does not exist"); - } - } - - public static Rectangle2D[] Table { get; } - } -} +/*************************************************************************** + * ItemBounds.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.IO; + +namespace Server +{ + public static class ItemBounds + { + static ItemBounds() + { + Table = new Rectangle2D[TileData.ItemTable.Length]; + + if (File.Exists("Data/Binary/Bounds.bin")) + { + using var fs = new FileStream( + "Data/Binary/Bounds.bin", + FileMode.Open, + FileAccess.Read, + FileShare.Read + ); + var bin = new BinaryReader(fs); + + var count = Math.Min(Table.Length, (int)(fs.Length / 8)); + + for (var i = 0; i < count; ++i) + { + int xMin = bin.ReadInt16(); + int yMin = bin.ReadInt16(); + int xMax = bin.ReadInt16(); + int yMax = bin.ReadInt16(); + + Table[i].Set(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1); + } + + bin.Close(); + } + else + { + Console.WriteLine("Warning: Data/Binary/Bounds.bin does not exist"); + } + } + + public static Rectangle2D[] Table { get; } + } +} diff --git a/Projects/Server/Items/BaseMulti.cs b/Projects/Server/Items/BaseMulti.cs index f74e7ef97..148471c68 100644 --- a/Projects/Server/Items/BaseMulti.cs +++ b/Projects/Server/Items/BaseMulti.cs @@ -1,136 +1,136 @@ -/*************************************************************************** - * BaseMulti.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server.Items -{ - public abstract class BaseMulti : Item - { - public BaseMulti(int itemID) : base(itemID) => Movable = false; - - public BaseMulti(Serial serial) : base(serial) - { - } - - [CommandProperty(AccessLevel.GameMaster)] - public override int ItemID - { - get => base.ItemID; - set - { - if (base.ItemID != value) - { - var facet = Parent == null ? Map : null; - - facet?.OnLeave(this); - - base.ItemID = value; - - facet?.OnEnter(this); - } - } - } - - public override int LabelNumber - { - get - { - var mcl = Components; - - if (mcl.List.Length > 0) - { - int id = mcl.List[0].ItemId; - - if (id < 0x4000) - return 1020000 + id; - return 1078872 + id; - } - - return base.LabelNumber; - } - } - - public virtual bool AllowsRelativeDrop => false; - - public virtual MultiComponentList Components => MultiData.GetComponents(ItemID); - - [Obsolete("Replace with calls to OnLeave and OnEnter surrounding component invalidation.", true)] - public virtual void RefreshComponents() - { - if (Parent == null) - { - var facet = Map; - - if (facet != null) - { - facet.OnLeave(this); - facet.OnEnter(this); - } - } - } - - public override int GetMaxUpdateRange() => 22; - - public override int GetUpdateRange(Mobile m) => 22; - - public virtual bool Contains(Point2D p) => Contains(p.m_X, p.m_Y); - - public virtual bool Contains(Point3D p) => Contains(p.m_X, p.m_Y); - - public virtual bool Contains(IPoint3D p) => Contains(p.X, p.Y); - - public virtual bool Contains(int x, int y) - { - var mcl = Components; - - x -= X + mcl.Min.m_X; - y -= Y + mcl.Min.m_Y; - - return x >= 0 - && x < mcl.Width - && y >= 0 - && y < mcl.Height - && mcl.Tiles[x][y].Length > 0; - } - - public bool Contains(Mobile m) => m.Map == Map && Contains(m.X, m.Y); - - public bool Contains(Item item) => item.Map == Map && Contains(item.X, item.Y); - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(1); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - if (version == 0) - if (ItemID >= 0x4000) - ItemID -= 0x4000; - } - } -} +/*************************************************************************** + * BaseMulti.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server.Items +{ + public abstract class BaseMulti : Item + { + public BaseMulti(int itemID) : base(itemID) => Movable = false; + + public BaseMulti(Serial serial) : base(serial) + { + } + + [CommandProperty(AccessLevel.GameMaster)] + public override int ItemID + { + get => base.ItemID; + set + { + if (base.ItemID != value) + { + var facet = Parent == null ? Map : null; + + facet?.OnLeave(this); + + base.ItemID = value; + + facet?.OnEnter(this); + } + } + } + + public override int LabelNumber + { + get + { + var mcl = Components; + + if (mcl.List.Length > 0) + { + int id = mcl.List[0].ItemId; + + if (id < 0x4000) + return 1020000 + id; + return 1078872 + id; + } + + return base.LabelNumber; + } + } + + public virtual bool AllowsRelativeDrop => false; + + public virtual MultiComponentList Components => MultiData.GetComponents(ItemID); + + [Obsolete("Replace with calls to OnLeave and OnEnter surrounding component invalidation.", true)] + public virtual void RefreshComponents() + { + if (Parent == null) + { + var facet = Map; + + if (facet != null) + { + facet.OnLeave(this); + facet.OnEnter(this); + } + } + } + + public override int GetMaxUpdateRange() => 22; + + public override int GetUpdateRange(Mobile m) => 22; + + public virtual bool Contains(Point2D p) => Contains(p.m_X, p.m_Y); + + public virtual bool Contains(Point3D p) => Contains(p.m_X, p.m_Y); + + public virtual bool Contains(IPoint3D p) => Contains(p.X, p.Y); + + public virtual bool Contains(int x, int y) + { + var mcl = Components; + + x -= X + mcl.Min.m_X; + y -= Y + mcl.Min.m_Y; + + return x >= 0 + && x < mcl.Width + && y >= 0 + && y < mcl.Height + && mcl.Tiles[x][y].Length > 0; + } + + public bool Contains(Mobile m) => m.Map == Map && Contains(m.X, m.Y); + + public bool Contains(Item item) => item.Map == Map && Contains(item.X, item.Y); + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(1); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + if (version == 0) + if (ItemID >= 0x4000) + ItemID -= 0x4000; + } + } +} diff --git a/Projects/Server/Items/Container.cs b/Projects/Server/Items/Container.cs index 548626464..c592d97bf 100644 --- a/Projects/Server/Items/Container.cs +++ b/Projects/Server/Items/Container.cs @@ -1,1669 +1,1700 @@ -/*************************************************************************** - * Container.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Server.Network; -using Server.Utilities; -using QueuePool = Server.Utilities.RefPool>; - -namespace Server.Items -{ - public delegate void OnItemConsumed(Item item, int amount); - - public delegate int CheckItemGroup(Item a, Item b); - - public delegate void ContainerSnoopHandler(Container cont, Mobile from); - - public class Container : Item - { - private static readonly QueuePool m_QueuePool = new QueuePool(QueueRef.Generate, 2, 5); - private static readonly List m_FindItemsList = new List(); - - private ContainerData m_ContainerData; - - private int m_DropSound; - private int m_GumpID; - - internal List m_Items; - private int m_MaxItems; - private int m_TotalGold; - - private int m_TotalItems; - private int m_TotalWeight; - - public Container(int itemID) : base(itemID) - { - m_GumpID = -1; - m_DropSound = -1; - m_MaxItems = -1; - - UpdateContainerData(); - } - - public Container(Serial serial) : base(serial) - { - } - - public static ContainerSnoopHandler SnoopHandler { get; set; } - - public ContainerData ContainerData - { - get - { - if (m_ContainerData == null) - UpdateContainerData(); - - return m_ContainerData; - } - set => m_ContainerData = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public override int ItemID - { - get => base.ItemID; - set - { - var oldID = ItemID; - - base.ItemID = value; - - if (ItemID != oldID) - UpdateContainerData(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int GumpID - { - get => m_GumpID == -1 ? DefaultGumpID : m_GumpID; - set => m_GumpID = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int DropSound - { - get => m_DropSound == -1 ? DefaultDropSound : m_DropSound; - set => m_DropSound = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int MaxItems - { - get => m_MaxItems == -1 ? DefaultMaxItems : m_MaxItems; - set - { - m_MaxItems = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int MaxWeight - { - get - { - if (Parent is Container container && container.MaxWeight == 0) return 0; - - return DefaultMaxWeight; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool LiftOverride { get; set; } - - public virtual Rectangle2D Bounds => ContainerData.Bounds; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int DefaultGumpID => ContainerData.GumpID; - - [CommandProperty(AccessLevel.GameMaster)] - public virtual int DefaultDropSound => ContainerData.DropSound; - - public virtual int DefaultMaxItems => GlobalMaxItems; - public virtual int DefaultMaxWeight => GlobalMaxWeight; - - public virtual bool IsDecoContainer => !Movable && !IsLockedDown && !IsSecure && Parent == null && !LiftOverride; - - public static int GlobalMaxItems { get; set; } = 125; - - public static int GlobalMaxWeight { get; set; } = 400; - - public virtual bool DisplaysContent => true; - - public List Openers { get; set; } - - public virtual bool IsPublicContainer => false; - - public virtual void UpdateContainerData() - { - ContainerData = ContainerData.GetData(ItemID); - } - - public virtual int GetDroppedSound(Item item) - { - var dropSound = item.GetDropSound(); - - return dropSound != -1 ? dropSound : DropSound; - } - - public override void OnSnoop(Mobile from) - { - SnoopHandler?.Invoke(this, from); - } - - public override bool CheckLift(Mobile from, Item item, ref LRReason reject) - { - if (from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) - { - reject = LRReason.CannotLift; - return false; - } - - return base.CheckLift(from, item, ref reject); - } - - public override bool CheckItemUse(Mobile from, Item item) - { - if (item != this && from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) - { - from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that. - return false; - } - - return base.CheckItemUse(from, item); - } - - public bool CheckHold(Mobile m, Item item, bool message) => CheckHold(m, item, message, true, 0, 0); - - public bool CheckHold(Mobile m, Item item, bool message, bool checkItems) => - CheckHold(m, item, message, checkItems, 0, 0); - - public virtual bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) - { - if (m == null || m.AccessLevel < AccessLevel.GameMaster) - { - if (IsDecoContainer) - { - if (message) - SendCantStoreMessage(m, item); - - return false; - } - - var maxItems = MaxItems; - - if (checkItems && maxItems != 0 && - TotalItems + plusItems + item.TotalItems + (item.IsVirtualItem ? 0 : 1) > maxItems) - { - if (message) - SendFullItemsMessage(m, item); - - return false; - } - - if (MaxWeight != 0 && TotalWeight + plusWeight + item.TotalWeight + item.PileWeight > MaxWeight) - { - if (message) - SendFullWeightMessage(m, item); - - return false; - } - } - - var parent = Parent; - - while (parent != null) - { - if (parent is Container container) - return container.CheckHold(m, item, message, checkItems, plusItems, plusWeight); - - if (!(parent is Item parentItem)) - break; - - parent = parentItem.Parent; - } - - return true; - } - - public virtual void SendFullItemsMessage(Mobile to, Item item) - { - to.SendMessage("That container cannot hold more items."); - } - - public virtual void SendFullWeightMessage(Mobile to, Item item) - { - to.SendMessage("That container cannot hold more weight."); - } - - public virtual void SendCantStoreMessage(Mobile to, Item item) - { - to.SendLocalizedMessage(500176); // That is not your container, you can't store things here. - } - - public virtual bool OnDragDropInto(Mobile from, Item item, Point3D p) - { - if (!CheckHold(from, item, true, true)) - return false; - - item.Location = new Point3D(p.m_X, p.m_Y, 0); - AddItem(item); - - from.SendSound(GetDroppedSound(item), GetWorldLocation()); - - return true; - } - - private static bool InTypeList(Item item, Type[] types) - { - var t = item.GetType(); - - for (var i = 0; i < types.Length; ++i) - if (types[i].IsAssignableFrom(t)) - return true; - - return false; - } - - private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) - { - if (setIf) - flags |= toSet; - } - - private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(2); // version - - var flags = SaveFlag.None; - - SetSaveFlag(ref flags, SaveFlag.MaxItems, m_MaxItems != -1); - SetSaveFlag(ref flags, SaveFlag.GumpID, m_GumpID != -1); - SetSaveFlag(ref flags, SaveFlag.DropSound, m_DropSound != -1); - SetSaveFlag(ref flags, SaveFlag.LiftOverride, LiftOverride); - - writer.Write((byte)flags); - - if (GetSaveFlag(flags, SaveFlag.MaxItems)) - writer.WriteEncodedInt(m_MaxItems); - - if (GetSaveFlag(flags, SaveFlag.GumpID)) - writer.WriteEncodedInt(m_GumpID); - - if (GetSaveFlag(flags, SaveFlag.DropSound)) - writer.WriteEncodedInt(m_DropSound); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 2: - { - var flags = (SaveFlag)reader.ReadByte(); - - if (GetSaveFlag(flags, SaveFlag.MaxItems)) - m_MaxItems = reader.ReadEncodedInt(); - else - m_MaxItems = -1; - - if (GetSaveFlag(flags, SaveFlag.GumpID)) - m_GumpID = reader.ReadEncodedInt(); - else - m_GumpID = -1; - - if (GetSaveFlag(flags, SaveFlag.DropSound)) - m_DropSound = reader.ReadEncodedInt(); - else - m_DropSound = -1; - - LiftOverride = GetSaveFlag(flags, SaveFlag.LiftOverride); - - break; - } - case 1: - { - m_MaxItems = reader.ReadInt(); - goto case 0; - } - case 0: - { - if (version < 1) - m_MaxItems = GlobalMaxItems; - - m_GumpID = reader.ReadInt(); - m_DropSound = reader.ReadInt(); - - if (m_GumpID == DefaultGumpID) - m_GumpID = -1; - - if (m_DropSound == DefaultDropSound) - m_DropSound = -1; - - if (m_MaxItems == DefaultMaxItems) - m_MaxItems = -1; - - // m_Bounds = new Rectangle2D( reader.ReadPoint2D(), reader.ReadPoint2D() ); - reader.ReadPoint2D(); - reader.ReadPoint2D(); - - break; - } - } - - UpdateContainerData(); - } - - public override int GetTotal(TotalType type) - { - return type switch - { - TotalType.Gold => m_TotalGold, - TotalType.Items => m_TotalItems, - TotalType.Weight => m_TotalWeight, - _ => base.GetTotal(type) - }; - } - - public override void UpdateTotal(Item sender, TotalType type, int delta) - { - if (sender != this && delta != 0 && !sender.IsVirtualItem) - switch (type) - { - case TotalType.Gold: - m_TotalGold += delta; - break; - - case TotalType.Items: - m_TotalItems += delta; - InvalidateProperties(); - break; - - case TotalType.Weight: - m_TotalWeight += delta; - InvalidateProperties(); - break; - } - - base.UpdateTotal(sender, type, delta); - } - - public override void UpdateTotals() - { - m_TotalGold = 0; - m_TotalItems = 0; - m_TotalWeight = 0; - - var items = m_Items; - - if (items == null) - return; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - item.UpdateTotals(); - - if (item.IsVirtualItem) - continue; - - m_TotalGold += item.TotalGold; - m_TotalItems += item.TotalItems + 1; - m_TotalWeight += item.TotalWeight + item.PileWeight; - } - } - - public virtual bool OnStackAttempt(Mobile from, Item stack, Item dropped) => - CheckHold(from, dropped, true, false) && stack.StackWith(from, dropped); - - public override bool OnDragDrop(Mobile from, Item dropped) - { - if (TryDropItem(from, dropped, true)) - { - from.SendSound(GetDroppedSound(dropped), GetWorldLocation()); - - return true; - } - - return false; - } - - public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage) => - TryDropItem(from, dropped, sendFullMessage, false); - - public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage, bool playSound) - { - var list = Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (!(item is Container) && CheckHold(from, dropped, false, false) && - item.StackWith(from, dropped, playSound)) - return true; - } - - if (CheckHold(from, dropped, sendFullMessage, true)) - { - DropItem(dropped); - return true; - } - - return false; - } - - public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params Item[] droppedItems) - { - var dropItems = new List(); - var stackItems = new List(); - - var extraItems = 0; - var extraWeight = 0; - - for (var i = 0; i < droppedItems.Length; i++) - { - var dropped = droppedItems[i]; - - var list = Items; - - var stacked = false; - - for (var j = 0; j < list.Count; ++j) - { - var item = list[j]; - - if (!(item is Container) && CheckHold(from, dropped, false, false, 0, extraWeight) && - item.CanStackWith(dropped)) - { - stackItems.Add(new ItemStackEntry(item, dropped)); - extraWeight += (int)Math.Ceiling(item.Weight * (item.Amount + dropped.Amount)) - - item.PileWeight; // extra weight delta, do not need TotalWeight as we do not have hybrid stackable container types - stacked = true; - break; - } - } - - if (!stacked && CheckHold(from, dropped, false, true, extraItems, extraWeight)) - { - dropItems.Add(dropped); - extraItems++; - extraWeight += dropped.TotalWeight + dropped.PileWeight; - } - } - - if (dropItems.Count + stackItems.Count == droppedItems.Length) // All good - { - for (var i = 0; i < dropItems.Count; i++) - DropItem(dropItems[i]); - - for (var i = 0; i < stackItems.Count; i++) - stackItems[i].m_StackItem.StackWith(from, stackItems[i].m_DropItem, false); - - return true; - } - - return false; - } - - public virtual void Destroy() - { - var loc = GetWorldLocation(); - var map = Map; - - for (var i = Items.Count - 1; i >= 0; --i) - if (i < Items.Count) - { - Items[i].SetLastMoved(); - Items[i].MoveToWorld(loc, map); - } - - Delete(); - } - - public virtual void DropItem(Item dropped) - { - if (dropped == null) - return; - - AddItem(dropped); - - var bounds = dropped.GetGraphicBounds(); - var ourBounds = Bounds; - - int x, y; - - if (bounds.Width >= ourBounds.Width) - x = (ourBounds.Width - bounds.Width) / 2; - else - x = Utility.Random(ourBounds.Width - bounds.Width); - - if (bounds.Height >= ourBounds.Height) - y = (ourBounds.Height - bounds.Height) / 2; - else - y = Utility.Random(ourBounds.Height - bounds.Height); - - x += ourBounds.X; - x -= bounds.X; - - y += ourBounds.Y; - y -= bounds.Y; - - dropped.Location = new Point3D(x, y, 0); - } - - public override void OnDoubleClickSecureTrade(Mobile from) - { - if (from.InRange(GetWorldLocation(), 2)) - { - DisplayTo(from); - - var trade = GetSecureTradeCont()?.Trade; - - if (trade != null) - { - if (trade.From.Mobile == from) - DisplayTo(trade.To.Mobile); - else if (trade.To.Mobile == from) - DisplayTo(trade.From.Mobile); - } - } - else - { - from.SendLocalizedMessage(500446); // That is too far away. - } - } - - public virtual bool CheckContentDisplay(Mobile from) => - (DisplaysContent && RootParent == null) || - RootParent is Item || RootParent == from || - from.AccessLevel > AccessLevel.Player; - - public override void OnSingleClick(Mobile from) - { - base.OnSingleClick(from); - - if (CheckContentDisplay(from)) - LabelTo(from, "({0} item{2}, {1} stones)", TotalItems, TotalWeight, TotalItems != 1 ? "s" : string.Empty); - // LabelTo( from, 1050044, String.Format( "{0}\t{1}", TotalItems.ToString(), TotalWeight.ToString() ) ); - } - - public override void OnDelete() - { - base.OnDelete(); - - Openers = null; - } - - public virtual void DisplayTo(Mobile to) - { - ProcessOpeners(to); - - var ns = to.NetState; - - if (ns != null) - { - if (ns.HighSeas) - to.Send(new ContainerDisplayHS(Serial, GumpID)); - else - to.Send(new ContainerDisplay(Serial, GumpID)); - - SendContentTo(ns); - - if (ObjectPropertyList.Enabled) - { - var items = Items; - - for (var i = 0; i < items.Count; ++i) - to.Send(items[i].OPLPacket); - } - } - } - - public void ProcessOpeners(Mobile opener) - { - if (IsPublicContainer) - return; - - var contains = false; - - if (Openers != null) - { - var worldLoc = GetWorldLocation(); - var map = Map; - - for (var i = 0; i < Openers.Count; ++i) - { - var mob = Openers[i]; - - if (mob == opener) - { - contains = true; - } - else - { - var range = GetUpdateRange(mob); - - if (mob.Map != map || !mob.InRange(worldLoc, range)) - Openers.RemoveAt(i--); - } - } - } - - if (!contains) - { - Openers ??= new List(); - - Openers.Add(opener); - } - else if (Openers?.Count == 0) - { - Openers = null; - } - } - - public virtual void SendContentTo(NetState state) - { - if (state == null) - return; - - if (state.ContainerGridLines) - state.Send(new ContainerContent6017(state.Mobile, this)); - else - state.Send(new ContainerContent(state.Mobile, this)); - } - - public override void GetProperties(ObjectPropertyList list) - { - base.GetProperties(list); - - if (DisplaysContent) // CheckContentDisplay( from )) - { - if (Core.ML) - { - if (ParentsContain()) // Root Parent is the Mobile. Parent could be another containter. - list.Add(1073841, "{0}\t{1}\t{2}", TotalItems, MaxItems, - TotalWeight); // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones - else - list.Add(1072241, "{0}\t{1}\t{2}\t{3}", TotalItems, MaxItems, TotalWeight, - MaxWeight); // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~/~4_MAXWEIGHT~ stones - - // TODO: Where do the other clilocs come into play? 1073839 & 1073840? - } - else - { - list.Add(1050044, "{0}\t{1}", TotalItems, TotalWeight); // ~1_COUNT~ items, ~2_WEIGHT~ stones - } - } - } - - public override void OnDoubleClick(Mobile from) - { - if (from.AccessLevel > AccessLevel.Player || from.InRange(GetWorldLocation(), 2)) - DisplayTo(from); - else - from.SendLocalizedMessage(500446); // That is too far away. - } - - private class GroupComparer : IComparer - { - private readonly CheckItemGroup m_Grouper; - - public GroupComparer(CheckItemGroup grouper) => m_Grouper = grouper; - - public int Compare(Item a, Item b) => m_Grouper(a, b); - } - - [Flags] - private enum SaveFlag : byte - { - None = 0x00000000, - MaxItems = 0x00000001, - GumpID = 0x00000002, - DropSound = 0x00000004, - LiftOverride = 0x00000008 - } - - private struct ItemStackEntry - { - public readonly Item m_StackItem; - public readonly Item m_DropItem; - - public ItemStackEntry(Item stack, Item drop) - { - m_StackItem = stack; - m_DropItem = drop; - } - } - - public bool ConsumeTotalGrouped(Type type, int amount, bool recurse, OnItemConsumed callback, CheckItemGroup grouper) - { - if (grouper == null) - throw new ArgumentNullException(nameof(grouper)); - - var typedItems = FindItemsByType(type, recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - group.Add(b); - else - break; - - ++idx; - } - - groups.Add(group); - } - - var items = new Item[groups.Count][]; - var totals = new int[groups.Count]; - - var hasEnough = false; - - for (var i = 0; i < groups.Count; ++i) - { - items[i] = groups[i].ToArray(); - - for (var j = 0; j < items[i].Length; ++j) - totals[i] += items[i][j].Amount; - - if (totals[i] >= amount) - hasEnough = true; - } - - if (!hasEnough) - return false; - - for (var i = 0; i < items.Length; ++i) - if (totals[i] >= amount) - { - var need = amount; - - for (var j = 0; j < items[i].Length; ++j) - { - var item = items[i][j]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - - break; - } - - return true; - } - - public int ConsumeTotalGrouped(Type[] types, int[] amounts, bool recurse, OnItemConsumed callback, - CheckItemGroup grouper) - { - if (types.Length != amounts.Length) - throw new ArgumentException("length of types and amounts must match"); - if (grouper == null) - throw new ArgumentNullException(nameof(grouper)); - - var items = new Item[types.Length][][]; - var totals = new int[types.Length][]; - - for (var i = 0; i < types.Length; ++i) - { - var typedItems = FindItemsByType(types[i], recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - group.Add(b); - else - break; - - ++idx; - } - - groups.Add(group); - } - - items[i] = new Item[groups.Count][]; - totals[i] = new int[groups.Count]; - - var hasEnough = false; - - for (var j = 0; j < groups.Count; ++j) - { - items[i][j] = groups[j].ToArray(); - - for (var k = 0; k < items[i][j].Length; ++k) - totals[i][j] += items[i][j][k].Amount; - - if (totals[i][j] >= amounts[i]) - hasEnough = true; - } - - if (!hasEnough) - return i; - } - - for (var i = 0; i < items.Length; ++i) - for (var j = 0; j < items[i].Length; ++j) - if (totals[i][j] >= amounts[i]) - { - var need = amounts[i]; - - for (var k = 0; k < items[i][j].Length; ++k) - { - var item = items[i][j][k]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - - break; - } - - return -1; - } - - public int ConsumeTotalGrouped(Type[][] types, int[] amounts, bool recurse, OnItemConsumed callback, - CheckItemGroup grouper) - { - if (types.Length != amounts.Length) - throw new ArgumentException("length of types and amounts must match"); - if (grouper == null) - throw new ArgumentNullException(nameof(grouper)); - - var items = new Item[types.Length][][]; - var totals = new int[types.Length][]; - - for (var i = 0; i < types.Length; ++i) - { - var typedItems = FindItemsByType(types[i], recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - group.Add(b); - else - break; - - ++idx; - } - - groups.Add(group); - } - - items[i] = new Item[groups.Count][]; - totals[i] = new int[groups.Count]; - - var hasEnough = false; - - for (var j = 0; j < groups.Count; ++j) - { - items[i][j] = groups[j].ToArray(); - - for (var k = 0; k < items[i][j].Length; ++k) - totals[i][j] += items[i][j][k].Amount; - - if (totals[i][j] >= amounts[i]) - hasEnough = true; - } - - if (!hasEnough) - return i; - } - - for (var i = 0; i < items.Length; ++i) - for (var j = 0; j < items[i].Length; ++j) - if (totals[i][j] >= amounts[i]) - { - var need = amounts[i]; - - for (var k = 0; k < items[i][j].Length; ++k) - { - var item = items[i][j][k]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - - break; - } - - return -1; - } - - public int ConsumeTotal(Type[][] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) - { - if (types.Length != amounts.Length) - throw new ArgumentException("length of types and amounts must match"); - - var items = new Item[types.Length][]; - var totals = new int[types.Length]; - - for (var i = 0; i < types.Length; ++i) - { - items[i] = FindItemsByType(types[i], recurse); - - for (var j = 0; j < items[i].Length; ++j) - totals[i] += items[i][j].Amount; - - if (totals[i] < amounts[i]) - return i; - } - - for (var i = 0; i < types.Length; ++i) - { - var need = amounts[i]; - - for (var j = 0; j < items[i].Length; ++j) - { - var item = items[i][j]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - } - - return -1; - } - - public int ConsumeTotal(Type[] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) - { - if (types.Length != amounts.Length) - throw new ArgumentException("length of types and amounts must match"); - - var items = new Item[types.Length][]; - var totals = new int[types.Length]; - - for (var i = 0; i < types.Length; ++i) - { - items[i] = FindItemsByType(types[i], recurse); - - for (var j = 0; j < items[i].Length; ++j) - totals[i] += items[i][j].Amount; - - if (totals[i] < amounts[i]) - return i; - } - - for (var i = 0; i < types.Length; ++i) - { - var need = amounts[i]; - - for (var j = 0; j < items[i].Length; ++j) - { - var item = items[i][j]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - break; - } - } - } - - return -1; - } - - public bool ConsumeTotal(Type type, int amount = 1, bool recurse = true, OnItemConsumed callback = null) - { - var items = FindItemsByType(type, recurse); - - // First pass, compute total - var total = 0; - - for (var i = 0; i < items.Length; ++i) - total += items[i].Amount; - - if (total >= amount) - { - // We've enough, so consume it - - var need = amount; - - for (var i = 0; i < items.Length; ++i) - { - var item = items[i]; - - var theirAmount = item.Amount; - - if (theirAmount < need) - { - callback?.Invoke(item, theirAmount); - - item.Consume(theirAmount); - need -= theirAmount; - } - else - { - callback?.Invoke(item, need); - - item.Consume(need); - - return true; - } - } - } - - return false; - } - - public int ConsumeUpTo(Type type, int amount, bool recurse = true) - { - var consumed = 0; - - var toDelete = new Queue(); - - RecurseConsumeUpTo(this, type, amount, recurse, ref consumed, toDelete); - - while (toDelete.Count > 0) - toDelete.Dequeue().Delete(); - - return consumed; - } - - private static void RecurseConsumeUpTo(Item current, Type type, int amount, bool recurse, ref int consumed, - Queue toDelete) - { - if (current == null || current.Items.Count == 0) - return; - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (type.IsInstanceOfType(item)) - { - var need = amount - consumed; - var theirAmount = item.Amount; - - if (theirAmount <= need) - { - toDelete.Enqueue(item); - consumed += theirAmount; - } - else - { - item.Amount -= need; - consumed += need; - - return; - } - } - else if (recurse && item is Container) - { - RecurseConsumeUpTo(item, type, amount, true, ref consumed, toDelete); - } - } - } - - public int GetBestGroupAmount(Type type, bool recurse, CheckItemGroup grouper) - { - if (grouper == null) - throw new ArgumentNullException(nameof(grouper)); - - var best = 0; - - var typedItems = FindItemsByType(type, recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - group.Add(b); - else - break; - - ++idx; - } - - groups.Add(group); - } - - for (var i = 0; i < groups.Count; ++i) - { - var items = groups[i].ToArray(); - - var total = 0; - - for (var j = 0; j < items.Length; ++j) - total += items[j].Amount; - - if (total >= best) - best = total; - } - - return best; - } - - public int GetBestGroupAmount(Type[] types, bool recurse, CheckItemGroup grouper) - { - if (grouper == null) - throw new ArgumentNullException(nameof(grouper)); - - var best = 0; - - var typedItems = FindItemsByType(types, recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - group.Add(b); - else - break; - - ++idx; - } - - groups.Add(group); - } - - for (var j = 0; j < groups.Count; ++j) - { - var items = groups[j].ToArray(); - var total = items.Sum(t => t.Amount); - - if (total >= best) - best = total; - } - - return best; - } - - public int GetBestGroupAmount(Type[][] types, bool recurse, CheckItemGroup grouper) - { - if (grouper == null) - throw new ArgumentNullException(nameof(grouper)); - - var best = 0; - - for (var i = 0; i < types.Length; ++i) - { - var typedItems = FindItemsByType(types[i], recurse); - - var groups = new List>(); - var idx = 0; - - while (idx < typedItems.Length) - { - var a = typedItems[idx++]; - var group = new List(); - - group.Add(a); - - while (idx < typedItems.Length) - { - var b = typedItems[idx]; - var v = grouper(a, b); - - if (v == 0) - group.Add(b); - else - break; - - ++idx; - } - - groups.Add(group); - } - - for (var j = 0; j < groups.Count; ++j) - { - var items = groups[j].ToArray(); - var total = 0; - - for (var k = 0; k < items.Length; ++k) - total += items[k].Amount; - - if (total >= best) - best = total; - } - } - - return best; - } - - public int GetAmount(Type type, bool recurse = true) => FindItemsByType(type, recurse).Sum(t => t.Amount); - - public int GetAmount(Type[] types, bool recurse = true) => FindItemsByType(types, recurse).Sum(t => t.Amount); - - public Item[] FindItemsByType(Type type, bool recurse = true) - { - if (m_FindItemsList.Count > 0) - m_FindItemsList.Clear(); - - RecurseFindItemsByType(this, type, recurse, m_FindItemsList); - - return m_FindItemsList.ToArray(); - } - - private static void RecurseFindItemsByType(Item current, Type type, bool recurse, List list) - { - if (current == null || current.Items.Count == 0) - return; - - var items = current.Items; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - if (type.IsInstanceOfType(item)) - list.Add(item); - - if (recurse && item is Container) - RecurseFindItemsByType(item, type, true, list); - } - } - - public Item[] FindItemsByType(Type[] types, bool recurse = true) - { - if (m_FindItemsList.Count > 0) - m_FindItemsList.Clear(); - - RecurseFindItemsByType(this, types, recurse, m_FindItemsList); - - return m_FindItemsList.ToArray(); - } - - private static void RecurseFindItemsByType(Item current, Type[] types, bool recurse, List list) - { - if (current == null || current.Items.Count == 0) - return; - - var items = current.Items; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - if (InTypeList(item, types)) - list.Add(item); - - if (recurse && item is Container) - RecurseFindItemsByType(item, types, true, list); - } - } - - public Item FindItemByType(Type type, bool recurse = true) => RecurseFindItemByType(this, type, recurse); - - private static Item RecurseFindItemByType(Item current, Type type, bool recurse) - { - if (current == null || current.Items.Count == 0) - return null; - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (type.IsInstanceOfType(item)) - return item; - - if (recurse && item is Container) - { - var check = RecurseFindItemByType(item, type, true); - - if (check != null) - return check; - } - } - - return null; - } - - public Item FindItemByType(Type[] types, bool recurse = true) => RecurseFindItemByType(this, types, recurse); - - private static Item RecurseFindItemByType(Item current, Type[] types, bool recurse) - { - if (current == null || current.Items.Count == 0) - return null; - - var list = current.Items; - - for (var i = 0; i < list.Count; ++i) - { - var item = list[i]; - - if (InTypeList(item, types)) return item; - - if (recurse && item is Container) - { - var check = RecurseFindItemByType(item, types, true); - - if (check != null) - return check; - } - } - - return null; - } - - public List FindItemsByType(Predicate predicate) where T : Item => FindItemsByType(true, predicate); - - /// - /// Performs a Breadth-First search through all the s and - /// nested s within this . - /// - /// Type of objects being searched for - /// Optional: If true, the search will recursively - /// check any nested s; otherwise, nested - /// s will not be searched. - /// Optional: A predicate to check if the - /// of type is one of the targets of the search. - /// A list of s of type that matche the optional . - public List FindItemsByType(bool recurse = true, Predicate predicate = null) where T : Item - { - using (var queue = m_QueuePool.Get()) - { - queue.Enqueue(this); - var items = new List(); - while (queue.Count > 0) - { - var container = queue.Dequeue(); - foreach (var item in container.Items) - if (item is T typedItem && predicate?.Invoke(typedItem) != false) - items.Add(typedItem); - else if (recurse && item is Container itemContainer) - queue.Enqueue(itemContainer); - } - - return items; - } - } - - /// - /// Performs a Breadth-First search through all the s and - /// nested s within this . - /// - /// Type of object being searched for - /// Optional: If true, the search will recursively - /// check any nested s; otherwise, nested - /// s will not be searched. - /// Optional: A predicate to check if the - /// of type is the target of the search. - /// The first of type that matches the optional . - public T FindItemByType(bool recurse = true, Predicate predicate = null) where T : Item - { - using (var queue = m_QueuePool.Get()) - { - queue.Enqueue(this); - while (queue.Count > 0) - { - var container = queue.Dequeue(); - foreach (var item in container.Items) - { - if (item is T typedItem && predicate?.Invoke(typedItem) != false) - return typedItem; - if (recurse && item is Container itemContainer) - queue.Enqueue(itemContainer); - } - } - - return null; - } - } - } - - public class ContainerData - { - private static readonly Dictionary m_Table; - - static ContainerData() - { - m_Table = new Dictionary(); - - var path = Path.Combine(Core.BaseDirectory, "Data/containers.cfg"); - - if (!File.Exists(path)) - { - Default = new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); - return; - } - - using (var reader = new StreamReader(path)) - { - string line; - - while ((line = reader.ReadLine()) != null) - { - line = line.Trim(); - - if (line.Length == 0 || line.StartsWith("#")) - continue; - - try - { - var split = line.Split('\t'); - - if (split.Length >= 3) - { - var gumpID = Utility.ToInt32(split[0]); - - var aRect = split[1].Split(' '); - if (aRect.Length < 4) - continue; - - var x = Utility.ToInt32(aRect[0]); - var y = Utility.ToInt32(aRect[1]); - var width = Utility.ToInt32(aRect[2]); - var height = Utility.ToInt32(aRect[3]); - - var bounds = new Rectangle2D(x, y, width, height); - - var dropSound = Utility.ToInt32(split[2]); - - var data = new ContainerData(gumpID, bounds, dropSound); - - Default ??= data; - - if (split.Length >= 4) - { - var aIDs = split[3].Split(','); - - for (var i = 0; i < aIDs.Length; i++) - { - var id = Utility.ToInt32(aIDs[i]); - - if (m_Table.ContainsKey(id)) - Console.WriteLine(@"Warning: double ItemID entry in Data\containers.cfg"); - else - m_Table[id] = data; - } - } - } - } - catch - { - // ignored - } - } - } - - Default ??= new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); - } - - public ContainerData(int gumpID, Rectangle2D bounds, int dropSound) - { - GumpID = gumpID; - Bounds = bounds; - DropSound = dropSound; - } - - public static ContainerData Default { get; set; } - - public int GumpID { get; } - - public Rectangle2D Bounds { get; } - - public int DropSound { get; } - - public static ContainerData GetData(int itemID) - { - m_Table.TryGetValue(itemID, out var data); - return data ?? Default; - } - } -} +/*************************************************************************** + * Container.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Server.Network; +using Server.Utilities; +using QueuePool = Server.Utilities.RefPool>; + +namespace Server.Items +{ + public delegate void OnItemConsumed(Item item, int amount); + + public delegate int CheckItemGroup(Item a, Item b); + + public delegate void ContainerSnoopHandler(Container cont, Mobile from); + + public class Container : Item + { + private static readonly QueuePool m_QueuePool = new QueuePool(QueueRef.Generate, 2, 5); + private static readonly List m_FindItemsList = new List(); + + private ContainerData m_ContainerData; + + private int m_DropSound; + private int m_GumpID; + + internal List m_Items; + private int m_MaxItems; + private int m_TotalGold; + + private int m_TotalItems; + private int m_TotalWeight; + + public Container(int itemID) : base(itemID) + { + m_GumpID = -1; + m_DropSound = -1; + m_MaxItems = -1; + + UpdateContainerData(); + } + + public Container(Serial serial) : base(serial) + { + } + + public static ContainerSnoopHandler SnoopHandler { get; set; } + + public ContainerData ContainerData + { + get + { + if (m_ContainerData == null) + UpdateContainerData(); + + return m_ContainerData; + } + set => m_ContainerData = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public override int ItemID + { + get => base.ItemID; + set + { + var oldID = ItemID; + + base.ItemID = value; + + if (ItemID != oldID) + UpdateContainerData(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int GumpID + { + get => m_GumpID == -1 ? DefaultGumpID : m_GumpID; + set => m_GumpID = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int DropSound + { + get => m_DropSound == -1 ? DefaultDropSound : m_DropSound; + set => m_DropSound = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int MaxItems + { + get => m_MaxItems == -1 ? DefaultMaxItems : m_MaxItems; + set + { + m_MaxItems = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int MaxWeight + { + get + { + if (Parent is Container container && container.MaxWeight == 0) return 0; + + return DefaultMaxWeight; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool LiftOverride { get; set; } + + public virtual Rectangle2D Bounds => ContainerData.Bounds; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int DefaultGumpID => ContainerData.GumpID; + + [CommandProperty(AccessLevel.GameMaster)] + public virtual int DefaultDropSound => ContainerData.DropSound; + + public virtual int DefaultMaxItems => GlobalMaxItems; + public virtual int DefaultMaxWeight => GlobalMaxWeight; + + public virtual bool IsDecoContainer => !Movable && !IsLockedDown && !IsSecure && Parent == null && !LiftOverride; + + public static int GlobalMaxItems { get; set; } = 125; + + public static int GlobalMaxWeight { get; set; } = 400; + + public virtual bool DisplaysContent => true; + + public List Openers { get; set; } + + public virtual bool IsPublicContainer => false; + + public virtual void UpdateContainerData() + { + ContainerData = ContainerData.GetData(ItemID); + } + + public virtual int GetDroppedSound(Item item) + { + var dropSound = item.GetDropSound(); + + return dropSound != -1 ? dropSound : DropSound; + } + + public override void OnSnoop(Mobile from) + { + SnoopHandler?.Invoke(this, from); + } + + public override bool CheckLift(Mobile from, Item item, ref LRReason reject) + { + if (from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) + { + reject = LRReason.CannotLift; + return false; + } + + return base.CheckLift(from, item, ref reject); + } + + public override bool CheckItemUse(Mobile from, Item item) + { + if (item != this && from.AccessLevel < AccessLevel.GameMaster && IsDecoContainer) + { + from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that. + return false; + } + + return base.CheckItemUse(from, item); + } + + public bool CheckHold(Mobile m, Item item, bool message) => CheckHold(m, item, message, true, 0, 0); + + public bool CheckHold(Mobile m, Item item, bool message, bool checkItems) => + CheckHold(m, item, message, checkItems, 0, 0); + + public virtual bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) + { + if (m == null || m.AccessLevel < AccessLevel.GameMaster) + { + if (IsDecoContainer) + { + if (message) + SendCantStoreMessage(m, item); + + return false; + } + + var maxItems = MaxItems; + + if (checkItems && maxItems != 0 && + TotalItems + plusItems + item.TotalItems + (item.IsVirtualItem ? 0 : 1) > maxItems) + { + if (message) + SendFullItemsMessage(m, item); + + return false; + } + + if (MaxWeight != 0 && TotalWeight + plusWeight + item.TotalWeight + item.PileWeight > MaxWeight) + { + if (message) + SendFullWeightMessage(m, item); + + return false; + } + } + + var parent = Parent; + + while (parent != null) + { + if (parent is Container container) + return container.CheckHold(m, item, message, checkItems, plusItems, plusWeight); + + if (!(parent is Item parentItem)) + break; + + parent = parentItem.Parent; + } + + return true; + } + + public virtual void SendFullItemsMessage(Mobile to, Item item) + { + to.SendMessage("That container cannot hold more items."); + } + + public virtual void SendFullWeightMessage(Mobile to, Item item) + { + to.SendMessage("That container cannot hold more weight."); + } + + public virtual void SendCantStoreMessage(Mobile to, Item item) + { + to.SendLocalizedMessage(500176); // That is not your container, you can't store things here. + } + + public virtual bool OnDragDropInto(Mobile from, Item item, Point3D p) + { + if (!CheckHold(from, item, true, true)) + return false; + + item.Location = new Point3D(p.m_X, p.m_Y, 0); + AddItem(item); + + from.SendSound(GetDroppedSound(item), GetWorldLocation()); + + return true; + } + + private static bool InTypeList(Item item, Type[] types) + { + var t = item.GetType(); + + for (var i = 0; i < types.Length; ++i) + if (types[i].IsAssignableFrom(t)) + return true; + + return false; + } + + private static void SetSaveFlag(ref SaveFlag flags, SaveFlag toSet, bool setIf) + { + if (setIf) + flags |= toSet; + } + + private static bool GetSaveFlag(SaveFlag flags, SaveFlag toGet) => (flags & toGet) != 0; + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(2); // version + + var flags = SaveFlag.None; + + SetSaveFlag(ref flags, SaveFlag.MaxItems, m_MaxItems != -1); + SetSaveFlag(ref flags, SaveFlag.GumpID, m_GumpID != -1); + SetSaveFlag(ref flags, SaveFlag.DropSound, m_DropSound != -1); + SetSaveFlag(ref flags, SaveFlag.LiftOverride, LiftOverride); + + writer.Write((byte)flags); + + if (GetSaveFlag(flags, SaveFlag.MaxItems)) + writer.WriteEncodedInt(m_MaxItems); + + if (GetSaveFlag(flags, SaveFlag.GumpID)) + writer.WriteEncodedInt(m_GumpID); + + if (GetSaveFlag(flags, SaveFlag.DropSound)) + writer.WriteEncodedInt(m_DropSound); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + switch (version) + { + case 2: + { + var flags = (SaveFlag)reader.ReadByte(); + + if (GetSaveFlag(flags, SaveFlag.MaxItems)) + m_MaxItems = reader.ReadEncodedInt(); + else + m_MaxItems = -1; + + if (GetSaveFlag(flags, SaveFlag.GumpID)) + m_GumpID = reader.ReadEncodedInt(); + else + m_GumpID = -1; + + if (GetSaveFlag(flags, SaveFlag.DropSound)) + m_DropSound = reader.ReadEncodedInt(); + else + m_DropSound = -1; + + LiftOverride = GetSaveFlag(flags, SaveFlag.LiftOverride); + + break; + } + case 1: + { + m_MaxItems = reader.ReadInt(); + goto case 0; + } + case 0: + { + if (version < 1) + m_MaxItems = GlobalMaxItems; + + m_GumpID = reader.ReadInt(); + m_DropSound = reader.ReadInt(); + + if (m_GumpID == DefaultGumpID) + m_GumpID = -1; + + if (m_DropSound == DefaultDropSound) + m_DropSound = -1; + + if (m_MaxItems == DefaultMaxItems) + m_MaxItems = -1; + + // m_Bounds = new Rectangle2D( reader.ReadPoint2D(), reader.ReadPoint2D() ); + reader.ReadPoint2D(); + reader.ReadPoint2D(); + + break; + } + } + + UpdateContainerData(); + } + + public override int GetTotal(TotalType type) + { + return type switch + { + TotalType.Gold => m_TotalGold, + TotalType.Items => m_TotalItems, + TotalType.Weight => m_TotalWeight, + _ => base.GetTotal(type) + }; + } + + public override void UpdateTotal(Item sender, TotalType type, int delta) + { + if (sender != this && delta != 0 && !sender.IsVirtualItem) + switch (type) + { + case TotalType.Gold: + m_TotalGold += delta; + break; + + case TotalType.Items: + m_TotalItems += delta; + InvalidateProperties(); + break; + + case TotalType.Weight: + m_TotalWeight += delta; + InvalidateProperties(); + break; + } + + base.UpdateTotal(sender, type, delta); + } + + public override void UpdateTotals() + { + m_TotalGold = 0; + m_TotalItems = 0; + m_TotalWeight = 0; + + var items = m_Items; + + if (items == null) + return; + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + + item.UpdateTotals(); + + if (item.IsVirtualItem) + continue; + + m_TotalGold += item.TotalGold; + m_TotalItems += item.TotalItems + 1; + m_TotalWeight += item.TotalWeight + item.PileWeight; + } + } + + public virtual bool OnStackAttempt(Mobile from, Item stack, Item dropped) => + CheckHold(from, dropped, true, false) && stack.StackWith(from, dropped); + + public override bool OnDragDrop(Mobile from, Item dropped) + { + if (TryDropItem(from, dropped, true)) + { + from.SendSound(GetDroppedSound(dropped), GetWorldLocation()); + + return true; + } + + return false; + } + + public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage) => + TryDropItem(from, dropped, sendFullMessage, false); + + public virtual bool TryDropItem(Mobile from, Item dropped, bool sendFullMessage, bool playSound) + { + var list = Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (!(item is Container) && CheckHold(from, dropped, false, false) && + item.StackWith(from, dropped, playSound)) + return true; + } + + if (CheckHold(from, dropped, sendFullMessage, true)) + { + DropItem(dropped); + return true; + } + + return false; + } + + public virtual bool TryDropItems(Mobile from, bool sendFullMessage, params Item[] droppedItems) + { + var dropItems = new List(); + var stackItems = new List(); + + var extraItems = 0; + var extraWeight = 0; + + for (var i = 0; i < droppedItems.Length; i++) + { + var dropped = droppedItems[i]; + + var list = Items; + + var stacked = false; + + for (var j = 0; j < list.Count; ++j) + { + var item = list[j]; + + if (!(item is Container) && CheckHold(from, dropped, false, false, 0, extraWeight) && + item.CanStackWith(dropped)) + { + stackItems.Add(new ItemStackEntry(item, dropped)); + extraWeight += (int)Math.Ceiling(item.Weight * (item.Amount + dropped.Amount)) - + item.PileWeight; // extra weight delta, do not need TotalWeight as we do not have hybrid stackable container types + stacked = true; + break; + } + } + + if (!stacked && CheckHold(from, dropped, false, true, extraItems, extraWeight)) + { + dropItems.Add(dropped); + extraItems++; + extraWeight += dropped.TotalWeight + dropped.PileWeight; + } + } + + if (dropItems.Count + stackItems.Count == droppedItems.Length) // All good + { + for (var i = 0; i < dropItems.Count; i++) + DropItem(dropItems[i]); + + for (var i = 0; i < stackItems.Count; i++) + stackItems[i].m_StackItem.StackWith(from, stackItems[i].m_DropItem, false); + + return true; + } + + return false; + } + + public virtual void Destroy() + { + var loc = GetWorldLocation(); + var map = Map; + + for (var i = Items.Count - 1; i >= 0; --i) + if (i < Items.Count) + { + Items[i].SetLastMoved(); + Items[i].MoveToWorld(loc, map); + } + + Delete(); + } + + public virtual void DropItem(Item dropped) + { + if (dropped == null) + return; + + AddItem(dropped); + + var bounds = dropped.GetGraphicBounds(); + var ourBounds = Bounds; + + int x, y; + + if (bounds.Width >= ourBounds.Width) + x = (ourBounds.Width - bounds.Width) / 2; + else + x = Utility.Random(ourBounds.Width - bounds.Width); + + if (bounds.Height >= ourBounds.Height) + y = (ourBounds.Height - bounds.Height) / 2; + else + y = Utility.Random(ourBounds.Height - bounds.Height); + + x += ourBounds.X; + x -= bounds.X; + + y += ourBounds.Y; + y -= bounds.Y; + + dropped.Location = new Point3D(x, y, 0); + } + + public override void OnDoubleClickSecureTrade(Mobile from) + { + if (from.InRange(GetWorldLocation(), 2)) + { + DisplayTo(from); + + var trade = GetSecureTradeCont()?.Trade; + + if (trade != null) + { + if (trade.From.Mobile == from) + DisplayTo(trade.To.Mobile); + else if (trade.To.Mobile == from) + DisplayTo(trade.From.Mobile); + } + } + else + { + from.SendLocalizedMessage(500446); // That is too far away. + } + } + + public virtual bool CheckContentDisplay(Mobile from) => + DisplaysContent && RootParent == null || + RootParent is Item || RootParent == from || + from.AccessLevel > AccessLevel.Player; + + public override void OnSingleClick(Mobile from) + { + base.OnSingleClick(from); + + if (CheckContentDisplay(from)) + LabelTo(from, "({0} item{2}, {1} stones)", TotalItems, TotalWeight, TotalItems != 1 ? "s" : string.Empty); + // LabelTo( from, 1050044, String.Format( "{0}\t{1}", TotalItems.ToString(), TotalWeight.ToString() ) ); + } + + public override void OnDelete() + { + base.OnDelete(); + + Openers = null; + } + + public virtual void DisplayTo(Mobile to) + { + ProcessOpeners(to); + + var ns = to.NetState; + + if (ns != null) + { + if (ns.HighSeas) + to.Send(new ContainerDisplayHS(Serial, GumpID)); + else + to.Send(new ContainerDisplay(Serial, GumpID)); + + SendContentTo(ns); + + if (ObjectPropertyList.Enabled) + { + var items = Items; + + for (var i = 0; i < items.Count; ++i) + to.Send(items[i].OPLPacket); + } + } + } + + public void ProcessOpeners(Mobile opener) + { + if (IsPublicContainer) + return; + + var contains = false; + + if (Openers != null) + { + var worldLoc = GetWorldLocation(); + var map = Map; + + for (var i = 0; i < Openers.Count; ++i) + { + var mob = Openers[i]; + + if (mob == opener) + { + contains = true; + } + else + { + var range = GetUpdateRange(mob); + + if (mob.Map != map || !mob.InRange(worldLoc, range)) + Openers.RemoveAt(i--); + } + } + } + + if (!contains) + { + Openers ??= new List(); + + Openers.Add(opener); + } + else if (Openers?.Count == 0) + { + Openers = null; + } + } + + public virtual void SendContentTo(NetState state) + { + if (state == null) + return; + + if (state.ContainerGridLines) + state.Send(new ContainerContent6017(state.Mobile, this)); + else + state.Send(new ContainerContent(state.Mobile, this)); + } + + public override void GetProperties(ObjectPropertyList list) + { + base.GetProperties(list); + + if (DisplaysContent) // CheckContentDisplay( from )) + { + if (Core.ML) + { + if (ParentsContain()) // Root Parent is the Mobile. Parent could be another containter. + list.Add( + 1073841, + "{0}\t{1}\t{2}", + TotalItems, + MaxItems, + TotalWeight + ); // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~ stones + else + list.Add( + 1072241, + "{0}\t{1}\t{2}\t{3}", + TotalItems, + MaxItems, + TotalWeight, + MaxWeight + ); // Contents: ~1_COUNT~/~2_MAXCOUNT~ items, ~3_WEIGHT~/~4_MAXWEIGHT~ stones + + // TODO: Where do the other clilocs come into play? 1073839 & 1073840? + } + else + { + list.Add(1050044, "{0}\t{1}", TotalItems, TotalWeight); // ~1_COUNT~ items, ~2_WEIGHT~ stones + } + } + } + + public override void OnDoubleClick(Mobile from) + { + if (from.AccessLevel > AccessLevel.Player || from.InRange(GetWorldLocation(), 2)) + DisplayTo(from); + else + from.SendLocalizedMessage(500446); // That is too far away. + } + + public bool ConsumeTotalGrouped(Type type, int amount, bool recurse, OnItemConsumed callback, CheckItemGroup grouper) + { + if (grouper == null) + throw new ArgumentNullException(nameof(grouper)); + + var typedItems = FindItemsByType(type, recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + group.Add(b); + else + break; + + ++idx; + } + + groups.Add(group); + } + + var items = new Item[groups.Count][]; + var totals = new int[groups.Count]; + + var hasEnough = false; + + for (var i = 0; i < groups.Count; ++i) + { + items[i] = groups[i].ToArray(); + + for (var j = 0; j < items[i].Length; ++j) + totals[i] += items[i][j].Amount; + + if (totals[i] >= amount) + hasEnough = true; + } + + if (!hasEnough) + return false; + + for (var i = 0; i < items.Length; ++i) + if (totals[i] >= amount) + { + var need = amount; + + for (var j = 0; j < items[i].Length; ++j) + { + var item = items[i][j]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + + break; + } + + return true; + } + + public int ConsumeTotalGrouped( + Type[] types, int[] amounts, bool recurse, OnItemConsumed callback, + CheckItemGroup grouper + ) + { + if (types.Length != amounts.Length) + throw new ArgumentException("length of types and amounts must match"); + if (grouper == null) + throw new ArgumentNullException(nameof(grouper)); + + var items = new Item[types.Length][][]; + var totals = new int[types.Length][]; + + for (var i = 0; i < types.Length; ++i) + { + var typedItems = FindItemsByType(types[i], recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + group.Add(b); + else + break; + + ++idx; + } + + groups.Add(group); + } + + items[i] = new Item[groups.Count][]; + totals[i] = new int[groups.Count]; + + var hasEnough = false; + + for (var j = 0; j < groups.Count; ++j) + { + items[i][j] = groups[j].ToArray(); + + for (var k = 0; k < items[i][j].Length; ++k) + totals[i][j] += items[i][j][k].Amount; + + if (totals[i][j] >= amounts[i]) + hasEnough = true; + } + + if (!hasEnough) + return i; + } + + for (var i = 0; i < items.Length; ++i) + for (var j = 0; j < items[i].Length; ++j) + if (totals[i][j] >= amounts[i]) + { + var need = amounts[i]; + + for (var k = 0; k < items[i][j].Length; ++k) + { + var item = items[i][j][k]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + + break; + } + + return -1; + } + + public int ConsumeTotalGrouped( + Type[][] types, int[] amounts, bool recurse, OnItemConsumed callback, + CheckItemGroup grouper + ) + { + if (types.Length != amounts.Length) + throw new ArgumentException("length of types and amounts must match"); + if (grouper == null) + throw new ArgumentNullException(nameof(grouper)); + + var items = new Item[types.Length][][]; + var totals = new int[types.Length][]; + + for (var i = 0; i < types.Length; ++i) + { + var typedItems = FindItemsByType(types[i], recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + group.Add(b); + else + break; + + ++idx; + } + + groups.Add(group); + } + + items[i] = new Item[groups.Count][]; + totals[i] = new int[groups.Count]; + + var hasEnough = false; + + for (var j = 0; j < groups.Count; ++j) + { + items[i][j] = groups[j].ToArray(); + + for (var k = 0; k < items[i][j].Length; ++k) + totals[i][j] += items[i][j][k].Amount; + + if (totals[i][j] >= amounts[i]) + hasEnough = true; + } + + if (!hasEnough) + return i; + } + + for (var i = 0; i < items.Length; ++i) + for (var j = 0; j < items[i].Length; ++j) + if (totals[i][j] >= amounts[i]) + { + var need = amounts[i]; + + for (var k = 0; k < items[i][j].Length; ++k) + { + var item = items[i][j][k]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + + break; + } + + return -1; + } + + public int ConsumeTotal(Type[][] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) + { + if (types.Length != amounts.Length) + throw new ArgumentException("length of types and amounts must match"); + + var items = new Item[types.Length][]; + var totals = new int[types.Length]; + + for (var i = 0; i < types.Length; ++i) + { + items[i] = FindItemsByType(types[i], recurse); + + for (var j = 0; j < items[i].Length; ++j) + totals[i] += items[i][j].Amount; + + if (totals[i] < amounts[i]) + return i; + } + + for (var i = 0; i < types.Length; ++i) + { + var need = amounts[i]; + + for (var j = 0; j < items[i].Length; ++j) + { + var item = items[i][j]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + } + + return -1; + } + + public int ConsumeTotal(Type[] types, int[] amounts, bool recurse = true, OnItemConsumed callback = null) + { + if (types.Length != amounts.Length) + throw new ArgumentException("length of types and amounts must match"); + + var items = new Item[types.Length][]; + var totals = new int[types.Length]; + + for (var i = 0; i < types.Length; ++i) + { + items[i] = FindItemsByType(types[i], recurse); + + for (var j = 0; j < items[i].Length; ++j) + totals[i] += items[i][j].Amount; + + if (totals[i] < amounts[i]) + return i; + } + + for (var i = 0; i < types.Length; ++i) + { + var need = amounts[i]; + + for (var j = 0; j < items[i].Length; ++j) + { + var item = items[i][j]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + break; + } + } + } + + return -1; + } + + public bool ConsumeTotal(Type type, int amount = 1, bool recurse = true, OnItemConsumed callback = null) + { + var items = FindItemsByType(type, recurse); + + // First pass, compute total + var total = 0; + + for (var i = 0; i < items.Length; ++i) + total += items[i].Amount; + + if (total >= amount) + { + // We've enough, so consume it + + var need = amount; + + for (var i = 0; i < items.Length; ++i) + { + var item = items[i]; + + var theirAmount = item.Amount; + + if (theirAmount < need) + { + callback?.Invoke(item, theirAmount); + + item.Consume(theirAmount); + need -= theirAmount; + } + else + { + callback?.Invoke(item, need); + + item.Consume(need); + + return true; + } + } + } + + return false; + } + + public int ConsumeUpTo(Type type, int amount, bool recurse = true) + { + var consumed = 0; + + var toDelete = new Queue(); + + RecurseConsumeUpTo(this, type, amount, recurse, ref consumed, toDelete); + + while (toDelete.Count > 0) + toDelete.Dequeue().Delete(); + + return consumed; + } + + private static void RecurseConsumeUpTo( + Item current, Type type, int amount, bool recurse, ref int consumed, + Queue toDelete + ) + { + if (current == null || current.Items.Count == 0) + return; + + var list = current.Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (type.IsInstanceOfType(item)) + { + var need = amount - consumed; + var theirAmount = item.Amount; + + if (theirAmount <= need) + { + toDelete.Enqueue(item); + consumed += theirAmount; + } + else + { + item.Amount -= need; + consumed += need; + + return; + } + } + else if (recurse && item is Container) + { + RecurseConsumeUpTo(item, type, amount, true, ref consumed, toDelete); + } + } + } + + public int GetBestGroupAmount(Type type, bool recurse, CheckItemGroup grouper) + { + if (grouper == null) + throw new ArgumentNullException(nameof(grouper)); + + var best = 0; + + var typedItems = FindItemsByType(type, recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + group.Add(b); + else + break; + + ++idx; + } + + groups.Add(group); + } + + for (var i = 0; i < groups.Count; ++i) + { + var items = groups[i].ToArray(); + + var total = 0; + + for (var j = 0; j < items.Length; ++j) + total += items[j].Amount; + + if (total >= best) + best = total; + } + + return best; + } + + public int GetBestGroupAmount(Type[] types, bool recurse, CheckItemGroup grouper) + { + if (grouper == null) + throw new ArgumentNullException(nameof(grouper)); + + var best = 0; + + var typedItems = FindItemsByType(types, recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + group.Add(b); + else + break; + + ++idx; + } + + groups.Add(group); + } + + for (var j = 0; j < groups.Count; ++j) + { + var items = groups[j].ToArray(); + var total = items.Sum(t => t.Amount); + + if (total >= best) + best = total; + } + + return best; + } + + public int GetBestGroupAmount(Type[][] types, bool recurse, CheckItemGroup grouper) + { + if (grouper == null) + throw new ArgumentNullException(nameof(grouper)); + + var best = 0; + + for (var i = 0; i < types.Length; ++i) + { + var typedItems = FindItemsByType(types[i], recurse); + + var groups = new List>(); + var idx = 0; + + while (idx < typedItems.Length) + { + var a = typedItems[idx++]; + var group = new List(); + + group.Add(a); + + while (idx < typedItems.Length) + { + var b = typedItems[idx]; + var v = grouper(a, b); + + if (v == 0) + group.Add(b); + else + break; + + ++idx; + } + + groups.Add(group); + } + + for (var j = 0; j < groups.Count; ++j) + { + var items = groups[j].ToArray(); + var total = 0; + + for (var k = 0; k < items.Length; ++k) + total += items[k].Amount; + + if (total >= best) + best = total; + } + } + + return best; + } + + public int GetAmount(Type type, bool recurse = true) => FindItemsByType(type, recurse).Sum(t => t.Amount); + + public int GetAmount(Type[] types, bool recurse = true) => FindItemsByType(types, recurse).Sum(t => t.Amount); + + public Item[] FindItemsByType(Type type, bool recurse = true) + { + if (m_FindItemsList.Count > 0) + m_FindItemsList.Clear(); + + RecurseFindItemsByType(this, type, recurse, m_FindItemsList); + + return m_FindItemsList.ToArray(); + } + + private static void RecurseFindItemsByType(Item current, Type type, bool recurse, List list) + { + if (current == null || current.Items.Count == 0) + return; + + var items = current.Items; + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + + if (type.IsInstanceOfType(item)) + list.Add(item); + + if (recurse && item is Container) + RecurseFindItemsByType(item, type, true, list); + } + } + + public Item[] FindItemsByType(Type[] types, bool recurse = true) + { + if (m_FindItemsList.Count > 0) + m_FindItemsList.Clear(); + + RecurseFindItemsByType(this, types, recurse, m_FindItemsList); + + return m_FindItemsList.ToArray(); + } + + private static void RecurseFindItemsByType(Item current, Type[] types, bool recurse, List list) + { + if (current == null || current.Items.Count == 0) + return; + + var items = current.Items; + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + + if (InTypeList(item, types)) + list.Add(item); + + if (recurse && item is Container) + RecurseFindItemsByType(item, types, true, list); + } + } + + public Item FindItemByType(Type type, bool recurse = true) => RecurseFindItemByType(this, type, recurse); + + private static Item RecurseFindItemByType(Item current, Type type, bool recurse) + { + if (current == null || current.Items.Count == 0) + return null; + + var list = current.Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (type.IsInstanceOfType(item)) + return item; + + if (recurse && item is Container) + { + var check = RecurseFindItemByType(item, type, true); + + if (check != null) + return check; + } + } + + return null; + } + + public Item FindItemByType(Type[] types, bool recurse = true) => RecurseFindItemByType(this, types, recurse); + + private static Item RecurseFindItemByType(Item current, Type[] types, bool recurse) + { + if (current == null || current.Items.Count == 0) + return null; + + var list = current.Items; + + for (var i = 0; i < list.Count; ++i) + { + var item = list[i]; + + if (InTypeList(item, types)) return item; + + if (recurse && item is Container) + { + var check = RecurseFindItemByType(item, types, true); + + if (check != null) + return check; + } + } + + return null; + } + + public List FindItemsByType(Predicate predicate) where T : Item => FindItemsByType(true, predicate); + + /// + /// Performs a Breadth-First search through all the s and + /// nested s within this . + /// + /// Type of objects being searched for + /// + /// Optional: If true, the search will recursively + /// check any nested s; otherwise, nested + /// s will not be searched. + /// + /// + /// Optional: A predicate to check if the + /// of type is one of the targets of the search. + /// + /// + /// A list of s of type that matche the optional + /// . + /// + public List FindItemsByType(bool recurse = true, Predicate predicate = null) where T : Item + { + using (var queue = m_QueuePool.Get()) + { + queue.Enqueue(this); + var items = new List(); + while (queue.Count > 0) + { + var container = queue.Dequeue(); + foreach (var item in container.Items) + if (item is T typedItem && predicate?.Invoke(typedItem) != false) + items.Add(typedItem); + else if (recurse && item is Container itemContainer) + queue.Enqueue(itemContainer); + } + + return items; + } + } + + /// + /// Performs a Breadth-First search through all the s and + /// nested s within this . + /// + /// Type of object being searched for + /// + /// Optional: If true, the search will recursively + /// check any nested s; otherwise, nested + /// s will not be searched. + /// + /// + /// Optional: A predicate to check if the + /// of type is the target of the search. + /// + /// + /// The first of type that matches the optional + /// . + /// + public T FindItemByType(bool recurse = true, Predicate predicate = null) where T : Item + { + using (var queue = m_QueuePool.Get()) + { + queue.Enqueue(this); + while (queue.Count > 0) + { + var container = queue.Dequeue(); + foreach (var item in container.Items) + { + if (item is T typedItem && predicate?.Invoke(typedItem) != false) + return typedItem; + if (recurse && item is Container itemContainer) + queue.Enqueue(itemContainer); + } + } + + return null; + } + } + + private class GroupComparer : IComparer + { + private readonly CheckItemGroup m_Grouper; + + public GroupComparer(CheckItemGroup grouper) => m_Grouper = grouper; + + public int Compare(Item a, Item b) => m_Grouper(a, b); + } + + [Flags] + private enum SaveFlag : byte + { + None = 0x00000000, + MaxItems = 0x00000001, + GumpID = 0x00000002, + DropSound = 0x00000004, + LiftOverride = 0x00000008 + } + + private struct ItemStackEntry + { + public readonly Item m_StackItem; + public readonly Item m_DropItem; + + public ItemStackEntry(Item stack, Item drop) + { + m_StackItem = stack; + m_DropItem = drop; + } + } + } + + public class ContainerData + { + private static readonly Dictionary m_Table; + + static ContainerData() + { + m_Table = new Dictionary(); + + var path = Path.Combine(Core.BaseDirectory, "Data/containers.cfg"); + + if (!File.Exists(path)) + { + Default = new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); + return; + } + + using (var reader = new StreamReader(path)) + { + string line; + + while ((line = reader.ReadLine()) != null) + { + line = line.Trim(); + + if (line.Length == 0 || line.StartsWith("#")) + continue; + + try + { + var split = line.Split('\t'); + + if (split.Length >= 3) + { + var gumpID = Utility.ToInt32(split[0]); + + var aRect = split[1].Split(' '); + if (aRect.Length < 4) + continue; + + var x = Utility.ToInt32(aRect[0]); + var y = Utility.ToInt32(aRect[1]); + var width = Utility.ToInt32(aRect[2]); + var height = Utility.ToInt32(aRect[3]); + + var bounds = new Rectangle2D(x, y, width, height); + + var dropSound = Utility.ToInt32(split[2]); + + var data = new ContainerData(gumpID, bounds, dropSound); + + Default ??= data; + + if (split.Length >= 4) + { + var aIDs = split[3].Split(','); + + for (var i = 0; i < aIDs.Length; i++) + { + var id = Utility.ToInt32(aIDs[i]); + + if (m_Table.ContainsKey(id)) + Console.WriteLine(@"Warning: double ItemID entry in Data\containers.cfg"); + else + m_Table[id] = data; + } + } + } + } + catch + { + // ignored + } + } + } + + Default ??= new ContainerData(0x3C, new Rectangle2D(44, 65, 142, 94), 0x48); + } + + public ContainerData(int gumpID, Rectangle2D bounds, int dropSound) + { + GumpID = gumpID; + Bounds = bounds; + DropSound = dropSound; + } + + public static ContainerData Default { get; set; } + + public int GumpID { get; } + + public Rectangle2D Bounds { get; } + + public int DropSound { get; } + + public static ContainerData GetData(int itemID) + { + m_Table.TryGetValue(itemID, out var data); + return data ?? Default; + } + } +} diff --git a/Projects/Server/Items/Containers.cs b/Projects/Server/Items/Containers.cs index 22d868e67..5c703642c 100644 --- a/Projects/Server/Items/Containers.cs +++ b/Projects/Server/Items/Containers.cs @@ -1,132 +1,137 @@ -/*************************************************************************** - * Containers.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Accounting; -using Server.Network; - -namespace Server.Items -{ - public class BankBox : Container - { - public BankBox(Serial serial) : base(serial) - { - } - - public BankBox(Mobile owner) : base(0xE7C) - { - Layer = Layer.Bank; - Movable = false; - Owner = owner; - } - - public override int DefaultMaxWeight => 0; - - public override bool IsVirtualItem => true; - - public Mobile Owner { get; private set; } - - public bool Opened { get; private set; } - - public static bool SendDeleteOnClose { get; set; } - - public void Open() - { - Opened = true; - - if (Owner != null) - { - Owner.PrivateOverheadMessage(MessageType.Regular, 0x3B2, true, - $"Bank container has {TotalItems} items, {TotalWeight} stones", Owner.NetState); - Owner.Send(new EquipUpdate(this)); - DisplayTo(Owner); - } - } - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - - writer.Write(Owner); - writer.Write(Opened); - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - Owner = reader.ReadMobile(); - Opened = reader.ReadBool(); - - if (Owner == null) - Delete(); - - break; - } - } - - if (ItemID == 0xE41) - ItemID = 0xE7C; - } - - public void Close() - { - Opened = false; - - if (SendDeleteOnClose) - Owner?.Send(RemovePacket); - } - - public override void OnSingleClick(Mobile from) - { - } - - public override void OnDoubleClick(Mobile from) - { - } - - public override DeathMoveResult OnParentDeath(Mobile parent) => DeathMoveResult.RemainEquipped; - - public override bool IsAccessibleTo(Mobile check) => - ((check == Owner && Opened) || check.AccessLevel >= AccessLevel.GameMaster) && base.IsAccessibleTo(check); - - public override bool OnDragDrop(Mobile from, Item dropped) => - ((from == Owner && Opened) || from.AccessLevel >= AccessLevel.GameMaster) && base.OnDragDrop(from, dropped); - - public override bool OnDragDropInto(Mobile from, Item item, Point3D p) => - ((from == Owner && Opened) || from.AccessLevel >= AccessLevel.GameMaster) && - base.OnDragDropInto(from, item, p); - - public override int GetTotal(TotalType type) - { - if (AccountGold.Enabled && Owner?.Account != null && type == TotalType.Gold) - return Owner.Account.TotalGold; - - return base.GetTotal(type); - } - } -} +/*************************************************************************** + * Containers.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Accounting; +using Server.Network; + +namespace Server.Items +{ + public class BankBox : Container + { + public BankBox(Serial serial) : base(serial) + { + } + + public BankBox(Mobile owner) : base(0xE7C) + { + Layer = Layer.Bank; + Movable = false; + Owner = owner; + } + + public override int DefaultMaxWeight => 0; + + public override bool IsVirtualItem => true; + + public Mobile Owner { get; private set; } + + public bool Opened { get; private set; } + + public static bool SendDeleteOnClose { get; set; } + + public void Open() + { + Opened = true; + + if (Owner != null) + { + Owner.PrivateOverheadMessage( + MessageType.Regular, + 0x3B2, + true, + $"Bank container has {TotalItems} items, {TotalWeight} stones", + Owner.NetState + ); + Owner.Send(new EquipUpdate(this)); + DisplayTo(Owner); + } + } + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); // version + + writer.Write(Owner); + writer.Write(Opened); + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + + switch (version) + { + case 0: + { + Owner = reader.ReadMobile(); + Opened = reader.ReadBool(); + + if (Owner == null) + Delete(); + + break; + } + } + + if (ItemID == 0xE41) + ItemID = 0xE7C; + } + + public void Close() + { + Opened = false; + + if (SendDeleteOnClose) + Owner?.Send(RemovePacket); + } + + public override void OnSingleClick(Mobile from) + { + } + + public override void OnDoubleClick(Mobile from) + { + } + + public override DeathMoveResult OnParentDeath(Mobile parent) => DeathMoveResult.RemainEquipped; + + public override bool IsAccessibleTo(Mobile check) => + (check == Owner && Opened || check.AccessLevel >= AccessLevel.GameMaster) && base.IsAccessibleTo(check); + + public override bool OnDragDrop(Mobile from, Item dropped) => + (from == Owner && Opened || from.AccessLevel >= AccessLevel.GameMaster) && base.OnDragDrop(from, dropped); + + public override bool OnDragDropInto(Mobile from, Item item, Point3D p) => + (from == Owner && Opened || from.AccessLevel >= AccessLevel.GameMaster) && + base.OnDragDropInto(from, item, p); + + public override int GetTotal(TotalType type) + { + if (AccountGold.Enabled && Owner?.Account != null && type == TotalType.Gold) + return Owner.Account.TotalGold; + + return base.GetTotal(type); + } + } +} diff --git a/Projects/Server/Items/SecureTradeContainer.cs b/Projects/Server/Items/SecureTradeContainer.cs index e373a6dc5..c5f8b676e 100644 --- a/Projects/Server/Items/SecureTradeContainer.cs +++ b/Projects/Server/Items/SecureTradeContainer.cs @@ -1,116 +1,116 @@ -/*************************************************************************** - * SecureTradeContainer.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Accounting; -using Server.Network; - -namespace Server.Items -{ - public class SecureTradeContainer : Container - { - public SecureTradeContainer(SecureTrade trade) : base(0x1E5E) - { - Trade = trade; - - Movable = false; - } - - public SecureTradeContainer(Serial serial) : base(serial) - { - } - - public SecureTrade Trade { get; } - - public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) - { - if (item == Trade.From.VirtualCheck || item == Trade.To.VirtualCheck) return true; - - var to = Trade.From.Container != this ? Trade.From.Mobile : Trade.To.Mobile; - - return m.CheckTrade(to, item, this, message, checkItems, plusItems, plusWeight); - } - - public override bool CheckLift(Mobile from, Item item, ref LRReason reject) - { - reject = LRReason.CannotLift; - return false; - } - - public override bool IsAccessibleTo(Mobile check) => - IsChildOf(check) && Trade?.Valid == true && base.IsAccessibleTo(check); - - public override void OnItemAdded(Item item) - { - if (!(item is VirtualCheck)) - ClearChecks(); - } - - public override void OnItemRemoved(Item item) - { - if (!(item is VirtualCheck)) - ClearChecks(); - } - - public override void OnSubItemAdded(Item item) - { - if (!(item is VirtualCheck)) - ClearChecks(); - } - - public override void OnSubItemRemoved(Item item) - { - if (!(item is VirtualCheck)) - ClearChecks(); - } - - public void ClearChecks() - { - if (Trade == null) - return; - - if (Trade.From?.IsDisposed == false) - Trade.From.Accepted = false; - - if (Trade.To?.IsDisposed == false) - Trade.To.Accepted = false; - - Trade.Update(); - } - - public override bool IsChildVisibleTo(Mobile m, Item child) => - child is VirtualCheck - ? AccountGold.Enabled && m.NetState?.NewSecureTrading != true - : base.IsChildVisibleTo(m, child); - - public override void Serialize(IGenericWriter writer) - { - base.Serialize(writer); - - writer.Write(0); // version - } - - public override void Deserialize(IGenericReader reader) - { - base.Deserialize(reader); - - var version = reader.ReadInt(); - } - } -} +/*************************************************************************** + * SecureTradeContainer.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Accounting; +using Server.Network; + +namespace Server.Items +{ + public class SecureTradeContainer : Container + { + public SecureTradeContainer(SecureTrade trade) : base(0x1E5E) + { + Trade = trade; + + Movable = false; + } + + public SecureTradeContainer(Serial serial) : base(serial) + { + } + + public SecureTrade Trade { get; } + + public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight) + { + if (item == Trade.From.VirtualCheck || item == Trade.To.VirtualCheck) return true; + + var to = Trade.From.Container != this ? Trade.From.Mobile : Trade.To.Mobile; + + return m.CheckTrade(to, item, this, message, checkItems, plusItems, plusWeight); + } + + public override bool CheckLift(Mobile from, Item item, ref LRReason reject) + { + reject = LRReason.CannotLift; + return false; + } + + public override bool IsAccessibleTo(Mobile check) => + IsChildOf(check) && Trade?.Valid == true && base.IsAccessibleTo(check); + + public override void OnItemAdded(Item item) + { + if (!(item is VirtualCheck)) + ClearChecks(); + } + + public override void OnItemRemoved(Item item) + { + if (!(item is VirtualCheck)) + ClearChecks(); + } + + public override void OnSubItemAdded(Item item) + { + if (!(item is VirtualCheck)) + ClearChecks(); + } + + public override void OnSubItemRemoved(Item item) + { + if (!(item is VirtualCheck)) + ClearChecks(); + } + + public void ClearChecks() + { + if (Trade == null) + return; + + if (Trade.From?.IsDisposed == false) + Trade.From.Accepted = false; + + if (Trade.To?.IsDisposed == false) + Trade.To.Accepted = false; + + Trade.Update(); + } + + public override bool IsChildVisibleTo(Mobile m, Item child) => + child is VirtualCheck + ? AccountGold.Enabled && m.NetState?.NewSecureTrading != true + : base.IsChildVisibleTo(m, child); + + public override void Serialize(IGenericWriter writer) + { + base.Serialize(writer); + + writer.Write(0); // version + } + + public override void Deserialize(IGenericReader reader) + { + base.Deserialize(reader); + + var version = reader.ReadInt(); + } + } +} diff --git a/Projects/Server/Items/VirtualCheck.cs b/Projects/Server/Items/VirtualCheck.cs index ae7f99946..bce48f97c 100644 --- a/Projects/Server/Items/VirtualCheck.cs +++ b/Projects/Server/Items/VirtualCheck.cs @@ -1,353 +1,353 @@ -using Server.Gumps; -using Server.Network; - -namespace Server.Items -{ - public sealed class VirtualCheck : Item - { - // TODO: Move to configuration - public static bool UseEditGump = false; - - private int m_Gold; - - private int m_Plat; - - public VirtualCheck(int plat = 0, int gold = 0) - : base(0x14F0) - { - Plat = plat; - Gold = gold; - - Movable = false; - } - - public VirtualCheck(Serial serial) - : base(serial) - { - } - - public override bool IsVirtualItem => true; - - public override bool DisplayWeight => false; - public override bool DisplayLootType => false; - - public override double DefaultWeight => 0; - - public override string DefaultName => "Offer Of Currency"; - - public EditGump Editor { get; private set; } - - [CommandProperty(AccessLevel.Administrator)] - public int Plat - { - get => m_Plat; - set - { - m_Plat = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.Administrator)] - public int Gold - { - get => m_Gold; - set - { - m_Gold = value; - InvalidateProperties(); - } - } - - public override bool IsAccessibleTo(Mobile check) - { - var c = GetSecureTradeCont(); - - if (check == null || c == null) return base.IsAccessibleTo(check); - - return c.RootParent == check && IsChildOf(c); - } - - public override void OnDoubleClickSecureTrade(Mobile from) - { - if (UseEditGump && IsAccessibleTo(from)) - { - if (Editor?.Check?.Deleted != false) - { - Editor = new EditGump(from, this); - Editor.Send(); - } - else - { - Editor.Refresh(true); - } - } - else - { - if (Editor != null) - { - Editor.Close(); - Editor = null; - } - - base.OnDoubleClickSecureTrade(from); - } - } - - public override void OnSingleClick(Mobile from) - { - LabelTo(from, "Offer: {0:#,0} platinum, {1:#,0} gold", Plat, Gold); - } - - public override void GetProperties(ObjectPropertyList list) - { - base.GetProperties(list); - - list.Add(1060738, $"{Plat:#,0} platinum, {Gold:#,0} gold"); // value: ~1_val~ - } - - public void UpdateTrade(Mobile user) - { - var c = GetSecureTradeCont(); - - if (c?.Trade == null) return; - - if (user == c.Trade.From.Mobile) - c.Trade.UpdateFromCurrency(); - else if (user == c.Trade.To.Mobile) c.Trade.UpdateToCurrency(); - - c.ClearChecks(); - } - - public override void OnAfterDelete() - { - base.OnAfterDelete(); - - if (Editor != null) - { - Editor.Close(); - Editor = null; - } - } - - public override void Serialize(IGenericWriter writer) - { - } - - public override void Deserialize(IGenericReader reader) - { - Delete(); - } - - public class EditGump : Gump - { - public enum Buttons - { - Close, - Clear, - Accept, - AllPlat, - AllGold - } - - private int m_Plat, m_Gold; - - public EditGump(Mobile user, VirtualCheck check) - : base(50, 50) - { - User = user; - Check = check; - - m_Plat = Check.Plat; - m_Gold = Check.Gold; - - Closable = true; - Disposable = true; - Draggable = true; - Resizable = false; - - User.CloseGump(); - - CompileLayout(); - } - - public Mobile User { get; } - public VirtualCheck Check { get; private set; } - - public override void OnServerClose(NetState owner) - { - base.OnServerClose(owner); - - if (Check?.Deleted == false) - Check.UpdateTrade(User); - } - - public void Close() - { - User.CloseGump(); - - if (Check?.Deleted == false) - Check.UpdateTrade(User); - else - Check = null; - } - - public void Send() - { - if (Check?.Deleted == false) - User.SendGump(this); - else - Close(); - } - - public void Refresh(bool recompile) - { - if (Check?.Deleted != false) - { - Close(); - return; - } - - if (recompile) - CompileLayout(); - - Close(); - Send(); - } - - private void CompileLayout() - { - if (Check?.Deleted != false) - return; - - Entries.ForEach(e => e.Parent = null); - Entries.Clear(); - - AddPage(0); - - AddBackground(0, 0, 400, 160, 3500); - - // Title - AddImageTiled(25, 35, 350, 3, 96); - AddImage(10, 8, 113); - AddImage(360, 8, 113); - - var title = - $"
BANK OF {User.RawName.ToUpper()}
"; - - AddHtml(40, 15, 320, 20, title); - - // Platinum Row - AddBackground(15, 60, 175, 20, 9300); - AddBackground(20, 45, 165, 30, 9350); - AddItem(20, 45, 3826); // Plat - AddLabel(60, 50, 0, User.Account.TotalPlat.ToString("#,0")); - - AddButton(195, 50, 95, 95, (int)Buttons.AllPlat); // -> - - AddBackground(210, 60, 175, 20, 9300); - AddBackground(215, 45, 165, 30, 9350); - AddTextEntry(225, 50, 145, 20, 0, 0, m_Plat.ToString(), User.Account.TotalPlat.ToString().Length); - - // Gold Row - AddBackground(15, 100, 175, 20, 9300); - AddBackground(20, 85, 165, 30, 9350); - AddItem(20, 85, 3823); // Gold - AddLabel(60, 90, 0, User.Account.TotalGold.ToString("#,0")); - - AddButton(195, 90, 95, 95, (int)Buttons.AllGold); // -> - - AddBackground(210, 100, 175, 20, 9300); - AddBackground(215, 85, 165, 30, 9350); - AddTextEntry(225, 90, 145, 20, 0, 1, m_Gold.ToString(), User.Account.TotalGold.ToString().Length); - - // Buttons - AddButton(20, 128, 12006, 12007, (int)Buttons.Close); - AddButton(215, 128, 12003, 12004, (int)Buttons.Clear); - AddButton(305, 128, 12000, 12002, (int)Buttons.Accept); - } - - public override void OnResponse(NetState sender, RelayInfo info) - { - if (Check?.Deleted != false || sender.Mobile != User) - { - Close(); - return; - } - - bool refresh = false, updated = false; - - switch ((Buttons)info.ButtonID) - { - case Buttons.Close: - break; - case Buttons.Clear: - { - m_Plat = m_Gold = 0; - refresh = true; - } - break; - case Buttons.Accept: - { - var platText = info.GetTextEntry(0).Text; - var goldText = info.GetTextEntry(1).Text; - - if (!int.TryParse(platText, out m_Plat)) - { - User.SendMessage("That is not a valid amount of platinum."); - refresh = true; - } - else if (!int.TryParse(goldText, out m_Gold)) - { - User.SendMessage("That is not a valid amount of gold."); - refresh = true; - } - else - { - var totalPlat = User.Account.TotalPlat; - var totalGold = User.Account.TotalGold; - - if (totalPlat < m_Plat || totalGold < m_Gold) - { - m_Plat = User.Account.TotalPlat; - m_Gold = User.Account.TotalGold; - User.SendMessage("You do not have that much currency."); - refresh = true; - } - else - { - Check.Plat = m_Plat; - Check.Gold = m_Gold; - updated = true; - } - } - } - break; - case Buttons.AllPlat: - { - m_Plat = User.Account.TotalPlat; - refresh = true; - } - break; - case Buttons.AllGold: - { - m_Gold = User.Account.TotalGold; - refresh = true; - } - break; - } - - if (updated) - User.SendMessage("Your offer has been updated."); - - if (refresh && Check?.Deleted == false) - { - Refresh(true); - return; - } - - Close(); - } - } - } -} +using Server.Gumps; +using Server.Network; + +namespace Server.Items +{ + public sealed class VirtualCheck : Item + { + // TODO: Move to configuration + public static bool UseEditGump = false; + + private int m_Gold; + + private int m_Plat; + + public VirtualCheck(int plat = 0, int gold = 0) + : base(0x14F0) + { + Plat = plat; + Gold = gold; + + Movable = false; + } + + public VirtualCheck(Serial serial) + : base(serial) + { + } + + public override bool IsVirtualItem => true; + + public override bool DisplayWeight => false; + public override bool DisplayLootType => false; + + public override double DefaultWeight => 0; + + public override string DefaultName => "Offer Of Currency"; + + public EditGump Editor { get; private set; } + + [CommandProperty(AccessLevel.Administrator)] + public int Plat + { + get => m_Plat; + set + { + m_Plat = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.Administrator)] + public int Gold + { + get => m_Gold; + set + { + m_Gold = value; + InvalidateProperties(); + } + } + + public override bool IsAccessibleTo(Mobile check) + { + var c = GetSecureTradeCont(); + + if (check == null || c == null) return base.IsAccessibleTo(check); + + return c.RootParent == check && IsChildOf(c); + } + + public override void OnDoubleClickSecureTrade(Mobile from) + { + if (UseEditGump && IsAccessibleTo(from)) + { + if (Editor?.Check?.Deleted != false) + { + Editor = new EditGump(from, this); + Editor.Send(); + } + else + { + Editor.Refresh(true); + } + } + else + { + if (Editor != null) + { + Editor.Close(); + Editor = null; + } + + base.OnDoubleClickSecureTrade(from); + } + } + + public override void OnSingleClick(Mobile from) + { + LabelTo(from, "Offer: {0:#,0} platinum, {1:#,0} gold", Plat, Gold); + } + + public override void GetProperties(ObjectPropertyList list) + { + base.GetProperties(list); + + list.Add(1060738, $"{Plat:#,0} platinum, {Gold:#,0} gold"); // value: ~1_val~ + } + + public void UpdateTrade(Mobile user) + { + var c = GetSecureTradeCont(); + + if (c?.Trade == null) return; + + if (user == c.Trade.From.Mobile) + c.Trade.UpdateFromCurrency(); + else if (user == c.Trade.To.Mobile) c.Trade.UpdateToCurrency(); + + c.ClearChecks(); + } + + public override void OnAfterDelete() + { + base.OnAfterDelete(); + + if (Editor != null) + { + Editor.Close(); + Editor = null; + } + } + + public override void Serialize(IGenericWriter writer) + { + } + + public override void Deserialize(IGenericReader reader) + { + Delete(); + } + + public class EditGump : Gump + { + public enum Buttons + { + Close, + Clear, + Accept, + AllPlat, + AllGold + } + + private int m_Plat, m_Gold; + + public EditGump(Mobile user, VirtualCheck check) + : base(50, 50) + { + User = user; + Check = check; + + m_Plat = Check.Plat; + m_Gold = Check.Gold; + + Closable = true; + Disposable = true; + Draggable = true; + Resizable = false; + + User.CloseGump(); + + CompileLayout(); + } + + public Mobile User { get; } + public VirtualCheck Check { get; private set; } + + public override void OnServerClose(NetState owner) + { + base.OnServerClose(owner); + + if (Check?.Deleted == false) + Check.UpdateTrade(User); + } + + public void Close() + { + User.CloseGump(); + + if (Check?.Deleted == false) + Check.UpdateTrade(User); + else + Check = null; + } + + public void Send() + { + if (Check?.Deleted == false) + User.SendGump(this); + else + Close(); + } + + public void Refresh(bool recompile) + { + if (Check?.Deleted != false) + { + Close(); + return; + } + + if (recompile) + CompileLayout(); + + Close(); + Send(); + } + + private void CompileLayout() + { + if (Check?.Deleted != false) + return; + + Entries.ForEach(e => e.Parent = null); + Entries.Clear(); + + AddPage(0); + + AddBackground(0, 0, 400, 160, 3500); + + // Title + AddImageTiled(25, 35, 350, 3, 96); + AddImage(10, 8, 113); + AddImage(360, 8, 113); + + var title = + $"
BANK OF {User.RawName.ToUpper()}
"; + + AddHtml(40, 15, 320, 20, title); + + // Platinum Row + AddBackground(15, 60, 175, 20, 9300); + AddBackground(20, 45, 165, 30, 9350); + AddItem(20, 45, 3826); // Plat + AddLabel(60, 50, 0, User.Account.TotalPlat.ToString("#,0")); + + AddButton(195, 50, 95, 95, (int)Buttons.AllPlat); // -> + + AddBackground(210, 60, 175, 20, 9300); + AddBackground(215, 45, 165, 30, 9350); + AddTextEntry(225, 50, 145, 20, 0, 0, m_Plat.ToString(), User.Account.TotalPlat.ToString().Length); + + // Gold Row + AddBackground(15, 100, 175, 20, 9300); + AddBackground(20, 85, 165, 30, 9350); + AddItem(20, 85, 3823); // Gold + AddLabel(60, 90, 0, User.Account.TotalGold.ToString("#,0")); + + AddButton(195, 90, 95, 95, (int)Buttons.AllGold); // -> + + AddBackground(210, 100, 175, 20, 9300); + AddBackground(215, 85, 165, 30, 9350); + AddTextEntry(225, 90, 145, 20, 0, 1, m_Gold.ToString(), User.Account.TotalGold.ToString().Length); + + // Buttons + AddButton(20, 128, 12006, 12007, (int)Buttons.Close); + AddButton(215, 128, 12003, 12004, (int)Buttons.Clear); + AddButton(305, 128, 12000, 12002, (int)Buttons.Accept); + } + + public override void OnResponse(NetState sender, RelayInfo info) + { + if (Check?.Deleted != false || sender.Mobile != User) + { + Close(); + return; + } + + bool refresh = false, updated = false; + + switch ((Buttons)info.ButtonID) + { + case Buttons.Close: + break; + case Buttons.Clear: + { + m_Plat = m_Gold = 0; + refresh = true; + } + break; + case Buttons.Accept: + { + var platText = info.GetTextEntry(0).Text; + var goldText = info.GetTextEntry(1).Text; + + if (!int.TryParse(platText, out m_Plat)) + { + User.SendMessage("That is not a valid amount of platinum."); + refresh = true; + } + else if (!int.TryParse(goldText, out m_Gold)) + { + User.SendMessage("That is not a valid amount of gold."); + refresh = true; + } + else + { + var totalPlat = User.Account.TotalPlat; + var totalGold = User.Account.TotalGold; + + if (totalPlat < m_Plat || totalGold < m_Gold) + { + m_Plat = User.Account.TotalPlat; + m_Gold = User.Account.TotalGold; + User.SendMessage("You do not have that much currency."); + refresh = true; + } + else + { + Check.Plat = m_Plat; + Check.Gold = m_Gold; + updated = true; + } + } + } + break; + case Buttons.AllPlat: + { + m_Plat = User.Account.TotalPlat; + refresh = true; + } + break; + case Buttons.AllGold: + { + m_Gold = User.Account.TotalGold; + refresh = true; + } + break; + } + + if (updated) + User.SendMessage("Your offer has been updated."); + + if (refresh && Check?.Deleted == false) + { + Refresh(true); + return; + } + + Close(); + } + } + } +} diff --git a/Projects/Server/Items/VirtualHair.cs b/Projects/Server/Items/VirtualHair.cs index 0fe2421e4..913e2a0de 100644 --- a/Projects/Server/Items/VirtualHair.cs +++ b/Projects/Server/Items/VirtualHair.cs @@ -1,153 +1,153 @@ -/*************************************************************************** - * VirtualHair.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server -{ - public abstract class BaseHairInfo - { - protected BaseHairInfo(int itemid, int hue = 0) - { - ItemID = itemid; - Hue = hue; - } - - protected BaseHairInfo(IGenericReader reader) - { - var version = reader.ReadInt(); - - switch (version) - { - case 0: - { - ItemID = reader.ReadInt(); - Hue = reader.ReadInt(); - break; - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int ItemID { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int Hue { get; set; } - - public virtual void Serialize(IGenericWriter writer) - { - writer.Write(0); // version - writer.Write(ItemID); - writer.Write(Hue); - } - } - - public class HairInfo : BaseHairInfo - { - public HairInfo(int itemid) - : base(itemid) - { - } - - public HairInfo(int itemid, int hue) - : base(itemid, hue) - { - } - - public HairInfo(IGenericReader reader) - : base(reader) - { - } - - // TODO: Can we make this higher for newer clients? - public static uint FakeSerial(Mobile parent) => 0x7FFFFFFF - 0x400 - parent.Serial * 4; - } - - public class FacialHairInfo : BaseHairInfo - { - public FacialHairInfo(int itemid) - : base(itemid) - { - } - - public FacialHairInfo(int itemid, int hue) - : base(itemid, hue) - { - } - - public FacialHairInfo(IGenericReader reader) - : base(reader) - { - } - - // TOOD: Can we make this higher for newer clients? - public static uint FakeSerial(Mobile parent) => 0x7FFFFFFF - 0x400 - 1 - parent.Serial * 4; - } - - public sealed class HairEquipUpdate : Packet - { - public HairEquipUpdate(Mobile parent) - : base(0x2E, 15) - { - var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.HairHue; - - Stream.Write(HairInfo.FakeSerial(parent)); - Stream.Write((short)parent.HairItemID); - Stream.Write((byte)0); - Stream.Write((byte)Layer.Hair); - Stream.Write(parent.Serial); - Stream.Write((short)hue); - } - } - - public sealed class FacialHairEquipUpdate : Packet - { - public FacialHairEquipUpdate(Mobile parent) - : base(0x2E, 15) - { - var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.FacialHairHue; - - Stream.Write(FacialHairInfo.FakeSerial(parent)); - Stream.Write((short)parent.FacialHairItemID); - Stream.Write((byte)0); - Stream.Write((byte)Layer.FacialHair); - Stream.Write(parent.Serial); - Stream.Write((short)hue); - } - } - - public sealed class RemoveHair : Packet - { - public RemoveHair(Mobile parent) - : base(0x1D, 5) - { - Stream.Write(HairInfo.FakeSerial(parent)); - } - } - - public sealed class RemoveFacialHair : Packet - { - public RemoveFacialHair(Mobile parent) - : base(0x1D, 5) - { - Stream.Write(FacialHairInfo.FakeSerial(parent)); - } - } -} +/*************************************************************************** + * VirtualHair.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server +{ + public abstract class BaseHairInfo + { + protected BaseHairInfo(int itemid, int hue = 0) + { + ItemID = itemid; + Hue = hue; + } + + protected BaseHairInfo(IGenericReader reader) + { + var version = reader.ReadInt(); + + switch (version) + { + case 0: + { + ItemID = reader.ReadInt(); + Hue = reader.ReadInt(); + break; + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int ItemID { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int Hue { get; set; } + + public virtual void Serialize(IGenericWriter writer) + { + writer.Write(0); // version + writer.Write(ItemID); + writer.Write(Hue); + } + } + + public class HairInfo : BaseHairInfo + { + public HairInfo(int itemid) + : base(itemid) + { + } + + public HairInfo(int itemid, int hue) + : base(itemid, hue) + { + } + + public HairInfo(IGenericReader reader) + : base(reader) + { + } + + // TODO: Can we make this higher for newer clients? + public static uint FakeSerial(Mobile parent) => 0x7FFFFFFF - 0x400 - parent.Serial * 4; + } + + public class FacialHairInfo : BaseHairInfo + { + public FacialHairInfo(int itemid) + : base(itemid) + { + } + + public FacialHairInfo(int itemid, int hue) + : base(itemid, hue) + { + } + + public FacialHairInfo(IGenericReader reader) + : base(reader) + { + } + + // TOOD: Can we make this higher for newer clients? + public static uint FakeSerial(Mobile parent) => 0x7FFFFFFF - 0x400 - 1 - parent.Serial * 4; + } + + public sealed class HairEquipUpdate : Packet + { + public HairEquipUpdate(Mobile parent) + : base(0x2E, 15) + { + var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.HairHue; + + Stream.Write(HairInfo.FakeSerial(parent)); + Stream.Write((short)parent.HairItemID); + Stream.Write((byte)0); + Stream.Write((byte)Layer.Hair); + Stream.Write(parent.Serial); + Stream.Write((short)hue); + } + } + + public sealed class FacialHairEquipUpdate : Packet + { + public FacialHairEquipUpdate(Mobile parent) + : base(0x2E, 15) + { + var hue = parent.SolidHueOverride >= 0 ? parent.SolidHueOverride : parent.FacialHairHue; + + Stream.Write(FacialHairInfo.FakeSerial(parent)); + Stream.Write((short)parent.FacialHairItemID); + Stream.Write((byte)0); + Stream.Write((byte)Layer.FacialHair); + Stream.Write(parent.Serial); + Stream.Write((short)hue); + } + } + + public sealed class RemoveHair : Packet + { + public RemoveHair(Mobile parent) + : base(0x1D, 5) + { + Stream.Write(HairInfo.FakeSerial(parent)); + } + } + + public sealed class RemoveFacialHair : Packet + { + public RemoveFacialHair(Mobile parent) + : base(0x1D, 5) + { + Stream.Write(FacialHairInfo.FakeSerial(parent)); + } + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/IPEndPointConverter.cs b/Projects/Server/JsonConfiguration/Converters/IPEndPointConverter.cs index c7d4ce307..a4e1fa686 100644 --- a/Projects/Server/JsonConfiguration/Converters/IPEndPointConverter.cs +++ b/Projects/Server/JsonConfiguration/Converters/IPEndPointConverter.cs @@ -1,42 +1,42 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: IPEndPointConverter.cs * - * Created: 2020/07/03 - Updated: 2020/07/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Net; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class IPEndPointConverter : JsonConverter - { - public override IPEndPoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (IPEndPoint.TryParse(reader.GetString(), out var ipep)) - return ipep; - - throw new JsonException("IPEndPoint must be in the correct format"); - } - - public override void Write(Utf8JsonWriter writer, IPEndPoint value, JsonSerializerOptions options) - => writer.WriteStringValue(value.ToString()); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IPEndPointConverter.cs * + * Created: 2020/07/03 - Updated: 2020/07/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class IPEndPointConverter : JsonConverter + { + public override IPEndPoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (IPEndPoint.TryParse(reader.GetString(), out var ipep)) + return ipep; + + throw new JsonException("IPEndPoint must be in the correct format"); + } + + public override void Write(Utf8JsonWriter writer, IPEndPoint value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/IPEndPointConverterFactory.cs b/Projects/Server/JsonConfiguration/Converters/IPEndPointConverterFactory.cs index 6bfc336c7..1f8a8f1ee 100644 --- a/Projects/Server/JsonConfiguration/Converters/IPEndPointConverterFactory.cs +++ b/Projects/Server/JsonConfiguration/Converters/IPEndPointConverterFactory.cs @@ -1,35 +1,36 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: IPEndPointConverterFactory.cs * - * Created: 2020/07/03 - Updated: 2020/07/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Net; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class IPEndPointConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(IPEndPoint); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new IPEndPointConverter(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IPEndPointConverterFactory.cs * + * Created: 2020/07/03 - Updated: 2020/07/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Net; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class IPEndPointConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(IPEndPoint); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new IPEndPointConverter(); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/MapConverter.cs b/Projects/Server/JsonConfiguration/Converters/MapConverter.cs index 06706f9b8..653d02568 100644 --- a/Projects/Server/JsonConfiguration/Converters/MapConverter.cs +++ b/Projects/Server/JsonConfiguration/Converters/MapConverter.cs @@ -1,35 +1,35 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MapConverter.cs - Created: 2020/04/12 - Updated: 2020/05/02 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class MapConverter : JsonConverter - { - public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => Map.Parse(reader.GetString()); - - public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) - => writer.WriteStringValue(value.Name); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MapConverter.cs - Created: 2020/04/12 - Updated: 2020/05/02 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class MapConverter : JsonConverter + { + public override Map Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => Map.Parse(reader.GetString()); + + public override void Write(Utf8JsonWriter writer, Map value, JsonSerializerOptions options) + => writer.WriteStringValue(value.Name); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/MapConverterFactory.cs b/Projects/Server/JsonConfiguration/Converters/MapConverterFactory.cs index 0780deb45..8552616f5 100644 --- a/Projects/Server/JsonConfiguration/Converters/MapConverterFactory.cs +++ b/Projects/Server/JsonConfiguration/Converters/MapConverterFactory.cs @@ -1,34 +1,35 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MapConverterFactory.cs * - * Created: 2020/05/23 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class MapConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Map); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new MapConverter(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MapConverterFactory.cs * + * Created: 2020/05/23 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class MapConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Map); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new MapConverter(); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/Point2DConverter.cs b/Projects/Server/JsonConfiguration/Converters/Point2DConverter.cs index c424313ba..7342e844c 100644 --- a/Projects/Server/JsonConfiguration/Converters/Point2DConverter.cs +++ b/Projects/Server/JsonConfiguration/Converters/Point2DConverter.cs @@ -1,105 +1,105 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Point2DConverter.cs * - * Created: 2020/04/12 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class Point2DConverter : JsonConverter - { - private Point2D DeserializeArray(ref Utf8JsonReader reader) - { - Span data = stackalloc int[2]; - var count = 0; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndArray) - break; - - if (reader.TokenType == JsonTokenType.Number) - { - if (count < 2) - data[count] = reader.GetInt32(); - - count++; - } - } - - if (count > 2) - throw new JsonException("Point2D must be an array of x, y"); - - return new Point2D(data[0], data[1]); - } - - private Point2D DeserializeObj(ref Utf8JsonReader reader) - { - Span data = stackalloc int[2]; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndObject) - break; - - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException("Invalid json structure for Point2D object"); - - var key = reader.GetString(); - - var i = key switch - { - "x" => 0, - "y" => 1, - _ => throw new JsonException($"Invalid property {key} for Point2D") - }; - - reader.Read(); - - if (reader.TokenType != JsonTokenType.Number) - throw new JsonException($"Value for {key} must be a number"); - - data[i] = reader.GetInt32(); - } - - return new Point2D(data[0], data[1]); - } - - public override Point2D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - reader.TokenType switch - { - JsonTokenType.StartArray => DeserializeArray(ref reader), - JsonTokenType.StartObject => DeserializeObj(ref reader), - _ => throw new JsonException("Invalid Json for Point3D") - }; - - public override void Write(Utf8JsonWriter writer, Point2D value, JsonSerializerOptions options) - { - writer.WriteStartArray(); - writer.WriteNumberValue(value.X); - writer.WriteNumberValue(value.Y); - writer.WriteEndArray(); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Point2DConverter.cs * + * Created: 2020/04/12 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class Point2DConverter : JsonConverter + { + private Point2D DeserializeArray(ref Utf8JsonReader reader) + { + Span data = stackalloc int[2]; + var count = 0; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndArray) + break; + + if (reader.TokenType == JsonTokenType.Number) + { + if (count < 2) + data[count] = reader.GetInt32(); + + count++; + } + } + + if (count > 2) + throw new JsonException("Point2D must be an array of x, y"); + + return new Point2D(data[0], data[1]); + } + + private Point2D DeserializeObj(ref Utf8JsonReader reader) + { + Span data = stackalloc int[2]; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Invalid json structure for Point2D object"); + + var key = reader.GetString(); + + var i = key switch + { + "x" => 0, + "y" => 1, + _ => throw new JsonException($"Invalid property {key} for Point2D") + }; + + reader.Read(); + + if (reader.TokenType != JsonTokenType.Number) + throw new JsonException($"Value for {key} must be a number"); + + data[i] = reader.GetInt32(); + } + + return new Point2D(data[0], data[1]); + } + + public override Point2D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + reader.TokenType switch + { + JsonTokenType.StartArray => DeserializeArray(ref reader), + JsonTokenType.StartObject => DeserializeObj(ref reader), + _ => throw new JsonException("Invalid Json for Point3D") + }; + + public override void Write(Utf8JsonWriter writer, Point2D value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteNumberValue(value.X); + writer.WriteNumberValue(value.Y); + writer.WriteEndArray(); + } + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/Point2DConverterFactory.cs b/Projects/Server/JsonConfiguration/Converters/Point2DConverterFactory.cs index 8bfb509c3..bf2be2441 100644 --- a/Projects/Server/JsonConfiguration/Converters/Point2DConverterFactory.cs +++ b/Projects/Server/JsonConfiguration/Converters/Point2DConverterFactory.cs @@ -1,34 +1,36 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Point2DConverterFactory.cs * - * Created: 2020/05/23 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class Point2DConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Point2D) || typeToConvert == typeof(IPoint2D); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new Point2DConverter(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Point2DConverterFactory.cs * + * Created: 2020/05/23 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class Point2DConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => + typeToConvert == typeof(Point2D) || typeToConvert == typeof(IPoint2D); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new Point2DConverter(); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/Point3DConverter.cs b/Projects/Server/JsonConfiguration/Converters/Point3DConverter.cs index 107f2b535..81d10e335 100644 --- a/Projects/Server/JsonConfiguration/Converters/Point3DConverter.cs +++ b/Projects/Server/JsonConfiguration/Converters/Point3DConverter.cs @@ -1,107 +1,107 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Point3DConverter.cs * - * Created: 2020/04/12 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class Point3DConverter : JsonConverter - { - private Point3D DeserializeArray(ref Utf8JsonReader reader) - { - Span data = stackalloc int[3]; - var count = 0; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndArray) - break; - - if (reader.TokenType == JsonTokenType.Number) - { - if (count < 3) - data[count] = reader.GetInt32(); - - count++; - } - } - - if (count > 3) - throw new JsonException("Point3D must be an array of x, y, z"); - - return new Point3D(data[0], data[1], data[2]); - } - - private Point3D DeserializeObj(ref Utf8JsonReader reader) - { - Span data = stackalloc int[3]; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndObject) - break; - - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException("Invalid json structure for Point3D object"); - - var key = reader.GetString(); - - var i = key switch - { - "x" => 0, - "y" => 1, - "z" => 2, - _ => throw new JsonException($"Invalid property {key} for Point3D") - }; - - reader.Read(); - - if (reader.TokenType != JsonTokenType.Number) - throw new JsonException($"Value for {key} must be a number"); - - data[i] = reader.GetInt32(); - } - - return new Point3D(data[0], data[1], data[2]); - } - - public override Point3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - reader.TokenType switch - { - JsonTokenType.StartArray => DeserializeArray(ref reader), - JsonTokenType.StartObject => DeserializeObj(ref reader), - _ => throw new JsonException("Invalid Json for Point3D") - }; - - public override void Write(Utf8JsonWriter writer, Point3D value, JsonSerializerOptions options) - { - writer.WriteStartArray(); - writer.WriteNumberValue(value.X); - writer.WriteNumberValue(value.Y); - writer.WriteNumberValue(value.Z); - writer.WriteEndArray(); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Point3DConverter.cs * + * Created: 2020/04/12 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class Point3DConverter : JsonConverter + { + private Point3D DeserializeArray(ref Utf8JsonReader reader) + { + Span data = stackalloc int[3]; + var count = 0; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndArray) + break; + + if (reader.TokenType == JsonTokenType.Number) + { + if (count < 3) + data[count] = reader.GetInt32(); + + count++; + } + } + + if (count > 3) + throw new JsonException("Point3D must be an array of x, y, z"); + + return new Point3D(data[0], data[1], data[2]); + } + + private Point3D DeserializeObj(ref Utf8JsonReader reader) + { + Span data = stackalloc int[3]; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Invalid json structure for Point3D object"); + + var key = reader.GetString(); + + var i = key switch + { + "x" => 0, + "y" => 1, + "z" => 2, + _ => throw new JsonException($"Invalid property {key} for Point3D") + }; + + reader.Read(); + + if (reader.TokenType != JsonTokenType.Number) + throw new JsonException($"Value for {key} must be a number"); + + data[i] = reader.GetInt32(); + } + + return new Point3D(data[0], data[1], data[2]); + } + + public override Point3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + reader.TokenType switch + { + JsonTokenType.StartArray => DeserializeArray(ref reader), + JsonTokenType.StartObject => DeserializeObj(ref reader), + _ => throw new JsonException("Invalid Json for Point3D") + }; + + public override void Write(Utf8JsonWriter writer, Point3D value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteNumberValue(value.X); + writer.WriteNumberValue(value.Y); + writer.WriteNumberValue(value.Z); + writer.WriteEndArray(); + } + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/Point3DConverterFactory.cs b/Projects/Server/JsonConfiguration/Converters/Point3DConverterFactory.cs index b88c6431a..64844bc9b 100644 --- a/Projects/Server/JsonConfiguration/Converters/Point3DConverterFactory.cs +++ b/Projects/Server/JsonConfiguration/Converters/Point3DConverterFactory.cs @@ -1,34 +1,36 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Point3DConverterFactory.cs * - * Created: 2020/05/23 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class Point3DConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Point3D) || typeToConvert == typeof(IPoint3D); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new Point3DConverter(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Point3DConverterFactory.cs * + * Created: 2020/05/23 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class Point3DConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => + typeToConvert == typeof(Point3D) || typeToConvert == typeof(IPoint3D); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new Point3DConverter(); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverter.cs b/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverter.cs index 101fc59b5..00c260ec8 100644 --- a/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverter.cs +++ b/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverter.cs @@ -1,156 +1,156 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Rectangle3DConverter.cs * - * Created: 2020/05/23 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class Rectangle3DConverter : JsonConverter - { - private Rectangle3D DeserializeArray(ref Utf8JsonReader reader) - { - Span data = stackalloc int[6]; - var count = 0; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndArray) - break; - - if (reader.TokenType == JsonTokenType.Number) - { - if (count < 6) - data[count] = reader.GetInt32(); - - count++; - } - } - - if (count > 6) - throw new JsonException("Rectangle3D must be an array of x, y, z, h, w, d"); - - return new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5]); - } - - private Rectangle3D DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options) - { - Span data = stackalloc int[6]; - - // 0 - xyzwhd, 1 - x1y1z1x2y2z2, 2 - start/end - int objType = -1; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndObject) - break; - - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException("Invalid json structure for Rectangle3D object"); - - var key = reader.GetString(); - - reader.Read(); - - if (key == "start" || key == "end") - { - if (objType > -1 && objType != 2) - throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both."); - - objType = 2; - - var point3D = reader.ToObject(options); - var offset = key == "end" ? 3 : 0; - data[0 + offset] = point3D.X; - data[1 + offset] = point3D.Y; - data[1 + offset] = point3D.Z; - continue; - } - - var i = key switch - { - "x" => 0, - "y" => 1, - "z" => 2, - "w" => 3, - "width" => 3, - "h" => 4, - "height" => 4, - "d" => 5, - "depth" => 5, - "x1" => 10, - "y1" => 11, - "z1" => 12, - "x2" => 13, - "y2" => 14, - "z2" => 15, - _ => throw new JsonException($"Invalid property {key} for Rectangle3D") - }; - - if (i < 10) - { - if (objType > -1 && objType != 0) - throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both."); - - objType = 0; - data[i] = reader.GetInt32(); - continue; - } - - if (objType > -1 && objType != 1) - throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both."); - - objType = 1; - data[i - 10] = reader.GetInt32(); - } - - return objType == 0 - ? new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5]) - : new Rectangle3D( - new Point3D(data[0], data[1], data[2]), - new Point3D(data[3], data[4], data[5]) - ); - } - - public override Rectangle3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - reader.TokenType switch - { - JsonTokenType.StartArray => DeserializeArray(ref reader), - JsonTokenType.StartObject => DeserializeObj(ref reader, options), - _ => throw new JsonException("Invalid Json for Point3D") - }; - - public override void Write(Utf8JsonWriter writer, Rectangle3D value, JsonSerializerOptions options) - { - writer.WriteStartArray(); - writer.WriteNumberValue(value.Start.X); - writer.WriteNumberValue(value.Start.Y); - writer.WriteNumberValue(value.Start.Z); - writer.WriteNumberValue(value.Width); - writer.WriteNumberValue(value.Height); - writer.WriteNumberValue(value.Depth); - writer.WriteEndArray(); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Rectangle3DConverter.cs * + * Created: 2020/05/23 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class Rectangle3DConverter : JsonConverter + { + private Rectangle3D DeserializeArray(ref Utf8JsonReader reader) + { + Span data = stackalloc int[6]; + var count = 0; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndArray) + break; + + if (reader.TokenType == JsonTokenType.Number) + { + if (count < 6) + data[count] = reader.GetInt32(); + + count++; + } + } + + if (count > 6) + throw new JsonException("Rectangle3D must be an array of x, y, z, h, w, d"); + + return new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5]); + } + + private Rectangle3D DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + Span data = stackalloc int[6]; + + // 0 - xyzwhd, 1 - x1y1z1x2y2z2, 2 - start/end + var objType = -1; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Invalid json structure for Rectangle3D object"); + + var key = reader.GetString(); + + reader.Read(); + + if (key == "start" || key == "end") + { + if (objType > -1 && objType != 2) + throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both."); + + objType = 2; + + var point3D = reader.ToObject(options); + var offset = key == "end" ? 3 : 0; + data[0 + offset] = point3D.X; + data[1 + offset] = point3D.Y; + data[1 + offset] = point3D.Z; + continue; + } + + var i = key switch + { + "x" => 0, + "y" => 1, + "z" => 2, + "w" => 3, + "width" => 3, + "h" => 4, + "height" => 4, + "d" => 5, + "depth" => 5, + "x1" => 10, + "y1" => 11, + "z1" => 12, + "x2" => 13, + "y2" => 14, + "z2" => 15, + _ => throw new JsonException($"Invalid property {key} for Rectangle3D") + }; + + if (i < 10) + { + if (objType > -1 && objType != 0) + throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both."); + + objType = 0; + data[i] = reader.GetInt32(); + continue; + } + + if (objType > -1 && objType != 1) + throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both."); + + objType = 1; + data[i - 10] = reader.GetInt32(); + } + + return objType == 0 + ? new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5]) + : new Rectangle3D( + new Point3D(data[0], data[1], data[2]), + new Point3D(data[3], data[4], data[5]) + ); + } + + public override Rectangle3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + reader.TokenType switch + { + JsonTokenType.StartArray => DeserializeArray(ref reader), + JsonTokenType.StartObject => DeserializeObj(ref reader, options), + _ => throw new JsonException("Invalid Json for Point3D") + }; + + public override void Write(Utf8JsonWriter writer, Rectangle3D value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteNumberValue(value.Start.X); + writer.WriteNumberValue(value.Start.Y); + writer.WriteNumberValue(value.Start.Z); + writer.WriteNumberValue(value.Width); + writer.WriteNumberValue(value.Height); + writer.WriteNumberValue(value.Depth); + writer.WriteEndArray(); + } + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverterFactory.cs b/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverterFactory.cs index f0c02d849..0e94f05e3 100644 --- a/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverterFactory.cs +++ b/Projects/Server/JsonConfiguration/Converters/Rectangle3DConverterFactory.cs @@ -1,35 +1,35 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Rectangle3DConverterFactory.cs * - * Created: 2020/05/23 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class Rectangle3DConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Rectangle3D); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => - new Rectangle3DConverter(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Rectangle3DConverterFactory.cs * + * Created: 2020/05/23 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class Rectangle3DConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Rectangle3D); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new Rectangle3DConverter(); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/TimeSpanConverter.cs b/Projects/Server/JsonConfiguration/Converters/TimeSpanConverter.cs index f68d3c6c7..2a8ba6d38 100644 --- a/Projects/Server/JsonConfiguration/Converters/TimeSpanConverter.cs +++ b/Projects/Server/JsonConfiguration/Converters/TimeSpanConverter.cs @@ -1,36 +1,36 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: TimeSpanConverter.cs * - * Created: 2020/04/12 - Updated: 2020/05/02 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class TimeSpanConverter : JsonConverter - { - public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => TimeSpan.Parse(reader.GetString()); - - public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) - => writer.WriteStringValue(value.ToString()); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: TimeSpanConverter.cs * + * Created: 2020/04/12 - Updated: 2020/05/02 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class TimeSpanConverter : JsonConverter + { + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => TimeSpan.Parse(reader.GetString()); + + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) + => writer.WriteStringValue(value.ToString()); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/TimeSpanConverterFactory .cs b/Projects/Server/JsonConfiguration/Converters/TimeSpanConverterFactory .cs index 7ed79f454..c2a178478 100644 --- a/Projects/Server/JsonConfiguration/Converters/TimeSpanConverterFactory .cs +++ b/Projects/Server/JsonConfiguration/Converters/TimeSpanConverterFactory .cs @@ -1,34 +1,35 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: TimeSpanConverterFactory.cs * - * Created: 2020/05/23 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class TimeSpanConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TimeSpan); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new TimeSpanConverter(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: TimeSpanConverterFactory.cs * + * Created: 2020/05/23 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class TimeSpanConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TimeSpan); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new TimeSpanConverter(); + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/WorldLocationConverter.cs b/Projects/Server/JsonConfiguration/Converters/WorldLocationConverter.cs index 79114b8d2..75e807f2c 100644 --- a/Projects/Server/JsonConfiguration/Converters/WorldLocationConverter.cs +++ b/Projects/Server/JsonConfiguration/Converters/WorldLocationConverter.cs @@ -1,160 +1,160 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: WorldLocationConverter.cs * - * Created: 2020/05/31 - Updated: 2020/05/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class WorldLocationConverter : JsonConverter - { - private WorldLocation DeserializeArray(ref Utf8JsonReader reader) - { - Span data = stackalloc int[3]; - var count = 0; - bool hasMap = false; - Map map = null; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndArray) - break; - - if (reader.TokenType == JsonTokenType.Number) - { - if (count < 3) - data[count] = reader.GetInt32(); - else if (count == 3) - map = Map.Maps[reader.GetInt32()]; - - count++; - } - - if (reader.TokenType == JsonTokenType.String) - { - map = Map.Parse(reader.GetString()); - break; - } - } - - if (!hasMap || count < 3 || count > 4) - throw new JsonException("WorldLocation must be an array of x, y, z, and map"); - - return new WorldLocation(data[0], data[1], data[2], map); - } - - private WorldLocation DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options) - { - Span data = stackalloc int[3]; - int count = 0; - bool hasLoc = false; - bool hasXYZ = false; - bool hasMap = false; - Map map = null; - - while (true) - { - reader.Read(); - if (reader.TokenType == JsonTokenType.EndObject) - break; - - if (reader.TokenType != JsonTokenType.PropertyName) - throw new JsonException("Invalid Json structure for WorldLocation object"); - - var key = reader.GetString(); - - var i = key switch - { - "x" => 0, - "y" => 1, - "z" => 2, - "loc" => 3, - "map" => 4, - _ => 5 - }; - - if (i == 5) - continue; - - reader.Read(); - - if (i < 3) - { - if (hasLoc) - throw new JsonException("WorldLocation must have loc or x, y, z, but not both"); - - if (reader.TokenType != JsonTokenType.Number) - throw new JsonException($"Value for {key} must be a number"); - - hasXYZ = true; - data[i] = reader.GetInt32(); - continue; - } - - if (i == 3) - { - if (hasXYZ) - throw new JsonException("WorldLocation must have loc or x, y, z, but not both"); - - hasLoc = true; - Point3D loc = new Point3DConverter().Read(ref reader, typeof(Point3D), options); - data[0] = loc.X; - data[1] = loc.Y; - data[2] = loc.Z; - count = 3; - continue; - } - - map = reader.TokenType switch - { - JsonTokenType.String => Map.Parse(reader.GetString()), - JsonTokenType.Number => Map.Maps[reader.GetInt32()], - _ => throw new JsonException($"Value for {key} must be a number or string") - }; - } - - if (!hasMap || count < 2) - throw new JsonException("WorldLocation must have an x, y, z, and map properties"); - - return new WorldLocation(data[0], data[1], data[2], map); - } - - public override WorldLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => - reader.TokenType switch - { - JsonTokenType.StartArray => DeserializeArray(ref reader), - JsonTokenType.StartObject => DeserializeObj(ref reader, options), - _ => throw new JsonException("Invalid Json for Point3D") - }; - - public override void Write(Utf8JsonWriter writer, WorldLocation value, JsonSerializerOptions options) - { - writer.WriteStartArray(); - writer.WriteNumberValue(value.X); - writer.WriteNumberValue(value.Y); - writer.WriteNumberValue(value.Z); - writer.WriteStringValue(value.Map.ToString()); - writer.WriteEndArray(); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: WorldLocationConverter.cs * + * Created: 2020/05/31 - Updated: 2020/05/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class WorldLocationConverter : JsonConverter + { + private WorldLocation DeserializeArray(ref Utf8JsonReader reader) + { + Span data = stackalloc int[3]; + var count = 0; + var hasMap = false; + Map map = null; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndArray) + break; + + if (reader.TokenType == JsonTokenType.Number) + { + if (count < 3) + data[count] = reader.GetInt32(); + else if (count == 3) + map = Map.Maps[reader.GetInt32()]; + + count++; + } + + if (reader.TokenType == JsonTokenType.String) + { + map = Map.Parse(reader.GetString()); + break; + } + } + + if (!hasMap || count < 3 || count > 4) + throw new JsonException("WorldLocation must be an array of x, y, z, and map"); + + return new WorldLocation(data[0], data[1], data[2], map); + } + + private WorldLocation DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + Span data = stackalloc int[3]; + var count = 0; + var hasLoc = false; + var hasXYZ = false; + var hasMap = false; + Map map = null; + + while (true) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.EndObject) + break; + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Invalid Json structure for WorldLocation object"); + + var key = reader.GetString(); + + var i = key switch + { + "x" => 0, + "y" => 1, + "z" => 2, + "loc" => 3, + "map" => 4, + _ => 5 + }; + + if (i == 5) + continue; + + reader.Read(); + + if (i < 3) + { + if (hasLoc) + throw new JsonException("WorldLocation must have loc or x, y, z, but not both"); + + if (reader.TokenType != JsonTokenType.Number) + throw new JsonException($"Value for {key} must be a number"); + + hasXYZ = true; + data[i] = reader.GetInt32(); + continue; + } + + if (i == 3) + { + if (hasXYZ) + throw new JsonException("WorldLocation must have loc or x, y, z, but not both"); + + hasLoc = true; + var loc = new Point3DConverter().Read(ref reader, typeof(Point3D), options); + data[0] = loc.X; + data[1] = loc.Y; + data[2] = loc.Z; + count = 3; + continue; + } + + map = reader.TokenType switch + { + JsonTokenType.String => Map.Parse(reader.GetString()), + JsonTokenType.Number => Map.Maps[reader.GetInt32()], + _ => throw new JsonException($"Value for {key} must be a number or string") + }; + } + + if (!hasMap || count < 2) + throw new JsonException("WorldLocation must have an x, y, z, and map properties"); + + return new WorldLocation(data[0], data[1], data[2], map); + } + + public override WorldLocation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + reader.TokenType switch + { + JsonTokenType.StartArray => DeserializeArray(ref reader), + JsonTokenType.StartObject => DeserializeObj(ref reader, options), + _ => throw new JsonException("Invalid Json for Point3D") + }; + + public override void Write(Utf8JsonWriter writer, WorldLocation value, JsonSerializerOptions options) + { + writer.WriteStartArray(); + writer.WriteNumberValue(value.X); + writer.WriteNumberValue(value.Y); + writer.WriteNumberValue(value.Z); + writer.WriteStringValue(value.Map.ToString()); + writer.WriteEndArray(); + } + } +} diff --git a/Projects/Server/JsonConfiguration/Converters/WorldLocationConverterFactory.cs b/Projects/Server/JsonConfiguration/Converters/WorldLocationConverterFactory.cs index 0aea4e21c..968a16962 100644 --- a/Projects/Server/JsonConfiguration/Converters/WorldLocationConverterFactory.cs +++ b/Projects/Server/JsonConfiguration/Converters/WorldLocationConverterFactory.cs @@ -1,34 +1,35 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: WorldLocationConverterFactory.cs * - * Created: 2020/05/31 - Updated: 2020/05/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class WorldLocationConverterFactory : JsonConverterFactory - { - public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(WorldLocation); - - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new WorldLocationConverter(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: WorldLocationConverterFactory.cs * + * Created: 2020/05/31 - Updated: 2020/05/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class WorldLocationConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(WorldLocation); + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + new WorldLocationConverter(); + } +} diff --git a/Projects/Server/JsonConfiguration/DynamicJson.cs b/Projects/Server/JsonConfiguration/DynamicJson.cs index c7f528e9f..b85bd4eb1 100644 --- a/Projects/Server/JsonConfiguration/DynamicJson.cs +++ b/Projects/Server/JsonConfiguration/DynamicJson.cs @@ -1,57 +1,55 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: DynamicJson.cs - Created: 2020/05/23 - Updated: 2020/05/23 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public class DynamicJson - { - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonExtensionData] - public Dictionary data { get; set; } - - public bool GetProperty(string key, JsonSerializerOptions options, out T t) - { - if (data.TryGetValue(key, out var el)) - { - t = el.ToObject(options); - return true; - } - - t = default; - return false; - } - - public bool GetEnumProperty(string key, JsonSerializerOptions options, out T t) where T : struct, Enum - { - if (data.TryGetValue(key, out var el)) - return Enum.TryParse(el.ToObject(options), out t); - - t = default; - return false; - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: DynamicJson.cs - Created: 2020/05/23 - Updated: 2020/05/23 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public class DynamicJson + { + [JsonPropertyName("type")] public string Type { get; set; } + + [JsonExtensionData] public Dictionary data { get; set; } + + public bool GetProperty(string key, JsonSerializerOptions options, out T t) + { + if (data.TryGetValue(key, out var el)) + { + t = el.ToObject(options); + return true; + } + + t = default; + return false; + } + + public bool GetEnumProperty(string key, JsonSerializerOptions options, out T t) where T : struct, Enum + { + if (data.TryGetValue(key, out var el)) + return Enum.TryParse(el.ToObject(options), out t); + + t = default; + return false; + } + } +} diff --git a/Projects/Server/JsonConfiguration/JsonConfig.cs b/Projects/Server/JsonConfiguration/JsonConfig.cs index b3858bf72..db63ecb9b 100644 --- a/Projects/Server/JsonConfiguration/JsonConfig.cs +++ b/Projects/Server/JsonConfiguration/JsonConfig.cs @@ -1,87 +1,90 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: JsonConfig.cs - Created: 2020/05/02 - Updated: 2020/05/02 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Buffers; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Server.Json -{ - public static class JsonConfig - { - public static readonly JsonSerializerOptions DefaultOptions = GetOptions(); - - public static JsonSerializerOptions GetOptions(params JsonConverterFactory[] converters) - { - // In the future this should be optimized by cloning DefaultOptions - var options = new JsonSerializerOptions - { - ReadCommentHandling = JsonCommentHandling.Skip, - WriteIndented = true, - AllowTrailingCommas = true, - IgnoreNullValues = true - }; - - options.Converters.Add(new MapConverterFactory()); - options.Converters.Add(new Point3DConverterFactory()); - options.Converters.Add(new Rectangle3DConverterFactory()); - options.Converters.Add(new TimeSpanConverterFactory()); - options.Converters.Add(new IPEndPointConverterFactory()); - - for (int i = 0; i < converters.Length; i++) options.Converters.Add(converters[i]); - - return options; - } - - public static T Deserialize(string filePath, JsonSerializerOptions options = null) - { - if (!File.Exists(filePath)) return default; - string text = File.ReadAllText(filePath, Utility.UTF8); - return JsonSerializer.Deserialize(text, options ?? DefaultOptions); - } - - public static void Serialize(string filePath, object value, JsonSerializerOptions options = null) - { - if (File.Exists(filePath)) File.Delete(filePath); - - File.WriteAllText(filePath, JsonSerializer.Serialize(value, options ?? DefaultOptions)); - } - - public static T ToObject(this ref Utf8JsonReader reader, JsonSerializerOptions options = null) => - JsonSerializer.Deserialize(ref reader, options); - - public static T ToObject(this JsonElement element, JsonSerializerOptions options = null) - { - var bufferWriter = new ArrayBufferWriter(); - using (var writer = new Utf8JsonWriter(bufferWriter)) - element.WriteTo(writer); - return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, options); - } - - public static T ToObject(this JsonDocument document, JsonSerializerOptions options = null) - { - if (document == null) - throw new ArgumentNullException(nameof(document)); - return document.RootElement.ToObject(options); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: JsonConfig.cs - Created: 2020/05/02 - Updated: 2020/05/02 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Buffers; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Server.Json +{ + public static class JsonConfig + { + public static readonly JsonSerializerOptions DefaultOptions = GetOptions(); + + public static JsonSerializerOptions GetOptions(params JsonConverterFactory[] converters) + { + // In the future this should be optimized by cloning DefaultOptions + var options = new JsonSerializerOptions + { + ReadCommentHandling = JsonCommentHandling.Skip, + WriteIndented = true, + AllowTrailingCommas = true, + IgnoreNullValues = true + }; + + options.Converters.Add(new MapConverterFactory()); + options.Converters.Add(new Point3DConverterFactory()); + options.Converters.Add(new Rectangle3DConverterFactory()); + options.Converters.Add(new TimeSpanConverterFactory()); + options.Converters.Add(new IPEndPointConverterFactory()); + + for (var i = 0; i < converters.Length; i++) options.Converters.Add(converters[i]); + + return options; + } + + public static T Deserialize(string filePath, JsonSerializerOptions options = null) + { + if (!File.Exists(filePath)) return default; + var text = File.ReadAllText(filePath, Utility.UTF8); + return JsonSerializer.Deserialize(text, options ?? DefaultOptions); + } + + public static void Serialize(string filePath, object value, JsonSerializerOptions options = null) + { + if (File.Exists(filePath)) File.Delete(filePath); + + File.WriteAllText(filePath, JsonSerializer.Serialize(value, options ?? DefaultOptions)); + } + + public static T ToObject(this ref Utf8JsonReader reader, JsonSerializerOptions options = null) => + JsonSerializer.Deserialize(ref reader, options); + + public static T ToObject(this JsonElement element, JsonSerializerOptions options = null) + { + var bufferWriter = new ArrayBufferWriter(); + using (var writer = new Utf8JsonWriter(bufferWriter)) + { + element.WriteTo(writer); + } + + return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, options); + } + + public static T ToObject(this JsonDocument document, JsonSerializerOptions options = null) + { + if (document == null) + throw new ArgumentNullException(nameof(document)); + return document.RootElement.ToObject(options); + } + } +} diff --git a/Projects/Server/KeywordList.cs b/Projects/Server/KeywordList.cs index 871e1e8e5..fd28d50d3 100644 --- a/Projects/Server/KeywordList.cs +++ b/Projects/Server/KeywordList.cs @@ -1,75 +1,77 @@ -/*************************************************************************** - * KeywordList.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server -{ - public class KeywordList - { - private static readonly int[] m_EmptyInts = System.Array.Empty(); - private int[] m_Keywords; - - public KeywordList() - { - m_Keywords = new int[8]; - Count = 0; - } - - public int Count { get; private set; } - - public bool Contains(int keyword) - { - var contains = false; - - for (var i = 0; !contains && i < Count; ++i) - contains = keyword == m_Keywords[i]; - - return contains; - } - - public void Add(int keyword) - { - if (Count + 1 > m_Keywords.Length) - { - var old = m_Keywords; - m_Keywords = new int[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - m_Keywords[i] = old[i]; - } - - m_Keywords[Count++] = keyword; - } - - public int[] ToArray() - { - if (Count == 0) - return m_EmptyInts; - - var keywords = new int[Count]; - - for (var i = 0; i < Count; ++i) - keywords[i] = m_Keywords[i]; - - Count = 0; - - return keywords; - } - } -} +/*************************************************************************** + * KeywordList.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server +{ + public class KeywordList + { + private static readonly int[] m_EmptyInts = Array.Empty(); + private int[] m_Keywords; + + public KeywordList() + { + m_Keywords = new int[8]; + Count = 0; + } + + public int Count { get; private set; } + + public bool Contains(int keyword) + { + var contains = false; + + for (var i = 0; !contains && i < Count; ++i) + contains = keyword == m_Keywords[i]; + + return contains; + } + + public void Add(int keyword) + { + if (Count + 1 > m_Keywords.Length) + { + var old = m_Keywords; + m_Keywords = new int[old.Length * 2]; + + for (var i = 0; i < old.Length; ++i) + m_Keywords[i] = old[i]; + } + + m_Keywords[Count++] = keyword; + } + + public int[] ToArray() + { + if (Count == 0) + return m_EmptyInts; + + var keywords = new int[Count]; + + for (var i = 0; i < Count; ++i) + keywords[i] = m_Keywords[i]; + + Count = 0; + + return keywords; + } + } +} diff --git a/Projects/Server/Layer.cs b/Projects/Server/Layer.cs index 58c00f41d..3d37d7140 100644 --- a/Projects/Server/Layer.cs +++ b/Projects/Server/Layer.cs @@ -1,193 +1,193 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Layer.cs - Created: 2019/03/15 - Updated: 2020/01/19 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server -{ - /// - /// Enumeration of item layer values. - /// - public enum Layer : byte - { - /// - /// Invalid layer. - /// - Invalid = 0x00, - - /// - /// First valid layer. Equivalent to Layer.OneHanded. - /// - FirstValid = 0x01, - - /// - /// One handed weapon. - /// - OneHanded = 0x01, - - /// - /// Two handed weapon or shield. - /// - TwoHanded = 0x02, - - /// - /// Shoes. - /// - Shoes = 0x03, - - /// - /// Pants. - /// - Pants = 0x04, - - /// - /// Shirts. - /// - Shirt = 0x05, - - /// - /// Helmets, hats, and masks. - /// - Helm = 0x06, - - /// - /// Gloves. - /// - Gloves = 0x07, - - /// - /// Rings. - /// - Ring = 0x08, - - /// - /// Talismans. - /// - Talisman = 0x09, - - /// - /// Gorgets and necklaces. - /// - Neck = 0x0A, - - /// - /// Hair. - /// - Hair = 0x0B, - - /// - /// Half aprons. - /// - Waist = 0x0C, - - /// - /// Torso, inner layer. - /// - InnerTorso = 0x0D, - - /// - /// Bracelets. - /// - Bracelet = 0x0E, - - /// - /// Unused. - /// - Unused_xF = 0x0F, - - /// - /// Beards and mustaches. - /// - FacialHair = 0x10, - - /// - /// Torso, outer layer. - /// - MiddleTorso = 0x11, - - /// - /// Earings. - /// - Earrings = 0x12, - - /// - /// Arms and sleeves. - /// - Arms = 0x13, - - /// - /// Cloaks. - /// - Cloak = 0x14, - - /// - /// Backpacks. - /// - Backpack = 0x15, - - /// - /// Torso, outer layer. - /// - OuterTorso = 0x16, - - /// - /// Leggings, outer layer. - /// - OuterLegs = 0x17, - - /// - /// Leggings, inner layer. - /// - InnerLegs = 0x18, - - /// - /// Last valid non-internal layer. Equivalent to Layer.InnerLegs. - /// - LastUserValid = 0x18, - - /// - /// Mount item layer. - /// - Mount = 0x19, - - /// - /// Vendor 'buy pack' layer. - /// - ShopBuy = 0x1A, - - /// - /// Vendor 'resale pack' layer. - /// - ShopResale = 0x1B, - - /// - /// Vendor 'sell pack' layer. - /// - ShopSell = 0x1C, - - /// - /// Bank box layer. - /// - Bank = 0x1D, - - /// - /// Last valid layer. Equivalent to Layer.Bank. - /// - LastValid = 0x1D - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Layer.cs - Created: 2019/03/15 - Updated: 2020/01/19 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server +{ + /// + /// Enumeration of item layer values. + /// + public enum Layer : byte + { + /// + /// Invalid layer. + /// + Invalid = 0x00, + + /// + /// First valid layer. Equivalent to Layer.OneHanded. + /// + FirstValid = 0x01, + + /// + /// One handed weapon. + /// + OneHanded = 0x01, + + /// + /// Two handed weapon or shield. + /// + TwoHanded = 0x02, + + /// + /// Shoes. + /// + Shoes = 0x03, + + /// + /// Pants. + /// + Pants = 0x04, + + /// + /// Shirts. + /// + Shirt = 0x05, + + /// + /// Helmets, hats, and masks. + /// + Helm = 0x06, + + /// + /// Gloves. + /// + Gloves = 0x07, + + /// + /// Rings. + /// + Ring = 0x08, + + /// + /// Talismans. + /// + Talisman = 0x09, + + /// + /// Gorgets and necklaces. + /// + Neck = 0x0A, + + /// + /// Hair. + /// + Hair = 0x0B, + + /// + /// Half aprons. + /// + Waist = 0x0C, + + /// + /// Torso, inner layer. + /// + InnerTorso = 0x0D, + + /// + /// Bracelets. + /// + Bracelet = 0x0E, + + /// + /// Unused. + /// + Unused_xF = 0x0F, + + /// + /// Beards and mustaches. + /// + FacialHair = 0x10, + + /// + /// Torso, outer layer. + /// + MiddleTorso = 0x11, + + /// + /// Earings. + /// + Earrings = 0x12, + + /// + /// Arms and sleeves. + /// + Arms = 0x13, + + /// + /// Cloaks. + /// + Cloak = 0x14, + + /// + /// Backpacks. + /// + Backpack = 0x15, + + /// + /// Torso, outer layer. + /// + OuterTorso = 0x16, + + /// + /// Leggings, outer layer. + /// + OuterLegs = 0x17, + + /// + /// Leggings, inner layer. + /// + InnerLegs = 0x18, + + /// + /// Last valid non-internal layer. Equivalent to Layer.InnerLegs. + /// + LastUserValid = 0x18, + + /// + /// Mount item layer. + /// + Mount = 0x19, + + /// + /// Vendor 'buy pack' layer. + /// + ShopBuy = 0x1A, + + /// + /// Vendor 'resale pack' layer. + /// + ShopResale = 0x1B, + + /// + /// Vendor 'sell pack' layer. + /// + ShopSell = 0x1C, + + /// + /// Bank box layer. + /// + Bank = 0x1D, + + /// + /// Last valid layer. Equivalent to Layer.Bank. + /// + LastValid = 0x1D + } +} diff --git a/Projects/Server/LightType.cs b/Projects/Server/LightType.cs index 89596acc2..185786620 100644 --- a/Projects/Server/LightType.cs +++ b/Projects/Server/LightType.cs @@ -1,307 +1,308 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: LightType.cs - Created: 2019/03/15 - Updated: 2020/01/19 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server -{ - public enum LightType - { - /// - /// Window shape, arched, ray shining east. - /// - ArchedWindowEast, - - /// - /// Medium circular shape. - /// - Circle225, - - /// - /// Small circular shape. - /// - Circle150, - - /// - /// Door shape, shining south. - /// - DoorSouth, - - /// - /// Door shape, shining east. - /// - DoorEast, - - /// - /// Large semicircular shape (180 degrees), north wall. - /// - NorthBig, - - /// - /// Large pie shape (90 degrees), north-east corner. - /// - NorthEastBig, - - /// - /// Large semicircular shape (180 degrees), east wall. - /// - EastBig, - - /// - /// Large semicircular shape (180 degrees), west wall. - /// - WestBig, - - /// - /// Large pie shape (90 degrees), south-west corner. - /// - SouthWestBig, - - /// - /// Large semicircular shape (180 degrees), south wall. - /// - SouthBig, - - /// - /// Medium semicircular shape (180 degrees), north wall. - /// - NorthSmall, - - /// - /// Medium pie shape (90 degrees), north-east corner. - /// - NorthEastSmall, - - /// - /// Medium semicircular shape (180 degrees), east wall. - /// - EastSmall, - - /// - /// Medium semicircular shape (180 degrees), west wall. - /// - WestSmall, - - /// - /// Medium semicircular shape (180 degrees), south wall. - /// - SouthSmall, - - /// - /// Shaped like a wall decoration, north wall. - /// - DecorationNorth, - - /// - /// Shaped like a wall decoration, north-east corner. - /// - DecorationNorthEast, - - /// - /// Small semicircular shape (180 degrees), east wall. - /// - EastTiny, - - /// - /// Shaped like a wall decoration, west wall. - /// - DecorationWest, - - /// - /// Shaped like a wall decoration, south-west corner. - /// - DecorationSouthWest, - - /// - /// Small semicircular shape (180 degrees), south wall. - /// - SouthTiny, - - /// - /// Window shape, rectangular, no ray, shining south. - /// - RectWindowSouthNoRay, - - /// - /// Window shape, rectangular, no ray, shining east. - /// - RectWindowEastNoRay, - - /// - /// Window shape, rectangular, ray shining south. - /// - RectWindowSouth, - - /// - /// Window shape, rectangular, ray shining east. - /// - RectWindowEast, - - /// - /// Window shape, arched, no ray, shining south. - /// - ArchedWindowSouthNoRay, - - /// - /// Window shape, arched, no ray, shining east. - /// - ArchedWindowEastNoRay, - - /// - /// Window shape, arched, ray shining south. - /// - ArchedWindowSouth, - - /// - /// Large circular shape. - /// - Circle300, - - /// - /// Large pie shape (90 degrees), north-west corner. - /// - NorthWestBig, - - /// - /// Negative light. Medium pie shape (90 degrees), south-east corner. - /// - DarkSouthEast, - - /// - /// Negative light. Medium semicircular shape (180 degrees), south wall. - /// - DarkSouth, - - /// - /// Negative light. Medium pie shape (90 degrees), north-west corner. - /// - DarkNorthWest, - - /// - /// Negative light. Medium pie shape (90 degrees), south-east corner. Equivalent to LightType.SouthEast. - /// - DarkSouthEast2, - - /// - /// Negative light. Medium circular shape (180 degrees), east wall. - /// - DarkEast, - - /// - /// Negative light. Large circular shape. - /// - DarkCircle300, - - /// - /// Opened door shape, shining south. - /// - DoorOpenSouth, - - /// - /// Opened door shape, shining east. - /// - DoorOpenEast, - - /// - /// Window shape, square, ray shining east. - /// - SquareWindowEast, - - /// - /// Window shape, square, no ray, shining east. - /// - SquareWindowEastNoRay, - - /// - /// Window shape, square, ray shining south. - /// - SquareWindowSouth, - - /// - /// Window shape, square, no ray, shining south. - /// - SquareWindowSouthNoRay, - - /// - /// Empty. - /// - Empty, - - /// - /// Window shape, skinny, no ray, shining south. - /// - SkinnyWindowSouthNoRay, - - /// - /// Window shape, skinny, ray shining east. - /// - SkinnyWindowEast, - - /// - /// Window shape, skinny, no ray, shining east. - /// - SkinnyWindowEastNoRay, - - /// - /// Shaped like a hole, shining south. - /// - HoleSouth, - - /// - /// Shaped like a hole, shining south. - /// - HoleEast, - - /// - /// Large circular shape with a moongate graphic embedded. - /// - Moongate, - - /// - /// Unknown usage. Many rows of slightly angled lines. - /// - Strips, - - /// - /// Shaped like a small hole, shining south. - /// - SmallHoleSouth, - - /// - /// Shaped like a small hole, shining east. - /// - SmallHoleEast, - - /// - /// Large semicircular shape (180 degrees), north wall. Identical graphic as LightType.NorthBig, but slightly different - /// positioning. - /// - NorthBig2, - - /// - /// Large semicircular shape (180 degrees), west wall. Identical graphic as LightType.WestBig, but slightly different - /// positioning. - /// - WestBig2, - - /// - /// Large pie shape (90 degrees), north-west corner. Equivalent to LightType.NorthWestBig. - /// - NorthWestBig2 - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: LightType.cs - Created: 2019/03/15 - Updated: 2020/01/19 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server +{ + public enum LightType + { + /// + /// Window shape, arched, ray shining east. + /// + ArchedWindowEast, + + /// + /// Medium circular shape. + /// + Circle225, + + /// + /// Small circular shape. + /// + Circle150, + + /// + /// Door shape, shining south. + /// + DoorSouth, + + /// + /// Door shape, shining east. + /// + DoorEast, + + /// + /// Large semicircular shape (180 degrees), north wall. + /// + NorthBig, + + /// + /// Large pie shape (90 degrees), north-east corner. + /// + NorthEastBig, + + /// + /// Large semicircular shape (180 degrees), east wall. + /// + EastBig, + + /// + /// Large semicircular shape (180 degrees), west wall. + /// + WestBig, + + /// + /// Large pie shape (90 degrees), south-west corner. + /// + SouthWestBig, + + /// + /// Large semicircular shape (180 degrees), south wall. + /// + SouthBig, + + /// + /// Medium semicircular shape (180 degrees), north wall. + /// + NorthSmall, + + /// + /// Medium pie shape (90 degrees), north-east corner. + /// + NorthEastSmall, + + /// + /// Medium semicircular shape (180 degrees), east wall. + /// + EastSmall, + + /// + /// Medium semicircular shape (180 degrees), west wall. + /// + WestSmall, + + /// + /// Medium semicircular shape (180 degrees), south wall. + /// + SouthSmall, + + /// + /// Shaped like a wall decoration, north wall. + /// + DecorationNorth, + + /// + /// Shaped like a wall decoration, north-east corner. + /// + DecorationNorthEast, + + /// + /// Small semicircular shape (180 degrees), east wall. + /// + EastTiny, + + /// + /// Shaped like a wall decoration, west wall. + /// + DecorationWest, + + /// + /// Shaped like a wall decoration, south-west corner. + /// + DecorationSouthWest, + + /// + /// Small semicircular shape (180 degrees), south wall. + /// + SouthTiny, + + /// + /// Window shape, rectangular, no ray, shining south. + /// + RectWindowSouthNoRay, + + /// + /// Window shape, rectangular, no ray, shining east. + /// + RectWindowEastNoRay, + + /// + /// Window shape, rectangular, ray shining south. + /// + RectWindowSouth, + + /// + /// Window shape, rectangular, ray shining east. + /// + RectWindowEast, + + /// + /// Window shape, arched, no ray, shining south. + /// + ArchedWindowSouthNoRay, + + /// + /// Window shape, arched, no ray, shining east. + /// + ArchedWindowEastNoRay, + + /// + /// Window shape, arched, ray shining south. + /// + ArchedWindowSouth, + + /// + /// Large circular shape. + /// + Circle300, + + /// + /// Large pie shape (90 degrees), north-west corner. + /// + NorthWestBig, + + /// + /// Negative light. Medium pie shape (90 degrees), south-east corner. + /// + DarkSouthEast, + + /// + /// Negative light. Medium semicircular shape (180 degrees), south wall. + /// + DarkSouth, + + /// + /// Negative light. Medium pie shape (90 degrees), north-west corner. + /// + DarkNorthWest, + + /// + /// Negative light. Medium pie shape (90 degrees), south-east corner. Equivalent to LightType.SouthEast. + /// + DarkSouthEast2, + + /// + /// Negative light. Medium circular shape (180 degrees), east wall. + /// + DarkEast, + + /// + /// Negative light. Large circular shape. + /// + DarkCircle300, + + /// + /// Opened door shape, shining south. + /// + DoorOpenSouth, + + /// + /// Opened door shape, shining east. + /// + DoorOpenEast, + + /// + /// Window shape, square, ray shining east. + /// + SquareWindowEast, + + /// + /// Window shape, square, no ray, shining east. + /// + SquareWindowEastNoRay, + + /// + /// Window shape, square, ray shining south. + /// + SquareWindowSouth, + + /// + /// Window shape, square, no ray, shining south. + /// + SquareWindowSouthNoRay, + + /// + /// Empty. + /// + Empty, + + /// + /// Window shape, skinny, no ray, shining south. + /// + SkinnyWindowSouthNoRay, + + /// + /// Window shape, skinny, ray shining east. + /// + SkinnyWindowEast, + + /// + /// Window shape, skinny, no ray, shining east. + /// + SkinnyWindowEastNoRay, + + /// + /// Shaped like a hole, shining south. + /// + HoleSouth, + + /// + /// Shaped like a hole, shining south. + /// + HoleEast, + + /// + /// Large circular shape with a moongate graphic embedded. + /// + Moongate, + + /// + /// Unknown usage. Many rows of slightly angled lines. + /// + Strips, + + /// + /// Shaped like a small hole, shining south. + /// + SmallHoleSouth, + + /// + /// Shaped like a small hole, shining east. + /// + SmallHoleEast, + + /// + /// Large semicircular shape (180 degrees), north wall. Identical graphic as LightType.NorthBig, but slightly + /// different + /// positioning. + /// + NorthBig2, + + /// + /// Large semicircular shape (180 degrees), west wall. Identical graphic as LightType.WestBig, but slightly different + /// positioning. + /// + WestBig2, + + /// + /// Large pie shape (90 degrees), north-west corner. Equivalent to LightType.NorthWestBig. + /// + NorthWestBig2 + } +} diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs index 31854a0ad..866f44a25 100644 --- a/Projects/Server/Main.cs +++ b/Projects/Server/Main.cs @@ -1,549 +1,563 @@ -/*************************************************************************** - * Main.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Server.Json; -using Server.Network; - -namespace Server -{ - public static class Core - { - private static bool m_Crashed; - private static Thread timerThread; - private static string m_BaseDirectory; - private static string m_ExePath; - - private static bool m_Profiling; - private static DateTime m_ProfileStart; - private static TimeSpan m_ProfileTime; - private static bool? m_IsRunningFromXUnit; - - public static bool IsRunningFromXUnit => - m_IsRunningFromXUnit ??= AppDomain.CurrentDomain.GetAssemblies().Any( - a => a.FullName?.ToLowerInvariant().StartsWith("xunit") ?? false); - - /* - * DateTime.Now and DateTime.UtcNow are based on actual system clock time. - * The resolution is acceptable but large clock jumps are possible and cause issues. - * GetTickCount and GetTickCount64 have poor resolution. - * Stopwatch.GetTimestamp() (QueryPerformanceCounter) is high resolution, but - * somewhat expensive to call because of its difference to DateTime.Now, - * which is why Stopwatch has been used to verify HRT before calling GetTimestamp(), - * enabling the usage of DateTime.UtcNow instead. - */ - - private static readonly double m_HighFrequency = 1000.0 / Stopwatch.Frequency; - private static readonly double m_LowFrequency = 1000.0 / TimeSpan.TicksPerSecond; - - internal static ConsoleEventHandler m_ConsoleEventHandler; - - private static int m_CycleIndex = 1; - private static readonly float[] m_CyclesPerSecond = new float[100]; - - private static readonly AutoResetEvent m_Signal = new AutoResetEvent(true); - - private static int m_ItemCount, m_MobileCount; - - private static readonly Type[] m_SerialTypeArray = { typeof(Serial) }; - - public static bool Profiling - { - get => m_Profiling; - set - { - if (m_Profiling == value) - return; - - m_Profiling = value; - - if (m_ProfileStart > DateTime.MinValue) - m_ProfileTime += DateTime.UtcNow - m_ProfileStart; - - m_ProfileStart = m_Profiling ? DateTime.UtcNow : DateTime.MinValue; - } - } - - public static TimeSpan ProfileTime - { - get - { - if (m_ProfileStart > DateTime.MinValue) - return m_ProfileTime + (DateTime.UtcNow - m_ProfileStart); - - return m_ProfileTime; - } - } - - internal static bool HaltOnWarning { get; private set; } - - public static Assembly Assembly { get; set; } - - public static Version Version => Assembly.GetName().Version; - public static Process Process { get; private set; } - - public static Thread Thread { get; private set; } - - public static long TickCount => (long)Ticks; - - public static double Ticks => - Stopwatch.IsHighResolution - ? Stopwatch.GetTimestamp() * m_HighFrequency - : DateTime.UtcNow.Ticks * m_LowFrequency; - - public static bool MultiProcessor { get; private set; } - - public static int ProcessorCount { get; private set; } - - public static bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - public static bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - public static bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); - public static bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD; - public static bool Unix = IsDarwin || IsFreeBSD || IsLinux; - - public static string ExePath => m_ExePath ??= Assembly.Location; - - public static string BaseDirectory - { - get - { - if (m_BaseDirectory == null) - try - { - m_BaseDirectory = ExePath; - - if (m_BaseDirectory.Length > 0) - m_BaseDirectory = Path.GetDirectoryName(m_BaseDirectory); - } - catch - { - m_BaseDirectory = ""; - } - - return m_BaseDirectory; - } - } - - public static bool Closing { get; private set; } - - public static float CyclesPerSecond => m_CyclesPerSecond[(m_CycleIndex - 1) % m_CyclesPerSecond.Length]; - - public static float AverageCPS => m_CyclesPerSecond.Take(m_CycleIndex).Average(); - - public static string Arguments - { - get - { - var sb = new StringBuilder(); - - if (m_Profiling) - Utility.Separate(sb, "-profile", " "); - - if (HaltOnWarning) - Utility.Separate(sb, "-haltonwarning", " "); - - return sb.ToString(); - } - } - - public static int GlobalUpdateRange { get; set; } = 18; - - public static int GlobalMaxUpdateRange { get; set; } = 24; - - public static int ScriptItems => m_ItemCount; - public static int ScriptMobiles => m_MobileCount; - - public static string FindDataFile(string path, bool throwNotFound = true, bool warnNotFound = false) - { - string fullPath = null; - - foreach (var p in ServerConfiguration.DataDirectories) - { - fullPath = Path.Combine(p, path); - - if (File.Exists(fullPath)) - break; - - fullPath = null; - } - - if (fullPath == null && (throwNotFound || warnNotFound)) - { - Utility.PushColor(ConsoleColor.Red); - Console.WriteLine($"Data: {path} was not found"); - Console.WriteLine("Make sure modernuo.json is properly configured"); - Utility.PopColor(); - if (throwNotFound) - throw new FileNotFoundException($"Data: {path} was not found"); - } - - return fullPath; - } - - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - Console.WriteLine(e.IsTerminating ? "Error:" : "Warning:"); - Console.WriteLine(e.ExceptionObject); - - if (e.IsTerminating) - { - m_Crashed = true; - - var close = false; - - try - { - var args = new ServerCrashedEventArgs(e.ExceptionObject as Exception); - - EventSink.InvokeServerCrashed(args); - - close = args.Close; - } - catch - { - // ignored - } - - if (!close) - { - try - { - // Close all listeners - } - catch - { - // ignored - } - - Console.WriteLine("This exception is fatal, press return to exit"); - Console.ReadLine(); - } - - Kill(); - } - } - - private static bool OnConsoleEvent(ConsoleEventType type) - { - if (World.Saving || type == ConsoleEventType.CTRL_LOGOFF_EVENT) - return true; - - Kill(); // Kill -> HandleClosed will handle waiting for the completion of flushing to disk - - return true; - } - - private static void CurrentDomain_ProcessExit(object sender, EventArgs e) - { - HandleClosed(); - } - - public static void Kill(bool restart = false) - { - HandleClosed(); - - if (restart) - Process.Start(ExePath, Arguments); - - Process.Kill(); - } - - private static void HandleClosed() - { - if (Closing) - return; - - Closing = true; - - Console.Write("Exiting..."); - - World.WaitForWriteCompletion(); - - if (!m_Crashed) - EventSink.InvokeShutdown(); - - Timer.TimerThread.Set(); - - Console.WriteLine("done"); - } - - public static void Set() - { - m_Signal.Set(); - } - - private static string assembliesConfiguration = "Configuration/assemblies.json"; - - public static void Main(string[] args) - { - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; - - foreach (var a in args) - if (Insensitive.Equals(a, "-profile")) - Profiling = true; - else if (Insensitive.Equals(a, "-haltonwarning")) - HaltOnWarning = true; - - Thread = Thread.CurrentThread; - Process = Process.GetCurrentProcess(); - Assembly = Assembly.GetEntryAssembly(); - - if (Assembly == null) - throw new Exception("Core: Assembly entry is missing."); - - if (Thread != null) - Thread.Name = "Core Thread"; - - if (BaseDirectory.Length > 0) - Directory.SetCurrentDirectory(BaseDirectory); - - var ver = Assembly.GetName().Version ?? new Version(); - - Utility.PushColor(ConsoleColor.Green); - // Added to help future code support on forums, as a 'check' people can ask for to it see if they recompiled core or not - Console.WriteLine("ModernUO - [https://github.com/modernuo/modernuo] Version {0}.{1}.{2}.{3}", ver.Major, - ver.Minor, ver.Build, - ver.Revision); - Console.WriteLine("Core: Running on {0}\n", RuntimeInformation.FrameworkDescription); - Utility.PopColor(); - - var ttObj = new Timer.TimerThread(); - timerThread = new Thread(ttObj.TimerMain) - { - Name = "Timer Thread" - }; - - var s = Arguments; - - if (s.Length > 0) - Console.WriteLine("Core: Running with arguments: {0}", s); - - ProcessorCount = Environment.ProcessorCount; - - if (ProcessorCount > 1) - MultiProcessor = true; - - if (MultiProcessor) - Console.WriteLine("Core: Optimizing for {0} processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s"); - - if (IsWindows) - { - m_ConsoleEventHandler = OnConsoleEvent; - UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true); - } - - if (GCSettings.IsServerGC) - Console.WriteLine("Core: Server garbage collection mode enabled"); - - Console.WriteLine("Core: High resolution timing ({0})", - Stopwatch.IsHighResolution ? "Supported" : "Unsupported"); - - ServerConfiguration.Load(); - - // Load UOContent.dll - string[] assemblyFiles = JsonConfig.Deserialize>( - Path.Join(BaseDirectory, assembliesConfiguration) - ).Select(t => Path.Join(BaseDirectory, "Assemblies", t)).ToArray(); - AssemblyHandler.LoadScripts(assemblyFiles); - - VerifySerialization(); - - AssemblyHandler.Invoke("Configure"); - - RegionLoader.LoadRegions(); - World.Load(); - - AssemblyHandler.Invoke("Initialize"); - - timerThread.Start(); - - foreach (var m in Map.AllMaps) - m.Tiles.Force(); - - EventSink.InvokeServerStarted(); - - // Start net socket server - var host = TcpServer.CreateWebHostBuilder().Build(); - var life = host.Services.GetRequiredService(); - life.ApplicationStopping.Register(() => { Kill(); }); - - host.Run(); - } - - public static void RunEventLoop(IMessagePumpService messagePumpService) - { - try - { - var last = TickCount; - - const int sampleInterval = 100; - const float ticksPerSecond = 1000.0f * sampleInterval; - - long sample = 0; - - while (!Closing) - { - m_Signal.WaitOne(); - - Task.WaitAll( - Task.Run(Mobile.ProcessDeltaQueue), - Task.Run(Item.ProcessDeltaQueue)); - - Timer.Slice(); - messagePumpService.DoWork(); - - NetState.ProcessDisposedQueue(); - - if (sample++ % sampleInterval != 0) - continue; - - var now = TickCount; - m_CyclesPerSecond[m_CycleIndex++ % m_CyclesPerSecond.Length] = ticksPerSecond / (now - last); - last = now; - } - } - catch (Exception e) - { - CurrentDomain_UnhandledException(null, new UnhandledExceptionEventArgs(e, true)); - } - } - - public static void VerifySerialization() - { - m_ItemCount = 0; - m_MobileCount = 0; - - var ca = Assembly.GetCallingAssembly(); - - VerifySerialization(ca); - - foreach (var a in AssemblyHandler.Assemblies) - if (a != ca) VerifySerialization(a); - } - - private static void VerifyType(Type t) - { - var isItem = t.IsSubclassOf(typeof(Item)); - - if (!isItem && !t.IsSubclassOf(typeof(Mobile))) return; - - if (isItem) - Interlocked.Increment(ref m_ItemCount); - else - Interlocked.Increment(ref m_MobileCount); - - StringBuilder warningSb = null; - - try - { - if (t.GetConstructor(m_SerialTypeArray) == null) - { - warningSb = new StringBuilder(); - warningSb.AppendLine(" - No serialization constructor"); - } - - if ( - t.GetMethod( - "Serialize", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) == - null) - { - warningSb ??= new StringBuilder(); - warningSb.AppendLine(" - No Serialize() method"); - } - - if (t.GetMethod( - "Deserialize", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly) == - null) - { - warningSb ??= new StringBuilder(); - warningSb.AppendLine(" - No Deserialize() method"); - } - - if (warningSb?.Length > 0) Console.WriteLine("Warning: {0}\n{1}", t, warningSb); - } - catch - { - Console.WriteLine("Warning: Exception in serialization verification of type {0}", t); - } - } - - private static void VerifySerialization(Assembly a) - { - if (a != null) Parallel.ForEach(a.GetTypes(), VerifyType); - } - - internal enum ConsoleEventType - { - CTRL_C_EVENT, - CTRL_BREAK_EVENT, - CTRL_CLOSE_EVENT, - CTRL_LOGOFF_EVENT = 5, - CTRL_SHUTDOWN_EVENT - } - - internal delegate bool ConsoleEventHandler(ConsoleEventType type); - - internal class UnsafeNativeMethods - { - [DllImport("Kernel32")] - internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler callback, bool add); - } - - public static Expansion Expansion { get; set; } - - public static bool T2A => Expansion >= Expansion.T2A; - - public static bool UOR => Expansion >= Expansion.UOR; - - public static bool UOTD => Expansion >= Expansion.UOTD; - - public static bool LBR => Expansion >= Expansion.LBR; - - public static bool AOS => Expansion >= Expansion.AOS; - - public static bool SE => Expansion >= Expansion.SE; - - public static bool ML => Expansion >= Expansion.ML; - - public static bool SA => Expansion >= Expansion.SA; - - public static bool HS => Expansion >= Expansion.HS; - - public static bool TOL => Expansion >= Expansion.TOL; - - public static bool EJ => Expansion >= Expansion.EJ; - } -} +/*************************************************************************** + * Main.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Server.Json; +using Server.Network; + +namespace Server +{ + public static class Core + { + private static bool m_Crashed; + private static Thread timerThread; + private static string m_BaseDirectory; + private static string m_ExePath; + + private static bool m_Profiling; + private static DateTime m_ProfileStart; + private static TimeSpan m_ProfileTime; + private static bool? m_IsRunningFromXUnit; + + /* + * DateTime.Now and DateTime.UtcNow are based on actual system clock time. + * The resolution is acceptable but large clock jumps are possible and cause issues. + * GetTickCount and GetTickCount64 have poor resolution. + * Stopwatch.GetTimestamp() (QueryPerformanceCounter) is high resolution, but + * somewhat expensive to call because of its difference to DateTime.Now, + * which is why Stopwatch has been used to verify HRT before calling GetTimestamp(), + * enabling the usage of DateTime.UtcNow instead. + */ + + private static readonly double m_HighFrequency = 1000.0 / Stopwatch.Frequency; + private static readonly double m_LowFrequency = 1000.0 / TimeSpan.TicksPerSecond; + + internal static ConsoleEventHandler m_ConsoleEventHandler; + + private static int m_CycleIndex = 1; + private static readonly float[] m_CyclesPerSecond = new float[100]; + + private static readonly AutoResetEvent m_Signal = new AutoResetEvent(true); + + private static int m_ItemCount, m_MobileCount; + + private static readonly Type[] m_SerialTypeArray = { typeof(Serial) }; + + public static bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + public static bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + public static bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); + public static bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD; + public static bool Unix = IsDarwin || IsFreeBSD || IsLinux; + + private static readonly string assembliesConfiguration = "Configuration/assemblies.json"; + + public static bool IsRunningFromXUnit => + m_IsRunningFromXUnit ??= AppDomain.CurrentDomain.GetAssemblies() + .Any( + a => a.FullName?.ToLowerInvariant().StartsWith("xunit") ?? false + ); + + public static bool Profiling + { + get => m_Profiling; + set + { + if (m_Profiling == value) + return; + + m_Profiling = value; + + if (m_ProfileStart > DateTime.MinValue) + m_ProfileTime += DateTime.UtcNow - m_ProfileStart; + + m_ProfileStart = m_Profiling ? DateTime.UtcNow : DateTime.MinValue; + } + } + + public static TimeSpan ProfileTime + { + get + { + if (m_ProfileStart > DateTime.MinValue) + return m_ProfileTime + (DateTime.UtcNow - m_ProfileStart); + + return m_ProfileTime; + } + } + + internal static bool HaltOnWarning { get; private set; } + + public static Assembly Assembly { get; set; } + + public static Version Version => Assembly.GetName().Version; + public static Process Process { get; private set; } + + public static Thread Thread { get; private set; } + + public static long TickCount => (long)Ticks; + + public static double Ticks => + Stopwatch.IsHighResolution + ? Stopwatch.GetTimestamp() * m_HighFrequency + : DateTime.UtcNow.Ticks * m_LowFrequency; + + public static bool MultiProcessor { get; private set; } + + public static int ProcessorCount { get; private set; } + + public static string ExePath => m_ExePath ??= Assembly.Location; + + public static string BaseDirectory + { + get + { + if (m_BaseDirectory == null) + try + { + m_BaseDirectory = ExePath; + + if (m_BaseDirectory.Length > 0) + m_BaseDirectory = Path.GetDirectoryName(m_BaseDirectory); + } + catch + { + m_BaseDirectory = ""; + } + + return m_BaseDirectory; + } + } + + public static bool Closing { get; private set; } + + public static float CyclesPerSecond => m_CyclesPerSecond[(m_CycleIndex - 1) % m_CyclesPerSecond.Length]; + + public static float AverageCPS => m_CyclesPerSecond.Take(m_CycleIndex).Average(); + + public static string Arguments + { + get + { + var sb = new StringBuilder(); + + if (m_Profiling) + Utility.Separate(sb, "-profile", " "); + + if (HaltOnWarning) + Utility.Separate(sb, "-haltonwarning", " "); + + return sb.ToString(); + } + } + + public static int GlobalUpdateRange { get; set; } = 18; + + public static int GlobalMaxUpdateRange { get; set; } = 24; + + public static int ScriptItems => m_ItemCount; + public static int ScriptMobiles => m_MobileCount; + + public static Expansion Expansion { get; set; } + + public static bool T2A => Expansion >= Expansion.T2A; + + public static bool UOR => Expansion >= Expansion.UOR; + + public static bool UOTD => Expansion >= Expansion.UOTD; + + public static bool LBR => Expansion >= Expansion.LBR; + + public static bool AOS => Expansion >= Expansion.AOS; + + public static bool SE => Expansion >= Expansion.SE; + + public static bool ML => Expansion >= Expansion.ML; + + public static bool SA => Expansion >= Expansion.SA; + + public static bool HS => Expansion >= Expansion.HS; + + public static bool TOL => Expansion >= Expansion.TOL; + + public static bool EJ => Expansion >= Expansion.EJ; + + public static string FindDataFile(string path, bool throwNotFound = true, bool warnNotFound = false) + { + string fullPath = null; + + foreach (var p in ServerConfiguration.DataDirectories) + { + fullPath = Path.Combine(p, path); + + if (File.Exists(fullPath)) + break; + + fullPath = null; + } + + if (fullPath == null && (throwNotFound || warnNotFound)) + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine($"Data: {path} was not found"); + Console.WriteLine("Make sure modernuo.json is properly configured"); + Utility.PopColor(); + if (throwNotFound) + throw new FileNotFoundException($"Data: {path} was not found"); + } + + return fullPath; + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Console.WriteLine(e.IsTerminating ? "Error:" : "Warning:"); + Console.WriteLine(e.ExceptionObject); + + if (e.IsTerminating) + { + m_Crashed = true; + + var close = false; + + try + { + var args = new ServerCrashedEventArgs(e.ExceptionObject as Exception); + + EventSink.InvokeServerCrashed(args); + + close = args.Close; + } + catch + { + // ignored + } + + if (!close) + { + try + { + // Close all listeners + } + catch + { + // ignored + } + + Console.WriteLine("This exception is fatal, press return to exit"); + Console.ReadLine(); + } + + Kill(); + } + } + + private static bool OnConsoleEvent(ConsoleEventType type) + { + if (World.Saving || type == ConsoleEventType.CTRL_LOGOFF_EVENT) + return true; + + Kill(); // Kill -> HandleClosed will handle waiting for the completion of flushing to disk + + return true; + } + + private static void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + HandleClosed(); + } + + public static void Kill(bool restart = false) + { + HandleClosed(); + + if (restart) + Process.Start(ExePath, Arguments); + + Process.Kill(); + } + + private static void HandleClosed() + { + if (Closing) + return; + + Closing = true; + + Console.Write("Exiting..."); + + World.WaitForWriteCompletion(); + + if (!m_Crashed) + EventSink.InvokeShutdown(); + + Timer.TimerThread.Set(); + + Console.WriteLine("done"); + } + + public static void Set() + { + m_Signal.Set(); + } + + public static void Main(string[] args) + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + + foreach (var a in args) + if (Insensitive.Equals(a, "-profile")) + Profiling = true; + else if (Insensitive.Equals(a, "-haltonwarning")) + HaltOnWarning = true; + + Thread = Thread.CurrentThread; + Process = Process.GetCurrentProcess(); + Assembly = Assembly.GetEntryAssembly(); + + if (Assembly == null) + throw new Exception("Core: Assembly entry is missing."); + + if (Thread != null) + Thread.Name = "Core Thread"; + + if (BaseDirectory.Length > 0) + Directory.SetCurrentDirectory(BaseDirectory); + + var ver = Assembly.GetName().Version ?? new Version(); + + Utility.PushColor(ConsoleColor.Green); + // Added to help future code support on forums, as a 'check' people can ask for to it see if they recompiled core or not + Console.WriteLine( + "ModernUO - [https://github.com/modernuo/modernuo] Version {0}.{1}.{2}.{3}", + ver.Major, + ver.Minor, + ver.Build, + ver.Revision + ); + Console.WriteLine("Core: Running on {0}\n", RuntimeInformation.FrameworkDescription); + Utility.PopColor(); + + var ttObj = new Timer.TimerThread(); + timerThread = new Thread(ttObj.TimerMain) + { + Name = "Timer Thread" + }; + + var s = Arguments; + + if (s.Length > 0) + Console.WriteLine("Core: Running with arguments: {0}", s); + + ProcessorCount = Environment.ProcessorCount; + + if (ProcessorCount > 1) + MultiProcessor = true; + + if (MultiProcessor) + Console.WriteLine("Core: Optimizing for {0} processor{1}", ProcessorCount, ProcessorCount == 1 ? "" : "s"); + + if (IsWindows) + { + m_ConsoleEventHandler = OnConsoleEvent; + UnsafeNativeMethods.SetConsoleCtrlHandler(m_ConsoleEventHandler, true); + } + + if (GCSettings.IsServerGC) + Console.WriteLine("Core: Server garbage collection mode enabled"); + + Console.WriteLine( + "Core: High resolution timing ({0})", + Stopwatch.IsHighResolution ? "Supported" : "Unsupported" + ); + + ServerConfiguration.Load(); + + // Load UOContent.dll + var assemblyFiles = JsonConfig.Deserialize>( + Path.Join(BaseDirectory, assembliesConfiguration) + ) + .Select(t => Path.Join(BaseDirectory, "Assemblies", t)) + .ToArray(); + AssemblyHandler.LoadScripts(assemblyFiles); + + VerifySerialization(); + + AssemblyHandler.Invoke("Configure"); + + RegionLoader.LoadRegions(); + World.Load(); + + AssemblyHandler.Invoke("Initialize"); + + timerThread.Start(); + + foreach (var m in Map.AllMaps) + m.Tiles.Force(); + + EventSink.InvokeServerStarted(); + + // Start net socket server + var host = TcpServer.CreateWebHostBuilder().Build(); + var life = host.Services.GetRequiredService(); + life.ApplicationStopping.Register(() => { Kill(); }); + + host.Run(); + } + + public static void RunEventLoop(IMessagePumpService messagePumpService) + { + try + { + var last = TickCount; + + const int sampleInterval = 100; + const float ticksPerSecond = 1000.0f * sampleInterval; + + long sample = 0; + + while (!Closing) + { + m_Signal.WaitOne(); + + Task.WaitAll( + Task.Run(Mobile.ProcessDeltaQueue), + Task.Run(Item.ProcessDeltaQueue) + ); + + Timer.Slice(); + messagePumpService.DoWork(); + + NetState.ProcessDisposedQueue(); + + if (sample++ % sampleInterval != 0) + continue; + + var now = TickCount; + m_CyclesPerSecond[m_CycleIndex++ % m_CyclesPerSecond.Length] = ticksPerSecond / (now - last); + last = now; + } + } + catch (Exception e) + { + CurrentDomain_UnhandledException(null, new UnhandledExceptionEventArgs(e, true)); + } + } + + public static void VerifySerialization() + { + m_ItemCount = 0; + m_MobileCount = 0; + + var ca = Assembly.GetCallingAssembly(); + + VerifySerialization(ca); + + foreach (var a in AssemblyHandler.Assemblies) + if (a != ca) + VerifySerialization(a); + } + + private static void VerifyType(Type t) + { + var isItem = t.IsSubclassOf(typeof(Item)); + + if (!isItem && !t.IsSubclassOf(typeof(Mobile))) return; + + if (isItem) + Interlocked.Increment(ref m_ItemCount); + else + Interlocked.Increment(ref m_MobileCount); + + StringBuilder warningSb = null; + + try + { + if (t.GetConstructor(m_SerialTypeArray) == null) + { + warningSb = new StringBuilder(); + warningSb.AppendLine(" - No serialization constructor"); + } + + if ( + t.GetMethod( + "Serialize", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly + ) == + null) + { + warningSb ??= new StringBuilder(); + warningSb.AppendLine(" - No Serialize() method"); + } + + if (t.GetMethod( + "Deserialize", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly + ) == + null) + { + warningSb ??= new StringBuilder(); + warningSb.AppendLine(" - No Deserialize() method"); + } + + if (warningSb?.Length > 0) Console.WriteLine("Warning: {0}\n{1}", t, warningSb); + } + catch + { + Console.WriteLine("Warning: Exception in serialization verification of type {0}", t); + } + } + + private static void VerifySerialization(Assembly a) + { + if (a != null) Parallel.ForEach(a.GetTypes(), VerifyType); + } + + internal enum ConsoleEventType + { + CTRL_C_EVENT, + CTRL_BREAK_EVENT, + CTRL_CLOSE_EVENT, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT + } + + internal delegate bool ConsoleEventHandler(ConsoleEventType type); + + internal class UnsafeNativeMethods + { + [DllImport("Kernel32")] + internal static extern bool SetConsoleCtrlHandler(ConsoleEventHandler callback, bool add); + } + } +} diff --git a/Projects/Server/Map.cs b/Projects/Server/Map.cs index 536e40c9c..98a70ef99 100644 --- a/Projects/Server/Map.cs +++ b/Projects/Server/Map.cs @@ -1,1419 +1,1441 @@ -/*************************************************************************** - * Map.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Server.Items; -using Server.Network; -using Server.Targeting; - -namespace Server -{ - [Flags] - public enum MapRules - { - None = 0x0000, - Internal = 0x0001, // Internal map (used for dragging, commodity deeds, etc) - FreeMovement = 0x0002, // Anyone can move over anyone else without taking stamina loss - BeneficialRestrictions = 0x0004, // Disallow performing beneficial actions on criminals/murderers - HarmfulRestrictions = 0x0008, // Disallow performing harmful actions on innocents - TrammelRules = FreeMovement | BeneficialRestrictions | HarmfulRestrictions, - FeluccaRules = None - } - - public interface IPooledEnumerable : IEnumerable - { - void Free(); - } - - public interface IPooledEnumerable : IPooledEnumerable, IEnumerable - { - } - - public static class PooledEnumeration - { - public delegate IEnumerable Selector(Sector sector, Rectangle2D bounds); - - static PooledEnumeration() - { - ClientSelector = SelectClients; - EntitySelector = SelectEntities; - MobileSelector = SelectMobiles; - ItemSelector = SelectItems; - MultiSelector = SelectMultis; - MultiTileSelector = SelectMultiTiles; - } - - public static Selector ClientSelector { get; set; } - public static Selector EntitySelector { get; set; } - public static Selector MobileSelector { get; set; } - public static Selector ItemSelector { get; set; } - public static Selector MultiSelector { get; set; } - public static Selector MultiTileSelector { get; set; } - - public static IEnumerable SelectClients(Sector s, Rectangle2D bounds) - { - return s.Clients.Where(o => o?.Mobile?.Deleted == false && bounds.Contains(o.Mobile)); - } - - public static IEnumerable SelectEntities(Sector s, Rectangle2D bounds) => SelectEntities(s, true, true, bounds); - - public static IEnumerable SelectEntities(Sector s, bool items, bool mobiles, Rectangle2D bounds) - { - var eable = Enumerable.Empty(); - if (mobiles) - eable = eable.Union(s.Mobiles.Where(o => o?.Deleted == false)); - if (items) - eable = eable.Union(s.Items.Where(o => o?.Deleted == false && o.Parent == null)); - - return eable.Where(bounds.Contains); - } - - public static IEnumerable SelectMobiles(Sector s, Rectangle2D bounds) where T : Mobile - { - return s.Mobiles.OfType().Where(o => !o.Deleted && bounds.Contains(o)); - } - - public static IEnumerable SelectItems(Sector s, Rectangle2D bounds) where T : Item - { - return s.Items.OfType() - .Where(o => o.Deleted == false && o.Parent == null && bounds.Contains(o)); - } - - public static IEnumerable SelectMultis(Sector s, Rectangle2D bounds) - { - return s.Multis.Where(o => o?.Deleted == false && bounds.Contains(o.Location)); - } - - public static IEnumerable SelectMultiTiles(Sector s, Rectangle2D bounds) - { - foreach (var o in s.Multis.Where(o => o?.Deleted == false)) - { - var c = o.Components; - - int x, y, xo, yo; - StaticTile[] t, r; - - for (x = bounds.Start.X; x < bounds.End.X; x++) - { - xo = x - (o.X + c.Min.X); - - if (xo < 0 || xo >= c.Width) continue; - - for (y = bounds.Start.Y; y < bounds.End.Y; y++) - { - yo = y - (o.Y + c.Min.Y); - - if (yo < 0 || yo >= c.Height) continue; - - t = c.Tiles[xo][yo]; - - if (t.Length <= 0) continue; - - r = new StaticTile[t.Length]; - - for (var i = 0; i < t.Length; i++) - { - r[i] = t[i]; - r[i].Z += o.Z; - } - - yield return r; - } - } - } - } - - public static Map.PooledEnumerable GetClients(Map map, Rectangle2D bounds) => - Map.PooledEnumerable.Instantiate(map, bounds, ClientSelector ?? SelectClients); - - public static Map.PooledEnumerable GetEntities(Map map, Rectangle2D bounds, bool items = true, - bool mobiles = true) => Map.PooledEnumerable.Instantiate(map, bounds, EntitySelector ?? SelectEntities); - - public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) => GetMobiles(map, bounds); - - public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) where T : Mobile => - Map.PooledEnumerable.Instantiate(map, bounds, SelectMobiles); - - public static Map.PooledEnumerable GetItems(Map map, Rectangle2D bounds) where T : Item => - Map.PooledEnumerable.Instantiate(map, bounds, SelectItems); - - public static Map.PooledEnumerable GetMultis(Map map, Rectangle2D bounds) => - Map.PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); - - public static Map.PooledEnumerable GetMultiTiles(Map map, Rectangle2D bounds) => - Map.PooledEnumerable.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles); - - public static IEnumerable EnumerateSectors(Map map, Rectangle2D bounds) - { - if (map == null || map == Map.Internal) - yield break; - - var x1 = bounds.Start.X; - var y1 = bounds.Start.Y; - var x2 = bounds.End.X; - var y2 = bounds.End.Y; - - if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out var xSector, out var ySector)) - yield break; - - var index = 0; - - while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out var s)) - yield return s; - } - - public static bool Bound( - Map map, - ref int x1, - ref int y1, - ref int x2, - ref int y2, - out int xSector, - out int ySector) - { - if (map == null || map == Map.Internal) - { - xSector = ySector = 0; - return false; - } - - map.Bound(x1, y1, out x1, out y1); - map.Bound(x2 - 1, y2 - 1, out x2, out y2); - - x1 >>= Map.SectorShift; - y1 >>= Map.SectorShift; - x2 >>= Map.SectorShift; - y2 >>= Map.SectorShift; - - xSector = x1; - ySector = y1; - - return true; - } - - private static bool NextSector( - Map map, - int x1, - int y1, - int x2, - int y2, - ref int index, - ref int xSector, - ref int ySector, - out Sector s) - { - if (map == null) - { - s = null; - xSector = ySector = 0; - return false; - } - - if (map == Map.Internal) - { - s = map.InvalidSector; - xSector = ySector = 0; - return false; - } - - if (index++ > 0) - if (++ySector > y2) - { - ySector = y1; - - if (++xSector > x2) - { - xSector = x1; - - s = map.InvalidSector; - return false; - } - } - - s = map.GetRealSector(xSector, ySector); - return true; - } - } - - [Parsable] - public sealed class Map : IComparable - { - public const int SectorSize = 16; - public const int SectorShift = 4; - public static readonly int SectorActiveRange = 2; - - private static readonly Queue> m_FixPool = new Queue>(128); - - private static readonly List m_EmptyFixItems = new List(); - private Region m_DefaultRegion; - - private readonly int m_FileIndex; - - private string m_Name; - private readonly Sector[][] m_Sectors; - - private readonly int m_SectorsWidth; - private readonly int m_SectorsHeight; - - private TileMatrix m_Tiles; - - private readonly object tileLock = new object(); - - public Map(int mapID, int mapIndex, int fileIndex, int width, int height, int season, string name, MapRules rules) - { - MapID = mapID; - MapIndex = mapIndex; - m_FileIndex = fileIndex; - Width = width; - Height = height; - Season = season; - m_Name = name; - Rules = rules; - Regions = new Dictionary(StringComparer.OrdinalIgnoreCase); - InvalidSector = new Sector(0, 0, this); - m_SectorsWidth = width >> SectorShift; - m_SectorsHeight = height >> SectorShift; - m_Sectors = new Sector[m_SectorsWidth][]; - } - - public static Map[] Maps { get; } = new Map[0x100]; - - public static Map Felucca => Maps[0]; - public static Map Trammel => Maps[1]; - public static Map Ilshenar => Maps[2]; - public static Map Malas => Maps[3]; - public static Map Tokuno => Maps[4]; - public static Map TerMur => Maps[5]; - public static Map Internal => Maps[0x7F]; - - public static List AllMaps { get; } = new List(); - - public int Season { get; set; } - - public TileMatrix Tiles - { - get - { - if (m_Tiles == null) - lock (tileLock) - { - m_Tiles = new TileMatrix(this, m_FileIndex, MapID, Width, Height); - } - - return m_Tiles; - } - } - - public int MapID { get; } - - public int MapIndex { get; } - - public int Width { get; } - - public int Height { get; } - - public Dictionary Regions { get; } - - public Region DefaultRegion - { - get => m_DefaultRegion ??= new Region(null, this, 0, Array.Empty()); - set => m_DefaultRegion = value; - } - - public MapRules Rules { get; set; } - - public Sector InvalidSector { get; } - - public string Name - { - get - { - if (this == Internal && m_Name != "Internal") - { - Console.WriteLine("Internal Map Name was changed to '{0}'", m_Name); - - m_Name = "Internal"; - } - - return m_Name; - } - set - { - if (this == Internal && value != "Internal") - { - Console.WriteLine("Attempted to set Internal Map Name to '{0}'", value); - - value = "Internal"; - } - - m_Name = value; - } - } - - public static int[] InvalidLandTiles { get; set; } = { 0x244 }; - - public int CompareTo(Map other) => other == null ? -1 : MapID.CompareTo(other.MapID); - - public static string[] GetMapNames() => Maps.Where(m => m != null).Select(m => m.Name).ToArray(); - - public static Map[] GetMapValues() => Maps.Where(m => m != null).ToArray(); - - public static Map Parse(string value) - { - if (string.IsNullOrWhiteSpace(value)) - return null; - - if (Insensitive.Equals(value, "Internal")) - return Internal; - - if (!int.TryParse(value, out var index)) - return Maps.FirstOrDefault(m => m != null && Insensitive.Equals(m.Name, value)); - - return index == 127 ? Internal : Maps.FirstOrDefault(m => m?.MapIndex == index); - } - - public override string ToString() => Name; - - public int GetAverageZ(int x, int y) - { - int z = 0, avg = 0, top = 0; - - GetAverageZ(x, y, ref z, ref avg, ref top); - - return avg; - } - - public void GetAverageZ(int x, int y, ref int z, ref int avg, ref int top) - { - var zTop = Tiles.GetLandTile(x, y).Z; - var zLeft = Tiles.GetLandTile(x, y + 1).Z; - var zRight = Tiles.GetLandTile(x + 1, y).Z; - var zBottom = Tiles.GetLandTile(x + 1, y + 1).Z; - - z = zTop; - if (zLeft < z) - z = zLeft; - if (zRight < z) - z = zRight; - if (zBottom < z) - z = zBottom; - - top = zTop; - if (zLeft > top) - top = zLeft; - if (zRight > top) - top = zRight; - if (zBottom > top) - top = zBottom; - - avg = Math.Abs(zTop - zBottom) > Math.Abs(zLeft - zRight) ? FloorAverage(zLeft, zRight) : FloorAverage(zTop, zBottom); - } - - private static int FloorAverage(int a, int b) - { - var v = a + b; - - if (v < 0) - --v; - - return v / 2; - } - - public IPooledEnumerable GetMultiTilesAt(int x, int y) => - PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); - - private static List AcquireFixItems(Map map, int x, int y) - { - if (map == null || map == Internal || x < 0 || x > map.Width || y < 0 || y > map.Height) - return m_EmptyFixItems; - - List pool = null; - - lock (m_FixPool) - { - if (m_FixPool.Count > 0) - pool = m_FixPool.Dequeue(); - } - - pool ??= new List(128); // Arbitrary limit - - var eable = map.GetItemsInRange(new Point3D(x, y, 0), 0); - - pool.AddRange( - eable.Where(item => item.ItemID <= TileData.MaxItemValue && !(item is BaseMulti)) - .OrderBy(item => item.Z) - .Take(pool.Capacity)); - - eable.Free(); - - return pool; - } - - private static void FreeFixItems(List pool) - { - if (pool == m_EmptyFixItems) - return; - - pool.Clear(); - - lock (m_FixPool) - { - if (m_FixPool.Count < 128) - m_FixPool.Enqueue(pool); - } - } - - public void FixColumn(int x, int y) - { - var landTile = Tiles.GetLandTile(x, y); - var tiles = Tiles.GetStaticTiles(x, y, true); - - int landZ = 0, landAvg = 0, landTop = 0; - GetAverageZ(x, y, ref landZ, ref landAvg, ref landTop); - - var items = AcquireFixItems(this, x, y); - - for (var i = 0; i < items.Count; i++) - { - var toFix = items[i]; - - if (!toFix.Movable) - continue; - - var z = int.MinValue; - var currentZ = toFix.Z; - - if (!landTile.Ignored && landAvg <= currentZ) - z = landAvg; - - foreach (var tile in tiles) - { - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - var checkZ = tile.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - ++checkTop; - - if (checkTop > z && checkTop <= currentZ) - z = checkTop; - } - - for (var j = 0; j < items.Count; ++j) - { - if (j == i) - continue; - - var item = items[j]; - var id = item.ItemData; - - var checkZ = item.Z; - var checkTop = checkZ + id.CalcHeight; - - if (checkTop == checkZ && !id.Surface) - ++checkTop; - - if (checkTop > z && checkTop <= currentZ) - z = checkTop; - } - - if (z != int.MinValue) - toFix.Location = new Point3D(toFix.X, toFix.Y, z); - } - - FreeFixItems(items); - } - - /* This could probably be re-implemented if necessary (perhaps via an ITile interface?). - public List GetTilesAt( Point2D p, bool items, bool land, bool statics ) - { - List list = new List(); - - if (this == Internal) - return list; - - if (land) - list.Add( Tiles.GetLandTile( p.m_X, p.m_Y ) ); - - if (statics) - list.AddRange( Tiles.GetStaticTiles( p.m_X, p.m_Y, true ) ); - - if (items) - { - Sector sector = GetSector( p ); - - foreach ( Item item in sector.Items ) - if (item.AtWorldPoint( p.m_X, p.m_Y )) - list.Add( new StaticTile( (ushort)item.ItemID, (sbyte) item.Z ) ); - } - - return list; - } - */ - - /// - /// Gets the highest surface that is lower than . - /// - /// The reference point. - /// A surface or . - public object GetTopSurface(Point3D p) - { - if (this == Internal) - return null; - - object surface = null; - var surfaceZ = int.MinValue; - - var lt = Tiles.GetLandTile(p.X, p.Y); - - if (!lt.Ignored) - { - var avgZ = GetAverageZ(p.X, p.Y); - - if (avgZ <= p.Z) - { - surface = lt; - surfaceZ = avgZ; - - if (surfaceZ == p.Z) - return surface; - } - } - - var staticTiles = Tiles.GetStaticTiles(p.X, p.Y, true); - - for (var i = 0; i < staticTiles.Length; i++) - { - var tile = staticTiles[i]; - var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; - - if (id.Surface || (id.Flags & TileFlag.Wet) != 0) - { - var tileZ = tile.Z + id.CalcHeight; - - if (tileZ > surfaceZ && tileZ <= p.Z) - { - surface = tile; - surfaceZ = tileZ; - - if (surfaceZ == p.Z) - return surface; - } - } - } - - var sector = GetSector(p.X, p.Y); - - for (var i = 0; i < sector.Items.Count; i++) - { - var item = sector.Items[i]; - - if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(p.X, p.Y) && - !item.Movable) - { - var id = item.ItemData; - - if (id.Surface || (id.Flags & TileFlag.Wet) != 0) - { - var itemZ = item.Z + id.CalcHeight; - - if (itemZ > surfaceZ && itemZ <= p.Z) - { - surface = item; - surfaceZ = itemZ; - - if (surfaceZ == p.Z) - return surface; - } - } - } - } - - return surface; - } - - public void Bound(int x, int y, out int newX, out int newY) - { - newX = Math.Clamp(x, 0, Width - 1); - newY = Math.Clamp(y, 0, Height - 1); - } - - public Point2D Bound(Point2D p) - { - int x = Math.Clamp(p.m_X, 0, Width - 1); - int y = Math.Clamp(p.m_Y, 0, Height - 1); - - return new Point2D(x, y); - } - - public void ActivateSectors(int cx, int cy) - { - for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) - for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) - { - var sect = GetRealSector(x, y); - if (sect != InvalidSector) - sect.Activate(); - } - } - - public void DeactivateSectors(int cx, int cy) - { - for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) - for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) - { - var sect = GetRealSector(x, y); - if (sect != InvalidSector && !PlayersInRange(sect, SectorActiveRange)) - sect.Deactivate(); - } - } - - private bool PlayersInRange(Sector sect, int range) - { - for (var x = sect.X - range; x <= sect.X + range; ++x) - for (var y = sect.Y - range; y <= sect.Y + range; ++y) - { - var check = GetRealSector(x, y); - if (check != InvalidSector && check.Players.Count > 0) - return true; - } - - return false; - } - - public void OnClientChange(NetState oldState, NetState newState, Mobile m) - { - if (this != Internal) - GetSector(m).OnClientChange(oldState, newState); - } - - public void OnEnter(Mobile m) - { - if (this != Internal) - GetSector(m).OnEnter(m); - } - - public void OnEnter(Item item) - { - if (this == Internal) - return; - - GetSector(item).OnEnter(item); - - if (item is BaseMulti m) - { - var mcl = m.Components; - - var start = GetMultiMinSector(m.Location, mcl); - var end = GetMultiMaxSector(m.Location, mcl); - - AddMulti(m, start, end); - } - } - - public void OnLeave(Mobile m) - { - if (this != Internal) - GetSector(m).OnLeave(m); - } - - public void OnLeave(Item item) - { - if (this == Internal) - return; - - GetSector(item).OnLeave(item); - - if (item is BaseMulti m) - { - var mcl = m.Components; - - var start = GetMultiMinSector(m.Location, mcl); - var end = GetMultiMaxSector(m.Location, mcl); - - RemoveMulti(m, start, end); - } - } - - public void RemoveMulti(BaseMulti m, Sector start, Sector end) - { - if (this == Internal) - return; - - for (var x = start.X; x <= end.X; ++x) - for (var y = start.Y; y <= end.Y; ++y) - InternalGetSector(x, y).OnMultiLeave(m); - } - - public void AddMulti(BaseMulti m, Sector start, Sector end) - { - if (this == Internal) - return; - - for (var x = start.X; x <= end.X; ++x) - for (var y = start.Y; y <= end.Y; ++y) - InternalGetSector(x, y).OnMultiEnter(m); - } - - public Sector GetMultiMinSector(Point3D loc, MultiComponentList mcl) => - GetSector(Bound(new Point2D(loc.m_X + mcl.Min.m_X, loc.m_Y + mcl.Min.m_Y))); - - public Sector GetMultiMaxSector(Point3D loc, MultiComponentList mcl) => - GetSector(Bound(new Point2D(loc.m_X + mcl.Max.m_X, loc.m_Y + mcl.Max.m_Y))); - - public void OnMove(Point3D oldLocation, Mobile m) - { - if (this == Internal) - return; - - var oldSector = GetSector(oldLocation); - var newSector = GetSector(m.Location); - - if (oldSector != newSector) - { - oldSector.OnLeave(m); - newSector.OnEnter(m); - } - } - - public void OnMove(Point3D oldLocation, Item item) - { - if (this == Internal) - return; - - var oldSector = GetSector(oldLocation); - var newSector = GetSector(item.Location); - - if (oldSector != newSector) - { - oldSector.OnLeave(item); - newSector.OnEnter(item); - } - - if (item is BaseMulti m) - { - var mcl = m.Components; - - var start = GetMultiMinSector(m.Location, mcl); - var end = GetMultiMaxSector(m.Location, mcl); - - var oldStart = GetMultiMinSector(oldLocation, mcl); - var oldEnd = GetMultiMaxSector(oldLocation, mcl); - - if (oldStart != start || oldEnd != end) - { - RemoveMulti(m, oldStart, oldEnd); - AddMulti(m, start, end); - } - } - } - - public void RegisterRegion(Region reg) - { - var regName = reg.Name; - - if (regName == null) - return; - - if (Regions.ContainsKey(regName)) - Console.WriteLine("Warning: Duplicate region name '{0}' for map '{1}'", regName, Name); - else - Regions[regName] = reg; - } - - public void UnregisterRegion(Region reg) - { - var regName = reg.Name; - - if (regName != null) - Regions.Remove(regName); - } - - public Point3D GetPoint(object o, bool eye) - { - Point3D p; - - if (o is Mobile mobile) - { - p = mobile.Location; - p.Z += 14; // eye ? 15 : 10; - } - else if (o is Item item) - { - p = item.GetWorldLocation(); - p.Z += item.ItemData.Height / 2 + 1; - } - else if (o is Point3D point3D) - { - p = point3D; - } - else if (o is LandTarget target) - { - p = target.Location; - - int low = 0, avg = 0, top = 0; - GetAverageZ(p.X, p.Y, ref low, ref avg, ref top); - - p.Z = top + 1; - } - else if (o is StaticTarget st) - { - var id = TileData.ItemTable[st.ItemID & TileData.MaxItemValue]; - - p = new Point3D(st.X, st.Y, st.Z - id.CalcHeight + id.Height / 2 + 1); - } - else if (o is IPoint3D d) - { - p = new Point3D(d); - } - else - { - Console.WriteLine("Warning: Invalid object ({0}) in line of sight", o); - p = Point3D.Zero; - } - - return p; - } - - public class NullEnumerable : IPooledEnumerable - { - public static readonly NullEnumerable Instance = new NullEnumerable(); - - private readonly IEnumerable m_Empty; - - private NullEnumerable() => m_Empty = Enumerable.Empty(); - - IEnumerator IEnumerable.GetEnumerator() => m_Empty.GetEnumerator(); - - public IEnumerator GetEnumerator() => m_Empty.GetEnumerator(); - - public void Free() - { - } - } - - public sealed class PooledEnumerable : IPooledEnumerable, IDisposable - { - private static readonly Queue> _Buffer = new Queue>(0x400); - - private bool m_IsDisposed; - - private List m_Pool = new List(0x40); - - public PooledEnumerable(IEnumerable pool) - { - m_Pool.AddRange(pool); - } - - public void Dispose() - { - m_IsDisposed = true; - - m_Pool.Clear(); - m_Pool.TrimExcess(); - m_Pool = null; - } - - IEnumerator IEnumerable.GetEnumerator() => m_Pool.GetEnumerator(); - - public IEnumerator GetEnumerator() => m_Pool.GetEnumerator(); - - public void Free() - { - if (m_IsDisposed) - return; - - m_Pool.Clear(); - m_Pool.Capacity = Math.Max(m_Pool.Capacity, 0x100); - - lock (((ICollection)_Buffer).SyncRoot) - { - _Buffer.Enqueue(this); - } - } - -#pragma warning disable CA1000 // Do not declare static members on generic types - public static PooledEnumerable Instantiate(Map map, Rectangle2D bounds, PooledEnumeration.Selector selector) - { - PooledEnumerable e = null; - - lock (((ICollection)_Buffer).SyncRoot) - { - if (_Buffer.Count > 0) - e = _Buffer.Dequeue(); - } - - var pool = PooledEnumeration.EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); - - if (e == null) - return new PooledEnumerable(pool); - - e.m_Pool.AddRange(pool); - return e; - } - } -#pragma warning restore CA1000 // Do not declare static members on generic types - - public IPooledEnumerable GetObjectsInRange(Point3D p) => GetObjectsInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetObjectsInRange(Point3D p, int range, bool items = true, bool mobiles = true) => - GetObjectsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1), items, - mobiles); - - public IPooledEnumerable GetObjectsInBounds(Rectangle2D bounds, bool items = true, bool mobiles = true) => - PooledEnumeration.GetEntities(this, bounds, items, mobiles); - - public IPooledEnumerable GetClientsInRange(Point3D p) => GetClientsInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetClientsInRange(Point3D p, int range) => - GetClientsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) => PooledEnumeration.GetClients(this, bounds); - - public IPooledEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); - - public IPooledEnumerable GetItemsInRange(Point3D p, int range) where T : Item => - GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); - - public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) where T : Item => - PooledEnumeration.GetItems(this, bounds); - - public IPooledEnumerable GetMobilesInRange(Point3D p) => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); - - public IPooledEnumerable GetMobilesInRange(Point3D p, int range) => GetMobilesInRange(p, range); - - public IPooledEnumerable GetMobilesInRange(Point3D p, int range) where T : Mobile => - GetMobilesInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); - - public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) => GetMobilesInBounds(bounds); - - public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) where T : Mobile => - PooledEnumeration.GetMobiles(this, bounds); - - public bool CanFit(Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true, - bool requireSurface = true) => - CanFit(p.m_X, p.m_Y, p.m_Z, height, checkBlocksFit, checkMobiles, requireSurface); - - public bool CanFit(Point2D p, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, - bool requireSurface = true) => - CanFit(p.m_X, p.m_Y, z, height, checkBlocksFit, checkMobiles, requireSurface); - - public bool CanFit(int x, int y, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, - bool requireSurface = true) - { - if (this == Internal) - return false; - - if (x < 0 || y < 0 || x >= Width || y >= Height) - return false; - - var hasSurface = false; - - var lt = Tiles.GetLandTile(x, y); - int lowZ = 0, avgZ = 0, topZ = 0; - - GetAverageZ(x, y, ref lowZ, ref avgZ, ref topZ); - var landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags; - - if ((landFlags & TileFlag.Impassable) != 0 && avgZ > z && z + height > lowZ) - return false; - - if ((landFlags & TileFlag.Impassable) == 0 && z == avgZ && !lt.Ignored) - hasSurface = true; - - var staticTiles = Tiles.GetStaticTiles(x, y, true); - - bool surface, impassable; - - for (var i = 0; i < staticTiles.Length; ++i) - { - var id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue]; - surface = id.Surface; - impassable = id.Impassable; - - 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; - } - - var sector = GetSector(x, y); - var items = sector.Items; - var mobs = sector.Mobiles; - - for (var i = 0; i < items.Count; ++i) - { - var item = items[i]; - - if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y)) - { - var id = item.ItemData; - surface = id.Surface; - impassable = id.Impassable; - - 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 (checkMobiles) - for (var i = 0; i < mobs.Count; ++i) - { - var m = mobs[i]; - - if (m.Location.m_X == x && m.Location.m_Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden) && - m.Z + 16 > z && z + height > m.Z) - return false; - } - - return !requireSurface || hasSurface; - } - - public bool CanSpawnMobile(Point3D p) => CanSpawnMobile(p.m_X, p.m_Y, p.m_Z); - - public bool CanSpawnMobile(Point2D p, int z) => CanSpawnMobile(p.m_X, p.m_Y, z); - - public bool CanSpawnMobile(int x, int y, int z) => - Region.Find(new Point3D(x, y, z), this).AllowSpawn() && CanFit(x, y, z, 16); - - public Sector GetSector(Point3D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); - - public Sector GetSector(Point2D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); - - public Sector GetSector(IPoint2D p) => InternalGetSector(p.X >> SectorShift, p.Y >> SectorShift); - - public Sector GetSector(int x, int y) => InternalGetSector(x >> SectorShift, y >> SectorShift); - - public Sector GetRealSector(int x, int y) => InternalGetSector(x, y); - - private Sector InternalGetSector(int x, int y) - { - if (x >= 0 && x < m_SectorsWidth && y >= 0 && y < m_SectorsHeight) - { - var xSectors = m_Sectors[x]; - - if (xSectors == null) - m_Sectors[x] = xSectors = new Sector[m_SectorsHeight]; - - var sec = xSectors[y]; - - if (sec == null) - xSectors[y] = sec = new Sector(x, y, this); - - return sec; - } - - return InvalidSector; - } - - public static int MaxLOSDistance { get; set; } = 25; - - public bool LineOfSight(Point3D org, Point3D dest) - { - if (this == Internal) - return false; - - if (!Utility.InRange(org, dest, MaxLOSDistance)) - return false; - - var end = dest; - - if (org.X > dest.X || org.X == dest.X && org.Y > dest.Y || org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z) - { - var swap = org; - org = dest; - dest = swap; - } - - int height; - Point3D p; - var path = new Point3DList(); - TileFlag flags; - - if (org == dest) - return true; - - if (path.Count > 0) - path.Clear(); - - var xd = dest.m_X - org.m_X; - var yd = dest.m_Y - org.m_Y; - var zd = dest.m_Z - org.m_Z; - var zslp = Math.Sqrt(xd * xd + yd * yd); - var sq3d = zd != 0 ? Math.Sqrt(zslp * zslp + zd * zd) : zslp; - - var rise = yd / sq3d; - var run = xd / sq3d; - zslp = zd / sq3d; - - double y = org.m_Y; - double z = org.m_Z; - double x = org.m_X; - while (Utility.NumberBetween(x, dest.m_X, org.m_X, 0.5) && Utility.NumberBetween(y, dest.m_Y, org.m_Y, 0.5) && - Utility.NumberBetween(z, dest.m_Z, org.m_Z, 0.5)) - { - var ix = (int)Math.Round(x); - var iy = (int)Math.Round(y); - var iz = (int)Math.Round(z); - if (path.Count > 0) - { - p = path.Last; - - if (p.m_X != ix || p.m_Y != iy || p.m_Z != iz) - path.Add(ix, iy, iz); - } - else - { - path.Add(ix, iy, iz); - } - - x += run; - y += rise; - z += zslp; - } - - if (path.Count == 0) - return true; // <--should never happen, but to be safe. - - p = path.Last; - - if (p != dest) - path.Add(dest); - - Point3D pTop = org, pBottom = dest; - Utility.FixPoints(ref pTop, ref pBottom); - - var pathCount = path.Count; - var endTop = end.m_Z + 1; - - for (var i = 0; i < pathCount; ++i) - { - var point = path[i]; - var pointTop = point.m_Z + 1; - - var landTile = Tiles.GetLandTile(point.X, point.Y); - int landZ = 0, landAvg = 0, landTop = 0; - GetAverageZ(point.m_X, point.m_Y, ref landZ, ref landAvg, ref landTop); - - if (landZ <= pointTop && landTop >= point.m_Z && - (point.m_X != end.m_X || point.m_Y != end.m_Y || landZ > endTop || landTop < end.m_Z) && - !landTile.Ignored) - return false; - - /* --Do land tiles need to be checked? There is never land between two people, always statics.-- - LandTile landTile = Tiles.GetLandTile( point.X, point.Y ); - if (landTile.Z-1 >= point.Z && landTile.Z+1 <= point.Z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Impassable) != 0) - return false; - */ - - var statics = Tiles.GetStaticTiles(point.m_X, point.m_Y, true); - - var contains = false; - var ltID = landTile.ID; - - for (var j = 0; !contains && j < InvalidLandTiles.Length; ++j) - contains = ltID == InvalidLandTiles[j]; - - if (contains && statics.Length == 0) - { - var eable = GetItemsInRange(point, 0); - - contains = !eable.Any(item => item.Visible); - - eable.Free(); - - if (contains) - return false; - } - - for (var j = 0; j < statics.Length; ++j) - { - var t = statics[j]; - - var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; - - flags = id.Flags; - height = id.CalcHeight; - - if (t.Z <= pointTop && t.Z + height >= point.Z && (flags & (TileFlag.Window | TileFlag.NoShoot)) != 0) - { - if (point.m_X == end.m_X && point.m_Y == end.m_Y && t.Z <= endTop && t.Z + height >= end.m_Z) - continue; - - return false; - } - - /*if (t.Z <= point.Z && t.Z+height >= point.Z && (flags&TileFlag.Window)==0 && (flags&TileFlag.NoShoot)!=0 - && ( (flags&TileFlag.Wall)!=0 || (flags&TileFlag.Roof)!=0 || (((flags&TileFlag.Surface)!=0 && zd != 0)) ) )*/ - /*{ - //Console.WriteLine( "LoS: Blocked by Static \"{0}\" Z:{1} T:{3} P:{2} F:x{4:X}", TileData.ItemTable[t.ID&TileData.MaxItemValue].Name, t.Z, point, t.Z+height, flags ); - //Console.WriteLine( "if ({0} && {1} && {2} && ( {3} || {4} || {5} || ({6} && {7} && {8}) ) )", t.Z <= point.Z, t.Z+height >= point.Z, (flags&TileFlag.Window)==0, (flags&TileFlag.Impassable)!=0, (flags&TileFlag.Wall)!=0, (flags&TileFlag.Roof)!=0, (flags&TileFlag.Surface)!=0, t.Z != dest.Z, zd != 0 ) ; - return false; - }*/ - } - } - - var rect = new Rectangle2D(pTop.m_X, pTop.m_Y, pBottom.m_X - pTop.m_X + 1, pBottom.m_Y - pTop.m_Y + 1); - - var area = GetItemsInBounds(rect); - - foreach (var i in area) - { - if (!i.Visible) - continue; - - if (i is BaseMulti || i.ItemID > TileData.MaxItemValue) - continue; - - var id = i.ItemData; - flags = id.Flags; - - if ((flags & (TileFlag.Window | TileFlag.NoShoot)) == 0) - continue; - - height = id.CalcHeight; - - var found = false; - - var count = path.Count; - - for (var j = 0; j < count; ++j) - { - var point = path[j]; - var pointTop = point.m_Z + 1; - var loc = i.Location; - - // if (t.Z <= point.Z && t.Z+height >= point.Z && ( height != 0 || ( t.Z == dest.Z && zd != 0 ) )) - if (loc.m_X == point.m_X && loc.m_Y == point.m_Y && loc.m_Z <= pointTop && loc.m_Z + height >= point.m_Z) - if (loc.m_X != end.m_X || loc.m_Y != end.m_Y || loc.m_Z > endTop || loc.m_Z + height < end.m_Z) - { - found = true; - break; - } - } - - if (!found) - continue; - - area.Free(); - return false; - - /*if ((flags & (TileFlag.Impassable | TileFlag.Surface | TileFlag.Roof)) != 0) - - //flags = TileData.ItemTable[i.ItemID&TileData.MaxItemValue].Flags; - //if ((flags&TileFlag.Window)==0 && (flags&TileFlag.NoShoot)!=0 && ( (flags&TileFlag.Wall)!=0 || (flags&TileFlag.Roof)!=0 || (((flags&TileFlag.Surface)!=0 && zd != 0)) )) - { - //height = TileData.ItemTable[i.ItemID&TileData.MaxItemValue].Height; - //Console.WriteLine( "LoS: Blocked by ITEM \"{0}\" P:{1} T:{2} F:x{3:X}", TileData.ItemTable[i.ItemID&TileData.MaxItemValue].Name, i.Location, i.Location.Z+height, flags ); - area.Free(); - return false; - }*/ - } - - area.Free(); - return true; - } - - public bool LineOfSight(object from, object dest) => - from == dest || (from as Mobile)?.AccessLevel > AccessLevel.Player || - (dest as Item)?.RootParent == from || LineOfSight(GetPoint(from, true), GetPoint(dest, false)); - - public bool LineOfSight(Mobile from, Point3D target) - { - if (from.AccessLevel > AccessLevel.Player) - return true; - - var eye = from.Location; - - eye.Z += 14; - - return LineOfSight(eye, target); - } - - public bool LineOfSight(Mobile from, Mobile to) - { - if (from == to || from.AccessLevel > AccessLevel.Player) - return true; - - var eye = from.Location; - var target = to.Location; - - eye.Z += 14; - target.Z += 14; // 10; - - return LineOfSight(eye, target); - } - - public Point3D GetRandomNearbyLocation(Point3D loc, int maxRange = 2, int minRange = 0, int retryCount = 10, - int height = 16, bool checkBlocksFit = false, - bool checkMobiles = false) - { - var j = 0; - var range = maxRange - minRange; - var locs = range <= 10 ? new bool[range + 1, range + 1] : null; - - do - { - var xRand = Utility.Random(range); - var yRand = Utility.Random(range); - - if (locs?[xRand, yRand] != true) - { - var x = loc.X + xRand + minRange; - var y = loc.Y + yRand + minRange; - - if (CanFit(x, y, loc.Z, height, checkBlocksFit, checkMobiles)) - { - loc = new Point3D(x, y, loc.Z); - break; - } - - var z = GetAverageZ(x, y); - - if (CanFit(x, y, z, height, checkBlocksFit, checkMobiles)) - { - loc = new Point3D(x, y, z); - break; - } - - if (locs != null) - locs[xRand, yRand] = true; - } - - j++; - } while (j < retryCount); - - return loc; - } - } -} +/*************************************************************************** + * Map.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Server.Items; +using Server.Network; +using Server.Targeting; + +namespace Server +{ + [Flags] + public enum MapRules + { + None = 0x0000, + Internal = 0x0001, // Internal map (used for dragging, commodity deeds, etc) + FreeMovement = 0x0002, // Anyone can move over anyone else without taking stamina loss + BeneficialRestrictions = 0x0004, // Disallow performing beneficial actions on criminals/murderers + HarmfulRestrictions = 0x0008, // Disallow performing harmful actions on innocents + TrammelRules = FreeMovement | BeneficialRestrictions | HarmfulRestrictions, + FeluccaRules = None + } + + public interface IPooledEnumerable : IEnumerable + { + void Free(); + } + + public interface IPooledEnumerable : IPooledEnumerable, IEnumerable + { + } + + public static class PooledEnumeration + { + public delegate IEnumerable Selector(Sector sector, Rectangle2D bounds); + + static PooledEnumeration() + { + ClientSelector = SelectClients; + EntitySelector = SelectEntities; + MobileSelector = SelectMobiles; + ItemSelector = SelectItems; + MultiSelector = SelectMultis; + MultiTileSelector = SelectMultiTiles; + } + + public static Selector ClientSelector { get; set; } + public static Selector EntitySelector { get; set; } + public static Selector MobileSelector { get; set; } + public static Selector ItemSelector { get; set; } + public static Selector MultiSelector { get; set; } + public static Selector MultiTileSelector { get; set; } + + public static IEnumerable SelectClients(Sector s, Rectangle2D bounds) + { + return s.Clients.Where(o => o?.Mobile?.Deleted == false && bounds.Contains(o.Mobile)); + } + + public static IEnumerable SelectEntities(Sector s, Rectangle2D bounds) => + SelectEntities(s, true, true, bounds); + + public static IEnumerable SelectEntities(Sector s, bool items, bool mobiles, Rectangle2D bounds) + { + var eable = Enumerable.Empty(); + if (mobiles) + eable = eable.Union(s.Mobiles.Where(o => o?.Deleted == false)); + if (items) + eable = eable.Union(s.Items.Where(o => o?.Deleted == false && o.Parent == null)); + + return eable.Where(bounds.Contains); + } + + public static IEnumerable SelectMobiles(Sector s, Rectangle2D bounds) where T : Mobile + { + return s.Mobiles.OfType().Where(o => !o.Deleted && bounds.Contains(o)); + } + + public static IEnumerable SelectItems(Sector s, Rectangle2D bounds) where T : Item + { + return s.Items.OfType() + .Where(o => o.Deleted == false && o.Parent == null && bounds.Contains(o)); + } + + public static IEnumerable SelectMultis(Sector s, Rectangle2D bounds) + { + return s.Multis.Where(o => o?.Deleted == false && bounds.Contains(o.Location)); + } + + public static IEnumerable SelectMultiTiles(Sector s, Rectangle2D bounds) + { + foreach (var o in s.Multis.Where(o => o?.Deleted == false)) + { + var c = o.Components; + + int x, y, xo, yo; + StaticTile[] t, r; + + for (x = bounds.Start.X; x < bounds.End.X; x++) + { + xo = x - (o.X + c.Min.X); + + if (xo < 0 || xo >= c.Width) continue; + + for (y = bounds.Start.Y; y < bounds.End.Y; y++) + { + yo = y - (o.Y + c.Min.Y); + + if (yo < 0 || yo >= c.Height) continue; + + t = c.Tiles[xo][yo]; + + if (t.Length <= 0) continue; + + r = new StaticTile[t.Length]; + + for (var i = 0; i < t.Length; i++) + { + r[i] = t[i]; + r[i].Z += o.Z; + } + + yield return r; + } + } + } + } + + public static Map.PooledEnumerable GetClients(Map map, Rectangle2D bounds) => + Map.PooledEnumerable.Instantiate(map, bounds, ClientSelector ?? SelectClients); + + public static Map.PooledEnumerable GetEntities( + Map map, Rectangle2D bounds, bool items = true, + bool mobiles = true + ) => Map.PooledEnumerable.Instantiate(map, bounds, EntitySelector ?? SelectEntities); + + public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) => + GetMobiles(map, bounds); + + public static Map.PooledEnumerable GetMobiles(Map map, Rectangle2D bounds) where T : Mobile => + Map.PooledEnumerable.Instantiate(map, bounds, SelectMobiles); + + public static Map.PooledEnumerable GetItems(Map map, Rectangle2D bounds) where T : Item => + Map.PooledEnumerable.Instantiate(map, bounds, SelectItems); + + public static Map.PooledEnumerable GetMultis(Map map, Rectangle2D bounds) => + Map.PooledEnumerable.Instantiate(map, bounds, MultiSelector ?? SelectMultis); + + public static Map.PooledEnumerable GetMultiTiles(Map map, Rectangle2D bounds) => + Map.PooledEnumerable.Instantiate(map, bounds, MultiTileSelector ?? SelectMultiTiles); + + public static IEnumerable EnumerateSectors(Map map, Rectangle2D bounds) + { + if (map == null || map == Map.Internal) + yield break; + + var x1 = bounds.Start.X; + var y1 = bounds.Start.Y; + var x2 = bounds.End.X; + var y2 = bounds.End.Y; + + if (!Bound(map, ref x1, ref y1, ref x2, ref y2, out var xSector, out var ySector)) + yield break; + + var index = 0; + + while (NextSector(map, x1, y1, x2, y2, ref index, ref xSector, ref ySector, out var s)) + yield return s; + } + + public static bool Bound( + Map map, + ref int x1, + ref int y1, + ref int x2, + ref int y2, + out int xSector, + out int ySector + ) + { + if (map == null || map == Map.Internal) + { + xSector = ySector = 0; + return false; + } + + map.Bound(x1, y1, out x1, out y1); + map.Bound(x2 - 1, y2 - 1, out x2, out y2); + + x1 >>= Map.SectorShift; + y1 >>= Map.SectorShift; + x2 >>= Map.SectorShift; + y2 >>= Map.SectorShift; + + xSector = x1; + ySector = y1; + + return true; + } + + private static bool NextSector( + Map map, + int x1, + int y1, + int x2, + int y2, + ref int index, + ref int xSector, + ref int ySector, + out Sector s + ) + { + if (map == null) + { + s = null; + xSector = ySector = 0; + return false; + } + + if (map == Map.Internal) + { + s = map.InvalidSector; + xSector = ySector = 0; + return false; + } + + if (index++ > 0) + if (++ySector > y2) + { + ySector = y1; + + if (++xSector > x2) + { + xSector = x1; + + s = map.InvalidSector; + return false; + } + } + + s = map.GetRealSector(xSector, ySector); + return true; + } + } + + [Parsable] + public sealed class Map : IComparable + { + public const int SectorSize = 16; + public const int SectorShift = 4; + public static readonly int SectorActiveRange = 2; + + private static readonly Queue> m_FixPool = new Queue>(128); + + private static readonly List m_EmptyFixItems = new List(); + + private readonly int m_FileIndex; + private readonly Sector[][] m_Sectors; + private readonly int m_SectorsHeight; + + private readonly int m_SectorsWidth; + + private readonly object tileLock = new object(); + private Region m_DefaultRegion; + + private string m_Name; + + private TileMatrix m_Tiles; + + public Map(int mapID, int mapIndex, int fileIndex, int width, int height, int season, string name, MapRules rules) + { + MapID = mapID; + MapIndex = mapIndex; + m_FileIndex = fileIndex; + Width = width; + Height = height; + Season = season; + m_Name = name; + Rules = rules; + Regions = new Dictionary(StringComparer.OrdinalIgnoreCase); + InvalidSector = new Sector(0, 0, this); + m_SectorsWidth = width >> SectorShift; + m_SectorsHeight = height >> SectorShift; + m_Sectors = new Sector[m_SectorsWidth][]; + } + + public static Map[] Maps { get; } = new Map[0x100]; + + public static Map Felucca => Maps[0]; + public static Map Trammel => Maps[1]; + public static Map Ilshenar => Maps[2]; + public static Map Malas => Maps[3]; + public static Map Tokuno => Maps[4]; + public static Map TerMur => Maps[5]; + public static Map Internal => Maps[0x7F]; + + public static List AllMaps { get; } = new List(); + + public int Season { get; set; } + + public TileMatrix Tiles + { + get + { + if (m_Tiles == null) + lock (tileLock) + { + m_Tiles = new TileMatrix(this, m_FileIndex, MapID, Width, Height); + } + + return m_Tiles; + } + } + + public int MapID { get; } + + public int MapIndex { get; } + + public int Width { get; } + + public int Height { get; } + + public Dictionary Regions { get; } + + public Region DefaultRegion + { + get => m_DefaultRegion ??= new Region(null, this, 0, Array.Empty()); + set => m_DefaultRegion = value; + } + + public MapRules Rules { get; set; } + + public Sector InvalidSector { get; } + + public string Name + { + get + { + if (this == Internal && m_Name != "Internal") + { + Console.WriteLine("Internal Map Name was changed to '{0}'", m_Name); + + m_Name = "Internal"; + } + + return m_Name; + } + set + { + if (this == Internal && value != "Internal") + { + Console.WriteLine("Attempted to set Internal Map Name to '{0}'", value); + + value = "Internal"; + } + + m_Name = value; + } + } + + public static int[] InvalidLandTiles { get; set; } = { 0x244 }; + + public static int MaxLOSDistance { get; set; } = 25; + + public int CompareTo(Map other) => other == null ? -1 : MapID.CompareTo(other.MapID); + + public static string[] GetMapNames() => Maps.Where(m => m != null).Select(m => m.Name).ToArray(); + + public static Map[] GetMapValues() => Maps.Where(m => m != null).ToArray(); + + public static Map Parse(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + if (Insensitive.Equals(value, "Internal")) + return Internal; + + if (!int.TryParse(value, out var index)) + return Maps.FirstOrDefault(m => m != null && Insensitive.Equals(m.Name, value)); + + return index == 127 ? Internal : Maps.FirstOrDefault(m => m?.MapIndex == index); + } + + public override string ToString() => Name; + + public int GetAverageZ(int x, int y) + { + int z = 0, avg = 0, top = 0; + + GetAverageZ(x, y, ref z, ref avg, ref top); + + return avg; + } + + public void GetAverageZ(int x, int y, ref int z, ref int avg, ref int top) + { + var zTop = Tiles.GetLandTile(x, y).Z; + var zLeft = Tiles.GetLandTile(x, y + 1).Z; + var zRight = Tiles.GetLandTile(x + 1, y).Z; + var zBottom = Tiles.GetLandTile(x + 1, y + 1).Z; + + z = zTop; + if (zLeft < z) + z = zLeft; + if (zRight < z) + z = zRight; + if (zBottom < z) + z = zBottom; + + top = zTop; + if (zLeft > top) + top = zLeft; + if (zRight > top) + top = zRight; + if (zBottom > top) + top = zBottom; + + avg = Math.Abs(zTop - zBottom) > Math.Abs(zLeft - zRight) + ? FloorAverage(zLeft, zRight) + : FloorAverage(zTop, zBottom); + } + + private static int FloorAverage(int a, int b) + { + var v = a + b; + + if (v < 0) + --v; + + return v / 2; + } + + public IPooledEnumerable GetMultiTilesAt(int x, int y) => + PooledEnumeration.GetMultiTiles(this, new Rectangle2D(x, y, 1, 1)); + + private static List AcquireFixItems(Map map, int x, int y) + { + if (map == null || map == Internal || x < 0 || x > map.Width || y < 0 || y > map.Height) + return m_EmptyFixItems; + + List pool = null; + + lock (m_FixPool) + { + if (m_FixPool.Count > 0) + pool = m_FixPool.Dequeue(); + } + + pool ??= new List(128); // Arbitrary limit + + var eable = map.GetItemsInRange(new Point3D(x, y, 0), 0); + + pool.AddRange( + eable.Where(item => item.ItemID <= TileData.MaxItemValue && !(item is BaseMulti)) + .OrderBy(item => item.Z) + .Take(pool.Capacity) + ); + + eable.Free(); + + return pool; + } + + private static void FreeFixItems(List pool) + { + if (pool == m_EmptyFixItems) + return; + + pool.Clear(); + + lock (m_FixPool) + { + if (m_FixPool.Count < 128) + m_FixPool.Enqueue(pool); + } + } + + public void FixColumn(int x, int y) + { + var landTile = Tiles.GetLandTile(x, y); + var tiles = Tiles.GetStaticTiles(x, y, true); + + int landZ = 0, landAvg = 0, landTop = 0; + GetAverageZ(x, y, ref landZ, ref landAvg, ref landTop); + + var items = AcquireFixItems(this, x, y); + + for (var i = 0; i < items.Count; i++) + { + var toFix = items[i]; + + if (!toFix.Movable) + continue; + + var z = int.MinValue; + var currentZ = toFix.Z; + + if (!landTile.Ignored && landAvg <= currentZ) + z = landAvg; + + foreach (var tile in tiles) + { + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + var checkZ = tile.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + ++checkTop; + + if (checkTop > z && checkTop <= currentZ) + z = checkTop; + } + + for (var j = 0; j < items.Count; ++j) + { + if (j == i) + continue; + + var item = items[j]; + var id = item.ItemData; + + var checkZ = item.Z; + var checkTop = checkZ + id.CalcHeight; + + if (checkTop == checkZ && !id.Surface) + ++checkTop; + + if (checkTop > z && checkTop <= currentZ) + z = checkTop; + } + + if (z != int.MinValue) + toFix.Location = new Point3D(toFix.X, toFix.Y, z); + } + + FreeFixItems(items); + } + + /* This could probably be re-implemented if necessary (perhaps via an ITile interface?). + public List GetTilesAt( Point2D p, bool items, bool land, bool statics ) + { + List list = new List(); + + if (this == Internal) + return list; + + if (land) + list.Add( Tiles.GetLandTile( p.m_X, p.m_Y ) ); + + if (statics) + list.AddRange( Tiles.GetStaticTiles( p.m_X, p.m_Y, true ) ); + + if (items) + { + Sector sector = GetSector( p ); + + foreach ( Item item in sector.Items ) + if (item.AtWorldPoint( p.m_X, p.m_Y )) + list.Add( new StaticTile( (ushort)item.ItemID, (sbyte) item.Z ) ); + } + + return list; + } + */ + + /// + /// Gets the highest surface that is lower than . + /// + /// The reference point. + /// A surface or . + public object GetTopSurface(Point3D p) + { + if (this == Internal) + return null; + + object surface = null; + var surfaceZ = int.MinValue; + + var lt = Tiles.GetLandTile(p.X, p.Y); + + if (!lt.Ignored) + { + var avgZ = GetAverageZ(p.X, p.Y); + + if (avgZ <= p.Z) + { + surface = lt; + surfaceZ = avgZ; + + if (surfaceZ == p.Z) + return surface; + } + } + + var staticTiles = Tiles.GetStaticTiles(p.X, p.Y, true); + + for (var i = 0; i < staticTiles.Length; i++) + { + var tile = staticTiles[i]; + var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue]; + + if (id.Surface || (id.Flags & TileFlag.Wet) != 0) + { + var tileZ = tile.Z + id.CalcHeight; + + if (tileZ > surfaceZ && tileZ <= p.Z) + { + surface = tile; + surfaceZ = tileZ; + + if (surfaceZ == p.Z) + return surface; + } + } + } + + var sector = GetSector(p.X, p.Y); + + for (var i = 0; i < sector.Items.Count; i++) + { + var item = sector.Items[i]; + + if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(p.X, p.Y) && + !item.Movable) + { + var id = item.ItemData; + + if (id.Surface || (id.Flags & TileFlag.Wet) != 0) + { + var itemZ = item.Z + id.CalcHeight; + + if (itemZ > surfaceZ && itemZ <= p.Z) + { + surface = item; + surfaceZ = itemZ; + + if (surfaceZ == p.Z) + return surface; + } + } + } + } + + return surface; + } + + public void Bound(int x, int y, out int newX, out int newY) + { + newX = Math.Clamp(x, 0, Width - 1); + newY = Math.Clamp(y, 0, Height - 1); + } + + public Point2D Bound(Point2D p) + { + var x = Math.Clamp(p.m_X, 0, Width - 1); + var y = Math.Clamp(p.m_Y, 0, Height - 1); + + return new Point2D(x, y); + } + + public void ActivateSectors(int cx, int cy) + { + for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) + for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) + { + var sect = GetRealSector(x, y); + if (sect != InvalidSector) + sect.Activate(); + } + } + + public void DeactivateSectors(int cx, int cy) + { + for (var x = cx - SectorActiveRange; x <= cx + SectorActiveRange; ++x) + for (var y = cy - SectorActiveRange; y <= cy + SectorActiveRange; ++y) + { + var sect = GetRealSector(x, y); + if (sect != InvalidSector && !PlayersInRange(sect, SectorActiveRange)) + sect.Deactivate(); + } + } + + private bool PlayersInRange(Sector sect, int range) + { + for (var x = sect.X - range; x <= sect.X + range; ++x) + for (var y = sect.Y - range; y <= sect.Y + range; ++y) + { + var check = GetRealSector(x, y); + if (check != InvalidSector && check.Players.Count > 0) + return true; + } + + return false; + } + + public void OnClientChange(NetState oldState, NetState newState, Mobile m) + { + if (this != Internal) + GetSector(m).OnClientChange(oldState, newState); + } + + public void OnEnter(Mobile m) + { + if (this != Internal) + GetSector(m).OnEnter(m); + } + + public void OnEnter(Item item) + { + if (this == Internal) + return; + + GetSector(item).OnEnter(item); + + if (item is BaseMulti m) + { + var mcl = m.Components; + + var start = GetMultiMinSector(m.Location, mcl); + var end = GetMultiMaxSector(m.Location, mcl); + + AddMulti(m, start, end); + } + } + + public void OnLeave(Mobile m) + { + if (this != Internal) + GetSector(m).OnLeave(m); + } + + public void OnLeave(Item item) + { + if (this == Internal) + return; + + GetSector(item).OnLeave(item); + + if (item is BaseMulti m) + { + var mcl = m.Components; + + var start = GetMultiMinSector(m.Location, mcl); + var end = GetMultiMaxSector(m.Location, mcl); + + RemoveMulti(m, start, end); + } + } + + public void RemoveMulti(BaseMulti m, Sector start, Sector end) + { + if (this == Internal) + return; + + for (var x = start.X; x <= end.X; ++x) + for (var y = start.Y; y <= end.Y; ++y) + InternalGetSector(x, y).OnMultiLeave(m); + } + + public void AddMulti(BaseMulti m, Sector start, Sector end) + { + if (this == Internal) + return; + + for (var x = start.X; x <= end.X; ++x) + for (var y = start.Y; y <= end.Y; ++y) + InternalGetSector(x, y).OnMultiEnter(m); + } + + public Sector GetMultiMinSector(Point3D loc, MultiComponentList mcl) => + GetSector(Bound(new Point2D(loc.m_X + mcl.Min.m_X, loc.m_Y + mcl.Min.m_Y))); + + public Sector GetMultiMaxSector(Point3D loc, MultiComponentList mcl) => + GetSector(Bound(new Point2D(loc.m_X + mcl.Max.m_X, loc.m_Y + mcl.Max.m_Y))); + + public void OnMove(Point3D oldLocation, Mobile m) + { + if (this == Internal) + return; + + var oldSector = GetSector(oldLocation); + var newSector = GetSector(m.Location); + + if (oldSector != newSector) + { + oldSector.OnLeave(m); + newSector.OnEnter(m); + } + } + + public void OnMove(Point3D oldLocation, Item item) + { + if (this == Internal) + return; + + var oldSector = GetSector(oldLocation); + var newSector = GetSector(item.Location); + + if (oldSector != newSector) + { + oldSector.OnLeave(item); + newSector.OnEnter(item); + } + + if (item is BaseMulti m) + { + var mcl = m.Components; + + var start = GetMultiMinSector(m.Location, mcl); + var end = GetMultiMaxSector(m.Location, mcl); + + var oldStart = GetMultiMinSector(oldLocation, mcl); + var oldEnd = GetMultiMaxSector(oldLocation, mcl); + + if (oldStart != start || oldEnd != end) + { + RemoveMulti(m, oldStart, oldEnd); + AddMulti(m, start, end); + } + } + } + + public void RegisterRegion(Region reg) + { + var regName = reg.Name; + + if (regName == null) + return; + + if (Regions.ContainsKey(regName)) + Console.WriteLine("Warning: Duplicate region name '{0}' for map '{1}'", regName, Name); + else + Regions[regName] = reg; + } + + public void UnregisterRegion(Region reg) + { + var regName = reg.Name; + + if (regName != null) + Regions.Remove(regName); + } + + public Point3D GetPoint(object o, bool eye) + { + Point3D p; + + if (o is Mobile mobile) + { + p = mobile.Location; + p.Z += 14; // eye ? 15 : 10; + } + else if (o is Item item) + { + p = item.GetWorldLocation(); + p.Z += item.ItemData.Height / 2 + 1; + } + else if (o is Point3D point3D) + { + p = point3D; + } + else if (o is LandTarget target) + { + p = target.Location; + + int low = 0, avg = 0, top = 0; + GetAverageZ(p.X, p.Y, ref low, ref avg, ref top); + + p.Z = top + 1; + } + else if (o is StaticTarget st) + { + var id = TileData.ItemTable[st.ItemID & TileData.MaxItemValue]; + + p = new Point3D(st.X, st.Y, st.Z - id.CalcHeight + id.Height / 2 + 1); + } + else if (o is IPoint3D d) + { + p = new Point3D(d); + } + else + { + Console.WriteLine("Warning: Invalid object ({0}) in line of sight", o); + p = Point3D.Zero; + } + + return p; + } + + public IPooledEnumerable GetObjectsInRange(Point3D p) => GetObjectsInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetObjectsInRange(Point3D p, int range, bool items = true, bool mobiles = true) => + GetObjectsInBounds( + new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1), + items, + mobiles + ); + + public IPooledEnumerable GetObjectsInBounds(Rectangle2D bounds, bool items = true, bool mobiles = true) => + PooledEnumeration.GetEntities(this, bounds, items, mobiles); + + public IPooledEnumerable GetClientsInRange(Point3D p) => GetClientsInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetClientsInRange(Point3D p, int range) => + GetClientsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public IPooledEnumerable GetClientsInBounds(Rectangle2D bounds) => + PooledEnumeration.GetClients(this, bounds); + + public IPooledEnumerable GetItemsInRange(Point3D p) => GetItemsInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetItemsInRange(Point3D p, int range) => GetItemsInRange(p, range); + + public IPooledEnumerable GetItemsInRange(Point3D p, int range) where T : Item => + GetItemsInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) => GetItemsInBounds(bounds); + + public IPooledEnumerable GetItemsInBounds(Rectangle2D bounds) where T : Item => + PooledEnumeration.GetItems(this, bounds); + + public IPooledEnumerable GetMobilesInRange(Point3D p) => GetMobilesInRange(p, Core.GlobalMaxUpdateRange); + + public IPooledEnumerable GetMobilesInRange(Point3D p, int range) => GetMobilesInRange(p, range); + + public IPooledEnumerable GetMobilesInRange(Point3D p, int range) where T : Mobile => + GetMobilesInBounds(new Rectangle2D(p.m_X - range, p.m_Y - range, range * 2 + 1, range * 2 + 1)); + + public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) => GetMobilesInBounds(bounds); + + public IPooledEnumerable GetMobilesInBounds(Rectangle2D bounds) where T : Mobile => + PooledEnumeration.GetMobiles(this, bounds); + + public bool CanFit( + Point3D p, int height, bool checkBlocksFit = false, bool checkMobiles = true, + bool requireSurface = true + ) => + CanFit(p.m_X, p.m_Y, p.m_Z, height, checkBlocksFit, checkMobiles, requireSurface); + + public bool CanFit( + Point2D p, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, + bool requireSurface = true + ) => + CanFit(p.m_X, p.m_Y, z, height, checkBlocksFit, checkMobiles, requireSurface); + + public bool CanFit( + int x, int y, int z, int height, bool checkBlocksFit = false, bool checkMobiles = true, + bool requireSurface = true + ) + { + if (this == Internal) + return false; + + if (x < 0 || y < 0 || x >= Width || y >= Height) + return false; + + var hasSurface = false; + + var lt = Tiles.GetLandTile(x, y); + int lowZ = 0, avgZ = 0, topZ = 0; + + GetAverageZ(x, y, ref lowZ, ref avgZ, ref topZ); + var landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags; + + if ((landFlags & TileFlag.Impassable) != 0 && avgZ > z && z + height > lowZ) + return false; + + if ((landFlags & TileFlag.Impassable) == 0 && z == avgZ && !lt.Ignored) + hasSurface = true; + + var staticTiles = Tiles.GetStaticTiles(x, y, true); + + bool surface, impassable; + + for (var i = 0; i < staticTiles.Length; ++i) + { + var id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue]; + surface = id.Surface; + impassable = id.Impassable; + + 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; + } + + var sector = GetSector(x, y); + var items = sector.Items; + var mobs = sector.Mobiles; + + for (var i = 0; i < items.Count; ++i) + { + var item = items[i]; + + if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y)) + { + var id = item.ItemData; + surface = id.Surface; + impassable = id.Impassable; + + 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 (checkMobiles) + for (var i = 0; i < mobs.Count; ++i) + { + var m = mobs[i]; + + if (m.Location.m_X == x && m.Location.m_Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden) && + m.Z + 16 > z && z + height > m.Z) + return false; + } + + return !requireSurface || hasSurface; + } + + public bool CanSpawnMobile(Point3D p) => CanSpawnMobile(p.m_X, p.m_Y, p.m_Z); + + public bool CanSpawnMobile(Point2D p, int z) => CanSpawnMobile(p.m_X, p.m_Y, z); + + public bool CanSpawnMobile(int x, int y, int z) => + Region.Find(new Point3D(x, y, z), this).AllowSpawn() && CanFit(x, y, z, 16); + + public Sector GetSector(Point3D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); + + public Sector GetSector(Point2D p) => InternalGetSector(p.m_X >> SectorShift, p.m_Y >> SectorShift); + + public Sector GetSector(IPoint2D p) => InternalGetSector(p.X >> SectorShift, p.Y >> SectorShift); + + public Sector GetSector(int x, int y) => InternalGetSector(x >> SectorShift, y >> SectorShift); + + public Sector GetRealSector(int x, int y) => InternalGetSector(x, y); + + private Sector InternalGetSector(int x, int y) + { + if (x >= 0 && x < m_SectorsWidth && y >= 0 && y < m_SectorsHeight) + { + var xSectors = m_Sectors[x]; + + if (xSectors == null) + m_Sectors[x] = xSectors = new Sector[m_SectorsHeight]; + + var sec = xSectors[y]; + + if (sec == null) + xSectors[y] = sec = new Sector(x, y, this); + + return sec; + } + + return InvalidSector; + } + + public bool LineOfSight(Point3D org, Point3D dest) + { + if (this == Internal) + return false; + + if (!Utility.InRange(org, dest, MaxLOSDistance)) + return false; + + var end = dest; + + if (org.X > dest.X || org.X == dest.X && org.Y > dest.Y || org.X == dest.X && org.Y == dest.Y && org.Z > dest.Z) + { + var swap = org; + org = dest; + dest = swap; + } + + int height; + Point3D p; + var path = new Point3DList(); + TileFlag flags; + + if (org == dest) + return true; + + if (path.Count > 0) + path.Clear(); + + var xd = dest.m_X - org.m_X; + var yd = dest.m_Y - org.m_Y; + var zd = dest.m_Z - org.m_Z; + var zslp = Math.Sqrt(xd * xd + yd * yd); + var sq3d = zd != 0 ? Math.Sqrt(zslp * zslp + zd * zd) : zslp; + + var rise = yd / sq3d; + var run = xd / sq3d; + zslp = zd / sq3d; + + double y = org.m_Y; + double z = org.m_Z; + double x = org.m_X; + while (Utility.NumberBetween(x, dest.m_X, org.m_X, 0.5) && Utility.NumberBetween(y, dest.m_Y, org.m_Y, 0.5) && + Utility.NumberBetween(z, dest.m_Z, org.m_Z, 0.5)) + { + var ix = (int)Math.Round(x); + var iy = (int)Math.Round(y); + var iz = (int)Math.Round(z); + if (path.Count > 0) + { + p = path.Last; + + if (p.m_X != ix || p.m_Y != iy || p.m_Z != iz) + path.Add(ix, iy, iz); + } + else + { + path.Add(ix, iy, iz); + } + + x += run; + y += rise; + z += zslp; + } + + if (path.Count == 0) + return true; // <--should never happen, but to be safe. + + p = path.Last; + + if (p != dest) + path.Add(dest); + + Point3D pTop = org, pBottom = dest; + Utility.FixPoints(ref pTop, ref pBottom); + + var pathCount = path.Count; + var endTop = end.m_Z + 1; + + for (var i = 0; i < pathCount; ++i) + { + var point = path[i]; + var pointTop = point.m_Z + 1; + + var landTile = Tiles.GetLandTile(point.X, point.Y); + int landZ = 0, landAvg = 0, landTop = 0; + GetAverageZ(point.m_X, point.m_Y, ref landZ, ref landAvg, ref landTop); + + if (landZ <= pointTop && landTop >= point.m_Z && + (point.m_X != end.m_X || point.m_Y != end.m_Y || landZ > endTop || landTop < end.m_Z) && + !landTile.Ignored) + return false; + + /* --Do land tiles need to be checked? There is never land between two people, always statics.-- + LandTile landTile = Tiles.GetLandTile( point.X, point.Y ); + if (landTile.Z-1 >= point.Z && landTile.Z+1 <= point.Z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Impassable) != 0) + return false; + */ + + var statics = Tiles.GetStaticTiles(point.m_X, point.m_Y, true); + + var contains = false; + var ltID = landTile.ID; + + for (var j = 0; !contains && j < InvalidLandTiles.Length; ++j) + contains = ltID == InvalidLandTiles[j]; + + if (contains && statics.Length == 0) + { + var eable = GetItemsInRange(point, 0); + + contains = !eable.Any(item => item.Visible); + + eable.Free(); + + if (contains) + return false; + } + + for (var j = 0; j < statics.Length; ++j) + { + var t = statics[j]; + + var id = TileData.ItemTable[t.ID & TileData.MaxItemValue]; + + flags = id.Flags; + height = id.CalcHeight; + + if (t.Z <= pointTop && t.Z + height >= point.Z && (flags & (TileFlag.Window | TileFlag.NoShoot)) != 0) + { + if (point.m_X == end.m_X && point.m_Y == end.m_Y && t.Z <= endTop && t.Z + height >= end.m_Z) + continue; + + return false; + } + + /*if (t.Z <= point.Z && t.Z+height >= point.Z && (flags&TileFlag.Window)==0 && (flags&TileFlag.NoShoot)!=0 + && ( (flags&TileFlag.Wall)!=0 || (flags&TileFlag.Roof)!=0 || (((flags&TileFlag.Surface)!=0 && zd != 0)) ) )*/ + /*{ + //Console.WriteLine( "LoS: Blocked by Static \"{0}\" Z:{1} T:{3} P:{2} F:x{4:X}", TileData.ItemTable[t.ID&TileData.MaxItemValue].Name, t.Z, point, t.Z+height, flags ); + //Console.WriteLine( "if ({0} && {1} && {2} && ( {3} || {4} || {5} || ({6} && {7} && {8}) ) )", t.Z <= point.Z, t.Z+height >= point.Z, (flags&TileFlag.Window)==0, (flags&TileFlag.Impassable)!=0, (flags&TileFlag.Wall)!=0, (flags&TileFlag.Roof)!=0, (flags&TileFlag.Surface)!=0, t.Z != dest.Z, zd != 0 ) ; + return false; + }*/ + } + } + + var rect = new Rectangle2D(pTop.m_X, pTop.m_Y, pBottom.m_X - pTop.m_X + 1, pBottom.m_Y - pTop.m_Y + 1); + + var area = GetItemsInBounds(rect); + + foreach (var i in area) + { + if (!i.Visible) + continue; + + if (i is BaseMulti || i.ItemID > TileData.MaxItemValue) + continue; + + var id = i.ItemData; + flags = id.Flags; + + if ((flags & (TileFlag.Window | TileFlag.NoShoot)) == 0) + continue; + + height = id.CalcHeight; + + var found = false; + + var count = path.Count; + + for (var j = 0; j < count; ++j) + { + var point = path[j]; + var pointTop = point.m_Z + 1; + var loc = i.Location; + + // if (t.Z <= point.Z && t.Z+height >= point.Z && ( height != 0 || ( t.Z == dest.Z && zd != 0 ) )) + if (loc.m_X == point.m_X && loc.m_Y == point.m_Y && loc.m_Z <= pointTop && loc.m_Z + height >= point.m_Z) + if (loc.m_X != end.m_X || loc.m_Y != end.m_Y || loc.m_Z > endTop || loc.m_Z + height < end.m_Z) + { + found = true; + break; + } + } + + if (!found) + continue; + + area.Free(); + return false; + + /*if ((flags & (TileFlag.Impassable | TileFlag.Surface | TileFlag.Roof)) != 0) + + //flags = TileData.ItemTable[i.ItemID&TileData.MaxItemValue].Flags; + //if ((flags&TileFlag.Window)==0 && (flags&TileFlag.NoShoot)!=0 && ( (flags&TileFlag.Wall)!=0 || (flags&TileFlag.Roof)!=0 || (((flags&TileFlag.Surface)!=0 && zd != 0)) )) + { + //height = TileData.ItemTable[i.ItemID&TileData.MaxItemValue].Height; + //Console.WriteLine( "LoS: Blocked by ITEM \"{0}\" P:{1} T:{2} F:x{3:X}", TileData.ItemTable[i.ItemID&TileData.MaxItemValue].Name, i.Location, i.Location.Z+height, flags ); + area.Free(); + return false; + }*/ + } + + area.Free(); + return true; + } + + public bool LineOfSight(object from, object dest) => + from == dest || (from as Mobile)?.AccessLevel > AccessLevel.Player || + (dest as Item)?.RootParent == from || LineOfSight(GetPoint(from, true), GetPoint(dest, false)); + + public bool LineOfSight(Mobile from, Point3D target) + { + if (from.AccessLevel > AccessLevel.Player) + return true; + + var eye = from.Location; + + eye.Z += 14; + + return LineOfSight(eye, target); + } + + public bool LineOfSight(Mobile from, Mobile to) + { + if (from == to || from.AccessLevel > AccessLevel.Player) + return true; + + var eye = from.Location; + var target = to.Location; + + eye.Z += 14; + target.Z += 14; // 10; + + return LineOfSight(eye, target); + } + + public Point3D GetRandomNearbyLocation( + Point3D loc, int maxRange = 2, int minRange = 0, int retryCount = 10, + int height = 16, bool checkBlocksFit = false, + bool checkMobiles = false + ) + { + var j = 0; + var range = maxRange - minRange; + var locs = range <= 10 ? new bool[range + 1, range + 1] : null; + + do + { + var xRand = Utility.Random(range); + var yRand = Utility.Random(range); + + if (locs?[xRand, yRand] != true) + { + var x = loc.X + xRand + minRange; + var y = loc.Y + yRand + minRange; + + if (CanFit(x, y, loc.Z, height, checkBlocksFit, checkMobiles)) + { + loc = new Point3D(x, y, loc.Z); + break; + } + + var z = GetAverageZ(x, y); + + if (CanFit(x, y, z, height, checkBlocksFit, checkMobiles)) + { + loc = new Point3D(x, y, z); + break; + } + + if (locs != null) + locs[xRand, yRand] = true; + } + + j++; + } while (j < retryCount); + + return loc; + } + + public class NullEnumerable : IPooledEnumerable + { + public static readonly NullEnumerable Instance = new NullEnumerable(); + + private readonly IEnumerable m_Empty; + + private NullEnumerable() => m_Empty = Enumerable.Empty(); + + IEnumerator IEnumerable.GetEnumerator() => m_Empty.GetEnumerator(); + + public IEnumerator GetEnumerator() => m_Empty.GetEnumerator(); + + public void Free() + { + } + } + + public sealed class PooledEnumerable : IPooledEnumerable, IDisposable + { + private static readonly Queue> _Buffer = new Queue>(0x400); + + private bool m_IsDisposed; + + private List m_Pool = new List(0x40); + + public PooledEnumerable(IEnumerable pool) + { + m_Pool.AddRange(pool); + } + + public void Dispose() + { + m_IsDisposed = true; + + m_Pool.Clear(); + m_Pool.TrimExcess(); + m_Pool = null; + } + + IEnumerator IEnumerable.GetEnumerator() => m_Pool.GetEnumerator(); + + public IEnumerator GetEnumerator() => m_Pool.GetEnumerator(); + + public void Free() + { + if (m_IsDisposed) + return; + + m_Pool.Clear(); + m_Pool.Capacity = Math.Max(m_Pool.Capacity, 0x100); + + lock (((ICollection)_Buffer).SyncRoot) + { + _Buffer.Enqueue(this); + } + } +#pragma warning disable CA1000 // Do not declare static members on generic types + public static PooledEnumerable Instantiate( + Map map, Rectangle2D bounds, PooledEnumeration.Selector selector + ) + { + PooledEnumerable e = null; + + lock (((ICollection)_Buffer).SyncRoot) + { + if (_Buffer.Count > 0) + e = _Buffer.Dequeue(); + } + + var pool = PooledEnumeration.EnumerateSectors(map, bounds).SelectMany(s => selector(s, bounds)); + + if (e == null) + return new PooledEnumerable(pool); + + e.m_Pool.AddRange(pool); + return e; + } + } +#pragma warning restore CA1000 // Do not declare static members on generic types + } +} diff --git a/Projects/Server/Menus/IMenu.cs b/Projects/Server/Menus/IMenu.cs index 658367f2e..d9892ab59 100644 --- a/Projects/Server/Menus/IMenu.cs +++ b/Projects/Server/Menus/IMenu.cs @@ -1,33 +1,33 @@ -/*************************************************************************** - * IMenu.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Menus -{ - public interface IMenu - { - int Serial { get; } - int EntryLength { get; } - void SendTo(NetState state); - void OnCancel(NetState state); - void OnResponse(NetState state, int index); - } -} +/*************************************************************************** + * IMenu.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Menus +{ + public interface IMenu + { + int Serial { get; } + int EntryLength { get; } + void SendTo(NetState state); + void OnCancel(NetState state); + void OnResponse(NetState state, int index); + } +} diff --git a/Projects/Server/Menus/ItemListMenu.cs b/Projects/Server/Menus/ItemListMenu.cs index 70286c8e2..b83a167f7 100644 --- a/Projects/Server/Menus/ItemListMenu.cs +++ b/Projects/Server/Menus/ItemListMenu.cs @@ -1,81 +1,81 @@ -/*************************************************************************** - * ItemListMenu.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Menus.ItemLists -{ - public class ItemListEntry - { - public ItemListEntry(string name, int itemID, int hue = 0) - { - Name = name?.Trim() ?? ""; - ItemID = itemID; - Hue = hue; - } - - public string Name { get; } - - public int ItemID { get; } - - public int Hue { get; } - } - - public class ItemListMenu : IMenu - { - private static int m_NextSerial; - - public ItemListMenu(string question, ItemListEntry[] entries) - { - Question = question.Trim(); - Entries = entries; - - do - { - Serial = m_NextSerial++; - Serial &= 0x7FFFFFFF; - } while (Serial == 0); - - Serial = (int)((uint)Serial | 0x80000000); - } - - public string Question { get; } - - public ItemListEntry[] Entries { get; set; } - - public int Serial { get; } - - public int EntryLength => Entries.Length; - - public virtual void OnCancel(NetState state) - { - } - - public virtual void OnResponse(NetState state, int index) - { - } - - public void SendTo(NetState state) - { - state.AddMenu(this); - state.Send(new DisplayItemListMenu(this)); - } - } -} +/*************************************************************************** + * ItemListMenu.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Menus.ItemLists +{ + public class ItemListEntry + { + public ItemListEntry(string name, int itemID, int hue = 0) + { + Name = name?.Trim() ?? ""; + ItemID = itemID; + Hue = hue; + } + + public string Name { get; } + + public int ItemID { get; } + + public int Hue { get; } + } + + public class ItemListMenu : IMenu + { + private static int m_NextSerial; + + public ItemListMenu(string question, ItemListEntry[] entries) + { + Question = question.Trim(); + Entries = entries; + + do + { + Serial = m_NextSerial++; + Serial &= 0x7FFFFFFF; + } while (Serial == 0); + + Serial = (int)((uint)Serial | 0x80000000); + } + + public string Question { get; } + + public ItemListEntry[] Entries { get; set; } + + public int Serial { get; } + + public int EntryLength => Entries.Length; + + public virtual void OnCancel(NetState state) + { + } + + public virtual void OnResponse(NetState state, int index) + { + } + + public void SendTo(NetState state) + { + state.AddMenu(this); + state.Send(new DisplayItemListMenu(this)); + } + } +} diff --git a/Projects/Server/Menus/QuestionMenu.cs b/Projects/Server/Menus/QuestionMenu.cs index 8b6d4761e..9b3475563 100644 --- a/Projects/Server/Menus/QuestionMenu.cs +++ b/Projects/Server/Menus/QuestionMenu.cs @@ -1,63 +1,63 @@ -/*************************************************************************** - * QuestionMenu.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Menus.Questions -{ - public class QuestionMenu : IMenu - { - private static int m_NextSerial; - - public QuestionMenu(string question, string[] answers) - { - Question = question?.Trim() ?? ""; - Answers = answers; - - do - { - Serial = ++m_NextSerial; - Serial &= 0x7FFFFFFF; - } while (Serial == 0); - } - - public string Question { get; } - - public string[] Answers { get; } - - public int Serial { get; } - - public int EntryLength => Answers.Length; - - public virtual void OnCancel(NetState state) - { - } - - public virtual void OnResponse(NetState state, int index) - { - } - - public void SendTo(NetState state) - { - state.AddMenu(this); - state.Send(new DisplayQuestionMenu(this)); - } - } -} +/*************************************************************************** + * QuestionMenu.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Menus.Questions +{ + public class QuestionMenu : IMenu + { + private static int m_NextSerial; + + public QuestionMenu(string question, string[] answers) + { + Question = question?.Trim() ?? ""; + Answers = answers; + + do + { + Serial = ++m_NextSerial; + Serial &= 0x7FFFFFFF; + } while (Serial == 0); + } + + public string Question { get; } + + public string[] Answers { get; } + + public int Serial { get; } + + public int EntryLength => Answers.Length; + + public virtual void OnCancel(NetState state) + { + } + + public virtual void OnResponse(NetState state, int index) + { + } + + public void SendTo(NetState state) + { + state.AddMenu(this); + state.Send(new DisplayQuestionMenu(this)); + } + } +} diff --git a/Projects/Server/Mobile.cs b/Projects/Server/Mobile.cs index 512a4453e..d1765068a 100644 --- a/Projects/Server/Mobile.cs +++ b/Projects/Server/Mobile.cs @@ -1,9332 +1,9500 @@ -/*************************************************************************** - * Mobile.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; -using Server.Accounting; -using Server.ContextMenus; -using Server.Guilds; -using Server.Gumps; -using Server.HuePickers; -using Server.Items; -using Server.Menus; -using Server.Mobiles; -using Server.Network; -using Server.Prompts; -using Server.Targeting; -using Server.Utilities; - -namespace Server -{ - public delegate void TargetCallback(Mobile from, object targeted); - - public delegate void TargetStateCallback(Mobile from, object targeted, T state); - - public delegate void PromptCallback(Mobile from, string text); - - public delegate void PromptStateCallback(Mobile from, string text, T state); - - public class TimedSkillMod : SkillMod - { - private readonly DateTime m_Expire; - - public TimedSkillMod(SkillName skill, bool relative, double value, TimeSpan delay) - : this(skill, relative, value, DateTime.UtcNow + delay) - { - } - - public TimedSkillMod(SkillName skill, bool relative, double value, DateTime expire) - : base(skill, relative, value) => - m_Expire = expire; - - public override bool CheckCondition() => DateTime.UtcNow < m_Expire; - } - - public class EquippedSkillMod : SkillMod - { - private readonly Item m_Item; - private readonly Mobile m_Mobile; - - public EquippedSkillMod(SkillName skill, bool relative, double value, Item item, Mobile mobile) - : base(skill, relative, value) - { - m_Item = item; - m_Mobile = mobile; - } - - public override bool CheckCondition() => !m_Item.Deleted && !m_Mobile.Deleted && m_Item.Parent == m_Mobile; - } - - public class DefaultSkillMod : SkillMod - { - public DefaultSkillMod(SkillName skill, bool relative, double value) - : base(skill, relative, value) - { - } - - public override bool CheckCondition() => true; - } - - public abstract class SkillMod - { - private bool m_ObeyCap; - private Mobile m_Owner; - private bool m_Relative; - private SkillName m_Skill; - private double m_Value; - - protected SkillMod(SkillName skill, bool relative, double value) - { - m_Skill = skill; - m_Relative = relative; - m_Value = value; - } - - public bool ObeyCap - { - get => m_ObeyCap; - set - { - m_ObeyCap = value; - - var sk = m_Owner?.Skills[m_Skill]; - sk?.Update(); - } - } - - public Mobile Owner - { - get => m_Owner; - set - { - if (m_Owner != value) - { - m_Owner?.RemoveSkillMod(this); - - m_Owner = value; - - if (m_Owner != value) - m_Owner.AddSkillMod(this); - } - } - } - - public SkillName Skill - { - get => m_Skill; - set - { - if (m_Skill != value) - { - var oldUpdate = m_Owner?.Skills[m_Skill]; - - m_Skill = value; - - var sk = m_Owner?.Skills[m_Skill]; - sk?.Update(); - oldUpdate?.Update(); - } - } - } - - public bool Relative - { - get => m_Relative; - set - { - if (m_Relative != value) - { - m_Relative = value; - - var sk = m_Owner?.Skills[m_Skill]; - sk?.Update(); - } - } - } - - public bool Absolute - { - get => !m_Relative; - set - { - if (m_Relative == value) - { - m_Relative = !value; - - var sk = m_Owner?.Skills[m_Skill]; - sk?.Update(); - } - } - } - - public double Value - { - get => m_Value; - set - { - if (m_Value != value) - { - m_Value = value; - - var sk = m_Owner?.Skills[m_Skill]; - sk?.Update(); - } - } - } - - public void Remove() - { - Owner = null; - } - - public abstract bool CheckCondition(); - } - - public class ResistanceMod - { - private int m_Offset; - private ResistanceType m_Type; - - public ResistanceMod(ResistanceType type, int offset) - { - m_Type = type; - m_Offset = offset; - } - - public Mobile Owner { get; set; } - - public ResistanceType Type - { - get => m_Type; - set - { - if (m_Type != value) - { - m_Type = value; - - Owner?.UpdateResistances(); - } - } - } - - public int Offset - { - get => m_Offset; - set - { - if (m_Offset != value) - { - m_Offset = value; - - Owner?.UpdateResistances(); - } - } - } - } - - public class StatMod - { - private readonly DateTime m_Added; - private readonly TimeSpan m_Duration; - - public StatMod(StatType type, string name, int offset, TimeSpan duration) - { - Type = type; - Name = name; - Offset = offset; - m_Duration = duration; - m_Added = DateTime.UtcNow; - } - - public StatType Type { get; } - - public string Name { get; } - - public int Offset { get; } - - public bool HasElapsed() - { - if (m_Duration == TimeSpan.Zero) - return false; - - return DateTime.UtcNow - m_Added >= m_Duration; - } - } - - public class DamageEntry - { - public DamageEntry(Mobile damager) => Damager = damager; - - public Mobile Damager { get; } - - public int DamageGiven { get; set; } - - public DateTime LastDamage { get; set; } - - public bool HasExpired => DateTime.UtcNow > LastDamage + ExpireDelay; - - public List Responsible { get; set; } - - public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); - } - - [Flags] - public enum StatType - { - Str = 1, - Dex = 2, - Int = 4, - All = 7 - } - - public enum StatLockType : byte - { - Up, - Down, - Locked - } - - [CustomEnum(new[] { "North", "Right", "East", "Down", "South", "Left", "West", "Up" })] - [Flags] - public enum Direction : byte - { - North = 0x0, - Right = 0x1, - East = 0x2, - Down = 0x3, - South = 0x4, - Left = 0x5, - West = 0x6, - Up = 0x7, - - Mask = 0x7, - Running = 0x80, - ValueMask = 0x87 - } - - [Flags] - public enum MobileDelta - { - None = 0x00000000, - Name = 0x00000001, - Flags = 0x00000002, - Hits = 0x00000004, - Mana = 0x00000008, - Stam = 0x00000010, - Stat = 0x00000020, - Noto = 0x00000040, - Gold = 0x00000080, - Weight = 0x00000100, - Direction = 0x00000200, - Hue = 0x00000400, - Body = 0x00000800, - Armor = 0x00001000, - StatCap = 0x00002000, - GhostUpdate = 0x00004000, - Followers = 0x00008000, - Properties = 0x00010000, - TithingPoints = 0x00020000, - Resistances = 0x00040000, - WeaponDamage = 0x00080000, - Hair = 0x00100000, - FacialHair = 0x00200000, - Race = 0x00400000, - HealthbarYellow = 0x00800000, - HealthbarPoison = 0x01000000, - - Attributes = 0x0000001C - } - - public enum AccessLevel - { - Player, - Counselor, - GameMaster, - Seer, - Administrator, - Developer, - Owner - } - - public enum VisibleDamageType - { - None, - Related, - Everyone, - Selective - } - - public enum ResistanceType - { - Physical, - Fire, - Cold, - Poison, - Energy - } - - public enum ApplyPoisonResult - { - Poisoned, - Immune, - HigherPoisonActive, - Cured - } - - [Serializable] - public class MobileNotConnectedException : Exception - { - public MobileNotConnectedException(Mobile source, string message) - : base(message) => - Source = source.ToString(); - - public MobileNotConnectedException(Mobile source, string message, Exception innerException) - : base(message, innerException) => - Source = source.ToString(); - - protected MobileNotConnectedException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } - - public delegate bool SkillCheckTargetHandler(Mobile from, SkillName skill, object target, double minSkill, - double maxSkill); - - public delegate bool SkillCheckLocationHandler(Mobile from, SkillName skill, double minSkill, double maxSkill); - - public delegate bool SkillCheckDirectTargetHandler(Mobile from, SkillName skill, object target, double chance); - - public delegate bool SkillCheckDirectLocationHandler(Mobile from, SkillName skill, double chance); - - public delegate TimeSpan RegenRateHandler(Mobile from); - - public delegate bool AllowBeneficialHandler(Mobile from, Mobile target); - - public delegate bool AllowHarmfulHandler(Mobile from, Mobile target); - - public delegate Container CreateCorpseHandler(Mobile from, HairInfo hair, FacialHairInfo facialhair, - List initialContent, List equippedItems); - - public delegate int AOSStatusHandler(Mobile from, int index); - - /// - /// Base class representing players, npcs, and creatures. - /// - public class Mobile : IHued, IComparable, ISerializable, ISpawnable, IPropertyListObject - { - private readonly BufferWriter m_SaveBuffer; - public BufferWriter SaveBuffer => m_SaveBuffer; - - private const int - WarmodeCatchCount = 4; // Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds - - private static readonly TimeSpan WarmodeSpamCatch = TimeSpan.FromSeconds(Core.SE ? 1.0 : 0.5); - private static readonly TimeSpan WarmodeSpamDelay = TimeSpan.FromSeconds(Core.SE ? 4.0 : 2.0); - - private static readonly Packet[][] m_MovingPacketCache = { - new Packet[8], - new Packet[8] - }; - - private static readonly List m_MoveList = new List(); - private static readonly List m_MoveClientList = new List(); - - private static readonly object m_GhostMutateContext = new object(); - - private static readonly List m_Hears = new List(); - private static readonly List m_OnSpeech = new List(); - - private static readonly string[] m_AccessLevelNames = - { - "a player", - "a counselor", - "a game master", - "a seer", - "an administrator", - "a developer", - "an owner" - }; - - private static readonly int[] m_InvalidBodies = - { - 32, - 95, - 156, - 197, - 198 - }; - - private static readonly Queue m_DeltaQueue = new Queue(); - private static readonly Queue m_DeltaQueueR = new Queue(); - - private static bool _processing; - - private static readonly string[] m_GuildTypes = - { - "", - " (Chaos)", - " (Order)" - }; - - private Timer m_AutoManifestTimer; - - private Container m_Backpack; - - private BankBox m_BankBox; - - private int m_ChangingCombatant; - - private MobileDelta m_DeltaFlags; - - private long m_EndQueue; - - private Item m_Holding; - - private int m_HueMod = -1; - - private bool m_InDeltaQueue; - - /* Logout: - * - * When a client logs into mobile x - * - if (x is Internalized ) move x to logout location and map - * - * When a client attached to a mobile disconnects - * - LogoutTimer is started - * - Delay is taken from Region.GetLogoutDelay to allow insta-logout regions. - * - OnTick : Location and map are stored, and mobile is internalized - * - * Some things to consider: - * - An internalized person getting killed (say, by poison). Where does the body go? - * - Regions now have a GetLogoutDelay( Mobile m ); virtual function (see above) - */ - - private Item m_MountItem; - - private string m_NameMod; - - private QuestArrow m_QuestArrow; - - private int m_SolidHueOverride = -1; - - private StatLockType m_StrLock, m_DexLock, m_IntLock; - private IWeapon m_Weapon; - - private bool m_YellowHealthbar; - - public Mobile(Serial serial) - { - m_Region = Map.Internal.DefaultRegion; - Serial = serial; - Aggressors = new List(); - Aggressed = new List(); - NextSkillTime = Core.TickCount; - DamageEntries = new List(); - - var ourType = GetType(); - TypeRef = World.m_MobileTypes.IndexOf(ourType); - - if (TypeRef == -1) - { - World.m_MobileTypes.Add(ourType); - TypeRef = World.m_MobileTypes.Count - 1; - } - - m_SaveBuffer = new BufferWriter(true); - } - - public Mobile() - { - m_Region = Map.Internal.DefaultRegion; - Serial = Serial.NewMobile; - - DefaultMobileInit(); - - World.AddMobile(this); - - var ourType = GetType(); - TypeRef = World.m_MobileTypes.IndexOf(ourType); - - if (TypeRef == -1) - { - World.m_MobileTypes.Add(ourType); - TypeRef = World.m_MobileTypes.Count - 1; - } - - m_SaveBuffer = new BufferWriter(true); - } - - public static bool DragEffects { get; set; } = true; - - [CommandProperty(AccessLevel.GameMaster)] - public Race Race - { - get => m_Race ?? (m_Race = Race.DefaultRace); - set - { - var oldRace = Race; - - m_Race = value ?? Race.DefaultRace; - - Body = m_Race.Body(this); - UpdateResistances(); - - Delta(MobileDelta.Race); - - OnRaceChange(oldRace); - } - } - - public virtual double RacialSkillBonus => 0; - - public int[] Resistances { get; private set; } - - public virtual int BasePhysicalResistance => 0; - public virtual int BaseFireResistance => 0; - public virtual int BaseColdResistance => 0; - public virtual int BasePoisonResistance => 0; - public virtual int BaseEnergyResistance => 0; - - [CommandProperty(AccessLevel.Counselor)] - public virtual int PhysicalResistance => GetResistance(ResistanceType.Physical); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int FireResistance => GetResistance(ResistanceType.Fire); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int ColdResistance => GetResistance(ResistanceType.Cold); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int PoisonResistance => GetResistance(ResistanceType.Poison); - - [CommandProperty(AccessLevel.Counselor)] - public virtual int EnergyResistance => GetResistance(ResistanceType.Energy); - - public List ResistanceMods { get; set; } - - public static int MaxPlayerResistance { get; set; } = 70; - - public virtual bool NewGuildDisplay => false; - - public List Stabled { get; private set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public VirtueInfo Virtues { get; private set; } - - public object Party { get; set; } - - public List SkillMods { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int VirtualArmorMod - { - get => m_VirtualArmorMod; - set - { - if (m_VirtualArmorMod != value) - { - m_VirtualArmorMod = value; - - Delta(MobileDelta.Armor); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int MeleeDamageAbsorb { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int MagicDamageAbsorb { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int SkillsTotal => Skills?.Total ?? 0; - - [CommandProperty(AccessLevel.GameMaster)] - public int SkillsCap - { - get => Skills?.Cap ?? 0; - set - { - if (Skills != null) - Skills.Cap = value; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int BaseSoundID { get; set; } - - public long NextCombatTime { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int NameHue { get; set; } = -1; - - [CommandProperty(AccessLevel.GameMaster)] - public int Hunger - { - get => m_Hunger; - set - { - var oldValue = m_Hunger; - - if (oldValue != value) - { - m_Hunger = value; - - EventSink.InvokeHungerChanged(this, oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Thirst { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int BAC { get; set; } - - /// - /// Gets or sets the number of steps this player may take when hidden before being revealed. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int AllowedStealthSteps { get; set; } - - public Item Holding - { - get => m_Holding; - set - { - if (m_Holding != value) - { - if (m_Holding != null) - { - UpdateTotal(m_Holding, TotalType.Weight, -(m_Holding.TotalWeight + m_Holding.PileWeight)); - - if (m_Holding.HeldBy == this) - m_Holding.HeldBy = null; - } - - if (value != null && m_Holding != null) - DropHolding(); - - m_Holding = value; - - if (m_Holding != null) - { - UpdateTotal(m_Holding, TotalType.Weight, m_Holding.TotalWeight + m_Holding.PileWeight); - - m_Holding.HeldBy ??= this; - } - } - } - } - - public long LastMoveTime { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual bool Paralyzed - { - get => m_Paralyzed; - set - { - if (m_Paralyzed != value) - { - m_Paralyzed = value; - Delta(MobileDelta.Flags); - - SendLocalizedMessage(m_Paralyzed ? 502381 : 502382); - - if (m_ParaTimer != null) - { - m_ParaTimer.Stop(); - m_ParaTimer = null; - } - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool DisarmReady { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool StunReady { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Frozen - { - get => m_Frozen; - set - { - if (m_Frozen != value) - { - m_Frozen = value; - Delta(MobileDelta.Flags); - - if (m_FrozenTimer != null) - { - m_FrozenTimer.Stop(); - m_FrozenTimer = null; - } - } - } - } - - /// - /// Gets or sets the lock state for the property. - /// - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public StatLockType StrLock - { - get => m_StrLock; - set - { - if (m_StrLock != value) - { - m_StrLock = value; - - m_NetState?.Send(new StatLockInfo(this)); - } - } - } - - /// - /// Gets or sets the lock state for the property. - /// - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public StatLockType DexLock - { - get => m_DexLock; - set - { - if (m_DexLock != value) - { - m_DexLock = value; - - m_NetState?.Send(new StatLockInfo(this)); - } - } - } - - /// - /// Gets or sets the lock state for the property. - /// - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public StatLockType IntLock - { - get => m_IntLock; - set - { - if (m_IntLock != value) - { - m_IntLock = value; - - m_NetState?.Send(new StatLockInfo(this)); - } - } - } - - public long NextActionTime { get; set; } - - public long NextActionMessage { get; set; } - - public static int ActionMessageDelay { get; set; } = 125; - - public static bool GlobalRegenThroughPoison { get; set; } = true; - - public virtual bool RegenThroughPoison => GlobalRegenThroughPoison; - - public virtual bool CanRegenHits => Alive && (RegenThroughPoison || !Poisoned); - public virtual bool CanRegenStam => Alive; - public virtual bool CanRegenMana => Alive; - - public long NextSkillTime { get; set; } - - public List Aggressors { get; private set; } - - public List Aggressed { get; private set; } - - public bool ChangingCombatant => m_ChangingCombatant > 0; - - /// - /// Overridable. Gets or sets which Mobile that this Mobile is currently engaged in combat with. - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual Mobile Combatant - { - get => m_Combatant; - set - { - if (Deleted) - return; - - if (m_Combatant != value && value != this) - { - var old = m_Combatant; - - ++m_ChangingCombatant; - m_Combatant = value; - - if (m_Combatant != null && !CanBeHarmful(m_Combatant, false) || - !Region.OnCombatantChange(this, old, m_Combatant)) - { - m_Combatant = old; - --m_ChangingCombatant; - return; - } - - if (m_Combatant == null) - { - m_NetState?.Send(new ChangeCombatant(Serial.Zero)); - m_ExpireCombatant?.Stop(); - m_CombatTimer?.Stop(); - - m_ExpireCombatant = null; - m_CombatTimer = null; - } - else - { - m_NetState?.Send(new ChangeCombatant(m_Combatant.Serial)); - m_ExpireCombatant ??= new ExpireCombatantTimer(this); - m_ExpireCombatant.Start(); - - m_CombatTimer ??= new CombatTimer(this); - m_CombatTimer.Start(); - - if (CanBeHarmful(m_Combatant, false)) - { - DoHarmful(m_Combatant); - m_Combatant.PlaySound(m_Combatant.GetAngerSound()); - } - } - - OnCombatantChange(); - --m_ChangingCombatant; - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalGold => GetTotal(TotalType.Gold); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalItems => GetTotal(TotalType.Items); - - [CommandProperty(AccessLevel.GameMaster)] - public int TotalWeight => GetTotal(TotalType.Weight); - - [CommandProperty(AccessLevel.GameMaster)] - public int TithingPoints - { - get => m_TithingPoints; - set - { - if (m_TithingPoints != value) - { - m_TithingPoints = value; - - Delta(MobileDelta.TithingPoints); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Followers - { - get => m_Followers; - set - { - if (m_Followers != value) - { - m_Followers = value; - - Delta(MobileDelta.Followers); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int FollowersMax - { - get => m_FollowersMax; - set - { - if (m_FollowersMax != value) - { - m_FollowersMax = value; - - Delta(MobileDelta.Followers); - } - } - } - - public bool TargetLocked { get; set; } - - public Target Target - { - get => m_Target; - set - { - var oldTarget = m_Target; - var newTarget = value; - - if (oldTarget == newTarget) - return; - - m_Target = null; - - if (oldTarget != null && newTarget != null) - oldTarget.Cancel(this, TargetCancelType.Overridden); - - m_Target = newTarget; - - if (newTarget != null && m_NetState != null && !TargetLocked) - m_NetState.Send(newTarget.GetPacketFor(m_NetState)); - - OnTargetChange(); - } - } - - public ContextMenu ContextMenu - { - get => m_ContextMenu; - set - { - m_ContextMenu = value; - - if (m_ContextMenu != null && m_NetState != null) - { - // Old packet is preferred until assistants catch up - if (m_NetState.NewHaven && m_ContextMenu.RequiresNewPacket) - Send(new DisplayContextMenu(m_ContextMenu)); - else - Send(new DisplayContextMenuOld(m_ContextMenu)); - } - } - } - - public bool Pushing { get; set; } - - public static int WalkFoot { get; set; } = 400; - - public static int RunFoot { get; set; } = 200; - - public static int WalkMount { get; set; } = 200; - - public static int RunMount { get; set; } = 100; - - public static AccessLevel FwdAccessOverride { get; set; } = AccessLevel.Counselor; - - public static bool FwdEnabled { get; set; } = true; - - public static bool FwdUOTDOverride { get; set; } - - public static int FwdMaxSteps { get; set; } = 4; - - public virtual bool IsDeadBondedPet => false; - - public ISpell Spell - { - get => m_Spell; - set - { - if (m_Spell != null && value != null) - Console.WriteLine("Warning: Spell has been overwritten"); - - m_Spell = value; - } - } - - [CommandProperty(AccessLevel.Administrator)] - public bool AutoPageNotify { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] - public IAccount Account { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int VirtualArmor - { - get => m_VirtualArmor; - set - { - if (m_VirtualArmor != value) - { - m_VirtualArmor = value; - - Delta(MobileDelta.Armor); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual double ArmorRating => 0.0; - - /// - /// Overridable. Returns true if the player is alive, false if otherwise. By default, this is computed by: - /// !Deleted && (!Player || !Body.IsGhost) - /// - [CommandProperty(AccessLevel.Counselor)] - public virtual bool Alive => !Deleted && (!m_Player || !m_Body.IsGhost); - - public static CreateCorpseHandler CreateCorpseHandler { get; set; } - - public virtual bool RetainPackLocsOnDeath => Core.AOS; - - [CommandProperty(AccessLevel.GameMaster)] - public Container Corpse { get; set; } - - public static char[] GhostChars { get; set; } = { 'o', 'O' }; - - public static bool NoSpeechLOS { get; set; } - - public static TimeSpan AutoManifestTimeout { get; set; } = TimeSpan.FromSeconds(5.0); - - public static bool InsuranceEnabled { get; set; } - - public static int ActionDelay { get; set; } = 500; - - public static VisibleDamageType VisibleDamageType { get; set; } - - public List DamageEntries { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public Mobile LastKiller { get; set; } - - public static bool DefaultShowVisibleDamage { get; set; } - - public static bool DefaultCanSeeVisibleDamage { get; set; } - - public virtual bool ShowVisibleDamage => DefaultShowVisibleDamage; - public virtual bool CanSeeVisibleDamage => DefaultCanSeeVisibleDamage; - - [CommandProperty(AccessLevel.GameMaster)] - public bool Squelched { get; set; } - - public virtual bool ShouldCheckStatTimers => true; - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime CreationTime { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int LightLevel - { - get => m_LightLevel; - set - { - if (m_LightLevel != value) - { - m_LightLevel = value; - - CheckLightLevels(false); - - /*if (m_NetState != null) - m_NetState.Send( new PersonalLightLevel( this ) );*/ - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public string Profile { get; set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public bool ProfileLocked { get; set; } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] - public bool Player - { - get => m_Player; - set - { - m_Player = value; - InvalidateProperties(); - - if (!m_Player && m_Dex <= 100 && m_CombatTimer != null) - m_CombatTimer.Priority = TimerPriority.FiftyMS; - else if (m_CombatTimer != null) - m_CombatTimer.Priority = TimerPriority.EveryTick; - - CheckStatTimers(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public string Title - { - get => m_Title; - set - { - m_Title = value; - InvalidateProperties(); - } - } - - public List Items { get; private set; } - - public virtual int MaxWeight => int.MaxValue; - - public static IWeapon DefaultWeapon { get; set; } - - [CommandProperty(AccessLevel.Counselor)] - public Skills Skills { get; private set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.Administrator)] - public AccessLevel AccessLevel - { - get => m_AccessLevel; - set - { - var oldValue = m_AccessLevel; - - if (oldValue != value) - { - m_AccessLevel = value; - Delta(MobileDelta.Noto); - InvalidateProperties(); - - SendMessage("Your access level has been changed. You are now {0}.", GetAccessLevelName(value)); - - ClearScreen(); - SendEverything(); - - OnAccessLevelChanged(oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Fame - { - get => m_Fame; - set - { - var oldValue = m_Fame; - - if (oldValue != value) - { - m_Fame = value; - - if (ShowFameTitle && (m_Player || m_Body.IsHuman) && oldValue >= 10000 != value >= 10000) - InvalidateProperties(); - - OnFameChange(oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int Karma - { - get => m_Karma; - set - { - var old = m_Karma; - - if (old != value) - { - m_Karma = value; - OnKarmaChange(old); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Blessed - { - get => m_Blessed; - set - { - if (m_Blessed != value) - { - m_Blessed = value; - Delta(MobileDelta.HealthbarYellow); - } - } - } - - public virtual int Luck => 0; - - [Hue] - [CommandProperty(AccessLevel.GameMaster)] - public int HueMod - { - get => m_HueMod; - set - { - if (m_HueMod != value) - { - m_HueMod = value; - - Delta(MobileDelta.Hue); - } - } - } - - [Hue] - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Hue - { - get - { - if (m_HueMod != -1) - return m_HueMod; - - return m_Hue; - } - set - { - var oldHue = m_Hue; - - if (oldHue != value) - { - m_Hue = value; - - Delta(MobileDelta.Hue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Direction Direction - { - get => m_Direction; - set - { - if (m_Direction != value) - { - m_Direction = value; - - Delta(MobileDelta.Direction); - // ProcessDelta(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Female - { - get => m_Female; - set - { - if (m_Female != value) - { - m_Female = value; - Delta(MobileDelta.Flags); - OnGenderChanged(!m_Female); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Flying - { - get => m_Flying; - set - { - if (m_Flying != value) - { - m_Flying = value; - Delta(MobileDelta.Flags); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Warmode - { - get => m_Warmode; - set - { - if (Deleted) - return; - - if (m_Warmode != value) - { - if (m_AutoManifestTimer != null) - { - m_AutoManifestTimer.Stop(); - m_AutoManifestTimer = null; - } - - m_Warmode = value; - Delta(MobileDelta.Flags); - - if (m_NetState != null) - Send(SetWarMode.Instantiate(value)); - - if (!m_Warmode) - Combatant = null; - - if (!Alive) - { - if (value) - Delta(MobileDelta.GhostUpdate); - else - SendRemovePacket(false); - } - - OnWarmodeChanged(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Hidden - { - get => m_Hidden; - set - { - if (m_Hidden != value) - { - m_Hidden = value; - // Delta( MobileDelta.Flags ); - - OnHiddenChanged(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] - public NetState NetState - { - get => m_NetState?.Connection != null && !m_NetState.IsDisposing ? m_NetState : null; - set - { - if (m_NetState != value) - { - m_Map?.OnClientChange(m_NetState, value, this); - - m_Target?.Cancel(this, TargetCancelType.Disconnected); - - QuestArrow = null; - - m_Spell?.OnConnectionChanged(); - - // if (m_Spell != null) - // m_Spell.FinishSequence(); - - m_NetState?.CancelAllTrades(); - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - box.Close(); - - // REMOVED: - // m_Actions.Clear(); - - m_NetState = value; - - if (m_NetState == null) - { - OnDisconnected(); - EventSink.InvokeDisconnected(this); - - // Disconnected, start the logout timer - - if (m_LogoutTimer == null) - m_LogoutTimer = new LogoutTimer(this); - else - m_LogoutTimer.Stop(); - - m_LogoutTimer.Delay = GetLogoutDelay(); - m_LogoutTimer.Start(); - } - else - { - OnConnected(); - EventSink.InvokeConnected(this); - - // Connected, stop the logout timer and if needed, move to the world - - m_LogoutTimer?.Stop(); - - m_LogoutTimer = null; - - if (m_Map == Map.Internal && LogoutMap != null) - { - Map = LogoutMap; - Location = LogoutLocation; - } - } - - for (var i = Items.Count - 1; i >= 0; --i) - { - if (i >= Items.Count) - continue; - - var item = Items[i]; - - if (item is SecureTradeContainer) - { - for (var j = item.Items.Count - 1; j >= 0; --j) - if (j < item.Items.Count) - { - item.Items[j].OnSecureTrade(this, this, this, false); - AddToBackpack(item.Items[j]); - } - - Timer.DelayCall(item.Delete); - } - } - - DropHolding(); - OnNetStateChanged(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public string Language - { - get => m_Language; - set => m_Language = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int SpeechHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int EmoteHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int WhisperHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public int YellHue { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public string GuildTitle - { - get => m_GuildTitle; - set - { - var old = m_GuildTitle; - - if (old != value) - { - m_GuildTitle = value; - - if (m_Guild?.Disbanded == false && m_GuildTitle != null) - SendLocalizedMessage(1018026, true, m_GuildTitle); // Your guild title has changed : - - InvalidateProperties(); - - OnGuildTitleChange(old); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool DisplayGuildTitle - { - get => m_DisplayGuildTitle; - set - { - m_DisplayGuildTitle = value; - InvalidateProperties(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Mobile GuildFealty { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public string NameMod - { - get => m_NameMod; - set - { - if (m_NameMod != value) - { - m_NameMod = value; - Delta(MobileDelta.Name); - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool YellowHealthbar - { - get => m_YellowHealthbar; - set - { - m_YellowHealthbar = value; - Delta(MobileDelta.HealthbarYellow); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public string RawName - { - get => m_Name; - set => Name = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual string Name - { - get => m_NameMod ?? m_Name; - set - { - if (m_Name != value) // I'm leaving out the && m_NameMod == null - { - var oldName = m_Name; - m_Name = value; - OnAfterNameChange(oldName, m_Name); - Delta(MobileDelta.Name); - InvalidateProperties(); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime LastStrGain { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime LastIntGain { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public DateTime LastDexGain { get; set; } - - public DateTime LastStatGain - { - get - { - var d = LastStrGain; - - if (LastIntGain > d) - d = LastIntGain; - - if (LastDexGain > d) - d = LastDexGain; - - return d; - } - set - { - LastStrGain = value; - LastIntGain = value; - LastDexGain = value; - } - } - - public BaseGuild Guild - { - get => m_Guild; - set - { - var old = m_Guild; - - if (old != value) - { - if (value == null) - GuildTitle = null; - - m_Guild = value; - - Delta(MobileDelta.Noto); - InvalidateProperties(); - - OnGuildChange(old); - } - } - } - - public Region WalkRegion { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Poisoned => m_Poison != null; - - [CommandProperty(AccessLevel.GameMaster)] - public bool IsBodyMod => m_BodyMod.BodyID != 0; - - [CommandProperty(AccessLevel.GameMaster)] - public Body BodyMod - { - get => m_BodyMod; - set - { - if (m_BodyMod != value) - { - m_BodyMod = value; - - Delta(MobileDelta.Body); - InvalidateProperties(); - - CheckStatTimers(); - } - } - } - - [Body] - [CommandProperty(AccessLevel.GameMaster)] - public Body Body - { - get - { - if (IsBodyMod) - return m_BodyMod; - - return m_Body; - } - set - { - if (m_Body != value && !IsBodyMod) - { - m_Body = SafeBody(value); - - Delta(MobileDelta.Body); - InvalidateProperties(); - - CheckStatTimers(); - } - } - } - - [Body] - [CommandProperty(AccessLevel.GameMaster)] - public int BodyValue - { - get => Body.BodyID; - set => Body = value; - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Point3D LogoutLocation { get; set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Map LogoutMap { get; set; } - - public Region Region => m_Region ?? (Map == null ? Map.Internal.DefaultRegion : Map.DefaultRegion); - - public Packet RemovePacket => StaticPacketHandlers.GetRemoveEntityPacket(this); - public OPLInfo OPLPacket => StaticPacketHandlers.GetOPLInfoPacket(this); - private ObjectPropertyList m_PropertyList; - public ObjectPropertyList PropertyList => m_PropertyList ??= NewObjectPropertyList(); - - public void ReleaseOPLPacket() - { - if (m_PropertyList == null) - return; - - Packet.Release(m_PropertyList); - m_PropertyList = null; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int SolidHueOverride - { - get => m_SolidHueOverride; - set - { - if (m_SolidHueOverride == value) return; - m_SolidHueOverride = value; - Delta(MobileDelta.Hue | MobileDelta.Body); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public virtual IWeapon Weapon - { - get - { - if (m_Weapon is Item item && !item.Deleted && item.Parent == this && CanSee(item)) - return m_Weapon; - - m_Weapon = null; - - item = FindItemOnLayer(Layer.OneHanded) ?? FindItemOnLayer(Layer.TwoHanded); - - if (item is IWeapon weapon) - return m_Weapon = weapon; - - return GetDefaultWeapon(); - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public BankBox BankBox - { - get - { - if (m_BankBox?.Deleted == false && m_BankBox.Parent == this) - return m_BankBox; - - m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; - - if (m_BankBox == null) - AddItem(m_BankBox = new BankBox(this)); - - return m_BankBox; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public Container Backpack - { - get - { - if (m_Backpack?.Deleted != false || m_Backpack.Parent != this) - m_Backpack = FindItemOnLayer(Layer.Backpack) as Container; - - return m_Backpack; - } - } - - public virtual bool KeepsItemsOnDeath => m_AccessLevel > AccessLevel.Player; - - public bool HasTrade => m_NetState?.Trades.Count > 0; - - public bool NoMoveHS { get; set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Kills - { - get => m_Kills; - set - { - var oldValue = m_Kills; - - if (m_Kills != value) - { - m_Kills = Math.Max(value, 0); - - if (oldValue >= 5 != m_Kills >= 5) - { - Delta(MobileDelta.Noto); - InvalidateProperties(); - } - - OnKillsChange(oldValue); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int ShortTermMurders - { - get => m_ShortTermMurders; - set - { - if (m_ShortTermMurders != value) - m_ShortTermMurders = Math.Max(value, 0); - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public bool Criminal - { - get => m_Criminal; - set - { - if (m_Criminal != value) - { - m_Criminal = value; - Delta(MobileDelta.Noto); - InvalidateProperties(); - } - - if (m_Criminal) - { - if (m_ExpireCriminal == null) - m_ExpireCriminal = new ExpireCriminalTimer(this); - else - m_ExpireCriminal.Stop(); - - m_ExpireCriminal.Start(); - } - else if (m_ExpireCriminal != null) - { - m_ExpireCriminal.Stop(); - m_ExpireCriminal = null; - } - } - } - - public static bool DisableDismountInWarmode { get; set; } - - public static int BodyWeight { get; set; } = 14; - - [CommandProperty(AccessLevel.GameMaster)] - public IMount Mount - { - get - { - Item item = null; - - if (m_MountItem?.Deleted == false && m_MountItem.Parent == this) - item = m_MountItem; - - item ??= FindItemOnLayer(Layer.Mount); - - if (!(item is IMountItem mountItem)) - return null; - - m_MountItem = item; - return mountItem.Mount; - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Mounted => Mount != null; - - public QuestArrow QuestArrow - { - get => m_QuestArrow; - set - { - if (m_QuestArrow != value) - { - m_QuestArrow?.Stop(); - - m_QuestArrow = value; - } - } - } - - public virtual bool CanTarget => true; - public virtual bool ClickTitle => true; - - public virtual bool PropertyTitle => OldPropertyTitles ? ClickTitle : true; - - public static bool DisableHiddenSelfClick { get; set; } = true; - - public static bool AsciiClickMessage { get; set; } = true; - - public static bool GuildClickMessage { get; set; } = true; - - public static bool OldPropertyTitles { get; set; } - - public virtual bool ShowFameTitle // (m_Player || m_Body.IsHuman) && m_Fame >= 10000; } - => true; - - /// - /// Gets or sets the maximum attainable value for , , and . - /// - [CommandProperty(AccessLevel.GameMaster)] - public int StatCap - { - get => m_StatCap; - set - { - if (m_StatCap != value) - { - m_StatCap = value; - - Delta(MobileDelta.StatCap); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public bool Meditating { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool CanSwim { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool CantWalk { get; set; } - - [CommandProperty(AccessLevel.GameMaster)] - public bool CanHearGhosts - { - get => m_CanHearGhosts || AccessLevel >= AccessLevel.Counselor; - set => m_CanHearGhosts = value; - } - - [CommandProperty(AccessLevel.GameMaster)] - public int RawStatTotal => RawStr + RawDex + RawInt; - - public long NextSpellTime { get; set; } - - public bool Deleted { get; private set; } - - public virtual void Delete() - { - if (Deleted) - return; - - if (!World.OnDelete(this)) - return; - - if (m_NetState != null) - { - m_NetState.CancelAllTrades(); - m_NetState.Dispose(); - } - - DropHolding(); - - Region.OnRegionChange(this, m_Region, null); - - m_Region = null; - // Is the above line REALLY needed? The old Region system did NOT have said line - // and worked fine, because of this a LOT of extra checks have to be done everywhere... - // I guess this should be there for Garbage collection purposes, but, still, is it /really/ needed? - - OnDelete(); - - for (var i = Items.Count - 1; i >= 0; --i) - if (i < Items.Count) - Items[i].OnParentDeleted(this); - - for (var i = 0; i < Stabled.Count; i++) - Stabled[i].Delete(); - - SendRemovePacket(); - - m_Guild?.OnDelete(this); - - Deleted = true; - - m_Map?.OnLeave(this); - m_Map = null; - - m_Hair = null; - m_FacialHair = null; - m_MountItem = null; - - World.RemoveMobile(this); - - OnAfterDelete(); - - FreeCache(); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Map Map - { - get => m_Map; - set - { - if (Deleted) - return; - - if (m_Map != value) - { - m_NetState?.ValidateAllTrades(); - - var oldMap = m_Map; - - if (m_Map != null) - { - m_Map.OnLeave(this); - - ClearScreen(); - SendRemovePacket(); - } - - for (var i = 0; i < Items.Count; ++i) - Items[i].Map = value; - - m_Map = value; - - UpdateRegion(); - - m_Map?.OnEnter(this); - - var ns = m_NetState; - - if (ns != null && m_Map != null) - { - ns.Sequence = 0; - if (Map != null) - ns.Send(new MapChange(Map)); - - if (!Core.SE && ns.ProtocolChanges < ProtocolChanges.Version6000) - ns.Send(new MapPatches()); - - ns.Send(SeasonChange.Instantiate(GetSeason(), true)); - - if (ns.StygianAbyss) - ns.Send(new MobileUpdate(this)); - else - ns.Send(new MobileUpdateOld(this)); - - ClearFastwalkStack(); - } - - if (ns != null) - { - if (m_Map != null) - ns.Send(new ServerChange(m_Location, m_Map)); - - ns.Sequence = 0; - ClearFastwalkStack(); - - ns.Send(MobileIncoming.Create(ns, this, this)); - - if (ns.StygianAbyss) - { - ns.Send(new MobileUpdate(this)); - CheckLightLevels(true); - ns.Send(new MobileUpdate(this)); - } - else - { - ns.Send(new MobileUpdateOld(this)); - CheckLightLevels(true); - ns.Send(new MobileUpdateOld(this)); - } - } - - SendEverything(); - SendIncomingPacket(); - - if (ns != null) - { - ns.Sequence = 0; - ClearFastwalkStack(); - - ns.Send(MobileIncoming.Create(ns, this, this)); - - if (ns.StygianAbyss) - { - ns.Send(SupportedFeatures.Instantiate(ns)); - ns.Send(new MobileUpdate(this)); - ns.Send(new MobileAttributes(this)); - } - else - { - ns.Send(SupportedFeatures.Instantiate(ns)); - ns.Send(new MobileUpdateOld(this)); - ns.Send(new MobileAttributes(this)); - } - } - - OnMapChange(oldMap); - } - } - } - - [CommandProperty(AccessLevel.Counselor)] - public Serial Serial { get; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public Point3D Location - { - get => m_Location; - set => SetLocation(value, true); - } - - public virtual void MoveToWorld(Point3D newLocation, Map map) - { - if (Deleted) - return; - - if (m_Map == map) - { - SetLocation(newLocation, true); - return; - } - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - box.Close(); - - var oldLocation = m_Location; - var oldMap = m_Map; - - var oldRegion = m_Region; - - if (oldMap != null) - { - oldMap.OnLeave(this); - - ClearScreen(); - SendRemovePacket(); - } - - for (var i = 0; i < Items.Count; ++i) - Items[i].Map = map; - - m_Map = map; - - m_Location = newLocation; - - var ns = m_NetState; - - if (m_Map != null) - { - m_Map.OnEnter(this); - - UpdateRegion(); - - if (ns != null && m_Map != null) - { - ns.Sequence = 0; - if (Map != null) - ns.Send(new MapChange(Map)); - - if (!Core.SE && ns.ProtocolChanges < ProtocolChanges.Version6000) - ns.Send(new MapPatches()); - - ns.Send(SeasonChange.Instantiate(GetSeason(), true)); - - if (ns.StygianAbyss) - ns.Send(new MobileUpdate(this)); - else - ns.Send(new MobileUpdateOld(this)); - - ClearFastwalkStack(); - } - } - else - { - UpdateRegion(); - } - - if (ns != null) - { - if (m_Map != null) - Send(new ServerChange(m_Location, m_Map)); - - ns.Sequence = 0; - ClearFastwalkStack(); - - ns.Send(MobileIncoming.Create(ns, this, this)); - - if (ns.StygianAbyss) - { - ns.Send(new MobileUpdate(this)); - CheckLightLevels(true); - ns.Send(new MobileUpdate(this)); - } - else - { - ns.Send(new MobileUpdateOld(this)); - CheckLightLevels(true); - ns.Send(new MobileUpdateOld(this)); - } - } - - SendEverything(); - SendIncomingPacket(); - - if (ns != null) - { - ns.Sequence = 0; - ClearFastwalkStack(); - - ns.Send(MobileIncoming.Create(ns, this, this)); - - if (ns.StygianAbyss) - { - ns.Send(SupportedFeatures.Instantiate(ns)); - ns.Send(new MobileUpdate(this)); - ns.Send(new MobileAttributes(this)); - } - else - { - ns.Send(SupportedFeatures.Instantiate(ns)); - ns.Send(new MobileUpdateOld(this)); - ns.Send(new MobileAttributes(this)); - } - } - - OnMapChange(oldMap); - OnLocationChange(oldLocation); - - m_Region?.OnLocationChanged(this, oldLocation); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int X - { - get => m_Location.m_X; - set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Y - { - get => m_Location.m_Y; - set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Z - { - get => m_Location.m_Z; - set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); - } - - public virtual void ProcessDelta() - { - var m = this; - var delta = m.m_DeltaFlags; - - if (delta == MobileDelta.None) - return; - - var attrs = delta & MobileDelta.Attributes; - - m.m_DeltaFlags = MobileDelta.None; - m.m_InDeltaQueue = false; - - bool sendHits = false, sendStam = false, sendMana = false, sendAll = false, sendAny = false; - bool sendIncoming = false, sendNonlocalIncoming = false; - bool sendUpdate = false, sendRemove = false; - bool sendPublicStats = false, sendPrivateStats = false; - bool sendMoving = false, sendNonlocalMoving = false; - var sendOPLUpdate = ObjectPropertyList.Enabled && (delta & MobileDelta.Properties) != 0; - - bool sendHair = false, sendFacialHair = false, removeHair = false, removeFacialHair = false; - - bool sendHealthbarPoison = false, sendHealthbarYellow = false; - - if (attrs != MobileDelta.None) - { - sendAny = true; - - if (attrs == MobileDelta.Attributes) - { - sendAll = true; - } - else - { - sendHits = (attrs & MobileDelta.Hits) != 0; - sendStam = (attrs & MobileDelta.Stam) != 0; - sendMana = (attrs & MobileDelta.Mana) != 0; - } - } - - if ((delta & MobileDelta.GhostUpdate) != 0) sendNonlocalIncoming = true; - - if ((delta & MobileDelta.Hue) != 0) - { - sendNonlocalIncoming = true; - sendUpdate = true; - sendRemove = true; - } - - if ((delta & MobileDelta.Direction) != 0) - { - sendNonlocalMoving = true; - sendUpdate = true; - } - - if ((delta & MobileDelta.Body) != 0) - { - sendUpdate = true; - sendIncoming = true; - } - - /*if ((delta & MobileDelta.Hue) != 0) - { - sendNonlocalIncoming = true; - sendUpdate = true; - } - else if ((delta & (MobileDelta.Direction | MobileDelta.Body)) != 0) - { - sendNonlocalMoving = true; - sendUpdate = true; - } - else*/ - if ((delta & (MobileDelta.Flags | MobileDelta.Noto)) != 0) sendMoving = true; - - if ((delta & MobileDelta.HealthbarPoison) != 0) sendHealthbarPoison = true; - - if ((delta & MobileDelta.HealthbarYellow) != 0) sendHealthbarYellow = true; - - if ((delta & MobileDelta.Name) != 0) - { - sendAll = false; - sendHits = false; - sendAny = sendStam || sendMana; - sendPublicStats = true; - } - - if ((delta & (MobileDelta.WeaponDamage | MobileDelta.Resistances | MobileDelta.Stat | - MobileDelta.Weight | MobileDelta.Gold | MobileDelta.Armor | MobileDelta.StatCap | - MobileDelta.Followers | MobileDelta.TithingPoints | MobileDelta.Race)) != 0) - sendPrivateStats = true; - - if ((delta & MobileDelta.Hair) != 0) - { - if (m.HairItemID <= 0) - removeHair = true; - - sendHair = true; - } - - if ((delta & MobileDelta.FacialHair) != 0) - { - if (m.FacialHairItemID <= 0) - removeFacialHair = true; - - sendFacialHair = true; - } - - var cache = new[] { new Packet[8], new Packet[8] }; - - var ourState = m.m_NetState; - - if (ourState != null) - { - if (sendUpdate) - { - ourState.Sequence = 0; - - if (ourState.StygianAbyss) - ourState.Send(new MobileUpdate(m)); - else - ourState.Send(new MobileUpdateOld(m)); - - ClearFastwalkStack(); - } - - if (sendIncoming) - ourState.Send(MobileIncoming.Create(ourState, m, m)); - - if (ourState.StygianAbyss) - { - if (sendMoving) - { - var noto = Notoriety.Compute(m, m); - ourState.Send(cache[0][noto] = Packet.Acquire(new MobileMoving(m, noto))); - } - - if (sendHealthbarPoison) - ourState.Send(new HealthbarPoison(m)); - - if (sendHealthbarYellow) - ourState.Send(new HealthbarYellow(m)); - } - else - { - if (sendMoving || sendHealthbarPoison || sendHealthbarYellow) - { - var noto = Notoriety.Compute(m, m); - ourState.Send(cache[1][noto] = Packet.Acquire(new MobileMovingOld(m, noto))); - } - } - - if (sendPublicStats || sendPrivateStats) - { - ourState.Send(new MobileStatusExtended(m, m_NetState)); - } - else if (sendAll) - { - ourState.Send(new MobileAttributes(m)); - } - else if (sendAny) - { - if (sendHits) - ourState.Send(new MobileHits(m)); - - if (sendStam) - ourState.Send(new MobileStam(m)); - - if (sendMana) - ourState.Send(new MobileMana(m)); - } - - if (sendStam || sendMana) - if (Party is IParty ip) - { - if (sendStam) - ip.OnStamChanged(this); - - if (sendMana) - ip.OnManaChanged(this); - } - - if (sendHair) - { - if (removeHair) - ourState.Send(new RemoveHair(m)); - else - ourState.Send(new HairEquipUpdate(m)); - } - - if (sendFacialHair) - { - if (removeFacialHair) - ourState.Send(new RemoveFacialHair(m)); - else - ourState.Send(new FacialHairEquipUpdate(m)); - } - - if (sendOPLUpdate) - ourState.Send(OPLPacket); - } - - sendMoving = sendMoving || sendNonlocalMoving; - sendIncoming = sendIncoming || sendNonlocalIncoming; - sendHits = sendHits || sendAll; - - if (m.m_Map != null && (sendRemove || sendIncoming || sendPublicStats || sendHits || sendMoving || - sendOPLUpdate || sendHair || sendFacialHair || sendHealthbarPoison || - sendHealthbarYellow)) - { - Mobile beholder; - - Packet hitsPacket = null; - Packet statPacketTrue = null; - Packet statPacketFalse = null; - Packet deadPacket = null; - Packet hairPacket = null; - Packet facialhairPacket = null; - Packet hbpPacket = null; - Packet hbyPacket = null; - - var eable = m.Map.GetClientsInRange(m.m_Location); - - foreach (var state in eable) - { - beholder = state.Mobile; - - if (beholder != m && beholder.CanSee(m)) - { - if (sendRemove) - state.Send(RemovePacket); - - if (sendIncoming) - { - state.Send(MobileIncoming.Create(state, beholder, m)); - - if (m.IsDeadBondedPet) - { - deadPacket ??= Packet.Acquire(new BondedStatus(m.Serial, true)); - - state.Send(deadPacket); - } - } - - if (state.StygianAbyss) - { - if (sendMoving) - { - var noto = Notoriety.Compute(beholder, m); - - var p = cache[0][noto]; - - if (p == null) - cache[0][noto] = p = Packet.Acquire(new MobileMoving(m, noto)); - - state.Send(p); - } - - if (sendHealthbarPoison) - { - hbpPacket ??= Packet.Acquire(new HealthbarPoison(m)); - - state.Send(hbpPacket); - } - - if (sendHealthbarYellow) - { - hbyPacket ??= Packet.Acquire(new HealthbarYellow(m)); - - state.Send(hbyPacket); - } - } - else - { - if (sendMoving || sendHealthbarPoison || sendHealthbarYellow) - { - var noto = Notoriety.Compute(beholder, m); - - var p = cache[1][noto]; - - if (p == null) - cache[1][noto] = p = Packet.Acquire(new MobileMovingOld(m, noto)); - - state.Send(p); - } - } - - if (sendPublicStats) - { - if (m.CanBeRenamedBy(beholder)) - { - statPacketTrue ??= Packet.Acquire(new MobileStatusCompact(true, m)); - - state.Send(statPacketTrue); - } - else - { - statPacketFalse ??= Packet.Acquire(new MobileStatusCompact(false, m)); - - state.Send(statPacketFalse); - } - } - else if (sendHits) - { - hitsPacket ??= Packet.Acquire(new MobileHitsN(m)); - - state.Send(hitsPacket); - } - - if (sendHair) - { - hairPacket ??= removeHair ? Packet.Acquire(new RemoveHair(m)) : Packet.Acquire(new HairEquipUpdate(m)); - - state.Send(hairPacket); - } - - if (sendFacialHair) - { - facialhairPacket ??= removeFacialHair - ? Packet.Acquire(new RemoveFacialHair(m)) - : Packet.Acquire(new FacialHairEquipUpdate(m)); - - state.Send(facialhairPacket); - } - - if (sendOPLUpdate) - state.Send(OPLPacket); - } - } - - Packet.Release(hitsPacket); - Packet.Release(statPacketTrue); - Packet.Release(statPacketFalse); - Packet.Release(deadPacket); - Packet.Release(hairPacket); - Packet.Release(facialhairPacket); - Packet.Release(hbpPacket); - Packet.Release(hbyPacket); - - eable.Free(); - } - - if (sendMoving || sendNonlocalMoving || sendHealthbarPoison || sendHealthbarYellow) - for (var i = 0; i < cache.Length; ++i) - for (var j = 0; j < cache[i].Length; ++j) - Packet.Release(ref cache[i][j]); - } - - public virtual int HuedItemID => m_Female ? 0x2107 : 0x2106; - - public int TypeRef { get; } - - public void Serialize() - { - SaveBuffer.Flush(); - Serialize(SaveBuffer); - } - - public virtual void Serialize(IGenericWriter writer) - { - writer.Write(32); // version - - writer.WriteDeltaTime(LastStrGain); - writer.WriteDeltaTime(LastIntGain); - writer.WriteDeltaTime(LastDexGain); - - byte hairflag = 0x00; - - if (m_Hair != null) - hairflag |= 0x01; - if (m_FacialHair != null) - hairflag |= 0x02; - - writer.Write(hairflag); - - if ((hairflag & 0x01) != 0) - m_Hair?.Serialize(writer); - if ((hairflag & 0x02) != 0) - m_FacialHair?.Serialize(writer); - - writer.Write(Race); - - writer.Write(m_TithingPoints); - - writer.Write(Corpse); - - writer.Write(CreationTime); - - writer.Write(Stabled, true); - - writer.Write(CantWalk); - - VirtueInfo.Serialize(writer, Virtues); - - writer.Write(Thirst); - writer.Write(BAC); - - writer.Write(m_ShortTermMurders); - // writer.Write( m_ShortTermElapse ); - // writer.Write( m_LongTermElapse ); - - // writer.Write( m_Followers ); - writer.Write(m_FollowersMax); - - writer.Write(MagicDamageAbsorb); - - writer.Write(GuildFealty); - - writer.Write(m_Guild); - - writer.Write(m_DisplayGuildTitle); - - writer.Write(CanSwim); - - writer.Write(Squelched); - - writer.Write(m_Holding); - - writer.Write(m_VirtualArmor); - - writer.Write(BaseSoundID); - - writer.Write(DisarmReady); - writer.Write(StunReady); - - // Poison.Serialize( m_Poison, writer ); - - writer.Write(m_StatCap); - - writer.Write(NameHue); - - writer.Write(m_Hunger); - - writer.Write(m_Location); - writer.Write(m_Body); - writer.Write(m_Name); - writer.Write(m_GuildTitle); - writer.Write(m_Criminal); - writer.Write(m_Kills); - writer.Write(SpeechHue); - writer.Write(EmoteHue); - writer.Write(WhisperHue); - writer.Write(YellHue); - writer.Write(m_Language); - writer.Write(m_Female); - writer.Write(m_Warmode); - writer.Write(m_Hidden); - writer.Write((byte)m_Direction); - writer.Write(m_Hue); - writer.Write(m_Str); - writer.Write(m_Dex); - writer.Write(m_Int); - writer.Write(m_Hits); - writer.Write(m_Stam); - writer.Write(m_Mana); - - writer.Write(m_Map); - - writer.Write(m_Blessed); - writer.Write(m_Fame); - writer.Write(m_Karma); - writer.Write((byte)m_AccessLevel); - Skills.Serialize(writer); - - writer.Write(Items); - - writer.Write(m_Player); - writer.Write(m_Title); - writer.Write(Profile); - writer.Write(ProfileLocked); - writer.Write(AutoPageNotify); - - writer.Write(LogoutLocation); - writer.Write(LogoutMap); - - writer.Write((byte)m_StrLock); - writer.Write((byte)m_DexLock); - writer.Write((byte)m_IntLock); - } - - public ISpawner Spawner { get; set; } - - public virtual void OnBeforeSpawn(Point3D location, Map m) - { - } - - public virtual void OnAfterSpawn() - { - } - - protected virtual void OnRaceChange(Race oldRace) - { - } - - public virtual void ComputeLightLevels(out int global, out int personal) - { - ComputeBaseLightLevels(out global, out personal); - - m_Region?.AlterLightLevel(this, ref global, ref personal); - } - - public virtual void ComputeBaseLightLevels(out int global, out int personal) - { - global = 0; - personal = m_LightLevel; - } - - public virtual void CheckLightLevels(bool forceResend) - { - } - - public virtual void UpdateResistances() - { - Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; - - var delta = false; - - for (var i = 0; i < Resistances.Length; ++i) - if (Resistances[i] != int.MinValue) - { - Resistances[i] = int.MinValue; - delta = true; - } - - if (delta) - Delta(MobileDelta.Resistances); - } - - public virtual int GetResistance(ResistanceType type) - { - Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; - - var v = (int)type; - - if (v < 0 || v >= Resistances.Length) - return 0; - - var res = Resistances[v]; - - if (res == int.MinValue) - { - ComputeResistances(); - res = Resistances[v]; - } - - return res; - } - - public virtual void AddResistanceMod(ResistanceMod toAdd) - { - ResistanceMods ??= new List(); - - ResistanceMods.Add(toAdd); - UpdateResistances(); - } - - public virtual void RemoveResistanceMod(ResistanceMod toRemove) - { - if (ResistanceMods != null) - { - ResistanceMods.Remove(toRemove); - - if (ResistanceMods.Count == 0) - ResistanceMods = null; - } - - UpdateResistances(); - } - - public virtual void ComputeResistances() - { - Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; - - for (var i = 0; i < Resistances.Length; ++i) - Resistances[i] = 0; - - Resistances[0] += BasePhysicalResistance; - Resistances[1] += BaseFireResistance; - Resistances[2] += BaseColdResistance; - Resistances[3] += BasePoisonResistance; - Resistances[4] += BaseEnergyResistance; - - for (var i = 0; ResistanceMods != null && i < ResistanceMods.Count; ++i) - { - var mod = ResistanceMods[i]; - var v = (int)mod.Type; - - if (v >= 0 && v < Resistances.Length) - Resistances[v] += mod.Offset; - } - - for (var i = 0; i < Items.Count; ++i) - { - var item = Items[i]; - - if (item.CheckPropertyConflict(this)) - continue; - - Resistances[0] += item.PhysicalResistance; - Resistances[1] += item.FireResistance; - Resistances[2] += item.ColdResistance; - Resistances[3] += item.PoisonResistance; - Resistances[4] += item.EnergyResistance; - } - - for (var i = 0; i < Resistances.Length; ++i) - { - var min = GetMinResistance((ResistanceType)i); - var max = GetMaxResistance((ResistanceType)i); - - if (max < min) - max = min; - - if (Resistances[i] > max) - Resistances[i] = max; - else if (Resistances[i] < min) - Resistances[i] = min; - } - } - - public virtual int GetMinResistance(ResistanceType type) => int.MinValue; - - public virtual int GetMaxResistance(ResistanceType type) => m_Player ? MaxPlayerResistance : int.MaxValue; - - public int GetAOSStatus(int index) => AOSStatusHandler?.Invoke(this, index) ?? 0; - - public virtual void SendPropertiesTo(Mobile from) - { - from.Send(PropertyList); - } - - public virtual void OnAosSingleClick(Mobile from) - { - var opl = PropertyList; - - if (opl.Header > 0) - { - int hue; - - if (NameHue != -1) - hue = NameHue; - else if (m_AccessLevel > AccessLevel.Player) - hue = 11; - else - hue = Notoriety.GetHue(Notoriety.Compute(from, this)); - - from.Send(new MessageLocalized(Serial, Body, MessageType.Label, hue, 3, opl.Header, Name, opl.HeaderArgs)); - } - } - - public virtual string ApplyNameSuffix(string suffix) => suffix; - - public virtual void AddNameProperties(ObjectPropertyList list) - { - var name = Name ?? string.Empty; - - string prefix; - - if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) - prefix = m_Female ? "Lady" : "Lord"; - else - prefix = ""; - - var suffix = ""; - - if (PropertyTitle && !string.IsNullOrEmpty(Title)) - suffix = Title; - - var guild = m_Guild; - - if (guild != null && (m_Player || m_DisplayGuildTitle)) - suffix = suffix.Length > 0 - ? $"{suffix} [{Utility.FixHtml(guild.Abbreviation)}]" - : $"[{Utility.FixHtml(guild.Abbreviation)}]"; - - suffix = ApplyNameSuffix(suffix); - - list.Add(1050045, "{0} \t{1}\t {2}", prefix, name, suffix); // ~1_PREFIX~~2_NAME~~3_SUFFIX~ - - if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) - { - var type = guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length ? m_GuildTypes[(int)guild.Type] : ""; - - var title = GuildTitle?.Trim() ?? ""; - - if (title.Length > 0) - { - if (NewGuildDisplay) - list.Add("{0}, {1}", Utility.FixHtml(title), Utility.FixHtml(guild.Name)); - else - list.Add("{0}, {1} Guild{2}", Utility.FixHtml(title), Utility.FixHtml(guild.Name), type); - } - else - { - list.Add(Utility.FixHtml(guild.Name)); - } - } - } - - public virtual void GetProperties(ObjectPropertyList list) - { - AddNameProperties(list); - } - - public virtual void GetChildProperties(ObjectPropertyList list, Item item) - { - } - - public virtual void GetChildNameProperties(ObjectPropertyList list, Item item) - { - } - - private void UpdateAggrExpire() - { - if (Deleted || Aggressors.Count == 0 && Aggressed.Count == 0) - { - StopAggrExpire(); - } - else if (m_ExpireAggrTimer == null) - { - m_ExpireAggrTimer = new ExpireAggressorsTimer(this); - m_ExpireAggrTimer.Start(); - } - } - - private void StopAggrExpire() - { - m_ExpireAggrTimer?.Stop(); - - m_ExpireAggrTimer = null; - } - - private void CheckAggrExpire() - { - for (var i = Aggressors.Count - 1; i >= 0; --i) - { - if (i >= Aggressors.Count) - continue; - - var info = Aggressors[i]; - - if (info.Expired) - { - var attacker = info.Attacker; - attacker.RemoveAggressed(this); - - Aggressors.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(attacker) && Utility.InUpdateRange(m_Location, attacker.m_Location)) - m_NetState.Send(MobileIncoming.Create(m_NetState, this, attacker)); - } - } - - for (var i = Aggressed.Count - 1; i >= 0; --i) - { - if (i >= Aggressed.Count) - continue; - - var info = Aggressed[i]; - - if (info.Expired) - { - var defender = info.Defender; - defender.RemoveAggressor(this); - - Aggressed.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(defender) && Utility.InUpdateRange(m_Location, defender.m_Location)) - m_NetState.Send(MobileIncoming.Create(m_NetState, this, defender)); - } - } - - UpdateAggrExpire(); - } - - /// - /// Overridable. Virtual event invoked when changes in some way. - /// - public virtual void OnSkillInvalidated(Skill skill) - { - } - - public virtual void UpdateSkillMods() - { - ValidateSkillMods(); - - for (var i = 0; i < SkillMods.Count; ++i) - { - var mod = SkillMods[i]; - var sk = Skills[mod.Skill]; - sk?.Update(); - } - } - - public virtual void ValidateSkillMods() - { - for (var i = 0; i < SkillMods.Count;) - { - var mod = SkillMods[i]; - - if (mod.CheckCondition()) - ++i; - else - InternalRemoveSkillMod(mod); - } - } - - public virtual void AddSkillMod(SkillMod mod) - { - if (mod == null) - return; - - ValidateSkillMods(); - - if (!SkillMods.Contains(mod)) - { - SkillMods.Add(mod); - mod.Owner = this; - - var sk = Skills[mod.Skill]; - sk?.Update(); - } - } - - public virtual void RemoveSkillMod(SkillMod mod) - { - if (mod == null) - return; - - ValidateSkillMods(); - - InternalRemoveSkillMod(mod); - } - - private void InternalRemoveSkillMod(SkillMod mod) - { - if (SkillMods.Contains(mod)) - { - SkillMods.Remove(mod); - mod.Owner = null; - - var sk = Skills[mod.Skill]; - sk?.Update(); - } - } - - /// - /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Mobile. - /// Seemingly no longer functional in newer clients. - /// - public virtual void OnHelpRequest(Mobile from) - { - } - - public void DelayChangeWarmode(bool value) - { - if (m_WarmodeTimer != null) - { - m_WarmodeTimer.Value = value; - return; - } - - if (m_Warmode == value) - return; - - DateTime now = DateTime.UtcNow, next = m_NextWarmodeChange; - - if (now > next || m_WarmodeChanges == 0) - { - m_WarmodeChanges = 1; - m_NextWarmodeChange = now + WarmodeSpamCatch; - } - else if (m_WarmodeChanges == WarmodeCatchCount) - { - m_WarmodeTimer = new WarmodeTimer(this, value); - m_WarmodeTimer.Start(); - - return; - } - else - { - ++m_WarmodeChanges; - } - - Warmode = value; - } - - public bool InLOS(Mobile target) => - !Deleted && m_Map != null && - (target == this || m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); - - public bool InLOS(object target) => - !Deleted && m_Map != null && - (target == this || m_AccessLevel > AccessLevel.Player || target is Item item && item.RootParent == this - || m_Map.LineOfSight(this, target)); - - public bool InLOS(Point3D target) => - !Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); - - public bool BeginAction() => BeginAction(typeof(T)); - - public bool BeginAction(object toLock) - { - if (_actions == null) - { - _actions = new List { toLock }; - return true; - } - - if (!_actions.Contains(toLock)) - { - _actions.Add(toLock); - return true; - } - - return false; - } - - public bool CanBeginAction() => CanBeginAction(typeof(T)); - - public bool CanBeginAction(object toLock) => _actions?.Contains(toLock) != true; - - public void EndAction() => EndAction(typeof(T)); - - public void EndAction(object toLock) - { - if (_actions != null) - { - _actions.Remove(toLock); - - if (_actions.Count == 0) _actions = null; - } - } - - public virtual TimeSpan GetLogoutDelay() => Region.GetLogoutDelay(this); - - public void Paralyze(TimeSpan duration) - { - if (!m_Paralyzed) - { - Paralyzed = true; - - m_ParaTimer = new ParalyzedTimer(this, duration); - m_ParaTimer.Start(); - } - } - - public void Freeze(TimeSpan duration) - { - if (!m_Frozen) - { - Frozen = true; - - m_FrozenTimer = new FrozenTimer(this, duration); - m_FrozenTimer.Start(); - } - } - - public override string ToString() => $"0x{Serial.Value:X} \"{Name}\""; - - public virtual void SendSkillMessage() - { - if (NextActionMessage - Core.TickCount >= 0) - return; - - NextActionMessage = Core.TickCount + ActionMessageDelay; - - SendLocalizedMessage(500118); // You must wait a few moments to use another skill. - } - - public virtual void SendActionMessage() - { - if (NextActionMessage - Core.TickCount >= 0) - return; - - NextActionMessage = Core.TickCount + ActionMessageDelay; - - SendLocalizedMessage(500119); // You must wait to perform another action. - } - - public virtual void ClearHands() - { - ClearHand(FindItemOnLayer(Layer.OneHanded)); - ClearHand(FindItemOnLayer(Layer.TwoHanded)); - } - - public virtual void ClearHand(Item item) - { - if (item?.Movable == true && !item.AllowEquippedCast(this)) - { - var pack = Backpack; - - if (pack == null) - AddToBackpack(item); - else - pack.DropItem(item); - } - } - - public virtual void Attack(Mobile m) - { - if (CheckAttack(m)) - Combatant = m; - } - - public virtual bool CheckAttack(Mobile m) => Utility.InUpdateRange(this, m) && CanSee(m) && InLOS(m); - - /// - /// Overridable. Virtual event invoked after the property has changed. - /// - /// - public virtual void OnCombatantChange() - { - } - - public double GetDistanceToSqrt(Point3D p) - { - var xDelta = m_Location.m_X - p.m_X; - var yDelta = m_Location.m_Y - p.m_Y; - - return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); - } - - public double GetDistanceToSqrt(Mobile m) - { - var xDelta = m_Location.m_X - m.m_Location.m_X; - var yDelta = m_Location.m_Y - m.m_Location.m_Y; - - return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); - } - - public double GetDistanceToSqrt(IPoint2D p) - { - var xDelta = m_Location.m_X - p.X; - var yDelta = m_Location.m_Y - p.Y; - - return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); - } - - public virtual void AggressiveAction(Mobile aggressor) => AggressiveAction(aggressor, false); - - public virtual void AggressiveAction(Mobile aggressor, bool criminal) - { - if (aggressor == this) - return; - - var args = AggressiveActionEventArgs.Create(this, aggressor, criminal); - - EventSink.InvokeAggressiveAction(args); - - args.Free(); - - if (Combatant == aggressor) - { - if (m_ExpireCombatant == null) - m_ExpireCombatant = new ExpireCombatantTimer(this); - else - m_ExpireCombatant.Stop(); - - m_ExpireCombatant.Start(); - } - - var addAggressor = true; - - var list = Aggressors; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Attacker == aggressor) - { - info.Refresh(); - info.CriminalAggression = criminal; - info.CanReportMurder = criminal; - - addAggressor = false; - } - } - - list = aggressor.Aggressors; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Attacker == this) - { - info.Refresh(); - - addAggressor = false; - } - } - - var addAggressed = true; - - list = Aggressed; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Defender == aggressor) - { - info.Refresh(); - - addAggressed = false; - } - } - - list = aggressor.Aggressed; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Defender == this) - { - info.Refresh(); - info.CriminalAggression = criminal; - info.CanReportMurder = criminal; - - addAggressed = false; - } - } - - var setCombatant = false; - - if (addAggressor) - { - Aggressors.Add(AggressorInfo.Create(aggressor, this, - criminal)); // new AggressorInfo( aggressor, this, criminal, true ) ); - - if (CanSee(aggressor)) m_NetState?.Send(MobileIncoming.Create(m_NetState, this, aggressor)); - - if (Combatant == null) - setCombatant = true; - - UpdateAggrExpire(); - } - - if (addAggressed) - { - aggressor.Aggressed.Add(AggressorInfo.Create(aggressor, this, - criminal)); // new AggressorInfo( aggressor, this, criminal, false ) ); - - if (CanSee(aggressor)) m_NetState?.Send(MobileIncoming.Create(m_NetState, this, aggressor)); - - if (Combatant == null) - setCombatant = true; - - UpdateAggrExpire(); - } - - if (setCombatant) - Combatant = aggressor; - - Region.OnAggressed(aggressor, this, criminal); - } - - public void RemoveAggressed(Mobile aggressed) - { - if (Deleted) - return; - - var list = Aggressed; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Defender == aggressed) - { - Aggressed.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(aggressed)) - m_NetState.Send(MobileIncoming.Create(m_NetState, this, aggressed)); - - break; - } - } - - UpdateAggrExpire(); - } - - public void RemoveAggressor(Mobile aggressor) - { - if (Deleted) - return; - - var list = Aggressors; - - for (var i = 0; i < list.Count; ++i) - { - var info = list[i]; - - if (info.Attacker == aggressor) - { - Aggressors.RemoveAt(i); - info.Free(); - - if (m_NetState != null && CanSee(aggressor)) - m_NetState.Send(MobileIncoming.Create(m_NetState, this, aggressor)); - - break; - } - } - - UpdateAggrExpire(); - } - - public virtual int GetTotal(TotalType type) => - type switch - { - TotalType.Gold => m_TotalGold, - TotalType.Items => m_TotalItems, - TotalType.Weight => m_TotalWeight, - _ => 0 - }; - - public virtual void UpdateTotal(Item sender, TotalType type, int delta) - { - if (delta == 0 || sender.IsVirtualItem) - return; - - switch (type) - { - default: - m_TotalGold += delta; - Delta(MobileDelta.Gold); - break; - - case TotalType.Items: - m_TotalItems += delta; - break; - - case TotalType.Weight: - m_TotalWeight += delta; - Delta(MobileDelta.Weight); - OnWeightChange(m_TotalWeight - delta); - break; - } - } - - public virtual void UpdateTotals() - { - if (Items == null) - return; - - var oldWeight = m_TotalWeight; - - m_TotalGold = 0; - m_TotalItems = 0; - m_TotalWeight = 0; - - for (var i = 0; i < Items.Count; ++i) - { - var item = Items[i]; - - item.UpdateTotals(); - - if (item.IsVirtualItem) - continue; - - m_TotalGold += item.TotalGold; - m_TotalItems += item.TotalItems + 1; - m_TotalWeight += item.TotalWeight + item.PileWeight; - } - - if (m_Holding != null) - m_TotalWeight += m_Holding.TotalWeight + m_Holding.PileWeight; - - if (m_TotalWeight != oldWeight) - OnWeightChange(oldWeight); - } - - public void ClearQuestArrow() => m_QuestArrow = null; - - public void ClearTarget() => m_Target = null; - - public Target BeginTarget(int range, bool allowGround, TargetFlags flags, TargetCallback callback) => - Target = new SimpleTarget(range, flags, allowGround, callback); - - public Target BeginTarget(int range, bool allowGround, TargetFlags flags, TargetStateCallback callback, - T state) => - Target = new SimpleStateTarget(range, flags, allowGround, callback, state); - - /// - /// Overridable. Virtual event invoked after the Target property has changed. - /// - protected virtual void OnTargetChange() - { - } - - public virtual bool CheckContextMenuDisplay(IEntity target) => true; - - private bool InternalOnMove(Direction d) - { - if (!OnMove(d)) - return false; - - var e = MovementEventArgs.Create(this, d); - - EventSink.InvokeMovement(e); - - var ret = !e.Blocked; - - e.Free(); - - return ret; - } - - /// - /// Overridable. Event invoked before the Mobile moves. - /// - /// True if the move is allowed, false if not. - protected virtual bool OnMove(Direction d) - { - if (m_Hidden && m_AccessLevel == AccessLevel.Player) - if (AllowedStealthSteps-- <= 0 || (d & Direction.Running) != 0 || Mounted) - RevealingAction(); - - return true; - } - - public virtual void ClearFastwalkStack() - { - if (m_MoveRecords != null && m_MoveRecords.Count > 0) - m_MoveRecords.Clear(); - - m_EndQueue = Core.TickCount; - } - - public virtual bool CheckMovement(Direction d, out int newZ) => Movement.Movement.CheckMovement(this, d, out newZ); - - public virtual bool Move(Direction d) - { - if (Deleted) - return false; - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - box.Close(); - - var newLocation = m_Location; - var oldLocation = newLocation; - - if ((m_Direction & Direction.Mask) == (d & Direction.Mask)) - { - // We are actually moving (not just a direction change) - - if (m_Spell?.OnCasterMoving(d) == false) - return false; - - if (m_Paralyzed || m_Frozen) - { - SendLocalizedMessage(500111); // You are frozen and can not move. - - return false; - } - - if (CheckMovement(d, out var newZ)) - { - int x = oldLocation.m_X, y = oldLocation.m_Y; - int oldX = x, oldY = y; - var oldZ = oldLocation.m_Z; - - switch (d & Direction.Mask) - { - case Direction.North: - --y; - break; - case Direction.Right: - ++x; - --y; - break; - case Direction.East: - ++x; - break; - case Direction.Down: - ++x; - ++y; - break; - case Direction.South: - ++y; - break; - case Direction.Left: - --x; - ++y; - break; - case Direction.West: - --x; - break; - case Direction.Up: - --x; - --y; - break; - } - - newLocation.m_X = x; - newLocation.m_Y = y; - newLocation.m_Z = newZ; - - Pushing = false; - - var map = m_Map; - - if (map != null) - { - var oldSector = map.GetSector(oldX, oldY); - var newSector = map.GetSector(x, y); - - if (oldSector != newSector) - { - for (var i = 0; i < oldSector.Mobiles.Count; ++i) - { - var m = oldSector.Mobiles[i]; - - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) - return false; - } - - for (var i = 0; i < oldSector.Items.Count; ++i) - { - var item = oldSector.Items[i]; - - if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) - return false; - } - - for (var i = 0; i < newSector.Mobiles.Count; ++i) - { - var m = newSector.Mobiles[i]; - - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) - return false; - } - - for (var i = 0; i < newSector.Items.Count; ++i) - { - var item = newSector.Items[i]; - - if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) - return false; - } - } - else - { - for (var i = 0; i < oldSector.Mobiles.Count; ++i) - { - var m = oldSector.Mobiles[i]; - - if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && - !m.OnMoveOff(this)) - return false; - if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) - return false; - } - - for (var i = 0; i < oldSector.Items.Count; ++i) - { - var item = oldSector.Items[i]; - - if (item.AtWorldPoint(oldX, oldY) && - (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && - !item.OnMoveOff(this)) - return false; - if (item.AtWorldPoint(x, y) && - (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && - !item.OnMoveOver(this)) - return false; - } - } - - if (!Region.CanMove(this, d, newLocation, oldLocation, m_Map)) - return false; - } - else - { - return false; - } - - if (!InternalOnMove(d)) - return false; - - if (FwdEnabled && m_NetState != null && m_AccessLevel < FwdAccessOverride && - (!FwdUOTDOverride || !m_NetState.IsUOTDClient)) - { - m_MoveRecords ??= new Queue(6); - - while (m_MoveRecords.Count > 0) - { - var r = m_MoveRecords.Peek(); - - if (r.Expired()) - m_MoveRecords.Dequeue(); - else - break; - } - - if (m_MoveRecords.Count >= FwdMaxSteps) - { - var fw = new FastWalkEventArgs(m_NetState); - EventSink.InvokeFastWalk(fw); - - if (fw.Blocked) - return false; - } - - var delay = ComputeMovementSpeed(d); - - long end; - - if (m_MoveRecords.Count > 0) - end = m_EndQueue + delay; - else - end = Core.TickCount + delay; - - m_MoveRecords.Enqueue(MovementRecord.NewInstance(end)); - - m_EndQueue = end; - } - - LastMoveTime = Core.TickCount; - } - else - { - return false; - } - - DisruptiveAction(); - } - - m_NetState?.Send(MovementAck.Instantiate(m_NetState.Sequence, - this)); // new MovementAck( m_NetState.Sequence, this ) ); - - SetLocation(newLocation, false); - SetDirection(d); - - if (m_Map != null) - { - var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); - - foreach (var o in eable) - { - if (o == this) - continue; - - if (o is Mobile mob) - { - if (mob.NetState != null) - m_MoveClientList.Add(mob); - m_MoveList.Add(mob); - } - else if (o is Item item && item.HandlesOnMovement) - { - m_MoveList.Add(item); - } - } - - eable.Free(); - - var cache = m_MovingPacketCache; - - /*for( int i = 0; i < cache.Length; ++i ) - for( int j = 0; j < cache[i].Length; ++j ) - Packet.Release( ref cache[i][j] );*/ - - foreach (var m in m_MoveClientList) - { - var ns = m.NetState; - - if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) - { - if (ns.StygianAbyss) - { - var noto = Notoriety.Compute(m, this); - var p = cache[0][noto]; - - if (p == null) - cache[0][noto] = p = Packet.Acquire(new MobileMoving(this, noto)); - - ns.Send(p); - } - else - { - var noto = Notoriety.Compute(m, this); - var p = cache[1][noto]; - - if (p == null) - cache[1][noto] = p = Packet.Acquire(new MobileMovingOld(this, noto)); - - ns.Send(p); - } - } - } - - for (var i = 0; i < cache.Length; ++i) - for (var j = 0; j < cache[i].Length; ++j) - Packet.Release(ref cache[i][j]); - - for (var i = 0; i < m_MoveList.Count; ++i) - { - var o = m_MoveList[i]; - - if (o is Mobile mobile) - mobile.OnMovement(this, oldLocation); - else if (o is Item item) item.OnMovement(this, oldLocation); - } - - if (m_MoveList.Count > 0) - m_MoveList.Clear(); - - if (m_MoveClientList.Count > 0) - m_MoveClientList.Clear(); - } - - OnAfterMove(oldLocation); - return true; - } - - public virtual void OnAfterMove(Point3D oldLocation) - { - } - - public int ComputeMovementSpeed() => ComputeMovementSpeed(Direction, false); - - public int ComputeMovementSpeed(Direction dir) => ComputeMovementSpeed(dir, true); - - public virtual int ComputeMovementSpeed(Direction dir, bool checkTurning) - { - int delay; - - if (Mounted) - delay = (dir & Direction.Running) != 0 ? RunMount : WalkMount; - else - delay = (dir & Direction.Running) != 0 ? RunFoot : WalkFoot; - - return delay; - } - - /// - /// Overridable. Virtual event invoked when a Mobile moves off this Mobile. - /// - /// True if the move is allowed, false if not. - public virtual bool OnMoveOff(Mobile m) => true; - - /// - /// Overridable. Event invoked when a Mobile moves over this Mobile. - /// - /// True if the move is allowed, false if not. - public virtual bool OnMoveOver(Mobile m) => m_Map == null || Deleted || m.CheckShove(this); - - public virtual bool CheckShove(Mobile shoved) - { - if ((m_Map.Rules & MapRules.FreeMovement) == 0) - { - if (!shoved.Alive || !Alive || shoved.IsDeadBondedPet || IsDeadBondedPet) - return true; - if (shoved.m_Hidden && shoved.m_AccessLevel > AccessLevel.Player) - return true; - - if (!Pushing) - { - Pushing = true; - - int number; - - if (AccessLevel > AccessLevel.Player) - { - number = shoved.m_Hidden ? 1019041 : 1019040; - } - else - { - if (Stam == StamMax) - { - number = shoved.m_Hidden ? 1019043 : 1019042; - Stam -= 10; - - RevealingAction(); - } - else - { - return false; - } - } - - SendLocalizedMessage(number); - } - } - - return true; - } - - /// - /// Overridable. Virtual event invoked when the Mobile sees another Mobile, , move. - /// - public virtual void OnMovement(Mobile m, Point3D oldLocation) - { - } - - public virtual void CriminalAction(bool message) - { - if (Deleted) - return; - - Criminal = true; - - Region.OnCriminalAction(this, message); - } - - public virtual bool IsSnoop(Mobile from) => from != this; - - /// - /// Overridable. Any call to will silently fail if this method returns false. - /// - /// - public virtual bool CheckResurrect() => true; - - /// - /// Overridable. Event invoked before the Mobile is resurrected. - /// - /// - public virtual void OnBeforeResurrect() - { - } - - /// - /// Overridable. Event invoked after the Mobile is resurrected. - /// - /// - public virtual void OnAfterResurrect() - { - } - - public virtual void Resurrect() - { - if (!Alive) - { - if (!Region.OnResurrect(this)) - return; - - if (!CheckResurrect()) - return; - - OnBeforeResurrect(); - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - box.Close(); - - Poison = null; - - Warmode = false; - - Hits = 10; - Stam = StamMax; - Mana = 0; - - BodyMod = 0; - Body = Race.AliveBody(this); - - ProcessDeltaQueue(); - - for (var i = Items.Count - 1; i >= 0; --i) - { - if (i >= Items.Count) - continue; - - var item = Items[i]; - - if (item.ItemID == 0x204E) - item.Delete(); - } - - SendIncomingPacket(); - SendIncomingPacket(); - - OnAfterResurrect(); - - // Send( new DeathStatus( false ) ); - } - } - - public void DropHolding() - { - var holding = m_Holding; - - if (holding != null) - { - if (!holding.Deleted && holding.HeldBy == this && holding.Map == Map.Internal) - AddToBackpack(holding); - - Holding = null; - holding.ClearBounce(); - } - } - - /// - /// Overridable. Virtual event invoked before the Mobile is deleted. - /// - public virtual void OnDelete() - { - Spawner?.Remove(this); - Spawner = null; - } - - public virtual bool CheckSpellCast(ISpell spell) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile casts a . - /// - /// - public virtual void OnSpellCast(ISpell spell) - { - } - - /// - /// Overridable. Virtual event invoked after changes. - /// - public virtual void OnWeightChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when the or property of - /// changes. - /// - public virtual void OnSkillChange(SkillName skill, double oldBase) - { - } - - /// - /// Overridable. Invoked after the mobile is deleted. When overridden, be sure to call the base method. - /// - public virtual void OnAfterDelete() - { - StopAggrExpire(); - - CheckAggrExpire(); - - PoisonTimer?.Stop(); - m_HitsTimer?.Stop(); - m_StamTimer?.Stop(); - m_ManaTimer?.Stop(); - m_CombatTimer?.Stop(); - m_ExpireCombatant?.Stop(); - m_LogoutTimer?.Stop(); - m_ExpireCriminal?.Stop(); - m_WarmodeTimer?.Stop(); - m_ParaTimer?.Stop(); - m_FrozenTimer?.Stop(); - m_AutoManifestTimer?.Stop(); - } - - public virtual bool AllowSkillUse(SkillName name) => true; - - public virtual bool UseSkill(SkillName name) => Skills.UseSkill(this, name); - - public virtual bool UseSkill(int skillID) => Skills.UseSkill(this, skillID); - - public virtual DeathMoveResult GetParentMoveResultFor(Item item) => item.OnParentDeath(this); - - public virtual DeathMoveResult GetInventoryMoveResultFor(Item item) => item.OnInventoryDeath(this); - - public virtual void Kill() - { - if (!CanBeDamaged()) - return; - if (!Alive || IsDeadBondedPet) - return; - if (Deleted) - return; - if (!Region.OnBeforeDeath(this)) - return; - if (!OnBeforeDeath()) - return; - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - box.Close(); - - m_NetState?.CancelAllTrades(); - - m_Spell?.OnCasterKilled(); - // m_Spell.Disturb( DisturbType.Kill ); - - m_Target?.Cancel(this, TargetCancelType.Canceled); - - DisruptiveAction(); - - Warmode = false; - - DropHolding(); - - Hits = 0; - Stam = 0; - Mana = 0; - - Poison = null; - Combatant = null; - - if (Paralyzed) - { - Paralyzed = false; - - m_ParaTimer?.Stop(); - } - - if (Frozen) - { - Frozen = false; - - m_FrozenTimer?.Stop(); - } - - var content = new List(); - var equip = new List(); - var moveToPack = new List(); - - var itemsCopy = new List(Items); - - var pack = Backpack; - - for (var i = 0; i < itemsCopy.Count; ++i) - { - var item = itemsCopy[i]; - - if (item == pack) - continue; - - var res = GetParentMoveResultFor(item); - - switch (res) - { - case DeathMoveResult.MoveToCorpse: - { - content.Add(item); - equip.Add(item); - break; - } - case DeathMoveResult.MoveToBackpack: - { - moveToPack.Add(item); - break; - } - } - } - - if (pack != null) - { - var packCopy = new List(pack.Items); - - for (var i = 0; i < packCopy.Count; ++i) - { - var item = packCopy[i]; - - var res = GetInventoryMoveResultFor(item); - - if (res == DeathMoveResult.MoveToCorpse) - content.Add(item); - else - moveToPack.Add(item); - } - - for (var i = 0; i < moveToPack.Count; ++i) - { - var item = moveToPack[i]; - - if (RetainPackLocsOnDeath && item.Parent == pack) - continue; - - pack.DropItem(item); - } - } - - HairInfo hair = null; - if (m_Hair != null) - hair = new HairInfo(m_Hair.ItemID, m_Hair.Hue); - - FacialHairInfo facialhair = null; - if (m_FacialHair != null) - facialhair = new FacialHairInfo(m_FacialHair.ItemID, m_FacialHair.Hue); - - var c = CreateCorpseHandler?.Invoke(this, hair, facialhair, content, equip); - - /*m_Corpse = c; - - for ( int i = 0; c != null && i < content.Count; ++i ) - c.DropItem( (Item)content[i] ); - - if (c != null) - c.MoveToWorld( this.Location, this.Map );*/ - - if (m_Map != null) - { - Packet animPacket = null; - - var eable = m_Map.GetClientsInRange(m_Location); - var corpseSerial = c?.Serial ?? Serial.Zero; - - foreach (var state in eable) - if (state != m_NetState) - { - animPacket ??= Packet.Acquire(new DeathAnimation(Serial, corpseSerial)); - - state.Send(animPacket); - - if (!state.Mobile.CanSee(this)) state.Send(RemovePacket); - } - - Packet.Release(animPacket); - - eable.Free(); - } - - Region.OnDeath(this); - OnDeath(c); - } - - /// - /// Overridable. Event invoked before the Mobile is killed. - /// - /// - /// - /// True to continue with death, false to override it. - public virtual bool OnBeforeDeath() => true; - - /// - /// Overridable. Event invoked after the Mobile is killed. Primarily, this method is responsible for - /// deleting an NPC or turning a PC into a ghost. - /// - /// - /// - public virtual void OnDeath(Container c) - { - var sound = GetDeathSound(); - - if (sound >= 0) - Effects.PlaySound(this, Map, sound); - - if (!m_Player) - { - Delete(); - } - else - { - Send(DeathStatus.Instantiate(true)); - - Warmode = false; - - BodyMod = 0; - // Body = this.Female ? 0x193 : 0x192; - Body = Race.GhostBody(this); - - var deathShroud = new Item(0x204E) { Movable = false, Layer = Layer.OuterTorso }; - - AddItem(deathShroud); - - Items.Remove(deathShroud); - Items.Insert(0, deathShroud); - - Poison = null; - Combatant = null; - - Hits = 0; - Stam = 0; - Mana = 0; - - EventSink.InvokePlayerDeath(this); - - ProcessDeltaQueue(); - - Send(DeathStatus.Instantiate(false)); - - CheckStatTimers(); - } - } - - public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => true; - - public virtual void Use(Item item) - { - if (item?.Deleted != false || item.QuestItem || Deleted) - return; - - DisruptiveAction(); - - if (m_Spell?.OnCasterUsingObject(item) == false) - return; - - var root = item.RootParent; - var okay = false; - - if (!Utility.InUpdateRange(this, item.GetWorldLocation())) - { - item.OnDoubleClickOutOfRange(this); - } - else if (!CanSee(item)) - { - item.OnDoubleClickCantSee(this); - } - else if (!item.IsAccessibleTo(this)) - { - var reg = Region.Find(item.GetWorldLocation(), item.Map); - - if (reg?.SendInaccessibleMessage(item, this) != true) - item.OnDoubleClickNotAccessible(this); - } - else if (!CheckAlive(false)) - { - item.OnDoubleClickDead(this); - } - else if (item.InSecureTrade) - { - item.OnDoubleClickSecureTrade(this); - } - else if (!AllowItemUse(item)) - { - } - else if (!item.CheckItemUse(this, item)) - { - } - else if (root is Mobile mobile && mobile.IsSnoop(this)) - { - item.OnSnoop(this); - } - else if (Region.OnDoubleClick(this, item)) - { - okay = true; - } - - if (okay) - { - // TODO: Is this correct? - if (!item.Deleted) - item.OnItemUsed(this, item); - - // TODO: Is this correct? - if (!item.Deleted) - item.OnDoubleClick(this); - } - } - - public virtual void Use(Mobile m) - { - if (m?.Deleted != false || Deleted) - return; - - DisruptiveAction(); - - if (m_Spell?.OnCasterUsingObject(m) == false) - return; - - if (!Utility.InUpdateRange(this, m)) - m.OnDoubleClickOutOfRange(this); - else if (!CanSee(m)) - m.OnDoubleClickCantSee(this); - else if (!CheckAlive(false)) - m.OnDoubleClickDead(this); - else if (Region.OnDoubleClick(this, m) && !m.Deleted) - m.OnDoubleClick(this); - } - - public virtual void Lift(Item item, int amount, out bool rejected, out LRReason reject) - { - rejected = true; - reject = LRReason.Inspecific; - - if (item == null) - return; - - var from = this; - var state = m_NetState; - - if (from.AccessLevel >= AccessLevel.GameMaster || Core.TickCount - from.NextActionTime >= 0) - { - if (from.CheckAlive()) - { - from.DisruptiveAction(); - - if (from.Holding != null) - { - reject = LRReason.AreHolding; - } - else if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(item.GetWorldLocation(), 2)) - { - reject = LRReason.OutOfRange; - } - else if (!from.CanSee(item) || !from.InLOS(item)) - { - reject = LRReason.OutOfSight; - } - else if (!item.VerifyMove(from)) - { - reject = LRReason.CannotLift; - } - else if (!item.IsAccessibleTo(from)) - { - reject = LRReason.CannotLift; - } - else if (item.Nontransferable && amount != item.Amount) - { - if (item.QuestItem) - from.SendLocalizedMessage(1074868); // Stacks of quest items cannot be unstacked. - - reject = LRReason.CannotLift; - } - else if (!item.CheckLift(from, item, ref reject)) - { - } - else - { - var root = item.RootParent; - - if (root is Mobile mobile && !mobile.CheckNonlocalLift(from, item)) - { - reject = LRReason.TryToSteal; - } - else if (!from.OnDragLift(item) || !item.OnDragLift(from)) - { - reject = LRReason.Inspecific; - } - else if (!from.CheckAlive()) - { - reject = LRReason.Inspecific; - } - else - { - item.SetLastMoved(); - - if (item.Spawner != null) - { - item.Spawner.Remove(item); - item.Spawner = null; - } - - if (amount == 0) - amount = 1; - - if (amount > item.Amount) - amount = item.Amount; - - var oldAmount = item.Amount; - // item.Amount = amount; //Set in LiftItemDupe - - if (amount < oldAmount) - LiftItemDupe(item, amount); - // item.Dupe( oldAmount - amount ); - - var map = from.Map; - - if (DragEffects && map != null && (root == null || root is Item)) - { - var eable = map.GetClientsInRange(from.Location); - Packet p = null; - var rootItem = root as Item; - - foreach (var ns in eable) - if (ns.Mobile != from && ns.Mobile.CanSee(from) && ns.Mobile.InLOS(from) && - ns.Mobile.CanSee(root)) - { - if (p == null) - { - IEntity src = new Entity(rootItem?.Serial ?? Serial.Zero, - rootItem?.Location ?? item.Location, map); - - p = Packet.Acquire(new DragEffect(src, from, item.ItemID, item.Hue, amount)); - } - - ns.Send(p); - } - - Packet.Release(p); - - eable.Free(); - } - - var fixLoc = item.Location; - var fixMap = item.Map; - var shouldFix = item.Parent == null; - - item.RecordBounce(); - item.OnItemLifted(from, item); - item.Internalize(); - - from.Holding = item; - - var liftSound = item.GetLiftSound(from); - - if (liftSound != -1) - from.Send(new PlaySound(liftSound, from)); - - from.NextActionTime = Core.TickCount + ActionDelay; - - if (fixMap != null && shouldFix) - fixMap.FixColumn(fixLoc.m_X, fixLoc.m_Y); - - reject = LRReason.Inspecific; - rejected = false; - } - } - } - else - { - reject = LRReason.Inspecific; - } - } - else - { - SendActionMessage(); - reject = LRReason.Inspecific; - } - - if (rejected && state != null) - { - state.Send(new LiftRej(reject)); - - if (item.Deleted) - return; - - if (item.Parent is Item) - { - if (state.ContainerGridLines) - state.Send(new ContainerContentUpdate6017(item)); - else - state.Send(new ContainerContentUpdate(item)); - } - else if (item.Parent is Mobile) - { - state.Send(new EquipUpdate(item)); - } - else - { - item.SendInfoTo(state); - } - - if (ObjectPropertyList.Enabled && item.Parent != null) - state.Send(item.OPLPacket); - } - } - - public static Item LiftItemDupe(Item oldItem, int amount) - { - Item item; - try - { - item = (Item)ActivatorUtil.CreateInstance(oldItem.GetType()); - } - catch - { - Console.WriteLine( - "Warning: 0x{0:X}: Item must have a zero parameter constructor to be separated from a stack. '{1}'.", - oldItem.Serial.Value, oldItem.GetType().Name); - return null; - } - - item.Visible = oldItem.Visible; - item.Movable = oldItem.Movable; - item.LootType = oldItem.LootType; - item.Direction = oldItem.Direction; - item.Hue = oldItem.Hue; - item.ItemID = oldItem.ItemID; - item.Location = oldItem.Location; - item.Layer = oldItem.Layer; - item.Name = oldItem.Name; - item.Weight = oldItem.Weight; - - item.Amount = oldItem.Amount - amount; - item.Map = oldItem.Map; - - oldItem.Amount = amount; - oldItem.OnAfterDuped(item); - - if (oldItem.Parent is Mobile parentMobile) - parentMobile.AddItem(item); - else if (oldItem.Parent is Item parentItem) parentItem.AddItem(item); - - item.Delta(ItemDelta.Update); - - return item; - } - - public virtual void SendDropEffect(Item item) - { - if (DragEffects && !item.Deleted) - { - var map = m_Map; - var root = item.RootParent; - - if (map != null && (root == null || root is Item)) - { - var eable = map.GetClientsInRange(m_Location); - Packet p = null; - var rootItem = root as Item; - - foreach (var ns in eable) - { - if (ns.StygianAbyss) - continue; - - if (ns.Mobile != this && ns.Mobile.CanSee(this) && ns.Mobile.InLOS(this) && ns.Mobile.CanSee(root)) - { - if (p == null) - { - IEntity trg = new Entity(rootItem?.Serial ?? Serial.Zero, - rootItem?.Location ?? item.Location, map); - - p = Packet.Acquire(new DragEffect(this, trg, item.ItemID, item.Hue, item.Amount)); - } - - ns.Send(p); - } - } - - Packet.Release(p); - - eable.Free(); - } - } - } - - public virtual bool Drop(Item to, Point3D loc) - { - var from = this; - var item = from.Holding; - - var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; - - from.Holding = null; - - if (!valid) return false; - - var bounced = true; - - item.SetLastMoved(); - - if (to == null || !item.DropToItem(from, to, loc)) - item.Bounce(from); - else - bounced = false; - - item.ClearBounce(); - - if (!bounced) - SendDropEffect(item); - - return !bounced; - } - - public virtual bool Drop(Point3D loc) - { - var from = this; - var item = from.Holding; - - var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; - - from.Holding = null; - - if (!valid) return false; - - var bounced = true; - - item.SetLastMoved(); - - if (!item.DropToWorld(from, loc)) - item.Bounce(from); - else - bounced = false; - - item.ClearBounce(); - - if (!bounced) - SendDropEffect(item); - - return !bounced; - } - - public virtual bool Drop(Mobile to, Point3D loc) - { - var from = this; - var item = from.Holding; - - var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; - - from.Holding = null; - - if (!valid) return false; - - var bounced = true; - - item.SetLastMoved(); - - if (to == null || !item.DropToMobile(from, to, loc)) - item.Bounce(from); - else - bounced = false; - - item.ClearBounce(); - - if (!bounced) - SendDropEffect(item); - - return !bounced; - } - - public virtual bool MutateSpeech(List hears, ref string text, ref object context) - { - if (Alive) - return false; - - var sb = new StringBuilder(text.Length, text.Length); - - for (var i = 0; i < text.Length; ++i) - sb.Append(text[i] != ' ' ? GhostChars.RandomElement() : ' '); - - text = sb.ToString(); - context = m_GhostMutateContext; - return true; - } - - public virtual void Manifest(TimeSpan delay) - { - Warmode = true; - - if (m_AutoManifestTimer == null) - m_AutoManifestTimer = new AutoManifestTimer(this, delay); - else - m_AutoManifestTimer.Stop(); - - m_AutoManifestTimer.Start(); - } - - public virtual bool CheckSpeechManifest() - { - if (Alive) - return false; - - var delay = AutoManifestTimeout; - - if (delay > TimeSpan.Zero && (!Warmode || m_AutoManifestTimer != null)) - { - Manifest(delay); - return true; - } - - return false; - } - - public virtual bool CheckHearsMutatedSpeech(Mobile m, object context) => - context != m_GhostMutateContext || m.Alive && !m.CanHearGhosts; - - private void AddSpeechItemsFrom(List list, Container cont) - { - for (var i = 0; i < cont.Items.Count; ++i) - { - var item = cont.Items[i]; - - if (item.HandlesOnSpeech) - list.Add(item); - - if (item is Container container) - AddSpeechItemsFrom(list, container); - } - } - - public virtual void DoSpeech(string text, int[] keywords, MessageType type, int hue) - { - if (Deleted || CommandSystem.Handle(this, text, type)) - return; - - var range = 15; - - switch (type) - { - case MessageType.Regular: - SpeechHue = hue; - break; - case MessageType.Emote: - EmoteHue = hue; - break; - case MessageType.Whisper: - WhisperHue = hue; - range = 1; - break; - case MessageType.Yell: - YellHue = hue; - range = 18; - break; - case MessageType.System: - break; - case MessageType.Label: - break; - case MessageType.Focus: - break; - case MessageType.Spell: - break; - case MessageType.Guild: - break; - case MessageType.Alliance: - break; - case MessageType.Command: - break; - case MessageType.Encoded: - break; - default: - type = MessageType.Regular; - break; - } - - var regArgs = new SpeechEventArgs(this, text, type, hue, keywords); - - EventSink.InvokeSpeech(regArgs); - Region.OnSpeech(regArgs); - OnSaid(regArgs); - - if (regArgs.Blocked) - return; - - text = regArgs.Speech; - - if (string.IsNullOrEmpty(text)) - return; - - var hears = m_Hears; - var onSpeech = m_OnSpeech; - - if (m_Map != null) - { - var eable = m_Map.GetObjectsInRange(m_Location, range); - - foreach (var o in eable) - if (o is Mobile heard) - { - if (!heard.CanSee(this) || !NoSpeechLOS && heard.Player && !heard.InLOS(this)) - continue; - - if (heard.m_NetState != null) - hears.Add(heard); - - if (heard.HandlesOnSpeech(this)) - onSpeech.Add(heard); - - for (var i = 0; i < heard.Items.Count; ++i) - { - var item = heard.Items[i]; - - if (item.HandlesOnSpeech) - onSpeech.Add(item); - - if (item is Container container) - AddSpeechItemsFrom(onSpeech, container); - } - } - else if (o is Item item) - { - if (item.HandlesOnSpeech) - onSpeech.Add(item); - - if (item is Container container) - AddSpeechItemsFrom(onSpeech, container); - } - - eable.Free(); - - object mutateContext = null; - var mutatedText = text; - SpeechEventArgs mutatedArgs = null; - - if (MutateSpeech(hears, ref mutatedText, ref mutateContext)) - mutatedArgs = new SpeechEventArgs(this, mutatedText, type, hue, Array.Empty()); - - CheckSpeechManifest(); - - ProcessDelta(); - - Packet regp = null; - Packet mutp = null; - - // TODO: Should this be sorted like onSpeech is below? - - for (var i = 0; i < hears.Count; ++i) - { - var heard = hears[i]; - - if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) - { - heard.OnSpeech(regArgs); - - var ns = heard.NetState; - - if (ns != null) - { - regp ??= Packet.Acquire(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); - - ns.Send(regp); - } - } - else - { - heard.OnSpeech(mutatedArgs); - - var ns = heard.NetState; - - if (ns != null) - { - mutp ??= Packet.Acquire(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, mutatedText)); - - ns.Send(mutp); - } - } - } - - Packet.Release(regp); - Packet.Release(mutp); - - if (onSpeech.Count > 1) - onSpeech.Sort(LocationComparer.GetInstance(this)); - - for (var i = 0; i < onSpeech.Count; ++i) - { - var obj = onSpeech[i]; - - if (obj is Mobile heard) - { - if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) - heard.OnSpeech(regArgs); - else - heard.OnSpeech(mutatedArgs); - } - else - { - ((Item)obj).OnSpeech(regArgs); - } - } - - if (m_Hears.Count > 0) - m_Hears.Clear(); - - if (m_OnSpeech.Count > 0) - m_OnSpeech.Clear(); - } - } - - public static Mobile GetDamagerFrom(DamageEntry de) => de?.Damager; - - public Mobile FindMostRecentDamager(bool allowSelf) => GetDamagerFrom(FindMostRecentDamageEntry(allowSelf)); - - public DamageEntry FindMostRecentDamageEntry(bool allowSelf) - { - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - continue; - - var de = DamageEntries[i]; - - if (de.HasExpired) - DamageEntries.RemoveAt(i); - else if (allowSelf || de.Damager != this) - return de; - } - - return null; - } - - public Mobile FindLeastRecentDamager(bool allowSelf) => GetDamagerFrom(FindLeastRecentDamageEntry(allowSelf)); - - public DamageEntry FindLeastRecentDamageEntry(bool allowSelf) - { - for (var i = 0; i < DamageEntries.Count; ++i) - { - if (i < 0) - continue; - - var de = DamageEntries[i]; - - if (de.HasExpired) - { - DamageEntries.RemoveAt(i); - --i; - } - else if (allowSelf || de.Damager != this) - { - return de; - } - } - - return null; - } - - public Mobile FindMostTotalDamager(bool allowSelf) => GetDamagerFrom(FindMostTotalDamageEntry(allowSelf)); - - public DamageEntry FindMostTotalDamageEntry(bool allowSelf) - { - DamageEntry mostTotal = null; - - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - continue; - - var de = DamageEntries[i]; - - if (de.HasExpired) - DamageEntries.RemoveAt(i); - else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven > mostTotal.DamageGiven)) - mostTotal = de; - } - - return mostTotal; - } - - public Mobile FindLeastTotalDamager(bool allowSelf) => GetDamagerFrom(FindLeastTotalDamageEntry(allowSelf)); - - public DamageEntry FindLeastTotalDamageEntry(bool allowSelf) - { - DamageEntry mostTotal = null; - - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - continue; - - var de = DamageEntries[i]; - - if (de.HasExpired) - DamageEntries.RemoveAt(i); - else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven < mostTotal.DamageGiven)) - mostTotal = de; - } - - return mostTotal; - } - - public DamageEntry FindDamageEntryFor(Mobile m) - { - for (var i = DamageEntries.Count - 1; i >= 0; --i) - { - if (i >= DamageEntries.Count) - continue; - - var de = DamageEntries[i]; - - if (de.HasExpired) - DamageEntries.RemoveAt(i); - else if (de.Damager == m) - return de; - } - - return null; - } - - public virtual Mobile GetDamageMaster(Mobile damagee) => null; - - public virtual DamageEntry RegisterDamage(int amount, Mobile from) - { - var de = FindDamageEntryFor(from) ?? new DamageEntry(from); - - de.DamageGiven += amount; - de.LastDamage = DateTime.UtcNow; - - DamageEntries.Remove(de); - DamageEntries.Add(de); - - var master = from.GetDamageMaster(this); - - if (master != null) - { - var list = de.Responsible; - - if (list == null) - de.Responsible = list = new List(); - - var resp = list.FirstOrDefault(check => check.Damager == master); - - if (resp == null) - list.Add(resp = new DamageEntry(master)); - - resp.DamageGiven += amount; - resp.LastDamage = DateTime.UtcNow; - } - - return de; - } - - /// - /// Overridable. Virtual event invoked when the Mobile is damaged. It is called before - /// hit points are lowered or the Mobile is killed. - /// - /// - /// - /// - public virtual void OnDamage(int amount, Mobile from, bool willKill) - { - } - - public virtual void Damage(int amount) - { - Damage(amount, null); - } - - public virtual bool CanBeDamaged() => !m_Blessed; - - public virtual void Damage(int amount, Mobile from) - { - Damage(amount, from, true); - } - - public virtual void Damage(int amount, Mobile from, bool informMount) - { - if (!CanBeDamaged() || Deleted) - return; - - if (!Region.OnDamage(this, ref amount)) - return; - - if (amount > 0) - { - var oldHits = Hits; - var newHits = oldHits - amount; - - m_Spell?.OnCasterHurt(); - - // if (m_Spell != null && m_Spell.State == SpellState.Casting) - // m_Spell.Disturb( DisturbType.Hurt, false, true ); - - if (from != null) - RegisterDamage(amount, from); - - DisruptiveAction(); - - Paralyzed = false; - - switch (VisibleDamageType) - { - case VisibleDamageType.Related: - { - SendVisibleDamageRelated(from, amount); - break; - } - case VisibleDamageType.Everyone: - { - SendVisibleDamageEveryone(amount); - break; - } - case VisibleDamageType.Selective: - { - SendVisibleDamageSelective(from, amount); - break; - } - } - - OnDamage(amount, from, newHits < 0); - - if (informMount) - Mount?.OnRiderDamaged(amount, from, newHits < 0); - - if (newHits < 0) - { - LastKiller = from; - - Hits = 0; - - if (oldHits >= 0) - Kill(); - } - else - { - Hits = newHits; - } - } - } - - public void SendVisibleDamageRelated(Mobile from, int amount) - { - NetState ourState = m_NetState, theirState = from?.m_NetState; - - if (ourState == null) - { - var master = GetDamageMaster(from); - - if (master != null) - ourState = master.m_NetState; - } - - if (theirState == null && from != null) - { - var master = from.GetDamageMaster(this); - - if (master != null) - theirState = master.m_NetState; - } - - if (amount > 0 && (ourState != null || theirState != null)) - { - Packet p = null; // = new DamagePacket( this, amount ); - - if (ourState != null) - { - p = ourState.DamagePacket - ? Packet.Acquire(new DamagePacket(Serial, amount)) - : Packet.Acquire(new DamagePacketOld(Serial, amount)); - - ourState.Send(p); - } - - if (theirState != null && theirState != ourState) - { - var newPacket = theirState.DamagePacket; - - if (newPacket && !(p is DamagePacket)) - { - Packet.Release(p); - p = Packet.Acquire(new DamagePacket(Serial, amount)); - } - else if (!newPacket && !(p is DamagePacketOld)) - { - Packet.Release(p); - p = Packet.Acquire(new DamagePacketOld(Serial, amount)); - } - - theirState.Send(p); - } - - Packet.Release(p); - } - } - - public void SendVisibleDamageEveryone(int amount) - { - if (amount < 0) - return; - - var map = m_Map; - - if (map == null) - return; - - var eable = map.GetClientsInRange(m_Location); - - Packet pNew = null; - Packet pOld = null; - - foreach (var ns in eable) - if (ns.Mobile.CanSee(this)) - { - if (ns.DamagePacket) - { - pNew ??= Packet.Acquire(new DamagePacket(Serial, amount)); - - ns.Send(pNew); - } - else - { - pOld ??= Packet.Acquire(new DamagePacketOld(Serial, amount)); - - ns.Send(pOld); - } - } - - Packet.Release(pNew); - Packet.Release(pOld); - - eable.Free(); - } - - public void SendVisibleDamageSelective(Mobile from, int amount) - { - NetState ourState = m_NetState, theirState = from?.m_NetState; - - var damager = from; - var damaged = this; - - if (ourState == null) - { - var master = GetDamageMaster(from); - - if (master != null) - { - damaged = master; - ourState = master.m_NetState; - } - } - - if (!damaged.ShowVisibleDamage) - return; - - if (theirState == null && from != null) - { - var master = from.GetDamageMaster(this); - - if (master != null) - { - damager = master; - theirState = master.m_NetState; - } - } - - if (amount > 0 && (ourState != null || theirState != null)) - { - if (damaged.CanSeeVisibleDamage && ourState != null) - { - if (ourState.DamagePacket) - ourState.Send(new DamagePacket(Serial, amount)); - else - ourState.Send(new DamagePacketOld(Serial, amount)); - } - - if (theirState != null && theirState != ourState && damager.CanSeeVisibleDamage) - { - if (theirState.DamagePacket) - theirState.Send(new DamagePacket(Serial, amount)); - else - theirState.Send(new DamagePacketOld(Serial, amount)); - } - } - } - - public void Heal(int amount) - { - Heal(amount, this, true); - } - - public void Heal(int amount, Mobile from) - { - Heal(amount, from, true); - } - - public void Heal(int amount, Mobile from, bool message) - { - if (!Alive || IsDeadBondedPet) - return; - - if (!Region.OnHeal(this, ref amount)) - return; - - OnHeal(ref amount, from); - - if (Hits + amount > HitsMax) amount = HitsMax - Hits; - - Hits += amount; - - if (message && amount > 0) - m_NetState?.Send(new MessageLocalizedAffix(Serial.MinusOne, -1, MessageType.Label, 0x3B2, 3, 1008158, "", - AffixType.Append | AffixType.System, amount.ToString(), "")); - } - - public virtual void OnHeal(ref int amount, Mobile from) - { - } - - public virtual void Deserialize(IGenericReader reader) - { - var version = reader.ReadInt(); - - switch (version) - { - case 32: - { - // Removed StuckMenu - goto case 31; - } - case 31: - { - LastStrGain = reader.ReadDeltaTime(); - LastIntGain = reader.ReadDeltaTime(); - LastDexGain = reader.ReadDeltaTime(); - - goto case 30; - } - case 30: - { - var hairflag = reader.ReadByte(); - - if ((hairflag & 0x01) != 0) - m_Hair = new HairInfo(reader); - if ((hairflag & 0x02) != 0) - m_FacialHair = new FacialHairInfo(reader); - - goto case 29; - } - case 29: - { - m_Race = reader.ReadRace(); - goto case 28; - } - case 28: - { - if (version <= 30) - LastStatGain = reader.ReadDeltaTime(); - - goto case 27; - } - case 27: - { - m_TithingPoints = reader.ReadInt(); - - goto case 26; - } - case 26: - case 25: - case 24: - { - Corpse = reader.ReadItem() as Container; - - goto case 23; - } - case 23: - { - CreationTime = reader.ReadDateTime(); - - goto case 22; - } - case 22: // Just removed followers - case 21: - { - Stabled = reader.ReadStrongMobileList(); - - goto case 20; - } - case 20: - { - CantWalk = reader.ReadBool(); - - goto case 19; - } - case 19: // Just removed variables - case 18: - { - Virtues = new VirtueInfo(reader); - - goto case 17; - } - case 17: - { - Thirst = reader.ReadInt(); - BAC = reader.ReadInt(); - - goto case 16; - } - case 16: - { - m_ShortTermMurders = reader.ReadInt(); - - if (version <= 24) - { - reader.ReadDateTime(); - reader.ReadDateTime(); - } - - goto case 15; - } - case 15: - { - if (version < 22) - reader.ReadInt(); // followers - - m_FollowersMax = reader.ReadInt(); - - goto case 14; - } - case 14: - { - MagicDamageAbsorb = reader.ReadInt(); - - goto case 13; - } - case 13: - { - GuildFealty = reader.ReadMobile(); - - goto case 12; - } - case 12: - { - m_Guild = reader.ReadGuild(); - - goto case 11; - } - case 11: - { - m_DisplayGuildTitle = reader.ReadBool(); - - goto case 10; - } - case 10: - { - CanSwim = reader.ReadBool(); - - goto case 9; - } - case 9: - { - Squelched = reader.ReadBool(); - - goto case 8; - } - case 8: - { - m_Holding = reader.ReadItem(); - - goto case 7; - } - case 7: - { - m_VirtualArmor = reader.ReadInt(); - - goto case 6; - } - case 6: - { - BaseSoundID = reader.ReadInt(); - - goto case 5; - } - case 5: - { - DisarmReady = reader.ReadBool(); - StunReady = reader.ReadBool(); - - goto case 4; - } - case 4: - { - if (version <= 25) Poison.Deserialize(reader); - - goto case 3; - } - case 3: - { - m_StatCap = reader.ReadInt(); - - goto case 2; - } - case 2: - { - NameHue = reader.ReadInt(); - - goto case 1; - } - case 1: - { - m_Hunger = reader.ReadInt(); - - goto case 0; - } - case 0: - { - if (version < 21) - Stabled = new List(); - - if (version < 18) - Virtues = new VirtueInfo(); - - if (version < 11) - m_DisplayGuildTitle = true; - - if (version < 3) - m_StatCap = 225; - - if (version < 15) - { - m_Followers = 0; - m_FollowersMax = 5; - } - - m_Location = reader.ReadPoint3D(); - m_Body = new Body(reader.ReadInt()); - m_Name = reader.ReadString(); - m_GuildTitle = reader.ReadString(); - m_Criminal = reader.ReadBool(); - m_Kills = reader.ReadInt(); - SpeechHue = reader.ReadInt(); - EmoteHue = reader.ReadInt(); - WhisperHue = reader.ReadInt(); - YellHue = reader.ReadInt(); - m_Language = reader.ReadString(); - m_Female = reader.ReadBool(); - m_Warmode = reader.ReadBool(); - m_Hidden = reader.ReadBool(); - m_Direction = (Direction)reader.ReadByte(); - m_Hue = reader.ReadInt(); - m_Str = reader.ReadInt(); - m_Dex = reader.ReadInt(); - m_Int = reader.ReadInt(); - m_Hits = reader.ReadInt(); - m_Stam = reader.ReadInt(); - m_Mana = reader.ReadInt(); - m_Map = reader.ReadMap(); - m_Blessed = reader.ReadBool(); - m_Fame = reader.ReadInt(); - m_Karma = reader.ReadInt(); - m_AccessLevel = (AccessLevel)reader.ReadByte(); - - Skills = new Skills(this, reader); - - Items = reader.ReadStrongItemList(); - - m_Player = reader.ReadBool(); - m_Title = reader.ReadString(); - Profile = reader.ReadString(); - ProfileLocked = reader.ReadBool(); - - if (version <= 18) - { - reader.ReadInt(); - reader.ReadInt(); - reader.ReadInt(); - } - - AutoPageNotify = reader.ReadBool(); - - LogoutLocation = reader.ReadPoint3D(); - LogoutMap = reader.ReadMap(); - - m_StrLock = (StatLockType)reader.ReadByte(); - m_DexLock = (StatLockType)reader.ReadByte(); - m_IntLock = (StatLockType)reader.ReadByte(); - - StatMods = new List(); - SkillMods = new List(); - - if (version < 32) - if (reader.ReadBool()) - { - var count = reader.ReadInt(); - for (var i = 0; i < count; ++i) reader.ReadDateTime(); - } - - if (m_Player && m_Map != Map.Internal) - { - LogoutLocation = m_Location; - LogoutMap = m_Map; - - m_Map = Map.Internal; - } - - m_Map?.OnEnter(this); - - if (m_Criminal) - { - m_ExpireCriminal ??= new ExpireCriminalTimer(this); - - m_ExpireCriminal.Start(); - } - - if (ShouldCheckStatTimers) - CheckStatTimers(); - - if (!m_Player && m_Dex <= 100 && m_CombatTimer != null) - m_CombatTimer.Priority = TimerPriority.FiftyMS; - else if (m_CombatTimer != null) - m_CombatTimer.Priority = TimerPriority.EveryTick; - - UpdateRegion(); - - UpdateResistances(); - - break; - } - } - - if (!m_Player) - Utility.Intern(ref m_Name); - - Utility.Intern(ref m_Title); - Utility.Intern(ref m_Language); - } - - public void ConvertHair() - { - Item hair; - - if ((hair = FindItemOnLayer(Layer.Hair)) != null) - { - HairItemID = hair.ItemID; - HairHue = hair.Hue; - hair.Delete(); - } - - if ((hair = FindItemOnLayer(Layer.FacialHair)) != null) - { - FacialHairItemID = hair.ItemID; - FacialHairHue = hair.Hue; - hair.Delete(); - } - } - - public virtual void CheckStatTimers() - { - if (Deleted) - return; - - if (Hits < HitsMax) - { - if (CanRegenHits) - { - m_HitsTimer ??= new HitsTimer(this); - - m_HitsTimer.Start(); - } - else - { - m_HitsTimer?.Stop(); - } - } - else - { - Hits = HitsMax; - } - - if (Stam < StamMax) - { - if (CanRegenStam) - { - m_StamTimer ??= new StamTimer(this); - - m_StamTimer.Start(); - } - else - { - m_StamTimer?.Stop(); - } - } - else - { - Stam = StamMax; - } - - if (Mana < ManaMax) - { - if (CanRegenMana) - { - m_ManaTimer ??= new ManaTimer(this); - - m_ManaTimer.Start(); - } - else - { - m_ManaTimer?.Stop(); - } - } - else - { - Mana = ManaMax; - } - } - - public static string GetAccessLevelName(AccessLevel level) => m_AccessLevelNames[(int)level]; - - public virtual bool CanPaperdollBeOpenedBy(Mobile from) => Body.IsHuman || Body.IsGhost || IsBodyMod; - - public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) - { - } - - public virtual void GetContextMenuEntries(Mobile from, List list) - { - if (Deleted) - return; - - if (CanPaperdollBeOpenedBy(from)) - list.Add(new PaperdollEntry(this)); - - if (from == this && Backpack != null && CanSee(Backpack) && CheckAlive(false)) - list.Add(new OpenBackpackEntry(this)); - } - - public void Internalize() - { - Map = Map.Internal; - } - - /// - /// Overridable. Virtual event invoked when is added from the Mobile, such - /// as when it is equipped. - /// - /// - /// - public virtual void OnItemAdded(Item item) - { - } - - /// - /// Overridable. Virtual event invoked when is removed from the Mobile. - /// - /// - /// - public virtual void OnItemRemoved(Item item) - { - } - - /// - /// Overridable. Virtual event invoked when is becomes a child of the Mobile; it's worn or contained - /// at some level of the Mobile's backpack or bank box - /// - /// - /// - public virtual void OnSubItemAdded(Item item) - { - } - - /// - /// Overridable. Virtual event invoked when is removed from the Mobile, its - /// backpack, or its bank box. - /// - /// - /// - public virtual void OnSubItemRemoved(Item item) - { - } - - public virtual void OnItemBounceCleared(Item item) - { - } - - public virtual void OnSubItemBounceCleared(Item item) - { - } - - public void AddItem(Item item) - { - if (item?.Deleted != false) - return; - - if (item.Parent == this) - return; - if (item.Parent is Mobile parentMobile) - parentMobile.RemoveItem(item); - else if (item.Parent is Item parentItem) - parentItem.RemoveItem(item); - else - item.SendRemovePacket(); - - item.Parent = this; - item.Map = m_Map; - - Items.Add(item); - - if (!item.IsVirtualItem) - { - UpdateTotal(item, TotalType.Gold, item.TotalGold); - UpdateTotal(item, TotalType.Items, item.TotalItems + 1); - UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); - } - - item.Delta(ItemDelta.Update); - - item.OnAdded(this); - OnItemAdded(item); - - if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || - item.PoisonResistance != 0 || item.EnergyResistance != 0) - UpdateResistances(); - } - - public void RemoveItem(Item item) - { - if (item == null || Items == null) - return; - - if (Items.Contains(item)) - { - item.SendRemovePacket(); - - // int oldCount = m_Items.Count; - - Items.Remove(item); - - if (!item.IsVirtualItem) - { - UpdateTotal(item, TotalType.Gold, -item.TotalGold); - UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); - UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); - } - - item.Parent = null; - - item.OnRemoved(this); - OnItemRemoved(item); - - if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || - item.PoisonResistance != 0 || item.EnergyResistance != 0) - UpdateResistances(); - } - } - - public virtual void Animate(int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay) - { - var map = m_Map; - - if (map == null) - return; - ProcessDelta(); - - Packet p = null; - // Packet pNew = null; - - var eable = map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state.Mobile.CanSee(this)) - { - state.Mobile.ProcessDelta(); - - // if (state.StygianAbyss) { - // if (pNew == null) - // pNew = Packet.Acquire(new NewMobileAnimation(this.Serial, action, frameCount, delay)); - - // state.Send(pNew); - // } else { - if (p == null) - { - if (Body.IsGargoyle) - { - frameCount = 10; - - if (Flying) - { - if (action >= 9 && action <= 11) - action = 71; - else if (action >= 12 && action <= 14) - action = 72; - else if (action == 20) - action = 77; - else if (action == 31) - action = 71; - else if (action == 34) - action = 78; - else if (action >= 200 && action <= 259) - action = 75; - else if (action >= 260 && action <= 270) action = 75; - } - else - { - if (action >= 200 && action <= 259) - action = 17; - else if (action >= 260 && action <= 270) action = 16; - } - } - - p = Packet.Acquire(new MobileAnimation(Serial, action, frameCount, repeatCount, forward, repeat, - delay)); - } - - state.Send(p); - // } - } - - Packet.Release(p); - // Packet.Release( pNew ); - - eable.Free(); - } - - public void SendSound(int soundID) - { - if (soundID != -1 && m_NetState != null) - Send(new PlaySound(soundID, this)); - } - - public void SendSound(int soundID, IPoint3D p) - { - if (soundID != -1 && m_NetState != null) - Send(new PlaySound(soundID, p)); - } - - public void PlaySound(int soundID) - { - if (soundID == -1 || m_Map == null) - return; - - var p = Packet.Acquire(new PlaySound(soundID, this)); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state.Mobile.CanSee(this)) - state.Send(p); - - Packet.Release(p); - - eable.Free(); - } - - public virtual void OnAccessLevelChanged(AccessLevel oldLevel) - { - } - - public virtual void OnFameChange(int oldValue) - { - } - - public virtual void OnKarmaChange(int oldValue) - { - } - - // Mobile did something which should unhide him - public virtual void RevealingAction() - { - if (m_Hidden && m_AccessLevel == AccessLevel.Player) - Hidden = false; - - DisruptiveAction(); // Anything that unhides you will also distrupt meditation - } - - public void SendRemovePacket() - { - SendRemovePacket(true); - } - - public void SendRemovePacket(bool everyone) - { - if (m_Map == null) - return; - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state != m_NetState && (everyone || !state.Mobile.CanSee(this))) - state.Send(RemovePacket); - - eable.Free(); - } - - public void ClearScreen() - { - if (m_Map == null || m_NetState == null) - return; - - var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); - - foreach (var o in eable) - if (o is Mobile m) - { - if (m != this && Utility.InUpdateRange(m_Location, m.m_Location)) - m_NetState.Send(m.RemovePacket); - } - else if (o is Item item) - { - if (InRange(item.Location, item.GetUpdateRange(this))) - m_NetState.Send(item.RemovePacket); - } - - eable.Free(); - } - - public bool Send(Packet p) => Send(p, false); - - public bool Send(Packet p, bool throwOnOffline) - { - if (m_NetState != null) - { - m_NetState.Send(p); - return true; - } - - if (throwOnOffline) - throw new MobileNotConnectedException(this, "Packet could not be sent."); - - return false; - } - - /// - /// Overridable. Event invoked before the Mobile says something. - /// - /// - public virtual void OnSaid(SpeechEventArgs e) - { - if (Squelched) - { - if (Core.ML) - SendLocalizedMessage(500168); // You can not say anything, you have been muted. - else - SendMessage("You can not say anything, you have been squelched."); // Cliloc ITSELF changed during ML. - - e.Blocked = true; - } - - if (!e.Blocked) - RevealingAction(); - } - - public virtual bool HandlesOnSpeech(Mobile from) => false; - - /// - /// Overridable. Virtual event invoked when the Mobile hears speech. This event will only be invoked if - /// returns true. - /// - /// - public virtual void OnSpeech(SpeechEventArgs e) - { - } - - public void SendEverything() - { - var ns = m_NetState; - - if (m_Map != null && ns != null) - { - var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); - - foreach (var o in eable) - if (o is Item item) - { - if (CanSee(item) && InRange(item.Location, item.GetUpdateRange(this))) - item.SendInfoTo(ns); - } - else if (o is Mobile m) - { - if (CanSee(m) && Utility.InUpdateRange(m_Location, m.m_Location)) - { - ns.Send(MobileIncoming.Create(ns, this, m)); - - if (ns.StygianAbyss) - { - if (m.Poisoned) - ns.Send(new HealthbarPoison(m)); - - if (m.Blessed || m.YellowHealthbar) - ns.Send(new HealthbarYellow(m)); - } - - if (m.IsDeadBondedPet) - ns.Send(new BondedStatus(m.Serial, true)); - - if (ObjectPropertyList.Enabled) ns.Send(m.OPLPacket); - } - } - - eable.Free(); - } - } - - public void UpdateRegion() - { - if (Deleted) - return; - - var newRegion = Region.Find(m_Location, m_Map); - - if (newRegion != m_Region) - { - Region.OnRegionChange(this, m_Region, newRegion); - - m_Region = newRegion; - OnRegionChange(m_Region, newRegion); - } - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - protected virtual void OnMapChange(Map oldMap) - { - } - - public void SetDirection(Direction dir) - { - m_Direction = dir; - } - - public virtual int GetSeason() => m_Map?.Season ?? 1; - - public virtual int GetPacketFlags() - { - var flags = 0x0; - - if (m_Paralyzed || m_Frozen) - flags |= 0x01; - - if (m_Female) - flags |= 0x02; - - if (m_Flying) - flags |= 0x04; - - if (m_Blessed || m_YellowHealthbar) - flags |= 0x08; - - if (m_Warmode) - flags |= 0x40; - - if (m_Hidden) - flags |= 0x80; - - return flags; - } - - // Pre-7.0.0.0 Packet Flags - public virtual int GetOldPacketFlags() - { - var flags = 0x0; - - if (m_Paralyzed || m_Frozen) - flags |= 0x01; - - if (m_Female) - flags |= 0x02; - - if (m_Poison != null) - flags |= 0x04; - - if (m_Blessed || m_YellowHealthbar) - flags |= 0x08; - - if (m_Warmode) - flags |= 0x40; - - if (m_Hidden) - flags |= 0x80; - - return flags; - } - - public virtual void OnGenderChanged(bool oldFemale) - { - } - - public virtual void ToggleFlying() - { - } - - /// - /// Overridable. Virtual event invoked after the Warmode property has changed. - /// - public virtual void OnWarmodeChanged() - { - } - - public virtual void OnHiddenChanged() - { - AllowedStealthSteps = 0; - - if (m_Map == null) - return; - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (!state.Mobile.CanSee(this)) - { - state.Send(RemovePacket); - } - else - { - state.Send(MobileIncoming.Create(state, state.Mobile, this)); - - if (IsDeadBondedPet) - state.Send(new BondedStatus(Serial, true)); - - if (ObjectPropertyList.Enabled) state.Send(OPLPacket); - } - - eable.Free(); - } - - public virtual void OnConnected() - { - } - - public virtual void OnDisconnected() - { - } - - public virtual void OnNetStateChanged() - { - } - - public virtual bool CanSee(object o) - { - if (o is Item item) - return CanSee(item); - - if (o is Mobile mobile) - return CanSee(mobile); - - return true; - } - - public virtual bool CanSee(Item item) - { - if (m_Map == Map.Internal) - return false; - if (item.Map == Map.Internal) - return false; - - if (item.Parent != null) - { - if (item.Parent is Item parent) - { - if (!(CanSee(parent) && parent.IsChildVisibleTo(this, item))) - return false; - } - else if (item.Parent is Mobile mobile) - { - if (!CanSee(mobile)) - return false; - } - } - - if (item is BankBox box && m_AccessLevel <= AccessLevel.Counselor && (box.Owner != this || !box.Opened)) - return false; - - if (item is SecureTradeContainer container) - { - var trade = container.Trade; - - if (trade != null && trade.From.Mobile != this && trade.To.Mobile != this) - return false; - } - - return !item.Deleted && item.Map == m_Map && (item.Visible || m_AccessLevel > AccessLevel.Counselor); - } - - public virtual bool CanSee(Mobile m) - { - if (Deleted || m.Deleted || m_Map == Map.Internal || m.m_Map == Map.Internal) - return false; - - return this == m || m.m_Map == m_Map && - (!m.Hidden || m_AccessLevel != AccessLevel.Player && - (m_AccessLevel >= m.AccessLevel || m_AccessLevel >= AccessLevel.Administrator)) && - (m.Alive || Core.SE && Skills.SpiritSpeak.Value >= 100.0 || !Alive || - m_AccessLevel > AccessLevel.Player || m.Warmode); - } - - public virtual bool CanBeRenamedBy(Mobile from) => - from.AccessLevel >= AccessLevel.GameMaster && from.m_AccessLevel > m_AccessLevel; - - public virtual void OnGuildTitleChange(string oldTitle) - { - } - - public virtual void OnAfterNameChange(string oldName, string newName) - { - } - - public virtual void OnGuildChange(BaseGuild oldGuild) - { - } - - public virtual int SafeBody(int body) - { - var delta = -1; - - for (var i = 0; delta < 0 && i < m_InvalidBodies.Length; ++i) - delta = m_InvalidBodies[i] - body; - - return delta != 0 ? body : 0; - } - - public virtual void FreeCache() - { - StaticPacketHandlers.FreeRemoveItemPacket(this); - StaticPacketHandlers.FreeOPLInfoPacket(this); - ReleaseOPLPacket(); - } - - public ObjectPropertyList NewObjectPropertyList() - { - var list = new ObjectPropertyList(this); - - GetProperties(list); - - list.Terminate(); - list.SetStatic(); - return list; - } - - public void ClearProperties() - { - ReleaseOPLPacket(); - StaticPacketHandlers.FreeOPLInfoPacket(this); - } - - public void InvalidateProperties() - { - if (!ObjectPropertyList.Enabled) - return; - - if (m_Map != null && m_Map != Map.Internal && !World.Loading) - { - var oldList = m_PropertyList; - m_PropertyList = null; - - if (oldList != null && oldList.Hash != PropertyList.Hash) - { - StaticPacketHandlers.FreeOPLInfoPacket(this); - Delta(MobileDelta.Properties); - } - } - else - { - ClearProperties(); - } - } - - public virtual void SetLocation(Point3D newLocation, bool isTeleport) - { - if (Deleted) - return; - - var oldLocation = m_Location; - - if (oldLocation == newLocation) - return; - - m_Location = newLocation; - UpdateRegion(); - - var box = FindBankNoCreate(); - - if (box?.Opened == true) - box.Close(); - - m_NetState?.ValidateAllTrades(); - - m_Map?.OnMove(oldLocation, this); - - if (isTeleport && m_NetState != null && (!m_NetState.HighSeas || !NoMoveHS)) - { - m_NetState.Sequence = 0; - - if (m_NetState.StygianAbyss) - m_NetState.Send(new MobileUpdate(this)); - else - m_NetState.Send(new MobileUpdateOld(this)); - - ClearFastwalkStack(); - } - - var map = m_Map; - - if (map != null) - { - // First, send a remove message to everyone who can no longer see us. (inOldRange && !inNewRange) - - var eable = map.GetClientsInRange(oldLocation); - - foreach (var ns in eable) - if (ns != m_NetState && !Utility.InUpdateRange(newLocation, ns.Mobile.Location)) - ns.Send(RemovePacket); - - eable.Free(); - - var ourState = m_NetState; - - // Check to see if we are attached to a client - if (ourState != null) - { - var eeable = map.GetObjectsInRange(newLocation, Core.GlobalMaxUpdateRange); - - // We are attached to a client, so it's a bit more complex. We need to send new items and people to ourself, and ourself to other clients - - foreach (var o in eeable) - if (o is Item item) - { - var range = item.GetUpdateRange(this); - var loc = item.Location; - - if (!Utility.InRange(oldLocation, loc, range) && Utility.InRange(newLocation, loc, range) && - CanSee(item)) - item.SendInfoTo(ourState); - } - else if (o != this && o is Mobile m) - { - if (!Utility.InUpdateRange(newLocation, m.m_Location)) - continue; - - var inOldRange = Utility.InUpdateRange(oldLocation, m.m_Location); - - if (m.m_NetState != null && - (isTeleport && (!m.m_NetState.HighSeas || !NoMoveHS) || !inOldRange) && m.CanSee(this)) - { - m.m_NetState.Send(MobileIncoming.Create(m.m_NetState, m, this)); - - if (m.m_NetState.StygianAbyss) - { - // if (m_Poison != null) - m.m_NetState.Send(new HealthbarPoison(this)); - - // if (m_Blessed || m_YellowHealthbar) - m.m_NetState.Send(new HealthbarYellow(this)); - } - - if (IsDeadBondedPet) - m.m_NetState.Send(new BondedStatus(Serial, true)); - - if (ObjectPropertyList.Enabled) m.m_NetState.Send(OPLPacket); - } - - if (inOldRange || !CanSee(m)) - continue; - - ourState.Send(MobileIncoming.Create(ourState, this, m)); - - if (ourState.StygianAbyss) - { - // if (m.Poisoned) - ourState.Send(new HealthbarPoison(m)); - - // if (m.Blessed || m.YellowHealthbar) - ourState.Send(new HealthbarYellow(m)); - } - - if (m.IsDeadBondedPet) - ourState.Send(new BondedStatus(m.Serial, true)); - - if (ObjectPropertyList.Enabled) ourState.Send(m.OPLPacket); - } - - eeable.Free(); - } - else - { - eable = map.GetClientsInRange(newLocation); - - // We're not attached to a client, so simply send an Incoming - foreach (var ns in eable) - if ((isTeleport && (!ns.HighSeas || !NoMoveHS) || - !Utility.InUpdateRange(oldLocation, ns.Mobile.Location)) && ns.Mobile.CanSee(this)) - { - ns.Send(MobileIncoming.Create(ns, ns.Mobile, this)); - - if (ns.StygianAbyss) - { - // if (m_Poison != null) - ns.Send(new HealthbarPoison(this)); - - // if (m_Blessed || m_YellowHealthbar) - ns.Send(new HealthbarYellow(this)); - } - - if (IsDeadBondedPet) - ns.Send(new BondedStatus(Serial, true)); - - if (ObjectPropertyList.Enabled) ns.Send(OPLPacket); - } - - eable.Free(); - } - } - - OnLocationChange(oldLocation); - - Region.OnLocationChanged(this, oldLocation); - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - protected virtual void OnLocationChange(Point3D oldLocation) - { - } - - public bool HasFreeHand() => FindItemOnLayer(Layer.TwoHanded) == null; - - public virtual IWeapon GetDefaultWeapon() => DefaultWeapon; - - public BankBox FindBankNoCreate() - { - if (m_BankBox?.Deleted != false || m_BankBox.Parent != this) - m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; - - return m_BankBox; - } - - public Item FindItemOnLayer(Layer layer) - { - var eq = Items; - var count = eq.Count; - - for (var i = 0; i < count; ++i) - { - var item = eq[i]; - - if (!item.Deleted && item.Layer == layer) return item; - } - - return null; - } - - public void SendIncomingPacket() - { - if (m_Map == null) - return; - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state.Mobile.CanSee(this)) - { - state.Send(MobileIncoming.Create(state, state.Mobile, this)); - - if (state.StygianAbyss) - { - if (m_Poison != null) - state.Send(new HealthbarPoison(this)); - - if (m_Blessed || m_YellowHealthbar) - state.Send(new HealthbarYellow(this)); - } - - if (IsDeadBondedPet) - state.Send(new BondedStatus(Serial, true)); - - if (ObjectPropertyList.Enabled) state.Send(OPLPacket); - } - - eable.Free(); - } - - public bool PlaceInBackpack(Item item) - { - if (item.Deleted) - return false; - - return Backpack?.TryDropItem(this, item, false) == true; - } - - public bool AddToBackpack(Item item) - { - if (item.Deleted) - return false; - - if (!PlaceInBackpack(item)) - { - var loc = m_Location; - var map = m_Map; - - if ((map == null || map == Map.Internal) && LogoutMap != null) - { - loc = LogoutLocation; - map = LogoutMap; - } - - item.MoveToWorld(loc, map); - return false; - } - - return true; - } - - public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => true; - - public virtual bool CheckNonlocalLift(Mobile from, Item item) => - from == this || @from.AccessLevel > AccessLevel && @from.AccessLevel >= AccessLevel.GameMaster; - - public virtual bool CheckTrade(Mobile to, Item item, SecureTradeContainer cont, bool message, bool checkItems, - int plusItems, int plusWeight) => - true; - - public virtual bool OpenTrade(Mobile from, Item offer = null) - { - if (!from.Player || !Player || !from.Alive || !Alive) return false; - - var ourState = m_NetState; - var theirState = from.m_NetState; - - if (ourState == null || theirState == null) return false; - - var cont = theirState.FindTradeContainer(this); - - if (!from.CheckTrade(this, offer, cont, true, true, 0, 0)) return false; - - cont ??= theirState.AddTrade(ourState); - - if (offer != null) cont.DropItem(offer); - - return true; - } - - /// - /// Overridable. Event invoked when a Mobile () drops an - /// - /// - /// - /// onto the Mobile. - /// - public virtual bool OnDragDrop(Mobile from, Item dropped) - { - if (from == this) - { - var pack = Backpack; - return pack != null && dropped.DropToItem(from, pack, new Point3D(-1, -1, 0)); - } - - return from.InRange(Location, 2) && OpenTrade(from, dropped); - } - - public virtual bool CheckEquip(Item item) - { - for (var i = 0; i < Items.Count; ++i) - if (Items[i].CheckConflictingLayer(this, item, item.Layer) || - item.CheckConflictingLayer(this, Items[i], Items[i].Layer)) - return false; - - return true; - } - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to wear . - /// - /// True if the request is accepted, false if otherwise. - public virtual bool OnEquip(Item item) - { - // For some reason OSI allows equipping quest items, but they are unmarked in the process - if (item.QuestItem) - { - item.QuestItem = false; - SendLocalizedMessage( - 1074769); // An item must be in your backpack (and not in a container within) to be toggled as a quest item. - } - - return true; - } - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to lift . - /// - /// True if the lift is allowed, false if otherwise. - /// - /// The following example demonstrates usage. It will disallow any attempts to pick up a pick axe if the Mobile does not have - /// enough strength. - /// - /// public override bool OnDragLift( Item item ) - /// { - /// if (item is Pickaxe && this.Str < 60) - /// { - /// SendMessage( "That is too heavy for you to lift." ); - /// return false; - /// } - /// - /// return base.OnDragLift( item ); - /// } - /// - public virtual bool OnDragLift(Item item) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop into a - /// - /// - /// - /// . - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemInto(Item item, Container container, Point3D loc) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop directly onto another - /// , . This is the case of stacking items. - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemOnto(Item item, Item target) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop into another - /// , . The target item is most likely a . - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemToItem(Item item, Item target, Point3D loc) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to give to a Mobile ( - /// ). - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemToMobile(Item item, Mobile target) => true; - - /// - /// Overridable. Virtual event invoked when the Mobile attempts to drop to the world at a - /// - /// - /// - /// . - /// - /// True if the drop is allowed, false if otherwise. - public virtual bool OnDroppedItemToWorld(Item item, Point3D location) => true; - - /// - /// Overridable. Virtual event when successfully uses while it's on this - /// Mobile. - /// - /// - public virtual void OnItemUsed(Mobile from, Item item) - { - } - - public virtual bool CheckNonlocalDrop(Mobile from, Item item, Item target) => - from == this || @from.AccessLevel > AccessLevel && @from.AccessLevel >= AccessLevel.GameMaster; - - public virtual bool CheckItemUse(Mobile from, Item item) => true; - - /// - /// Overridable. Virtual event invoked when successfully lifts from this - /// Mobile. - /// - /// - public virtual void OnItemLifted(Mobile from, Item item) - { - } - - public virtual bool AllowItemUse(Item item) => true; - - public virtual bool AllowEquipFrom(Mobile mob) => - mob == this || mob.AccessLevel >= AccessLevel.GameMaster && mob.AccessLevel > AccessLevel; - - public virtual bool EquipItem(Item item) - { - if (item?.Deleted != false || !item.CanEquip(this)) - return false; - - if (CheckEquip(item) && OnEquip(item) && item.OnEquip(this)) - { - if (m_Spell?.OnCasterEquipping(item) == false) - return false; - - // if (m_Spell != null && m_Spell.State == SpellState.Casting) - // m_Spell.Disturb( DisturbType.EquipRequest ); - - AddItem(item); - return true; - } - - return false; - } - - public void DefaultMobileInit() - { - m_StatCap = 225; - m_FollowersMax = 5; - Skills = new Skills(this); - Items = new List(); - StatMods = new List(); - SkillMods = new List(); - Map = Map.Internal; - AutoPageNotify = true; - Aggressors = new List(); - Aggressed = new List(); - Virtues = new VirtueInfo(); - Stabled = new List(); - DamageEntries = new List(); - - NextSkillTime = Core.TickCount; - CreationTime = DateTime.UtcNow; - } - - public virtual void Delta(MobileDelta flag) - { - if (m_Map == null || m_Map == Map.Internal || Deleted) - return; - - m_DeltaFlags |= flag; - - if (!m_InDeltaQueue) - { - m_InDeltaQueue = true; - - if (_processing) - lock (m_DeltaQueueR) - { - m_DeltaQueueR.Enqueue(this); - - try - { - using (var op = new StreamWriter("delta-recursion.log", true)) - { - op.WriteLine("# {0}", DateTime.UtcNow); - op.WriteLine(new StackTrace()); - op.WriteLine(); - } - } - catch - { - // ignored - } - } - else - m_DeltaQueue.Enqueue(this); - } - - Core.Set(); - } - - public static void ProcessDeltaQueue() - { - _processing = true; - - if (m_DeltaQueue.Count >= 512) - { - Parallel.ForEach(m_DeltaQueue, m => m.ProcessDelta()); - m_DeltaQueue.Clear(); - } - else - { - while (m_DeltaQueue.TryDequeue(out var m)) - m.ProcessDelta(); - } - - _processing = false; - - while (m_DeltaQueueR.TryDequeue(out var m)) - m.ProcessDelta(); - } - - public virtual void OnKillsChange(int oldValue) - { - } - - public bool CheckAlive(bool message = true) - { - if (Alive) - return true; - - if (message) - LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. - - return false; - } - - public void LaunchBrowser(string url) - { - m_NetState?.LaunchBrowser(url); - } - - public void InitStats(int str, int dex, int intel) - { - m_Str = str; - m_Dex = dex; - m_Int = intel; - - Hits = HitsMax; - Stam = StamMax; - Mana = ManaMax; - - Delta(MobileDelta.Stat | MobileDelta.Hits | MobileDelta.Stam | MobileDelta.Mana); - } - - public virtual void DisplayPaperdollTo(Mobile to) - { - EventSink.InvokePaperdollRequest(to, this); - } - - /// - /// Overridable. Event invoked when the Mobile requests to open his own paperdoll via the 'Open Paperdoll' macro. - /// - public virtual void OnPaperdollRequest() - { - if (CanPaperdollBeOpenedBy(this)) - DisplayPaperdollTo(this); - } - - /// - /// Overridable. Event invoked when wants to see this Mobile's stats. - /// - /// - public virtual void OnStatsQuery(Mobile from) - { - if (from.Map == Map && Utility.InUpdateRange(this, from) && from.CanSee(this)) - from.Send(new MobileStatus(from, this, m_NetState)); - - if (from == this) - Send(new StatLockInfo(this)); - - if (Party is IParty ip) - ip.OnStatsQuery(from, this); - } - - /// - /// Overridable. Event invoked when wants to see this Mobile's skills. - /// - public virtual void OnSkillsQuery(Mobile from) - { - if (from == this) - Send(new SkillUpdate(Skills)); - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - public virtual void OnRegionChange(Region old, Region @new) - { - } - - /// - /// Overridable. Event invoked when the Mobile is single clicked. - /// - public virtual void OnSingleClick(Mobile from) - { - if (Deleted || - AccessLevel == AccessLevel.Player && DisableHiddenSelfClick && Hidden && @from == this) - return; - - if (GuildClickMessage) - { - var guild = m_Guild; - - if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) - { - var title = GuildTitle?.Trim() ?? ""; - string type; - - if (guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length) - type = m_GuildTypes[(int)guild.Type]; - else - type = ""; - - var text = string.Format(title.Length <= 0 ? "[{1}]{2}" : "[{0}, {1}]{2}", title, guild.Abbreviation, - type); - - PrivateOverheadMessage(MessageType.Regular, SpeechHue, true, text, from.NetState); - } - } - - int hue; - - if (NameHue != -1) - hue = NameHue; - else if (AccessLevel > AccessLevel.Player) - hue = 11; - else - hue = Notoriety.GetHue(Notoriety.Compute(from, this)); - - var name = Name ?? string.Empty; - - var prefix = ""; - - if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) - prefix = m_Female ? "Lady" : "Lord"; - - var suffix = ""; - - if (ClickTitle && !string.IsNullOrEmpty(Title)) - suffix = Title; - - suffix = ApplyNameSuffix(suffix); - - string val; - - if (prefix.Length > 0 && suffix.Length > 0) - val = $"{prefix} {name} {suffix}"; - else if (prefix.Length > 0) - val = $"{prefix} {name}"; - else if (suffix.Length > 0) - val = $"{name} {suffix}"; - else - val = name; - - PrivateOverheadMessage(MessageType.Label, hue, AsciiClickMessage, val, from.NetState); - } - - public bool CheckSkill(SkillName skill, double minSkill, double maxSkill) => - SkillCheckLocationHandler?.Invoke(this, skill, minSkill, maxSkill) == true; - - public bool CheckSkill(SkillName skill, double chance) => - SkillCheckDirectLocationHandler?.Invoke(this, skill, chance) == true; - - public bool CheckTargetSkill(SkillName skill, object target, double minSkill, double maxSkill) => - SkillCheckTargetHandler?.Invoke(this, skill, target, minSkill, maxSkill) == true; - - public bool CheckTargetSkill(SkillName skill, object target, double chance) => - SkillCheckDirectTargetHandler?.Invoke(this, skill, target, chance) == true; - - public virtual void DisruptiveAction() - { - if (Meditating) - { - Meditating = false; - SendLocalizedMessage(500134); // You stop meditating. - } - } - - /// - /// Overridable. Virtual event invoked when the sector this Mobile is in gets activated. - /// - public virtual void OnSectorActivate() - { - } - - /// - /// Overridable. Virtual event invoked when the sector this Mobile is in gets deactivated. - /// - public virtual void OnSectorDeactivate() - { - } - - private class MovementRecord - { - private static readonly Queue m_InstancePool = new Queue(); - public long m_End; - - private MovementRecord(long end) => m_End = end; - - public static MovementRecord NewInstance(long end) - { - MovementRecord r; - - if (m_InstancePool.Count > 0) - { - r = m_InstancePool.Dequeue(); - - r.m_End = end; - } - else - { - r = new MovementRecord(end); - } - - return r; - } - - public bool Expired() - { - var v = Core.TickCount - m_End >= 0; - - if (v) - m_InstancePool.Enqueue(this); - - return v; - } - } - - private class WarmodeTimer : Timer - { - private readonly Mobile m_Mobile; - - public WarmodeTimer(Mobile m, bool value) - : base(WarmodeSpamDelay) - { - m_Mobile = m; - Value = value; - } - - public bool Value { get; set; } - - protected override void OnTick() - { - m_Mobile.Warmode = Value; - m_Mobile.m_WarmodeChanges = 0; - - m_Mobile.m_WarmodeTimer = null; - } - } - - private class SimpleTarget : Target - { - private readonly TargetCallback m_Callback; - - public SimpleTarget(int range, TargetFlags flags, bool allowGround, TargetCallback callback) - : base(range, allowGround, flags) => - m_Callback = callback; - - protected override void OnTarget(Mobile from, object targeted) - { - m_Callback?.Invoke(from, targeted); - } - } - - private class SimpleStateTarget : Target - { - private readonly TargetStateCallback m_Callback; - private readonly T m_State; - - public SimpleStateTarget(int range, TargetFlags flags, bool allowGround, TargetStateCallback callback, - T state) - : base(range, allowGround, flags) - { - m_Callback = callback; - m_State = state; - } - - protected override void OnTarget(Mobile from, object targeted) - { - m_Callback?.Invoke(from, targeted, m_State); - } - } - - private class AutoManifestTimer : Timer - { - private readonly Mobile m_Mobile; - - public AutoManifestTimer(Mobile m, TimeSpan delay) - : base(delay) => - m_Mobile = m; - - protected override void OnTick() - { - if (!m_Mobile.Alive) - m_Mobile.Warmode = false; - } - } - - private class LocationComparer : IComparer - { - private static LocationComparer m_Instance; - - public LocationComparer(IEntity relativeTo) => RelativeTo = relativeTo; - - public IEntity RelativeTo { get; set; } - - public int Compare(IEntity x, IEntity y) => GetDistance(x) - GetDistance(y); - - public static LocationComparer GetInstance(IEntity relativeTo) - { - if (m_Instance == null) - m_Instance = new LocationComparer(relativeTo); - else - m_Instance.RelativeTo = relativeTo; - - return m_Instance; - } - - private int GetDistance(IEntity p) - { - var x = RelativeTo.X - p.X; - var y = RelativeTo.Y - p.Y; - var z = RelativeTo.Z - p.Z; - - x *= 11; - y *= 11; - - return x * x + y * y + z * z; - } - } - - int IComparable.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); - - public int CompareTo(Mobile other) => other == null ? -1 : Serial.CompareTo(other.Serial); - - public static AllowBeneficialHandler AllowBeneficialHandler { get; set; } - - public static AllowHarmfulHandler AllowHarmfulHandler { get; set; } - - public static SkillCheckTargetHandler SkillCheckTargetHandler { get; set; } - - public static SkillCheckLocationHandler SkillCheckLocationHandler { get; set; } - - public static SkillCheckDirectTargetHandler SkillCheckDirectTargetHandler { get; set; } - - public static SkillCheckDirectLocationHandler SkillCheckDirectLocationHandler { get; set; } - - public static AOSStatusHandler AOSStatusHandler { get; set; } - - public static RegenRateHandler HitsRegenRateHandler { get; set; } - - public static TimeSpan DefaultHitsRate { get; set; } - - public static RegenRateHandler StamRegenRateHandler { get; set; } - - public static TimeSpan DefaultStamRate { get; set; } - - public static RegenRateHandler ManaRegenRateHandler { get; set; } - - public static TimeSpan DefaultManaRate { get; set; } - - public static TimeSpan GetHitsRegenRate(Mobile m) - { - if (HitsRegenRateHandler == null) - return DefaultHitsRate; - return HitsRegenRateHandler(m); - } - - public static TimeSpan GetStamRegenRate(Mobile m) - { - if (StamRegenRateHandler == null) - return DefaultStamRate; - return StamRegenRateHandler(m); - } - - public static TimeSpan GetManaRegenRate(Mobile m) - { - if (ManaRegenRateHandler == null) - return DefaultManaRate; - return ManaRegenRateHandler(m); - } - - private Map m_Map; - private Point3D m_Location; - private Direction m_Direction; - private Body m_Body; - private int m_Hue; - private Poison m_Poison; - private BaseGuild m_Guild; - private string m_GuildTitle; - private bool m_Criminal; - private string m_Name; - private int m_Kills, m_ShortTermMurders; - private string m_Language; - private NetState m_NetState; - private bool m_Female, m_Warmode, m_Hidden, m_Blessed, m_Flying; - private int m_StatCap; - private int m_Str, m_Dex, m_Int; - private int m_Hits, m_Stam, m_Mana; - private int m_Fame, m_Karma; - private AccessLevel m_AccessLevel; - private bool m_Player; - private string m_Title; - private int m_LightLevel; - private int m_TotalGold, m_TotalItems, m_TotalWeight; - private ISpell m_Spell; - private Target m_Target; - private Prompt m_Prompt; - private ContextMenu m_ContextMenu; - private Mobile m_Combatant; - private bool m_CanHearGhosts; - private int m_TithingPoints; - private bool m_DisplayGuildTitle; - private Timer m_ExpireCombatant; - private Timer m_ExpireCriminal; - private Timer m_ExpireAggrTimer; - private Timer m_LogoutTimer; - private Timer m_CombatTimer; - private Timer m_ManaTimer, m_HitsTimer, m_StamTimer; - private bool m_Paralyzed; - private ParalyzedTimer m_ParaTimer; - private bool m_Frozen; - private FrozenTimer m_FrozenTimer; - private int m_Hunger; - private Region m_Region; - private int m_VirtualArmor; - private int m_Followers, m_FollowersMax; - private List _actions; - private Queue m_MoveRecords; - private int m_WarmodeChanges; - private DateTime m_NextWarmodeChange; - private WarmodeTimer m_WarmodeTimer; - private int m_VirtualArmorMod; - private Body m_BodyMod; - private Race m_Race; - - private class ManaTimer : Timer - { - private readonly Mobile m_Owner; - - public ManaTimer(Mobile m) - : base(GetManaRegenRate(m), GetManaRegenRate(m)) - { - Priority = TimerPriority.FiftyMS; - m_Owner = m; - } - - protected override void OnTick() - { - if (m_Owner.CanRegenMana) - m_Owner.Mana++; - - Delay = Interval = GetManaRegenRate(m_Owner); - } - } - - private class HitsTimer : Timer - { - private readonly Mobile m_Owner; - - public HitsTimer(Mobile m) - : base(GetHitsRegenRate(m), GetHitsRegenRate(m)) - { - Priority = TimerPriority.FiftyMS; - m_Owner = m; - } - - protected override void OnTick() - { - if (m_Owner.CanRegenHits) - m_Owner.Hits++; - - Delay = Interval = GetHitsRegenRate(m_Owner); - } - } - - private class StamTimer : Timer - { - private readonly Mobile m_Owner; - - public StamTimer(Mobile m) - : base(GetStamRegenRate(m), GetStamRegenRate(m)) - { - Priority = TimerPriority.FiftyMS; - m_Owner = m; - } - - protected override void OnTick() - { - if (m_Owner.CanRegenStam) - m_Owner.Stam++; - - Delay = Interval = GetStamRegenRate(m_Owner); - } - } - - private class LogoutTimer : Timer - { - private readonly Mobile m_Mobile; - - public LogoutTimer(Mobile m) - : base(TimeSpan.FromDays(1.0)) - { - Priority = TimerPriority.OneSecond; - m_Mobile = m; - } - - protected override void OnTick() - { - if (m_Mobile.m_Map != Map.Internal) - { - EventSink.InvokeLogout(m_Mobile); - - m_Mobile.LogoutLocation = m_Mobile.m_Location; - m_Mobile.LogoutMap = m_Mobile.m_Map; - - m_Mobile.Internalize(); - } - } - } - - private class ParalyzedTimer : Timer - { - private readonly Mobile m_Mobile; - - public ParalyzedTimer(Mobile m, TimeSpan duration) - : base(duration) - { - Priority = TimerPriority.TwentyFiveMS; - m_Mobile = m; - } - - protected override void OnTick() - { - m_Mobile.Paralyzed = false; - } - } - - private class FrozenTimer : Timer - { - private readonly Mobile m_Mobile; - - public FrozenTimer(Mobile m, TimeSpan duration) - : base(duration) - { - Priority = TimerPriority.TwentyFiveMS; - m_Mobile = m; - } - - protected override void OnTick() - { - m_Mobile.Frozen = false; - } - } - - private class CombatTimer : Timer - { - private readonly Mobile m_Mobile; - - public CombatTimer(Mobile m) : base(TimeSpan.FromSeconds(0.0), TimeSpan.FromSeconds(0.01)) - { - m_Mobile = m; - - if (!m_Mobile.m_Player && m_Mobile.m_Dex <= 100) - Priority = TimerPriority.FiftyMS; - } - - protected override void OnTick() - { - if (Core.TickCount - m_Mobile.NextCombatTime < 0) - return; - - var combatant = m_Mobile.Combatant; - - // If no combatant, wrong map, one of us is a ghost, or cannot see, or deleted, then stop combat - if (combatant?.Deleted != false || m_Mobile.Deleted || combatant.m_Map != m_Mobile.m_Map || - !combatant.Alive || !m_Mobile.Alive || !m_Mobile.CanSee(combatant) || combatant.IsDeadBondedPet || - m_Mobile.IsDeadBondedPet) - { - m_Mobile.Combatant = null; - return; - } - - var weapon = m_Mobile.Weapon; - - if (!m_Mobile.InRange(combatant, weapon.MaxRange)) - return; - - if (m_Mobile.InLOS(combatant)) - { - weapon.OnBeforeSwing(m_Mobile, - combatant); // OnBeforeSwing for checking in regards to being hidden and whatnot - m_Mobile.RevealingAction(); - m_Mobile.NextCombatTime = - Core.TickCount + (int)weapon.OnSwing(m_Mobile, combatant).TotalMilliseconds; - } - } - } - - private class ExpireCombatantTimer : Timer - { - private readonly Mobile m_Mobile; - - public ExpireCombatantTimer(Mobile m) - : base(TimeSpan.FromMinutes(1.0)) - { - Priority = TimerPriority.FiveSeconds; - m_Mobile = m; - } - - protected override void OnTick() - { - m_Mobile.Combatant = null; - } - } - - public static TimeSpan ExpireCriminalDelay { get; set; } = TimeSpan.FromMinutes(2.0); - - private class ExpireCriminalTimer : Timer - { - private readonly Mobile m_Mobile; - - public ExpireCriminalTimer(Mobile m) - : base(ExpireCriminalDelay) - { - Priority = TimerPriority.FiveSeconds; - m_Mobile = m; - } - - protected override void OnTick() - { - m_Mobile.Criminal = false; - } - } - - private class ExpireAggressorsTimer : Timer - { - private readonly Mobile m_Mobile; - - public ExpireAggressorsTimer(Mobile m) - : base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(5.0)) - { - m_Mobile = m; - Priority = TimerPriority.FiveSeconds; - } - - protected override void OnTick() - { - if (m_Mobile.Deleted || m_Mobile.Aggressors.Count == 0 && m_Mobile.Aggressed.Count == 0) - m_Mobile.StopAggrExpire(); - else - m_Mobile.CheckAggrExpire(); - } - } - - private class SimplePrompt : Prompt - { - private readonly PromptCallback m_Callback; - private readonly bool m_CallbackHandlesCancel; - private readonly PromptCallback m_CancelCallback; - - public SimplePrompt(PromptCallback callback, PromptCallback cancelCallback) - { - m_Callback = callback; - m_CancelCallback = cancelCallback; - } - - public SimplePrompt(PromptCallback callback, bool callbackHandlesCancel = false) - { - m_Callback = callback; - m_CallbackHandlesCancel = callbackHandlesCancel; - } - - public override void OnResponse(Mobile from, string text) - { - m_Callback?.Invoke(from, text); - } - - public override void OnCancel(Mobile from) - { - if (m_CallbackHandlesCancel && m_Callback != null) - m_Callback(from, ""); - else - m_CancelCallback?.Invoke(from, ""); - } - } - - public Prompt BeginPrompt(PromptCallback callback, PromptCallback cancelCallback) - { - return Prompt = new SimplePrompt(callback, cancelCallback); - } - - public Prompt BeginPrompt(PromptCallback callback, bool callbackHandlesCancel = false) - { - return Prompt = new SimplePrompt(callback, callbackHandlesCancel); - } - - private class SimpleStatePrompt : Prompt - { - private readonly PromptStateCallback m_Callback; - private readonly PromptStateCallback m_CancelCallback; - - private readonly T m_State; - - public SimpleStatePrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) - { - m_Callback = callback; - m_CancelCallback = cancelCallback; - m_State = state; - } - - public SimpleStatePrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) - { - m_Callback = callback; - m_State = state; - m_CancelCallback = callbackHandlesCancel ? callback : null; - } - - public SimpleStatePrompt(PromptStateCallback callback, T state) : this(callback, false, state) - { - } - - public override void OnResponse(Mobile from, string text) - { - m_Callback?.Invoke(from, text, m_State); - } - - public override void OnCancel(Mobile from) - { - m_CancelCallback?.Invoke(from, "", m_State); - } - } - - public Prompt BeginPrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) => - Prompt = new SimpleStatePrompt(callback, cancelCallback, state); - - public Prompt BeginPrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) => - Prompt = new SimpleStatePrompt(callback, callbackHandlesCancel, state); - - public Prompt BeginPrompt(PromptStateCallback callback, T state) => - BeginPrompt(callback, false, state); - - public Prompt Prompt - { - get => m_Prompt; - set - { - var oldPrompt = m_Prompt; - var newPrompt = value; - - if (oldPrompt == newPrompt) - return; - - m_Prompt = null; - - if (newPrompt != null) - oldPrompt?.OnCancel(this); - - m_Prompt = newPrompt; - - if (newPrompt != null) - Send(new UnicodePrompt(newPrompt)); - } - } - - public virtual int GetAngerSound() - { - if (BaseSoundID != 0) - return BaseSoundID; - - return -1; - } - - public virtual int GetIdleSound() - { - if (BaseSoundID != 0) - return BaseSoundID + 1; - - return -1; - } - - public virtual int GetAttackSound() - { - if (BaseSoundID != 0) - return BaseSoundID + 2; - - return -1; - } - - public virtual int GetHurtSound() - { - if (BaseSoundID != 0) - return BaseSoundID + 3; - - return -1; - } - - public virtual int GetDeathSound() - { - if (BaseSoundID != 0) return BaseSoundID + 4; - - if (m_Body.IsHuman) return Utility.Random(m_Female ? 0x314 : 0x423, m_Female ? 4 : 5); - return -1; - } - - public IPooledEnumerable GetItemsInRange(int range) => GetItemsInRange(range); - - public IPooledEnumerable GetItemsInRange(int range) where T : Item - { - var map = m_Map; - - if (map == null) - return Map.NullEnumerable.Instance; - - return map.GetItemsInRange(m_Location, range); - } - - public IPooledEnumerable GetObjectsInRange(int range) - { - var map = m_Map; - - if (map == null) - return Map.NullEnumerable.Instance; - - return map.GetObjectsInRange(m_Location, range); - } - - public IPooledEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); - - public IPooledEnumerable GetMobilesInRange(int range) where T : Mobile - { - var map = m_Map; - - if (map == null) - return Map.NullEnumerable.Instance; - - return map.GetMobilesInRange(m_Location, range); - } - - public IPooledEnumerable GetClientsInRange(int range) - { - var map = m_Map; - - if (map == null) - return Map.NullEnumerable.Instance; - - return map.GetClientsInRange(m_Location, range); - } - - public void SayTo(Mobile to, bool ascii, string text) - { - PrivateOverheadMessage(MessageType.Regular, SpeechHue, ascii, text, to.NetState); - } - - public void SayTo(Mobile to, string text) - { - SayTo(to, false, text); - } - - public void SayTo(Mobile to, string format, params object[] args) - { - SayTo(to, false, string.Format(format, args)); - } - - public void SayTo(Mobile to, bool ascii, string format, params object[] args) - { - SayTo(to, ascii, string.Format(format, args)); - } - - public void SayTo(Mobile to, int number) - { - to.Send(new MessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, "")); - } - - public void SayTo(Mobile to, int number, string args) - { - to.Send(new MessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, args)); - } - - public void Say(bool ascii, string text) - { - PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text); - } - - public void Say(string text) - { - PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text); - } - - public void Say(string format, params object[] args) - { - Say(string.Format(format, args)); - } - - public void Say(int number, AffixType type, string affix, string args) - { - PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args); - } - - public void Say(int number, string args = "") - { - PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args); - } - - public void Emote(string text) - { - PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text); - } - - public void Emote(string format, params object[] args) - { - Emote(string.Format(format, args)); - } - - public void Emote(int number, string args = "") - { - PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args); - } - - public void Whisper(string text) - { - PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text); - } - - public void Whisper(string format, params object[] args) - { - Whisper(string.Format(format, args)); - } - - public void Whisper(int number, string args = "") - { - PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args); - } - - public void Yell(string text) - { - PublicOverheadMessage(MessageType.Yell, YellHue, false, text); - } - - public void Yell(string format, params object[] args) - { - Yell(string.Format(format, args)); - } - - public void Yell(int number, string args = "") - { - PublicOverheadMessage(MessageType.Yell, YellHue, number, args); - } - - public bool SendHuePicker(HuePicker p, bool throwOnOffline = false) - { - if (m_NetState != null) - { - p.SendTo(m_NetState); - return true; - } - - if (throwOnOffline) throw new MobileNotConnectedException(this, "Hue picker could not be sent."); - - return false; - } - - public Gump FindGump() where T : Gump - { - return m_NetState?.Gumps.Find(g => g is T); - } - - public bool CloseGump() where T : Gump - { - if (m_NetState == null) - return false; - - var gump = FindGump(); - - if (gump != null) - { - // TODO: Recycle CloseGump - m_NetState.Send(new CloseGump(gump.TypeID, 0)); - m_NetState.RemoveGump(gump); - gump.OnServerClose(m_NetState); - } - - return true; - } - - public bool CloseAllGumps() - { - var ns = m_NetState; - - if (ns == null) - return false; - - var gumps = new List(ns.Gumps); - - ns.ClearGumps(); - - foreach (var gump in gumps) - { - ns.Send(new CloseGump(gump.TypeID, 0)); - - gump.OnServerClose(ns); - } - - return true; - } - - public bool HasGump() where T : Gump => FindGump() != null; - - public bool SendGump(Gump g) - { - if (m_NetState == null) - return false; - - g.SendTo(m_NetState); - return true; - } - - public bool SendMenu(IMenu m) - { - if (m_NetState == null) - return false; - - m.SendTo(m_NetState); - return true; - } - - public virtual bool CanBeBeneficial(Mobile target) => CanBeBeneficial(target, true, false); - - public virtual bool CanBeBeneficial(Mobile target, bool message) => CanBeBeneficial(target, message, false); - - public virtual bool CanBeBeneficial(Mobile target, bool message, bool allowDead) - { - if (target == null) - return false; - - if (Deleted || target.Deleted || !Alive || IsDeadBondedPet || - !allowDead && (!target.Alive || target.IsDeadBondedPet)) - { - if (message) - SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. - - return false; - } - - if (target == this) - return true; - - if (/*m_Player &&*/!Region.AllowBeneficial(this, target)) - { - // TODO: Pets - // if (!(target.m_Player || target.Body.IsHuman || target.Body.IsAnimal)) - // { - if (message) - SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. - - return false; - // } - } - - return true; - } - - public virtual bool IsBeneficialCriminal(Mobile target) - { - if (this == target) - return false; - - var n = Notoriety.Compute(this, target); - - return n == Notoriety.Criminal || n == Notoriety.Murderer; - } - - /// - /// Overridable. Event invoked when the Mobile does a beneficial action. - /// - public virtual void OnBeneficialAction(Mobile target, bool isCriminal) - { - if (isCriminal) - CriminalAction(false); - } - - public virtual void DoBeneficial(Mobile target) - { - if (target == null) - return; - - OnBeneficialAction(target, IsBeneficialCriminal(target)); - - Region.OnBeneficialAction(this, target); - target.Region.OnGotBeneficialAction(this, target); - } - - public virtual bool BeneficialCheck(Mobile target) - { - if (CanBeBeneficial(target, true)) - { - DoBeneficial(target); - return true; - } - - return false; - } - - public virtual bool CanBeHarmful(Mobile target) => CanBeHarmful(target, true); - - public virtual bool CanBeHarmful(Mobile target, bool message) => CanBeHarmful(target, message, false); - - public virtual bool CanBeHarmful(Mobile target, bool message, bool ignoreOurBlessedness) - { - if (target == null) - return false; - - if (Deleted || !ignoreOurBlessedness && m_Blessed || target.Deleted || target.m_Blessed || !Alive || - IsDeadBondedPet || !target.Alive || target.IsDeadBondedPet) - { - if (message) - SendLocalizedMessage(1001018); // You can not perform negative acts on your target. - - return false; - } - - if (target == this) - return true; - - // TODO: Pets - if (/*m_Player &&*/ - !Region.AllowHarmful(this, target)) // (target.m_Player || target.Body.IsHuman) && !Region.AllowHarmful( this, target ) ) - { - if (message) - SendLocalizedMessage(1001018); // You can not perform negative acts on your target. - - return false; - } - - return true; - } - - public virtual bool IsHarmfulCriminal(Mobile target) => - this != target && Notoriety.Compute(this, target) == Notoriety.Innocent; - - /// - /// Overridable. Event invoked when the Mobile does a harmful action. - /// - public virtual void OnHarmfulAction(Mobile target, bool isCriminal) - { - if (isCriminal) - CriminalAction(false); - } - - public virtual void DoHarmful(Mobile target) - { - DoHarmful(target, false); - } - - public virtual void DoHarmful(Mobile target, bool indirect) - { - if (target == null || Deleted) - return; - - var isCriminal = IsHarmfulCriminal(target); - - OnHarmfulAction(target, isCriminal); - target.AggressiveAction(this, isCriminal); - - Region.OnDidHarmful(this, target); - target.Region.OnGotHarmful(this, target); - - if (!indirect) - Combatant = target; - - if (m_ExpireCombatant == null) - m_ExpireCombatant = new ExpireCombatantTimer(this); - else - m_ExpireCombatant.Stop(); - - m_ExpireCombatant.Start(); - } - - public virtual bool HarmfulCheck(Mobile target) - { - if (CanBeHarmful(target)) - { - DoHarmful(target); - return true; - } - - return false; - } - - /// - /// Gets a list of all StatMod's currently active for the Mobile. - /// - public List StatMods { get; private set; } - - public bool RemoveStatMod(string name) - { - for (var i = 0; i < StatMods.Count; ++i) - { - var check = StatMods[i]; - - if (check.Name == name) - { - StatMods.RemoveAt(i); - CheckStatTimers(); - Delta(MobileDelta.Stat | GetStatDelta(check.Type)); - return true; - } - } - - return false; - } - - public StatMod GetStatMod(string name) - { - for (var i = 0; i < StatMods.Count; ++i) - { - var check = StatMods[i]; - - if (check.Name == name) - return check; - } - - return null; - } - - public void AddStatMod(StatMod mod) - { - for (var i = 0; i < StatMods.Count; ++i) - { - var check = StatMods[i]; - - if (check.Name == mod.Name) - { - Delta(MobileDelta.Stat | GetStatDelta(check.Type)); - StatMods.RemoveAt(i); - break; - } - } - - StatMods.Add(mod); - Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); - CheckStatTimers(); - } - - private MobileDelta GetStatDelta(StatType type) - { - MobileDelta delta = 0; - - if ((type & StatType.Str) != 0) - delta |= MobileDelta.Hits; - - if ((type & StatType.Dex) != 0) - delta |= MobileDelta.Stam; - - if ((type & StatType.Int) != 0) - delta |= MobileDelta.Mana; - - return delta; - } - - /// - /// Computes the total modified offset for the specified stat type. Expired instances are removed. - /// - public int GetStatOffset(StatType type) - { - var offset = 0; - - for (var i = 0; i < StatMods.Count; ++i) - { - var mod = StatMods[i]; - - if (mod.HasElapsed()) - { - StatMods.RemoveAt(i); - Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); - CheckStatTimers(); - - --i; - } - else if ((mod.Type & type) != 0) - { - offset += mod.Offset; - } - } - - return offset; - } - - /// - /// Overridable. Virtual event invoked when the changes. - /// - /// - /// - public virtual void OnRawStrChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when changes. - /// - /// - /// - public virtual void OnRawDexChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when the changes. - /// - /// - /// - public virtual void OnRawIntChange(int oldValue) - { - } - - /// - /// Overridable. Virtual event invoked when the , , or - /// changes. - /// - /// - /// - /// - public virtual void OnRawStatChange(StatType stat, int oldValue) - { - } - - /// - /// Gets or sets the base, unmodified, strength of the Mobile. Ranges from 1 to 65000, inclusive. - /// - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public int RawStr - { - get => m_Str; - set - { - value = Math.Clamp(value, 1, 65000); - - if (m_Str != value) - { - var oldValue = m_Str; - - m_Str = value; - Delta(MobileDelta.Stat | MobileDelta.Hits); - - if (Hits < HitsMax) - { - m_HitsTimer ??= new HitsTimer(this); - - m_HitsTimer.Start(); - } - else if (Hits > HitsMax) - { - Hits = HitsMax; - } - - OnRawStrChange(oldValue); - OnRawStatChange(StatType.Str, oldValue); - } - } - } - - /// - /// Gets or sets the effective strength of the Mobile. This is the sum of the plus any additional - /// modifiers. Any attempts to set this value when under the influence of a will result in no change. - /// It ranges from 1 to 65000, inclusive. - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Str - { - get => Math.Clamp(m_Str + GetStatOffset(StatType.Str), 1, 65000); - set - { - if (StatMods.Count == 0) - RawStr = value; - } - } - - /// - /// Gets or sets the base, unmodified, dexterity of the Mobile. Ranges from 1 to 65000, inclusive. - /// - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public int RawDex - { - get => m_Dex; - set - { - value = Math.Clamp(value, 1, 65000); - - if (m_Dex != value) - { - var oldValue = m_Dex; - - m_Dex = value; - Delta(MobileDelta.Stat | MobileDelta.Stam); - - if (Stam < StamMax) - { - m_StamTimer ??= new StamTimer(this); - - m_StamTimer.Start(); - } - else if (Stam > StamMax) - { - Stam = StamMax; - } - - OnRawDexChange(oldValue); - OnRawStatChange(StatType.Dex, oldValue); - } - } - } - - /// - /// Gets or sets the effective dexterity of the Mobile. This is the sum of the plus any additional - /// modifiers. Any attempts to set this value when under the influence of a will result in no change. - /// It ranges from 1 to 65000, inclusive. - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Dex - { - get => Math.Clamp(m_Dex + GetStatOffset(StatType.Dex), 0, 65000); - set - { - if (StatMods.Count == 0) - RawDex = value; - } - } - - /// - /// Gets or sets the base, unmodified, intelligence of the Mobile. Ranges from 1 to 65000, inclusive. - /// - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public int RawInt - { - get => m_Int; - set - { - value = Math.Clamp(value, 1, 65000); - - if (m_Int != value) - { - var oldValue = m_Int; - - m_Int = value; - Delta(MobileDelta.Stat | MobileDelta.Mana); - - if (Mana < ManaMax) - { - m_ManaTimer ??= new ManaTimer(this); - - m_ManaTimer.Start(); - } - else if (Mana > ManaMax) - { - Mana = ManaMax; - } - - OnRawIntChange(oldValue); - OnRawStatChange(StatType.Int, oldValue); - } - } - } - - /// - /// Gets or sets the effective intelligence of the Mobile. This is the sum of the plus any additional - /// modifiers. Any attempts to set this value when under the influence of a will result in no change. - /// It ranges from 1 to 65000, inclusive. - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int Int - { - get => Math.Clamp(m_Int + GetStatOffset(StatType.Int), 0, 65000); - set - { - if (StatMods.Count == 0) - RawInt = value; - } - } - - public virtual void OnHitsChange(int oldValue) - { - } - - public virtual void OnStamChange(int oldValue) - { - } - - public virtual void OnManaChange(int oldValue) - { - } - - /// - /// Gets or sets the current hit point of the Mobile. This value ranges from 0 to , inclusive. When set - /// to the value of , the CanReportMurder flag of all - /// aggressors is reset to false, and the list of damage entries is cleared. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int Hits - { - get => m_Hits; - set - { - if (Deleted) - return; - - value = Math.Clamp(value, 0, HitsMax); - - if (value == HitsMax) - { - m_HitsTimer?.Stop(); - - for (var i = 0; i < Aggressors.Count; i++) // reset reports on full HP - Aggressors[i].CanReportMurder = false; - - if (DamageEntries.Count > 0) - DamageEntries.Clear(); // reset damage entries on full HP - } - else - { - if (CanRegenHits) - { - m_HitsTimer ??= new HitsTimer(this); - - m_HitsTimer.Start(); - } - else - { - m_HitsTimer?.Stop(); - } - } - - if (m_Hits != value) - { - var oldValue = m_Hits; - m_Hits = value; - Delta(MobileDelta.Hits); - OnHitsChange(oldValue); - } - } - } - - /// - /// Overridable. Gets the maximum hit point of the Mobile. By default, this returns: 50 + ( / 2) - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int HitsMax => 50 + Str / 2; - - /// - /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int Stam - { - get => m_Stam; - set - { - if (Deleted) - return; - - value = Math.Clamp(value, 0, StamMax); - - if (value == StamMax) - { - m_StamTimer?.Stop(); - } - else - { - if (CanRegenStam) - { - m_StamTimer ??= new StamTimer(this); - - m_StamTimer.Start(); - } - else - { - m_StamTimer?.Stop(); - } - } - - if (m_Stam != value) - { - var oldValue = m_Stam; - m_Stam = value; - Delta(MobileDelta.Stam); - OnStamChange(oldValue); - } - } - } - - /// - /// Overridable. Gets the maximum stamina of the Mobile. By default, this returns: - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int StamMax => Dex; - - /// - /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. - /// - [CommandProperty(AccessLevel.GameMaster)] - public int Mana - { - get => m_Mana; - set - { - if (Deleted) - return; - - value = Math.Clamp(value, 0, ManaMax); - - if (value == ManaMax) - { - m_ManaTimer?.Stop(); - - if (Meditating) - { - Meditating = false; - SendLocalizedMessage(501846); // You are at peace. - } - } - else - { - if (CanRegenMana) - { - m_ManaTimer ??= new ManaTimer(this); - - m_ManaTimer.Start(); - } - else - { - m_ManaTimer?.Stop(); - } - } - - if (m_Mana != value) - { - var oldValue = m_Mana; - m_Mana = value; - Delta(MobileDelta.Mana); - OnManaChange(oldValue); - } - } - } - - /// - /// Overridable. Gets the maximum mana of the Mobile. By default, this returns: - /// - /// - /// - /// - [CommandProperty(AccessLevel.GameMaster)] - public virtual int ManaMax => Int; - - public Timer PoisonTimer { get; private set; } - - [CommandProperty(AccessLevel.GameMaster)] - public Poison Poison - { - get => m_Poison; - set - { - /*if (m_Poison != value && (m_Poison == null || value == null || m_Poison.Level < value.Level)) - {*/ - m_Poison = value; - Delta(MobileDelta.HealthbarPoison); - - if (PoisonTimer != null) - { - PoisonTimer.Stop(); - PoisonTimer = null; - } - - if (m_Poison != null) - { - PoisonTimer = m_Poison.ConstructTimer(this); - - PoisonTimer?.Start(); - } - - CheckStatTimers(); - /*}*/ - } - } - - /// - /// Overridable. Event invoked when a call to failed because - /// returned false: the Mobile was resistant to the poison. By default, this broadcasts an overhead message: * The poison - /// seems to have no effect. * - /// - /// - /// - /// - public virtual void OnPoisonImmunity(Mobile from, Poison poison) - { - PublicOverheadMessage(MessageType.Emote, 0x3B2, 1005534); // * The poison seems to have no effect. * - } - - /// - /// Overridable. Virtual event invoked when a call to failed because - /// returned false: the Mobile was already poisoned by an equal or greater strength poison. - /// - /// - /// - /// - public virtual void OnHigherPoison(Mobile from, Poison poison) - { - } - - /// - /// Overridable. Event invoked when a call to succeeded. By default, this broadcasts an overhead - /// message varying by the level of the poison. Example: * Zippy begins to spasm uncontrollably. * - /// - /// - /// - public virtual void OnPoisoned(Mobile from, Poison poison, Poison oldPoison) - { - if (poison != null) - { - LocalOverheadMessage(MessageType.Regular, 0x21, 1042857 + poison.Level * 2); - NonlocalOverheadMessage(MessageType.Regular, 0x21, 1042858 + poison.Level * 2, Name); - } - } - - /// - /// Overridable. Called from , this method checks if the Mobile is immune to some - /// . If true, will be invoked and - /// is returned. - /// - /// - /// - /// - public virtual bool CheckPoisonImmunity(Mobile from, Poison poison) => false; - - /// - /// Overridable. Called from , this method checks if the Mobile is already poisoned by some - /// of equal or greater strength. If true, will be invoked and - /// is returned. - /// - /// - /// - /// - public virtual bool CheckHigherPoison(Mobile from, Poison poison) => m_Poison != null && m_Poison.Level >= poison.Level; - - /// - /// Overridable. Attempts to apply poison to the Mobile. Checks are made such that no - /// higher poison is active and that the Mobile is not - /// immune to the poison. Provided those assertions are true, the - /// is applied and is invoked. - /// - /// - /// - /// - /// One of four possible values: - /// - /// - /// - /// Cured - /// - /// The parameter was null and so was invoked. - /// - /// - /// - /// HigherPoisonActive - /// - /// The call to returned false. - /// - /// - /// - /// Immune - /// - /// The call to returned false. - /// - /// - /// - /// Poisoned - /// - /// The was successfully applied. - /// - /// - /// - public virtual ApplyPoisonResult ApplyPoison(Mobile from, Poison poison) - { - if (poison == null) - { - CurePoison(from); - return ApplyPoisonResult.Cured; - } - - if (CheckHigherPoison(from, poison)) - { - OnHigherPoison(from, poison); - return ApplyPoisonResult.HigherPoisonActive; - } - - if (CheckPoisonImmunity(from, poison)) - { - OnPoisonImmunity(from, poison); - return ApplyPoisonResult.Immune; - } - - var oldPoison = m_Poison; - Poison = poison; - - OnPoisoned(from, poison, oldPoison); - - return ApplyPoisonResult.Poisoned; - } - - /// - /// Overridable. Called from , this method checks to see that the Mobile can be cured of - /// - /// - /// - /// - public virtual bool CheckCure(Mobile from) => true; - - /// - /// Overridable. Virtual event invoked when a call to succeeded. - /// - /// - /// - /// - public virtual void OnCured(Mobile from, Poison oldPoison) - { - } - - /// - /// Overridable. Virtual event invoked when a call to failed. - /// - /// - /// - /// - public virtual void OnFailedCure(Mobile from) - { - } - - /// - /// Overridable. Attempts to cure any poison that is currently active. - /// - /// True if poison was cured, false if otherwise. - public virtual bool CurePoison(Mobile from) - { - if (CheckCure(from)) - { - var oldPoison = m_Poison; - Poison = null; - - OnCured(from, oldPoison); - - return true; - } - - OnFailedCure(from); - - return false; - } - - private HairInfo m_Hair; - private FacialHairInfo m_FacialHair; - - [CommandProperty(AccessLevel.GameMaster)] - public int HairItemID - { - get => m_Hair?.ItemID ?? 0; - set - { - if (m_Hair == null && value > 0) - m_Hair = new HairInfo(value); - else if (value <= 0) - m_Hair = null; - else if (m_Hair != null) - m_Hair.ItemID = value; - - Delta(MobileDelta.Hair); - } - } - - // [CommandProperty( AccessLevel.GameMaster )] - // public int HairSerial { get { return HairInfo.FakeSerial( this ); } } - - [CommandProperty(AccessLevel.GameMaster)] - public int FacialHairItemID - { - get => m_FacialHair?.ItemID ?? 0; - set - { - if (m_FacialHair == null && value > 0) - m_FacialHair = new FacialHairInfo(value); - else if (value <= 0) - m_FacialHair = null; - else if (m_FacialHair != null) - m_FacialHair.ItemID = value; - - Delta(MobileDelta.FacialHair); - } - } - - // [CommandProperty( AccessLevel.GameMaster )] - // public int FacialHairSerial { get { return FacialHairInfo.FakeSerial( this ); } } - - [CommandProperty(AccessLevel.GameMaster)] - public int HairHue - { - get => m_Hair?.Hue ?? 0; - set - { - if (m_Hair != null) - { - m_Hair.Hue = value; - Delta(MobileDelta.Hair); - } - } - } - - [CommandProperty(AccessLevel.GameMaster)] - public int FacialHairHue - { - get => m_FacialHair?.Hue ?? 0; - set - { - if (m_FacialHair != null) - { - m_FacialHair.Hue = value; - Delta(MobileDelta.FacialHair); - } - } - } - - public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int hue, int renderMode) - { - Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode); - } - - public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes) - { - Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes); - } - - public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer, int unknown) - { - Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, - explodeEffect, explodeSound, layer, unknown); - } - - public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int hue, int renderMode, int effect, int explodeEffect, int explodeSound, int unknown) - { - Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode, effect, - explodeEffect, explodeSound, (EffectLayer)255, unknown); - } - - public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int effect, int explodeEffect, int explodeSound, int unknown) - { - Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, effect, explodeEffect, - explodeSound, unknown); - } - - public void MovingParticles(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, - int effect, int explodeEffect, int explodeSound) - { - Effects.SendMovingParticles(this, to, itemID, speed, duration, fixedDirection, explodes, 0, 0, effect, - explodeEffect, explodeSound, 0); - } - - public void FixedEffect(int itemID, int speed, int duration, int hue, int renderMode) - { - Effects.SendTargetEffect(this, itemID, speed, duration, hue, renderMode); - } - - public void FixedEffect(int itemID, int speed, int duration) - { - Effects.SendTargetEffect(this, itemID, speed, duration, 0, 0); - } - - public void FixedParticles(int itemID, int speed, int duration, int effect, int hue, int renderMode, - EffectLayer layer, int unknown) - { - Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, unknown); - } - - public void FixedParticles(int itemID, int speed, int duration, int effect, int hue, int renderMode, - EffectLayer layer) - { - Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, 0); - } - - public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer, int unknown) - { - Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, unknown); - } - - public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer) - { - Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, 0); - } - - public void BoltEffect(int hue) - { - Effects.SendBoltEffect(this, true, hue); - } - - public Direction GetDirectionTo(int x, int y) - { - var dx = m_Location.m_X - x; - var dy = m_Location.m_Y - y; - - var rx = (dx - dy) * 44; - var ry = (dx + dy) * 44; - - var ax = Math.Abs(rx); - var ay = Math.Abs(ry); - - Direction ret; - - if ((ay >> 1) - ax >= 0) - ret = ry > 0 ? Direction.Up : Direction.Down; - else if ((ax >> 1) - ay >= 0) - ret = rx > 0 ? Direction.Left : Direction.Right; - else if (rx >= 0 && ry >= 0) - ret = Direction.West; - else if (rx >= 0 && ry < 0) - ret = Direction.South; - else if (rx < 0 && ry < 0) - ret = Direction.East; - else - ret = Direction.North; - - return ret; - } - - public Direction GetDirectionTo(Point2D p) => GetDirectionTo(p.m_X, p.m_Y); - - public Direction GetDirectionTo(Point3D p) => GetDirectionTo(p.m_X, p.m_Y); - - public Direction GetDirectionTo(IPoint2D p) - { - if (p == null) - return Direction.North; - - return GetDirectionTo(p.X, p.Y); - } - - public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true) - { - if (m_Map == null) - return; - - var p = ascii - ? (Packet)new AsciiMessage(Serial, Body, type, hue, 3, Name, text) - : new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text); - - p.Acquire(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) - state.Send(p); - - Packet.Release(p); - - eable.Free(); - } - - public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "", bool noLineOfSight = true) - { - if (m_Map == null) - return; - - var p = Packet.Acquire(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) - state.Send(p); - - Packet.Release(p); - - eable.Free(); - } - - public void PublicOverheadMessage(MessageType type, int hue, int number, AffixType affixType, string affix, - string args = "", bool noLineOfSight = false) - { - if (m_Map == null) - return; - - var p = Packet.Acquire(new MessageLocalizedAffix(Serial, Body, type, hue, 3, number, Name, affixType, - affix, args)); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) - state.Send(p); - - Packet.Release(p); - - eable.Free(); - } - - public void PrivateOverheadMessage(MessageType type, int hue, bool ascii, string text, NetState state) - { - if (state == null) - return; - - if (ascii) - state.Send(new AsciiMessage(Serial, Body, type, hue, 3, Name, text)); - else - state.Send(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); - } - - public void PrivateOverheadMessage(MessageType type, int hue, int number, NetState state) - { - PrivateOverheadMessage(type, hue, number, "", state); - } - - public void PrivateOverheadMessage(MessageType type, int hue, int number, string args, NetState state) - { - state?.Send(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); - } - - public void LocalOverheadMessage(MessageType type, int hue, bool ascii, string text) - { - var ns = m_NetState; - - if (ns == null) - return; - - if (ascii) - ns.Send(new AsciiMessage(Serial, Body, type, hue, 3, Name, text)); - else - ns.Send(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); - } - - public void LocalOverheadMessage(MessageType type, int hue, int number, string args = "") - { - m_NetState?.Send(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); - } - - public void NonlocalOverheadMessage(MessageType type, int hue, int number, string args = "") - { - if (m_Map == null) - return; - - var p = Packet.Acquire(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state != m_NetState && state.Mobile.CanSee(this)) - state.Send(p); - - Packet.Release(p); - - eable.Free(); - } - - public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, string text) - { - if (m_Map == null) - return; - - var p = ascii - ? (Packet)new AsciiMessage(Serial, Body, type, hue, 3, Name, text) - : new UnicodeMessage(Serial, Body, type, hue, 3, Language, Name, text); - - p.Acquire(); - - var eable = m_Map.GetClientsInRange(m_Location); - - foreach (var state in eable) - if (state != m_NetState && state.Mobile.CanSee(this)) - state.Send(p); - - Packet.Release(p); - - eable.Free(); - } - - public void SendLocalizedMessage(int number) - { - m_NetState?.Send(MessageLocalized.InstantiateGeneric(number)); - } - - public void SendLocalizedMessage(int number, string args, int hue = 0x3B2) - { - if (hue == 0x3B2 && string.IsNullOrEmpty(args)) - m_NetState?.Send(MessageLocalized.InstantiateGeneric(number)); - else - m_NetState?.Send(new MessageLocalized(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", args)); - } - - public void SendLocalizedMessage(int number, bool append, string affix, string args = "", int hue = 0x3B2) - { - m_NetState?.Send(new MessageLocalizedAffix(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", - (append ? AffixType.Append : AffixType.Prepend) | AffixType.System, affix, args)); - } - - public void SendMessage(string text) - { - SendMessage(0x3B2, text); - } - - public void SendMessage(string format, params object[] args) - { - SendMessage(0x3B2, string.Format(format, args)); - } - - public void SendMessage(int hue, string text) - { - m_NetState?.Send(new UnicodeMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text)); - } - - public void SendMessage(int hue, string format, params object[] args) - { - SendMessage(hue, string.Format(format, args)); - } - - public void SendAsciiMessage(string text) - { - SendAsciiMessage(0x3B2, text); - } - - public void SendAsciiMessage(string format, params object[] args) - { - SendAsciiMessage(0x3B2, string.Format(format, args)); - } - - public void SendAsciiMessage(int hue, string text) - { - m_NetState?.Send(new AsciiMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "System", text)); - } - - public void SendAsciiMessage(int hue, string format, params object[] args) - { - SendAsciiMessage(hue, string.Format(format, args)); - } - - public virtual bool InRange(Point2D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public virtual bool InRange(Point3D p, int range) => - p.m_X >= Location.m_X - range - && p.m_X <= Location.m_X + range - && p.m_Y >= Location.m_Y - range - && p.m_Y <= Location.m_Y + range; - - public virtual bool InRange(IPoint2D p, int range) => - p.X >= Location.m_X - range - && p.X <= Location.m_X + range - && p.Y >= Location.m_Y - range - && p.Y <= Location.m_Y + range; - - /// - /// Overridable. Event invoked when the Mobile is double clicked. By default, this method can either dismount or open the - /// paperdoll. - /// - /// - /// - public virtual void OnDoubleClick(Mobile from) - { - if (this == from && (!DisableDismountInWarmode || !m_Warmode)) - { - var mount = Mount; - - if (mount != null) - { - mount.Rider = null; - return; - } - } - - if (CanPaperdollBeOpenedBy(from)) - DisplayPaperdollTo(from); - } - - /// - /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who is over 18 tiles away. - /// - /// - public virtual void OnDoubleClickOutOfRange(Mobile from) - { - } - - /// - /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who can no longer see the Mobile. This may - /// happen, for example, using 'Last Object' after the Mobile has hidden. - /// - /// - public virtual void OnDoubleClickCantSee(Mobile from) - { - } - - /// - /// Overridable. Event invoked when the Mobile is double clicked by someone who is not alive. Similar to - /// , this method will show the paperdoll. It does not, however, provide any dismount - /// functionality. - /// - /// - public virtual void OnDoubleClickDead(Mobile from) - { - if (CanPaperdollBeOpenedBy(from)) - DisplayPaperdollTo(from); - } - - public Item ShieldArmor => FindItemOnLayer(Layer.TwoHanded); - - public Item NeckArmor => FindItemOnLayer(Layer.Neck); - - public Item HandArmor => FindItemOnLayer(Layer.Gloves); - - public Item HeadArmor => FindItemOnLayer(Layer.Helm); - - public Item ArmsArmor => FindItemOnLayer(Layer.Arms); - - public Item LegsArmor => FindItemOnLayer(Layer.InnerLegs) ?? FindItemOnLayer(Layer.Pants); - - public Item ChestArmor => FindItemOnLayer(Layer.InnerTorso) ?? FindItemOnLayer(Layer.Shirt); - - public Item Talisman => FindItemOnLayer(Layer.Talisman); - } -} +/*************************************************************************** + * Mobile.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Server.Accounting; +using Server.ContextMenus; +using Server.Guilds; +using Server.Gumps; +using Server.HuePickers; +using Server.Items; +using Server.Menus; +using Server.Mobiles; +using Server.Network; +using Server.Prompts; +using Server.Targeting; +using Server.Utilities; + +namespace Server +{ + public delegate void TargetCallback(Mobile from, object targeted); + + public delegate void TargetStateCallback(Mobile from, object targeted, T state); + + public delegate void PromptCallback(Mobile from, string text); + + public delegate void PromptStateCallback(Mobile from, string text, T state); + + public class TimedSkillMod : SkillMod + { + private readonly DateTime m_Expire; + + public TimedSkillMod(SkillName skill, bool relative, double value, TimeSpan delay) + : this(skill, relative, value, DateTime.UtcNow + delay) + { + } + + public TimedSkillMod(SkillName skill, bool relative, double value, DateTime expire) + : base(skill, relative, value) => + m_Expire = expire; + + public override bool CheckCondition() => DateTime.UtcNow < m_Expire; + } + + public class EquippedSkillMod : SkillMod + { + private readonly Item m_Item; + private readonly Mobile m_Mobile; + + public EquippedSkillMod(SkillName skill, bool relative, double value, Item item, Mobile mobile) + : base(skill, relative, value) + { + m_Item = item; + m_Mobile = mobile; + } + + public override bool CheckCondition() => !m_Item.Deleted && !m_Mobile.Deleted && m_Item.Parent == m_Mobile; + } + + public class DefaultSkillMod : SkillMod + { + public DefaultSkillMod(SkillName skill, bool relative, double value) + : base(skill, relative, value) + { + } + + public override bool CheckCondition() => true; + } + + public abstract class SkillMod + { + private bool m_ObeyCap; + private Mobile m_Owner; + private bool m_Relative; + private SkillName m_Skill; + private double m_Value; + + protected SkillMod(SkillName skill, bool relative, double value) + { + m_Skill = skill; + m_Relative = relative; + m_Value = value; + } + + public bool ObeyCap + { + get => m_ObeyCap; + set + { + m_ObeyCap = value; + + var sk = m_Owner?.Skills[m_Skill]; + sk?.Update(); + } + } + + public Mobile Owner + { + get => m_Owner; + set + { + if (m_Owner != value) + { + m_Owner?.RemoveSkillMod(this); + + m_Owner = value; + + if (m_Owner != value) + m_Owner.AddSkillMod(this); + } + } + } + + public SkillName Skill + { + get => m_Skill; + set + { + if (m_Skill != value) + { + var oldUpdate = m_Owner?.Skills[m_Skill]; + + m_Skill = value; + + var sk = m_Owner?.Skills[m_Skill]; + sk?.Update(); + oldUpdate?.Update(); + } + } + } + + public bool Relative + { + get => m_Relative; + set + { + if (m_Relative != value) + { + m_Relative = value; + + var sk = m_Owner?.Skills[m_Skill]; + sk?.Update(); + } + } + } + + public bool Absolute + { + get => !m_Relative; + set + { + if (m_Relative == value) + { + m_Relative = !value; + + var sk = m_Owner?.Skills[m_Skill]; + sk?.Update(); + } + } + } + + public double Value + { + get => m_Value; + set + { + if (m_Value != value) + { + m_Value = value; + + var sk = m_Owner?.Skills[m_Skill]; + sk?.Update(); + } + } + } + + public void Remove() + { + Owner = null; + } + + public abstract bool CheckCondition(); + } + + public class ResistanceMod + { + private int m_Offset; + private ResistanceType m_Type; + + public ResistanceMod(ResistanceType type, int offset) + { + m_Type = type; + m_Offset = offset; + } + + public Mobile Owner { get; set; } + + public ResistanceType Type + { + get => m_Type; + set + { + if (m_Type != value) + { + m_Type = value; + + Owner?.UpdateResistances(); + } + } + } + + public int Offset + { + get => m_Offset; + set + { + if (m_Offset != value) + { + m_Offset = value; + + Owner?.UpdateResistances(); + } + } + } + } + + public class StatMod + { + private readonly DateTime m_Added; + private readonly TimeSpan m_Duration; + + public StatMod(StatType type, string name, int offset, TimeSpan duration) + { + Type = type; + Name = name; + Offset = offset; + m_Duration = duration; + m_Added = DateTime.UtcNow; + } + + public StatType Type { get; } + + public string Name { get; } + + public int Offset { get; } + + public bool HasElapsed() + { + if (m_Duration == TimeSpan.Zero) + return false; + + return DateTime.UtcNow - m_Added >= m_Duration; + } + } + + public class DamageEntry + { + public DamageEntry(Mobile damager) => Damager = damager; + + public Mobile Damager { get; } + + public int DamageGiven { get; set; } + + public DateTime LastDamage { get; set; } + + public bool HasExpired => DateTime.UtcNow > LastDamage + ExpireDelay; + + public List Responsible { get; set; } + + public static TimeSpan ExpireDelay { get; set; } = TimeSpan.FromMinutes(2.0); + } + + [Flags] + public enum StatType + { + Str = 1, + Dex = 2, + Int = 4, + All = 7 + } + + public enum StatLockType : byte + { + Up, + Down, + Locked + } + + [CustomEnum(new[] { "North", "Right", "East", "Down", "South", "Left", "West", "Up" })] + [Flags] + public enum Direction : byte + { + North = 0x0, + Right = 0x1, + East = 0x2, + Down = 0x3, + South = 0x4, + Left = 0x5, + West = 0x6, + Up = 0x7, + + Mask = 0x7, + Running = 0x80, + ValueMask = 0x87 + } + + [Flags] + public enum MobileDelta + { + None = 0x00000000, + Name = 0x00000001, + Flags = 0x00000002, + Hits = 0x00000004, + Mana = 0x00000008, + Stam = 0x00000010, + Stat = 0x00000020, + Noto = 0x00000040, + Gold = 0x00000080, + Weight = 0x00000100, + Direction = 0x00000200, + Hue = 0x00000400, + Body = 0x00000800, + Armor = 0x00001000, + StatCap = 0x00002000, + GhostUpdate = 0x00004000, + Followers = 0x00008000, + Properties = 0x00010000, + TithingPoints = 0x00020000, + Resistances = 0x00040000, + WeaponDamage = 0x00080000, + Hair = 0x00100000, + FacialHair = 0x00200000, + Race = 0x00400000, + HealthbarYellow = 0x00800000, + HealthbarPoison = 0x01000000, + + Attributes = 0x0000001C + } + + public enum AccessLevel + { + Player, + Counselor, + GameMaster, + Seer, + Administrator, + Developer, + Owner + } + + public enum VisibleDamageType + { + None, + Related, + Everyone, + Selective + } + + public enum ResistanceType + { + Physical, + Fire, + Cold, + Poison, + Energy + } + + public enum ApplyPoisonResult + { + Poisoned, + Immune, + HigherPoisonActive, + Cured + } + + [Serializable] + public class MobileNotConnectedException : Exception + { + public MobileNotConnectedException(Mobile source, string message) + : base(message) => + Source = source.ToString(); + + public MobileNotConnectedException(Mobile source, string message, Exception innerException) + : base(message, innerException) => + Source = source.ToString(); + + protected MobileNotConnectedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + + public delegate bool SkillCheckTargetHandler( + Mobile from, SkillName skill, object target, double minSkill, + double maxSkill + ); + + public delegate bool SkillCheckLocationHandler(Mobile from, SkillName skill, double minSkill, double maxSkill); + + public delegate bool SkillCheckDirectTargetHandler(Mobile from, SkillName skill, object target, double chance); + + public delegate bool SkillCheckDirectLocationHandler(Mobile from, SkillName skill, double chance); + + public delegate TimeSpan RegenRateHandler(Mobile from); + + public delegate bool AllowBeneficialHandler(Mobile from, Mobile target); + + public delegate bool AllowHarmfulHandler(Mobile from, Mobile target); + + public delegate Container CreateCorpseHandler( + Mobile from, HairInfo hair, FacialHairInfo facialhair, + List initialContent, List equippedItems + ); + + public delegate int AOSStatusHandler(Mobile from, int index); + + /// + /// Base class representing players, npcs, and creatures. + /// + public class Mobile : IHued, IComparable, ISerializable, ISpawnable, IPropertyListObject + { + private const int + WarmodeCatchCount = 4; // Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds + + private static readonly TimeSpan WarmodeSpamCatch = TimeSpan.FromSeconds(Core.SE ? 1.0 : 0.5); + private static readonly TimeSpan WarmodeSpamDelay = TimeSpan.FromSeconds(Core.SE ? 4.0 : 2.0); + + private static readonly Packet[][] m_MovingPacketCache = + { + new Packet[8], + new Packet[8] + }; + + private static readonly List m_MoveList = new List(); + private static readonly List m_MoveClientList = new List(); + + private static readonly object m_GhostMutateContext = new object(); + + private static readonly List m_Hears = new List(); + private static readonly List m_OnSpeech = new List(); + + private static readonly string[] m_AccessLevelNames = + { + "a player", + "a counselor", + "a game master", + "a seer", + "an administrator", + "a developer", + "an owner" + }; + + private static readonly int[] m_InvalidBodies = + { + 32, + 95, + 156, + 197, + 198 + }; + + private static readonly Queue m_DeltaQueue = new Queue(); + private static readonly Queue m_DeltaQueueR = new Queue(); + + private static bool _processing; + + private static readonly string[] m_GuildTypes = + { + "", + " (Chaos)", + " (Order)" + }; + + private List _actions; + private AccessLevel m_AccessLevel; + + private Timer m_AutoManifestTimer; + + private Container m_Backpack; + + private BankBox m_BankBox; + private Body m_Body; + private Body m_BodyMod; + private bool m_CanHearGhosts; + + private int m_ChangingCombatant; + private Mobile m_Combatant; + private Timer m_CombatTimer; + private ContextMenu m_ContextMenu; + private bool m_Criminal; + + private MobileDelta m_DeltaFlags; + private Direction m_Direction; + private bool m_DisplayGuildTitle; + + private long m_EndQueue; + private Timer m_ExpireAggrTimer; + private Timer m_ExpireCombatant; + private Timer m_ExpireCriminal; + private FacialHairInfo m_FacialHair; + private int m_Fame, m_Karma; + private bool m_Female, m_Warmode, m_Hidden, m_Blessed, m_Flying; + private int m_Followers, m_FollowersMax; + private bool m_Frozen; + private FrozenTimer m_FrozenTimer; + private BaseGuild m_Guild; + private string m_GuildTitle; + + private HairInfo m_Hair; + private int m_Hits, m_Stam, m_Mana; + + private Item m_Holding; + private int m_Hue; + + private int m_HueMod = -1; + private int m_Hunger; + + private bool m_InDeltaQueue; + private int m_Kills, m_ShortTermMurders; + private string m_Language; + private int m_LightLevel; + private Point3D m_Location; + private Timer m_LogoutTimer; + private Timer m_ManaTimer, m_HitsTimer, m_StamTimer; + + private Map m_Map; + + /* Logout: + * + * When a client logs into mobile x + * - if (x is Internalized ) move x to logout location and map + * + * When a client attached to a mobile disconnects + * - LogoutTimer is started + * - Delay is taken from Region.GetLogoutDelay to allow insta-logout regions. + * - OnTick : Location and map are stored, and mobile is internalized + * + * Some things to consider: + * - An internalized person getting killed (say, by poison). Where does the body go? + * - Regions now have a GetLogoutDelay( Mobile m ); virtual function (see above) + */ + + private Item m_MountItem; + private Queue m_MoveRecords; + private string m_Name; + + private string m_NameMod; + private NetState m_NetState; + private DateTime m_NextWarmodeChange; + private bool m_Paralyzed; + private ParalyzedTimer m_ParaTimer; + private bool m_Player; + private Poison m_Poison; + private Prompt m_Prompt; + private ObjectPropertyList m_PropertyList; + + private QuestArrow m_QuestArrow; + private Race m_Race; + private Region m_Region; + + private int m_SolidHueOverride = -1; + private ISpell m_Spell; + private int m_StatCap; + private int m_Str, m_Dex, m_Int; + + private StatLockType m_StrLock, m_DexLock, m_IntLock; + private Target m_Target; + private int m_TithingPoints; + private string m_Title; + private int m_TotalGold, m_TotalItems, m_TotalWeight; + private int m_VirtualArmor; + private int m_VirtualArmorMod; + private int m_WarmodeChanges; + private WarmodeTimer m_WarmodeTimer; + private IWeapon m_Weapon; + + private bool m_YellowHealthbar; + + public Mobile(Serial serial) + { + m_Region = Map.Internal.DefaultRegion; + Serial = serial; + Aggressors = new List(); + Aggressed = new List(); + NextSkillTime = Core.TickCount; + DamageEntries = new List(); + + var ourType = GetType(); + TypeRef = World.m_MobileTypes.IndexOf(ourType); + + if (TypeRef == -1) + { + World.m_MobileTypes.Add(ourType); + TypeRef = World.m_MobileTypes.Count - 1; + } + + SaveBuffer = new BufferWriter(true); + } + + public Mobile() + { + m_Region = Map.Internal.DefaultRegion; + Serial = Serial.NewMobile; + + DefaultMobileInit(); + + World.AddMobile(this); + + var ourType = GetType(); + TypeRef = World.m_MobileTypes.IndexOf(ourType); + + if (TypeRef == -1) + { + World.m_MobileTypes.Add(ourType); + TypeRef = World.m_MobileTypes.Count - 1; + } + + SaveBuffer = new BufferWriter(true); + } + + public static bool DragEffects { get; set; } = true; + + [CommandProperty(AccessLevel.GameMaster)] + public Race Race + { + get => m_Race ?? (m_Race = Race.DefaultRace); + set + { + var oldRace = Race; + + m_Race = value ?? Race.DefaultRace; + + Body = m_Race.Body(this); + UpdateResistances(); + + Delta(MobileDelta.Race); + + OnRaceChange(oldRace); + } + } + + public virtual double RacialSkillBonus => 0; + + public int[] Resistances { get; private set; } + + public virtual int BasePhysicalResistance => 0; + public virtual int BaseFireResistance => 0; + public virtual int BaseColdResistance => 0; + public virtual int BasePoisonResistance => 0; + public virtual int BaseEnergyResistance => 0; + + [CommandProperty(AccessLevel.Counselor)] + public virtual int PhysicalResistance => GetResistance(ResistanceType.Physical); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int FireResistance => GetResistance(ResistanceType.Fire); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int ColdResistance => GetResistance(ResistanceType.Cold); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int PoisonResistance => GetResistance(ResistanceType.Poison); + + [CommandProperty(AccessLevel.Counselor)] + public virtual int EnergyResistance => GetResistance(ResistanceType.Energy); + + public List ResistanceMods { get; set; } + + public static int MaxPlayerResistance { get; set; } = 70; + + public virtual bool NewGuildDisplay => false; + + public List Stabled { get; private set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public VirtueInfo Virtues { get; private set; } + + public object Party { get; set; } + + public List SkillMods { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int VirtualArmorMod + { + get => m_VirtualArmorMod; + set + { + if (m_VirtualArmorMod != value) + { + m_VirtualArmorMod = value; + + Delta(MobileDelta.Armor); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int MeleeDamageAbsorb { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int MagicDamageAbsorb { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int SkillsTotal => Skills?.Total ?? 0; + + [CommandProperty(AccessLevel.GameMaster)] + public int SkillsCap + { + get => Skills?.Cap ?? 0; + set + { + if (Skills != null) + Skills.Cap = value; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int BaseSoundID { get; set; } + + public long NextCombatTime { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int NameHue { get; set; } = -1; + + [CommandProperty(AccessLevel.GameMaster)] + public int Hunger + { + get => m_Hunger; + set + { + var oldValue = m_Hunger; + + if (oldValue != value) + { + m_Hunger = value; + + EventSink.InvokeHungerChanged(this, oldValue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Thirst { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int BAC { get; set; } + + /// + /// Gets or sets the number of steps this player may take when hidden before being revealed. + /// + [CommandProperty(AccessLevel.GameMaster)] + public int AllowedStealthSteps { get; set; } + + public Item Holding + { + get => m_Holding; + set + { + if (m_Holding != value) + { + if (m_Holding != null) + { + UpdateTotal(m_Holding, TotalType.Weight, -(m_Holding.TotalWeight + m_Holding.PileWeight)); + + if (m_Holding.HeldBy == this) + m_Holding.HeldBy = null; + } + + if (value != null && m_Holding != null) + DropHolding(); + + m_Holding = value; + + if (m_Holding != null) + { + UpdateTotal(m_Holding, TotalType.Weight, m_Holding.TotalWeight + m_Holding.PileWeight); + + m_Holding.HeldBy ??= this; + } + } + } + } + + public long LastMoveTime { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual bool Paralyzed + { + get => m_Paralyzed; + set + { + if (m_Paralyzed != value) + { + m_Paralyzed = value; + Delta(MobileDelta.Flags); + + SendLocalizedMessage(m_Paralyzed ? 502381 : 502382); + + if (m_ParaTimer != null) + { + m_ParaTimer.Stop(); + m_ParaTimer = null; + } + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool DisarmReady { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool StunReady { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Frozen + { + get => m_Frozen; + set + { + if (m_Frozen != value) + { + m_Frozen = value; + Delta(MobileDelta.Flags); + + if (m_FrozenTimer != null) + { + m_FrozenTimer.Stop(); + m_FrozenTimer = null; + } + } + } + } + + /// + /// Gets or sets the lock state for the property. + /// + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public StatLockType StrLock + { + get => m_StrLock; + set + { + if (m_StrLock != value) + { + m_StrLock = value; + + m_NetState?.Send(new StatLockInfo(this)); + } + } + } + + /// + /// Gets or sets the lock state for the property. + /// + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public StatLockType DexLock + { + get => m_DexLock; + set + { + if (m_DexLock != value) + { + m_DexLock = value; + + m_NetState?.Send(new StatLockInfo(this)); + } + } + } + + /// + /// Gets or sets the lock state for the property. + /// + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public StatLockType IntLock + { + get => m_IntLock; + set + { + if (m_IntLock != value) + { + m_IntLock = value; + + m_NetState?.Send(new StatLockInfo(this)); + } + } + } + + public long NextActionTime { get; set; } + + public long NextActionMessage { get; set; } + + public static int ActionMessageDelay { get; set; } = 125; + + public static bool GlobalRegenThroughPoison { get; set; } = true; + + public virtual bool RegenThroughPoison => GlobalRegenThroughPoison; + + public virtual bool CanRegenHits => Alive && (RegenThroughPoison || !Poisoned); + public virtual bool CanRegenStam => Alive; + public virtual bool CanRegenMana => Alive; + + public long NextSkillTime { get; set; } + + public List Aggressors { get; private set; } + + public List Aggressed { get; private set; } + + public bool ChangingCombatant => m_ChangingCombatant > 0; + + /// + /// Overridable. Gets or sets which Mobile that this Mobile is currently engaged in combat with. + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual Mobile Combatant + { + get => m_Combatant; + set + { + if (Deleted) + return; + + if (m_Combatant != value && value != this) + { + var old = m_Combatant; + + ++m_ChangingCombatant; + m_Combatant = value; + + if (m_Combatant != null && !CanBeHarmful(m_Combatant, false) || + !Region.OnCombatantChange(this, old, m_Combatant)) + { + m_Combatant = old; + --m_ChangingCombatant; + return; + } + + if (m_Combatant == null) + { + m_NetState?.Send(new ChangeCombatant(Serial.Zero)); + m_ExpireCombatant?.Stop(); + m_CombatTimer?.Stop(); + + m_ExpireCombatant = null; + m_CombatTimer = null; + } + else + { + m_NetState?.Send(new ChangeCombatant(m_Combatant.Serial)); + m_ExpireCombatant ??= new ExpireCombatantTimer(this); + m_ExpireCombatant.Start(); + + m_CombatTimer ??= new CombatTimer(this); + m_CombatTimer.Start(); + + if (CanBeHarmful(m_Combatant, false)) + { + DoHarmful(m_Combatant); + m_Combatant.PlaySound(m_Combatant.GetAngerSound()); + } + } + + OnCombatantChange(); + --m_ChangingCombatant; + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalGold => GetTotal(TotalType.Gold); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalItems => GetTotal(TotalType.Items); + + [CommandProperty(AccessLevel.GameMaster)] + public int TotalWeight => GetTotal(TotalType.Weight); + + [CommandProperty(AccessLevel.GameMaster)] + public int TithingPoints + { + get => m_TithingPoints; + set + { + if (m_TithingPoints != value) + { + m_TithingPoints = value; + + Delta(MobileDelta.TithingPoints); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Followers + { + get => m_Followers; + set + { + if (m_Followers != value) + { + m_Followers = value; + + Delta(MobileDelta.Followers); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int FollowersMax + { + get => m_FollowersMax; + set + { + if (m_FollowersMax != value) + { + m_FollowersMax = value; + + Delta(MobileDelta.Followers); + } + } + } + + public bool TargetLocked { get; set; } + + public Target Target + { + get => m_Target; + set + { + var oldTarget = m_Target; + var newTarget = value; + + if (oldTarget == newTarget) + return; + + m_Target = null; + + if (oldTarget != null && newTarget != null) + oldTarget.Cancel(this, TargetCancelType.Overridden); + + m_Target = newTarget; + + if (newTarget != null && m_NetState != null && !TargetLocked) + m_NetState.Send(newTarget.GetPacketFor(m_NetState)); + + OnTargetChange(); + } + } + + public ContextMenu ContextMenu + { + get => m_ContextMenu; + set + { + m_ContextMenu = value; + + if (m_ContextMenu != null && m_NetState != null) + { + // Old packet is preferred until assistants catch up + if (m_NetState.NewHaven && m_ContextMenu.RequiresNewPacket) + Send(new DisplayContextMenu(m_ContextMenu)); + else + Send(new DisplayContextMenuOld(m_ContextMenu)); + } + } + } + + public bool Pushing { get; set; } + + public static int WalkFoot { get; set; } = 400; + + public static int RunFoot { get; set; } = 200; + + public static int WalkMount { get; set; } = 200; + + public static int RunMount { get; set; } = 100; + + public static AccessLevel FwdAccessOverride { get; set; } = AccessLevel.Counselor; + + public static bool FwdEnabled { get; set; } = true; + + public static bool FwdUOTDOverride { get; set; } + + public static int FwdMaxSteps { get; set; } = 4; + + public virtual bool IsDeadBondedPet => false; + + public ISpell Spell + { + get => m_Spell; + set + { + if (m_Spell != null && value != null) + Console.WriteLine("Warning: Spell has been overwritten"); + + m_Spell = value; + } + } + + [CommandProperty(AccessLevel.Administrator)] + public bool AutoPageNotify { get; set; } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] + public IAccount Account { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int VirtualArmor + { + get => m_VirtualArmor; + set + { + if (m_VirtualArmor != value) + { + m_VirtualArmor = value; + + Delta(MobileDelta.Armor); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual double ArmorRating => 0.0; + + /// + /// Overridable. Returns true if the player is alive, false if otherwise. By default, this is computed by: + /// !Deleted && (!Player || !Body.IsGhost) + /// + [CommandProperty(AccessLevel.Counselor)] + public virtual bool Alive => !Deleted && (!m_Player || !m_Body.IsGhost); + + public static CreateCorpseHandler CreateCorpseHandler { get; set; } + + public virtual bool RetainPackLocsOnDeath => Core.AOS; + + [CommandProperty(AccessLevel.GameMaster)] + public Container Corpse { get; set; } + + public static char[] GhostChars { get; set; } = { 'o', 'O' }; + + public static bool NoSpeechLOS { get; set; } + + public static TimeSpan AutoManifestTimeout { get; set; } = TimeSpan.FromSeconds(5.0); + + public static bool InsuranceEnabled { get; set; } + + public static int ActionDelay { get; set; } = 500; + + public static VisibleDamageType VisibleDamageType { get; set; } + + public List DamageEntries { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public Mobile LastKiller { get; set; } + + public static bool DefaultShowVisibleDamage { get; set; } + + public static bool DefaultCanSeeVisibleDamage { get; set; } + + public virtual bool ShowVisibleDamage => DefaultShowVisibleDamage; + public virtual bool CanSeeVisibleDamage => DefaultCanSeeVisibleDamage; + + [CommandProperty(AccessLevel.GameMaster)] + public bool Squelched { get; set; } + + public virtual bool ShouldCheckStatTimers => true; + + [CommandProperty(AccessLevel.GameMaster)] + public DateTime CreationTime { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int LightLevel + { + get => m_LightLevel; + set + { + if (m_LightLevel != value) + { + m_LightLevel = value; + + CheckLightLevels(false); + + /*if (m_NetState != null) + m_NetState.Send( new PersonalLightLevel( this ) );*/ + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public string Profile { get; set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public bool ProfileLocked { get; set; } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)] + public bool Player + { + get => m_Player; + set + { + m_Player = value; + InvalidateProperties(); + + if (!m_Player && m_Dex <= 100 && m_CombatTimer != null) + m_CombatTimer.Priority = TimerPriority.FiftyMS; + else if (m_CombatTimer != null) + m_CombatTimer.Priority = TimerPriority.EveryTick; + + CheckStatTimers(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public string Title + { + get => m_Title; + set + { + m_Title = value; + InvalidateProperties(); + } + } + + public List Items { get; private set; } + + public virtual int MaxWeight => int.MaxValue; + + public static IWeapon DefaultWeapon { get; set; } + + [CommandProperty(AccessLevel.Counselor)] + public Skills Skills { get; private set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.Administrator)] + public AccessLevel AccessLevel + { + get => m_AccessLevel; + set + { + var oldValue = m_AccessLevel; + + if (oldValue != value) + { + m_AccessLevel = value; + Delta(MobileDelta.Noto); + InvalidateProperties(); + + SendMessage("Your access level has been changed. You are now {0}.", GetAccessLevelName(value)); + + ClearScreen(); + SendEverything(); + + OnAccessLevelChanged(oldValue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Fame + { + get => m_Fame; + set + { + var oldValue = m_Fame; + + if (oldValue != value) + { + m_Fame = value; + + if (ShowFameTitle && (m_Player || m_Body.IsHuman) && oldValue >= 10000 != value >= 10000) + InvalidateProperties(); + + OnFameChange(oldValue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int Karma + { + get => m_Karma; + set + { + var old = m_Karma; + + if (old != value) + { + m_Karma = value; + OnKarmaChange(old); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Blessed + { + get => m_Blessed; + set + { + if (m_Blessed != value) + { + m_Blessed = value; + Delta(MobileDelta.HealthbarYellow); + } + } + } + + public virtual int Luck => 0; + + [Hue] + [CommandProperty(AccessLevel.GameMaster)] + public int HueMod + { + get => m_HueMod; + set + { + if (m_HueMod != value) + { + m_HueMod = value; + + Delta(MobileDelta.Hue); + } + } + } + + [Hue] + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Hue + { + get + { + if (m_HueMod != -1) + return m_HueMod; + + return m_Hue; + } + set + { + var oldHue = m_Hue; + + if (oldHue != value) + { + m_Hue = value; + + Delta(MobileDelta.Hue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Direction Direction + { + get => m_Direction; + set + { + if (m_Direction != value) + { + m_Direction = value; + + Delta(MobileDelta.Direction); + // ProcessDelta(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Female + { + get => m_Female; + set + { + if (m_Female != value) + { + m_Female = value; + Delta(MobileDelta.Flags); + OnGenderChanged(!m_Female); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Flying + { + get => m_Flying; + set + { + if (m_Flying != value) + { + m_Flying = value; + Delta(MobileDelta.Flags); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Warmode + { + get => m_Warmode; + set + { + if (Deleted) + return; + + if (m_Warmode != value) + { + if (m_AutoManifestTimer != null) + { + m_AutoManifestTimer.Stop(); + m_AutoManifestTimer = null; + } + + m_Warmode = value; + Delta(MobileDelta.Flags); + + if (m_NetState != null) + Send(SetWarMode.Instantiate(value)); + + if (!m_Warmode) + Combatant = null; + + if (!Alive) + { + if (value) + Delta(MobileDelta.GhostUpdate); + else + SendRemovePacket(false); + } + + OnWarmodeChanged(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Hidden + { + get => m_Hidden; + set + { + if (m_Hidden != value) + { + m_Hidden = value; + // Delta( MobileDelta.Flags ); + + OnHiddenChanged(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster, AccessLevel.Owner)] + public NetState NetState + { + get => m_NetState?.Connection != null && !m_NetState.IsDisposing ? m_NetState : null; + set + { + if (m_NetState != value) + { + m_Map?.OnClientChange(m_NetState, value, this); + + m_Target?.Cancel(this, TargetCancelType.Disconnected); + + QuestArrow = null; + + m_Spell?.OnConnectionChanged(); + + // if (m_Spell != null) + // m_Spell.FinishSequence(); + + m_NetState?.CancelAllTrades(); + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + box.Close(); + + // REMOVED: + // m_Actions.Clear(); + + m_NetState = value; + + if (m_NetState == null) + { + OnDisconnected(); + EventSink.InvokeDisconnected(this); + + // Disconnected, start the logout timer + + if (m_LogoutTimer == null) + m_LogoutTimer = new LogoutTimer(this); + else + m_LogoutTimer.Stop(); + + m_LogoutTimer.Delay = GetLogoutDelay(); + m_LogoutTimer.Start(); + } + else + { + OnConnected(); + EventSink.InvokeConnected(this); + + // Connected, stop the logout timer and if needed, move to the world + + m_LogoutTimer?.Stop(); + + m_LogoutTimer = null; + + if (m_Map == Map.Internal && LogoutMap != null) + { + Map = LogoutMap; + Location = LogoutLocation; + } + } + + for (var i = Items.Count - 1; i >= 0; --i) + { + if (i >= Items.Count) + continue; + + var item = Items[i]; + + if (item is SecureTradeContainer) + { + for (var j = item.Items.Count - 1; j >= 0; --j) + if (j < item.Items.Count) + { + item.Items[j].OnSecureTrade(this, this, this, false); + AddToBackpack(item.Items[j]); + } + + Timer.DelayCall(item.Delete); + } + } + + DropHolding(); + OnNetStateChanged(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public string Language + { + get => m_Language; + set => m_Language = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int SpeechHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int EmoteHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int WhisperHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public int YellHue { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public string GuildTitle + { + get => m_GuildTitle; + set + { + var old = m_GuildTitle; + + if (old != value) + { + m_GuildTitle = value; + + if (m_Guild?.Disbanded == false && m_GuildTitle != null) + SendLocalizedMessage(1018026, true, m_GuildTitle); // Your guild title has changed : + + InvalidateProperties(); + + OnGuildTitleChange(old); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool DisplayGuildTitle + { + get => m_DisplayGuildTitle; + set + { + m_DisplayGuildTitle = value; + InvalidateProperties(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Mobile GuildFealty { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public string NameMod + { + get => m_NameMod; + set + { + if (m_NameMod != value) + { + m_NameMod = value; + Delta(MobileDelta.Name); + InvalidateProperties(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool YellowHealthbar + { + get => m_YellowHealthbar; + set + { + m_YellowHealthbar = value; + Delta(MobileDelta.HealthbarYellow); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public string RawName + { + get => m_Name; + set => Name = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual string Name + { + get => m_NameMod ?? m_Name; + set + { + if (m_Name != value) // I'm leaving out the && m_NameMod == null + { + var oldName = m_Name; + m_Name = value; + OnAfterNameChange(oldName, m_Name); + Delta(MobileDelta.Name); + InvalidateProperties(); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public DateTime LastStrGain { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public DateTime LastIntGain { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public DateTime LastDexGain { get; set; } + + public DateTime LastStatGain + { + get + { + var d = LastStrGain; + + if (LastIntGain > d) + d = LastIntGain; + + if (LastDexGain > d) + d = LastDexGain; + + return d; + } + set + { + LastStrGain = value; + LastIntGain = value; + LastDexGain = value; + } + } + + public BaseGuild Guild + { + get => m_Guild; + set + { + var old = m_Guild; + + if (old != value) + { + if (value == null) + GuildTitle = null; + + m_Guild = value; + + Delta(MobileDelta.Noto); + InvalidateProperties(); + + OnGuildChange(old); + } + } + } + + public Region WalkRegion { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Poisoned => m_Poison != null; + + [CommandProperty(AccessLevel.GameMaster)] + public bool IsBodyMod => m_BodyMod.BodyID != 0; + + [CommandProperty(AccessLevel.GameMaster)] + public Body BodyMod + { + get => m_BodyMod; + set + { + if (m_BodyMod != value) + { + m_BodyMod = value; + + Delta(MobileDelta.Body); + InvalidateProperties(); + + CheckStatTimers(); + } + } + } + + [Body] + [CommandProperty(AccessLevel.GameMaster)] + public Body Body + { + get + { + if (IsBodyMod) + return m_BodyMod; + + return m_Body; + } + set + { + if (m_Body != value && !IsBodyMod) + { + m_Body = SafeBody(value); + + Delta(MobileDelta.Body); + InvalidateProperties(); + + CheckStatTimers(); + } + } + } + + [Body] + [CommandProperty(AccessLevel.GameMaster)] + public int BodyValue + { + get => Body.BodyID; + set => Body = value; + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Point3D LogoutLocation { get; set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Map LogoutMap { get; set; } + + public Region Region => m_Region ?? (Map == null ? Map.Internal.DefaultRegion : Map.DefaultRegion); + + public Packet RemovePacket => StaticPacketHandlers.GetRemoveEntityPacket(this); + + [CommandProperty(AccessLevel.GameMaster)] + public int SolidHueOverride + { + get => m_SolidHueOverride; + set + { + if (m_SolidHueOverride == value) return; + m_SolidHueOverride = value; + Delta(MobileDelta.Hue | MobileDelta.Body); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public virtual IWeapon Weapon + { + get + { + if (m_Weapon is Item item && !item.Deleted && item.Parent == this && CanSee(item)) + return m_Weapon; + + m_Weapon = null; + + item = FindItemOnLayer(Layer.OneHanded) ?? FindItemOnLayer(Layer.TwoHanded); + + if (item is IWeapon weapon) + return m_Weapon = weapon; + + return GetDefaultWeapon(); + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public BankBox BankBox + { + get + { + if (m_BankBox?.Deleted == false && m_BankBox.Parent == this) + return m_BankBox; + + m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; + + if (m_BankBox == null) + AddItem(m_BankBox = new BankBox(this)); + + return m_BankBox; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public Container Backpack + { + get + { + if (m_Backpack?.Deleted != false || m_Backpack.Parent != this) + m_Backpack = FindItemOnLayer(Layer.Backpack) as Container; + + return m_Backpack; + } + } + + public virtual bool KeepsItemsOnDeath => m_AccessLevel > AccessLevel.Player; + + public bool HasTrade => m_NetState?.Trades.Count > 0; + + public bool NoMoveHS { get; set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Kills + { + get => m_Kills; + set + { + var oldValue = m_Kills; + + if (m_Kills != value) + { + m_Kills = Math.Max(value, 0); + + if (oldValue >= 5 != m_Kills >= 5) + { + Delta(MobileDelta.Noto); + InvalidateProperties(); + } + + OnKillsChange(oldValue); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int ShortTermMurders + { + get => m_ShortTermMurders; + set + { + if (m_ShortTermMurders != value) + m_ShortTermMurders = Math.Max(value, 0); + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public bool Criminal + { + get => m_Criminal; + set + { + if (m_Criminal != value) + { + m_Criminal = value; + Delta(MobileDelta.Noto); + InvalidateProperties(); + } + + if (m_Criminal) + { + if (m_ExpireCriminal == null) + m_ExpireCriminal = new ExpireCriminalTimer(this); + else + m_ExpireCriminal.Stop(); + + m_ExpireCriminal.Start(); + } + else if (m_ExpireCriminal != null) + { + m_ExpireCriminal.Stop(); + m_ExpireCriminal = null; + } + } + } + + public static bool DisableDismountInWarmode { get; set; } + + public static int BodyWeight { get; set; } = 14; + + [CommandProperty(AccessLevel.GameMaster)] + public IMount Mount + { + get + { + Item item = null; + + if (m_MountItem?.Deleted == false && m_MountItem.Parent == this) + item = m_MountItem; + + item ??= FindItemOnLayer(Layer.Mount); + + if (!(item is IMountItem mountItem)) + return null; + + m_MountItem = item; + return mountItem.Mount; + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Mounted => Mount != null; + + public QuestArrow QuestArrow + { + get => m_QuestArrow; + set + { + if (m_QuestArrow != value) + { + m_QuestArrow?.Stop(); + + m_QuestArrow = value; + } + } + } + + public virtual bool CanTarget => true; + public virtual bool ClickTitle => true; + + public virtual bool PropertyTitle => OldPropertyTitles ? ClickTitle : true; + + public static bool DisableHiddenSelfClick { get; set; } = true; + + public static bool AsciiClickMessage { get; set; } = true; + + public static bool GuildClickMessage { get; set; } = true; + + public static bool OldPropertyTitles { get; set; } + + public virtual bool ShowFameTitle // (m_Player || m_Body.IsHuman) && m_Fame >= 10000; } + => true; + + /// + /// Gets or sets the maximum attainable value for , , and . + /// + [CommandProperty(AccessLevel.GameMaster)] + public int StatCap + { + get => m_StatCap; + set + { + if (m_StatCap != value) + { + m_StatCap = value; + + Delta(MobileDelta.StatCap); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public bool Meditating { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool CanSwim { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool CantWalk { get; set; } + + [CommandProperty(AccessLevel.GameMaster)] + public bool CanHearGhosts + { + get => m_CanHearGhosts || AccessLevel >= AccessLevel.Counselor; + set => m_CanHearGhosts = value; + } + + [CommandProperty(AccessLevel.GameMaster)] + public int RawStatTotal => RawStr + RawDex + RawInt; + + public long NextSpellTime { get; set; } + + public static AllowBeneficialHandler AllowBeneficialHandler { get; set; } + + public static AllowHarmfulHandler AllowHarmfulHandler { get; set; } + + public static SkillCheckTargetHandler SkillCheckTargetHandler { get; set; } + + public static SkillCheckLocationHandler SkillCheckLocationHandler { get; set; } + + public static SkillCheckDirectTargetHandler SkillCheckDirectTargetHandler { get; set; } + + public static SkillCheckDirectLocationHandler SkillCheckDirectLocationHandler { get; set; } + + public static AOSStatusHandler AOSStatusHandler { get; set; } + + public static RegenRateHandler HitsRegenRateHandler { get; set; } + + public static TimeSpan DefaultHitsRate { get; set; } + + public static RegenRateHandler StamRegenRateHandler { get; set; } + + public static TimeSpan DefaultStamRate { get; set; } + + public static RegenRateHandler ManaRegenRateHandler { get; set; } + + public static TimeSpan DefaultManaRate { get; set; } + + public static TimeSpan ExpireCriminalDelay { get; set; } = TimeSpan.FromMinutes(2.0); + + public Prompt Prompt + { + get => m_Prompt; + set + { + var oldPrompt = m_Prompt; + var newPrompt = value; + + if (oldPrompt == newPrompt) + return; + + m_Prompt = null; + + if (newPrompt != null) + oldPrompt?.OnCancel(this); + + m_Prompt = newPrompt; + + if (newPrompt != null) + Send(new UnicodePrompt(newPrompt)); + } + } + + /// + /// Gets a list of all StatMod's currently active for the Mobile. + /// + public List StatMods { get; private set; } + + /// + /// Gets or sets the base, unmodified, strength of the Mobile. Ranges from 1 to 65000, inclusive. + /// + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public int RawStr + { + get => m_Str; + set + { + value = Math.Clamp(value, 1, 65000); + + if (m_Str != value) + { + var oldValue = m_Str; + + m_Str = value; + Delta(MobileDelta.Stat | MobileDelta.Hits); + + if (Hits < HitsMax) + { + m_HitsTimer ??= new HitsTimer(this); + + m_HitsTimer.Start(); + } + else if (Hits > HitsMax) + { + Hits = HitsMax; + } + + OnRawStrChange(oldValue); + OnRawStatChange(StatType.Str, oldValue); + } + } + } + + /// + /// Gets or sets the effective strength of the Mobile. This is the sum of the plus any additional + /// modifiers. Any attempts to set this value when under the influence of a will result in no change. + /// It ranges from 1 to 65000, inclusive. + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Str + { + get => Math.Clamp(m_Str + GetStatOffset(StatType.Str), 1, 65000); + set + { + if (StatMods.Count == 0) + RawStr = value; + } + } + + /// + /// Gets or sets the base, unmodified, dexterity of the Mobile. Ranges from 1 to 65000, inclusive. + /// + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public int RawDex + { + get => m_Dex; + set + { + value = Math.Clamp(value, 1, 65000); + + if (m_Dex != value) + { + var oldValue = m_Dex; + + m_Dex = value; + Delta(MobileDelta.Stat | MobileDelta.Stam); + + if (Stam < StamMax) + { + m_StamTimer ??= new StamTimer(this); + + m_StamTimer.Start(); + } + else if (Stam > StamMax) + { + Stam = StamMax; + } + + OnRawDexChange(oldValue); + OnRawStatChange(StatType.Dex, oldValue); + } + } + } + + /// + /// Gets or sets the effective dexterity of the Mobile. This is the sum of the plus any additional + /// modifiers. Any attempts to set this value when under the influence of a will result in no change. + /// It ranges from 1 to 65000, inclusive. + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Dex + { + get => Math.Clamp(m_Dex + GetStatOffset(StatType.Dex), 0, 65000); + set + { + if (StatMods.Count == 0) + RawDex = value; + } + } + + /// + /// Gets or sets the base, unmodified, intelligence of the Mobile. Ranges from 1 to 65000, inclusive. + /// + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public int RawInt + { + get => m_Int; + set + { + value = Math.Clamp(value, 1, 65000); + + if (m_Int != value) + { + var oldValue = m_Int; + + m_Int = value; + Delta(MobileDelta.Stat | MobileDelta.Mana); + + if (Mana < ManaMax) + { + m_ManaTimer ??= new ManaTimer(this); + + m_ManaTimer.Start(); + } + else if (Mana > ManaMax) + { + Mana = ManaMax; + } + + OnRawIntChange(oldValue); + OnRawStatChange(StatType.Int, oldValue); + } + } + } + + /// + /// Gets or sets the effective intelligence of the Mobile. This is the sum of the plus any additional + /// modifiers. Any attempts to set this value when under the influence of a will result in no change. + /// It ranges from 1 to 65000, inclusive. + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int Int + { + get => Math.Clamp(m_Int + GetStatOffset(StatType.Int), 0, 65000); + set + { + if (StatMods.Count == 0) + RawInt = value; + } + } + + /// + /// Gets or sets the current hit point of the Mobile. This value ranges from 0 to , inclusive. When + /// set + /// to the value of , the CanReportMurder flag of all + /// aggressors is reset to false, and the list of damage entries is cleared. + /// + [CommandProperty(AccessLevel.GameMaster)] + public int Hits + { + get => m_Hits; + set + { + if (Deleted) + return; + + value = Math.Clamp(value, 0, HitsMax); + + if (value == HitsMax) + { + m_HitsTimer?.Stop(); + + for (var i = 0; i < Aggressors.Count; i++) // reset reports on full HP + Aggressors[i].CanReportMurder = false; + + if (DamageEntries.Count > 0) + DamageEntries.Clear(); // reset damage entries on full HP + } + else + { + if (CanRegenHits) + { + m_HitsTimer ??= new HitsTimer(this); + + m_HitsTimer.Start(); + } + else + { + m_HitsTimer?.Stop(); + } + } + + if (m_Hits != value) + { + var oldValue = m_Hits; + m_Hits = value; + Delta(MobileDelta.Hits); + OnHitsChange(oldValue); + } + } + } + + /// + /// Overridable. Gets the maximum hit point of the Mobile. By default, this returns: 50 + ( / 2) + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int HitsMax => 50 + Str / 2; + + /// + /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. + /// + [CommandProperty(AccessLevel.GameMaster)] + public int Stam + { + get => m_Stam; + set + { + if (Deleted) + return; + + value = Math.Clamp(value, 0, StamMax); + + if (value == StamMax) + { + m_StamTimer?.Stop(); + } + else + { + if (CanRegenStam) + { + m_StamTimer ??= new StamTimer(this); + + m_StamTimer.Start(); + } + else + { + m_StamTimer?.Stop(); + } + } + + if (m_Stam != value) + { + var oldValue = m_Stam; + m_Stam = value; + Delta(MobileDelta.Stam); + OnStamChange(oldValue); + } + } + } + + /// + /// Overridable. Gets the maximum stamina of the Mobile. By default, this returns: + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int StamMax => Dex; + + /// + /// Gets or sets the current stamina of the Mobile. This value ranges from 0 to , inclusive. + /// + [CommandProperty(AccessLevel.GameMaster)] + public int Mana + { + get => m_Mana; + set + { + if (Deleted) + return; + + value = Math.Clamp(value, 0, ManaMax); + + if (value == ManaMax) + { + m_ManaTimer?.Stop(); + + if (Meditating) + { + Meditating = false; + SendLocalizedMessage(501846); // You are at peace. + } + } + else + { + if (CanRegenMana) + { + m_ManaTimer ??= new ManaTimer(this); + + m_ManaTimer.Start(); + } + else + { + m_ManaTimer?.Stop(); + } + } + + if (m_Mana != value) + { + var oldValue = m_Mana; + m_Mana = value; + Delta(MobileDelta.Mana); + OnManaChange(oldValue); + } + } + } + + /// + /// Overridable. Gets the maximum mana of the Mobile. By default, this returns: + /// + /// + /// + /// + [CommandProperty(AccessLevel.GameMaster)] + public virtual int ManaMax => Int; + + public Timer PoisonTimer { get; private set; } + + [CommandProperty(AccessLevel.GameMaster)] + public Poison Poison + { + get => m_Poison; + set + { + /*if (m_Poison != value && (m_Poison == null || value == null || m_Poison.Level < value.Level)) + {*/ + m_Poison = value; + Delta(MobileDelta.HealthbarPoison); + + if (PoisonTimer != null) + { + PoisonTimer.Stop(); + PoisonTimer = null; + } + + if (m_Poison != null) + { + PoisonTimer = m_Poison.ConstructTimer(this); + + PoisonTimer?.Start(); + } + + CheckStatTimers(); + /*}*/ + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int HairItemID + { + get => m_Hair?.ItemID ?? 0; + set + { + if (m_Hair == null && value > 0) + m_Hair = new HairInfo(value); + else if (value <= 0) + m_Hair = null; + else if (m_Hair != null) + m_Hair.ItemID = value; + + Delta(MobileDelta.Hair); + } + } + + // [CommandProperty( AccessLevel.GameMaster )] + // public int HairSerial { get { return HairInfo.FakeSerial( this ); } } + + [CommandProperty(AccessLevel.GameMaster)] + public int FacialHairItemID + { + get => m_FacialHair?.ItemID ?? 0; + set + { + if (m_FacialHair == null && value > 0) + m_FacialHair = new FacialHairInfo(value); + else if (value <= 0) + m_FacialHair = null; + else if (m_FacialHair != null) + m_FacialHair.ItemID = value; + + Delta(MobileDelta.FacialHair); + } + } + + // [CommandProperty( AccessLevel.GameMaster )] + // public int FacialHairSerial { get { return FacialHairInfo.FakeSerial( this ); } } + + [CommandProperty(AccessLevel.GameMaster)] + public int HairHue + { + get => m_Hair?.Hue ?? 0; + set + { + if (m_Hair != null) + { + m_Hair.Hue = value; + Delta(MobileDelta.Hair); + } + } + } + + [CommandProperty(AccessLevel.GameMaster)] + public int FacialHairHue + { + get => m_FacialHair?.Hue ?? 0; + set + { + if (m_FacialHair != null) + { + m_FacialHair.Hue = value; + Delta(MobileDelta.FacialHair); + } + } + } + + public Item ShieldArmor => FindItemOnLayer(Layer.TwoHanded); + + public Item NeckArmor => FindItemOnLayer(Layer.Neck); + + public Item HandArmor => FindItemOnLayer(Layer.Gloves); + + public Item HeadArmor => FindItemOnLayer(Layer.Helm); + + public Item ArmsArmor => FindItemOnLayer(Layer.Arms); + + public Item LegsArmor => FindItemOnLayer(Layer.InnerLegs) ?? FindItemOnLayer(Layer.Pants); + + public Item ChestArmor => FindItemOnLayer(Layer.InnerTorso) ?? FindItemOnLayer(Layer.Shirt); + + public Item Talisman => FindItemOnLayer(Layer.Talisman); + + public int CompareTo(Mobile other) => other == null ? -1 : Serial.CompareTo(other.Serial); + + public virtual int HuedItemID => m_Female ? 0x2107 : 0x2106; + public OPLInfo OPLPacket => StaticPacketHandlers.GetOPLInfoPacket(this); + public ObjectPropertyList PropertyList => m_PropertyList ??= NewObjectPropertyList(); + + public virtual void GetProperties(ObjectPropertyList list) + { + AddNameProperties(list); + } + + public BufferWriter SaveBuffer { get; } + + [CommandProperty(AccessLevel.Counselor)] + public Serial Serial { get; } + + public int TypeRef { get; } + + public void Serialize() + { + SaveBuffer.Flush(); + Serialize(SaveBuffer); + } + + public virtual void Serialize(IGenericWriter writer) + { + writer.Write(32); // version + + writer.WriteDeltaTime(LastStrGain); + writer.WriteDeltaTime(LastIntGain); + writer.WriteDeltaTime(LastDexGain); + + byte hairflag = 0x00; + + if (m_Hair != null) + hairflag |= 0x01; + if (m_FacialHair != null) + hairflag |= 0x02; + + writer.Write(hairflag); + + if ((hairflag & 0x01) != 0) + m_Hair?.Serialize(writer); + if ((hairflag & 0x02) != 0) + m_FacialHair?.Serialize(writer); + + writer.Write(Race); + + writer.Write(m_TithingPoints); + + writer.Write(Corpse); + + writer.Write(CreationTime); + + writer.Write(Stabled, true); + + writer.Write(CantWalk); + + VirtueInfo.Serialize(writer, Virtues); + + writer.Write(Thirst); + writer.Write(BAC); + + writer.Write(m_ShortTermMurders); + // writer.Write( m_ShortTermElapse ); + // writer.Write( m_LongTermElapse ); + + // writer.Write( m_Followers ); + writer.Write(m_FollowersMax); + + writer.Write(MagicDamageAbsorb); + + writer.Write(GuildFealty); + + writer.Write(m_Guild); + + writer.Write(m_DisplayGuildTitle); + + writer.Write(CanSwim); + + writer.Write(Squelched); + + writer.Write(m_Holding); + + writer.Write(m_VirtualArmor); + + writer.Write(BaseSoundID); + + writer.Write(DisarmReady); + writer.Write(StunReady); + + // Poison.Serialize( m_Poison, writer ); + + writer.Write(m_StatCap); + + writer.Write(NameHue); + + writer.Write(m_Hunger); + + writer.Write(m_Location); + writer.Write(m_Body); + writer.Write(m_Name); + writer.Write(m_GuildTitle); + writer.Write(m_Criminal); + writer.Write(m_Kills); + writer.Write(SpeechHue); + writer.Write(EmoteHue); + writer.Write(WhisperHue); + writer.Write(YellHue); + writer.Write(m_Language); + writer.Write(m_Female); + writer.Write(m_Warmode); + writer.Write(m_Hidden); + writer.Write((byte)m_Direction); + writer.Write(m_Hue); + writer.Write(m_Str); + writer.Write(m_Dex); + writer.Write(m_Int); + writer.Write(m_Hits); + writer.Write(m_Stam); + writer.Write(m_Mana); + + writer.Write(m_Map); + + writer.Write(m_Blessed); + writer.Write(m_Fame); + writer.Write(m_Karma); + writer.Write((byte)m_AccessLevel); + Skills.Serialize(writer); + + writer.Write(Items); + + writer.Write(m_Player); + writer.Write(m_Title); + writer.Write(Profile); + writer.Write(ProfileLocked); + writer.Write(AutoPageNotify); + + writer.Write(LogoutLocation); + writer.Write(LogoutMap); + + writer.Write((byte)m_StrLock); + writer.Write((byte)m_DexLock); + writer.Write((byte)m_IntLock); + } + + public bool Deleted { get; private set; } + + public virtual void Delete() + { + if (Deleted) + return; + + if (!World.OnDelete(this)) + return; + + if (m_NetState != null) + { + m_NetState.CancelAllTrades(); + m_NetState.Dispose(); + } + + DropHolding(); + + Region.OnRegionChange(this, m_Region, null); + + m_Region = null; + // Is the above line REALLY needed? The old Region system did NOT have said line + // and worked fine, because of this a LOT of extra checks have to be done everywhere... + // I guess this should be there for Garbage collection purposes, but, still, is it /really/ needed? + + OnDelete(); + + for (var i = Items.Count - 1; i >= 0; --i) + if (i < Items.Count) + Items[i].OnParentDeleted(this); + + for (var i = 0; i < Stabled.Count; i++) + Stabled[i].Delete(); + + SendRemovePacket(); + + m_Guild?.OnDelete(this); + + Deleted = true; + + m_Map?.OnLeave(this); + m_Map = null; + + m_Hair = null; + m_FacialHair = null; + m_MountItem = null; + + World.RemoveMobile(this); + + OnAfterDelete(); + + FreeCache(); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Map Map + { + get => m_Map; + set + { + if (Deleted) + return; + + if (m_Map != value) + { + m_NetState?.ValidateAllTrades(); + + var oldMap = m_Map; + + if (m_Map != null) + { + m_Map.OnLeave(this); + + ClearScreen(); + SendRemovePacket(); + } + + for (var i = 0; i < Items.Count; ++i) + Items[i].Map = value; + + m_Map = value; + + UpdateRegion(); + + m_Map?.OnEnter(this); + + var ns = m_NetState; + + if (ns != null && m_Map != null) + { + ns.Sequence = 0; + if (Map != null) + ns.Send(new MapChange(Map)); + + if (!Core.SE && ns.ProtocolChanges < ProtocolChanges.Version6000) + ns.Send(new MapPatches()); + + ns.Send(SeasonChange.Instantiate(GetSeason(), true)); + + if (ns.StygianAbyss) + ns.Send(new MobileUpdate(this)); + else + ns.Send(new MobileUpdateOld(this)); + + ClearFastwalkStack(); + } + + if (ns != null) + { + if (m_Map != null) + ns.Send(new ServerChange(m_Location, m_Map)); + + ns.Sequence = 0; + ClearFastwalkStack(); + + ns.Send(MobileIncoming.Create(ns, this, this)); + + if (ns.StygianAbyss) + { + ns.Send(new MobileUpdate(this)); + CheckLightLevels(true); + ns.Send(new MobileUpdate(this)); + } + else + { + ns.Send(new MobileUpdateOld(this)); + CheckLightLevels(true); + ns.Send(new MobileUpdateOld(this)); + } + } + + SendEverything(); + SendIncomingPacket(); + + if (ns != null) + { + ns.Sequence = 0; + ClearFastwalkStack(); + + ns.Send(MobileIncoming.Create(ns, this, this)); + + if (ns.StygianAbyss) + { + ns.Send(SupportedFeatures.Instantiate(ns)); + ns.Send(new MobileUpdate(this)); + ns.Send(new MobileAttributes(this)); + } + else + { + ns.Send(SupportedFeatures.Instantiate(ns)); + ns.Send(new MobileUpdateOld(this)); + ns.Send(new MobileAttributes(this)); + } + } + + OnMapChange(oldMap); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public Point3D Location + { + get => m_Location; + set => SetLocation(value, true); + } + + public virtual void MoveToWorld(Point3D newLocation, Map map) + { + if (Deleted) + return; + + if (m_Map == map) + { + SetLocation(newLocation, true); + return; + } + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + box.Close(); + + var oldLocation = m_Location; + var oldMap = m_Map; + + var oldRegion = m_Region; + + if (oldMap != null) + { + oldMap.OnLeave(this); + + ClearScreen(); + SendRemovePacket(); + } + + for (var i = 0; i < Items.Count; ++i) + Items[i].Map = map; + + m_Map = map; + + m_Location = newLocation; + + var ns = m_NetState; + + if (m_Map != null) + { + m_Map.OnEnter(this); + + UpdateRegion(); + + if (ns != null && m_Map != null) + { + ns.Sequence = 0; + if (Map != null) + ns.Send(new MapChange(Map)); + + if (!Core.SE && ns.ProtocolChanges < ProtocolChanges.Version6000) + ns.Send(new MapPatches()); + + ns.Send(SeasonChange.Instantiate(GetSeason(), true)); + + if (ns.StygianAbyss) + ns.Send(new MobileUpdate(this)); + else + ns.Send(new MobileUpdateOld(this)); + + ClearFastwalkStack(); + } + } + else + { + UpdateRegion(); + } + + if (ns != null) + { + if (m_Map != null) + Send(new ServerChange(m_Location, m_Map)); + + ns.Sequence = 0; + ClearFastwalkStack(); + + ns.Send(MobileIncoming.Create(ns, this, this)); + + if (ns.StygianAbyss) + { + ns.Send(new MobileUpdate(this)); + CheckLightLevels(true); + ns.Send(new MobileUpdate(this)); + } + else + { + ns.Send(new MobileUpdateOld(this)); + CheckLightLevels(true); + ns.Send(new MobileUpdateOld(this)); + } + } + + SendEverything(); + SendIncomingPacket(); + + if (ns != null) + { + ns.Sequence = 0; + ClearFastwalkStack(); + + ns.Send(MobileIncoming.Create(ns, this, this)); + + if (ns.StygianAbyss) + { + ns.Send(SupportedFeatures.Instantiate(ns)); + ns.Send(new MobileUpdate(this)); + ns.Send(new MobileAttributes(this)); + } + else + { + ns.Send(SupportedFeatures.Instantiate(ns)); + ns.Send(new MobileUpdateOld(this)); + ns.Send(new MobileAttributes(this)); + } + } + + OnMapChange(oldMap); + OnLocationChange(oldLocation); + + m_Region?.OnLocationChanged(this, oldLocation); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int X + { + get => m_Location.m_X; + set => Location = new Point3D(value, m_Location.m_Y, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Y + { + get => m_Location.m_Y; + set => Location = new Point3D(m_Location.m_X, value, m_Location.m_Z); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Z + { + get => m_Location.m_Z; + set => Location = new Point3D(m_Location.m_X, m_Location.m_Y, value); + } + + public virtual void ProcessDelta() + { + var m = this; + var delta = m.m_DeltaFlags; + + if (delta == MobileDelta.None) + return; + + var attrs = delta & MobileDelta.Attributes; + + m.m_DeltaFlags = MobileDelta.None; + m.m_InDeltaQueue = false; + + bool sendHits = false, sendStam = false, sendMana = false, sendAll = false, sendAny = false; + bool sendIncoming = false, sendNonlocalIncoming = false; + bool sendUpdate = false, sendRemove = false; + bool sendPublicStats = false, sendPrivateStats = false; + bool sendMoving = false, sendNonlocalMoving = false; + var sendOPLUpdate = ObjectPropertyList.Enabled && (delta & MobileDelta.Properties) != 0; + + bool sendHair = false, sendFacialHair = false, removeHair = false, removeFacialHair = false; + + bool sendHealthbarPoison = false, sendHealthbarYellow = false; + + if (attrs != MobileDelta.None) + { + sendAny = true; + + if (attrs == MobileDelta.Attributes) + { + sendAll = true; + } + else + { + sendHits = (attrs & MobileDelta.Hits) != 0; + sendStam = (attrs & MobileDelta.Stam) != 0; + sendMana = (attrs & MobileDelta.Mana) != 0; + } + } + + if ((delta & MobileDelta.GhostUpdate) != 0) sendNonlocalIncoming = true; + + if ((delta & MobileDelta.Hue) != 0) + { + sendNonlocalIncoming = true; + sendUpdate = true; + sendRemove = true; + } + + if ((delta & MobileDelta.Direction) != 0) + { + sendNonlocalMoving = true; + sendUpdate = true; + } + + if ((delta & MobileDelta.Body) != 0) + { + sendUpdate = true; + sendIncoming = true; + } + + /*if ((delta & MobileDelta.Hue) != 0) + { + sendNonlocalIncoming = true; + sendUpdate = true; + } + else if ((delta & (MobileDelta.Direction | MobileDelta.Body)) != 0) + { + sendNonlocalMoving = true; + sendUpdate = true; + } + else*/ + if ((delta & (MobileDelta.Flags | MobileDelta.Noto)) != 0) sendMoving = true; + + if ((delta & MobileDelta.HealthbarPoison) != 0) sendHealthbarPoison = true; + + if ((delta & MobileDelta.HealthbarYellow) != 0) sendHealthbarYellow = true; + + if ((delta & MobileDelta.Name) != 0) + { + sendAll = false; + sendHits = false; + sendAny = sendStam || sendMana; + sendPublicStats = true; + } + + if ((delta & (MobileDelta.WeaponDamage | MobileDelta.Resistances | MobileDelta.Stat | + MobileDelta.Weight | MobileDelta.Gold | MobileDelta.Armor | MobileDelta.StatCap | + MobileDelta.Followers | MobileDelta.TithingPoints | MobileDelta.Race)) != 0) + sendPrivateStats = true; + + if ((delta & MobileDelta.Hair) != 0) + { + if (m.HairItemID <= 0) + removeHair = true; + + sendHair = true; + } + + if ((delta & MobileDelta.FacialHair) != 0) + { + if (m.FacialHairItemID <= 0) + removeFacialHair = true; + + sendFacialHair = true; + } + + var cache = new[] { new Packet[8], new Packet[8] }; + + var ourState = m.m_NetState; + + if (ourState != null) + { + if (sendUpdate) + { + ourState.Sequence = 0; + + if (ourState.StygianAbyss) + ourState.Send(new MobileUpdate(m)); + else + ourState.Send(new MobileUpdateOld(m)); + + ClearFastwalkStack(); + } + + if (sendIncoming) + ourState.Send(MobileIncoming.Create(ourState, m, m)); + + if (ourState.StygianAbyss) + { + if (sendMoving) + { + var noto = Notoriety.Compute(m, m); + ourState.Send(cache[0][noto] = Packet.Acquire(new MobileMoving(m, noto))); + } + + if (sendHealthbarPoison) + ourState.Send(new HealthbarPoison(m)); + + if (sendHealthbarYellow) + ourState.Send(new HealthbarYellow(m)); + } + else + { + if (sendMoving || sendHealthbarPoison || sendHealthbarYellow) + { + var noto = Notoriety.Compute(m, m); + ourState.Send(cache[1][noto] = Packet.Acquire(new MobileMovingOld(m, noto))); + } + } + + if (sendPublicStats || sendPrivateStats) + { + ourState.Send(new MobileStatusExtended(m, m_NetState)); + } + else if (sendAll) + { + ourState.Send(new MobileAttributes(m)); + } + else if (sendAny) + { + if (sendHits) + ourState.Send(new MobileHits(m)); + + if (sendStam) + ourState.Send(new MobileStam(m)); + + if (sendMana) + ourState.Send(new MobileMana(m)); + } + + if (sendStam || sendMana) + if (Party is IParty ip) + { + if (sendStam) + ip.OnStamChanged(this); + + if (sendMana) + ip.OnManaChanged(this); + } + + if (sendHair) + { + if (removeHair) + ourState.Send(new RemoveHair(m)); + else + ourState.Send(new HairEquipUpdate(m)); + } + + if (sendFacialHair) + { + if (removeFacialHair) + ourState.Send(new RemoveFacialHair(m)); + else + ourState.Send(new FacialHairEquipUpdate(m)); + } + + if (sendOPLUpdate) + ourState.Send(OPLPacket); + } + + sendMoving = sendMoving || sendNonlocalMoving; + sendIncoming = sendIncoming || sendNonlocalIncoming; + sendHits = sendHits || sendAll; + + if (m.m_Map != null && (sendRemove || sendIncoming || sendPublicStats || sendHits || sendMoving || + sendOPLUpdate || sendHair || sendFacialHair || sendHealthbarPoison || + sendHealthbarYellow)) + { + Mobile beholder; + + Packet hitsPacket = null; + Packet statPacketTrue = null; + Packet statPacketFalse = null; + Packet deadPacket = null; + Packet hairPacket = null; + Packet facialhairPacket = null; + Packet hbpPacket = null; + Packet hbyPacket = null; + + var eable = m.Map.GetClientsInRange(m.m_Location); + + foreach (var state in eable) + { + beholder = state.Mobile; + + if (beholder != m && beholder.CanSee(m)) + { + if (sendRemove) + state.Send(RemovePacket); + + if (sendIncoming) + { + state.Send(MobileIncoming.Create(state, beholder, m)); + + if (m.IsDeadBondedPet) + { + deadPacket ??= Packet.Acquire(new BondedStatus(m.Serial, true)); + + state.Send(deadPacket); + } + } + + if (state.StygianAbyss) + { + if (sendMoving) + { + var noto = Notoriety.Compute(beholder, m); + + var p = cache[0][noto]; + + if (p == null) + cache[0][noto] = p = Packet.Acquire(new MobileMoving(m, noto)); + + state.Send(p); + } + + if (sendHealthbarPoison) + { + hbpPacket ??= Packet.Acquire(new HealthbarPoison(m)); + + state.Send(hbpPacket); + } + + if (sendHealthbarYellow) + { + hbyPacket ??= Packet.Acquire(new HealthbarYellow(m)); + + state.Send(hbyPacket); + } + } + else + { + if (sendMoving || sendHealthbarPoison || sendHealthbarYellow) + { + var noto = Notoriety.Compute(beholder, m); + + var p = cache[1][noto]; + + if (p == null) + cache[1][noto] = p = Packet.Acquire(new MobileMovingOld(m, noto)); + + state.Send(p); + } + } + + if (sendPublicStats) + { + if (m.CanBeRenamedBy(beholder)) + { + statPacketTrue ??= Packet.Acquire(new MobileStatusCompact(true, m)); + + state.Send(statPacketTrue); + } + else + { + statPacketFalse ??= Packet.Acquire(new MobileStatusCompact(false, m)); + + state.Send(statPacketFalse); + } + } + else if (sendHits) + { + hitsPacket ??= Packet.Acquire(new MobileHitsN(m)); + + state.Send(hitsPacket); + } + + if (sendHair) + { + hairPacket ??= removeHair + ? Packet.Acquire(new RemoveHair(m)) + : Packet.Acquire(new HairEquipUpdate(m)); + + state.Send(hairPacket); + } + + if (sendFacialHair) + { + facialhairPacket ??= removeFacialHair + ? Packet.Acquire(new RemoveFacialHair(m)) + : Packet.Acquire(new FacialHairEquipUpdate(m)); + + state.Send(facialhairPacket); + } + + if (sendOPLUpdate) + state.Send(OPLPacket); + } + } + + Packet.Release(hitsPacket); + Packet.Release(statPacketTrue); + Packet.Release(statPacketFalse); + Packet.Release(deadPacket); + Packet.Release(hairPacket); + Packet.Release(facialhairPacket); + Packet.Release(hbpPacket); + Packet.Release(hbyPacket); + + eable.Free(); + } + + if (sendMoving || sendNonlocalMoving || sendHealthbarPoison || sendHealthbarYellow) + for (var i = 0; i < cache.Length; ++i) + for (var j = 0; j < cache[i].Length; ++j) + Packet.Release(ref cache[i][j]); + } + + public ISpawner Spawner { get; set; } + + public virtual void OnBeforeSpawn(Point3D location, Map m) + { + } + + public virtual void OnAfterSpawn() + { + } + + int IComparable.CompareTo(IEntity other) => other == null ? -1 : Serial.CompareTo(other.Serial); + + public virtual bool InRange(Point2D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public virtual bool InRange(Point3D p, int range) => + p.m_X >= Location.m_X - range + && p.m_X <= Location.m_X + range + && p.m_Y >= Location.m_Y - range + && p.m_Y <= Location.m_Y + range; + + public virtual bool InRange(IPoint2D p, int range) => + p.X >= Location.m_X - range + && p.X <= Location.m_X + range + && p.Y >= Location.m_Y - range + && p.Y <= Location.m_Y + range; + + public void ReleaseOPLPacket() + { + if (m_PropertyList == null) + return; + + Packet.Release(m_PropertyList); + m_PropertyList = null; + } + + protected virtual void OnRaceChange(Race oldRace) + { + } + + public virtual void ComputeLightLevels(out int global, out int personal) + { + ComputeBaseLightLevels(out global, out personal); + + m_Region?.AlterLightLevel(this, ref global, ref personal); + } + + public virtual void ComputeBaseLightLevels(out int global, out int personal) + { + global = 0; + personal = m_LightLevel; + } + + public virtual void CheckLightLevels(bool forceResend) + { + } + + public virtual void UpdateResistances() + { + Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; + + var delta = false; + + for (var i = 0; i < Resistances.Length; ++i) + if (Resistances[i] != int.MinValue) + { + Resistances[i] = int.MinValue; + delta = true; + } + + if (delta) + Delta(MobileDelta.Resistances); + } + + public virtual int GetResistance(ResistanceType type) + { + Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; + + var v = (int)type; + + if (v < 0 || v >= Resistances.Length) + return 0; + + var res = Resistances[v]; + + if (res == int.MinValue) + { + ComputeResistances(); + res = Resistances[v]; + } + + return res; + } + + public virtual void AddResistanceMod(ResistanceMod toAdd) + { + ResistanceMods ??= new List(); + + ResistanceMods.Add(toAdd); + UpdateResistances(); + } + + public virtual void RemoveResistanceMod(ResistanceMod toRemove) + { + if (ResistanceMods != null) + { + ResistanceMods.Remove(toRemove); + + if (ResistanceMods.Count == 0) + ResistanceMods = null; + } + + UpdateResistances(); + } + + public virtual void ComputeResistances() + { + Resistances ??= new[] { int.MinValue, int.MinValue, int.MinValue, int.MinValue, int.MinValue }; + + for (var i = 0; i < Resistances.Length; ++i) + Resistances[i] = 0; + + Resistances[0] += BasePhysicalResistance; + Resistances[1] += BaseFireResistance; + Resistances[2] += BaseColdResistance; + Resistances[3] += BasePoisonResistance; + Resistances[4] += BaseEnergyResistance; + + for (var i = 0; ResistanceMods != null && i < ResistanceMods.Count; ++i) + { + var mod = ResistanceMods[i]; + var v = (int)mod.Type; + + if (v >= 0 && v < Resistances.Length) + Resistances[v] += mod.Offset; + } + + for (var i = 0; i < Items.Count; ++i) + { + var item = Items[i]; + + if (item.CheckPropertyConflict(this)) + continue; + + Resistances[0] += item.PhysicalResistance; + Resistances[1] += item.FireResistance; + Resistances[2] += item.ColdResistance; + Resistances[3] += item.PoisonResistance; + Resistances[4] += item.EnergyResistance; + } + + for (var i = 0; i < Resistances.Length; ++i) + { + var min = GetMinResistance((ResistanceType)i); + var max = GetMaxResistance((ResistanceType)i); + + if (max < min) + max = min; + + if (Resistances[i] > max) + Resistances[i] = max; + else if (Resistances[i] < min) + Resistances[i] = min; + } + } + + public virtual int GetMinResistance(ResistanceType type) => int.MinValue; + + public virtual int GetMaxResistance(ResistanceType type) => m_Player ? MaxPlayerResistance : int.MaxValue; + + public int GetAOSStatus(int index) => AOSStatusHandler?.Invoke(this, index) ?? 0; + + public virtual void SendPropertiesTo(Mobile from) + { + from.Send(PropertyList); + } + + public virtual void OnAosSingleClick(Mobile from) + { + var opl = PropertyList; + + if (opl.Header > 0) + { + int hue; + + if (NameHue != -1) + hue = NameHue; + else if (m_AccessLevel > AccessLevel.Player) + hue = 11; + else + hue = Notoriety.GetHue(Notoriety.Compute(from, this)); + + from.Send(new MessageLocalized(Serial, Body, MessageType.Label, hue, 3, opl.Header, Name, opl.HeaderArgs)); + } + } + + public virtual string ApplyNameSuffix(string suffix) => suffix; + + public virtual void AddNameProperties(ObjectPropertyList list) + { + var name = Name ?? string.Empty; + + string prefix; + + if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) + prefix = m_Female ? "Lady" : "Lord"; + else + prefix = ""; + + var suffix = ""; + + if (PropertyTitle && !string.IsNullOrEmpty(Title)) + suffix = Title; + + var guild = m_Guild; + + if (guild != null && (m_Player || m_DisplayGuildTitle)) + suffix = suffix.Length > 0 + ? $"{suffix} [{Utility.FixHtml(guild.Abbreviation)}]" + : $"[{Utility.FixHtml(guild.Abbreviation)}]"; + + suffix = ApplyNameSuffix(suffix); + + list.Add(1050045, "{0} \t{1}\t {2}", prefix, name, suffix); // ~1_PREFIX~~2_NAME~~3_SUFFIX~ + + if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) + { + var type = guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length ? m_GuildTypes[(int)guild.Type] : ""; + + var title = GuildTitle?.Trim() ?? ""; + + if (title.Length > 0) + { + if (NewGuildDisplay) + list.Add("{0}, {1}", Utility.FixHtml(title), Utility.FixHtml(guild.Name)); + else + list.Add("{0}, {1} Guild{2}", Utility.FixHtml(title), Utility.FixHtml(guild.Name), type); + } + else + { + list.Add(Utility.FixHtml(guild.Name)); + } + } + } + + public virtual void GetChildProperties(ObjectPropertyList list, Item item) + { + } + + public virtual void GetChildNameProperties(ObjectPropertyList list, Item item) + { + } + + private void UpdateAggrExpire() + { + if (Deleted || Aggressors.Count == 0 && Aggressed.Count == 0) + { + StopAggrExpire(); + } + else if (m_ExpireAggrTimer == null) + { + m_ExpireAggrTimer = new ExpireAggressorsTimer(this); + m_ExpireAggrTimer.Start(); + } + } + + private void StopAggrExpire() + { + m_ExpireAggrTimer?.Stop(); + + m_ExpireAggrTimer = null; + } + + private void CheckAggrExpire() + { + for (var i = Aggressors.Count - 1; i >= 0; --i) + { + if (i >= Aggressors.Count) + continue; + + var info = Aggressors[i]; + + if (info.Expired) + { + var attacker = info.Attacker; + attacker.RemoveAggressed(this); + + Aggressors.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(attacker) && Utility.InUpdateRange(m_Location, attacker.m_Location)) + m_NetState.Send(MobileIncoming.Create(m_NetState, this, attacker)); + } + } + + for (var i = Aggressed.Count - 1; i >= 0; --i) + { + if (i >= Aggressed.Count) + continue; + + var info = Aggressed[i]; + + if (info.Expired) + { + var defender = info.Defender; + defender.RemoveAggressor(this); + + Aggressed.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(defender) && Utility.InUpdateRange(m_Location, defender.m_Location)) + m_NetState.Send(MobileIncoming.Create(m_NetState, this, defender)); + } + } + + UpdateAggrExpire(); + } + + /// + /// Overridable. Virtual event invoked when changes in some way. + /// + public virtual void OnSkillInvalidated(Skill skill) + { + } + + public virtual void UpdateSkillMods() + { + ValidateSkillMods(); + + for (var i = 0; i < SkillMods.Count; ++i) + { + var mod = SkillMods[i]; + var sk = Skills[mod.Skill]; + sk?.Update(); + } + } + + public virtual void ValidateSkillMods() + { + for (var i = 0; i < SkillMods.Count;) + { + var mod = SkillMods[i]; + + if (mod.CheckCondition()) + ++i; + else + InternalRemoveSkillMod(mod); + } + } + + public virtual void AddSkillMod(SkillMod mod) + { + if (mod == null) + return; + + ValidateSkillMods(); + + if (!SkillMods.Contains(mod)) + { + SkillMods.Add(mod); + mod.Owner = this; + + var sk = Skills[mod.Skill]; + sk?.Update(); + } + } + + public virtual void RemoveSkillMod(SkillMod mod) + { + if (mod == null) + return; + + ValidateSkillMods(); + + InternalRemoveSkillMod(mod); + } + + private void InternalRemoveSkillMod(SkillMod mod) + { + if (SkillMods.Contains(mod)) + { + SkillMods.Remove(mod); + mod.Owner = null; + + var sk = Skills[mod.Skill]; + sk?.Update(); + } + } + + /// + /// Overridable. Virtual event invoked when a client, , invokes a 'help request' for the Mobile. + /// Seemingly no longer functional in newer clients. + /// + public virtual void OnHelpRequest(Mobile from) + { + } + + public void DelayChangeWarmode(bool value) + { + if (m_WarmodeTimer != null) + { + m_WarmodeTimer.Value = value; + return; + } + + if (m_Warmode == value) + return; + + DateTime now = DateTime.UtcNow, next = m_NextWarmodeChange; + + if (now > next || m_WarmodeChanges == 0) + { + m_WarmodeChanges = 1; + m_NextWarmodeChange = now + WarmodeSpamCatch; + } + else if (m_WarmodeChanges == WarmodeCatchCount) + { + m_WarmodeTimer = new WarmodeTimer(this, value); + m_WarmodeTimer.Start(); + + return; + } + else + { + ++m_WarmodeChanges; + } + + Warmode = value; + } + + public bool InLOS(Mobile target) => + !Deleted && m_Map != null && + (target == this || m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); + + public bool InLOS(object target) => + !Deleted && m_Map != null && + (target == this || m_AccessLevel > AccessLevel.Player || target is Item item && item.RootParent == this + || m_Map.LineOfSight(this, target)); + + public bool InLOS(Point3D target) => + !Deleted && m_Map != null && (m_AccessLevel > AccessLevel.Player || m_Map.LineOfSight(this, target)); + + public bool BeginAction() => BeginAction(typeof(T)); + + public bool BeginAction(object toLock) + { + if (_actions == null) + { + _actions = new List { toLock }; + return true; + } + + if (!_actions.Contains(toLock)) + { + _actions.Add(toLock); + return true; + } + + return false; + } + + public bool CanBeginAction() => CanBeginAction(typeof(T)); + + public bool CanBeginAction(object toLock) => _actions?.Contains(toLock) != true; + + public void EndAction() => EndAction(typeof(T)); + + public void EndAction(object toLock) + { + if (_actions != null) + { + _actions.Remove(toLock); + + if (_actions.Count == 0) _actions = null; + } + } + + public virtual TimeSpan GetLogoutDelay() => Region.GetLogoutDelay(this); + + public void Paralyze(TimeSpan duration) + { + if (!m_Paralyzed) + { + Paralyzed = true; + + m_ParaTimer = new ParalyzedTimer(this, duration); + m_ParaTimer.Start(); + } + } + + public void Freeze(TimeSpan duration) + { + if (!m_Frozen) + { + Frozen = true; + + m_FrozenTimer = new FrozenTimer(this, duration); + m_FrozenTimer.Start(); + } + } + + public override string ToString() => $"0x{Serial.Value:X} \"{Name}\""; + + public virtual void SendSkillMessage() + { + if (NextActionMessage - Core.TickCount >= 0) + return; + + NextActionMessage = Core.TickCount + ActionMessageDelay; + + SendLocalizedMessage(500118); // You must wait a few moments to use another skill. + } + + public virtual void SendActionMessage() + { + if (NextActionMessage - Core.TickCount >= 0) + return; + + NextActionMessage = Core.TickCount + ActionMessageDelay; + + SendLocalizedMessage(500119); // You must wait to perform another action. + } + + public virtual void ClearHands() + { + ClearHand(FindItemOnLayer(Layer.OneHanded)); + ClearHand(FindItemOnLayer(Layer.TwoHanded)); + } + + public virtual void ClearHand(Item item) + { + if (item?.Movable == true && !item.AllowEquippedCast(this)) + { + var pack = Backpack; + + if (pack == null) + AddToBackpack(item); + else + pack.DropItem(item); + } + } + + public virtual void Attack(Mobile m) + { + if (CheckAttack(m)) + Combatant = m; + } + + public virtual bool CheckAttack(Mobile m) => Utility.InUpdateRange(this, m) && CanSee(m) && InLOS(m); + + /// + /// Overridable. Virtual event invoked after the property has changed. + /// + /// + public virtual void OnCombatantChange() + { + } + + public double GetDistanceToSqrt(Point3D p) + { + var xDelta = m_Location.m_X - p.m_X; + var yDelta = m_Location.m_Y - p.m_Y; + + return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + } + + public double GetDistanceToSqrt(Mobile m) + { + var xDelta = m_Location.m_X - m.m_Location.m_X; + var yDelta = m_Location.m_Y - m.m_Location.m_Y; + + return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + } + + public double GetDistanceToSqrt(IPoint2D p) + { + var xDelta = m_Location.m_X - p.X; + var yDelta = m_Location.m_Y - p.Y; + + return Math.Sqrt(xDelta * xDelta + yDelta * yDelta); + } + + public virtual void AggressiveAction(Mobile aggressor) => AggressiveAction(aggressor, false); + + public virtual void AggressiveAction(Mobile aggressor, bool criminal) + { + if (aggressor == this) + return; + + var args = AggressiveActionEventArgs.Create(this, aggressor, criminal); + + EventSink.InvokeAggressiveAction(args); + + args.Free(); + + if (Combatant == aggressor) + { + if (m_ExpireCombatant == null) + m_ExpireCombatant = new ExpireCombatantTimer(this); + else + m_ExpireCombatant.Stop(); + + m_ExpireCombatant.Start(); + } + + var addAggressor = true; + + var list = Aggressors; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Attacker == aggressor) + { + info.Refresh(); + info.CriminalAggression = criminal; + info.CanReportMurder = criminal; + + addAggressor = false; + } + } + + list = aggressor.Aggressors; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Attacker == this) + { + info.Refresh(); + + addAggressor = false; + } + } + + var addAggressed = true; + + list = Aggressed; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Defender == aggressor) + { + info.Refresh(); + + addAggressed = false; + } + } + + list = aggressor.Aggressed; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Defender == this) + { + info.Refresh(); + info.CriminalAggression = criminal; + info.CanReportMurder = criminal; + + addAggressed = false; + } + } + + var setCombatant = false; + + if (addAggressor) + { + Aggressors.Add( + AggressorInfo.Create( + aggressor, + this, + criminal + ) + ); // new AggressorInfo( aggressor, this, criminal, true ) ); + + if (CanSee(aggressor)) m_NetState?.Send(MobileIncoming.Create(m_NetState, this, aggressor)); + + if (Combatant == null) + setCombatant = true; + + UpdateAggrExpire(); + } + + if (addAggressed) + { + aggressor.Aggressed.Add( + AggressorInfo.Create( + aggressor, + this, + criminal + ) + ); // new AggressorInfo( aggressor, this, criminal, false ) ); + + if (CanSee(aggressor)) m_NetState?.Send(MobileIncoming.Create(m_NetState, this, aggressor)); + + if (Combatant == null) + setCombatant = true; + + UpdateAggrExpire(); + } + + if (setCombatant) + Combatant = aggressor; + + Region.OnAggressed(aggressor, this, criminal); + } + + public void RemoveAggressed(Mobile aggressed) + { + if (Deleted) + return; + + var list = Aggressed; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Defender == aggressed) + { + Aggressed.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(aggressed)) + m_NetState.Send(MobileIncoming.Create(m_NetState, this, aggressed)); + + break; + } + } + + UpdateAggrExpire(); + } + + public void RemoveAggressor(Mobile aggressor) + { + if (Deleted) + return; + + var list = Aggressors; + + for (var i = 0; i < list.Count; ++i) + { + var info = list[i]; + + if (info.Attacker == aggressor) + { + Aggressors.RemoveAt(i); + info.Free(); + + if (m_NetState != null && CanSee(aggressor)) + m_NetState.Send(MobileIncoming.Create(m_NetState, this, aggressor)); + + break; + } + } + + UpdateAggrExpire(); + } + + public virtual int GetTotal(TotalType type) => + type switch + { + TotalType.Gold => m_TotalGold, + TotalType.Items => m_TotalItems, + TotalType.Weight => m_TotalWeight, + _ => 0 + }; + + public virtual void UpdateTotal(Item sender, TotalType type, int delta) + { + if (delta == 0 || sender.IsVirtualItem) + return; + + switch (type) + { + default: + m_TotalGold += delta; + Delta(MobileDelta.Gold); + break; + + case TotalType.Items: + m_TotalItems += delta; + break; + + case TotalType.Weight: + m_TotalWeight += delta; + Delta(MobileDelta.Weight); + OnWeightChange(m_TotalWeight - delta); + break; + } + } + + public virtual void UpdateTotals() + { + if (Items == null) + return; + + var oldWeight = m_TotalWeight; + + m_TotalGold = 0; + m_TotalItems = 0; + m_TotalWeight = 0; + + for (var i = 0; i < Items.Count; ++i) + { + var item = Items[i]; + + item.UpdateTotals(); + + if (item.IsVirtualItem) + continue; + + m_TotalGold += item.TotalGold; + m_TotalItems += item.TotalItems + 1; + m_TotalWeight += item.TotalWeight + item.PileWeight; + } + + if (m_Holding != null) + m_TotalWeight += m_Holding.TotalWeight + m_Holding.PileWeight; + + if (m_TotalWeight != oldWeight) + OnWeightChange(oldWeight); + } + + public void ClearQuestArrow() => m_QuestArrow = null; + + public void ClearTarget() => m_Target = null; + + public Target BeginTarget(int range, bool allowGround, TargetFlags flags, TargetCallback callback) => + Target = new SimpleTarget(range, flags, allowGround, callback); + + public Target BeginTarget( + int range, bool allowGround, TargetFlags flags, TargetStateCallback callback, + T state + ) => + Target = new SimpleStateTarget(range, flags, allowGround, callback, state); + + /// + /// Overridable. Virtual event invoked after the Target property has changed. + /// + protected virtual void OnTargetChange() + { + } + + public virtual bool CheckContextMenuDisplay(IEntity target) => true; + + private bool InternalOnMove(Direction d) + { + if (!OnMove(d)) + return false; + + var e = MovementEventArgs.Create(this, d); + + EventSink.InvokeMovement(e); + + var ret = !e.Blocked; + + e.Free(); + + return ret; + } + + /// + /// Overridable. Event invoked before the Mobile moves. + /// + /// True if the move is allowed, false if not. + protected virtual bool OnMove(Direction d) + { + if (m_Hidden && m_AccessLevel == AccessLevel.Player) + if (AllowedStealthSteps-- <= 0 || (d & Direction.Running) != 0 || Mounted) + RevealingAction(); + + return true; + } + + public virtual void ClearFastwalkStack() + { + if (m_MoveRecords != null && m_MoveRecords.Count > 0) + m_MoveRecords.Clear(); + + m_EndQueue = Core.TickCount; + } + + public virtual bool CheckMovement(Direction d, out int newZ) => Movement.Movement.CheckMovement(this, d, out newZ); + + public virtual bool Move(Direction d) + { + if (Deleted) + return false; + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + box.Close(); + + var newLocation = m_Location; + var oldLocation = newLocation; + + if ((m_Direction & Direction.Mask) == (d & Direction.Mask)) + { + // We are actually moving (not just a direction change) + + if (m_Spell?.OnCasterMoving(d) == false) + return false; + + if (m_Paralyzed || m_Frozen) + { + SendLocalizedMessage(500111); // You are frozen and can not move. + + return false; + } + + if (CheckMovement(d, out var newZ)) + { + int x = oldLocation.m_X, y = oldLocation.m_Y; + int oldX = x, oldY = y; + var oldZ = oldLocation.m_Z; + + switch (d & Direction.Mask) + { + case Direction.North: + --y; + break; + case Direction.Right: + ++x; + --y; + break; + case Direction.East: + ++x; + break; + case Direction.Down: + ++x; + ++y; + break; + case Direction.South: + ++y; + break; + case Direction.Left: + --x; + ++y; + break; + case Direction.West: + --x; + break; + case Direction.Up: + --x; + --y; + break; + } + + newLocation.m_X = x; + newLocation.m_Y = y; + newLocation.m_Z = newZ; + + Pushing = false; + + var map = m_Map; + + if (map != null) + { + var oldSector = map.GetSector(oldX, oldY); + var newSector = map.GetSector(x, y); + + if (oldSector != newSector) + { + for (var i = 0; i < oldSector.Mobiles.Count; ++i) + { + var m = oldSector.Mobiles[i]; + + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && + !m.OnMoveOff(this)) + return false; + } + + for (var i = 0; i < oldSector.Items.Count; ++i) + { + var item = oldSector.Items[i]; + + if (item.AtWorldPoint(oldX, oldY) && + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && + !item.OnMoveOff(this)) + return false; + } + + for (var i = 0; i < newSector.Mobiles.Count; ++i) + { + var m = newSector.Mobiles[i]; + + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + return false; + } + + for (var i = 0; i < newSector.Items.Count; ++i) + { + var item = newSector.Items[i]; + + if (item.AtWorldPoint(x, y) && + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && + !item.OnMoveOver(this)) + return false; + } + } + else + { + for (var i = 0; i < oldSector.Mobiles.Count; ++i) + { + var m = oldSector.Mobiles[i]; + + if (m != this && m.X == oldX && m.Y == oldY && m.Z + 15 > oldZ && oldZ + 15 > m.Z && + !m.OnMoveOff(this)) + return false; + if (m.X == x && m.Y == y && m.Z + 15 > newZ && newZ + 15 > m.Z && !m.OnMoveOver(this)) + return false; + } + + for (var i = 0; i < oldSector.Items.Count; ++i) + { + var item = oldSector.Items[i]; + + if (item.AtWorldPoint(oldX, oldY) && + (item.Z == oldZ || item.Z + item.ItemData.Height > oldZ && oldZ + 15 > item.Z) && + !item.OnMoveOff(this)) + return false; + if (item.AtWorldPoint(x, y) && + (item.Z == newZ || item.Z + item.ItemData.Height > newZ && newZ + 15 > item.Z) && + !item.OnMoveOver(this)) + return false; + } + } + + if (!Region.CanMove(this, d, newLocation, oldLocation, m_Map)) + return false; + } + else + { + return false; + } + + if (!InternalOnMove(d)) + return false; + + if (FwdEnabled && m_NetState != null && m_AccessLevel < FwdAccessOverride && + (!FwdUOTDOverride || !m_NetState.IsUOTDClient)) + { + m_MoveRecords ??= new Queue(6); + + while (m_MoveRecords.Count > 0) + { + var r = m_MoveRecords.Peek(); + + if (r.Expired()) + m_MoveRecords.Dequeue(); + else + break; + } + + if (m_MoveRecords.Count >= FwdMaxSteps) + { + var fw = new FastWalkEventArgs(m_NetState); + EventSink.InvokeFastWalk(fw); + + if (fw.Blocked) + return false; + } + + var delay = ComputeMovementSpeed(d); + + long end; + + if (m_MoveRecords.Count > 0) + end = m_EndQueue + delay; + else + end = Core.TickCount + delay; + + m_MoveRecords.Enqueue(MovementRecord.NewInstance(end)); + + m_EndQueue = end; + } + + LastMoveTime = Core.TickCount; + } + else + { + return false; + } + + DisruptiveAction(); + } + + m_NetState?.Send( + MovementAck.Instantiate( + m_NetState.Sequence, + this + ) + ); // new MovementAck( m_NetState.Sequence, this ) ); + + SetLocation(newLocation, false); + SetDirection(d); + + if (m_Map != null) + { + var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); + + foreach (var o in eable) + { + if (o == this) + continue; + + if (o is Mobile mob) + { + if (mob.NetState != null) + m_MoveClientList.Add(mob); + m_MoveList.Add(mob); + } + else if (o is Item item && item.HandlesOnMovement) + { + m_MoveList.Add(item); + } + } + + eable.Free(); + + var cache = m_MovingPacketCache; + + /*for( int i = 0; i < cache.Length; ++i ) + for( int j = 0; j < cache[i].Length; ++j ) + Packet.Release( ref cache[i][j] );*/ + + foreach (var m in m_MoveClientList) + { + var ns = m.NetState; + + if (ns != null && Utility.InUpdateRange(m_Location, m.m_Location) && m.CanSee(this)) + { + if (ns.StygianAbyss) + { + var noto = Notoriety.Compute(m, this); + var p = cache[0][noto]; + + if (p == null) + cache[0][noto] = p = Packet.Acquire(new MobileMoving(this, noto)); + + ns.Send(p); + } + else + { + var noto = Notoriety.Compute(m, this); + var p = cache[1][noto]; + + if (p == null) + cache[1][noto] = p = Packet.Acquire(new MobileMovingOld(this, noto)); + + ns.Send(p); + } + } + } + + for (var i = 0; i < cache.Length; ++i) + for (var j = 0; j < cache[i].Length; ++j) + Packet.Release(ref cache[i][j]); + + for (var i = 0; i < m_MoveList.Count; ++i) + { + var o = m_MoveList[i]; + + if (o is Mobile mobile) + mobile.OnMovement(this, oldLocation); + else if (o is Item item) item.OnMovement(this, oldLocation); + } + + if (m_MoveList.Count > 0) + m_MoveList.Clear(); + + if (m_MoveClientList.Count > 0) + m_MoveClientList.Clear(); + } + + OnAfterMove(oldLocation); + return true; + } + + public virtual void OnAfterMove(Point3D oldLocation) + { + } + + public int ComputeMovementSpeed() => ComputeMovementSpeed(Direction, false); + + public int ComputeMovementSpeed(Direction dir) => ComputeMovementSpeed(dir, true); + + public virtual int ComputeMovementSpeed(Direction dir, bool checkTurning) + { + int delay; + + if (Mounted) + delay = (dir & Direction.Running) != 0 ? RunMount : WalkMount; + else + delay = (dir & Direction.Running) != 0 ? RunFoot : WalkFoot; + + return delay; + } + + /// + /// Overridable. Virtual event invoked when a Mobile moves off this Mobile. + /// + /// True if the move is allowed, false if not. + public virtual bool OnMoveOff(Mobile m) => true; + + /// + /// Overridable. Event invoked when a Mobile moves over this Mobile. + /// + /// True if the move is allowed, false if not. + public virtual bool OnMoveOver(Mobile m) => m_Map == null || Deleted || m.CheckShove(this); + + public virtual bool CheckShove(Mobile shoved) + { + if ((m_Map.Rules & MapRules.FreeMovement) == 0) + { + if (!shoved.Alive || !Alive || shoved.IsDeadBondedPet || IsDeadBondedPet) + return true; + if (shoved.m_Hidden && shoved.m_AccessLevel > AccessLevel.Player) + return true; + + if (!Pushing) + { + Pushing = true; + + int number; + + if (AccessLevel > AccessLevel.Player) + { + number = shoved.m_Hidden ? 1019041 : 1019040; + } + else + { + if (Stam == StamMax) + { + number = shoved.m_Hidden ? 1019043 : 1019042; + Stam -= 10; + + RevealingAction(); + } + else + { + return false; + } + } + + SendLocalizedMessage(number); + } + } + + return true; + } + + /// + /// Overridable. Virtual event invoked when the Mobile sees another Mobile, , move. + /// + public virtual void OnMovement(Mobile m, Point3D oldLocation) + { + } + + public virtual void CriminalAction(bool message) + { + if (Deleted) + return; + + Criminal = true; + + Region.OnCriminalAction(this, message); + } + + public virtual bool IsSnoop(Mobile from) => from != this; + + /// + /// Overridable. Any call to will silently fail if this method returns false. + /// + /// + public virtual bool CheckResurrect() => true; + + /// + /// Overridable. Event invoked before the Mobile is resurrected. + /// + /// + public virtual void OnBeforeResurrect() + { + } + + /// + /// Overridable. Event invoked after the Mobile is resurrected. + /// + /// + public virtual void OnAfterResurrect() + { + } + + public virtual void Resurrect() + { + if (!Alive) + { + if (!Region.OnResurrect(this)) + return; + + if (!CheckResurrect()) + return; + + OnBeforeResurrect(); + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + box.Close(); + + Poison = null; + + Warmode = false; + + Hits = 10; + Stam = StamMax; + Mana = 0; + + BodyMod = 0; + Body = Race.AliveBody(this); + + ProcessDeltaQueue(); + + for (var i = Items.Count - 1; i >= 0; --i) + { + if (i >= Items.Count) + continue; + + var item = Items[i]; + + if (item.ItemID == 0x204E) + item.Delete(); + } + + SendIncomingPacket(); + SendIncomingPacket(); + + OnAfterResurrect(); + + // Send( new DeathStatus( false ) ); + } + } + + public void DropHolding() + { + var holding = m_Holding; + + if (holding != null) + { + if (!holding.Deleted && holding.HeldBy == this && holding.Map == Map.Internal) + AddToBackpack(holding); + + Holding = null; + holding.ClearBounce(); + } + } + + /// + /// Overridable. Virtual event invoked before the Mobile is deleted. + /// + public virtual void OnDelete() + { + Spawner?.Remove(this); + Spawner = null; + } + + public virtual bool CheckSpellCast(ISpell spell) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile casts a . + /// + /// + public virtual void OnSpellCast(ISpell spell) + { + } + + /// + /// Overridable. Virtual event invoked after changes. + /// + public virtual void OnWeightChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when the or property of + /// changes. + /// + public virtual void OnSkillChange(SkillName skill, double oldBase) + { + } + + /// + /// Overridable. Invoked after the mobile is deleted. When overridden, be sure to call the base method. + /// + public virtual void OnAfterDelete() + { + StopAggrExpire(); + + CheckAggrExpire(); + + PoisonTimer?.Stop(); + m_HitsTimer?.Stop(); + m_StamTimer?.Stop(); + m_ManaTimer?.Stop(); + m_CombatTimer?.Stop(); + m_ExpireCombatant?.Stop(); + m_LogoutTimer?.Stop(); + m_ExpireCriminal?.Stop(); + m_WarmodeTimer?.Stop(); + m_ParaTimer?.Stop(); + m_FrozenTimer?.Stop(); + m_AutoManifestTimer?.Stop(); + } + + public virtual bool AllowSkillUse(SkillName name) => true; + + public virtual bool UseSkill(SkillName name) => Skills.UseSkill(this, name); + + public virtual bool UseSkill(int skillID) => Skills.UseSkill(this, skillID); + + public virtual DeathMoveResult GetParentMoveResultFor(Item item) => item.OnParentDeath(this); + + public virtual DeathMoveResult GetInventoryMoveResultFor(Item item) => item.OnInventoryDeath(this); + + public virtual void Kill() + { + if (!CanBeDamaged()) + return; + if (!Alive || IsDeadBondedPet) + return; + if (Deleted) + return; + if (!Region.OnBeforeDeath(this)) + return; + if (!OnBeforeDeath()) + return; + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + box.Close(); + + m_NetState?.CancelAllTrades(); + + m_Spell?.OnCasterKilled(); + // m_Spell.Disturb( DisturbType.Kill ); + + m_Target?.Cancel(this, TargetCancelType.Canceled); + + DisruptiveAction(); + + Warmode = false; + + DropHolding(); + + Hits = 0; + Stam = 0; + Mana = 0; + + Poison = null; + Combatant = null; + + if (Paralyzed) + { + Paralyzed = false; + + m_ParaTimer?.Stop(); + } + + if (Frozen) + { + Frozen = false; + + m_FrozenTimer?.Stop(); + } + + var content = new List(); + var equip = new List(); + var moveToPack = new List(); + + var itemsCopy = new List(Items); + + var pack = Backpack; + + for (var i = 0; i < itemsCopy.Count; ++i) + { + var item = itemsCopy[i]; + + if (item == pack) + continue; + + var res = GetParentMoveResultFor(item); + + switch (res) + { + case DeathMoveResult.MoveToCorpse: + { + content.Add(item); + equip.Add(item); + break; + } + case DeathMoveResult.MoveToBackpack: + { + moveToPack.Add(item); + break; + } + } + } + + if (pack != null) + { + var packCopy = new List(pack.Items); + + for (var i = 0; i < packCopy.Count; ++i) + { + var item = packCopy[i]; + + var res = GetInventoryMoveResultFor(item); + + if (res == DeathMoveResult.MoveToCorpse) + content.Add(item); + else + moveToPack.Add(item); + } + + for (var i = 0; i < moveToPack.Count; ++i) + { + var item = moveToPack[i]; + + if (RetainPackLocsOnDeath && item.Parent == pack) + continue; + + pack.DropItem(item); + } + } + + HairInfo hair = null; + if (m_Hair != null) + hair = new HairInfo(m_Hair.ItemID, m_Hair.Hue); + + FacialHairInfo facialhair = null; + if (m_FacialHair != null) + facialhair = new FacialHairInfo(m_FacialHair.ItemID, m_FacialHair.Hue); + + var c = CreateCorpseHandler?.Invoke(this, hair, facialhair, content, equip); + + /*m_Corpse = c; + + for ( int i = 0; c != null && i < content.Count; ++i ) + c.DropItem( (Item)content[i] ); + + if (c != null) + c.MoveToWorld( this.Location, this.Map );*/ + + if (m_Map != null) + { + Packet animPacket = null; + + var eable = m_Map.GetClientsInRange(m_Location); + var corpseSerial = c?.Serial ?? Serial.Zero; + + foreach (var state in eable) + if (state != m_NetState) + { + animPacket ??= Packet.Acquire(new DeathAnimation(Serial, corpseSerial)); + + state.Send(animPacket); + + if (!state.Mobile.CanSee(this)) state.Send(RemovePacket); + } + + Packet.Release(animPacket); + + eable.Free(); + } + + Region.OnDeath(this); + OnDeath(c); + } + + /// + /// Overridable. Event invoked before the Mobile is killed. + /// + /// + /// + /// True to continue with death, false to override it. + public virtual bool OnBeforeDeath() => true; + + /// + /// Overridable. Event invoked after the Mobile is killed. Primarily, this method is responsible for + /// deleting an NPC or turning a PC into a ghost. + /// + /// + /// + public virtual void OnDeath(Container c) + { + var sound = GetDeathSound(); + + if (sound >= 0) + Effects.PlaySound(this, Map, sound); + + if (!m_Player) + { + Delete(); + } + else + { + Send(DeathStatus.Instantiate(true)); + + Warmode = false; + + BodyMod = 0; + // Body = this.Female ? 0x193 : 0x192; + Body = Race.GhostBody(this); + + var deathShroud = new Item(0x204E) { Movable = false, Layer = Layer.OuterTorso }; + + AddItem(deathShroud); + + Items.Remove(deathShroud); + Items.Insert(0, deathShroud); + + Poison = null; + Combatant = null; + + Hits = 0; + Stam = 0; + Mana = 0; + + EventSink.InvokePlayerDeath(this); + + ProcessDeltaQueue(); + + Send(DeathStatus.Instantiate(false)); + + CheckStatTimers(); + } + } + + public virtual bool CheckTarget(Mobile from, Target targ, object targeted) => true; + + public virtual void Use(Item item) + { + if (item?.Deleted != false || item.QuestItem || Deleted) + return; + + DisruptiveAction(); + + if (m_Spell?.OnCasterUsingObject(item) == false) + return; + + var root = item.RootParent; + var okay = false; + + if (!Utility.InUpdateRange(this, item.GetWorldLocation())) + { + item.OnDoubleClickOutOfRange(this); + } + else if (!CanSee(item)) + { + item.OnDoubleClickCantSee(this); + } + else if (!item.IsAccessibleTo(this)) + { + var reg = Region.Find(item.GetWorldLocation(), item.Map); + + if (reg?.SendInaccessibleMessage(item, this) != true) + item.OnDoubleClickNotAccessible(this); + } + else if (!CheckAlive(false)) + { + item.OnDoubleClickDead(this); + } + else if (item.InSecureTrade) + { + item.OnDoubleClickSecureTrade(this); + } + else if (!AllowItemUse(item)) + { + } + else if (!item.CheckItemUse(this, item)) + { + } + else if (root is Mobile mobile && mobile.IsSnoop(this)) + { + item.OnSnoop(this); + } + else if (Region.OnDoubleClick(this, item)) + { + okay = true; + } + + if (okay) + { + // TODO: Is this correct? + if (!item.Deleted) + item.OnItemUsed(this, item); + + // TODO: Is this correct? + if (!item.Deleted) + item.OnDoubleClick(this); + } + } + + public virtual void Use(Mobile m) + { + if (m?.Deleted != false || Deleted) + return; + + DisruptiveAction(); + + if (m_Spell?.OnCasterUsingObject(m) == false) + return; + + if (!Utility.InUpdateRange(this, m)) + m.OnDoubleClickOutOfRange(this); + else if (!CanSee(m)) + m.OnDoubleClickCantSee(this); + else if (!CheckAlive(false)) + m.OnDoubleClickDead(this); + else if (Region.OnDoubleClick(this, m) && !m.Deleted) + m.OnDoubleClick(this); + } + + public virtual void Lift(Item item, int amount, out bool rejected, out LRReason reject) + { + rejected = true; + reject = LRReason.Inspecific; + + if (item == null) + return; + + var from = this; + var state = m_NetState; + + if (from.AccessLevel >= AccessLevel.GameMaster || Core.TickCount - from.NextActionTime >= 0) + { + if (from.CheckAlive()) + { + from.DisruptiveAction(); + + if (from.Holding != null) + { + reject = LRReason.AreHolding; + } + else if (from.AccessLevel < AccessLevel.GameMaster && !from.InRange(item.GetWorldLocation(), 2)) + { + reject = LRReason.OutOfRange; + } + else if (!from.CanSee(item) || !from.InLOS(item)) + { + reject = LRReason.OutOfSight; + } + else if (!item.VerifyMove(from)) + { + reject = LRReason.CannotLift; + } + else if (!item.IsAccessibleTo(from)) + { + reject = LRReason.CannotLift; + } + else if (item.Nontransferable && amount != item.Amount) + { + if (item.QuestItem) + from.SendLocalizedMessage(1074868); // Stacks of quest items cannot be unstacked. + + reject = LRReason.CannotLift; + } + else if (!item.CheckLift(from, item, ref reject)) + { + } + else + { + var root = item.RootParent; + + if (root is Mobile mobile && !mobile.CheckNonlocalLift(from, item)) + { + reject = LRReason.TryToSteal; + } + else if (!from.OnDragLift(item) || !item.OnDragLift(from)) + { + reject = LRReason.Inspecific; + } + else if (!from.CheckAlive()) + { + reject = LRReason.Inspecific; + } + else + { + item.SetLastMoved(); + + if (item.Spawner != null) + { + item.Spawner.Remove(item); + item.Spawner = null; + } + + if (amount == 0) + amount = 1; + + if (amount > item.Amount) + amount = item.Amount; + + var oldAmount = item.Amount; + // item.Amount = amount; //Set in LiftItemDupe + + if (amount < oldAmount) + LiftItemDupe(item, amount); + // item.Dupe( oldAmount - amount ); + + var map = from.Map; + + if (DragEffects && map != null && (root == null || root is Item)) + { + var eable = map.GetClientsInRange(from.Location); + Packet p = null; + var rootItem = root as Item; + + foreach (var ns in eable) + if (ns.Mobile != from && ns.Mobile.CanSee(from) && ns.Mobile.InLOS(from) && + ns.Mobile.CanSee(root)) + { + if (p == null) + { + IEntity src = new Entity( + rootItem?.Serial ?? Serial.Zero, + rootItem?.Location ?? item.Location, + map + ); + + p = Packet.Acquire(new DragEffect(src, from, item.ItemID, item.Hue, amount)); + } + + ns.Send(p); + } + + Packet.Release(p); + + eable.Free(); + } + + var fixLoc = item.Location; + var fixMap = item.Map; + var shouldFix = item.Parent == null; + + item.RecordBounce(); + item.OnItemLifted(from, item); + item.Internalize(); + + from.Holding = item; + + var liftSound = item.GetLiftSound(from); + + if (liftSound != -1) + from.Send(new PlaySound(liftSound, from)); + + from.NextActionTime = Core.TickCount + ActionDelay; + + if (fixMap != null && shouldFix) + fixMap.FixColumn(fixLoc.m_X, fixLoc.m_Y); + + reject = LRReason.Inspecific; + rejected = false; + } + } + } + else + { + reject = LRReason.Inspecific; + } + } + else + { + SendActionMessage(); + reject = LRReason.Inspecific; + } + + if (rejected && state != null) + { + state.Send(new LiftRej(reject)); + + if (item.Deleted) + return; + + if (item.Parent is Item) + { + if (state.ContainerGridLines) + state.Send(new ContainerContentUpdate6017(item)); + else + state.Send(new ContainerContentUpdate(item)); + } + else if (item.Parent is Mobile) + { + state.Send(new EquipUpdate(item)); + } + else + { + item.SendInfoTo(state); + } + + if (ObjectPropertyList.Enabled && item.Parent != null) + state.Send(item.OPLPacket); + } + } + + public static Item LiftItemDupe(Item oldItem, int amount) + { + Item item; + try + { + item = (Item)ActivatorUtil.CreateInstance(oldItem.GetType()); + } + catch + { + Console.WriteLine( + "Warning: 0x{0:X}: Item must have a zero parameter constructor to be separated from a stack. '{1}'.", + oldItem.Serial.Value, + oldItem.GetType().Name + ); + return null; + } + + item.Visible = oldItem.Visible; + item.Movable = oldItem.Movable; + item.LootType = oldItem.LootType; + item.Direction = oldItem.Direction; + item.Hue = oldItem.Hue; + item.ItemID = oldItem.ItemID; + item.Location = oldItem.Location; + item.Layer = oldItem.Layer; + item.Name = oldItem.Name; + item.Weight = oldItem.Weight; + + item.Amount = oldItem.Amount - amount; + item.Map = oldItem.Map; + + oldItem.Amount = amount; + oldItem.OnAfterDuped(item); + + if (oldItem.Parent is Mobile parentMobile) + parentMobile.AddItem(item); + else if (oldItem.Parent is Item parentItem) parentItem.AddItem(item); + + item.Delta(ItemDelta.Update); + + return item; + } + + public virtual void SendDropEffect(Item item) + { + if (DragEffects && !item.Deleted) + { + var map = m_Map; + var root = item.RootParent; + + if (map != null && (root == null || root is Item)) + { + var eable = map.GetClientsInRange(m_Location); + Packet p = null; + var rootItem = root as Item; + + foreach (var ns in eable) + { + if (ns.StygianAbyss) + continue; + + if (ns.Mobile != this && ns.Mobile.CanSee(this) && ns.Mobile.InLOS(this) && ns.Mobile.CanSee(root)) + { + if (p == null) + { + IEntity trg = new Entity( + rootItem?.Serial ?? Serial.Zero, + rootItem?.Location ?? item.Location, + map + ); + + p = Packet.Acquire(new DragEffect(this, trg, item.ItemID, item.Hue, item.Amount)); + } + + ns.Send(p); + } + } + + Packet.Release(p); + + eable.Free(); + } + } + } + + public virtual bool Drop(Item to, Point3D loc) + { + var from = this; + var item = from.Holding; + + var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; + + from.Holding = null; + + if (!valid) return false; + + var bounced = true; + + item.SetLastMoved(); + + if (to == null || !item.DropToItem(from, to, loc)) + item.Bounce(from); + else + bounced = false; + + item.ClearBounce(); + + if (!bounced) + SendDropEffect(item); + + return !bounced; + } + + public virtual bool Drop(Point3D loc) + { + var from = this; + var item = from.Holding; + + var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; + + from.Holding = null; + + if (!valid) return false; + + var bounced = true; + + item.SetLastMoved(); + + if (!item.DropToWorld(from, loc)) + item.Bounce(from); + else + bounced = false; + + item.ClearBounce(); + + if (!bounced) + SendDropEffect(item); + + return !bounced; + } + + public virtual bool Drop(Mobile to, Point3D loc) + { + var from = this; + var item = from.Holding; + + var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; + + from.Holding = null; + + if (!valid) return false; + + var bounced = true; + + item.SetLastMoved(); + + if (to == null || !item.DropToMobile(from, to, loc)) + item.Bounce(from); + else + bounced = false; + + item.ClearBounce(); + + if (!bounced) + SendDropEffect(item); + + return !bounced; + } + + public virtual bool MutateSpeech(List hears, ref string text, ref object context) + { + if (Alive) + return false; + + var sb = new StringBuilder(text.Length, text.Length); + + for (var i = 0; i < text.Length; ++i) + sb.Append(text[i] != ' ' ? GhostChars.RandomElement() : ' '); + + text = sb.ToString(); + context = m_GhostMutateContext; + return true; + } + + public virtual void Manifest(TimeSpan delay) + { + Warmode = true; + + if (m_AutoManifestTimer == null) + m_AutoManifestTimer = new AutoManifestTimer(this, delay); + else + m_AutoManifestTimer.Stop(); + + m_AutoManifestTimer.Start(); + } + + public virtual bool CheckSpeechManifest() + { + if (Alive) + return false; + + var delay = AutoManifestTimeout; + + if (delay > TimeSpan.Zero && (!Warmode || m_AutoManifestTimer != null)) + { + Manifest(delay); + return true; + } + + return false; + } + + public virtual bool CheckHearsMutatedSpeech(Mobile m, object context) => + context != m_GhostMutateContext || m.Alive && !m.CanHearGhosts; + + private void AddSpeechItemsFrom(List list, Container cont) + { + for (var i = 0; i < cont.Items.Count; ++i) + { + var item = cont.Items[i]; + + if (item.HandlesOnSpeech) + list.Add(item); + + if (item is Container container) + AddSpeechItemsFrom(list, container); + } + } + + public virtual void DoSpeech(string text, int[] keywords, MessageType type, int hue) + { + if (Deleted || CommandSystem.Handle(this, text, type)) + return; + + var range = 15; + + switch (type) + { + case MessageType.Regular: + SpeechHue = hue; + break; + case MessageType.Emote: + EmoteHue = hue; + break; + case MessageType.Whisper: + WhisperHue = hue; + range = 1; + break; + case MessageType.Yell: + YellHue = hue; + range = 18; + break; + case MessageType.System: + break; + case MessageType.Label: + break; + case MessageType.Focus: + break; + case MessageType.Spell: + break; + case MessageType.Guild: + break; + case MessageType.Alliance: + break; + case MessageType.Command: + break; + case MessageType.Encoded: + break; + default: + type = MessageType.Regular; + break; + } + + var regArgs = new SpeechEventArgs(this, text, type, hue, keywords); + + EventSink.InvokeSpeech(regArgs); + Region.OnSpeech(regArgs); + OnSaid(regArgs); + + if (regArgs.Blocked) + return; + + text = regArgs.Speech; + + if (string.IsNullOrEmpty(text)) + return; + + var hears = m_Hears; + var onSpeech = m_OnSpeech; + + if (m_Map != null) + { + var eable = m_Map.GetObjectsInRange(m_Location, range); + + foreach (var o in eable) + if (o is Mobile heard) + { + if (!heard.CanSee(this) || !NoSpeechLOS && heard.Player && !heard.InLOS(this)) + continue; + + if (heard.m_NetState != null) + hears.Add(heard); + + if (heard.HandlesOnSpeech(this)) + onSpeech.Add(heard); + + for (var i = 0; i < heard.Items.Count; ++i) + { + var item = heard.Items[i]; + + if (item.HandlesOnSpeech) + onSpeech.Add(item); + + if (item is Container container) + AddSpeechItemsFrom(onSpeech, container); + } + } + else if (o is Item item) + { + if (item.HandlesOnSpeech) + onSpeech.Add(item); + + if (item is Container container) + AddSpeechItemsFrom(onSpeech, container); + } + + eable.Free(); + + object mutateContext = null; + var mutatedText = text; + SpeechEventArgs mutatedArgs = null; + + if (MutateSpeech(hears, ref mutatedText, ref mutateContext)) + mutatedArgs = new SpeechEventArgs(this, mutatedText, type, hue, Array.Empty()); + + CheckSpeechManifest(); + + ProcessDelta(); + + Packet regp = null; + Packet mutp = null; + + // TODO: Should this be sorted like onSpeech is below? + + for (var i = 0; i < hears.Count; ++i) + { + var heard = hears[i]; + + if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) + { + heard.OnSpeech(regArgs); + + var ns = heard.NetState; + + if (ns != null) + { + regp ??= Packet.Acquire(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); + + ns.Send(regp); + } + } + else + { + heard.OnSpeech(mutatedArgs); + + var ns = heard.NetState; + + if (ns != null) + { + mutp ??= Packet.Acquire( + new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, mutatedText) + ); + + ns.Send(mutp); + } + } + } + + Packet.Release(regp); + Packet.Release(mutp); + + if (onSpeech.Count > 1) + onSpeech.Sort(LocationComparer.GetInstance(this)); + + for (var i = 0; i < onSpeech.Count; ++i) + { + var obj = onSpeech[i]; + + if (obj is Mobile heard) + { + if (mutatedArgs == null || !CheckHearsMutatedSpeech(heard, mutateContext)) + heard.OnSpeech(regArgs); + else + heard.OnSpeech(mutatedArgs); + } + else + { + ((Item)obj).OnSpeech(regArgs); + } + } + + if (m_Hears.Count > 0) + m_Hears.Clear(); + + if (m_OnSpeech.Count > 0) + m_OnSpeech.Clear(); + } + } + + public static Mobile GetDamagerFrom(DamageEntry de) => de?.Damager; + + public Mobile FindMostRecentDamager(bool allowSelf) => GetDamagerFrom(FindMostRecentDamageEntry(allowSelf)); + + public DamageEntry FindMostRecentDamageEntry(bool allowSelf) + { + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + continue; + + var de = DamageEntries[i]; + + if (de.HasExpired) + DamageEntries.RemoveAt(i); + else if (allowSelf || de.Damager != this) + return de; + } + + return null; + } + + public Mobile FindLeastRecentDamager(bool allowSelf) => GetDamagerFrom(FindLeastRecentDamageEntry(allowSelf)); + + public DamageEntry FindLeastRecentDamageEntry(bool allowSelf) + { + for (var i = 0; i < DamageEntries.Count; ++i) + { + if (i < 0) + continue; + + var de = DamageEntries[i]; + + if (de.HasExpired) + { + DamageEntries.RemoveAt(i); + --i; + } + else if (allowSelf || de.Damager != this) + { + return de; + } + } + + return null; + } + + public Mobile FindMostTotalDamager(bool allowSelf) => GetDamagerFrom(FindMostTotalDamageEntry(allowSelf)); + + public DamageEntry FindMostTotalDamageEntry(bool allowSelf) + { + DamageEntry mostTotal = null; + + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + continue; + + var de = DamageEntries[i]; + + if (de.HasExpired) + DamageEntries.RemoveAt(i); + else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven > mostTotal.DamageGiven)) + mostTotal = de; + } + + return mostTotal; + } + + public Mobile FindLeastTotalDamager(bool allowSelf) => GetDamagerFrom(FindLeastTotalDamageEntry(allowSelf)); + + public DamageEntry FindLeastTotalDamageEntry(bool allowSelf) + { + DamageEntry mostTotal = null; + + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + continue; + + var de = DamageEntries[i]; + + if (de.HasExpired) + DamageEntries.RemoveAt(i); + else if ((allowSelf || de.Damager != this) && (mostTotal == null || de.DamageGiven < mostTotal.DamageGiven)) + mostTotal = de; + } + + return mostTotal; + } + + public DamageEntry FindDamageEntryFor(Mobile m) + { + for (var i = DamageEntries.Count - 1; i >= 0; --i) + { + if (i >= DamageEntries.Count) + continue; + + var de = DamageEntries[i]; + + if (de.HasExpired) + DamageEntries.RemoveAt(i); + else if (de.Damager == m) + return de; + } + + return null; + } + + public virtual Mobile GetDamageMaster(Mobile damagee) => null; + + public virtual DamageEntry RegisterDamage(int amount, Mobile from) + { + var de = FindDamageEntryFor(from) ?? new DamageEntry(from); + + de.DamageGiven += amount; + de.LastDamage = DateTime.UtcNow; + + DamageEntries.Remove(de); + DamageEntries.Add(de); + + var master = from.GetDamageMaster(this); + + if (master != null) + { + var list = de.Responsible; + + if (list == null) + de.Responsible = list = new List(); + + var resp = list.FirstOrDefault(check => check.Damager == master); + + if (resp == null) + list.Add(resp = new DamageEntry(master)); + + resp.DamageGiven += amount; + resp.LastDamage = DateTime.UtcNow; + } + + return de; + } + + /// + /// Overridable. Virtual event invoked when the Mobile is damaged. It is called before + /// hit points are lowered or the Mobile is killed. + /// + /// + /// + /// + public virtual void OnDamage(int amount, Mobile from, bool willKill) + { + } + + public virtual void Damage(int amount) + { + Damage(amount, null); + } + + public virtual bool CanBeDamaged() => !m_Blessed; + + public virtual void Damage(int amount, Mobile from) + { + Damage(amount, from, true); + } + + public virtual void Damage(int amount, Mobile from, bool informMount) + { + if (!CanBeDamaged() || Deleted) + return; + + if (!Region.OnDamage(this, ref amount)) + return; + + if (amount > 0) + { + var oldHits = Hits; + var newHits = oldHits - amount; + + m_Spell?.OnCasterHurt(); + + // if (m_Spell != null && m_Spell.State == SpellState.Casting) + // m_Spell.Disturb( DisturbType.Hurt, false, true ); + + if (from != null) + RegisterDamage(amount, from); + + DisruptiveAction(); + + Paralyzed = false; + + switch (VisibleDamageType) + { + case VisibleDamageType.Related: + { + SendVisibleDamageRelated(from, amount); + break; + } + case VisibleDamageType.Everyone: + { + SendVisibleDamageEveryone(amount); + break; + } + case VisibleDamageType.Selective: + { + SendVisibleDamageSelective(from, amount); + break; + } + } + + OnDamage(amount, from, newHits < 0); + + if (informMount) + Mount?.OnRiderDamaged(amount, from, newHits < 0); + + if (newHits < 0) + { + LastKiller = from; + + Hits = 0; + + if (oldHits >= 0) + Kill(); + } + else + { + Hits = newHits; + } + } + } + + public void SendVisibleDamageRelated(Mobile from, int amount) + { + NetState ourState = m_NetState, theirState = from?.m_NetState; + + if (ourState == null) + { + var master = GetDamageMaster(from); + + if (master != null) + ourState = master.m_NetState; + } + + if (theirState == null && from != null) + { + var master = from.GetDamageMaster(this); + + if (master != null) + theirState = master.m_NetState; + } + + if (amount > 0 && (ourState != null || theirState != null)) + { + Packet p = null; // = new DamagePacket( this, amount ); + + if (ourState != null) + { + p = ourState.DamagePacket + ? Packet.Acquire(new DamagePacket(Serial, amount)) + : Packet.Acquire(new DamagePacketOld(Serial, amount)); + + ourState.Send(p); + } + + if (theirState != null && theirState != ourState) + { + var newPacket = theirState.DamagePacket; + + if (newPacket && !(p is DamagePacket)) + { + Packet.Release(p); + p = Packet.Acquire(new DamagePacket(Serial, amount)); + } + else if (!newPacket && !(p is DamagePacketOld)) + { + Packet.Release(p); + p = Packet.Acquire(new DamagePacketOld(Serial, amount)); + } + + theirState.Send(p); + } + + Packet.Release(p); + } + } + + public void SendVisibleDamageEveryone(int amount) + { + if (amount < 0) + return; + + var map = m_Map; + + if (map == null) + return; + + var eable = map.GetClientsInRange(m_Location); + + Packet pNew = null; + Packet pOld = null; + + foreach (var ns in eable) + if (ns.Mobile.CanSee(this)) + { + if (ns.DamagePacket) + { + pNew ??= Packet.Acquire(new DamagePacket(Serial, amount)); + + ns.Send(pNew); + } + else + { + pOld ??= Packet.Acquire(new DamagePacketOld(Serial, amount)); + + ns.Send(pOld); + } + } + + Packet.Release(pNew); + Packet.Release(pOld); + + eable.Free(); + } + + public void SendVisibleDamageSelective(Mobile from, int amount) + { + NetState ourState = m_NetState, theirState = from?.m_NetState; + + var damager = from; + var damaged = this; + + if (ourState == null) + { + var master = GetDamageMaster(from); + + if (master != null) + { + damaged = master; + ourState = master.m_NetState; + } + } + + if (!damaged.ShowVisibleDamage) + return; + + if (theirState == null && from != null) + { + var master = from.GetDamageMaster(this); + + if (master != null) + { + damager = master; + theirState = master.m_NetState; + } + } + + if (amount > 0 && (ourState != null || theirState != null)) + { + if (damaged.CanSeeVisibleDamage && ourState != null) + { + if (ourState.DamagePacket) + ourState.Send(new DamagePacket(Serial, amount)); + else + ourState.Send(new DamagePacketOld(Serial, amount)); + } + + if (theirState != null && theirState != ourState && damager.CanSeeVisibleDamage) + { + if (theirState.DamagePacket) + theirState.Send(new DamagePacket(Serial, amount)); + else + theirState.Send(new DamagePacketOld(Serial, amount)); + } + } + } + + public void Heal(int amount) + { + Heal(amount, this, true); + } + + public void Heal(int amount, Mobile from) + { + Heal(amount, from, true); + } + + public void Heal(int amount, Mobile from, bool message) + { + if (!Alive || IsDeadBondedPet) + return; + + if (!Region.OnHeal(this, ref amount)) + return; + + OnHeal(ref amount, from); + + if (Hits + amount > HitsMax) amount = HitsMax - Hits; + + Hits += amount; + + if (message && amount > 0) + m_NetState?.Send( + new MessageLocalizedAffix( + Serial.MinusOne, + -1, + MessageType.Label, + 0x3B2, + 3, + 1008158, + "", + AffixType.Append | AffixType.System, + amount.ToString(), + "" + ) + ); + } + + public virtual void OnHeal(ref int amount, Mobile from) + { + } + + public virtual void Deserialize(IGenericReader reader) + { + var version = reader.ReadInt(); + + switch (version) + { + case 32: + { + // Removed StuckMenu + goto case 31; + } + case 31: + { + LastStrGain = reader.ReadDeltaTime(); + LastIntGain = reader.ReadDeltaTime(); + LastDexGain = reader.ReadDeltaTime(); + + goto case 30; + } + case 30: + { + var hairflag = reader.ReadByte(); + + if ((hairflag & 0x01) != 0) + m_Hair = new HairInfo(reader); + if ((hairflag & 0x02) != 0) + m_FacialHair = new FacialHairInfo(reader); + + goto case 29; + } + case 29: + { + m_Race = reader.ReadRace(); + goto case 28; + } + case 28: + { + if (version <= 30) + LastStatGain = reader.ReadDeltaTime(); + + goto case 27; + } + case 27: + { + m_TithingPoints = reader.ReadInt(); + + goto case 26; + } + case 26: + case 25: + case 24: + { + Corpse = reader.ReadItem() as Container; + + goto case 23; + } + case 23: + { + CreationTime = reader.ReadDateTime(); + + goto case 22; + } + case 22: // Just removed followers + case 21: + { + Stabled = reader.ReadStrongMobileList(); + + goto case 20; + } + case 20: + { + CantWalk = reader.ReadBool(); + + goto case 19; + } + case 19: // Just removed variables + case 18: + { + Virtues = new VirtueInfo(reader); + + goto case 17; + } + case 17: + { + Thirst = reader.ReadInt(); + BAC = reader.ReadInt(); + + goto case 16; + } + case 16: + { + m_ShortTermMurders = reader.ReadInt(); + + if (version <= 24) + { + reader.ReadDateTime(); + reader.ReadDateTime(); + } + + goto case 15; + } + case 15: + { + if (version < 22) + reader.ReadInt(); // followers + + m_FollowersMax = reader.ReadInt(); + + goto case 14; + } + case 14: + { + MagicDamageAbsorb = reader.ReadInt(); + + goto case 13; + } + case 13: + { + GuildFealty = reader.ReadMobile(); + + goto case 12; + } + case 12: + { + m_Guild = reader.ReadGuild(); + + goto case 11; + } + case 11: + { + m_DisplayGuildTitle = reader.ReadBool(); + + goto case 10; + } + case 10: + { + CanSwim = reader.ReadBool(); + + goto case 9; + } + case 9: + { + Squelched = reader.ReadBool(); + + goto case 8; + } + case 8: + { + m_Holding = reader.ReadItem(); + + goto case 7; + } + case 7: + { + m_VirtualArmor = reader.ReadInt(); + + goto case 6; + } + case 6: + { + BaseSoundID = reader.ReadInt(); + + goto case 5; + } + case 5: + { + DisarmReady = reader.ReadBool(); + StunReady = reader.ReadBool(); + + goto case 4; + } + case 4: + { + if (version <= 25) Poison.Deserialize(reader); + + goto case 3; + } + case 3: + { + m_StatCap = reader.ReadInt(); + + goto case 2; + } + case 2: + { + NameHue = reader.ReadInt(); + + goto case 1; + } + case 1: + { + m_Hunger = reader.ReadInt(); + + goto case 0; + } + case 0: + { + if (version < 21) + Stabled = new List(); + + if (version < 18) + Virtues = new VirtueInfo(); + + if (version < 11) + m_DisplayGuildTitle = true; + + if (version < 3) + m_StatCap = 225; + + if (version < 15) + { + m_Followers = 0; + m_FollowersMax = 5; + } + + m_Location = reader.ReadPoint3D(); + m_Body = new Body(reader.ReadInt()); + m_Name = reader.ReadString(); + m_GuildTitle = reader.ReadString(); + m_Criminal = reader.ReadBool(); + m_Kills = reader.ReadInt(); + SpeechHue = reader.ReadInt(); + EmoteHue = reader.ReadInt(); + WhisperHue = reader.ReadInt(); + YellHue = reader.ReadInt(); + m_Language = reader.ReadString(); + m_Female = reader.ReadBool(); + m_Warmode = reader.ReadBool(); + m_Hidden = reader.ReadBool(); + m_Direction = (Direction)reader.ReadByte(); + m_Hue = reader.ReadInt(); + m_Str = reader.ReadInt(); + m_Dex = reader.ReadInt(); + m_Int = reader.ReadInt(); + m_Hits = reader.ReadInt(); + m_Stam = reader.ReadInt(); + m_Mana = reader.ReadInt(); + m_Map = reader.ReadMap(); + m_Blessed = reader.ReadBool(); + m_Fame = reader.ReadInt(); + m_Karma = reader.ReadInt(); + m_AccessLevel = (AccessLevel)reader.ReadByte(); + + Skills = new Skills(this, reader); + + Items = reader.ReadStrongItemList(); + + m_Player = reader.ReadBool(); + m_Title = reader.ReadString(); + Profile = reader.ReadString(); + ProfileLocked = reader.ReadBool(); + + if (version <= 18) + { + reader.ReadInt(); + reader.ReadInt(); + reader.ReadInt(); + } + + AutoPageNotify = reader.ReadBool(); + + LogoutLocation = reader.ReadPoint3D(); + LogoutMap = reader.ReadMap(); + + m_StrLock = (StatLockType)reader.ReadByte(); + m_DexLock = (StatLockType)reader.ReadByte(); + m_IntLock = (StatLockType)reader.ReadByte(); + + StatMods = new List(); + SkillMods = new List(); + + if (version < 32) + if (reader.ReadBool()) + { + var count = reader.ReadInt(); + for (var i = 0; i < count; ++i) reader.ReadDateTime(); + } + + if (m_Player && m_Map != Map.Internal) + { + LogoutLocation = m_Location; + LogoutMap = m_Map; + + m_Map = Map.Internal; + } + + m_Map?.OnEnter(this); + + if (m_Criminal) + { + m_ExpireCriminal ??= new ExpireCriminalTimer(this); + + m_ExpireCriminal.Start(); + } + + if (ShouldCheckStatTimers) + CheckStatTimers(); + + if (!m_Player && m_Dex <= 100 && m_CombatTimer != null) + m_CombatTimer.Priority = TimerPriority.FiftyMS; + else if (m_CombatTimer != null) + m_CombatTimer.Priority = TimerPriority.EveryTick; + + UpdateRegion(); + + UpdateResistances(); + + break; + } + } + + if (!m_Player) + Utility.Intern(ref m_Name); + + Utility.Intern(ref m_Title); + Utility.Intern(ref m_Language); + } + + public void ConvertHair() + { + Item hair; + + if ((hair = FindItemOnLayer(Layer.Hair)) != null) + { + HairItemID = hair.ItemID; + HairHue = hair.Hue; + hair.Delete(); + } + + if ((hair = FindItemOnLayer(Layer.FacialHair)) != null) + { + FacialHairItemID = hair.ItemID; + FacialHairHue = hair.Hue; + hair.Delete(); + } + } + + public virtual void CheckStatTimers() + { + if (Deleted) + return; + + if (Hits < HitsMax) + { + if (CanRegenHits) + { + m_HitsTimer ??= new HitsTimer(this); + + m_HitsTimer.Start(); + } + else + { + m_HitsTimer?.Stop(); + } + } + else + { + Hits = HitsMax; + } + + if (Stam < StamMax) + { + if (CanRegenStam) + { + m_StamTimer ??= new StamTimer(this); + + m_StamTimer.Start(); + } + else + { + m_StamTimer?.Stop(); + } + } + else + { + Stam = StamMax; + } + + if (Mana < ManaMax) + { + if (CanRegenMana) + { + m_ManaTimer ??= new ManaTimer(this); + + m_ManaTimer.Start(); + } + else + { + m_ManaTimer?.Stop(); + } + } + else + { + Mana = ManaMax; + } + } + + public static string GetAccessLevelName(AccessLevel level) => m_AccessLevelNames[(int)level]; + + public virtual bool CanPaperdollBeOpenedBy(Mobile from) => Body.IsHuman || Body.IsGhost || IsBodyMod; + + public virtual void GetChildContextMenuEntries(Mobile from, List list, Item item) + { + } + + public virtual void GetContextMenuEntries(Mobile from, List list) + { + if (Deleted) + return; + + if (CanPaperdollBeOpenedBy(from)) + list.Add(new PaperdollEntry(this)); + + if (from == this && Backpack != null && CanSee(Backpack) && CheckAlive(false)) + list.Add(new OpenBackpackEntry(this)); + } + + public void Internalize() + { + Map = Map.Internal; + } + + /// + /// Overridable. Virtual event invoked when is added from the Mobile, + /// such + /// as when it is equipped. + /// + /// + /// + public virtual void OnItemAdded(Item item) + { + } + + /// + /// Overridable. Virtual event invoked when is removed from the + /// Mobile. + /// + /// + /// + public virtual void OnItemRemoved(Item item) + { + } + + /// + /// Overridable. Virtual event invoked when is becomes a child of the Mobile; it's worn or + /// contained + /// at some level of the Mobile's backpack or bank box + /// + /// + /// + public virtual void OnSubItemAdded(Item item) + { + } + + /// + /// Overridable. Virtual event invoked when is removed from the Mobile, its + /// backpack, or its bank box. + /// + /// + /// + public virtual void OnSubItemRemoved(Item item) + { + } + + public virtual void OnItemBounceCleared(Item item) + { + } + + public virtual void OnSubItemBounceCleared(Item item) + { + } + + public void AddItem(Item item) + { + if (item?.Deleted != false) + return; + + if (item.Parent == this) + return; + if (item.Parent is Mobile parentMobile) + parentMobile.RemoveItem(item); + else if (item.Parent is Item parentItem) + parentItem.RemoveItem(item); + else + item.SendRemovePacket(); + + item.Parent = this; + item.Map = m_Map; + + Items.Add(item); + + if (!item.IsVirtualItem) + { + UpdateTotal(item, TotalType.Gold, item.TotalGold); + UpdateTotal(item, TotalType.Items, item.TotalItems + 1); + UpdateTotal(item, TotalType.Weight, item.TotalWeight + item.PileWeight); + } + + item.Delta(ItemDelta.Update); + + item.OnAdded(this); + OnItemAdded(item); + + if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || + item.PoisonResistance != 0 || item.EnergyResistance != 0) + UpdateResistances(); + } + + public void RemoveItem(Item item) + { + if (item == null || Items == null) + return; + + if (Items.Contains(item)) + { + item.SendRemovePacket(); + + // int oldCount = m_Items.Count; + + Items.Remove(item); + + if (!item.IsVirtualItem) + { + UpdateTotal(item, TotalType.Gold, -item.TotalGold); + UpdateTotal(item, TotalType.Items, -(item.TotalItems + 1)); + UpdateTotal(item, TotalType.Weight, -(item.TotalWeight + item.PileWeight)); + } + + item.Parent = null; + + item.OnRemoved(this); + OnItemRemoved(item); + + if (item.PhysicalResistance != 0 || item.FireResistance != 0 || item.ColdResistance != 0 || + item.PoisonResistance != 0 || item.EnergyResistance != 0) + UpdateResistances(); + } + } + + public virtual void Animate(int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay) + { + var map = m_Map; + + if (map == null) + return; + ProcessDelta(); + + Packet p = null; + // Packet pNew = null; + + var eable = map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state.Mobile.CanSee(this)) + { + state.Mobile.ProcessDelta(); + + // if (state.StygianAbyss) { + // if (pNew == null) + // pNew = Packet.Acquire(new NewMobileAnimation(this.Serial, action, frameCount, delay)); + + // state.Send(pNew); + // } else { + if (p == null) + { + if (Body.IsGargoyle) + { + frameCount = 10; + + if (Flying) + { + if (action >= 9 && action <= 11) + action = 71; + else if (action >= 12 && action <= 14) + action = 72; + else if (action == 20) + action = 77; + else if (action == 31) + action = 71; + else if (action == 34) + action = 78; + else if (action >= 200 && action <= 259) + action = 75; + else if (action >= 260 && action <= 270) action = 75; + } + else + { + if (action >= 200 && action <= 259) + action = 17; + else if (action >= 260 && action <= 270) action = 16; + } + } + + p = Packet.Acquire( + new MobileAnimation( + Serial, + action, + frameCount, + repeatCount, + forward, + repeat, + delay + ) + ); + } + + state.Send(p); + // } + } + + Packet.Release(p); + // Packet.Release( pNew ); + + eable.Free(); + } + + public void SendSound(int soundID) + { + if (soundID != -1 && m_NetState != null) + Send(new PlaySound(soundID, this)); + } + + public void SendSound(int soundID, IPoint3D p) + { + if (soundID != -1 && m_NetState != null) + Send(new PlaySound(soundID, p)); + } + + public void PlaySound(int soundID) + { + if (soundID == -1 || m_Map == null) + return; + + var p = Packet.Acquire(new PlaySound(soundID, this)); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state.Mobile.CanSee(this)) + state.Send(p); + + Packet.Release(p); + + eable.Free(); + } + + public virtual void OnAccessLevelChanged(AccessLevel oldLevel) + { + } + + public virtual void OnFameChange(int oldValue) + { + } + + public virtual void OnKarmaChange(int oldValue) + { + } + + // Mobile did something which should unhide him + public virtual void RevealingAction() + { + if (m_Hidden && m_AccessLevel == AccessLevel.Player) + Hidden = false; + + DisruptiveAction(); // Anything that unhides you will also distrupt meditation + } + + public void SendRemovePacket() + { + SendRemovePacket(true); + } + + public void SendRemovePacket(bool everyone) + { + if (m_Map == null) + return; + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state != m_NetState && (everyone || !state.Mobile.CanSee(this))) + state.Send(RemovePacket); + + eable.Free(); + } + + public void ClearScreen() + { + if (m_Map == null || m_NetState == null) + return; + + var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); + + foreach (var o in eable) + if (o is Mobile m) + { + if (m != this && Utility.InUpdateRange(m_Location, m.m_Location)) + m_NetState.Send(m.RemovePacket); + } + else if (o is Item item) + { + if (InRange(item.Location, item.GetUpdateRange(this))) + m_NetState.Send(item.RemovePacket); + } + + eable.Free(); + } + + public bool Send(Packet p) => Send(p, false); + + public bool Send(Packet p, bool throwOnOffline) + { + if (m_NetState != null) + { + m_NetState.Send(p); + return true; + } + + if (throwOnOffline) + throw new MobileNotConnectedException(this, "Packet could not be sent."); + + return false; + } + + /// + /// Overridable. Event invoked before the Mobile says something. + /// + /// + public virtual void OnSaid(SpeechEventArgs e) + { + if (Squelched) + { + if (Core.ML) + SendLocalizedMessage(500168); // You can not say anything, you have been muted. + else + SendMessage("You can not say anything, you have been squelched."); // Cliloc ITSELF changed during ML. + + e.Blocked = true; + } + + if (!e.Blocked) + RevealingAction(); + } + + public virtual bool HandlesOnSpeech(Mobile from) => false; + + /// + /// Overridable. Virtual event invoked when the Mobile hears speech. This event will only be invoked if + /// returns true. + /// + /// + public virtual void OnSpeech(SpeechEventArgs e) + { + } + + public void SendEverything() + { + var ns = m_NetState; + + if (m_Map != null && ns != null) + { + var eable = m_Map.GetObjectsInRange(m_Location, Core.GlobalMaxUpdateRange); + + foreach (var o in eable) + if (o is Item item) + { + if (CanSee(item) && InRange(item.Location, item.GetUpdateRange(this))) + item.SendInfoTo(ns); + } + else if (o is Mobile m) + { + if (CanSee(m) && Utility.InUpdateRange(m_Location, m.m_Location)) + { + ns.Send(MobileIncoming.Create(ns, this, m)); + + if (ns.StygianAbyss) + { + if (m.Poisoned) + ns.Send(new HealthbarPoison(m)); + + if (m.Blessed || m.YellowHealthbar) + ns.Send(new HealthbarYellow(m)); + } + + if (m.IsDeadBondedPet) + ns.Send(new BondedStatus(m.Serial, true)); + + if (ObjectPropertyList.Enabled) ns.Send(m.OPLPacket); + } + } + + eable.Free(); + } + } + + public void UpdateRegion() + { + if (Deleted) + return; + + var newRegion = Region.Find(m_Location, m_Map); + + if (newRegion != m_Region) + { + Region.OnRegionChange(this, m_Region, newRegion); + + m_Region = newRegion; + OnRegionChange(m_Region, newRegion); + } + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + protected virtual void OnMapChange(Map oldMap) + { + } + + public void SetDirection(Direction dir) + { + m_Direction = dir; + } + + public virtual int GetSeason() => m_Map?.Season ?? 1; + + public virtual int GetPacketFlags() + { + var flags = 0x0; + + if (m_Paralyzed || m_Frozen) + flags |= 0x01; + + if (m_Female) + flags |= 0x02; + + if (m_Flying) + flags |= 0x04; + + if (m_Blessed || m_YellowHealthbar) + flags |= 0x08; + + if (m_Warmode) + flags |= 0x40; + + if (m_Hidden) + flags |= 0x80; + + return flags; + } + + // Pre-7.0.0.0 Packet Flags + public virtual int GetOldPacketFlags() + { + var flags = 0x0; + + if (m_Paralyzed || m_Frozen) + flags |= 0x01; + + if (m_Female) + flags |= 0x02; + + if (m_Poison != null) + flags |= 0x04; + + if (m_Blessed || m_YellowHealthbar) + flags |= 0x08; + + if (m_Warmode) + flags |= 0x40; + + if (m_Hidden) + flags |= 0x80; + + return flags; + } + + public virtual void OnGenderChanged(bool oldFemale) + { + } + + public virtual void ToggleFlying() + { + } + + /// + /// Overridable. Virtual event invoked after the Warmode property has changed. + /// + public virtual void OnWarmodeChanged() + { + } + + public virtual void OnHiddenChanged() + { + AllowedStealthSteps = 0; + + if (m_Map == null) + return; + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (!state.Mobile.CanSee(this)) + { + state.Send(RemovePacket); + } + else + { + state.Send(MobileIncoming.Create(state, state.Mobile, this)); + + if (IsDeadBondedPet) + state.Send(new BondedStatus(Serial, true)); + + if (ObjectPropertyList.Enabled) state.Send(OPLPacket); + } + + eable.Free(); + } + + public virtual void OnConnected() + { + } + + public virtual void OnDisconnected() + { + } + + public virtual void OnNetStateChanged() + { + } + + public virtual bool CanSee(object o) + { + if (o is Item item) + return CanSee(item); + + if (o is Mobile mobile) + return CanSee(mobile); + + return true; + } + + public virtual bool CanSee(Item item) + { + if (m_Map == Map.Internal) + return false; + if (item.Map == Map.Internal) + return false; + + if (item.Parent != null) + { + if (item.Parent is Item parent) + { + if (!(CanSee(parent) && parent.IsChildVisibleTo(this, item))) + return false; + } + else if (item.Parent is Mobile mobile) + { + if (!CanSee(mobile)) + return false; + } + } + + if (item is BankBox box && m_AccessLevel <= AccessLevel.Counselor && (box.Owner != this || !box.Opened)) + return false; + + if (item is SecureTradeContainer container) + { + var trade = container.Trade; + + if (trade != null && trade.From.Mobile != this && trade.To.Mobile != this) + return false; + } + + return !item.Deleted && item.Map == m_Map && (item.Visible || m_AccessLevel > AccessLevel.Counselor); + } + + public virtual bool CanSee(Mobile m) + { + if (Deleted || m.Deleted || m_Map == Map.Internal || m.m_Map == Map.Internal) + return false; + + return this == m || m.m_Map == m_Map && + (!m.Hidden || m_AccessLevel != AccessLevel.Player && + (m_AccessLevel >= m.AccessLevel || m_AccessLevel >= AccessLevel.Administrator)) && + (m.Alive || Core.SE && Skills.SpiritSpeak.Value >= 100.0 || !Alive || + m_AccessLevel > AccessLevel.Player || m.Warmode); + } + + public virtual bool CanBeRenamedBy(Mobile from) => + from.AccessLevel >= AccessLevel.GameMaster && from.m_AccessLevel > m_AccessLevel; + + public virtual void OnGuildTitleChange(string oldTitle) + { + } + + public virtual void OnAfterNameChange(string oldName, string newName) + { + } + + public virtual void OnGuildChange(BaseGuild oldGuild) + { + } + + public virtual int SafeBody(int body) + { + var delta = -1; + + for (var i = 0; delta < 0 && i < m_InvalidBodies.Length; ++i) + delta = m_InvalidBodies[i] - body; + + return delta != 0 ? body : 0; + } + + public virtual void FreeCache() + { + StaticPacketHandlers.FreeRemoveItemPacket(this); + StaticPacketHandlers.FreeOPLInfoPacket(this); + ReleaseOPLPacket(); + } + + public ObjectPropertyList NewObjectPropertyList() + { + var list = new ObjectPropertyList(this); + + GetProperties(list); + + list.Terminate(); + list.SetStatic(); + return list; + } + + public void ClearProperties() + { + ReleaseOPLPacket(); + StaticPacketHandlers.FreeOPLInfoPacket(this); + } + + public void InvalidateProperties() + { + if (!ObjectPropertyList.Enabled) + return; + + if (m_Map != null && m_Map != Map.Internal && !World.Loading) + { + var oldList = m_PropertyList; + m_PropertyList = null; + + if (oldList != null && oldList.Hash != PropertyList.Hash) + { + StaticPacketHandlers.FreeOPLInfoPacket(this); + Delta(MobileDelta.Properties); + } + } + else + { + ClearProperties(); + } + } + + public virtual void SetLocation(Point3D newLocation, bool isTeleport) + { + if (Deleted) + return; + + var oldLocation = m_Location; + + if (oldLocation == newLocation) + return; + + m_Location = newLocation; + UpdateRegion(); + + var box = FindBankNoCreate(); + + if (box?.Opened == true) + box.Close(); + + m_NetState?.ValidateAllTrades(); + + m_Map?.OnMove(oldLocation, this); + + if (isTeleport && m_NetState != null && (!m_NetState.HighSeas || !NoMoveHS)) + { + m_NetState.Sequence = 0; + + if (m_NetState.StygianAbyss) + m_NetState.Send(new MobileUpdate(this)); + else + m_NetState.Send(new MobileUpdateOld(this)); + + ClearFastwalkStack(); + } + + var map = m_Map; + + if (map != null) + { + // First, send a remove message to everyone who can no longer see us. (inOldRange && !inNewRange) + + var eable = map.GetClientsInRange(oldLocation); + + foreach (var ns in eable) + if (ns != m_NetState && !Utility.InUpdateRange(newLocation, ns.Mobile.Location)) + ns.Send(RemovePacket); + + eable.Free(); + + var ourState = m_NetState; + + // Check to see if we are attached to a client + if (ourState != null) + { + var eeable = map.GetObjectsInRange(newLocation, Core.GlobalMaxUpdateRange); + + // We are attached to a client, so it's a bit more complex. We need to send new items and people to ourself, and ourself to other clients + + foreach (var o in eeable) + if (o is Item item) + { + var range = item.GetUpdateRange(this); + var loc = item.Location; + + if (!Utility.InRange(oldLocation, loc, range) && Utility.InRange(newLocation, loc, range) && + CanSee(item)) + item.SendInfoTo(ourState); + } + else if (o != this && o is Mobile m) + { + if (!Utility.InUpdateRange(newLocation, m.m_Location)) + continue; + + var inOldRange = Utility.InUpdateRange(oldLocation, m.m_Location); + + if (m.m_NetState != null && + (isTeleport && (!m.m_NetState.HighSeas || !NoMoveHS) || !inOldRange) && m.CanSee(this)) + { + m.m_NetState.Send(MobileIncoming.Create(m.m_NetState, m, this)); + + if (m.m_NetState.StygianAbyss) + { + // if (m_Poison != null) + m.m_NetState.Send(new HealthbarPoison(this)); + + // if (m_Blessed || m_YellowHealthbar) + m.m_NetState.Send(new HealthbarYellow(this)); + } + + if (IsDeadBondedPet) + m.m_NetState.Send(new BondedStatus(Serial, true)); + + if (ObjectPropertyList.Enabled) m.m_NetState.Send(OPLPacket); + } + + if (inOldRange || !CanSee(m)) + continue; + + ourState.Send(MobileIncoming.Create(ourState, this, m)); + + if (ourState.StygianAbyss) + { + // if (m.Poisoned) + ourState.Send(new HealthbarPoison(m)); + + // if (m.Blessed || m.YellowHealthbar) + ourState.Send(new HealthbarYellow(m)); + } + + if (m.IsDeadBondedPet) + ourState.Send(new BondedStatus(m.Serial, true)); + + if (ObjectPropertyList.Enabled) ourState.Send(m.OPLPacket); + } + + eeable.Free(); + } + else + { + eable = map.GetClientsInRange(newLocation); + + // We're not attached to a client, so simply send an Incoming + foreach (var ns in eable) + if ((isTeleport && (!ns.HighSeas || !NoMoveHS) || + !Utility.InUpdateRange(oldLocation, ns.Mobile.Location)) && ns.Mobile.CanSee(this)) + { + ns.Send(MobileIncoming.Create(ns, ns.Mobile, this)); + + if (ns.StygianAbyss) + { + // if (m_Poison != null) + ns.Send(new HealthbarPoison(this)); + + // if (m_Blessed || m_YellowHealthbar) + ns.Send(new HealthbarYellow(this)); + } + + if (IsDeadBondedPet) + ns.Send(new BondedStatus(Serial, true)); + + if (ObjectPropertyList.Enabled) ns.Send(OPLPacket); + } + + eable.Free(); + } + } + + OnLocationChange(oldLocation); + + Region.OnLocationChanged(this, oldLocation); + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + protected virtual void OnLocationChange(Point3D oldLocation) + { + } + + public bool HasFreeHand() => FindItemOnLayer(Layer.TwoHanded) == null; + + public virtual IWeapon GetDefaultWeapon() => DefaultWeapon; + + public BankBox FindBankNoCreate() + { + if (m_BankBox?.Deleted != false || m_BankBox.Parent != this) + m_BankBox = FindItemOnLayer(Layer.Bank) as BankBox; + + return m_BankBox; + } + + public Item FindItemOnLayer(Layer layer) + { + var eq = Items; + var count = eq.Count; + + for (var i = 0; i < count; ++i) + { + var item = eq[i]; + + if (!item.Deleted && item.Layer == layer) return item; + } + + return null; + } + + public void SendIncomingPacket() + { + if (m_Map == null) + return; + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state.Mobile.CanSee(this)) + { + state.Send(MobileIncoming.Create(state, state.Mobile, this)); + + if (state.StygianAbyss) + { + if (m_Poison != null) + state.Send(new HealthbarPoison(this)); + + if (m_Blessed || m_YellowHealthbar) + state.Send(new HealthbarYellow(this)); + } + + if (IsDeadBondedPet) + state.Send(new BondedStatus(Serial, true)); + + if (ObjectPropertyList.Enabled) state.Send(OPLPacket); + } + + eable.Free(); + } + + public bool PlaceInBackpack(Item item) + { + if (item.Deleted) + return false; + + return Backpack?.TryDropItem(this, item, false) == true; + } + + public bool AddToBackpack(Item item) + { + if (item.Deleted) + return false; + + if (!PlaceInBackpack(item)) + { + var loc = m_Location; + var map = m_Map; + + if ((map == null || map == Map.Internal) && LogoutMap != null) + { + loc = LogoutLocation; + map = LogoutMap; + } + + item.MoveToWorld(loc, map); + return false; + } + + return true; + } + + public virtual bool CheckLift(Mobile from, Item item, ref LRReason reject) => true; + + public virtual bool CheckNonlocalLift(Mobile from, Item item) => + from == this || from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster; + + public virtual bool CheckTrade( + Mobile to, Item item, SecureTradeContainer cont, bool message, bool checkItems, + int plusItems, int plusWeight + ) => + true; + + public virtual bool OpenTrade(Mobile from, Item offer = null) + { + if (!from.Player || !Player || !from.Alive || !Alive) return false; + + var ourState = m_NetState; + var theirState = from.m_NetState; + + if (ourState == null || theirState == null) return false; + + var cont = theirState.FindTradeContainer(this); + + if (!from.CheckTrade(this, offer, cont, true, true, 0, 0)) return false; + + cont ??= theirState.AddTrade(ourState); + + if (offer != null) cont.DropItem(offer); + + return true; + } + + /// + /// Overridable. Event invoked when a Mobile () drops an + /// + /// + /// + /// onto the Mobile. + /// + public virtual bool OnDragDrop(Mobile from, Item dropped) + { + if (from == this) + { + var pack = Backpack; + return pack != null && dropped.DropToItem(from, pack, new Point3D(-1, -1, 0)); + } + + return from.InRange(Location, 2) && OpenTrade(from, dropped); + } + + public virtual bool CheckEquip(Item item) + { + for (var i = 0; i < Items.Count; ++i) + if (Items[i].CheckConflictingLayer(this, item, item.Layer) || + item.CheckConflictingLayer(this, Items[i], Items[i].Layer)) + return false; + + return true; + } + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to wear . + /// + /// True if the request is accepted, false if otherwise. + public virtual bool OnEquip(Item item) + { + // For some reason OSI allows equipping quest items, but they are unmarked in the process + if (item.QuestItem) + { + item.QuestItem = false; + SendLocalizedMessage( + 1074769 + ); // An item must be in your backpack (and not in a container within) to be toggled as a quest item. + } + + return true; + } + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to lift . + /// + /// True if the lift is allowed, false if otherwise. + /// + /// The following example demonstrates usage. It will disallow any attempts to pick up a pick axe if the Mobile does not + /// have + /// enough strength. + /// + /// public override bool OnDragLift( Item item ) + /// { + /// if (item is Pickaxe && this.Str < 60) + /// { + /// SendMessage( "That is too heavy for you to lift." ); + /// return false; + /// } + /// + /// return base.OnDragLift( item ); + /// } + /// + public virtual bool OnDragLift(Item item) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop into a + /// + /// + /// + /// . + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemInto(Item item, Container container, Point3D loc) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop directly onto another + /// , . This is the case of stacking items. + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemOnto(Item item, Item target) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop into another + /// , . The target item is most likely a . + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemToItem(Item item, Item target, Point3D loc) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to give to a Mobile ( + /// ). + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemToMobile(Item item, Mobile target) => true; + + /// + /// Overridable. Virtual event invoked when the Mobile attempts to drop to the world at a + /// + /// + /// + /// . + /// + /// True if the drop is allowed, false if otherwise. + public virtual bool OnDroppedItemToWorld(Item item, Point3D location) => true; + + /// + /// Overridable. Virtual event when successfully uses while it's on this + /// Mobile. + /// + /// + public virtual void OnItemUsed(Mobile from, Item item) + { + } + + public virtual bool CheckNonlocalDrop(Mobile from, Item item, Item target) => + from == this || from.AccessLevel > AccessLevel && from.AccessLevel >= AccessLevel.GameMaster; + + public virtual bool CheckItemUse(Mobile from, Item item) => true; + + /// + /// Overridable. Virtual event invoked when successfully lifts from this + /// Mobile. + /// + /// + public virtual void OnItemLifted(Mobile from, Item item) + { + } + + public virtual bool AllowItemUse(Item item) => true; + + public virtual bool AllowEquipFrom(Mobile mob) => + mob == this || mob.AccessLevel >= AccessLevel.GameMaster && mob.AccessLevel > AccessLevel; + + public virtual bool EquipItem(Item item) + { + if (item?.Deleted != false || !item.CanEquip(this)) + return false; + + if (CheckEquip(item) && OnEquip(item) && item.OnEquip(this)) + { + if (m_Spell?.OnCasterEquipping(item) == false) + return false; + + // if (m_Spell != null && m_Spell.State == SpellState.Casting) + // m_Spell.Disturb( DisturbType.EquipRequest ); + + AddItem(item); + return true; + } + + return false; + } + + public void DefaultMobileInit() + { + m_StatCap = 225; + m_FollowersMax = 5; + Skills = new Skills(this); + Items = new List(); + StatMods = new List(); + SkillMods = new List(); + Map = Map.Internal; + AutoPageNotify = true; + Aggressors = new List(); + Aggressed = new List(); + Virtues = new VirtueInfo(); + Stabled = new List(); + DamageEntries = new List(); + + NextSkillTime = Core.TickCount; + CreationTime = DateTime.UtcNow; + } + + public virtual void Delta(MobileDelta flag) + { + if (m_Map == null || m_Map == Map.Internal || Deleted) + return; + + m_DeltaFlags |= flag; + + if (!m_InDeltaQueue) + { + m_InDeltaQueue = true; + + if (_processing) + lock (m_DeltaQueueR) + { + m_DeltaQueueR.Enqueue(this); + + try + { + using (var op = new StreamWriter("delta-recursion.log", true)) + { + op.WriteLine("# {0}", DateTime.UtcNow); + op.WriteLine(new StackTrace()); + op.WriteLine(); + } + } + catch + { + // ignored + } + } + else + m_DeltaQueue.Enqueue(this); + } + + Core.Set(); + } + + public static void ProcessDeltaQueue() + { + _processing = true; + + if (m_DeltaQueue.Count >= 512) + { + Parallel.ForEach(m_DeltaQueue, m => m.ProcessDelta()); + m_DeltaQueue.Clear(); + } + else + { + while (m_DeltaQueue.TryDequeue(out var m)) + m.ProcessDelta(); + } + + _processing = false; + + while (m_DeltaQueueR.TryDequeue(out var m)) + m.ProcessDelta(); + } + + public virtual void OnKillsChange(int oldValue) + { + } + + public bool CheckAlive(bool message = true) + { + if (Alive) + return true; + + if (message) + LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019048); // I am dead and cannot do that. + + return false; + } + + public void LaunchBrowser(string url) + { + m_NetState?.LaunchBrowser(url); + } + + public void InitStats(int str, int dex, int intel) + { + m_Str = str; + m_Dex = dex; + m_Int = intel; + + Hits = HitsMax; + Stam = StamMax; + Mana = ManaMax; + + Delta(MobileDelta.Stat | MobileDelta.Hits | MobileDelta.Stam | MobileDelta.Mana); + } + + public virtual void DisplayPaperdollTo(Mobile to) + { + EventSink.InvokePaperdollRequest(to, this); + } + + /// + /// Overridable. Event invoked when the Mobile requests to open his own paperdoll via the 'Open Paperdoll' macro. + /// + public virtual void OnPaperdollRequest() + { + if (CanPaperdollBeOpenedBy(this)) + DisplayPaperdollTo(this); + } + + /// + /// Overridable. Event invoked when wants to see this Mobile's stats. + /// + /// + public virtual void OnStatsQuery(Mobile from) + { + if (from.Map == Map && Utility.InUpdateRange(this, from) && from.CanSee(this)) + from.Send(new MobileStatus(from, this, m_NetState)); + + if (from == this) + Send(new StatLockInfo(this)); + + if (Party is IParty ip) + ip.OnStatsQuery(from, this); + } + + /// + /// Overridable. Event invoked when wants to see this Mobile's skills. + /// + public virtual void OnSkillsQuery(Mobile from) + { + if (from == this) + Send(new SkillUpdate(Skills)); + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + public virtual void OnRegionChange(Region old, Region @new) + { + } + + /// + /// Overridable. Event invoked when the Mobile is single clicked. + /// + public virtual void OnSingleClick(Mobile from) + { + if (Deleted || + AccessLevel == AccessLevel.Player && DisableHiddenSelfClick && Hidden && from == this) + return; + + if (GuildClickMessage) + { + var guild = m_Guild; + + if (guild != null && (m_DisplayGuildTitle || m_Player && guild.Type != GuildType.Regular)) + { + var title = GuildTitle?.Trim() ?? ""; + string type; + + if (guild.Type >= 0 && (int)guild.Type < m_GuildTypes.Length) + type = m_GuildTypes[(int)guild.Type]; + else + type = ""; + + var text = string.Format( + title.Length <= 0 ? "[{1}]{2}" : "[{0}, {1}]{2}", + title, + guild.Abbreviation, + type + ); + + PrivateOverheadMessage(MessageType.Regular, SpeechHue, true, text, from.NetState); + } + } + + int hue; + + if (NameHue != -1) + hue = NameHue; + else if (AccessLevel > AccessLevel.Player) + hue = 11; + else + hue = Notoriety.GetHue(Notoriety.Compute(from, this)); + + var name = Name ?? string.Empty; + + var prefix = ""; + + if (ShowFameTitle && (m_Player || m_Body.IsHuman) && m_Fame >= 10000) + prefix = m_Female ? "Lady" : "Lord"; + + var suffix = ""; + + if (ClickTitle && !string.IsNullOrEmpty(Title)) + suffix = Title; + + suffix = ApplyNameSuffix(suffix); + + string val; + + if (prefix.Length > 0 && suffix.Length > 0) + val = $"{prefix} {name} {suffix}"; + else if (prefix.Length > 0) + val = $"{prefix} {name}"; + else if (suffix.Length > 0) + val = $"{name} {suffix}"; + else + val = name; + + PrivateOverheadMessage(MessageType.Label, hue, AsciiClickMessage, val, from.NetState); + } + + public bool CheckSkill(SkillName skill, double minSkill, double maxSkill) => + SkillCheckLocationHandler?.Invoke(this, skill, minSkill, maxSkill) == true; + + public bool CheckSkill(SkillName skill, double chance) => + SkillCheckDirectLocationHandler?.Invoke(this, skill, chance) == true; + + public bool CheckTargetSkill(SkillName skill, object target, double minSkill, double maxSkill) => + SkillCheckTargetHandler?.Invoke(this, skill, target, minSkill, maxSkill) == true; + + public bool CheckTargetSkill(SkillName skill, object target, double chance) => + SkillCheckDirectTargetHandler?.Invoke(this, skill, target, chance) == true; + + public virtual void DisruptiveAction() + { + if (Meditating) + { + Meditating = false; + SendLocalizedMessage(500134); // You stop meditating. + } + } + + /// + /// Overridable. Virtual event invoked when the sector this Mobile is in gets activated. + /// + public virtual void OnSectorActivate() + { + } + + /// + /// Overridable. Virtual event invoked when the sector this Mobile is in gets + /// deactivated. + /// + public virtual void OnSectorDeactivate() + { + } + + public static TimeSpan GetHitsRegenRate(Mobile m) + { + if (HitsRegenRateHandler == null) + return DefaultHitsRate; + return HitsRegenRateHandler(m); + } + + public static TimeSpan GetStamRegenRate(Mobile m) + { + if (StamRegenRateHandler == null) + return DefaultStamRate; + return StamRegenRateHandler(m); + } + + public static TimeSpan GetManaRegenRate(Mobile m) + { + if (ManaRegenRateHandler == null) + return DefaultManaRate; + return ManaRegenRateHandler(m); + } + + public Prompt BeginPrompt(PromptCallback callback, PromptCallback cancelCallback) + { + return Prompt = new SimplePrompt(callback, cancelCallback); + } + + public Prompt BeginPrompt(PromptCallback callback, bool callbackHandlesCancel = false) + { + return Prompt = new SimplePrompt(callback, callbackHandlesCancel); + } + + public Prompt BeginPrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) => + Prompt = new SimpleStatePrompt(callback, cancelCallback, state); + + public Prompt BeginPrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) => + Prompt = new SimpleStatePrompt(callback, callbackHandlesCancel, state); + + public Prompt BeginPrompt(PromptStateCallback callback, T state) => + BeginPrompt(callback, false, state); + + public virtual int GetAngerSound() + { + if (BaseSoundID != 0) + return BaseSoundID; + + return -1; + } + + public virtual int GetIdleSound() + { + if (BaseSoundID != 0) + return BaseSoundID + 1; + + return -1; + } + + public virtual int GetAttackSound() + { + if (BaseSoundID != 0) + return BaseSoundID + 2; + + return -1; + } + + public virtual int GetHurtSound() + { + if (BaseSoundID != 0) + return BaseSoundID + 3; + + return -1; + } + + public virtual int GetDeathSound() + { + if (BaseSoundID != 0) return BaseSoundID + 4; + + if (m_Body.IsHuman) return Utility.Random(m_Female ? 0x314 : 0x423, m_Female ? 4 : 5); + return -1; + } + + public IPooledEnumerable GetItemsInRange(int range) => GetItemsInRange(range); + + public IPooledEnumerable GetItemsInRange(int range) where T : Item + { + var map = m_Map; + + if (map == null) + return Map.NullEnumerable.Instance; + + return map.GetItemsInRange(m_Location, range); + } + + public IPooledEnumerable GetObjectsInRange(int range) + { + var map = m_Map; + + if (map == null) + return Map.NullEnumerable.Instance; + + return map.GetObjectsInRange(m_Location, range); + } + + public IPooledEnumerable GetMobilesInRange(int range) => GetMobilesInRange(range); + + public IPooledEnumerable GetMobilesInRange(int range) where T : Mobile + { + var map = m_Map; + + if (map == null) + return Map.NullEnumerable.Instance; + + return map.GetMobilesInRange(m_Location, range); + } + + public IPooledEnumerable GetClientsInRange(int range) + { + var map = m_Map; + + if (map == null) + return Map.NullEnumerable.Instance; + + return map.GetClientsInRange(m_Location, range); + } + + public void SayTo(Mobile to, bool ascii, string text) + { + PrivateOverheadMessage(MessageType.Regular, SpeechHue, ascii, text, to.NetState); + } + + public void SayTo(Mobile to, string text) + { + SayTo(to, false, text); + } + + public void SayTo(Mobile to, string format, params object[] args) + { + SayTo(to, false, string.Format(format, args)); + } + + public void SayTo(Mobile to, bool ascii, string format, params object[] args) + { + SayTo(to, ascii, string.Format(format, args)); + } + + public void SayTo(Mobile to, int number) + { + to.Send(new MessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, "")); + } + + public void SayTo(Mobile to, int number, string args) + { + to.Send(new MessageLocalized(Serial, Body, MessageType.Regular, SpeechHue, 3, number, Name, args)); + } + + public void Say(bool ascii, string text) + { + PublicOverheadMessage(MessageType.Regular, SpeechHue, ascii, text); + } + + public void Say(string text) + { + PublicOverheadMessage(MessageType.Regular, SpeechHue, false, text); + } + + public void Say(string format, params object[] args) + { + Say(string.Format(format, args)); + } + + public void Say(int number, AffixType type, string affix, string args) + { + PublicOverheadMessage(MessageType.Regular, SpeechHue, number, type, affix, args); + } + + public void Say(int number, string args = "") + { + PublicOverheadMessage(MessageType.Regular, SpeechHue, number, args); + } + + public void Emote(string text) + { + PublicOverheadMessage(MessageType.Emote, EmoteHue, false, text); + } + + public void Emote(string format, params object[] args) + { + Emote(string.Format(format, args)); + } + + public void Emote(int number, string args = "") + { + PublicOverheadMessage(MessageType.Emote, EmoteHue, number, args); + } + + public void Whisper(string text) + { + PublicOverheadMessage(MessageType.Whisper, WhisperHue, false, text); + } + + public void Whisper(string format, params object[] args) + { + Whisper(string.Format(format, args)); + } + + public void Whisper(int number, string args = "") + { + PublicOverheadMessage(MessageType.Whisper, WhisperHue, number, args); + } + + public void Yell(string text) + { + PublicOverheadMessage(MessageType.Yell, YellHue, false, text); + } + + public void Yell(string format, params object[] args) + { + Yell(string.Format(format, args)); + } + + public void Yell(int number, string args = "") + { + PublicOverheadMessage(MessageType.Yell, YellHue, number, args); + } + + public bool SendHuePicker(HuePicker p, bool throwOnOffline = false) + { + if (m_NetState != null) + { + p.SendTo(m_NetState); + return true; + } + + if (throwOnOffline) throw new MobileNotConnectedException(this, "Hue picker could not be sent."); + + return false; + } + + public Gump FindGump() where T : Gump + { + return m_NetState?.Gumps.Find(g => g is T); + } + + public bool CloseGump() where T : Gump + { + if (m_NetState == null) + return false; + + var gump = FindGump(); + + if (gump != null) + { + // TODO: Recycle CloseGump + m_NetState.Send(new CloseGump(gump.TypeID, 0)); + m_NetState.RemoveGump(gump); + gump.OnServerClose(m_NetState); + } + + return true; + } + + public bool CloseAllGumps() + { + var ns = m_NetState; + + if (ns == null) + return false; + + var gumps = new List(ns.Gumps); + + ns.ClearGumps(); + + foreach (var gump in gumps) + { + ns.Send(new CloseGump(gump.TypeID, 0)); + + gump.OnServerClose(ns); + } + + return true; + } + + public bool HasGump() where T : Gump => FindGump() != null; + + public bool SendGump(Gump g) + { + if (m_NetState == null) + return false; + + g.SendTo(m_NetState); + return true; + } + + public bool SendMenu(IMenu m) + { + if (m_NetState == null) + return false; + + m.SendTo(m_NetState); + return true; + } + + public virtual bool CanBeBeneficial(Mobile target) => CanBeBeneficial(target, true, false); + + public virtual bool CanBeBeneficial(Mobile target, bool message) => CanBeBeneficial(target, message, false); + + public virtual bool CanBeBeneficial(Mobile target, bool message, bool allowDead) + { + if (target == null) + return false; + + if (Deleted || target.Deleted || !Alive || IsDeadBondedPet || + !allowDead && (!target.Alive || target.IsDeadBondedPet)) + { + if (message) + SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. + + return false; + } + + if (target == this) + return true; + + if ( /*m_Player &&*/!Region.AllowBeneficial(this, target)) + { + // TODO: Pets + // if (!(target.m_Player || target.Body.IsHuman || target.Body.IsAnimal)) + // { + if (message) + SendLocalizedMessage(1001017); // You can not perform beneficial acts on your target. + + return false; + // } + } + + return true; + } + + public virtual bool IsBeneficialCriminal(Mobile target) + { + if (this == target) + return false; + + var n = Notoriety.Compute(this, target); + + return n == Notoriety.Criminal || n == Notoriety.Murderer; + } + + /// + /// Overridable. Event invoked when the Mobile does a beneficial action. + /// + public virtual void OnBeneficialAction(Mobile target, bool isCriminal) + { + if (isCriminal) + CriminalAction(false); + } + + public virtual void DoBeneficial(Mobile target) + { + if (target == null) + return; + + OnBeneficialAction(target, IsBeneficialCriminal(target)); + + Region.OnBeneficialAction(this, target); + target.Region.OnGotBeneficialAction(this, target); + } + + public virtual bool BeneficialCheck(Mobile target) + { + if (CanBeBeneficial(target, true)) + { + DoBeneficial(target); + return true; + } + + return false; + } + + public virtual bool CanBeHarmful(Mobile target) => CanBeHarmful(target, true); + + public virtual bool CanBeHarmful(Mobile target, bool message) => CanBeHarmful(target, message, false); + + public virtual bool CanBeHarmful(Mobile target, bool message, bool ignoreOurBlessedness) + { + if (target == null) + return false; + + if (Deleted || !ignoreOurBlessedness && m_Blessed || target.Deleted || target.m_Blessed || !Alive || + IsDeadBondedPet || !target.Alive || target.IsDeadBondedPet) + { + if (message) + SendLocalizedMessage(1001018); // You can not perform negative acts on your target. + + return false; + } + + if (target == this) + return true; + + // TODO: Pets + if ( /*m_Player &&*/ + !Region.AllowHarmful(this, target) + ) // (target.m_Player || target.Body.IsHuman) && !Region.AllowHarmful( this, target ) ) + { + if (message) + SendLocalizedMessage(1001018); // You can not perform negative acts on your target. + + return false; + } + + return true; + } + + public virtual bool IsHarmfulCriminal(Mobile target) => + this != target && Notoriety.Compute(this, target) == Notoriety.Innocent; + + /// + /// Overridable. Event invoked when the Mobile does a harmful action. + /// + public virtual void OnHarmfulAction(Mobile target, bool isCriminal) + { + if (isCriminal) + CriminalAction(false); + } + + public virtual void DoHarmful(Mobile target) + { + DoHarmful(target, false); + } + + public virtual void DoHarmful(Mobile target, bool indirect) + { + if (target == null || Deleted) + return; + + var isCriminal = IsHarmfulCriminal(target); + + OnHarmfulAction(target, isCriminal); + target.AggressiveAction(this, isCriminal); + + Region.OnDidHarmful(this, target); + target.Region.OnGotHarmful(this, target); + + if (!indirect) + Combatant = target; + + if (m_ExpireCombatant == null) + m_ExpireCombatant = new ExpireCombatantTimer(this); + else + m_ExpireCombatant.Stop(); + + m_ExpireCombatant.Start(); + } + + public virtual bool HarmfulCheck(Mobile target) + { + if (CanBeHarmful(target)) + { + DoHarmful(target); + return true; + } + + return false; + } + + public bool RemoveStatMod(string name) + { + for (var i = 0; i < StatMods.Count; ++i) + { + var check = StatMods[i]; + + if (check.Name == name) + { + StatMods.RemoveAt(i); + CheckStatTimers(); + Delta(MobileDelta.Stat | GetStatDelta(check.Type)); + return true; + } + } + + return false; + } + + public StatMod GetStatMod(string name) + { + for (var i = 0; i < StatMods.Count; ++i) + { + var check = StatMods[i]; + + if (check.Name == name) + return check; + } + + return null; + } + + public void AddStatMod(StatMod mod) + { + for (var i = 0; i < StatMods.Count; ++i) + { + var check = StatMods[i]; + + if (check.Name == mod.Name) + { + Delta(MobileDelta.Stat | GetStatDelta(check.Type)); + StatMods.RemoveAt(i); + break; + } + } + + StatMods.Add(mod); + Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); + CheckStatTimers(); + } + + private MobileDelta GetStatDelta(StatType type) + { + MobileDelta delta = 0; + + if ((type & StatType.Str) != 0) + delta |= MobileDelta.Hits; + + if ((type & StatType.Dex) != 0) + delta |= MobileDelta.Stam; + + if ((type & StatType.Int) != 0) + delta |= MobileDelta.Mana; + + return delta; + } + + /// + /// Computes the total modified offset for the specified stat type. Expired instances are removed. + /// + public int GetStatOffset(StatType type) + { + var offset = 0; + + for (var i = 0; i < StatMods.Count; ++i) + { + var mod = StatMods[i]; + + if (mod.HasElapsed()) + { + StatMods.RemoveAt(i); + Delta(MobileDelta.Stat | GetStatDelta(mod.Type)); + CheckStatTimers(); + + --i; + } + else if ((mod.Type & type) != 0) + { + offset += mod.Offset; + } + } + + return offset; + } + + /// + /// Overridable. Virtual event invoked when the changes. + /// + /// + /// + public virtual void OnRawStrChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when changes. + /// + /// + /// + public virtual void OnRawDexChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when the changes. + /// + /// + /// + public virtual void OnRawIntChange(int oldValue) + { + } + + /// + /// Overridable. Virtual event invoked when the , , or + /// changes. + /// + /// + /// + /// + public virtual void OnRawStatChange(StatType stat, int oldValue) + { + } + + public virtual void OnHitsChange(int oldValue) + { + } + + public virtual void OnStamChange(int oldValue) + { + } + + public virtual void OnManaChange(int oldValue) + { + } + + /// + /// Overridable. Event invoked when a call to failed because + /// returned false: the Mobile was resistant to the poison. By default, this broadcasts an overhead message: * The poison + /// seems to have no effect. * + /// + /// + /// + /// + public virtual void OnPoisonImmunity(Mobile from, Poison poison) + { + PublicOverheadMessage(MessageType.Emote, 0x3B2, 1005534); // * The poison seems to have no effect. * + } + + /// + /// Overridable. Virtual event invoked when a call to failed because + /// returned false: the Mobile was already poisoned by an equal or greater strength poison. + /// + /// + /// + /// + public virtual void OnHigherPoison(Mobile from, Poison poison) + { + } + + /// + /// Overridable. Event invoked when a call to succeeded. By default, this broadcasts an overhead + /// message varying by the level of the poison. Example: * Zippy begins to spasm uncontrollably. * + /// + /// + /// + public virtual void OnPoisoned(Mobile from, Poison poison, Poison oldPoison) + { + if (poison != null) + { + LocalOverheadMessage(MessageType.Regular, 0x21, 1042857 + poison.Level * 2); + NonlocalOverheadMessage(MessageType.Regular, 0x21, 1042858 + poison.Level * 2, Name); + } + } + + /// + /// Overridable. Called from , this method checks if the Mobile is immune to some + /// . If true, will be invoked and + /// is returned. + /// + /// + /// + /// + public virtual bool CheckPoisonImmunity(Mobile from, Poison poison) => false; + + /// + /// Overridable. Called from , this method checks if the Mobile is already poisoned by some + /// of equal or greater strength. If true, will be invoked and + /// is returned. + /// + /// + /// + /// + public virtual bool CheckHigherPoison(Mobile from, Poison poison) => + m_Poison != null && m_Poison.Level >= poison.Level; + + /// + /// Overridable. Attempts to apply poison to the Mobile. Checks are made such that no + /// higher poison is active and that the Mobile is not + /// immune to the poison. Provided those assertions are true, the + /// is applied and is invoked. + /// + /// + /// + /// + /// One of four possible values: + /// + /// + /// + /// Cured + /// + /// The parameter was null and so was invoked. + /// + /// + /// + /// HigherPoisonActive + /// + /// The call to returned false. + /// + /// + /// + /// Immune + /// + /// The call to returned false. + /// + /// + /// + /// Poisoned + /// + /// The was successfully applied. + /// + /// + /// + public virtual ApplyPoisonResult ApplyPoison(Mobile from, Poison poison) + { + if (poison == null) + { + CurePoison(from); + return ApplyPoisonResult.Cured; + } + + if (CheckHigherPoison(from, poison)) + { + OnHigherPoison(from, poison); + return ApplyPoisonResult.HigherPoisonActive; + } + + if (CheckPoisonImmunity(from, poison)) + { + OnPoisonImmunity(from, poison); + return ApplyPoisonResult.Immune; + } + + var oldPoison = m_Poison; + Poison = poison; + + OnPoisoned(from, poison, oldPoison); + + return ApplyPoisonResult.Poisoned; + } + + /// + /// Overridable. Called from , this method checks to see that the Mobile can be cured of + /// + /// + /// + /// + public virtual bool CheckCure(Mobile from) => true; + + /// + /// Overridable. Virtual event invoked when a call to succeeded. + /// + /// + /// + /// + public virtual void OnCured(Mobile from, Poison oldPoison) + { + } + + /// + /// Overridable. Virtual event invoked when a call to failed. + /// + /// + /// + /// + public virtual void OnFailedCure(Mobile from) + { + } + + /// + /// Overridable. Attempts to cure any poison that is currently active. + /// + /// True if poison was cured, false if otherwise. + public virtual bool CurePoison(Mobile from) + { + if (CheckCure(from)) + { + var oldPoison = m_Poison; + Poison = null; + + OnCured(from, oldPoison); + + return true; + } + + OnFailedCure(from); + + return false; + } + + public void MovingEffect( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int hue, int renderMode + ) + { + Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode); + } + + public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes) + { + Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes); + } + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer, int unknown + ) + { + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + layer, + unknown + ); + } + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int hue, int renderMode, int effect, int explodeEffect, int explodeSound, int unknown + ) + { + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + (EffectLayer)255, + unknown + ); + } + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int effect, int explodeEffect, int explodeSound, int unknown + ) + { + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + effect, + explodeEffect, + explodeSound, + unknown + ); + } + + public void MovingParticles( + IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, + int effect, int explodeEffect, int explodeSound + ) + { + Effects.SendMovingParticles( + this, + to, + itemID, + speed, + duration, + fixedDirection, + explodes, + 0, + 0, + effect, + explodeEffect, + explodeSound, + 0 + ); + } + + public void FixedEffect(int itemID, int speed, int duration, int hue, int renderMode) + { + Effects.SendTargetEffect(this, itemID, speed, duration, hue, renderMode); + } + + public void FixedEffect(int itemID, int speed, int duration) + { + Effects.SendTargetEffect(this, itemID, speed, duration, 0, 0); + } + + public void FixedParticles( + int itemID, int speed, int duration, int effect, int hue, int renderMode, + EffectLayer layer, int unknown + ) + { + Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, unknown); + } + + public void FixedParticles( + int itemID, int speed, int duration, int effect, int hue, int renderMode, + EffectLayer layer + ) + { + Effects.SendTargetParticles(this, itemID, speed, duration, hue, renderMode, effect, layer, 0); + } + + public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer, int unknown) + { + Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, unknown); + } + + public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer) + { + Effects.SendTargetParticles(this, itemID, speed, duration, 0, 0, effect, layer, 0); + } + + public void BoltEffect(int hue) + { + Effects.SendBoltEffect(this, true, hue); + } + + public Direction GetDirectionTo(int x, int y) + { + var dx = m_Location.m_X - x; + var dy = m_Location.m_Y - y; + + var rx = (dx - dy) * 44; + var ry = (dx + dy) * 44; + + var ax = Math.Abs(rx); + var ay = Math.Abs(ry); + + Direction ret; + + if ((ay >> 1) - ax >= 0) + ret = ry > 0 ? Direction.Up : Direction.Down; + else if ((ax >> 1) - ay >= 0) + ret = rx > 0 ? Direction.Left : Direction.Right; + else if (rx >= 0 && ry >= 0) + ret = Direction.West; + else if (rx >= 0 && ry < 0) + ret = Direction.South; + else if (rx < 0 && ry < 0) + ret = Direction.East; + else + ret = Direction.North; + + return ret; + } + + public Direction GetDirectionTo(Point2D p) => GetDirectionTo(p.m_X, p.m_Y); + + public Direction GetDirectionTo(Point3D p) => GetDirectionTo(p.m_X, p.m_Y); + + public Direction GetDirectionTo(IPoint2D p) + { + if (p == null) + return Direction.North; + + return GetDirectionTo(p.X, p.Y); + } + + public void PublicOverheadMessage(MessageType type, int hue, bool ascii, string text, bool noLineOfSight = true) + { + if (m_Map == null) + return; + + var p = ascii + ? (Packet)new AsciiMessage(Serial, Body, type, hue, 3, Name, text) + : new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text); + + p.Acquire(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) + state.Send(p); + + Packet.Release(p); + + eable.Free(); + } + + public void PublicOverheadMessage(MessageType type, int hue, int number, string args = "", bool noLineOfSight = true) + { + if (m_Map == null) + return; + + var p = Packet.Acquire(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) + state.Send(p); + + Packet.Release(p); + + eable.Free(); + } + + public void PublicOverheadMessage( + MessageType type, int hue, int number, AffixType affixType, string affix, + string args = "", bool noLineOfSight = false + ) + { + if (m_Map == null) + return; + + var p = Packet.Acquire( + new MessageLocalizedAffix( + Serial, + Body, + type, + hue, + 3, + number, + Name, + affixType, + affix, + args + ) + ); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state.Mobile.CanSee(this) && (noLineOfSight || state.Mobile.InLOS(this))) + state.Send(p); + + Packet.Release(p); + + eable.Free(); + } + + public void PrivateOverheadMessage(MessageType type, int hue, bool ascii, string text, NetState state) + { + if (state == null) + return; + + if (ascii) + state.Send(new AsciiMessage(Serial, Body, type, hue, 3, Name, text)); + else + state.Send(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); + } + + public void PrivateOverheadMessage(MessageType type, int hue, int number, NetState state) + { + PrivateOverheadMessage(type, hue, number, "", state); + } + + public void PrivateOverheadMessage(MessageType type, int hue, int number, string args, NetState state) + { + state?.Send(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); + } + + public void LocalOverheadMessage(MessageType type, int hue, bool ascii, string text) + { + var ns = m_NetState; + + if (ns == null) + return; + + if (ascii) + ns.Send(new AsciiMessage(Serial, Body, type, hue, 3, Name, text)); + else + ns.Send(new UnicodeMessage(Serial, Body, type, hue, 3, m_Language, Name, text)); + } + + public void LocalOverheadMessage(MessageType type, int hue, int number, string args = "") + { + m_NetState?.Send(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); + } + + public void NonlocalOverheadMessage(MessageType type, int hue, int number, string args = "") + { + if (m_Map == null) + return; + + var p = Packet.Acquire(new MessageLocalized(Serial, Body, type, hue, 3, number, Name, args)); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state != m_NetState && state.Mobile.CanSee(this)) + state.Send(p); + + Packet.Release(p); + + eable.Free(); + } + + public void NonlocalOverheadMessage(MessageType type, int hue, bool ascii, string text) + { + if (m_Map == null) + return; + + var p = ascii + ? (Packet)new AsciiMessage(Serial, Body, type, hue, 3, Name, text) + : new UnicodeMessage(Serial, Body, type, hue, 3, Language, Name, text); + + p.Acquire(); + + var eable = m_Map.GetClientsInRange(m_Location); + + foreach (var state in eable) + if (state != m_NetState && state.Mobile.CanSee(this)) + state.Send(p); + + Packet.Release(p); + + eable.Free(); + } + + public void SendLocalizedMessage(int number) + { + m_NetState?.Send(MessageLocalized.InstantiateGeneric(number)); + } + + public void SendLocalizedMessage(int number, string args, int hue = 0x3B2) + { + if (hue == 0x3B2 && string.IsNullOrEmpty(args)) + m_NetState?.Send(MessageLocalized.InstantiateGeneric(number)); + else + m_NetState?.Send( + new MessageLocalized(Serial.MinusOne, -1, MessageType.Regular, hue, 3, number, "System", args) + ); + } + + public void SendLocalizedMessage(int number, bool append, string affix, string args = "", int hue = 0x3B2) + { + m_NetState?.Send( + new MessageLocalizedAffix( + Serial.MinusOne, + -1, + MessageType.Regular, + hue, + 3, + number, + "System", + (append ? AffixType.Append : AffixType.Prepend) | AffixType.System, + affix, + args + ) + ); + } + + public void SendMessage(string text) + { + SendMessage(0x3B2, text); + } + + public void SendMessage(string format, params object[] args) + { + SendMessage(0x3B2, string.Format(format, args)); + } + + public void SendMessage(int hue, string text) + { + m_NetState?.Send(new UnicodeMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text)); + } + + public void SendMessage(int hue, string format, params object[] args) + { + SendMessage(hue, string.Format(format, args)); + } + + public void SendAsciiMessage(string text) + { + SendAsciiMessage(0x3B2, text); + } + + public void SendAsciiMessage(string format, params object[] args) + { + SendAsciiMessage(0x3B2, string.Format(format, args)); + } + + public void SendAsciiMessage(int hue, string text) + { + m_NetState?.Send(new AsciiMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "System", text)); + } + + public void SendAsciiMessage(int hue, string format, params object[] args) + { + SendAsciiMessage(hue, string.Format(format, args)); + } + + /// + /// Overridable. Event invoked when the Mobile is double clicked. By default, this method can either dismount or open the + /// paperdoll. + /// + /// + /// + public virtual void OnDoubleClick(Mobile from) + { + if (this == from && (!DisableDismountInWarmode || !m_Warmode)) + { + var mount = Mount; + + if (mount != null) + { + mount.Rider = null; + return; + } + } + + if (CanPaperdollBeOpenedBy(from)) + DisplayPaperdollTo(from); + } + + /// + /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who is over 18 tiles away. + /// + /// + public virtual void OnDoubleClickOutOfRange(Mobile from) + { + } + + /// + /// Overridable. Virtual event invoked when the Mobile is double clicked by someone who can no longer see the Mobile. This + /// may + /// happen, for example, using 'Last Object' after the Mobile has hidden. + /// + /// + public virtual void OnDoubleClickCantSee(Mobile from) + { + } + + /// + /// Overridable. Event invoked when the Mobile is double clicked by someone who is not alive. Similar to + /// , this method will show the paperdoll. It does not, however, provide any dismount + /// functionality. + /// + /// + public virtual void OnDoubleClickDead(Mobile from) + { + if (CanPaperdollBeOpenedBy(from)) + DisplayPaperdollTo(from); + } + + private class MovementRecord + { + private static readonly Queue m_InstancePool = new Queue(); + public long m_End; + + private MovementRecord(long end) => m_End = end; + + public static MovementRecord NewInstance(long end) + { + MovementRecord r; + + if (m_InstancePool.Count > 0) + { + r = m_InstancePool.Dequeue(); + + r.m_End = end; + } + else + { + r = new MovementRecord(end); + } + + return r; + } + + public bool Expired() + { + var v = Core.TickCount - m_End >= 0; + + if (v) + m_InstancePool.Enqueue(this); + + return v; + } + } + + private class WarmodeTimer : Timer + { + private readonly Mobile m_Mobile; + + public WarmodeTimer(Mobile m, bool value) + : base(WarmodeSpamDelay) + { + m_Mobile = m; + Value = value; + } + + public bool Value { get; set; } + + protected override void OnTick() + { + m_Mobile.Warmode = Value; + m_Mobile.m_WarmodeChanges = 0; + + m_Mobile.m_WarmodeTimer = null; + } + } + + private class SimpleTarget : Target + { + private readonly TargetCallback m_Callback; + + public SimpleTarget(int range, TargetFlags flags, bool allowGround, TargetCallback callback) + : base(range, allowGround, flags) => + m_Callback = callback; + + protected override void OnTarget(Mobile from, object targeted) + { + m_Callback?.Invoke(from, targeted); + } + } + + private class SimpleStateTarget : Target + { + private readonly TargetStateCallback m_Callback; + private readonly T m_State; + + public SimpleStateTarget( + int range, TargetFlags flags, bool allowGround, TargetStateCallback callback, + T state + ) + : base(range, allowGround, flags) + { + m_Callback = callback; + m_State = state; + } + + protected override void OnTarget(Mobile from, object targeted) + { + m_Callback?.Invoke(from, targeted, m_State); + } + } + + private class AutoManifestTimer : Timer + { + private readonly Mobile m_Mobile; + + public AutoManifestTimer(Mobile m, TimeSpan delay) + : base(delay) => + m_Mobile = m; + + protected override void OnTick() + { + if (!m_Mobile.Alive) + m_Mobile.Warmode = false; + } + } + + private class LocationComparer : IComparer + { + private static LocationComparer m_Instance; + + public LocationComparer(IEntity relativeTo) => RelativeTo = relativeTo; + + public IEntity RelativeTo { get; set; } + + public int Compare(IEntity x, IEntity y) => GetDistance(x) - GetDistance(y); + + public static LocationComparer GetInstance(IEntity relativeTo) + { + if (m_Instance == null) + m_Instance = new LocationComparer(relativeTo); + else + m_Instance.RelativeTo = relativeTo; + + return m_Instance; + } + + private int GetDistance(IEntity p) + { + var x = RelativeTo.X - p.X; + var y = RelativeTo.Y - p.Y; + var z = RelativeTo.Z - p.Z; + + x *= 11; + y *= 11; + + return x * x + y * y + z * z; + } + } + + private class ManaTimer : Timer + { + private readonly Mobile m_Owner; + + public ManaTimer(Mobile m) + : base(GetManaRegenRate(m), GetManaRegenRate(m)) + { + Priority = TimerPriority.FiftyMS; + m_Owner = m; + } + + protected override void OnTick() + { + if (m_Owner.CanRegenMana) + m_Owner.Mana++; + + Delay = Interval = GetManaRegenRate(m_Owner); + } + } + + private class HitsTimer : Timer + { + private readonly Mobile m_Owner; + + public HitsTimer(Mobile m) + : base(GetHitsRegenRate(m), GetHitsRegenRate(m)) + { + Priority = TimerPriority.FiftyMS; + m_Owner = m; + } + + protected override void OnTick() + { + if (m_Owner.CanRegenHits) + m_Owner.Hits++; + + Delay = Interval = GetHitsRegenRate(m_Owner); + } + } + + private class StamTimer : Timer + { + private readonly Mobile m_Owner; + + public StamTimer(Mobile m) + : base(GetStamRegenRate(m), GetStamRegenRate(m)) + { + Priority = TimerPriority.FiftyMS; + m_Owner = m; + } + + protected override void OnTick() + { + if (m_Owner.CanRegenStam) + m_Owner.Stam++; + + Delay = Interval = GetStamRegenRate(m_Owner); + } + } + + private class LogoutTimer : Timer + { + private readonly Mobile m_Mobile; + + public LogoutTimer(Mobile m) + : base(TimeSpan.FromDays(1.0)) + { + Priority = TimerPriority.OneSecond; + m_Mobile = m; + } + + protected override void OnTick() + { + if (m_Mobile.m_Map != Map.Internal) + { + EventSink.InvokeLogout(m_Mobile); + + m_Mobile.LogoutLocation = m_Mobile.m_Location; + m_Mobile.LogoutMap = m_Mobile.m_Map; + + m_Mobile.Internalize(); + } + } + } + + private class ParalyzedTimer : Timer + { + private readonly Mobile m_Mobile; + + public ParalyzedTimer(Mobile m, TimeSpan duration) + : base(duration) + { + Priority = TimerPriority.TwentyFiveMS; + m_Mobile = m; + } + + protected override void OnTick() + { + m_Mobile.Paralyzed = false; + } + } + + private class FrozenTimer : Timer + { + private readonly Mobile m_Mobile; + + public FrozenTimer(Mobile m, TimeSpan duration) + : base(duration) + { + Priority = TimerPriority.TwentyFiveMS; + m_Mobile = m; + } + + protected override void OnTick() + { + m_Mobile.Frozen = false; + } + } + + private class CombatTimer : Timer + { + private readonly Mobile m_Mobile; + + public CombatTimer(Mobile m) : base(TimeSpan.FromSeconds(0.0), TimeSpan.FromSeconds(0.01)) + { + m_Mobile = m; + + if (!m_Mobile.m_Player && m_Mobile.m_Dex <= 100) + Priority = TimerPriority.FiftyMS; + } + + protected override void OnTick() + { + if (Core.TickCount - m_Mobile.NextCombatTime < 0) + return; + + var combatant = m_Mobile.Combatant; + + // If no combatant, wrong map, one of us is a ghost, or cannot see, or deleted, then stop combat + if (combatant?.Deleted != false || m_Mobile.Deleted || combatant.m_Map != m_Mobile.m_Map || + !combatant.Alive || !m_Mobile.Alive || !m_Mobile.CanSee(combatant) || combatant.IsDeadBondedPet || + m_Mobile.IsDeadBondedPet) + { + m_Mobile.Combatant = null; + return; + } + + var weapon = m_Mobile.Weapon; + + if (!m_Mobile.InRange(combatant, weapon.MaxRange)) + return; + + if (m_Mobile.InLOS(combatant)) + { + weapon.OnBeforeSwing( + m_Mobile, + combatant + ); // OnBeforeSwing for checking in regards to being hidden and whatnot + m_Mobile.RevealingAction(); + m_Mobile.NextCombatTime = + Core.TickCount + (int)weapon.OnSwing(m_Mobile, combatant).TotalMilliseconds; + } + } + } + + private class ExpireCombatantTimer : Timer + { + private readonly Mobile m_Mobile; + + public ExpireCombatantTimer(Mobile m) + : base(TimeSpan.FromMinutes(1.0)) + { + Priority = TimerPriority.FiveSeconds; + m_Mobile = m; + } + + protected override void OnTick() + { + m_Mobile.Combatant = null; + } + } + + private class ExpireCriminalTimer : Timer + { + private readonly Mobile m_Mobile; + + public ExpireCriminalTimer(Mobile m) + : base(ExpireCriminalDelay) + { + Priority = TimerPriority.FiveSeconds; + m_Mobile = m; + } + + protected override void OnTick() + { + m_Mobile.Criminal = false; + } + } + + private class ExpireAggressorsTimer : Timer + { + private readonly Mobile m_Mobile; + + public ExpireAggressorsTimer(Mobile m) + : base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(5.0)) + { + m_Mobile = m; + Priority = TimerPriority.FiveSeconds; + } + + protected override void OnTick() + { + if (m_Mobile.Deleted || m_Mobile.Aggressors.Count == 0 && m_Mobile.Aggressed.Count == 0) + m_Mobile.StopAggrExpire(); + else + m_Mobile.CheckAggrExpire(); + } + } + + private class SimplePrompt : Prompt + { + private readonly PromptCallback m_Callback; + private readonly bool m_CallbackHandlesCancel; + private readonly PromptCallback m_CancelCallback; + + public SimplePrompt(PromptCallback callback, PromptCallback cancelCallback) + { + m_Callback = callback; + m_CancelCallback = cancelCallback; + } + + public SimplePrompt(PromptCallback callback, bool callbackHandlesCancel = false) + { + m_Callback = callback; + m_CallbackHandlesCancel = callbackHandlesCancel; + } + + public override void OnResponse(Mobile from, string text) + { + m_Callback?.Invoke(from, text); + } + + public override void OnCancel(Mobile from) + { + if (m_CallbackHandlesCancel && m_Callback != null) + m_Callback(from, ""); + else + m_CancelCallback?.Invoke(from, ""); + } + } + + private class SimpleStatePrompt : Prompt + { + private readonly PromptStateCallback m_Callback; + private readonly PromptStateCallback m_CancelCallback; + + private readonly T m_State; + + public SimpleStatePrompt(PromptStateCallback callback, PromptStateCallback cancelCallback, T state) + { + m_Callback = callback; + m_CancelCallback = cancelCallback; + m_State = state; + } + + public SimpleStatePrompt(PromptStateCallback callback, bool callbackHandlesCancel, T state) + { + m_Callback = callback; + m_State = state; + m_CancelCallback = callbackHandlesCancel ? callback : null; + } + + public SimpleStatePrompt(PromptStateCallback callback, T state) : this(callback, false, state) + { + } + + public override void OnResponse(Mobile from, string text) + { + m_Callback?.Invoke(from, text, m_State); + } + + public override void OnCancel(Mobile from) + { + m_CancelCallback?.Invoke(from, "", m_State); + } + } + } +} diff --git a/Projects/Server/Mobiles/IMount.cs b/Projects/Server/Mobiles/IMount.cs index 113c92b1f..8d2ce7879 100644 --- a/Projects/Server/Mobiles/IMount.cs +++ b/Projects/Server/Mobiles/IMount.cs @@ -1,34 +1,34 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: IMount.cs * - * Created: 2020/04/25 - Updated: 2020/04/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Mobiles -{ - public interface IMount - { - Mobile Rider { get; set; } - void OnRiderDamaged(int amount, Mobile from, bool willKill); - } - - public interface IMountItem - { - IMount Mount { get; } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IMount.cs * + * Created: 2020/04/25 - Updated: 2020/04/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Mobiles +{ + public interface IMount + { + Mobile Rider { get; set; } + void OnRiderDamaged(int amount, Mobile from, bool willKill); + } + + public interface IMountItem + { + IMount Mount { get; } + } +} diff --git a/Projects/Server/Mobiles/IVendor.cs b/Projects/Server/Mobiles/IVendor.cs index 9743c6836..45c505267 100644 --- a/Projects/Server/Mobiles/IVendor.cs +++ b/Projects/Server/Mobiles/IVendor.cs @@ -1,33 +1,33 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: IVendor.cs * - * Created: 2020/04/25 - Updated: 2020/04/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using Server; - -public interface IVendor -{ - DateTime LastRestock { get; set; } - TimeSpan RestockDelay { get; } - bool OnBuyItems(Mobile from, List list); - bool OnSellItems(Mobile from, List list); - void Restock(); -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IVendor.cs * + * Created: 2020/04/25 - Updated: 2020/04/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using Server; + +public interface IVendor +{ + DateTime LastRestock { get; set; } + TimeSpan RestockDelay { get; } + bool OnBuyItems(Mobile from, List list); + bool OnSellItems(Mobile from, List list); + void Restock(); +} diff --git a/Projects/Server/Movement.cs b/Projects/Server/Movement.cs index 2ba3b2eab..ff6712caf 100644 --- a/Projects/Server/Movement.cs +++ b/Projects/Server/Movement.cs @@ -1,86 +1,86 @@ -/*************************************************************************** - * Movement.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Movement -{ - public static class Movement - { - public static IMovementImpl Impl { get; set; } - - public static bool CheckMovement(Mobile m, Direction d, out int newZ) - { - if (Impl != null) - return Impl.CheckMovement(m, d, out newZ); - - newZ = m.Z; - return false; - } - - public static bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ) - { - if (Impl != null) - return Impl.CheckMovement(m, map, loc, d, out newZ); - - newZ = m.Z; - return false; - } - - public static void Offset(Direction d, ref int x, ref int y) - { - switch (d & Direction.Mask) - { - case Direction.North: - --y; - break; - case Direction.South: - ++y; - break; - case Direction.West: - --x; - break; - case Direction.East: - ++x; - break; - case Direction.Right: - ++x; - --y; - break; - case Direction.Left: - --x; - ++y; - break; - case Direction.Down: - ++x; - ++y; - break; - case Direction.Up: - --x; - --y; - break; - } - } - } - - public interface IMovementImpl - { - bool CheckMovement(Mobile m, Direction d, out int newZ); - bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ); - } -} +/*************************************************************************** + * Movement.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Movement +{ + public static class Movement + { + public static IMovementImpl Impl { get; set; } + + public static bool CheckMovement(Mobile m, Direction d, out int newZ) + { + if (Impl != null) + return Impl.CheckMovement(m, d, out newZ); + + newZ = m.Z; + return false; + } + + public static bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ) + { + if (Impl != null) + return Impl.CheckMovement(m, map, loc, d, out newZ); + + newZ = m.Z; + return false; + } + + public static void Offset(Direction d, ref int x, ref int y) + { + switch (d & Direction.Mask) + { + case Direction.North: + --y; + break; + case Direction.South: + ++y; + break; + case Direction.West: + --x; + break; + case Direction.East: + ++x; + break; + case Direction.Right: + ++x; + --y; + break; + case Direction.Left: + --x; + ++y; + break; + case Direction.Down: + ++x; + ++y; + break; + case Direction.Up: + --x; + --y; + break; + } + } + } + + public interface IMovementImpl + { + bool CheckMovement(Mobile m, Direction d, out int newZ); + bool CheckMovement(Mobile m, Map map, Point3D loc, Direction d, out int newZ); + } +} diff --git a/Projects/Server/MultiData.cs b/Projects/Server/MultiData.cs index 5d4eafe2e..57eb644f1 100644 --- a/Projects/Server/MultiData.cs +++ b/Projects/Server/MultiData.cs @@ -1,873 +1,885 @@ -/*************************************************************************** - * MultiData.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; - -namespace Server -{ - public static class MultiData - { - public static Dictionary Components { get; } = new Dictionary(); - - private static readonly BinaryReader m_IndexReader; - private static readonly BinaryReader m_StreamReader; - - private static readonly bool UsingUOPFormat; - - static MultiData() - { - var multiUOPPath = Core.FindDataFile("MultiCollection.uop", false); - - if (File.Exists(multiUOPPath)) - { - LoadUOP(multiUOPPath); - UsingUOPFormat = true; - return; - } - - var idxPath = Core.FindDataFile("multi.idx"); - var mulPath = Core.FindDataFile("multi.mul"); - - var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read); - m_IndexReader = new BinaryReader(idx); - - var stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read); - m_StreamReader = new BinaryReader(stream); - - var vdPath = Core.FindDataFile("verdata.mul", false); - - if (!File.Exists(vdPath)) return; - - using var fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var bin = new BinaryReader(fs); - - var count = bin.ReadInt32(); - - for (var i = 0; i < count; ++i) - { - var file = bin.ReadInt32(); - var index = bin.ReadInt32(); - var lookup = bin.ReadInt32(); - var length = bin.ReadInt32(); - bin.ReadInt32(); // extra - - if (file == 14 && index >= 0 && lookup >= 0 && length > 0) - { - bin.BaseStream.Seek(lookup, SeekOrigin.Begin); - - Components[index] = new MultiComponentList(bin, length / 12); - - bin.BaseStream.Seek(24 + i * 20, SeekOrigin.Begin); - } - } - - bin.Close(); - } - - public static MultiComponentList GetComponents(int multiID) - { - MultiComponentList mcl; - - multiID &= 0x3FFF; - - if (Components.ContainsKey(multiID)) - mcl = Components[multiID]; - else if (!UsingUOPFormat) - Components[multiID] = mcl = Load(multiID); - else - mcl = MultiComponentList.Empty; - - return mcl; - } - - public static void LoadUOP(string path) - { - var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); - var streamReader = new BinaryReader(stream); - - // Head Information Start - if (streamReader.ReadInt32() != 0x0050594D) // Not a UOP Files - return; - - if (streamReader.ReadInt32() > 5) // Bad Version - return; - - // Multi ID List Array Start - UOPHash.BuildChunkIDs(out var chunkIds); - // Multi ID List Array End - - streamReader.ReadUInt32(); // format timestamp? 0xFD23EC43 - var startAddress = streamReader.ReadInt64(); - - streamReader.ReadInt32(); - streamReader.ReadInt32(); - - stream.Seek(startAddress, SeekOrigin.Begin); // Head Information End - - long nextBlock; - - do - { - var blockFileCount = streamReader.ReadInt32(); - nextBlock = streamReader.ReadInt64(); - - var index = 0; - - do - { - var offset = streamReader.ReadInt64(); - - var headerSize = streamReader.ReadInt32(); // header length - var compressedSize = streamReader.ReadInt32(); // compressed size - var decompressedSize = streamReader.ReadInt32(); // decompressed size - - var filehash = streamReader.ReadUInt64(); // filename hash (HashLittle2) - streamReader.ReadUInt32(); - var compressionMethod = streamReader.ReadInt16(); // compression method (0 = none, 1 = zlib) - - index++; - - if (offset == 0 || decompressedSize == 0 || filehash == 0x126D1E99DDEDEE0A) // Exclude housing.bin - continue; - - chunkIds.TryGetValue(filehash, out var chunkID); - - var position = stream.Position; // save current position - - stream.Seek(offset + headerSize, SeekOrigin.Begin); - - Span sourceData = new byte[compressedSize]; - - if (stream.Read(sourceData) != compressedSize) - continue; - - Span data; - - if (compressionMethod == 1) - { - data = new byte[decompressedSize]; - Zlib.Unpack(data, ref decompressedSize, sourceData, compressedSize); - } - else - { - data = sourceData; - } - - var tileList = new List(); - - // Skip the first 4 bytes - var reader = new BufferReader(data); - - reader.Advance(4); // ??? - reader.TryReadLittleEndian(out uint count); - - for (uint i = 0; i < count; i++) - { - reader.TryReadLittleEndian(out ushort itemid); - reader.TryReadLittleEndian(out short x); - reader.TryReadLittleEndian(out short y); - reader.TryReadLittleEndian(out short z); - reader.TryReadLittleEndian(out ushort flagValue); - - var tileFlag = flagValue switch - { - 1 => TileFlag.None, - 257 => TileFlag.Generic, - _ => TileFlag.Background // 0 - }; - - reader.TryReadLittleEndian(out uint clilocsCount); - reader.Advance(clilocsCount * 4); // bypass binary block - - tileList.Add(new MultiTileEntry(itemid, x, y, z, tileFlag)); - } - - Components[chunkID] = new MultiComponentList(tileList); - - stream.Seek(position, SeekOrigin.Begin); // back to position - } while (index < blockFileCount); - } while (stream.Seek(nextBlock, SeekOrigin.Begin) != 0); - } - - // TODO: Change this to read the file all during load time - public static MultiComponentList Load(int multiID) - { - try - { - m_IndexReader.BaseStream.Seek(multiID * 12, SeekOrigin.Begin); - - var lookup = m_IndexReader.ReadInt32(); - var length = m_IndexReader.ReadInt32(); - - if (lookup < 0 || length <= 0) - return MultiComponentList.Empty; - - m_StreamReader.BaseStream.Seek(lookup, SeekOrigin.Begin); - - return new MultiComponentList(m_StreamReader, length / (MultiComponentList.PostHSFormat ? 16 : 12)); - } - catch - { - return MultiComponentList.Empty; - } - } - } - - public struct MultiTileEntry - { - public ushort ItemId { get; set; } - public short OffsetX { get; set; } - public short OffsetY { get; set; } - public short OffsetZ { get; set; } - public TileFlag Flags { get; set; } - - public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, TileFlag flags) - { - ItemId = itemID; - OffsetX = xOffset; - OffsetY = yOffset; - OffsetZ = zOffset; - Flags = flags; - } - } - - public sealed class MultiComponentList - { - public static readonly MultiComponentList Empty = new MultiComponentList(); - - private Point2D m_Min, m_Max; - - public MultiComponentList(MultiComponentList toCopy) - { - m_Min = toCopy.m_Min; - m_Max = toCopy.m_Max; - - Center = toCopy.Center; - - Width = toCopy.Width; - Height = toCopy.Height; - - Tiles = new StaticTile[Width][][]; - - for (var x = 0; x < Width; ++x) - { - Tiles[x] = new StaticTile[Height][]; - - for (var y = 0; y < Height; ++y) - { - Tiles[x][y] = new StaticTile[toCopy.Tiles[x][y].Length]; - - for (var i = 0; i < Tiles[x][y].Length; ++i) - Tiles[x][y][i] = toCopy.Tiles[x][y][i]; - } - } - - List = new MultiTileEntry[toCopy.List.Length]; - - for (var i = 0; i < List.Length; ++i) - List[i] = toCopy.List[i]; - } - - public MultiComponentList(IGenericReader reader) - { - var version = reader.ReadInt(); - - m_Min = reader.ReadPoint2D(); - m_Max = reader.ReadPoint2D(); - Center = reader.ReadPoint2D(); - Width = reader.ReadInt(); - Height = reader.ReadInt(); - - var length = reader.ReadInt(); - - var allTiles = List = new MultiTileEntry[length]; - - if (version == 0) - for (var i = 0; i < length; ++i) - { - int id = reader.ReadShort(); - if (id >= 0x4000) - id -= 0x4000; - - allTiles[i].ItemId = (ushort)id; - allTiles[i].OffsetX = reader.ReadShort(); - allTiles[i].OffsetY = reader.ReadShort(); - allTiles[i].OffsetZ = reader.ReadShort(); - allTiles[i].Flags = (TileFlag)reader.ReadInt(); - } - else - for (var i = 0; i < length; ++i) - { - allTiles[i].ItemId = reader.ReadUShort(); - allTiles[i].OffsetX = reader.ReadShort(); - allTiles[i].OffsetY = reader.ReadShort(); - allTiles[i].OffsetZ = reader.ReadShort(); - allTiles[i].Flags = (TileFlag)reader.ReadInt(); - } - - var tiles = new TileList[Width][]; - Tiles = new StaticTile[Width][][]; - - for (var x = 0; x < Width; ++x) - { - tiles[x] = new TileList[Height]; - Tiles[x] = new StaticTile[Height][]; - - for (var y = 0; y < Height; ++y) - tiles[x][y] = new TileList(); - } - - for (var i = 0; i < allTiles.Length; ++i) - if (i == 0 || allTiles[i].Flags != 0) - { - var xOffset = allTiles[i].OffsetX + Center.m_X; - var yOffset = allTiles[i].OffsetY + Center.m_Y; - - tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); - } - - for (var x = 0; x < Width; ++x) - for (var y = 0; y < Height; ++y) - Tiles[x][y] = tiles[x][y].ToArray(); - } - - public MultiComponentList(BinaryReader reader, int count) - { - var allTiles = List = new MultiTileEntry[count]; - - for (var i = 0; i < count; ++i) - { - allTiles[i].ItemId = reader.ReadUInt16(); - allTiles[i].OffsetX = reader.ReadInt16(); - allTiles[i].OffsetY = reader.ReadInt16(); - allTiles[i].OffsetZ = reader.ReadInt16(); - - if (PostHSFormat) - allTiles[i].Flags = (TileFlag)reader.ReadUInt64(); - else - allTiles[i].Flags = (TileFlag)reader.ReadUInt32(); - - var e = allTiles[i]; - - if (i == 0 || e.Flags != 0) - { - if (e.OffsetX < m_Min.m_X) - m_Min.m_X = e.OffsetX; - - if (e.OffsetY < m_Min.m_Y) - m_Min.m_Y = e.OffsetY; - - if (e.OffsetX > m_Max.m_X) - m_Max.m_X = e.OffsetX; - - if (e.OffsetY > m_Max.m_Y) - m_Max.m_Y = e.OffsetY; - } - } - - Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); - Width = m_Max.m_X - m_Min.m_X + 1; - Height = m_Max.m_Y - m_Min.m_Y + 1; - - var tiles = new TileList[Width][]; - Tiles = new StaticTile[Width][][]; - - for (var x = 0; x < Width; ++x) - { - tiles[x] = new TileList[Height]; - Tiles[x] = new StaticTile[Height][]; - - for (var y = 0; y < Height; ++y) - tiles[x][y] = new TileList(); - } - - for (var i = 0; i < allTiles.Length; ++i) - if (i == 0 || allTiles[i].Flags != 0) - { - var xOffset = allTiles[i].OffsetX + Center.m_X; - var yOffset = allTiles[i].OffsetY + Center.m_Y; - - tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); - } - - for (var x = 0; x < Width; ++x) - for (var y = 0; y < Height; ++y) - Tiles[x][y] = tiles[x][y].ToArray(); - } - - public MultiComponentList(List list) - { - var allTiles = List = new MultiTileEntry[list.Count]; - - for (var i = 0; i < list.Count; ++i) - { - allTiles[i].ItemId = list[i].ItemId; - allTiles[i].OffsetX = list[i].OffsetX; - allTiles[i].OffsetY = list[i].OffsetY; - allTiles[i].OffsetZ = list[i].OffsetZ; - - allTiles[i].Flags = list[i].Flags; - - var e = allTiles[i]; - - if (i == 0 || e.Flags != 0) - { - if (e.OffsetX < m_Min.m_X) m_Min.m_X = e.OffsetX; - - if (e.OffsetY < m_Min.m_Y) m_Min.m_Y = e.OffsetY; - - if (e.OffsetX > m_Max.m_X) m_Max.m_X = e.OffsetX; - - if (e.OffsetY > m_Max.m_Y) m_Max.m_Y = e.OffsetY; - } - } - - Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); - Width = m_Max.m_X - m_Min.m_X + 1; - Height = m_Max.m_Y - m_Min.m_Y + 1; - - var tiles = new TileList[Width][]; - Tiles = new StaticTile[Width][][]; - - for (var x = 0; x < Width; ++x) - { - tiles[x] = new TileList[Height]; - Tiles[x] = new StaticTile[Height][]; - - for (var y = 0; y < Height; ++y) tiles[x][y] = new TileList(); - } - - for (var i = 0; i < allTiles.Length; ++i) - if (i == 0 || allTiles[i].Flags != 0) - { - var xOffset = allTiles[i].OffsetX + Center.m_X; - var yOffset = allTiles[i].OffsetY + Center.m_Y; - var itemID = (allTiles[i].ItemId & TileData.MaxItemValue) | 0x10000; - - tiles[xOffset][yOffset].Add((ushort)itemID, (sbyte)allTiles[i].OffsetZ); - } - - for (var x = 0; x < Width; ++x) - for (var y = 0; y < Height; ++y) - Tiles[x][y] = tiles[x][y].ToArray(); - } - - private MultiComponentList() - { - Tiles = Array.Empty(); - List = Array.Empty(); - } - - public static bool PostHSFormat { get; set; } - - public Point2D Min => m_Min; - public Point2D Max => m_Max; - - public Point2D Center { get; } - - public int Width { get; private set; } - - public int Height { get; private set; } - - public StaticTile[][][] Tiles { get; private set; } - - public MultiTileEntry[] List { get; private set; } - - public void Add(int itemID, int x, int y, int z) - { - var vx = x + Center.m_X; - var vy = y + Center.m_Y; - - if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) - { - var oldTiles = Tiles[vx][vy]; - - for (var i = oldTiles.Length - 1; i >= 0; --i) - { - var data = TileData.ItemTable[itemID & TileData.MaxItemValue]; - - if (oldTiles[i].Z == z && oldTiles[i].Height > 0 == data.Height > 0) - { - var newIsRoof = (data.Flags & TileFlag.Roof) != 0; - var oldIsRoof = - (TileData.ItemTable[oldTiles[i].ID & TileData.MaxItemValue].Flags & TileFlag.Roof) != 0; - - if (newIsRoof == oldIsRoof) - Remove(oldTiles[i].ID, x, y, z); - } - } - - oldTiles = Tiles[vx][vy]; - - var newTiles = new StaticTile[oldTiles.Length + 1]; - - for (var i = 0; i < oldTiles.Length; ++i) - newTiles[i] = oldTiles[i]; - - newTiles[oldTiles.Length] = new StaticTile((ushort)itemID, (sbyte)z); - - Tiles[vx][vy] = newTiles; - - var oldList = List; - var newList = new MultiTileEntry[oldList.Length + 1]; - - for (var i = 0; i < oldList.Length; ++i) - newList[i] = oldList[i]; - - newList[oldList.Length] = new MultiTileEntry((ushort)itemID, (short)x, (short)y, (short)z, TileFlag.Background); - - List = newList; - - if (x < m_Min.m_X) - m_Min.m_X = x; - - if (y < m_Min.m_Y) - m_Min.m_Y = y; - - if (x > m_Max.m_X) - m_Max.m_X = x; - - if (y > m_Max.m_Y) - m_Max.m_Y = y; - } - } - - public void RemoveXYZH(int x, int y, int z, int minHeight) - { - var vx = x + Center.m_X; - var vy = y + Center.m_Y; - - if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) - { - var oldTiles = Tiles[vx][vy]; - - for (var i = 0; i < oldTiles.Length; ++i) - { - var tile = oldTiles[i]; - - if (tile.Z == z && tile.Height >= minHeight) - { - var newTiles = new StaticTile[oldTiles.Length - 1]; - - for (var j = 0; j < i; ++j) - newTiles[j] = oldTiles[j]; - - for (var j = i + 1; j < oldTiles.Length; ++j) - newTiles[j - 1] = oldTiles[j]; - - Tiles[vx][vy] = newTiles; - - break; - } - } - - var oldList = List; - - for (var i = 0; i < oldList.Length; ++i) - { - var tile = oldList[i]; - - if (tile.OffsetX == (short)x && tile.OffsetY == (short)y && tile.OffsetZ == (short)z && - TileData.ItemTable[tile.ItemId & TileData.MaxItemValue].Height >= minHeight) - { - var newList = new MultiTileEntry[oldList.Length - 1]; - - for (var j = 0; j < i; ++j) - newList[j] = oldList[j]; - - for (var j = i + 1; j < oldList.Length; ++j) - newList[j - 1] = oldList[j]; - - List = newList; - - break; - } - } - } - } - - public void Remove(int itemID, int x, int y, int z) - { - var vx = x + Center.m_X; - var vy = y + Center.m_Y; - - if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) - { - var oldTiles = Tiles[vx][vy]; - - for (var i = 0; i < oldTiles.Length; ++i) - { - var tile = oldTiles[i]; - - if (tile.ID == itemID && tile.Z == z) - { - var newTiles = new StaticTile[oldTiles.Length - 1]; - - for (var j = 0; j < i; ++j) - newTiles[j] = oldTiles[j]; - - for (var j = i + 1; j < oldTiles.Length; ++j) - newTiles[j - 1] = oldTiles[j]; - - Tiles[vx][vy] = newTiles; - - break; - } - } - - var oldList = List; - - for (var i = 0; i < oldList.Length; ++i) - { - var tile = oldList[i]; - - if (tile.ItemId == itemID && tile.OffsetX == (short)x && tile.OffsetY == (short)y && - tile.OffsetZ == (short)z) - { - var newList = new MultiTileEntry[oldList.Length - 1]; - - for (var j = 0; j < i; ++j) - newList[j] = oldList[j]; - - for (var j = i + 1; j < oldList.Length; ++j) - newList[j - 1] = oldList[j]; - - List = newList; - - break; - } - } - } - } - - public void Resize(int newWidth, int newHeight) - { - int oldWidth = Width, oldHeight = Height; - var oldTiles = Tiles; - - var totalLength = 0; - - var newTiles = new StaticTile[newWidth][][]; - - for (var x = 0; x < newWidth; ++x) - { - newTiles[x] = new StaticTile[newHeight][]; - - for (var y = 0; y < newHeight; ++y) - { - if (x < oldWidth && y < oldHeight) - newTiles[x][y] = oldTiles[x][y]; - else - newTiles[x][y] = Array.Empty(); - - totalLength += newTiles[x][y].Length; - } - } - - Tiles = newTiles; - List = new MultiTileEntry[totalLength]; - Width = newWidth; - Height = newHeight; - - m_Min = Point2D.Zero; - m_Max = Point2D.Zero; - - var index = 0; - - for (var x = 0; x < newWidth; ++x) - for (var y = 0; y < newHeight; ++y) - { - var tiles = newTiles[x][y]; - - for (var i = 0; i < tiles.Length; ++i) - { - var tile = tiles[i]; - - var vx = x - Center.X; - var vy = y - Center.Y; - - if (vx < m_Min.m_X) - m_Min.m_X = vx; - - if (vy < m_Min.m_Y) - m_Min.m_Y = vy; - - if (vx > m_Max.m_X) - m_Max.m_X = vx; - - if (vy > m_Max.m_Y) - m_Max.m_Y = vy; - - List[index++] = new MultiTileEntry((ushort)tile.ID, (short)vx, (short)vy, (short)tile.Z, TileFlag.Background); - } - } - } - - public void Serialize(IGenericWriter writer) - { - writer.Write(1); // version; - - writer.Write(m_Min); - writer.Write(m_Max); - writer.Write(Center); - - writer.Write(Width); - writer.Write(Height); - - writer.Write(List.Length); - - for (var i = 0; i < List.Length; ++i) - { - var ent = List[i]; - - writer.Write(ent.ItemId); - writer.Write(ent.OffsetX); - writer.Write(ent.OffsetY); - writer.Write(ent.OffsetZ); - writer.Write((int)ent.Flags); - } - } - } - - public static class UOPHash - { - public static void BuildChunkIDs(out Dictionary chunkIds) - { - const int maxId = 0x10000; - - chunkIds = new Dictionary(); - - for (var i = 0; i < maxId; ++i) - chunkIds[HashLittle2($"build/multicollection/{i:000000}.bin")] = i; - } - - private static ulong HashLittle2(string s) - { - var length = s.Length; - - uint b, c; - var a = b = c = 0xDEADBEEF + (uint)length; - - var k = 0; - - while (length > 12) - { - a += s[k]; - a += (uint)s[k + 1] << 8; - a += (uint)s[k + 2] << 16; - a += (uint)s[k + 3] << 24; - b += s[k + 4]; - b += (uint)s[k + 5] << 8; - b += (uint)s[k + 6] << 16; - b += (uint)s[k + 7] << 24; - c += s[k + 8]; - c += (uint)s[k + 9] << 8; - c += (uint)s[k + 10] << 16; - c += (uint)s[k + 11] << 24; - - a -= c; - a ^= (c << 4) | (c >> 28); - c += b; - b -= a; - b ^= (a << 6) | (a >> 26); - a += c; - c -= b; - c ^= (b << 8) | (b >> 24); - b += a; - a -= c; - a ^= (c << 16) | (c >> 16); - c += b; - b -= a; - b ^= (a << 19) | (a >> 13); - a += c; - c -= b; - c ^= (b << 4) | (b >> 28); - b += a; - - length -= 12; - k += 12; - } - - if (length != 0) - { - switch (length) - { - case 12: - c += (uint)s[k + 11] << 24; - goto case 11; - case 11: - c += (uint)s[k + 10] << 16; - goto case 10; - case 10: - c += (uint)s[k + 9] << 8; - goto case 9; - case 9: - c += s[k + 8]; - goto case 8; - case 8: - b += (uint)s[k + 7] << 24; - goto case 7; - case 7: - b += (uint)s[k + 6] << 16; - goto case 6; - case 6: - b += (uint)s[k + 5] << 8; - goto case 5; - case 5: - b += s[k + 4]; - goto case 4; - case 4: - a += (uint)s[k + 3] << 24; - goto case 3; - case 3: - a += (uint)s[k + 2] << 16; - goto case 2; - case 2: - a += (uint)s[k + 1] << 8; - goto case 1; - case 1: - a += s[k]; - break; - } - - c ^= b; - c -= (b << 14) | (b >> 18); - a ^= c; - a -= (c << 11) | (c >> 21); - b ^= a; - b -= (a << 25) | (a >> 7); - c ^= b; - c -= (b << 16) | (b >> 16); - a ^= c; - a -= (c << 4) | (c >> 28); - b ^= a; - b -= (a << 14) | (a >> 18); - c ^= b; - c -= (b << 24) | (b >> 8); - } - - return ((ulong)b << 32) | c; - } - } -} +/*************************************************************************** + * MultiData.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; + +namespace Server +{ + public static class MultiData + { + private static readonly BinaryReader m_IndexReader; + private static readonly BinaryReader m_StreamReader; + + private static readonly bool UsingUOPFormat; + + static MultiData() + { + var multiUOPPath = Core.FindDataFile("MultiCollection.uop", false); + + if (File.Exists(multiUOPPath)) + { + LoadUOP(multiUOPPath); + UsingUOPFormat = true; + return; + } + + var idxPath = Core.FindDataFile("multi.idx"); + var mulPath = Core.FindDataFile("multi.mul"); + + var idx = new FileStream(idxPath, FileMode.Open, FileAccess.Read, FileShare.Read); + m_IndexReader = new BinaryReader(idx); + + var stream = new FileStream(mulPath, FileMode.Open, FileAccess.Read, FileShare.Read); + m_StreamReader = new BinaryReader(stream); + + var vdPath = Core.FindDataFile("verdata.mul", false); + + if (!File.Exists(vdPath)) return; + + using var fs = new FileStream(vdPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var bin = new BinaryReader(fs); + + var count = bin.ReadInt32(); + + for (var i = 0; i < count; ++i) + { + var file = bin.ReadInt32(); + var index = bin.ReadInt32(); + var lookup = bin.ReadInt32(); + var length = bin.ReadInt32(); + bin.ReadInt32(); // extra + + if (file == 14 && index >= 0 && lookup >= 0 && length > 0) + { + bin.BaseStream.Seek(lookup, SeekOrigin.Begin); + + Components[index] = new MultiComponentList(bin, length / 12); + + bin.BaseStream.Seek(24 + i * 20, SeekOrigin.Begin); + } + } + + bin.Close(); + } + + public static Dictionary Components { get; } = new Dictionary(); + + public static MultiComponentList GetComponents(int multiID) + { + MultiComponentList mcl; + + multiID &= 0x3FFF; + + if (Components.ContainsKey(multiID)) + mcl = Components[multiID]; + else if (!UsingUOPFormat) + Components[multiID] = mcl = Load(multiID); + else + mcl = MultiComponentList.Empty; + + return mcl; + } + + public static void LoadUOP(string path) + { + var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var streamReader = new BinaryReader(stream); + + // Head Information Start + if (streamReader.ReadInt32() != 0x0050594D) // Not a UOP Files + return; + + if (streamReader.ReadInt32() > 5) // Bad Version + return; + + // Multi ID List Array Start + UOPHash.BuildChunkIDs(out var chunkIds); + // Multi ID List Array End + + streamReader.ReadUInt32(); // format timestamp? 0xFD23EC43 + var startAddress = streamReader.ReadInt64(); + + streamReader.ReadInt32(); + streamReader.ReadInt32(); + + stream.Seek(startAddress, SeekOrigin.Begin); // Head Information End + + long nextBlock; + + do + { + var blockFileCount = streamReader.ReadInt32(); + nextBlock = streamReader.ReadInt64(); + + var index = 0; + + do + { + var offset = streamReader.ReadInt64(); + + var headerSize = streamReader.ReadInt32(); // header length + var compressedSize = streamReader.ReadInt32(); // compressed size + var decompressedSize = streamReader.ReadInt32(); // decompressed size + + var filehash = streamReader.ReadUInt64(); // filename hash (HashLittle2) + streamReader.ReadUInt32(); + var compressionMethod = streamReader.ReadInt16(); // compression method (0 = none, 1 = zlib) + + index++; + + if (offset == 0 || decompressedSize == 0 || filehash == 0x126D1E99DDEDEE0A) // Exclude housing.bin + continue; + + chunkIds.TryGetValue(filehash, out var chunkID); + + var position = stream.Position; // save current position + + stream.Seek(offset + headerSize, SeekOrigin.Begin); + + Span sourceData = new byte[compressedSize]; + + if (stream.Read(sourceData) != compressedSize) + continue; + + Span data; + + if (compressionMethod == 1) + { + data = new byte[decompressedSize]; + Zlib.Unpack(data, ref decompressedSize, sourceData, compressedSize); + } + else + { + data = sourceData; + } + + var tileList = new List(); + + // Skip the first 4 bytes + var reader = new BufferReader(data); + + reader.Advance(4); // ??? + reader.TryReadLittleEndian(out uint count); + + for (uint i = 0; i < count; i++) + { + reader.TryReadLittleEndian(out ushort itemid); + reader.TryReadLittleEndian(out short x); + reader.TryReadLittleEndian(out short y); + reader.TryReadLittleEndian(out short z); + reader.TryReadLittleEndian(out ushort flagValue); + + var tileFlag = flagValue switch + { + 1 => TileFlag.None, + 257 => TileFlag.Generic, + _ => TileFlag.Background // 0 + }; + + reader.TryReadLittleEndian(out uint clilocsCount); + reader.Advance(clilocsCount * 4); // bypass binary block + + tileList.Add(new MultiTileEntry(itemid, x, y, z, tileFlag)); + } + + Components[chunkID] = new MultiComponentList(tileList); + + stream.Seek(position, SeekOrigin.Begin); // back to position + } while (index < blockFileCount); + } while (stream.Seek(nextBlock, SeekOrigin.Begin) != 0); + } + + // TODO: Change this to read the file all during load time + public static MultiComponentList Load(int multiID) + { + try + { + m_IndexReader.BaseStream.Seek(multiID * 12, SeekOrigin.Begin); + + var lookup = m_IndexReader.ReadInt32(); + var length = m_IndexReader.ReadInt32(); + + if (lookup < 0 || length <= 0) + return MultiComponentList.Empty; + + m_StreamReader.BaseStream.Seek(lookup, SeekOrigin.Begin); + + return new MultiComponentList(m_StreamReader, length / (MultiComponentList.PostHSFormat ? 16 : 12)); + } + catch + { + return MultiComponentList.Empty; + } + } + } + + public struct MultiTileEntry + { + public ushort ItemId { get; set; } + public short OffsetX { get; set; } + public short OffsetY { get; set; } + public short OffsetZ { get; set; } + public TileFlag Flags { get; set; } + + public MultiTileEntry(ushort itemID, short xOffset, short yOffset, short zOffset, TileFlag flags) + { + ItemId = itemID; + OffsetX = xOffset; + OffsetY = yOffset; + OffsetZ = zOffset; + Flags = flags; + } + } + + public sealed class MultiComponentList + { + public static readonly MultiComponentList Empty = new MultiComponentList(); + + private Point2D m_Min, m_Max; + + public MultiComponentList(MultiComponentList toCopy) + { + m_Min = toCopy.m_Min; + m_Max = toCopy.m_Max; + + Center = toCopy.Center; + + Width = toCopy.Width; + Height = toCopy.Height; + + Tiles = new StaticTile[Width][][]; + + for (var x = 0; x < Width; ++x) + { + Tiles[x] = new StaticTile[Height][]; + + for (var y = 0; y < Height; ++y) + { + Tiles[x][y] = new StaticTile[toCopy.Tiles[x][y].Length]; + + for (var i = 0; i < Tiles[x][y].Length; ++i) + Tiles[x][y][i] = toCopy.Tiles[x][y][i]; + } + } + + List = new MultiTileEntry[toCopy.List.Length]; + + for (var i = 0; i < List.Length; ++i) + List[i] = toCopy.List[i]; + } + + public MultiComponentList(IGenericReader reader) + { + var version = reader.ReadInt(); + + m_Min = reader.ReadPoint2D(); + m_Max = reader.ReadPoint2D(); + Center = reader.ReadPoint2D(); + Width = reader.ReadInt(); + Height = reader.ReadInt(); + + var length = reader.ReadInt(); + + var allTiles = List = new MultiTileEntry[length]; + + if (version == 0) + for (var i = 0; i < length; ++i) + { + int id = reader.ReadShort(); + if (id >= 0x4000) + id -= 0x4000; + + allTiles[i].ItemId = (ushort)id; + allTiles[i].OffsetX = reader.ReadShort(); + allTiles[i].OffsetY = reader.ReadShort(); + allTiles[i].OffsetZ = reader.ReadShort(); + allTiles[i].Flags = (TileFlag)reader.ReadInt(); + } + else + for (var i = 0; i < length; ++i) + { + allTiles[i].ItemId = reader.ReadUShort(); + allTiles[i].OffsetX = reader.ReadShort(); + allTiles[i].OffsetY = reader.ReadShort(); + allTiles[i].OffsetZ = reader.ReadShort(); + allTiles[i].Flags = (TileFlag)reader.ReadInt(); + } + + var tiles = new TileList[Width][]; + Tiles = new StaticTile[Width][][]; + + for (var x = 0; x < Width; ++x) + { + tiles[x] = new TileList[Height]; + Tiles[x] = new StaticTile[Height][]; + + for (var y = 0; y < Height; ++y) + tiles[x][y] = new TileList(); + } + + for (var i = 0; i < allTiles.Length; ++i) + if (i == 0 || allTiles[i].Flags != 0) + { + var xOffset = allTiles[i].OffsetX + Center.m_X; + var yOffset = allTiles[i].OffsetY + Center.m_Y; + + tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); + } + + for (var x = 0; x < Width; ++x) + for (var y = 0; y < Height; ++y) + Tiles[x][y] = tiles[x][y].ToArray(); + } + + public MultiComponentList(BinaryReader reader, int count) + { + var allTiles = List = new MultiTileEntry[count]; + + for (var i = 0; i < count; ++i) + { + allTiles[i].ItemId = reader.ReadUInt16(); + allTiles[i].OffsetX = reader.ReadInt16(); + allTiles[i].OffsetY = reader.ReadInt16(); + allTiles[i].OffsetZ = reader.ReadInt16(); + + if (PostHSFormat) + allTiles[i].Flags = (TileFlag)reader.ReadUInt64(); + else + allTiles[i].Flags = (TileFlag)reader.ReadUInt32(); + + var e = allTiles[i]; + + if (i == 0 || e.Flags != 0) + { + if (e.OffsetX < m_Min.m_X) + m_Min.m_X = e.OffsetX; + + if (e.OffsetY < m_Min.m_Y) + m_Min.m_Y = e.OffsetY; + + if (e.OffsetX > m_Max.m_X) + m_Max.m_X = e.OffsetX; + + if (e.OffsetY > m_Max.m_Y) + m_Max.m_Y = e.OffsetY; + } + } + + Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); + Width = m_Max.m_X - m_Min.m_X + 1; + Height = m_Max.m_Y - m_Min.m_Y + 1; + + var tiles = new TileList[Width][]; + Tiles = new StaticTile[Width][][]; + + for (var x = 0; x < Width; ++x) + { + tiles[x] = new TileList[Height]; + Tiles[x] = new StaticTile[Height][]; + + for (var y = 0; y < Height; ++y) + tiles[x][y] = new TileList(); + } + + for (var i = 0; i < allTiles.Length; ++i) + if (i == 0 || allTiles[i].Flags != 0) + { + var xOffset = allTiles[i].OffsetX + Center.m_X; + var yOffset = allTiles[i].OffsetY + Center.m_Y; + + tiles[xOffset][yOffset].Add(allTiles[i].ItemId, (sbyte)allTiles[i].OffsetZ); + } + + for (var x = 0; x < Width; ++x) + for (var y = 0; y < Height; ++y) + Tiles[x][y] = tiles[x][y].ToArray(); + } + + public MultiComponentList(List list) + { + var allTiles = List = new MultiTileEntry[list.Count]; + + for (var i = 0; i < list.Count; ++i) + { + allTiles[i].ItemId = list[i].ItemId; + allTiles[i].OffsetX = list[i].OffsetX; + allTiles[i].OffsetY = list[i].OffsetY; + allTiles[i].OffsetZ = list[i].OffsetZ; + + allTiles[i].Flags = list[i].Flags; + + var e = allTiles[i]; + + if (i == 0 || e.Flags != 0) + { + if (e.OffsetX < m_Min.m_X) m_Min.m_X = e.OffsetX; + + if (e.OffsetY < m_Min.m_Y) m_Min.m_Y = e.OffsetY; + + if (e.OffsetX > m_Max.m_X) m_Max.m_X = e.OffsetX; + + if (e.OffsetY > m_Max.m_Y) m_Max.m_Y = e.OffsetY; + } + } + + Center = new Point2D(-m_Min.m_X, -m_Min.m_Y); + Width = m_Max.m_X - m_Min.m_X + 1; + Height = m_Max.m_Y - m_Min.m_Y + 1; + + var tiles = new TileList[Width][]; + Tiles = new StaticTile[Width][][]; + + for (var x = 0; x < Width; ++x) + { + tiles[x] = new TileList[Height]; + Tiles[x] = new StaticTile[Height][]; + + for (var y = 0; y < Height; ++y) tiles[x][y] = new TileList(); + } + + for (var i = 0; i < allTiles.Length; ++i) + if (i == 0 || allTiles[i].Flags != 0) + { + var xOffset = allTiles[i].OffsetX + Center.m_X; + var yOffset = allTiles[i].OffsetY + Center.m_Y; + var itemID = (allTiles[i].ItemId & TileData.MaxItemValue) | 0x10000; + + tiles[xOffset][yOffset].Add((ushort)itemID, (sbyte)allTiles[i].OffsetZ); + } + + for (var x = 0; x < Width; ++x) + for (var y = 0; y < Height; ++y) + Tiles[x][y] = tiles[x][y].ToArray(); + } + + private MultiComponentList() + { + Tiles = Array.Empty(); + List = Array.Empty(); + } + + public static bool PostHSFormat { get; set; } + + public Point2D Min => m_Min; + public Point2D Max => m_Max; + + public Point2D Center { get; } + + public int Width { get; private set; } + + public int Height { get; private set; } + + public StaticTile[][][] Tiles { get; private set; } + + public MultiTileEntry[] List { get; private set; } + + public void Add(int itemID, int x, int y, int z) + { + var vx = x + Center.m_X; + var vy = y + Center.m_Y; + + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + { + var oldTiles = Tiles[vx][vy]; + + for (var i = oldTiles.Length - 1; i >= 0; --i) + { + var data = TileData.ItemTable[itemID & TileData.MaxItemValue]; + + if (oldTiles[i].Z == z && oldTiles[i].Height > 0 == data.Height > 0) + { + var newIsRoof = (data.Flags & TileFlag.Roof) != 0; + var oldIsRoof = + (TileData.ItemTable[oldTiles[i].ID & TileData.MaxItemValue].Flags & TileFlag.Roof) != 0; + + if (newIsRoof == oldIsRoof) + Remove(oldTiles[i].ID, x, y, z); + } + } + + oldTiles = Tiles[vx][vy]; + + var newTiles = new StaticTile[oldTiles.Length + 1]; + + for (var i = 0; i < oldTiles.Length; ++i) + newTiles[i] = oldTiles[i]; + + newTiles[oldTiles.Length] = new StaticTile((ushort)itemID, (sbyte)z); + + Tiles[vx][vy] = newTiles; + + var oldList = List; + var newList = new MultiTileEntry[oldList.Length + 1]; + + for (var i = 0; i < oldList.Length; ++i) + newList[i] = oldList[i]; + + newList[oldList.Length] = new MultiTileEntry( + (ushort)itemID, + (short)x, + (short)y, + (short)z, + TileFlag.Background + ); + + List = newList; + + if (x < m_Min.m_X) + m_Min.m_X = x; + + if (y < m_Min.m_Y) + m_Min.m_Y = y; + + if (x > m_Max.m_X) + m_Max.m_X = x; + + if (y > m_Max.m_Y) + m_Max.m_Y = y; + } + } + + public void RemoveXYZH(int x, int y, int z, int minHeight) + { + var vx = x + Center.m_X; + var vy = y + Center.m_Y; + + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + { + var oldTiles = Tiles[vx][vy]; + + for (var i = 0; i < oldTiles.Length; ++i) + { + var tile = oldTiles[i]; + + if (tile.Z == z && tile.Height >= minHeight) + { + var newTiles = new StaticTile[oldTiles.Length - 1]; + + for (var j = 0; j < i; ++j) + newTiles[j] = oldTiles[j]; + + for (var j = i + 1; j < oldTiles.Length; ++j) + newTiles[j - 1] = oldTiles[j]; + + Tiles[vx][vy] = newTiles; + + break; + } + } + + var oldList = List; + + for (var i = 0; i < oldList.Length; ++i) + { + var tile = oldList[i]; + + if (tile.OffsetX == (short)x && tile.OffsetY == (short)y && tile.OffsetZ == (short)z && + TileData.ItemTable[tile.ItemId & TileData.MaxItemValue].Height >= minHeight) + { + var newList = new MultiTileEntry[oldList.Length - 1]; + + for (var j = 0; j < i; ++j) + newList[j] = oldList[j]; + + for (var j = i + 1; j < oldList.Length; ++j) + newList[j - 1] = oldList[j]; + + List = newList; + + break; + } + } + } + } + + public void Remove(int itemID, int x, int y, int z) + { + var vx = x + Center.m_X; + var vy = y + Center.m_Y; + + if (vx >= 0 && vx < Width && vy >= 0 && vy < Height) + { + var oldTiles = Tiles[vx][vy]; + + for (var i = 0; i < oldTiles.Length; ++i) + { + var tile = oldTiles[i]; + + if (tile.ID == itemID && tile.Z == z) + { + var newTiles = new StaticTile[oldTiles.Length - 1]; + + for (var j = 0; j < i; ++j) + newTiles[j] = oldTiles[j]; + + for (var j = i + 1; j < oldTiles.Length; ++j) + newTiles[j - 1] = oldTiles[j]; + + Tiles[vx][vy] = newTiles; + + break; + } + } + + var oldList = List; + + for (var i = 0; i < oldList.Length; ++i) + { + var tile = oldList[i]; + + if (tile.ItemId == itemID && tile.OffsetX == (short)x && tile.OffsetY == (short)y && + tile.OffsetZ == (short)z) + { + var newList = new MultiTileEntry[oldList.Length - 1]; + + for (var j = 0; j < i; ++j) + newList[j] = oldList[j]; + + for (var j = i + 1; j < oldList.Length; ++j) + newList[j - 1] = oldList[j]; + + List = newList; + + break; + } + } + } + } + + public void Resize(int newWidth, int newHeight) + { + int oldWidth = Width, oldHeight = Height; + var oldTiles = Tiles; + + var totalLength = 0; + + var newTiles = new StaticTile[newWidth][][]; + + for (var x = 0; x < newWidth; ++x) + { + newTiles[x] = new StaticTile[newHeight][]; + + for (var y = 0; y < newHeight; ++y) + { + if (x < oldWidth && y < oldHeight) + newTiles[x][y] = oldTiles[x][y]; + else + newTiles[x][y] = Array.Empty(); + + totalLength += newTiles[x][y].Length; + } + } + + Tiles = newTiles; + List = new MultiTileEntry[totalLength]; + Width = newWidth; + Height = newHeight; + + m_Min = Point2D.Zero; + m_Max = Point2D.Zero; + + var index = 0; + + for (var x = 0; x < newWidth; ++x) + for (var y = 0; y < newHeight; ++y) + { + var tiles = newTiles[x][y]; + + for (var i = 0; i < tiles.Length; ++i) + { + var tile = tiles[i]; + + var vx = x - Center.X; + var vy = y - Center.Y; + + if (vx < m_Min.m_X) + m_Min.m_X = vx; + + if (vy < m_Min.m_Y) + m_Min.m_Y = vy; + + if (vx > m_Max.m_X) + m_Max.m_X = vx; + + if (vy > m_Max.m_Y) + m_Max.m_Y = vy; + + List[index++] = new MultiTileEntry( + (ushort)tile.ID, + (short)vx, + (short)vy, + (short)tile.Z, + TileFlag.Background + ); + } + } + } + + public void Serialize(IGenericWriter writer) + { + writer.Write(1); // version; + + writer.Write(m_Min); + writer.Write(m_Max); + writer.Write(Center); + + writer.Write(Width); + writer.Write(Height); + + writer.Write(List.Length); + + for (var i = 0; i < List.Length; ++i) + { + var ent = List[i]; + + writer.Write(ent.ItemId); + writer.Write(ent.OffsetX); + writer.Write(ent.OffsetY); + writer.Write(ent.OffsetZ); + writer.Write((int)ent.Flags); + } + } + } + + public static class UOPHash + { + public static void BuildChunkIDs(out Dictionary chunkIds) + { + const int maxId = 0x10000; + + chunkIds = new Dictionary(); + + for (var i = 0; i < maxId; ++i) + chunkIds[HashLittle2($"build/multicollection/{i:000000}.bin")] = i; + } + + private static ulong HashLittle2(string s) + { + var length = s.Length; + + uint b, c; + var a = b = c = 0xDEADBEEF + (uint)length; + + var k = 0; + + while (length > 12) + { + a += s[k]; + a += (uint)s[k + 1] << 8; + a += (uint)s[k + 2] << 16; + a += (uint)s[k + 3] << 24; + b += s[k + 4]; + b += (uint)s[k + 5] << 8; + b += (uint)s[k + 6] << 16; + b += (uint)s[k + 7] << 24; + c += s[k + 8]; + c += (uint)s[k + 9] << 8; + c += (uint)s[k + 10] << 16; + c += (uint)s[k + 11] << 24; + + a -= c; + a ^= (c << 4) | (c >> 28); + c += b; + b -= a; + b ^= (a << 6) | (a >> 26); + a += c; + c -= b; + c ^= (b << 8) | (b >> 24); + b += a; + a -= c; + a ^= (c << 16) | (c >> 16); + c += b; + b -= a; + b ^= (a << 19) | (a >> 13); + a += c; + c -= b; + c ^= (b << 4) | (b >> 28); + b += a; + + length -= 12; + k += 12; + } + + if (length != 0) + { + switch (length) + { + case 12: + c += (uint)s[k + 11] << 24; + goto case 11; + case 11: + c += (uint)s[k + 10] << 16; + goto case 10; + case 10: + c += (uint)s[k + 9] << 8; + goto case 9; + case 9: + c += s[k + 8]; + goto case 8; + case 8: + b += (uint)s[k + 7] << 24; + goto case 7; + case 7: + b += (uint)s[k + 6] << 16; + goto case 6; + case 6: + b += (uint)s[k + 5] << 8; + goto case 5; + case 5: + b += s[k + 4]; + goto case 4; + case 4: + a += (uint)s[k + 3] << 24; + goto case 3; + case 3: + a += (uint)s[k + 2] << 16; + goto case 2; + case 2: + a += (uint)s[k + 1] << 8; + goto case 1; + case 1: + a += s[k]; + break; + } + + c ^= b; + c -= (b << 14) | (b >> 18); + a ^= c; + a -= (c << 11) | (c >> 21); + b ^= a; + b -= (a << 25) | (a >> 7); + c ^= b; + c -= (b << 16) | (b >> 16); + a ^= c; + a -= (c << 4) | (c >> 28); + b ^= a; + b -= (a << 14) | (a >> 18); + c ^= b; + c -= (b << 24) | (b >> 8); + } + + return ((ulong)b << 32) | c; + } + } +} diff --git a/Projects/Server/NativeReader.cs b/Projects/Server/NativeReader.cs index c3220f4de..6ff474e39 100644 --- a/Projects/Server/NativeReader.cs +++ b/Projects/Server/NativeReader.cs @@ -1,82 +1,84 @@ -/*************************************************************************** - * NativeReader.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Server -{ - public static class NativeReader - { - private static readonly INativeReader m_NativeReader; - - static NativeReader() - { - if (Core.Unix) - m_NativeReader = new NativeReaderUnix(); - else - m_NativeReader = new NativeReaderWin32(); - } - - public static unsafe void Read(IntPtr p, void* buffer, int length) - { - m_NativeReader.Read(p, buffer, length); - } - } - - public interface INativeReader - { - unsafe void Read(IntPtr p, void* buffer, int length); - } - - public sealed class NativeReaderWin32 : INativeReader - { - public unsafe void Read(IntPtr p, void* buffer, int length) - { - uint lpNumberOfBytesRead = 0; - UnsafeNativeMethods.ReadFile(p, buffer, (uint)length, ref lpNumberOfBytesRead, null); - } - - internal static class UnsafeNativeMethods - { - /*[DllImport("kernel32")] - internal unsafe static extern int _lread(IntPtr hFile, void* lpBuffer, int wBytes);*/ - - [DllImport("kernel32")] - internal static extern unsafe bool ReadFile(IntPtr hFile, void* lpBuffer, uint nNumberOfBytesToRead, - ref uint lpNumberOfBytesRead, NativeOverlapped* lpOverlapped); - } - } - - public sealed class NativeReaderUnix : INativeReader - { - public unsafe void Read(IntPtr p, void* buffer, int length) - { - _ = UnsafeNativeMethods.read(p, buffer, length); - } - - internal static class UnsafeNativeMethods - { - [DllImport("libc")] - internal static extern unsafe int read(IntPtr p, void* buffer, int length); - } - } -} +/*************************************************************************** + * NativeReader.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Server +{ + public static class NativeReader + { + private static readonly INativeReader m_NativeReader; + + static NativeReader() + { + if (Core.Unix) + m_NativeReader = new NativeReaderUnix(); + else + m_NativeReader = new NativeReaderWin32(); + } + + public static unsafe void Read(IntPtr p, void* buffer, int length) + { + m_NativeReader.Read(p, buffer, length); + } + } + + public interface INativeReader + { + unsafe void Read(IntPtr p, void* buffer, int length); + } + + public sealed class NativeReaderWin32 : INativeReader + { + public unsafe void Read(IntPtr p, void* buffer, int length) + { + uint lpNumberOfBytesRead = 0; + UnsafeNativeMethods.ReadFile(p, buffer, (uint)length, ref lpNumberOfBytesRead, null); + } + + internal static class UnsafeNativeMethods + { + /*[DllImport("kernel32")] + internal unsafe static extern int _lread(IntPtr hFile, void* lpBuffer, int wBytes);*/ + + [DllImport("kernel32")] + internal static extern unsafe bool ReadFile( + IntPtr hFile, void* lpBuffer, uint nNumberOfBytesToRead, + ref uint lpNumberOfBytesRead, NativeOverlapped* lpOverlapped + ); + } + } + + public sealed class NativeReaderUnix : INativeReader + { + public unsafe void Read(IntPtr p, void* buffer, int length) + { + _ = UnsafeNativeMethods.read(p, buffer, length); + } + + internal static class UnsafeNativeMethods + { + [DllImport("libc")] + internal static extern unsafe int read(IntPtr p, void* buffer, int length); + } + } +} diff --git a/Projects/Server/Network/EncodedPacketHandler.cs b/Projects/Server/Network/EncodedPacketHandler.cs index d6394f79f..58163f9c5 100644 --- a/Projects/Server/Network/EncodedPacketHandler.cs +++ b/Projects/Server/Network/EncodedPacketHandler.cs @@ -1,40 +1,40 @@ -/*************************************************************************** - * EncodedPacketHandler.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Network -{ - public delegate void OnEncodedPacketReceive(NetState state, IEntity ent, EncodedReader pvSrc); - - public class EncodedPacketHandler - { - public EncodedPacketHandler(int packetID, bool ingame, OnEncodedPacketReceive onReceive) - { - PacketID = packetID; - Ingame = ingame; - OnReceive = onReceive; - } - - public int PacketID { get; } - - public OnEncodedPacketReceive OnReceive { get; } - - public bool Ingame { get; } - } -} +/*************************************************************************** + * EncodedPacketHandler.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Network +{ + public delegate void OnEncodedPacketReceive(NetState state, IEntity ent, EncodedReader pvSrc); + + public class EncodedPacketHandler + { + public EncodedPacketHandler(int packetID, bool ingame, OnEncodedPacketReceive onReceive) + { + PacketID = packetID; + Ingame = ingame; + OnReceive = onReceive; + } + + public int PacketID { get; } + + public OnEncodedPacketReceive OnReceive { get; } + + public bool Ingame { get; } + } +} diff --git a/Projects/Server/Network/EncodedReader.cs b/Projects/Server/Network/EncodedReader.cs index d360aa834..ad71681de 100644 --- a/Projects/Server/Network/EncodedReader.cs +++ b/Projects/Server/Network/EncodedReader.cs @@ -1,46 +1,46 @@ -/*************************************************************************** - * EncodedReader.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Network -{ - public ref struct EncodedReader - { - private PacketReader m_Reader; - - public EncodedReader(PacketReader reader) => m_Reader = reader; - - public void Trace(NetState state) - { - m_Reader.Trace(state); - } - - public int ReadInt32() => m_Reader.ReadByte() != 0 ? 0 : m_Reader.ReadInt32(); - - public Point3D ReadPoint3D() => m_Reader.ReadByte() != 3 - ? Point3D.Zero - : new Point3D(m_Reader.ReadInt16(), m_Reader.ReadInt16(), m_Reader.ReadByte()); - - public string ReadUnicodeStringSafe() => - m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeStringSafe(m_Reader.ReadUInt16()); - - public string ReadUnicodeString() => - m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeString(m_Reader.ReadUInt16()); - } -} +/*************************************************************************** + * EncodedReader.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Network +{ + public ref struct EncodedReader + { + private PacketReader m_Reader; + + public EncodedReader(PacketReader reader) => m_Reader = reader; + + public void Trace(NetState state) + { + m_Reader.Trace(state); + } + + public int ReadInt32() => m_Reader.ReadByte() != 0 ? 0 : m_Reader.ReadInt32(); + + public Point3D ReadPoint3D() => m_Reader.ReadByte() != 3 + ? Point3D.Zero + : new Point3D(m_Reader.ReadInt16(), m_Reader.ReadInt16(), m_Reader.ReadByte()); + + public string ReadUnicodeStringSafe() => + m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeStringSafe(m_Reader.ReadUInt16()); + + public string ReadUnicodeString() => + m_Reader.ReadByte() != 2 ? string.Empty : m_Reader.ReadUnicodeString(m_Reader.ReadUInt16()); + } +} diff --git a/Projects/Server/Network/MessagePumpService.cs b/Projects/Server/Network/MessagePumpService.cs index da54e0989..f7f88c015 100644 --- a/Projects/Server/Network/MessagePumpService.cs +++ b/Projects/Server/Network/MessagePumpService.cs @@ -1,73 +1,73 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MessagePumpService.cs * - * Created: 2020/04/12 - Updated: 2020/04/12 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.Buffers; -using System.Collections.Concurrent; - -namespace Server.Network -{ - public interface IMessagePumpService - { - void QueueWork(NetState ns, IMemoryOwner memOwner, int length, OnPacketReceive onReceive); - void DoWork(); - } - - public class MessagePumpService : IMessagePumpService - { - private readonly ConcurrentQueue m_WorkQueue = new ConcurrentQueue(); - - public void QueueWork(NetState ns, IMemoryOwner memOwner, int length, OnPacketReceive onReceive) - { - m_WorkQueue.Enqueue(new Work(ns, memOwner, length, onReceive)); - Core.Set(); - } - - public void DoWork() - { - var count = 0; - while (!m_WorkQueue.IsEmpty && count++ < 250) - { - if (!m_WorkQueue.TryDequeue(out var work)) - break; - - var seq = new ReadOnlySequence(work.MemoryOwner.Memory.Slice(0, work.Length)); - work.OnReceive(work.State, new PacketReader(seq)); - work.MemoryOwner.Dispose(); - } - } - - private class Work - { - public readonly NetState State; - public readonly IMemoryOwner MemoryOwner; - public readonly int Length; - public readonly OnPacketReceive OnReceive; - - public Work(NetState ns, IMemoryOwner memOwner, int length, OnPacketReceive onReceive) - { - State = ns; - MemoryOwner = memOwner; - OnReceive = onReceive; - Length = length; - } - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MessagePumpService.cs * + * Created: 2020/04/12 - Updated: 2020/04/12 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Buffers; +using System.Collections.Concurrent; + +namespace Server.Network +{ + public interface IMessagePumpService + { + void QueueWork(NetState ns, IMemoryOwner memOwner, int length, OnPacketReceive onReceive); + void DoWork(); + } + + public class MessagePumpService : IMessagePumpService + { + private readonly ConcurrentQueue m_WorkQueue = new ConcurrentQueue(); + + public void QueueWork(NetState ns, IMemoryOwner memOwner, int length, OnPacketReceive onReceive) + { + m_WorkQueue.Enqueue(new Work(ns, memOwner, length, onReceive)); + Core.Set(); + } + + public void DoWork() + { + var count = 0; + while (!m_WorkQueue.IsEmpty && count++ < 250) + { + if (!m_WorkQueue.TryDequeue(out var work)) + break; + + var seq = new ReadOnlySequence(work.MemoryOwner.Memory.Slice(0, work.Length)); + work.OnReceive(work.State, new PacketReader(seq)); + work.MemoryOwner.Dispose(); + } + } + + private class Work + { + public readonly int Length; + public readonly IMemoryOwner MemoryOwner; + public readonly OnPacketReceive OnReceive; + public readonly NetState State; + + public Work(NetState ns, IMemoryOwner memOwner, int length, OnPacketReceive onReceive) + { + State = ns; + MemoryOwner = memOwner; + OnReceive = onReceive; + Length = length; + } + } + } +} diff --git a/Projects/Server/Network/NetState.cs b/Projects/Server/Network/NetState.cs index 566e0c5ef..5793ae19f 100644 --- a/Projects/Server/Network/NetState.cs +++ b/Projects/Server/Network/NetState.cs @@ -1,655 +1,656 @@ -/*************************************************************************** - * NetState.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Server.Accounting; -using Server.Gumps; -using Server.HuePickers; -using Server.Items; -using Server.Menus; - -namespace Server.Network -{ - public interface IPacketEncoder - { - void EncodeOutgoingPacket(NetState to, ref Memory seq); - void DecodeIncomingPacket(NetState from, ref Memory seq); - } - - public delegate void NetStateCreatedCallback(NetState ns); - - [Flags] - public enum ProtocolChanges - { - NewSpellbook = 0x00000001, - DamagePacket = 0x00000002, - Unpack = 0x00000004, - BuffIcon = 0x00000008, - NewHaven = 0x00000010, - ContainerGridLines = 0x00000020, - ExtendedSupportedFeatures = 0x00000040, - StygianAbyss = 0x00000080, - HighSeas = 0x00000100, - NewCharacterList = 0x00000200, - NewCharacterCreation = 0x00000400, - ExtendedStatus = 0x00000800, - NewMobileIncoming = 0x00001000, - NewSecureTrading = 0x00002000, - UltimaStore = 0x00004000, - EndlessJourney = 0x00008000, - - Version400a = NewSpellbook, - Version407a = Version400a | DamagePacket, - Version500a = Version407a | Unpack, - Version502b = Version500a | BuffIcon, - Version6000 = Version502b | NewHaven, - Version6017 = Version6000 | ContainerGridLines, - Version60142 = Version6017 | ExtendedSupportedFeatures, - Version7000 = Version60142 | StygianAbyss, - Version7090 = Version7000 | HighSeas, - Version70130 = Version7090 | NewCharacterList, - Version70160 = Version70130 | NewCharacterCreation, - Version70300 = Version70160 | ExtendedStatus, - Version70331 = Version70300 | NewMobileIncoming, - Version704565 = Version70331 | NewSecureTrading, - Version70500 = Version704565 | UltimaStore, - Version70610 = Version70500 | EndlessJourney - } - - public class AsyncState - { - public bool Paused { get; set; } - public AsyncState(bool paused) => Paused = paused; - } - - public class NetState : IComparable - { - private readonly string m_ToString; - private ClientVersion m_Version; - - public DateTime ConnectedOn { get; } - - public TimeSpan ConnectedFor => DateTime.UtcNow - ConnectedOn; - - public DateTime ThrottledUntil { get; set; } - - internal int m_Seed; - internal int m_AuthID; - - public IPAddress Address { get; } - - private static readonly AsyncState m_PauseState = new AsyncState(true); - private static readonly AsyncState m_ResumeState = new AsyncState(false); - - private static AsyncState m_AsyncState = m_ResumeState; - public static AsyncState AsyncState => m_AsyncState; - - public IPacketEncoder PacketEncoder { get; set; } - - public static NetStateCreatedCallback CreatedCallback { get; set; } - - public bool SentFirstPacket { get; set; } - - public bool BlockAllPackets { get; set; } - - public ClientFlags Flags { get; set; } - - public ClientVersion Version - { - get => m_Version; - set - { - m_Version = value; - - if (value >= m_Version70610) - ProtocolChanges = ProtocolChanges.Version70610; - if (value >= m_Version70500) - ProtocolChanges = ProtocolChanges.Version70500; - if (value >= m_Version704565) - ProtocolChanges = ProtocolChanges.Version704565; - else if (value >= m_Version70331) - ProtocolChanges = ProtocolChanges.Version70331; - else if (value >= m_Version70300) - ProtocolChanges = ProtocolChanges.Version70300; - else if (value >= m_Version70160) - ProtocolChanges = ProtocolChanges.Version70160; - else if (value >= m_Version70130) - ProtocolChanges = ProtocolChanges.Version70130; - else if (value >= m_Version7090) - ProtocolChanges = ProtocolChanges.Version7090; - else if (value >= m_Version7000) - ProtocolChanges = ProtocolChanges.Version7000; - else if (value >= m_Version60142) - ProtocolChanges = ProtocolChanges.Version60142; - else if (value >= m_Version6017) - ProtocolChanges = ProtocolChanges.Version6017; - else if (value >= m_Version6000) - ProtocolChanges = ProtocolChanges.Version6000; - else if (value >= m_Version502b) - ProtocolChanges = ProtocolChanges.Version502b; - else if (value >= m_Version500a) - ProtocolChanges = ProtocolChanges.Version500a; - else if (value >= m_Version407a) - ProtocolChanges = ProtocolChanges.Version407a; - else if (value >= m_Version400a) ProtocolChanges = ProtocolChanges.Version400a; - } - } - - private static readonly ClientVersion m_Version400a = new ClientVersion("4.0.0a"); - private static readonly ClientVersion m_Version407a = new ClientVersion("4.0.7a"); - private static readonly ClientVersion m_Version500a = new ClientVersion("5.0.0a"); - private static readonly ClientVersion m_Version502b = new ClientVersion("5.0.2b"); - private static readonly ClientVersion m_Version6000 = new ClientVersion("6.0.0.0"); - private static readonly ClientVersion m_Version6017 = new ClientVersion("6.0.1.7"); - private static readonly ClientVersion m_Version60142 = new ClientVersion("6.0.14.2"); - private static readonly ClientVersion m_Version7000 = new ClientVersion("7.0.0.0"); - private static readonly ClientVersion m_Version7090 = new ClientVersion("7.0.9.0"); - private static readonly ClientVersion m_Version70130 = new ClientVersion("7.0.13.0"); - private static readonly ClientVersion m_Version70160 = new ClientVersion("7.0.16.0"); - private static readonly ClientVersion m_Version70300 = new ClientVersion("7.0.30.0"); - private static readonly ClientVersion m_Version70331 = new ClientVersion("7.0.33.1"); - private static readonly ClientVersion m_Version704565 = new ClientVersion("7.0.45.65"); - private static readonly ClientVersion m_Version70500 = new ClientVersion("7.0.50.0"); - private static readonly ClientVersion m_Version70610 = new ClientVersion("7.0.61.0"); - - public bool NewSpellbook => (ProtocolChanges & ProtocolChanges.NewSpellbook) != 0; - public bool DamagePacket => (ProtocolChanges & ProtocolChanges.DamagePacket) != 0; - public bool Unpack => (ProtocolChanges & ProtocolChanges.Unpack) != 0; - public bool BuffIcon => (ProtocolChanges & ProtocolChanges.BuffIcon) != 0; - public bool NewHaven => (ProtocolChanges & ProtocolChanges.NewHaven) != 0; - public bool ContainerGridLines => (ProtocolChanges & ProtocolChanges.ContainerGridLines) != 0; - public bool ExtendedSupportedFeatures => (ProtocolChanges & ProtocolChanges.ExtendedSupportedFeatures) != 0; - public bool StygianAbyss => (ProtocolChanges & ProtocolChanges.StygianAbyss) != 0; - public bool HighSeas => (ProtocolChanges & ProtocolChanges.HighSeas) != 0; - public bool NewCharacterList => (ProtocolChanges & ProtocolChanges.NewCharacterList) != 0; - public bool NewCharacterCreation => (ProtocolChanges & ProtocolChanges.NewCharacterCreation) != 0; - public bool ExtendedStatus => (ProtocolChanges & ProtocolChanges.ExtendedStatus) != 0; - public bool NewMobileIncoming => (ProtocolChanges & ProtocolChanges.NewMobileIncoming) != 0; - public bool NewSecureTrading => (ProtocolChanges & ProtocolChanges.NewSecureTrading) != 0; - - public bool IsUOTDClient => - (Flags & ClientFlags.UOTD) != 0 || m_Version?.Type == ClientType.UOTD; - - public bool IsSAClient => m_Version?.Type == ClientType.SA; - - public List Trades { get; } - - public void ValidateAllTrades() - { - for (var i = Trades.Count - 1; i >= 0; --i) - { - if (i >= Trades.Count) continue; - - var trade = Trades[i]; - - if (trade.From.Mobile.Deleted || trade.To.Mobile.Deleted || !trade.From.Mobile.Alive || - !trade.To.Mobile.Alive || !trade.From.Mobile.InRange(trade.To.Mobile, 2) || - trade.From.Mobile.Map != trade.To.Mobile.Map) trade.Cancel(); - } - } - - public void CancelAllTrades() - { - for (var i = Trades.Count - 1; i >= 0; --i) - if (i < Trades.Count) - Trades[i].Cancel(); - } - - public void RemoveTrade(SecureTrade trade) - { - Trades.Remove(trade); - } - - public SecureTrade FindTrade(Mobile m) - { - for (var i = 0; i < Trades.Count; ++i) - { - var trade = Trades[i]; - - if (trade.From.Mobile == m || trade.To.Mobile == m) return trade; - } - - return null; - } - - public SecureTradeContainer FindTradeContainer(Mobile m) - { - for (var i = 0; i < Trades.Count; ++i) - { - var trade = Trades[i]; - - var from = trade.From; - var to = trade.To; - - if (from.Mobile == Mobile && to.Mobile == m) return from.Container; - - if (from.Mobile == m && to.Mobile == Mobile) return to.Container; - } - - return null; - } - - public SecureTradeContainer AddTrade(NetState state) - { - var newTrade = new SecureTrade(Mobile, state.Mobile); - - Trades.Add(newTrade); - state.Trades.Add(newTrade); - - return newTrade.From.Container; - } - - public bool Seeded { get; set; } - - public ConnectionContext Connection { get; private set; } - - public bool CompressionEnabled { get; set; } - - public int Sequence { get; set; } - - public List Gumps { get; private set; } - - public List HuePickers { get; private set; } - - public List Menus { get; private set; } - - public static int GumpCap { get; set; } = 512; - - public static int HuePickerCap { get; set; } = 512; - - public static int MenuCap { get; set; } = 512; - - public void WriteConsole(string text) - { - Console.WriteLine("Client: {0}: {1}", this, text); - } - - public void WriteConsole(string format, params object[] args) - { - WriteConsole(string.Format(format, args)); - } - - public void AddMenu(IMenu menu) - { - Menus ??= new List(); - - if (Menus.Count < MenuCap) - { - Menus.Add(menu); - } - else - { - WriteConsole("Exceeded menu cap, disconnecting..."); - Dispose(); - } - } - - public void RemoveMenu(IMenu menu) - { - Menus?.Remove(menu); - } - - public void RemoveMenu(int index) - { - Menus?.RemoveAt(index); - } - - public void ClearMenus() - { - Menus?.Clear(); - } - - public void AddHuePicker(HuePicker huePicker) - { - HuePickers ??= new List(); - - if (HuePickers.Count < HuePickerCap) - { - HuePickers.Add(huePicker); - } - else - { - WriteConsole("Exceeded hue picker cap, disconnecting..."); - Dispose(); - } - } - - public void RemoveHuePicker(HuePicker huePicker) - { - HuePickers?.Remove(huePicker); - } - - public void RemoveHuePicker(int index) - { - HuePickers?.RemoveAt(index); - } - - public void ClearHuePickers() - { - HuePickers?.Clear(); - } - - public void AddGump(Gump gump) - { - Gumps ??= new List(); - - if (Gumps.Count < GumpCap) - { - Gumps.Add(gump); - } - else - { - WriteConsole("Exceeded gump cap, disconnecting..."); - Dispose(); - } - } - - public void RemoveGump(Gump gump) - { - Gumps?.Remove(gump); - } - - public void RemoveGump(int index) - { - Gumps?.RemoveAt(index); - } - - public void ClearGumps() - { - Gumps?.Clear(); - } - - public void LaunchBrowser(string url) - { - Send(new MessageLocalized(Serial.MinusOne, -1, MessageType.Label, 0x35, 3, 501231, "", "")); - Send(new LaunchBrowser(url)); - } - - public CityInfo[] CityInfo { get; set; } - - public Mobile Mobile { get; set; } - - public ServerInfo[] ServerInfo { get; set; } - - public IAccount Account { get; set; } - - public override string ToString() => m_ToString; - - public NetState(ConnectionContext connection) - { - Connection = connection; - Seeded = false; - Gumps = new List(); - HuePickers = new List(); - Menus = new List(); - Trades = new List(); - - try - { - Address = Utility.Intern(((IPEndPoint)Connection.RemoteEndPoint).Address); - m_ToString = Address.ToString(); - } - catch (Exception ex) - { - TraceException(ex); - Address = IPAddress.None; - m_ToString = "(error)"; - } - - ConnectedOn = DateTime.UtcNow; - - connection.ConnectionClosed.Register(() => - { - TcpServer.Instances.Remove(this); - Dispose(); - }); - - CreatedCallback?.Invoke(this); - } - - public static void Pause() - { - m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_PauseState); - } - - public static void Resume() - { - m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_ResumeState); - } - - public virtual async void Send(Packet p) - { - if (Connection == null || BlockAllPackets) - { - p.OnSend(); - return; - } - - var outPipe = Connection.Transport.Output; - - try - { - // TODO: Rented memory - ReadOnlyMemory buffer = p.Compile(CompressionEnabled, out var length); - - if (buffer.Length > 0 && length > 0) - { - var result = await outPipe.WriteAsync(buffer.Slice(0, length)); - - if (result.IsCanceled || result.IsCompleted) - { - Dispose(); - return; - } - } - - p.OnSend(); - } - catch (SocketException ex) - { - Console.WriteLine(ex); - TraceException(ex); - Dispose(); - } - catch (Exception ex) - { - Console.WriteLine(ex); - Dispose(); - } - } - - public async Task ProcessIncoming(IMessagePumpService messagePumpService) - { - var inPipe = Connection.Transport.Input; - - try - { - while (true) - { - if (AsyncState.Paused) - continue; - - var result = await inPipe.ReadAsync(); - if (result.IsCanceled || result.IsCompleted) - return; - - var seq = result.Buffer; - - if (seq.IsEmpty) - break; - - var pos = PacketHandlers.ProcessPacket(messagePumpService, this, seq); - - if (pos <= 0) - break; - - inPipe.AdvanceTo(seq.Slice(0, pos).End); - } - } - catch (SocketException ex) - { - Console.WriteLine(ex); - TraceException(ex); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - finally - { - Dispose(); - } - } - - public bool CheckEncrypted(int packetID) - { - if (!SentFirstPacket && packetID != 0xF0 && packetID != 0xF1 && packetID != 0xCF && packetID != 0x80 && - packetID != 0x91 && packetID != 0xA4 && packetID != 0xEF) - { - Console.WriteLine("Client: {0}: Encrypted client detected, disconnecting", this); - Dispose(); - return true; - } - - return false; - } - - public PacketHandler GetHandler(int packetID) => - ContainerGridLines ? PacketHandlers.Get6017Handler(packetID) : PacketHandlers.GetHandler(packetID); - - public static void TraceException(Exception ex) - { - try - { - using var op = new StreamWriter("network-errors.log", true); - op.WriteLine("# {0}", DateTime.UtcNow); - - op.WriteLine(ex); - - op.WriteLine(); - op.WriteLine(); - } - catch - { - // ignored - } - - Console.WriteLine(ex); - } - - private int m_Disposing; - - public bool IsDisposing => m_Disposing != 0; - - public virtual void Dispose() - { - var disposing = Interlocked.Exchange(ref m_Disposing, 1); - if (disposing == 1) - return; - - try - { - Connection.Transport.Input.Complete(); - Connection.Transport.Output.Complete(); - Connection.Abort(); - Task.Run(Connection.DisposeAsync).Wait(); - } - catch (Exception ex) - { - TraceException(ex); - } - - Connection = null; - m_Disposed.Enqueue(this); - } - - private static readonly ConcurrentQueue m_Disposed = new ConcurrentQueue(); - - public static void ProcessDisposedQueue() - { - var breakout = 0; - - while (breakout++ < 200) - { - if (!m_Disposed.TryDequeue(out var ns)) - break; - - var m = ns.Mobile; - var a = ns.Account; - - if (m != null) - { - m.NetState = null; - ns.Mobile = null; - } - - ns.Gumps.Clear(); - ns.Menus.Clear(); - ns.HuePickers.Clear(); - ns.Account = null; - ns.ServerInfo = null; - ns.CityInfo = null; - - if (a != null) - ns.WriteConsole("Disconnected. [{0} Online] [{1}]", TcpServer.Instances.Count, a); - else - ns.WriteConsole("Disconnected. [{0} Online]", TcpServer.Instances.Count); - } - } - - public ExpansionInfo ExpansionInfo - { - get - { - for (var i = ExpansionInfo.Table.Length - 1; i >= 0; i--) - { - var info = ExpansionInfo.Table[i]; - - if (info.RequiredClient != null && Version >= info.RequiredClient || (Flags & info.ClientFlags) != 0) - return info; - } - - return ExpansionInfo.GetInfo(Expansion.None); - } - } - - public Expansion Expansion => (Expansion)ExpansionInfo.ID; - - public ProtocolChanges ProtocolChanges { get; set; } - - public bool SupportsExpansion(ExpansionInfo info, bool checkCoreExpansion = true) => - info != null && (!checkCoreExpansion || (int)Core.Expansion >= info.ID) && (info.RequiredClient != null - ? Version >= info.RequiredClient - : (Flags & info.ClientFlags) != 0); - - public bool SupportsExpansion(Expansion ex, bool checkCoreExpansion = true) => - SupportsExpansion(ExpansionInfo.GetInfo(ex), checkCoreExpansion); - - public int CompareTo(NetState other) => other == null ? 1 : m_ToString.CompareTo(other.m_ToString); - } -} +/*************************************************************************** + * NetState.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Server.Accounting; +using Server.Gumps; +using Server.HuePickers; +using Server.Items; +using Server.Menus; + +namespace Server.Network +{ + public interface IPacketEncoder + { + void EncodeOutgoingPacket(NetState to, ref Memory seq); + void DecodeIncomingPacket(NetState from, ref Memory seq); + } + + public delegate void NetStateCreatedCallback(NetState ns); + + [Flags] + public enum ProtocolChanges + { + NewSpellbook = 0x00000001, + DamagePacket = 0x00000002, + Unpack = 0x00000004, + BuffIcon = 0x00000008, + NewHaven = 0x00000010, + ContainerGridLines = 0x00000020, + ExtendedSupportedFeatures = 0x00000040, + StygianAbyss = 0x00000080, + HighSeas = 0x00000100, + NewCharacterList = 0x00000200, + NewCharacterCreation = 0x00000400, + ExtendedStatus = 0x00000800, + NewMobileIncoming = 0x00001000, + NewSecureTrading = 0x00002000, + UltimaStore = 0x00004000, + EndlessJourney = 0x00008000, + + Version400a = NewSpellbook, + Version407a = Version400a | DamagePacket, + Version500a = Version407a | Unpack, + Version502b = Version500a | BuffIcon, + Version6000 = Version502b | NewHaven, + Version6017 = Version6000 | ContainerGridLines, + Version60142 = Version6017 | ExtendedSupportedFeatures, + Version7000 = Version60142 | StygianAbyss, + Version7090 = Version7000 | HighSeas, + Version70130 = Version7090 | NewCharacterList, + Version70160 = Version70130 | NewCharacterCreation, + Version70300 = Version70160 | ExtendedStatus, + Version70331 = Version70300 | NewMobileIncoming, + Version704565 = Version70331 | NewSecureTrading, + Version70500 = Version704565 | UltimaStore, + Version70610 = Version70500 | EndlessJourney + } + + public class AsyncState + { + public AsyncState(bool paused) => Paused = paused; + public bool Paused { get; set; } + } + + public class NetState : IComparable + { + private static readonly AsyncState m_PauseState = new AsyncState(true); + private static readonly AsyncState m_ResumeState = new AsyncState(false); + + private static AsyncState m_AsyncState = m_ResumeState; + + private static readonly ClientVersion m_Version400a = new ClientVersion("4.0.0a"); + private static readonly ClientVersion m_Version407a = new ClientVersion("4.0.7a"); + private static readonly ClientVersion m_Version500a = new ClientVersion("5.0.0a"); + private static readonly ClientVersion m_Version502b = new ClientVersion("5.0.2b"); + private static readonly ClientVersion m_Version6000 = new ClientVersion("6.0.0.0"); + private static readonly ClientVersion m_Version6017 = new ClientVersion("6.0.1.7"); + private static readonly ClientVersion m_Version60142 = new ClientVersion("6.0.14.2"); + private static readonly ClientVersion m_Version7000 = new ClientVersion("7.0.0.0"); + private static readonly ClientVersion m_Version7090 = new ClientVersion("7.0.9.0"); + private static readonly ClientVersion m_Version70130 = new ClientVersion("7.0.13.0"); + private static readonly ClientVersion m_Version70160 = new ClientVersion("7.0.16.0"); + private static readonly ClientVersion m_Version70300 = new ClientVersion("7.0.30.0"); + private static readonly ClientVersion m_Version70331 = new ClientVersion("7.0.33.1"); + private static readonly ClientVersion m_Version704565 = new ClientVersion("7.0.45.65"); + private static readonly ClientVersion m_Version70500 = new ClientVersion("7.0.50.0"); + private static readonly ClientVersion m_Version70610 = new ClientVersion("7.0.61.0"); + + private static readonly ConcurrentQueue m_Disposed = new ConcurrentQueue(); + private readonly string m_ToString; + internal int m_AuthID; + + private int m_Disposing; + + internal int m_Seed; + private ClientVersion m_Version; + + public NetState(ConnectionContext connection) + { + Connection = connection; + Seeded = false; + Gumps = new List(); + HuePickers = new List(); + Menus = new List(); + Trades = new List(); + + try + { + Address = Utility.Intern(((IPEndPoint)Connection.RemoteEndPoint).Address); + m_ToString = Address.ToString(); + } + catch (Exception ex) + { + TraceException(ex); + Address = IPAddress.None; + m_ToString = "(error)"; + } + + ConnectedOn = DateTime.UtcNow; + + connection.ConnectionClosed.Register( + () => + { + TcpServer.Instances.Remove(this); + Dispose(); + } + ); + + CreatedCallback?.Invoke(this); + } + + public DateTime ConnectedOn { get; } + + public TimeSpan ConnectedFor => DateTime.UtcNow - ConnectedOn; + + public DateTime ThrottledUntil { get; set; } + + public IPAddress Address { get; } + public static AsyncState AsyncState => m_AsyncState; + + public IPacketEncoder PacketEncoder { get; set; } + + public static NetStateCreatedCallback CreatedCallback { get; set; } + + public bool SentFirstPacket { get; set; } + + public bool BlockAllPackets { get; set; } + + public ClientFlags Flags { get; set; } + + public ClientVersion Version + { + get => m_Version; + set + { + m_Version = value; + + if (value >= m_Version70610) + ProtocolChanges = ProtocolChanges.Version70610; + if (value >= m_Version70500) + ProtocolChanges = ProtocolChanges.Version70500; + if (value >= m_Version704565) + ProtocolChanges = ProtocolChanges.Version704565; + else if (value >= m_Version70331) + ProtocolChanges = ProtocolChanges.Version70331; + else if (value >= m_Version70300) + ProtocolChanges = ProtocolChanges.Version70300; + else if (value >= m_Version70160) + ProtocolChanges = ProtocolChanges.Version70160; + else if (value >= m_Version70130) + ProtocolChanges = ProtocolChanges.Version70130; + else if (value >= m_Version7090) + ProtocolChanges = ProtocolChanges.Version7090; + else if (value >= m_Version7000) + ProtocolChanges = ProtocolChanges.Version7000; + else if (value >= m_Version60142) + ProtocolChanges = ProtocolChanges.Version60142; + else if (value >= m_Version6017) + ProtocolChanges = ProtocolChanges.Version6017; + else if (value >= m_Version6000) + ProtocolChanges = ProtocolChanges.Version6000; + else if (value >= m_Version502b) + ProtocolChanges = ProtocolChanges.Version502b; + else if (value >= m_Version500a) + ProtocolChanges = ProtocolChanges.Version500a; + else if (value >= m_Version407a) + ProtocolChanges = ProtocolChanges.Version407a; + else if (value >= m_Version400a) ProtocolChanges = ProtocolChanges.Version400a; + } + } + + public bool NewSpellbook => (ProtocolChanges & ProtocolChanges.NewSpellbook) != 0; + public bool DamagePacket => (ProtocolChanges & ProtocolChanges.DamagePacket) != 0; + public bool Unpack => (ProtocolChanges & ProtocolChanges.Unpack) != 0; + public bool BuffIcon => (ProtocolChanges & ProtocolChanges.BuffIcon) != 0; + public bool NewHaven => (ProtocolChanges & ProtocolChanges.NewHaven) != 0; + public bool ContainerGridLines => (ProtocolChanges & ProtocolChanges.ContainerGridLines) != 0; + public bool ExtendedSupportedFeatures => (ProtocolChanges & ProtocolChanges.ExtendedSupportedFeatures) != 0; + public bool StygianAbyss => (ProtocolChanges & ProtocolChanges.StygianAbyss) != 0; + public bool HighSeas => (ProtocolChanges & ProtocolChanges.HighSeas) != 0; + public bool NewCharacterList => (ProtocolChanges & ProtocolChanges.NewCharacterList) != 0; + public bool NewCharacterCreation => (ProtocolChanges & ProtocolChanges.NewCharacterCreation) != 0; + public bool ExtendedStatus => (ProtocolChanges & ProtocolChanges.ExtendedStatus) != 0; + public bool NewMobileIncoming => (ProtocolChanges & ProtocolChanges.NewMobileIncoming) != 0; + public bool NewSecureTrading => (ProtocolChanges & ProtocolChanges.NewSecureTrading) != 0; + + public bool IsUOTDClient => + (Flags & ClientFlags.UOTD) != 0 || m_Version?.Type == ClientType.UOTD; + + public bool IsSAClient => m_Version?.Type == ClientType.SA; + + public List Trades { get; } + + public bool Seeded { get; set; } + + public ConnectionContext Connection { get; private set; } + + public bool CompressionEnabled { get; set; } + + public int Sequence { get; set; } + + public List Gumps { get; private set; } + + public List HuePickers { get; private set; } + + public List Menus { get; private set; } + + public static int GumpCap { get; set; } = 512; + + public static int HuePickerCap { get; set; } = 512; + + public static int MenuCap { get; set; } = 512; + + public CityInfo[] CityInfo { get; set; } + + public Mobile Mobile { get; set; } + + public ServerInfo[] ServerInfo { get; set; } + + public IAccount Account { get; set; } + + public bool IsDisposing => m_Disposing != 0; + + public ExpansionInfo ExpansionInfo + { + get + { + for (var i = ExpansionInfo.Table.Length - 1; i >= 0; i--) + { + var info = ExpansionInfo.Table[i]; + + if (info.RequiredClient != null && Version >= info.RequiredClient || (Flags & info.ClientFlags) != 0) + return info; + } + + return ExpansionInfo.GetInfo(Expansion.None); + } + } + + public Expansion Expansion => (Expansion)ExpansionInfo.ID; + + public ProtocolChanges ProtocolChanges { get; set; } + + public int CompareTo(NetState other) => other == null ? 1 : m_ToString.CompareTo(other.m_ToString); + + public void ValidateAllTrades() + { + for (var i = Trades.Count - 1; i >= 0; --i) + { + if (i >= Trades.Count) continue; + + var trade = Trades[i]; + + if (trade.From.Mobile.Deleted || trade.To.Mobile.Deleted || !trade.From.Mobile.Alive || + !trade.To.Mobile.Alive || !trade.From.Mobile.InRange(trade.To.Mobile, 2) || + trade.From.Mobile.Map != trade.To.Mobile.Map) trade.Cancel(); + } + } + + public void CancelAllTrades() + { + for (var i = Trades.Count - 1; i >= 0; --i) + if (i < Trades.Count) + Trades[i].Cancel(); + } + + public void RemoveTrade(SecureTrade trade) + { + Trades.Remove(trade); + } + + public SecureTrade FindTrade(Mobile m) + { + for (var i = 0; i < Trades.Count; ++i) + { + var trade = Trades[i]; + + if (trade.From.Mobile == m || trade.To.Mobile == m) return trade; + } + + return null; + } + + public SecureTradeContainer FindTradeContainer(Mobile m) + { + for (var i = 0; i < Trades.Count; ++i) + { + var trade = Trades[i]; + + var from = trade.From; + var to = trade.To; + + if (from.Mobile == Mobile && to.Mobile == m) return from.Container; + + if (from.Mobile == m && to.Mobile == Mobile) return to.Container; + } + + return null; + } + + public SecureTradeContainer AddTrade(NetState state) + { + var newTrade = new SecureTrade(Mobile, state.Mobile); + + Trades.Add(newTrade); + state.Trades.Add(newTrade); + + return newTrade.From.Container; + } + + public void WriteConsole(string text) + { + Console.WriteLine("Client: {0}: {1}", this, text); + } + + public void WriteConsole(string format, params object[] args) + { + WriteConsole(string.Format(format, args)); + } + + public void AddMenu(IMenu menu) + { + Menus ??= new List(); + + if (Menus.Count < MenuCap) + { + Menus.Add(menu); + } + else + { + WriteConsole("Exceeded menu cap, disconnecting..."); + Dispose(); + } + } + + public void RemoveMenu(IMenu menu) + { + Menus?.Remove(menu); + } + + public void RemoveMenu(int index) + { + Menus?.RemoveAt(index); + } + + public void ClearMenus() + { + Menus?.Clear(); + } + + public void AddHuePicker(HuePicker huePicker) + { + HuePickers ??= new List(); + + if (HuePickers.Count < HuePickerCap) + { + HuePickers.Add(huePicker); + } + else + { + WriteConsole("Exceeded hue picker cap, disconnecting..."); + Dispose(); + } + } + + public void RemoveHuePicker(HuePicker huePicker) + { + HuePickers?.Remove(huePicker); + } + + public void RemoveHuePicker(int index) + { + HuePickers?.RemoveAt(index); + } + + public void ClearHuePickers() + { + HuePickers?.Clear(); + } + + public void AddGump(Gump gump) + { + Gumps ??= new List(); + + if (Gumps.Count < GumpCap) + { + Gumps.Add(gump); + } + else + { + WriteConsole("Exceeded gump cap, disconnecting..."); + Dispose(); + } + } + + public void RemoveGump(Gump gump) + { + Gumps?.Remove(gump); + } + + public void RemoveGump(int index) + { + Gumps?.RemoveAt(index); + } + + public void ClearGumps() + { + Gumps?.Clear(); + } + + public void LaunchBrowser(string url) + { + Send(new MessageLocalized(Serial.MinusOne, -1, MessageType.Label, 0x35, 3, 501231, "", "")); + Send(new LaunchBrowser(url)); + } + + public override string ToString() => m_ToString; + + public static void Pause() + { + m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_PauseState); + } + + public static void Resume() + { + m_AsyncState = Interlocked.Exchange(ref m_AsyncState, m_ResumeState); + } + + public virtual async void Send(Packet p) + { + if (Connection == null || BlockAllPackets) + { + p.OnSend(); + return; + } + + var outPipe = Connection.Transport.Output; + + try + { + // TODO: Rented memory + ReadOnlyMemory buffer = p.Compile(CompressionEnabled, out var length); + + if (buffer.Length > 0 && length > 0) + { + var result = await outPipe.WriteAsync(buffer.Slice(0, length)); + + if (result.IsCanceled || result.IsCompleted) + { + Dispose(); + return; + } + } + + p.OnSend(); + } + catch (SocketException ex) + { + Console.WriteLine(ex); + TraceException(ex); + Dispose(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + Dispose(); + } + } + + public async Task ProcessIncoming(IMessagePumpService messagePumpService) + { + var inPipe = Connection.Transport.Input; + + try + { + while (true) + { + if (AsyncState.Paused) + continue; + + var result = await inPipe.ReadAsync(); + if (result.IsCanceled || result.IsCompleted) + return; + + var seq = result.Buffer; + + if (seq.IsEmpty) + break; + + var pos = PacketHandlers.ProcessPacket(messagePumpService, this, seq); + + if (pos <= 0) + break; + + inPipe.AdvanceTo(seq.Slice(0, pos).End); + } + } + catch (SocketException ex) + { + Console.WriteLine(ex); + TraceException(ex); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + finally + { + Dispose(); + } + } + + public bool CheckEncrypted(int packetID) + { + if (!SentFirstPacket && packetID != 0xF0 && packetID != 0xF1 && packetID != 0xCF && packetID != 0x80 && + packetID != 0x91 && packetID != 0xA4 && packetID != 0xEF) + { + Console.WriteLine("Client: {0}: Encrypted client detected, disconnecting", this); + Dispose(); + return true; + } + + return false; + } + + public PacketHandler GetHandler(int packetID) => + ContainerGridLines ? PacketHandlers.Get6017Handler(packetID) : PacketHandlers.GetHandler(packetID); + + public static void TraceException(Exception ex) + { + try + { + using var op = new StreamWriter("network-errors.log", true); + op.WriteLine("# {0}", DateTime.UtcNow); + + op.WriteLine(ex); + + op.WriteLine(); + op.WriteLine(); + } + catch + { + // ignored + } + + Console.WriteLine(ex); + } + + public virtual void Dispose() + { + var disposing = Interlocked.Exchange(ref m_Disposing, 1); + if (disposing == 1) + return; + + try + { + Connection.Transport.Input.Complete(); + Connection.Transport.Output.Complete(); + Connection.Abort(); + Task.Run(Connection.DisposeAsync).Wait(); + } + catch (Exception ex) + { + TraceException(ex); + } + + Connection = null; + m_Disposed.Enqueue(this); + } + + public static void ProcessDisposedQueue() + { + var breakout = 0; + + while (breakout++ < 200) + { + if (!m_Disposed.TryDequeue(out var ns)) + break; + + var m = ns.Mobile; + var a = ns.Account; + + if (m != null) + { + m.NetState = null; + ns.Mobile = null; + } + + ns.Gumps.Clear(); + ns.Menus.Clear(); + ns.HuePickers.Clear(); + ns.Account = null; + ns.ServerInfo = null; + ns.CityInfo = null; + + if (a != null) + ns.WriteConsole("Disconnected. [{0} Online] [{1}]", TcpServer.Instances.Count, a); + else + ns.WriteConsole("Disconnected. [{0} Online]", TcpServer.Instances.Count); + } + } + + public bool SupportsExpansion(ExpansionInfo info, bool checkCoreExpansion = true) => + info != null && (!checkCoreExpansion || (int)Core.Expansion >= info.ID) && (info.RequiredClient != null + ? Version >= info.RequiredClient + : (Flags & info.ClientFlags) != 0); + + public bool SupportsExpansion(Expansion ex, bool checkCoreExpansion = true) => + SupportsExpansion(ExpansionInfo.GetInfo(ex), checkCoreExpansion); + } +} diff --git a/Projects/Server/Network/NetworkCompression.cs b/Projects/Server/Network/NetworkCompression.cs index ed178821d..631c4f6c3 100644 --- a/Projects/Server/Network/NetworkCompression.cs +++ b/Projects/Server/Network/NetworkCompression.cs @@ -1,170 +1,172 @@ -/*************************************************************************** - * Compression.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server.Network -{ - /// - /// Handles outgoing packet compression for the network. - /// - public static class NetworkCompression - { - private const int CountIndex = 0; - private const int ValueIndex = 1; - - // UO packets may not exceed 64kb in length - private const int BufferSize = 0x10000; - - // Optimal compression ratio is 2 / 8; worst compression ratio is 11 / 8 - private const int MinimalCodeLength = 2; - private const int MaximalCodeLength = 11; - - // Fixed overhead, in bits, per compression call - private const int TerminalCodeLength = 4; - - // If our input exceeds this length, we cannot possibly compress it within the buffer - private const int DefiniteOverflow = (BufferSize * 8 - TerminalCodeLength) / MinimalCodeLength; - - private static readonly int[] _huffmanTable = - { - 0x2, 0x000, 0x5, 0x01F, 0x6, 0x022, 0x7, 0x034, 0x7, 0x075, 0x6, 0x028, 0x6, 0x03B, 0x7, 0x032, - 0x8, 0x0E0, 0x8, 0x062, 0x7, 0x056, 0x8, 0x079, 0x9, 0x19D, 0x8, 0x097, 0x6, 0x02A, 0x7, 0x057, - 0x8, 0x071, 0x8, 0x05B, 0x9, 0x1CC, 0x8, 0x0A7, 0x7, 0x025, 0x7, 0x04F, 0x8, 0x066, 0x8, 0x07D, - 0x9, 0x191, 0x9, 0x1CE, 0x7, 0x03F, 0x9, 0x090, 0x8, 0x059, 0x8, 0x07B, 0x8, 0x091, 0x8, 0x0C6, - 0x6, 0x02D, 0x9, 0x186, 0x8, 0x06F, 0x9, 0x093, 0xA, 0x1CC, 0x8, 0x05A, 0xA, 0x1AE, 0xA, 0x1C0, - 0x9, 0x148, 0x9, 0x14A, 0x9, 0x082, 0xA, 0x19F, 0x9, 0x171, 0x9, 0x120, 0x9, 0x0E7, 0xA, 0x1F3, - 0x9, 0x14B, 0x9, 0x100, 0x9, 0x190, 0x6, 0x013, 0x9, 0x161, 0x9, 0x125, 0x9, 0x133, 0x9, 0x195, - 0x9, 0x173, 0x9, 0x1CA, 0x9, 0x086, 0x9, 0x1E9, 0x9, 0x0DB, 0x9, 0x1EC, 0x9, 0x08B, 0x9, 0x085, - 0x5, 0x00A, 0x8, 0x096, 0x8, 0x09C, 0x9, 0x1C3, 0x9, 0x19C, 0x9, 0x08F, 0x9, 0x18F, 0x9, 0x091, - 0x9, 0x087, 0x9, 0x0C6, 0x9, 0x177, 0x9, 0x089, 0x9, 0x0D6, 0x9, 0x08C, 0x9, 0x1EE, 0x9, 0x1EB, - 0x9, 0x084, 0x9, 0x164, 0x9, 0x175, 0x9, 0x1CD, 0x8, 0x05E, 0x9, 0x088, 0x9, 0x12B, 0x9, 0x172, - 0x9, 0x10A, 0x9, 0x08D, 0x9, 0x13A, 0x9, 0x11C, 0xA, 0x1E1, 0xA, 0x1E0, 0x9, 0x187, 0xA, 0x1DC, - 0xA, 0x1DF, 0x7, 0x074, 0x9, 0x19F, 0x8, 0x08D, 0x8, 0x0E4, 0x7, 0x079, 0x9, 0x0EA, 0x9, 0x0E1, - 0x8, 0x040, 0x7, 0x041, 0x9, 0x10B, 0x9, 0x0B0, 0x8, 0x06A, 0x8, 0x0C1, 0x7, 0x071, 0x7, 0x078, - 0x8, 0x0B1, 0x9, 0x14C, 0x7, 0x043, 0x8, 0x076, 0x7, 0x066, 0x7, 0x04D, 0x9, 0x08A, 0x6, 0x02F, - 0x8, 0x0C9, 0x9, 0x0CE, 0x9, 0x149, 0x9, 0x160, 0xA, 0x1BA, 0xA, 0x19E, 0xA, 0x39F, 0x9, 0x0E5, - 0x9, 0x194, 0x9, 0x184, 0x9, 0x126, 0x7, 0x030, 0x8, 0x06C, 0x9, 0x121, 0x9, 0x1E8, 0xA, 0x1C1, - 0xA, 0x11D, 0xA, 0x163, 0xA, 0x385, 0xA, 0x3DB, 0xA, 0x17D, 0xA, 0x106, 0xA, 0x397, 0xA, 0x24E, - 0x7, 0x02E, 0x8, 0x098, 0xA, 0x33C, 0xA, 0x32E, 0xA, 0x1E9, 0x9, 0x0BF, 0xA, 0x3DF, 0xA, 0x1DD, - 0xA, 0x32D, 0xA, 0x2ED, 0xA, 0x30B, 0xA, 0x107, 0xA, 0x2E8, 0xA, 0x3DE, 0xA, 0x125, 0xA, 0x1E8, - 0x9, 0x0E9, 0xA, 0x1CD, 0xA, 0x1B5, 0x9, 0x165, 0xA, 0x232, 0xA, 0x2E1, 0xB, 0x3AE, 0xB, 0x3C6, - 0xB, 0x3E2, 0xA, 0x205, 0xA, 0x29A, 0xA, 0x248, 0xA, 0x2CD, 0xA, 0x23B, 0xB, 0x3C5, 0xA, 0x251, - 0xA, 0x2E9, 0xA, 0x252, 0x9, 0x1EA, 0xB, 0x3A0, 0xB, 0x391, 0xA, 0x23C, 0xB, 0x392, 0xB, 0x3D5, - 0xA, 0x233, 0xA, 0x2CC, 0xB, 0x390, 0xA, 0x1BB, 0xB, 0x3A1, 0xB, 0x3C4, 0xA, 0x211, 0xA, 0x203, - 0x9, 0x12A, 0xA, 0x231, 0xB, 0x3E0, 0xA, 0x29B, 0xB, 0x3D7, 0xA, 0x202, 0xB, 0x3AD, 0xA, 0x213, - 0xA, 0x253, 0xA, 0x32C, 0xA, 0x23D, 0xA, 0x23F, 0xA, 0x32F, 0xA, 0x11C, 0xA, 0x384, 0xA, 0x31C, - 0xA, 0x17C, 0xA, 0x30A, 0xA, 0x2E0, 0xA, 0x276, 0xA, 0x250, 0xB, 0x3E3, 0xA, 0x396, 0xA, 0x18F, - 0xA, 0x204, 0xA, 0x206, 0xA, 0x230, 0xA, 0x265, 0xA, 0x212, 0xA, 0x23E, 0xB, 0x3AC, 0xB, 0x393, - 0xB, 0x3E1, 0xA, 0x1DE, 0xB, 0x3D6, 0xA, 0x31D, 0xB, 0x3E5, 0xB, 0x3E4, 0xA, 0x207, 0xB, 0x3C7, - 0xA, 0x277, 0xB, 0x3D4, 0x8, 0x0C0, 0xA, 0x162, 0xA, 0x3DA, 0xA, 0x124, 0xA, 0x1B4, 0xA, 0x264, - 0xA, 0x33D, 0xA, 0x1D1, 0xA, 0x1AF, 0xA, 0x39E, 0xA, 0x24F, 0xB, 0x373, 0xA, 0x249, 0xB, 0x372, - 0x9, 0x167, 0xA, 0x210, 0xA, 0x23A, 0xA, 0x1B8, 0xB, 0x3AF, 0xA, 0x18E, 0xA, 0x2EC, 0x7, 0x062, - 0x4, 0x00D - }; - - public static unsafe void Compress(ReadOnlySpan input, int offset, int count, Span output, out int length) - { - if (input == null) throw new ArgumentNullException(nameof(input)); - - if (offset < 0 || offset >= input.Length) throw new ArgumentOutOfRangeException(nameof(offset)); - if (count < 0 || count > input.Length) throw new ArgumentOutOfRangeException(nameof(count)); - if (input.Length - offset < count) throw new ArgumentOutOfRangeException(nameof(offset)); - - length = 0; - - if (count > DefiniteOverflow) return; - - var bitCount = 0; - var bitValue = 0; - - fixed (int* pTable = _huffmanTable) - { - fixed (byte* pInputBuffer = input) - { - byte* pInput = pInputBuffer + offset, pInputEnd = pInput + count; - - fixed (byte* pOutputBuffer = output) - { - byte* pOutput = pOutputBuffer, pOutputEnd = pOutput + BufferSize; - - int* pEntry; - while (pInput < pInputEnd) - { - pEntry = &pTable[*pInput++ << 1]; - - bitCount += pEntry[CountIndex]; - - bitValue <<= pEntry[CountIndex]; - bitValue |= pEntry[ValueIndex]; - - while (bitCount >= 8) - { - bitCount -= 8; - - if (pOutput < pOutputEnd) - { - *pOutput++ = (byte)(bitValue >> bitCount); - } - else - { - length = 0; - return; - } - } - } - - // terminal code - pEntry = &pTable[0x200]; - - bitCount += pEntry[CountIndex]; - - bitValue <<= pEntry[CountIndex]; - bitValue |= pEntry[ValueIndex]; - - // align on byte boundary - if ((bitCount & 7) != 0) - { - bitValue <<= 8 - (bitCount & 7); - bitCount += 8 - (bitCount & 7); - } - - while (bitCount >= 8) - { - bitCount -= 8; - - if (pOutput < pOutputEnd) - { - *pOutput++ = (byte)(bitValue >> bitCount); - } - else - { - length = 0; - return; - } - } - - length = (int)(pOutput - pOutputBuffer); - } - } - } - } - } -} +/*************************************************************************** + * Compression.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server.Network +{ + /// + /// Handles outgoing packet compression for the network. + /// + public static class NetworkCompression + { + private const int CountIndex = 0; + private const int ValueIndex = 1; + + // UO packets may not exceed 64kb in length + private const int BufferSize = 0x10000; + + // Optimal compression ratio is 2 / 8; worst compression ratio is 11 / 8 + private const int MinimalCodeLength = 2; + private const int MaximalCodeLength = 11; + + // Fixed overhead, in bits, per compression call + private const int TerminalCodeLength = 4; + + // If our input exceeds this length, we cannot possibly compress it within the buffer + private const int DefiniteOverflow = (BufferSize * 8 - TerminalCodeLength) / MinimalCodeLength; + + private static readonly int[] _huffmanTable = + { + 0x2, 0x000, 0x5, 0x01F, 0x6, 0x022, 0x7, 0x034, 0x7, 0x075, 0x6, 0x028, 0x6, 0x03B, 0x7, 0x032, + 0x8, 0x0E0, 0x8, 0x062, 0x7, 0x056, 0x8, 0x079, 0x9, 0x19D, 0x8, 0x097, 0x6, 0x02A, 0x7, 0x057, + 0x8, 0x071, 0x8, 0x05B, 0x9, 0x1CC, 0x8, 0x0A7, 0x7, 0x025, 0x7, 0x04F, 0x8, 0x066, 0x8, 0x07D, + 0x9, 0x191, 0x9, 0x1CE, 0x7, 0x03F, 0x9, 0x090, 0x8, 0x059, 0x8, 0x07B, 0x8, 0x091, 0x8, 0x0C6, + 0x6, 0x02D, 0x9, 0x186, 0x8, 0x06F, 0x9, 0x093, 0xA, 0x1CC, 0x8, 0x05A, 0xA, 0x1AE, 0xA, 0x1C0, + 0x9, 0x148, 0x9, 0x14A, 0x9, 0x082, 0xA, 0x19F, 0x9, 0x171, 0x9, 0x120, 0x9, 0x0E7, 0xA, 0x1F3, + 0x9, 0x14B, 0x9, 0x100, 0x9, 0x190, 0x6, 0x013, 0x9, 0x161, 0x9, 0x125, 0x9, 0x133, 0x9, 0x195, + 0x9, 0x173, 0x9, 0x1CA, 0x9, 0x086, 0x9, 0x1E9, 0x9, 0x0DB, 0x9, 0x1EC, 0x9, 0x08B, 0x9, 0x085, + 0x5, 0x00A, 0x8, 0x096, 0x8, 0x09C, 0x9, 0x1C3, 0x9, 0x19C, 0x9, 0x08F, 0x9, 0x18F, 0x9, 0x091, + 0x9, 0x087, 0x9, 0x0C6, 0x9, 0x177, 0x9, 0x089, 0x9, 0x0D6, 0x9, 0x08C, 0x9, 0x1EE, 0x9, 0x1EB, + 0x9, 0x084, 0x9, 0x164, 0x9, 0x175, 0x9, 0x1CD, 0x8, 0x05E, 0x9, 0x088, 0x9, 0x12B, 0x9, 0x172, + 0x9, 0x10A, 0x9, 0x08D, 0x9, 0x13A, 0x9, 0x11C, 0xA, 0x1E1, 0xA, 0x1E0, 0x9, 0x187, 0xA, 0x1DC, + 0xA, 0x1DF, 0x7, 0x074, 0x9, 0x19F, 0x8, 0x08D, 0x8, 0x0E4, 0x7, 0x079, 0x9, 0x0EA, 0x9, 0x0E1, + 0x8, 0x040, 0x7, 0x041, 0x9, 0x10B, 0x9, 0x0B0, 0x8, 0x06A, 0x8, 0x0C1, 0x7, 0x071, 0x7, 0x078, + 0x8, 0x0B1, 0x9, 0x14C, 0x7, 0x043, 0x8, 0x076, 0x7, 0x066, 0x7, 0x04D, 0x9, 0x08A, 0x6, 0x02F, + 0x8, 0x0C9, 0x9, 0x0CE, 0x9, 0x149, 0x9, 0x160, 0xA, 0x1BA, 0xA, 0x19E, 0xA, 0x39F, 0x9, 0x0E5, + 0x9, 0x194, 0x9, 0x184, 0x9, 0x126, 0x7, 0x030, 0x8, 0x06C, 0x9, 0x121, 0x9, 0x1E8, 0xA, 0x1C1, + 0xA, 0x11D, 0xA, 0x163, 0xA, 0x385, 0xA, 0x3DB, 0xA, 0x17D, 0xA, 0x106, 0xA, 0x397, 0xA, 0x24E, + 0x7, 0x02E, 0x8, 0x098, 0xA, 0x33C, 0xA, 0x32E, 0xA, 0x1E9, 0x9, 0x0BF, 0xA, 0x3DF, 0xA, 0x1DD, + 0xA, 0x32D, 0xA, 0x2ED, 0xA, 0x30B, 0xA, 0x107, 0xA, 0x2E8, 0xA, 0x3DE, 0xA, 0x125, 0xA, 0x1E8, + 0x9, 0x0E9, 0xA, 0x1CD, 0xA, 0x1B5, 0x9, 0x165, 0xA, 0x232, 0xA, 0x2E1, 0xB, 0x3AE, 0xB, 0x3C6, + 0xB, 0x3E2, 0xA, 0x205, 0xA, 0x29A, 0xA, 0x248, 0xA, 0x2CD, 0xA, 0x23B, 0xB, 0x3C5, 0xA, 0x251, + 0xA, 0x2E9, 0xA, 0x252, 0x9, 0x1EA, 0xB, 0x3A0, 0xB, 0x391, 0xA, 0x23C, 0xB, 0x392, 0xB, 0x3D5, + 0xA, 0x233, 0xA, 0x2CC, 0xB, 0x390, 0xA, 0x1BB, 0xB, 0x3A1, 0xB, 0x3C4, 0xA, 0x211, 0xA, 0x203, + 0x9, 0x12A, 0xA, 0x231, 0xB, 0x3E0, 0xA, 0x29B, 0xB, 0x3D7, 0xA, 0x202, 0xB, 0x3AD, 0xA, 0x213, + 0xA, 0x253, 0xA, 0x32C, 0xA, 0x23D, 0xA, 0x23F, 0xA, 0x32F, 0xA, 0x11C, 0xA, 0x384, 0xA, 0x31C, + 0xA, 0x17C, 0xA, 0x30A, 0xA, 0x2E0, 0xA, 0x276, 0xA, 0x250, 0xB, 0x3E3, 0xA, 0x396, 0xA, 0x18F, + 0xA, 0x204, 0xA, 0x206, 0xA, 0x230, 0xA, 0x265, 0xA, 0x212, 0xA, 0x23E, 0xB, 0x3AC, 0xB, 0x393, + 0xB, 0x3E1, 0xA, 0x1DE, 0xB, 0x3D6, 0xA, 0x31D, 0xB, 0x3E5, 0xB, 0x3E4, 0xA, 0x207, 0xB, 0x3C7, + 0xA, 0x277, 0xB, 0x3D4, 0x8, 0x0C0, 0xA, 0x162, 0xA, 0x3DA, 0xA, 0x124, 0xA, 0x1B4, 0xA, 0x264, + 0xA, 0x33D, 0xA, 0x1D1, 0xA, 0x1AF, 0xA, 0x39E, 0xA, 0x24F, 0xB, 0x373, 0xA, 0x249, 0xB, 0x372, + 0x9, 0x167, 0xA, 0x210, 0xA, 0x23A, 0xA, 0x1B8, 0xB, 0x3AF, 0xA, 0x18E, 0xA, 0x2EC, 0x7, 0x062, + 0x4, 0x00D + }; + + public static unsafe void Compress( + ReadOnlySpan input, int offset, int count, Span output, out int length + ) + { + if (input == null) throw new ArgumentNullException(nameof(input)); + + if (offset < 0 || offset >= input.Length) throw new ArgumentOutOfRangeException(nameof(offset)); + if (count < 0 || count > input.Length) throw new ArgumentOutOfRangeException(nameof(count)); + if (input.Length - offset < count) throw new ArgumentOutOfRangeException(nameof(offset)); + + length = 0; + + if (count > DefiniteOverflow) return; + + var bitCount = 0; + var bitValue = 0; + + fixed (int* pTable = _huffmanTable) + { + fixed (byte* pInputBuffer = input) + { + byte* pInput = pInputBuffer + offset, pInputEnd = pInput + count; + + fixed (byte* pOutputBuffer = output) + { + byte* pOutput = pOutputBuffer, pOutputEnd = pOutput + BufferSize; + + int* pEntry; + while (pInput < pInputEnd) + { + pEntry = &pTable[*pInput++ << 1]; + + bitCount += pEntry[CountIndex]; + + bitValue <<= pEntry[CountIndex]; + bitValue |= pEntry[ValueIndex]; + + while (bitCount >= 8) + { + bitCount -= 8; + + if (pOutput < pOutputEnd) + { + *pOutput++ = (byte)(bitValue >> bitCount); + } + else + { + length = 0; + return; + } + } + } + + // terminal code + pEntry = &pTable[0x200]; + + bitCount += pEntry[CountIndex]; + + bitValue <<= pEntry[CountIndex]; + bitValue |= pEntry[ValueIndex]; + + // align on byte boundary + if ((bitCount & 7) != 0) + { + bitValue <<= 8 - (bitCount & 7); + bitCount += 8 - (bitCount & 7); + } + + while (bitCount >= 8) + { + bitCount -= 8; + + if (pOutput < pOutputEnd) + { + *pOutput++ = (byte)(bitValue >> bitCount); + } + else + { + length = 0; + return; + } + } + + length = (int)(pOutput - pOutputBuffer); + } + } + } + } + } +} diff --git a/Projects/Server/Network/Packet.cs b/Projects/Server/Network/Packet.cs index 03c9b31ae..3c9c4b767 100644 --- a/Projects/Server/Network/Packet.cs +++ b/Projects/Server/Network/Packet.cs @@ -1,267 +1,280 @@ -/*************************************************************************** - * Packet.cs - * ------------------- - * begin : August 2, 2019 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Buffers; -using System.Diagnostics; -using System.IO; -using Server.Diagnostics; - -namespace Server.Network -{ - public abstract class Packet - { - private const int CompressorBufferSize = 0x10000; - - private const int BufferSize = 4096; - - private byte[] m_CompiledBuffer; - private int m_CompiledLength; - private readonly int m_Length; - private State m_State; - - protected Packet(int packetID) - { - PacketID = packetID; - - if (Core.Profiling) - { - var prof = PacketSendProfile.Acquire(GetType()); - prof.Increment(); - } - } - - protected Packet(int packetID, int length) - { - PacketID = packetID; - m_Length = length; - - Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length ); - Stream.Write((byte)packetID); - - if (Core.Profiling) - { - var prof = PacketSendProfile.Acquire(GetType()); - prof.Increment(); - } - } - - public int PacketID { get; } - - public PacketWriter Stream { get; protected set; } - - public void EnsureCapacity(int length) - { - Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length ); - Stream.Write((byte)PacketID); - Stream.Write((short)0); - } - - public static Packet SetStatic(Packet p) - { - p.SetStatic(); - return p; - } - - public static Packet Acquire(Packet p) - { - p.Acquire(); - return p; - } - - public static void Release(ref Packet p) - { - p?.Release(); - p = null; - } - - public static void Release(Packet p) - { - p?.Release(); - } - - public void SetStatic() - { - m_State |= State.Static | State.Acquired; - } - - public void Acquire() - { - m_State |= State.Acquired; - } - - public void OnSend() - { - Core.Set(); // Is this still needed if this is done async? - - if ((m_State & (State.Acquired | State.Static)) == 0) - Free(); - } - - private void Free() - { - if (m_CompiledBuffer == null) - return; - - if ((m_State & State.Buffered) != 0) - ArrayPool.Shared.Return(m_CompiledBuffer); - - m_State &= ~(State.Static | State.Acquired | State.Buffered); - - m_CompiledBuffer = null; - } - - public void Release() - { - if ((m_State & State.Acquired) != 0) - Free(); - } - - public byte[] Compile(bool compress, out int length) - { - lock (this) - { - if (m_CompiledBuffer == null) - { - if ((m_State & State.Accessed) == 0) - { - m_State |= State.Accessed; - } - else - { - if ((m_State & State.Warned) == 0) - { - m_State |= State.Warned; - - try - { - using var op = new StreamWriter("net_opt.log", true); - op.WriteLine("Redundant compile for packet {0}, use Acquire() and Release()", GetType()); - op.WriteLine(new StackTrace()); - } - catch - { - // ignored - } - } - - m_CompiledBuffer = Array.Empty(); - m_CompiledLength = 0; - - length = m_CompiledLength; - return m_CompiledBuffer; - } - - InternalCompile(compress); - } - - length = m_CompiledLength; - return m_CompiledBuffer; - } - } - - private void InternalCompile(bool compress) - { - if (m_Length == 0) - { - var streamLen = Stream.Length; - - Stream.Seek(1, SeekOrigin.Begin); - Stream.Write((ushort)streamLen); - } - else if (Stream.Length != m_Length) - { - var diff = (int)Stream.Length - m_Length; - - Console.WriteLine("Packet: 0x{0:X2}: Bad packet length! ({1}{2} bytes)", PacketID, diff >= 0 ? "+" : "", - diff); - } - - var ms = Stream.UnderlyingStream; - - m_CompiledBuffer = ms.GetBuffer(); - var length = (int)ms.Length; - - if (compress) - { - var buffer = ArrayPool.Shared.Rent(CompressorBufferSize); - - NetworkCompression.Compress(m_CompiledBuffer, 0, length, buffer, out length); - - if (length <= 0) - { - Console.WriteLine("Warning: Compression buffer overflowed on packet 0x{0:X2} ('{1}') (length={2})", - PacketID, GetType().Name, length); - using var op = new StreamWriter("compression_overflow.log", true); - op.WriteLine("{0} Warning: Compression buffer overflowed on packet 0x{1:X2} ('{2}') (length={3})", - DateTime.UtcNow, PacketID, GetType().Name, length); - op.WriteLine(new StackTrace()); - } - else - { - m_CompiledLength = length; - - if ((m_State & State.Static) != 0) - { - m_CompiledBuffer = new byte[length]; - Buffer.BlockCopy(buffer, 0, m_CompiledBuffer, 0, length); - ArrayPool.Shared.Return(buffer); - } - else - { - m_CompiledBuffer = buffer; - m_State |= State.Buffered; - } - } - } - else if (length > 0) - { - var old = m_CompiledBuffer; - m_CompiledLength = length; - - if ((m_State & State.Static) != 0) - { - m_CompiledBuffer = new byte[length]; - } - else - { - m_CompiledBuffer = ArrayPool.Shared.Rent(length); - m_State |= State.Buffered; - } - - Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, length); - } - - PacketWriter.ReleaseInstance(Stream); - Stream = null; - } - - [Flags] - private enum State - { - Inactive = 0x00, - Static = 0x01, - Acquired = 0x02, - Accessed = 0x04, - Buffered = 0x08, - Warned = 0x10 - } - } -} +/*************************************************************************** + * Packet.cs + * ------------------- + * begin : August 2, 2019 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Buffers; +using System.Diagnostics; +using System.IO; +using Server.Diagnostics; + +namespace Server.Network +{ + public abstract class Packet + { + private const int CompressorBufferSize = 0x10000; + + private const int BufferSize = 4096; + private readonly int m_Length; + + private byte[] m_CompiledBuffer; + private int m_CompiledLength; + private State m_State; + + protected Packet(int packetID) + { + PacketID = packetID; + + if (Core.Profiling) + { + var prof = PacketSendProfile.Acquire(GetType()); + prof.Increment(); + } + } + + protected Packet(int packetID, int length) + { + PacketID = packetID; + m_Length = length; + + Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length ); + Stream.Write((byte)packetID); + + if (Core.Profiling) + { + var prof = PacketSendProfile.Acquire(GetType()); + prof.Increment(); + } + } + + public int PacketID { get; } + + public PacketWriter Stream { get; protected set; } + + public void EnsureCapacity(int length) + { + Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length ); + Stream.Write((byte)PacketID); + Stream.Write((short)0); + } + + public static Packet SetStatic(Packet p) + { + p.SetStatic(); + return p; + } + + public static Packet Acquire(Packet p) + { + p.Acquire(); + return p; + } + + public static void Release(ref Packet p) + { + p?.Release(); + p = null; + } + + public static void Release(Packet p) + { + p?.Release(); + } + + public void SetStatic() + { + m_State |= State.Static | State.Acquired; + } + + public void Acquire() + { + m_State |= State.Acquired; + } + + public void OnSend() + { + Core.Set(); // Is this still needed if this is done async? + + if ((m_State & (State.Acquired | State.Static)) == 0) + Free(); + } + + private void Free() + { + if (m_CompiledBuffer == null) + return; + + if ((m_State & State.Buffered) != 0) + ArrayPool.Shared.Return(m_CompiledBuffer); + + m_State &= ~(State.Static | State.Acquired | State.Buffered); + + m_CompiledBuffer = null; + } + + public void Release() + { + if ((m_State & State.Acquired) != 0) + Free(); + } + + public byte[] Compile(bool compress, out int length) + { + lock (this) + { + if (m_CompiledBuffer == null) + { + if ((m_State & State.Accessed) == 0) + { + m_State |= State.Accessed; + } + else + { + if ((m_State & State.Warned) == 0) + { + m_State |= State.Warned; + + try + { + using var op = new StreamWriter("net_opt.log", true); + op.WriteLine("Redundant compile for packet {0}, use Acquire() and Release()", GetType()); + op.WriteLine(new StackTrace()); + } + catch + { + // ignored + } + } + + m_CompiledBuffer = Array.Empty(); + m_CompiledLength = 0; + + length = m_CompiledLength; + return m_CompiledBuffer; + } + + InternalCompile(compress); + } + + length = m_CompiledLength; + return m_CompiledBuffer; + } + } + + private void InternalCompile(bool compress) + { + if (m_Length == 0) + { + var streamLen = Stream.Length; + + Stream.Seek(1, SeekOrigin.Begin); + Stream.Write((ushort)streamLen); + } + else if (Stream.Length != m_Length) + { + var diff = (int)Stream.Length - m_Length; + + Console.WriteLine( + "Packet: 0x{0:X2}: Bad packet length! ({1}{2} bytes)", + PacketID, + diff >= 0 ? "+" : "", + diff + ); + } + + var ms = Stream.UnderlyingStream; + + m_CompiledBuffer = ms.GetBuffer(); + var length = (int)ms.Length; + + if (compress) + { + var buffer = ArrayPool.Shared.Rent(CompressorBufferSize); + + NetworkCompression.Compress(m_CompiledBuffer, 0, length, buffer, out length); + + if (length <= 0) + { + Console.WriteLine( + "Warning: Compression buffer overflowed on packet 0x{0:X2} ('{1}') (length={2})", + PacketID, + GetType().Name, + length + ); + using var op = new StreamWriter("compression_overflow.log", true); + op.WriteLine( + "{0} Warning: Compression buffer overflowed on packet 0x{1:X2} ('{2}') (length={3})", + DateTime.UtcNow, + PacketID, + GetType().Name, + length + ); + op.WriteLine(new StackTrace()); + } + else + { + m_CompiledLength = length; + + if ((m_State & State.Static) != 0) + { + m_CompiledBuffer = new byte[length]; + Buffer.BlockCopy(buffer, 0, m_CompiledBuffer, 0, length); + ArrayPool.Shared.Return(buffer); + } + else + { + m_CompiledBuffer = buffer; + m_State |= State.Buffered; + } + } + } + else if (length > 0) + { + var old = m_CompiledBuffer; + m_CompiledLength = length; + + if ((m_State & State.Static) != 0) + { + m_CompiledBuffer = new byte[length]; + } + else + { + m_CompiledBuffer = ArrayPool.Shared.Rent(length); + m_State |= State.Buffered; + } + + Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, length); + } + + PacketWriter.ReleaseInstance(Stream); + Stream = null; + } + + [Flags] + private enum State + { + Inactive = 0x00, + Static = 0x01, + Acquired = 0x02, + Accessed = 0x04, + Buffered = 0x08, + Warned = 0x10 + } + } +} diff --git a/Projects/Server/Network/PacketHandler.cs b/Projects/Server/Network/PacketHandler.cs index b5942788d..7bee08af8 100644 --- a/Projects/Server/Network/PacketHandler.cs +++ b/Projects/Server/Network/PacketHandler.cs @@ -1,49 +1,49 @@ -/*************************************************************************** - * PacketHandler.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server.Network -{ - public delegate void OnPacketReceive(NetState state, PacketReader pvSrc); - - public delegate TimeSpan ThrottlePacketCallback(NetState state); - - public class PacketHandler - { - public PacketHandler(int packetID, int length, bool ingame, OnPacketReceive onReceive) - { - PacketID = packetID; - Length = length; - Ingame = ingame; - OnReceive = onReceive; - } - - public int PacketID { get; } - - public int Length { get; } - - public OnPacketReceive OnReceive { get; } - - public ThrottlePacketCallback ThrottleCallback { get; set; } - - public bool Ingame { get; } - } -} +/*************************************************************************** + * PacketHandler.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server.Network +{ + public delegate void OnPacketReceive(NetState state, PacketReader pvSrc); + + public delegate TimeSpan ThrottlePacketCallback(NetState state); + + public class PacketHandler + { + public PacketHandler(int packetID, int length, bool ingame, OnPacketReceive onReceive) + { + PacketID = packetID; + Length = length; + Ingame = ingame; + OnReceive = onReceive; + } + + public int PacketID { get; } + + public int Length { get; } + + public OnPacketReceive OnReceive { get; } + + public ThrottlePacketCallback ThrottleCallback { get; set; } + + public bool Ingame { get; } + } +} diff --git a/Projects/Server/Network/PacketHandlers.cs b/Projects/Server/Network/PacketHandlers.cs index 7281cd016..7f5978223 100644 --- a/Projects/Server/Network/PacketHandlers.cs +++ b/Projects/Server/Network/PacketHandlers.cs @@ -1,2567 +1,2593 @@ -/*************************************************************************** - * PacketHandlers.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using Server.ContextMenus; -using Server.Diagnostics; -using Server.Gumps; -using Server.Items; -using Server.Targeting; -using CV = Server.ClientVersion; - -namespace Server.Network -{ - [Flags] - public enum MessageType - { - Regular = 0x00, - System = 0x01, - Emote = 0x02, - Label = 0x06, - Focus = 0x07, - Whisper = 0x08, - Yell = 0x09, - Spell = 0x0A, - - Guild = 0x0D, - Alliance = 0x0E, - Command = 0x0F, - - Encoded = 0xC0 - } - - public static class PacketHandlers - { - public delegate void PlayCharCallback(NetState state, bool val); - - private const int BadFood = unchecked((int)0xBAADF00D); - private const int BadUOTD = unchecked((int)0xFFCEFFCE); - - private const int m_AuthIDWindowSize = 128; - private static readonly PacketHandler[] m_6017Handlers = new PacketHandler[0x100]; - - private static readonly PacketHandler[] m_ExtendedHandlersLow = new PacketHandler[0x100]; - private static readonly Dictionary m_ExtendedHandlersHigh = new Dictionary(); - - private static readonly EncodedPacketHandler[] m_EncodedHandlersLow = new EncodedPacketHandler[0x100]; - private static readonly Dictionary m_EncodedHandlersHigh = new Dictionary(); - - private static readonly int[] m_EmptyInts = Array.Empty(); - - private static readonly KeywordList m_KeywordList = new KeywordList(); - - public static PlayCharCallback ThirdPartyAuthCallback { get; set; } - public static PlayCharCallback ThirdPartyHackedCallback { get; set; } - - private static readonly Dictionary m_AuthIDWindow = - new Dictionary(m_AuthIDWindowSize); - - static PacketHandlers() - { - Register(0x00, 104, false, CreateCharacter); - Register(0x01, 5, false, Disconnect); - Register(0x02, 7, true, MovementReq); - Register(0x03, 0, true, AsciiSpeech); - Register(0x05, 5, true, AttackReq); - Register(0x06, 5, true, UseReq); - Register(0x07, 7, true, LiftReq); - Register(0x08, 14, true, DropReq); - Register(0x09, 5, true, LookReq); - Register(0x12, 0, true, TextCommand); - Register(0x13, 10, true, EquipReq); - Register(0x22, 3, true, Resynchronize); - Register(0x2C, 2, true, DeathStatusResponse); - Register(0x34, 10, true, MobileQuery); - Register(0x3A, 0, true, ChangeSkillLock); - Register(0x3B, 0, true, VendorBuyReply); - Register(0x5D, 73, false, PlayCharacter); - Register(0x6C, 19, true, TargetResponse); - Register(0x6F, 0, true, SecureTrade); - Register(0x72, 5, true, SetWarMode); - Register(0x73, 2, false, PingReq); - Register(0x75, 35, true, RenameRequest); - Register(0x7D, 13, true, MenuResponse); - Register(0x80, 62, false, AccountLogin); - Register(0x83, 39, false, DeleteCharacter); - Register(0x91, 65, false, GameLogin); - Register(0x95, 9, true, HuePickerResponse); - Register(0x98, 0, true, MobileNameRequest); - Register(0x9A, 0, true, AsciiPromptResponse); - Register(0x9B, 258, true, HelpRequest); - Register(0x9F, 0, true, VendorSellReply); - Register(0xA0, 3, false, PlayServer); - Register(0xA4, 149, false, SystemInfo); - Register(0xA7, 4, true, RequestScrollWindow); - Register(0xAD, 0, true, UnicodeSpeech); - Register(0xB1, 0, true, DisplayGumpResponse); - Register(0xB5, 64, true, ChatRequest); - Register(0xB6, 9, true, ObjectHelpRequest); - Register(0xB8, 0, true, ProfileReq); - Register(0xBB, 9, false, AccountID); - Register(0xBD, 0, false, ClientVersion); - Register(0xBE, 0, true, AssistVersion); - Register(0xBF, 0, true, ExtendedCommand); - Register(0xC2, 0, true, UnicodePromptResponse); - Register(0xC8, 2, true, SetUpdateRange); - Register(0xCF, 0, false, AccountLogin); - Register(0xD0, 0, true, ConfigurationFile); - Register(0xD1, 2, true, LogoutReq); - Register(0xD6, 0, true, BatchQueryProperties); - Register(0xD7, 0, true, EncodedCommand); - Register(0xE1, 0, false, ClientType); - Register(0xEF, 21, false, LoginServerSeed); - Register(0xEC, 0, false, EquipMacro); - Register(0xED, 0, false, UnequipMacro); - Register(0xF4, 0, false, CrashReport); - Register(0xF8, 106, false, CreateCharacter70160); - Register(0xFB, 2, false, ShowPublicHouseContent); - - Register6017(0x08, 15, true, DropReq6017); - - RegisterExtended(0x05, false, ScreenSize); - RegisterExtended(0x06, true, PartyMessage); - RegisterExtended(0x07, true, QuestArrow); - RegisterExtended(0x09, true, DisarmRequest); - RegisterExtended(0x0A, true, StunRequest); - RegisterExtended(0x0B, false, Language); - RegisterExtended(0x0C, true, CloseStatus); - RegisterExtended(0x0E, true, Animate); - RegisterExtended(0x0F, false, Empty); // What's this? - RegisterExtended(0x10, true, QueryProperties); - RegisterExtended(0x13, true, ContextMenuRequest); - RegisterExtended(0x15, true, ContextMenuResponse); - RegisterExtended(0x1A, true, StatLockChange); - RegisterExtended(0x1C, true, CastSpell); - RegisterExtended(0x24, false, UnhandledBF); - RegisterExtended(0x2C, true, BandageTarget); - RegisterExtended(0x2D, true, TargetedSpell); - RegisterExtended(0x2E, true, TargetedSkillUse); - RegisterExtended(0x30, true, TargetByResourceMacro); - RegisterExtended(0x32, true, ToggleFlying); - - RegisterEncoded(0x19, true, SetAbility); - RegisterEncoded(0x28, true, GuildGumpRequest); - - RegisterEncoded(0x32, true, QuestGumpRequest); - } - - public static PacketHandler[] Handlers { get; } = new PacketHandler[0x100]; - - public static bool SingleClickProps { get; set; } - - // TODO: Change to outside configuration - public static int[] ValidAnimations { get; set; } = { - 6, 21, 32, 33, - 100, 101, 102, - 103, 104, 105, - 106, 107, 108, - 109, 110, 111, - 112, 113, 114, - 115, 116, 117, - 118, 119, 120, - 121, 123, 124, - 125, 126, 127, - 128 - }; - - public static bool ClientVerification { get; set; } = true; - - public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive) - { - Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive); - m_6017Handlers[packetID] ??= new PacketHandler(packetID, length, ingame, onReceive); - } - - public static PacketHandler GetHandler(int packetID) => Handlers[packetID]; - - public static void Register6017(int packetID, int length, bool ingame, OnPacketReceive onReceive) - { - m_6017Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive); - } - - public static PacketHandler Get6017Handler(int packetID) => m_6017Handlers[packetID]; - - public static void RegisterExtended(int packetID, bool ingame, OnPacketReceive onReceive) - { - if (packetID >= 0 && packetID < 0x100) - m_ExtendedHandlersLow[packetID] = new PacketHandler(packetID, 0, ingame, onReceive); - else - m_ExtendedHandlersHigh[packetID] = new PacketHandler(packetID, 0, ingame, onReceive); - } - - public static PacketHandler GetExtendedHandler(int packetID) - { - if (packetID >= 0 && packetID < 0x100) - return m_ExtendedHandlersLow[packetID]; - - m_ExtendedHandlersHigh.TryGetValue(packetID, out var handler); - return handler; - } - - public static void RemoveExtendedHandler(int packetID) - { - if (packetID >= 0 && packetID < 0x100) - m_ExtendedHandlersLow[packetID] = null; - else - m_ExtendedHandlersHigh.Remove(packetID); - } - - public static void RegisterEncoded(int packetID, bool ingame, OnEncodedPacketReceive onReceive) - { - if (packetID >= 0 && packetID < 0x100) - m_EncodedHandlersLow[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive); - else - m_EncodedHandlersHigh[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive); - } - - public static EncodedPacketHandler GetEncodedHandler(int packetID) - { - if (packetID >= 0 && packetID < 0x100) - return m_EncodedHandlersLow[packetID]; - - m_EncodedHandlersHigh.TryGetValue(packetID, out var handler); - return handler; - } - - public static void RemoveEncodedHandler(int packetID) - { - if (packetID >= 0 && packetID < 0x100) - m_EncodedHandlersLow[packetID] = null; - else - m_EncodedHandlersHigh.Remove(packetID); - } - - public static void RegisterThrottler(int packetID, ThrottlePacketCallback t) - { - var ph = GetHandler(packetID); - - if (ph != null) - ph.ThrottleCallback = t; - - ph = Get6017Handler(packetID); - - if (ph != null) - ph.ThrottleCallback = t; - } - - private static readonly MemoryPool _memoryPool = SlabMemoryPoolFactory.Create(); - - public static int ProcessPacket(IMessagePumpService pump, NetState ns, in ReadOnlySequence seq) - { - var r = new PacketReader(seq); - - if (!r.TryReadByte(out var packetId)) - { - ns.Dispose(); - return -1; - } - - if (!ns.Seeded) - { - if (packetId == 0xEF) - { - // new packet in client 6.0.5.0 replaces the traditional seed method with a seed packet - // 0xEF = 239 = multicast IP, so this should never appear in a normal seed. So this is backwards compatible with older clients. - ns.Seeded = true; - } - else - { - var seed = (packetId << 24) | (r.ReadByte() << 16) | (r.ReadByte() << 8) | r.ReadByte(); - - if (seed == 0) - { - Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", ns); - ns.Dispose(); - return -1; - } - - ns.m_Seed = seed; - ns.Seeded = true; - - return 4; - } - } - - if (ns.CheckEncrypted(packetId)) - { - ns.Dispose(); - return -1; - } - - // Get Handlers - var handler = ns.GetHandler(packetId); - - if (handler == null) - { - r.Trace(ns); - return -1; - } - - var packetLength = handler.Length; - if (handler.Length <= 0 && r.Length >= 3) - { - packetLength = r.ReadUInt16(); - if (packetLength < 3) - { - ns.Dispose(); - return -1; - } - } - - if (r.Length < packetLength) - return 0; - - if (handler.Ingame && ns.Mobile?.Deleted != false) - { - Console.WriteLine( - "Client: {0}: Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.", - ns, - packetId); - ns.Dispose(); - return -1; - } - - var throttled = handler.ThrottleCallback?.Invoke(ns) ?? TimeSpan.Zero; - - if (throttled > TimeSpan.Zero) - ns.ThrottledUntil = DateTime.UtcNow + throttled; - - var packet = seq.Slice(r.Position); - int length = (int)packet.Length; - var memOwner = _memoryPool.Rent(length); - - // TODO: This is slow, find another way - packet.CopyTo(memOwner.Memory.Span); - - pump.QueueWork(ns, memOwner, length, handler.OnReceive); - - return packetLength; - } - - private static void UnhandledBF(NetState state, PacketReader pvSrc) - { - } - - public static void Empty(NetState state, PacketReader pvSrc) - { - } - - public static void SetAbility(NetState state, IEntity e, EncodedReader reader) - { - EventSink.InvokeSetAbility(state.Mobile, reader.ReadInt32()); - } - - public static void GuildGumpRequest(NetState state, IEntity e, EncodedReader reader) - { - EventSink.InvokeGuildGumpRequest(state.Mobile); - } - - public static void QuestGumpRequest(NetState state, IEntity e, EncodedReader reader) - { - EventSink.InvokeQuestGumpRequest(state.Mobile); - } - - public static void EncodedCommand(NetState state, PacketReader pvSrc) - { - var e = World.FindEntity(pvSrc.ReadUInt32()); - int packetId = pvSrc.ReadUInt16(); - - var ph = GetEncodedHandler(packetId); - - if (ph != null) - { - if (ph.Ingame && state.Mobile == null) - { - Console.WriteLine( - "Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile", state, - packetId); - state.Dispose(); - } - else if (ph.Ingame && state.Mobile.Deleted) - { - state.Dispose(); - } - else - { - ph.OnReceive(state, e, new EncodedReader(pvSrc)); - } - } - else - { - pvSrc.Trace(state); - } - } - - public static void RenameRequest(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - var targ = World.FindMobile(pvSrc.ReadUInt32()); - - if (targ != null) - EventSink.InvokeRenameRequest(from, targ, pvSrc.ReadStringSafe()); - } - - public static void ChatRequest(NetState state, PacketReader pvSrc) - { - EventSink.InvokeChatRequest(state.Mobile); - } - - public static void SecureTrade(NetState state, PacketReader pvSrc) - { - switch (pvSrc.ReadByte()) - { - case 1: // Cancel - { - Serial serial = pvSrc.ReadUInt32(); - - if (World.FindItem(serial) is SecureTradeContainer cont && cont.Trade != null && - (cont.Trade.From.Mobile == state.Mobile || cont.Trade.To.Mobile == state.Mobile)) - cont.Trade.Cancel(); - - break; - } - case 2: // Check - { - Serial serial = pvSrc.ReadUInt32(); - - if (World.FindItem(serial) is SecureTradeContainer cont) - { - var trade = cont.Trade; - - var value = pvSrc.ReadInt32() != 0; - - if (trade != null && trade.From.Mobile == state.Mobile) - { - trade.From.Accepted = value; - trade.Update(); - } - else if (trade != null && trade.To.Mobile == state.Mobile) - { - trade.To.Accepted = value; - trade.Update(); - } - } - - break; - } - case 3: // Update Gold - { - Serial serial = pvSrc.ReadUInt32(); - - if (World.FindItem(serial) is SecureTradeContainer cont) - { - var gold = pvSrc.ReadInt32(); - var plat = pvSrc.ReadInt32(); - - var trade = cont.Trade; - - if (trade != null) - { - if (trade.From.Mobile == state.Mobile) - { - trade.From.Gold = gold; - trade.From.Plat = plat; - trade.UpdateFromCurrency(); - } - else if (trade.To.Mobile == state.Mobile) - { - trade.To.Gold = gold; - trade.To.Plat = plat; - trade.UpdateToCurrency(); - } - } - } - } - break; - } - } - - public static void VendorBuyReply(NetState state, PacketReader pvSrc) - { - var vendor = World.FindMobile(pvSrc.ReadUInt32()); - var flag = pvSrc.ReadByte(); - - if (vendor == null) return; - - if (vendor.Deleted || !Utility.RangeCheck(vendor.Location, state.Mobile.Location, 10)) - { - state.Send(new EndVendorBuy(vendor)); - return; - } - - if (flag == 0x02) - { - int msgSize = (int)pvSrc.Remaining; - - if (msgSize / 7 > 100) - return; - - var buyList = new List(msgSize / 7); - while (msgSize > 0) - { - var layer = pvSrc.ReadByte(); - Serial serial = pvSrc.ReadUInt32(); - int amount = pvSrc.ReadInt16(); - - buyList.Add(new BuyItemResponse(serial, amount)); - msgSize -= 7; - } - - if (buyList.Count > 0 && vendor is IVendor v && v.OnBuyItems(state.Mobile, buyList)) - state.Send(new EndVendorBuy(vendor)); - } - else - { - state.Send(new EndVendorBuy(vendor)); - } - } - - public static void VendorSellReply(NetState state, PacketReader pvSrc) - { - Serial serial = pvSrc.ReadUInt32(); - var vendor = World.FindMobile(serial); - - if (vendor == null) return; - - if (vendor.Deleted || !Utility.RangeCheck(vendor.Location, state.Mobile.Location, 10)) - { - state.Send(new EndVendorSell(vendor)); - return; - } - - int count = pvSrc.ReadUInt16(); - - if (count >= 100 || pvSrc.Remaining != count * 6) - return; - - var sellList = new List(count); - - for (var i = 0; i < count; i++) - { - var item = World.FindItem(pvSrc.ReadUInt32()); - int amount = pvSrc.ReadInt16(); - - if (item != null && amount > 0) - sellList.Add(new SellItemResponse(item, amount)); - } - - if (sellList.Count > 0 && vendor is IVendor v && v.OnSellItems(state.Mobile, sellList)) - state.Send(new EndVendorSell(vendor)); - } - - public static void DeleteCharacter(NetState state, PacketReader pvSrc) - { - pvSrc.Seek(30, SeekOrigin.Current); - var index = pvSrc.ReadInt32(); - - EventSink.InvokeDeleteRequest(state, index); - } - - public static void DeathStatusResponse(NetState state, PacketReader pvSrc) - { - // Ignored - } - - public static void ObjectHelpRequest(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - Serial serial = pvSrc.ReadUInt32(); - int unk = pvSrc.ReadByte(); - var lang = pvSrc.ReadString(3); - - if (serial.IsItem) - { - var item = World.FindItem(serial); - - if (item != null && from.Map == item.Map && Utility.InUpdateRange(item.GetWorldLocation(), from.Location) && - from.CanSee(item)) - item.OnHelpRequest(from); - } - else if (serial.IsMobile) - { - var m = World.FindMobile(serial); - - if (m != null && from.Map == m.Map && Utility.InUpdateRange(m.Location, from.Location) && from.CanSee(m)) - m.OnHelpRequest(m); - } - } - - public static void MobileNameRequest(NetState state, PacketReader pvSrc) - { - var m = World.FindMobile(pvSrc.ReadUInt32()); - - if (m != null && Utility.InUpdateRange(state.Mobile, m) && state.Mobile.CanSee(m)) - state.Send(new MobileName(m)); - } - - public static void RequestScrollWindow(NetState state, PacketReader pvSrc) - { - int lastTip = pvSrc.ReadInt16(); - int type = pvSrc.ReadByte(); - } - - public static void AttackReq(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - var m = World.FindMobile(pvSrc.ReadUInt32()); - - if (m != null) - from.Attack(m); - } - - public static void HuePickerResponse(NetState state, PacketReader pvSrc) - { - var serial = pvSrc.ReadUInt32(); - _ = pvSrc.ReadInt16(); // Item ID - var hue = pvSrc.ReadInt16() & 0x3FFF; - - hue = Utility.ClipDyedHue(hue); - - foreach (var huePicker in state.HuePickers) - if (huePicker.Serial == serial) - { - state.RemoveHuePicker(huePicker); - - huePicker.OnResponse(hue); - - break; - } - } - - public static void SystemInfo(NetState state, PacketReader pvSrc) - { - int v1 = pvSrc.ReadByte(); - int v2 = pvSrc.ReadUInt16(); - int v3 = pvSrc.ReadByte(); - var s1 = pvSrc.ReadString(32); - var s2 = pvSrc.ReadString(32); - var s3 = pvSrc.ReadString(32); - var s4 = pvSrc.ReadString(32); - int v4 = pvSrc.ReadUInt16(); - int v5 = pvSrc.ReadUInt16(); - var v6 = pvSrc.ReadInt32(); - var v7 = pvSrc.ReadInt32(); - var v8 = pvSrc.ReadInt32(); - } - - public static void AccountID(NetState state, PacketReader pvSrc) - { - } - - public static void TextCommand(NetState state, PacketReader pvSrc) - { - int type = pvSrc.ReadByte(); - var command = pvSrc.ReadString(); - - var m = state.Mobile; - - switch (type) - { - case 0xC7: // Animate - { - EventSink.InvokeAnimateRequest(m, command); - - break; - } - case 0x24: // Use skill - { - if (!int.TryParse(command.Split(' ')[0], out var skillIndex)) - break; - - Skills.UseSkill(m, skillIndex); - - break; - } - case 0x43: // Open spellbook - { - if (!int.TryParse(command, out var booktype)) - booktype = 1; - - EventSink.InvokeOpenSpellbookRequest(m, booktype); - - break; - } - case 0x27: // Cast spell from book - { - var split = command.Split(' '); - - if (split.Length > 0) - { - var spellID = Utility.ToInt32(split[0]) - 1; - var serial = split.Length > 1 ? Utility.ToUInt32(split[1]) : (uint)Serial.MinusOne; - - EventSink.InvokeCastSpellRequest(m, spellID, World.FindItem(serial)); - } - - break; - } - case 0x58: // Open door - { - EventSink.InvokeOpenDoorMacroUsed(m); - - break; - } - case 0x56: // Cast spell from macro - { - var spellID = Utility.ToInt32(command) - 1; - - EventSink.InvokeCastSpellRequest(m, spellID, null); - - break; - } - case 0xF4: // Invoke virtues from macro - { - var virtueID = Utility.ToInt32(command) - 1; - - EventSink.InvokeVirtueMacroRequest(m, virtueID); - - break; - } - case 0x2F: // Old scroll double click - { - /* - * This command is still sent for items 0xEF3 - 0xEF9 - * - * Command is one of three, depending on the item ID of the scroll: - * - [scroll serial] - * - [scroll serial] [target serial] - * - [scroll serial] [x] [y] [z] - */ - break; - } - default: - { - Console.WriteLine("Client: {0}: Unknown text-command type 0x{1:X2}: {2}", state, type, command); - break; - } - } - } - - public static void AsciiPromptResponse(NetState state, PacketReader pvSrc) - { - var serial = pvSrc.ReadUInt32(); - var prompt = pvSrc.ReadInt32(); - var type = pvSrc.ReadInt32(); - var text = pvSrc.ReadStringSafe(); - - if (text.Length > 128) - return; - - var from = state.Mobile; - var p = from.Prompt; - - if (p != null && p.Serial == serial && p.Serial == prompt) - { - from.Prompt = null; - - if (type == 0) - p.OnCancel(from); - else - p.OnResponse(from, text); - } - } - - public static void UnicodePromptResponse(NetState state, PacketReader pvSrc) - { - var serial = pvSrc.ReadUInt32(); - var prompt = pvSrc.ReadInt32(); - var type = pvSrc.ReadInt32(); - var lang = pvSrc.ReadString(4); - var text = pvSrc.ReadUnicodeStringLESafe(); - - if (text.Length > 128) - return; - - var from = state.Mobile; - var p = from.Prompt; - - if (p != null && p.Serial == serial && p.Serial == prompt) - { - from.Prompt = null; - - if (type == 0) - p.OnCancel(from); - else - p.OnResponse(from, text); - } - } - - public static void MenuResponse(NetState state, PacketReader pvSrc) - { - var serial = pvSrc.ReadUInt32(); - int menuID = pvSrc.ReadInt16(); // unused in our implementation - int index = pvSrc.ReadInt16(); - int itemID = pvSrc.ReadInt16(); - int hue = pvSrc.ReadInt16(); - - index -= 1; // convert from 1-based to 0-based - - foreach (var menu in state.Menus) - if (menu.Serial == serial) - { - state.RemoveMenu(menu); - - if (index >= 0 && index < menu.EntryLength) - menu.OnResponse(state, index); - else - menu.OnCancel(state); - - break; - } - } - - public static void ProfileReq(NetState state, PacketReader pvSrc) - { - int type = pvSrc.ReadByte(); - Serial serial = pvSrc.ReadUInt32(); - - var beholder = state.Mobile; - var beheld = World.FindMobile(serial); - - if (beheld == null) return; - - switch (type) - { - case 0x00: // display request - { - EventSink.InvokeProfileRequest(beholder, beheld); - - break; - } - case 0x01: // edit request - { - pvSrc.ReadInt16(); // Skip - int length = pvSrc.ReadUInt16(); - - if (length > 511) - return; - - var text = pvSrc.ReadUnicodeString(length); - - EventSink.InvokeChangeProfileRequest(beholder, beheld, text); - - break; - } - } - } - - public static void Disconnect(NetState state, PacketReader pvSrc) - { - var minusOne = pvSrc.ReadInt32(); - } - - public static void LiftReq(NetState state, PacketReader pvSrc) - { - Serial serial = pvSrc.ReadUInt32(); - int amount = pvSrc.ReadUInt16(); - var item = World.FindItem(serial); - - state.Mobile.Lift(item, amount, out var rejected, out var reject); - } - - public static void EquipReq(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - var item = from.Holding; - - var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; - - from.Holding = null; - - if (!valid) return; - - pvSrc.Seek(5, SeekOrigin.Current); - var to = World.FindMobile(pvSrc.ReadUInt32()) ?? from; - - if (!to.AllowEquipFrom(from) || !to.EquipItem(item)) - item.Bounce(from); - - item.ClearBounce(); - } - - public static void DropReq(NetState state, PacketReader pvSrc) - { - pvSrc.ReadInt32(); // serial, ignored - int x = pvSrc.ReadInt16(); - int y = pvSrc.ReadInt16(); - int z = pvSrc.ReadSByte(); - Serial dest = pvSrc.ReadUInt32(); - - var loc = new Point3D(x, y, z); - - var from = state.Mobile; - - if (dest.IsMobile) - { - from.Drop(World.FindMobile(dest), loc); - } - else if (dest.IsItem) - { - var item = World.FindItem(dest); - - if (item is BaseMulti multi && multi.AllowsRelativeDrop) - { - loc.m_X += multi.X; - loc.m_Y += multi.Y; - from.Drop(loc); - } - else - { - from.Drop(item, loc); - } - } - else - { - from.Drop(loc); - } - } - - public static void DropReq6017(NetState state, PacketReader pvSrc) - { - pvSrc.ReadInt32(); // serial, ignored - int x = pvSrc.ReadInt16(); - int y = pvSrc.ReadInt16(); - int z = pvSrc.ReadSByte(); - pvSrc.ReadByte(); // Grid Location? - Serial dest = pvSrc.ReadUInt32(); - - var loc = new Point3D(x, y, z); - - var from = state.Mobile; - - if (dest.IsMobile) - { - from.Drop(World.FindMobile(dest), loc); - } - else if (dest.IsItem) - { - var item = World.FindItem(dest); - - if (item is BaseMulti multi && multi.AllowsRelativeDrop) - { - loc.m_X += multi.X; - loc.m_Y += multi.Y; - from.Drop(loc); - } - else - { - from.Drop(item, loc); - } - } - else - { - from.Drop(loc); - } - } - - public static void ConfigurationFile(NetState state, PacketReader pvSrc) - { - } - - public static void LogoutReq(NetState state, PacketReader pvSrc) - { - state.Send(new LogoutAck()); - } - - public static void ChangeSkillLock(NetState state, PacketReader pvSrc) - { - var s = state.Mobile.Skills[pvSrc.ReadInt16()]; - - s?.SetLockNoRelay((SkillLock)pvSrc.ReadByte()); - } - - public static void HelpRequest(NetState state, PacketReader pvSrc) - { - EventSink.InvokeHelpRequest(state.Mobile); - } - - public static void TargetResponse(NetState state, PacketReader pvSrc) - { - int type = pvSrc.ReadByte(); - var targetID = pvSrc.ReadInt32(); - int flags = pvSrc.ReadByte(); - Serial serial = pvSrc.ReadUInt32(); - int x = pvSrc.ReadInt16(), y = pvSrc.ReadInt16(), z = pvSrc.ReadInt16(); - int graphic = pvSrc.ReadUInt16(); - - if (targetID == unchecked((int)0xDEADBEEF)) - return; - - var from = state.Mobile; - - var t = from.Target; - - if (t == null) return; - - var prof = TargetProfile.Acquire(t.GetType()); - prof?.Start(); - - try - { - if (x == -1 && y == -1 && !serial.IsValid) - { - // User pressed escape - t.Cancel(from, TargetCancelType.Canceled); - } - else if (t.TargetID != targetID) - { - // Sanity, prevent fake target - } - else - { - object toTarget; - - if (type == 1) - { - if (graphic == 0) - { - toTarget = new LandTarget(new Point3D(x, y, z), from.Map); - } - else - { - var map = from.Map; - - if (map == null || map == Map.Internal) - { - t.Cancel(from, TargetCancelType.Canceled); - return; - } - else - { - var tiles = map.Tiles.GetStaticTiles(x, y, !t.DisallowMultis); - - var valid = false; - - if (state.HighSeas) - { - var id = TileData.ItemTable[graphic & TileData.MaxItemValue]; - if (id.Surface) z -= id.Height; - } - - for (var i = 0; !valid && i < tiles.Length; ++i) - if (tiles[i].Z == z && tiles[i].ID == graphic) - valid = true; - - if (!valid) - { - t.Cancel(from, TargetCancelType.Canceled); - return; - } - else - { - toTarget = new StaticTarget(new Point3D(x, y, z), graphic); - } - } - } - } - else if (serial.IsMobile) - { - toTarget = World.FindMobile(serial); - } - else if (serial.IsItem) - { - toTarget = World.FindItem(serial); - } - else - { - t.Cancel(from, TargetCancelType.Canceled); - return; - } - - t.Invoke(from, toTarget); - } - } - finally - { - prof?.Finish(); - } - } - - public static void DisplayGumpResponse(NetState state, PacketReader pvSrc) - { - var serial = pvSrc.ReadUInt32(); - var typeID = pvSrc.ReadInt32(); - var buttonID = pvSrc.ReadInt32(); - - foreach (var gump in state.Gumps) - { - if (gump.Serial != serial || gump.TypeID != typeID) - continue; - var buttonExists = buttonID == 0; // 0 is always 'close' - - if (!buttonExists) - foreach (var e in gump.Entries) - { - if (e is GumpButton button && button.ButtonID == buttonID) - { - buttonExists = true; - break; - } - - if (e is GumpImageTileButton tileButton && tileButton.ButtonID == buttonID) - { - buttonExists = true; - break; - } - } - - if (!buttonExists) - { - state.WriteConsole("Invalid gump response, disconnecting..."); - state.Dispose(); - return; - } - - var switchCount = pvSrc.ReadInt32(); - - if (switchCount < 0 || switchCount > gump.m_Switches) - { - state.WriteConsole("Invalid gump response, disconnecting..."); - state.Dispose(); - return; - } - - var switches = new int[switchCount]; - - for (var j = 0; j < switches.Length; ++j) - switches[j] = pvSrc.ReadInt32(); - - var textCount = pvSrc.ReadInt32(); - - if (textCount < 0 || textCount > gump.m_TextEntries) - { - state.WriteConsole("Invalid gump response, disconnecting..."); - state.Dispose(); - return; - } - - var textEntries = new TextRelay[textCount]; - - for (var j = 0; j < textEntries.Length; ++j) - { - int entryID = pvSrc.ReadUInt16(); - int textLength = pvSrc.ReadUInt16(); - - if (textLength > 239) - { - state.WriteConsole("Invalid gump response, disconnecting..."); - state.Dispose(); - return; - } - - var text = pvSrc.ReadUnicodeStringSafe(textLength); - textEntries[j] = new TextRelay(entryID, text); - } - - state.RemoveGump(gump); - - var prof = GumpProfile.Acquire(gump.GetType()); - - prof?.Start(); - - gump.OnResponse(state, new RelayInfo(buttonID, switches, textEntries)); - - prof?.Finish(); - - return; - } - - if (typeID == 461) - { - // Virtue gump - var switchCount = pvSrc.ReadInt32(); - - if (buttonID == 1 && switchCount > 0) - { - var beheld = World.FindMobile(pvSrc.ReadUInt32()); - - if (beheld != null) - EventSink.InvokeVirtueGumpRequest(state.Mobile, beheld); - } - else - { - var beheld = World.FindMobile(serial); - - if (beheld != null) - EventSink.InvokeVirtueItemRequest(state.Mobile, beheld, buttonID); - } - } - } - - public static void SetWarMode(NetState state, PacketReader pvSrc) - { - state.Mobile.DelayChangeWarmode(pvSrc.ReadBoolean()); - } - - public static void Resynchronize(NetState state, PacketReader pvSrc) - { - var m = state.Mobile; - - if (state.StygianAbyss) - state.Send(new MobileUpdate(m)); - else - state.Send(new MobileUpdateOld(m)); - - state.Send(MobileIncoming.Create(state, m, m)); - - m.SendEverything(); - - state.Sequence = 0; - - m.ClearFastwalkStack(); - } - - public static void AsciiSpeech(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - var type = (MessageType)pvSrc.ReadByte(); - int hue = pvSrc.ReadInt16(); - pvSrc.ReadInt16(); // font - var text = pvSrc.ReadStringSafe().Trim(); - - if (text.Length <= 0 || text.Length > 128) - return; - - if (!Enum.IsDefined(typeof(MessageType), type)) - type = MessageType.Regular; - - from.DoSpeech(text, m_EmptyInts, type, Utility.ClipDyedHue(hue)); - } - - public static void UnicodeSpeech(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - var type = (MessageType)pvSrc.ReadByte(); - int hue = pvSrc.ReadInt16(); - pvSrc.ReadInt16(); // font - var lang = pvSrc.ReadString(4); - string text; - - var isEncoded = (type & MessageType.Encoded) != 0; - int[] keywords; - - if (isEncoded) - { - int value = pvSrc.ReadInt16(); - var count = (value & 0xFFF0) >> 4; - var hold = value & 0xF; - - if (count < 0 || count > 50) - return; - - var keyList = m_KeywordList; - - for (var i = 0; i < count; ++i) - { - int speechID; - - if ((i & 1) == 0) - { - hold <<= 8; - hold |= pvSrc.ReadByte(); - speechID = hold; - hold = 0; - } - else - { - value = pvSrc.ReadInt16(); - speechID = (value & 0xFFF0) >> 4; - hold = value & 0xF; - } - - if (!keyList.Contains(speechID)) - keyList.Add(speechID); - } - - text = pvSrc.ReadUTF8StringSafe(); - - keywords = keyList.ToArray(); - } - else - { - text = pvSrc.ReadUnicodeStringSafe(); - - keywords = m_EmptyInts; - } - - text = text.Trim(); - - if (text.Length <= 0 || text.Length > 128) - return; - - type &= ~MessageType.Encoded; - - if (!Enum.IsDefined(typeof(MessageType), type)) - type = MessageType.Regular; - - from.Language = lang; - from.DoSpeech(text, keywords, type, Utility.ClipDyedHue(hue)); - } - - public static void UseReq(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0) - { - var value = pvSrc.ReadUInt32(); - - if ((value & ~0x7FFFFFFF) != 0) - { - from.OnPaperdollRequest(); - } - else - { - Serial s = value; - - if (s.IsMobile) - { - var m = World.FindMobile(s); - - if (m?.Deleted == false) - from.Use(m); - } - else if (s.IsItem) - { - var item = World.FindItem(s); - - if (item?.Deleted == false) - from.Use(item); - } - } - - from.NextActionTime = Core.TickCount + Mobile.ActionDelay; - } - else - { - from.SendActionMessage(); - } - } - - public static void LookReq(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - Serial s = pvSrc.ReadUInt32(); - - if (s.IsMobile) - { - var m = World.FindMobile(s); - - if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m)) - { - if (SingleClickProps) - { - m.OnAosSingleClick(from); - } - else - { - if (from.Region.OnSingleClick(from, m)) - m.OnSingleClick(from); - } - } - } - else if (s.IsItem) - { - var item = World.FindItem(s); - - if (item?.Deleted == false && from.CanSee(item) && - Utility.InUpdateRange(from.Location, item.GetWorldLocation())) - { - if (SingleClickProps) - { - item.OnAosSingleClick(from); - } - else if (from.Region.OnSingleClick(from, item)) - { - if (item.Parent is Item parentItem) - parentItem.OnSingleClickContained(from, item); - - item.OnSingleClick(from); - } - } - } - } - - public static void PingReq(NetState state, PacketReader pvSrc) - { - state.Send(PingAck.Instantiate(pvSrc.ReadByte())); - } - - public static void SetUpdateRange(NetState state, PacketReader pvSrc) - { - state.Send(ChangeUpdateRange.Instantiate(18)); - } - - public static void MovementReq(NetState state, PacketReader pvSrc) - { - var dir = (Direction)pvSrc.ReadByte(); - int seq = pvSrc.ReadByte(); - var key = pvSrc.ReadInt32(); - - var m = state.Mobile; - - if ((state.Sequence == 0 && seq != 0) || !m.Move(dir)) - { - state.Send(new MovementRej(seq, m)); - state.Sequence = 0; - - m.ClearFastwalkStack(); - } - else - { - ++seq; - - if (seq == 256) - seq = 1; - - state.Sequence = seq; - } - } - - public static void Animate(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - var action = pvSrc.ReadInt32(); - - var ok = false; - - for (var i = 0; !ok && i < ValidAnimations.Length; ++i) - ok = action == ValidAnimations[i]; - - if (from != null && ok && from.Alive && from.Body.IsHuman && !from.Mounted) - from.Animate(action, 7, 1, true, false, 0); - } - - public static void QuestArrow(NetState state, PacketReader pvSrc) - { - var rightClick = pvSrc.ReadBoolean(); - var from = state.Mobile; - - from?.QuestArrow?.OnClick(rightClick); - } - - public static void ExtendedCommand(NetState state, PacketReader pvSrc) - { - int packetID = pvSrc.ReadUInt16(); - - var ph = GetExtendedHandler(packetID); - - if (ph == null) - { - pvSrc.Trace(state); - return; - } - - if (ph.Ingame && state.Mobile?.Deleted != false) - { - if (state.Mobile == null) - Console.WriteLine( - "Client: {0}: Sent in-game packet (0xBFx{1:X2}) before having been attached to a mobile", state, - packetID); - state.Dispose(); - } - else - { - ph.OnReceive(state, pvSrc); - } - } - - public static void CastSpell(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - if (from == null) - return; - - Item spellbook = null; - - if (pvSrc.ReadInt16() == 1) - spellbook = World.FindItem(pvSrc.ReadUInt32()); - - var spellID = pvSrc.ReadInt16() - 1; - - EventSink.InvokeCastSpellRequest(from, spellID, spellbook); - } - - public static void BandageTarget(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - if (from == null) - return; - - if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0) - { - var bandage = World.FindItem(pvSrc.ReadUInt32()); - - if (bandage == null) - return; - - var target = World.FindMobile(pvSrc.ReadUInt32()); - - if (target == null) - return; - - EventSink.InvokeBandageTargetRequest(from, bandage, target); - - from.NextActionTime = Core.TickCount + Mobile.ActionDelay; - } - else - { - from.SendActionMessage(); - } - } - - public static void ToggleFlying(NetState state, PacketReader pvSrc) - { - state.Mobile.ToggleFlying(); - } - - public static void BatchQueryProperties(NetState state, PacketReader pvSrc) - { - if (!ObjectPropertyList.Enabled) - return; - - var from = state.Mobile; - - var length = pvSrc.Remaining; - - if (length % 4 != 0) - return; - - while (pvSrc.Remaining > 0) - { - Serial s = pvSrc.ReadUInt32(); - - if (s.IsMobile) - { - var m = World.FindMobile(s); - - if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m)) - m.SendPropertiesTo(from); - } - else if (s.IsItem) - { - var item = World.FindItem(s); - - if (item?.Deleted == false && from.CanSee(item) && - Utility.InUpdateRange(from.Location, item.GetWorldLocation())) - item.SendPropertiesTo(from); - } - } - } - - public static void QueryProperties(NetState state, PacketReader pvSrc) - { - if (!ObjectPropertyList.Enabled) - return; - - var from = state.Mobile; - - Serial s = pvSrc.ReadUInt32(); - - if (s.IsMobile) - { - var m = World.FindMobile(s); - - if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m)) - m.SendPropertiesTo(from); - } - else if (s.IsItem) - { - var item = World.FindItem(s); - - if (item?.Deleted == false && from.CanSee(item) && - Utility.InUpdateRange(from.Location, item.GetWorldLocation())) - item.SendPropertiesTo(from); - } - } - - public static void PartyMessage(NetState state, PacketReader pvSrc) - { - if (state.Mobile == null) - return; - - switch (pvSrc.ReadByte()) - { - case 0x01: - PartyMessage_AddMember(state, pvSrc); - break; - case 0x02: - PartyMessage_RemoveMember(state, pvSrc); - break; - case 0x03: - PartyMessage_PrivateMessage(state, pvSrc); - break; - case 0x04: - PartyMessage_PublicMessage(state, pvSrc); - break; - case 0x06: - PartyMessage_SetCanLoot(state, pvSrc); - break; - case 0x08: - PartyMessage_Accept(state, pvSrc); - break; - case 0x09: - PartyMessage_Decline(state, pvSrc); - break; - default: - pvSrc.Trace(state); - break; - } - } - - public static void PartyMessage_AddMember(NetState state, PacketReader pvSrc) - { - PartyCommands.Handler?.OnAdd(state.Mobile); - } - - public static void PartyMessage_RemoveMember(NetState state, PacketReader pvSrc) - { - PartyCommands.Handler?.OnRemove(state.Mobile, World.FindMobile(pvSrc.ReadUInt32())); - } - - public static void PartyMessage_PrivateMessage(NetState state, PacketReader pvSrc) - { - PartyCommands.Handler?.OnPrivateMessage( - state.Mobile, - World.FindMobile(pvSrc.ReadUInt32()), - pvSrc.ReadUnicodeStringSafe() - ); - } - - public static void PartyMessage_PublicMessage(NetState state, PacketReader pvSrc) - { - PartyCommands.Handler?.OnPublicMessage(state.Mobile, pvSrc.ReadUnicodeStringSafe()); - } - - public static void PartyMessage_SetCanLoot(NetState state, PacketReader pvSrc) - { - PartyCommands.Handler?.OnSetCanLoot(state.Mobile, pvSrc.ReadBoolean()); - } - - public static void PartyMessage_Accept(NetState state, PacketReader pvSrc) - { - PartyCommands.Handler?.OnAccept(state.Mobile, World.FindMobile(pvSrc.ReadUInt32())); - } - - public static void PartyMessage_Decline(NetState state, PacketReader pvSrc) - { - PartyCommands.Handler?.OnDecline(state.Mobile, World.FindMobile(pvSrc.ReadUInt32())); - } - - public static void StunRequest(NetState state, PacketReader pvSrc) - { - EventSink.InvokeStunRequest(state.Mobile); - } - - public static void DisarmRequest(NetState state, PacketReader pvSrc) - { - EventSink.InvokeDisarmRequest(state.Mobile); - } - - public static void StatLockChange(NetState state, PacketReader pvSrc) - { - int stat = pvSrc.ReadByte(); - int lockValue = pvSrc.ReadByte(); - - if (lockValue > 2) lockValue = 0; - - var m = state.Mobile; - - if (m != null) - switch (stat) - { - case 0: - m.StrLock = (StatLockType)lockValue; - break; - case 1: - m.DexLock = (StatLockType)lockValue; - break; - case 2: - m.IntLock = (StatLockType)lockValue; - break; - } - } - - public static void ScreenSize(NetState state, PacketReader pvSrc) - { - var width = pvSrc.ReadInt32(); - var unk = pvSrc.ReadInt32(); - } - - public static void ContextMenuResponse(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - if (from == null) return; - - var menu = from.ContextMenu; - - from.ContextMenu = null; - - if (menu != null && from == menu.From) - { - var entity = World.FindEntity(pvSrc.ReadUInt32()); - - if (entity != null && entity == menu.Target && from.CanSee(entity)) - { - Point3D p; - - if (entity is Mobile) - p = entity.Location; - else if (entity is Item item) - p = item.GetWorldLocation(); - else - return; - - int index = pvSrc.ReadUInt16(); - - if (index >= 0 && index < menu.Entries.Length) - { - var e = menu.Entries[index]; - - var range = e.Range; - - if (range == -1) - range = 18; - - if (e.Enabled && from.InRange(p, range)) - e.OnClick(); - } - } - } - } - - public static void ContextMenuRequest(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - var target = World.FindEntity(pvSrc.ReadUInt32()); - - if (from != null && target != null && from.Map == target.Map && from.CanSee(target)) - { - if (target is Mobile && !Utility.InUpdateRange(from.Location, target.Location)) - return; - - var item = target as Item; - - if (item != null && !Utility.InUpdateRange(from.Location, item.GetWorldLocation())) - return; - - if (!from.CheckContextMenuDisplay(target)) - return; - - var c = new ContextMenu(from, target); - - if (c.Entries.Length > 0) - { - if (item?.RootParent is Mobile mobile && mobile != from && mobile.AccessLevel >= from.AccessLevel) - for (var i = 0; i < c.Entries.Length; ++i) - if (!c.Entries[i].NonLocalUse) - c.Entries[i].Enabled = false; - - from.ContextMenu = c; - } - } - } - - public static void CloseStatus(NetState state, PacketReader pvSrc) - { - Serial serial = pvSrc.ReadUInt32(); - } - - public static void Language(NetState state, PacketReader pvSrc) - { - var lang = pvSrc.ReadString(4); - - if (state.Mobile != null) - state.Mobile.Language = lang; - } - - public static void AssistVersion(NetState state, PacketReader pvSrc) - { - var unk = pvSrc.ReadInt32(); - var av = pvSrc.ReadString(); - } - - public static void ClientVersion(NetState state, PacketReader pvSrc) - { - var version = state.Version = new CV(pvSrc.ReadString()); - - EventSink.InvokeClientVersionReceived(state, version); - } - - public static void ClientType(NetState state, PacketReader pvSrc) - { - pvSrc.ReadUInt16(); - - int type = pvSrc.ReadUInt16(); - var version = state.Version = new CV(pvSrc.ReadString()); - - EventSink.InvokeClientVersionReceived(state, version); - } - - public static void MobileQuery(NetState state, PacketReader pvSrc) - { - var from = state.Mobile; - - pvSrc.ReadInt32(); // 0xEDEDEDED - int type = pvSrc.ReadByte(); - var m = World.FindMobile(pvSrc.ReadUInt32()); - - if (m != null) - switch (type) - { - case 0x04: // Stats - { - m.OnStatsQuery(from); - break; - } - case 0x05: - { - m.OnSkillsQuery(from); - break; - } - default: - { - pvSrc.Trace(state); - break; - } - } - } - - public static void PlayCharacter(NetState state, PacketReader pvSrc) - { - pvSrc.ReadInt32(); // 0xEDEDEDED - - var name = pvSrc.ReadString(30); - - pvSrc.Seek(2, SeekOrigin.Current); - - var flags = pvSrc.ReadInt32(); - - pvSrc.Seek(24, SeekOrigin.Current); - - var charSlot = pvSrc.ReadInt32(); - var clientIP = pvSrc.ReadInt32(); - - var a = state.Account; - - if (a == null || charSlot < 0 || charSlot >= a.Length) - { - state.Dispose(); - } - else - { - var m = a[charSlot]; - - // Check if anyone is using this account - for (var i = 0; i < a.Length; ++i) - { - var check = a[i]; - - if (check != null && check.Map != Map.Internal && check != m) - { - Console.WriteLine("Login: {0}: Account in use", state); - state.Send(new PopupMessage(PMMessage.CharInWorld)); - return; - } - } - - if (m == null) - { - state.Dispose(); - return; - } - - m.NetState?.Dispose(); - - // TODO: Make this wait one tick so we don't have to call it unnecessarily - NetState.ProcessDisposedQueue(); - - state.Send(new ClientVersionReq()); - - state.BlockAllPackets = true; - - state.Flags = (ClientFlags)flags; - - state.Mobile = m; - m.NetState = state; - - new LoginTimer(state, m).Start(); - } - } - - public static void ShowPublicHouseContent(NetState state, PacketReader pvSrc) - { - var showPublicHouseContent = pvSrc.ReadBoolean(); - } - - public static void DoLogin(NetState state, Mobile m) - { - state.Send(new LoginConfirm(m)); - - if (m.Map != null) - state.Send(new MapChange(m.Map)); - - if (!Core.SE && state.ProtocolChanges < ProtocolChanges.Version6000) - state.Send(new MapPatches()); - - state.Send(SeasonChange.Instantiate(m.GetSeason(), true)); - - state.Send(SupportedFeatures.Instantiate(state)); - - state.Sequence = 0; - - if (state.NewMobileIncoming) - { - state.Send(new MobileUpdate(m)); - state.Send(new MobileUpdate(m)); - - m.CheckLightLevels(true); - - state.Send(new MobileUpdate(m)); - - state.Send(new MobileIncoming(m, m)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); - state.Send(Network.SetWarMode.Instantiate(m.Warmode)); - - m.SendEverything(); - - state.Send(SupportedFeatures.Instantiate(state)); - state.Send(new MobileUpdate(m)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); - state.Send(Network.SetWarMode.Instantiate(m.Warmode)); - state.Send(new MobileIncoming(m, m)); - } - else if (state.StygianAbyss) - { - state.Send(new MobileUpdate(m)); - state.Send(new MobileUpdate(m)); - - m.CheckLightLevels(true); - - state.Send(new MobileUpdate(m)); - - state.Send(new MobileIncomingSA(m, m)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); - state.Send(Network.SetWarMode.Instantiate(m.Warmode)); - - m.SendEverything(); - - state.Send(SupportedFeatures.Instantiate(state)); - state.Send(new MobileUpdate(m)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); - state.Send(Network.SetWarMode.Instantiate(m.Warmode)); - state.Send(new MobileIncomingSA(m, m)); - } - else - { - state.Send(new MobileUpdateOld(m)); - state.Send(new MobileUpdateOld(m)); - - m.CheckLightLevels(true); - - state.Send(new MobileUpdateOld(m)); - - state.Send(new MobileIncomingOld(m, m)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); - state.Send(Network.SetWarMode.Instantiate(m.Warmode)); - - m.SendEverything(); - - state.Send(SupportedFeatures.Instantiate(state)); - state.Send(new MobileUpdateOld(m)); - // state.Send( new MobileAttributes( m ) ); - state.Send(new MobileStatus(m, m)); - state.Send(Network.SetWarMode.Instantiate(m.Warmode)); - state.Send(new MobileIncomingOld(m, m)); - } - - state.Send(LoginComplete.Instance); - state.Send(new CurrentTime()); - state.Send(SeasonChange.Instantiate(m.GetSeason(), true)); - if (m.Map != null) - state.Send(new MapChange(m.Map)); - - EventSink.InvokeLogin(m); - - m.ClearFastwalkStack(); - } - - public static void CreateCharacter(NetState state, PacketReader pvSrc) - { - var unk1 = pvSrc.ReadInt32(); - var unk2 = pvSrc.ReadInt32(); - int unk3 = pvSrc.ReadByte(); - var name = pvSrc.ReadString(30); - - pvSrc.Seek(2, SeekOrigin.Current); - var flags = pvSrc.ReadInt32(); - pvSrc.Seek(8, SeekOrigin.Current); - int prof = pvSrc.ReadByte(); - pvSrc.Seek(15, SeekOrigin.Current); - - int genderRace = pvSrc.ReadByte(); - - int str = pvSrc.ReadByte(); - int dex = pvSrc.ReadByte(); - int intl = pvSrc.ReadByte(); - int is1 = pvSrc.ReadByte(); - int vs1 = pvSrc.ReadByte(); - int is2 = pvSrc.ReadByte(); - int vs2 = pvSrc.ReadByte(); - int is3 = pvSrc.ReadByte(); - int vs3 = pvSrc.ReadByte(); - int hue = pvSrc.ReadUInt16(); - int hairVal = pvSrc.ReadInt16(); - int hairHue = pvSrc.ReadInt16(); - int hairValf = pvSrc.ReadInt16(); - int hairHuef = pvSrc.ReadInt16(); - pvSrc.ReadByte(); - int cityIndex = pvSrc.ReadByte(); - var charSlot = pvSrc.ReadInt32(); - var clientIP = pvSrc.ReadInt32(); - int shirtHue = pvSrc.ReadInt16(); - int pantsHue = pvSrc.ReadInt16(); - - /* - Pre-7.0.0.0: - 0x00, 0x01 -> Human Male, Human Female - 0x02, 0x03 -> Elf Male, Elf Female - - Post-7.0.0.0: - 0x00, 0x01 - 0x02, 0x03 -> Human Male, Human Female - 0x04, 0x05 -> Elf Male, Elf Female - 0x05, 0x06 -> Gargoyle Male, Gargoyle Female - */ - - var female = genderRace % 2 != 0; - - Race race; - - if (state.StygianAbyss) - { - var raceID = (byte)(genderRace < 4 ? 0 : genderRace / 2 - 1); - race = Race.Races[raceID]; - } - else - { - race = Race.Races[(byte)(genderRace / 2)]; - } - - race ??= Race.DefaultRace; - - var info = state.CityInfo; - var a = state.Account; - - if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length) - { - state.Dispose(); - } - else - { - // Check if anyone is using this account - for (var i = 0; i < a.Length; ++i) - { - var check = a[i]; - - if (check != null && check.Map != Map.Internal) - { - Console.WriteLine("Login: {0}: Account in use", state); - state.Send(new PopupMessage(PMMessage.CharInWorld)); - return; - } - } - - state.Flags = (ClientFlags)flags; - - var args = new CharacterCreatedEventArgs( - state, a, - name, female, hue, - str, dex, intl, - info[cityIndex], - new[] - { - new SkillNameValue((SkillName)is1, vs1), - new SkillNameValue((SkillName)is2, vs2), - new SkillNameValue((SkillName)is3, vs3) - }, - shirtHue, pantsHue, - hairVal, hairHue, - hairValf, hairHuef, - prof, - race); - - state.Send(new ClientVersionReq()); - - state.BlockAllPackets = true; - - EventSink.InvokeCharacterCreated(args); - - var m = args.Mobile; - - if (m != null) - { - state.Mobile = m; - m.NetState = state; - new LoginTimer(state, m).Start(); - } - else - { - state.BlockAllPackets = false; - state.Dispose(); - } - } - } - - public static void CreateCharacter70160(NetState state, PacketReader pvSrc) - { - var unk1 = pvSrc.ReadInt32(); - var unk2 = pvSrc.ReadInt32(); - int unk3 = pvSrc.ReadByte(); - var name = pvSrc.ReadString(30); - - pvSrc.Seek(2, SeekOrigin.Current); - var flags = pvSrc.ReadInt32(); - pvSrc.Seek(8, SeekOrigin.Current); - int prof = pvSrc.ReadByte(); - pvSrc.Seek(15, SeekOrigin.Current); - - int genderRace = pvSrc.ReadByte(); - - int str = pvSrc.ReadByte(); - int dex = pvSrc.ReadByte(); - int intl = pvSrc.ReadByte(); - int is1 = pvSrc.ReadByte(); - int vs1 = pvSrc.ReadByte(); - int is2 = pvSrc.ReadByte(); - int vs2 = pvSrc.ReadByte(); - int is3 = pvSrc.ReadByte(); - int vs3 = pvSrc.ReadByte(); - int is4 = pvSrc.ReadByte(); - int vs4 = pvSrc.ReadByte(); - - int hue = pvSrc.ReadUInt16(); - int hairVal = pvSrc.ReadInt16(); - int hairHue = pvSrc.ReadInt16(); - int hairValf = pvSrc.ReadInt16(); - int hairHuef = pvSrc.ReadInt16(); - pvSrc.ReadByte(); - int cityIndex = pvSrc.ReadByte(); - var charSlot = pvSrc.ReadInt32(); - var clientIP = pvSrc.ReadInt32(); - int shirtHue = pvSrc.ReadInt16(); - int pantsHue = pvSrc.ReadInt16(); - - /* - 0x00, 0x01 - 0x02, 0x03 -> Human Male, Human Female - 0x04, 0x05 -> Elf Male, Elf Female - 0x05, 0x06 -> Gargoyle Male, Gargoyle Female - */ - - var female = genderRace % 2 != 0; - - Race race; - - var raceID = (byte)(genderRace < 4 ? 0 : genderRace / 2 - 1); - race = Race.Races[raceID] ?? Race.DefaultRace; - - var info = state.CityInfo; - var a = state.Account; - - if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length) - { - state.Dispose(); - } - else - { - // Check if anyone is using this account - for (var i = 0; i < a.Length; ++i) - { - var check = a[i]; - - if (check != null && check.Map != Map.Internal) - { - Console.WriteLine("Login: {0}: Account in use", state); - state.Send(new PopupMessage(PMMessage.CharInWorld)); - return; - } - } - - state.Flags = (ClientFlags)flags; - - var args = new CharacterCreatedEventArgs( - state, a, - name, female, hue, - str, dex, intl, - info[cityIndex], - new[] - { - new SkillNameValue((SkillName)is1, vs1), - new SkillNameValue((SkillName)is2, vs2), - new SkillNameValue((SkillName)is3, vs3), - new SkillNameValue((SkillName)is4, vs4) - }, - shirtHue, pantsHue, - hairVal, hairHue, - hairValf, hairHuef, - prof, - race); - - state.Send(new ClientVersionReq()); - - state.BlockAllPackets = true; - - EventSink.InvokeCharacterCreated(args); - - var m = args.Mobile; - - if (m != null) - { - state.Mobile = m; - m.NetState = state; - new LoginTimer(state, m).Start(); - } - else - { - state.BlockAllPackets = false; - state.Dispose(); - } - } - } - - private static int GenerateAuthID(NetState state) - { - if (m_AuthIDWindow.Count == m_AuthIDWindowSize) - { - var oldestID = 0; - var oldest = DateTime.MaxValue; - - foreach (var kvp in m_AuthIDWindow) - if (kvp.Value.Age < oldest) - { - oldestID = kvp.Key; - oldest = kvp.Value.Age; - } - - m_AuthIDWindow.Remove(oldestID); - } - - int authID; - - do - { - authID = Utility.Random(1, int.MaxValue - 1); - - if (Utility.RandomBool()) - authID |= 1 << 31; - } while (m_AuthIDWindow.ContainsKey(authID)); - - m_AuthIDWindow[authID] = new AuthIDPersistence(state.Version); - - return authID; - } - - public static void GameLogin(NetState state, PacketReader pvSrc) - { - if (state.SentFirstPacket) - { - state.Dispose(); - return; - } - - state.SentFirstPacket = true; - - var authID = pvSrc.ReadInt32(); - - if (m_AuthIDWindow.TryGetValue(authID, out var ap)) - { - m_AuthIDWindow.Remove(authID); - - state.Version = ap.Version; - } - else if (ClientVerification) - { - Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); - state.Dispose(); - return; - } - - if (state.m_AuthID != 0 && authID != state.m_AuthID) - { - Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); - state.Dispose(); - return; - } - - if (state.m_AuthID == 0 && authID != state.m_Seed) - { - Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); - state.Dispose(); - return; - } - - var username = pvSrc.ReadString(30); - var password = pvSrc.ReadString(30); - - var e = new GameLoginEventArgs(state, username, password); - - EventSink.InvokeGameLogin(e); - - if (e.Accepted) - { - state.CityInfo = e.CityInfo; - state.CompressionEnabled = true; - - state.Send(SupportedFeatures.Instantiate(state)); - - if (state.NewCharacterList) - state.Send(new CharacterList(state.Account, state.CityInfo)); - else - state.Send(new CharacterListOld(state.Account, state.CityInfo)); - } - else - { - state.Dispose(); - } - } - - public static void PlayServer(NetState state, PacketReader pvSrc) - { - int index = pvSrc.ReadInt16(); - var info = state.ServerInfo; - var a = state.Account; - - if (info == null || a == null || index < 0 || index >= info.Length) - { - state.Dispose(); - } - else - { - var si = info[index]; - - state.m_AuthID = PlayServerAck.m_AuthID = GenerateAuthID(state); - - state.SentFirstPacket = false; - state.Send(new PlayServerAck(si)); - } - } - - public static void LoginServerSeed(NetState state, PacketReader pvSrc) - { - state.m_Seed = pvSrc.ReadInt32(); - state.Seeded = true; - - if (state.m_Seed == 0) - { - Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); - state.Dispose(); - return; - } - - var clientMaj = pvSrc.ReadInt32(); - var clientMin = pvSrc.ReadInt32(); - var clientRev = pvSrc.ReadInt32(); - var clientPat = pvSrc.ReadInt32(); - - state.Version = new ClientVersion(clientMaj, clientMin, clientRev, clientPat); - } - - public static void CrashReport(NetState state, PacketReader pvSrc) - { - var clientMaj = pvSrc.ReadByte(); - var clientMin = pvSrc.ReadByte(); - var clientRev = pvSrc.ReadByte(); - var clientPat = pvSrc.ReadByte(); - - var x = pvSrc.ReadUInt16(); - var y = pvSrc.ReadUInt16(); - var z = pvSrc.ReadSByte(); - var map = pvSrc.ReadByte(); - - var account = pvSrc.ReadString(32); - var character = pvSrc.ReadString(32); - var ip = pvSrc.ReadString(15); - - var unk1 = pvSrc.ReadInt32(); - var exception = pvSrc.ReadInt32(); - - var process = pvSrc.ReadString(100); - var report = pvSrc.ReadString(100); - - pvSrc.ReadByte(); // 0x00 - - var offset = pvSrc.ReadInt32(); - - int count = pvSrc.ReadByte(); - - for (var i = 0; i < count; i++) - { - var address = pvSrc.ReadInt32(); - } - } - - public static void AccountLogin(NetState state, PacketReader pvSrc) - { - if (state.SentFirstPacket) - { - state.Dispose(); - return; - } - - state.SentFirstPacket = true; - - var username = pvSrc.ReadString(30); - var password = pvSrc.ReadString(30); - - var e = new AccountLoginEventArgs(state, username, password); - - EventSink.InvokeAccountLogin(e); - - if (e.Accepted) - AccountLogin_ReplyAck(state); - else - AccountLogin_ReplyRej(state, e.RejectReason); - } - - public static void AccountLogin_ReplyAck(NetState state) - { - var e = new ServerListEventArgs(state, state.Account); - - EventSink.InvokeServerList(e); - - if (e.Rejected) - { - state.Account = null; - AccountLogin_ReplyRej(state, ALRReason.BadComm); - } - else - { - var info = e.Servers.ToArray(); - - state.ServerInfo = info; - - state.Send(new AccountLoginAck(info)); - } - } - - public static void AccountLogin_ReplyRej(NetState state, ALRReason reason) - { - state.Send(new AccountLoginRej(reason)); - state.Dispose(); - } - - public static void EquipMacro(NetState ns, PacketReader pvSrc) - { - int count = pvSrc.ReadByte(); - var serialList = new List(count); - for (var i = 0; i < count; ++i) - serialList.Add(pvSrc.ReadUInt32()); - - EventSink.InvokeEquipMacro(ns.Mobile, serialList); - } - - public static void UnequipMacro(NetState ns, PacketReader pvSrc) - { - int count = pvSrc.ReadByte(); - var layers = new List(count); - for (var i = 0; i < count; ++i) - layers.Add((Layer)pvSrc.ReadUInt16()); - - EventSink.InvokeUnequipMacro(ns.Mobile, layers); - } - - public static void TargetedSpell(NetState ns, PacketReader pvSrc) - { - var spellId = (short)(pvSrc.ReadInt16() - 1); // zero based; - - EventSink.InvokeTargetedSpell(ns.Mobile, World.FindEntity(pvSrc.ReadUInt32()), spellId); - } - - public static void TargetedSkillUse(NetState ns, PacketReader pvSrc) - { - var skillId = pvSrc.ReadInt16(); - - EventSink.InvokeTargetedSkillUse(ns.Mobile, World.FindEntity(pvSrc.ReadUInt32()), skillId); - } - - public static void TargetByResourceMacro(NetState ns, PacketReader pvSrc) - { - Serial serial = pvSrc.ReadUInt32(); - - if (serial.IsItem) EventSink.InvokeTargetByResourceMacro(ns.Mobile, World.FindItem(serial), pvSrc.ReadInt16()); - } - - private class LoginTimer : Timer - { - private readonly Mobile m_Mobile; - private readonly NetState m_State; - - public LoginTimer(NetState state, Mobile m) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0)) - { - m_State = state; - m_Mobile = m; - } - - protected override void OnTick() - { - if (m_State == null) - { - Stop(); - return; - } - - if (m_State.Version != null) - { - m_State.BlockAllPackets = false; - DoLogin(m_State, m_Mobile); - Stop(); - } - } - } - - internal struct AuthIDPersistence - { - public DateTime Age; - public ClientVersion Version; - - public AuthIDPersistence(ClientVersion v) - { - Age = DateTime.UtcNow; - Version = v; - } - } - } -} +/*************************************************************************** + * PacketHandlers.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using Server.ContextMenus; +using Server.Diagnostics; +using Server.Gumps; +using Server.Items; +using Server.Targeting; +using CV = Server.ClientVersion; + +namespace Server.Network +{ + [Flags] + public enum MessageType + { + Regular = 0x00, + System = 0x01, + Emote = 0x02, + Label = 0x06, + Focus = 0x07, + Whisper = 0x08, + Yell = 0x09, + Spell = 0x0A, + + Guild = 0x0D, + Alliance = 0x0E, + Command = 0x0F, + + Encoded = 0xC0 + } + + public static class PacketHandlers + { + public delegate void PlayCharCallback(NetState state, bool val); + + private const int BadFood = unchecked((int)0xBAADF00D); + private const int BadUOTD = unchecked((int)0xFFCEFFCE); + + private const int m_AuthIDWindowSize = 128; + private static readonly PacketHandler[] m_6017Handlers = new PacketHandler[0x100]; + + private static readonly PacketHandler[] m_ExtendedHandlersLow = new PacketHandler[0x100]; + private static readonly Dictionary m_ExtendedHandlersHigh = new Dictionary(); + + private static readonly EncodedPacketHandler[] m_EncodedHandlersLow = new EncodedPacketHandler[0x100]; + + private static readonly Dictionary m_EncodedHandlersHigh = + new Dictionary(); + + private static readonly int[] m_EmptyInts = Array.Empty(); + + private static readonly KeywordList m_KeywordList = new KeywordList(); + + private static readonly Dictionary m_AuthIDWindow = + new Dictionary(m_AuthIDWindowSize); + + private static readonly MemoryPool _memoryPool = SlabMemoryPoolFactory.Create(); + + static PacketHandlers() + { + Register(0x00, 104, false, CreateCharacter); + Register(0x01, 5, false, Disconnect); + Register(0x02, 7, true, MovementReq); + Register(0x03, 0, true, AsciiSpeech); + Register(0x05, 5, true, AttackReq); + Register(0x06, 5, true, UseReq); + Register(0x07, 7, true, LiftReq); + Register(0x08, 14, true, DropReq); + Register(0x09, 5, true, LookReq); + Register(0x12, 0, true, TextCommand); + Register(0x13, 10, true, EquipReq); + Register(0x22, 3, true, Resynchronize); + Register(0x2C, 2, true, DeathStatusResponse); + Register(0x34, 10, true, MobileQuery); + Register(0x3A, 0, true, ChangeSkillLock); + Register(0x3B, 0, true, VendorBuyReply); + Register(0x5D, 73, false, PlayCharacter); + Register(0x6C, 19, true, TargetResponse); + Register(0x6F, 0, true, SecureTrade); + Register(0x72, 5, true, SetWarMode); + Register(0x73, 2, false, PingReq); + Register(0x75, 35, true, RenameRequest); + Register(0x7D, 13, true, MenuResponse); + Register(0x80, 62, false, AccountLogin); + Register(0x83, 39, false, DeleteCharacter); + Register(0x91, 65, false, GameLogin); + Register(0x95, 9, true, HuePickerResponse); + Register(0x98, 0, true, MobileNameRequest); + Register(0x9A, 0, true, AsciiPromptResponse); + Register(0x9B, 258, true, HelpRequest); + Register(0x9F, 0, true, VendorSellReply); + Register(0xA0, 3, false, PlayServer); + Register(0xA4, 149, false, SystemInfo); + Register(0xA7, 4, true, RequestScrollWindow); + Register(0xAD, 0, true, UnicodeSpeech); + Register(0xB1, 0, true, DisplayGumpResponse); + Register(0xB5, 64, true, ChatRequest); + Register(0xB6, 9, true, ObjectHelpRequest); + Register(0xB8, 0, true, ProfileReq); + Register(0xBB, 9, false, AccountID); + Register(0xBD, 0, false, ClientVersion); + Register(0xBE, 0, true, AssistVersion); + Register(0xBF, 0, true, ExtendedCommand); + Register(0xC2, 0, true, UnicodePromptResponse); + Register(0xC8, 2, true, SetUpdateRange); + Register(0xCF, 0, false, AccountLogin); + Register(0xD0, 0, true, ConfigurationFile); + Register(0xD1, 2, true, LogoutReq); + Register(0xD6, 0, true, BatchQueryProperties); + Register(0xD7, 0, true, EncodedCommand); + Register(0xE1, 0, false, ClientType); + Register(0xEF, 21, false, LoginServerSeed); + Register(0xEC, 0, false, EquipMacro); + Register(0xED, 0, false, UnequipMacro); + Register(0xF4, 0, false, CrashReport); + Register(0xF8, 106, false, CreateCharacter70160); + Register(0xFB, 2, false, ShowPublicHouseContent); + + Register6017(0x08, 15, true, DropReq6017); + + RegisterExtended(0x05, false, ScreenSize); + RegisterExtended(0x06, true, PartyMessage); + RegisterExtended(0x07, true, QuestArrow); + RegisterExtended(0x09, true, DisarmRequest); + RegisterExtended(0x0A, true, StunRequest); + RegisterExtended(0x0B, false, Language); + RegisterExtended(0x0C, true, CloseStatus); + RegisterExtended(0x0E, true, Animate); + RegisterExtended(0x0F, false, Empty); // What's this? + RegisterExtended(0x10, true, QueryProperties); + RegisterExtended(0x13, true, ContextMenuRequest); + RegisterExtended(0x15, true, ContextMenuResponse); + RegisterExtended(0x1A, true, StatLockChange); + RegisterExtended(0x1C, true, CastSpell); + RegisterExtended(0x24, false, UnhandledBF); + RegisterExtended(0x2C, true, BandageTarget); + RegisterExtended(0x2D, true, TargetedSpell); + RegisterExtended(0x2E, true, TargetedSkillUse); + RegisterExtended(0x30, true, TargetByResourceMacro); + RegisterExtended(0x32, true, ToggleFlying); + + RegisterEncoded(0x19, true, SetAbility); + RegisterEncoded(0x28, true, GuildGumpRequest); + + RegisterEncoded(0x32, true, QuestGumpRequest); + } + + public static PlayCharCallback ThirdPartyAuthCallback { get; set; } + public static PlayCharCallback ThirdPartyHackedCallback { get; set; } + + public static PacketHandler[] Handlers { get; } = new PacketHandler[0x100]; + + public static bool SingleClickProps { get; set; } + + // TODO: Change to outside configuration + public static int[] ValidAnimations { get; set; } = + { + 6, 21, 32, 33, + 100, 101, 102, + 103, 104, 105, + 106, 107, 108, + 109, 110, 111, + 112, 113, 114, + 115, 116, 117, + 118, 119, 120, + 121, 123, 124, + 125, 126, 127, + 128 + }; + + public static bool ClientVerification { get; set; } = true; + + public static void Register(int packetID, int length, bool ingame, OnPacketReceive onReceive) + { + Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive); + m_6017Handlers[packetID] ??= new PacketHandler(packetID, length, ingame, onReceive); + } + + public static PacketHandler GetHandler(int packetID) => Handlers[packetID]; + + public static void Register6017(int packetID, int length, bool ingame, OnPacketReceive onReceive) + { + m_6017Handlers[packetID] = new PacketHandler(packetID, length, ingame, onReceive); + } + + public static PacketHandler Get6017Handler(int packetID) => m_6017Handlers[packetID]; + + public static void RegisterExtended(int packetID, bool ingame, OnPacketReceive onReceive) + { + if (packetID >= 0 && packetID < 0x100) + m_ExtendedHandlersLow[packetID] = new PacketHandler(packetID, 0, ingame, onReceive); + else + m_ExtendedHandlersHigh[packetID] = new PacketHandler(packetID, 0, ingame, onReceive); + } + + public static PacketHandler GetExtendedHandler(int packetID) + { + if (packetID >= 0 && packetID < 0x100) + return m_ExtendedHandlersLow[packetID]; + + m_ExtendedHandlersHigh.TryGetValue(packetID, out var handler); + return handler; + } + + public static void RemoveExtendedHandler(int packetID) + { + if (packetID >= 0 && packetID < 0x100) + m_ExtendedHandlersLow[packetID] = null; + else + m_ExtendedHandlersHigh.Remove(packetID); + } + + public static void RegisterEncoded(int packetID, bool ingame, OnEncodedPacketReceive onReceive) + { + if (packetID >= 0 && packetID < 0x100) + m_EncodedHandlersLow[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive); + else + m_EncodedHandlersHigh[packetID] = new EncodedPacketHandler(packetID, ingame, onReceive); + } + + public static EncodedPacketHandler GetEncodedHandler(int packetID) + { + if (packetID >= 0 && packetID < 0x100) + return m_EncodedHandlersLow[packetID]; + + m_EncodedHandlersHigh.TryGetValue(packetID, out var handler); + return handler; + } + + public static void RemoveEncodedHandler(int packetID) + { + if (packetID >= 0 && packetID < 0x100) + m_EncodedHandlersLow[packetID] = null; + else + m_EncodedHandlersHigh.Remove(packetID); + } + + public static void RegisterThrottler(int packetID, ThrottlePacketCallback t) + { + var ph = GetHandler(packetID); + + if (ph != null) + ph.ThrottleCallback = t; + + ph = Get6017Handler(packetID); + + if (ph != null) + ph.ThrottleCallback = t; + } + + public static int ProcessPacket(IMessagePumpService pump, NetState ns, in ReadOnlySequence seq) + { + var r = new PacketReader(seq); + + if (!r.TryReadByte(out var packetId)) + { + ns.Dispose(); + return -1; + } + + if (!ns.Seeded) + { + if (packetId == 0xEF) + { + // new packet in client 6.0.5.0 replaces the traditional seed method with a seed packet + // 0xEF = 239 = multicast IP, so this should never appear in a normal seed. So this is backwards compatible with older clients. + ns.Seeded = true; + } + else + { + var seed = (packetId << 24) | (r.ReadByte() << 16) | (r.ReadByte() << 8) | r.ReadByte(); + + if (seed == 0) + { + Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", ns); + ns.Dispose(); + return -1; + } + + ns.m_Seed = seed; + ns.Seeded = true; + + return 4; + } + } + + if (ns.CheckEncrypted(packetId)) + { + ns.Dispose(); + return -1; + } + + // Get Handlers + var handler = ns.GetHandler(packetId); + + if (handler == null) + { + r.Trace(ns); + return -1; + } + + var packetLength = handler.Length; + if (handler.Length <= 0 && r.Length >= 3) + { + packetLength = r.ReadUInt16(); + if (packetLength < 3) + { + ns.Dispose(); + return -1; + } + } + + if (r.Length < packetLength) + return 0; + + if (handler.Ingame && ns.Mobile?.Deleted != false) + { + Console.WriteLine( + "Client: {0}: Sent ingame packet (0x{1:X2}) without being attached to a valid mobile.", + ns, + packetId + ); + ns.Dispose(); + return -1; + } + + var throttled = handler.ThrottleCallback?.Invoke(ns) ?? TimeSpan.Zero; + + if (throttled > TimeSpan.Zero) + ns.ThrottledUntil = DateTime.UtcNow + throttled; + + var packet = seq.Slice(r.Position); + var length = (int)packet.Length; + var memOwner = _memoryPool.Rent(length); + + // TODO: This is slow, find another way + packet.CopyTo(memOwner.Memory.Span); + + pump.QueueWork(ns, memOwner, length, handler.OnReceive); + + return packetLength; + } + + private static void UnhandledBF(NetState state, PacketReader pvSrc) + { + } + + public static void Empty(NetState state, PacketReader pvSrc) + { + } + + public static void SetAbility(NetState state, IEntity e, EncodedReader reader) + { + EventSink.InvokeSetAbility(state.Mobile, reader.ReadInt32()); + } + + public static void GuildGumpRequest(NetState state, IEntity e, EncodedReader reader) + { + EventSink.InvokeGuildGumpRequest(state.Mobile); + } + + public static void QuestGumpRequest(NetState state, IEntity e, EncodedReader reader) + { + EventSink.InvokeQuestGumpRequest(state.Mobile); + } + + public static void EncodedCommand(NetState state, PacketReader pvSrc) + { + var e = World.FindEntity(pvSrc.ReadUInt32()); + int packetId = pvSrc.ReadUInt16(); + + var ph = GetEncodedHandler(packetId); + + if (ph != null) + { + if (ph.Ingame && state.Mobile == null) + { + Console.WriteLine( + "Client: {0}: Sent ingame packet (0xD7x{1:X2}) before having been attached to a mobile", + state, + packetId + ); + state.Dispose(); + } + else if (ph.Ingame && state.Mobile.Deleted) + { + state.Dispose(); + } + else + { + ph.OnReceive(state, e, new EncodedReader(pvSrc)); + } + } + else + { + pvSrc.Trace(state); + } + } + + public static void RenameRequest(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + var targ = World.FindMobile(pvSrc.ReadUInt32()); + + if (targ != null) + EventSink.InvokeRenameRequest(from, targ, pvSrc.ReadStringSafe()); + } + + public static void ChatRequest(NetState state, PacketReader pvSrc) + { + EventSink.InvokeChatRequest(state.Mobile); + } + + public static void SecureTrade(NetState state, PacketReader pvSrc) + { + switch (pvSrc.ReadByte()) + { + case 1: // Cancel + { + Serial serial = pvSrc.ReadUInt32(); + + if (World.FindItem(serial) is SecureTradeContainer cont && cont.Trade != null && + (cont.Trade.From.Mobile == state.Mobile || cont.Trade.To.Mobile == state.Mobile)) + cont.Trade.Cancel(); + + break; + } + case 2: // Check + { + Serial serial = pvSrc.ReadUInt32(); + + if (World.FindItem(serial) is SecureTradeContainer cont) + { + var trade = cont.Trade; + + var value = pvSrc.ReadInt32() != 0; + + if (trade != null && trade.From.Mobile == state.Mobile) + { + trade.From.Accepted = value; + trade.Update(); + } + else if (trade != null && trade.To.Mobile == state.Mobile) + { + trade.To.Accepted = value; + trade.Update(); + } + } + + break; + } + case 3: // Update Gold + { + Serial serial = pvSrc.ReadUInt32(); + + if (World.FindItem(serial) is SecureTradeContainer cont) + { + var gold = pvSrc.ReadInt32(); + var plat = pvSrc.ReadInt32(); + + var trade = cont.Trade; + + if (trade != null) + { + if (trade.From.Mobile == state.Mobile) + { + trade.From.Gold = gold; + trade.From.Plat = plat; + trade.UpdateFromCurrency(); + } + else if (trade.To.Mobile == state.Mobile) + { + trade.To.Gold = gold; + trade.To.Plat = plat; + trade.UpdateToCurrency(); + } + } + } + } + break; + } + } + + public static void VendorBuyReply(NetState state, PacketReader pvSrc) + { + var vendor = World.FindMobile(pvSrc.ReadUInt32()); + var flag = pvSrc.ReadByte(); + + if (vendor == null) return; + + if (vendor.Deleted || !Utility.RangeCheck(vendor.Location, state.Mobile.Location, 10)) + { + state.Send(new EndVendorBuy(vendor)); + return; + } + + if (flag == 0x02) + { + var msgSize = (int)pvSrc.Remaining; + + if (msgSize / 7 > 100) + return; + + var buyList = new List(msgSize / 7); + while (msgSize > 0) + { + var layer = pvSrc.ReadByte(); + Serial serial = pvSrc.ReadUInt32(); + int amount = pvSrc.ReadInt16(); + + buyList.Add(new BuyItemResponse(serial, amount)); + msgSize -= 7; + } + + if (buyList.Count > 0 && vendor is IVendor v && v.OnBuyItems(state.Mobile, buyList)) + state.Send(new EndVendorBuy(vendor)); + } + else + { + state.Send(new EndVendorBuy(vendor)); + } + } + + public static void VendorSellReply(NetState state, PacketReader pvSrc) + { + Serial serial = pvSrc.ReadUInt32(); + var vendor = World.FindMobile(serial); + + if (vendor == null) return; + + if (vendor.Deleted || !Utility.RangeCheck(vendor.Location, state.Mobile.Location, 10)) + { + state.Send(new EndVendorSell(vendor)); + return; + } + + int count = pvSrc.ReadUInt16(); + + if (count >= 100 || pvSrc.Remaining != count * 6) + return; + + var sellList = new List(count); + + for (var i = 0; i < count; i++) + { + var item = World.FindItem(pvSrc.ReadUInt32()); + int amount = pvSrc.ReadInt16(); + + if (item != null && amount > 0) + sellList.Add(new SellItemResponse(item, amount)); + } + + if (sellList.Count > 0 && vendor is IVendor v && v.OnSellItems(state.Mobile, sellList)) + state.Send(new EndVendorSell(vendor)); + } + + public static void DeleteCharacter(NetState state, PacketReader pvSrc) + { + pvSrc.Seek(30, SeekOrigin.Current); + var index = pvSrc.ReadInt32(); + + EventSink.InvokeDeleteRequest(state, index); + } + + public static void DeathStatusResponse(NetState state, PacketReader pvSrc) + { + // Ignored + } + + public static void ObjectHelpRequest(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + Serial serial = pvSrc.ReadUInt32(); + int unk = pvSrc.ReadByte(); + var lang = pvSrc.ReadString(3); + + if (serial.IsItem) + { + var item = World.FindItem(serial); + + if (item != null && from.Map == item.Map && Utility.InUpdateRange(item.GetWorldLocation(), from.Location) && + from.CanSee(item)) + item.OnHelpRequest(from); + } + else if (serial.IsMobile) + { + var m = World.FindMobile(serial); + + if (m != null && from.Map == m.Map && Utility.InUpdateRange(m.Location, from.Location) && from.CanSee(m)) + m.OnHelpRequest(m); + } + } + + public static void MobileNameRequest(NetState state, PacketReader pvSrc) + { + var m = World.FindMobile(pvSrc.ReadUInt32()); + + if (m != null && Utility.InUpdateRange(state.Mobile, m) && state.Mobile.CanSee(m)) + state.Send(new MobileName(m)); + } + + public static void RequestScrollWindow(NetState state, PacketReader pvSrc) + { + int lastTip = pvSrc.ReadInt16(); + int type = pvSrc.ReadByte(); + } + + public static void AttackReq(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + var m = World.FindMobile(pvSrc.ReadUInt32()); + + if (m != null) + from.Attack(m); + } + + public static void HuePickerResponse(NetState state, PacketReader pvSrc) + { + var serial = pvSrc.ReadUInt32(); + _ = pvSrc.ReadInt16(); // Item ID + var hue = pvSrc.ReadInt16() & 0x3FFF; + + hue = Utility.ClipDyedHue(hue); + + foreach (var huePicker in state.HuePickers) + if (huePicker.Serial == serial) + { + state.RemoveHuePicker(huePicker); + + huePicker.OnResponse(hue); + + break; + } + } + + public static void SystemInfo(NetState state, PacketReader pvSrc) + { + int v1 = pvSrc.ReadByte(); + int v2 = pvSrc.ReadUInt16(); + int v3 = pvSrc.ReadByte(); + var s1 = pvSrc.ReadString(32); + var s2 = pvSrc.ReadString(32); + var s3 = pvSrc.ReadString(32); + var s4 = pvSrc.ReadString(32); + int v4 = pvSrc.ReadUInt16(); + int v5 = pvSrc.ReadUInt16(); + var v6 = pvSrc.ReadInt32(); + var v7 = pvSrc.ReadInt32(); + var v8 = pvSrc.ReadInt32(); + } + + public static void AccountID(NetState state, PacketReader pvSrc) + { + } + + public static void TextCommand(NetState state, PacketReader pvSrc) + { + int type = pvSrc.ReadByte(); + var command = pvSrc.ReadString(); + + var m = state.Mobile; + + switch (type) + { + case 0xC7: // Animate + { + EventSink.InvokeAnimateRequest(m, command); + + break; + } + case 0x24: // Use skill + { + if (!int.TryParse(command.Split(' ')[0], out var skillIndex)) + break; + + Skills.UseSkill(m, skillIndex); + + break; + } + case 0x43: // Open spellbook + { + if (!int.TryParse(command, out var booktype)) + booktype = 1; + + EventSink.InvokeOpenSpellbookRequest(m, booktype); + + break; + } + case 0x27: // Cast spell from book + { + var split = command.Split(' '); + + if (split.Length > 0) + { + var spellID = Utility.ToInt32(split[0]) - 1; + var serial = split.Length > 1 ? Utility.ToUInt32(split[1]) : (uint)Serial.MinusOne; + + EventSink.InvokeCastSpellRequest(m, spellID, World.FindItem(serial)); + } + + break; + } + case 0x58: // Open door + { + EventSink.InvokeOpenDoorMacroUsed(m); + + break; + } + case 0x56: // Cast spell from macro + { + var spellID = Utility.ToInt32(command) - 1; + + EventSink.InvokeCastSpellRequest(m, spellID, null); + + break; + } + case 0xF4: // Invoke virtues from macro + { + var virtueID = Utility.ToInt32(command) - 1; + + EventSink.InvokeVirtueMacroRequest(m, virtueID); + + break; + } + case 0x2F: // Old scroll double click + { + /* + * This command is still sent for items 0xEF3 - 0xEF9 + * + * Command is one of three, depending on the item ID of the scroll: + * - [scroll serial] + * - [scroll serial] [target serial] + * - [scroll serial] [x] [y] [z] + */ + break; + } + default: + { + Console.WriteLine("Client: {0}: Unknown text-command type 0x{1:X2}: {2}", state, type, command); + break; + } + } + } + + public static void AsciiPromptResponse(NetState state, PacketReader pvSrc) + { + var serial = pvSrc.ReadUInt32(); + var prompt = pvSrc.ReadInt32(); + var type = pvSrc.ReadInt32(); + var text = pvSrc.ReadStringSafe(); + + if (text.Length > 128) + return; + + var from = state.Mobile; + var p = from.Prompt; + + if (p != null && p.Serial == serial && p.Serial == prompt) + { + from.Prompt = null; + + if (type == 0) + p.OnCancel(from); + else + p.OnResponse(from, text); + } + } + + public static void UnicodePromptResponse(NetState state, PacketReader pvSrc) + { + var serial = pvSrc.ReadUInt32(); + var prompt = pvSrc.ReadInt32(); + var type = pvSrc.ReadInt32(); + var lang = pvSrc.ReadString(4); + var text = pvSrc.ReadUnicodeStringLESafe(); + + if (text.Length > 128) + return; + + var from = state.Mobile; + var p = from.Prompt; + + if (p != null && p.Serial == serial && p.Serial == prompt) + { + from.Prompt = null; + + if (type == 0) + p.OnCancel(from); + else + p.OnResponse(from, text); + } + } + + public static void MenuResponse(NetState state, PacketReader pvSrc) + { + var serial = pvSrc.ReadUInt32(); + int menuID = pvSrc.ReadInt16(); // unused in our implementation + int index = pvSrc.ReadInt16(); + int itemID = pvSrc.ReadInt16(); + int hue = pvSrc.ReadInt16(); + + index -= 1; // convert from 1-based to 0-based + + foreach (var menu in state.Menus) + if (menu.Serial == serial) + { + state.RemoveMenu(menu); + + if (index >= 0 && index < menu.EntryLength) + menu.OnResponse(state, index); + else + menu.OnCancel(state); + + break; + } + } + + public static void ProfileReq(NetState state, PacketReader pvSrc) + { + int type = pvSrc.ReadByte(); + Serial serial = pvSrc.ReadUInt32(); + + var beholder = state.Mobile; + var beheld = World.FindMobile(serial); + + if (beheld == null) return; + + switch (type) + { + case 0x00: // display request + { + EventSink.InvokeProfileRequest(beholder, beheld); + + break; + } + case 0x01: // edit request + { + pvSrc.ReadInt16(); // Skip + int length = pvSrc.ReadUInt16(); + + if (length > 511) + return; + + var text = pvSrc.ReadUnicodeString(length); + + EventSink.InvokeChangeProfileRequest(beholder, beheld, text); + + break; + } + } + } + + public static void Disconnect(NetState state, PacketReader pvSrc) + { + var minusOne = pvSrc.ReadInt32(); + } + + public static void LiftReq(NetState state, PacketReader pvSrc) + { + Serial serial = pvSrc.ReadUInt32(); + int amount = pvSrc.ReadUInt16(); + var item = World.FindItem(serial); + + state.Mobile.Lift(item, amount, out var rejected, out var reject); + } + + public static void EquipReq(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + var item = from.Holding; + + var valid = item != null && item.HeldBy == from && item.Map == Map.Internal; + + from.Holding = null; + + if (!valid) return; + + pvSrc.Seek(5, SeekOrigin.Current); + var to = World.FindMobile(pvSrc.ReadUInt32()) ?? from; + + if (!to.AllowEquipFrom(from) || !to.EquipItem(item)) + item.Bounce(from); + + item.ClearBounce(); + } + + public static void DropReq(NetState state, PacketReader pvSrc) + { + pvSrc.ReadInt32(); // serial, ignored + int x = pvSrc.ReadInt16(); + int y = pvSrc.ReadInt16(); + int z = pvSrc.ReadSByte(); + Serial dest = pvSrc.ReadUInt32(); + + var loc = new Point3D(x, y, z); + + var from = state.Mobile; + + if (dest.IsMobile) + { + from.Drop(World.FindMobile(dest), loc); + } + else if (dest.IsItem) + { + var item = World.FindItem(dest); + + if (item is BaseMulti multi && multi.AllowsRelativeDrop) + { + loc.m_X += multi.X; + loc.m_Y += multi.Y; + from.Drop(loc); + } + else + { + from.Drop(item, loc); + } + } + else + { + from.Drop(loc); + } + } + + public static void DropReq6017(NetState state, PacketReader pvSrc) + { + pvSrc.ReadInt32(); // serial, ignored + int x = pvSrc.ReadInt16(); + int y = pvSrc.ReadInt16(); + int z = pvSrc.ReadSByte(); + pvSrc.ReadByte(); // Grid Location? + Serial dest = pvSrc.ReadUInt32(); + + var loc = new Point3D(x, y, z); + + var from = state.Mobile; + + if (dest.IsMobile) + { + from.Drop(World.FindMobile(dest), loc); + } + else if (dest.IsItem) + { + var item = World.FindItem(dest); + + if (item is BaseMulti multi && multi.AllowsRelativeDrop) + { + loc.m_X += multi.X; + loc.m_Y += multi.Y; + from.Drop(loc); + } + else + { + from.Drop(item, loc); + } + } + else + { + from.Drop(loc); + } + } + + public static void ConfigurationFile(NetState state, PacketReader pvSrc) + { + } + + public static void LogoutReq(NetState state, PacketReader pvSrc) + { + state.Send(new LogoutAck()); + } + + public static void ChangeSkillLock(NetState state, PacketReader pvSrc) + { + var s = state.Mobile.Skills[pvSrc.ReadInt16()]; + + s?.SetLockNoRelay((SkillLock)pvSrc.ReadByte()); + } + + public static void HelpRequest(NetState state, PacketReader pvSrc) + { + EventSink.InvokeHelpRequest(state.Mobile); + } + + public static void TargetResponse(NetState state, PacketReader pvSrc) + { + int type = pvSrc.ReadByte(); + var targetID = pvSrc.ReadInt32(); + int flags = pvSrc.ReadByte(); + Serial serial = pvSrc.ReadUInt32(); + int x = pvSrc.ReadInt16(), y = pvSrc.ReadInt16(), z = pvSrc.ReadInt16(); + int graphic = pvSrc.ReadUInt16(); + + if (targetID == unchecked((int)0xDEADBEEF)) + return; + + var from = state.Mobile; + + var t = from.Target; + + if (t == null) return; + + var prof = TargetProfile.Acquire(t.GetType()); + prof?.Start(); + + try + { + if (x == -1 && y == -1 && !serial.IsValid) + { + // User pressed escape + t.Cancel(from, TargetCancelType.Canceled); + } + else if (t.TargetID != targetID) + { + // Sanity, prevent fake target + } + else + { + object toTarget; + + if (type == 1) + { + if (graphic == 0) + { + toTarget = new LandTarget(new Point3D(x, y, z), from.Map); + } + else + { + var map = from.Map; + + if (map == null || map == Map.Internal) + { + t.Cancel(from, TargetCancelType.Canceled); + return; + } + else + { + var tiles = map.Tiles.GetStaticTiles(x, y, !t.DisallowMultis); + + var valid = false; + + if (state.HighSeas) + { + var id = TileData.ItemTable[graphic & TileData.MaxItemValue]; + if (id.Surface) z -= id.Height; + } + + for (var i = 0; !valid && i < tiles.Length; ++i) + if (tiles[i].Z == z && tiles[i].ID == graphic) + valid = true; + + if (!valid) + { + t.Cancel(from, TargetCancelType.Canceled); + return; + } + else + { + toTarget = new StaticTarget(new Point3D(x, y, z), graphic); + } + } + } + } + else if (serial.IsMobile) + { + toTarget = World.FindMobile(serial); + } + else if (serial.IsItem) + { + toTarget = World.FindItem(serial); + } + else + { + t.Cancel(from, TargetCancelType.Canceled); + return; + } + + t.Invoke(from, toTarget); + } + } + finally + { + prof?.Finish(); + } + } + + public static void DisplayGumpResponse(NetState state, PacketReader pvSrc) + { + var serial = pvSrc.ReadUInt32(); + var typeID = pvSrc.ReadInt32(); + var buttonID = pvSrc.ReadInt32(); + + foreach (var gump in state.Gumps) + { + if (gump.Serial != serial || gump.TypeID != typeID) + continue; + var buttonExists = buttonID == 0; // 0 is always 'close' + + if (!buttonExists) + foreach (var e in gump.Entries) + { + if (e is GumpButton button && button.ButtonID == buttonID) + { + buttonExists = true; + break; + } + + if (e is GumpImageTileButton tileButton && tileButton.ButtonID == buttonID) + { + buttonExists = true; + break; + } + } + + if (!buttonExists) + { + state.WriteConsole("Invalid gump response, disconnecting..."); + state.Dispose(); + return; + } + + var switchCount = pvSrc.ReadInt32(); + + if (switchCount < 0 || switchCount > gump.m_Switches) + { + state.WriteConsole("Invalid gump response, disconnecting..."); + state.Dispose(); + return; + } + + var switches = new int[switchCount]; + + for (var j = 0; j < switches.Length; ++j) + switches[j] = pvSrc.ReadInt32(); + + var textCount = pvSrc.ReadInt32(); + + if (textCount < 0 || textCount > gump.m_TextEntries) + { + state.WriteConsole("Invalid gump response, disconnecting..."); + state.Dispose(); + return; + } + + var textEntries = new TextRelay[textCount]; + + for (var j = 0; j < textEntries.Length; ++j) + { + int entryID = pvSrc.ReadUInt16(); + int textLength = pvSrc.ReadUInt16(); + + if (textLength > 239) + { + state.WriteConsole("Invalid gump response, disconnecting..."); + state.Dispose(); + return; + } + + var text = pvSrc.ReadUnicodeStringSafe(textLength); + textEntries[j] = new TextRelay(entryID, text); + } + + state.RemoveGump(gump); + + var prof = GumpProfile.Acquire(gump.GetType()); + + prof?.Start(); + + gump.OnResponse(state, new RelayInfo(buttonID, switches, textEntries)); + + prof?.Finish(); + + return; + } + + if (typeID == 461) + { + // Virtue gump + var switchCount = pvSrc.ReadInt32(); + + if (buttonID == 1 && switchCount > 0) + { + var beheld = World.FindMobile(pvSrc.ReadUInt32()); + + if (beheld != null) + EventSink.InvokeVirtueGumpRequest(state.Mobile, beheld); + } + else + { + var beheld = World.FindMobile(serial); + + if (beheld != null) + EventSink.InvokeVirtueItemRequest(state.Mobile, beheld, buttonID); + } + } + } + + public static void SetWarMode(NetState state, PacketReader pvSrc) + { + state.Mobile.DelayChangeWarmode(pvSrc.ReadBoolean()); + } + + public static void Resynchronize(NetState state, PacketReader pvSrc) + { + var m = state.Mobile; + + if (state.StygianAbyss) + state.Send(new MobileUpdate(m)); + else + state.Send(new MobileUpdateOld(m)); + + state.Send(MobileIncoming.Create(state, m, m)); + + m.SendEverything(); + + state.Sequence = 0; + + m.ClearFastwalkStack(); + } + + public static void AsciiSpeech(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + var type = (MessageType)pvSrc.ReadByte(); + int hue = pvSrc.ReadInt16(); + pvSrc.ReadInt16(); // font + var text = pvSrc.ReadStringSafe().Trim(); + + if (text.Length <= 0 || text.Length > 128) + return; + + if (!Enum.IsDefined(typeof(MessageType), type)) + type = MessageType.Regular; + + from.DoSpeech(text, m_EmptyInts, type, Utility.ClipDyedHue(hue)); + } + + public static void UnicodeSpeech(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + var type = (MessageType)pvSrc.ReadByte(); + int hue = pvSrc.ReadInt16(); + pvSrc.ReadInt16(); // font + var lang = pvSrc.ReadString(4); + string text; + + var isEncoded = (type & MessageType.Encoded) != 0; + int[] keywords; + + if (isEncoded) + { + int value = pvSrc.ReadInt16(); + var count = (value & 0xFFF0) >> 4; + var hold = value & 0xF; + + if (count < 0 || count > 50) + return; + + var keyList = m_KeywordList; + + for (var i = 0; i < count; ++i) + { + int speechID; + + if ((i & 1) == 0) + { + hold <<= 8; + hold |= pvSrc.ReadByte(); + speechID = hold; + hold = 0; + } + else + { + value = pvSrc.ReadInt16(); + speechID = (value & 0xFFF0) >> 4; + hold = value & 0xF; + } + + if (!keyList.Contains(speechID)) + keyList.Add(speechID); + } + + text = pvSrc.ReadUTF8StringSafe(); + + keywords = keyList.ToArray(); + } + else + { + text = pvSrc.ReadUnicodeStringSafe(); + + keywords = m_EmptyInts; + } + + text = text.Trim(); + + if (text.Length <= 0 || text.Length > 128) + return; + + type &= ~MessageType.Encoded; + + if (!Enum.IsDefined(typeof(MessageType), type)) + type = MessageType.Regular; + + from.Language = lang; + from.DoSpeech(text, keywords, type, Utility.ClipDyedHue(hue)); + } + + public static void UseReq(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0) + { + var value = pvSrc.ReadUInt32(); + + if ((value & ~0x7FFFFFFF) != 0) + { + from.OnPaperdollRequest(); + } + else + { + Serial s = value; + + if (s.IsMobile) + { + var m = World.FindMobile(s); + + if (m?.Deleted == false) + from.Use(m); + } + else if (s.IsItem) + { + var item = World.FindItem(s); + + if (item?.Deleted == false) + from.Use(item); + } + } + + from.NextActionTime = Core.TickCount + Mobile.ActionDelay; + } + else + { + from.SendActionMessage(); + } + } + + public static void LookReq(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + Serial s = pvSrc.ReadUInt32(); + + if (s.IsMobile) + { + var m = World.FindMobile(s); + + if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m)) + { + if (SingleClickProps) + { + m.OnAosSingleClick(from); + } + else + { + if (from.Region.OnSingleClick(from, m)) + m.OnSingleClick(from); + } + } + } + else if (s.IsItem) + { + var item = World.FindItem(s); + + if (item?.Deleted == false && from.CanSee(item) && + Utility.InUpdateRange(from.Location, item.GetWorldLocation())) + { + if (SingleClickProps) + { + item.OnAosSingleClick(from); + } + else if (from.Region.OnSingleClick(from, item)) + { + if (item.Parent is Item parentItem) + parentItem.OnSingleClickContained(from, item); + + item.OnSingleClick(from); + } + } + } + } + + public static void PingReq(NetState state, PacketReader pvSrc) + { + state.Send(PingAck.Instantiate(pvSrc.ReadByte())); + } + + public static void SetUpdateRange(NetState state, PacketReader pvSrc) + { + state.Send(ChangeUpdateRange.Instantiate(18)); + } + + public static void MovementReq(NetState state, PacketReader pvSrc) + { + var dir = (Direction)pvSrc.ReadByte(); + int seq = pvSrc.ReadByte(); + var key = pvSrc.ReadInt32(); + + var m = state.Mobile; + + if (state.Sequence == 0 && seq != 0 || !m.Move(dir)) + { + state.Send(new MovementRej(seq, m)); + state.Sequence = 0; + + m.ClearFastwalkStack(); + } + else + { + ++seq; + + if (seq == 256) + seq = 1; + + state.Sequence = seq; + } + } + + public static void Animate(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + var action = pvSrc.ReadInt32(); + + var ok = false; + + for (var i = 0; !ok && i < ValidAnimations.Length; ++i) + ok = action == ValidAnimations[i]; + + if (from != null && ok && from.Alive && from.Body.IsHuman && !from.Mounted) + from.Animate(action, 7, 1, true, false, 0); + } + + public static void QuestArrow(NetState state, PacketReader pvSrc) + { + var rightClick = pvSrc.ReadBoolean(); + var from = state.Mobile; + + from?.QuestArrow?.OnClick(rightClick); + } + + public static void ExtendedCommand(NetState state, PacketReader pvSrc) + { + int packetID = pvSrc.ReadUInt16(); + + var ph = GetExtendedHandler(packetID); + + if (ph == null) + { + pvSrc.Trace(state); + return; + } + + if (ph.Ingame && state.Mobile?.Deleted != false) + { + if (state.Mobile == null) + Console.WriteLine( + "Client: {0}: Sent in-game packet (0xBFx{1:X2}) before having been attached to a mobile", + state, + packetID + ); + state.Dispose(); + } + else + { + ph.OnReceive(state, pvSrc); + } + } + + public static void CastSpell(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + if (from == null) + return; + + Item spellbook = null; + + if (pvSrc.ReadInt16() == 1) + spellbook = World.FindItem(pvSrc.ReadUInt32()); + + var spellID = pvSrc.ReadInt16() - 1; + + EventSink.InvokeCastSpellRequest(from, spellID, spellbook); + } + + public static void BandageTarget(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + if (from == null) + return; + + if (from.AccessLevel >= AccessLevel.Counselor || Core.TickCount - from.NextActionTime >= 0) + { + var bandage = World.FindItem(pvSrc.ReadUInt32()); + + if (bandage == null) + return; + + var target = World.FindMobile(pvSrc.ReadUInt32()); + + if (target == null) + return; + + EventSink.InvokeBandageTargetRequest(from, bandage, target); + + from.NextActionTime = Core.TickCount + Mobile.ActionDelay; + } + else + { + from.SendActionMessage(); + } + } + + public static void ToggleFlying(NetState state, PacketReader pvSrc) + { + state.Mobile.ToggleFlying(); + } + + public static void BatchQueryProperties(NetState state, PacketReader pvSrc) + { + if (!ObjectPropertyList.Enabled) + return; + + var from = state.Mobile; + + var length = pvSrc.Remaining; + + if (length % 4 != 0) + return; + + while (pvSrc.Remaining > 0) + { + Serial s = pvSrc.ReadUInt32(); + + if (s.IsMobile) + { + var m = World.FindMobile(s); + + if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m)) + m.SendPropertiesTo(from); + } + else if (s.IsItem) + { + var item = World.FindItem(s); + + if (item?.Deleted == false && from.CanSee(item) && + Utility.InUpdateRange(from.Location, item.GetWorldLocation())) + item.SendPropertiesTo(from); + } + } + } + + public static void QueryProperties(NetState state, PacketReader pvSrc) + { + if (!ObjectPropertyList.Enabled) + return; + + var from = state.Mobile; + + Serial s = pvSrc.ReadUInt32(); + + if (s.IsMobile) + { + var m = World.FindMobile(s); + + if (m != null && from.CanSee(m) && Utility.InUpdateRange(from, m)) + m.SendPropertiesTo(from); + } + else if (s.IsItem) + { + var item = World.FindItem(s); + + if (item?.Deleted == false && from.CanSee(item) && + Utility.InUpdateRange(from.Location, item.GetWorldLocation())) + item.SendPropertiesTo(from); + } + } + + public static void PartyMessage(NetState state, PacketReader pvSrc) + { + if (state.Mobile == null) + return; + + switch (pvSrc.ReadByte()) + { + case 0x01: + PartyMessage_AddMember(state, pvSrc); + break; + case 0x02: + PartyMessage_RemoveMember(state, pvSrc); + break; + case 0x03: + PartyMessage_PrivateMessage(state, pvSrc); + break; + case 0x04: + PartyMessage_PublicMessage(state, pvSrc); + break; + case 0x06: + PartyMessage_SetCanLoot(state, pvSrc); + break; + case 0x08: + PartyMessage_Accept(state, pvSrc); + break; + case 0x09: + PartyMessage_Decline(state, pvSrc); + break; + default: + pvSrc.Trace(state); + break; + } + } + + public static void PartyMessage_AddMember(NetState state, PacketReader pvSrc) + { + PartyCommands.Handler?.OnAdd(state.Mobile); + } + + public static void PartyMessage_RemoveMember(NetState state, PacketReader pvSrc) + { + PartyCommands.Handler?.OnRemove(state.Mobile, World.FindMobile(pvSrc.ReadUInt32())); + } + + public static void PartyMessage_PrivateMessage(NetState state, PacketReader pvSrc) + { + PartyCommands.Handler?.OnPrivateMessage( + state.Mobile, + World.FindMobile(pvSrc.ReadUInt32()), + pvSrc.ReadUnicodeStringSafe() + ); + } + + public static void PartyMessage_PublicMessage(NetState state, PacketReader pvSrc) + { + PartyCommands.Handler?.OnPublicMessage(state.Mobile, pvSrc.ReadUnicodeStringSafe()); + } + + public static void PartyMessage_SetCanLoot(NetState state, PacketReader pvSrc) + { + PartyCommands.Handler?.OnSetCanLoot(state.Mobile, pvSrc.ReadBoolean()); + } + + public static void PartyMessage_Accept(NetState state, PacketReader pvSrc) + { + PartyCommands.Handler?.OnAccept(state.Mobile, World.FindMobile(pvSrc.ReadUInt32())); + } + + public static void PartyMessage_Decline(NetState state, PacketReader pvSrc) + { + PartyCommands.Handler?.OnDecline(state.Mobile, World.FindMobile(pvSrc.ReadUInt32())); + } + + public static void StunRequest(NetState state, PacketReader pvSrc) + { + EventSink.InvokeStunRequest(state.Mobile); + } + + public static void DisarmRequest(NetState state, PacketReader pvSrc) + { + EventSink.InvokeDisarmRequest(state.Mobile); + } + + public static void StatLockChange(NetState state, PacketReader pvSrc) + { + int stat = pvSrc.ReadByte(); + int lockValue = pvSrc.ReadByte(); + + if (lockValue > 2) lockValue = 0; + + var m = state.Mobile; + + if (m != null) + switch (stat) + { + case 0: + m.StrLock = (StatLockType)lockValue; + break; + case 1: + m.DexLock = (StatLockType)lockValue; + break; + case 2: + m.IntLock = (StatLockType)lockValue; + break; + } + } + + public static void ScreenSize(NetState state, PacketReader pvSrc) + { + var width = pvSrc.ReadInt32(); + var unk = pvSrc.ReadInt32(); + } + + public static void ContextMenuResponse(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + if (from == null) return; + + var menu = from.ContextMenu; + + from.ContextMenu = null; + + if (menu != null && from == menu.From) + { + var entity = World.FindEntity(pvSrc.ReadUInt32()); + + if (entity != null && entity == menu.Target && from.CanSee(entity)) + { + Point3D p; + + if (entity is Mobile) + p = entity.Location; + else if (entity is Item item) + p = item.GetWorldLocation(); + else + return; + + int index = pvSrc.ReadUInt16(); + + if (index >= 0 && index < menu.Entries.Length) + { + var e = menu.Entries[index]; + + var range = e.Range; + + if (range == -1) + range = 18; + + if (e.Enabled && from.InRange(p, range)) + e.OnClick(); + } + } + } + } + + public static void ContextMenuRequest(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + var target = World.FindEntity(pvSrc.ReadUInt32()); + + if (from != null && target != null && from.Map == target.Map && from.CanSee(target)) + { + if (target is Mobile && !Utility.InUpdateRange(from.Location, target.Location)) + return; + + var item = target as Item; + + if (item != null && !Utility.InUpdateRange(from.Location, item.GetWorldLocation())) + return; + + if (!from.CheckContextMenuDisplay(target)) + return; + + var c = new ContextMenu(from, target); + + if (c.Entries.Length > 0) + { + if (item?.RootParent is Mobile mobile && mobile != from && mobile.AccessLevel >= from.AccessLevel) + for (var i = 0; i < c.Entries.Length; ++i) + if (!c.Entries[i].NonLocalUse) + c.Entries[i].Enabled = false; + + from.ContextMenu = c; + } + } + } + + public static void CloseStatus(NetState state, PacketReader pvSrc) + { + Serial serial = pvSrc.ReadUInt32(); + } + + public static void Language(NetState state, PacketReader pvSrc) + { + var lang = pvSrc.ReadString(4); + + if (state.Mobile != null) + state.Mobile.Language = lang; + } + + public static void AssistVersion(NetState state, PacketReader pvSrc) + { + var unk = pvSrc.ReadInt32(); + var av = pvSrc.ReadString(); + } + + public static void ClientVersion(NetState state, PacketReader pvSrc) + { + var version = state.Version = new CV(pvSrc.ReadString()); + + EventSink.InvokeClientVersionReceived(state, version); + } + + public static void ClientType(NetState state, PacketReader pvSrc) + { + pvSrc.ReadUInt16(); + + int type = pvSrc.ReadUInt16(); + var version = state.Version = new CV(pvSrc.ReadString()); + + EventSink.InvokeClientVersionReceived(state, version); + } + + public static void MobileQuery(NetState state, PacketReader pvSrc) + { + var from = state.Mobile; + + pvSrc.ReadInt32(); // 0xEDEDEDED + int type = pvSrc.ReadByte(); + var m = World.FindMobile(pvSrc.ReadUInt32()); + + if (m != null) + switch (type) + { + case 0x04: // Stats + { + m.OnStatsQuery(from); + break; + } + case 0x05: + { + m.OnSkillsQuery(from); + break; + } + default: + { + pvSrc.Trace(state); + break; + } + } + } + + public static void PlayCharacter(NetState state, PacketReader pvSrc) + { + pvSrc.ReadInt32(); // 0xEDEDEDED + + var name = pvSrc.ReadString(30); + + pvSrc.Seek(2, SeekOrigin.Current); + + var flags = pvSrc.ReadInt32(); + + pvSrc.Seek(24, SeekOrigin.Current); + + var charSlot = pvSrc.ReadInt32(); + var clientIP = pvSrc.ReadInt32(); + + var a = state.Account; + + if (a == null || charSlot < 0 || charSlot >= a.Length) + { + state.Dispose(); + } + else + { + var m = a[charSlot]; + + // Check if anyone is using this account + for (var i = 0; i < a.Length; ++i) + { + var check = a[i]; + + if (check != null && check.Map != Map.Internal && check != m) + { + Console.WriteLine("Login: {0}: Account in use", state); + state.Send(new PopupMessage(PMMessage.CharInWorld)); + return; + } + } + + if (m == null) + { + state.Dispose(); + return; + } + + m.NetState?.Dispose(); + + // TODO: Make this wait one tick so we don't have to call it unnecessarily + NetState.ProcessDisposedQueue(); + + state.Send(new ClientVersionReq()); + + state.BlockAllPackets = true; + + state.Flags = (ClientFlags)flags; + + state.Mobile = m; + m.NetState = state; + + new LoginTimer(state, m).Start(); + } + } + + public static void ShowPublicHouseContent(NetState state, PacketReader pvSrc) + { + var showPublicHouseContent = pvSrc.ReadBoolean(); + } + + public static void DoLogin(NetState state, Mobile m) + { + state.Send(new LoginConfirm(m)); + + if (m.Map != null) + state.Send(new MapChange(m.Map)); + + if (!Core.SE && state.ProtocolChanges < ProtocolChanges.Version6000) + state.Send(new MapPatches()); + + state.Send(SeasonChange.Instantiate(m.GetSeason(), true)); + + state.Send(SupportedFeatures.Instantiate(state)); + + state.Sequence = 0; + + if (state.NewMobileIncoming) + { + state.Send(new MobileUpdate(m)); + state.Send(new MobileUpdate(m)); + + m.CheckLightLevels(true); + + state.Send(new MobileUpdate(m)); + + state.Send(new MobileIncoming(m, m)); + // state.Send( new MobileAttributes( m ) ); + state.Send(new MobileStatus(m, m)); + state.Send(Network.SetWarMode.Instantiate(m.Warmode)); + + m.SendEverything(); + + state.Send(SupportedFeatures.Instantiate(state)); + state.Send(new MobileUpdate(m)); + // state.Send( new MobileAttributes( m ) ); + state.Send(new MobileStatus(m, m)); + state.Send(Network.SetWarMode.Instantiate(m.Warmode)); + state.Send(new MobileIncoming(m, m)); + } + else if (state.StygianAbyss) + { + state.Send(new MobileUpdate(m)); + state.Send(new MobileUpdate(m)); + + m.CheckLightLevels(true); + + state.Send(new MobileUpdate(m)); + + state.Send(new MobileIncomingSA(m, m)); + // state.Send( new MobileAttributes( m ) ); + state.Send(new MobileStatus(m, m)); + state.Send(Network.SetWarMode.Instantiate(m.Warmode)); + + m.SendEverything(); + + state.Send(SupportedFeatures.Instantiate(state)); + state.Send(new MobileUpdate(m)); + // state.Send( new MobileAttributes( m ) ); + state.Send(new MobileStatus(m, m)); + state.Send(Network.SetWarMode.Instantiate(m.Warmode)); + state.Send(new MobileIncomingSA(m, m)); + } + else + { + state.Send(new MobileUpdateOld(m)); + state.Send(new MobileUpdateOld(m)); + + m.CheckLightLevels(true); + + state.Send(new MobileUpdateOld(m)); + + state.Send(new MobileIncomingOld(m, m)); + // state.Send( new MobileAttributes( m ) ); + state.Send(new MobileStatus(m, m)); + state.Send(Network.SetWarMode.Instantiate(m.Warmode)); + + m.SendEverything(); + + state.Send(SupportedFeatures.Instantiate(state)); + state.Send(new MobileUpdateOld(m)); + // state.Send( new MobileAttributes( m ) ); + state.Send(new MobileStatus(m, m)); + state.Send(Network.SetWarMode.Instantiate(m.Warmode)); + state.Send(new MobileIncomingOld(m, m)); + } + + state.Send(LoginComplete.Instance); + state.Send(new CurrentTime()); + state.Send(SeasonChange.Instantiate(m.GetSeason(), true)); + if (m.Map != null) + state.Send(new MapChange(m.Map)); + + EventSink.InvokeLogin(m); + + m.ClearFastwalkStack(); + } + + public static void CreateCharacter(NetState state, PacketReader pvSrc) + { + var unk1 = pvSrc.ReadInt32(); + var unk2 = pvSrc.ReadInt32(); + int unk3 = pvSrc.ReadByte(); + var name = pvSrc.ReadString(30); + + pvSrc.Seek(2, SeekOrigin.Current); + var flags = pvSrc.ReadInt32(); + pvSrc.Seek(8, SeekOrigin.Current); + int prof = pvSrc.ReadByte(); + pvSrc.Seek(15, SeekOrigin.Current); + + int genderRace = pvSrc.ReadByte(); + + int str = pvSrc.ReadByte(); + int dex = pvSrc.ReadByte(); + int intl = pvSrc.ReadByte(); + int is1 = pvSrc.ReadByte(); + int vs1 = pvSrc.ReadByte(); + int is2 = pvSrc.ReadByte(); + int vs2 = pvSrc.ReadByte(); + int is3 = pvSrc.ReadByte(); + int vs3 = pvSrc.ReadByte(); + int hue = pvSrc.ReadUInt16(); + int hairVal = pvSrc.ReadInt16(); + int hairHue = pvSrc.ReadInt16(); + int hairValf = pvSrc.ReadInt16(); + int hairHuef = pvSrc.ReadInt16(); + pvSrc.ReadByte(); + int cityIndex = pvSrc.ReadByte(); + var charSlot = pvSrc.ReadInt32(); + var clientIP = pvSrc.ReadInt32(); + int shirtHue = pvSrc.ReadInt16(); + int pantsHue = pvSrc.ReadInt16(); + + /* + Pre-7.0.0.0: + 0x00, 0x01 -> Human Male, Human Female + 0x02, 0x03 -> Elf Male, Elf Female + + Post-7.0.0.0: + 0x00, 0x01 + 0x02, 0x03 -> Human Male, Human Female + 0x04, 0x05 -> Elf Male, Elf Female + 0x05, 0x06 -> Gargoyle Male, Gargoyle Female + */ + + var female = genderRace % 2 != 0; + + Race race; + + if (state.StygianAbyss) + { + var raceID = (byte)(genderRace < 4 ? 0 : genderRace / 2 - 1); + race = Race.Races[raceID]; + } + else + { + race = Race.Races[(byte)(genderRace / 2)]; + } + + race ??= Race.DefaultRace; + + var info = state.CityInfo; + var a = state.Account; + + if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length) + { + state.Dispose(); + } + else + { + // Check if anyone is using this account + for (var i = 0; i < a.Length; ++i) + { + var check = a[i]; + + if (check != null && check.Map != Map.Internal) + { + Console.WriteLine("Login: {0}: Account in use", state); + state.Send(new PopupMessage(PMMessage.CharInWorld)); + return; + } + } + + state.Flags = (ClientFlags)flags; + + var args = new CharacterCreatedEventArgs( + state, + a, + name, + female, + hue, + str, + dex, + intl, + info[cityIndex], + new[] + { + new SkillNameValue((SkillName)is1, vs1), + new SkillNameValue((SkillName)is2, vs2), + new SkillNameValue((SkillName)is3, vs3) + }, + shirtHue, + pantsHue, + hairVal, + hairHue, + hairValf, + hairHuef, + prof, + race + ); + + state.Send(new ClientVersionReq()); + + state.BlockAllPackets = true; + + EventSink.InvokeCharacterCreated(args); + + var m = args.Mobile; + + if (m != null) + { + state.Mobile = m; + m.NetState = state; + new LoginTimer(state, m).Start(); + } + else + { + state.BlockAllPackets = false; + state.Dispose(); + } + } + } + + public static void CreateCharacter70160(NetState state, PacketReader pvSrc) + { + var unk1 = pvSrc.ReadInt32(); + var unk2 = pvSrc.ReadInt32(); + int unk3 = pvSrc.ReadByte(); + var name = pvSrc.ReadString(30); + + pvSrc.Seek(2, SeekOrigin.Current); + var flags = pvSrc.ReadInt32(); + pvSrc.Seek(8, SeekOrigin.Current); + int prof = pvSrc.ReadByte(); + pvSrc.Seek(15, SeekOrigin.Current); + + int genderRace = pvSrc.ReadByte(); + + int str = pvSrc.ReadByte(); + int dex = pvSrc.ReadByte(); + int intl = pvSrc.ReadByte(); + int is1 = pvSrc.ReadByte(); + int vs1 = pvSrc.ReadByte(); + int is2 = pvSrc.ReadByte(); + int vs2 = pvSrc.ReadByte(); + int is3 = pvSrc.ReadByte(); + int vs3 = pvSrc.ReadByte(); + int is4 = pvSrc.ReadByte(); + int vs4 = pvSrc.ReadByte(); + + int hue = pvSrc.ReadUInt16(); + int hairVal = pvSrc.ReadInt16(); + int hairHue = pvSrc.ReadInt16(); + int hairValf = pvSrc.ReadInt16(); + int hairHuef = pvSrc.ReadInt16(); + pvSrc.ReadByte(); + int cityIndex = pvSrc.ReadByte(); + var charSlot = pvSrc.ReadInt32(); + var clientIP = pvSrc.ReadInt32(); + int shirtHue = pvSrc.ReadInt16(); + int pantsHue = pvSrc.ReadInt16(); + + /* + 0x00, 0x01 + 0x02, 0x03 -> Human Male, Human Female + 0x04, 0x05 -> Elf Male, Elf Female + 0x05, 0x06 -> Gargoyle Male, Gargoyle Female + */ + + var female = genderRace % 2 != 0; + + Race race; + + var raceID = (byte)(genderRace < 4 ? 0 : genderRace / 2 - 1); + race = Race.Races[raceID] ?? Race.DefaultRace; + + var info = state.CityInfo; + var a = state.Account; + + if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length) + { + state.Dispose(); + } + else + { + // Check if anyone is using this account + for (var i = 0; i < a.Length; ++i) + { + var check = a[i]; + + if (check != null && check.Map != Map.Internal) + { + Console.WriteLine("Login: {0}: Account in use", state); + state.Send(new PopupMessage(PMMessage.CharInWorld)); + return; + } + } + + state.Flags = (ClientFlags)flags; + + var args = new CharacterCreatedEventArgs( + state, + a, + name, + female, + hue, + str, + dex, + intl, + info[cityIndex], + new[] + { + new SkillNameValue((SkillName)is1, vs1), + new SkillNameValue((SkillName)is2, vs2), + new SkillNameValue((SkillName)is3, vs3), + new SkillNameValue((SkillName)is4, vs4) + }, + shirtHue, + pantsHue, + hairVal, + hairHue, + hairValf, + hairHuef, + prof, + race + ); + + state.Send(new ClientVersionReq()); + + state.BlockAllPackets = true; + + EventSink.InvokeCharacterCreated(args); + + var m = args.Mobile; + + if (m != null) + { + state.Mobile = m; + m.NetState = state; + new LoginTimer(state, m).Start(); + } + else + { + state.BlockAllPackets = false; + state.Dispose(); + } + } + } + + private static int GenerateAuthID(NetState state) + { + if (m_AuthIDWindow.Count == m_AuthIDWindowSize) + { + var oldestID = 0; + var oldest = DateTime.MaxValue; + + foreach (var kvp in m_AuthIDWindow) + if (kvp.Value.Age < oldest) + { + oldestID = kvp.Key; + oldest = kvp.Value.Age; + } + + m_AuthIDWindow.Remove(oldestID); + } + + int authID; + + do + { + authID = Utility.Random(1, int.MaxValue - 1); + + if (Utility.RandomBool()) + authID |= 1 << 31; + } while (m_AuthIDWindow.ContainsKey(authID)); + + m_AuthIDWindow[authID] = new AuthIDPersistence(state.Version); + + return authID; + } + + public static void GameLogin(NetState state, PacketReader pvSrc) + { + if (state.SentFirstPacket) + { + state.Dispose(); + return; + } + + state.SentFirstPacket = true; + + var authID = pvSrc.ReadInt32(); + + if (m_AuthIDWindow.TryGetValue(authID, out var ap)) + { + m_AuthIDWindow.Remove(authID); + + state.Version = ap.Version; + } + else if (ClientVerification) + { + Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); + state.Dispose(); + return; + } + + if (state.m_AuthID != 0 && authID != state.m_AuthID) + { + Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); + state.Dispose(); + return; + } + + if (state.m_AuthID == 0 && authID != state.m_Seed) + { + Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); + state.Dispose(); + return; + } + + var username = pvSrc.ReadString(30); + var password = pvSrc.ReadString(30); + + var e = new GameLoginEventArgs(state, username, password); + + EventSink.InvokeGameLogin(e); + + if (e.Accepted) + { + state.CityInfo = e.CityInfo; + state.CompressionEnabled = true; + + state.Send(SupportedFeatures.Instantiate(state)); + + if (state.NewCharacterList) + state.Send(new CharacterList(state.Account, state.CityInfo)); + else + state.Send(new CharacterListOld(state.Account, state.CityInfo)); + } + else + { + state.Dispose(); + } + } + + public static void PlayServer(NetState state, PacketReader pvSrc) + { + int index = pvSrc.ReadInt16(); + var info = state.ServerInfo; + var a = state.Account; + + if (info == null || a == null || index < 0 || index >= info.Length) + { + state.Dispose(); + } + else + { + var si = info[index]; + + state.m_AuthID = PlayServerAck.m_AuthID = GenerateAuthID(state); + + state.SentFirstPacket = false; + state.Send(new PlayServerAck(si)); + } + } + + public static void LoginServerSeed(NetState state, PacketReader pvSrc) + { + state.m_Seed = pvSrc.ReadInt32(); + state.Seeded = true; + + if (state.m_Seed == 0) + { + Console.WriteLine("Login: {0}: Invalid client detected, disconnecting", state); + state.Dispose(); + return; + } + + var clientMaj = pvSrc.ReadInt32(); + var clientMin = pvSrc.ReadInt32(); + var clientRev = pvSrc.ReadInt32(); + var clientPat = pvSrc.ReadInt32(); + + state.Version = new ClientVersion(clientMaj, clientMin, clientRev, clientPat); + } + + public static void CrashReport(NetState state, PacketReader pvSrc) + { + var clientMaj = pvSrc.ReadByte(); + var clientMin = pvSrc.ReadByte(); + var clientRev = pvSrc.ReadByte(); + var clientPat = pvSrc.ReadByte(); + + var x = pvSrc.ReadUInt16(); + var y = pvSrc.ReadUInt16(); + var z = pvSrc.ReadSByte(); + var map = pvSrc.ReadByte(); + + var account = pvSrc.ReadString(32); + var character = pvSrc.ReadString(32); + var ip = pvSrc.ReadString(15); + + var unk1 = pvSrc.ReadInt32(); + var exception = pvSrc.ReadInt32(); + + var process = pvSrc.ReadString(100); + var report = pvSrc.ReadString(100); + + pvSrc.ReadByte(); // 0x00 + + var offset = pvSrc.ReadInt32(); + + int count = pvSrc.ReadByte(); + + for (var i = 0; i < count; i++) + { + var address = pvSrc.ReadInt32(); + } + } + + public static void AccountLogin(NetState state, PacketReader pvSrc) + { + if (state.SentFirstPacket) + { + state.Dispose(); + return; + } + + state.SentFirstPacket = true; + + var username = pvSrc.ReadString(30); + var password = pvSrc.ReadString(30); + + var e = new AccountLoginEventArgs(state, username, password); + + EventSink.InvokeAccountLogin(e); + + if (e.Accepted) + AccountLogin_ReplyAck(state); + else + AccountLogin_ReplyRej(state, e.RejectReason); + } + + public static void AccountLogin_ReplyAck(NetState state) + { + var e = new ServerListEventArgs(state, state.Account); + + EventSink.InvokeServerList(e); + + if (e.Rejected) + { + state.Account = null; + AccountLogin_ReplyRej(state, ALRReason.BadComm); + } + else + { + var info = e.Servers.ToArray(); + + state.ServerInfo = info; + + state.Send(new AccountLoginAck(info)); + } + } + + public static void AccountLogin_ReplyRej(NetState state, ALRReason reason) + { + state.Send(new AccountLoginRej(reason)); + state.Dispose(); + } + + public static void EquipMacro(NetState ns, PacketReader pvSrc) + { + int count = pvSrc.ReadByte(); + var serialList = new List(count); + for (var i = 0; i < count; ++i) + serialList.Add(pvSrc.ReadUInt32()); + + EventSink.InvokeEquipMacro(ns.Mobile, serialList); + } + + public static void UnequipMacro(NetState ns, PacketReader pvSrc) + { + int count = pvSrc.ReadByte(); + var layers = new List(count); + for (var i = 0; i < count; ++i) + layers.Add((Layer)pvSrc.ReadUInt16()); + + EventSink.InvokeUnequipMacro(ns.Mobile, layers); + } + + public static void TargetedSpell(NetState ns, PacketReader pvSrc) + { + var spellId = (short)(pvSrc.ReadInt16() - 1); // zero based; + + EventSink.InvokeTargetedSpell(ns.Mobile, World.FindEntity(pvSrc.ReadUInt32()), spellId); + } + + public static void TargetedSkillUse(NetState ns, PacketReader pvSrc) + { + var skillId = pvSrc.ReadInt16(); + + EventSink.InvokeTargetedSkillUse(ns.Mobile, World.FindEntity(pvSrc.ReadUInt32()), skillId); + } + + public static void TargetByResourceMacro(NetState ns, PacketReader pvSrc) + { + Serial serial = pvSrc.ReadUInt32(); + + if (serial.IsItem) EventSink.InvokeTargetByResourceMacro(ns.Mobile, World.FindItem(serial), pvSrc.ReadInt16()); + } + + private class LoginTimer : Timer + { + private readonly Mobile m_Mobile; + private readonly NetState m_State; + + public LoginTimer(NetState state, Mobile m) : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0)) + { + m_State = state; + m_Mobile = m; + } + + protected override void OnTick() + { + if (m_State == null) + { + Stop(); + return; + } + + if (m_State.Version != null) + { + m_State.BlockAllPackets = false; + DoLogin(m_State, m_Mobile); + Stop(); + } + } + } + + internal struct AuthIDPersistence + { + public DateTime Age; + public ClientVersion Version; + + public AuthIDPersistence(ClientVersion v) + { + Age = DateTime.UtcNow; + Version = v; + } + } + } +} diff --git a/Projects/Server/Network/PacketReader.cs b/Projects/Server/Network/PacketReader.cs index d22cdbdc7..876225d01 100644 --- a/Projects/Server/Network/PacketReader.cs +++ b/Projects/Server/Network/PacketReader.cs @@ -1,308 +1,309 @@ -/*************************************************************************** - * PacketReader.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Buffers; -using System.IO; -using System.Text; - -namespace Server.Network -{ - public ref struct PacketReader - { - private SequenceReader m_Reader; - - public SequencePosition Position => m_Reader.Position; - public long Length => m_Reader.Length; - public long Consumed => m_Reader.Consumed; - public long Remaining => m_Reader.Remaining; - - public PacketReader(ReadOnlySequence seq) => m_Reader = new SequenceReader(seq); - - public byte Peek() => m_Reader.TryPeek(out var value) ? value : (byte)0; - - public void Trace(NetState state) - { - try - { - using var sw = new StreamWriter("Packets.log", true); - var buffer = m_Reader.Sequence.ToArray(); - - if (buffer.Length > 0) - sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, buffer[0]); - - using (var ms = new MemoryStream(buffer)) - { - Utility.FormatBuffer(sw, ms, buffer.Length); - } - - sw.WriteLine(); - sw.WriteLine(); - } - catch - { - // ignored - } - } - - public SequencePosition Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - if (offset < m_Reader.Consumed) - m_Reader.Rewind(m_Reader.Consumed - Math.Max(offset, 0L)); - else - m_Reader.Advance(offset - m_Reader.Consumed); - break; - case SeekOrigin.Current: - if (offset < 0) - m_Reader.Rewind(Math.Min(m_Reader.Consumed, offset * -1)); - else - m_Reader.Advance(Math.Min(m_Reader.Remaining, offset)); - break; - case SeekOrigin.End: - var count = m_Reader.Remaining - offset; - if (count < 0) - m_Reader.Rewind(count * -1); - else if (count > 0) - m_Reader.Advance(count); - break; - } - - return m_Reader.Position; - } - - public bool TryReadByte(out byte value) => m_Reader.TryRead(out value); - - public int ReadInt32() => m_Reader.TryReadBigEndian(out int value) ? value : 0; - - public short ReadInt16() => m_Reader.TryReadBigEndian(out short value) ? value : (short)0; - - public byte ReadByte() => m_Reader.TryRead(out var value) ? value : (byte)0; - - public uint ReadUInt32() => (uint)ReadInt32(); - - public ushort ReadUInt16() => (ushort)ReadInt16(); - - public sbyte ReadSByte() => (sbyte)ReadByte(); - - public bool ReadBoolean() => ReadByte() > 0; - - public string ReadUnicodeStringLE() - { - var sb = new StringBuilder(); - - while (m_Reader.TryReadLittleEndian(out short c) && c != 0) - sb.Append((char)c); - - return sb.ToString(); - } - - public string ReadUnicodeStringLE(int fixedLength) - { - var sb = new StringBuilder(); - - while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0) - sb.Append((char)c); - - if (fixedLength > 0) - m_Reader.Advance(fixedLength); - - return sb.ToString(); - } - - public string ReadUnicodeStringLESafe(int fixedLength) - { - var sb = new StringBuilder(); - - while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0) - if (IsSafeChar(c)) - sb.Append((char)c); - - if (fixedLength > 0) - m_Reader.Advance(fixedLength * 2); - - return sb.ToString(); - } - - public string ReadUnicodeStringLESafe() - { - var sb = new StringBuilder(); - - while (m_Reader.TryReadLittleEndian(out short c) && c != 0) - if (IsSafeChar(c)) - sb.Append((char)c); - - return sb.ToString(); - } - - public string ReadUnicodeStringSafe() - { - var sb = new StringBuilder(); - - while (m_Reader.TryReadBigEndian(out short c) && c != 0) - if (IsSafeChar(c)) - sb.Append((char)c); - - return sb.ToString(); - } - - public string ReadUnicodeString() - { - var sb = new StringBuilder(); - - while (m_Reader.TryReadBigEndian(out short c) && c != 0) - sb.Append((char)c); - - return sb.ToString(); - } - - private static bool IsSafeChar(int c) => c >= 0x20 && c < 0xFFFE; - - public string ReadUTF8StringSafe(int fixedLength) - { - string s; - - if (m_Reader.TryReadTo(out ReadOnlySpan span, (byte)'\0')) - { - s = Utility.UTF8.GetString(span.Length > fixedLength ? span.Slice(0, fixedLength) : span); - } - else - { - var size = Math.Min(m_Reader.Remaining, fixedLength); - s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, size).ToArray()); - m_Reader.Advance(size); - } - - var sb = new StringBuilder(s.Length); - - for (var i = 0; i < s.Length; ++i) - if (IsSafeChar(s[i])) - sb.Append(s[i]); - - return sb.ToString(); - } - - public string ReadUTF8StringSafe() - { - string s; - - if (m_Reader.TryReadTo(out ReadOnlySpan span, (byte)'\0')) - { - s = Utility.UTF8.GetString(span); - } - else - { - s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray()); - m_Reader.Advance(m_Reader.Remaining); - } - - var sb = new StringBuilder(s.Length); - - for (var i = 0; i < s.Length; ++i) - if (IsSafeChar(s[i])) - sb.Append(s[i]); - - return sb.ToString(); - } - - public string ReadUTF8String() => - Utility.UTF8.GetString( - m_Reader.TryReadTo(out ReadOnlySpan span, (byte)'\0') - ? span - : m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray()); - - public string ReadString() - { - var sb = new StringBuilder(); - - while (m_Reader.TryRead(out var c)) - sb.Append((char)c); - - return sb.ToString(); - } - - public string ReadStringSafe() - { - var sb = new StringBuilder(); - - while (m_Reader.TryRead(out var c)) - if (IsSafeChar(c)) - sb.Append((char)c); - - return sb.ToString(); - } - - public string ReadUnicodeStringSafe(int fixedLength) - { - var sb = new StringBuilder(); - - while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0) - if (IsSafeChar(c)) - sb.Append((char)c); - - if (fixedLength > 0) - m_Reader.Advance(fixedLength * 2); - - return sb.ToString(); - } - - public string ReadUnicodeString(int fixedLength) - { - var sb = new StringBuilder(); - - while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0) - sb.Append((char)c); - - if (fixedLength > 0) - m_Reader.Advance(fixedLength * 2); - - return sb.ToString(); - } - - public string ReadStringSafe(int fixedLength) - { - var sb = new StringBuilder(); - - while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0) - if (IsSafeChar(c)) - sb.Append((char)c); - - if (fixedLength > 0) - m_Reader.Advance(fixedLength); - - return sb.ToString(); - } - - public string ReadString(int fixedLength) - { - var sb = new StringBuilder(); - - while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0) - sb.Append((char)c); - - if (fixedLength > 0) - m_Reader.Advance(fixedLength); - - return sb.ToString(); - } - } -} +/*************************************************************************** + * PacketReader.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Buffers; +using System.IO; +using System.Text; + +namespace Server.Network +{ + public ref struct PacketReader + { + private SequenceReader m_Reader; + + public SequencePosition Position => m_Reader.Position; + public long Length => m_Reader.Length; + public long Consumed => m_Reader.Consumed; + public long Remaining => m_Reader.Remaining; + + public PacketReader(ReadOnlySequence seq) => m_Reader = new SequenceReader(seq); + + public byte Peek() => m_Reader.TryPeek(out var value) ? value : (byte)0; + + public void Trace(NetState state) + { + try + { + using var sw = new StreamWriter("Packets.log", true); + var buffer = m_Reader.Sequence.ToArray(); + + if (buffer.Length > 0) + sw.WriteLine("Client: {0}: Unhandled packet 0x{1:X2}", state, buffer[0]); + + using (var ms = new MemoryStream(buffer)) + { + Utility.FormatBuffer(sw, ms, buffer.Length); + } + + sw.WriteLine(); + sw.WriteLine(); + } + catch + { + // ignored + } + } + + public SequencePosition Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + if (offset < m_Reader.Consumed) + m_Reader.Rewind(m_Reader.Consumed - Math.Max(offset, 0L)); + else + m_Reader.Advance(offset - m_Reader.Consumed); + break; + case SeekOrigin.Current: + if (offset < 0) + m_Reader.Rewind(Math.Min(m_Reader.Consumed, offset * -1)); + else + m_Reader.Advance(Math.Min(m_Reader.Remaining, offset)); + break; + case SeekOrigin.End: + var count = m_Reader.Remaining - offset; + if (count < 0) + m_Reader.Rewind(count * -1); + else if (count > 0) + m_Reader.Advance(count); + break; + } + + return m_Reader.Position; + } + + public bool TryReadByte(out byte value) => m_Reader.TryRead(out value); + + public int ReadInt32() => m_Reader.TryReadBigEndian(out int value) ? value : 0; + + public short ReadInt16() => m_Reader.TryReadBigEndian(out short value) ? value : (short)0; + + public byte ReadByte() => m_Reader.TryRead(out var value) ? value : (byte)0; + + public uint ReadUInt32() => (uint)ReadInt32(); + + public ushort ReadUInt16() => (ushort)ReadInt16(); + + public sbyte ReadSByte() => (sbyte)ReadByte(); + + public bool ReadBoolean() => ReadByte() > 0; + + public string ReadUnicodeStringLE() + { + var sb = new StringBuilder(); + + while (m_Reader.TryReadLittleEndian(out short c) && c != 0) + sb.Append((char)c); + + return sb.ToString(); + } + + public string ReadUnicodeStringLE(int fixedLength) + { + var sb = new StringBuilder(); + + while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0) + sb.Append((char)c); + + if (fixedLength > 0) + m_Reader.Advance(fixedLength); + + return sb.ToString(); + } + + public string ReadUnicodeStringLESafe(int fixedLength) + { + var sb = new StringBuilder(); + + while (fixedLength-- > 0 && m_Reader.TryReadLittleEndian(out short c) && c != 0) + if (IsSafeChar(c)) + sb.Append((char)c); + + if (fixedLength > 0) + m_Reader.Advance(fixedLength * 2); + + return sb.ToString(); + } + + public string ReadUnicodeStringLESafe() + { + var sb = new StringBuilder(); + + while (m_Reader.TryReadLittleEndian(out short c) && c != 0) + if (IsSafeChar(c)) + sb.Append((char)c); + + return sb.ToString(); + } + + public string ReadUnicodeStringSafe() + { + var sb = new StringBuilder(); + + while (m_Reader.TryReadBigEndian(out short c) && c != 0) + if (IsSafeChar(c)) + sb.Append((char)c); + + return sb.ToString(); + } + + public string ReadUnicodeString() + { + var sb = new StringBuilder(); + + while (m_Reader.TryReadBigEndian(out short c) && c != 0) + sb.Append((char)c); + + return sb.ToString(); + } + + private static bool IsSafeChar(int c) => c >= 0x20 && c < 0xFFFE; + + public string ReadUTF8StringSafe(int fixedLength) + { + string s; + + if (m_Reader.TryReadTo(out ReadOnlySpan span, (byte)'\0')) + { + s = Utility.UTF8.GetString(span.Length > fixedLength ? span.Slice(0, fixedLength) : span); + } + else + { + var size = Math.Min(m_Reader.Remaining, fixedLength); + s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, size).ToArray()); + m_Reader.Advance(size); + } + + var sb = new StringBuilder(s.Length); + + for (var i = 0; i < s.Length; ++i) + if (IsSafeChar(s[i])) + sb.Append(s[i]); + + return sb.ToString(); + } + + public string ReadUTF8StringSafe() + { + string s; + + if (m_Reader.TryReadTo(out ReadOnlySpan span, (byte)'\0')) + { + s = Utility.UTF8.GetString(span); + } + else + { + s = Utility.UTF8.GetString(m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray()); + m_Reader.Advance(m_Reader.Remaining); + } + + var sb = new StringBuilder(s.Length); + + for (var i = 0; i < s.Length; ++i) + if (IsSafeChar(s[i])) + sb.Append(s[i]); + + return sb.ToString(); + } + + public string ReadUTF8String() => + Utility.UTF8.GetString( + m_Reader.TryReadTo(out ReadOnlySpan span, (byte)'\0') + ? span + : m_Reader.Sequence.Slice(m_Reader.Position, m_Reader.Remaining).ToArray() + ); + + public string ReadString() + { + var sb = new StringBuilder(); + + while (m_Reader.TryRead(out var c)) + sb.Append((char)c); + + return sb.ToString(); + } + + public string ReadStringSafe() + { + var sb = new StringBuilder(); + + while (m_Reader.TryRead(out var c)) + if (IsSafeChar(c)) + sb.Append((char)c); + + return sb.ToString(); + } + + public string ReadUnicodeStringSafe(int fixedLength) + { + var sb = new StringBuilder(); + + while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0) + if (IsSafeChar(c)) + sb.Append((char)c); + + if (fixedLength > 0) + m_Reader.Advance(fixedLength * 2); + + return sb.ToString(); + } + + public string ReadUnicodeString(int fixedLength) + { + var sb = new StringBuilder(); + + while (fixedLength-- > 0 && m_Reader.TryReadBigEndian(out short c) && c != 0) + sb.Append((char)c); + + if (fixedLength > 0) + m_Reader.Advance(fixedLength * 2); + + return sb.ToString(); + } + + public string ReadStringSafe(int fixedLength) + { + var sb = new StringBuilder(); + + while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0) + if (IsSafeChar(c)) + sb.Append((char)c); + + if (fixedLength > 0) + m_Reader.Advance(fixedLength); + + return sb.ToString(); + } + + public string ReadString(int fixedLength) + { + var sb = new StringBuilder(); + + while (fixedLength-- > 0 && m_Reader.TryRead(out var c) && c != 0) + sb.Append((char)c); + + if (fixedLength > 0) + m_Reader.Advance(fixedLength); + + return sb.ToString(); + } + } +} diff --git a/Projects/Server/Network/PacketWriter.cs b/Projects/Server/Network/PacketWriter.cs index 186907483..ee991ed0e 100644 --- a/Projects/Server/Network/PacketWriter.cs +++ b/Projects/Server/Network/PacketWriter.cs @@ -1,348 +1,372 @@ -/*************************************************************************** - * PacketWriter.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Text; - -namespace Server.Network -{ - /// - /// Provides functionality for writing primitive binary data. - /// - public class PacketWriter - { - private static readonly ConcurrentQueue m_Pool = new ConcurrentQueue(); - - /// - /// Internal format buffer. - /// - private readonly byte[] m_Buffer = new byte[4]; - - private int m_Capacity; - - /// - /// Instantiates a new PacketWriter instance with a given capacity. - /// - /// Initial capacity for the internal stream. - public PacketWriter(int capacity = 32) - { - UnderlyingStream = new MemoryStream(capacity); - m_Capacity = capacity; - } - - /// - /// Gets the total stream length. - /// - public long Length => UnderlyingStream.Length; - - /// - /// Gets or sets the current stream position. - /// - public long Position - { - get => UnderlyingStream.Position; - set => UnderlyingStream.Position = value; - } - - /// - /// The internal stream used by this PacketWriter instance. - /// - public MemoryStream UnderlyingStream { get; private set; } - - public static PacketWriter CreateInstance(int capacity = 32) - { - if (m_Pool.TryDequeue(out var pw)) - { - pw.m_Capacity = capacity; - pw.UnderlyingStream.SetLength(0); - return pw; - } - - return new PacketWriter(capacity); - } - - public static void ReleaseInstance(PacketWriter pw) - { - m_Pool.Enqueue(pw); - } - - /// - /// Writes a 1-byte boolean value to the underlying stream. False is represented by 0, true by 1. - /// - public void Write(bool value) - { - UnderlyingStream.WriteByte((byte)(value ? 1 : 0)); - } - - /// - /// Writes a 1-byte unsigned integer value to the underlying stream. - /// - public void Write(byte value) - { - UnderlyingStream.WriteByte(value); - } - - /// - /// Writes a 1-byte signed integer value to the underlying stream. - /// - public void Write(sbyte value) - { - UnderlyingStream.WriteByte((byte)value); - } - - /// - /// Writes a 2-byte signed integer value to the underlying stream. - /// - public void Write(short value) - { - m_Buffer[0] = (byte)(value >> 8); - m_Buffer[1] = (byte)value; - - UnderlyingStream.Write(m_Buffer, 0, 2); - } - - /// - /// Writes a 2-byte unsigned integer value to the underlying stream. - /// - public void Write(ushort value) - { - m_Buffer[0] = (byte)(value >> 8); - m_Buffer[1] = (byte)value; - - UnderlyingStream.Write(m_Buffer, 0, 2); - } - - /// - /// Writes a 4-byte signed integer value to the underlying stream. - /// - public void Write(int value) - { - m_Buffer[0] = (byte)(value >> 24); - m_Buffer[1] = (byte)(value >> 16); - m_Buffer[2] = (byte)(value >> 8); - m_Buffer[3] = (byte)value; - - UnderlyingStream.Write(m_Buffer, 0, 4); - } - - /// - /// Writes a 4-byte unsigned integer value to the underlying stream. - /// - public void Write(uint value) - { - m_Buffer[0] = (byte)(value >> 24); - m_Buffer[1] = (byte)(value >> 16); - m_Buffer[2] = (byte)(value >> 8); - m_Buffer[3] = (byte)value; - - UnderlyingStream.Write(m_Buffer, 0, 4); - } - - /// - /// Writes a sequence of bytes to the underlying stream - /// - public void Write(byte[] buffer, int offset, int size) - { - UnderlyingStream.Write(buffer, offset, size); - } - - /// - /// Writes a fixed-length ASCII-encoded string value to the underlying stream. To fit (size), the string content is either - /// truncated or padded with null characters. - /// - public void WriteAsciiFixed(string value, int size) - { - if (value == null) - { - Console.WriteLine("Network: Attempted to WriteAsciiFixed() with null value"); - value = string.Empty; - } - - var length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + size); - - if (length >= size) - { - UnderlyingStream.Position += - Encoding.ASCII.GetBytes(value, 0, size, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - } - else - { - Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += size; - } - } - - /// - /// Writes a dynamic-length ASCII-encoded string value to the underlying stream, followed by a 1-byte null character. - /// - public void WriteAsciiNull(string value) - { - if (value == null) - { - Console.WriteLine("Network: Attempted to WriteAsciiNull() with null value"); - value = string.Empty; - } - - var length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + length + 1); - - Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += length + 1; - } - - /// - /// Writes a dynamic-length little-endian unicode string value to the underlying stream, followed by a 2-byte null character. - /// - public void WriteLittleUniNull(string value) - { - if (value == null) - { - Console.WriteLine("Network: Attempted to WriteLittleUniNull() with null value"); - value = string.Empty; - } - - var length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2); - - UnderlyingStream.Position += - Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += 2; - } - - /// - /// Writes a fixed-length little-endian unicode string value to the underlying stream. To fit (size), the string content is - /// either truncated or padded with null characters. - /// - public void WriteLittleUniFixed(string value, int size) - { - if (value == null) - { - Console.WriteLine("Network: Attempted to WriteLittleUniFixed() with null value"); - value = string.Empty; - } - - var length = value.Length; - size *= 2; - - UnderlyingStream.SetLength(UnderlyingStream.Length + size); - - if (length * 2 >= size) - { - UnderlyingStream.Position += - Encoding.Unicode.GetBytes(value, 0, size / 2, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - } - else - { - Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += size; - } - } - - /// - /// Writes a dynamic-length big-endian unicode string value to the underlying stream, followed by a 2-byte null character. - /// - public void WriteBigUniNull(string value) - { - if (value == null) - { - Console.WriteLine("Network: Attempted to WriteBigUniNull() with null value"); - value = string.Empty; - } - - var length = value.Length; - - UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2); - - UnderlyingStream.Position += - Encoding.BigEndianUnicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += 2; - } - - /// - /// Writes a fixed-length big-endian unicode string value to the underlying stream. To fit (size), the string content is - /// either truncated or padded with null characters. - /// - public void WriteBigUniFixed(string value, int size) - { - if (value == null) - { - Console.WriteLine("Network: Attempted to WriteBigUniFixed() with null value"); - value = string.Empty; - } - - var length = value.Length; - size *= 2; - - UnderlyingStream.SetLength(UnderlyingStream.Length + size); - - if (length * 2 >= size) - { - UnderlyingStream.Position += - Encoding.BigEndianUnicode.GetBytes(value, 0, size / 2, UnderlyingStream.GetBuffer(), - (int)UnderlyingStream.Position); - } - else - { - Encoding.BigEndianUnicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); - UnderlyingStream.Position += size; - } - } - - /// - /// Fills the stream from the current position up to (capacity) with 0x00's - /// - public void Fill() - { - Fill(m_Capacity - UnderlyingStream.Length); - } - - /// - /// Writes a number of 0x00 byte values to the underlying stream. - /// - public void Fill(long length) - { - if (UnderlyingStream.Position == UnderlyingStream.Length) - { - UnderlyingStream.SetLength(UnderlyingStream.Length + length); - UnderlyingStream.Seek(0, SeekOrigin.End); - } - else - { - UnderlyingStream.Write(new byte[length], 0, (int)length); - } - } - - /// - /// Offsets the current position from an origin. - /// - public long Seek(long offset, SeekOrigin origin) => UnderlyingStream.Seek(offset, origin); - - /// - /// Gets the entire stream content as a byte array. - /// - public byte[] ToArray() => UnderlyingStream.ToArray(); - } -} +/*************************************************************************** + * PacketWriter.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Text; + +namespace Server.Network +{ + /// + /// Provides functionality for writing primitive binary data. + /// + public class PacketWriter + { + private static readonly ConcurrentQueue m_Pool = new ConcurrentQueue(); + + /// + /// Internal format buffer. + /// + private readonly byte[] m_Buffer = new byte[4]; + + private int m_Capacity; + + /// + /// Instantiates a new PacketWriter instance with a given capacity. + /// + /// Initial capacity for the internal stream. + public PacketWriter(int capacity = 32) + { + UnderlyingStream = new MemoryStream(capacity); + m_Capacity = capacity; + } + + /// + /// Gets the total stream length. + /// + public long Length => UnderlyingStream.Length; + + /// + /// Gets or sets the current stream position. + /// + public long Position + { + get => UnderlyingStream.Position; + set => UnderlyingStream.Position = value; + } + + /// + /// The internal stream used by this PacketWriter instance. + /// + public MemoryStream UnderlyingStream { get; } + + public static PacketWriter CreateInstance(int capacity = 32) + { + if (m_Pool.TryDequeue(out var pw)) + { + pw.m_Capacity = capacity; + pw.UnderlyingStream.SetLength(0); + return pw; + } + + return new PacketWriter(capacity); + } + + public static void ReleaseInstance(PacketWriter pw) + { + m_Pool.Enqueue(pw); + } + + /// + /// Writes a 1-byte boolean value to the underlying stream. False is represented by 0, true by 1. + /// + public void Write(bool value) + { + UnderlyingStream.WriteByte((byte)(value ? 1 : 0)); + } + + /// + /// Writes a 1-byte unsigned integer value to the underlying stream. + /// + public void Write(byte value) + { + UnderlyingStream.WriteByte(value); + } + + /// + /// Writes a 1-byte signed integer value to the underlying stream. + /// + public void Write(sbyte value) + { + UnderlyingStream.WriteByte((byte)value); + } + + /// + /// Writes a 2-byte signed integer value to the underlying stream. + /// + public void Write(short value) + { + m_Buffer[0] = (byte)(value >> 8); + m_Buffer[1] = (byte)value; + + UnderlyingStream.Write(m_Buffer, 0, 2); + } + + /// + /// Writes a 2-byte unsigned integer value to the underlying stream. + /// + public void Write(ushort value) + { + m_Buffer[0] = (byte)(value >> 8); + m_Buffer[1] = (byte)value; + + UnderlyingStream.Write(m_Buffer, 0, 2); + } + + /// + /// Writes a 4-byte signed integer value to the underlying stream. + /// + public void Write(int value) + { + m_Buffer[0] = (byte)(value >> 24); + m_Buffer[1] = (byte)(value >> 16); + m_Buffer[2] = (byte)(value >> 8); + m_Buffer[3] = (byte)value; + + UnderlyingStream.Write(m_Buffer, 0, 4); + } + + /// + /// Writes a 4-byte unsigned integer value to the underlying stream. + /// + public void Write(uint value) + { + m_Buffer[0] = (byte)(value >> 24); + m_Buffer[1] = (byte)(value >> 16); + m_Buffer[2] = (byte)(value >> 8); + m_Buffer[3] = (byte)value; + + UnderlyingStream.Write(m_Buffer, 0, 4); + } + + /// + /// Writes a sequence of bytes to the underlying stream + /// + public void Write(byte[] buffer, int offset, int size) + { + UnderlyingStream.Write(buffer, offset, size); + } + + /// + /// Writes a fixed-length ASCII-encoded string value to the underlying stream. To fit (size), the string content is either + /// truncated or padded with null characters. + /// + public void WriteAsciiFixed(string value, int size) + { + if (value == null) + { + Console.WriteLine("Network: Attempted to WriteAsciiFixed() with null value"); + value = string.Empty; + } + + var length = value.Length; + + UnderlyingStream.SetLength(UnderlyingStream.Length + size); + + if (length >= size) + { + UnderlyingStream.Position += + Encoding.ASCII.GetBytes(value, 0, size, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); + } + else + { + Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); + UnderlyingStream.Position += size; + } + } + + /// + /// Writes a dynamic-length ASCII-encoded string value to the underlying stream, followed by a 1-byte null character. + /// + public void WriteAsciiNull(string value) + { + if (value == null) + { + Console.WriteLine("Network: Attempted to WriteAsciiNull() with null value"); + value = string.Empty; + } + + var length = value.Length; + + UnderlyingStream.SetLength(UnderlyingStream.Length + length + 1); + + Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); + UnderlyingStream.Position += length + 1; + } + + /// + /// Writes a dynamic-length little-endian unicode string value to the underlying stream, followed by a 2-byte null + /// character. + /// + public void WriteLittleUniNull(string value) + { + if (value == null) + { + Console.WriteLine("Network: Attempted to WriteLittleUniNull() with null value"); + value = string.Empty; + } + + var length = value.Length; + + UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2); + + UnderlyingStream.Position += + Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); + UnderlyingStream.Position += 2; + } + + /// + /// Writes a fixed-length little-endian unicode string value to the underlying stream. To fit (size), the string content is + /// either truncated or padded with null characters. + /// + public void WriteLittleUniFixed(string value, int size) + { + if (value == null) + { + Console.WriteLine("Network: Attempted to WriteLittleUniFixed() with null value"); + value = string.Empty; + } + + var length = value.Length; + size *= 2; + + UnderlyingStream.SetLength(UnderlyingStream.Length + size); + + if (length * 2 >= size) + { + UnderlyingStream.Position += + Encoding.Unicode.GetBytes( + value, + 0, + size / 2, + UnderlyingStream.GetBuffer(), + (int)UnderlyingStream.Position + ); + } + else + { + Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position); + UnderlyingStream.Position += size; + } + } + + /// + /// Writes a dynamic-length big-endian unicode string value to the underlying stream, followed by a 2-byte null character. + /// + public void WriteBigUniNull(string value) + { + if (value == null) + { + Console.WriteLine("Network: Attempted to WriteBigUniNull() with null value"); + value = string.Empty; + } + + var length = value.Length; + + UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2); + + UnderlyingStream.Position += + Encoding.BigEndianUnicode.GetBytes( + value, + 0, + length, + UnderlyingStream.GetBuffer(), + (int)UnderlyingStream.Position + ); + UnderlyingStream.Position += 2; + } + + /// + /// Writes a fixed-length big-endian unicode string value to the underlying stream. To fit (size), the string content is + /// either truncated or padded with null characters. + /// + public void WriteBigUniFixed(string value, int size) + { + if (value == null) + { + Console.WriteLine("Network: Attempted to WriteBigUniFixed() with null value"); + value = string.Empty; + } + + var length = value.Length; + size *= 2; + + UnderlyingStream.SetLength(UnderlyingStream.Length + size); + + if (length * 2 >= size) + { + UnderlyingStream.Position += + Encoding.BigEndianUnicode.GetBytes( + value, + 0, + size / 2, + UnderlyingStream.GetBuffer(), + (int)UnderlyingStream.Position + ); + } + else + { + Encoding.BigEndianUnicode.GetBytes( + value, + 0, + length, + UnderlyingStream.GetBuffer(), + (int)UnderlyingStream.Position + ); + UnderlyingStream.Position += size; + } + } + + /// + /// Fills the stream from the current position up to (capacity) with 0x00's + /// + public void Fill() + { + Fill(m_Capacity - UnderlyingStream.Length); + } + + /// + /// Writes a number of 0x00 byte values to the underlying stream. + /// + public void Fill(long length) + { + if (UnderlyingStream.Position == UnderlyingStream.Length) + { + UnderlyingStream.SetLength(UnderlyingStream.Length + length); + UnderlyingStream.Seek(0, SeekOrigin.End); + } + else + { + UnderlyingStream.Write(new byte[length], 0, (int)length); + } + } + + /// + /// Offsets the current position from an origin. + /// + public long Seek(long offset, SeekOrigin origin) => UnderlyingStream.Seek(offset, origin); + + /// + /// Gets the entire stream content as a byte array. + /// + public byte[] ToArray() => UnderlyingStream.ToArray(); + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/AccountPackets.cs b/Projects/Server/Network/Packets/Old Packets/AccountPackets.cs index 9ec0f2d7b..1bcd7f874 100644 --- a/Projects/Server/Network/Packets/Old Packets/AccountPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/AccountPackets.cs @@ -1,381 +1,381 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: AccountPackets.cs - Created: 2020/05/08 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Server.Accounting; - -namespace Server.Network -{ - public enum ALRReason : byte - { - Invalid = 0x00, - InUse = 0x01, - Blocked = 0x02, - BadPass = 0x03, - Idle = 0xFE, - BadComm = 0xFF - } - - public enum PMMessage : byte - { - CharNoExist = 1, - CharExists = 2, - CharInWorld = 5, - LoginSyncError = 6, - IdleWarning = 7 - } - - public enum DeleteResultType - { - PasswordInvalid, - CharNotExist, - CharBeingPlayed, - CharTooYoung, - CharQueued, - BadRequest - } - - public sealed class ChangeCharacter : Packet - { - public ChangeCharacter(IAccount a) : base(0x81) - { - EnsureCapacity(305); - - var count = 0; - - for (var i = 0; i < a.Length; ++i) - if (a[i] != null) - ++count; - - Stream.Write((byte)count); - Stream.Write((byte)0); - - for (var i = 0; i < a.Length; ++i) - if (a[i] != null) - { - var name = a[i].Name; - - if (name == null) - name = "-null-"; - else if ((name = name.Trim()).Length == 0) - name = "-empty-"; - - Stream.WriteAsciiFixed(name, 30); - Stream.Fill(30); // password - } - else - { - Stream.Fill(60); - } - } - } - - /// - /// Asks the client for it's version - /// - public sealed class ClientVersionReq : Packet - { - public ClientVersionReq() : base(0xBD) - { - EnsureCapacity(3); - } - } - - public sealed class DeleteResult : Packet - { - public DeleteResult(DeleteResultType res) : base(0x85, 2) - { - Stream.Write((byte)res); - } - } - - public sealed class PopupMessage : Packet - { - public PopupMessage(PMMessage msg) : base(0x53, 2) - { - Stream.Write((byte)msg); - } - } - - public sealed class SupportedFeatures : Packet - { - public SupportedFeatures(NetState ns) : base(0xB9, ns.ExtendedSupportedFeatures ? 5 : 3) - { - var flags = ExpansionInfo.CoreExpansion.SupportedFeatures; - - flags |= Value; - - if (ns.Account.Limit >= 6) - { - flags |= FeatureFlags.LiveAccount; - flags &= ~FeatureFlags.UOTD; - - if (ns.Account.Limit > 6) - flags |= FeatureFlags.SeventhCharacterSlot; - else - flags |= FeatureFlags.SixthCharacterSlot; - } - - if (ns.ExtendedSupportedFeatures) - Stream.Write((uint)flags); - else - Stream.Write((ushort)flags); - } - - public static FeatureFlags Value { get; set; } - - public static SupportedFeatures Instantiate(NetState ns) => new SupportedFeatures(ns); - } - - public sealed class LoginConfirm : Packet - { - public LoginConfirm(Mobile m) : base(0x1B, 37) - { - Stream.Write(m.Serial); - Stream.Write(0); - Stream.Write((short)m.Body); - Stream.Write((short)m.X); - Stream.Write((short)m.Y); - Stream.Write((short)m.Z); - Stream.Write((byte)m.Direction); - Stream.Write((byte)0); - Stream.Write(-1); - - var map = m.Map; - - if (map == null || map == Map.Internal) - map = m.LogoutMap; - - Stream.Write((short)0); - Stream.Write((short)0); - Stream.Write((short)(map?.Width ?? Map.Felucca.Width)); - Stream.Write((short)(map?.Height ?? Map.Felucca.Height)); - - Stream.Fill(); - } - } - - public sealed class LoginComplete : Packet - { - public static readonly Packet Instance = SetStatic(new LoginComplete()); - - public LoginComplete() : base(0x55, 1) - { - } - } - - public sealed class CharacterListUpdate : Packet - { - public CharacterListUpdate(IAccount a) : base(0x86) - { - EnsureCapacity(4 + a.Length * 60); - - var highSlot = -1; - - for (var i = 0; i < a.Length; ++i) - if (a[i] != null) - highSlot = i; - - var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5); - - Stream.Write((byte)count); - - for (var i = 0; i < count; ++i) - { - var m = a[i]; - - if (m != null) - { - Stream.WriteAsciiFixed(m.Name, 30); - Stream.Fill(30); // password - } - else - { - Stream.Fill(60); - } - } - } - } - - public sealed class CharacterList : Packet - { - public CharacterList(IAccount a, CityInfo[] info) : base(0xA9) - { - EnsureCapacity(11 + a.Length * 60 + info.Length * 89); - - var highSlot = -1; - - for (var i = 0; i < a.Length; ++i) - if (a[i] != null) - highSlot = i; - - var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5); - - Stream.Write((byte)count); - - for (var i = 0; i < count; ++i) - if (a[i] != null) - { - Stream.WriteAsciiFixed(a[i].Name, 30); - Stream.Fill(30); // password - } - else - { - Stream.Fill(60); - } - - Stream.Write((byte)info.Length); - - for (var i = 0; i < info.Length; ++i) - { - var ci = info[i]; - - Stream.Write((byte)i); - Stream.WriteAsciiFixed(ci.City, 32); - Stream.WriteAsciiFixed(ci.Building, 32); - Stream.Write(ci.X); - Stream.Write(ci.Y); - Stream.Write(ci.Z); - Stream.Write(ci.Map.MapID); - Stream.Write(ci.Description); - Stream.Write(0); - } - - var flags = ExpansionInfo.CoreExpansion.CharacterListFlags; - - if (count > 6) - flags |= CharacterListFlags.SeventhCharacterSlot | - CharacterListFlags.SixthCharacterSlot; // 7th Character Slot - TODO: Is SixthCharacterSlot Required? - else if (count == 6) - flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot - else if (a.Limit == 1) - flags |= CharacterListFlags.SlotLimit & - CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character - - Stream.Write((int)(flags | AdditionalFlags)); // Additional Flags - - Stream.Write((short)-1); - } - - public static CharacterListFlags AdditionalFlags { get; set; } - } - - public sealed class CharacterListOld : Packet - { - public CharacterListOld(IAccount a, CityInfo[] info) : base(0xA9) - { - EnsureCapacity(9 + a.Length * 60 + info.Length * 63); - - var highSlot = -1; - - for (var i = 0; i < a.Length; ++i) - if (a[i] != null) - highSlot = i; - - var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5); - - Stream.Write((byte)count); - - for (var i = 0; i < count; ++i) - if (a[i] != null) - { - Stream.WriteAsciiFixed(a[i].Name, 30); - Stream.Fill(30); // password - } - else - { - Stream.Fill(60); - } - - Stream.Write((byte)info.Length); - - for (var i = 0; i < info.Length; ++i) - { - var ci = info[i]; - - Stream.Write((byte)i); - Stream.WriteAsciiFixed(ci.City, 31); - Stream.WriteAsciiFixed(ci.Building, 31); - } - - var flags = ExpansionInfo.CoreExpansion.CharacterListFlags; - - if (count > 6) - flags |= CharacterListFlags.SeventhCharacterSlot | - CharacterListFlags.SixthCharacterSlot; // 7th Character Slot - TODO: Is SixthCharacterSlot Required? - else if (count == 6) - flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot - else if (a.Limit == 1) - flags |= CharacterListFlags.SlotLimit & - CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character - - Stream.Write((int)(flags | CharacterList.AdditionalFlags)); // Additional Flags - } - } - - public sealed class AccountLoginRej : Packet - { - public AccountLoginRej(ALRReason reason) : base(0x82, 2) - { - Stream.Write((byte)reason); - } - } - - public sealed class AccountLoginAck : Packet - { - public AccountLoginAck(ServerInfo[] info) : base(0xA8) - { - EnsureCapacity(6 + info.Length * 40); - - Stream.Write((byte)0x5D); // Unknown - - Stream.Write((ushort)info.Length); - - for (var i = 0; i < info.Length; ++i) - { - var si = info[i]; - - Stream.Write((ushort)i); - Stream.WriteAsciiFixed(si.Name, 32); - Stream.Write((byte)si.FullPercent); - Stream.Write((sbyte)si.TimeZone); - Stream.Write(Utility.GetAddressValue(si.Address.Address)); - } - } - } - - public sealed class PlayServerAck : Packet - { - internal static int m_AuthID = -1; - - public PlayServerAck(ServerInfo si) : base(0x8C, 11) - { - var addr = Utility.GetAddressValue(si.Address.Address); - - Stream.Write((byte)addr); - Stream.Write((byte)(addr >> 8)); - Stream.Write((byte)(addr >> 16)); - Stream.Write((byte)(addr >> 24)); - - Stream.Write((short)si.Address.Port); - Stream.Write(m_AuthID); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AccountPackets.cs - Created: 2020/05/08 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.Accounting; + +namespace Server.Network +{ + public enum ALRReason : byte + { + Invalid = 0x00, + InUse = 0x01, + Blocked = 0x02, + BadPass = 0x03, + Idle = 0xFE, + BadComm = 0xFF + } + + public enum PMMessage : byte + { + CharNoExist = 1, + CharExists = 2, + CharInWorld = 5, + LoginSyncError = 6, + IdleWarning = 7 + } + + public enum DeleteResultType + { + PasswordInvalid, + CharNotExist, + CharBeingPlayed, + CharTooYoung, + CharQueued, + BadRequest + } + + public sealed class ChangeCharacter : Packet + { + public ChangeCharacter(IAccount a) : base(0x81) + { + EnsureCapacity(305); + + var count = 0; + + for (var i = 0; i < a.Length; ++i) + if (a[i] != null) + ++count; + + Stream.Write((byte)count); + Stream.Write((byte)0); + + for (var i = 0; i < a.Length; ++i) + if (a[i] != null) + { + var name = a[i].Name; + + if (name == null) + name = "-null-"; + else if ((name = name.Trim()).Length == 0) + name = "-empty-"; + + Stream.WriteAsciiFixed(name, 30); + Stream.Fill(30); // password + } + else + { + Stream.Fill(60); + } + } + } + + /// + /// Asks the client for it's version + /// + public sealed class ClientVersionReq : Packet + { + public ClientVersionReq() : base(0xBD) + { + EnsureCapacity(3); + } + } + + public sealed class DeleteResult : Packet + { + public DeleteResult(DeleteResultType res) : base(0x85, 2) + { + Stream.Write((byte)res); + } + } + + public sealed class PopupMessage : Packet + { + public PopupMessage(PMMessage msg) : base(0x53, 2) + { + Stream.Write((byte)msg); + } + } + + public sealed class SupportedFeatures : Packet + { + public SupportedFeatures(NetState ns) : base(0xB9, ns.ExtendedSupportedFeatures ? 5 : 3) + { + var flags = ExpansionInfo.CoreExpansion.SupportedFeatures; + + flags |= Value; + + if (ns.Account.Limit >= 6) + { + flags |= FeatureFlags.LiveAccount; + flags &= ~FeatureFlags.UOTD; + + if (ns.Account.Limit > 6) + flags |= FeatureFlags.SeventhCharacterSlot; + else + flags |= FeatureFlags.SixthCharacterSlot; + } + + if (ns.ExtendedSupportedFeatures) + Stream.Write((uint)flags); + else + Stream.Write((ushort)flags); + } + + public static FeatureFlags Value { get; set; } + + public static SupportedFeatures Instantiate(NetState ns) => new SupportedFeatures(ns); + } + + public sealed class LoginConfirm : Packet + { + public LoginConfirm(Mobile m) : base(0x1B, 37) + { + Stream.Write(m.Serial); + Stream.Write(0); + Stream.Write((short)m.Body); + Stream.Write((short)m.X); + Stream.Write((short)m.Y); + Stream.Write((short)m.Z); + Stream.Write((byte)m.Direction); + Stream.Write((byte)0); + Stream.Write(-1); + + var map = m.Map; + + if (map == null || map == Map.Internal) + map = m.LogoutMap; + + Stream.Write((short)0); + Stream.Write((short)0); + Stream.Write((short)(map?.Width ?? Map.Felucca.Width)); + Stream.Write((short)(map?.Height ?? Map.Felucca.Height)); + + Stream.Fill(); + } + } + + public sealed class LoginComplete : Packet + { + public static readonly Packet Instance = SetStatic(new LoginComplete()); + + public LoginComplete() : base(0x55, 1) + { + } + } + + public sealed class CharacterListUpdate : Packet + { + public CharacterListUpdate(IAccount a) : base(0x86) + { + EnsureCapacity(4 + a.Length * 60); + + var highSlot = -1; + + for (var i = 0; i < a.Length; ++i) + if (a[i] != null) + highSlot = i; + + var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5); + + Stream.Write((byte)count); + + for (var i = 0; i < count; ++i) + { + var m = a[i]; + + if (m != null) + { + Stream.WriteAsciiFixed(m.Name, 30); + Stream.Fill(30); // password + } + else + { + Stream.Fill(60); + } + } + } + } + + public sealed class CharacterList : Packet + { + public CharacterList(IAccount a, CityInfo[] info) : base(0xA9) + { + EnsureCapacity(11 + a.Length * 60 + info.Length * 89); + + var highSlot = -1; + + for (var i = 0; i < a.Length; ++i) + if (a[i] != null) + highSlot = i; + + var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5); + + Stream.Write((byte)count); + + for (var i = 0; i < count; ++i) + if (a[i] != null) + { + Stream.WriteAsciiFixed(a[i].Name, 30); + Stream.Fill(30); // password + } + else + { + Stream.Fill(60); + } + + Stream.Write((byte)info.Length); + + for (var i = 0; i < info.Length; ++i) + { + var ci = info[i]; + + Stream.Write((byte)i); + Stream.WriteAsciiFixed(ci.City, 32); + Stream.WriteAsciiFixed(ci.Building, 32); + Stream.Write(ci.X); + Stream.Write(ci.Y); + Stream.Write(ci.Z); + Stream.Write(ci.Map.MapID); + Stream.Write(ci.Description); + Stream.Write(0); + } + + var flags = ExpansionInfo.CoreExpansion.CharacterListFlags; + + if (count > 6) + flags |= CharacterListFlags.SeventhCharacterSlot | + CharacterListFlags.SixthCharacterSlot; // 7th Character Slot - TODO: Is SixthCharacterSlot Required? + else if (count == 6) + flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot + else if (a.Limit == 1) + flags |= CharacterListFlags.SlotLimit & + CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character + + Stream.Write((int)(flags | AdditionalFlags)); // Additional Flags + + Stream.Write((short)-1); + } + + public static CharacterListFlags AdditionalFlags { get; set; } + } + + public sealed class CharacterListOld : Packet + { + public CharacterListOld(IAccount a, CityInfo[] info) : base(0xA9) + { + EnsureCapacity(9 + a.Length * 60 + info.Length * 63); + + var highSlot = -1; + + for (var i = 0; i < a.Length; ++i) + if (a[i] != null) + highSlot = i; + + var count = Math.Max(Math.Max(highSlot + 1, a.Limit), 5); + + Stream.Write((byte)count); + + for (var i = 0; i < count; ++i) + if (a[i] != null) + { + Stream.WriteAsciiFixed(a[i].Name, 30); + Stream.Fill(30); // password + } + else + { + Stream.Fill(60); + } + + Stream.Write((byte)info.Length); + + for (var i = 0; i < info.Length; ++i) + { + var ci = info[i]; + + Stream.Write((byte)i); + Stream.WriteAsciiFixed(ci.City, 31); + Stream.WriteAsciiFixed(ci.Building, 31); + } + + var flags = ExpansionInfo.CoreExpansion.CharacterListFlags; + + if (count > 6) + flags |= CharacterListFlags.SeventhCharacterSlot | + CharacterListFlags.SixthCharacterSlot; // 7th Character Slot - TODO: Is SixthCharacterSlot Required? + else if (count == 6) + flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot + else if (a.Limit == 1) + flags |= CharacterListFlags.SlotLimit & + CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character + + Stream.Write((int)(flags | CharacterList.AdditionalFlags)); // Additional Flags + } + } + + public sealed class AccountLoginRej : Packet + { + public AccountLoginRej(ALRReason reason) : base(0x82, 2) + { + Stream.Write((byte)reason); + } + } + + public sealed class AccountLoginAck : Packet + { + public AccountLoginAck(ServerInfo[] info) : base(0xA8) + { + EnsureCapacity(6 + info.Length * 40); + + Stream.Write((byte)0x5D); // Unknown + + Stream.Write((ushort)info.Length); + + for (var i = 0; i < info.Length; ++i) + { + var si = info[i]; + + Stream.Write((ushort)i); + Stream.WriteAsciiFixed(si.Name, 32); + Stream.Write((byte)si.FullPercent); + Stream.Write((sbyte)si.TimeZone); + Stream.Write(Utility.GetAddressValue(si.Address.Address)); + } + } + } + + public sealed class PlayServerAck : Packet + { + internal static int m_AuthID = -1; + + public PlayServerAck(ServerInfo si) : base(0x8C, 11) + { + var addr = Utility.GetAddressValue(si.Address.Address); + + Stream.Write((byte)addr); + Stream.Write((byte)(addr >> 8)); + Stream.Write((byte)(addr >> 16)); + Stream.Write((byte)(addr >> 24)); + + Stream.Write((short)si.Address.Port); + Stream.Write(m_AuthID); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/ArrowPackets.cs b/Projects/Server/Network/Packets/Old Packets/ArrowPackets.cs index ca6fe8cfb..1e8b8e5d9 100644 --- a/Projects/Server/Network/Packets/Old Packets/ArrowPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/ArrowPackets.cs @@ -1,64 +1,64 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ArrowPackets.cs - Created: 2020/05/03 - Updated: 2020/05/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public sealed class CancelArrow : Packet - { - public CancelArrow() : base(0xBA, 6) - { - Stream.Write((byte)0); - Stream.Write((short)-1); - Stream.Write((short)-1); - } - } - - public sealed class SetArrow : Packet - { - public SetArrow(int x, int y) : base(0xBA, 6) - { - Stream.Write((byte)1); - Stream.Write((short)x); - Stream.Write((short)y); - } - } - - public sealed class CancelArrowHS : Packet - { - public CancelArrowHS(int x, int y, Serial s) : base(0xBA, 10) - { - Stream.Write((byte)0); - Stream.Write((short)x); - Stream.Write((short)y); - Stream.Write(s); - } - } - - public sealed class SetArrowHS : Packet - { - public SetArrowHS(int x, int y, Serial s) : base(0xBA, 10) - { - Stream.Write((byte)1); - Stream.Write((short)x); - Stream.Write((short)y); - Stream.Write(s); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ArrowPackets.cs - Created: 2020/05/03 - Updated: 2020/05/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public sealed class CancelArrow : Packet + { + public CancelArrow() : base(0xBA, 6) + { + Stream.Write((byte)0); + Stream.Write((short)-1); + Stream.Write((short)-1); + } + } + + public sealed class SetArrow : Packet + { + public SetArrow(int x, int y) : base(0xBA, 6) + { + Stream.Write((byte)1); + Stream.Write((short)x); + Stream.Write((short)y); + } + } + + public sealed class CancelArrowHS : Packet + { + public CancelArrowHS(int x, int y, Serial s) : base(0xBA, 10) + { + Stream.Write((byte)0); + Stream.Write((short)x); + Stream.Write((short)y); + Stream.Write(s); + } + } + + public sealed class SetArrowHS : Packet + { + public SetArrowHS(int x, int y, Serial s) : base(0xBA, 10) + { + Stream.Write((byte)1); + Stream.Write((short)x); + Stream.Write((short)y); + Stream.Write(s); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/AttributeNormalizer.cs b/Projects/Server/Network/Packets/Old Packets/AttributeNormalizer.cs index b9a34445a..15b4fbae9 100644 --- a/Projects/Server/Network/Packets/Old Packets/AttributeNormalizer.cs +++ b/Projects/Server/Network/Packets/Old Packets/AttributeNormalizer.cs @@ -1,58 +1,58 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: AttributeNormalizer.cs * - * Created: 2020/06/24 - Updated: 2020/06/24 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public static class AttributeNormalizer - { - public static int Maximum { get; set; } = 25; - - public static bool Enabled { get; set; } = true; - - public static void Write(PacketWriter stream, int cur, int max) - { - if (Enabled && max != 0) - { - stream.Write((short)Maximum); - stream.Write((short)(cur * Maximum / max)); - } - else - { - stream.Write((short)max); - stream.Write((short)cur); - } - } - - public static void WriteReverse(PacketWriter stream, int cur, int max) - { - if (Enabled && max != 0) - { - stream.Write((short)(cur * Maximum / max)); - stream.Write((short)Maximum); - } - else - { - stream.Write((short)cur); - stream.Write((short)max); - } - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AttributeNormalizer.cs * + * Created: 2020/06/24 - Updated: 2020/06/24 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public static class AttributeNormalizer + { + public static int Maximum { get; set; } = 25; + + public static bool Enabled { get; set; } = true; + + public static void Write(PacketWriter stream, int cur, int max) + { + if (Enabled && max != 0) + { + stream.Write((short)Maximum); + stream.Write((short)(cur * Maximum / max)); + } + else + { + stream.Write((short)max); + stream.Write((short)cur); + } + } + + public static void WriteReverse(PacketWriter stream, int cur, int max) + { + if (Enabled && max != 0) + { + stream.Write((short)(cur * Maximum / max)); + stream.Write((short)Maximum); + } + else + { + stream.Write((short)cur); + stream.Write((short)max); + } + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/CombatPackets.cs b/Projects/Server/Network/Packets/Old Packets/CombatPackets.cs index a2fb9a220..9a37dd44a 100644 --- a/Projects/Server/Network/Packets/Old Packets/CombatPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/CombatPackets.cs @@ -1,56 +1,56 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: CombatPackets.cs - Created: 2020/06/25 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public sealed class Swing : Packet - { - public Swing(Serial attacker, Serial defender) : base(0x2F, 10) - { - Stream.Write((byte)0); - Stream.Write(attacker); - Stream.Write(defender); - } - } - - public sealed class SetWarMode : Packet - { - public static readonly Packet InWarMode = SetStatic(new SetWarMode(true)); - public static readonly Packet InPeaceMode = SetStatic(new SetWarMode(false)); - - public SetWarMode(bool mode) : base(0x72, 5) - { - Stream.Write(mode); - Stream.Write((byte)0x00); - Stream.Write((byte)0x32); - Stream.Write((byte)0x00); - } - - public static Packet Instantiate(bool mode) => mode ? InWarMode : InPeaceMode; - } - - public sealed class ChangeCombatant : Packet - { - public ChangeCombatant(Serial combatant) : base(0xAA, 5) - { - Stream.Write(combatant); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: CombatPackets.cs - Created: 2020/06/25 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public sealed class Swing : Packet + { + public Swing(Serial attacker, Serial defender) : base(0x2F, 10) + { + Stream.Write((byte)0); + Stream.Write(attacker); + Stream.Write(defender); + } + } + + public sealed class SetWarMode : Packet + { + public static readonly Packet InWarMode = SetStatic(new SetWarMode(true)); + public static readonly Packet InPeaceMode = SetStatic(new SetWarMode(false)); + + public SetWarMode(bool mode) : base(0x72, 5) + { + Stream.Write(mode); + Stream.Write((byte)0x00); + Stream.Write((byte)0x32); + Stream.Write((byte)0x00); + } + + public static Packet Instantiate(bool mode) => mode ? InWarMode : InPeaceMode; + } + + public sealed class ChangeCombatant : Packet + { + public ChangeCombatant(Serial combatant) : base(0xAA, 5) + { + Stream.Write(combatant); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/DamagePackets.cs b/Projects/Server/Network/Packets/Old Packets/DamagePackets.cs index 7b7486f1c..960bd4870 100644 --- a/Projects/Server/Network/Packets/Old Packets/DamagePackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/DamagePackets.cs @@ -1,48 +1,48 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: DamagePackets.cs - Created: 2020/05/03 - Updated: 2020/05/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server.Network -{ - public sealed class DamagePacketOld : Packet - { - public DamagePacketOld(Serial mobile, int amount) : base(0xBF) - { - EnsureCapacity(11); - - Stream.Write((short)0x22); - Stream.Write((byte)1); - Stream.Write(mobile); - - Stream.Write((byte)Math.Clamp(amount, 0, 255)); - } - } - - public sealed class DamagePacket : Packet - { - public DamagePacket(Serial mobile, int amount) : base(0x0B, 7) - { - Stream.Write(mobile); - - Stream.Write((ushort)Math.Clamp(amount, 0, 0xFFFF)); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: DamagePackets.cs - Created: 2020/05/03 - Updated: 2020/05/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server.Network +{ + public sealed class DamagePacketOld : Packet + { + public DamagePacketOld(Serial mobile, int amount) : base(0xBF) + { + EnsureCapacity(11); + + Stream.Write((short)0x22); + Stream.Write((byte)1); + Stream.Write(mobile); + + Stream.Write((byte)Math.Clamp(amount, 0, 255)); + } + } + + public sealed class DamagePacket : Packet + { + public DamagePacket(Serial mobile, int amount) : base(0x0B, 7) + { + Stream.Write(mobile); + + Stream.Write((ushort)Math.Clamp(amount, 0, 0xFFFF)); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/DisplayHuePicker.cs b/Projects/Server/Network/Packets/Old Packets/DisplayHuePicker.cs index 7125b1376..f3bba6b5c 100644 --- a/Projects/Server/Network/Packets/Old Packets/DisplayHuePicker.cs +++ b/Projects/Server/Network/Packets/Old Packets/DisplayHuePicker.cs @@ -1,35 +1,35 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: DisplayHuePicker.cs * - * Created: 2020/05/08 - Updated: 2020/05/08 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using Server.HuePickers; - -namespace Server.Network -{ - public sealed class DisplayHuePicker : Packet - { - public DisplayHuePicker(HuePicker huePicker) : base(0x95, 9) - { - Stream.Write(huePicker.Serial); - Stream.Write((short)0); - Stream.Write((short)huePicker.ItemID); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: DisplayHuePicker.cs * + * Created: 2020/05/08 - Updated: 2020/05/08 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Server.HuePickers; + +namespace Server.Network +{ + public sealed class DisplayHuePicker : Packet + { + public DisplayHuePicker(HuePicker huePicker) : base(0x95, 9) + { + Stream.Write(huePicker.Serial); + Stream.Write((short)0); + Stream.Write((short)huePicker.ItemID); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/EffectPackets.cs b/Projects/Server/Network/Packets/Old Packets/EffectPackets.cs index 03d348df6..22cee8148 100644 --- a/Projects/Server/Network/Packets/Old Packets/EffectPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/EffectPackets.cs @@ -1,292 +1,396 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: EffectPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public enum EffectType - { - Moving, - Lightning, - FixedXYZ, - FixedFrom - } - - public class ParticleEffect : Packet - { - public ParticleEffect(EffectType type, Serial from, Serial to, int itemID, Point3D fromPoint, Point3D toPoint, - int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode, int effect, - int explodeEffect, int explodeSound, Serial serial, int layer, int unknown) : base(0xC7, 49) - { - Stream.Write((byte)type); - Stream.Write(from); - Stream.Write(to); - Stream.Write((short)itemID); - Stream.Write((short)fromPoint.m_X); - Stream.Write((short)fromPoint.m_Y); - Stream.Write((sbyte)fromPoint.m_Z); - Stream.Write((short)toPoint.m_X); - Stream.Write((short)toPoint.m_Y); - Stream.Write((sbyte)toPoint.m_Z); - Stream.Write((byte)speed); - Stream.Write((byte)duration); - Stream.Write((byte)0); - Stream.Write((byte)0); - Stream.Write(fixedDirection); - Stream.Write(explode); - Stream.Write(hue); - Stream.Write(renderMode); - Stream.Write((short)effect); - Stream.Write((short)explodeEffect); - Stream.Write((short)explodeSound); - Stream.Write(serial); - Stream.Write((byte)layer); - Stream.Write((short)unknown); - } - - public ParticleEffect(EffectType type, Serial from, Serial to, int itemID, IPoint3D fromPoint, IPoint3D toPoint, - int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode, int effect, - int explodeEffect, int explodeSound, Serial serial, int layer, int unknown) : base(0xC7, 49) - { - Stream.Write((byte)type); - Stream.Write(from); - Stream.Write(to); - Stream.Write((short)itemID); - Stream.Write((short)fromPoint.X); - Stream.Write((short)fromPoint.Y); - Stream.Write((sbyte)fromPoint.Z); - Stream.Write((short)toPoint.X); - Stream.Write((short)toPoint.Y); - Stream.Write((sbyte)toPoint.Z); - Stream.Write((byte)speed); - Stream.Write((byte)duration); - Stream.Write((byte)0); - Stream.Write((byte)0); - Stream.Write(fixedDirection); - Stream.Write(explode); - Stream.Write(hue); - Stream.Write(renderMode); - Stream.Write((short)effect); - Stream.Write((short)explodeEffect); - Stream.Write((short)explodeSound); - Stream.Write(serial); - Stream.Write((byte)layer); - Stream.Write((short)unknown); - } - } - - public class HuedEffect : Packet - { - public HuedEffect(EffectType type, Serial from, Serial to, int itemID, Point3D fromPoint, Point3D toPoint, int speed, - int duration, bool fixedDirection, bool explode, int hue, int renderMode) : base(0xC0, 36) - { - Stream.Write((byte)type); - Stream.Write(from); - Stream.Write(to); - Stream.Write((short)itemID); - Stream.Write((short)fromPoint.m_X); - Stream.Write((short)fromPoint.m_Y); - Stream.Write((sbyte)fromPoint.m_Z); - Stream.Write((short)toPoint.m_X); - Stream.Write((short)toPoint.m_Y); - Stream.Write((sbyte)toPoint.m_Z); - Stream.Write((byte)speed); - Stream.Write((byte)duration); - Stream.Write((byte)0); - Stream.Write((byte)0); - Stream.Write(fixedDirection); - Stream.Write(explode); - Stream.Write(hue); - Stream.Write(renderMode); - } - - public HuedEffect(EffectType type, Serial from, Serial to, int itemID, IPoint3D fromPoint, IPoint3D toPoint, - int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode) : base(0xC0, 36) - { - Stream.Write((byte)type); - Stream.Write(from); - Stream.Write(to); - Stream.Write((short)itemID); - Stream.Write((short)fromPoint.X); - Stream.Write((short)fromPoint.Y); - Stream.Write((sbyte)fromPoint.Z); - Stream.Write((short)toPoint.X); - Stream.Write((short)toPoint.Y); - Stream.Write((sbyte)toPoint.Z); - Stream.Write((byte)speed); - Stream.Write((byte)duration); - Stream.Write((byte)0); - Stream.Write((byte)0); - Stream.Write(fixedDirection); - Stream.Write(explode); - Stream.Write(hue); - Stream.Write(renderMode); - } - } - - public sealed class TargetParticleEffect : ParticleEffect - { - public TargetParticleEffect(IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, - int layer, int unknown) : base(EffectType.FixedFrom, e.Serial, Serial.Zero, itemID, e.Location, e.Location, - speed, duration, true, false, hue, renderMode, effect, 1, 0, e.Serial, layer, unknown) - { - } - } - - public sealed class TargetEffect : HuedEffect - { - public TargetEffect(IEntity e, int itemID, int speed, int duration, int hue, int renderMode) : base( - EffectType.FixedFrom, e.Serial, Serial.Zero, itemID, e.Location, e.Location, speed, duration, true, false, hue, - renderMode) - { - } - } - - public sealed class LocationParticleEffect : ParticleEffect - { - public LocationParticleEffect(IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, - int unknown) : base(EffectType.FixedXYZ, e.Serial, Serial.Zero, itemID, e.Location, e.Location, speed, duration, - true, false, hue, renderMode, effect, 1, 0, e.Serial, 255, unknown) - { - } - } - - public sealed class LocationEffect : HuedEffect - { - public LocationEffect(IPoint3D p, int itemID, int speed, int duration, int hue, int renderMode) : base( - EffectType.FixedXYZ, Serial.Zero, Serial.Zero, itemID, p, p, speed, duration, true, false, hue, renderMode) - { - } - } - - public sealed class MovingParticleEffect : ParticleEffect - { - public MovingParticleEffect(IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection, - bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer, - int unknown) : base(EffectType.Moving, from.Serial, to.Serial, itemID, from.Location, to.Location, speed, - duration, fixedDirection, explodes, hue, renderMode, effect, explodeEffect, explodeSound, Serial.Zero, - (int)layer, unknown) - { - } - } - - public sealed class MovingEffect : HuedEffect - { - public MovingEffect(IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection, - bool explodes, int hue, int renderMode) : base(EffectType.Moving, from.Serial, to.Serial, itemID, from.Location, - to.Location, speed, duration, fixedDirection, explodes, hue, renderMode) - { - } - } - - public enum ScreenEffectType - { - FadeOut = 0x00, - FadeIn = 0x01, - LightFlash = 0x02, - FadeInOut = 0x03, - DarkFlash = 0x04 - } - - public class ScreenEffect : Packet - { - public ScreenEffect(ScreenEffectType type) - : base(0x70, 28) - { - Stream.Write((byte)0x04); - Stream.Fill(8); - Stream.Write((short)type); - Stream.Fill(16); - } - } - - public sealed class ScreenFadeOut : ScreenEffect - { - public static readonly Packet Instance = SetStatic(new ScreenFadeOut()); - - public ScreenFadeOut() - : base(ScreenEffectType.FadeOut) - { - } - } - - public sealed class ScreenFadeIn : ScreenEffect - { - public static readonly Packet Instance = SetStatic(new ScreenFadeIn()); - - public ScreenFadeIn() - : base(ScreenEffectType.FadeIn) - { - } - } - - public sealed class ScreenFadeInOut : ScreenEffect - { - public static readonly Packet Instance = SetStatic(new ScreenFadeInOut()); - - public ScreenFadeInOut() - : base(ScreenEffectType.FadeInOut) - { - } - } - - public sealed class ScreenLightFlash : ScreenEffect - { - public static readonly Packet Instance = SetStatic(new ScreenLightFlash()); - - public ScreenLightFlash() - : base(ScreenEffectType.LightFlash) - { - } - } - - public sealed class ScreenDarkFlash : ScreenEffect - { - public static readonly Packet Instance = SetStatic(new ScreenDarkFlash()); - - public ScreenDarkFlash() - : base(ScreenEffectType.DarkFlash) - { - } - } - - public sealed class BoltEffect : Packet - { - public BoltEffect(IEntity target, int hue) : base(0xC0, 36) - { - Stream.Write((byte)0x01); // type - Stream.Write(target.Serial); - Stream.Write(Serial.Zero); - Stream.Write((short)0); // itemID - Stream.Write((short)target.X); - Stream.Write((short)target.Y); - Stream.Write((sbyte)target.Z); - Stream.Write((short)target.X); - Stream.Write((short)target.Y); - Stream.Write((sbyte)target.Z); - Stream.Write((byte)0); // speed - Stream.Write((byte)0); // duration - Stream.Write((short)0); // unk - Stream.Write(false); // fixed direction - Stream.Write(false); // explode - Stream.Write(hue); - Stream.Write(0); // render mode - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EffectPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public enum EffectType + { + Moving, + Lightning, + FixedXYZ, + FixedFrom + } + + public class ParticleEffect : Packet + { + public ParticleEffect( + EffectType type, Serial from, Serial to, int itemID, Point3D fromPoint, Point3D toPoint, + int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode, int effect, + int explodeEffect, int explodeSound, Serial serial, int layer, int unknown + ) : base(0xC7, 49) + { + Stream.Write((byte)type); + Stream.Write(from); + Stream.Write(to); + Stream.Write((short)itemID); + Stream.Write((short)fromPoint.m_X); + Stream.Write((short)fromPoint.m_Y); + Stream.Write((sbyte)fromPoint.m_Z); + Stream.Write((short)toPoint.m_X); + Stream.Write((short)toPoint.m_Y); + Stream.Write((sbyte)toPoint.m_Z); + Stream.Write((byte)speed); + Stream.Write((byte)duration); + Stream.Write((byte)0); + Stream.Write((byte)0); + Stream.Write(fixedDirection); + Stream.Write(explode); + Stream.Write(hue); + Stream.Write(renderMode); + Stream.Write((short)effect); + Stream.Write((short)explodeEffect); + Stream.Write((short)explodeSound); + Stream.Write(serial); + Stream.Write((byte)layer); + Stream.Write((short)unknown); + } + + public ParticleEffect( + EffectType type, Serial from, Serial to, int itemID, IPoint3D fromPoint, IPoint3D toPoint, + int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode, int effect, + int explodeEffect, int explodeSound, Serial serial, int layer, int unknown + ) : base(0xC7, 49) + { + Stream.Write((byte)type); + Stream.Write(from); + Stream.Write(to); + Stream.Write((short)itemID); + Stream.Write((short)fromPoint.X); + Stream.Write((short)fromPoint.Y); + Stream.Write((sbyte)fromPoint.Z); + Stream.Write((short)toPoint.X); + Stream.Write((short)toPoint.Y); + Stream.Write((sbyte)toPoint.Z); + Stream.Write((byte)speed); + Stream.Write((byte)duration); + Stream.Write((byte)0); + Stream.Write((byte)0); + Stream.Write(fixedDirection); + Stream.Write(explode); + Stream.Write(hue); + Stream.Write(renderMode); + Stream.Write((short)effect); + Stream.Write((short)explodeEffect); + Stream.Write((short)explodeSound); + Stream.Write(serial); + Stream.Write((byte)layer); + Stream.Write((short)unknown); + } + } + + public class HuedEffect : Packet + { + public HuedEffect( + EffectType type, Serial from, Serial to, int itemID, Point3D fromPoint, Point3D toPoint, int speed, + int duration, bool fixedDirection, bool explode, int hue, int renderMode + ) : base(0xC0, 36) + { + Stream.Write((byte)type); + Stream.Write(from); + Stream.Write(to); + Stream.Write((short)itemID); + Stream.Write((short)fromPoint.m_X); + Stream.Write((short)fromPoint.m_Y); + Stream.Write((sbyte)fromPoint.m_Z); + Stream.Write((short)toPoint.m_X); + Stream.Write((short)toPoint.m_Y); + Stream.Write((sbyte)toPoint.m_Z); + Stream.Write((byte)speed); + Stream.Write((byte)duration); + Stream.Write((byte)0); + Stream.Write((byte)0); + Stream.Write(fixedDirection); + Stream.Write(explode); + Stream.Write(hue); + Stream.Write(renderMode); + } + + public HuedEffect( + EffectType type, Serial from, Serial to, int itemID, IPoint3D fromPoint, IPoint3D toPoint, + int speed, int duration, bool fixedDirection, bool explode, int hue, int renderMode + ) : base(0xC0, 36) + { + Stream.Write((byte)type); + Stream.Write(from); + Stream.Write(to); + Stream.Write((short)itemID); + Stream.Write((short)fromPoint.X); + Stream.Write((short)fromPoint.Y); + Stream.Write((sbyte)fromPoint.Z); + Stream.Write((short)toPoint.X); + Stream.Write((short)toPoint.Y); + Stream.Write((sbyte)toPoint.Z); + Stream.Write((byte)speed); + Stream.Write((byte)duration); + Stream.Write((byte)0); + Stream.Write((byte)0); + Stream.Write(fixedDirection); + Stream.Write(explode); + Stream.Write(hue); + Stream.Write(renderMode); + } + } + + public sealed class TargetParticleEffect : ParticleEffect + { + public TargetParticleEffect( + IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, + int layer, int unknown + ) : base( + EffectType.FixedFrom, + e.Serial, + Serial.Zero, + itemID, + e.Location, + e.Location, + speed, + duration, + true, + false, + hue, + renderMode, + effect, + 1, + 0, + e.Serial, + layer, + unknown + ) + { + } + } + + public sealed class TargetEffect : HuedEffect + { + public TargetEffect(IEntity e, int itemID, int speed, int duration, int hue, int renderMode) : base( + EffectType.FixedFrom, + e.Serial, + Serial.Zero, + itemID, + e.Location, + e.Location, + speed, + duration, + true, + false, + hue, + renderMode + ) + { + } + } + + public sealed class LocationParticleEffect : ParticleEffect + { + public LocationParticleEffect( + IEntity e, int itemID, int speed, int duration, int hue, int renderMode, int effect, + int unknown + ) : base( + EffectType.FixedXYZ, + e.Serial, + Serial.Zero, + itemID, + e.Location, + e.Location, + speed, + duration, + true, + false, + hue, + renderMode, + effect, + 1, + 0, + e.Serial, + 255, + unknown + ) + { + } + } + + public sealed class LocationEffect : HuedEffect + { + public LocationEffect(IPoint3D p, int itemID, int speed, int duration, int hue, int renderMode) : base( + EffectType.FixedXYZ, + Serial.Zero, + Serial.Zero, + itemID, + p, + p, + speed, + duration, + true, + false, + hue, + renderMode + ) + { + } + } + + public sealed class MovingParticleEffect : ParticleEffect + { + public MovingParticleEffect( + IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection, + bool explodes, int hue, int renderMode, int effect, int explodeEffect, int explodeSound, EffectLayer layer, + int unknown + ) : base( + EffectType.Moving, + from.Serial, + to.Serial, + itemID, + from.Location, + to.Location, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode, + effect, + explodeEffect, + explodeSound, + Serial.Zero, + (int)layer, + unknown + ) + { + } + } + + public sealed class MovingEffect : HuedEffect + { + public MovingEffect( + IEntity from, IEntity to, int itemID, int speed, int duration, bool fixedDirection, + bool explodes, int hue, int renderMode + ) : base( + EffectType.Moving, + from.Serial, + to.Serial, + itemID, + from.Location, + to.Location, + speed, + duration, + fixedDirection, + explodes, + hue, + renderMode + ) + { + } + } + + public enum ScreenEffectType + { + FadeOut = 0x00, + FadeIn = 0x01, + LightFlash = 0x02, + FadeInOut = 0x03, + DarkFlash = 0x04 + } + + public class ScreenEffect : Packet + { + public ScreenEffect(ScreenEffectType type) + : base(0x70, 28) + { + Stream.Write((byte)0x04); + Stream.Fill(8); + Stream.Write((short)type); + Stream.Fill(16); + } + } + + public sealed class ScreenFadeOut : ScreenEffect + { + public static readonly Packet Instance = SetStatic(new ScreenFadeOut()); + + public ScreenFadeOut() + : base(ScreenEffectType.FadeOut) + { + } + } + + public sealed class ScreenFadeIn : ScreenEffect + { + public static readonly Packet Instance = SetStatic(new ScreenFadeIn()); + + public ScreenFadeIn() + : base(ScreenEffectType.FadeIn) + { + } + } + + public sealed class ScreenFadeInOut : ScreenEffect + { + public static readonly Packet Instance = SetStatic(new ScreenFadeInOut()); + + public ScreenFadeInOut() + : base(ScreenEffectType.FadeInOut) + { + } + } + + public sealed class ScreenLightFlash : ScreenEffect + { + public static readonly Packet Instance = SetStatic(new ScreenLightFlash()); + + public ScreenLightFlash() + : base(ScreenEffectType.LightFlash) + { + } + } + + public sealed class ScreenDarkFlash : ScreenEffect + { + public static readonly Packet Instance = SetStatic(new ScreenDarkFlash()); + + public ScreenDarkFlash() + : base(ScreenEffectType.DarkFlash) + { + } + } + + public sealed class BoltEffect : Packet + { + public BoltEffect(IEntity target, int hue) : base(0xC0, 36) + { + Stream.Write((byte)0x01); // type + Stream.Write(target.Serial); + Stream.Write(Serial.Zero); + Stream.Write((short)0); // itemID + Stream.Write((short)target.X); + Stream.Write((short)target.Y); + Stream.Write((sbyte)target.Z); + Stream.Write((short)target.X); + Stream.Write((short)target.Y); + Stream.Write((sbyte)target.Z); + Stream.Write((byte)0); // speed + Stream.Write((byte)0); // duration + Stream.Write((short)0); // unk + Stream.Write(false); // fixed direction + Stream.Write(false); // explode + Stream.Write(hue); + Stream.Write(0); // render mode + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/EquipmentPackets.cs b/Projects/Server/Network/Packets/Old Packets/EquipmentPackets.cs index ae6881c9f..d0f2a77ef 100644 --- a/Projects/Server/Network/Packets/Old Packets/EquipmentPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/EquipmentPackets.cs @@ -1,131 +1,133 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: EquipmentPackets.cs - Created: 2020/05/07 - Updated: 2020/05/07 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server.Network -{ - public class EquipInfoAttribute - { - public EquipInfoAttribute(int number, int charges = -1) - { - Number = number; - Charges = charges; - } - - public int Number { get; } - - public int Charges { get; } - } - - public class EquipmentInfo - { - public EquipmentInfo(int number, Mobile crafter, bool unidentified, EquipInfoAttribute[] attributes) - { - Number = number; - Crafter = crafter; - Unidentified = unidentified; - Attributes = attributes; - } - - public int Number { get; } - - public Mobile Crafter { get; } - - public bool Unidentified { get; } - - public EquipInfoAttribute[] Attributes { get; } - } - - public sealed class DisplayEquipmentInfo : Packet - { - public DisplayEquipmentInfo(Item item, EquipmentInfo info) : base(0xBF) - { - var attrs = info.Attributes; - - EnsureCapacity(17 + (info.Crafter?.Name?.Length ?? 0) + - (info.Unidentified ? 4 : 0) + attrs.Length * 6); - - Stream.Write((short)0x10); - Stream.Write(item.Serial); - - Stream.Write(info.Number); - - if (info.Crafter != null) - { - var name = info.Crafter.Name; - - Stream.Write(-3); - - if (name == null) - { - Stream.Write((ushort)0); - } - else - { - var length = name.Length; - Stream.Write((ushort)length); - Stream.WriteAsciiFixed(name, length); - } - } - - if (info.Unidentified) Stream.Write(-4); - - for (var i = 0; i < attrs.Length; ++i) - { - Stream.Write(attrs[i].Number); - Stream.Write((short)attrs[i].Charges); - } - - Stream.Write(-1); - } - } - - public sealed class EquipUpdate : Packet - { - public EquipUpdate(Item item) : base(0x2E, 15) - { - Serial parentSerial; - - var parent = item.Parent as Mobile; - var hue = item.Hue; - - if (parent != null) - { - parentSerial = parent.Serial; - - if (parent.SolidHueOverride >= 0) - hue = parent.SolidHueOverride; - } - else - { - Console.WriteLine("Warning: EquipUpdate on item with !(parent is Mobile)"); - parentSerial = Serial.Zero; - } - - Stream.Write(item.Serial); - Stream.Write((short)item.ItemID); - Stream.Write((byte)0); - Stream.Write((byte)item.Layer); - Stream.Write(parentSerial); - Stream.Write((short)hue); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EquipmentPackets.cs - Created: 2020/05/07 - Updated: 2020/05/07 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server.Network +{ + public class EquipInfoAttribute + { + public EquipInfoAttribute(int number, int charges = -1) + { + Number = number; + Charges = charges; + } + + public int Number { get; } + + public int Charges { get; } + } + + public class EquipmentInfo + { + public EquipmentInfo(int number, Mobile crafter, bool unidentified, EquipInfoAttribute[] attributes) + { + Number = number; + Crafter = crafter; + Unidentified = unidentified; + Attributes = attributes; + } + + public int Number { get; } + + public Mobile Crafter { get; } + + public bool Unidentified { get; } + + public EquipInfoAttribute[] Attributes { get; } + } + + public sealed class DisplayEquipmentInfo : Packet + { + public DisplayEquipmentInfo(Item item, EquipmentInfo info) : base(0xBF) + { + var attrs = info.Attributes; + + EnsureCapacity( + 17 + (info.Crafter?.Name?.Length ?? 0) + + (info.Unidentified ? 4 : 0) + attrs.Length * 6 + ); + + Stream.Write((short)0x10); + Stream.Write(item.Serial); + + Stream.Write(info.Number); + + if (info.Crafter != null) + { + var name = info.Crafter.Name; + + Stream.Write(-3); + + if (name == null) + { + Stream.Write((ushort)0); + } + else + { + var length = name.Length; + Stream.Write((ushort)length); + Stream.WriteAsciiFixed(name, length); + } + } + + if (info.Unidentified) Stream.Write(-4); + + for (var i = 0; i < attrs.Length; ++i) + { + Stream.Write(attrs[i].Number); + Stream.Write((short)attrs[i].Charges); + } + + Stream.Write(-1); + } + } + + public sealed class EquipUpdate : Packet + { + public EquipUpdate(Item item) : base(0x2E, 15) + { + Serial parentSerial; + + var parent = item.Parent as Mobile; + var hue = item.Hue; + + if (parent != null) + { + parentSerial = parent.Serial; + + if (parent.SolidHueOverride >= 0) + hue = parent.SolidHueOverride; + } + else + { + Console.WriteLine("Warning: EquipUpdate on item with !(parent is Mobile)"); + parentSerial = Serial.Zero; + } + + Stream.Write(item.Serial); + Stream.Write((short)item.ItemID); + Stream.Write((byte)0); + Stream.Write((byte)item.Layer); + Stream.Write(parentSerial); + Stream.Write((short)hue); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/GumpPackets.cs b/Projects/Server/Network/Packets/Old Packets/GumpPackets.cs index d31126cb4..51c17f7c1 100644 --- a/Projects/Server/Network/Packets/Old Packets/GumpPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/GumpPackets.cs @@ -1,344 +1,344 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: GumpPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Text; -using Server.Gumps; - -namespace Server.Network -{ - public sealed class CloseGump : Packet - { - public CloseGump(int typeID, int buttonID) : base(0xBF) - { - EnsureCapacity(13); - - Stream.Write((short)0x04); - Stream.Write(typeID); - Stream.Write(buttonID); - } - } - - public interface IGumpWriter - { - int TextEntries { get; set; } - int Switches { get; set; } - - void AppendLayout(bool val); - void AppendLayout(int val); - void AppendLayout(uint val); - void AppendLayoutNS(int val); - void AppendLayout(string text); - void AppendLayout(byte[] buffer); - void WriteStrings(List strings); - void Flush(); - } - - public sealed class DisplayGumpPacked : Packet, IGumpWriter - { - private static readonly byte[] m_True = Gump.StringToBuffer(" 1"); - private static readonly byte[] m_False = Gump.StringToBuffer(" 0"); - - private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @"); - private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@"); - - private static readonly byte[] m_Buffer = new byte[48]; - - private readonly Gump m_Gump; - - private readonly PacketWriter m_Layout; - - private int m_StringCount; - private readonly PacketWriter m_Strings; - - static DisplayGumpPacked() => m_Buffer[0] = (byte)' '; - - public DisplayGumpPacked(Gump gump) - : base(0xDD) - { - m_Gump = gump; - - m_Layout = PacketWriter.CreateInstance(8192); - m_Strings = PacketWriter.CreateInstance(8192); - } - - public int TextEntries { get; set; } - - public int Switches { get; set; } - - public void AppendLayout(bool val) - { - AppendLayout(val ? m_True : m_False); - } - - public void AppendLayout(int val) - { - var toString = val.ToString(); - var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; - - m_Layout.Write(m_Buffer, 0, bytes); - } - - public void AppendLayout(uint val) - { - var toString = val.ToString(); - var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; - - m_Layout.Write(m_Buffer, 0, bytes); - } - - public void AppendLayoutNS(int val) - { - var toString = val.ToString(); - var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1); - - m_Layout.Write(m_Buffer, 1, bytes); - } - - public void AppendLayout(string text) - { - AppendLayout(m_BeginTextSeparator); - - m_Layout.WriteAsciiFixed(text, text.Length); - - AppendLayout(m_EndTextSeparator); - } - - public void AppendLayout(byte[] buffer) - { - m_Layout.Write(buffer, 0, buffer.Length); - } - - public void WriteStrings(List strings) - { - m_StringCount = strings.Count; - - for (var i = 0; i < strings.Count; ++i) - { - var v = strings[i] ?? ""; - - m_Strings.Write((ushort)v.Length); - m_Strings.WriteBigUniFixed(v, v.Length); - } - } - - public void Flush() - { - EnsureCapacity(28 + (int)m_Layout.Length + (int)m_Strings.Length); - - Stream.Write(m_Gump.Serial); - Stream.Write(m_Gump.TypeID); - Stream.Write(m_Gump.X); - Stream.Write(m_Gump.Y); - - // Note: layout MUST be null terminated (don't listen to krrios) - m_Layout.Write((byte)0); - WritePacked(m_Layout); - - Stream.Write(m_StringCount); - - WritePacked(m_Strings); - - PacketWriter.ReleaseInstance(m_Layout); - PacketWriter.ReleaseInstance(m_Strings); - } - - private void WritePacked(PacketWriter src) - { - var buffer = src.UnderlyingStream.GetBuffer(); - var length = (int)src.Length; - - if (length == 0) - { - Stream.Write(0); - return; - } - - var wantLength = 1 + buffer.Length * 1024 / 1000; - - wantLength += 4095; - wantLength &= ~4095; - - var packBuffer = ArrayPool.Shared.Rent(wantLength); - - var packLength = wantLength; - - Zlib.Pack(packBuffer, ref packLength, buffer, length, ZlibQuality.Default); - - Stream.Write(4 + packLength); - Stream.Write(length); - Stream.Write(packBuffer, 0, packLength); - - ArrayPool.Shared.Return(packBuffer); - } - } - - public sealed class DisplayGumpFast : Packet, IGumpWriter - { - private static readonly byte[] m_True = Gump.StringToBuffer(" 1"); - private static readonly byte[] m_False = Gump.StringToBuffer(" 0"); - - private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @"); - private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@"); - - private readonly byte[] m_Buffer = new byte[48]; - private int m_LayoutLength; - - public DisplayGumpFast(Gump g) : base(0xB0) - { - m_Buffer[0] = (byte)' '; - - EnsureCapacity(4096); - - Stream.Write(g.Serial); - Stream.Write(g.TypeID); - Stream.Write(g.X); - Stream.Write(g.Y); - Stream.Write((ushort)0xFFFF); - } - - public int TextEntries { get; set; } - - public int Switches { get; set; } - - public void AppendLayout(bool val) - { - AppendLayout(val ? m_True : m_False); - } - - public void AppendLayout(int val) - { - var toString = val.ToString(); - var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; - - Stream.Write(m_Buffer, 0, bytes); - m_LayoutLength += bytes; - } - - public void AppendLayout(uint val) - { - var toString = val.ToString(); - var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; - - Stream.Write(m_Buffer, 0, bytes); - m_LayoutLength += bytes; - } - - public void AppendLayoutNS(int val) - { - var toString = val.ToString(); - var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1); - - Stream.Write(m_Buffer, 1, bytes); - m_LayoutLength += bytes; - } - - public void AppendLayout(string text) - { - AppendLayout(m_BeginTextSeparator); - - var length = text.Length; - Stream.WriteAsciiFixed(text, length); - m_LayoutLength += length; - - AppendLayout(m_EndTextSeparator); - } - - public void AppendLayout(byte[] buffer) - { - var length = buffer.Length; - Stream.Write(buffer, 0, length); - m_LayoutLength += length; - } - - public void WriteStrings(List text) - { - Stream.Seek(19, SeekOrigin.Begin); - Stream.Write((ushort)m_LayoutLength); - Stream.Seek(0, SeekOrigin.End); - - Stream.Write((ushort)text.Count); - - for (var i = 0; i < text.Count; ++i) - { - var v = text[i] ?? ""; - - int length = (ushort)v.Length; - - Stream.Write((ushort)length); - Stream.WriteBigUniFixed(v, length); - } - } - - public void Flush() - { - } - } - - public sealed class DisplayGump : Packet - { - public DisplayGump(Gump g, string layout, string[] text) : base(0xB0) - { - layout ??= ""; - - EnsureCapacity(256); - - Stream.Write(g.Serial); - Stream.Write(g.TypeID); - Stream.Write(g.X); - Stream.Write(g.Y); - Stream.Write((ushort)(layout.Length + 1)); - Stream.WriteAsciiNull(layout); - - Stream.Write((ushort)text.Length); - - for (var i = 0; i < text.Length; ++i) - { - var v = text[i] ?? ""; - - var length = (ushort)v.Length; - - Stream.Write(length); - Stream.WriteBigUniFixed(v, length); - } - } - } - - public sealed class DisplaySignGump : Packet - { - public DisplaySignGump(Serial serial, int gumpID, string unknown, string caption) : base(0x8B) - { - unknown ??= ""; - caption ??= ""; - - EnsureCapacity(15 + unknown.Length + caption.Length); - - Stream.Write(serial); - Stream.Write((short)gumpID); - Stream.Write((short)(unknown.Length + 1)); - Stream.WriteAsciiNull(unknown); - Stream.Write((short)(caption.Length + 1)); - Stream.WriteAsciiNull(caption); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: GumpPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Buffers; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; +using Server.Gumps; + +namespace Server.Network +{ + public sealed class CloseGump : Packet + { + public CloseGump(int typeID, int buttonID) : base(0xBF) + { + EnsureCapacity(13); + + Stream.Write((short)0x04); + Stream.Write(typeID); + Stream.Write(buttonID); + } + } + + public interface IGumpWriter + { + int TextEntries { get; set; } + int Switches { get; set; } + + void AppendLayout(bool val); + void AppendLayout(int val); + void AppendLayout(uint val); + void AppendLayoutNS(int val); + void AppendLayout(string text); + void AppendLayout(byte[] buffer); + void WriteStrings(List strings); + void Flush(); + } + + public sealed class DisplayGumpPacked : Packet, IGumpWriter + { + private static readonly byte[] m_True = Gump.StringToBuffer(" 1"); + private static readonly byte[] m_False = Gump.StringToBuffer(" 0"); + + private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @"); + private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@"); + + private static readonly byte[] m_Buffer = new byte[48]; + + private readonly Gump m_Gump; + + private readonly PacketWriter m_Layout; + private readonly PacketWriter m_Strings; + + private int m_StringCount; + + static DisplayGumpPacked() => m_Buffer[0] = (byte)' '; + + public DisplayGumpPacked(Gump gump) + : base(0xDD) + { + m_Gump = gump; + + m_Layout = PacketWriter.CreateInstance(8192); + m_Strings = PacketWriter.CreateInstance(8192); + } + + public int TextEntries { get; set; } + + public int Switches { get; set; } + + public void AppendLayout(bool val) + { + AppendLayout(val ? m_True : m_False); + } + + public void AppendLayout(int val) + { + var toString = val.ToString(); + var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; + + m_Layout.Write(m_Buffer, 0, bytes); + } + + public void AppendLayout(uint val) + { + var toString = val.ToString(); + var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; + + m_Layout.Write(m_Buffer, 0, bytes); + } + + public void AppendLayoutNS(int val) + { + var toString = val.ToString(); + var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1); + + m_Layout.Write(m_Buffer, 1, bytes); + } + + public void AppendLayout(string text) + { + AppendLayout(m_BeginTextSeparator); + + m_Layout.WriteAsciiFixed(text, text.Length); + + AppendLayout(m_EndTextSeparator); + } + + public void AppendLayout(byte[] buffer) + { + m_Layout.Write(buffer, 0, buffer.Length); + } + + public void WriteStrings(List strings) + { + m_StringCount = strings.Count; + + for (var i = 0; i < strings.Count; ++i) + { + var v = strings[i] ?? ""; + + m_Strings.Write((ushort)v.Length); + m_Strings.WriteBigUniFixed(v, v.Length); + } + } + + public void Flush() + { + EnsureCapacity(28 + (int)m_Layout.Length + (int)m_Strings.Length); + + Stream.Write(m_Gump.Serial); + Stream.Write(m_Gump.TypeID); + Stream.Write(m_Gump.X); + Stream.Write(m_Gump.Y); + + // Note: layout MUST be null terminated (don't listen to krrios) + m_Layout.Write((byte)0); + WritePacked(m_Layout); + + Stream.Write(m_StringCount); + + WritePacked(m_Strings); + + PacketWriter.ReleaseInstance(m_Layout); + PacketWriter.ReleaseInstance(m_Strings); + } + + private void WritePacked(PacketWriter src) + { + var buffer = src.UnderlyingStream.GetBuffer(); + var length = (int)src.Length; + + if (length == 0) + { + Stream.Write(0); + return; + } + + var wantLength = 1 + buffer.Length * 1024 / 1000; + + wantLength += 4095; + wantLength &= ~4095; + + var packBuffer = ArrayPool.Shared.Rent(wantLength); + + var packLength = wantLength; + + Zlib.Pack(packBuffer, ref packLength, buffer, length, ZlibQuality.Default); + + Stream.Write(4 + packLength); + Stream.Write(length); + Stream.Write(packBuffer, 0, packLength); + + ArrayPool.Shared.Return(packBuffer); + } + } + + public sealed class DisplayGumpFast : Packet, IGumpWriter + { + private static readonly byte[] m_True = Gump.StringToBuffer(" 1"); + private static readonly byte[] m_False = Gump.StringToBuffer(" 0"); + + private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @"); + private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@"); + + private readonly byte[] m_Buffer = new byte[48]; + private int m_LayoutLength; + + public DisplayGumpFast(Gump g) : base(0xB0) + { + m_Buffer[0] = (byte)' '; + + EnsureCapacity(4096); + + Stream.Write(g.Serial); + Stream.Write(g.TypeID); + Stream.Write(g.X); + Stream.Write(g.Y); + Stream.Write((ushort)0xFFFF); + } + + public int TextEntries { get; set; } + + public int Switches { get; set; } + + public void AppendLayout(bool val) + { + AppendLayout(val ? m_True : m_False); + } + + public void AppendLayout(int val) + { + var toString = val.ToString(); + var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; + + Stream.Write(m_Buffer, 0, bytes); + m_LayoutLength += bytes; + } + + public void AppendLayout(uint val) + { + var toString = val.ToString(); + var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1; + + Stream.Write(m_Buffer, 0, bytes); + m_LayoutLength += bytes; + } + + public void AppendLayoutNS(int val) + { + var toString = val.ToString(); + var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1); + + Stream.Write(m_Buffer, 1, bytes); + m_LayoutLength += bytes; + } + + public void AppendLayout(string text) + { + AppendLayout(m_BeginTextSeparator); + + var length = text.Length; + Stream.WriteAsciiFixed(text, length); + m_LayoutLength += length; + + AppendLayout(m_EndTextSeparator); + } + + public void AppendLayout(byte[] buffer) + { + var length = buffer.Length; + Stream.Write(buffer, 0, length); + m_LayoutLength += length; + } + + public void WriteStrings(List text) + { + Stream.Seek(19, SeekOrigin.Begin); + Stream.Write((ushort)m_LayoutLength); + Stream.Seek(0, SeekOrigin.End); + + Stream.Write((ushort)text.Count); + + for (var i = 0; i < text.Count; ++i) + { + var v = text[i] ?? ""; + + int length = (ushort)v.Length; + + Stream.Write((ushort)length); + Stream.WriteBigUniFixed(v, length); + } + } + + public void Flush() + { + } + } + + public sealed class DisplayGump : Packet + { + public DisplayGump(Gump g, string layout, string[] text) : base(0xB0) + { + layout ??= ""; + + EnsureCapacity(256); + + Stream.Write(g.Serial); + Stream.Write(g.TypeID); + Stream.Write(g.X); + Stream.Write(g.Y); + Stream.Write((ushort)(layout.Length + 1)); + Stream.WriteAsciiNull(layout); + + Stream.Write((ushort)text.Length); + + for (var i = 0; i < text.Length; ++i) + { + var v = text[i] ?? ""; + + var length = (ushort)v.Length; + + Stream.Write(length); + Stream.WriteBigUniFixed(v, length); + } + } + } + + public sealed class DisplaySignGump : Packet + { + public DisplaySignGump(Serial serial, int gumpID, string unknown, string caption) : base(0x8B) + { + unknown ??= ""; + caption ??= ""; + + EnsureCapacity(15 + unknown.Length + caption.Length); + + Stream.Write(serial); + Stream.Write((short)gumpID); + Stream.Write((short)(unknown.Length + 1)); + Stream.WriteAsciiNull(unknown); + Stream.Write((short)(caption.Length + 1)); + Stream.WriteAsciiNull(caption); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/ItemPackets.cs b/Projects/Server/Network/Packets/Old Packets/ItemPackets.cs index 05045742d..1bdc9064c 100644 --- a/Projects/Server/Network/Packets/Old Packets/ItemPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/ItemPackets.cs @@ -1,445 +1,445 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ItemPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.IO; -using Server.Items; - -namespace Server.Network -{ - public sealed class WorldItem : Packet - { - public WorldItem(Item item) : base(0x1A) - { - EnsureCapacity(20); - - // 14 base length - // +2 - Amount - // +2 - Hue - // +1 - Flags - - var serial = item.Serial.Value; - var itemID = item.ItemID & 0x3FFF; - var amount = item.Amount; - var loc = item.Location; - var x = loc.m_X; - var y = loc.m_Y; - var hue = item.Hue; - var flags = item.GetPacketFlags(); - var direction = (int)item.Direction; - - if (amount != 0) - serial |= 0x80000000; - else - serial &= 0x7FFFFFFF; - - Stream.Write(serial); - - if (item is BaseMulti) - Stream.Write((short)(itemID | 0x4000)); - else - Stream.Write((short)itemID); - - if (amount != 0) Stream.Write((short)amount); - - x &= 0x7FFF; - - if (direction != 0) x |= 0x8000; - - Stream.Write((short)x); - - y &= 0x3FFF; - - if (hue != 0) y |= 0x8000; - - if (flags != 0) y |= 0x4000; - - Stream.Write((short)y); - - if (direction != 0) - Stream.Write((byte)direction); - - Stream.Write((sbyte)loc.m_Z); - - if (hue != 0) - Stream.Write((ushort)hue); - - if (flags != 0) - Stream.Write((byte)flags); - } - } - - public sealed class WorldItemSA : Packet - { - public WorldItemSA(Item item) : base(0xF3, 24) - { - Stream.Write((short)0x1); - - var itemID = item.ItemID; - - if (item is BaseMulti) - { - Stream.Write((byte)0x02); - - Stream.Write(item.Serial); - - itemID &= 0x3FFF; - - Stream.Write((short)itemID); - - Stream.Write((byte)0); - } - else - { - Stream.Write((byte)0x00); - - Stream.Write(item.Serial); - - itemID &= 0x7FFF; - - Stream.Write((short)itemID); - - Stream.Write((byte)0); - } - - var amount = item.Amount; - Stream.Write((short)amount); - Stream.Write((short)amount); - - var loc = item.Location; - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write((sbyte)loc.m_Z); - - Stream.Write((byte)item.Light); - Stream.Write((short)item.Hue); - Stream.Write((byte)item.GetPacketFlags()); - } - } - - public sealed class WorldItemHS : Packet - { - public WorldItemHS(Item item) : base(0xF3, 26) - { - Stream.Write((short)0x1); - - var itemID = item.ItemID; - - if (item is BaseMulti) - { - Stream.Write((byte)0x02); - - Stream.Write(item.Serial); - - itemID &= 0x3FFF; - - Stream.Write((ushort)itemID); - - Stream.Write((byte)0); - } - else - { - Stream.Write((byte)0x00); - - Stream.Write(item.Serial); - - itemID &= 0xFFFF; - - Stream.Write((ushort)itemID); - - Stream.Write((byte)0); - } - - var amount = item.Amount; - Stream.Write((short)amount); - Stream.Write((short)amount); - - var loc = item.Location; - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write((sbyte)loc.m_Z); - - Stream.Write((byte)item.Light); - Stream.Write((short)item.Hue); - Stream.Write((byte)item.GetPacketFlags()); - - Stream.Write((short)0x00); // ?? - } - } - - public sealed class DisplaySpellbook : Packet - { - public DisplaySpellbook(Serial book) : base(0x24, 7) - { - Stream.Write(book); - Stream.Write((short)-1); - } - } - - public sealed class DisplaySpellbookHS : Packet - { - public DisplaySpellbookHS(Serial book) : base(0x24, 9) - { - Stream.Write(book); - Stream.Write((short)-1); - Stream.Write((short)0x7D); - } - } - - public sealed class NewSpellbookContent : Packet - { - public NewSpellbookContent(Serial spellbook, int graphic, int offset, ulong content) : base(0xBF) - { - EnsureCapacity(23); - - Stream.Write((short)0x1B); - Stream.Write((short)0x01); - - Stream.Write(spellbook); - Stream.Write((short)graphic); - Stream.Write((short)offset); - - for (var i = 0; i < 8; ++i) - Stream.Write((byte)(content >> (i * 8))); - } - } - - public sealed class SpellbookContent : Packet - { - public SpellbookContent(Serial spellbook, int offset, ulong content) : base(0x3C) - { - EnsureCapacity(5 + 64 * 19); - - var written = 0; - - Stream.Write((ushort)0); - - ulong mask = 1; - - for (var i = 0; i < 64; ++i, mask <<= 1) - if ((content & mask) != 0) - { - Stream.Write(0x7FFFFFFF - i); - Stream.Write((ushort)0); - Stream.Write((byte)0); - Stream.Write((ushort)(i + offset)); - Stream.Write((short)0); - Stream.Write((short)0); - Stream.Write(spellbook); - Stream.Write((short)0); - - ++written; - } - - Stream.Seek(3, SeekOrigin.Begin); - Stream.Write((ushort)written); - } - } - - public sealed class SpellbookContent6017 : Packet - { - public SpellbookContent6017(Serial spellbook, int offset, ulong content) : base(0x3C) - { - EnsureCapacity(5 + 64 * 20); - - var written = 0; - - Stream.Write((ushort)0); - - ulong mask = 1; - - for (var i = 0; i < 64; ++i, mask <<= 1) - if ((content & mask) != 0) - { - Stream.Write(0x7FFFFFFF - i); - Stream.Write((ushort)0); - Stream.Write((byte)0); - Stream.Write((ushort)(i + offset)); - Stream.Write((short)0); - Stream.Write((short)0); - Stream.Write((byte)0); // Grid Location? - Stream.Write(spellbook); - Stream.Write((short)0); - - ++written; - } - - Stream.Seek(3, SeekOrigin.Begin); - Stream.Write((ushort)written); - } - } - - public sealed class ContainerDisplay : Packet - { - public ContainerDisplay(Serial cont, int gumpId) : base(0x24, 7) - { - Stream.Write(cont); - Stream.Write((short)gumpId); - } - } - - public sealed class ContainerDisplayHS : Packet - { - public ContainerDisplayHS(Serial cont, int gumpId) : base(0x24, 9) - { - Stream.Write(cont); - Stream.Write((short)gumpId); - Stream.Write((short)0x7D); - } - } - - public sealed class ContainerContentUpdate : Packet - { - public ContainerContentUpdate(Item item) : base(0x25, 20) - { - Serial parentSerial; - - if (item.Parent is Item parentItem) - { - parentSerial = parentItem.Serial; - } - else - { - Console.WriteLine("Warning: ContainerContentUpdate on item with !(parent is Item)"); - parentSerial = Serial.Zero; - } - - Stream.Write(item.Serial); - Stream.Write((ushort)item.ItemID); - Stream.Write((byte)0); // signed, itemID offset - Stream.Write((ushort)Math.Min(item.Amount, ushort.MaxValue)); - Stream.Write((short)item.X); - Stream.Write((short)item.Y); - Stream.Write(parentSerial); - Stream.Write((ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue)); - } - } - - public sealed class ContainerContentUpdate6017 : Packet - { - public ContainerContentUpdate6017(Item item) : base(0x25, 21) - { - Serial parentSerial; - - if (item.Parent is Item parentItem) - { - parentSerial = parentItem.Serial; - } - else - { - Console.WriteLine("Warning: ContainerContentUpdate on item with !(parent is Item)"); - parentSerial = Serial.Zero; - } - - Stream.Write(item.Serial); - Stream.Write((ushort)item.ItemID); - Stream.Write((byte)0); // signed, itemID offset - Stream.Write((ushort)Math.Min(item.Amount, ushort.MaxValue)); - Stream.Write((short)item.X); - Stream.Write((short)item.Y); - Stream.Write((byte)0); // Grid Location? - Stream.Write(parentSerial); - Stream.Write((ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue)); - } - } - - public sealed class ContainerContent : Packet - { - public ContainerContent(Mobile beholder, Item beheld) : base(0x3C) - { - var items = beheld.Items; - var count = items.Count; - - EnsureCapacity(5 + count * 19); - - var pos = Stream.Position; - - var written = 0; - - Stream.Write((ushort)0); - - for (var i = 0; i < count; ++i) - { - var child = items[i]; - - if (!child.Deleted && beholder.CanSee(child)) - { - var loc = child.Location; - - Stream.Write(child.Serial); - Stream.Write((ushort)child.ItemID); - Stream.Write((byte)0); // signed, itemID offset - Stream.Write((ushort)Math.Min(child.Amount, ushort.MaxValue)); - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write(beheld.Serial); - Stream.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); - - ++written; - } - } - - Stream.Seek(pos, SeekOrigin.Begin); - Stream.Write((ushort)written); - } - } - - public sealed class ContainerContent6017 : Packet - { - public ContainerContent6017(Mobile beholder, Item beheld) : base(0x3C) - { - var items = beheld.Items; - var count = items.Count; - - EnsureCapacity(5 + count * 20); - - var pos = Stream.Position; - - var written = 0; - - Stream.Write((ushort)0); - - for (var i = 0; i < count; ++i) - { - var child = items[i]; - - if (!child.Deleted && beholder.CanSee(child)) - { - var loc = child.Location; - - Stream.Write(child.Serial); - Stream.Write((ushort)child.ItemID); - Stream.Write((byte)0); // signed, itemID offset - Stream.Write((ushort)Math.Min(child.Amount, ushort.MaxValue)); - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write((byte)0); // Grid Location? - Stream.Write(beheld.Serial); - Stream.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); - - ++written; - } - } - - Stream.Seek(pos, SeekOrigin.Begin); - Stream.Write((ushort)written); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ItemPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.IO; +using Server.Items; + +namespace Server.Network +{ + public sealed class WorldItem : Packet + { + public WorldItem(Item item) : base(0x1A) + { + EnsureCapacity(20); + + // 14 base length + // +2 - Amount + // +2 - Hue + // +1 - Flags + + var serial = item.Serial.Value; + var itemID = item.ItemID & 0x3FFF; + var amount = item.Amount; + var loc = item.Location; + var x = loc.m_X; + var y = loc.m_Y; + var hue = item.Hue; + var flags = item.GetPacketFlags(); + var direction = (int)item.Direction; + + if (amount != 0) + serial |= 0x80000000; + else + serial &= 0x7FFFFFFF; + + Stream.Write(serial); + + if (item is BaseMulti) + Stream.Write((short)(itemID | 0x4000)); + else + Stream.Write((short)itemID); + + if (amount != 0) Stream.Write((short)amount); + + x &= 0x7FFF; + + if (direction != 0) x |= 0x8000; + + Stream.Write((short)x); + + y &= 0x3FFF; + + if (hue != 0) y |= 0x8000; + + if (flags != 0) y |= 0x4000; + + Stream.Write((short)y); + + if (direction != 0) + Stream.Write((byte)direction); + + Stream.Write((sbyte)loc.m_Z); + + if (hue != 0) + Stream.Write((ushort)hue); + + if (flags != 0) + Stream.Write((byte)flags); + } + } + + public sealed class WorldItemSA : Packet + { + public WorldItemSA(Item item) : base(0xF3, 24) + { + Stream.Write((short)0x1); + + var itemID = item.ItemID; + + if (item is BaseMulti) + { + Stream.Write((byte)0x02); + + Stream.Write(item.Serial); + + itemID &= 0x3FFF; + + Stream.Write((short)itemID); + + Stream.Write((byte)0); + } + else + { + Stream.Write((byte)0x00); + + Stream.Write(item.Serial); + + itemID &= 0x7FFF; + + Stream.Write((short)itemID); + + Stream.Write((byte)0); + } + + var amount = item.Amount; + Stream.Write((short)amount); + Stream.Write((short)amount); + + var loc = item.Location; + Stream.Write((short)loc.m_X); + Stream.Write((short)loc.m_Y); + Stream.Write((sbyte)loc.m_Z); + + Stream.Write((byte)item.Light); + Stream.Write((short)item.Hue); + Stream.Write((byte)item.GetPacketFlags()); + } + } + + public sealed class WorldItemHS : Packet + { + public WorldItemHS(Item item) : base(0xF3, 26) + { + Stream.Write((short)0x1); + + var itemID = item.ItemID; + + if (item is BaseMulti) + { + Stream.Write((byte)0x02); + + Stream.Write(item.Serial); + + itemID &= 0x3FFF; + + Stream.Write((ushort)itemID); + + Stream.Write((byte)0); + } + else + { + Stream.Write((byte)0x00); + + Stream.Write(item.Serial); + + itemID &= 0xFFFF; + + Stream.Write((ushort)itemID); + + Stream.Write((byte)0); + } + + var amount = item.Amount; + Stream.Write((short)amount); + Stream.Write((short)amount); + + var loc = item.Location; + Stream.Write((short)loc.m_X); + Stream.Write((short)loc.m_Y); + Stream.Write((sbyte)loc.m_Z); + + Stream.Write((byte)item.Light); + Stream.Write((short)item.Hue); + Stream.Write((byte)item.GetPacketFlags()); + + Stream.Write((short)0x00); // ?? + } + } + + public sealed class DisplaySpellbook : Packet + { + public DisplaySpellbook(Serial book) : base(0x24, 7) + { + Stream.Write(book); + Stream.Write((short)-1); + } + } + + public sealed class DisplaySpellbookHS : Packet + { + public DisplaySpellbookHS(Serial book) : base(0x24, 9) + { + Stream.Write(book); + Stream.Write((short)-1); + Stream.Write((short)0x7D); + } + } + + public sealed class NewSpellbookContent : Packet + { + public NewSpellbookContent(Serial spellbook, int graphic, int offset, ulong content) : base(0xBF) + { + EnsureCapacity(23); + + Stream.Write((short)0x1B); + Stream.Write((short)0x01); + + Stream.Write(spellbook); + Stream.Write((short)graphic); + Stream.Write((short)offset); + + for (var i = 0; i < 8; ++i) + Stream.Write((byte)(content >> (i * 8))); + } + } + + public sealed class SpellbookContent : Packet + { + public SpellbookContent(Serial spellbook, int offset, ulong content) : base(0x3C) + { + EnsureCapacity(5 + 64 * 19); + + var written = 0; + + Stream.Write((ushort)0); + + ulong mask = 1; + + for (var i = 0; i < 64; ++i, mask <<= 1) + if ((content & mask) != 0) + { + Stream.Write(0x7FFFFFFF - i); + Stream.Write((ushort)0); + Stream.Write((byte)0); + Stream.Write((ushort)(i + offset)); + Stream.Write((short)0); + Stream.Write((short)0); + Stream.Write(spellbook); + Stream.Write((short)0); + + ++written; + } + + Stream.Seek(3, SeekOrigin.Begin); + Stream.Write((ushort)written); + } + } + + public sealed class SpellbookContent6017 : Packet + { + public SpellbookContent6017(Serial spellbook, int offset, ulong content) : base(0x3C) + { + EnsureCapacity(5 + 64 * 20); + + var written = 0; + + Stream.Write((ushort)0); + + ulong mask = 1; + + for (var i = 0; i < 64; ++i, mask <<= 1) + if ((content & mask) != 0) + { + Stream.Write(0x7FFFFFFF - i); + Stream.Write((ushort)0); + Stream.Write((byte)0); + Stream.Write((ushort)(i + offset)); + Stream.Write((short)0); + Stream.Write((short)0); + Stream.Write((byte)0); // Grid Location? + Stream.Write(spellbook); + Stream.Write((short)0); + + ++written; + } + + Stream.Seek(3, SeekOrigin.Begin); + Stream.Write((ushort)written); + } + } + + public sealed class ContainerDisplay : Packet + { + public ContainerDisplay(Serial cont, int gumpId) : base(0x24, 7) + { + Stream.Write(cont); + Stream.Write((short)gumpId); + } + } + + public sealed class ContainerDisplayHS : Packet + { + public ContainerDisplayHS(Serial cont, int gumpId) : base(0x24, 9) + { + Stream.Write(cont); + Stream.Write((short)gumpId); + Stream.Write((short)0x7D); + } + } + + public sealed class ContainerContentUpdate : Packet + { + public ContainerContentUpdate(Item item) : base(0x25, 20) + { + Serial parentSerial; + + if (item.Parent is Item parentItem) + { + parentSerial = parentItem.Serial; + } + else + { + Console.WriteLine("Warning: ContainerContentUpdate on item with !(parent is Item)"); + parentSerial = Serial.Zero; + } + + Stream.Write(item.Serial); + Stream.Write((ushort)item.ItemID); + Stream.Write((byte)0); // signed, itemID offset + Stream.Write((ushort)Math.Min(item.Amount, ushort.MaxValue)); + Stream.Write((short)item.X); + Stream.Write((short)item.Y); + Stream.Write(parentSerial); + Stream.Write((ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue)); + } + } + + public sealed class ContainerContentUpdate6017 : Packet + { + public ContainerContentUpdate6017(Item item) : base(0x25, 21) + { + Serial parentSerial; + + if (item.Parent is Item parentItem) + { + parentSerial = parentItem.Serial; + } + else + { + Console.WriteLine("Warning: ContainerContentUpdate on item with !(parent is Item)"); + parentSerial = Serial.Zero; + } + + Stream.Write(item.Serial); + Stream.Write((ushort)item.ItemID); + Stream.Write((byte)0); // signed, itemID offset + Stream.Write((ushort)Math.Min(item.Amount, ushort.MaxValue)); + Stream.Write((short)item.X); + Stream.Write((short)item.Y); + Stream.Write((byte)0); // Grid Location? + Stream.Write(parentSerial); + Stream.Write((ushort)(item.QuestItem ? Item.QuestItemHue : item.Hue)); + } + } + + public sealed class ContainerContent : Packet + { + public ContainerContent(Mobile beholder, Item beheld) : base(0x3C) + { + var items = beheld.Items; + var count = items.Count; + + EnsureCapacity(5 + count * 19); + + var pos = Stream.Position; + + var written = 0; + + Stream.Write((ushort)0); + + for (var i = 0; i < count; ++i) + { + var child = items[i]; + + if (!child.Deleted && beholder.CanSee(child)) + { + var loc = child.Location; + + Stream.Write(child.Serial); + Stream.Write((ushort)child.ItemID); + Stream.Write((byte)0); // signed, itemID offset + Stream.Write((ushort)Math.Min(child.Amount, ushort.MaxValue)); + Stream.Write((short)loc.m_X); + Stream.Write((short)loc.m_Y); + Stream.Write(beheld.Serial); + Stream.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); + + ++written; + } + } + + Stream.Seek(pos, SeekOrigin.Begin); + Stream.Write((ushort)written); + } + } + + public sealed class ContainerContent6017 : Packet + { + public ContainerContent6017(Mobile beholder, Item beheld) : base(0x3C) + { + var items = beheld.Items; + var count = items.Count; + + EnsureCapacity(5 + count * 20); + + var pos = Stream.Position; + + var written = 0; + + Stream.Write((ushort)0); + + for (var i = 0; i < count; ++i) + { + var child = items[i]; + + if (!child.Deleted && beholder.CanSee(child)) + { + var loc = child.Location; + + Stream.Write(child.Serial); + Stream.Write((ushort)child.ItemID); + Stream.Write((byte)0); // signed, itemID offset + Stream.Write((ushort)Math.Min(child.Amount, ushort.MaxValue)); + Stream.Write((short)loc.m_X); + Stream.Write((short)loc.m_Y); + Stream.Write((byte)0); // Grid Location? + Stream.Write(beheld.Serial); + Stream.Write((ushort)(child.QuestItem ? Item.QuestItemHue : child.Hue)); + + ++written; + } + } + + Stream.Seek(pos, SeekOrigin.Begin); + Stream.Write((ushort)written); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/LightPackets.cs b/Projects/Server/Network/Packets/Old Packets/LightPackets.cs index 1d5099596..d88708046 100644 --- a/Projects/Server/Network/Packets/Old Packets/LightPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/LightPackets.cs @@ -1,55 +1,55 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: LightPackets.cs - Created: 2020/06/25 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public sealed class GlobalLightLevel : Packet - { - private static readonly GlobalLightLevel[] m_Cache = new GlobalLightLevel[0x100]; - - public GlobalLightLevel(int level) : base(0x4F, 2) - { - Stream.Write((sbyte)level); - } - - public static GlobalLightLevel Instantiate(int level) - { - var lvl = (byte)level; - var p = m_Cache[lvl]; - - if (p == null) - { - m_Cache[lvl] = p = new GlobalLightLevel(level); - p.SetStatic(); - } - - return p; - } - } - - public sealed class PersonalLightLevel : Packet - { - public PersonalLightLevel(Serial mobile, int level = 0) : base(0x4E, 6) - { - Stream.Write(mobile); - Stream.Write((sbyte)level); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: LightPackets.cs - Created: 2020/06/25 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public sealed class GlobalLightLevel : Packet + { + private static readonly GlobalLightLevel[] m_Cache = new GlobalLightLevel[0x100]; + + public GlobalLightLevel(int level) : base(0x4F, 2) + { + Stream.Write((sbyte)level); + } + + public static GlobalLightLevel Instantiate(int level) + { + var lvl = (byte)level; + var p = m_Cache[lvl]; + + if (p == null) + { + m_Cache[lvl] = p = new GlobalLightLevel(level); + p.SetStatic(); + } + + return p; + } + } + + public sealed class PersonalLightLevel : Packet + { + public PersonalLightLevel(Serial mobile, int level = 0) : base(0x4E, 6) + { + Stream.Write(mobile); + Stream.Write((sbyte)level); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/MapPackets.cs b/Projects/Server/Network/Packets/Old Packets/MapPackets.cs index 5f583c756..0dc7612f3 100644 --- a/Projects/Server/Network/Packets/Old Packets/MapPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/MapPackets.cs @@ -1,65 +1,65 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MapPackets.cs - Created: 2020/05/03 - Updated: 2020/06/24 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public sealed class MapPatches : Packet - { - // TODO: Base this on the client version and expansion - public MapPatches() : base(0xBF) - { - EnsureCapacity(9 + 4 * 8); - - Stream.Write((short)0x18); - - Stream.Write(4); - - Stream.Write(Map.Felucca.Tiles.Patch.StaticBlocks); - Stream.Write(Map.Felucca.Tiles.Patch.LandBlocks); - - Stream.Write(Map.Trammel.Tiles.Patch.StaticBlocks); - Stream.Write(Map.Trammel.Tiles.Patch.LandBlocks); - - Stream.Write(Map.Ilshenar.Tiles.Patch.StaticBlocks); - Stream.Write(Map.Ilshenar.Tiles.Patch.LandBlocks); - - Stream.Write(Map.Malas.Tiles.Patch.StaticBlocks); - Stream.Write(Map.Malas.Tiles.Patch.LandBlocks); - } - } - - public sealed class InvalidMapEnable : Packet - { - public InvalidMapEnable() : base(0xC6, 1) - { - } - } - - public sealed class MapChange : Packet - { - public MapChange(Map map) : base(0xBF) - { - EnsureCapacity(6); - - Stream.Write((short)0x08); - Stream.Write((byte)map.MapID); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MapPackets.cs - Created: 2020/05/03 - Updated: 2020/06/24 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public sealed class MapPatches : Packet + { + // TODO: Base this on the client version and expansion + public MapPatches() : base(0xBF) + { + EnsureCapacity(9 + 4 * 8); + + Stream.Write((short)0x18); + + Stream.Write(4); + + Stream.Write(Map.Felucca.Tiles.Patch.StaticBlocks); + Stream.Write(Map.Felucca.Tiles.Patch.LandBlocks); + + Stream.Write(Map.Trammel.Tiles.Patch.StaticBlocks); + Stream.Write(Map.Trammel.Tiles.Patch.LandBlocks); + + Stream.Write(Map.Ilshenar.Tiles.Patch.StaticBlocks); + Stream.Write(Map.Ilshenar.Tiles.Patch.LandBlocks); + + Stream.Write(Map.Malas.Tiles.Patch.StaticBlocks); + Stream.Write(Map.Malas.Tiles.Patch.LandBlocks); + } + } + + public sealed class InvalidMapEnable : Packet + { + public InvalidMapEnable() : base(0xC6, 1) + { + } + } + + public sealed class MapChange : Packet + { + public MapChange(Map map) : base(0xBF) + { + EnsureCapacity(6); + + Stream.Write((short)0x08); + Stream.Write((byte)map.MapID); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/MenuPackets.cs b/Projects/Server/Network/Packets/Old Packets/MenuPackets.cs index 03cd385d4..93df68a23 100644 --- a/Projects/Server/Network/Packets/Old Packets/MenuPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/MenuPackets.cs @@ -1,239 +1,239 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MenuPackets.cs - Created: 2020/05/08 - Updated: 2020/05/26 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using Server.ContextMenus; -using Server.Menus; -using Server.Menus.ItemLists; -using Server.Menus.Questions; - -namespace Server.Network -{ - [Flags] - public enum CMEFlags - { - None = 0x00, - Disabled = 0x01, - Arrow = 0x02, - Highlighted = 0x04, - Colored = 0x20 - } - - public sealed class DisplayItemListMenu : Packet - { - public DisplayItemListMenu(ItemListMenu menu) : base(0x7C) - { - EnsureCapacity(256); - - Stream.Write(((IMenu)menu).Serial); - Stream.Write((short)0); - - var question = menu.Question; - - if (question == null) - { - Stream.Write((byte)0); - } - else - { - var questionLength = question.Length; - Stream.Write((byte)questionLength); - Stream.WriteAsciiFixed(question, questionLength); - } - - var entries = menu.Entries; - - int entriesLength = (byte)entries.Length; - - Stream.Write((byte)entriesLength); - - for (var i = 0; i < entriesLength; ++i) - { - var e = entries[i]; - - Stream.Write((ushort)e.ItemID); - Stream.Write((short)e.Hue); - - var name = e.Name; - - if (name == null) - { - Stream.Write((byte)0); - } - else - { - var nameLength = name.Length; - Stream.Write((byte)nameLength); - Stream.WriteAsciiFixed(name, nameLength); - } - } - } - } - - public sealed class DisplayQuestionMenu : Packet - { - public DisplayQuestionMenu(QuestionMenu menu) : base(0x7C) - { - EnsureCapacity(256); - - Stream.Write(((IMenu)menu).Serial); - Stream.Write((short)0); - - var question = menu.Question; - - if (question == null) - { - Stream.Write((byte)0); - } - else - { - var questionLength = question.Length; - Stream.Write((byte)questionLength); - Stream.WriteAsciiFixed(question, questionLength); - } - - var answers = menu.Answers; - - int answersLength = (byte)answers.Length; - - Stream.Write((byte)answersLength); - - for (var i = 0; i < answersLength; ++i) - { - Stream.Write(0); - - var answer = answers[i]; - - if (answer == null) - { - Stream.Write((byte)0); - } - else - { - var answerLength = answer.Length; - Stream.Write((byte)answerLength); - Stream.WriteAsciiFixed(answer, answerLength); - } - } - } - } - - public sealed class DisplayContextMenu : Packet - { - public DisplayContextMenu(ContextMenu menu) : base(0xBF) - { - var entries = menu.Entries; - - int length = (byte)entries.Length; - - EnsureCapacity(12 + length * 8); - - Stream.Write((short)0x14); - Stream.Write((short)0x02); - - var target = menu.Target; - - Stream.Write(target.Serial); - - Stream.Write((byte)length); - - Point3D p = target switch - { - Mobile _ => target.Location, - Item item => item.GetWorldLocation(), - _ => Point3D.Zero - }; - - for (var i = 0; i < length; ++i) - { - var e = entries[i]; - - Stream.Write(e.Number); - Stream.Write((short)i); - - var range = e.Range; - - if (range == -1) - range = 18; - - var flags = e.Flags; - if (!(e.Enabled && menu.From.InRange(p, range))) - flags |= CMEFlags.Disabled; - - Stream.Write((short)flags); - } - } - } - - public sealed class DisplayContextMenuOld : Packet - { - public DisplayContextMenuOld(ContextMenu menu) : base(0xBF) - { - var entries = menu.Entries; - - int length = (byte)entries.Length; - - EnsureCapacity(12 + length * 8); - - Stream.Write((short)0x14); - Stream.Write((short)0x01); - - var target = menu.Target; - - Stream.Write(target.Serial); - - Stream.Write((byte)length); - - Point3D p = target switch - { - Mobile _ => target.Location, - Item item => item.GetWorldLocation(), - _ => Point3D.Zero - }; - - for (var i = 0; i < length; ++i) - { - var e = entries[i]; - - Stream.Write((short)i); - Stream.Write((ushort)(e.Number - 3000000)); - - var range = e.Range; - - if (range == -1) - range = 18; - - var flags = e.Flags; - if (!(e.Enabled && menu.From.InRange(p, range))) - flags |= CMEFlags.Disabled; - - var color = e.Color & 0xFFFF; - - if (color != 0xFFFF) - flags |= CMEFlags.Colored; - - Stream.Write((short)flags); - - if ((flags & CMEFlags.Colored) != 0) - Stream.Write((short)color); - } - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MenuPackets.cs - Created: 2020/05/08 - Updated: 2020/05/26 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using Server.ContextMenus; +using Server.Menus; +using Server.Menus.ItemLists; +using Server.Menus.Questions; + +namespace Server.Network +{ + [Flags] + public enum CMEFlags + { + None = 0x00, + Disabled = 0x01, + Arrow = 0x02, + Highlighted = 0x04, + Colored = 0x20 + } + + public sealed class DisplayItemListMenu : Packet + { + public DisplayItemListMenu(ItemListMenu menu) : base(0x7C) + { + EnsureCapacity(256); + + Stream.Write(((IMenu)menu).Serial); + Stream.Write((short)0); + + var question = menu.Question; + + if (question == null) + { + Stream.Write((byte)0); + } + else + { + var questionLength = question.Length; + Stream.Write((byte)questionLength); + Stream.WriteAsciiFixed(question, questionLength); + } + + var entries = menu.Entries; + + int entriesLength = (byte)entries.Length; + + Stream.Write((byte)entriesLength); + + for (var i = 0; i < entriesLength; ++i) + { + var e = entries[i]; + + Stream.Write((ushort)e.ItemID); + Stream.Write((short)e.Hue); + + var name = e.Name; + + if (name == null) + { + Stream.Write((byte)0); + } + else + { + var nameLength = name.Length; + Stream.Write((byte)nameLength); + Stream.WriteAsciiFixed(name, nameLength); + } + } + } + } + + public sealed class DisplayQuestionMenu : Packet + { + public DisplayQuestionMenu(QuestionMenu menu) : base(0x7C) + { + EnsureCapacity(256); + + Stream.Write(((IMenu)menu).Serial); + Stream.Write((short)0); + + var question = menu.Question; + + if (question == null) + { + Stream.Write((byte)0); + } + else + { + var questionLength = question.Length; + Stream.Write((byte)questionLength); + Stream.WriteAsciiFixed(question, questionLength); + } + + var answers = menu.Answers; + + int answersLength = (byte)answers.Length; + + Stream.Write((byte)answersLength); + + for (var i = 0; i < answersLength; ++i) + { + Stream.Write(0); + + var answer = answers[i]; + + if (answer == null) + { + Stream.Write((byte)0); + } + else + { + var answerLength = answer.Length; + Stream.Write((byte)answerLength); + Stream.WriteAsciiFixed(answer, answerLength); + } + } + } + } + + public sealed class DisplayContextMenu : Packet + { + public DisplayContextMenu(ContextMenu menu) : base(0xBF) + { + var entries = menu.Entries; + + int length = (byte)entries.Length; + + EnsureCapacity(12 + length * 8); + + Stream.Write((short)0x14); + Stream.Write((short)0x02); + + var target = menu.Target; + + Stream.Write(target.Serial); + + Stream.Write((byte)length); + + var p = target switch + { + Mobile _ => target.Location, + Item item => item.GetWorldLocation(), + _ => Point3D.Zero + }; + + for (var i = 0; i < length; ++i) + { + var e = entries[i]; + + Stream.Write(e.Number); + Stream.Write((short)i); + + var range = e.Range; + + if (range == -1) + range = 18; + + var flags = e.Flags; + if (!(e.Enabled && menu.From.InRange(p, range))) + flags |= CMEFlags.Disabled; + + Stream.Write((short)flags); + } + } + } + + public sealed class DisplayContextMenuOld : Packet + { + public DisplayContextMenuOld(ContextMenu menu) : base(0xBF) + { + var entries = menu.Entries; + + int length = (byte)entries.Length; + + EnsureCapacity(12 + length * 8); + + Stream.Write((short)0x14); + Stream.Write((short)0x01); + + var target = menu.Target; + + Stream.Write(target.Serial); + + Stream.Write((byte)length); + + var p = target switch + { + Mobile _ => target.Location, + Item item => item.GetWorldLocation(), + _ => Point3D.Zero + }; + + for (var i = 0; i < length; ++i) + { + var e = entries[i]; + + Stream.Write((short)i); + Stream.Write((ushort)(e.Number - 3000000)); + + var range = e.Range; + + if (range == -1) + range = 18; + + var flags = e.Flags; + if (!(e.Enabled && menu.From.InRange(p, range))) + flags |= CMEFlags.Disabled; + + var color = e.Color & 0xFFFF; + + if (color != 0xFFFF) + flags |= CMEFlags.Colored; + + Stream.Write((short)flags); + + if ((flags & CMEFlags.Colored) != 0) + Stream.Write((short)color); + } + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/MessagePackets.cs b/Projects/Server/Network/Packets/Old Packets/MessagePackets.cs index eee605245..6058b64ae 100644 --- a/Projects/Server/Network/Packets/Old Packets/MessagePackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/MessagePackets.cs @@ -1,185 +1,201 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MessagePackets.cs - Created: 2020/05/26 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server.Network -{ - [Flags] - public enum AffixType : byte - { - Append = 0x00, - Prepend = 0x01, - System = 0x02 - } - - public sealed class MessageLocalized : Packet - { - private static readonly MessageLocalized[] m_Cache_IntLoc = new MessageLocalized[15000]; - private static readonly MessageLocalized[] m_Cache_CliLoc = new MessageLocalized[100000]; - private static readonly MessageLocalized[] m_Cache_CliLocCmp = new MessageLocalized[5000]; - - public MessageLocalized(Serial serial, int graphic, MessageType type, int hue, int font, int number, string name, - string args) : base(0xC1) - { - name ??= ""; - args ??= ""; - - if (hue == 0) - hue = 0x3B2; - - EnsureCapacity(50 + args.Length * 2); - - Stream.Write(serial); - Stream.Write((short)graphic); - Stream.Write((byte)type); - Stream.Write((short)hue); - Stream.Write((short)font); - Stream.Write(number); - Stream.WriteAsciiFixed(name, 30); - Stream.WriteLittleUniNull(args); - } - - public static MessageLocalized InstantiateGeneric(int number) - { - MessageLocalized[] cache = null; - var index = 0; - - if (number >= 3000000) - { - cache = m_Cache_IntLoc; - index = number - 3000000; - } - else if (number >= 1000000) - { - cache = m_Cache_CliLoc; - index = number - 1000000; - } - else if (number >= 500000) - { - cache = m_Cache_CliLocCmp; - index = number - 500000; - } - - MessageLocalized p; - - if (cache != null && index < cache.Length) - { - p = cache[index]; - - if (p == null) - { - cache[index] = p = new MessageLocalized(Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, number, - "System", ""); - p.SetStatic(); - } - } - else - { - p = new MessageLocalized(Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, number, "System", ""); - } - - return p; - } - } - - public sealed class MessageLocalizedAffix : Packet - { - public MessageLocalizedAffix(Serial serial, int graphic, MessageType messageType, int hue, int font, int number, - string name, AffixType affixType, string affix, string args) : base(0xCC) - { - name ??= ""; - affix ??= ""; - args ??= ""; - - if (hue == 0) - hue = 0x3B2; - - EnsureCapacity(52 + affix.Length + args.Length * 2); - - Stream.Write(serial); - Stream.Write((short)graphic); - Stream.Write((byte)messageType); - Stream.Write((short)hue); - Stream.Write((short)font); - Stream.Write(number); - Stream.Write((byte)affixType); - Stream.WriteAsciiFixed(name, 30); - Stream.WriteAsciiNull(affix); - Stream.WriteBigUniNull(args); - } - } - - public sealed class AsciiMessage : Packet - { - public AsciiMessage(Serial serial, int graphic, MessageType type, int hue, int font, string name, string text) : base(0x1C) - { - name ??= ""; - text ??= ""; - - if (hue == 0) - hue = 0x3B2; - - EnsureCapacity(45 + text.Length); - - Stream.Write(serial); - Stream.Write((short)graphic); - Stream.Write((byte)type); - Stream.Write((short)hue); - Stream.Write((short)font); - Stream.WriteAsciiFixed(name, 30); - Stream.WriteAsciiNull(text); - } - } - - public sealed class UnicodeMessage : Packet - { - public UnicodeMessage(Serial serial, int graphic, MessageType type, int hue, int font, string lang, string name, - string text) : base(0xAE) - { - if (string.IsNullOrEmpty(lang)) lang = "ENU"; - name ??= ""; - text ??= ""; - - if (hue == 0) - hue = 0x3B2; - - EnsureCapacity(50 + text.Length * 2); - - Stream.Write(serial); - Stream.Write((short)graphic); - Stream.Write((byte)type); - Stream.Write((short)hue); - Stream.Write((short)font); - Stream.WriteAsciiFixed(lang, 4); - Stream.WriteAsciiFixed(name, 30); - Stream.WriteBigUniNull(text); - } - } - - public sealed class FollowMessage : Packet - { - public FollowMessage(Serial serial1, Serial serial2) : base(0x15, 9) - { - Stream.Write(serial1); - Stream.Write(serial2); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MessagePackets.cs - Created: 2020/05/26 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server.Network +{ + [Flags] + public enum AffixType : byte + { + Append = 0x00, + Prepend = 0x01, + System = 0x02 + } + + public sealed class MessageLocalized : Packet + { + private static readonly MessageLocalized[] m_Cache_IntLoc = new MessageLocalized[15000]; + private static readonly MessageLocalized[] m_Cache_CliLoc = new MessageLocalized[100000]; + private static readonly MessageLocalized[] m_Cache_CliLocCmp = new MessageLocalized[5000]; + + public MessageLocalized( + Serial serial, int graphic, MessageType type, int hue, int font, int number, string name, + string args + ) : base(0xC1) + { + name ??= ""; + args ??= ""; + + if (hue == 0) + hue = 0x3B2; + + EnsureCapacity(50 + args.Length * 2); + + Stream.Write(serial); + Stream.Write((short)graphic); + Stream.Write((byte)type); + Stream.Write((short)hue); + Stream.Write((short)font); + Stream.Write(number); + Stream.WriteAsciiFixed(name, 30); + Stream.WriteLittleUniNull(args); + } + + public static MessageLocalized InstantiateGeneric(int number) + { + MessageLocalized[] cache = null; + var index = 0; + + if (number >= 3000000) + { + cache = m_Cache_IntLoc; + index = number - 3000000; + } + else if (number >= 1000000) + { + cache = m_Cache_CliLoc; + index = number - 1000000; + } + else if (number >= 500000) + { + cache = m_Cache_CliLocCmp; + index = number - 500000; + } + + MessageLocalized p; + + if (cache != null && index < cache.Length) + { + p = cache[index]; + + if (p == null) + { + cache[index] = p = new MessageLocalized( + Serial.MinusOne, + -1, + MessageType.Regular, + 0x3B2, + 3, + number, + "System", + "" + ); + p.SetStatic(); + } + } + else + { + p = new MessageLocalized(Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, number, "System", ""); + } + + return p; + } + } + + public sealed class MessageLocalizedAffix : Packet + { + public MessageLocalizedAffix( + Serial serial, int graphic, MessageType messageType, int hue, int font, int number, + string name, AffixType affixType, string affix, string args + ) : base(0xCC) + { + name ??= ""; + affix ??= ""; + args ??= ""; + + if (hue == 0) + hue = 0x3B2; + + EnsureCapacity(52 + affix.Length + args.Length * 2); + + Stream.Write(serial); + Stream.Write((short)graphic); + Stream.Write((byte)messageType); + Stream.Write((short)hue); + Stream.Write((short)font); + Stream.Write(number); + Stream.Write((byte)affixType); + Stream.WriteAsciiFixed(name, 30); + Stream.WriteAsciiNull(affix); + Stream.WriteBigUniNull(args); + } + } + + public sealed class AsciiMessage : Packet + { + public AsciiMessage( + Serial serial, int graphic, MessageType type, int hue, int font, string name, string text + ) : base(0x1C) + { + name ??= ""; + text ??= ""; + + if (hue == 0) + hue = 0x3B2; + + EnsureCapacity(45 + text.Length); + + Stream.Write(serial); + Stream.Write((short)graphic); + Stream.Write((byte)type); + Stream.Write((short)hue); + Stream.Write((short)font); + Stream.WriteAsciiFixed(name, 30); + Stream.WriteAsciiNull(text); + } + } + + public sealed class UnicodeMessage : Packet + { + public UnicodeMessage( + Serial serial, int graphic, MessageType type, int hue, int font, string lang, string name, + string text + ) : base(0xAE) + { + if (string.IsNullOrEmpty(lang)) lang = "ENU"; + name ??= ""; + text ??= ""; + + if (hue == 0) + hue = 0x3B2; + + EnsureCapacity(50 + text.Length * 2); + + Stream.Write(serial); + Stream.Write((short)graphic); + Stream.Write((byte)type); + Stream.Write((short)hue); + Stream.Write((short)font); + Stream.WriteAsciiFixed(lang, 4); + Stream.WriteAsciiFixed(name, 30); + Stream.WriteBigUniNull(text); + } + } + + public sealed class FollowMessage : Packet + { + public FollowMessage(Serial serial1, Serial serial2) : base(0x15, 9) + { + Stream.Write(serial1); + Stream.Write(serial2); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/MobilePackets.cs b/Projects/Server/Network/Packets/Old Packets/MobilePackets.cs index 02f141eaa..5001c291f 100644 --- a/Projects/Server/Network/Packets/Old Packets/MobilePackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/MobilePackets.cs @@ -1,871 +1,873 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MobilePackets.cs - Created: 2020/05/07 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.Threading; - -namespace Server.Network -{ - public sealed class DeathAnimation : Packet - { - public DeathAnimation(Serial killed, Serial corpse) : base(0xAF, 13) - { - Stream.Write(killed); - Stream.Write(corpse); - Stream.Write(0); - } - } - - public sealed class BondedStatus : Packet - { - public BondedStatus(Serial serial, bool bonded) : base(0xBF) - { - EnsureCapacity(11); - - Stream.Write((short)0x19); - Stream.Write((byte)0); - Stream.Write(serial); - Stream.Write((byte)(bonded ? 1 : 0)); - } - } - - public sealed class MobileMoving : Packet - { - public MobileMoving(Mobile m, int noto) : base(0x77, 17) - { - var loc = m.Location; - - var hue = m.Hue; - - if (m.SolidHueOverride >= 0) - hue = m.SolidHueOverride; - - Stream.Write(m.Serial); - Stream.Write((short)m.Body); - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write((sbyte)loc.m_Z); - Stream.Write((byte)m.Direction); - Stream.Write((short)hue); - Stream.Write((byte)m.GetPacketFlags()); - Stream.Write((byte)noto); - } - } - - public sealed class MobileMovingOld : Packet - { - public MobileMovingOld(Mobile m, int noto) : base(0x77, 17) - { - var loc = m.Location; - - var hue = m.Hue; - - if (m.SolidHueOverride >= 0) - hue = m.SolidHueOverride; - - Stream.Write(m.Serial); - Stream.Write((short)m.Body); - Stream.Write((short)loc.m_X); - Stream.Write((short)loc.m_Y); - Stream.Write((sbyte)loc.m_Z); - Stream.Write((byte)m.Direction); - Stream.Write((short)hue); - Stream.Write((byte)m.GetOldPacketFlags()); - Stream.Write((byte)noto); - } - } - - public sealed class MobileHits : Packet - { - public MobileHits(Mobile m) : base(0xA1, 9) - { - Stream.Write(m.Serial); - Stream.Write((short)m.HitsMax); - Stream.Write((short)m.Hits); - } - } - - public sealed class MobileHitsN : Packet - { - public MobileHitsN(Mobile m) : base(0xA1, 9) - { - Stream.Write(m.Serial); - AttributeNormalizer.Write(Stream, m.Hits, m.HitsMax); - } - } - - public sealed class MobileMana : Packet - { - public MobileMana(Mobile m) : base(0xA2, 9) - { - Stream.Write(m.Serial); - Stream.Write((short)m.ManaMax); - Stream.Write((short)m.Mana); - } - } - - public sealed class MobileManaN : Packet - { - public MobileManaN(Mobile m) : base(0xA2, 9) - { - Stream.Write(m.Serial); - AttributeNormalizer.Write(Stream, m.Mana, m.ManaMax); - } - } - - public sealed class MobileStam : Packet - { - public MobileStam(Mobile m) : base(0xA3, 9) - { - Stream.Write(m.Serial); - Stream.Write((short)m.StamMax); - Stream.Write((short)m.Stam); - } - } - - public sealed class MobileStamN : Packet - { - public MobileStamN(Mobile m) : base(0xA3, 9) - { - Stream.Write(m.Serial); - AttributeNormalizer.Write(Stream, m.Stam, m.StamMax); - } - } - - public sealed class MobileAttributes : Packet - { - public MobileAttributes(Mobile m) : base(0x2D, 17) - { - Stream.Write(m.Serial); - - Stream.Write((short)m.HitsMax); - Stream.Write((short)m.Hits); - - Stream.Write((short)m.ManaMax); - Stream.Write((short)m.Mana); - - Stream.Write((short)m.StamMax); - Stream.Write((short)m.Stam); - } - } - - public sealed class MobileAttributesN : Packet - { - public MobileAttributesN(Mobile m) : base(0x2D, 17) - { - Stream.Write(m.Serial); - - AttributeNormalizer.Write(Stream, m.Hits, m.HitsMax); - AttributeNormalizer.Write(Stream, m.Mana, m.ManaMax); - AttributeNormalizer.Write(Stream, m.Stam, m.StamMax); - } - } - - public sealed class MobileName : Packet - { - public MobileName(Mobile m) : base(0x98) - { - EnsureCapacity(37); - - Stream.Write(m.Serial); - Stream.WriteAsciiFixed(m.Name ?? "", 29); - Stream.Write((byte)0); // Null terminator - } - } - - public sealed class MobileAnimation : Packet - { - public MobileAnimation(Serial mobile, int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay) : base(0x6E, 14) - { - Stream.Write(mobile); - Stream.Write((short)action); - Stream.Write((short)frameCount); - Stream.Write((short)repeatCount); - Stream.Write(!forward); // protocol has really "reverse" but I find this more intuitive - Stream.Write(repeat); - Stream.Write((byte)delay); - } - } - - public sealed class NewMobileAnimation : Packet - { - public NewMobileAnimation(Serial mobile, int action, int frameCount, int delay) : base(0xE2, 10) - { - Stream.Write(mobile); - Stream.Write((short)action); - Stream.Write((short)frameCount); - Stream.Write((byte)delay); - } - } - - public sealed class MobileStatusCompact : Packet - { - public MobileStatusCompact(bool canBeRenamed, Mobile m) : base(0x11) - { - EnsureCapacity(43); - - Stream.Write(m.Serial); - Stream.WriteAsciiFixed(m.Name ?? "", 30); - - AttributeNormalizer.WriteReverse(Stream, m.Hits, m.HitsMax); - - Stream.Write(canBeRenamed); - - Stream.Write((byte)0); // type - } - } - - public sealed class MobileStatusExtended : Packet - { - public MobileStatusExtended(Mobile m) : this(m, m.NetState) - { - } - - public MobileStatusExtended(Mobile m, NetState ns) : base(0x11) - { - var name = m.Name ?? ""; - - int type; - - if (Core.HS && ns?.ExtendedStatus == true) - { - type = 6; - EnsureCapacity(121); - } - else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) - { - type = 5; - EnsureCapacity(91); - } - else - { - type = Core.AOS ? 4 : 3; - EnsureCapacity(88); - } - - Stream.Write(m.Serial); - Stream.WriteAsciiFixed(name, 30); - - Stream.Write((short)m.Hits); - Stream.Write((short)m.HitsMax); - - Stream.Write(m.CanBeRenamedBy(m)); - - Stream.Write((byte)type); - - Stream.Write(m.Female); - - Stream.Write((short)m.Str); - Stream.Write((short)m.Dex); - Stream.Write((short)m.Int); - - Stream.Write((short)m.Stam); - Stream.Write((short)m.StamMax); - - Stream.Write((short)m.Mana); - Stream.Write((short)m.ManaMax); - - Stream.Write(m.TotalGold); - Stream.Write((short)(Core.AOS ? m.PhysicalResistance : (int)(m.ArmorRating + 0.5))); - Stream.Write((short)(Mobile.BodyWeight + m.TotalWeight)); - - if (type >= 5) - { - Stream.Write((short)m.MaxWeight); - Stream.Write((byte)(m.Race.RaceID + 1)); // Would be 0x00 if it's a non-ML enabled account but... - } - - Stream.Write((short)m.StatCap); - - Stream.Write((byte)m.Followers); - Stream.Write((byte)m.FollowersMax); - - if (type >= 4) - { - Stream.Write((short)m.FireResistance); // Fire - Stream.Write((short)m.ColdResistance); // Cold - Stream.Write((short)m.PoisonResistance); // Poison - Stream.Write((short)m.EnergyResistance); // Energy - Stream.Write((short)m.Luck); // Luck - - var weapon = m.Weapon; - - if (weapon != null) - { - weapon.GetStatusDamage(m, out var min, out var max); - Stream.Write((short)min); // Damage min - Stream.Write((short)max); // Damage max - } - else - { - Stream.Write((short)0); // Damage min - Stream.Write((short)0); // Damage max - } - - Stream.Write(m.TithingPoints); - } - - if (type >= 6) - for (var i = 0; i < 15; ++i) - Stream.Write((short)m.GetAOSStatus(i)); - } - } - - public sealed class MobileStatus : Packet - { - public MobileStatus(Mobile beholder, Mobile beheld) : this(beholder, beheld, beheld.NetState) - { - } - - public MobileStatus(Mobile beholder, Mobile beheld, NetState ns) : base(0x11) - { - var name = beheld.Name ?? ""; - - int type; - - if (beholder != beheld) - { - type = 0; - EnsureCapacity(43); - } - else if (Core.HS && ns?.ExtendedStatus == true) - { - type = 6; - EnsureCapacity(121); - } - else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) - { - type = 5; - EnsureCapacity(91); - } - else - { - type = Core.AOS ? 4 : 3; - EnsureCapacity(88); - } - - Stream.Write(beheld.Serial); - - Stream.WriteAsciiFixed(name, 30); - - if (beholder == beheld) - WriteAttr(beheld.Hits, beheld.HitsMax); - else - WriteAttrNorm(beheld.Hits, beheld.HitsMax); - - Stream.Write(beheld.CanBeRenamedBy(beholder)); - - Stream.Write((byte)type); - - if (type <= 0) - return; - - Stream.Write(beheld.Female); - - Stream.Write((short)beheld.Str); - Stream.Write((short)beheld.Dex); - Stream.Write((short)beheld.Int); - - WriteAttr(beheld.Stam, beheld.StamMax); - WriteAttr(beheld.Mana, beheld.ManaMax); - - Stream.Write(beheld.TotalGold); - Stream.Write((short)(Core.AOS ? beheld.PhysicalResistance : (int)(beheld.ArmorRating + 0.5))); - Stream.Write((short)(Mobile.BodyWeight + beheld.TotalWeight)); - - if (type >= 5) - { - Stream.Write((short)beheld.MaxWeight); - Stream.Write((byte)(beheld.Race.RaceID + 1)); // Would be 0x00 if it's a non-ML enabled account but... - } - - Stream.Write((short)beheld.StatCap); - - Stream.Write((byte)beheld.Followers); - Stream.Write((byte)beheld.FollowersMax); - - if (type >= 4) - { - Stream.Write((short)beheld.FireResistance); // Fire - Stream.Write((short)beheld.ColdResistance); // Cold - Stream.Write((short)beheld.PoisonResistance); // Poison - Stream.Write((short)beheld.EnergyResistance); // Energy - Stream.Write((short)beheld.Luck); // Luck - - var weapon = beheld.Weapon; - - if (weapon != null) - { - weapon.GetStatusDamage(beheld, out var min, out var max); - Stream.Write((short)min); // Damage min - Stream.Write((short)max); // Damage max - } - else - { - Stream.Write((short)0); // Damage min - Stream.Write((short)0); // Damage max - } - - Stream.Write(beheld.TithingPoints); - } - - if (type >= 6) - for (var i = 0; i < 15; ++i) - Stream.Write((short)beheld.GetAOSStatus(i)); - } - - private void WriteAttr(int current, int maximum) - { - Stream.Write((short)current); - Stream.Write((short)maximum); - } - - private void WriteAttrNorm(int current, int maximum) - { - AttributeNormalizer.WriteReverse(Stream, current, maximum); - } - } - - public sealed class HealthbarPoison : Packet - { - public HealthbarPoison(Mobile m) : base(0x17) - { - EnsureCapacity(12); - - Stream.Write(m.Serial); - Stream.Write((short)1); // Show Bar? - - Stream.Write((short)1); // Poison Bar - - var p = m.Poison; - - if (p != null) - Stream.Write((byte)(p.Level + 1)); - else - Stream.Write((byte)0); - } - } - - public sealed class HealthbarYellow : Packet - { - public HealthbarYellow(Mobile m) : base(0x17) - { - EnsureCapacity(12); - - Stream.Write(m.Serial); - Stream.Write((short)1); - - Stream.Write((short)2); - - if (m.Blessed || m.YellowHealthbar) - Stream.Write((byte)1); - else - Stream.Write((byte)0); - } - } - - public sealed class MobileUpdate : Packet - { - public MobileUpdate(Mobile m) : base(0x20, 19) - { - var hue = m.Hue; - - if (m.SolidHueOverride >= 0) - hue = m.SolidHueOverride; - - Stream.Write(m.Serial); - Stream.Write((short)m.Body); - Stream.Write((byte)0); - Stream.Write((short)hue); - Stream.Write((byte)m.GetPacketFlags()); - Stream.Write((short)m.X); - Stream.Write((short)m.Y); - Stream.Write((short)0); - Stream.Write((byte)m.Direction); - Stream.Write((sbyte)m.Z); - } - } - - // Pre-7.0.0.0 Mobile Update - public sealed class MobileUpdateOld : Packet - { - public MobileUpdateOld(Mobile m) : base(0x20, 19) - { - var hue = m.Hue; - - if (m.SolidHueOverride >= 0) - hue = m.SolidHueOverride; - - Stream.Write(m.Serial); - Stream.Write((short)m.Body); - Stream.Write((byte)0); - Stream.Write((short)hue); - Stream.Write((byte)m.GetOldPacketFlags()); - Stream.Write((short)m.X); - Stream.Write((short)m.Y); - Stream.Write((short)0); - Stream.Write((byte)m.Direction); - Stream.Write((sbyte)m.Z); - } - } - - public sealed class MobileIncoming : Packet - { - private static readonly ThreadLocal m_DupedLayersTL = new ThreadLocal(() => new int[256]); - private static readonly ThreadLocal m_VersionTL = new ThreadLocal(); - - public MobileIncoming(Mobile beholder, Mobile beheld) : base(0x78) - { - var m_Version = ++m_VersionTL.Value; - var m_DupedLayers = m_DupedLayersTL.Value; - - var eq = beheld.Items; - var count = eq.Count; - - if (beheld.HairItemID > 0) - count++; - if (beheld.FacialHairItemID > 0) - count++; - - EnsureCapacity(23 + count * 9); - - var hue = beheld.Hue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - Stream.Write(beheld.Serial); - Stream.Write((short)beheld.Body); - Stream.Write((short)beheld.X); - Stream.Write((short)beheld.Y); - Stream.Write((sbyte)beheld.Z); - Stream.Write((byte)beheld.Direction); - Stream.Write((short)hue); - Stream.Write((byte)beheld.GetPacketFlags()); - Stream.Write((byte)Notoriety.Compute(beholder, beheld)); - - for (var i = 0; i < eq.Count; ++i) - { - var item = eq[i]; - - var layer = (byte)item.Layer; - - if (!item.Deleted && beholder.CanSee(item) && m_DupedLayers[layer] != m_Version) - { - m_DupedLayers[layer] = m_Version; - - hue = item.Hue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = item.ItemID & 0xFFFF; - - Stream.Write(item.Serial); - Stream.Write((ushort)itemID); - Stream.Write(layer); - - Stream.Write((short)hue); - } - } - - if (beheld.HairItemID > 0) - if (m_DupedLayers[(int)Layer.Hair] != m_Version) - { - m_DupedLayers[(int)Layer.Hair] = m_Version; - hue = beheld.HairHue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = beheld.HairItemID & 0xFFFF; - - Stream.Write(HairInfo.FakeSerial(beheld)); - Stream.Write((ushort)itemID); - Stream.Write((byte)Layer.Hair); - - Stream.Write((short)hue); - } - - if (beheld.FacialHairItemID > 0) - if (m_DupedLayers[(int)Layer.FacialHair] != m_Version) - { - m_DupedLayers[(int)Layer.FacialHair] = m_Version; - hue = beheld.FacialHairHue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = beheld.FacialHairItemID & 0xFFFF; - - Stream.Write(FacialHairInfo.FakeSerial(beheld)); - Stream.Write((ushort)itemID); - Stream.Write((byte)Layer.FacialHair); - - Stream.Write((short)hue); - } - - Stream.Write(0); // terminate - } - - public static Packet Create(NetState ns, Mobile beholder, Mobile beheld) - { - if (ns.NewMobileIncoming) - return new MobileIncoming(beholder, beheld); - if (ns.StygianAbyss) - return new MobileIncomingSA(beholder, beheld); - return new MobileIncomingOld(beholder, beheld); - } - } - - public sealed class MobileIncomingSA : Packet - { - private static readonly ThreadLocal m_DupedLayersTL = new ThreadLocal(() => new int[256]); - private static readonly ThreadLocal m_VersionTL = new ThreadLocal(); - - public MobileIncomingSA(Mobile beholder, Mobile beheld) : base(0x78) - { - var m_Version = ++m_VersionTL.Value; - var m_DupedLayers = m_DupedLayersTL.Value; - - var eq = beheld.Items; - var count = eq.Count; - - if (beheld.HairItemID > 0) - count++; - if (beheld.FacialHairItemID > 0) - count++; - - EnsureCapacity(23 + count * 9); - - var hue = beheld.Hue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - Stream.Write(beheld.Serial); - Stream.Write((short)beheld.Body); - Stream.Write((short)beheld.X); - Stream.Write((short)beheld.Y); - Stream.Write((sbyte)beheld.Z); - Stream.Write((byte)beheld.Direction); - Stream.Write((short)hue); - Stream.Write((byte)beheld.GetPacketFlags()); - Stream.Write((byte)Notoriety.Compute(beholder, beheld)); - - for (var i = 0; i < eq.Count; ++i) - { - var item = eq[i]; - - var layer = (byte)item.Layer; - - if (!item.Deleted && beholder.CanSee(item) && m_DupedLayers[layer] != m_Version) - { - m_DupedLayers[layer] = m_Version; - - hue = item.Hue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = item.ItemID & 0x7FFF; - var writeHue = hue != 0; - - if (writeHue) - itemID |= 0x8000; - - Stream.Write(item.Serial); - Stream.Write((ushort)itemID); - Stream.Write(layer); - - if (writeHue) - Stream.Write((short)hue); - } - } - - if (beheld.HairItemID > 0) - if (m_DupedLayers[(int)Layer.Hair] != m_Version) - { - m_DupedLayers[(int)Layer.Hair] = m_Version; - hue = beheld.HairHue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = beheld.HairItemID & 0x7FFF; - - var writeHue = hue != 0; - - if (writeHue) - itemID |= 0x8000; - - Stream.Write(HairInfo.FakeSerial(beheld)); - Stream.Write((ushort)itemID); - Stream.Write((byte)Layer.Hair); - - if (writeHue) - Stream.Write((short)hue); - } - - if (beheld.FacialHairItemID > 0) - if (m_DupedLayers[(int)Layer.FacialHair] != m_Version) - { - m_DupedLayers[(int)Layer.FacialHair] = m_Version; - hue = beheld.FacialHairHue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = beheld.FacialHairItemID & 0x7FFF; - - var writeHue = hue != 0; - - if (writeHue) - itemID |= 0x8000; - - Stream.Write(FacialHairInfo.FakeSerial(beheld)); - Stream.Write((ushort)itemID); - Stream.Write((byte)Layer.FacialHair); - - if (writeHue) - Stream.Write((short)hue); - } - - Stream.Write(0); // terminate - } - } - - // Pre-7.0.0.0 Mobile Incoming - public sealed class MobileIncomingOld : Packet - { - private static readonly ThreadLocal m_DupedLayersTL = new ThreadLocal(() => new int[256]); - private static readonly ThreadLocal m_VersionTL = new ThreadLocal(); - - public MobileIncomingOld(Mobile beholder, Mobile beheld) : base(0x78) - { - var m_Version = ++m_VersionTL.Value; - var m_DupedLayers = m_DupedLayersTL.Value; - - var eq = beheld.Items; - var count = eq.Count; - - if (beheld.HairItemID > 0) - count++; - if (beheld.FacialHairItemID > 0) - count++; - - EnsureCapacity(23 + count * 9); - - var hue = beheld.Hue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - Stream.Write(beheld.Serial); - Stream.Write((short)beheld.Body); - Stream.Write((short)beheld.X); - Stream.Write((short)beheld.Y); - Stream.Write((sbyte)beheld.Z); - Stream.Write((byte)beheld.Direction); - Stream.Write((short)hue); - Stream.Write((byte)beheld.GetOldPacketFlags()); - Stream.Write((byte)Notoriety.Compute(beholder, beheld)); - - for (var i = 0; i < eq.Count; ++i) - { - var item = eq[i]; - - var layer = (byte)item.Layer; - - if (!item.Deleted && beholder.CanSee(item) && m_DupedLayers[layer] != m_Version) - { - m_DupedLayers[layer] = m_Version; - - hue = item.Hue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = item.ItemID & 0x7FFF; - var writeHue = hue != 0; - - if (writeHue) - itemID |= 0x8000; - - Stream.Write(item.Serial); - Stream.Write((ushort)itemID); - Stream.Write(layer); - - if (writeHue) - Stream.Write((short)hue); - } - } - - if (beheld.HairItemID > 0) - if (m_DupedLayers[(int)Layer.Hair] != m_Version) - { - m_DupedLayers[(int)Layer.Hair] = m_Version; - hue = beheld.HairHue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = beheld.HairItemID & 0x7FFF; - - var writeHue = hue != 0; - - if (writeHue) - itemID |= 0x8000; - - Stream.Write(HairInfo.FakeSerial(beheld)); - Stream.Write((ushort)itemID); - Stream.Write((byte)Layer.Hair); - - if (writeHue) - Stream.Write((short)hue); - } - - if (beheld.FacialHairItemID > 0) - if (m_DupedLayers[(int)Layer.FacialHair] != m_Version) - { - m_DupedLayers[(int)Layer.FacialHair] = m_Version; - hue = beheld.FacialHairHue; - - if (beheld.SolidHueOverride >= 0) - hue = beheld.SolidHueOverride; - - var itemID = beheld.FacialHairItemID & 0x7FFF; - - var writeHue = hue != 0; - - if (writeHue) - itemID |= 0x8000; - - Stream.Write(FacialHairInfo.FakeSerial(beheld)); - Stream.Write((ushort)itemID); - Stream.Write((byte)Layer.FacialHair); - - if (writeHue) - Stream.Write((short)hue); - } - - Stream.Write(0); // terminate - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MobilePackets.cs - Created: 2020/05/07 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Threading; + +namespace Server.Network +{ + public sealed class DeathAnimation : Packet + { + public DeathAnimation(Serial killed, Serial corpse) : base(0xAF, 13) + { + Stream.Write(killed); + Stream.Write(corpse); + Stream.Write(0); + } + } + + public sealed class BondedStatus : Packet + { + public BondedStatus(Serial serial, bool bonded) : base(0xBF) + { + EnsureCapacity(11); + + Stream.Write((short)0x19); + Stream.Write((byte)0); + Stream.Write(serial); + Stream.Write((byte)(bonded ? 1 : 0)); + } + } + + public sealed class MobileMoving : Packet + { + public MobileMoving(Mobile m, int noto) : base(0x77, 17) + { + var loc = m.Location; + + var hue = m.Hue; + + if (m.SolidHueOverride >= 0) + hue = m.SolidHueOverride; + + Stream.Write(m.Serial); + Stream.Write((short)m.Body); + Stream.Write((short)loc.m_X); + Stream.Write((short)loc.m_Y); + Stream.Write((sbyte)loc.m_Z); + Stream.Write((byte)m.Direction); + Stream.Write((short)hue); + Stream.Write((byte)m.GetPacketFlags()); + Stream.Write((byte)noto); + } + } + + public sealed class MobileMovingOld : Packet + { + public MobileMovingOld(Mobile m, int noto) : base(0x77, 17) + { + var loc = m.Location; + + var hue = m.Hue; + + if (m.SolidHueOverride >= 0) + hue = m.SolidHueOverride; + + Stream.Write(m.Serial); + Stream.Write((short)m.Body); + Stream.Write((short)loc.m_X); + Stream.Write((short)loc.m_Y); + Stream.Write((sbyte)loc.m_Z); + Stream.Write((byte)m.Direction); + Stream.Write((short)hue); + Stream.Write((byte)m.GetOldPacketFlags()); + Stream.Write((byte)noto); + } + } + + public sealed class MobileHits : Packet + { + public MobileHits(Mobile m) : base(0xA1, 9) + { + Stream.Write(m.Serial); + Stream.Write((short)m.HitsMax); + Stream.Write((short)m.Hits); + } + } + + public sealed class MobileHitsN : Packet + { + public MobileHitsN(Mobile m) : base(0xA1, 9) + { + Stream.Write(m.Serial); + AttributeNormalizer.Write(Stream, m.Hits, m.HitsMax); + } + } + + public sealed class MobileMana : Packet + { + public MobileMana(Mobile m) : base(0xA2, 9) + { + Stream.Write(m.Serial); + Stream.Write((short)m.ManaMax); + Stream.Write((short)m.Mana); + } + } + + public sealed class MobileManaN : Packet + { + public MobileManaN(Mobile m) : base(0xA2, 9) + { + Stream.Write(m.Serial); + AttributeNormalizer.Write(Stream, m.Mana, m.ManaMax); + } + } + + public sealed class MobileStam : Packet + { + public MobileStam(Mobile m) : base(0xA3, 9) + { + Stream.Write(m.Serial); + Stream.Write((short)m.StamMax); + Stream.Write((short)m.Stam); + } + } + + public sealed class MobileStamN : Packet + { + public MobileStamN(Mobile m) : base(0xA3, 9) + { + Stream.Write(m.Serial); + AttributeNormalizer.Write(Stream, m.Stam, m.StamMax); + } + } + + public sealed class MobileAttributes : Packet + { + public MobileAttributes(Mobile m) : base(0x2D, 17) + { + Stream.Write(m.Serial); + + Stream.Write((short)m.HitsMax); + Stream.Write((short)m.Hits); + + Stream.Write((short)m.ManaMax); + Stream.Write((short)m.Mana); + + Stream.Write((short)m.StamMax); + Stream.Write((short)m.Stam); + } + } + + public sealed class MobileAttributesN : Packet + { + public MobileAttributesN(Mobile m) : base(0x2D, 17) + { + Stream.Write(m.Serial); + + AttributeNormalizer.Write(Stream, m.Hits, m.HitsMax); + AttributeNormalizer.Write(Stream, m.Mana, m.ManaMax); + AttributeNormalizer.Write(Stream, m.Stam, m.StamMax); + } + } + + public sealed class MobileName : Packet + { + public MobileName(Mobile m) : base(0x98) + { + EnsureCapacity(37); + + Stream.Write(m.Serial); + Stream.WriteAsciiFixed(m.Name ?? "", 29); + Stream.Write((byte)0); // Null terminator + } + } + + public sealed class MobileAnimation : Packet + { + public MobileAnimation( + Serial mobile, int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay + ) : base(0x6E, 14) + { + Stream.Write(mobile); + Stream.Write((short)action); + Stream.Write((short)frameCount); + Stream.Write((short)repeatCount); + Stream.Write(!forward); // protocol has really "reverse" but I find this more intuitive + Stream.Write(repeat); + Stream.Write((byte)delay); + } + } + + public sealed class NewMobileAnimation : Packet + { + public NewMobileAnimation(Serial mobile, int action, int frameCount, int delay) : base(0xE2, 10) + { + Stream.Write(mobile); + Stream.Write((short)action); + Stream.Write((short)frameCount); + Stream.Write((byte)delay); + } + } + + public sealed class MobileStatusCompact : Packet + { + public MobileStatusCompact(bool canBeRenamed, Mobile m) : base(0x11) + { + EnsureCapacity(43); + + Stream.Write(m.Serial); + Stream.WriteAsciiFixed(m.Name ?? "", 30); + + AttributeNormalizer.WriteReverse(Stream, m.Hits, m.HitsMax); + + Stream.Write(canBeRenamed); + + Stream.Write((byte)0); // type + } + } + + public sealed class MobileStatusExtended : Packet + { + public MobileStatusExtended(Mobile m) : this(m, m.NetState) + { + } + + public MobileStatusExtended(Mobile m, NetState ns) : base(0x11) + { + var name = m.Name ?? ""; + + int type; + + if (Core.HS && ns?.ExtendedStatus == true) + { + type = 6; + EnsureCapacity(121); + } + else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) + { + type = 5; + EnsureCapacity(91); + } + else + { + type = Core.AOS ? 4 : 3; + EnsureCapacity(88); + } + + Stream.Write(m.Serial); + Stream.WriteAsciiFixed(name, 30); + + Stream.Write((short)m.Hits); + Stream.Write((short)m.HitsMax); + + Stream.Write(m.CanBeRenamedBy(m)); + + Stream.Write((byte)type); + + Stream.Write(m.Female); + + Stream.Write((short)m.Str); + Stream.Write((short)m.Dex); + Stream.Write((short)m.Int); + + Stream.Write((short)m.Stam); + Stream.Write((short)m.StamMax); + + Stream.Write((short)m.Mana); + Stream.Write((short)m.ManaMax); + + Stream.Write(m.TotalGold); + Stream.Write((short)(Core.AOS ? m.PhysicalResistance : (int)(m.ArmorRating + 0.5))); + Stream.Write((short)(Mobile.BodyWeight + m.TotalWeight)); + + if (type >= 5) + { + Stream.Write((short)m.MaxWeight); + Stream.Write((byte)(m.Race.RaceID + 1)); // Would be 0x00 if it's a non-ML enabled account but... + } + + Stream.Write((short)m.StatCap); + + Stream.Write((byte)m.Followers); + Stream.Write((byte)m.FollowersMax); + + if (type >= 4) + { + Stream.Write((short)m.FireResistance); // Fire + Stream.Write((short)m.ColdResistance); // Cold + Stream.Write((short)m.PoisonResistance); // Poison + Stream.Write((short)m.EnergyResistance); // Energy + Stream.Write((short)m.Luck); // Luck + + var weapon = m.Weapon; + + if (weapon != null) + { + weapon.GetStatusDamage(m, out var min, out var max); + Stream.Write((short)min); // Damage min + Stream.Write((short)max); // Damage max + } + else + { + Stream.Write((short)0); // Damage min + Stream.Write((short)0); // Damage max + } + + Stream.Write(m.TithingPoints); + } + + if (type >= 6) + for (var i = 0; i < 15; ++i) + Stream.Write((short)m.GetAOSStatus(i)); + } + } + + public sealed class MobileStatus : Packet + { + public MobileStatus(Mobile beholder, Mobile beheld) : this(beholder, beheld, beheld.NetState) + { + } + + public MobileStatus(Mobile beholder, Mobile beheld, NetState ns) : base(0x11) + { + var name = beheld.Name ?? ""; + + int type; + + if (beholder != beheld) + { + type = 0; + EnsureCapacity(43); + } + else if (Core.HS && ns?.ExtendedStatus == true) + { + type = 6; + EnsureCapacity(121); + } + else if (Core.ML && ns?.SupportsExpansion(Expansion.ML) == true) + { + type = 5; + EnsureCapacity(91); + } + else + { + type = Core.AOS ? 4 : 3; + EnsureCapacity(88); + } + + Stream.Write(beheld.Serial); + + Stream.WriteAsciiFixed(name, 30); + + if (beholder == beheld) + WriteAttr(beheld.Hits, beheld.HitsMax); + else + WriteAttrNorm(beheld.Hits, beheld.HitsMax); + + Stream.Write(beheld.CanBeRenamedBy(beholder)); + + Stream.Write((byte)type); + + if (type <= 0) + return; + + Stream.Write(beheld.Female); + + Stream.Write((short)beheld.Str); + Stream.Write((short)beheld.Dex); + Stream.Write((short)beheld.Int); + + WriteAttr(beheld.Stam, beheld.StamMax); + WriteAttr(beheld.Mana, beheld.ManaMax); + + Stream.Write(beheld.TotalGold); + Stream.Write((short)(Core.AOS ? beheld.PhysicalResistance : (int)(beheld.ArmorRating + 0.5))); + Stream.Write((short)(Mobile.BodyWeight + beheld.TotalWeight)); + + if (type >= 5) + { + Stream.Write((short)beheld.MaxWeight); + Stream.Write((byte)(beheld.Race.RaceID + 1)); // Would be 0x00 if it's a non-ML enabled account but... + } + + Stream.Write((short)beheld.StatCap); + + Stream.Write((byte)beheld.Followers); + Stream.Write((byte)beheld.FollowersMax); + + if (type >= 4) + { + Stream.Write((short)beheld.FireResistance); // Fire + Stream.Write((short)beheld.ColdResistance); // Cold + Stream.Write((short)beheld.PoisonResistance); // Poison + Stream.Write((short)beheld.EnergyResistance); // Energy + Stream.Write((short)beheld.Luck); // Luck + + var weapon = beheld.Weapon; + + if (weapon != null) + { + weapon.GetStatusDamage(beheld, out var min, out var max); + Stream.Write((short)min); // Damage min + Stream.Write((short)max); // Damage max + } + else + { + Stream.Write((short)0); // Damage min + Stream.Write((short)0); // Damage max + } + + Stream.Write(beheld.TithingPoints); + } + + if (type >= 6) + for (var i = 0; i < 15; ++i) + Stream.Write((short)beheld.GetAOSStatus(i)); + } + + private void WriteAttr(int current, int maximum) + { + Stream.Write((short)current); + Stream.Write((short)maximum); + } + + private void WriteAttrNorm(int current, int maximum) + { + AttributeNormalizer.WriteReverse(Stream, current, maximum); + } + } + + public sealed class HealthbarPoison : Packet + { + public HealthbarPoison(Mobile m) : base(0x17) + { + EnsureCapacity(12); + + Stream.Write(m.Serial); + Stream.Write((short)1); // Show Bar? + + Stream.Write((short)1); // Poison Bar + + var p = m.Poison; + + if (p != null) + Stream.Write((byte)(p.Level + 1)); + else + Stream.Write((byte)0); + } + } + + public sealed class HealthbarYellow : Packet + { + public HealthbarYellow(Mobile m) : base(0x17) + { + EnsureCapacity(12); + + Stream.Write(m.Serial); + Stream.Write((short)1); + + Stream.Write((short)2); + + if (m.Blessed || m.YellowHealthbar) + Stream.Write((byte)1); + else + Stream.Write((byte)0); + } + } + + public sealed class MobileUpdate : Packet + { + public MobileUpdate(Mobile m) : base(0x20, 19) + { + var hue = m.Hue; + + if (m.SolidHueOverride >= 0) + hue = m.SolidHueOverride; + + Stream.Write(m.Serial); + Stream.Write((short)m.Body); + Stream.Write((byte)0); + Stream.Write((short)hue); + Stream.Write((byte)m.GetPacketFlags()); + Stream.Write((short)m.X); + Stream.Write((short)m.Y); + Stream.Write((short)0); + Stream.Write((byte)m.Direction); + Stream.Write((sbyte)m.Z); + } + } + + // Pre-7.0.0.0 Mobile Update + public sealed class MobileUpdateOld : Packet + { + public MobileUpdateOld(Mobile m) : base(0x20, 19) + { + var hue = m.Hue; + + if (m.SolidHueOverride >= 0) + hue = m.SolidHueOverride; + + Stream.Write(m.Serial); + Stream.Write((short)m.Body); + Stream.Write((byte)0); + Stream.Write((short)hue); + Stream.Write((byte)m.GetOldPacketFlags()); + Stream.Write((short)m.X); + Stream.Write((short)m.Y); + Stream.Write((short)0); + Stream.Write((byte)m.Direction); + Stream.Write((sbyte)m.Z); + } + } + + public sealed class MobileIncoming : Packet + { + private static readonly ThreadLocal m_DupedLayersTL = new ThreadLocal(() => new int[256]); + private static readonly ThreadLocal m_VersionTL = new ThreadLocal(); + + public MobileIncoming(Mobile beholder, Mobile beheld) : base(0x78) + { + var m_Version = ++m_VersionTL.Value; + var m_DupedLayers = m_DupedLayersTL.Value; + + var eq = beheld.Items; + var count = eq.Count; + + if (beheld.HairItemID > 0) + count++; + if (beheld.FacialHairItemID > 0) + count++; + + EnsureCapacity(23 + count * 9); + + var hue = beheld.Hue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + Stream.Write(beheld.Serial); + Stream.Write((short)beheld.Body); + Stream.Write((short)beheld.X); + Stream.Write((short)beheld.Y); + Stream.Write((sbyte)beheld.Z); + Stream.Write((byte)beheld.Direction); + Stream.Write((short)hue); + Stream.Write((byte)beheld.GetPacketFlags()); + Stream.Write((byte)Notoriety.Compute(beholder, beheld)); + + for (var i = 0; i < eq.Count; ++i) + { + var item = eq[i]; + + var layer = (byte)item.Layer; + + if (!item.Deleted && beholder.CanSee(item) && m_DupedLayers[layer] != m_Version) + { + m_DupedLayers[layer] = m_Version; + + hue = item.Hue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = item.ItemID & 0xFFFF; + + Stream.Write(item.Serial); + Stream.Write((ushort)itemID); + Stream.Write(layer); + + Stream.Write((short)hue); + } + } + + if (beheld.HairItemID > 0) + if (m_DupedLayers[(int)Layer.Hair] != m_Version) + { + m_DupedLayers[(int)Layer.Hair] = m_Version; + hue = beheld.HairHue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = beheld.HairItemID & 0xFFFF; + + Stream.Write(HairInfo.FakeSerial(beheld)); + Stream.Write((ushort)itemID); + Stream.Write((byte)Layer.Hair); + + Stream.Write((short)hue); + } + + if (beheld.FacialHairItemID > 0) + if (m_DupedLayers[(int)Layer.FacialHair] != m_Version) + { + m_DupedLayers[(int)Layer.FacialHair] = m_Version; + hue = beheld.FacialHairHue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = beheld.FacialHairItemID & 0xFFFF; + + Stream.Write(FacialHairInfo.FakeSerial(beheld)); + Stream.Write((ushort)itemID); + Stream.Write((byte)Layer.FacialHair); + + Stream.Write((short)hue); + } + + Stream.Write(0); // terminate + } + + public static Packet Create(NetState ns, Mobile beholder, Mobile beheld) + { + if (ns.NewMobileIncoming) + return new MobileIncoming(beholder, beheld); + if (ns.StygianAbyss) + return new MobileIncomingSA(beholder, beheld); + return new MobileIncomingOld(beholder, beheld); + } + } + + public sealed class MobileIncomingSA : Packet + { + private static readonly ThreadLocal m_DupedLayersTL = new ThreadLocal(() => new int[256]); + private static readonly ThreadLocal m_VersionTL = new ThreadLocal(); + + public MobileIncomingSA(Mobile beholder, Mobile beheld) : base(0x78) + { + var m_Version = ++m_VersionTL.Value; + var m_DupedLayers = m_DupedLayersTL.Value; + + var eq = beheld.Items; + var count = eq.Count; + + if (beheld.HairItemID > 0) + count++; + if (beheld.FacialHairItemID > 0) + count++; + + EnsureCapacity(23 + count * 9); + + var hue = beheld.Hue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + Stream.Write(beheld.Serial); + Stream.Write((short)beheld.Body); + Stream.Write((short)beheld.X); + Stream.Write((short)beheld.Y); + Stream.Write((sbyte)beheld.Z); + Stream.Write((byte)beheld.Direction); + Stream.Write((short)hue); + Stream.Write((byte)beheld.GetPacketFlags()); + Stream.Write((byte)Notoriety.Compute(beholder, beheld)); + + for (var i = 0; i < eq.Count; ++i) + { + var item = eq[i]; + + var layer = (byte)item.Layer; + + if (!item.Deleted && beholder.CanSee(item) && m_DupedLayers[layer] != m_Version) + { + m_DupedLayers[layer] = m_Version; + + hue = item.Hue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = item.ItemID & 0x7FFF; + var writeHue = hue != 0; + + if (writeHue) + itemID |= 0x8000; + + Stream.Write(item.Serial); + Stream.Write((ushort)itemID); + Stream.Write(layer); + + if (writeHue) + Stream.Write((short)hue); + } + } + + if (beheld.HairItemID > 0) + if (m_DupedLayers[(int)Layer.Hair] != m_Version) + { + m_DupedLayers[(int)Layer.Hair] = m_Version; + hue = beheld.HairHue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = beheld.HairItemID & 0x7FFF; + + var writeHue = hue != 0; + + if (writeHue) + itemID |= 0x8000; + + Stream.Write(HairInfo.FakeSerial(beheld)); + Stream.Write((ushort)itemID); + Stream.Write((byte)Layer.Hair); + + if (writeHue) + Stream.Write((short)hue); + } + + if (beheld.FacialHairItemID > 0) + if (m_DupedLayers[(int)Layer.FacialHair] != m_Version) + { + m_DupedLayers[(int)Layer.FacialHair] = m_Version; + hue = beheld.FacialHairHue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = beheld.FacialHairItemID & 0x7FFF; + + var writeHue = hue != 0; + + if (writeHue) + itemID |= 0x8000; + + Stream.Write(FacialHairInfo.FakeSerial(beheld)); + Stream.Write((ushort)itemID); + Stream.Write((byte)Layer.FacialHair); + + if (writeHue) + Stream.Write((short)hue); + } + + Stream.Write(0); // terminate + } + } + + // Pre-7.0.0.0 Mobile Incoming + public sealed class MobileIncomingOld : Packet + { + private static readonly ThreadLocal m_DupedLayersTL = new ThreadLocal(() => new int[256]); + private static readonly ThreadLocal m_VersionTL = new ThreadLocal(); + + public MobileIncomingOld(Mobile beholder, Mobile beheld) : base(0x78) + { + var m_Version = ++m_VersionTL.Value; + var m_DupedLayers = m_DupedLayersTL.Value; + + var eq = beheld.Items; + var count = eq.Count; + + if (beheld.HairItemID > 0) + count++; + if (beheld.FacialHairItemID > 0) + count++; + + EnsureCapacity(23 + count * 9); + + var hue = beheld.Hue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + Stream.Write(beheld.Serial); + Stream.Write((short)beheld.Body); + Stream.Write((short)beheld.X); + Stream.Write((short)beheld.Y); + Stream.Write((sbyte)beheld.Z); + Stream.Write((byte)beheld.Direction); + Stream.Write((short)hue); + Stream.Write((byte)beheld.GetOldPacketFlags()); + Stream.Write((byte)Notoriety.Compute(beholder, beheld)); + + for (var i = 0; i < eq.Count; ++i) + { + var item = eq[i]; + + var layer = (byte)item.Layer; + + if (!item.Deleted && beholder.CanSee(item) && m_DupedLayers[layer] != m_Version) + { + m_DupedLayers[layer] = m_Version; + + hue = item.Hue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = item.ItemID & 0x7FFF; + var writeHue = hue != 0; + + if (writeHue) + itemID |= 0x8000; + + Stream.Write(item.Serial); + Stream.Write((ushort)itemID); + Stream.Write(layer); + + if (writeHue) + Stream.Write((short)hue); + } + } + + if (beheld.HairItemID > 0) + if (m_DupedLayers[(int)Layer.Hair] != m_Version) + { + m_DupedLayers[(int)Layer.Hair] = m_Version; + hue = beheld.HairHue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = beheld.HairItemID & 0x7FFF; + + var writeHue = hue != 0; + + if (writeHue) + itemID |= 0x8000; + + Stream.Write(HairInfo.FakeSerial(beheld)); + Stream.Write((ushort)itemID); + Stream.Write((byte)Layer.Hair); + + if (writeHue) + Stream.Write((short)hue); + } + + if (beheld.FacialHairItemID > 0) + if (m_DupedLayers[(int)Layer.FacialHair] != m_Version) + { + m_DupedLayers[(int)Layer.FacialHair] = m_Version; + hue = beheld.FacialHairHue; + + if (beheld.SolidHueOverride >= 0) + hue = beheld.SolidHueOverride; + + var itemID = beheld.FacialHairItemID & 0x7FFF; + + var writeHue = hue != 0; + + if (writeHue) + itemID |= 0x8000; + + Stream.Write(FacialHairInfo.FakeSerial(beheld)); + Stream.Write((ushort)itemID); + Stream.Write((byte)Layer.FacialHair); + + if (writeHue) + Stream.Write((short)hue); + } + + Stream.Write(0); // terminate + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/MovementPackets.cs b/Projects/Server/Network/Packets/Old Packets/MovementPackets.cs index e4d7e0247..428285597 100644 --- a/Projects/Server/Network/Packets/Old Packets/MovementPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/MovementPackets.cs @@ -1,103 +1,103 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: MovementPackets.cs - Created: 2020/06/25 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public sealed class SpeedControl : Packet - { - public static readonly Packet WalkSpeed = SetStatic(new SpeedControl(2)); - public static readonly Packet MountSpeed = SetStatic(new SpeedControl(1)); - public static readonly Packet Disable = SetStatic(new SpeedControl(0)); - - public SpeedControl(int speedControl) : base(0xBF) - { - EnsureCapacity(3); - - Stream.Write((short)0x26); - Stream.Write((byte)speedControl); - } - } - - /// - /// Causes the client to walk in a given direction. It does not send a movement request. - /// - public sealed class MovePlayer : Packet - { - public MovePlayer(Direction d) : base(0x97, 2) - { - Stream.Write((byte)d); - - // @4C63B0 - } - } - - public sealed class MovementRej : Packet - { - public MovementRej(int seq, Mobile m) : base(0x21, 8) - { - Stream.Write((byte)seq); - Stream.Write((short)m.X); - Stream.Write((short)m.Y); - Stream.Write((byte)m.Direction); - Stream.Write((sbyte)m.Z); - } - } - - public sealed class MovementAck : Packet - { - private static readonly MovementAck[] m_Cache = new MovementAck[8 * 256]; - - private MovementAck(int seq, int noto) : base(0x22, 3) - { - Stream.Write((byte)seq); - Stream.Write((byte)noto); - } - - public static MovementAck Instantiate(int seq, Mobile m) - { - var noto = Notoriety.Compute(m, m); - - var p = m_Cache[noto * seq]; - - if (p == null) - { - m_Cache[noto * seq] = p = new MovementAck(seq, noto); - p.SetStatic(); - } - - return p; - } - } - - public sealed class NullFastwalkStack : Packet - { - public NullFastwalkStack() : base(0xBF) - { - EnsureCapacity(256); - Stream.Write((short)0x1); - Stream.Write(0x0); - Stream.Write(0x0); - Stream.Write(0x0); - Stream.Write(0x0); - Stream.Write(0x0); - Stream.Write(0x0); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MovementPackets.cs - Created: 2020/06/25 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public sealed class SpeedControl : Packet + { + public static readonly Packet WalkSpeed = SetStatic(new SpeedControl(2)); + public static readonly Packet MountSpeed = SetStatic(new SpeedControl(1)); + public static readonly Packet Disable = SetStatic(new SpeedControl(0)); + + public SpeedControl(int speedControl) : base(0xBF) + { + EnsureCapacity(3); + + Stream.Write((short)0x26); + Stream.Write((byte)speedControl); + } + } + + /// + /// Causes the client to walk in a given direction. It does not send a movement request. + /// + public sealed class MovePlayer : Packet + { + public MovePlayer(Direction d) : base(0x97, 2) + { + Stream.Write((byte)d); + + // @4C63B0 + } + } + + public sealed class MovementRej : Packet + { + public MovementRej(int seq, Mobile m) : base(0x21, 8) + { + Stream.Write((byte)seq); + Stream.Write((short)m.X); + Stream.Write((short)m.Y); + Stream.Write((byte)m.Direction); + Stream.Write((sbyte)m.Z); + } + } + + public sealed class MovementAck : Packet + { + private static readonly MovementAck[] m_Cache = new MovementAck[8 * 256]; + + private MovementAck(int seq, int noto) : base(0x22, 3) + { + Stream.Write((byte)seq); + Stream.Write((byte)noto); + } + + public static MovementAck Instantiate(int seq, Mobile m) + { + var noto = Notoriety.Compute(m, m); + + var p = m_Cache[noto * seq]; + + if (p == null) + { + m_Cache[noto * seq] = p = new MovementAck(seq, noto); + p.SetStatic(); + } + + return p; + } + } + + public sealed class NullFastwalkStack : Packet + { + public NullFastwalkStack() : base(0xBF) + { + EnsureCapacity(256); + Stream.Write((short)0x1); + Stream.Write(0x0); + Stream.Write(0x0); + Stream.Write(0x0); + Stream.Write(0x0); + Stream.Write(0x0); + Stream.Write(0x0); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/ObjectHelpResponsePackets.cs b/Projects/Server/Network/Packets/Old Packets/ObjectHelpResponsePackets.cs index d19b284ec..91d437f18 100644 --- a/Projects/Server/Network/Packets/Old Packets/ObjectHelpResponsePackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/ObjectHelpResponsePackets.cs @@ -1,34 +1,34 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ObjectHelpResponse.cs * - * Created: 2020/05/03 - Updated: 2020/05/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Network -{ - public sealed class ObjectHelpResponse : Packet - { - public ObjectHelpResponse(Serial e, string text) : base(0xB7) - { - EnsureCapacity(9 + text.Length * 2); - - Stream.Write(e); - Stream.WriteBigUniNull(text); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ObjectHelpResponse.cs * + * Created: 2020/05/03 - Updated: 2020/05/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public sealed class ObjectHelpResponse : Packet + { + public ObjectHelpResponse(Serial e, string text) : base(0xB7) + { + EnsureCapacity(9 + text.Length * 2); + + Stream.Write(e); + Stream.WriteBigUniNull(text); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/PlayerPackets.cs b/Projects/Server/Network/Packets/Old Packets/PlayerPackets.cs index c6fd1a837..cde587bdd 100644 --- a/Projects/Server/Network/Packets/Old Packets/PlayerPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/PlayerPackets.cs @@ -1,429 +1,430 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: PlayerPackets.cs - Created: 2020/05/07 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server.Network -{ - public enum LRReason : byte - { - CannotLift = 0, - OutOfRange = 1, - OutOfSight = 2, - TryToSteal = 3, - AreHolding = 4, - Inspecific = 5 - } - - public sealed class StatLockInfo : Packet - { - public StatLockInfo(Mobile m) : base(0xBF) - { - EnsureCapacity(12); - - Stream.Write((short)0x19); - Stream.Write((byte)2); - Stream.Write(m.Serial); - Stream.Write((byte)0); - - var lockBits = ((int)m.StrLock << 4) | ((int)m.DexLock << 2) | (int)m.IntLock; - - Stream.Write((byte)lockBits); - } - } - - public sealed class ChangeUpdateRange : Packet - { - private static readonly ChangeUpdateRange[] m_Cache = new ChangeUpdateRange[0x100]; - - public ChangeUpdateRange(int range) : base(0xC8, 2) - { - Stream.Write((byte)range); - } - - public static ChangeUpdateRange Instantiate(int range) - { - var idx = (byte)range; - var p = m_Cache[idx]; - - if (p == null) - { - m_Cache[idx] = p = new ChangeUpdateRange(range); - p.SetStatic(); - } - - return p; - } - } - - public sealed class DeathStatus : Packet - { - public static readonly Packet Dead = SetStatic(new DeathStatus(true)); - public static readonly Packet Alive = SetStatic(new DeathStatus(false)); - - public DeathStatus(bool dead) : base(0x2C, 2) - { - Stream.Write((byte)(dead ? 0 : 2)); - } - - public static Packet Instantiate(bool dead) => dead ? Dead : Alive; - } - - public sealed class ToggleSpecialAbility : Packet - { - public ToggleSpecialAbility(int abilityID, bool active) : base(0xBF) - { - EnsureCapacity(7); - - Stream.Write((short)0x25); - - Stream.Write((short)abilityID); - Stream.Write(active); - } - } - - public sealed class DisplayProfile : Packet - { - public DisplayProfile(Serial m, string header, string body, string footer) : base(0xB8) - { - header ??= ""; - body ??= ""; - footer ??= ""; - - EnsureCapacity(12 + header.Length + footer.Length * 2 + body.Length * 2); - - Stream.Write(m); - Stream.WriteAsciiNull(header); - Stream.WriteBigUniNull(footer); - Stream.WriteBigUniNull(body); - } - } - - public sealed class LiftRej : Packet - { - public LiftRej(LRReason reason) : base(0x27, 2) - { - Stream.Write((byte)reason); - } - } - - public sealed class LogoutAck : Packet - { - public LogoutAck() : base(0xD1, 2) - { - Stream.Write((byte)0x01); - } - } - - public sealed class Weather : Packet - { - public Weather(int type, int density, int temp) : base(0x65, 4) - { - Stream.Write((byte)type); - Stream.Write((byte)density); - Stream.Write((byte)temp); - } - } - - public sealed class RemoveEntity : Packet - { - public RemoveEntity(Serial entity) : base(0x1D, 5) - { - Stream.Write(entity); - } - } - - public sealed class ServerChange : Packet - { - public ServerChange(Point3D p, Map map) : base(0x76, 16) - { - Stream.Write((short)p.X); - Stream.Write((short)p.Y); - Stream.Write((short)p.Z); - Stream.Write((byte)0); - Stream.Write((short)0); - Stream.Write((short)0); - Stream.Write((short)map.Width); - Stream.Write((short)map.Height); - } - } - - public sealed class SkillUpdate : Packet - { - public SkillUpdate(Skills skills) : base(0x3A) - { - EnsureCapacity(6 + skills.Length * 9); - - Stream.Write((byte)0x02); // type: absolute, capped - - for (var i = 0; i < skills.Length; ++i) - { - var s = skills[i]; - - var v = s.NonRacialValue; - var uv = Math.Clamp((int)(v * 10), 0, 0xFFFF); - - Stream.Write((ushort)(s.Info.SkillID + 1)); - Stream.Write((ushort)uv); - Stream.Write((ushort)s.BaseFixedPoint); - Stream.Write((byte)s.Lock); - Stream.Write((ushort)s.CapFixedPoint); - } - - Stream.Write((short)0); // terminate - } - } - - public sealed class Sequence : Packet - { - public Sequence(int num) : base(0x7B, 2) - { - Stream.Write((byte)num); - } - } - - public sealed class SkillChange : Packet - { - public SkillChange(Skill skill) : base(0x3A) - { - EnsureCapacity(13); - - var v = skill.NonRacialValue; - var uv = Math.Clamp((int)(v * 10), 0, 0xFFFF); - - Stream.Write((byte)0xDF); // type: delta, capped - Stream.Write((ushort)skill.Info.SkillID); - Stream.Write((ushort)uv); - Stream.Write((ushort)skill.BaseFixedPoint); - Stream.Write((byte)skill.Lock); - Stream.Write((ushort)skill.CapFixedPoint); - } - } - - public sealed class LaunchBrowser : Packet - { - public LaunchBrowser(string url) : base(0xA5) - { - url ??= ""; - - EnsureCapacity(4 + url.Length); - - Stream.WriteAsciiNull(url); - } - } - - public sealed class DragEffect : Packet - { - public DragEffect(IEntity src, IEntity trg, int itemID, int hue, int amount) : base(0x23, 26) - { - Stream.Write((short)itemID); - Stream.Write((byte)0); - Stream.Write((short)hue); - Stream.Write((short)amount); - Stream.Write(src.Serial); - Stream.Write((short)src.X); - Stream.Write((short)src.Y); - Stream.Write((sbyte)src.Z); - Stream.Write(trg.Serial); - Stream.Write((short)trg.X); - Stream.Write((short)trg.Y); - Stream.Write((sbyte)trg.Z); - } - } - - public sealed class SeasonChange : Packet - { - private static readonly SeasonChange[][] m_Cache = { - new SeasonChange[2], - new SeasonChange[2], - new SeasonChange[2], - new SeasonChange[2], - new SeasonChange[2] - }; - - public SeasonChange(int season, bool playSound = true) : base(0xBC, 3) - { - Stream.Write((byte)season); - Stream.Write(playSound); - } - - public static SeasonChange Instantiate(int season) => Instantiate(season, true); - - public static SeasonChange Instantiate(int season, bool playSound) - { - if (season >= 0 && season < m_Cache.Length) - { - var idx = playSound ? 1 : 0; - - var p = m_Cache[season][idx]; - - if (p == null) - { - m_Cache[season][idx] = p = new SeasonChange(season, playSound); - p.SetStatic(); - } - - return p; - } - - return new SeasonChange(season, playSound); - } - } - - public sealed class DisplayPaperdoll : Packet - { - public DisplayPaperdoll(Serial m, string title, bool warmode, bool canLift) : base(0x88, 66) - { - byte flags = 0x00; - - if (warmode) - flags |= 0x01; - - if (canLift) - flags |= 0x02; - - Stream.Write(m); - Stream.WriteAsciiFixed(title, 60); - Stream.Write(flags); - } - } - - public sealed class PlaySound : Packet - { - public PlaySound(int soundID, IPoint3D target) : base(0x54, 12) - { - Stream.Write((byte)1); // flags - Stream.Write((short)soundID); - Stream.Write((short)0); // volume - Stream.Write((short)target.X); - Stream.Write((short)target.Y); - Stream.Write((short)target.Z); - } - } - - public sealed class PlayMusic : Packet - { - public static readonly Packet InvalidInstance = SetStatic(new PlayMusic(MusicName.Invalid)); - - private static readonly Packet[] m_Instances = new Packet[60]; - - public PlayMusic(MusicName name) : base(0x6D, 3) - { - Stream.Write((short)name); - } - - public static Packet GetInstance(MusicName name) - { - if (name == MusicName.Invalid) - return InvalidInstance; - - var v = (int)name; - Packet p; - - if (v >= 0 && v < m_Instances.Length) - { - p = m_Instances[v]; - - if (p == null) - m_Instances[v] = p = SetStatic(new PlayMusic(name)); - } - else - { - p = new PlayMusic(name); - } - - return p; - } - } - - public sealed class ScrollMessage : Packet - { - public ScrollMessage(int type, int tip, string text) : base(0xA6) - { - text ??= ""; - - EnsureCapacity(10 + text.Length); - - Stream.Write((byte)type); - Stream.Write(tip); - Stream.Write((ushort)text.Length); - Stream.WriteAsciiFixed(text, text.Length); - } - } - - public sealed class CurrentTime : Packet - { - public CurrentTime() : this(DateTime.Now) - { - } - - public CurrentTime(DateTime date) : base(0x5B, 4) - { - Stream.Write((byte)date.Hour); - Stream.Write((byte)date.Minute); - Stream.Write((byte)date.Second); - } - } - - public sealed class PathfindMessage : Packet - { - public PathfindMessage(Point3D p) : base(0x38, 7) - { - Stream.Write((short)p.X); - Stream.Write((short)p.Y); - Stream.Write((short)p.Z); - } - } - - public sealed class PingAck : Packet - { - private static readonly PingAck[] m_Cache = new PingAck[0x100]; - - public PingAck(byte ping) : base(0x73, 2) - { - Stream.Write(ping); - } - - public static PingAck Instantiate(byte ping) - { - var p = m_Cache[ping]; - - if (p == null) - { - m_Cache[ping] = p = new PingAck(ping); - p.SetStatic(); - } - - return p; - } - } - - public sealed class ClearWeaponAbility : Packet - { - public static readonly Packet Instance = SetStatic(new ClearWeaponAbility()); - - public ClearWeaponAbility() : base(0xBF) - { - EnsureCapacity(5); - - Stream.Write((short)0x21); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: PlayerPackets.cs - Created: 2020/05/07 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server.Network +{ + public enum LRReason : byte + { + CannotLift = 0, + OutOfRange = 1, + OutOfSight = 2, + TryToSteal = 3, + AreHolding = 4, + Inspecific = 5 + } + + public sealed class StatLockInfo : Packet + { + public StatLockInfo(Mobile m) : base(0xBF) + { + EnsureCapacity(12); + + Stream.Write((short)0x19); + Stream.Write((byte)2); + Stream.Write(m.Serial); + Stream.Write((byte)0); + + var lockBits = ((int)m.StrLock << 4) | ((int)m.DexLock << 2) | (int)m.IntLock; + + Stream.Write((byte)lockBits); + } + } + + public sealed class ChangeUpdateRange : Packet + { + private static readonly ChangeUpdateRange[] m_Cache = new ChangeUpdateRange[0x100]; + + public ChangeUpdateRange(int range) : base(0xC8, 2) + { + Stream.Write((byte)range); + } + + public static ChangeUpdateRange Instantiate(int range) + { + var idx = (byte)range; + var p = m_Cache[idx]; + + if (p == null) + { + m_Cache[idx] = p = new ChangeUpdateRange(range); + p.SetStatic(); + } + + return p; + } + } + + public sealed class DeathStatus : Packet + { + public static readonly Packet Dead = SetStatic(new DeathStatus(true)); + public static readonly Packet Alive = SetStatic(new DeathStatus(false)); + + public DeathStatus(bool dead) : base(0x2C, 2) + { + Stream.Write((byte)(dead ? 0 : 2)); + } + + public static Packet Instantiate(bool dead) => dead ? Dead : Alive; + } + + public sealed class ToggleSpecialAbility : Packet + { + public ToggleSpecialAbility(int abilityID, bool active) : base(0xBF) + { + EnsureCapacity(7); + + Stream.Write((short)0x25); + + Stream.Write((short)abilityID); + Stream.Write(active); + } + } + + public sealed class DisplayProfile : Packet + { + public DisplayProfile(Serial m, string header, string body, string footer) : base(0xB8) + { + header ??= ""; + body ??= ""; + footer ??= ""; + + EnsureCapacity(12 + header.Length + footer.Length * 2 + body.Length * 2); + + Stream.Write(m); + Stream.WriteAsciiNull(header); + Stream.WriteBigUniNull(footer); + Stream.WriteBigUniNull(body); + } + } + + public sealed class LiftRej : Packet + { + public LiftRej(LRReason reason) : base(0x27, 2) + { + Stream.Write((byte)reason); + } + } + + public sealed class LogoutAck : Packet + { + public LogoutAck() : base(0xD1, 2) + { + Stream.Write((byte)0x01); + } + } + + public sealed class Weather : Packet + { + public Weather(int type, int density, int temp) : base(0x65, 4) + { + Stream.Write((byte)type); + Stream.Write((byte)density); + Stream.Write((byte)temp); + } + } + + public sealed class RemoveEntity : Packet + { + public RemoveEntity(Serial entity) : base(0x1D, 5) + { + Stream.Write(entity); + } + } + + public sealed class ServerChange : Packet + { + public ServerChange(Point3D p, Map map) : base(0x76, 16) + { + Stream.Write((short)p.X); + Stream.Write((short)p.Y); + Stream.Write((short)p.Z); + Stream.Write((byte)0); + Stream.Write((short)0); + Stream.Write((short)0); + Stream.Write((short)map.Width); + Stream.Write((short)map.Height); + } + } + + public sealed class SkillUpdate : Packet + { + public SkillUpdate(Skills skills) : base(0x3A) + { + EnsureCapacity(6 + skills.Length * 9); + + Stream.Write((byte)0x02); // type: absolute, capped + + for (var i = 0; i < skills.Length; ++i) + { + var s = skills[i]; + + var v = s.NonRacialValue; + var uv = Math.Clamp((int)(v * 10), 0, 0xFFFF); + + Stream.Write((ushort)(s.Info.SkillID + 1)); + Stream.Write((ushort)uv); + Stream.Write((ushort)s.BaseFixedPoint); + Stream.Write((byte)s.Lock); + Stream.Write((ushort)s.CapFixedPoint); + } + + Stream.Write((short)0); // terminate + } + } + + public sealed class Sequence : Packet + { + public Sequence(int num) : base(0x7B, 2) + { + Stream.Write((byte)num); + } + } + + public sealed class SkillChange : Packet + { + public SkillChange(Skill skill) : base(0x3A) + { + EnsureCapacity(13); + + var v = skill.NonRacialValue; + var uv = Math.Clamp((int)(v * 10), 0, 0xFFFF); + + Stream.Write((byte)0xDF); // type: delta, capped + Stream.Write((ushort)skill.Info.SkillID); + Stream.Write((ushort)uv); + Stream.Write((ushort)skill.BaseFixedPoint); + Stream.Write((byte)skill.Lock); + Stream.Write((ushort)skill.CapFixedPoint); + } + } + + public sealed class LaunchBrowser : Packet + { + public LaunchBrowser(string url) : base(0xA5) + { + url ??= ""; + + EnsureCapacity(4 + url.Length); + + Stream.WriteAsciiNull(url); + } + } + + public sealed class DragEffect : Packet + { + public DragEffect(IEntity src, IEntity trg, int itemID, int hue, int amount) : base(0x23, 26) + { + Stream.Write((short)itemID); + Stream.Write((byte)0); + Stream.Write((short)hue); + Stream.Write((short)amount); + Stream.Write(src.Serial); + Stream.Write((short)src.X); + Stream.Write((short)src.Y); + Stream.Write((sbyte)src.Z); + Stream.Write(trg.Serial); + Stream.Write((short)trg.X); + Stream.Write((short)trg.Y); + Stream.Write((sbyte)trg.Z); + } + } + + public sealed class SeasonChange : Packet + { + private static readonly SeasonChange[][] m_Cache = + { + new SeasonChange[2], + new SeasonChange[2], + new SeasonChange[2], + new SeasonChange[2], + new SeasonChange[2] + }; + + public SeasonChange(int season, bool playSound = true) : base(0xBC, 3) + { + Stream.Write((byte)season); + Stream.Write(playSound); + } + + public static SeasonChange Instantiate(int season) => Instantiate(season, true); + + public static SeasonChange Instantiate(int season, bool playSound) + { + if (season >= 0 && season < m_Cache.Length) + { + var idx = playSound ? 1 : 0; + + var p = m_Cache[season][idx]; + + if (p == null) + { + m_Cache[season][idx] = p = new SeasonChange(season, playSound); + p.SetStatic(); + } + + return p; + } + + return new SeasonChange(season, playSound); + } + } + + public sealed class DisplayPaperdoll : Packet + { + public DisplayPaperdoll(Serial m, string title, bool warmode, bool canLift) : base(0x88, 66) + { + byte flags = 0x00; + + if (warmode) + flags |= 0x01; + + if (canLift) + flags |= 0x02; + + Stream.Write(m); + Stream.WriteAsciiFixed(title, 60); + Stream.Write(flags); + } + } + + public sealed class PlaySound : Packet + { + public PlaySound(int soundID, IPoint3D target) : base(0x54, 12) + { + Stream.Write((byte)1); // flags + Stream.Write((short)soundID); + Stream.Write((short)0); // volume + Stream.Write((short)target.X); + Stream.Write((short)target.Y); + Stream.Write((short)target.Z); + } + } + + public sealed class PlayMusic : Packet + { + public static readonly Packet InvalidInstance = SetStatic(new PlayMusic(MusicName.Invalid)); + + private static readonly Packet[] m_Instances = new Packet[60]; + + public PlayMusic(MusicName name) : base(0x6D, 3) + { + Stream.Write((short)name); + } + + public static Packet GetInstance(MusicName name) + { + if (name == MusicName.Invalid) + return InvalidInstance; + + var v = (int)name; + Packet p; + + if (v >= 0 && v < m_Instances.Length) + { + p = m_Instances[v]; + + if (p == null) + m_Instances[v] = p = SetStatic(new PlayMusic(name)); + } + else + { + p = new PlayMusic(name); + } + + return p; + } + } + + public sealed class ScrollMessage : Packet + { + public ScrollMessage(int type, int tip, string text) : base(0xA6) + { + text ??= ""; + + EnsureCapacity(10 + text.Length); + + Stream.Write((byte)type); + Stream.Write(tip); + Stream.Write((ushort)text.Length); + Stream.WriteAsciiFixed(text, text.Length); + } + } + + public sealed class CurrentTime : Packet + { + public CurrentTime() : this(DateTime.Now) + { + } + + public CurrentTime(DateTime date) : base(0x5B, 4) + { + Stream.Write((byte)date.Hour); + Stream.Write((byte)date.Minute); + Stream.Write((byte)date.Second); + } + } + + public sealed class PathfindMessage : Packet + { + public PathfindMessage(Point3D p) : base(0x38, 7) + { + Stream.Write((short)p.X); + Stream.Write((short)p.Y); + Stream.Write((short)p.Z); + } + } + + public sealed class PingAck : Packet + { + private static readonly PingAck[] m_Cache = new PingAck[0x100]; + + public PingAck(byte ping) : base(0x73, 2) + { + Stream.Write(ping); + } + + public static PingAck Instantiate(byte ping) + { + var p = m_Cache[ping]; + + if (p == null) + { + m_Cache[ping] = p = new PingAck(ping); + p.SetStatic(); + } + + return p; + } + } + + public sealed class ClearWeaponAbility : Packet + { + public static readonly Packet Instance = SetStatic(new ClearWeaponAbility()); + + public ClearWeaponAbility() : base(0xBF) + { + EnsureCapacity(5); + + Stream.Write((short)0x21); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/SecureTradePackets.cs b/Projects/Server/Network/Packets/Old Packets/SecureTradePackets.cs index 65675d463..7dc3012a3 100644 --- a/Projects/Server/Network/Packets/Old Packets/SecureTradePackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/SecureTradePackets.cs @@ -1,115 +1,115 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: SecureTradePackets.cs * - * Created: 2020/05/03 - Updated: 2020/05/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using Server.Items; - -namespace Server.Network -{ - public sealed class DisplaySecureTrade : Packet - { - public DisplaySecureTrade(Mobile them, Container first, Container second, string name) - : base(0x6F) - { - name ??= ""; - - EnsureCapacity(18 + name.Length); - - Stream.Write((byte)0); // Display - Stream.Write(them.Serial); - Stream.Write(first.Serial); - Stream.Write(second.Serial); - Stream.Write(true); - - Stream.WriteAsciiFixed(name, 30); - } - } - - public sealed class CloseSecureTrade : Packet - { - public CloseSecureTrade(Container cont) - : base(0x6F) - { - EnsureCapacity(8); - - Stream.Write((byte)1); // Close - Stream.Write(cont.Serial); - } - } - - public enum TradeFlag : byte - { - Display = 0x0, - Close = 0x1, - Update = 0x2, - UpdateGold = 0x3, - UpdateLedger = 0x4 - } - - public sealed class UpdateSecureTrade : Packet - { - public UpdateSecureTrade(Container cont, bool first, bool second) - : this(cont, TradeFlag.Update, first ? 1 : 0, second ? 1 : 0) - { - } - - public UpdateSecureTrade(Container cont, TradeFlag flag, int first, int second) - : base(0x6F) - { - EnsureCapacity(17); - - Stream.Write((byte)flag); - Stream.Write(cont.Serial); - Stream.Write(first); - Stream.Write(second); - } - } - - public sealed class SecureTradeEquip : Packet - { - public SecureTradeEquip(Item item, Mobile m) : base(0x25, 20) - { - Stream.Write(item.Serial); - Stream.Write((short)item.ItemID); - Stream.Write((byte)0); - Stream.Write((short)item.Amount); - Stream.Write((short)item.X); - Stream.Write((short)item.Y); - Stream.Write(m.Serial); - Stream.Write((short)item.Hue); - } - } - - public sealed class SecureTradeEquip6017 : Packet - { - public SecureTradeEquip6017(Item item, Mobile m) : base(0x25, 21) - { - Stream.Write(item.Serial); - Stream.Write((short)item.ItemID); - Stream.Write((byte)0); - Stream.Write((short)item.Amount); - Stream.Write((short)item.X); - Stream.Write((short)item.Y); - Stream.Write((byte)0); // Grid Location? - Stream.Write(m.Serial); - Stream.Write((short)item.Hue); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SecureTradePackets.cs * + * Created: 2020/05/03 - Updated: 2020/05/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Server.Items; + +namespace Server.Network +{ + public sealed class DisplaySecureTrade : Packet + { + public DisplaySecureTrade(Mobile them, Container first, Container second, string name) + : base(0x6F) + { + name ??= ""; + + EnsureCapacity(18 + name.Length); + + Stream.Write((byte)0); // Display + Stream.Write(them.Serial); + Stream.Write(first.Serial); + Stream.Write(second.Serial); + Stream.Write(true); + + Stream.WriteAsciiFixed(name, 30); + } + } + + public sealed class CloseSecureTrade : Packet + { + public CloseSecureTrade(Container cont) + : base(0x6F) + { + EnsureCapacity(8); + + Stream.Write((byte)1); // Close + Stream.Write(cont.Serial); + } + } + + public enum TradeFlag : byte + { + Display = 0x0, + Close = 0x1, + Update = 0x2, + UpdateGold = 0x3, + UpdateLedger = 0x4 + } + + public sealed class UpdateSecureTrade : Packet + { + public UpdateSecureTrade(Container cont, bool first, bool second) + : this(cont, TradeFlag.Update, first ? 1 : 0, second ? 1 : 0) + { + } + + public UpdateSecureTrade(Container cont, TradeFlag flag, int first, int second) + : base(0x6F) + { + EnsureCapacity(17); + + Stream.Write((byte)flag); + Stream.Write(cont.Serial); + Stream.Write(first); + Stream.Write(second); + } + } + + public sealed class SecureTradeEquip : Packet + { + public SecureTradeEquip(Item item, Mobile m) : base(0x25, 20) + { + Stream.Write(item.Serial); + Stream.Write((short)item.ItemID); + Stream.Write((byte)0); + Stream.Write((short)item.Amount); + Stream.Write((short)item.X); + Stream.Write((short)item.Y); + Stream.Write(m.Serial); + Stream.Write((short)item.Hue); + } + } + + public sealed class SecureTradeEquip6017 : Packet + { + public SecureTradeEquip6017(Item item, Mobile m) : base(0x25, 21) + { + Stream.Write(item.Serial); + Stream.Write((short)item.ItemID); + Stream.Write((byte)0); + Stream.Write((short)item.Amount); + Stream.Write((short)item.X); + Stream.Write((short)item.Y); + Stream.Write((byte)0); // Grid Location? + Stream.Write(m.Serial); + Stream.Write((short)item.Hue); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/TargetPackets.cs b/Projects/Server/Network/Packets/Old Packets/TargetPackets.cs index e649c977f..5e5abbac9 100644 --- a/Projects/Server/Network/Packets/Old Packets/TargetPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/TargetPackets.cs @@ -1,87 +1,87 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: TargetPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.IO; -using Server.Targeting; - -namespace Server.Network -{ - public sealed class MultiTargetReqHS : Packet - { - public MultiTargetReqHS(MultiTarget t) : base(0x99, 30) - { - Stream.Write(t.AllowGround); - Stream.Write(t.TargetID); - Stream.Write((byte)t.Flags); - - Stream.Fill(); - - Stream.Seek(18, SeekOrigin.Begin); - Stream.Write((short)t.MultiID); - Stream.Write((short)t.Offset.X); - Stream.Write((short)t.Offset.Y); - Stream.Write((short)t.Offset.Z); - - // DWORD Hue - } - } - - public sealed class MultiTargetReq : Packet - { - public MultiTargetReq(MultiTarget t) : base(0x99, 26) - { - Stream.Write(t.AllowGround); - Stream.Write(t.TargetID); - Stream.Write((byte)t.Flags); - - Stream.Fill(); - - Stream.Seek(18, SeekOrigin.Begin); - Stream.Write((short)t.MultiID); - Stream.Write((short)t.Offset.X); - Stream.Write((short)t.Offset.Y); - Stream.Write((short)t.Offset.Z); - } - } - - public sealed class CancelTarget : Packet - { - public static readonly Packet Instance = SetStatic(new CancelTarget()); - - public CancelTarget() : base(0x6C, 19) - { - Stream.Write((byte)0); - Stream.Write(0); - Stream.Write((byte)3); - Stream.Fill(); - } - } - - public sealed class TargetReq : Packet - { - public TargetReq(Target t) : base(0x6C, 19) - { - Stream.Write(t.AllowGround); - Stream.Write(t.TargetID); - Stream.Write((byte)t.Flags); - Stream.Fill(); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: TargetPackets.cs - Created: 2020/05/26 - Updated: 2020/05/26 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.IO; +using Server.Targeting; + +namespace Server.Network +{ + public sealed class MultiTargetReqHS : Packet + { + public MultiTargetReqHS(MultiTarget t) : base(0x99, 30) + { + Stream.Write(t.AllowGround); + Stream.Write(t.TargetID); + Stream.Write((byte)t.Flags); + + Stream.Fill(); + + Stream.Seek(18, SeekOrigin.Begin); + Stream.Write((short)t.MultiID); + Stream.Write((short)t.Offset.X); + Stream.Write((short)t.Offset.Y); + Stream.Write((short)t.Offset.Z); + + // DWORD Hue + } + } + + public sealed class MultiTargetReq : Packet + { + public MultiTargetReq(MultiTarget t) : base(0x99, 26) + { + Stream.Write(t.AllowGround); + Stream.Write(t.TargetID); + Stream.Write((byte)t.Flags); + + Stream.Fill(); + + Stream.Seek(18, SeekOrigin.Begin); + Stream.Write((short)t.MultiID); + Stream.Write((short)t.Offset.X); + Stream.Write((short)t.Offset.Y); + Stream.Write((short)t.Offset.Z); + } + } + + public sealed class CancelTarget : Packet + { + public static readonly Packet Instance = SetStatic(new CancelTarget()); + + public CancelTarget() : base(0x6C, 19) + { + Stream.Write((byte)0); + Stream.Write(0); + Stream.Write((byte)3); + Stream.Fill(); + } + } + + public sealed class TargetReq : Packet + { + public TargetReq(Target t) : base(0x6C, 19) + { + Stream.Write(t.AllowGround); + Stream.Write(t.TargetID); + Stream.Write((byte)t.Flags); + Stream.Fill(); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/UnicodePromptPackets.cs b/Projects/Server/Network/Packets/Old Packets/UnicodePromptPackets.cs index 54472fc9c..2487001a9 100644 --- a/Projects/Server/Network/Packets/Old Packets/UnicodePromptPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/UnicodePromptPackets.cs @@ -1,38 +1,38 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: UnicodePrompt.cs - Created: 2020/05/08 - Updated: 2020/05/08 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using Server.Prompts; - -namespace Server.Network -{ - public sealed class UnicodePrompt : Packet - { - public UnicodePrompt(Prompt prompt) : base(0xC2) - { - EnsureCapacity(21); - - Stream.Write(prompt.Serial); // TODO: Does this value even matter? - Stream.Write(prompt.Serial); - Stream.Write(0); - Stream.Write(0); - Stream.Write((short)0); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: UnicodePrompt.cs - Created: 2020/05/08 - Updated: 2020/05/08 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using Server.Prompts; + +namespace Server.Network +{ + public sealed class UnicodePrompt : Packet + { + public UnicodePrompt(Prompt prompt) : base(0xC2) + { + EnsureCapacity(21); + + Stream.Write(prompt.Serial); // TODO: Does this value even matter? + Stream.Write(prompt.Serial); + Stream.Write(0); + Stream.Write(0); + Stream.Write((short)0); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/VendorBuyPackets.cs b/Projects/Server/Network/Packets/Old Packets/VendorBuyPackets.cs index 7d1c24a7a..7ae17a86c 100644 --- a/Projects/Server/Network/Packets/Old Packets/VendorBuyPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/VendorBuyPackets.cs @@ -1,128 +1,128 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: VendorBuyPackets.cs - Created: 2020/05/03 - Updated: 2020/05/03 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.Collections.Generic; -using Server.Items; - -namespace Server.Network -{ - public sealed class VendorBuyContent : Packet - { - public VendorBuyContent(List list) : base(0x3C) - { - EnsureCapacity(list.Count * 19 + 5); - - Stream.Write((short)list.Count); - - for (var i = list.Count - 1; i >= 0; --i) - { - var bis = list[i]; - - Stream.Write(bis.MySerial); - Stream.Write((ushort)bis.ItemID); - Stream.Write((byte)0); // itemID offset - Stream.Write((ushort)bis.Amount); - Stream.Write((short)(i + 1)); // x - Stream.Write((short)1); // y - Stream.Write(bis.ContainerSerial); - Stream.Write((ushort)bis.Hue); - } - } - } - - public sealed class VendorBuyContent6017 : Packet - { - public VendorBuyContent6017(List list) : base(0x3C) - { - EnsureCapacity(list.Count * 20 + 5); - - Stream.Write((short)list.Count); - - for (var i = list.Count - 1; i >= 0; --i) - { - var bis = list[i]; - - Stream.Write(bis.MySerial); - Stream.Write((ushort)bis.ItemID); - Stream.Write((byte)0); // itemID offset - Stream.Write((ushort)bis.Amount); - Stream.Write((short)(i + 1)); // x - Stream.Write((short)1); // y - Stream.Write((byte)0); // Grid Location? - Stream.Write(bis.ContainerSerial); - Stream.Write((ushort)bis.Hue); - } - } - } - - public sealed class DisplayBuyList : Packet - { - public DisplayBuyList(Mobile vendor) : base(0x24, 7) - { - Stream.Write(vendor.Serial); - Stream.Write((short)0x30); // buy window id? - } - } - - public sealed class DisplayBuyListHS : Packet - { - public DisplayBuyListHS(Mobile vendor) : base(0x24, 9) - { - Stream.Write(vendor.Serial); - Stream.Write((short)0x30); // buy window id? - Stream.Write((short)0x00); - } - } - - public sealed class VendorBuyList : Packet - { - public VendorBuyList(Mobile vendor, List list) : base(0x74) - { - EnsureCapacity(256); - - Stream.Write(!(vendor.FindItemOnLayer(Layer.ShopBuy) is Container buyPack) ? Serial.MinusOne : buyPack.Serial); - - Stream.Write((byte)list.Count); - - for (var i = 0; i < list.Count; ++i) - { - var bis = list[i]; - - Stream.Write(bis.Price); - - var desc = bis.Description ?? ""; - - // TODO: Test if this is actually WriteAsciiFixed and the extra null doesn't matter. - Stream.Write((byte)(desc.Length + 1)); - Stream.WriteAsciiNull(desc); - } - } - } - - public sealed class EndVendorBuy : Packet - { - public EndVendorBuy(Mobile vendor) : base(0x3B, 8) - { - Stream.Write((ushort)8); // length - Stream.Write(vendor.Serial); - Stream.Write((byte)0); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: VendorBuyPackets.cs - Created: 2020/05/03 - Updated: 2020/05/03 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Collections.Generic; +using Server.Items; + +namespace Server.Network +{ + public sealed class VendorBuyContent : Packet + { + public VendorBuyContent(List list) : base(0x3C) + { + EnsureCapacity(list.Count * 19 + 5); + + Stream.Write((short)list.Count); + + for (var i = list.Count - 1; i >= 0; --i) + { + var bis = list[i]; + + Stream.Write(bis.MySerial); + Stream.Write((ushort)bis.ItemID); + Stream.Write((byte)0); // itemID offset + Stream.Write((ushort)bis.Amount); + Stream.Write((short)(i + 1)); // x + Stream.Write((short)1); // y + Stream.Write(bis.ContainerSerial); + Stream.Write((ushort)bis.Hue); + } + } + } + + public sealed class VendorBuyContent6017 : Packet + { + public VendorBuyContent6017(List list) : base(0x3C) + { + EnsureCapacity(list.Count * 20 + 5); + + Stream.Write((short)list.Count); + + for (var i = list.Count - 1; i >= 0; --i) + { + var bis = list[i]; + + Stream.Write(bis.MySerial); + Stream.Write((ushort)bis.ItemID); + Stream.Write((byte)0); // itemID offset + Stream.Write((ushort)bis.Amount); + Stream.Write((short)(i + 1)); // x + Stream.Write((short)1); // y + Stream.Write((byte)0); // Grid Location? + Stream.Write(bis.ContainerSerial); + Stream.Write((ushort)bis.Hue); + } + } + } + + public sealed class DisplayBuyList : Packet + { + public DisplayBuyList(Mobile vendor) : base(0x24, 7) + { + Stream.Write(vendor.Serial); + Stream.Write((short)0x30); // buy window id? + } + } + + public sealed class DisplayBuyListHS : Packet + { + public DisplayBuyListHS(Mobile vendor) : base(0x24, 9) + { + Stream.Write(vendor.Serial); + Stream.Write((short)0x30); // buy window id? + Stream.Write((short)0x00); + } + } + + public sealed class VendorBuyList : Packet + { + public VendorBuyList(Mobile vendor, List list) : base(0x74) + { + EnsureCapacity(256); + + Stream.Write(!(vendor.FindItemOnLayer(Layer.ShopBuy) is Container buyPack) ? Serial.MinusOne : buyPack.Serial); + + Stream.Write((byte)list.Count); + + for (var i = 0; i < list.Count; ++i) + { + var bis = list[i]; + + Stream.Write(bis.Price); + + var desc = bis.Description ?? ""; + + // TODO: Test if this is actually WriteAsciiFixed and the extra null doesn't matter. + Stream.Write((byte)(desc.Length + 1)); + Stream.WriteAsciiNull(desc); + } + } + } + + public sealed class EndVendorBuy : Packet + { + public EndVendorBuy(Mobile vendor) : base(0x3B, 8) + { + Stream.Write((ushort)8); // length + Stream.Write(vendor.Serial); + Stream.Write((byte)0); + } + } +} diff --git a/Projects/Server/Network/Packets/Old Packets/VendorSellPackets.cs b/Projects/Server/Network/Packets/Old Packets/VendorSellPackets.cs index e0ac8ab8c..f9f5f8e00 100644 --- a/Projects/Server/Network/Packets/Old Packets/VendorSellPackets.cs +++ b/Projects/Server/Network/Packets/Old Packets/VendorSellPackets.cs @@ -1,61 +1,61 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: VendorSellPackets.cs * - * Created: 2020/05/07 - Updated: 2020/05/07 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.Collections.Generic; - -namespace Server.Network -{ - public sealed class VendorSellList : Packet - { - public VendorSellList(Mobile shopkeeper, List sis) : base(0x9E) - { - EnsureCapacity(256); - - Stream.Write(shopkeeper.Serial); - - Stream.Write((ushort)sis.Count); - - foreach (var state in sis) - { - Stream.Write(state.Item.Serial); - Stream.Write((ushort)state.Item.ItemID); - Stream.Write((ushort)state.Item.Hue); - Stream.Write((ushort)state.Item.Amount); - Stream.Write((ushort)state.Price); - - var name = string.IsNullOrWhiteSpace(state.Item.Name) ? state.Name ?? "" : state.Item.Name.Trim(); - - Stream.Write((ushort)name.Length); - Stream.WriteAsciiFixed(name, (ushort)name.Length); - } - } - } - - public sealed class EndVendorSell : Packet - { - public EndVendorSell(Mobile vendor) : base(0x3B, 8) - { - Stream.Write((ushort)8); // length - Stream.Write(vendor.Serial); - Stream.Write((byte)0); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: VendorSellPackets.cs * + * Created: 2020/05/07 - Updated: 2020/05/07 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Collections.Generic; + +namespace Server.Network +{ + public sealed class VendorSellList : Packet + { + public VendorSellList(Mobile shopkeeper, List sis) : base(0x9E) + { + EnsureCapacity(256); + + Stream.Write(shopkeeper.Serial); + + Stream.Write((ushort)sis.Count); + + foreach (var state in sis) + { + Stream.Write(state.Item.Serial); + Stream.Write((ushort)state.Item.ItemID); + Stream.Write((ushort)state.Item.Hue); + Stream.Write((ushort)state.Item.Amount); + Stream.Write((ushort)state.Price); + + var name = string.IsNullOrWhiteSpace(state.Item.Name) ? state.Name ?? "" : state.Item.Name.Trim(); + + Stream.Write((ushort)name.Length); + Stream.WriteAsciiFixed(name, (ushort)name.Length); + } + } + } + + public sealed class EndVendorSell : Packet + { + public EndVendorSell(Mobile vendor) : base(0x3B, 8) + { + Stream.Write((ushort)8); // length + Stream.Write(vendor.Serial); + Stream.Write((byte)0); + } + } +} diff --git a/Projects/Server/Network/ServerConnectionHandler.cs b/Projects/Server/Network/ServerConnectionHandler.cs index 436778784..062563c2d 100644 --- a/Projects/Server/Network/ServerConnectionHandler.cs +++ b/Projects/Server/Network/ServerConnectionHandler.cs @@ -1,96 +1,97 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ServerConnectionHandler.cs * - * Created: 2020/04/12 - Updated: 2020/04/12 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Connections; -using Microsoft.Extensions.Logging; - -namespace Server.Network -{ - public class ServerConnectionHandler : ConnectionHandler - { - private readonly IMessagePumpService _messagePumpService; - private readonly ILogger _logger; - - public ServerConnectionHandler( - IMessagePumpService messagePumpService, - ILogger logger) - { - _messagePumpService = messagePumpService; - _logger = logger; - } - - public override async Task OnConnectedAsync(ConnectionContext connection) - { - if (!VerifySocket(connection)) - { - Release(connection); - return; - } - - var ns = new NetState(connection); - TcpServer.Instances.Add(ns); - Console.WriteLine($"Client: {ns}: Connected. [{TcpServer.Instances.Count} Online]"); - - await ns.ProcessIncoming(_messagePumpService).ConfigureAwait(false); - } - - private static bool VerifySocket(ConnectionContext connection) - { - try - { - var args = new SocketConnectEventArgs(connection); - - EventSink.InvokeSocketConnect(args); - - return args.AllowConnection; - } - catch (Exception ex) - { - NetState.TraceException(ex); - return false; - } - } - - private static void Release(ConnectionContext connection) - { - try - { - connection.Abort(new ConnectionAbortedException("Failed socket verification.")); - } - catch (Exception ex) - { - NetState.TraceException(ex); - } - - try - { - // TODO: Is this needed? - connection.DisposeAsync(); - } - catch (Exception ex) - { - NetState.TraceException(ex); - } - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ServerConnectionHandler.cs * + * Created: 2020/04/12 - Updated: 2020/04/12 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Connections; +using Microsoft.Extensions.Logging; + +namespace Server.Network +{ + public class ServerConnectionHandler : ConnectionHandler + { + private readonly ILogger _logger; + private readonly IMessagePumpService _messagePumpService; + + public ServerConnectionHandler( + IMessagePumpService messagePumpService, + ILogger logger + ) + { + _messagePumpService = messagePumpService; + _logger = logger; + } + + public override async Task OnConnectedAsync(ConnectionContext connection) + { + if (!VerifySocket(connection)) + { + Release(connection); + return; + } + + var ns = new NetState(connection); + TcpServer.Instances.Add(ns); + Console.WriteLine($"Client: {ns}: Connected. [{TcpServer.Instances.Count} Online]"); + + await ns.ProcessIncoming(_messagePumpService).ConfigureAwait(false); + } + + private static bool VerifySocket(ConnectionContext connection) + { + try + { + var args = new SocketConnectEventArgs(connection); + + EventSink.InvokeSocketConnect(args); + + return args.AllowConnection; + } + catch (Exception ex) + { + NetState.TraceException(ex); + return false; + } + } + + private static void Release(ConnectionContext connection) + { + try + { + connection.Abort(new ConnectionAbortedException("Failed socket verification.")); + } + catch (Exception ex) + { + NetState.TraceException(ex); + } + + try + { + // TODO: Is this needed? + connection.DisposeAsync(); + } + catch (Exception ex) + { + NetState.TraceException(ex); + } + } + } +} diff --git a/Projects/Server/Network/ServerInfo.cs b/Projects/Server/Network/ServerInfo.cs index d31ad0792..cf6a9445e 100644 --- a/Projects/Server/Network/ServerInfo.cs +++ b/Projects/Server/Network/ServerInfo.cs @@ -1,44 +1,44 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ServerInfo.cs - Created: 2020/06/25 - Updated: 2020/06/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Net; - -namespace Server.Network -{ - public sealed class ServerInfo - { - public ServerInfo(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address) - { - Name = name; - FullPercent = fullPercent; - TimeZone = tz.GetUtcOffset(DateTime.Now).Hours; - Address = address; - } - - public string Name { get; set; } - - public int FullPercent { get; set; } - - public int TimeZone { get; set; } - - public IPEndPoint Address { get; set; } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ServerInfo.cs - Created: 2020/06/25 - Updated: 2020/06/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Net; + +namespace Server.Network +{ + public sealed class ServerInfo + { + public ServerInfo(string name, int fullPercent, TimeZoneInfo tz, IPEndPoint address) + { + Name = name; + FullPercent = fullPercent; + TimeZone = tz.GetUtcOffset(DateTime.Now).Hours; + Address = address; + } + + public string Name { get; set; } + + public int FullPercent { get; set; } + + public int TimeZone { get; set; } + + public IPEndPoint Address { get; set; } + } +} diff --git a/Projects/Server/Network/ServerStartup.cs b/Projects/Server/Network/ServerStartup.cs index 56a35c268..2ef47b15b 100644 --- a/Projects/Server/Network/ServerStartup.cs +++ b/Projects/Server/Network/ServerStartup.cs @@ -1,43 +1,43 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ServerStartup.cs * - * Created: 2020/04/12 - Updated: 2020/04/12 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace Server.Network -{ - public class ServerStartup - { - private readonly IMessagePumpService _messagePumpService; - public ServerStartup(IMessagePumpService messagePumpService) => _messagePumpService = messagePumpService; - - public void ConfigureServices(IServiceCollection services) - { - } - - public void Configure(IApplicationBuilder app) - { - // Run async? - Task.Run(() => Core.RunEventLoop(_messagePumpService)); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ServerStartup.cs * + * Created: 2020/04/12 - Updated: 2020/04/12 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Server.Network +{ + public class ServerStartup + { + private readonly IMessagePumpService _messagePumpService; + public ServerStartup(IMessagePumpService messagePumpService) => _messagePumpService = messagePumpService; + + public void ConfigureServices(IServiceCollection services) + { + } + + public void Configure(IApplicationBuilder app) + { + // Run async? + Task.Run(() => Core.RunEventLoop(_messagePumpService)); + } + } +} diff --git a/Projects/Server/Network/StaticPacketHandlers.cs b/Projects/Server/Network/StaticPacketHandlers.cs index 7d36a8cef..38e5f4eb3 100644 --- a/Projects/Server/Network/StaticPacketHandlers.cs +++ b/Projects/Server/Network/StaticPacketHandlers.cs @@ -1,119 +1,134 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: StaticPacketHandlers.cs * - * Created: 2019/03/15 - Updated: 2019/12/24 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System.Collections.Concurrent; - -namespace Server.Network -{ - public static class StaticPacketHandlers - { - private static readonly ConcurrentDictionary OPLInfoPackets = - new ConcurrentDictionary(); - - private static readonly ConcurrentDictionary RemoveEntityPackets = - new ConcurrentDictionary(); - - private static readonly ConcurrentDictionary WorldItemPackets = - new ConcurrentDictionary(); - - private static readonly ConcurrentDictionary WorldItemSAPackets = - new ConcurrentDictionary(); - - private static readonly ConcurrentDictionary WorldItemHSPackets = - new ConcurrentDictionary(); - - public static OPLInfo GetOPLInfoPacket(IPropertyListObject obj) - { - return OPLInfoPackets.GetOrAdd(obj, value => - { - var packet = new OPLInfo(value.PropertyList.Entity.Serial, value.PropertyList.Hash); - packet.SetStatic(); - return packet; - }); - } - - public static OPLInfo FreeOPLInfoPacket(IPropertyListObject obj) - { - if (OPLInfoPackets.TryRemove(obj, out var p)) - Packet.Release(p); - - return p; - } - - public static RemoveEntity GetRemoveEntityPacket(IEntity entity) - { - return RemoveEntityPackets.GetOrAdd(entity, value => - { - var packet = new RemoveEntity(value.Serial); - packet.SetStatic(); - return packet; - }); - } - - public static void FreeRemoveItemPacket(IEntity entity) - { - if (RemoveEntityPackets.TryRemove(entity, out var p)) - Packet.Release(p); - } - - public static WorldItem GetWorldItemPacket(Item item) - { - return WorldItemPackets.GetOrAdd(item, value => - { - var packet = new WorldItem(value); - packet.SetStatic(); - return packet; - }); - } - - public static WorldItemSA GetWorldItemSAPacket(Item item) - { - return WorldItemSAPackets.GetOrAdd(item, value => - { - var packet = new WorldItemSA(value); - packet.SetStatic(); - return packet; - }); - } - - public static WorldItemHS GetWorldItemHSPacket(Item item) - { - return WorldItemHSPackets.GetOrAdd(item, value => - { - var packet = new WorldItemHS(value); - packet.SetStatic(); - return packet; - }); - } - - public static void FreeWorldItemPackets(Item item) - { - if (WorldItemPackets.TryRemove(item, out var wi)) - Packet.Release(wi); - - if (WorldItemSAPackets.TryRemove(item, out var wisa)) - Packet.Release(wisa); - - if (WorldItemHSPackets.TryRemove(item, out var wihs)) - Packet.Release(wihs); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: StaticPacketHandlers.cs * + * Created: 2019/03/15 - Updated: 2019/12/24 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Collections.Concurrent; + +namespace Server.Network +{ + public static class StaticPacketHandlers + { + private static readonly ConcurrentDictionary OPLInfoPackets = + new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary RemoveEntityPackets = + new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary WorldItemPackets = + new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary WorldItemSAPackets = + new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary WorldItemHSPackets = + new ConcurrentDictionary(); + + public static OPLInfo GetOPLInfoPacket(IPropertyListObject obj) + { + return OPLInfoPackets.GetOrAdd( + obj, + value => + { + var packet = new OPLInfo(value.PropertyList.Entity.Serial, value.PropertyList.Hash); + packet.SetStatic(); + return packet; + } + ); + } + + public static OPLInfo FreeOPLInfoPacket(IPropertyListObject obj) + { + if (OPLInfoPackets.TryRemove(obj, out var p)) + Packet.Release(p); + + return p; + } + + public static RemoveEntity GetRemoveEntityPacket(IEntity entity) + { + return RemoveEntityPackets.GetOrAdd( + entity, + value => + { + var packet = new RemoveEntity(value.Serial); + packet.SetStatic(); + return packet; + } + ); + } + + public static void FreeRemoveItemPacket(IEntity entity) + { + if (RemoveEntityPackets.TryRemove(entity, out var p)) + Packet.Release(p); + } + + public static WorldItem GetWorldItemPacket(Item item) + { + return WorldItemPackets.GetOrAdd( + item, + value => + { + var packet = new WorldItem(value); + packet.SetStatic(); + return packet; + } + ); + } + + public static WorldItemSA GetWorldItemSAPacket(Item item) + { + return WorldItemSAPackets.GetOrAdd( + item, + value => + { + var packet = new WorldItemSA(value); + packet.SetStatic(); + return packet; + } + ); + } + + public static WorldItemHS GetWorldItemHSPacket(Item item) + { + return WorldItemHSPackets.GetOrAdd( + item, + value => + { + var packet = new WorldItemHS(value); + packet.SetStatic(); + return packet; + } + ); + } + + public static void FreeWorldItemPackets(Item item) + { + if (WorldItemPackets.TryRemove(item, out var wi)) + Packet.Release(wi); + + if (WorldItemSAPackets.TryRemove(item, out var wisa)) + Packet.Release(wisa); + + if (WorldItemHSPackets.TryRemove(item, out var wihs)) + Packet.Release(wihs); + } + } +} diff --git a/Projects/Server/Network/TcpServer.cs b/Projects/Server/Network/TcpServer.cs index 69da009b4..b2940c518 100644 --- a/Projects/Server/Network/TcpServer.cs +++ b/Projects/Server/Network/TcpServer.cs @@ -1,84 +1,86 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: TcpServer.cs * - * Created: 2020/04/12 - Updated: 2020/04/12 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.NetworkInformation; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Connections; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace Server.Network -{ - public static class TcpServer - { - // Make this thread safe - public static List Instances { get; } = new List(); - - private static IPAddress[] m_ListeningAddresses; - - public static IWebHostBuilder CreateWebHostBuilder(string[] args = null) => - WebHost.CreateDefaultBuilder(args) - .UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "True") - .ConfigureServices(services => { services.AddSingleton(new MessagePumpService()); }) - .UseKestrel(options => - { - foreach (var ipep in ServerConfiguration.Listeners) - { - options.Listen(ipep, builder => { builder.UseConnectionHandler(); }); - m_ListeningAddresses = GetListeningAddresses(ipep); - DisplayListener(ipep); - } - - // Webservices here - }) - .UseLibuv() - .UseStartup(); - - public static IPAddress[] GetListeningAddresses(IPEndPoint ipep) - { - if (m_ListeningAddresses != null) - return m_ListeningAddresses; - - var list = new List(); - foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces()) - { - var properties = adapter.GetIPProperties(); - foreach (var unicast in properties.UnicastAddresses) - if (ipep.AddressFamily == unicast.Address.AddressFamily) - list.Add(unicast.Address); - } - - return list.ToArray(); - } - - private static void DisplayListener(IPEndPoint ipep) - { - if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) - foreach (var ip in m_ListeningAddresses) - Console.WriteLine("Listening: {0}:{1}", ip, ipep.Port); - else - Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: TcpServer.cs * + * Created: 2020/04/12 - Updated: 2020/04/12 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace Server.Network +{ + public static class TcpServer + { + private static IPAddress[] m_ListeningAddresses; + + // Make this thread safe + public static List Instances { get; } = new List(); + + public static IWebHostBuilder CreateWebHostBuilder(string[] args = null) => + WebHost.CreateDefaultBuilder(args) + .UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "True") + .ConfigureServices(services => { services.AddSingleton(new MessagePumpService()); }) + .UseKestrel( + options => + { + foreach (var ipep in ServerConfiguration.Listeners) + { + options.Listen(ipep, builder => { builder.UseConnectionHandler(); }); + m_ListeningAddresses = GetListeningAddresses(ipep); + DisplayListener(ipep); + } + + // Webservices here + } + ) + .UseLibuv() + .UseStartup(); + + public static IPAddress[] GetListeningAddresses(IPEndPoint ipep) + { + if (m_ListeningAddresses != null) + return m_ListeningAddresses; + + var list = new List(); + foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces()) + { + var properties = adapter.GetIPProperties(); + foreach (var unicast in properties.UnicastAddresses) + if (ipep.AddressFamily == unicast.Address.AddressFamily) + list.Add(unicast.Address); + } + + return list.ToArray(); + } + + private static void DisplayListener(IPEndPoint ipep) + { + if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) + foreach (var ip in m_ListeningAddresses) + Console.WriteLine("Listening: {0}:{1}", ip, ipep.Port); + else + Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port); + } + } +} diff --git a/Projects/Server/Notoriety.cs b/Projects/Server/Notoriety.cs index 6569da11a..90b2a3b0f 100644 --- a/Projects/Server/Notoriety.cs +++ b/Projects/Server/Notoriety.cs @@ -1,53 +1,53 @@ -/*************************************************************************** - * Notoriety.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server -{ - public delegate int NotorietyHandler(Mobile source, Mobile target); - - public static class Notoriety - { - public const int Innocent = 1; - public const int Ally = 2; - public const int CanBeAttacked = 3; - public const int Criminal = 4; - public const int Enemy = 5; - public const int Murderer = 6; - public const int Invulnerable = 7; - - public static NotorietyHandler Handler { get; set; } - - public static int[] Hues { get; set; } = - { - 0x000, - 0x059, - 0x03F, - 0x3B2, - 0x3B2, - 0x090, - 0x022, - 0x035 - }; - - public static int GetHue(int noto) => noto < 0 || noto >= Hues.Length ? 0 : Hues[noto]; - - public static int Compute(Mobile source, Mobile target) => Handler?.Invoke(source, target) ?? CanBeAttacked; - } -} +/*************************************************************************** + * Notoriety.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server +{ + public delegate int NotorietyHandler(Mobile source, Mobile target); + + public static class Notoriety + { + public const int Innocent = 1; + public const int Ally = 2; + public const int CanBeAttacked = 3; + public const int Criminal = 4; + public const int Enemy = 5; + public const int Murderer = 6; + public const int Invulnerable = 7; + + public static NotorietyHandler Handler { get; set; } + + public static int[] Hues { get; set; } = + { + 0x000, + 0x059, + 0x03F, + 0x3B2, + 0x3B2, + 0x090, + 0x022, + 0x035 + }; + + public static int GetHue(int noto) => noto < 0 || noto >= Hues.Length ? 0 : Hues[noto]; + + public static int Compute(Mobile source, Mobile target) => Handler?.Invoke(source, target) ?? CanBeAttacked; + } +} diff --git a/Projects/Server/ObjectPropertyList.cs b/Projects/Server/ObjectPropertyList.cs index 3aba129da..8565d290f 100644 --- a/Projects/Server/ObjectPropertyList.cs +++ b/Projects/Server/ObjectPropertyList.cs @@ -1,198 +1,198 @@ -/*************************************************************************** - * ObjectPropertyList.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.IO; -using System.Text; -using Server.Network; - -namespace Server -{ - public interface IPropertyListObject : IEntity - { - ObjectPropertyList PropertyList { get; } - OPLInfo OPLPacket { get; } - - void GetProperties(ObjectPropertyList list); - } - - public sealed class ObjectPropertyList : Packet - { - private static byte[] m_Buffer = new byte[1024]; - private static readonly Encoding m_Encoding = Encoding.Unicode; - - // Each of these are localized to "~1_NOTHING~" which allows the string argument to be used - private static readonly int[] m_StringNumbers = - { - 1042971, - 1070722 - }; - - private int m_Hash; - private int m_Strings; - - public ObjectPropertyList(IEntity e) : base(0xD6) - { - EnsureCapacity(128); - - Entity = e; - - Stream.Write((short)1); - Stream.Write(e.Serial); - Stream.Write((byte)0); - Stream.Write((byte)0); - Stream.Write(e.Serial); - } - - public IEntity Entity { get; } - - public int Hash => 0x40000000 + m_Hash; - - public int Header { get; set; } - - public string HeaderArgs { get; set; } - - public static bool Enabled { get; set; } - - public void Add(int number) - { - if (number == 0) - return; - - AddHash(number); - - if (Header == 0) - { - Header = number; - HeaderArgs = ""; - } - - Stream.Write(number); - Stream.Write((short)0); - } - - public void Terminate() - { - Stream.Write(0); - - Stream.Seek(11, SeekOrigin.Begin); - Stream.Write(m_Hash); - } - - public void AddHash(int val) - { - m_Hash ^= val & 0x3FFFFFF; - m_Hash ^= (val >> 26) & 0x3F; - } - - public void Add(int number, string arguments) - { - if (number == 0) - return; - - arguments ??= ""; - - if (Header == 0) - { - Header = number; - HeaderArgs = arguments; - } - - AddHash(number); - AddHash(arguments.GetHashCode()); - - Stream.Write(number); - - var byteCount = m_Encoding.GetByteCount(arguments); - - if (byteCount > m_Buffer.Length) - m_Buffer = new byte[byteCount]; - - byteCount = m_Encoding.GetBytes(arguments, 0, arguments.Length, m_Buffer, 0); - - Stream.Write((short)byteCount); - Stream.Write(m_Buffer, 0, byteCount); - } - - public void Add(int number, string format, object arg0) - { - Add(number, string.Format(format, arg0)); - } - - public void Add(int number, string format, object arg0, object arg1) - { - Add(number, string.Format(format, arg0, arg1)); - } - - public void Add(int number, string format, object arg0, object arg1, object arg2) - { - Add(number, string.Format(format, arg0, arg1, arg2)); - } - - public void Add(int number, string format, params object[] args) - { - Add(number, string.Format(format, args)); - } - - private int GetStringNumber() => m_StringNumbers[m_Strings++ % m_StringNumbers.Length]; - - public void Add(string text) - { - Add(GetStringNumber(), text); - } - - public void Add(string format, string arg0) - { - Add(GetStringNumber(), string.Format(format, arg0)); - } - - public void Add(string format, string arg0, string arg1) - { - Add(GetStringNumber(), string.Format(format, arg0, arg1)); - } - - public void Add(string format, string arg0, string arg1, string arg2) - { - Add(GetStringNumber(), string.Format(format, arg0, arg1, arg2)); - } - - public void Add(string format, params object[] args) - { - Add(GetStringNumber(), string.Format(format, args)); - } - } - - public sealed class OPLInfo : Packet - { - /*public OPLInfo( ObjectPropertyList list ) : base( 0xBF ) - { - EnsureCapacity( 13 ); - - m_Stream.Write( (short) 0x10 ); - m_Stream.Write( (int) list.Entity.Serial ); - m_Stream.Write( (int) list.Hash ); - }*/ - - public OPLInfo(Serial serial, int hash) : base(0xDC, 9) - { - Stream.Write(serial); - Stream.Write(hash); - } - } -} +/*************************************************************************** + * ObjectPropertyList.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.IO; +using System.Text; +using Server.Network; + +namespace Server +{ + public interface IPropertyListObject : IEntity + { + ObjectPropertyList PropertyList { get; } + OPLInfo OPLPacket { get; } + + void GetProperties(ObjectPropertyList list); + } + + public sealed class ObjectPropertyList : Packet + { + private static byte[] m_Buffer = new byte[1024]; + private static readonly Encoding m_Encoding = Encoding.Unicode; + + // Each of these are localized to "~1_NOTHING~" which allows the string argument to be used + private static readonly int[] m_StringNumbers = + { + 1042971, + 1070722 + }; + + private int m_Hash; + private int m_Strings; + + public ObjectPropertyList(IEntity e) : base(0xD6) + { + EnsureCapacity(128); + + Entity = e; + + Stream.Write((short)1); + Stream.Write(e.Serial); + Stream.Write((byte)0); + Stream.Write((byte)0); + Stream.Write(e.Serial); + } + + public IEntity Entity { get; } + + public int Hash => 0x40000000 + m_Hash; + + public int Header { get; set; } + + public string HeaderArgs { get; set; } + + public static bool Enabled { get; set; } + + public void Add(int number) + { + if (number == 0) + return; + + AddHash(number); + + if (Header == 0) + { + Header = number; + HeaderArgs = ""; + } + + Stream.Write(number); + Stream.Write((short)0); + } + + public void Terminate() + { + Stream.Write(0); + + Stream.Seek(11, SeekOrigin.Begin); + Stream.Write(m_Hash); + } + + public void AddHash(int val) + { + m_Hash ^= val & 0x3FFFFFF; + m_Hash ^= (val >> 26) & 0x3F; + } + + public void Add(int number, string arguments) + { + if (number == 0) + return; + + arguments ??= ""; + + if (Header == 0) + { + Header = number; + HeaderArgs = arguments; + } + + AddHash(number); + AddHash(arguments.GetHashCode()); + + Stream.Write(number); + + var byteCount = m_Encoding.GetByteCount(arguments); + + if (byteCount > m_Buffer.Length) + m_Buffer = new byte[byteCount]; + + byteCount = m_Encoding.GetBytes(arguments, 0, arguments.Length, m_Buffer, 0); + + Stream.Write((short)byteCount); + Stream.Write(m_Buffer, 0, byteCount); + } + + public void Add(int number, string format, object arg0) + { + Add(number, string.Format(format, arg0)); + } + + public void Add(int number, string format, object arg0, object arg1) + { + Add(number, string.Format(format, arg0, arg1)); + } + + public void Add(int number, string format, object arg0, object arg1, object arg2) + { + Add(number, string.Format(format, arg0, arg1, arg2)); + } + + public void Add(int number, string format, params object[] args) + { + Add(number, string.Format(format, args)); + } + + private int GetStringNumber() => m_StringNumbers[m_Strings++ % m_StringNumbers.Length]; + + public void Add(string text) + { + Add(GetStringNumber(), text); + } + + public void Add(string format, string arg0) + { + Add(GetStringNumber(), string.Format(format, arg0)); + } + + public void Add(string format, string arg0, string arg1) + { + Add(GetStringNumber(), string.Format(format, arg0, arg1)); + } + + public void Add(string format, string arg0, string arg1, string arg2) + { + Add(GetStringNumber(), string.Format(format, arg0, arg1, arg2)); + } + + public void Add(string format, params object[] args) + { + Add(GetStringNumber(), string.Format(format, args)); + } + } + + public sealed class OPLInfo : Packet + { + /*public OPLInfo( ObjectPropertyList list ) : base( 0xBF ) + { + EnsureCapacity( 13 ); + + m_Stream.Write( (short) 0x10 ); + m_Stream.Write( (int) list.Entity.Serial ); + m_Stream.Write( (int) list.Hash ); + }*/ + + public OPLInfo(Serial serial, int hash) : base(0xDC, 9) + { + Stream.Write(serial); + Stream.Write(hash); + } + } +} diff --git a/Projects/Server/Party.cs b/Projects/Server/Party.cs index e86a3db45..d285b71fc 100644 --- a/Projects/Server/Party.cs +++ b/Projects/Server/Party.cs @@ -1,35 +1,35 @@ -/*************************************************************************** - * Party.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server -{ - public abstract class PartyCommands - { - public static PartyCommands Handler { get; set; } - - public abstract void OnAdd(Mobile from); - public abstract void OnRemove(Mobile from, Mobile target); - public abstract void OnPrivateMessage(Mobile from, Mobile target, string text); - public abstract void OnPublicMessage(Mobile from, string text); - public abstract void OnSetCanLoot(Mobile from, bool canLoot); - public abstract void OnAccept(Mobile from, Mobile leader); - public abstract void OnDecline(Mobile from, Mobile leader); - } -} +/*************************************************************************** + * Party.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server +{ + public abstract class PartyCommands + { + public static PartyCommands Handler { get; set; } + + public abstract void OnAdd(Mobile from); + public abstract void OnRemove(Mobile from, Mobile target); + public abstract void OnPrivateMessage(Mobile from, Mobile target, string text); + public abstract void OnPublicMessage(Mobile from, string text); + public abstract void OnSetCanLoot(Mobile from, bool canLoot); + public abstract void OnAccept(Mobile from, Mobile leader); + public abstract void OnDecline(Mobile from, Mobile leader); + } +} diff --git a/Projects/Server/Persistence/BinaryMemoryWriter.cs b/Projects/Server/Persistence/BinaryMemoryWriter.cs index 2946c9019..5c9e349f2 100644 --- a/Projects/Server/Persistence/BinaryMemoryWriter.cs +++ b/Projects/Server/Persistence/BinaryMemoryWriter.cs @@ -1,80 +1,82 @@ -/*************************************************************************** - * BinaryMemoryWriter.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.IO; - -namespace Server -{ - public sealed class BinaryMemoryWriter : BinaryFileWriter - { - private static byte[] indexBuffer; - private readonly MemoryStream stream; - - public BinaryMemoryWriter() - : base(new MemoryStream(512), true) => - stream = UnderlyingStream as MemoryStream; - - protected override int BufferSize => 512; - - public int CommitTo(SequentialFileWriterStream dataFile, SequentialFileWriterStream indexFile, int typeCode, uint serial) - { - Flush(); - - var buffer = stream.GetBuffer(); - var length = (int)stream.Length; - - var position = dataFile.Position; - - dataFile.Write(buffer, 0, length); - - indexBuffer ??= new byte[20]; - - indexBuffer[0] = (byte)typeCode; - indexBuffer[1] = (byte)(typeCode >> 8); - indexBuffer[2] = (byte)(typeCode >> 16); - indexBuffer[3] = (byte)(typeCode >> 24); - - indexBuffer[4] = (byte)serial; - indexBuffer[5] = (byte)(serial >> 8); - indexBuffer[6] = (byte)(serial >> 16); - indexBuffer[7] = (byte)(serial >> 24); - - indexBuffer[8] = (byte)position; - indexBuffer[9] = (byte)(position >> 8); - indexBuffer[10] = (byte)(position >> 16); - indexBuffer[11] = (byte)(position >> 24); - indexBuffer[12] = (byte)(position >> 32); - indexBuffer[13] = (byte)(position >> 40); - indexBuffer[14] = (byte)(position >> 48); - indexBuffer[15] = (byte)(position >> 56); - - indexBuffer[16] = (byte)length; - indexBuffer[17] = (byte)(length >> 8); - indexBuffer[18] = (byte)(length >> 16); - indexBuffer[19] = (byte)(length >> 24); - - indexFile.Write(indexBuffer, 0, indexBuffer.Length); - - stream.SetLength(0); - - return length; - } - } -} +/*************************************************************************** + * BinaryMemoryWriter.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.IO; + +namespace Server +{ + public sealed class BinaryMemoryWriter : BinaryFileWriter + { + private static byte[] indexBuffer; + private readonly MemoryStream stream; + + public BinaryMemoryWriter() + : base(new MemoryStream(512), true) => + stream = UnderlyingStream as MemoryStream; + + protected override int BufferSize => 512; + + public int CommitTo( + SequentialFileWriterStream dataFile, SequentialFileWriterStream indexFile, int typeCode, uint serial + ) + { + Flush(); + + var buffer = stream.GetBuffer(); + var length = (int)stream.Length; + + var position = dataFile.Position; + + dataFile.Write(buffer, 0, length); + + indexBuffer ??= new byte[20]; + + indexBuffer[0] = (byte)typeCode; + indexBuffer[1] = (byte)(typeCode >> 8); + indexBuffer[2] = (byte)(typeCode >> 16); + indexBuffer[3] = (byte)(typeCode >> 24); + + indexBuffer[4] = (byte)serial; + indexBuffer[5] = (byte)(serial >> 8); + indexBuffer[6] = (byte)(serial >> 16); + indexBuffer[7] = (byte)(serial >> 24); + + indexBuffer[8] = (byte)position; + indexBuffer[9] = (byte)(position >> 8); + indexBuffer[10] = (byte)(position >> 16); + indexBuffer[11] = (byte)(position >> 24); + indexBuffer[12] = (byte)(position >> 32); + indexBuffer[13] = (byte)(position >> 40); + indexBuffer[14] = (byte)(position >> 48); + indexBuffer[15] = (byte)(position >> 56); + + indexBuffer[16] = (byte)length; + indexBuffer[17] = (byte)(length >> 8); + indexBuffer[18] = (byte)(length >> 16); + indexBuffer[19] = (byte)(length >> 24); + + indexFile.Write(indexBuffer, 0, indexBuffer.Length); + + stream.SetLength(0); + + return length; + } + } +} diff --git a/Projects/Server/Persistence/DualSaveStrategy.cs b/Projects/Server/Persistence/DualSaveStrategy.cs index 7784e619f..4b26e52ac 100644 --- a/Projects/Server/Persistence/DualSaveStrategy.cs +++ b/Projects/Server/Persistence/DualSaveStrategy.cs @@ -1,47 +1,48 @@ -/*************************************************************************** - * DualSaveStrategy.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.Threading; - -namespace Server -{ - public sealed class DualSaveStrategy : StandardSaveStrategy - { - public override string Name => "Dual"; - - public override void Save(bool permitBackgroundWrite) - { - PermitBackgroundWrite = permitBackgroundWrite; - - var saveThread = new Thread(SaveItems); - - saveThread.Name = "Item Save Subset"; - saveThread.Start(); - - SaveMobiles(); - SaveGuilds(); - - saveThread.Join(); - - if (permitBackgroundWrite && UseSequentialWriters) // If we're permitted to write in the background, but we don't anyways, then notify. - World.NotifyDiskWriteComplete(); - } - } -} +/*************************************************************************** + * DualSaveStrategy.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.Threading; + +namespace Server +{ + public sealed class DualSaveStrategy : StandardSaveStrategy + { + public override string Name => "Dual"; + + public override void Save(bool permitBackgroundWrite) + { + PermitBackgroundWrite = permitBackgroundWrite; + + var saveThread = new Thread(SaveItems); + + saveThread.Name = "Item Save Subset"; + saveThread.Start(); + + SaveMobiles(); + SaveGuilds(); + + saveThread.Join(); + + if (permitBackgroundWrite && UseSequentialWriters + ) // If we're permitted to write in the background, but we don't anyways, then notify. + World.NotifyDiskWriteComplete(); + } + } +} diff --git a/Projects/Server/Persistence/DynamicSaveStrategy.cs b/Projects/Server/Persistence/DynamicSaveStrategy.cs index e7bcf0122..127b6cf2f 100644 --- a/Projects/Server/Persistence/DynamicSaveStrategy.cs +++ b/Projects/Server/Persistence/DynamicSaveStrategy.cs @@ -1,281 +1,297 @@ -/*************************************************************************** - * DynamicSaveStrategy.cs - * ------------------- - * begin : December 16, 2010 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; -using Server.Guilds; - -namespace Server -{ - public sealed class DynamicSaveStrategy : SaveStrategy - { - private readonly ConcurrentBag _decayBag; - private SequentialFileWriterStream _guildData, _guildIndex; - private readonly BlockingCollection _guildThreadWriters; - - private SequentialFileWriterStream _itemData, _itemIndex; - - private readonly BlockingCollection _itemThreadWriters; - - private SequentialFileWriterStream _mobileData, _mobileIndex; - private readonly BlockingCollection _mobileThreadWriters; - - public DynamicSaveStrategy() - { - _decayBag = new ConcurrentBag(); - _itemThreadWriters = new BlockingCollection(); - _mobileThreadWriters = new BlockingCollection(); - _guildThreadWriters = new BlockingCollection(); - } - - public override string Name => "Dynamic"; - - public override void Save(bool permitBackgroundWrite) - { - OpenFiles(); - - var saveTasks = new Task[3]; - - saveTasks[0] = SaveItems(); - saveTasks[1] = SaveMobiles(); - saveTasks[2] = SaveGuilds(); - - SaveTypeDatabases(); - - if (permitBackgroundWrite) - { -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - // This option makes it finish the writing to disk in the background, continuing even after Save() returns. - Task.Factory.ContinueWhenAll(saveTasks, _ => - { - CloseFiles(); - - World.NotifyDiskWriteComplete(); - }); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - else - { - Task.WaitAll(saveTasks); // Waits for the completion of all of the tasks(committing to disk) - CloseFiles(); - } - } - - private Task StartCommitTask(BlockingCollection threadWriter, SequentialFileWriterStream data, - SequentialFileWriterStream index) - { -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - var commitTask = Task.Factory.StartNew(() => - { - while (!threadWriter.IsCompleted) - { - QueuedMemoryWriter writer; - - try - { - writer = threadWriter.Take(); - } - catch (InvalidOperationException) - { - // Per MSDN, it's fine if we're here, successful completion of adding can rarely put us into this state. - break; - } - - writer.CommitTo(data, index); - } - }); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - - return commitTask; - } - - private Task SaveItems() - { - // Start the blocking consumer; this runs in background. - var commitTask = StartCommitTask(_itemThreadWriters, _itemData, _itemIndex); - - IEnumerable items = World.Items.Values; - - // Start the producer. - Parallel.ForEach(items, () => new QueuedMemoryWriter(), - (item, state, writer) => - { - var startPosition = writer.Position; - - item.Serialize(writer); - - var size = (int)(writer.Position - startPosition); - - writer.QueueForIndex(item, size); - - if (item.Decays && item.Parent == null && item.Map != Map.Internal && - DateTime.UtcNow > item.LastMoved + item.DecayTime) _decayBag.Add(item); - - return writer; - }, - writer => - { - writer.Flush(); - - _itemThreadWriters.Add(writer); - }); - - _itemThreadWriters.CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task - - return commitTask; - } - - private Task SaveMobiles() - { - // Start the blocking consumer; this runs in background. - var commitTask = StartCommitTask(_mobileThreadWriters, _mobileData, _mobileIndex); - - IEnumerable mobiles = World.Mobiles.Values; - - // Start the producer. - Parallel.ForEach(mobiles, () => new QueuedMemoryWriter(), - (mobile, state, writer) => - { - var startPosition = writer.Position; - - mobile.Serialize(writer); - - var size = (int)(writer.Position - startPosition); - - writer.QueueForIndex(mobile, size); - - return writer; - }, - writer => - { - writer.Flush(); - - _mobileThreadWriters.Add(writer); - }); - - _mobileThreadWriters - .CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task tell the consumer that we're done - - return commitTask; - } - - private Task SaveGuilds() - { - // Start the blocking consumer; this runs in background. - var commitTask = StartCommitTask(_guildThreadWriters, _guildData, _guildIndex); - - IEnumerable guilds = BaseGuild.List.Values; - - // Start the producer. - Parallel.ForEach(guilds, () => new QueuedMemoryWriter(), - (guild, state, writer) => - { - var startPosition = writer.Position; - - guild.Serialize(writer); - - var size = (int)(writer.Position - startPosition); - - writer.QueueForIndex(guild, size); - - return writer; - }, - writer => - { - writer.Flush(); - - _guildThreadWriters.Add(writer); - }); - - _guildThreadWriters.CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task - - return commitTask; - } - - public override void ProcessDecay() - { - while (_decayBag.TryTake(out var item)) - if (item.OnDecay()) - item.Delete(); - } - - private void OpenFiles() - { - _itemData = new SequentialFileWriterStream(World.ItemDataPath); - _itemIndex = new SequentialFileWriterStream(World.ItemIndexPath); - - _mobileData = new SequentialFileWriterStream(World.MobileDataPath); - _mobileIndex = new SequentialFileWriterStream(World.MobileIndexPath); - - _guildData = new SequentialFileWriterStream(World.GuildDataPath); - _guildIndex = new SequentialFileWriterStream(World.GuildIndexPath); - - WriteCount(_itemIndex, World.Items.Count); - WriteCount(_mobileIndex, World.Mobiles.Count); - WriteCount(_guildIndex, BaseGuild.List.Count); - } - - private void CloseFiles() - { - _itemData.Close(); - _itemIndex.Close(); - - _mobileData.Close(); - _mobileIndex.Close(); - - _guildData.Close(); - _guildIndex.Close(); - } - - private void WriteCount(SequentialFileWriterStream indexFile, int count) - { - // Equiv to GenericWriter.Write( (int)count ); - var buffer = new byte[4]; - - buffer[0] = (byte)count; - buffer[1] = (byte)(count >> 8); - buffer[2] = (byte)(count >> 16); - buffer[3] = (byte)(count >> 24); - - indexFile.Write(buffer, 0, buffer.Length); - } - - private void SaveTypeDatabases() - { - SaveTypeDatabase(World.ItemTypesPath, World.m_ItemTypes); - SaveTypeDatabase(World.MobileTypesPath, World.m_MobileTypes); - } - - private void SaveTypeDatabase(string path, List types) - { - var bfw = new BinaryFileWriter(path, false); - - bfw.Write(types.Count); - - foreach (var type in types) bfw.Write(type.FullName); - - bfw.Flush(); - - bfw.Close(); - } - } -} +/*************************************************************************** + * DynamicSaveStrategy.cs + * ------------------- + * begin : December 16, 2010 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; +using Server.Guilds; + +namespace Server +{ + public sealed class DynamicSaveStrategy : SaveStrategy + { + private readonly ConcurrentBag _decayBag; + private SequentialFileWriterStream _guildData, _guildIndex; + private readonly BlockingCollection _guildThreadWriters; + + private SequentialFileWriterStream _itemData, _itemIndex; + + private readonly BlockingCollection _itemThreadWriters; + + private SequentialFileWriterStream _mobileData, _mobileIndex; + private readonly BlockingCollection _mobileThreadWriters; + + public DynamicSaveStrategy() + { + _decayBag = new ConcurrentBag(); + _itemThreadWriters = new BlockingCollection(); + _mobileThreadWriters = new BlockingCollection(); + _guildThreadWriters = new BlockingCollection(); + } + + public override string Name => "Dynamic"; + + public override void Save(bool permitBackgroundWrite) + { + OpenFiles(); + + var saveTasks = new Task[3]; + + saveTasks[0] = SaveItems(); + saveTasks[1] = SaveMobiles(); + saveTasks[2] = SaveGuilds(); + + SaveTypeDatabases(); + + if (permitBackgroundWrite) + { +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + // This option makes it finish the writing to disk in the background, continuing even after Save() returns. + Task.Factory.ContinueWhenAll( + saveTasks, + _ => + { + CloseFiles(); + + World.NotifyDiskWriteComplete(); + } + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + } + else + { + Task.WaitAll(saveTasks); // Waits for the completion of all of the tasks(committing to disk) + CloseFiles(); + } + } + + private Task StartCommitTask( + BlockingCollection threadWriter, SequentialFileWriterStream data, + SequentialFileWriterStream index + ) + { +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * + var commitTask = Task.Factory.StartNew( + () => + { + while (!threadWriter.IsCompleted) + { + QueuedMemoryWriter writer; + + try + { + writer = threadWriter.Take(); + } + catch (InvalidOperationException) + { + // Per MSDN, it's fine if we're here, successful completion of adding can rarely put us into this state. + break; + } + + writer.CommitTo(data, index); + } + } + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + + return commitTask; + } + + private Task SaveItems() + { + // Start the blocking consumer; this runs in background. + var commitTask = StartCommitTask(_itemThreadWriters, _itemData, _itemIndex); + + IEnumerable items = World.Items.Values; + + // Start the producer. + Parallel.ForEach( + items, + () => new QueuedMemoryWriter(), + (item, state, writer) => + { + var startPosition = writer.Position; + + item.Serialize(writer); + + var size = (int)(writer.Position - startPosition); + + writer.QueueForIndex(item, size); + + if (item.Decays && item.Parent == null && item.Map != Map.Internal && + DateTime.UtcNow > item.LastMoved + item.DecayTime) _decayBag.Add(item); + + return writer; + }, + writer => + { + writer.Flush(); + + _itemThreadWriters.Add(writer); + } + ); + + _itemThreadWriters.CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task + + return commitTask; + } + + private Task SaveMobiles() + { + // Start the blocking consumer; this runs in background. + var commitTask = StartCommitTask(_mobileThreadWriters, _mobileData, _mobileIndex); + + IEnumerable mobiles = World.Mobiles.Values; + + // Start the producer. + Parallel.ForEach( + mobiles, + () => new QueuedMemoryWriter(), + (mobile, state, writer) => + { + var startPosition = writer.Position; + + mobile.Serialize(writer); + + var size = (int)(writer.Position - startPosition); + + writer.QueueForIndex(mobile, size); + + return writer; + }, + writer => + { + writer.Flush(); + + _mobileThreadWriters.Add(writer); + } + ); + + _mobileThreadWriters + .CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task tell the consumer that we're done + + return commitTask; + } + + private Task SaveGuilds() + { + // Start the blocking consumer; this runs in background. + var commitTask = StartCommitTask(_guildThreadWriters, _guildData, _guildIndex); + + IEnumerable guilds = BaseGuild.List.Values; + + // Start the producer. + Parallel.ForEach( + guilds, + () => new QueuedMemoryWriter(), + (guild, state, writer) => + { + var startPosition = writer.Position; + + guild.Serialize(writer); + + var size = (int)(writer.Position - startPosition); + + writer.QueueForIndex(guild, size); + + return writer; + }, + writer => + { + writer.Flush(); + + _guildThreadWriters.Add(writer); + } + ); + + _guildThreadWriters.CompleteAdding(); // We only get here after the Parallel.ForEach completes. Lets our task + + return commitTask; + } + + public override void ProcessDecay() + { + while (_decayBag.TryTake(out var item)) + if (item.OnDecay()) + item.Delete(); + } + + private void OpenFiles() + { + _itemData = new SequentialFileWriterStream(World.ItemDataPath); + _itemIndex = new SequentialFileWriterStream(World.ItemIndexPath); + + _mobileData = new SequentialFileWriterStream(World.MobileDataPath); + _mobileIndex = new SequentialFileWriterStream(World.MobileIndexPath); + + _guildData = new SequentialFileWriterStream(World.GuildDataPath); + _guildIndex = new SequentialFileWriterStream(World.GuildIndexPath); + + WriteCount(_itemIndex, World.Items.Count); + WriteCount(_mobileIndex, World.Mobiles.Count); + WriteCount(_guildIndex, BaseGuild.List.Count); + } + + private void CloseFiles() + { + _itemData.Close(); + _itemIndex.Close(); + + _mobileData.Close(); + _mobileIndex.Close(); + + _guildData.Close(); + _guildIndex.Close(); + } + + private void WriteCount(SequentialFileWriterStream indexFile, int count) + { + // Equiv to GenericWriter.Write( (int)count ); + var buffer = new byte[4]; + + buffer[0] = (byte)count; + buffer[1] = (byte)(count >> 8); + buffer[2] = (byte)(count >> 16); + buffer[3] = (byte)(count >> 24); + + indexFile.Write(buffer, 0, buffer.Length); + } + + private void SaveTypeDatabases() + { + SaveTypeDatabase(World.ItemTypesPath, World.m_ItemTypes); + SaveTypeDatabase(World.MobileTypesPath, World.m_MobileTypes); + } + + private void SaveTypeDatabase(string path, List types) + { + var bfw = new BinaryFileWriter(path, false); + + bfw.Write(types.Count); + + foreach (var type in types) bfw.Write(type.FullName); + + bfw.Flush(); + + bfw.Close(); + } + } +} diff --git a/Projects/Server/Persistence/FileOperations.cs b/Projects/Server/Persistence/FileOperations.cs index c042d91a9..2ed74fe99 100644 --- a/Projects/Server/Persistence/FileOperations.cs +++ b/Projects/Server/Persistence/FileOperations.cs @@ -1,107 +1,108 @@ -/*************************************************************************** - * FileOperations.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.IO; -#if WINDOWS -using System; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; -#endif - -namespace Server -{ - public static class FileOperations - { - public const int KB = 1024; - public const int MB = 1024 * KB; - - public static int BufferSize { get; set; } = 1 * MB; - - public static int Concurrency { get; set; } = 1; - -#if WINDOWS - public static bool Unbuffered { get; set; } = true; -#endif - - public static bool AreSynchronous => Concurrency < 1; - - public static FileStream OpenSequentialStream(string path, FileMode mode, FileAccess access, FileShare share) - { - var options = FileOptions.SequentialScan; - - if (Concurrency > 0) - options |= FileOptions.Asynchronous; - -#if !WINDOWS - return new FileStream( path, mode, access, share, BufferSize, options ); -#else - if (Unbuffered) - options |= NoBuffering; - else - return new FileStream(path, mode, access, share, BufferSize, options); - - var fileHandle = - UnsafeNativeMethods.CreateFile(path, (int)access, share, IntPtr.Zero, mode, (int)options, IntPtr.Zero); - - if (fileHandle.IsInvalid) throw new IOException(); - - return new UnbufferedFileStream(fileHandle, access, BufferSize, Concurrency > 0); -#endif - } - -#if WINDOWS - private class UnbufferedFileStream : FileStream - { - private readonly SafeFileHandle fileHandle; - - public UnbufferedFileStream(SafeFileHandle fileHandle, FileAccess access, int bufferSize, bool isAsync) - : base(fileHandle, access, bufferSize, isAsync) => - this.fileHandle = fileHandle; - - public override void Write(byte[] array, int offset, int count) - { - base.Write(array, offset, BufferSize); - } - - public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, - object stateObject) => - base.BeginWrite(array, offset, BufferSize, userCallback, stateObject); - - protected override void Dispose(bool disposing) - { - if (!fileHandle.IsClosed) fileHandle.Close(); - - base.Dispose(disposing); - } - } -#endif - -#if WINDOWS - private const FileOptions NoBuffering = (FileOptions)0x20000000; - - internal static class UnsafeNativeMethods - { - [DllImport("Kernel32", CharSet = CharSet.Unicode, SetLastError = true)] - internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, - IntPtr securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); - } -#endif - } -} +/*************************************************************************** + * FileOperations.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.IO; + +#if WINDOWS +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; +#endif + +namespace Server +{ + public static class FileOperations + { + public const int KB = 1024; + public const int MB = 1024 * KB; + + public static int BufferSize { get; set; } = 1 * MB; + + public static int Concurrency { get; set; } = 1; + +#if WINDOWS + public static bool Unbuffered { get; set; } = true; +#endif + + public static bool AreSynchronous => Concurrency < 1; + + public static FileStream OpenSequentialStream(string path, FileMode mode, FileAccess access, FileShare share) + { + var options = FileOptions.SequentialScan; + + if (Concurrency > 0) + options |= FileOptions.Asynchronous; + +#if !WINDOWS + return new FileStream(path, mode, access, share, BufferSize, options); +#else + if (Unbuffered) + options |= NoBuffering; + else + return new FileStream(path, mode, access, share, BufferSize, options); + + var fileHandle = + UnsafeNativeMethods.CreateFile(path, (int)access, share, IntPtr.Zero, mode, (int)options, IntPtr.Zero); + + if (fileHandle.IsInvalid) throw new IOException(); + + return new UnbufferedFileStream(fileHandle, access, BufferSize, Concurrency > 0); +#endif + } + +#if WINDOWS + private class UnbufferedFileStream : FileStream + { + private readonly SafeFileHandle fileHandle; + + public UnbufferedFileStream(SafeFileHandle fileHandle, FileAccess access, int bufferSize, bool isAsync) + : base(fileHandle, access, bufferSize, isAsync) => + this.fileHandle = fileHandle; + + public override void Write(byte[] array, int offset, int count) + { + base.Write(array, offset, BufferSize); + } + + public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, + object stateObject) => + base.BeginWrite(array, offset, BufferSize, userCallback, stateObject); + + protected override void Dispose(bool disposing) + { + if (!fileHandle.IsClosed) fileHandle.Close(); + + base.Dispose(disposing); + } + } +#endif + +#if WINDOWS + private const FileOptions NoBuffering = (FileOptions)0x20000000; + + internal static class UnsafeNativeMethods + { + [DllImport("Kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, + IntPtr securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); + } +#endif + } +} diff --git a/Projects/Server/Persistence/FileQueue.cs b/Projects/Server/Persistence/FileQueue.cs index 0a4c4fb9b..9eaf86ba2 100644 --- a/Projects/Server/Persistence/FileQueue.cs +++ b/Projects/Server/Persistence/FileQueue.cs @@ -1,228 +1,228 @@ -/*************************************************************************** - * FileQueue.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Threading; - -namespace Server -{ - public delegate void FileCommitCallback(FileQueue.Chunk chunk); - - public sealed class FileQueue : IDisposable - { - private static readonly int bufferSize; - - private readonly Chunk[] active; - private int activeCount; - private Page buffered; - - private readonly FileCommitCallback callback; - - private ManualResetEvent idle; - - private readonly Queue pending; - - private readonly object syncRoot; - - static FileQueue() => bufferSize = FileOperations.BufferSize; - - public FileQueue(int concurrentWrites, FileCommitCallback callback) - { - if (concurrentWrites < 1) throw new ArgumentOutOfRangeException(nameof(concurrentWrites)); - - if (bufferSize < 1) -#pragma warning disable CA2208 // Instantiate argument exceptions correctly - throw new ArgumentOutOfRangeException(nameof(FileOperations.BufferSize)); -#pragma warning restore CA2208 // Instantiate argument exceptions correctly - - syncRoot = new object(); - - active = new Chunk[concurrentWrites]; - pending = new Queue(); - - this.callback = callback; - - idle = new ManualResetEvent(true); - } - - public long Position { get; private set; } - - public void Dispose() - { - if (idle != null) - { - idle.Close(); - idle = null; - } - } - - private void Append(Page page) - { - lock (syncRoot) - { - if (activeCount == 0) idle.Reset(); - - ++activeCount; - - for (var slot = 0; slot < active.Length; ++slot) - if (active[slot] == null) - { - active[slot] = new Chunk(this, slot, page.buffer, 0, page.length); - - callback(active[slot]); - - return; - } - - pending.Enqueue(page); - } - } - - public void Flush() - { - if (buffered.buffer != null) - { - Append(buffered); - - buffered.buffer = null; - buffered.length = 0; - } - - /*lock ( syncRoot ) { - if (pending.Count > 0 ) { - idle.Reset(); - } - - for ( int slot = 0; slot < active.Length && pending.Count > 0; ++slot ) { - if (active[slot] == null ) { - Page page = pending.Dequeue(); - - active[slot] = new Chunk( this, slot, page.buffer, 0, page.length ); - - ++activeCount; - - callback( active[slot] ); - } - } - }*/ - - idle.WaitOne(); - } - - private void Commit(Chunk chunk, int slot) - { - if (slot < 0 || slot >= active.Length) throw new ArgumentOutOfRangeException(nameof(slot)); - - lock (syncRoot) - { - if (active[slot] != chunk) throw new ArgumentException("active slot is not the current chunk"); - - ArrayPool.Shared.Return(chunk.Buffer); - - if (pending.Count > 0) - { - var page = pending.Dequeue(); - - active[slot] = new Chunk(this, slot, page.buffer, 0, page.length); - - callback(active[slot]); - } - else - { - active[slot] = null; - } - - --activeCount; - - if (activeCount == 0) idle.Set(); - } - } - - public void Enqueue(byte[] buffer, int offset, int size) - { - if (buffer == null) throw new ArgumentNullException(nameof(buffer)); - - if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); - if (size < 0) throw new ArgumentOutOfRangeException(nameof(size)); - if (buffer.Length - offset < size) throw new ArgumentOutOfRangeException(nameof(offset)); - - Position += size; - - while (size > 0) - { - buffered.buffer ??= ArrayPool.Shared.Rent(bufferSize); - - var page = buffered.buffer; // buffer page - var pageSpace = page.Length - buffered.length; // available bytes in page - var byteCount = size > pageSpace ? pageSpace : size; // how many bytes we can copy over - - Buffer.BlockCopy(buffer, offset, page, buffered.length, byteCount); - - buffered.length += byteCount; - offset += byteCount; - size -= byteCount; - - if (buffered.length == page.Length) - { - // page full - Append(buffered); - - buffered.buffer = null; - buffered.length = 0; - } - } - } - - public sealed class Chunk - { - private readonly FileQueue m_Owner; - private readonly int m_Slot; - - public Chunk(FileQueue owner, int slot, byte[] buffer, int offset, int size) - { - m_Owner = owner; - m_Slot = slot; - - Buffer = buffer; - Offset = offset; - Size = size; - } - - public byte[] Buffer { get; } - - public int Offset { get; } - - public int Size { get; } - - public void Commit() - { - m_Owner.Commit(this, m_Slot); - } - } - - private struct Page - { - public byte[] buffer; - public int length; - } - } -} +/*************************************************************************** + * FileQueue.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Threading; + +namespace Server +{ + public delegate void FileCommitCallback(FileQueue.Chunk chunk); + + public sealed class FileQueue : IDisposable + { + private static readonly int bufferSize; + + private readonly Chunk[] active; + + private readonly FileCommitCallback callback; + + private readonly Queue pending; + + private readonly object syncRoot; + private int activeCount; + private Page buffered; + + private ManualResetEvent idle; + + static FileQueue() => bufferSize = FileOperations.BufferSize; + + public FileQueue(int concurrentWrites, FileCommitCallback callback) + { + if (concurrentWrites < 1) throw new ArgumentOutOfRangeException(nameof(concurrentWrites)); + + if (bufferSize < 1) +#pragma warning disable CA2208 // Instantiate argument exceptions correctly + throw new ArgumentOutOfRangeException(nameof(FileOperations.BufferSize)); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly + + syncRoot = new object(); + + active = new Chunk[concurrentWrites]; + pending = new Queue(); + + this.callback = callback; + + idle = new ManualResetEvent(true); + } + + public long Position { get; private set; } + + public void Dispose() + { + if (idle != null) + { + idle.Close(); + idle = null; + } + } + + private void Append(Page page) + { + lock (syncRoot) + { + if (activeCount == 0) idle.Reset(); + + ++activeCount; + + for (var slot = 0; slot < active.Length; ++slot) + if (active[slot] == null) + { + active[slot] = new Chunk(this, slot, page.buffer, 0, page.length); + + callback(active[slot]); + + return; + } + + pending.Enqueue(page); + } + } + + public void Flush() + { + if (buffered.buffer != null) + { + Append(buffered); + + buffered.buffer = null; + buffered.length = 0; + } + + /*lock ( syncRoot ) { + if (pending.Count > 0 ) { + idle.Reset(); + } + + for ( int slot = 0; slot < active.Length && pending.Count > 0; ++slot ) { + if (active[slot] == null ) { + Page page = pending.Dequeue(); + + active[slot] = new Chunk( this, slot, page.buffer, 0, page.length ); + + ++activeCount; + + callback( active[slot] ); + } + } + }*/ + + idle.WaitOne(); + } + + private void Commit(Chunk chunk, int slot) + { + if (slot < 0 || slot >= active.Length) throw new ArgumentOutOfRangeException(nameof(slot)); + + lock (syncRoot) + { + if (active[slot] != chunk) throw new ArgumentException("active slot is not the current chunk"); + + ArrayPool.Shared.Return(chunk.Buffer); + + if (pending.Count > 0) + { + var page = pending.Dequeue(); + + active[slot] = new Chunk(this, slot, page.buffer, 0, page.length); + + callback(active[slot]); + } + else + { + active[slot] = null; + } + + --activeCount; + + if (activeCount == 0) idle.Set(); + } + } + + public void Enqueue(byte[] buffer, int offset, int size) + { + if (buffer == null) throw new ArgumentNullException(nameof(buffer)); + + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if (size < 0) throw new ArgumentOutOfRangeException(nameof(size)); + if (buffer.Length - offset < size) throw new ArgumentOutOfRangeException(nameof(offset)); + + Position += size; + + while (size > 0) + { + buffered.buffer ??= ArrayPool.Shared.Rent(bufferSize); + + var page = buffered.buffer; // buffer page + var pageSpace = page.Length - buffered.length; // available bytes in page + var byteCount = size > pageSpace ? pageSpace : size; // how many bytes we can copy over + + Buffer.BlockCopy(buffer, offset, page, buffered.length, byteCount); + + buffered.length += byteCount; + offset += byteCount; + size -= byteCount; + + if (buffered.length == page.Length) + { + // page full + Append(buffered); + + buffered.buffer = null; + buffered.length = 0; + } + } + } + + public sealed class Chunk + { + private readonly FileQueue m_Owner; + private readonly int m_Slot; + + public Chunk(FileQueue owner, int slot, byte[] buffer, int offset, int size) + { + m_Owner = owner; + m_Slot = slot; + + Buffer = buffer; + Offset = offset; + Size = size; + } + + public byte[] Buffer { get; } + + public int Offset { get; } + + public int Size { get; } + + public void Commit() + { + m_Owner.Commit(this, m_Slot); + } + } + + private struct Page + { + public byte[] buffer; + public int length; + } + } +} diff --git a/Projects/Server/Persistence/ParallelSaveStrategy.cs b/Projects/Server/Persistence/ParallelSaveStrategy.cs index d46b429c3..dc4ccafcd 100644 --- a/Projects/Server/Persistence/ParallelSaveStrategy.cs +++ b/Projects/Server/Persistence/ParallelSaveStrategy.cs @@ -1,352 +1,354 @@ -/*************************************************************************** - * ParallelSaveStrategy.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; -using Server.Guilds; - -namespace Server -{ - public sealed class ParallelSaveStrategy : SaveStrategy, IDisposable - { - private readonly Queue _decayQueue; - - private Consumer[] consumers; - private int cycle; - - private bool finished; - private SequentialFileWriterStream guildData, guildIndex; - - private SequentialFileWriterStream itemData, itemIndex; - - private SequentialFileWriterStream mobileData, mobileIndex; - - private readonly int processorCount; - - public ParallelSaveStrategy(int processorCount) - { - this.processorCount = processorCount; - - _decayQueue = new Queue(); - } - - public override string Name => "Parallel"; - - private int GetThreadCount() => processorCount - 1; - - public override void Save(bool permitBackgroundWrite) - { - OpenFiles(); - - consumers = new Consumer[GetThreadCount()]; - - for (var i = 0; i < consumers.Length; ++i) consumers[i] = new Consumer(this, 256); - - IEnumerable collection = new Producer(); - - foreach (var value in collection) - while (!Enqueue(value)) - if (!Commit()) - Thread.Sleep(0); - - finished = true; - - SaveTypeDatabases(); - - WaitHandle.WaitAll( - Array.ConvertAll( - consumers, - input => input.completionEvent)); - - Commit(); - - CloseFiles(); - } - - public override void ProcessDecay() - { - while (_decayQueue.Count > 0) - { - var item = _decayQueue.Dequeue(); - - if (item.OnDecay()) item.Delete(); - } - } - - private void SaveTypeDatabases() - { - SaveTypeDatabase(World.ItemTypesPath, World.m_ItemTypes); - SaveTypeDatabase(World.MobileTypesPath, World.m_MobileTypes); - } - - private void SaveTypeDatabase(string path, List types) - { - var bfw = new BinaryFileWriter(path, false); - - bfw.Write(types.Count); - - foreach (var type in types) bfw.Write(type.FullName); - - bfw.Flush(); - - bfw.Close(); - } - - private void OpenFiles() - { - itemData = new SequentialFileWriterStream(World.ItemDataPath); - itemIndex = new SequentialFileWriterStream(World.ItemIndexPath); - - mobileData = new SequentialFileWriterStream(World.MobileDataPath); - mobileIndex = new SequentialFileWriterStream(World.MobileIndexPath); - - guildData = new SequentialFileWriterStream(World.GuildDataPath); - guildIndex = new SequentialFileWriterStream(World.GuildIndexPath); - - WriteCount(itemIndex, World.Items.Count); - WriteCount(mobileIndex, World.Mobiles.Count); - WriteCount(guildIndex, BaseGuild.List.Count); - } - - private void WriteCount(SequentialFileWriterStream indexFile, int count) - { - var buffer = new byte[4]; - - buffer[0] = (byte)count; - buffer[1] = (byte)(count >> 8); - buffer[2] = (byte)(count >> 16); - buffer[3] = (byte)(count >> 24); - - indexFile.Write(buffer, 0, buffer.Length); - } - - private void CloseFiles() - { - itemData.Close(); - itemIndex.Close(); - - mobileData.Close(); - mobileIndex.Close(); - - guildData.Close(); - guildIndex.Close(); - - World.NotifyDiskWriteComplete(); - } - - private void OnSerialized(ConsumableEntry entry) - { - var value = entry.value; - var writer = entry.writer; - - if (value is Item item) - Save(item, writer); - else if (value is Mobile mob) - Save(mob, writer); - else if (value is BaseGuild guild) - Save(guild, writer); - } - - private void Save(Item item, BinaryMemoryWriter writer) - { - writer.CommitTo(itemData, itemIndex, item.TypeRef, item.Serial); - - if (item.Decays && item.Parent == null && item.Map != Map.Internal && - DateTime.UtcNow > item.LastMoved + item.DecayTime) _decayQueue.Enqueue(item); - } - - private void Save(Mobile mob, BinaryMemoryWriter writer) - { - writer.CommitTo(mobileData, mobileIndex, mob.TypeRef, mob.Serial); - } - - private void Save(BaseGuild guild, BinaryMemoryWriter writer) - { - writer.CommitTo(guildData, guildIndex, 0, guild.Serial); - } - - private bool Enqueue(ISerializable value) - { - for (var i = 0; i < consumers.Length; ++i) - { - var consumer = consumers[cycle++ % consumers.Length]; - - if (consumer.tail - consumer.head < consumer.buffer.Length) - { - consumer.buffer[consumer.tail % consumer.buffer.Length].value = value; - consumer.tail++; - - return true; - } - } - - return false; - } - - private bool Commit() - { - var committed = false; - - for (var i = 0; i < consumers.Length; ++i) - { - var consumer = consumers[i]; - - while (consumer.head < consumer.done) - { - OnSerialized(consumer.buffer[consumer.head % consumer.buffer.Length]); - consumer.head++; - - committed = true; - } - } - - return committed; - } - - private sealed class Producer : IEnumerable - { - private readonly IEnumerable guilds; - private readonly IEnumerable items; - private readonly IEnumerable mobiles; - - public Producer() - { - items = World.Items.Values; - mobiles = World.Mobiles.Values; - guilds = BaseGuild.List.Values; - } - - public IEnumerator GetEnumerator() - { - foreach (var item in items) yield return item; - - foreach (var mob in mobiles) yield return mob; - - foreach (var guild in guilds) yield return guild; - } - - IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); - } - - private struct ConsumableEntry - { - public ISerializable value; - public BinaryMemoryWriter writer; - } - - private sealed class Consumer - { - public readonly ConsumableEntry[] buffer; - - public readonly ManualResetEvent completionEvent; - public int head, done, tail; - private readonly ParallelSaveStrategy owner; - - private readonly Thread thread; - - public Consumer(ParallelSaveStrategy owner, int bufferSize) - { - this.owner = owner; - - buffer = new ConsumableEntry[bufferSize]; - - for (var i = 0; i < buffer.Length; ++i) buffer[i].writer = new BinaryMemoryWriter(); - - completionEvent = new ManualResetEvent(false); - - thread = new Thread(Processor); - - thread.Name = "Parallel Serialization Thread"; - - thread.Start(); - } - - private void Processor() - { - try - { - while (!owner.finished) - { - Process(); - Thread.Sleep(0); - } - - Process(); - - completionEvent.Set(); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - - private void Process() - { - ConsumableEntry entry; - - while (done < tail) - { - entry = buffer[done % buffer.Length]; - - entry.value.Serialize(entry.writer); - - ++done; - } - } - } - - private bool disposedValue = false; // To detect redundant calls - - public void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~ParallelSaveStrategy() - // { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - } -} +/*************************************************************************** + * ParallelSaveStrategy.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using Server.Guilds; + +namespace Server +{ + public sealed class ParallelSaveStrategy : SaveStrategy, IDisposable + { + private readonly Queue _decayQueue; + + private readonly int processorCount; + + private Consumer[] consumers; + private int cycle; + + private bool disposedValue; // To detect redundant calls + + private bool finished; + private SequentialFileWriterStream guildData, guildIndex; + + private SequentialFileWriterStream itemData, itemIndex; + + private SequentialFileWriterStream mobileData, mobileIndex; + + public ParallelSaveStrategy(int processorCount) + { + this.processorCount = processorCount; + + _decayQueue = new Queue(); + } + + public override string Name => "Parallel"; + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~ParallelSaveStrategy() + // { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + + private int GetThreadCount() => processorCount - 1; + + public override void Save(bool permitBackgroundWrite) + { + OpenFiles(); + + consumers = new Consumer[GetThreadCount()]; + + for (var i = 0; i < consumers.Length; ++i) consumers[i] = new Consumer(this, 256); + + IEnumerable collection = new Producer(); + + foreach (var value in collection) + while (!Enqueue(value)) + if (!Commit()) + Thread.Sleep(0); + + finished = true; + + SaveTypeDatabases(); + + WaitHandle.WaitAll( + Array.ConvertAll( + consumers, + input => input.completionEvent + ) + ); + + Commit(); + + CloseFiles(); + } + + public override void ProcessDecay() + { + while (_decayQueue.Count > 0) + { + var item = _decayQueue.Dequeue(); + + if (item.OnDecay()) item.Delete(); + } + } + + private void SaveTypeDatabases() + { + SaveTypeDatabase(World.ItemTypesPath, World.m_ItemTypes); + SaveTypeDatabase(World.MobileTypesPath, World.m_MobileTypes); + } + + private void SaveTypeDatabase(string path, List types) + { + var bfw = new BinaryFileWriter(path, false); + + bfw.Write(types.Count); + + foreach (var type in types) bfw.Write(type.FullName); + + bfw.Flush(); + + bfw.Close(); + } + + private void OpenFiles() + { + itemData = new SequentialFileWriterStream(World.ItemDataPath); + itemIndex = new SequentialFileWriterStream(World.ItemIndexPath); + + mobileData = new SequentialFileWriterStream(World.MobileDataPath); + mobileIndex = new SequentialFileWriterStream(World.MobileIndexPath); + + guildData = new SequentialFileWriterStream(World.GuildDataPath); + guildIndex = new SequentialFileWriterStream(World.GuildIndexPath); + + WriteCount(itemIndex, World.Items.Count); + WriteCount(mobileIndex, World.Mobiles.Count); + WriteCount(guildIndex, BaseGuild.List.Count); + } + + private void WriteCount(SequentialFileWriterStream indexFile, int count) + { + var buffer = new byte[4]; + + buffer[0] = (byte)count; + buffer[1] = (byte)(count >> 8); + buffer[2] = (byte)(count >> 16); + buffer[3] = (byte)(count >> 24); + + indexFile.Write(buffer, 0, buffer.Length); + } + + private void CloseFiles() + { + itemData.Close(); + itemIndex.Close(); + + mobileData.Close(); + mobileIndex.Close(); + + guildData.Close(); + guildIndex.Close(); + + World.NotifyDiskWriteComplete(); + } + + private void OnSerialized(ConsumableEntry entry) + { + var value = entry.value; + var writer = entry.writer; + + if (value is Item item) + Save(item, writer); + else if (value is Mobile mob) + Save(mob, writer); + else if (value is BaseGuild guild) + Save(guild, writer); + } + + private void Save(Item item, BinaryMemoryWriter writer) + { + writer.CommitTo(itemData, itemIndex, item.TypeRef, item.Serial); + + if (item.Decays && item.Parent == null && item.Map != Map.Internal && + DateTime.UtcNow > item.LastMoved + item.DecayTime) _decayQueue.Enqueue(item); + } + + private void Save(Mobile mob, BinaryMemoryWriter writer) + { + writer.CommitTo(mobileData, mobileIndex, mob.TypeRef, mob.Serial); + } + + private void Save(BaseGuild guild, BinaryMemoryWriter writer) + { + writer.CommitTo(guildData, guildIndex, 0, guild.Serial); + } + + private bool Enqueue(ISerializable value) + { + for (var i = 0; i < consumers.Length; ++i) + { + var consumer = consumers[cycle++ % consumers.Length]; + + if (consumer.tail - consumer.head < consumer.buffer.Length) + { + consumer.buffer[consumer.tail % consumer.buffer.Length].value = value; + consumer.tail++; + + return true; + } + } + + return false; + } + + private bool Commit() + { + var committed = false; + + for (var i = 0; i < consumers.Length; ++i) + { + var consumer = consumers[i]; + + while (consumer.head < consumer.done) + { + OnSerialized(consumer.buffer[consumer.head % consumer.buffer.Length]); + consumer.head++; + + committed = true; + } + } + + return committed; + } + + public void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + private sealed class Producer : IEnumerable + { + private readonly IEnumerable guilds; + private readonly IEnumerable items; + private readonly IEnumerable mobiles; + + public Producer() + { + items = World.Items.Values; + mobiles = World.Mobiles.Values; + guilds = BaseGuild.List.Values; + } + + public IEnumerator GetEnumerator() + { + foreach (var item in items) yield return item; + + foreach (var mob in mobiles) yield return mob; + + foreach (var guild in guilds) yield return guild; + } + + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); + } + + private struct ConsumableEntry + { + public ISerializable value; + public BinaryMemoryWriter writer; + } + + private sealed class Consumer + { + public readonly ConsumableEntry[] buffer; + + public readonly ManualResetEvent completionEvent; + private readonly ParallelSaveStrategy owner; + + private readonly Thread thread; + public int head, done, tail; + + public Consumer(ParallelSaveStrategy owner, int bufferSize) + { + this.owner = owner; + + buffer = new ConsumableEntry[bufferSize]; + + for (var i = 0; i < buffer.Length; ++i) buffer[i].writer = new BinaryMemoryWriter(); + + completionEvent = new ManualResetEvent(false); + + thread = new Thread(Processor); + + thread.Name = "Parallel Serialization Thread"; + + thread.Start(); + } + + private void Processor() + { + try + { + while (!owner.finished) + { + Process(); + Thread.Sleep(0); + } + + Process(); + + completionEvent.Set(); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + private void Process() + { + ConsumableEntry entry; + + while (done < tail) + { + entry = buffer[done % buffer.Length]; + + entry.value.Serialize(entry.writer); + + ++done; + } + } + } + } +} diff --git a/Projects/Server/Persistence/Persistence.cs b/Projects/Server/Persistence/Persistence.cs index d2ff00672..3d307b50f 100644 --- a/Projects/Server/Persistence/Persistence.cs +++ b/Projects/Server/Persistence/Persistence.cs @@ -1,99 +1,99 @@ -using System; -using System.IO; - -namespace Server -{ - public static class Persistence - { - public static void Serialize(string path, Action serializer) - { - Serialize(new FileInfo(path), serializer); - } - - public static void Serialize(FileInfo file, Action serializer) - { - file.Refresh(); - - if (file.Directory?.Exists == false) - file.Directory.Create(); - - if (!file.Exists) file.Create().Close(); - - file.Refresh(); - - using var fs = file.OpenWrite(); - var writer = new BinaryFileWriter(fs, true); - - try - { - serializer(writer); - } - finally - { - writer.Flush(); - writer.Close(); - } - } - - public static void Deserialize(string path, Action deserializer) - { - Deserialize(path, deserializer, true); - } - - public static void Deserialize(FileInfo file, Action deserializer) - { - Deserialize(file, deserializer, true); - } - - public static void Deserialize(string path, Action deserializer, bool ensure) - { - Deserialize(new FileInfo(path), deserializer, ensure); - } - - public static void Deserialize(FileInfo file, Action deserializer, bool ensure) - { - file.Refresh(); - - if (file.Directory?.Exists == false) - { - if (!ensure) - throw new DirectoryNotFoundException(); - - file.Directory.Create(); - } - - if (!file.Exists) - { - if (!ensure) - throw new FileNotFoundException - { - Source = file.FullName - }; - - file.Create().Close(); - } - - file.Refresh(); - - using var fs = file.OpenRead(); - var reader = new BinaryFileReader(new BinaryReader(fs)); - - try - { - deserializer(reader); - } - catch (EndOfStreamException eos) - { - if (file.Length > 0) Console.WriteLine("[Persistence]: {0}", eos); - } - catch (Exception e) - { - Console.WriteLine("[Persistence]: {0}", e); - } - finally - { - reader.Close(); - } - } - } -} +using System; +using System.IO; + +namespace Server +{ + public static class Persistence + { + public static void Serialize(string path, Action serializer) + { + Serialize(new FileInfo(path), serializer); + } + + public static void Serialize(FileInfo file, Action serializer) + { + file.Refresh(); + + if (file.Directory?.Exists == false) + file.Directory.Create(); + + if (!file.Exists) file.Create().Close(); + + file.Refresh(); + + using var fs = file.OpenWrite(); + var writer = new BinaryFileWriter(fs, true); + + try + { + serializer(writer); + } + finally + { + writer.Flush(); + writer.Close(); + } + } + + public static void Deserialize(string path, Action deserializer) + { + Deserialize(path, deserializer, true); + } + + public static void Deserialize(FileInfo file, Action deserializer) + { + Deserialize(file, deserializer, true); + } + + public static void Deserialize(string path, Action deserializer, bool ensure) + { + Deserialize(new FileInfo(path), deserializer, ensure); + } + + public static void Deserialize(FileInfo file, Action deserializer, bool ensure) + { + file.Refresh(); + + if (file.Directory?.Exists == false) + { + if (!ensure) + throw new DirectoryNotFoundException(); + + file.Directory.Create(); + } + + if (!file.Exists) + { + if (!ensure) + throw new FileNotFoundException + { + Source = file.FullName + }; + + file.Create().Close(); + } + + file.Refresh(); + + using var fs = file.OpenRead(); + var reader = new BinaryFileReader(new BinaryReader(fs)); + + try + { + deserializer(reader); + } + catch (EndOfStreamException eos) + { + if (file.Length > 0) Console.WriteLine("[Persistence]: {0}", eos); + } + catch (Exception e) + { + Console.WriteLine("[Persistence]: {0}", e); + } + finally + { + reader.Close(); + } + } + } +} diff --git a/Projects/Server/Persistence/QueuedMemoryWriter.cs b/Projects/Server/Persistence/QueuedMemoryWriter.cs index b86891a4b..4e5fc35e4 100644 --- a/Projects/Server/Persistence/QueuedMemoryWriter.cs +++ b/Projects/Server/Persistence/QueuedMemoryWriter.cs @@ -1,114 +1,114 @@ -/*************************************************************************** - * QueuedMemoryWriter.cs - * ------------------- - * begin : December 16, 2010 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.Collections.Generic; -using System.IO; - -namespace Server -{ - public sealed class QueuedMemoryWriter : BinaryFileWriter - { - private readonly MemoryStream _memStream; - private readonly List _orderedIndexInfo = new List(); - - public QueuedMemoryWriter() - : base(new MemoryStream(1024 * 1024), true) => - _memStream = UnderlyingStream as MemoryStream; - - protected override int BufferSize => 512; - - public void QueueForIndex(ISerializable serializable, int size) - { - IndexInfo info; - - info.size = size; - - info.typeCode = serializable.TypeRef; // For guilds, this will automagically be zero. - info.serial = serializable.Serial; - - _orderedIndexInfo.Add(info); - } - - public void CommitTo(SequentialFileWriterStream dataFile, SequentialFileWriterStream indexFile) - { - Flush(); - - var memLength = (int)_memStream.Position; - - if (memLength > 0) - { - var memBuffer = _memStream.GetBuffer(); - - var actualPosition = dataFile.Position; - - dataFile.Write(memBuffer, 0, memLength); // The buffer contains the data from many items. - - // Console.WriteLine("Writing {0} bytes starting at {1}, with {2} things", memLength, actualPosition, _orderedIndexInfo.Count); - - var indexBuffer = new byte[20]; - - // int indexWritten = _orderedIndexInfo.Count * indexBuffer.Length; - // int totalWritten = memLength + indexWritten - - for (var i = 0; i < _orderedIndexInfo.Count; i++) - { - var info = _orderedIndexInfo[i]; - - indexBuffer[0] = (byte)info.typeCode; - indexBuffer[1] = (byte)(info.typeCode >> 8); - indexBuffer[2] = (byte)(info.typeCode >> 16); - indexBuffer[3] = (byte)(info.typeCode >> 24); - - indexBuffer[4] = (byte)info.serial; - indexBuffer[5] = (byte)(info.serial >> 8); - indexBuffer[6] = (byte)(info.serial >> 16); - indexBuffer[7] = (byte)(info.serial >> 24); - - indexBuffer[8] = (byte)actualPosition; - indexBuffer[9] = (byte)(actualPosition >> 8); - indexBuffer[10] = (byte)(actualPosition >> 16); - indexBuffer[11] = (byte)(actualPosition >> 24); - indexBuffer[12] = (byte)(actualPosition >> 32); - indexBuffer[13] = (byte)(actualPosition >> 40); - indexBuffer[14] = (byte)(actualPosition >> 48); - indexBuffer[15] = (byte)(actualPosition >> 56); - - indexBuffer[16] = (byte)info.size; - indexBuffer[17] = (byte)(info.size >> 8); - indexBuffer[18] = (byte)(info.size >> 16); - indexBuffer[19] = (byte)(info.size >> 24); - - indexFile.Write(indexBuffer, 0, indexBuffer.Length); - - actualPosition += info.size; - } - } - - Close(); // We're done with this writer. - } - - private struct IndexInfo - { - public int size; - public int typeCode; - public uint serial; - } - } -} +/*************************************************************************** + * QueuedMemoryWriter.cs + * ------------------- + * begin : December 16, 2010 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.Collections.Generic; +using System.IO; + +namespace Server +{ + public sealed class QueuedMemoryWriter : BinaryFileWriter + { + private readonly MemoryStream _memStream; + private readonly List _orderedIndexInfo = new List(); + + public QueuedMemoryWriter() + : base(new MemoryStream(1024 * 1024), true) => + _memStream = UnderlyingStream as MemoryStream; + + protected override int BufferSize => 512; + + public void QueueForIndex(ISerializable serializable, int size) + { + IndexInfo info; + + info.size = size; + + info.typeCode = serializable.TypeRef; // For guilds, this will automagically be zero. + info.serial = serializable.Serial; + + _orderedIndexInfo.Add(info); + } + + public void CommitTo(SequentialFileWriterStream dataFile, SequentialFileWriterStream indexFile) + { + Flush(); + + var memLength = (int)_memStream.Position; + + if (memLength > 0) + { + var memBuffer = _memStream.GetBuffer(); + + var actualPosition = dataFile.Position; + + dataFile.Write(memBuffer, 0, memLength); // The buffer contains the data from many items. + + // Console.WriteLine("Writing {0} bytes starting at {1}, with {2} things", memLength, actualPosition, _orderedIndexInfo.Count); + + var indexBuffer = new byte[20]; + + // int indexWritten = _orderedIndexInfo.Count * indexBuffer.Length; + // int totalWritten = memLength + indexWritten + + for (var i = 0; i < _orderedIndexInfo.Count; i++) + { + var info = _orderedIndexInfo[i]; + + indexBuffer[0] = (byte)info.typeCode; + indexBuffer[1] = (byte)(info.typeCode >> 8); + indexBuffer[2] = (byte)(info.typeCode >> 16); + indexBuffer[3] = (byte)(info.typeCode >> 24); + + indexBuffer[4] = (byte)info.serial; + indexBuffer[5] = (byte)(info.serial >> 8); + indexBuffer[6] = (byte)(info.serial >> 16); + indexBuffer[7] = (byte)(info.serial >> 24); + + indexBuffer[8] = (byte)actualPosition; + indexBuffer[9] = (byte)(actualPosition >> 8); + indexBuffer[10] = (byte)(actualPosition >> 16); + indexBuffer[11] = (byte)(actualPosition >> 24); + indexBuffer[12] = (byte)(actualPosition >> 32); + indexBuffer[13] = (byte)(actualPosition >> 40); + indexBuffer[14] = (byte)(actualPosition >> 48); + indexBuffer[15] = (byte)(actualPosition >> 56); + + indexBuffer[16] = (byte)info.size; + indexBuffer[17] = (byte)(info.size >> 8); + indexBuffer[18] = (byte)(info.size >> 16); + indexBuffer[19] = (byte)(info.size >> 24); + + indexFile.Write(indexBuffer, 0, indexBuffer.Length); + + actualPosition += info.size; + } + } + + Close(); // We're done with this writer. + } + + private struct IndexInfo + { + public int size; + public int typeCode; + public uint serial; + } + } +} diff --git a/Projects/Server/Persistence/SaveStrategy.cs b/Projects/Server/Persistence/SaveStrategy.cs index 97225f224..bd96a51af 100644 --- a/Projects/Server/Persistence/SaveStrategy.cs +++ b/Projects/Server/Persistence/SaveStrategy.cs @@ -1,47 +1,47 @@ -/*************************************************************************** - * SaveStrategy.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server -{ - public abstract class SaveStrategy - { - public abstract string Name { get; } - - public static SaveStrategy Acquire() - { - if (Core.MultiProcessor) - { - var processorCount = Core.ProcessorCount; - - if (processorCount > 2) - return - new DualSaveStrategy(); // return new DynamicSaveStrategy(); (4.0 or return new ParallelSaveStrategy(processorCount); (2.0) - - return new DualSaveStrategy(); - } - - return new StandardSaveStrategy(); - } - - public abstract void Save(bool permitBackgroundWrite); - - public abstract void ProcessDecay(); - } -} +/*************************************************************************** + * SaveStrategy.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server +{ + public abstract class SaveStrategy + { + public abstract string Name { get; } + + public static SaveStrategy Acquire() + { + if (Core.MultiProcessor) + { + var processorCount = Core.ProcessorCount; + + if (processorCount > 2) + return + new DualSaveStrategy(); // return new DynamicSaveStrategy(); (4.0 or return new ParallelSaveStrategy(processorCount); (2.0) + + return new DualSaveStrategy(); + } + + return new StandardSaveStrategy(); + } + + public abstract void Save(bool permitBackgroundWrite); + + public abstract void ProcessDecay(); + } +} diff --git a/Projects/Server/Persistence/SequentialFileWriterStream.cs b/Projects/Server/Persistence/SequentialFileWriterStream.cs index bb48d5ab3..bf847f887 100644 --- a/Projects/Server/Persistence/SequentialFileWriterStream.cs +++ b/Projects/Server/Persistence/SequentialFileWriterStream.cs @@ -1,119 +1,120 @@ -/*************************************************************************** - * SequentialFileWriter.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.IO; - -namespace Server -{ - public sealed class SequentialFileWriterStream : Stream - { - private FileQueue fileQueue; - private FileStream fileStream; - - private AsyncCallback writeCallback; - - public SequentialFileWriterStream(string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - - fileStream = FileOperations.OpenSequentialStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - - fileQueue = new FileQueue( - Math.Max(FileOperations.Concurrency, 1), - FileCallback); - } - - public override long Position - { - get => fileQueue.Position; - set => throw new InvalidOperationException(); - } - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => Position; - - private void FileCallback(FileQueue.Chunk chunk) - { - if (FileOperations.AreSynchronous) - { - fileStream.Write(chunk.Buffer, chunk.Offset, chunk.Size); - - chunk.Commit(); - } - else - { - writeCallback ??= OnWrite; - - fileStream.BeginWrite(chunk.Buffer, chunk.Offset, chunk.Size, writeCallback, chunk); - } - } - - private void OnWrite(IAsyncResult asyncResult) - { - var chunk = asyncResult.AsyncState as FileQueue.Chunk; - - fileStream.EndWrite(asyncResult); - - chunk?.Commit(); - } - - public override void Write(byte[] buffer, int offset, int size) - { - fileQueue.Enqueue(buffer, offset, size); - } - - public override void Flush() - { - fileQueue.Flush(); - fileStream.Flush(); - } - - protected override void Dispose(bool disposing) - { - if (fileStream != null) - { - Flush(); - - fileQueue.Dispose(); - fileQueue = null; - - fileStream.Close(); - fileStream = null; - } - - base.Dispose(disposing); - } - - public override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException(); - - public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException(); - - public override void SetLength(long value) - { - fileStream.SetLength(value); - } - } -} +/*************************************************************************** + * SequentialFileWriter.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.IO; + +namespace Server +{ + public sealed class SequentialFileWriterStream : Stream + { + private FileQueue fileQueue; + private FileStream fileStream; + + private AsyncCallback writeCallback; + + public SequentialFileWriterStream(string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + + fileStream = FileOperations.OpenSequentialStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + + fileQueue = new FileQueue( + Math.Max(FileOperations.Concurrency, 1), + FileCallback + ); + } + + public override long Position + { + get => fileQueue.Position; + set => throw new InvalidOperationException(); + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => Position; + + private void FileCallback(FileQueue.Chunk chunk) + { + if (FileOperations.AreSynchronous) + { + fileStream.Write(chunk.Buffer, chunk.Offset, chunk.Size); + + chunk.Commit(); + } + else + { + writeCallback ??= OnWrite; + + fileStream.BeginWrite(chunk.Buffer, chunk.Offset, chunk.Size, writeCallback, chunk); + } + } + + private void OnWrite(IAsyncResult asyncResult) + { + var chunk = asyncResult.AsyncState as FileQueue.Chunk; + + fileStream.EndWrite(asyncResult); + + chunk?.Commit(); + } + + public override void Write(byte[] buffer, int offset, int size) + { + fileQueue.Enqueue(buffer, offset, size); + } + + public override void Flush() + { + fileQueue.Flush(); + fileStream.Flush(); + } + + protected override void Dispose(bool disposing) + { + if (fileStream != null) + { + Flush(); + + fileQueue.Dispose(); + fileQueue = null; + + fileStream.Close(); + fileStream = null; + } + + base.Dispose(disposing); + } + + public override int Read(byte[] buffer, int offset, int count) => throw new InvalidOperationException(); + + public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException(); + + public override void SetLength(long value) + { + fileStream.SetLength(value); + } + } +} diff --git a/Projects/Server/Persistence/StandardSaveStrategy.cs b/Projects/Server/Persistence/StandardSaveStrategy.cs index a9eb8ed21..2cc070f9e 100644 --- a/Projects/Server/Persistence/StandardSaveStrategy.cs +++ b/Projects/Server/Persistence/StandardSaveStrategy.cs @@ -1,234 +1,250 @@ -/*************************************************************************** - * StandardSaveStrategy.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Server.Guilds; - -namespace Server -{ - public class StandardSaveStrategy : SaveStrategy - { - public enum SaveOption - { - Normal, - Threaded - } - - // TODO: Move to configuration - public static SaveOption SaveType => SaveOption.Normal; - - private readonly Queue _decayQueue; - - public StandardSaveStrategy() => _decayQueue = new Queue(); - - public override string Name => "Standard"; - - protected bool PermitBackgroundWrite { get; set; } - - protected bool UseSequentialWriters => SaveType == SaveOption.Normal || !PermitBackgroundWrite; - - public override void Save(bool permitBackgroundWrite) - { - PermitBackgroundWrite = permitBackgroundWrite; - -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.WaitAll(Task.Factory.StartNew(SaveMobiles), Task.Factory.StartNew(SaveItems), Task.Factory.StartNew(SaveGuilds)); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - - if (permitBackgroundWrite && UseSequentialWriters) // If we're permitted to write in the background, but we don't anyways, then notify. - World.NotifyDiskWriteComplete(); - } - - protected void SaveMobiles() - { - var mobiles = World.Mobiles; - - IGenericWriter idx; - IGenericWriter tdb; - IGenericWriter bin; - - if (UseSequentialWriters) - { - idx = new BinaryFileWriter(World.MobileIndexPath, false); - tdb = new BinaryFileWriter(World.MobileTypesPath, false); - bin = new BinaryFileWriter(World.MobileDataPath, true); - } - else - { - idx = new AsyncWriter(World.MobileIndexPath, false); - tdb = new AsyncWriter(World.MobileTypesPath, false); - bin = new AsyncWriter(World.MobileDataPath, true); - } -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew(() => - { - tdb.Write(World.m_MobileTypes.Count); - - for (var i = 0; i < World.m_MobileTypes.Count; ++i) - tdb.Write(World.m_MobileTypes[i].FullName); - - tdb.Close(); - }); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - - Parallel.ForEach(mobiles.Values, mobile => mobile.Serialize()); -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew(() => - { - idx.Write(mobiles.Count); - foreach (var m in mobiles.Values) - { - var start = bin.Position; - - idx.Write(m.TypeRef); - idx.Write(m.Serial); - idx.Write(start); - idx.Write((int)m.SaveBuffer.Position); - - m.SaveBuffer.WriteTo(bin); - m.FreeCache(); - } - - idx.Close(); - bin.Close(); - }); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - - protected void SaveItems() - { - var items = World.Items; - - IGenericWriter idx; - IGenericWriter tdb; - IGenericWriter bin; - - if (UseSequentialWriters) - { - idx = new BinaryFileWriter(World.ItemIndexPath, false); - tdb = new BinaryFileWriter(World.ItemTypesPath, false); - bin = new BinaryFileWriter(World.ItemDataPath, true); - } - else - { - idx = new AsyncWriter(World.ItemIndexPath, false); - tdb = new AsyncWriter(World.ItemTypesPath, false); - bin = new AsyncWriter(World.ItemDataPath, true); - } - -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew(() => - { - tdb.Write(World.m_ItemTypes.Count); - - for (var i = 0; i < World.m_ItemTypes.Count; ++i) - tdb.Write(World.m_ItemTypes[i].FullName); - - tdb.Close(); - }); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - Parallel.ForEach(items.Values, item => item.Serialize()); - - idx.Write(items.Count); - - var n = DateTime.UtcNow; - -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew(() => - { - foreach (var item in items.Values) - { - if (item.Decays && item.Parent == null && item.Map != Map.Internal && item.LastMoved + item.DecayTime <= n) - { - Console.WriteLine($"Decay Item {item.Name ?? item.DefaultName} ({item.GetType().FullName})"); - _decayQueue.Enqueue(item); - } - - var start = bin.Position; - - idx.Write(item.TypeRef); - idx.Write(item.Serial); - idx.Write(start); - idx.Write((int)item.SaveBuffer.Position); - - item.SaveBuffer.WriteTo(bin); - item.FreeCache(); - } - - idx.Close(); - bin.Close(); - }); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - - protected void SaveGuilds() - { - IGenericWriter idx; - IGenericWriter bin; - - if (UseSequentialWriters) - { - idx = new BinaryFileWriter(World.GuildIndexPath, false); - bin = new BinaryFileWriter(World.GuildDataPath, true); - } - else - { - idx = new AsyncWriter(World.GuildIndexPath, false); - bin = new AsyncWriter(World.GuildDataPath, true); - } - - Parallel.ForEach(BaseGuild.List.Values, guild => guild.Serialize()); - - idx.Write(BaseGuild.List.Count); -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * - Task.Factory.StartNew(() => - { - foreach (var guild in BaseGuild.List.Values) - { - var start = bin.Position; - - idx.Write(0); // guilds have no typeid - idx.Write(guild.Serial); - idx.Write(start); - idx.Write((int)guild.SaveBuffer.Position); - - guild.SaveBuffer.WriteTo(bin); - } - - idx.Close(); - bin.Close(); - }); -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * - } - - public override void ProcessDecay() - { - while (_decayQueue.Count > 0) - { - var item = _decayQueue.Dequeue(); - - if (item.OnDecay()) - item.Delete(); - } - } - } -} +/*************************************************************************** + * StandardSaveStrategy.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Server.Guilds; + +namespace Server +{ + public class StandardSaveStrategy : SaveStrategy + { + public enum SaveOption + { + Normal, + Threaded + } + + private readonly Queue _decayQueue; + + public StandardSaveStrategy() => _decayQueue = new Queue(); + + // TODO: Move to configuration + public static SaveOption SaveType => SaveOption.Normal; + + public override string Name => "Standard"; + + protected bool PermitBackgroundWrite { get; set; } + + protected bool UseSequentialWriters => SaveType == SaveOption.Normal || !PermitBackgroundWrite; + + public override void Save(bool permitBackgroundWrite) + { + PermitBackgroundWrite = permitBackgroundWrite; + +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * + Task.WaitAll( + Task.Factory.StartNew(SaveMobiles), + Task.Factory.StartNew(SaveItems), + Task.Factory.StartNew(SaveGuilds) + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + + if (permitBackgroundWrite && UseSequentialWriters + ) // If we're permitted to write in the background, but we don't anyways, then notify. + World.NotifyDiskWriteComplete(); + } + + protected void SaveMobiles() + { + var mobiles = World.Mobiles; + + IGenericWriter idx; + IGenericWriter tdb; + IGenericWriter bin; + + if (UseSequentialWriters) + { + idx = new BinaryFileWriter(World.MobileIndexPath, false); + tdb = new BinaryFileWriter(World.MobileTypesPath, false); + bin = new BinaryFileWriter(World.MobileDataPath, true); + } + else + { + idx = new AsyncWriter(World.MobileIndexPath, false); + tdb = new AsyncWriter(World.MobileTypesPath, false); + bin = new AsyncWriter(World.MobileDataPath, true); + } +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * + Task.Factory.StartNew( + () => + { + tdb.Write(World.m_MobileTypes.Count); + + for (var i = 0; i < World.m_MobileTypes.Count; ++i) + tdb.Write(World.m_MobileTypes[i].FullName); + + tdb.Close(); + } + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + + Parallel.ForEach(mobiles.Values, mobile => mobile.Serialize()); +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * + Task.Factory.StartNew( + () => + { + idx.Write(mobiles.Count); + foreach (var m in mobiles.Values) + { + var start = bin.Position; + + idx.Write(m.TypeRef); + idx.Write(m.Serial); + idx.Write(start); + idx.Write((int)m.SaveBuffer.Position); + + m.SaveBuffer.WriteTo(bin); + m.FreeCache(); + } + + idx.Close(); + bin.Close(); + } + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + } + + protected void SaveItems() + { + var items = World.Items; + + IGenericWriter idx; + IGenericWriter tdb; + IGenericWriter bin; + + if (UseSequentialWriters) + { + idx = new BinaryFileWriter(World.ItemIndexPath, false); + tdb = new BinaryFileWriter(World.ItemTypesPath, false); + bin = new BinaryFileWriter(World.ItemDataPath, true); + } + else + { + idx = new AsyncWriter(World.ItemIndexPath, false); + tdb = new AsyncWriter(World.ItemTypesPath, false); + bin = new AsyncWriter(World.ItemDataPath, true); + } + +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * + Task.Factory.StartNew( + () => + { + tdb.Write(World.m_ItemTypes.Count); + + for (var i = 0; i < World.m_ItemTypes.Count; ++i) + tdb.Write(World.m_ItemTypes[i].FullName); + + tdb.Close(); + } + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + Parallel.ForEach(items.Values, item => item.Serialize()); + + idx.Write(items.Count); + + var n = DateTime.UtcNow; + +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * + Task.Factory.StartNew( + () => + { + foreach (var item in items.Values) + { + if (item.Decays && item.Parent == null && item.Map != Map.Internal && + item.LastMoved + item.DecayTime <= n) + { + Console.WriteLine($"Decay Item {item.Name ?? item.DefaultName} ({item.GetType().FullName})"); + _decayQueue.Enqueue(item); + } + + var start = bin.Position; + + idx.Write(item.TypeRef); + idx.Write(item.Serial); + idx.Write(start); + idx.Write((int)item.SaveBuffer.Position); + + item.SaveBuffer.WriteTo(bin); + item.FreeCache(); + } + + idx.Close(); + bin.Close(); + } + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + } + + protected void SaveGuilds() + { + IGenericWriter idx; + IGenericWriter bin; + + if (UseSequentialWriters) + { + idx = new BinaryFileWriter(World.GuildIndexPath, false); + bin = new BinaryFileWriter(World.GuildDataPath, true); + } + else + { + idx = new AsyncWriter(World.GuildIndexPath, false); + bin = new AsyncWriter(World.GuildDataPath, true); + } + + Parallel.ForEach(BaseGuild.List.Values, guild => guild.Serialize()); + + idx.Write(BaseGuild.List.Count); +#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler * + Task.Factory.StartNew( + () => + { + foreach (var guild in BaseGuild.List.Values) + { + var start = bin.Position; + + idx.Write(0); // guilds have no typeid + idx.Write(guild.Serial); + idx.Write(start); + idx.Write((int)guild.SaveBuffer.Position); + + guild.SaveBuffer.WriteTo(bin); + } + + idx.Close(); + bin.Close(); + } + ); +#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler * + } + + public override void ProcessDecay() + { + while (_decayQueue.Count > 0) + { + var item = _decayQueue.Dequeue(); + + if (item.OnDecay()) + item.Delete(); + } + } + } +} diff --git a/Projects/Server/Pipelines/DuplexPipe.cs b/Projects/Server/Pipelines/DuplexPipe.cs index 089591cc9..f7d6142ed 100644 --- a/Projects/Server/Pipelines/DuplexPipe.cs +++ b/Projects/Server/Pipelines/DuplexPipe.cs @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace System.IO.Pipelines -{ - public class DuplexPipe : IDuplexPipe - { - public DuplexPipe(PipeReader reader, PipeWriter writer) - { - Input = reader; - Output = writer; - } - - public PipeReader Input { get; } - - public PipeWriter Output { get; } - - public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions) - { - var input = new Pipe(inputOptions); - var output = new Pipe(outputOptions); - - var transportToApplication = new DuplexPipe(output.Reader, input.Writer); - var applicationToTransport = new DuplexPipe(input.Reader, output.Writer); - - return new DuplexPipePair(applicationToTransport, transportToApplication); - } - - // This class exists to work around issues with value tuple on .NET Framework - public readonly struct DuplexPipePair - { - public IDuplexPipe Transport { get; } - public IDuplexPipe Application { get; } - - public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application) - { - Transport = transport; - Application = application; - } - } - } -} +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.IO.Pipelines +{ + public class DuplexPipe : IDuplexPipe + { + public DuplexPipe(PipeReader reader, PipeWriter writer) + { + Input = reader; + Output = writer; + } + + public PipeReader Input { get; } + + public PipeWriter Output { get; } + + public static DuplexPipePair CreateConnectionPair(PipeOptions inputOptions, PipeOptions outputOptions) + { + var input = new Pipe(inputOptions); + var output = new Pipe(outputOptions); + + var transportToApplication = new DuplexPipe(output.Reader, input.Writer); + var applicationToTransport = new DuplexPipe(input.Reader, output.Writer); + + return new DuplexPipePair(applicationToTransport, transportToApplication); + } + + // This class exists to work around issues with value tuple on .NET Framework + public readonly struct DuplexPipePair + { + public IDuplexPipe Transport { get; } + public IDuplexPipe Application { get; } + + public DuplexPipePair(IDuplexPipe transport, IDuplexPipe application) + { + Transport = transport; + Application = application; + } + } + } +} diff --git a/Projects/Server/Poison.cs b/Projects/Server/Poison.cs index 9812028bc..028742004 100644 --- a/Projects/Server/Poison.cs +++ b/Projects/Server/Poison.cs @@ -1,121 +1,121 @@ -/*************************************************************************** - * Poison.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server -{ - [Parsable] - public abstract class Poison - { - /*public abstract TimeSpan Interval{ get; } - public abstract TimeSpan Duration{ get; }*/ - public abstract string Name { get; } - public abstract int Level { get; } - - public static Poison Lesser => GetPoison("Lesser"); - public static Poison Regular => GetPoison("Regular"); - public static Poison Greater => GetPoison("Greater"); - public static Poison Deadly => GetPoison("Deadly"); - public static Poison Lethal => GetPoison("Lethal"); - - public static List Poisons { get; } = new List(); - - public abstract Timer ConstructTimer(Mobile m); - /*public abstract void OnDamage( Mobile m, ref object state );*/ - - public override string ToString() => Name; - - public static void Register(Poison reg) - { - var regName = reg.Name.ToLower(); - - for (var i = 0; i < Poisons.Count; i++) - { - if (reg.Level == Poisons[i].Level) - throw new Exception("A poison with that level already exists."); - if (regName == Poisons[i].Name.ToLower()) - throw new Exception("A poison with that name already exists."); - } - - Poisons.Add(reg); - } - - public static Poison Parse(string value) => - (int.TryParse(value, out var plevel) ? GetPoison(plevel) : null) ?? GetPoison(value); - - public static Poison GetPoison(int level) - { - for (var i = 0; i < Poisons.Count; ++i) - { - var p = Poisons[i]; - - if (p.Level == level) - return p; - } - - return null; - } - - public static Poison GetPoison(string name) - { - for (var i = 0; i < Poisons.Count; ++i) - { - var p = Poisons[i]; - - if (Utility.InsensitiveCompare(p.Name, name) == 0) - return p; - } - - return null; - } - - public static void Serialize(Poison p, IGenericWriter writer) - { - if (p == null) - { - writer.Write((byte)0); - } - else - { - writer.Write((byte)1); - writer.Write((byte)p.Level); - } - } - - public static Poison Deserialize(IGenericReader reader) - { - switch (reader.ReadByte()) - { - case 1: return GetPoison(reader.ReadByte()); - case 2: - // no longer used, safe to remove? - reader.ReadInt(); - reader.ReadDouble(); - reader.ReadInt(); - reader.ReadTimeSpan(); - break; - } - - return null; - } - } -} +/*************************************************************************** + * Poison.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server +{ + [Parsable] + public abstract class Poison + { + /*public abstract TimeSpan Interval{ get; } + public abstract TimeSpan Duration{ get; }*/ + public abstract string Name { get; } + public abstract int Level { get; } + + public static Poison Lesser => GetPoison("Lesser"); + public static Poison Regular => GetPoison("Regular"); + public static Poison Greater => GetPoison("Greater"); + public static Poison Deadly => GetPoison("Deadly"); + public static Poison Lethal => GetPoison("Lethal"); + + public static List Poisons { get; } = new List(); + + public abstract Timer ConstructTimer(Mobile m); + /*public abstract void OnDamage( Mobile m, ref object state );*/ + + public override string ToString() => Name; + + public static void Register(Poison reg) + { + var regName = reg.Name.ToLower(); + + for (var i = 0; i < Poisons.Count; i++) + { + if (reg.Level == Poisons[i].Level) + throw new Exception("A poison with that level already exists."); + if (regName == Poisons[i].Name.ToLower()) + throw new Exception("A poison with that name already exists."); + } + + Poisons.Add(reg); + } + + public static Poison Parse(string value) => + (int.TryParse(value, out var plevel) ? GetPoison(plevel) : null) ?? GetPoison(value); + + public static Poison GetPoison(int level) + { + for (var i = 0; i < Poisons.Count; ++i) + { + var p = Poisons[i]; + + if (p.Level == level) + return p; + } + + return null; + } + + public static Poison GetPoison(string name) + { + for (var i = 0; i < Poisons.Count; ++i) + { + var p = Poisons[i]; + + if (Utility.InsensitiveCompare(p.Name, name) == 0) + return p; + } + + return null; + } + + public static void Serialize(Poison p, IGenericWriter writer) + { + if (p == null) + { + writer.Write((byte)0); + } + else + { + writer.Write((byte)1); + writer.Write((byte)p.Level); + } + } + + public static Poison Deserialize(IGenericReader reader) + { + switch (reader.ReadByte()) + { + case 1: return GetPoison(reader.ReadByte()); + case 2: + // no longer used, safe to remove? + reader.ReadInt(); + reader.ReadDouble(); + reader.ReadInt(); + reader.ReadTimeSpan(); + break; + } + + return null; + } + } +} diff --git a/Projects/Server/Prompt.cs b/Projects/Server/Prompt.cs index 64745e998..5c26ca299 100644 --- a/Projects/Server/Prompt.cs +++ b/Projects/Server/Prompt.cs @@ -1,45 +1,45 @@ -/*************************************************************************** - * Prompt.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Prompts -{ - public abstract class Prompt - { - private static int m_Serials; - - protected Prompt() - { - do - { - Serial = ++m_Serials; - } while (Serial == 0); - } - - public int Serial { get; } - - public virtual void OnCancel(Mobile from) - { - } - - public virtual void OnResponse(Mobile from, string text) - { - } - } -} +/*************************************************************************** + * Prompt.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Prompts +{ + public abstract class Prompt + { + private static int m_Serials; + + protected Prompt() + { + do + { + Serial = ++m_Serials; + } while (Serial == 0); + } + + public int Serial { get; } + + public virtual void OnCancel(Mobile from) + { + } + + public virtual void OnResponse(Mobile from, string text) + { + } + } +} diff --git a/Projects/Server/QuestArrow.cs b/Projects/Server/QuestArrow.cs index dc1ab624c..35967ca33 100644 --- a/Projects/Server/QuestArrow.cs +++ b/Projects/Server/QuestArrow.cs @@ -1,100 +1,100 @@ -/*************************************************************************** - * QuestArrow.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server -{ - public class QuestArrow - { - public QuestArrow(Mobile m, Mobile t) - { - Running = true; - Mobile = m; - Target = t; - } - - public QuestArrow(Mobile m, Mobile t, int x, int y) : this(m, t) - { - Update(x, y); - } - - public Mobile Mobile { get; } - - public Mobile Target { get; } - - public bool Running { get; private set; } - - public void Update() - { - Update(Target.X, Target.Y); - } - - public void Update(int x, int y) - { - if (!Running) - return; - - var ns = Mobile.NetState; - - if (ns == null) - return; - - if (ns.HighSeas) - ns.Send(new SetArrowHS(x, y, Target.Serial)); - else - ns.Send(new SetArrow(x, y)); - } - - public void Stop() - { - Stop(Target.X, Target.Y); - } - - public void Stop(int x, int y) - { - if (!Running) - return; - - Mobile.ClearQuestArrow(); - - var ns = Mobile.NetState; - - if (ns != null) - { - if (ns.HighSeas) - ns.Send(new CancelArrowHS(x, y, Target.Serial)); - else - ns.Send(new CancelArrow()); - } - - Running = false; - OnStop(); - } - - public virtual void OnStop() - { - } - - public virtual void OnClick(bool rightClick) - { - } - } -} +/*************************************************************************** + * QuestArrow.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server +{ + public class QuestArrow + { + public QuestArrow(Mobile m, Mobile t) + { + Running = true; + Mobile = m; + Target = t; + } + + public QuestArrow(Mobile m, Mobile t, int x, int y) : this(m, t) + { + Update(x, y); + } + + public Mobile Mobile { get; } + + public Mobile Target { get; } + + public bool Running { get; private set; } + + public void Update() + { + Update(Target.X, Target.Y); + } + + public void Update(int x, int y) + { + if (!Running) + return; + + var ns = Mobile.NetState; + + if (ns == null) + return; + + if (ns.HighSeas) + ns.Send(new SetArrowHS(x, y, Target.Serial)); + else + ns.Send(new SetArrow(x, y)); + } + + public void Stop() + { + Stop(Target.X, Target.Y); + } + + public void Stop(int x, int y) + { + if (!Running) + return; + + Mobile.ClearQuestArrow(); + + var ns = Mobile.NetState; + + if (ns != null) + { + if (ns.HighSeas) + ns.Send(new CancelArrowHS(x, y, Target.Serial)); + else + ns.Send(new CancelArrow()); + } + + Running = false; + OnStop(); + } + + public virtual void OnStop() + { + } + + public virtual void OnClick(bool rightClick) + { + } + } +} diff --git a/Projects/Server/Race.cs b/Projects/Server/Race.cs index 26674ad31..9cd6c0015 100644 --- a/Projects/Server/Race.cs +++ b/Projects/Server/Race.cs @@ -1,155 +1,157 @@ -/*************************************************************************** - * Race.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server -{ - [Parsable] - public abstract class Race - { - private static string[] m_RaceNames; - private static Race[] m_RaceValues; - - protected Race(int raceID, int raceIndex, string name, string pluralName, int maleBody, int femaleBody, - int maleGhostBody, int femaleGhostBody, Expansion requiredExpansion) - { - RaceID = raceID; - RaceIndex = raceIndex; - - Name = name; - - MaleBody = maleBody; - FemaleBody = femaleBody; - MaleGhostBody = maleGhostBody; - FemaleGhostBody = femaleGhostBody; - - RequiredExpansion = requiredExpansion; - PluralName = pluralName; - } - - public static Race DefaultRace => Races[0]; - - public static Race[] Races { get; } = new Race[0x100]; - - public static Race Human => Races[0]; - public static Race Elf => Races[1]; - public static Race Gargoyle => Races[2]; - - public static List AllRaces { get; } = new List(); - - public Expansion RequiredExpansion { get; } - - public int MaleBody { get; } - - public int MaleGhostBody { get; } - - public int FemaleBody { get; } - - public int FemaleGhostBody { get; } - - public int RaceID { get; } - - public int RaceIndex { get; } - - public string Name { get; set; } - - public string PluralName { get; set; } - - public static string[] GetRaceNames() - { - CheckNamesAndValues(); - return m_RaceNames; - } - - public static Race[] GetRaceValues() - { - CheckNamesAndValues(); - return m_RaceValues; - } - - public static Race Parse(string value) - { - CheckNamesAndValues(); - - for (var i = 0; i < m_RaceNames.Length; ++i) - if (Insensitive.Equals(m_RaceNames[i], value)) - return m_RaceValues[i]; - - if (int.TryParse(value, out var index) && index >= 0 && index < Races.Length && - Races[index] != null) - return Races[index]; - - throw new ArgumentException("Invalid race name"); - } - - private static void CheckNamesAndValues() - { - if (m_RaceNames?.Length == AllRaces.Count) - return; - - m_RaceNames = new string[AllRaces.Count]; - m_RaceValues = new Race[AllRaces.Count]; - - for (var i = 0; i < AllRaces.Count; ++i) - { - var race = AllRaces[i]; - - m_RaceNames[i] = race.Name; - m_RaceValues[i] = race; - } - } - - public override string ToString() => Name; - - public virtual bool ValidateHair(Mobile m, int itemID) => ValidateHair(m.Female, itemID); - - public abstract bool ValidateHair(bool female, int itemID); - - public virtual int RandomHair(Mobile m) => RandomHair(m.Female); - - public abstract int RandomHair(bool female); - - public virtual bool ValidateFacialHair(Mobile m, int itemID) => ValidateFacialHair(m.Female, itemID); - - public abstract bool ValidateFacialHair(bool female, int itemID); - - public virtual int RandomFacialHair(Mobile m) => RandomFacialHair(m.Female); - - public abstract int RandomFacialHair(bool female); // For the *ahem* bearded ladies - - public abstract int ClipSkinHue(int hue); - public abstract int RandomSkinHue(); - - public abstract int ClipHairHue(int hue); - public abstract int RandomHairHue(); - - public virtual int Body(Mobile m) => m.Alive ? AliveBody(m.Female) : GhostBody(m.Female); - - public virtual int AliveBody(Mobile m) => AliveBody(m.Female); - - public virtual int AliveBody(bool female) => female ? FemaleBody : MaleBody; - - public virtual int GhostBody(Mobile m) => GhostBody(m.Female); - - public virtual int GhostBody(bool female) => female ? FemaleGhostBody : MaleGhostBody; - } -} +/*************************************************************************** + * Race.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server +{ + [Parsable] + public abstract class Race + { + private static string[] m_RaceNames; + private static Race[] m_RaceValues; + + protected Race( + int raceID, int raceIndex, string name, string pluralName, int maleBody, int femaleBody, + int maleGhostBody, int femaleGhostBody, Expansion requiredExpansion + ) + { + RaceID = raceID; + RaceIndex = raceIndex; + + Name = name; + + MaleBody = maleBody; + FemaleBody = femaleBody; + MaleGhostBody = maleGhostBody; + FemaleGhostBody = femaleGhostBody; + + RequiredExpansion = requiredExpansion; + PluralName = pluralName; + } + + public static Race DefaultRace => Races[0]; + + public static Race[] Races { get; } = new Race[0x100]; + + public static Race Human => Races[0]; + public static Race Elf => Races[1]; + public static Race Gargoyle => Races[2]; + + public static List AllRaces { get; } = new List(); + + public Expansion RequiredExpansion { get; } + + public int MaleBody { get; } + + public int MaleGhostBody { get; } + + public int FemaleBody { get; } + + public int FemaleGhostBody { get; } + + public int RaceID { get; } + + public int RaceIndex { get; } + + public string Name { get; set; } + + public string PluralName { get; set; } + + public static string[] GetRaceNames() + { + CheckNamesAndValues(); + return m_RaceNames; + } + + public static Race[] GetRaceValues() + { + CheckNamesAndValues(); + return m_RaceValues; + } + + public static Race Parse(string value) + { + CheckNamesAndValues(); + + for (var i = 0; i < m_RaceNames.Length; ++i) + if (Insensitive.Equals(m_RaceNames[i], value)) + return m_RaceValues[i]; + + if (int.TryParse(value, out var index) && index >= 0 && index < Races.Length && + Races[index] != null) + return Races[index]; + + throw new ArgumentException("Invalid race name"); + } + + private static void CheckNamesAndValues() + { + if (m_RaceNames?.Length == AllRaces.Count) + return; + + m_RaceNames = new string[AllRaces.Count]; + m_RaceValues = new Race[AllRaces.Count]; + + for (var i = 0; i < AllRaces.Count; ++i) + { + var race = AllRaces[i]; + + m_RaceNames[i] = race.Name; + m_RaceValues[i] = race; + } + } + + public override string ToString() => Name; + + public virtual bool ValidateHair(Mobile m, int itemID) => ValidateHair(m.Female, itemID); + + public abstract bool ValidateHair(bool female, int itemID); + + public virtual int RandomHair(Mobile m) => RandomHair(m.Female); + + public abstract int RandomHair(bool female); + + public virtual bool ValidateFacialHair(Mobile m, int itemID) => ValidateFacialHair(m.Female, itemID); + + public abstract bool ValidateFacialHair(bool female, int itemID); + + public virtual int RandomFacialHair(Mobile m) => RandomFacialHair(m.Female); + + public abstract int RandomFacialHair(bool female); // For the *ahem* bearded ladies + + public abstract int ClipSkinHue(int hue); + public abstract int RandomSkinHue(); + + public abstract int ClipHairHue(int hue); + public abstract int RandomHairHue(); + + public virtual int Body(Mobile m) => m.Alive ? AliveBody(m.Female) : GhostBody(m.Female); + + public virtual int AliveBody(Mobile m) => AliveBody(m.Female); + + public virtual int AliveBody(bool female) => female ? FemaleBody : MaleBody; + + public virtual int GhostBody(Mobile m) => GhostBody(m.Female); + + public virtual int GhostBody(bool female) => female ? FemaleGhostBody : MaleGhostBody; + } +} diff --git a/Projects/Server/Random/BaseRandomSource.cs b/Projects/Server/Random/BaseRandomSource.cs index 2823fc4f1..1410d51d8 100644 --- a/Projects/Server/Random/BaseRandomSource.cs +++ b/Projects/Server/Random/BaseRandomSource.cs @@ -1,173 +1,181 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: BaseRandomSource.cs - Created: 2020/07/25 - Updated: 2020/07/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace Server.Random -{ - public abstract class BaseRandomSource : IRandomSource - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Log2(uint v) - { - int exp = BitOperations.Log2(v); - return v == 1 << exp ? exp : exp + 1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Log2(ulong v) - { - int exp = BitOperations.Log2(v); - return v == 1UL << exp ? exp : exp + 1; - } - - const double INCR_DOUBLE = 1.0 / (1UL << 53); - const float INCR_FLOAT = 1f / (1U << 24); - - public abstract ulong NextULong(); - public abstract void NextBytes(Span buffer); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Next() - { - ulong rtn; - do rtn = NextULong() >> 33; - while(rtn == 0x7fff_ffffUL); - - return (int)rtn; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Next(int count) - { - if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); - if (count == 1 || count == -1) return 0; - - bool negative = count < 0; - - int max = negative ? -count : count; - - int bits = Log2((uint)max); - - int x; - do x = (int)(NextULong() >> (64 - bits)); - while (x >= max); - - return negative ? -x : x; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Next(int minValue, int count) => minValue + Next(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Next(uint count) - { - if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); - if (count == 1) return 0; - - int bits = Log2(count); - - uint x; - do x = (uint)(NextULong() >> (64 - bits)); - while (x >= count); - - return x; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint Next(uint minValue, uint count) => minValue + Next(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long Next(long count) - { - if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); - if (count == 1 || count == -1) return 0; - - bool negative = count < 0; - - long max = negative ? -count : count; - - int bits = Log2((ulong)max); - - long x; - do x = (long)(NextULong() >> (64 - bits)); - while (x >= max); - - return negative ? -x : x; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long Next(long minValue, long count) => minValue + Next(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double NextDouble() => (NextULong() >> 11) * INCR_DOUBLE; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int NextInt() => (int)(NextULong() >> 33); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint NextUInt() => (uint)NextULong(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool NextBool() => (NextULong() & 0x8000000000000000) != 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte NextByte() => (byte)(NextULong() >> 56); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float NextFloat() => (NextULong() >> 40) * INCR_FLOAT; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float NextFloatNonZero() => NextFloat() + INCR_FLOAT; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double NextDoubleNonZero() => NextDouble() + INCR_DOUBLE; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double NextDoubleHighRes() - { - int exponent = -64; - ulong significand; - int shift; - - while ((significand = NextULong()) == 0) - { - exponent -= 64; - - if (exponent < -1074) - return 0; - } - - shift = BitOperations.LeadingZeroCount(significand); - if (shift != 0) - { - exponent -= shift; - significand <<= shift; - significand |= NextULong() >> (64 - shift); - } - - significand |= 1; - - return significand * Math.Pow(2, exponent); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: BaseRandomSource.cs - Created: 2020/07/25 - Updated: 2020/07/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Server.Random +{ + public abstract class BaseRandomSource : IRandomSource + { + private const double INCR_DOUBLE = 1.0 / (1UL << 53); + private const float INCR_FLOAT = 1f / (1U << 24); + + public abstract ulong NextULong(); + public abstract void NextBytes(Span buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next() + { + ulong rtn; + do + { + rtn = NextULong() >> 33; + } while (rtn == 0x7fff_ffffUL); + + return (int)rtn; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next(int count) + { + if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); + if (count == 1 || count == -1) return 0; + + var negative = count < 0; + + var max = negative ? -count : count; + + var bits = Log2((uint)max); + + int x; + do + { + x = (int)(NextULong() >> (64 - bits)); + } while (x >= max); + + return negative ? -x : x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next(int minValue, int count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next(uint count) + { + if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); + if (count == 1) return 0; + + var bits = Log2(count); + + uint x; + do + { + x = (uint)(NextULong() >> (64 - bits)); + } while (x >= count); + + return x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Next(uint minValue, uint count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Next(long count) + { + if (count == 0) throw new ArgumentOutOfRangeException(nameof(count), count, "count must not be 0"); + if (count == 1 || count == -1) return 0; + + var negative = count < 0; + + var max = negative ? -count : count; + + var bits = Log2((ulong)max); + + long x; + do + { + x = (long)(NextULong() >> (64 - bits)); + } while (x >= max); + + return negative ? -x : x; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long Next(long minValue, long count) => minValue + Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDouble() => (NextULong() >> 11) * INCR_DOUBLE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int NextInt() => (int)(NextULong() >> 33); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint NextUInt() => (uint)NextULong(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool NextBool() => (NextULong() & 0x8000000000000000) != 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte NextByte() => (byte)(NextULong() >> 56); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float NextFloat() => (NextULong() >> 40) * INCR_FLOAT; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float NextFloatNonZero() => NextFloat() + INCR_FLOAT; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDoubleNonZero() => NextDouble() + INCR_DOUBLE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double NextDoubleHighRes() + { + var exponent = -64; + ulong significand; + int shift; + + while ((significand = NextULong()) == 0) + { + exponent -= 64; + + if (exponent < -1074) + return 0; + } + + shift = BitOperations.LeadingZeroCount(significand); + if (shift != 0) + { + exponent -= shift; + significand <<= shift; + significand |= NextULong() >> (64 - shift); + } + + significand |= 1; + + return significand * Math.Pow(2, exponent); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Log2(uint v) + { + var exp = BitOperations.Log2(v); + return v == 1 << exp ? exp : exp + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Log2(ulong v) + { + var exp = BitOperations.Log2(v); + return v == 1UL << exp ? exp : exp + 1; + } + } +} diff --git a/Projects/Server/Random/IRandomSource.cs b/Projects/Server/Random/IRandomSource.cs index 4ae96be4d..46fe05e43 100644 --- a/Projects/Server/Random/IRandomSource.cs +++ b/Projects/Server/Random/IRandomSource.cs @@ -1,46 +1,46 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: IRandomSource.cs - Created: 2020/07/25 - Updated: 2020/07/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server.Random -{ - public interface IRandomSource - { - int Next(); - int Next(int maxValue); - int Next(int minValue, int count); - uint Next(uint maxValue); - uint Next(uint minValue, uint count); - long Next(long maxValue); - long Next(long minValue, long count); - double NextDouble(); - void NextBytes(Span buffer); - int NextInt(); - uint NextUInt(); - ulong NextULong(); - bool NextBool(); - byte NextByte(); - float NextFloat(); - float NextFloatNonZero(); - double NextDoubleNonZero(); - double NextDoubleHighRes(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IRandomSource.cs - Created: 2020/07/25 - Updated: 2020/07/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server.Random +{ + public interface IRandomSource + { + int Next(); + int Next(int maxValue); + int Next(int minValue, int count); + uint Next(uint maxValue); + uint Next(uint minValue, uint count); + long Next(long maxValue); + long Next(long minValue, long count); + double NextDouble(); + void NextBytes(Span buffer); + int NextInt(); + uint NextUInt(); + ulong NextULong(); + bool NextBool(); + byte NextByte(); + float NextFloat(); + float NextFloatNonZero(); + double NextDoubleNonZero(); + double NextDoubleHighRes(); + } +} diff --git a/Projects/Server/Random/RandomSources.cs b/Projects/Server/Random/RandomSources.cs index 9f9a592b0..66c642d4d 100644 --- a/Projects/Server/Random/RandomSources.cs +++ b/Projects/Server/Random/RandomSources.cs @@ -1,31 +1,31 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: RandomSources.cs - Created: 2020/05/24 - Updated: 2020/07/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server.Random -{ - public static class RandomSources - { - private static IRandomSource m_Source; - private static IRandomSource m_SecureSource; - - public static IRandomSource Source => m_Source ??= new Xoshiro256PlusPlus(); - public static IRandomSource SecureSource => m_SecureSource ??= new SecureRandom(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: RandomSources.cs - Created: 2020/05/24 - Updated: 2020/07/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Random +{ + public static class RandomSources + { + private static IRandomSource m_Source; + private static IRandomSource m_SecureSource; + + public static IRandomSource Source => m_Source ??= new Xoshiro256PlusPlus(); + public static IRandomSource SecureSource => m_SecureSource ??= new SecureRandom(); + } +} diff --git a/Projects/Server/Random/SecureRandom.cs b/Projects/Server/Random/SecureRandom.cs index 7edd1dc7d..5d96d2429 100644 --- a/Projects/Server/Random/SecureRandom.cs +++ b/Projects/Server/Random/SecureRandom.cs @@ -1,46 +1,46 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: SecureRandom.cs - Created: 2020/01/09 - Updated: 2020/07/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Buffers.Binary; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using Server.Random; - -namespace Server -{ - public class SecureRandom : BaseRandomSource - { - private RandomNumberGenerator m_Random; - - public RandomNumberGenerator Generator => m_Random ??= new RNGCryptoServiceProvider(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override ulong NextULong() - { - Span buffer = stackalloc byte[sizeof(ulong)]; - Generator.GetBytes(buffer); - return BinaryPrimitives.ReadUInt64BigEndian(buffer); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void NextBytes(Span buffer) => Generator.GetBytes(buffer); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SecureRandom.cs - Created: 2020/01/09 - Updated: 2020/07/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using Server.Random; + +namespace Server +{ + public class SecureRandom : BaseRandomSource + { + private RandomNumberGenerator m_Random; + + public RandomNumberGenerator Generator => m_Random ??= new RNGCryptoServiceProvider(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override ulong NextULong() + { + Span buffer = stackalloc byte[sizeof(ulong)]; + Generator.GetBytes(buffer); + return BinaryPrimitives.ReadUInt64BigEndian(buffer); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void NextBytes(Span buffer) => Generator.GetBytes(buffer); + } +} diff --git a/Projects/Server/Random/Xoshiro256PlusPlus.cs b/Projects/Server/Random/Xoshiro256PlusPlus.cs index f29687efa..af61d63f7 100644 --- a/Projects/Server/Random/Xoshiro256PlusPlus.cs +++ b/Projects/Server/Random/Xoshiro256PlusPlus.cs @@ -1,214 +1,214 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: Xoshiro256PlusPlus.cs * - * Created: 2020/01/09 - Updated: 2020/07/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Runtime.CompilerServices; - -namespace Server.Random -{ - public class Xoshiro256PlusPlus : BaseRandomSource - { - private ulong _s0, _s1, _s2, _s3; - - public Xoshiro256PlusPlus() : this((ulong)Environment.TickCount64) - { - } - - public Xoshiro256PlusPlus(ulong seed) - { - var mix = new SplitMix64(seed); - var state = new ulong[4]; - mix.FillArray(state); - _s0 = state[0]; - _s1 = state[1]; - _s2 = state[2]; - _s3 = state[3]; - } - - private Xoshiro256PlusPlus(ulong s0, ulong s1, ulong s2, ulong s3) - { - _s0 = s0; - _s1 = s1; - _s2 = s2; - _s3 = s3; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override ulong NextULong() - { - var r1 = (_s1 << 2) + _s1; - var r2 = (r1 << 7) | (r1 >> 57); - var rslt = (r2 << 3) + r2; - - var t = _s1 << 17; - - _s2 ^= _s0; - _s3 ^= _s1; - _s1 ^= _s2; - _s0 ^= _s3; - - _s2 ^= t; - - _s3 = (_s3 << 45) | (_s3 >> 19); - - return rslt; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override unsafe void NextBytes(Span b) - { - if (b.Length == 0) return; - - var s0 = _s0; - var s1 = _s1; - var s2 = _s2; - var s3 = _s3; - - var i = 0; - - fixed (byte* pBuffer = b) - { - var pULong = (ulong*)pBuffer; - - for (var bound = b.Length / sizeof(ulong); i < bound; i++) - { - var r1 = (s1 << 2) + s1; - var r2 = (r1 << 7) | (r1 >> 57); - pULong[i] = (r2 << 3) + r2; - - var t = s1 << 17; - s2 ^= s0; - s3 ^= s1; - s1 ^= s2; - s0 ^= s3; - - s2 ^= t; - - s3 = (s3 << 45) | (s3 >> 19); - } - } - - i *= 8; - - if (i < b.Length) - { - var r1 = (s1 << 2) + s1; - var r2 = (r1 << 7) | (r1 >> 57); - var rslt = (r2 << 3) + r2; - - var t = s1 << 17; - - s2 ^= s0; - s3 ^= s1; - s1 ^= s2; - s0 ^= s3; - - s2 ^= t; - - s3 = (s3 << 45) | (s3 >> 19); - - while (i < b.Length) - { - b[i++] = (byte)rslt; - rslt >>= 8; - } - } - - _s0 = s0; - _s1 = s1; - _s2 = s2; - _s3 = s3; - } - - private static readonly ulong[] JUMP = - { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; - - private static readonly ulong[] LONG_JUMP = - { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; - - public void Jump() => Jump(JUMP); - - public void LongJump() => Jump(LONG_JUMP); - - private void Jump(in ulong[] jumps) - { - ulong s0 = 0; - ulong s1 = 0; - ulong s2 = 0; - ulong s3 = 0; - - for (var i = 0; i < jumps.Length; i++) - for (var b = 0; b < 64; b++) - { - if ((jumps[i] & (1ul << b)) != 0) - { - s0 ^= _s0; - s1 ^= _s1; - s2 ^= _s2; - s3 ^= _s3; - } - - NextULong(); - } - - _s0 = s0; - _s1 = s1; - _s2 = s2; - _s3 = s3; - } - - public Xoshiro256PlusPlus Split() - { - var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); - rng.Jump(); - return rng; - } - - public Xoshiro256PlusPlus LongSplit() - { - var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); - rng.LongJump(); - return rng; - } - } - - public class SplitMix64 - { - private ulong x; - - public SplitMix64(ulong seed) => x = seed; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong Next() - { - var z = x += 0x9e3779b97f4a7c15; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void FillArray(ulong[] arr) - { - for (var i = 0; i < arr.Length; i++) arr[i] = Next(); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: Xoshiro256PlusPlus.cs * + * Created: 2020/01/09 - Updated: 2020/07/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Runtime.CompilerServices; + +namespace Server.Random +{ + public class Xoshiro256PlusPlus : BaseRandomSource + { + private static readonly ulong[] JUMP = + { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + + private static readonly ulong[] LONG_JUMP = + { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + + private ulong _s0, _s1, _s2, _s3; + + public Xoshiro256PlusPlus() : this((ulong)Environment.TickCount64) + { + } + + public Xoshiro256PlusPlus(ulong seed) + { + var mix = new SplitMix64(seed); + var state = new ulong[4]; + mix.FillArray(state); + _s0 = state[0]; + _s1 = state[1]; + _s2 = state[2]; + _s3 = state[3]; + } + + private Xoshiro256PlusPlus(ulong s0, ulong s1, ulong s2, ulong s3) + { + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override ulong NextULong() + { + var r1 = (_s1 << 2) + _s1; + var r2 = (r1 << 7) | (r1 >> 57); + var rslt = (r2 << 3) + r2; + + var t = _s1 << 17; + + _s2 ^= _s0; + _s3 ^= _s1; + _s1 ^= _s2; + _s0 ^= _s3; + + _s2 ^= t; + + _s3 = (_s3 << 45) | (_s3 >> 19); + + return rslt; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override unsafe void NextBytes(Span b) + { + if (b.Length == 0) return; + + var s0 = _s0; + var s1 = _s1; + var s2 = _s2; + var s3 = _s3; + + var i = 0; + + fixed (byte* pBuffer = b) + { + var pULong = (ulong*)pBuffer; + + for (var bound = b.Length / sizeof(ulong); i < bound; i++) + { + var r1 = (s1 << 2) + s1; + var r2 = (r1 << 7) | (r1 >> 57); + pULong[i] = (r2 << 3) + r2; + + var t = s1 << 17; + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + + s2 ^= t; + + s3 = (s3 << 45) | (s3 >> 19); + } + } + + i *= 8; + + if (i < b.Length) + { + var r1 = (s1 << 2) + s1; + var r2 = (r1 << 7) | (r1 >> 57); + var rslt = (r2 << 3) + r2; + + var t = s1 << 17; + + s2 ^= s0; + s3 ^= s1; + s1 ^= s2; + s0 ^= s3; + + s2 ^= t; + + s3 = (s3 << 45) | (s3 >> 19); + + while (i < b.Length) + { + b[i++] = (byte)rslt; + rslt >>= 8; + } + } + + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; + } + + public void Jump() => Jump(JUMP); + + public void LongJump() => Jump(LONG_JUMP); + + private void Jump(in ulong[] jumps) + { + ulong s0 = 0; + ulong s1 = 0; + ulong s2 = 0; + ulong s3 = 0; + + for (var i = 0; i < jumps.Length; i++) + for (var b = 0; b < 64; b++) + { + if ((jumps[i] & (1ul << b)) != 0) + { + s0 ^= _s0; + s1 ^= _s1; + s2 ^= _s2; + s3 ^= _s3; + } + + NextULong(); + } + + _s0 = s0; + _s1 = s1; + _s2 = s2; + _s3 = s3; + } + + public Xoshiro256PlusPlus Split() + { + var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); + rng.Jump(); + return rng; + } + + public Xoshiro256PlusPlus LongSplit() + { + var rng = new Xoshiro256PlusPlus(_s0, _s1, _s2, _s3); + rng.LongJump(); + return rng; + } + } + + public class SplitMix64 + { + private ulong x; + + public SplitMix64(ulong seed) => x = seed; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong Next() + { + var z = x += 0x9e3779b97f4a7c15; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FillArray(ulong[] arr) + { + for (var i = 0; i < arr.Length; i++) arr[i] = Next(); + } + } +} diff --git a/Projects/Server/Regions/Region.cs b/Projects/Server/Regions/Region.cs index c4767f67d..4b9ade479 100644 --- a/Projects/Server/Regions/Region.cs +++ b/Projects/Server/Regions/Region.cs @@ -1,739 +1,741 @@ -/*************************************************************************** - * Region.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Text.Json; -using Server.Json; -using Server.Network; -using Server.Targeting; - -namespace Server -{ - public enum MusicName - { - Invalid = -1, - OldUlt01 = 0, - Create1, - DragFlit, - OldUlt02, - OldUlt03, - OldUlt04, - OldUlt05, - OldUlt06, - Stones2, - Britain1, - Britain2, - Bucsden, - Jhelom, - LBCastle, - Linelle, - Magincia, - Minoc, - Ocllo, - Samlethe, - Serpents, - Skarabra, - Trinsic, - Vesper, - Wind, - Yew, - Cave01, - Dungeon9, - Forest_a, - InTown01, - Jungle_a, - Mountn_a, - Plains_a, - Sailing, - Swamp_a, - Tavern01, - Tavern02, - Tavern03, - Tavern04, - Combat1, - Combat2, - Combat3, - Approach, - Death, - Victory, - BTCastle, - Nujelm, - Dungeon2, - Cove, - Moonglow, - Zento, - TokunoDungeon, - Taiko, - DreadHornArea, - ElfCity, - GrizzleDungeon, - MelisandesLair, - ParoxysmusLair, - GwennoConversation, - GoodEndGame, - GoodVsEvil, - GreatEarthSerpents, - Humanoids_U9, - MinocNegative, - Paws, - SelimsBar, - SerpentIsleCombat_U7, - ValoriaShips - } - - public class Region : IComparable - { - public static readonly int DefaultPriority = 50; - - public static readonly int MinZ = sbyte.MinValue; - public static readonly int MaxZ = sbyte.MaxValue + 1; - - private Point3D m_GoLocation; - - private readonly string m_Name; - private readonly int m_Priority; - - public Region(string name, Map map, int priority, params Rectangle2D[] area) : this(name, map, priority, - ConvertTo3D(area)) - { - } - - public Region(string name, Map map, int priority, params Rectangle3D[] area) : this(name, map, null, area) => - m_Priority = priority; - - public Region(string name, Map map, Region parent, params Rectangle2D[] area) : this(name, map, parent, - ConvertTo3D(area)) - { - } - - public Region(string name, Map map, Region parent, params Rectangle3D[] area) - { - m_Name = name; - Map = map; - Parent = parent; - Area = area; - Dynamic = true; - Music = DefaultMusic; - - if (Parent == null) - { - ChildLevel = 0; - m_Priority = DefaultPriority; - } - else - { - ChildLevel = Parent.ChildLevel + 1; - m_Priority = Parent.Priority; - } - } - - public Region(DynamicJson json, JsonSerializerOptions options) - { - Map = json.GetProperty("map", options, out Map map) ? map : null; - Parent = json.GetProperty("parent", options, out string parent) ? Find(parent, Map) : null; - Dynamic = false; - - if (Parent == null) - { - ChildLevel = 0; - m_Priority = DefaultPriority; - } - else - { - ChildLevel = Parent.ChildLevel + 1; - m_Priority = Parent.Priority; - } - - m_Name = json.GetProperty("name", options, out string name) ? name : null; - - m_Priority = json.GetProperty("priority", options, out int priority) ? priority : 0; - - Area = json.GetProperty("rects", options, out List rects) ? - rects.ToArray() : Array.Empty(); - - if (Area.Length == 0) - Console.WriteLine("Empty area for region '{0}'", this); - - if (json.GetProperty("go", options, out Point3D go)) - { - m_GoLocation = go; - } - else if (Area.Length > 0) - { - var start = Area[0].Start; - var end = Area[0].End; - - var x = start.X + (end.X - start.X) / 2; - var y = start.Y + (end.Y - start.Y) / 2; - - m_GoLocation = new Point3D(x, y, Map?.GetAverageZ(x, y) ?? start.Z + (end.Z - start.Z) / 2); - } - - Music = json.GetEnumProperty("music", options, out MusicName music) ? music : DefaultMusic; - } - - public static List Regions { get; } = new List(); - - public static Type DefaultRegionType { get; set; } = typeof(Region); - - public static TimeSpan StaffLogoutDelay { get; set; } = TimeSpan.Zero; - - public static TimeSpan DefaultLogoutDelay { get; set; } = TimeSpan.FromMinutes(5.0); - - public string Name => m_Name; - public Map Map { get; } - - public Region Parent { get; } - - public List Children { get; } = new List(); - - public Rectangle3D[] Area { get; } - - public Sector[] Sectors { get; private set; } - - public bool Dynamic { get; } - - public int Priority => m_Priority; - public int ChildLevel { get; } - - public bool Registered { get; private set; } - - public Point3D GoLocation - { - get => m_GoLocation; - set => m_GoLocation = value; - } - - public MusicName Music { get; set; } - - public bool IsDefault => Map.DefaultRegion == this; - public virtual MusicName DefaultMusic => Parent?.Music ?? MusicName.Invalid; - - public int CompareTo(Region reg) - { - if (reg == null) - return 1; - - // Dynamic regions go first - if (Dynamic) - { - if (!reg.Dynamic) - return -1; - } - else if (reg.Dynamic) - { - return 1; - } - - var thisPriority = Priority; - var regPriority = reg.Priority; - - if (thisPriority != regPriority) - return regPriority - thisPriority; - - return reg.ChildLevel - ChildLevel; - } - - // This is not optimized. Use sparingly - public static Region Find(string name, Map map, bool insensitive = false) - { - if (insensitive) - name = name.ToLower(); - - for (int i = 0; i < Regions.Count; i++) - { - var region = Regions[i]; - if (region.Map != map) - continue; - - string rName = region.Name; - if (insensitive) - rName = rName.ToLower(); - - if (rName == name) - return region; - } - - return null; - } - - public static Region Find(Point3D p, Map map) - { - if (map == null) - return Map.Internal.DefaultRegion; - - var sector = map.GetSector(p); - var list = sector.RegionRects; - - for (var i = 0; i < list.Count; ++i) - { - var regRect = list[i]; - - if (regRect.Contains(p)) - return regRect.Region; - } - - return map.DefaultRegion; - } - - public static Rectangle3D ConvertTo3D(Rectangle2D rect) => - new Rectangle3D(new Point3D(rect.Start, MinZ), new Point3D(rect.End, MaxZ)); - - public static Rectangle3D[] ConvertTo3D(Rectangle2D[] rects) - { - var ret = new Rectangle3D[rects.Length]; - - for (var i = 0; i < ret.Length; i++) ret[i] = ConvertTo3D(rects[i]); - - return ret; - } - - public void Register() - { - if (Registered) - return; - - OnRegister(); - - Registered = true; - - if (Parent != null) - { - Parent.Children.Add(this); - Parent.OnChildAdded(this); - } - - Regions.Add(this); - - Map.RegisterRegion(this); - - var sectors = new List(); - - for (var i = 0; i < Area.Length; i++) - { - var rect = Area[i]; - - var start = Map.Bound(new Point2D(rect.Start)); - var end = Map.Bound(new Point2D(rect.End)); - - var startSector = Map.GetSector(start); - var endSector = Map.GetSector(end); - - for (var x = startSector.X; x <= endSector.X; x++) - for (var y = startSector.Y; y <= endSector.Y; y++) - { - var sector = Map.GetRealSector(x, y); - - sector.OnEnter(this, rect); - - if (!sectors.Contains(sector)) - sectors.Add(sector); - } - } - - Sectors = sectors.ToArray(); - } - - public void Unregister() - { - if (!Registered) - return; - - OnUnregister(); - - Registered = false; - - if (Children.Count > 0) - Console.WriteLine("Warning: Unregistering region '{0}' with children", this); - - if (Parent != null) - { - Parent.Children.Remove(this); - Parent.OnChildRemoved(this); - } - - Regions.Remove(this); - - Map.UnregisterRegion(this); - - if (Sectors != null) - for (var i = 0; i < Sectors.Length; i++) - Sectors[i].OnLeave(this); - - Sectors = null; - } - - public bool Contains(Point3D p) - { - for (var i = 0; i < Area.Length; i++) - { - var rect = Area[i]; - - if (rect.Contains(p)) - return true; - } - - return false; - } - - // TODO: Memoize this - public bool IsChildOf(Region region) - { - if (region == null) - return false; - - var p = Parent; - - while (p != null) - { - if (p == region) - return true; - - p = p.Parent; - } - - return false; - } - - // TODO: Memoize this - public T GetRegion() where T : Region - { - var r = this; - - do - { - if (r is T tr) - return tr; - - r = r.Parent; - } while (r != null); - - return null; - } - - public Region GetRegion(Type regionType) - { - if (regionType == null) - return null; - - var r = this; - - do - { - if (regionType.IsInstanceOfType(r)) - return r; - - r = r.Parent; - } while (r != null); - - return null; - } - - public Region GetRegion(string regionName) - { - if (regionName == null) - return null; - - var r = this; - - do - { - if (r.m_Name == regionName) - return r; - - r = r.Parent; - } while (r != null); - - return null; - } - - public bool IsPartOf() where T : Region => GetRegion() != null; - - public bool IsPartOf(Region region) => this == region || IsChildOf(region); - - public bool IsPartOf(string regionName) => GetRegion(regionName) != null; - - public virtual bool AcceptsSpawnsFrom(Region region) => - AllowSpawn() && (region == this || Parent?.AcceptsSpawnsFrom(region) == true); - - public List GetPlayers() - { - var list = new List(); - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var player in sector.Players) - if (player.Region.IsPartOf(this)) - list.Add(player); - } - - return list; - } - - public int GetPlayerCount() - { - var count = 0; - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var player in sector.Players) - if (player.Region.IsPartOf(this)) - count++; - } - - return count; - } - - public List GetMobiles() - { - var list = new List(); - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var mobile in sector.Mobiles) - if (mobile.Region.IsPartOf(this)) - list.Add(mobile); - } - - return list; - } - - public int GetMobileCount() - { - var count = 0; - - for (var i = 0; i < Sectors?.Length; i++) - { - var sector = Sectors[i]; - - foreach (var mobile in sector.Mobiles) - if (mobile.Region.IsPartOf(this)) - count++; - } - - return count; - } - - public override string ToString() => m_Name ?? GetType().Name; - - public virtual void OnRegister() - { - } - - public virtual void OnUnregister() - { - } - - public virtual void OnChildAdded(Region child) - { - } - - public virtual void OnChildRemoved(Region child) - { - } - - public virtual bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation) => - m.WalkRegion == null || AcceptsSpawnsFrom(m.WalkRegion); - - public virtual void OnEnter(Mobile m) - { - } - - public virtual void OnExit(Mobile m) - { - } - - public virtual void MakeGuard(Mobile focus) - { - Parent?.MakeGuard(focus); - } - - public virtual Type GetResource(Type type) => Parent?.GetResource(type) ?? type; - - public virtual bool CanUseStuckMenu(Mobile m) => Parent?.CanUseStuckMenu(m) != false; - - public virtual void OnAggressed(Mobile aggressor, Mobile aggressed, bool criminal) - { - Parent?.OnAggressed(aggressor, aggressed, criminal); - } - - public virtual void OnDidHarmful(Mobile harmer, Mobile harmed) - { - Parent?.OnDidHarmful(harmer, harmed); - } - - public virtual void OnGotHarmful(Mobile harmer, Mobile harmed) - { - Parent?.OnGotHarmful(harmer, harmed); - } - - public virtual void OnLocationChanged(Mobile m, Point3D oldLocation) - { - Parent?.OnLocationChanged(m, oldLocation); - } - - public virtual bool OnTarget(Mobile m, Target t, object o) => Parent?.OnTarget(m, t, o) != false; - - public virtual bool OnCombatantChange(Mobile m, Mobile old, Mobile @new) => - Parent?.OnCombatantChange(m, old, @new) != false; - - public virtual bool AllowHousing(Mobile from, Point3D p) => Parent?.AllowHousing(from, p) != false; - - public virtual bool SendInaccessibleMessage(Item item, Mobile from) => - Parent?.SendInaccessibleMessage(item, from) == true; - - public virtual bool CheckAccessibility(Item item, Mobile from) => Parent?.CheckAccessibility(item, from) != false; - - public virtual bool OnDecay(Item item) => Parent?.OnDecay(item) != false; - - public virtual bool AllowHarmful(Mobile from, Mobile target) => - Parent?.AllowHarmful(from, target) ?? Mobile.AllowHarmfulHandler?.Invoke(from, target) ?? true; - - public virtual void OnCriminalAction(Mobile m, bool message) - { - if (Parent != null) - Parent.OnCriminalAction(m, message); - else if (message) - m.SendLocalizedMessage(1005040); // You've committed a criminal act!! - } - - public virtual bool AllowBeneficial(Mobile from, Mobile target) => - Parent?.AllowBeneficial(from, target) ?? - Mobile.AllowBeneficialHandler?.Invoke(from, target) ?? true; - - public virtual void OnBeneficialAction(Mobile helper, Mobile target) - { - Parent?.OnBeneficialAction(helper, target); - } - - public virtual void OnGotBeneficialAction(Mobile helper, Mobile target) - { - Parent?.OnGotBeneficialAction(helper, target); - } - - public virtual void SpellDamageScalar(Mobile caster, Mobile target, ref double damage) - { - Parent?.SpellDamageScalar(caster, target, ref damage); - } - - public virtual void OnSpeech(SpeechEventArgs args) - { - Parent?.OnSpeech(args); - } - - public virtual bool OnSkillUse(Mobile m, int skill) => Parent?.OnSkillUse(m, skill) != false; - - public virtual bool OnBeginSpellCast(Mobile m, ISpell s) => Parent?.OnBeginSpellCast(m, s) != false; - - public virtual void OnSpellCast(Mobile m, ISpell s) - { - Parent?.OnSpellCast(m, s); - } - - public virtual bool OnResurrect(Mobile m) => Parent?.OnResurrect(m) != false; - - public virtual bool OnBeforeDeath(Mobile m) => Parent?.OnBeforeDeath(m) != false; - - public virtual void OnDeath(Mobile m) - { - Parent?.OnDeath(m); - } - - public virtual bool OnDamage(Mobile m, ref int damage) => Parent?.OnDamage(m, ref damage) != false; - - public virtual bool OnHeal(Mobile m, ref int heal) => Parent?.OnHeal(m, ref heal) != false; - - public virtual bool OnDoubleClick(Mobile m, object o) => Parent?.OnDoubleClick(m, o) != false; - - public virtual bool OnSingleClick(Mobile m, object o) => Parent?.OnSingleClick(m, o) != false; - - public virtual bool AllowSpawn() => Parent?.AllowSpawn() != false; - - public virtual void AlterLightLevel(Mobile m, ref int global, ref int personal) - { - Parent?.AlterLightLevel(m, ref global, ref personal); - } - - public virtual TimeSpan GetLogoutDelay(Mobile m) - { - if (Parent != null) - return Parent.GetLogoutDelay(m); - - return m.AccessLevel > AccessLevel.Player ? StaffLogoutDelay : DefaultLogoutDelay; - } - - internal static bool CanMove(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation, Map map) - { - var oldRegion = m.Region; - var newRegion = Find(newLocation, map); - - while (oldRegion != newRegion) - { - if (!newRegion.OnMoveInto(m, d, newLocation, oldLocation)) - return false; - - if (newRegion.Parent == null) - return true; - - newRegion = newRegion.Parent; - } - - return true; - } - - internal static void OnRegionChange(Mobile m, Region oldRegion, Region newRegion) - { - if (newRegion != null && m.NetState != null) - { - m.CheckLightLevels(false); - - if (oldRegion == null || oldRegion.Music != newRegion.Music) m.Send(PlayMusic.GetInstance(newRegion.Music)); - } - - var oldR = oldRegion; - var newR = newRegion; - - while (oldR != newR) - { - var oldRChild = oldR?.ChildLevel ?? -1; - var newRChild = newR?.ChildLevel ?? -1; - - if (oldRChild >= newRChild) - { - oldR?.OnExit(m); - oldR = oldR?.Parent; - } - - if (newRChild >= oldRChild) - { - newR?.OnEnter(m); - newR = newR?.Parent; - } - } - } - } -} +/*************************************************************************** + * Region.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Text.Json; +using Server.Json; +using Server.Network; +using Server.Targeting; + +namespace Server +{ + public enum MusicName + { + Invalid = -1, + OldUlt01 = 0, + Create1, + DragFlit, + OldUlt02, + OldUlt03, + OldUlt04, + OldUlt05, + OldUlt06, + Stones2, + Britain1, + Britain2, + Bucsden, + Jhelom, + LBCastle, + Linelle, + Magincia, + Minoc, + Ocllo, + Samlethe, + Serpents, + Skarabra, + Trinsic, + Vesper, + Wind, + Yew, + Cave01, + Dungeon9, + Forest_a, + InTown01, + Jungle_a, + Mountn_a, + Plains_a, + Sailing, + Swamp_a, + Tavern01, + Tavern02, + Tavern03, + Tavern04, + Combat1, + Combat2, + Combat3, + Approach, + Death, + Victory, + BTCastle, + Nujelm, + Dungeon2, + Cove, + Moonglow, + Zento, + TokunoDungeon, + Taiko, + DreadHornArea, + ElfCity, + GrizzleDungeon, + MelisandesLair, + ParoxysmusLair, + GwennoConversation, + GoodEndGame, + GoodVsEvil, + GreatEarthSerpents, + Humanoids_U9, + MinocNegative, + Paws, + SelimsBar, + SerpentIsleCombat_U7, + ValoriaShips + } + + public class Region : IComparable + { + public static readonly int DefaultPriority = 50; + + public static readonly int MinZ = sbyte.MinValue; + public static readonly int MaxZ = sbyte.MaxValue + 1; + + public Region(string name, Map map, int priority, params Rectangle2D[] area) : this( + name, + map, + priority, + ConvertTo3D(area) + ) + { + } + + public Region(string name, Map map, int priority, params Rectangle3D[] area) : this(name, map, null, area) => + Priority = priority; + + public Region(string name, Map map, Region parent, params Rectangle2D[] area) : this( + name, + map, + parent, + ConvertTo3D(area) + ) + { + } + + public Region(string name, Map map, Region parent, params Rectangle3D[] area) + { + Name = name; + Map = map; + Parent = parent; + Area = area; + Dynamic = true; + Music = DefaultMusic; + + if (Parent == null) + { + ChildLevel = 0; + Priority = DefaultPriority; + } + else + { + ChildLevel = Parent.ChildLevel + 1; + Priority = Parent.Priority; + } + } + + public Region(DynamicJson json, JsonSerializerOptions options) + { + Map = json.GetProperty("map", options, out Map map) ? map : null; + Parent = json.GetProperty("parent", options, out string parent) ? Find(parent, Map) : null; + Dynamic = false; + + if (Parent == null) + { + ChildLevel = 0; + Priority = DefaultPriority; + } + else + { + ChildLevel = Parent.ChildLevel + 1; + Priority = Parent.Priority; + } + + Name = json.GetProperty("name", options, out string name) ? name : null; + + Priority = json.GetProperty("priority", options, out int priority) ? priority : 0; + + Area = json.GetProperty("rects", options, out List rects) + ? rects.ToArray() + : Array.Empty(); + + if (Area.Length == 0) + Console.WriteLine("Empty area for region '{0}'", this); + + if (json.GetProperty("go", options, out Point3D go)) + { + GoLocation = go; + } + else if (Area.Length > 0) + { + var start = Area[0].Start; + var end = Area[0].End; + + var x = start.X + (end.X - start.X) / 2; + var y = start.Y + (end.Y - start.Y) / 2; + + GoLocation = new Point3D(x, y, Map?.GetAverageZ(x, y) ?? start.Z + (end.Z - start.Z) / 2); + } + + Music = json.GetEnumProperty("music", options, out MusicName music) ? music : DefaultMusic; + } + + public static List Regions { get; } = new List(); + + public static Type DefaultRegionType { get; set; } = typeof(Region); + + public static TimeSpan StaffLogoutDelay { get; set; } = TimeSpan.Zero; + + public static TimeSpan DefaultLogoutDelay { get; set; } = TimeSpan.FromMinutes(5.0); + + public string Name { get; } + + public Map Map { get; } + + public Region Parent { get; } + + public List Children { get; } = new List(); + + public Rectangle3D[] Area { get; } + + public Sector[] Sectors { get; private set; } + + public bool Dynamic { get; } + + public int Priority { get; } + + public int ChildLevel { get; } + + public bool Registered { get; private set; } + + public Point3D GoLocation { get; set; } + + public MusicName Music { get; set; } + + public bool IsDefault => Map.DefaultRegion == this; + public virtual MusicName DefaultMusic => Parent?.Music ?? MusicName.Invalid; + + public int CompareTo(Region reg) + { + if (reg == null) + return 1; + + // Dynamic regions go first + if (Dynamic) + { + if (!reg.Dynamic) + return -1; + } + else if (reg.Dynamic) + { + return 1; + } + + var thisPriority = Priority; + var regPriority = reg.Priority; + + if (thisPriority != regPriority) + return regPriority - thisPriority; + + return reg.ChildLevel - ChildLevel; + } + + // This is not optimized. Use sparingly + public static Region Find(string name, Map map, bool insensitive = false) + { + if (insensitive) + name = name.ToLower(); + + for (var i = 0; i < Regions.Count; i++) + { + var region = Regions[i]; + if (region.Map != map) + continue; + + var rName = region.Name; + if (insensitive) + rName = rName.ToLower(); + + if (rName == name) + return region; + } + + return null; + } + + public static Region Find(Point3D p, Map map) + { + if (map == null) + return Map.Internal.DefaultRegion; + + var sector = map.GetSector(p); + var list = sector.RegionRects; + + for (var i = 0; i < list.Count; ++i) + { + var regRect = list[i]; + + if (regRect.Contains(p)) + return regRect.Region; + } + + return map.DefaultRegion; + } + + public static Rectangle3D ConvertTo3D(Rectangle2D rect) => + new Rectangle3D(new Point3D(rect.Start, MinZ), new Point3D(rect.End, MaxZ)); + + public static Rectangle3D[] ConvertTo3D(Rectangle2D[] rects) + { + var ret = new Rectangle3D[rects.Length]; + + for (var i = 0; i < ret.Length; i++) ret[i] = ConvertTo3D(rects[i]); + + return ret; + } + + public void Register() + { + if (Registered) + return; + + OnRegister(); + + Registered = true; + + if (Parent != null) + { + Parent.Children.Add(this); + Parent.OnChildAdded(this); + } + + Regions.Add(this); + + Map.RegisterRegion(this); + + var sectors = new List(); + + for (var i = 0; i < Area.Length; i++) + { + var rect = Area[i]; + + var start = Map.Bound(new Point2D(rect.Start)); + var end = Map.Bound(new Point2D(rect.End)); + + var startSector = Map.GetSector(start); + var endSector = Map.GetSector(end); + + for (var x = startSector.X; x <= endSector.X; x++) + for (var y = startSector.Y; y <= endSector.Y; y++) + { + var sector = Map.GetRealSector(x, y); + + sector.OnEnter(this, rect); + + if (!sectors.Contains(sector)) + sectors.Add(sector); + } + } + + Sectors = sectors.ToArray(); + } + + public void Unregister() + { + if (!Registered) + return; + + OnUnregister(); + + Registered = false; + + if (Children.Count > 0) + Console.WriteLine("Warning: Unregistering region '{0}' with children", this); + + if (Parent != null) + { + Parent.Children.Remove(this); + Parent.OnChildRemoved(this); + } + + Regions.Remove(this); + + Map.UnregisterRegion(this); + + if (Sectors != null) + for (var i = 0; i < Sectors.Length; i++) + Sectors[i].OnLeave(this); + + Sectors = null; + } + + public bool Contains(Point3D p) + { + for (var i = 0; i < Area.Length; i++) + { + var rect = Area[i]; + + if (rect.Contains(p)) + return true; + } + + return false; + } + + // TODO: Memoize this + public bool IsChildOf(Region region) + { + if (region == null) + return false; + + var p = Parent; + + while (p != null) + { + if (p == region) + return true; + + p = p.Parent; + } + + return false; + } + + // TODO: Memoize this + public T GetRegion() where T : Region + { + var r = this; + + do + { + if (r is T tr) + return tr; + + r = r.Parent; + } while (r != null); + + return null; + } + + public Region GetRegion(Type regionType) + { + if (regionType == null) + return null; + + var r = this; + + do + { + if (regionType.IsInstanceOfType(r)) + return r; + + r = r.Parent; + } while (r != null); + + return null; + } + + public Region GetRegion(string regionName) + { + if (regionName == null) + return null; + + var r = this; + + do + { + if (r.Name == regionName) + return r; + + r = r.Parent; + } while (r != null); + + return null; + } + + public bool IsPartOf() where T : Region => GetRegion() != null; + + public bool IsPartOf(Region region) => this == region || IsChildOf(region); + + public bool IsPartOf(string regionName) => GetRegion(regionName) != null; + + public virtual bool AcceptsSpawnsFrom(Region region) => + AllowSpawn() && (region == this || Parent?.AcceptsSpawnsFrom(region) == true); + + public List GetPlayers() + { + var list = new List(); + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var player in sector.Players) + if (player.Region.IsPartOf(this)) + list.Add(player); + } + + return list; + } + + public int GetPlayerCount() + { + var count = 0; + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var player in sector.Players) + if (player.Region.IsPartOf(this)) + count++; + } + + return count; + } + + public List GetMobiles() + { + var list = new List(); + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var mobile in sector.Mobiles) + if (mobile.Region.IsPartOf(this)) + list.Add(mobile); + } + + return list; + } + + public int GetMobileCount() + { + var count = 0; + + for (var i = 0; i < Sectors?.Length; i++) + { + var sector = Sectors[i]; + + foreach (var mobile in sector.Mobiles) + if (mobile.Region.IsPartOf(this)) + count++; + } + + return count; + } + + public override string ToString() => Name ?? GetType().Name; + + public virtual void OnRegister() + { + } + + public virtual void OnUnregister() + { + } + + public virtual void OnChildAdded(Region child) + { + } + + public virtual void OnChildRemoved(Region child) + { + } + + public virtual bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation) => + m.WalkRegion == null || AcceptsSpawnsFrom(m.WalkRegion); + + public virtual void OnEnter(Mobile m) + { + } + + public virtual void OnExit(Mobile m) + { + } + + public virtual void MakeGuard(Mobile focus) + { + Parent?.MakeGuard(focus); + } + + public virtual Type GetResource(Type type) => Parent?.GetResource(type) ?? type; + + public virtual bool CanUseStuckMenu(Mobile m) => Parent?.CanUseStuckMenu(m) != false; + + public virtual void OnAggressed(Mobile aggressor, Mobile aggressed, bool criminal) + { + Parent?.OnAggressed(aggressor, aggressed, criminal); + } + + public virtual void OnDidHarmful(Mobile harmer, Mobile harmed) + { + Parent?.OnDidHarmful(harmer, harmed); + } + + public virtual void OnGotHarmful(Mobile harmer, Mobile harmed) + { + Parent?.OnGotHarmful(harmer, harmed); + } + + public virtual void OnLocationChanged(Mobile m, Point3D oldLocation) + { + Parent?.OnLocationChanged(m, oldLocation); + } + + public virtual bool OnTarget(Mobile m, Target t, object o) => Parent?.OnTarget(m, t, o) != false; + + public virtual bool OnCombatantChange(Mobile m, Mobile old, Mobile @new) => + Parent?.OnCombatantChange(m, old, @new) != false; + + public virtual bool AllowHousing(Mobile from, Point3D p) => Parent?.AllowHousing(from, p) != false; + + public virtual bool SendInaccessibleMessage(Item item, Mobile from) => + Parent?.SendInaccessibleMessage(item, from) == true; + + public virtual bool CheckAccessibility(Item item, Mobile from) => Parent?.CheckAccessibility(item, from) != false; + + public virtual bool OnDecay(Item item) => Parent?.OnDecay(item) != false; + + public virtual bool AllowHarmful(Mobile from, Mobile target) => + Parent?.AllowHarmful(from, target) ?? Mobile.AllowHarmfulHandler?.Invoke(from, target) ?? true; + + public virtual void OnCriminalAction(Mobile m, bool message) + { + if (Parent != null) + Parent.OnCriminalAction(m, message); + else if (message) + m.SendLocalizedMessage(1005040); // You've committed a criminal act!! + } + + public virtual bool AllowBeneficial(Mobile from, Mobile target) => + Parent?.AllowBeneficial(from, target) ?? + Mobile.AllowBeneficialHandler?.Invoke(from, target) ?? true; + + public virtual void OnBeneficialAction(Mobile helper, Mobile target) + { + Parent?.OnBeneficialAction(helper, target); + } + + public virtual void OnGotBeneficialAction(Mobile helper, Mobile target) + { + Parent?.OnGotBeneficialAction(helper, target); + } + + public virtual void SpellDamageScalar(Mobile caster, Mobile target, ref double damage) + { + Parent?.SpellDamageScalar(caster, target, ref damage); + } + + public virtual void OnSpeech(SpeechEventArgs args) + { + Parent?.OnSpeech(args); + } + + public virtual bool OnSkillUse(Mobile m, int skill) => Parent?.OnSkillUse(m, skill) != false; + + public virtual bool OnBeginSpellCast(Mobile m, ISpell s) => Parent?.OnBeginSpellCast(m, s) != false; + + public virtual void OnSpellCast(Mobile m, ISpell s) + { + Parent?.OnSpellCast(m, s); + } + + public virtual bool OnResurrect(Mobile m) => Parent?.OnResurrect(m) != false; + + public virtual bool OnBeforeDeath(Mobile m) => Parent?.OnBeforeDeath(m) != false; + + public virtual void OnDeath(Mobile m) + { + Parent?.OnDeath(m); + } + + public virtual bool OnDamage(Mobile m, ref int damage) => Parent?.OnDamage(m, ref damage) != false; + + public virtual bool OnHeal(Mobile m, ref int heal) => Parent?.OnHeal(m, ref heal) != false; + + public virtual bool OnDoubleClick(Mobile m, object o) => Parent?.OnDoubleClick(m, o) != false; + + public virtual bool OnSingleClick(Mobile m, object o) => Parent?.OnSingleClick(m, o) != false; + + public virtual bool AllowSpawn() => Parent?.AllowSpawn() != false; + + public virtual void AlterLightLevel(Mobile m, ref int global, ref int personal) + { + Parent?.AlterLightLevel(m, ref global, ref personal); + } + + public virtual TimeSpan GetLogoutDelay(Mobile m) + { + if (Parent != null) + return Parent.GetLogoutDelay(m); + + return m.AccessLevel > AccessLevel.Player ? StaffLogoutDelay : DefaultLogoutDelay; + } + + internal static bool CanMove(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation, Map map) + { + var oldRegion = m.Region; + var newRegion = Find(newLocation, map); + + while (oldRegion != newRegion) + { + if (!newRegion.OnMoveInto(m, d, newLocation, oldLocation)) + return false; + + if (newRegion.Parent == null) + return true; + + newRegion = newRegion.Parent; + } + + return true; + } + + internal static void OnRegionChange(Mobile m, Region oldRegion, Region newRegion) + { + if (newRegion != null && m.NetState != null) + { + m.CheckLightLevels(false); + + if (oldRegion == null || oldRegion.Music != newRegion.Music) m.Send(PlayMusic.GetInstance(newRegion.Music)); + } + + var oldR = oldRegion; + var newR = newRegion; + + while (oldR != newR) + { + var oldRChild = oldR?.ChildLevel ?? -1; + var newRChild = newR?.ChildLevel ?? -1; + + if (oldRChild >= newRChild) + { + oldR?.OnExit(m); + oldR = oldR?.Parent; + } + + if (newRChild >= oldRChild) + { + newR?.OnEnter(m); + newR = newR?.Parent; + } + } + } + } +} diff --git a/Projects/Server/Regions/RegionLoader.cs b/Projects/Server/Regions/RegionLoader.cs index 4626988ad..d64e097ba 100644 --- a/Projects/Server/Regions/RegionLoader.cs +++ b/Projects/Server/Regions/RegionLoader.cs @@ -1,53 +1,58 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using Server.Json; -using Server.Utilities; - -namespace Server -{ - public static class RegionLoader - { - public static void LoadRegions() - { - var path = Path.Join(Core.BaseDirectory, "Data/regions.json"); - - List failures = new List(); - int count = 0; - - Console.Write("Regions: Loading..."); - - var stopwatch = Stopwatch.StartNew(); - List regions = JsonConfig.Deserialize>(path); - - foreach (var json in regions) - { - Type type = AssemblyHandler.FindFirstTypeForName(json.Type); - - if (type == null || !typeof(Region).IsAssignableFrom(type)) - { - failures.Add($"\tInvalid region type {json.Type}"); - continue; - } - - var region = ActivatorUtil.CreateInstance(type, json, JsonConfig.DefaultOptions) as Region; - region?.Register(); - count++; - } - - stopwatch.Stop(); - - Console.ForegroundColor = failures.Count > 0 ? ConsoleColor.Yellow : ConsoleColor.Green; - Console.Write("done{0}. ", failures.Count > 0 ? " with failures" : ""); - Console.ResetColor(); - Console.WriteLine("({0} regions, {1} failures) ({2:F2} seconds)", count, failures.Count, stopwatch.Elapsed.TotalSeconds); - if (failures.Count > 0) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(string.Join("\n", failures)); - Console.ResetColor(); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using Server.Json; +using Server.Utilities; + +namespace Server +{ + public static class RegionLoader + { + public static void LoadRegions() + { + var path = Path.Join(Core.BaseDirectory, "Data/regions.json"); + + var failures = new List(); + var count = 0; + + Console.Write("Regions: Loading..."); + + var stopwatch = Stopwatch.StartNew(); + var regions = JsonConfig.Deserialize>(path); + + foreach (var json in regions) + { + var type = AssemblyHandler.FindFirstTypeForName(json.Type); + + if (type == null || !typeof(Region).IsAssignableFrom(type)) + { + failures.Add($"\tInvalid region type {json.Type}"); + continue; + } + + var region = ActivatorUtil.CreateInstance(type, json, JsonConfig.DefaultOptions) as Region; + region?.Register(); + count++; + } + + stopwatch.Stop(); + + Console.ForegroundColor = failures.Count > 0 ? ConsoleColor.Yellow : ConsoleColor.Green; + Console.Write("done{0}. ", failures.Count > 0 ? " with failures" : ""); + Console.ResetColor(); + Console.WriteLine( + "({0} regions, {1} failures) ({2:F2} seconds)", + count, + failures.Count, + stopwatch.Elapsed.TotalSeconds + ); + if (failures.Count > 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(string.Join("\n", failures)); + Console.ResetColor(); + } + } + } +} diff --git a/Projects/Server/Sector.cs b/Projects/Server/Sector.cs index a5132a62a..1bf5ffe33 100644 --- a/Projects/Server/Sector.cs +++ b/Projects/Server/Sector.cs @@ -1,250 +1,250 @@ -/*************************************************************************** - * Sector.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using Server.Items; -using Server.Network; - -namespace Server -{ - public class RegionRect : IComparable - { - private Rectangle3D m_Rect; - - public RegionRect(Region region, Rectangle3D rect) - { - Region = region; - m_Rect = rect; - } - - public Region Region { get; } - - public Rectangle3D Rect => m_Rect; - - public int CompareTo(RegionRect regRect) => regRect == null ? 1 : Region.CompareTo(regRect.Region); - - public bool Contains(Point3D loc) => m_Rect.Contains(loc); - } - - public class Sector - { - // TODO: Can we avoid this? - private static readonly List m_DefaultMobileList = new List(); - private static readonly List m_DefaultItemList = new List(); - private static readonly List m_DefaultClientList = new List(); - private static readonly List m_DefaultMultiList = new List(); - private static readonly List m_DefaultRectList = new List(); - private bool m_Active; - private List m_Clients; - private List m_Items; - private List m_Mobiles; - private List m_Multis; - private List m_Players; - private List m_RegionRects; - - public Sector(int x, int y, Map owner) - { - X = x; - Y = y; - Owner = owner; - m_Active = false; - } - - public List RegionRects => m_RegionRects ?? m_DefaultRectList; - - public List Multis => m_Multis ?? m_DefaultMultiList; - - public List Mobiles => m_Mobiles ?? m_DefaultMobileList; - - public List Items => m_Items ?? m_DefaultItemList; - - public List Clients => m_Clients ?? m_DefaultClientList; - - public List Players => m_Players ?? m_DefaultMobileList; - - public bool Active => m_Active && Owner != Map.Internal; - - public Map Owner { get; } - - public int X { get; } - - public int Y { get; } - - private void Add(ref List list, T value) - { - list ??= new List(); - - list.Add(value); - } - - private void Remove(ref List list, T value) - { - if (list != null) - { - list.Remove(value); - - if (list.Count == 0) list = null; - } - } - - private void Replace(ref List list, T oldValue, T newValue) - { - if (oldValue != null && newValue != null) - { - var index = list?.IndexOf(oldValue) ?? -1; - - if (index >= 0) - list[index] = newValue; - else - Add(ref list, newValue); - } - else if (oldValue != null) - { - Remove(ref list, oldValue); - } - else if (newValue != null) - { - Add(ref list, newValue); - } - } - - public void OnClientChange(NetState oldState, NetState newState) - { - Replace(ref m_Clients, oldState, newState); - } - - public void OnEnter(Item item) - { - Add(ref m_Items, item); - } - - public void OnLeave(Item item) - { - Remove(ref m_Items, item); - } - - public void OnEnter(Mobile mob) - { - Add(ref m_Mobiles, mob); - - if (mob.NetState != null) Add(ref m_Clients, mob.NetState); - - if (mob.Player) - { - if (m_Players == null) Owner.ActivateSectors(X, Y); - - Add(ref m_Players, mob); - } - } - - public void OnLeave(Mobile mob) - { - Remove(ref m_Mobiles, mob); - - if (mob.NetState != null) Remove(ref m_Clients, mob.NetState); - - if (mob.Player && m_Players != null) - { - Remove(ref m_Players, mob); - - if (m_Players == null) Owner.DeactivateSectors(X, Y); - } - } - - public void OnEnter(Region region, Rectangle3D rect) - { - Add(ref m_RegionRects, new RegionRect(region, rect)); - - m_RegionRects.Sort(); - - UpdateMobileRegions(); - } - - public void OnLeave(Region region) - { - if (m_RegionRects != null) - { - for (var i = m_RegionRects.Count - 1; i >= 0; i--) - { - var regRect = m_RegionRects[i]; - - if (regRect.Region == region) m_RegionRects.RemoveAt(i); - } - - if (m_RegionRects.Count == 0) m_RegionRects = null; - } - - UpdateMobileRegions(); - } - - private void UpdateMobileRegions() - { - if (m_Mobiles != null) - { - var sandbox = new List(m_Mobiles); - - foreach (var mob in sandbox) mob.UpdateRegion(); - } - } - - public void OnMultiEnter(BaseMulti multi) - { - Add(ref m_Multis, multi); - } - - public void OnMultiLeave(BaseMulti multi) - { - Remove(ref m_Multis, multi); - } - - public void Activate() - { - if (!Active && Owner != Map.Internal) - { - if (m_Items != null) - foreach (var item in m_Items) - item.OnSectorActivate(); - - if (m_Mobiles != null) - foreach (var mob in m_Mobiles) - mob.OnSectorActivate(); - - m_Active = true; - } - } - - public void Deactivate() - { - if (Active) - { - if (m_Items != null) - foreach (var item in m_Items) - item.OnSectorDeactivate(); - - if (m_Mobiles != null) - foreach (var mob in m_Mobiles) - mob.OnSectorDeactivate(); - - m_Active = false; - } - } - } -} +/*************************************************************************** + * Sector.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using Server.Items; +using Server.Network; + +namespace Server +{ + public class RegionRect : IComparable + { + private Rectangle3D m_Rect; + + public RegionRect(Region region, Rectangle3D rect) + { + Region = region; + m_Rect = rect; + } + + public Region Region { get; } + + public Rectangle3D Rect => m_Rect; + + public int CompareTo(RegionRect regRect) => regRect == null ? 1 : Region.CompareTo(regRect.Region); + + public bool Contains(Point3D loc) => m_Rect.Contains(loc); + } + + public class Sector + { + // TODO: Can we avoid this? + private static readonly List m_DefaultMobileList = new List(); + private static readonly List m_DefaultItemList = new List(); + private static readonly List m_DefaultClientList = new List(); + private static readonly List m_DefaultMultiList = new List(); + private static readonly List m_DefaultRectList = new List(); + private bool m_Active; + private List m_Clients; + private List m_Items; + private List m_Mobiles; + private List m_Multis; + private List m_Players; + private List m_RegionRects; + + public Sector(int x, int y, Map owner) + { + X = x; + Y = y; + Owner = owner; + m_Active = false; + } + + public List RegionRects => m_RegionRects ?? m_DefaultRectList; + + public List Multis => m_Multis ?? m_DefaultMultiList; + + public List Mobiles => m_Mobiles ?? m_DefaultMobileList; + + public List Items => m_Items ?? m_DefaultItemList; + + public List Clients => m_Clients ?? m_DefaultClientList; + + public List Players => m_Players ?? m_DefaultMobileList; + + public bool Active => m_Active && Owner != Map.Internal; + + public Map Owner { get; } + + public int X { get; } + + public int Y { get; } + + private void Add(ref List list, T value) + { + list ??= new List(); + + list.Add(value); + } + + private void Remove(ref List list, T value) + { + if (list != null) + { + list.Remove(value); + + if (list.Count == 0) list = null; + } + } + + private void Replace(ref List list, T oldValue, T newValue) + { + if (oldValue != null && newValue != null) + { + var index = list?.IndexOf(oldValue) ?? -1; + + if (index >= 0) + list[index] = newValue; + else + Add(ref list, newValue); + } + else if (oldValue != null) + { + Remove(ref list, oldValue); + } + else if (newValue != null) + { + Add(ref list, newValue); + } + } + + public void OnClientChange(NetState oldState, NetState newState) + { + Replace(ref m_Clients, oldState, newState); + } + + public void OnEnter(Item item) + { + Add(ref m_Items, item); + } + + public void OnLeave(Item item) + { + Remove(ref m_Items, item); + } + + public void OnEnter(Mobile mob) + { + Add(ref m_Mobiles, mob); + + if (mob.NetState != null) Add(ref m_Clients, mob.NetState); + + if (mob.Player) + { + if (m_Players == null) Owner.ActivateSectors(X, Y); + + Add(ref m_Players, mob); + } + } + + public void OnLeave(Mobile mob) + { + Remove(ref m_Mobiles, mob); + + if (mob.NetState != null) Remove(ref m_Clients, mob.NetState); + + if (mob.Player && m_Players != null) + { + Remove(ref m_Players, mob); + + if (m_Players == null) Owner.DeactivateSectors(X, Y); + } + } + + public void OnEnter(Region region, Rectangle3D rect) + { + Add(ref m_RegionRects, new RegionRect(region, rect)); + + m_RegionRects.Sort(); + + UpdateMobileRegions(); + } + + public void OnLeave(Region region) + { + if (m_RegionRects != null) + { + for (var i = m_RegionRects.Count - 1; i >= 0; i--) + { + var regRect = m_RegionRects[i]; + + if (regRect.Region == region) m_RegionRects.RemoveAt(i); + } + + if (m_RegionRects.Count == 0) m_RegionRects = null; + } + + UpdateMobileRegions(); + } + + private void UpdateMobileRegions() + { + if (m_Mobiles != null) + { + var sandbox = new List(m_Mobiles); + + foreach (var mob in sandbox) mob.UpdateRegion(); + } + } + + public void OnMultiEnter(BaseMulti multi) + { + Add(ref m_Multis, multi); + } + + public void OnMultiLeave(BaseMulti multi) + { + Remove(ref m_Multis, multi); + } + + public void Activate() + { + if (!Active && Owner != Map.Internal) + { + if (m_Items != null) + foreach (var item in m_Items) + item.OnSectorActivate(); + + if (m_Mobiles != null) + foreach (var mob in m_Mobiles) + mob.OnSectorActivate(); + + m_Active = true; + } + } + + public void Deactivate() + { + if (Active) + { + if (m_Items != null) + foreach (var item in m_Items) + item.OnSectorDeactivate(); + + if (m_Mobiles != null) + foreach (var mob in m_Mobiles) + mob.OnSectorDeactivate(); + + m_Active = false; + } + } + } +} diff --git a/Projects/Server/SecureTrade.cs b/Projects/Server/SecureTrade.cs index d412db917..5c754b45a 100644 --- a/Projects/Server/SecureTrade.cs +++ b/Projects/Server/SecureTrade.cs @@ -1,401 +1,421 @@ -/*************************************************************************** - * SecureTrade.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using Server.Accounting; -using Server.Items; -using Server.Network; - -namespace Server -{ - public class SecureTrade - { - public SecureTrade(Mobile from, Mobile to) - { - Valid = true; - - From = new SecureTradeInfo(this, from, new SecureTradeContainer(this)); - To = new SecureTradeInfo(this, to, new SecureTradeContainer(this)); - - var from6017 = from.NetState?.ContainerGridLines == true; - var to6017 = to.NetState?.ContainerGridLines == true; - - var from704565 = from.NetState?.NewSecureTrading == true; - var to704565 = to.NetState?.NewSecureTrading == true; - - from.Send(new MobileStatus(from, to)); - from.Send(new UpdateSecureTrade(From.Container, false, false)); - - if (from6017) - from.Send(new SecureTradeEquip6017(To.Container, to)); - else - from.Send(new SecureTradeEquip(To.Container, to)); - - from.Send(new UpdateSecureTrade(From.Container, false, false)); - - if (from6017) - from.Send(new SecureTradeEquip6017(From.Container, from)); - else - from.Send(new SecureTradeEquip(From.Container, from)); - - from.Send(new DisplaySecureTrade(to, From.Container, To.Container, to.Name)); - from.Send(new UpdateSecureTrade(From.Container, false, false)); - - if (from.Account != null && from704565) - from.Send( - new UpdateSecureTrade(From.Container, TradeFlag.UpdateLedger, from.Account.TotalGold, - from.Account.TotalPlat)); - - to.Send(new MobileStatus(to, from)); - to.Send(new UpdateSecureTrade(To.Container, false, false)); - - if (to6017) - to.Send(new SecureTradeEquip6017(From.Container, from)); - else - to.Send(new SecureTradeEquip(From.Container, from)); - - to.Send(new UpdateSecureTrade(To.Container, false, false)); - - if (to6017) - to.Send(new SecureTradeEquip6017(To.Container, to)); - else - to.Send(new SecureTradeEquip(To.Container, to)); - - to.Send(new DisplaySecureTrade(from, To.Container, From.Container, from.Name)); - to.Send(new UpdateSecureTrade(To.Container, false, false)); - - if (to.Account != null && to704565) - to.Send(new UpdateSecureTrade(To.Container, TradeFlag.UpdateLedger, to.Account.TotalGold, - to.Account.TotalPlat)); - } - - public SecureTradeInfo From { get; } - - public SecureTradeInfo To { get; } - - public bool Valid { get; private set; } - - public void Cancel() - { - if (!Valid) return; - - var list = From.Container.Items; - - for (var i = list.Count - 1; i >= 0; --i) - if (i < list.Count) - { - var item = list[i]; - - if (item == From.VirtualCheck) continue; - - item.OnSecureTrade(From.Mobile, To.Mobile, From.Mobile, false); - - if (!item.Deleted) From.Mobile.AddToBackpack(item); - } - - list = To.Container.Items; - - for (var i = list.Count - 1; i >= 0; --i) - if (i < list.Count) - { - var item = list[i]; - - if (item == To.VirtualCheck) continue; - - item.OnSecureTrade(To.Mobile, From.Mobile, To.Mobile, false); - - if (!item.Deleted) To.Mobile.AddToBackpack(item); - } - - Close(); - } - - public void Close() - { - if (!Valid) return; - - From.Mobile.Send(new CloseSecureTrade(From.Container)); - To.Mobile.Send(new CloseSecureTrade(To.Container)); - - Valid = false; - - var ns = From.Mobile.NetState; - - ns?.RemoveTrade(this); - - ns = To.Mobile.NetState; - - ns?.RemoveTrade(this); - - Timer.DelayCall(From.Dispose); - Timer.DelayCall(To.Dispose); - } - - public void UpdateFromCurrency() - { - UpdateCurrency(From, To); - } - - public void UpdateToCurrency() - { - UpdateCurrency(To, From); - } - - private static void UpdateCurrency(SecureTradeInfo left, SecureTradeInfo right) - { - if (left.Mobile.NetState?.NewSecureTrading == true) - { - var plat = left.Mobile.Account.TotalPlat; - var gold = left.Mobile.Account.TotalGold; - - left.Mobile.Send(new UpdateSecureTrade(left.Container, TradeFlag.UpdateLedger, gold, plat)); - } - - if (right.Mobile.NetState?.NewSecureTrading == true) - right.Mobile.Send(new UpdateSecureTrade(right.Container, TradeFlag.UpdateGold, left.Gold, left.Plat)); - } - - public void Update() - { - if (!Valid) return; - - if (!From.IsDisposed && From.Accepted && !To.IsDisposed && To.Accepted) - { - var list = From.Container.Items; - - var allowed = true; - - for (var i = list.Count - 1; allowed && i >= 0; --i) - if (i < list.Count) - { - var item = list[i]; - - if (item == From.VirtualCheck) continue; - - if (!item.AllowSecureTrade(From.Mobile, To.Mobile, To.Mobile, true)) allowed = false; - } - - list = To.Container.Items; - - for (var i = list.Count - 1; allowed && i >= 0; --i) - if (i < list.Count) - { - var item = list[i]; - - if (item == To.VirtualCheck) continue; - - if (!item.AllowSecureTrade(To.Mobile, From.Mobile, From.Mobile, true)) allowed = false; - } - - if (AccountGold.Enabled) - { - if (From.Mobile.Account != null) - { - var totalPlat = From.Mobile.Account.TotalPlat; - var totalGold = From.Mobile.Account.TotalGold; - - if (totalPlat < From.Plat || totalGold < From.Gold) - { - allowed = false; - From.Mobile.SendMessage("You do not have enough currency to complete this trade."); - } - } - - if (To.Mobile.Account != null) - { - var totalPlat = To.Mobile.Account.TotalPlat; - var totalGold = To.Mobile.Account.TotalGold; - - if (totalPlat < To.Plat || totalGold < To.Gold) - { - allowed = false; - To.Mobile.SendMessage("You do not have enough currency to complete this trade."); - } - } - } - - if (!allowed) - { - From.Accepted = false; - To.Accepted = false; - - From.Mobile.Send(new UpdateSecureTrade(From.Container, From.Accepted, To.Accepted)); - To.Mobile.Send(new UpdateSecureTrade(To.Container, To.Accepted, From.Accepted)); - - return; - } - - if (AccountGold.Enabled && From.Mobile.Account != null && To.Mobile.Account != null) - HandleAccountGoldTrade(); - - list = From.Container.Items; - - for (var i = list.Count - 1; i >= 0; --i) - if (i < list.Count) - { - var item = list[i]; - - if (item == From.VirtualCheck) continue; - - item.OnSecureTrade(From.Mobile, To.Mobile, To.Mobile, true); - - if (!item.Deleted) To.Mobile.AddToBackpack(item); - } - - list = To.Container.Items; - - for (var i = list.Count - 1; i >= 0; --i) - if (i < list.Count) - { - var item = list[i]; - - if (item == To.VirtualCheck) continue; - - item.OnSecureTrade(To.Mobile, From.Mobile, From.Mobile, true); - - if (!item.Deleted) From.Mobile.AddToBackpack(item); - } - - Close(); - } - else if (!From.IsDisposed && !To.IsDisposed) - { - From.Mobile.Send(new UpdateSecureTrade(From.Container, From.Accepted, To.Accepted)); - To.Mobile.Send(new UpdateSecureTrade(To.Container, To.Accepted, From.Accepted)); - } - } - - private void HandleAccountGoldTrade() - { - int fromPlatSend = 0, fromGoldSend = 0, fromPlatRecv = 0, fromGoldRecv = 0; - int toPlatSend = 0, toGoldSend = 0, toPlatRecv = 0, toGoldRecv = 0; - - if ((From.Plat > 0) & From.Mobile.Account.WithdrawPlat(From.Plat)) - { - fromPlatSend = From.Plat; - - if (To.Mobile.Account.DepositPlat(From.Plat)) toPlatRecv = fromPlatSend; - } - - if ((From.Gold > 0) & From.Mobile.Account.WithdrawGold(From.Gold)) - { - fromGoldSend = From.Gold; - - if (To.Mobile.Account.DepositGold(From.Gold)) toGoldRecv = fromGoldSend; - } - - if ((To.Plat > 0) & To.Mobile.Account.WithdrawPlat(To.Plat)) - { - toPlatSend = To.Plat; - - if (From.Mobile.Account.DepositPlat(To.Plat)) fromPlatRecv = toPlatSend; - } - - if ((To.Gold > 0) & To.Mobile.Account.WithdrawGold(To.Gold)) - { - toGoldSend = To.Gold; - - if (From.Mobile.Account.DepositGold(To.Gold)) fromGoldRecv = toGoldSend; - } - - HandleAccountGoldTrade(From.Mobile, To.Mobile, fromPlatSend, fromGoldSend, fromPlatRecv, fromGoldRecv); - HandleAccountGoldTrade(To.Mobile, From.Mobile, toPlatSend, toGoldSend, toPlatRecv, toGoldRecv); - } - - private static void HandleAccountGoldTrade( - Mobile left, - Mobile right, - int platSend, - int goldSend, - int platRecv, - int goldRecv) - { - if (platSend > 0 || goldSend > 0) - { - if (platSend > 0 && goldSend > 0) - left.SendMessage("You traded {0:#,0} platinum and {1:#,0} gold to {2}.", platSend, goldSend, - right.RawName); - else if (platSend > 0) - left.SendMessage("You traded {0:#,0} platinum to {1}.", platSend, right.RawName); - else if (goldSend > 0) left.SendMessage("You traded {0:#,0} gold to {1}.", goldSend, right.RawName); - } - - if (platRecv > 0 || goldRecv > 0) - { - if (platRecv > 0 && goldRecv > 0) - left.SendMessage("You received {0:#,0} platinum and {1:#,0} gold from {2}.", platRecv, goldRecv, - right.RawName); - else if (platRecv > 0) - left.SendMessage("You received {0:#,0} platinum from {1}.", platRecv, right.RawName); - else if (goldRecv > 0) left.SendMessage("You received {0:#,0} gold from {1}.", goldRecv, right.RawName); - } - } - } - - public class SecureTradeInfo : IDisposable - { - public SecureTradeInfo(SecureTrade owner, Mobile m, SecureTradeContainer c) - { - Owner = owner; - Mobile = m; - Container = c; - - Mobile.AddItem(Container); - - VirtualCheck = new VirtualCheck(); - Container.DropItem(VirtualCheck); - } - - public SecureTrade Owner { get; private set; } - public Mobile Mobile { get; private set; } - public SecureTradeContainer Container { get; private set; } - public VirtualCheck VirtualCheck { get; private set; } - - public int Gold - { - get => VirtualCheck.Gold; - set => VirtualCheck.Gold = value; - } - - public int Plat - { - get => VirtualCheck.Plat; - set => VirtualCheck.Plat = value; - } - - public bool Accepted { get; set; } - - public bool IsDisposed { get; private set; } - - public void Dispose() - { - VirtualCheck.Delete(); - VirtualCheck = null; - - Container.Delete(); - Container = null; - - Mobile = null; - Owner = null; - - IsDisposed = true; - } - } -} +/*************************************************************************** + * SecureTrade.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using Server.Accounting; +using Server.Items; +using Server.Network; + +namespace Server +{ + public class SecureTrade + { + public SecureTrade(Mobile from, Mobile to) + { + Valid = true; + + From = new SecureTradeInfo(this, from, new SecureTradeContainer(this)); + To = new SecureTradeInfo(this, to, new SecureTradeContainer(this)); + + var from6017 = from.NetState?.ContainerGridLines == true; + var to6017 = to.NetState?.ContainerGridLines == true; + + var from704565 = from.NetState?.NewSecureTrading == true; + var to704565 = to.NetState?.NewSecureTrading == true; + + from.Send(new MobileStatus(from, to)); + from.Send(new UpdateSecureTrade(From.Container, false, false)); + + if (from6017) + from.Send(new SecureTradeEquip6017(To.Container, to)); + else + from.Send(new SecureTradeEquip(To.Container, to)); + + from.Send(new UpdateSecureTrade(From.Container, false, false)); + + if (from6017) + from.Send(new SecureTradeEquip6017(From.Container, from)); + else + from.Send(new SecureTradeEquip(From.Container, from)); + + from.Send(new DisplaySecureTrade(to, From.Container, To.Container, to.Name)); + from.Send(new UpdateSecureTrade(From.Container, false, false)); + + if (from.Account != null && from704565) + from.Send( + new UpdateSecureTrade( + From.Container, + TradeFlag.UpdateLedger, + from.Account.TotalGold, + from.Account.TotalPlat + ) + ); + + to.Send(new MobileStatus(to, from)); + to.Send(new UpdateSecureTrade(To.Container, false, false)); + + if (to6017) + to.Send(new SecureTradeEquip6017(From.Container, from)); + else + to.Send(new SecureTradeEquip(From.Container, from)); + + to.Send(new UpdateSecureTrade(To.Container, false, false)); + + if (to6017) + to.Send(new SecureTradeEquip6017(To.Container, to)); + else + to.Send(new SecureTradeEquip(To.Container, to)); + + to.Send(new DisplaySecureTrade(from, To.Container, From.Container, from.Name)); + to.Send(new UpdateSecureTrade(To.Container, false, false)); + + if (to.Account != null && to704565) + to.Send( + new UpdateSecureTrade( + To.Container, + TradeFlag.UpdateLedger, + to.Account.TotalGold, + to.Account.TotalPlat + ) + ); + } + + public SecureTradeInfo From { get; } + + public SecureTradeInfo To { get; } + + public bool Valid { get; private set; } + + public void Cancel() + { + if (!Valid) return; + + var list = From.Container.Items; + + for (var i = list.Count - 1; i >= 0; --i) + if (i < list.Count) + { + var item = list[i]; + + if (item == From.VirtualCheck) continue; + + item.OnSecureTrade(From.Mobile, To.Mobile, From.Mobile, false); + + if (!item.Deleted) From.Mobile.AddToBackpack(item); + } + + list = To.Container.Items; + + for (var i = list.Count - 1; i >= 0; --i) + if (i < list.Count) + { + var item = list[i]; + + if (item == To.VirtualCheck) continue; + + item.OnSecureTrade(To.Mobile, From.Mobile, To.Mobile, false); + + if (!item.Deleted) To.Mobile.AddToBackpack(item); + } + + Close(); + } + + public void Close() + { + if (!Valid) return; + + From.Mobile.Send(new CloseSecureTrade(From.Container)); + To.Mobile.Send(new CloseSecureTrade(To.Container)); + + Valid = false; + + var ns = From.Mobile.NetState; + + ns?.RemoveTrade(this); + + ns = To.Mobile.NetState; + + ns?.RemoveTrade(this); + + Timer.DelayCall(From.Dispose); + Timer.DelayCall(To.Dispose); + } + + public void UpdateFromCurrency() + { + UpdateCurrency(From, To); + } + + public void UpdateToCurrency() + { + UpdateCurrency(To, From); + } + + private static void UpdateCurrency(SecureTradeInfo left, SecureTradeInfo right) + { + if (left.Mobile.NetState?.NewSecureTrading == true) + { + var plat = left.Mobile.Account.TotalPlat; + var gold = left.Mobile.Account.TotalGold; + + left.Mobile.Send(new UpdateSecureTrade(left.Container, TradeFlag.UpdateLedger, gold, plat)); + } + + if (right.Mobile.NetState?.NewSecureTrading == true) + right.Mobile.Send(new UpdateSecureTrade(right.Container, TradeFlag.UpdateGold, left.Gold, left.Plat)); + } + + public void Update() + { + if (!Valid) return; + + if (!From.IsDisposed && From.Accepted && !To.IsDisposed && To.Accepted) + { + var list = From.Container.Items; + + var allowed = true; + + for (var i = list.Count - 1; allowed && i >= 0; --i) + if (i < list.Count) + { + var item = list[i]; + + if (item == From.VirtualCheck) continue; + + if (!item.AllowSecureTrade(From.Mobile, To.Mobile, To.Mobile, true)) allowed = false; + } + + list = To.Container.Items; + + for (var i = list.Count - 1; allowed && i >= 0; --i) + if (i < list.Count) + { + var item = list[i]; + + if (item == To.VirtualCheck) continue; + + if (!item.AllowSecureTrade(To.Mobile, From.Mobile, From.Mobile, true)) allowed = false; + } + + if (AccountGold.Enabled) + { + if (From.Mobile.Account != null) + { + var totalPlat = From.Mobile.Account.TotalPlat; + var totalGold = From.Mobile.Account.TotalGold; + + if (totalPlat < From.Plat || totalGold < From.Gold) + { + allowed = false; + From.Mobile.SendMessage("You do not have enough currency to complete this trade."); + } + } + + if (To.Mobile.Account != null) + { + var totalPlat = To.Mobile.Account.TotalPlat; + var totalGold = To.Mobile.Account.TotalGold; + + if (totalPlat < To.Plat || totalGold < To.Gold) + { + allowed = false; + To.Mobile.SendMessage("You do not have enough currency to complete this trade."); + } + } + } + + if (!allowed) + { + From.Accepted = false; + To.Accepted = false; + + From.Mobile.Send(new UpdateSecureTrade(From.Container, From.Accepted, To.Accepted)); + To.Mobile.Send(new UpdateSecureTrade(To.Container, To.Accepted, From.Accepted)); + + return; + } + + if (AccountGold.Enabled && From.Mobile.Account != null && To.Mobile.Account != null) + HandleAccountGoldTrade(); + + list = From.Container.Items; + + for (var i = list.Count - 1; i >= 0; --i) + if (i < list.Count) + { + var item = list[i]; + + if (item == From.VirtualCheck) continue; + + item.OnSecureTrade(From.Mobile, To.Mobile, To.Mobile, true); + + if (!item.Deleted) To.Mobile.AddToBackpack(item); + } + + list = To.Container.Items; + + for (var i = list.Count - 1; i >= 0; --i) + if (i < list.Count) + { + var item = list[i]; + + if (item == To.VirtualCheck) continue; + + item.OnSecureTrade(To.Mobile, From.Mobile, From.Mobile, true); + + if (!item.Deleted) From.Mobile.AddToBackpack(item); + } + + Close(); + } + else if (!From.IsDisposed && !To.IsDisposed) + { + From.Mobile.Send(new UpdateSecureTrade(From.Container, From.Accepted, To.Accepted)); + To.Mobile.Send(new UpdateSecureTrade(To.Container, To.Accepted, From.Accepted)); + } + } + + private void HandleAccountGoldTrade() + { + int fromPlatSend = 0, fromGoldSend = 0, fromPlatRecv = 0, fromGoldRecv = 0; + int toPlatSend = 0, toGoldSend = 0, toPlatRecv = 0, toGoldRecv = 0; + + if ((From.Plat > 0) & From.Mobile.Account.WithdrawPlat(From.Plat)) + { + fromPlatSend = From.Plat; + + if (To.Mobile.Account.DepositPlat(From.Plat)) toPlatRecv = fromPlatSend; + } + + if ((From.Gold > 0) & From.Mobile.Account.WithdrawGold(From.Gold)) + { + fromGoldSend = From.Gold; + + if (To.Mobile.Account.DepositGold(From.Gold)) toGoldRecv = fromGoldSend; + } + + if ((To.Plat > 0) & To.Mobile.Account.WithdrawPlat(To.Plat)) + { + toPlatSend = To.Plat; + + if (From.Mobile.Account.DepositPlat(To.Plat)) fromPlatRecv = toPlatSend; + } + + if ((To.Gold > 0) & To.Mobile.Account.WithdrawGold(To.Gold)) + { + toGoldSend = To.Gold; + + if (From.Mobile.Account.DepositGold(To.Gold)) fromGoldRecv = toGoldSend; + } + + HandleAccountGoldTrade(From.Mobile, To.Mobile, fromPlatSend, fromGoldSend, fromPlatRecv, fromGoldRecv); + HandleAccountGoldTrade(To.Mobile, From.Mobile, toPlatSend, toGoldSend, toPlatRecv, toGoldRecv); + } + + private static void HandleAccountGoldTrade( + Mobile left, + Mobile right, + int platSend, + int goldSend, + int platRecv, + int goldRecv + ) + { + if (platSend > 0 || goldSend > 0) + { + if (platSend > 0 && goldSend > 0) + left.SendMessage( + "You traded {0:#,0} platinum and {1:#,0} gold to {2}.", + platSend, + goldSend, + right.RawName + ); + else if (platSend > 0) + left.SendMessage("You traded {0:#,0} platinum to {1}.", platSend, right.RawName); + else if (goldSend > 0) left.SendMessage("You traded {0:#,0} gold to {1}.", goldSend, right.RawName); + } + + if (platRecv > 0 || goldRecv > 0) + { + if (platRecv > 0 && goldRecv > 0) + left.SendMessage( + "You received {0:#,0} platinum and {1:#,0} gold from {2}.", + platRecv, + goldRecv, + right.RawName + ); + else if (platRecv > 0) + left.SendMessage("You received {0:#,0} platinum from {1}.", platRecv, right.RawName); + else if (goldRecv > 0) left.SendMessage("You received {0:#,0} gold from {1}.", goldRecv, right.RawName); + } + } + } + + public class SecureTradeInfo : IDisposable + { + public SecureTradeInfo(SecureTrade owner, Mobile m, SecureTradeContainer c) + { + Owner = owner; + Mobile = m; + Container = c; + + Mobile.AddItem(Container); + + VirtualCheck = new VirtualCheck(); + Container.DropItem(VirtualCheck); + } + + public SecureTrade Owner { get; private set; } + public Mobile Mobile { get; private set; } + public SecureTradeContainer Container { get; private set; } + public VirtualCheck VirtualCheck { get; private set; } + + public int Gold + { + get => VirtualCheck.Gold; + set => VirtualCheck.Gold = value; + } + + public int Plat + { + get => VirtualCheck.Plat; + set => VirtualCheck.Plat = value; + } + + public bool Accepted { get; set; } + + public bool IsDisposed { get; private set; } + + public void Dispose() + { + VirtualCheck.Delete(); + VirtualCheck = null; + + Container.Delete(); + Container = null; + + Mobile = null; + Owner = null; + + IsDisposed = true; + } + } +} diff --git a/Projects/Server/Serial.cs b/Projects/Server/Serial.cs index b7fb5e788..567020b07 100644 --- a/Projects/Server/Serial.cs +++ b/Projects/Server/Serial.cs @@ -1,105 +1,105 @@ -/*************************************************************************** - * Serial.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server -{ - public readonly struct Serial : IComparable, IComparable, IEquatable - { - public static readonly Serial MinusOne = new Serial(0xFFFFFFFF); - public static readonly Serial Zero = new Serial(0); - - public static Serial LastMobile { get; private set; } = Zero; - - public static Serial LastItem { get; private set; } = 0x40000000; - - public static Serial NewMobile - { - get - { - while (World.FindMobile(LastMobile += 1) != null) - { - } - - return LastMobile; - } - } - - public static Serial NewItem - { - get - { - while (World.FindItem(LastItem = LastItem + 1) != null) - { - } - - return LastItem; - } - } - - private Serial(uint serial) => Value = serial; - - public uint Value { get; } - - public bool IsMobile => Value > 0 && Value < 0x40000000; - - public bool IsItem => Value >= 0x40000000 && Value < 0x80000000; - - public bool IsValid => Value > 0; - - public override int GetHashCode() => Value.GetHashCode(); - - public int CompareTo(Serial other) => Value.CompareTo(other.Value); - - public int CompareTo(uint other) => Value.CompareTo(other); - - public override bool Equals(object obj) - { - if (obj is Serial serial) return this == serial; - - if (obj is uint u) return Value == u; - - return false; - } - - public static bool operator ==(Serial l, Serial r) => l.Value == r.Value; - - public static bool operator !=(Serial l, Serial r) => l.Value != r.Value; - - public static bool operator >(Serial l, Serial r) => l.Value > r.Value; - - public static bool operator <(Serial l, Serial r) => l.Value < r.Value; - - public static bool operator >=(Serial l, Serial r) => l.Value >= r.Value; - - public static bool operator <=(Serial l, Serial r) => l.Value <= r.Value; - - public override string ToString() => $"0x{Value:X8}"; - - public static implicit operator uint(Serial a) => a.Value; - - public static implicit operator Serial(uint a) => new Serial(a); - - public bool Equals(Serial other) => Value == other.Value; - - public int ToInt32() => (int)Value; - } -} +/*************************************************************************** + * Serial.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server +{ + public readonly struct Serial : IComparable, IComparable, IEquatable + { + public static readonly Serial MinusOne = new Serial(0xFFFFFFFF); + public static readonly Serial Zero = new Serial(0); + + public static Serial LastMobile { get; private set; } = Zero; + + public static Serial LastItem { get; private set; } = 0x40000000; + + public static Serial NewMobile + { + get + { + while (World.FindMobile(LastMobile += 1) != null) + { + } + + return LastMobile; + } + } + + public static Serial NewItem + { + get + { + while (World.FindItem(LastItem = LastItem + 1) != null) + { + } + + return LastItem; + } + } + + private Serial(uint serial) => Value = serial; + + public uint Value { get; } + + public bool IsMobile => Value > 0 && Value < 0x40000000; + + public bool IsItem => Value >= 0x40000000 && Value < 0x80000000; + + public bool IsValid => Value > 0; + + public override int GetHashCode() => Value.GetHashCode(); + + public int CompareTo(Serial other) => Value.CompareTo(other.Value); + + public int CompareTo(uint other) => Value.CompareTo(other); + + public override bool Equals(object obj) + { + if (obj is Serial serial) return this == serial; + + if (obj is uint u) return Value == u; + + return false; + } + + public static bool operator ==(Serial l, Serial r) => l.Value == r.Value; + + public static bool operator !=(Serial l, Serial r) => l.Value != r.Value; + + public static bool operator >(Serial l, Serial r) => l.Value > r.Value; + + public static bool operator <(Serial l, Serial r) => l.Value < r.Value; + + public static bool operator >=(Serial l, Serial r) => l.Value >= r.Value; + + public static bool operator <=(Serial l, Serial r) => l.Value <= r.Value; + + public override string ToString() => $"0x{Value:X8}"; + + public static implicit operator uint(Serial a) => a.Value; + + public static implicit operator Serial(uint a) => new Serial(a); + + public bool Equals(Serial other) => Value == other.Value; + + public int ToInt32() => (int)Value; + } +} diff --git a/Projects/Server/Serialization/AsyncWriter.cs b/Projects/Server/Serialization/AsyncWriter.cs index 222e70edd..9b8134514 100644 --- a/Projects/Server/Serialization/AsyncWriter.cs +++ b/Projects/Server/Serialization/AsyncWriter.cs @@ -1,604 +1,604 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: AsyncWriter.cs * - * Created: 2020/12/30 - Updated: 2020/04/25 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using Server.Guilds; - -namespace Server -{ - public sealed class AsyncWriter : IGenericWriter - { - private readonly int m_BufferSize; - private BinaryWriter m_Bin; - private bool m_Closed; - private readonly FileStream m_File; - - private long m_LastPos, m_CurPos; - - private MemoryStream m_Mem; - private Thread m_WorkerThread; - - private readonly Queue m_WriteQueue; - private readonly bool m_PrefixStrings; - - public AsyncWriter(string filename, bool prefix) - : this(filename, 1048576, prefix) // 1 mb buffer - { - } - - public AsyncWriter(string filename, int buffSize, bool prefix) - { - m_PrefixStrings = prefix; - m_Closed = false; - m_WriteQueue = new Queue(); - m_BufferSize = buffSize; - - m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - m_Mem = new MemoryStream(m_BufferSize + 1024); - m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); - } - - public static int ThreadCount { get; private set; } - - public MemoryStream MemStream - { - get => m_Mem; - set - { - if (m_Mem.Length > 0) - Enqueue(m_Mem); - - m_Mem = value; - m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); - m_LastPos = 0; - m_CurPos = m_Mem.Length; - m_Mem.Seek(0, SeekOrigin.End); - } - } - - public long Position => m_CurPos; - - private void Enqueue(MemoryStream mem) - { - lock (m_WriteQueue) - { - m_WriteQueue.Enqueue(mem); - } - - if (m_WorkerThread.IsAlive != true) - { - m_WorkerThread = new Thread(new WorkerThread(this).Worker) { Priority = ThreadPriority.BelowNormal }; - m_WorkerThread.Start(); - } - } - - private void OnWrite() - { - var curlen = m_Mem.Length; - m_CurPos += curlen - m_LastPos; - m_LastPos = curlen; - if (curlen >= m_BufferSize) - { - Enqueue(m_Mem); - m_Mem = new MemoryStream(m_BufferSize + 1024); - m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); - m_LastPos = 0; - } - } - - public void Close() - { - Enqueue(m_Mem); - m_Closed = true; - } - - public void Write(IPAddress value) - { - m_Bin.Write(Utility.GetLongAddressValue(value)); - OnWrite(); - } - - public void Write(string value) - { - if (m_PrefixStrings) - { - if (value == null) - { - m_Bin.Write((byte)0); - } - else - { - m_Bin.Write((byte)1); - m_Bin.Write(value); - } - } - else - { - m_Bin.Write(value); - } - - OnWrite(); - } - - public void WriteDeltaTime(DateTime value) - { - var ticks = value.Ticks; - var now = DateTime.UtcNow.Ticks; - - TimeSpan d; - - try - { - d = new TimeSpan(ticks - now); - } - catch - { - d = TimeSpan.MaxValue; - } - - Write(d); - } - - public void Write(DateTime value) - { - m_Bin.Write(value.Ticks); - OnWrite(); - } - - public void Write(DateTimeOffset value) - { - m_Bin.Write(value.Ticks); - m_Bin.Write(value.Offset.Ticks); - OnWrite(); - } - - public void Write(TimeSpan value) - { - m_Bin.Write(value.Ticks); - OnWrite(); - } - - public void Write(decimal value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(long value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(ulong value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void WriteEncodedInt(int value) - { - var v = (uint)value; - - while (v >= 0x80) - { - m_Bin.Write((byte)(v | 0x80)); - v >>= 7; - } - - m_Bin.Write((byte)v); - OnWrite(); - } - - public void Write(int value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(uint value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(short value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(ushort value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(double value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(float value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(char value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(byte value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(byte[] value) - { - Write(value, value.Length); - } - - public void Write(byte[] value, int length) - { - m_Bin.Write(value, 0, length); - OnWrite(); - } - - public void Write(sbyte value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(bool value) - { - m_Bin.Write(value); - OnWrite(); - } - - public void Write(Point3D value) - { - Write(value.m_X); - Write(value.m_Y); - Write(value.m_Z); - } - - public void Write(Point2D value) - { - Write(value.m_X); - Write(value.m_Y); - } - - public void Write(Rectangle2D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Rectangle3D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Map value) - { - if (value != null) - Write((byte)value.MapIndex); - else - Write((byte)0xFF); - } - - public void Write(Race value) - { - if (value != null) - Write((byte)value.RaceIndex); - else - Write((byte)0xFF); - } - - public void WriteEntity(IEntity value) - { - if (value?.Deleted != false) - Write(Serial.MinusOne); - else - Write(value.Serial); - } - - public void Write(Item value) - { - if (value?.Deleted != false) - Write(Serial.MinusOne); - else - Write(value.Serial); - } - - public void Write(Mobile value) - { - if (value?.Deleted != false) - Write(Serial.MinusOne); - else - Write(value.Serial); - } - - public void Write(BaseGuild value) - { - if (value == null) - Write(0); - else - Write(value.Serial); - } - - public void WriteItem(T value) where T : Item - { - Write(value); - } - - public void WriteMobile(T value) where T : Mobile - { - Write(value); - } - - public void WriteGuild(T value) where T : BaseGuild - { - Write(value); - } - - public void Write(List list) - { - WriteItemList(list); - } - - public void Write(List list, bool tidy) - { - WriteItemList(list, tidy); - } - - public void WriteItemList(List list) where T : Item - { - WriteItemList(list, false); - } - - public void WriteItemList(List list, bool tidy) where T : Item - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(item => item.Deleted); - - Write(set.Count); - - foreach (var item in set) Write(item); - } - - public void WriteItemSet(HashSet set) where T : Item - { - WriteItemSet(set, false); - } - - public void WriteItemSet(HashSet set, bool tidy) where T : Item - { - if (tidy) set.RemoveWhere(item => item.Deleted); - - Write(set.Count); - - foreach (var item in set) Write(item); - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void WriteMobileList(List list) where T : Mobile - { - WriteMobileList(list, false); - } - - public void WriteMobileList(List list, bool tidy) where T : Mobile - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(mobile => mobile.Deleted); - - Write(set.Count); - - foreach (var mob in set) Write(mob); - } - - public void WriteMobileSet(HashSet set) where T : Mobile - { - WriteMobileSet(set, false); - } - - public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile - { - if (tidy) set.RemoveWhere(mob => mob.Deleted); - - Write(set.Count); - - foreach (var mob in set) Write(mob); - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Disbanded) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void WriteGuildList(List list) where T : BaseGuild - { - WriteGuildList(list, false); - } - - public void WriteGuildList(List list, bool tidy) where T : BaseGuild - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Disbanded) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(guild => guild.Disbanded); - - Write(set.Count); - - foreach (var guild in set) Write(guild); - } - - public void WriteGuildSet(HashSet set) where T : BaseGuild - { - WriteGuildSet(set, false); - } - - public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild - { - if (tidy) set.RemoveWhere(guild => guild.Disbanded); - - Write(set.Count); - - foreach (var guild in set) Write(guild); - } - - private class WorkerThread - { - private readonly AsyncWriter m_Owner; - - public WorkerThread(AsyncWriter owner) => m_Owner = owner; - - public void Worker() - { - ThreadCount++; - - int lastCount; - - do - { - MemoryStream mem = null; - - lock (m_Owner.m_WriteQueue) - { - if ((lastCount = m_Owner.m_WriteQueue.Count) > 0) - mem = m_Owner.m_WriteQueue.Dequeue(); - } - - if (mem?.Length > 0) - mem.WriteTo(m_Owner.m_File); - } while (lastCount > 1); - - if (m_Owner.m_Closed) - m_Owner.m_File.Close(); - - ThreadCount--; - - if (ThreadCount <= 0) - World.NotifyDiskWriteComplete(); - } - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AsyncWriter.cs * + * Created: 2020/12/30 - Updated: 2020/04/25 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using Server.Guilds; + +namespace Server +{ + public sealed class AsyncWriter : IGenericWriter + { + private readonly int m_BufferSize; + private readonly FileStream m_File; + private readonly bool m_PrefixStrings; + + private readonly Queue m_WriteQueue; + private BinaryWriter m_Bin; + private bool m_Closed; + + private long m_LastPos; + + private MemoryStream m_Mem; + private Thread m_WorkerThread; + + public AsyncWriter(string filename, bool prefix) + : this(filename, 1048576, prefix) // 1 mb buffer + { + } + + public AsyncWriter(string filename, int buffSize, bool prefix) + { + m_PrefixStrings = prefix; + m_Closed = false; + m_WriteQueue = new Queue(); + m_BufferSize = buffSize; + + m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + m_Mem = new MemoryStream(m_BufferSize + 1024); + m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); + } + + public static int ThreadCount { get; private set; } + + public MemoryStream MemStream + { + get => m_Mem; + set + { + if (m_Mem.Length > 0) + Enqueue(m_Mem); + + m_Mem = value; + m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); + m_LastPos = 0; + Position = m_Mem.Length; + m_Mem.Seek(0, SeekOrigin.End); + } + } + + public long Position { get; private set; } + + public void Close() + { + Enqueue(m_Mem); + m_Closed = true; + } + + public void Write(IPAddress value) + { + m_Bin.Write(Utility.GetLongAddressValue(value)); + OnWrite(); + } + + public void Write(string value) + { + if (m_PrefixStrings) + { + if (value == null) + { + m_Bin.Write((byte)0); + } + else + { + m_Bin.Write((byte)1); + m_Bin.Write(value); + } + } + else + { + m_Bin.Write(value); + } + + OnWrite(); + } + + public void WriteDeltaTime(DateTime value) + { + var ticks = value.Ticks; + var now = DateTime.UtcNow.Ticks; + + TimeSpan d; + + try + { + d = new TimeSpan(ticks - now); + } + catch + { + d = TimeSpan.MaxValue; + } + + Write(d); + } + + public void Write(DateTime value) + { + m_Bin.Write(value.Ticks); + OnWrite(); + } + + public void Write(DateTimeOffset value) + { + m_Bin.Write(value.Ticks); + m_Bin.Write(value.Offset.Ticks); + OnWrite(); + } + + public void Write(TimeSpan value) + { + m_Bin.Write(value.Ticks); + OnWrite(); + } + + public void Write(decimal value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(long value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(ulong value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void WriteEncodedInt(int value) + { + var v = (uint)value; + + while (v >= 0x80) + { + m_Bin.Write((byte)(v | 0x80)); + v >>= 7; + } + + m_Bin.Write((byte)v); + OnWrite(); + } + + public void Write(int value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(uint value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(short value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(ushort value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(double value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(float value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(char value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(byte value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(byte[] value) + { + Write(value, value.Length); + } + + public void Write(byte[] value, int length) + { + m_Bin.Write(value, 0, length); + OnWrite(); + } + + public void Write(sbyte value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(bool value) + { + m_Bin.Write(value); + OnWrite(); + } + + public void Write(Point3D value) + { + Write(value.m_X); + Write(value.m_Y); + Write(value.m_Z); + } + + public void Write(Point2D value) + { + Write(value.m_X); + Write(value.m_Y); + } + + public void Write(Rectangle2D value) + { + Write(value.Start); + Write(value.End); + } + + public void Write(Rectangle3D value) + { + Write(value.Start); + Write(value.End); + } + + public void Write(Map value) + { + if (value != null) + Write((byte)value.MapIndex); + else + Write((byte)0xFF); + } + + public void Write(Race value) + { + if (value != null) + Write((byte)value.RaceIndex); + else + Write((byte)0xFF); + } + + public void WriteEntity(IEntity value) + { + if (value?.Deleted != false) + Write(Serial.MinusOne); + else + Write(value.Serial); + } + + public void Write(Item value) + { + if (value?.Deleted != false) + Write(Serial.MinusOne); + else + Write(value.Serial); + } + + public void Write(Mobile value) + { + if (value?.Deleted != false) + Write(Serial.MinusOne); + else + Write(value.Serial); + } + + public void Write(BaseGuild value) + { + if (value == null) + Write(0); + else + Write(value.Serial); + } + + public void WriteItem(T value) where T : Item + { + Write(value); + } + + public void WriteMobile(T value) where T : Mobile + { + Write(value); + } + + public void WriteGuild(T value) where T : BaseGuild + { + Write(value); + } + + public void Write(List list) + { + WriteItemList(list); + } + + public void Write(List list, bool tidy) + { + WriteItemList(list, tidy); + } + + public void WriteItemList(List list) where T : Item + { + WriteItemList(list, false); + } + + public void WriteItemList(List list, bool tidy) where T : Item + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(item => item.Deleted); + + Write(set.Count); + + foreach (var item in set) Write(item); + } + + public void WriteItemSet(HashSet set) where T : Item + { + WriteItemSet(set, false); + } + + public void WriteItemSet(HashSet set, bool tidy) where T : Item + { + if (tidy) set.RemoveWhere(item => item.Deleted); + + Write(set.Count); + + foreach (var item in set) Write(item); + } + + public void Write(List list) + { + Write(list, false); + } + + public void Write(List list, bool tidy) + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void WriteMobileList(List list) where T : Mobile + { + WriteMobileList(list, false); + } + + public void WriteMobileList(List list, bool tidy) where T : Mobile + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(mobile => mobile.Deleted); + + Write(set.Count); + + foreach (var mob in set) Write(mob); + } + + public void WriteMobileSet(HashSet set) where T : Mobile + { + WriteMobileSet(set, false); + } + + public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile + { + if (tidy) set.RemoveWhere(mob => mob.Deleted); + + Write(set.Count); + + foreach (var mob in set) Write(mob); + } + + public void Write(List list) + { + Write(list, false); + } + + public void Write(List list, bool tidy) + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Disbanded) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void WriteGuildList(List list) where T : BaseGuild + { + WriteGuildList(list, false); + } + + public void WriteGuildList(List list, bool tidy) where T : BaseGuild + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Disbanded) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(guild => guild.Disbanded); + + Write(set.Count); + + foreach (var guild in set) Write(guild); + } + + public void WriteGuildSet(HashSet set) where T : BaseGuild + { + WriteGuildSet(set, false); + } + + public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild + { + if (tidy) set.RemoveWhere(guild => guild.Disbanded); + + Write(set.Count); + + foreach (var guild in set) Write(guild); + } + + private void Enqueue(MemoryStream mem) + { + lock (m_WriteQueue) + { + m_WriteQueue.Enqueue(mem); + } + + if (m_WorkerThread.IsAlive != true) + { + m_WorkerThread = new Thread(new WorkerThread(this).Worker) { Priority = ThreadPriority.BelowNormal }; + m_WorkerThread.Start(); + } + } + + private void OnWrite() + { + var curlen = m_Mem.Length; + Position += curlen - m_LastPos; + m_LastPos = curlen; + if (curlen >= m_BufferSize) + { + Enqueue(m_Mem); + m_Mem = new MemoryStream(m_BufferSize + 1024); + m_Bin = new BinaryWriter(m_Mem, Utility.UTF8WithEncoding); + m_LastPos = 0; + } + } + + private class WorkerThread + { + private readonly AsyncWriter m_Owner; + + public WorkerThread(AsyncWriter owner) => m_Owner = owner; + + public void Worker() + { + ThreadCount++; + + int lastCount; + + do + { + MemoryStream mem = null; + + lock (m_Owner.m_WriteQueue) + { + if ((lastCount = m_Owner.m_WriteQueue.Count) > 0) + mem = m_Owner.m_WriteQueue.Dequeue(); + } + + if (mem?.Length > 0) + mem.WriteTo(m_Owner.m_File); + } while (lastCount > 1); + + if (m_Owner.m_Closed) + m_Owner.m_File.Close(); + + ThreadCount--; + + if (ThreadCount <= 0) + World.NotifyDiskWriteComplete(); + } + } + } +} diff --git a/Projects/Server/Serialization/BinaryFileReader.cs b/Projects/Server/Serialization/BinaryFileReader.cs index 7caf6d87e..445d112f0 100644 --- a/Projects/Server/Serialization/BinaryFileReader.cs +++ b/Projects/Server/Serialization/BinaryFileReader.cs @@ -1,274 +1,274 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: BinaryFileReader.cs * - * Created: 2019/12/30 - Updated: 2020/01/18 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using Server.Guilds; - -namespace Server -{ - public sealed class BinaryFileReader : IGenericReader - { - private readonly BinaryReader m_File; - - public BinaryFileReader(BinaryReader br) => m_File = br; - - public long Position => m_File.BaseStream.Position; - - public void Close() - { - m_File.Close(); - } - - public long Seek(long offset, SeekOrigin origin) => m_File.BaseStream.Seek(offset, origin); - - public string ReadString() => ReadByte() != 0 ? m_File.ReadString() : null; - - public DateTime ReadDeltaTime() - { - var ticks = m_File.ReadInt64(); - var now = DateTime.UtcNow.Ticks; - - if (ticks > 0 && ticks + now < 0) - return DateTime.MaxValue; - if (ticks < 0 && ticks + now < 0) - return DateTime.MinValue; - - try - { - return new DateTime(now + ticks); - } - catch - { - return ticks > 0 ? DateTime.MaxValue : DateTime.MinValue; - } - } - - public IPAddress ReadIPAddress() => new IPAddress(m_File.ReadInt64()); - - public int ReadEncodedInt() - { - int v = 0, shift = 0; - byte b; - - do - { - b = m_File.ReadByte(); - v |= (b & 0x7F) << shift; - shift += 7; - } while (b >= 0x80); - - return v; - } - - public DateTime ReadDateTime() => new DateTime(m_File.ReadInt64()); - - public DateTimeOffset ReadDateTimeOffset() - { - var ticks = m_File.ReadInt64(); - var offset = new TimeSpan(m_File.ReadInt64()); - - return new DateTimeOffset(ticks, offset); - } - - public TimeSpan ReadTimeSpan() => new TimeSpan(m_File.ReadInt64()); - - public decimal ReadDecimal() => m_File.ReadDecimal(); - - public long ReadLong() => m_File.ReadInt64(); - - public ulong ReadULong() => m_File.ReadUInt64(); - - public int ReadInt() => m_File.ReadInt32(); - - public uint ReadUInt() => m_File.ReadUInt32(); - - public short ReadShort() => m_File.ReadInt16(); - - public ushort ReadUShort() => m_File.ReadUInt16(); - - public double ReadDouble() => m_File.ReadDouble(); - - public float ReadFloat() => m_File.ReadSingle(); - - public char ReadChar() => m_File.ReadChar(); - - public byte ReadByte() => m_File.ReadByte(); - - public sbyte ReadSByte() => m_File.ReadSByte(); - - public bool ReadBool() => m_File.ReadBoolean(); - - public Point3D ReadPoint3D() => new Point3D(ReadInt(), ReadInt(), ReadInt()); - - public Point2D ReadPoint2D() => new Point2D(ReadInt(), ReadInt()); - - public Rectangle2D ReadRect2D() => new Rectangle2D(ReadPoint2D(), ReadPoint2D()); - - public Rectangle3D ReadRect3D() => new Rectangle3D(ReadPoint3D(), ReadPoint3D()); - - public Map ReadMap() => Map.Maps[ReadByte()]; - - public IEntity ReadEntity() - { - Serial serial = ReadUInt(); - return World.FindEntity(serial) ?? new Entity(serial, new Point3D(0, 0, 0), Map.Internal); - } - - public Item ReadItem() => World.FindItem(ReadUInt()); - - public Mobile ReadMobile() => World.FindMobile(ReadUInt()); - - public BaseGuild ReadGuild() => BaseGuild.Find(ReadUInt()); - - public T ReadItem() where T : Item => ReadItem() as T; - - public T ReadMobile() where T : Mobile => ReadMobile() as T; - - public T ReadGuild() where T : BaseGuild => ReadGuild() as T; - - public List ReadStrongItemList() => ReadStrongItemList(); - - public List ReadStrongItemList() where T : Item - { - var count = ReadInt(); - - if (count > 0) - { - var list = new List(count); - - for (var i = 0; i < count; ++i) - if (ReadItem() is T item) - list.Add(item); - - return list; - } - - return new List(); - } - - public HashSet ReadItemSet() => ReadItemSet(); - - public HashSet ReadItemSet() where T : Item - { - var count = ReadInt(); - - if (count > 0) - { - var set = new HashSet(); - - for (var i = 0; i < count; ++i) - if (ReadItem() is T item) - set.Add(item); - - return set; - } - - return new HashSet(); - } - - public List ReadStrongMobileList() => ReadStrongMobileList(); - - public List ReadStrongMobileList() where T : Mobile - { - var count = ReadInt(); - - if (count > 0) - { - var list = new List(count); - - for (var i = 0; i < count; ++i) - if (ReadMobile() is T m) - list.Add(m); - - return list; - } - - return new List(); - } - - public HashSet ReadMobileSet() => ReadMobileSet(); - - public HashSet ReadMobileSet() where T : Mobile - { - var count = ReadInt(); - - if (count > 0) - { - var set = new HashSet(); - - for (var i = 0; i < count; ++i) - if (ReadMobile() is T item) - set.Add(item); - - return set; - } - - return new HashSet(); - } - - public List ReadStrongGuildList() => ReadStrongGuildList(); - - public List ReadStrongGuildList() where T : BaseGuild - { - var count = ReadInt(); - - if (count > 0) - { - var list = new List(count); - - for (var i = 0; i < count; ++i) - if (ReadGuild() is T g) - list.Add(g); - - return list; - } - - return new List(); - } - - public HashSet ReadGuildSet() => ReadGuildSet(); - - public HashSet ReadGuildSet() where T : BaseGuild - { - var count = ReadInt(); - - if (count > 0) - { - var set = new HashSet(); - - for (var i = 0; i < count; ++i) - if (ReadGuild() is T item) - set.Add(item); - - return set; - } - - return new HashSet(); - } - - public Race ReadRace() => Race.Races[ReadByte()]; - - public bool End() => m_File.PeekChar() == -1; - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: BinaryFileReader.cs * + * Created: 2019/12/30 - Updated: 2020/01/18 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Server.Guilds; + +namespace Server +{ + public sealed class BinaryFileReader : IGenericReader + { + private readonly BinaryReader m_File; + + public BinaryFileReader(BinaryReader br) => m_File = br; + + public long Position => m_File.BaseStream.Position; + + public string ReadString() => ReadByte() != 0 ? m_File.ReadString() : null; + + public DateTime ReadDeltaTime() + { + var ticks = m_File.ReadInt64(); + var now = DateTime.UtcNow.Ticks; + + if (ticks > 0 && ticks + now < 0) + return DateTime.MaxValue; + if (ticks < 0 && ticks + now < 0) + return DateTime.MinValue; + + try + { + return new DateTime(now + ticks); + } + catch + { + return ticks > 0 ? DateTime.MaxValue : DateTime.MinValue; + } + } + + public IPAddress ReadIPAddress() => new IPAddress(m_File.ReadInt64()); + + public int ReadEncodedInt() + { + int v = 0, shift = 0; + byte b; + + do + { + b = m_File.ReadByte(); + v |= (b & 0x7F) << shift; + shift += 7; + } while (b >= 0x80); + + return v; + } + + public DateTime ReadDateTime() => new DateTime(m_File.ReadInt64()); + + public DateTimeOffset ReadDateTimeOffset() + { + var ticks = m_File.ReadInt64(); + var offset = new TimeSpan(m_File.ReadInt64()); + + return new DateTimeOffset(ticks, offset); + } + + public TimeSpan ReadTimeSpan() => new TimeSpan(m_File.ReadInt64()); + + public decimal ReadDecimal() => m_File.ReadDecimal(); + + public long ReadLong() => m_File.ReadInt64(); + + public ulong ReadULong() => m_File.ReadUInt64(); + + public int ReadInt() => m_File.ReadInt32(); + + public uint ReadUInt() => m_File.ReadUInt32(); + + public short ReadShort() => m_File.ReadInt16(); + + public ushort ReadUShort() => m_File.ReadUInt16(); + + public double ReadDouble() => m_File.ReadDouble(); + + public float ReadFloat() => m_File.ReadSingle(); + + public char ReadChar() => m_File.ReadChar(); + + public byte ReadByte() => m_File.ReadByte(); + + public sbyte ReadSByte() => m_File.ReadSByte(); + + public bool ReadBool() => m_File.ReadBoolean(); + + public Point3D ReadPoint3D() => new Point3D(ReadInt(), ReadInt(), ReadInt()); + + public Point2D ReadPoint2D() => new Point2D(ReadInt(), ReadInt()); + + public Rectangle2D ReadRect2D() => new Rectangle2D(ReadPoint2D(), ReadPoint2D()); + + public Rectangle3D ReadRect3D() => new Rectangle3D(ReadPoint3D(), ReadPoint3D()); + + public Map ReadMap() => Map.Maps[ReadByte()]; + + public IEntity ReadEntity() + { + Serial serial = ReadUInt(); + return World.FindEntity(serial) ?? new Entity(serial, new Point3D(0, 0, 0), Map.Internal); + } + + public Item ReadItem() => World.FindItem(ReadUInt()); + + public Mobile ReadMobile() => World.FindMobile(ReadUInt()); + + public BaseGuild ReadGuild() => BaseGuild.Find(ReadUInt()); + + public T ReadItem() where T : Item => ReadItem() as T; + + public T ReadMobile() where T : Mobile => ReadMobile() as T; + + public T ReadGuild() where T : BaseGuild => ReadGuild() as T; + + public List ReadStrongItemList() => ReadStrongItemList(); + + public List ReadStrongItemList() where T : Item + { + var count = ReadInt(); + + if (count > 0) + { + var list = new List(count); + + for (var i = 0; i < count; ++i) + if (ReadItem() is T item) + list.Add(item); + + return list; + } + + return new List(); + } + + public HashSet ReadItemSet() => ReadItemSet(); + + public HashSet ReadItemSet() where T : Item + { + var count = ReadInt(); + + if (count > 0) + { + var set = new HashSet(); + + for (var i = 0; i < count; ++i) + if (ReadItem() is T item) + set.Add(item); + + return set; + } + + return new HashSet(); + } + + public List ReadStrongMobileList() => ReadStrongMobileList(); + + public List ReadStrongMobileList() where T : Mobile + { + var count = ReadInt(); + + if (count > 0) + { + var list = new List(count); + + for (var i = 0; i < count; ++i) + if (ReadMobile() is T m) + list.Add(m); + + return list; + } + + return new List(); + } + + public HashSet ReadMobileSet() => ReadMobileSet(); + + public HashSet ReadMobileSet() where T : Mobile + { + var count = ReadInt(); + + if (count > 0) + { + var set = new HashSet(); + + for (var i = 0; i < count; ++i) + if (ReadMobile() is T item) + set.Add(item); + + return set; + } + + return new HashSet(); + } + + public List ReadStrongGuildList() => ReadStrongGuildList(); + + public List ReadStrongGuildList() where T : BaseGuild + { + var count = ReadInt(); + + if (count > 0) + { + var list = new List(count); + + for (var i = 0; i < count; ++i) + if (ReadGuild() is T g) + list.Add(g); + + return list; + } + + return new List(); + } + + public HashSet ReadGuildSet() => ReadGuildSet(); + + public HashSet ReadGuildSet() where T : BaseGuild + { + var count = ReadInt(); + + if (count > 0) + { + var set = new HashSet(); + + for (var i = 0; i < count; ++i) + if (ReadGuild() is T item) + set.Add(item); + + return set; + } + + return new HashSet(); + } + + public Race ReadRace() => Race.Races[ReadByte()]; + + public bool End() => m_File.PeekChar() == -1; + + public void Close() + { + m_File.Close(); + } + + public long Seek(long offset, SeekOrigin origin) => m_File.BaseStream.Seek(offset, origin); + } +} diff --git a/Projects/Server/Serialization/BinaryFileWriter.cs b/Projects/Server/Serialization/BinaryFileWriter.cs index 1bcea6083..91127dfa2 100644 --- a/Projects/Server/Serialization/BinaryFileWriter.cs +++ b/Projects/Server/Serialization/BinaryFileWriter.cs @@ -1,671 +1,671 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: BinaryFileWriter.cs * - * Created: 2019/12/30 - Updated: 2020/01/18 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; -using Server.Guilds; - -namespace Server -{ - public class BinaryFileWriter : IGenericWriter - { - private const int LargeByteBufferSize = 256; - - private readonly byte[] m_Buffer; - - private byte[] m_CharacterBuffer; - - private readonly Encoding m_Encoding; - private readonly Stream m_File; - - private int m_Index; - private int m_MaxBufferChars; - - private long m_Position; - - private readonly char[] m_SingleCharBuffer = new char[1]; - private readonly bool m_PrefixStrings; - - public BinaryFileWriter(Stream strm, bool prefixStr) - { - m_PrefixStrings = prefixStr; - m_Encoding = Utility.UTF8; - m_Buffer = new byte[BufferSize]; - m_File = strm; - } - - public BinaryFileWriter(string filename, bool prefixStr) - { - m_PrefixStrings = prefixStr; - m_Buffer = new byte[BufferSize]; - m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); - m_Encoding = Utility.UTF8WithEncoding; - } - - protected virtual int BufferSize => 64 * 1024; - - public long Position => m_Position + m_Index; - - public Stream UnderlyingStream - { - get - { - if (m_Index > 0) - Flush(); - - return m_File; - } - } - - public void Flush() - { - if (m_Index > 0) - { - m_Position += m_Index; - - m_File.Write(m_Buffer, 0, m_Index); - m_Index = 0; - } - } - - public void Close() - { - if (m_Index > 0) - Flush(); - - m_File.Close(); - } - - public void WriteEncodedInt(int value) - { - var v = (uint)value; - - while (v >= 0x80) - { - if (m_Index + 1 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index++] = (byte)(v | 0x80); - v >>= 7; - } - - if (m_Index + 1 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index++] = (byte)v; - } - - internal void InternalWriteString(string value) - { - var length = m_Encoding.GetByteCount(value); - - WriteEncodedInt(length); - - if (m_CharacterBuffer == null) - { - m_CharacterBuffer = new byte[LargeByteBufferSize]; - m_MaxBufferChars = LargeByteBufferSize / m_Encoding.GetMaxByteCount(1); - } - - if (length > LargeByteBufferSize) - { - var current = 0; - var charsLeft = value.Length; - - while (charsLeft > 0) - { - var charCount = charsLeft > m_MaxBufferChars ? m_MaxBufferChars : charsLeft; - var byteLength = m_Encoding.GetBytes(value, current, charCount, m_CharacterBuffer, 0); - - if (m_Index + byteLength > m_Buffer.Length) - Flush(); - - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); - m_Index += byteLength; - - current += charCount; - charsLeft -= charCount; - } - } - else - { - var byteLength = m_Encoding.GetBytes(value, 0, value.Length, m_CharacterBuffer, 0); - - if (m_Index + byteLength > m_Buffer.Length) - Flush(); - - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); - m_Index += byteLength; - } - } - - public void Write(string value) - { - if (m_PrefixStrings) - { - if (value == null) - { - if (m_Index + 1 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index++] = 0; - } - else - { - if (m_Index + 1 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index++] = 1; - - InternalWriteString(value); - } - } - else - { - InternalWriteString(value); - } - } - - public void Write(DateTime value) - { - Write(value.Ticks); - } - - public void Write(DateTimeOffset value) - { - Write(value.Ticks); - Write(value.Offset.Ticks); - } - - public void WriteDeltaTime(DateTime value) - { - var ticks = value.Ticks; - var now = DateTime.UtcNow.Ticks; - - TimeSpan d; - - try - { - d = new TimeSpan(ticks - now); - } - catch - { - d = TimeSpan.MaxValue; - } - - Write(d); - } - - public void Write(IPAddress value) - { - Write(Utility.GetLongAddressValue(value)); - } - - public void Write(TimeSpan value) - { - Write(value.Ticks); - } - - public void Write(decimal value) - { - var bits = decimal.GetBits(value); - - for (var i = 0; i < bits.Length; ++i) - Write(bits[i]); - } - - public void Write(long value) - { - if (m_Index + 8 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Buffer[m_Index + 4] = (byte)(value >> 32); - m_Buffer[m_Index + 5] = (byte)(value >> 40); - m_Buffer[m_Index + 6] = (byte)(value >> 48); - m_Buffer[m_Index + 7] = (byte)(value >> 56); - m_Index += 8; - } - - public void Write(ulong value) - { - if (m_Index + 8 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Buffer[m_Index + 4] = (byte)(value >> 32); - m_Buffer[m_Index + 5] = (byte)(value >> 40); - m_Buffer[m_Index + 6] = (byte)(value >> 48); - m_Buffer[m_Index + 7] = (byte)(value >> 56); - m_Index += 8; - } - - public void Write(int value) - { - if (m_Index + 4 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Index += 4; - } - - public void Write(uint value) - { - if (m_Index + 4 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Index += 4; - } - - public void Write(short value) - { - if (m_Index + 2 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Index += 2; - } - - public void Write(ushort value) - { - if (m_Index + 2 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Index += 2; - } - - public unsafe void Write(double value) - { - if (m_Index + 8 > m_Buffer.Length) - Flush(); - - fixed (byte* pBuffer = m_Buffer) - { - *(double*)(pBuffer + m_Index) = value; - } - - m_Index += 8; - } - - public unsafe void Write(float value) - { - if (m_Index + 4 > m_Buffer.Length) - Flush(); - - fixed (byte* pBuffer = m_Buffer) - { - *(float*)(pBuffer + m_Index) = value; - } - - m_Index += 4; - } - - public void Write(char value) - { - if (m_Index + 8 > m_Buffer.Length) - Flush(); - - m_SingleCharBuffer[0] = value; - - var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, m_Index); - m_Index += byteCount; - } - - public void Write(byte value) - { - if (m_Index + 1 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index++] = value; - } - - public void Write(byte[] value) - { - Write(value, value.Length); - } - - public void Write(byte[] value, int length) - { - if (m_Index + length > m_Buffer.Length) - Flush(); - - Buffer.BlockCopy(value, 0, m_Buffer, m_Index, length); - m_Index += length; - } - - public void Write(sbyte value) - { - if (m_Index + 1 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index++] = (byte)value; - } - - public void Write(bool value) - { - if (m_Index + 1 > m_Buffer.Length) - Flush(); - - m_Buffer[m_Index++] = (byte)(value ? 1 : 0); - } - - public void Write(Point3D value) - { - Write(value.m_X); - Write(value.m_Y); - Write(value.m_Z); - } - - public void Write(Point2D value) - { - Write(value.m_X); - Write(value.m_Y); - } - - public void Write(Rectangle2D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Rectangle3D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Map value) - { - if (value != null) - Write((byte)value.MapIndex); - else - Write((byte)0xFF); - } - - public void Write(Race value) - { - if (value != null) - Write((byte)value.RaceIndex); - else - Write((byte)0xFF); - } - - public void WriteEntity(IEntity value) - { - if (value?.Deleted != false) - Write(Serial.MinusOne); - else - Write(value.Serial); - } - - public void Write(Item value) - { - if (value?.Deleted != false) - Write(Serial.MinusOne); - else - Write(value.Serial); - } - - public void Write(Mobile value) - { - if (value?.Deleted != false) - Write(Serial.MinusOne); - else - Write(value.Serial); - } - - public void Write(BaseGuild value) - { - if (value == null) - Write(0); - else - Write(value.Serial); - } - - public void WriteItem(T value) where T : Item - { - Write(value); - } - - public void WriteMobile(T value) where T : Mobile - { - Write(value); - } - - public void WriteGuild(T value) where T : BaseGuild - { - Write(value); - } - - public void Write(List list) - { - WriteItemList(list); - } - - public void Write(List list, bool tidy) - { - WriteItemList(list, tidy); - } - - public void WriteItemList(List list) where T : Item - { - WriteItemList(list, false); - } - - public void WriteItemList(List list, bool tidy) where T : Item - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i]?.Deleted != false) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(item => item.Deleted); - - Write(set.Count); - - foreach (var item in set) Write(item); - } - - public void WriteItemSet(HashSet set) where T : Item - { - WriteItemSet(set, false); - } - - public void WriteItemSet(HashSet set, bool tidy) where T : Item - { - if (tidy) set.RemoveWhere(item => item.Deleted); - - Write(set.Count); - - foreach (var item in set) Write(item); - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void WriteMobileList(List list) where T : Mobile - { - WriteMobileList(list, false); - } - - public void WriteMobileList(List list, bool tidy) where T : Mobile - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(mobile => mobile.Deleted); - - Write(set.Count); - - foreach (var mob in set) Write(mob); - } - - public void WriteMobileSet(HashSet set) where T : Mobile - { - WriteMobileSet(set, false); - } - - public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile - { - if (tidy) set.RemoveWhere(mob => mob.Deleted); - - Write(set.Count); - - foreach (var mob in set) Write(mob); - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Disbanded) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void WriteGuildList(List list) where T : BaseGuild - { - WriteGuildList(list, false); - } - - public void WriteGuildList(List list, bool tidy) where T : BaseGuild - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Disbanded) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(guild => guild.Disbanded); - - Write(set.Count); - - foreach (var guild in set) Write(guild); - } - - public void WriteGuildSet(HashSet set) where T : BaseGuild - { - WriteGuildSet(set, false); - } - - public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild - { - if (tidy) set.RemoveWhere(guild => guild.Disbanded); - - Write(set.Count); - - foreach (var guild in set) Write(guild); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: BinaryFileWriter.cs * + * Created: 2019/12/30 - Updated: 2020/01/18 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using Server.Guilds; + +namespace Server +{ + public class BinaryFileWriter : IGenericWriter + { + private const int LargeByteBufferSize = 256; + + private readonly byte[] m_Buffer; + + private readonly Encoding m_Encoding; + private readonly Stream m_File; + private readonly bool m_PrefixStrings; + + private readonly char[] m_SingleCharBuffer = new char[1]; + + private byte[] m_CharacterBuffer; + + private int m_Index; + private int m_MaxBufferChars; + + private long m_Position; + + public BinaryFileWriter(Stream strm, bool prefixStr) + { + m_PrefixStrings = prefixStr; + m_Encoding = Utility.UTF8; + m_Buffer = new byte[BufferSize]; + m_File = strm; + } + + public BinaryFileWriter(string filename, bool prefixStr) + { + m_PrefixStrings = prefixStr; + m_Buffer = new byte[BufferSize]; + m_File = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None); + m_Encoding = Utility.UTF8WithEncoding; + } + + protected virtual int BufferSize => 64 * 1024; + + public Stream UnderlyingStream + { + get + { + if (m_Index > 0) + Flush(); + + return m_File; + } + } + + public long Position => m_Position + m_Index; + + public void Close() + { + if (m_Index > 0) + Flush(); + + m_File.Close(); + } + + public void WriteEncodedInt(int value) + { + var v = (uint)value; + + while (v >= 0x80) + { + if (m_Index + 1 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index++] = (byte)(v | 0x80); + v >>= 7; + } + + if (m_Index + 1 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index++] = (byte)v; + } + + public void Write(string value) + { + if (m_PrefixStrings) + { + if (value == null) + { + if (m_Index + 1 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index++] = 0; + } + else + { + if (m_Index + 1 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index++] = 1; + + InternalWriteString(value); + } + } + else + { + InternalWriteString(value); + } + } + + public void Write(DateTime value) + { + Write(value.Ticks); + } + + public void Write(DateTimeOffset value) + { + Write(value.Ticks); + Write(value.Offset.Ticks); + } + + public void WriteDeltaTime(DateTime value) + { + var ticks = value.Ticks; + var now = DateTime.UtcNow.Ticks; + + TimeSpan d; + + try + { + d = new TimeSpan(ticks - now); + } + catch + { + d = TimeSpan.MaxValue; + } + + Write(d); + } + + public void Write(IPAddress value) + { + Write(Utility.GetLongAddressValue(value)); + } + + public void Write(TimeSpan value) + { + Write(value.Ticks); + } + + public void Write(decimal value) + { + var bits = decimal.GetBits(value); + + for (var i = 0; i < bits.Length; ++i) + Write(bits[i]); + } + + public void Write(long value) + { + if (m_Index + 8 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); + m_Buffer[m_Index + 4] = (byte)(value >> 32); + m_Buffer[m_Index + 5] = (byte)(value >> 40); + m_Buffer[m_Index + 6] = (byte)(value >> 48); + m_Buffer[m_Index + 7] = (byte)(value >> 56); + m_Index += 8; + } + + public void Write(ulong value) + { + if (m_Index + 8 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); + m_Buffer[m_Index + 4] = (byte)(value >> 32); + m_Buffer[m_Index + 5] = (byte)(value >> 40); + m_Buffer[m_Index + 6] = (byte)(value >> 48); + m_Buffer[m_Index + 7] = (byte)(value >> 56); + m_Index += 8; + } + + public void Write(int value) + { + if (m_Index + 4 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); + m_Index += 4; + } + + public void Write(uint value) + { + if (m_Index + 4 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Buffer[m_Index + 2] = (byte)(value >> 16); + m_Buffer[m_Index + 3] = (byte)(value >> 24); + m_Index += 4; + } + + public void Write(short value) + { + if (m_Index + 2 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Index += 2; + } + + public void Write(ushort value) + { + if (m_Index + 2 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index] = (byte)value; + m_Buffer[m_Index + 1] = (byte)(value >> 8); + m_Index += 2; + } + + public unsafe void Write(double value) + { + if (m_Index + 8 > m_Buffer.Length) + Flush(); + + fixed (byte* pBuffer = m_Buffer) + { + *(double*)(pBuffer + m_Index) = value; + } + + m_Index += 8; + } + + public unsafe void Write(float value) + { + if (m_Index + 4 > m_Buffer.Length) + Flush(); + + fixed (byte* pBuffer = m_Buffer) + { + *(float*)(pBuffer + m_Index) = value; + } + + m_Index += 4; + } + + public void Write(char value) + { + if (m_Index + 8 > m_Buffer.Length) + Flush(); + + m_SingleCharBuffer[0] = value; + + var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, m_Index); + m_Index += byteCount; + } + + public void Write(byte value) + { + if (m_Index + 1 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index++] = value; + } + + public void Write(byte[] value) + { + Write(value, value.Length); + } + + public void Write(byte[] value, int length) + { + if (m_Index + length > m_Buffer.Length) + Flush(); + + Buffer.BlockCopy(value, 0, m_Buffer, m_Index, length); + m_Index += length; + } + + public void Write(sbyte value) + { + if (m_Index + 1 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index++] = (byte)value; + } + + public void Write(bool value) + { + if (m_Index + 1 > m_Buffer.Length) + Flush(); + + m_Buffer[m_Index++] = (byte)(value ? 1 : 0); + } + + public void Write(Point3D value) + { + Write(value.m_X); + Write(value.m_Y); + Write(value.m_Z); + } + + public void Write(Point2D value) + { + Write(value.m_X); + Write(value.m_Y); + } + + public void Write(Rectangle2D value) + { + Write(value.Start); + Write(value.End); + } + + public void Write(Rectangle3D value) + { + Write(value.Start); + Write(value.End); + } + + public void Write(Map value) + { + if (value != null) + Write((byte)value.MapIndex); + else + Write((byte)0xFF); + } + + public void Write(Race value) + { + if (value != null) + Write((byte)value.RaceIndex); + else + Write((byte)0xFF); + } + + public void WriteEntity(IEntity value) + { + if (value?.Deleted != false) + Write(Serial.MinusOne); + else + Write(value.Serial); + } + + public void Write(Item value) + { + if (value?.Deleted != false) + Write(Serial.MinusOne); + else + Write(value.Serial); + } + + public void Write(Mobile value) + { + if (value?.Deleted != false) + Write(Serial.MinusOne); + else + Write(value.Serial); + } + + public void Write(BaseGuild value) + { + if (value == null) + Write(0); + else + Write(value.Serial); + } + + public void WriteItem(T value) where T : Item + { + Write(value); + } + + public void WriteMobile(T value) where T : Mobile + { + Write(value); + } + + public void WriteGuild(T value) where T : BaseGuild + { + Write(value); + } + + public void Write(List list) + { + WriteItemList(list); + } + + public void Write(List list, bool tidy) + { + WriteItemList(list, tidy); + } + + public void WriteItemList(List list) where T : Item + { + WriteItemList(list, false); + } + + public void WriteItemList(List list, bool tidy) where T : Item + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i]?.Deleted != false) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(item => item.Deleted); + + Write(set.Count); + + foreach (var item in set) Write(item); + } + + public void WriteItemSet(HashSet set) where T : Item + { + WriteItemSet(set, false); + } + + public void WriteItemSet(HashSet set, bool tidy) where T : Item + { + if (tidy) set.RemoveWhere(item => item.Deleted); + + Write(set.Count); + + foreach (var item in set) Write(item); + } + + public void Write(List list) + { + Write(list, false); + } + + public void Write(List list, bool tidy) + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void WriteMobileList(List list) where T : Mobile + { + WriteMobileList(list, false); + } + + public void WriteMobileList(List list, bool tidy) where T : Mobile + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(mobile => mobile.Deleted); + + Write(set.Count); + + foreach (var mob in set) Write(mob); + } + + public void WriteMobileSet(HashSet set) where T : Mobile + { + WriteMobileSet(set, false); + } + + public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile + { + if (tidy) set.RemoveWhere(mob => mob.Deleted); + + Write(set.Count); + + foreach (var mob in set) Write(mob); + } + + public void Write(List list) + { + Write(list, false); + } + + public void Write(List list, bool tidy) + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Disbanded) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void WriteGuildList(List list) where T : BaseGuild + { + WriteGuildList(list, false); + } + + public void WriteGuildList(List list, bool tidy) where T : BaseGuild + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Disbanded) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(guild => guild.Disbanded); + + Write(set.Count); + + foreach (var guild in set) Write(guild); + } + + public void WriteGuildSet(HashSet set) where T : BaseGuild + { + WriteGuildSet(set, false); + } + + public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild + { + if (tidy) set.RemoveWhere(guild => guild.Disbanded); + + Write(set.Count); + + foreach (var guild in set) Write(guild); + } + + public void Flush() + { + if (m_Index > 0) + { + m_Position += m_Index; + + m_File.Write(m_Buffer, 0, m_Index); + m_Index = 0; + } + } + + internal void InternalWriteString(string value) + { + var length = m_Encoding.GetByteCount(value); + + WriteEncodedInt(length); + + if (m_CharacterBuffer == null) + { + m_CharacterBuffer = new byte[LargeByteBufferSize]; + m_MaxBufferChars = LargeByteBufferSize / m_Encoding.GetMaxByteCount(1); + } + + if (length > LargeByteBufferSize) + { + var current = 0; + var charsLeft = value.Length; + + while (charsLeft > 0) + { + var charCount = charsLeft > m_MaxBufferChars ? m_MaxBufferChars : charsLeft; + var byteLength = m_Encoding.GetBytes(value, current, charCount, m_CharacterBuffer, 0); + + if (m_Index + byteLength > m_Buffer.Length) + Flush(); + + Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); + m_Index += byteLength; + + current += charCount; + charsLeft -= charCount; + } + } + else + { + var byteLength = m_Encoding.GetBytes(value, 0, value.Length, m_CharacterBuffer, 0); + + if (m_Index + byteLength > m_Buffer.Length) + Flush(); + + Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); + m_Index += byteLength; + } + } + } +} diff --git a/Projects/Server/Serialization/BufferWriter.cs b/Projects/Server/Serialization/BufferWriter.cs index 9b765a6bb..c451dd599 100644 --- a/Projects/Server/Serialization/BufferWriter.cs +++ b/Projects/Server/Serialization/BufferWriter.cs @@ -1,642 +1,640 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: BufferWriter.cs * - * Created: 2019/12/30 - Updated: 2020/01/18 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; -using Server.Guilds; - -namespace Server -{ - public class BufferWriter : IGenericWriter - { - private const int LargeByteBufferSize = 256; - - private byte[] m_Buffer; - - private byte[] m_CharacterBuffer; - - private readonly Encoding m_Encoding; - - private int m_Index; - private int m_MaxBufferChars; - - private readonly char[] m_SingleCharBuffer = new char[1]; - private readonly bool m_PrefixStrings; - - public BufferWriter(bool prefixStr) - { - m_PrefixStrings = prefixStr; - m_Encoding = Utility.UTF8; - m_Buffer = new byte[BufferSize]; - } - - protected virtual int BufferSize => m_Buffer?.Length ?? 64; - - public long Position => m_Index; - - public byte[] Data => m_Buffer; - - public void Close() - { - } - - public void Flush() - { - m_Index = 0; - } - - private void Expand() - { - var newBuffer = new byte[BufferSize * 2]; - Buffer.BlockCopy(m_Buffer, 0, newBuffer, 0, m_Buffer.Length); - m_Buffer = newBuffer; - } - - public void WriteEncodedInt(int value) - { - var v = (uint)value; - - while (v >= 0x80) - { - if (m_Index + 1 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index++] = (byte)(v | 0x80); - v >>= 7; - } - - if (m_Index + 1 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index++] = (byte)v; - } - - internal void InternalWriteString(string value) - { - var length = m_Encoding.GetByteCount(value); - - WriteEncodedInt(length); - - if (m_CharacterBuffer == null) - { - m_CharacterBuffer = new byte[LargeByteBufferSize]; - m_MaxBufferChars = LargeByteBufferSize / m_Encoding.GetMaxByteCount(1); - } - - if (length > LargeByteBufferSize) - { - var current = 0; - var charsLeft = value.Length; - - while (charsLeft > 0) - { - var charCount = charsLeft > m_MaxBufferChars ? m_MaxBufferChars : charsLeft; - var byteLength = m_Encoding.GetBytes(value, current, charCount, m_CharacterBuffer, 0); - - if (m_Index + byteLength > m_Buffer.Length) - Expand(); - - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); - m_Index += byteLength; - - current += charCount; - charsLeft -= charCount; - } - } - else - { - var byteLength = m_Encoding.GetBytes(value, 0, value.Length, m_CharacterBuffer, 0); - - if (m_Index + byteLength > m_Buffer.Length) - Expand(); - - Buffer.BlockCopy(m_CharacterBuffer, 0, m_Buffer, m_Index, byteLength); - m_Index += byteLength; - } - } - - public void Write(string value) - { - if (m_PrefixStrings) - { - if (value == null) - { - if (m_Index + 1 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index++] = 0; - } - else - { - if (m_Index + 1 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index++] = 1; - - InternalWriteString(value); - } - } - else - { - InternalWriteString(value); - } - } - - public void Write(DateTime value) - { - Write(value.Ticks); - } - - public void Write(DateTimeOffset value) - { - Write(value.Ticks); - Write(value.Offset.Ticks); - } - - public void WriteDeltaTime(DateTime value) - { - var ticks = value.Ticks; - var now = DateTime.UtcNow.Ticks; - - TimeSpan d; - - try - { - d = new TimeSpan(ticks - now); - } - catch - { - d = TimeSpan.MaxValue; - } - - Write(d); - } - - public void Write(IPAddress value) - { - Write(Utility.GetLongAddressValue(value)); - } - - public void Write(TimeSpan value) - { - Write(value.Ticks); - } - - public void Write(decimal value) - { - var bits = decimal.GetBits(value); - - for (var i = 0; i < bits.Length; ++i) - Write(bits[i]); - } - - public void Write(long value) - { - if (m_Index + 8 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Buffer[m_Index + 4] = (byte)(value >> 32); - m_Buffer[m_Index + 5] = (byte)(value >> 40); - m_Buffer[m_Index + 6] = (byte)(value >> 48); - m_Buffer[m_Index + 7] = (byte)(value >> 56); - m_Index += 8; - } - - public void Write(ulong value) - { - if (m_Index + 8 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Buffer[m_Index + 4] = (byte)(value >> 32); - m_Buffer[m_Index + 5] = (byte)(value >> 40); - m_Buffer[m_Index + 6] = (byte)(value >> 48); - m_Buffer[m_Index + 7] = (byte)(value >> 56); - m_Index += 8; - } - - public void Write(int value) - { - if (m_Index + 4 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Index += 4; - } - - public void Write(uint value) - { - if (m_Index + 4 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Buffer[m_Index + 2] = (byte)(value >> 16); - m_Buffer[m_Index + 3] = (byte)(value >> 24); - m_Index += 4; - } - - public void Write(short value) - { - if (m_Index + 2 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Index += 2; - } - - public void Write(ushort value) - { - if (m_Index + 2 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index] = (byte)value; - m_Buffer[m_Index + 1] = (byte)(value >> 8); - m_Index += 2; - } - - public unsafe void Write(double value) - { - if (m_Index + 8 > m_Buffer.Length) - Expand(); - - fixed (byte* pBuffer = m_Buffer) - { - *(double*)(pBuffer + m_Index) = value; - } - - m_Index += 8; - } - - public unsafe void Write(float value) - { - if (m_Index + 4 > m_Buffer.Length) - Expand(); - - fixed (byte* pBuffer = m_Buffer) - { - *(float*)(pBuffer + m_Index) = value; - } - - m_Index += 4; - } - - public void Write(char value) - { - if (m_Index + 8 > m_Buffer.Length) - Expand(); - - m_SingleCharBuffer[0] = value; - - var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, m_Buffer, m_Index); - m_Index += byteCount; - } - - public void Write(byte value) - { - if (m_Index + 1 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index++] = value; - } - - public void Write(byte[] value) - { - Write(value, value.Length); - } - - public void Write(byte[] value, int length) - { - if (m_Index + length > m_Buffer.Length) - Expand(); - - Buffer.BlockCopy(value, 0, m_Buffer, m_Index, length); - m_Index += length; - } - - public void Write(sbyte value) - { - if (m_Index + 1 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index++] = (byte)value; - } - - public void Write(bool value) - { - if (m_Index + 1 > m_Buffer.Length) - Expand(); - - m_Buffer[m_Index++] = (byte)(value ? 1 : 0); - } - - public void Write(Point3D value) - { - Write(value.m_X); - Write(value.m_Y); - Write(value.m_Z); - } - - public void Write(Point2D value) - { - Write(value.m_X); - Write(value.m_Y); - } - - public void Write(Rectangle2D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Rectangle3D value) - { - Write(value.Start); - Write(value.End); - } - - public void Write(Map value) - { - if (value != null) - Write((byte)value.MapIndex); - else - Write((byte)0xFF); - } - - public void Write(Race value) - { - if (value != null) - Write((byte)value.RaceIndex); - else - Write((byte)0xFF); - } - - public void WriteEntity(IEntity value) - { - Write(value?.Deleted != false ? Serial.MinusOne : value.Serial); - } - - public void Write(Item value) - { - Write(value?.Deleted != false ? Serial.MinusOne : value.Serial); - } - - public void Write(Mobile value) - { - Write(value?.Deleted != false ? Serial.MinusOne : value.Serial); - } - - public void Write(BaseGuild value) - { - if (value == null) - Write(0); - else - Write(value.Serial); - } - - public void WriteItem(T value) where T : Item - { - Write(value); - } - - public void WriteMobile(T value) where T : Mobile - { - Write(value); - } - - public void WriteGuild(T value) where T : BaseGuild - { - Write(value); - } - - public void Write(List list) - { - WriteItemList(list); - } - - public void Write(List list, bool tidy) - { - WriteItemList(list, tidy); - } - - public void WriteItemList(List list) where T : Item - { - WriteItemList(list, false); - } - - public void WriteItemList(List list, bool tidy) where T : Item - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(item => item.Deleted); - - Write(set.Count); - - foreach (var item in set) Write(item); - } - - public void WriteItemSet(HashSet set) where T : Item - { - WriteItemSet(set, false); - } - - public void WriteItemSet(HashSet set, bool tidy) where T : Item - { - if (tidy) set.RemoveWhere(item => item.Deleted); - - Write(set.Count); - - foreach (var item in set) Write(item); - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void WriteMobileList(List list) where T : Mobile - { - WriteMobileList(list, false); - } - - public void WriteMobileList(List list, bool tidy) where T : Mobile - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Deleted) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(mobile => mobile.Deleted); - - Write(set.Count); - - foreach (var mob in set) Write(mob); - } - - public void WriteMobileSet(HashSet set) where T : Mobile - { - WriteMobileSet(set, false); - } - - public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile - { - if (tidy) set.RemoveWhere(mob => mob.Deleted); - - Write(set.Count); - - foreach (var mob in set) Write(mob); - } - - public void Write(List list) - { - Write(list, false); - } - - public void Write(List list, bool tidy) - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Disbanded) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void WriteGuildList(List list) where T : BaseGuild - { - WriteGuildList(list, false); - } - - public void WriteGuildList(List list, bool tidy) where T : BaseGuild - { - if (tidy) - for (var i = 0; i < list.Count;) - if (list[i].Disbanded) - list.RemoveAt(i); - else - ++i; - - Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - Write(list[i]); - } - - public void Write(HashSet set) - { - Write(set, false); - } - - public void Write(HashSet set, bool tidy) - { - if (tidy) set.RemoveWhere(guild => guild.Disbanded); - - Write(set.Count); - - foreach (var guild in set) Write(guild); - } - - public void WriteGuildSet(HashSet set) where T : BaseGuild - { - WriteGuildSet(set, false); - } - - public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild - { - if (tidy) set.RemoveWhere(guild => guild.Disbanded); - - Write(set.Count); - - foreach (var guild in set) Write(guild); - } - - public void WriteTo(IGenericWriter writer) - { - writer.Write(Data, (int)Position); - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: BufferWriter.cs * + * Created: 2019/12/30 - Updated: 2020/01/18 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using Server.Guilds; + +namespace Server +{ + public class BufferWriter : IGenericWriter + { + private const int LargeByteBufferSize = 256; + + private readonly Encoding m_Encoding; + private readonly bool m_PrefixStrings; + + private readonly char[] m_SingleCharBuffer = new char[1]; + + private byte[] m_CharacterBuffer; + + private int m_Index; + private int m_MaxBufferChars; + + public BufferWriter(bool prefixStr) + { + m_PrefixStrings = prefixStr; + m_Encoding = Utility.UTF8; + Data = new byte[BufferSize]; + } + + protected virtual int BufferSize => Data?.Length ?? 64; + + public byte[] Data { get; private set; } + + public long Position => m_Index; + + public void Close() + { + } + + public void WriteEncodedInt(int value) + { + var v = (uint)value; + + while (v >= 0x80) + { + if (m_Index + 1 > Data.Length) + Expand(); + + Data[m_Index++] = (byte)(v | 0x80); + v >>= 7; + } + + if (m_Index + 1 > Data.Length) + Expand(); + + Data[m_Index++] = (byte)v; + } + + public void Write(string value) + { + if (m_PrefixStrings) + { + if (value == null) + { + if (m_Index + 1 > Data.Length) + Expand(); + + Data[m_Index++] = 0; + } + else + { + if (m_Index + 1 > Data.Length) + Expand(); + + Data[m_Index++] = 1; + + InternalWriteString(value); + } + } + else + { + InternalWriteString(value); + } + } + + public void Write(DateTime value) + { + Write(value.Ticks); + } + + public void Write(DateTimeOffset value) + { + Write(value.Ticks); + Write(value.Offset.Ticks); + } + + public void WriteDeltaTime(DateTime value) + { + var ticks = value.Ticks; + var now = DateTime.UtcNow.Ticks; + + TimeSpan d; + + try + { + d = new TimeSpan(ticks - now); + } + catch + { + d = TimeSpan.MaxValue; + } + + Write(d); + } + + public void Write(IPAddress value) + { + Write(Utility.GetLongAddressValue(value)); + } + + public void Write(TimeSpan value) + { + Write(value.Ticks); + } + + public void Write(decimal value) + { + var bits = decimal.GetBits(value); + + for (var i = 0; i < bits.Length; ++i) + Write(bits[i]); + } + + public void Write(long value) + { + if (m_Index + 8 > Data.Length) + Expand(); + + Data[m_Index] = (byte)value; + Data[m_Index + 1] = (byte)(value >> 8); + Data[m_Index + 2] = (byte)(value >> 16); + Data[m_Index + 3] = (byte)(value >> 24); + Data[m_Index + 4] = (byte)(value >> 32); + Data[m_Index + 5] = (byte)(value >> 40); + Data[m_Index + 6] = (byte)(value >> 48); + Data[m_Index + 7] = (byte)(value >> 56); + m_Index += 8; + } + + public void Write(ulong value) + { + if (m_Index + 8 > Data.Length) + Expand(); + + Data[m_Index] = (byte)value; + Data[m_Index + 1] = (byte)(value >> 8); + Data[m_Index + 2] = (byte)(value >> 16); + Data[m_Index + 3] = (byte)(value >> 24); + Data[m_Index + 4] = (byte)(value >> 32); + Data[m_Index + 5] = (byte)(value >> 40); + Data[m_Index + 6] = (byte)(value >> 48); + Data[m_Index + 7] = (byte)(value >> 56); + m_Index += 8; + } + + public void Write(int value) + { + if (m_Index + 4 > Data.Length) + Expand(); + + Data[m_Index] = (byte)value; + Data[m_Index + 1] = (byte)(value >> 8); + Data[m_Index + 2] = (byte)(value >> 16); + Data[m_Index + 3] = (byte)(value >> 24); + m_Index += 4; + } + + public void Write(uint value) + { + if (m_Index + 4 > Data.Length) + Expand(); + + Data[m_Index] = (byte)value; + Data[m_Index + 1] = (byte)(value >> 8); + Data[m_Index + 2] = (byte)(value >> 16); + Data[m_Index + 3] = (byte)(value >> 24); + m_Index += 4; + } + + public void Write(short value) + { + if (m_Index + 2 > Data.Length) + Expand(); + + Data[m_Index] = (byte)value; + Data[m_Index + 1] = (byte)(value >> 8); + m_Index += 2; + } + + public void Write(ushort value) + { + if (m_Index + 2 > Data.Length) + Expand(); + + Data[m_Index] = (byte)value; + Data[m_Index + 1] = (byte)(value >> 8); + m_Index += 2; + } + + public unsafe void Write(double value) + { + if (m_Index + 8 > Data.Length) + Expand(); + + fixed (byte* pBuffer = Data) + { + *(double*)(pBuffer + m_Index) = value; + } + + m_Index += 8; + } + + public unsafe void Write(float value) + { + if (m_Index + 4 > Data.Length) + Expand(); + + fixed (byte* pBuffer = Data) + { + *(float*)(pBuffer + m_Index) = value; + } + + m_Index += 4; + } + + public void Write(char value) + { + if (m_Index + 8 > Data.Length) + Expand(); + + m_SingleCharBuffer[0] = value; + + var byteCount = m_Encoding.GetBytes(m_SingleCharBuffer, 0, 1, Data, m_Index); + m_Index += byteCount; + } + + public void Write(byte value) + { + if (m_Index + 1 > Data.Length) + Expand(); + + Data[m_Index++] = value; + } + + public void Write(byte[] value) + { + Write(value, value.Length); + } + + public void Write(byte[] value, int length) + { + if (m_Index + length > Data.Length) + Expand(); + + Buffer.BlockCopy(value, 0, Data, m_Index, length); + m_Index += length; + } + + public void Write(sbyte value) + { + if (m_Index + 1 > Data.Length) + Expand(); + + Data[m_Index++] = (byte)value; + } + + public void Write(bool value) + { + if (m_Index + 1 > Data.Length) + Expand(); + + Data[m_Index++] = (byte)(value ? 1 : 0); + } + + public void Write(Point3D value) + { + Write(value.m_X); + Write(value.m_Y); + Write(value.m_Z); + } + + public void Write(Point2D value) + { + Write(value.m_X); + Write(value.m_Y); + } + + public void Write(Rectangle2D value) + { + Write(value.Start); + Write(value.End); + } + + public void Write(Rectangle3D value) + { + Write(value.Start); + Write(value.End); + } + + public void Write(Map value) + { + if (value != null) + Write((byte)value.MapIndex); + else + Write((byte)0xFF); + } + + public void Write(Race value) + { + if (value != null) + Write((byte)value.RaceIndex); + else + Write((byte)0xFF); + } + + public void WriteEntity(IEntity value) + { + Write(value?.Deleted != false ? Serial.MinusOne : value.Serial); + } + + public void Write(Item value) + { + Write(value?.Deleted != false ? Serial.MinusOne : value.Serial); + } + + public void Write(Mobile value) + { + Write(value?.Deleted != false ? Serial.MinusOne : value.Serial); + } + + public void Write(BaseGuild value) + { + if (value == null) + Write(0); + else + Write(value.Serial); + } + + public void WriteItem(T value) where T : Item + { + Write(value); + } + + public void WriteMobile(T value) where T : Mobile + { + Write(value); + } + + public void WriteGuild(T value) where T : BaseGuild + { + Write(value); + } + + public void Write(List list) + { + WriteItemList(list); + } + + public void Write(List list, bool tidy) + { + WriteItemList(list, tidy); + } + + public void WriteItemList(List list) where T : Item + { + WriteItemList(list, false); + } + + public void WriteItemList(List list, bool tidy) where T : Item + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(item => item.Deleted); + + Write(set.Count); + + foreach (var item in set) Write(item); + } + + public void WriteItemSet(HashSet set) where T : Item + { + WriteItemSet(set, false); + } + + public void WriteItemSet(HashSet set, bool tidy) where T : Item + { + if (tidy) set.RemoveWhere(item => item.Deleted); + + Write(set.Count); + + foreach (var item in set) Write(item); + } + + public void Write(List list) + { + Write(list, false); + } + + public void Write(List list, bool tidy) + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void WriteMobileList(List list) where T : Mobile + { + WriteMobileList(list, false); + } + + public void WriteMobileList(List list, bool tidy) where T : Mobile + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Deleted) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(mobile => mobile.Deleted); + + Write(set.Count); + + foreach (var mob in set) Write(mob); + } + + public void WriteMobileSet(HashSet set) where T : Mobile + { + WriteMobileSet(set, false); + } + + public void WriteMobileSet(HashSet set, bool tidy) where T : Mobile + { + if (tidy) set.RemoveWhere(mob => mob.Deleted); + + Write(set.Count); + + foreach (var mob in set) Write(mob); + } + + public void Write(List list) + { + Write(list, false); + } + + public void Write(List list, bool tidy) + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Disbanded) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void WriteGuildList(List list) where T : BaseGuild + { + WriteGuildList(list, false); + } + + public void WriteGuildList(List list, bool tidy) where T : BaseGuild + { + if (tidy) + for (var i = 0; i < list.Count;) + if (list[i].Disbanded) + list.RemoveAt(i); + else + ++i; + + Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + Write(list[i]); + } + + public void Write(HashSet set) + { + Write(set, false); + } + + public void Write(HashSet set, bool tidy) + { + if (tidy) set.RemoveWhere(guild => guild.Disbanded); + + Write(set.Count); + + foreach (var guild in set) Write(guild); + } + + public void WriteGuildSet(HashSet set) where T : BaseGuild + { + WriteGuildSet(set, false); + } + + public void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild + { + if (tidy) set.RemoveWhere(guild => guild.Disbanded); + + Write(set.Count); + + foreach (var guild in set) Write(guild); + } + + public void Flush() + { + m_Index = 0; + } + + private void Expand() + { + var newBuffer = new byte[BufferSize * 2]; + Buffer.BlockCopy(Data, 0, newBuffer, 0, Data.Length); + Data = newBuffer; + } + + internal void InternalWriteString(string value) + { + var length = m_Encoding.GetByteCount(value); + + WriteEncodedInt(length); + + if (m_CharacterBuffer == null) + { + m_CharacterBuffer = new byte[LargeByteBufferSize]; + m_MaxBufferChars = LargeByteBufferSize / m_Encoding.GetMaxByteCount(1); + } + + if (length > LargeByteBufferSize) + { + var current = 0; + var charsLeft = value.Length; + + while (charsLeft > 0) + { + var charCount = charsLeft > m_MaxBufferChars ? m_MaxBufferChars : charsLeft; + var byteLength = m_Encoding.GetBytes(value, current, charCount, m_CharacterBuffer, 0); + + if (m_Index + byteLength > Data.Length) + Expand(); + + Buffer.BlockCopy(m_CharacterBuffer, 0, Data, m_Index, byteLength); + m_Index += byteLength; + + current += charCount; + charsLeft -= charCount; + } + } + else + { + var byteLength = m_Encoding.GetBytes(value, 0, value.Length, m_CharacterBuffer, 0); + + if (m_Index + byteLength > Data.Length) + Expand(); + + Buffer.BlockCopy(m_CharacterBuffer, 0, Data, m_Index, byteLength); + m_Index += byteLength; + } + } + + public void WriteTo(IGenericWriter writer) + { + writer.Write(Data, (int)Position); + } + } +} diff --git a/Projects/Server/Serialization/IGenericReader.cs b/Projects/Server/Serialization/IGenericReader.cs index 8bff436f6..05807dcca 100644 --- a/Projects/Server/Serialization/IGenericReader.cs +++ b/Projects/Server/Serialization/IGenericReader.cs @@ -1,78 +1,78 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: IGenericReader.cs * - * Created: 2019/12/30 - Updated: 2020/01/18 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Net; -using Server.Guilds; - -namespace Server -{ - public interface IGenericReader - { - string ReadString(); - DateTime ReadDateTime(); - DateTimeOffset ReadDateTimeOffset(); - TimeSpan ReadTimeSpan(); - DateTime ReadDeltaTime(); - decimal ReadDecimal(); - long ReadLong(); - ulong ReadULong(); - int ReadInt(); - uint ReadUInt(); - short ReadShort(); - ushort ReadUShort(); - double ReadDouble(); - float ReadFloat(); - char ReadChar(); - byte ReadByte(); - sbyte ReadSByte(); - bool ReadBool(); - int ReadEncodedInt(); - IPAddress ReadIPAddress(); - Point3D ReadPoint3D(); - Point2D ReadPoint2D(); - Rectangle2D ReadRect2D(); - Rectangle3D ReadRect3D(); - Map ReadMap(); - IEntity ReadEntity(); - Item ReadItem(); - Mobile ReadMobile(); - BaseGuild ReadGuild(); - T ReadItem() where T : Item; - T ReadMobile() where T : Mobile; - T ReadGuild() where T : BaseGuild; - List ReadStrongItemList(); - List ReadStrongItemList() where T : Item; - List ReadStrongMobileList(); - List ReadStrongMobileList() where T : Mobile; - List ReadStrongGuildList(); - List ReadStrongGuildList() where T : BaseGuild; - HashSet ReadItemSet(); - HashSet ReadItemSet() where T : Item; - HashSet ReadMobileSet(); - HashSet ReadMobileSet() where T : Mobile; - HashSet ReadGuildSet(); - HashSet ReadGuildSet() where T : BaseGuild; - Race ReadRace(); - bool End(); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IGenericReader.cs * + * Created: 2019/12/30 - Updated: 2020/01/18 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Net; +using Server.Guilds; + +namespace Server +{ + public interface IGenericReader + { + string ReadString(); + DateTime ReadDateTime(); + DateTimeOffset ReadDateTimeOffset(); + TimeSpan ReadTimeSpan(); + DateTime ReadDeltaTime(); + decimal ReadDecimal(); + long ReadLong(); + ulong ReadULong(); + int ReadInt(); + uint ReadUInt(); + short ReadShort(); + ushort ReadUShort(); + double ReadDouble(); + float ReadFloat(); + char ReadChar(); + byte ReadByte(); + sbyte ReadSByte(); + bool ReadBool(); + int ReadEncodedInt(); + IPAddress ReadIPAddress(); + Point3D ReadPoint3D(); + Point2D ReadPoint2D(); + Rectangle2D ReadRect2D(); + Rectangle3D ReadRect3D(); + Map ReadMap(); + IEntity ReadEntity(); + Item ReadItem(); + Mobile ReadMobile(); + BaseGuild ReadGuild(); + T ReadItem() where T : Item; + T ReadMobile() where T : Mobile; + T ReadGuild() where T : BaseGuild; + List ReadStrongItemList(); + List ReadStrongItemList() where T : Item; + List ReadStrongMobileList(); + List ReadStrongMobileList() where T : Mobile; + List ReadStrongGuildList(); + List ReadStrongGuildList() where T : BaseGuild; + HashSet ReadItemSet(); + HashSet ReadItemSet() where T : Item; + HashSet ReadMobileSet(); + HashSet ReadMobileSet() where T : Mobile; + HashSet ReadGuildSet(); + HashSet ReadGuildSet() where T : BaseGuild; + Race ReadRace(); + bool End(); + } +} diff --git a/Projects/Server/Serialization/IGenericWriter.cs b/Projects/Server/Serialization/IGenericWriter.cs index 0763165e3..22b7089fe 100644 --- a/Projects/Server/Serialization/IGenericWriter.cs +++ b/Projects/Server/Serialization/IGenericWriter.cs @@ -1,112 +1,112 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: IGenericWriter.cs * - * Created: 2019/12/30 - Updated: 2020/01/18 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Net; -using Server.Guilds; - -namespace Server -{ - public interface IGenericWriter - { - long Position { get; } - - void Close(); - - void Write(string value); - void Write(DateTime value); - void Write(DateTimeOffset value); - void Write(TimeSpan value); - void Write(decimal value); - void Write(long value); - void Write(ulong value); - void Write(int value); - void Write(uint value); - void Write(short value); - void Write(ushort value); - void Write(double value); - void Write(float value); - void Write(char value); - void Write(byte value); - void Write(byte[] value); - void Write(byte[] value, int length); - void Write(sbyte value); - void Write(bool value); - void WriteEncodedInt(int value); - void Write(IPAddress value); - - void WriteDeltaTime(DateTime value); - - void Write(Point3D value); - void Write(Point2D value); - void Write(Rectangle2D value); - void Write(Rectangle3D value); - void Write(Map value); - - void WriteEntity(IEntity value); - void Write(Item value); - void Write(Mobile value); - void Write(BaseGuild value); - - void WriteItem(T value) where T : Item; - void WriteMobile(T value) where T : Mobile; - void WriteGuild(T value) where T : BaseGuild; - - void Write(Race value); - - void Write(List list); - void Write(List list, bool tidy); - - void WriteItemList(List list) where T : Item; - void WriteItemList(List list, bool tidy) where T : Item; - - void Write(HashSet list); - void Write(HashSet list, bool tidy); - - void WriteItemSet(HashSet set) where T : Item; - void WriteItemSet(HashSet set, bool tidy) where T : Item; - - void Write(List list); - void Write(List list, bool tidy); - - void WriteMobileList(List list) where T : Mobile; - void WriteMobileList(List list, bool tidy) where T : Mobile; - - void Write(HashSet list); - void Write(HashSet list, bool tidy); - - void WriteMobileSet(HashSet set) where T : Mobile; - void WriteMobileSet(HashSet set, bool tidy) where T : Mobile; - - void Write(List list); - void Write(List list, bool tidy); - - void WriteGuildList(List list) where T : BaseGuild; - void WriteGuildList(List list, bool tidy) where T : BaseGuild; - - void Write(HashSet list); - void Write(HashSet list, bool tidy); - - void WriteGuildSet(HashSet set) where T : BaseGuild; - void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild; - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: IGenericWriter.cs * + * Created: 2019/12/30 - Updated: 2020/01/18 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Net; +using Server.Guilds; + +namespace Server +{ + public interface IGenericWriter + { + long Position { get; } + + void Close(); + + void Write(string value); + void Write(DateTime value); + void Write(DateTimeOffset value); + void Write(TimeSpan value); + void Write(decimal value); + void Write(long value); + void Write(ulong value); + void Write(int value); + void Write(uint value); + void Write(short value); + void Write(ushort value); + void Write(double value); + void Write(float value); + void Write(char value); + void Write(byte value); + void Write(byte[] value); + void Write(byte[] value, int length); + void Write(sbyte value); + void Write(bool value); + void WriteEncodedInt(int value); + void Write(IPAddress value); + + void WriteDeltaTime(DateTime value); + + void Write(Point3D value); + void Write(Point2D value); + void Write(Rectangle2D value); + void Write(Rectangle3D value); + void Write(Map value); + + void WriteEntity(IEntity value); + void Write(Item value); + void Write(Mobile value); + void Write(BaseGuild value); + + void WriteItem(T value) where T : Item; + void WriteMobile(T value) where T : Mobile; + void WriteGuild(T value) where T : BaseGuild; + + void Write(Race value); + + void Write(List list); + void Write(List list, bool tidy); + + void WriteItemList(List list) where T : Item; + void WriteItemList(List list, bool tidy) where T : Item; + + void Write(HashSet list); + void Write(HashSet list, bool tidy); + + void WriteItemSet(HashSet set) where T : Item; + void WriteItemSet(HashSet set, bool tidy) where T : Item; + + void Write(List list); + void Write(List list, bool tidy); + + void WriteMobileList(List list) where T : Mobile; + void WriteMobileList(List list, bool tidy) where T : Mobile; + + void Write(HashSet list); + void Write(HashSet list, bool tidy); + + void WriteMobileSet(HashSet set) where T : Mobile; + void WriteMobileSet(HashSet set, bool tidy) where T : Mobile; + + void Write(List list); + void Write(List list, bool tidy); + + void WriteGuildList(List list) where T : BaseGuild; + void WriteGuildList(List list, bool tidy) where T : BaseGuild; + + void Write(HashSet list); + void Write(HashSet list, bool tidy); + + void WriteGuildSet(HashSet set) where T : BaseGuild; + void WriteGuildSet(HashSet set, bool tidy) where T : BaseGuild; + } +} diff --git a/Projects/Server/Serialization/ISerializable.cs b/Projects/Server/Serialization/ISerializable.cs index 13a82b068..7369d2829 100644 --- a/Projects/Server/Serialization/ISerializable.cs +++ b/Projects/Server/Serialization/ISerializable.cs @@ -1,32 +1,32 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ISerializable.cs * - * Created: 2019/12/30 - Updated: 2020/01/18 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -namespace Server -{ - public interface ISerializable - { - BufferWriter SaveBuffer { get; } - int TypeRef { get; } - Serial Serial { get; } - void Serialize(); - void Serialize(IGenericWriter writer); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ISerializable.cs * + * Created: 2019/12/30 - Updated: 2020/01/18 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server +{ + public interface ISerializable + { + BufferWriter SaveBuffer { get; } + int TypeRef { get; } + Serial Serial { get; } + void Serialize(); + void Serialize(IGenericWriter writer); + } +} diff --git a/Projects/Server/Skills.cs b/Projects/Server/Skills.cs index bea3b8497..e61095c19 100644 --- a/Projects/Server/Skills.cs +++ b/Projects/Server/Skills.cs @@ -1,870 +1,872 @@ -/*************************************************************************** - * Skills.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Server.Network; - -namespace Server -{ - public delegate TimeSpan SkillUseCallback(Mobile user); - - public enum SkillLock : byte - { - Up = 0, - Down = 1, - Locked = 2 - } - - public enum SkillName - { - Alchemy = 0, - Anatomy = 1, - AnimalLore = 2, - ItemID = 3, - ArmsLore = 4, - Parry = 5, - Begging = 6, - Blacksmith = 7, - Fletching = 8, - Peacemaking = 9, - Camping = 10, - Carpentry = 11, - Cartography = 12, - Cooking = 13, - DetectHidden = 14, - Discordance = 15, - EvalInt = 16, - Healing = 17, - Fishing = 18, - Forensics = 19, - Herding = 20, - Hiding = 21, - Provocation = 22, - Inscribe = 23, - Lockpicking = 24, - Magery = 25, - MagicResist = 26, - Tactics = 27, - Snooping = 28, - Musicianship = 29, - Poisoning = 30, - Archery = 31, - SpiritSpeak = 32, - Stealing = 33, - Tailoring = 34, - AnimalTaming = 35, - TasteID = 36, - Tinkering = 37, - Tracking = 38, - Veterinary = 39, - Swords = 40, - Macing = 41, - Fencing = 42, - Wrestling = 43, - Lumberjacking = 44, - Mining = 45, - Meditation = 46, - Stealth = 47, - RemoveTrap = 48, - Necromancy = 49, - Focus = 50, - Chivalry = 51, - Bushido = 52, - Ninjitsu = 53, - Spellweaving = 54, - Mysticism = 55, - Imbuing = 56, - Throwing = 57 - } - - [PropertyObject] - public class Skill - { - private ushort m_Base; - private ushort m_Cap; - - public Skill(Skills owner, SkillInfo info, IGenericReader reader) - { - Owner = owner; - Info = info; - - int version = reader.ReadByte(); - - switch (version) - { - case 0: - { - m_Base = reader.ReadUShort(); - m_Cap = reader.ReadUShort(); - Lock = (SkillLock)reader.ReadByte(); - - break; - } - case 0xFF: - { - m_Base = 0; - m_Cap = 1000; - Lock = SkillLock.Up; - - break; - } - default: - { - if ((version & 0xC0) == 0x00) - { - if ((version & 0x1) != 0) - m_Base = reader.ReadUShort(); - - if ((version & 0x2) != 0) - m_Cap = reader.ReadUShort(); - else - m_Cap = 1000; - - if ((version & 0x4) != 0) - Lock = (SkillLock)reader.ReadByte(); - } - - break; - } - } - - if (Lock < SkillLock.Up || Lock > SkillLock.Locked) - { - Console.WriteLine("Bad skill lock -> {0}.{1}", owner.Owner, Lock); - Lock = SkillLock.Up; - } - } - - public Skill(Skills owner, SkillInfo info, int baseValue, int cap, SkillLock skillLock) - { - Owner = owner; - Info = info; - m_Base = (ushort)baseValue; - m_Cap = (ushort)cap; - Lock = skillLock; - } - - public Skills Owner { get; } - - public SkillName SkillName => (SkillName)Info.SkillID; - - public int SkillID => Info.SkillID; - - [CommandProperty(AccessLevel.Counselor)] - public string Name => Info.Name; - - public SkillInfo Info { get; } - - [CommandProperty(AccessLevel.Counselor)] - public SkillLock Lock { get; private set; } - - public int BaseFixedPoint - { - get => m_Base; - set - { - var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); - - int oldBase = m_Base; - - if (m_Base != sv) - { - Owner.Total = Owner.Total - m_Base + sv; - - m_Base = sv; - - Owner.OnSkillChange(this); - - var m = Owner.Owner; - - m?.OnSkillChange(SkillName, (double)oldBase / 10); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public double Base - { - get => m_Base / 10.0; - set => BaseFixedPoint = (int)(value * 10.0); - } - - public int CapFixedPoint - { - get => m_Cap; - set - { - var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); - - if (m_Cap != sv) - { - m_Cap = sv; - - Owner.OnSkillChange(this); - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public double Cap - { - get => m_Cap / 10.0; - set => CapFixedPoint = (int)(value * 10.0); - } - - public static bool UseStatMods { get; set; } - - public int Fixed => (int)(Value * 10); - - [CommandProperty(AccessLevel.Counselor)] - public double Value - { - get - { - // There has to be this distinction between the racial values and not to account for gaining skills and these skills aren't displayed nor Totaled up. - var value = NonRacialValue; - - var raceBonus = Owner.Owner.RacialSkillBonus; - - if (raceBonus > value) - value = raceBonus; - - return value; - } - } - - [CommandProperty(AccessLevel.Counselor)] - public double NonRacialValue - { - get - { - var baseValue = Base; - var inv = 100.0 - baseValue; - - if (inv < 0.0) inv = 0.0; - - inv /= 100.0; - - var statsOffset = (UseStatMods ? Owner.Owner.Str : Owner.Owner.RawStr) * Info.StrScale + - (UseStatMods ? Owner.Owner.Dex : Owner.Owner.RawDex) * Info.DexScale + - (UseStatMods ? Owner.Owner.Int : Owner.Owner.RawInt) * Info.IntScale; - var statTotal = Info.StatTotal * inv; - - statsOffset *= inv; - - if (statsOffset > statTotal) - statsOffset = statTotal; - - var value = baseValue + statsOffset; - - Owner.Owner.ValidateSkillMods(); - - var mods = Owner.Owner.SkillMods; - - double bonusObey = 0.0, bonusNotObey = 0.0; - - for (var i = 0; i < mods.Count; ++i) - { - var mod = mods[i]; - - if (mod.Skill == (SkillName)Info.SkillID) - { - if (mod.Relative) - { - if (mod.ObeyCap) - bonusObey += mod.Value; - else - bonusNotObey += mod.Value; - } - else - { - bonusObey = 0.0; - bonusNotObey = 0.0; - value = mod.Value; - } - } - } - - value += bonusNotObey; - - if (value < Cap) - { - value += bonusObey; - - if (value > Cap) - value = Cap; - } - - return value; - } - } - - public override string ToString() => $"[{Name}: {Base}]"; - - public void SetLockNoRelay(SkillLock skillLock) - { - if (skillLock < SkillLock.Up || skillLock > SkillLock.Locked) - return; - - Lock = skillLock; - } - - public void Serialize(IGenericWriter writer) - { - if (m_Base == 0 && m_Cap == 1000 && Lock == SkillLock.Up) - { - writer.Write((byte)0xFF); // default - } - else - { - var flags = 0x0; - - if (m_Base != 0) - flags |= 0x1; - - if (m_Cap != 1000) - flags |= 0x2; - - if (Lock != SkillLock.Up) - flags |= 0x4; - - writer.Write((byte)flags); // version - - if (m_Base != 0) - writer.Write((short)m_Base); - - if (m_Cap != 1000) - writer.Write((short)m_Cap); - - if (Lock != SkillLock.Up) - writer.Write((byte)Lock); - } - } - - public void Update() - { - Owner.OnSkillChange(this); - } - } - - public class SkillInfo - { - public SkillInfo(int skillID, string name, double strScale, double dexScale, double intScale, string title, - SkillUseCallback callback, double strGain, double dexGain, double intGain, double gainFactor) - { - Name = name; - Title = title; - SkillID = skillID; - StrScale = strScale / 100.0; - DexScale = dexScale / 100.0; - IntScale = intScale / 100.0; - Callback = callback; - StrGain = strGain; - DexGain = dexGain; - IntGain = intGain; - GainFactor = gainFactor; - - StatTotal = strScale + dexScale + intScale; - } - - public SkillUseCallback Callback { get; set; } - - public int SkillID { get; } - - public string Name { get; set; } - - public string Title { get; set; } - - public double StrScale { get; set; } - - public double DexScale { get; set; } - - public double IntScale { get; set; } - - public double StatTotal { get; set; } - - public double StrGain { get; set; } - - public double DexGain { get; set; } - - public double IntGain { get; set; } - - public double GainFactor { get; set; } - - public static SkillInfo[] Table { get; set; } = - { - new SkillInfo(0, "Alchemy", 0.0, 5.0, 5.0, "Alchemist", null, 0.0, 0.5, 0.5, 1.0), - new SkillInfo(1, "Anatomy", 0.0, 0.0, 0.0, "Biologist", null, 0.15, 0.15, 0.7, 1.0), - new SkillInfo(2, "Animal Lore", 0.0, 0.0, 0.0, "Naturalist", null, 0.0, 0.0, 1.0, 1.0), - new SkillInfo(3, "Item Identification", 0.0, 0.0, 0.0, "Merchant", null, 0.0, 0.0, 1.0, 1.0), - new SkillInfo(4, "Arms Lore", 0.0, 0.0, 0.0, "Weapon Master", null, 0.75, 0.15, 0.1, 1.0), - new SkillInfo(5, "Parrying", 7.5, 2.5, 0.0, "Duelist", null, 0.75, 0.25, 0.0, 1.0), - new SkillInfo(6, "Begging", 0.0, 0.0, 0.0, "Beggar", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(7, "Blacksmithy", 10.0, 0.0, 0.0, "Blacksmith", null, 1.0, 0.0, 0.0, 1.0), - new SkillInfo(8, "Bowcraft/Fletching", 6.0, 16.0, 0.0, "Bowyer", null, 0.6, 1.6, 0.0, 1.0), - new SkillInfo(9, "Peacemaking", 0.0, 0.0, 0.0, "Pacifier", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(10, "Camping", 20.0, 15.0, 15.0, "Explorer", null, 2.0, 1.5, 1.5, 1.0), - new SkillInfo(11, "Carpentry", 20.0, 5.0, 0.0, "Carpenter", null, 2.0, 0.5, 0.0, 1.0), - new SkillInfo(12, "Cartography", 0.0, 7.5, 7.5, "Cartographer", null, 0.0, 0.75, 0.75, 1.0), - new SkillInfo(13, "Cooking", 0.0, 20.0, 30.0, "Chef", null, 0.0, 2.0, 3.0, 1.0), - new SkillInfo(14, "Detecting Hidden", 0.0, 0.0, 0.0, "Scout", null, 0.0, 0.4, 0.6, 1.0), - new SkillInfo(15, "Discordance", 0.0, 2.5, 2.5, "Demoralizer", null, 0.0, 0.25, 0.25, 1.0), - new SkillInfo(16, "Evaluating Intelligence", 0.0, 0.0, 0.0, "Scholar", null, 0.0, 0.0, 1.0, 1.0), - new SkillInfo(17, "Healing", 6.0, 6.0, 8.0, "Healer", null, 0.6, 0.6, 0.8, 1.0), - new SkillInfo(18, "Fishing", 0.0, 0.0, 0.0, "Fisherman", null, 0.5, 0.5, 0.0, 1.0), - new SkillInfo(19, "Forensic Evaluation", 0.0, 0.0, 0.0, "Detective", null, 0.0, 0.2, 0.8, 1.0), - new SkillInfo(20, "Herding", 16.25, 6.25, 2.5, "Shepherd", null, 1.625, 0.625, 0.25, 1.0), - new SkillInfo(21, "Hiding", 0.0, 0.0, 0.0, "Shade", null, 0.0, 0.8, 0.2, 1.0), - new SkillInfo(22, "Provocation", 0.0, 4.5, 0.5, "Rouser", null, 0.0, 0.45, 0.05, 1.0), - new SkillInfo(23, "Inscription", 0.0, 2.0, 8.0, "Scribe", null, 0.0, 0.2, 0.8, 1.0), - new SkillInfo(24, "Lockpicking", 0.0, 25.0, 0.0, "Infiltrator", null, 0.0, 2.0, 0.0, 1.0), - new SkillInfo(25, "Magery", 0.0, 0.0, 15.0, "Mage", null, 0.0, 0.0, 1.5, 1.0), - new SkillInfo(26, "Resisting Spells", 0.0, 0.0, 0.0, "Warder", null, 0.25, 0.25, 0.5, 1.0), - new SkillInfo(27, "Tactics", 0.0, 0.0, 0.0, "Tactician", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(28, "Snooping", 0.0, 25.0, 0.0, "Spy", null, 0.0, 2.5, 0.0, 1.0), - new SkillInfo(29, "Musicianship", 0.0, 0.0, 0.0, "Bard", null, 0.0, 0.8, 0.2, 1.0), - new SkillInfo(30, "Poisoning", 0.0, 4.0, 16.0, "Assassin", null, 0.0, 0.4, 1.6, 1.0), - new SkillInfo(31, "Archery", 2.5, 7.5, 0.0, "Archer", null, 0.25, 0.75, 0.0, 1.0), - new SkillInfo(32, "Spirit Speak", 0.0, 0.0, 0.0, "Medium", null, 0.0, 0.0, 1.0, 1.0), - new SkillInfo(33, "Stealing", 0.0, 10.0, 0.0, "Pickpocket", null, 0.0, 1.0, 0.0, 1.0), - new SkillInfo(34, "Tailoring", 3.75, 16.25, 5.0, "Tailor", null, 0.38, 1.63, 0.5, 1.0), - new SkillInfo(35, "Animal Taming", 14.0, 2.0, 4.0, "Tamer", null, 1.4, 0.2, 0.4, 1.0), - new SkillInfo(36, "Taste Identification", 0.0, 0.0, 0.0, "Praegustator", null, 0.2, 0.0, 0.8, 1.0), - new SkillInfo(37, "Tinkering", 5.0, 2.0, 3.0, "Tinker", null, 0.5, 0.2, 0.3, 1.0), - new SkillInfo(38, "Tracking", 0.0, 12.5, 12.5, "Ranger", null, 0.0, 1.25, 1.25, 1.0), - new SkillInfo(39, "Veterinary", 8.0, 4.0, 8.0, "Veterinarian", null, 0.8, 0.4, 0.8, 1.0), - new SkillInfo(40, "Swordsmanship", 7.5, 2.5, 0.0, "Swordsman", null, 0.75, 0.25, 0.0, 1.0), - new SkillInfo(41, "Mace Fighting", 9.0, 1.0, 0.0, "Armsman", null, 0.9, 0.1, 0.0, 1.0), - new SkillInfo(42, "Fencing", 4.5, 5.5, 0.0, "Fencer", null, 0.45, 0.55, 0.0, 1.0), - new SkillInfo(43, "Wrestling", 9.0, 1.0, 0.0, "Wrestler", null, 0.9, 0.1, 0.0, 1.0), - new SkillInfo(44, "Lumberjacking", 20.0, 0.0, 0.0, "Lumberjack", null, 2.0, 0.0, 0.0, 1.0), - new SkillInfo(45, "Mining", 20.0, 0.0, 0.0, "Miner", null, 2.0, 0.0, 0.0, 1.0), - new SkillInfo(46, "Meditation", 0.0, 0.0, 0.0, "Stoic", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(47, "Stealth", 0.0, 0.0, 0.0, "Rogue", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(48, "Remove Trap", 0.0, 0.0, 0.0, "Trap Specialist", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(49, "Necromancy", 0.0, 0.0, 0.0, "Necromancer", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(50, "Focus", 0.0, 0.0, 0.0, "Driven", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(51, "Chivalry", 0.0, 0.0, 0.0, "Paladin", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(52, "Bushido", 0.0, 0.0, 0.0, "Samurai", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(53, "Ninjitsu", 0.0, 0.0, 0.0, "Ninja", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(54, "Spellweaving", 0.0, 0.0, 0.0, "Arcanist", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(55, "Mysticism", 0.0, 0.0, 0.0, "Mystic", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(56, "Imbuing", 0.0, 0.0, 0.0, "Artificer", null, 0.0, 0.0, 0.0, 1.0), - new SkillInfo(57, "Throwing", 0.0, 0.0, 0.0, "Bladeweaver", null, 0.0, 0.0, 0.0, 1.0) - }; - } - - [PropertyObject] - public class Skills : IEnumerable - { - private Skill m_Highest; - private readonly Skill[] m_Skills; - - public Skills(Mobile owner) - { - Owner = owner; - Cap = 7000; - - var info = SkillInfo.Table; - - m_Skills = new Skill[info.Length]; - - // for ( int i = 0; i < info.Length; ++i ) - // m_Skills[i] = new Skill( this, info[i], 0, 1000, SkillLock.Up ); - } - - public Skills(Mobile owner, IGenericReader reader) - { - Owner = owner; - - var version = reader.ReadInt(); - - switch (version) - { - case 3: - case 2: - { - Cap = reader.ReadInt(); - - goto case 1; - } - case 1: - { - if (version < 2) - Cap = 7000; - - if (version < 3) - /*m_Total =*/ - reader.ReadInt(); - - var info = SkillInfo.Table; - - m_Skills = new Skill[info.Length]; - - var count = reader.ReadInt(); - - for (var i = 0; i < count; ++i) - if (i < info.Length) - { - var sk = new Skill(this, info[i], reader); - - if (sk.BaseFixedPoint != 0 || sk.CapFixedPoint != 1000 || sk.Lock != SkillLock.Up) - { - m_Skills[i] = sk; - Total += sk.BaseFixedPoint; - } - } - else - { - // Will be discarded - _ = new Skill(this, null, reader); - } - - // for ( int i = count; i < info.Length; ++i ) - // m_Skills[i] = new Skill( this, info[i], 0, 1000, SkillLock.Up ); - - break; - } - case 0: - { - reader.ReadInt(); - - goto case 1; - } - } - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Cap { get; set; } - - public int Total { get; set; } - - public Mobile Owner { get; } - - public int Length => m_Skills.Length; - - public Skill this[SkillName name] => this[(int)name]; - - public Skill this[int skillID] - { - get - { - if (skillID < 0 || skillID >= m_Skills.Length) - return null; - - var sk = m_Skills[skillID]; - - if (sk == null) - m_Skills[skillID] = sk = new Skill(this, SkillInfo.Table[skillID], 0, 1000, SkillLock.Up); - - return sk; - } - } - - public Skill Highest - { - get - { - if (m_Highest == null) - { - Skill highest = null; - var value = int.MinValue; - - for (var i = 0; i < m_Skills.Length; ++i) - { - var sk = m_Skills[i]; - - if (sk != null && sk.BaseFixedPoint > value) - { - value = sk.BaseFixedPoint; - highest = sk; - } - } - - m_Highest = highest == null && m_Skills.Length > 0 ? this[0] : highest; - } - - return m_Highest; - } - } - - public IEnumerator GetEnumerator() - { - return m_Skills.Where(s => s != null).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_Skills.Where(s => s != null).GetEnumerator(); - } - - public override string ToString() => "..."; - - public static bool UseSkill(Mobile from, SkillName name) => UseSkill(from, (int)name); - - public static bool UseSkill(Mobile from, int skillID) - { - if (!from.CheckAlive()) - return false; - if (!from.Region.OnSkillUse(from, skillID)) - return false; - if (!from.AllowSkillUse((SkillName)skillID)) - return false; - - if (skillID >= 0 && skillID < SkillInfo.Table.Length) - { - var info = SkillInfo.Table[skillID]; - - if (info.Callback != null) - { - if (Core.TickCount - from.NextSkillTime >= 0 && from.Spell == null) - { - from.DisruptiveAction(); - - from.NextSkillTime = Core.TickCount + (int)info.Callback(from).TotalMilliseconds; - - return true; - } - - from.SendSkillMessage(); - } - else - { - from.SendLocalizedMessage(500014); // That skill cannot be used directly. - } - } - - return false; - } - - public void Serialize(IGenericWriter writer) - { - Total = 0; - - writer.Write(3); // version - - writer.Write(Cap); - writer.Write(m_Skills.Length); - - for (var i = 0; i < m_Skills.Length; ++i) - { - var sk = m_Skills[i]; - - if (sk == null) - { - writer.Write((byte)0xFF); - } - else - { - sk.Serialize(writer); - Total += sk.BaseFixedPoint; - } - } - } - - public void OnSkillChange(Skill skill) - { - if (skill == m_Highest) // could be downgrading the skill, force a recalc - m_Highest = null; - else if (m_Highest != null && skill.BaseFixedPoint > m_Highest.BaseFixedPoint) - m_Highest = skill; - - Owner.OnSkillInvalidated(skill); - Owner.NetState?.Send(new SkillChange(skill)); - } - - [CommandProperty(AccessLevel.Counselor)] - public Skill Alchemy => this[SkillName.Alchemy]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Anatomy => this[SkillName.Anatomy]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill AnimalLore => this[SkillName.AnimalLore]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill ItemID => this[SkillName.ItemID]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill ArmsLore => this[SkillName.ArmsLore]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Parry => this[SkillName.Parry]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Begging => this[SkillName.Begging]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Blacksmith => this[SkillName.Blacksmith]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Fletching => this[SkillName.Fletching]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Peacemaking => this[SkillName.Peacemaking]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Camping => this[SkillName.Camping]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Carpentry => this[SkillName.Carpentry]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Cartography => this[SkillName.Cartography]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Cooking => this[SkillName.Cooking]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill DetectHidden => this[SkillName.DetectHidden]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Discordance => this[SkillName.Discordance]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill EvalInt => this[SkillName.EvalInt]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Healing => this[SkillName.Healing]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Fishing => this[SkillName.Fishing]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Forensics => this[SkillName.Forensics]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Herding => this[SkillName.Herding]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Hiding => this[SkillName.Hiding]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Provocation => this[SkillName.Provocation]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Inscribe => this[SkillName.Inscribe]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Lockpicking => this[SkillName.Lockpicking]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Magery => this[SkillName.Magery]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill MagicResist => this[SkillName.MagicResist]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Tactics => this[SkillName.Tactics]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Snooping => this[SkillName.Snooping]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Musicianship => this[SkillName.Musicianship]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Poisoning => this[SkillName.Poisoning]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Archery => this[SkillName.Archery]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill SpiritSpeak => this[SkillName.SpiritSpeak]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Stealing => this[SkillName.Stealing]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Tailoring => this[SkillName.Tailoring]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill AnimalTaming => this[SkillName.AnimalTaming]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill TasteID => this[SkillName.TasteID]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Tinkering => this[SkillName.Tinkering]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Tracking => this[SkillName.Tracking]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Veterinary => this[SkillName.Veterinary]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Swords => this[SkillName.Swords]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Macing => this[SkillName.Macing]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Fencing => this[SkillName.Fencing]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Wrestling => this[SkillName.Wrestling]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Lumberjacking => this[SkillName.Lumberjacking]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Mining => this[SkillName.Mining]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Meditation => this[SkillName.Meditation]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Stealth => this[SkillName.Stealth]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill RemoveTrap => this[SkillName.RemoveTrap]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Necromancy => this[SkillName.Necromancy]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Focus => this[SkillName.Focus]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Chivalry => this[SkillName.Chivalry]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Bushido => this[SkillName.Bushido]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Ninjitsu => this[SkillName.Ninjitsu]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Spellweaving => this[SkillName.Spellweaving]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Mysticism => this[SkillName.Mysticism]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Imbuing => this[SkillName.Imbuing]; - - [CommandProperty(AccessLevel.Counselor)] - public Skill Throwing => this[SkillName.Throwing]; - } -} +/*************************************************************************** + * Skills.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Server.Network; + +namespace Server +{ + public delegate TimeSpan SkillUseCallback(Mobile user); + + public enum SkillLock : byte + { + Up = 0, + Down = 1, + Locked = 2 + } + + public enum SkillName + { + Alchemy = 0, + Anatomy = 1, + AnimalLore = 2, + ItemID = 3, + ArmsLore = 4, + Parry = 5, + Begging = 6, + Blacksmith = 7, + Fletching = 8, + Peacemaking = 9, + Camping = 10, + Carpentry = 11, + Cartography = 12, + Cooking = 13, + DetectHidden = 14, + Discordance = 15, + EvalInt = 16, + Healing = 17, + Fishing = 18, + Forensics = 19, + Herding = 20, + Hiding = 21, + Provocation = 22, + Inscribe = 23, + Lockpicking = 24, + Magery = 25, + MagicResist = 26, + Tactics = 27, + Snooping = 28, + Musicianship = 29, + Poisoning = 30, + Archery = 31, + SpiritSpeak = 32, + Stealing = 33, + Tailoring = 34, + AnimalTaming = 35, + TasteID = 36, + Tinkering = 37, + Tracking = 38, + Veterinary = 39, + Swords = 40, + Macing = 41, + Fencing = 42, + Wrestling = 43, + Lumberjacking = 44, + Mining = 45, + Meditation = 46, + Stealth = 47, + RemoveTrap = 48, + Necromancy = 49, + Focus = 50, + Chivalry = 51, + Bushido = 52, + Ninjitsu = 53, + Spellweaving = 54, + Mysticism = 55, + Imbuing = 56, + Throwing = 57 + } + + [PropertyObject] + public class Skill + { + private ushort m_Base; + private ushort m_Cap; + + public Skill(Skills owner, SkillInfo info, IGenericReader reader) + { + Owner = owner; + Info = info; + + int version = reader.ReadByte(); + + switch (version) + { + case 0: + { + m_Base = reader.ReadUShort(); + m_Cap = reader.ReadUShort(); + Lock = (SkillLock)reader.ReadByte(); + + break; + } + case 0xFF: + { + m_Base = 0; + m_Cap = 1000; + Lock = SkillLock.Up; + + break; + } + default: + { + if ((version & 0xC0) == 0x00) + { + if ((version & 0x1) != 0) + m_Base = reader.ReadUShort(); + + if ((version & 0x2) != 0) + m_Cap = reader.ReadUShort(); + else + m_Cap = 1000; + + if ((version & 0x4) != 0) + Lock = (SkillLock)reader.ReadByte(); + } + + break; + } + } + + if (Lock < SkillLock.Up || Lock > SkillLock.Locked) + { + Console.WriteLine("Bad skill lock -> {0}.{1}", owner.Owner, Lock); + Lock = SkillLock.Up; + } + } + + public Skill(Skills owner, SkillInfo info, int baseValue, int cap, SkillLock skillLock) + { + Owner = owner; + Info = info; + m_Base = (ushort)baseValue; + m_Cap = (ushort)cap; + Lock = skillLock; + } + + public Skills Owner { get; } + + public SkillName SkillName => (SkillName)Info.SkillID; + + public int SkillID => Info.SkillID; + + [CommandProperty(AccessLevel.Counselor)] + public string Name => Info.Name; + + public SkillInfo Info { get; } + + [CommandProperty(AccessLevel.Counselor)] + public SkillLock Lock { get; private set; } + + public int BaseFixedPoint + { + get => m_Base; + set + { + var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); + + int oldBase = m_Base; + + if (m_Base != sv) + { + Owner.Total = Owner.Total - m_Base + sv; + + m_Base = sv; + + Owner.OnSkillChange(this); + + var m = Owner.Owner; + + m?.OnSkillChange(SkillName, (double)oldBase / 10); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public double Base + { + get => m_Base / 10.0; + set => BaseFixedPoint = (int)(value * 10.0); + } + + public int CapFixedPoint + { + get => m_Cap; + set + { + var sv = (ushort)Math.Clamp(value, 0, 0xFFFF); + + if (m_Cap != sv) + { + m_Cap = sv; + + Owner.OnSkillChange(this); + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public double Cap + { + get => m_Cap / 10.0; + set => CapFixedPoint = (int)(value * 10.0); + } + + public static bool UseStatMods { get; set; } + + public int Fixed => (int)(Value * 10); + + [CommandProperty(AccessLevel.Counselor)] + public double Value + { + get + { + // There has to be this distinction between the racial values and not to account for gaining skills and these skills aren't displayed nor Totaled up. + var value = NonRacialValue; + + var raceBonus = Owner.Owner.RacialSkillBonus; + + if (raceBonus > value) + value = raceBonus; + + return value; + } + } + + [CommandProperty(AccessLevel.Counselor)] + public double NonRacialValue + { + get + { + var baseValue = Base; + var inv = 100.0 - baseValue; + + if (inv < 0.0) inv = 0.0; + + inv /= 100.0; + + var statsOffset = (UseStatMods ? Owner.Owner.Str : Owner.Owner.RawStr) * Info.StrScale + + (UseStatMods ? Owner.Owner.Dex : Owner.Owner.RawDex) * Info.DexScale + + (UseStatMods ? Owner.Owner.Int : Owner.Owner.RawInt) * Info.IntScale; + var statTotal = Info.StatTotal * inv; + + statsOffset *= inv; + + if (statsOffset > statTotal) + statsOffset = statTotal; + + var value = baseValue + statsOffset; + + Owner.Owner.ValidateSkillMods(); + + var mods = Owner.Owner.SkillMods; + + double bonusObey = 0.0, bonusNotObey = 0.0; + + for (var i = 0; i < mods.Count; ++i) + { + var mod = mods[i]; + + if (mod.Skill == (SkillName)Info.SkillID) + { + if (mod.Relative) + { + if (mod.ObeyCap) + bonusObey += mod.Value; + else + bonusNotObey += mod.Value; + } + else + { + bonusObey = 0.0; + bonusNotObey = 0.0; + value = mod.Value; + } + } + } + + value += bonusNotObey; + + if (value < Cap) + { + value += bonusObey; + + if (value > Cap) + value = Cap; + } + + return value; + } + } + + public override string ToString() => $"[{Name}: {Base}]"; + + public void SetLockNoRelay(SkillLock skillLock) + { + if (skillLock < SkillLock.Up || skillLock > SkillLock.Locked) + return; + + Lock = skillLock; + } + + public void Serialize(IGenericWriter writer) + { + if (m_Base == 0 && m_Cap == 1000 && Lock == SkillLock.Up) + { + writer.Write((byte)0xFF); // default + } + else + { + var flags = 0x0; + + if (m_Base != 0) + flags |= 0x1; + + if (m_Cap != 1000) + flags |= 0x2; + + if (Lock != SkillLock.Up) + flags |= 0x4; + + writer.Write((byte)flags); // version + + if (m_Base != 0) + writer.Write((short)m_Base); + + if (m_Cap != 1000) + writer.Write((short)m_Cap); + + if (Lock != SkillLock.Up) + writer.Write((byte)Lock); + } + } + + public void Update() + { + Owner.OnSkillChange(this); + } + } + + public class SkillInfo + { + public SkillInfo( + int skillID, string name, double strScale, double dexScale, double intScale, string title, + SkillUseCallback callback, double strGain, double dexGain, double intGain, double gainFactor + ) + { + Name = name; + Title = title; + SkillID = skillID; + StrScale = strScale / 100.0; + DexScale = dexScale / 100.0; + IntScale = intScale / 100.0; + Callback = callback; + StrGain = strGain; + DexGain = dexGain; + IntGain = intGain; + GainFactor = gainFactor; + + StatTotal = strScale + dexScale + intScale; + } + + public SkillUseCallback Callback { get; set; } + + public int SkillID { get; } + + public string Name { get; set; } + + public string Title { get; set; } + + public double StrScale { get; set; } + + public double DexScale { get; set; } + + public double IntScale { get; set; } + + public double StatTotal { get; set; } + + public double StrGain { get; set; } + + public double DexGain { get; set; } + + public double IntGain { get; set; } + + public double GainFactor { get; set; } + + public static SkillInfo[] Table { get; set; } = + { + new SkillInfo(0, "Alchemy", 0.0, 5.0, 5.0, "Alchemist", null, 0.0, 0.5, 0.5, 1.0), + new SkillInfo(1, "Anatomy", 0.0, 0.0, 0.0, "Biologist", null, 0.15, 0.15, 0.7, 1.0), + new SkillInfo(2, "Animal Lore", 0.0, 0.0, 0.0, "Naturalist", null, 0.0, 0.0, 1.0, 1.0), + new SkillInfo(3, "Item Identification", 0.0, 0.0, 0.0, "Merchant", null, 0.0, 0.0, 1.0, 1.0), + new SkillInfo(4, "Arms Lore", 0.0, 0.0, 0.0, "Weapon Master", null, 0.75, 0.15, 0.1, 1.0), + new SkillInfo(5, "Parrying", 7.5, 2.5, 0.0, "Duelist", null, 0.75, 0.25, 0.0, 1.0), + new SkillInfo(6, "Begging", 0.0, 0.0, 0.0, "Beggar", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(7, "Blacksmithy", 10.0, 0.0, 0.0, "Blacksmith", null, 1.0, 0.0, 0.0, 1.0), + new SkillInfo(8, "Bowcraft/Fletching", 6.0, 16.0, 0.0, "Bowyer", null, 0.6, 1.6, 0.0, 1.0), + new SkillInfo(9, "Peacemaking", 0.0, 0.0, 0.0, "Pacifier", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(10, "Camping", 20.0, 15.0, 15.0, "Explorer", null, 2.0, 1.5, 1.5, 1.0), + new SkillInfo(11, "Carpentry", 20.0, 5.0, 0.0, "Carpenter", null, 2.0, 0.5, 0.0, 1.0), + new SkillInfo(12, "Cartography", 0.0, 7.5, 7.5, "Cartographer", null, 0.0, 0.75, 0.75, 1.0), + new SkillInfo(13, "Cooking", 0.0, 20.0, 30.0, "Chef", null, 0.0, 2.0, 3.0, 1.0), + new SkillInfo(14, "Detecting Hidden", 0.0, 0.0, 0.0, "Scout", null, 0.0, 0.4, 0.6, 1.0), + new SkillInfo(15, "Discordance", 0.0, 2.5, 2.5, "Demoralizer", null, 0.0, 0.25, 0.25, 1.0), + new SkillInfo(16, "Evaluating Intelligence", 0.0, 0.0, 0.0, "Scholar", null, 0.0, 0.0, 1.0, 1.0), + new SkillInfo(17, "Healing", 6.0, 6.0, 8.0, "Healer", null, 0.6, 0.6, 0.8, 1.0), + new SkillInfo(18, "Fishing", 0.0, 0.0, 0.0, "Fisherman", null, 0.5, 0.5, 0.0, 1.0), + new SkillInfo(19, "Forensic Evaluation", 0.0, 0.0, 0.0, "Detective", null, 0.0, 0.2, 0.8, 1.0), + new SkillInfo(20, "Herding", 16.25, 6.25, 2.5, "Shepherd", null, 1.625, 0.625, 0.25, 1.0), + new SkillInfo(21, "Hiding", 0.0, 0.0, 0.0, "Shade", null, 0.0, 0.8, 0.2, 1.0), + new SkillInfo(22, "Provocation", 0.0, 4.5, 0.5, "Rouser", null, 0.0, 0.45, 0.05, 1.0), + new SkillInfo(23, "Inscription", 0.0, 2.0, 8.0, "Scribe", null, 0.0, 0.2, 0.8, 1.0), + new SkillInfo(24, "Lockpicking", 0.0, 25.0, 0.0, "Infiltrator", null, 0.0, 2.0, 0.0, 1.0), + new SkillInfo(25, "Magery", 0.0, 0.0, 15.0, "Mage", null, 0.0, 0.0, 1.5, 1.0), + new SkillInfo(26, "Resisting Spells", 0.0, 0.0, 0.0, "Warder", null, 0.25, 0.25, 0.5, 1.0), + new SkillInfo(27, "Tactics", 0.0, 0.0, 0.0, "Tactician", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(28, "Snooping", 0.0, 25.0, 0.0, "Spy", null, 0.0, 2.5, 0.0, 1.0), + new SkillInfo(29, "Musicianship", 0.0, 0.0, 0.0, "Bard", null, 0.0, 0.8, 0.2, 1.0), + new SkillInfo(30, "Poisoning", 0.0, 4.0, 16.0, "Assassin", null, 0.0, 0.4, 1.6, 1.0), + new SkillInfo(31, "Archery", 2.5, 7.5, 0.0, "Archer", null, 0.25, 0.75, 0.0, 1.0), + new SkillInfo(32, "Spirit Speak", 0.0, 0.0, 0.0, "Medium", null, 0.0, 0.0, 1.0, 1.0), + new SkillInfo(33, "Stealing", 0.0, 10.0, 0.0, "Pickpocket", null, 0.0, 1.0, 0.0, 1.0), + new SkillInfo(34, "Tailoring", 3.75, 16.25, 5.0, "Tailor", null, 0.38, 1.63, 0.5, 1.0), + new SkillInfo(35, "Animal Taming", 14.0, 2.0, 4.0, "Tamer", null, 1.4, 0.2, 0.4, 1.0), + new SkillInfo(36, "Taste Identification", 0.0, 0.0, 0.0, "Praegustator", null, 0.2, 0.0, 0.8, 1.0), + new SkillInfo(37, "Tinkering", 5.0, 2.0, 3.0, "Tinker", null, 0.5, 0.2, 0.3, 1.0), + new SkillInfo(38, "Tracking", 0.0, 12.5, 12.5, "Ranger", null, 0.0, 1.25, 1.25, 1.0), + new SkillInfo(39, "Veterinary", 8.0, 4.0, 8.0, "Veterinarian", null, 0.8, 0.4, 0.8, 1.0), + new SkillInfo(40, "Swordsmanship", 7.5, 2.5, 0.0, "Swordsman", null, 0.75, 0.25, 0.0, 1.0), + new SkillInfo(41, "Mace Fighting", 9.0, 1.0, 0.0, "Armsman", null, 0.9, 0.1, 0.0, 1.0), + new SkillInfo(42, "Fencing", 4.5, 5.5, 0.0, "Fencer", null, 0.45, 0.55, 0.0, 1.0), + new SkillInfo(43, "Wrestling", 9.0, 1.0, 0.0, "Wrestler", null, 0.9, 0.1, 0.0, 1.0), + new SkillInfo(44, "Lumberjacking", 20.0, 0.0, 0.0, "Lumberjack", null, 2.0, 0.0, 0.0, 1.0), + new SkillInfo(45, "Mining", 20.0, 0.0, 0.0, "Miner", null, 2.0, 0.0, 0.0, 1.0), + new SkillInfo(46, "Meditation", 0.0, 0.0, 0.0, "Stoic", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(47, "Stealth", 0.0, 0.0, 0.0, "Rogue", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(48, "Remove Trap", 0.0, 0.0, 0.0, "Trap Specialist", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(49, "Necromancy", 0.0, 0.0, 0.0, "Necromancer", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(50, "Focus", 0.0, 0.0, 0.0, "Driven", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(51, "Chivalry", 0.0, 0.0, 0.0, "Paladin", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(52, "Bushido", 0.0, 0.0, 0.0, "Samurai", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(53, "Ninjitsu", 0.0, 0.0, 0.0, "Ninja", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(54, "Spellweaving", 0.0, 0.0, 0.0, "Arcanist", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(55, "Mysticism", 0.0, 0.0, 0.0, "Mystic", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(56, "Imbuing", 0.0, 0.0, 0.0, "Artificer", null, 0.0, 0.0, 0.0, 1.0), + new SkillInfo(57, "Throwing", 0.0, 0.0, 0.0, "Bladeweaver", null, 0.0, 0.0, 0.0, 1.0) + }; + } + + [PropertyObject] + public class Skills : IEnumerable + { + private readonly Skill[] m_Skills; + private Skill m_Highest; + + public Skills(Mobile owner) + { + Owner = owner; + Cap = 7000; + + var info = SkillInfo.Table; + + m_Skills = new Skill[info.Length]; + + // for ( int i = 0; i < info.Length; ++i ) + // m_Skills[i] = new Skill( this, info[i], 0, 1000, SkillLock.Up ); + } + + public Skills(Mobile owner, IGenericReader reader) + { + Owner = owner; + + var version = reader.ReadInt(); + + switch (version) + { + case 3: + case 2: + { + Cap = reader.ReadInt(); + + goto case 1; + } + case 1: + { + if (version < 2) + Cap = 7000; + + if (version < 3) + /*m_Total =*/ + reader.ReadInt(); + + var info = SkillInfo.Table; + + m_Skills = new Skill[info.Length]; + + var count = reader.ReadInt(); + + for (var i = 0; i < count; ++i) + if (i < info.Length) + { + var sk = new Skill(this, info[i], reader); + + if (sk.BaseFixedPoint != 0 || sk.CapFixedPoint != 1000 || sk.Lock != SkillLock.Up) + { + m_Skills[i] = sk; + Total += sk.BaseFixedPoint; + } + } + else + { + // Will be discarded + _ = new Skill(this, null, reader); + } + + // for ( int i = count; i < info.Length; ++i ) + // m_Skills[i] = new Skill( this, info[i], 0, 1000, SkillLock.Up ); + + break; + } + case 0: + { + reader.ReadInt(); + + goto case 1; + } + } + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Cap { get; set; } + + public int Total { get; set; } + + public Mobile Owner { get; } + + public int Length => m_Skills.Length; + + public Skill this[SkillName name] => this[(int)name]; + + public Skill this[int skillID] + { + get + { + if (skillID < 0 || skillID >= m_Skills.Length) + return null; + + var sk = m_Skills[skillID]; + + if (sk == null) + m_Skills[skillID] = sk = new Skill(this, SkillInfo.Table[skillID], 0, 1000, SkillLock.Up); + + return sk; + } + } + + public Skill Highest + { + get + { + if (m_Highest == null) + { + Skill highest = null; + var value = int.MinValue; + + for (var i = 0; i < m_Skills.Length; ++i) + { + var sk = m_Skills[i]; + + if (sk != null && sk.BaseFixedPoint > value) + { + value = sk.BaseFixedPoint; + highest = sk; + } + } + + m_Highest = highest == null && m_Skills.Length > 0 ? this[0] : highest; + } + + return m_Highest; + } + } + + [CommandProperty(AccessLevel.Counselor)] + public Skill Alchemy => this[SkillName.Alchemy]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Anatomy => this[SkillName.Anatomy]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill AnimalLore => this[SkillName.AnimalLore]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill ItemID => this[SkillName.ItemID]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill ArmsLore => this[SkillName.ArmsLore]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Parry => this[SkillName.Parry]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Begging => this[SkillName.Begging]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Blacksmith => this[SkillName.Blacksmith]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Fletching => this[SkillName.Fletching]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Peacemaking => this[SkillName.Peacemaking]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Camping => this[SkillName.Camping]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Carpentry => this[SkillName.Carpentry]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Cartography => this[SkillName.Cartography]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Cooking => this[SkillName.Cooking]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill DetectHidden => this[SkillName.DetectHidden]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Discordance => this[SkillName.Discordance]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill EvalInt => this[SkillName.EvalInt]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Healing => this[SkillName.Healing]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Fishing => this[SkillName.Fishing]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Forensics => this[SkillName.Forensics]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Herding => this[SkillName.Herding]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Hiding => this[SkillName.Hiding]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Provocation => this[SkillName.Provocation]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Inscribe => this[SkillName.Inscribe]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Lockpicking => this[SkillName.Lockpicking]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Magery => this[SkillName.Magery]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill MagicResist => this[SkillName.MagicResist]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Tactics => this[SkillName.Tactics]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Snooping => this[SkillName.Snooping]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Musicianship => this[SkillName.Musicianship]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Poisoning => this[SkillName.Poisoning]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Archery => this[SkillName.Archery]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill SpiritSpeak => this[SkillName.SpiritSpeak]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Stealing => this[SkillName.Stealing]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Tailoring => this[SkillName.Tailoring]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill AnimalTaming => this[SkillName.AnimalTaming]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill TasteID => this[SkillName.TasteID]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Tinkering => this[SkillName.Tinkering]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Tracking => this[SkillName.Tracking]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Veterinary => this[SkillName.Veterinary]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Swords => this[SkillName.Swords]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Macing => this[SkillName.Macing]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Fencing => this[SkillName.Fencing]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Wrestling => this[SkillName.Wrestling]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Lumberjacking => this[SkillName.Lumberjacking]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Mining => this[SkillName.Mining]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Meditation => this[SkillName.Meditation]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Stealth => this[SkillName.Stealth]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill RemoveTrap => this[SkillName.RemoveTrap]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Necromancy => this[SkillName.Necromancy]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Focus => this[SkillName.Focus]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Chivalry => this[SkillName.Chivalry]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Bushido => this[SkillName.Bushido]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Ninjitsu => this[SkillName.Ninjitsu]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Spellweaving => this[SkillName.Spellweaving]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Mysticism => this[SkillName.Mysticism]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Imbuing => this[SkillName.Imbuing]; + + [CommandProperty(AccessLevel.Counselor)] + public Skill Throwing => this[SkillName.Throwing]; + + public IEnumerator GetEnumerator() + { + return m_Skills.Where(s => s != null).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_Skills.Where(s => s != null).GetEnumerator(); + } + + public override string ToString() => "..."; + + public static bool UseSkill(Mobile from, SkillName name) => UseSkill(from, (int)name); + + public static bool UseSkill(Mobile from, int skillID) + { + if (!from.CheckAlive()) + return false; + if (!from.Region.OnSkillUse(from, skillID)) + return false; + if (!from.AllowSkillUse((SkillName)skillID)) + return false; + + if (skillID >= 0 && skillID < SkillInfo.Table.Length) + { + var info = SkillInfo.Table[skillID]; + + if (info.Callback != null) + { + if (Core.TickCount - from.NextSkillTime >= 0 && from.Spell == null) + { + from.DisruptiveAction(); + + from.NextSkillTime = Core.TickCount + (int)info.Callback(from).TotalMilliseconds; + + return true; + } + + from.SendSkillMessage(); + } + else + { + from.SendLocalizedMessage(500014); // That skill cannot be used directly. + } + } + + return false; + } + + public void Serialize(IGenericWriter writer) + { + Total = 0; + + writer.Write(3); // version + + writer.Write(Cap); + writer.Write(m_Skills.Length); + + for (var i = 0; i < m_Skills.Length; ++i) + { + var sk = m_Skills[i]; + + if (sk == null) + { + writer.Write((byte)0xFF); + } + else + { + sk.Serialize(writer); + Total += sk.BaseFixedPoint; + } + } + } + + public void OnSkillChange(Skill skill) + { + if (skill == m_Highest) // could be downgrading the skill, force a recalc + m_Highest = null; + else if (m_Highest != null && skill.BaseFixedPoint > m_Highest.BaseFixedPoint) + m_Highest = skill; + + Owner.OnSkillInvalidated(skill); + Owner.NetState?.Send(new SkillChange(skill)); + } + } +} diff --git a/Projects/Server/Targeting/LandTarget.cs b/Projects/Server/Targeting/LandTarget.cs index da911f265..e6a6a23c4 100644 --- a/Projects/Server/Targeting/LandTarget.cs +++ b/Projects/Server/Targeting/LandTarget.cs @@ -1,59 +1,59 @@ -/*************************************************************************** - * LandTarget.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Targeting -{ - public class LandTarget : IPoint3D - { - private Point3D m_Location; - - public LandTarget(Point3D location, Map map) - { - m_Location = location; - - if (map != null) - { - m_Location.Z = map.GetAverageZ(m_Location.X, m_Location.Y); - TileID = map.Tiles.GetLandTile(m_Location.X, m_Location.Y).ID & TileData.MaxLandValue; - } - } - - [CommandProperty(AccessLevel.Counselor)] - public string Name => TileData.LandTable[TileID].Name; - - [CommandProperty(AccessLevel.Counselor)] - public TileFlag Flags => TileData.LandTable[TileID].Flags; - - [CommandProperty(AccessLevel.Counselor)] - public int TileID { get; } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D Location => m_Location; - - [CommandProperty(AccessLevel.Counselor)] - public int X => m_Location.X; - - [CommandProperty(AccessLevel.Counselor)] - public int Y => m_Location.Y; - - [CommandProperty(AccessLevel.Counselor)] - public int Z => m_Location.Z; - } -} +/*************************************************************************** + * LandTarget.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Targeting +{ + public class LandTarget : IPoint3D + { + private Point3D m_Location; + + public LandTarget(Point3D location, Map map) + { + m_Location = location; + + if (map != null) + { + m_Location.Z = map.GetAverageZ(m_Location.X, m_Location.Y); + TileID = map.Tiles.GetLandTile(m_Location.X, m_Location.Y).ID & TileData.MaxLandValue; + } + } + + [CommandProperty(AccessLevel.Counselor)] + public string Name => TileData.LandTable[TileID].Name; + + [CommandProperty(AccessLevel.Counselor)] + public TileFlag Flags => TileData.LandTable[TileID].Flags; + + [CommandProperty(AccessLevel.Counselor)] + public int TileID { get; } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Location => m_Location; + + [CommandProperty(AccessLevel.Counselor)] + public int X => m_Location.X; + + [CommandProperty(AccessLevel.Counselor)] + public int Y => m_Location.Y; + + [CommandProperty(AccessLevel.Counselor)] + public int Z => m_Location.Z; + } +} diff --git a/Projects/Server/Targeting/MultiTarget.cs b/Projects/Server/Targeting/MultiTarget.cs index 4b86ec9b0..f96dff4de 100644 --- a/Projects/Server/Targeting/MultiTarget.cs +++ b/Projects/Server/Targeting/MultiTarget.cs @@ -1,46 +1,48 @@ -/*************************************************************************** - * MultiTarget.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using Server.Network; - -namespace Server.Targeting -{ - public abstract class MultiTarget : Target - { - protected MultiTarget(int multiID, Point3D offset, int range = 10, bool allowGround = true, - TargetFlags flags = TargetFlags.None) - : base(range, allowGround, flags) - { - MultiID = multiID; - Offset = offset; - } - - public int MultiID { get; set; } - - public Point3D Offset { get; set; } - - public override Packet GetPacketFor(NetState ns) - { - if (ns.HighSeas) - return new MultiTargetReqHS(this); - return new MultiTargetReq(this); - } - } -} +/*************************************************************************** + * MultiTarget.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using Server.Network; + +namespace Server.Targeting +{ + public abstract class MultiTarget : Target + { + protected MultiTarget( + int multiID, Point3D offset, int range = 10, bool allowGround = true, + TargetFlags flags = TargetFlags.None + ) + : base(range, allowGround, flags) + { + MultiID = multiID; + Offset = offset; + } + + public int MultiID { get; set; } + + public Point3D Offset { get; set; } + + public override Packet GetPacketFor(NetState ns) + { + if (ns.HighSeas) + return new MultiTargetReqHS(this); + return new MultiTargetReq(this); + } + } +} diff --git a/Projects/Server/Targeting/StaticTarget.cs b/Projects/Server/Targeting/StaticTarget.cs index f90396f06..ae8d2c898 100644 --- a/Projects/Server/Targeting/StaticTarget.cs +++ b/Projects/Server/Targeting/StaticTarget.cs @@ -1,55 +1,55 @@ -/*************************************************************************** - * StaticTarget.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Targeting -{ - public class StaticTarget : IPoint3D - { - private Point3D m_Location; - - public StaticTarget(Point3D location, int itemID) - { - m_Location = location; - ItemID = itemID & TileData.MaxItemValue; - m_Location.Z += TileData.ItemTable[ItemID].CalcHeight; - } - - [CommandProperty(AccessLevel.Counselor)] - public Point3D Location => m_Location; - - [CommandProperty(AccessLevel.Counselor)] - public string Name => TileData.ItemTable[ItemID].Name; - - [CommandProperty(AccessLevel.Counselor)] - public TileFlag Flags => TileData.ItemTable[ItemID].Flags; - - [CommandProperty(AccessLevel.Counselor)] - public int ItemID { get; } - - [CommandProperty(AccessLevel.Counselor)] - public int X => m_Location.X; - - [CommandProperty(AccessLevel.Counselor)] - public int Y => m_Location.Y; - - [CommandProperty(AccessLevel.Counselor)] - public int Z => m_Location.Z; - } -} +/*************************************************************************** + * StaticTarget.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Targeting +{ + public class StaticTarget : IPoint3D + { + private Point3D m_Location; + + public StaticTarget(Point3D location, int itemID) + { + m_Location = location; + ItemID = itemID & TileData.MaxItemValue; + m_Location.Z += TileData.ItemTable[ItemID].CalcHeight; + } + + [CommandProperty(AccessLevel.Counselor)] + public Point3D Location => m_Location; + + [CommandProperty(AccessLevel.Counselor)] + public string Name => TileData.ItemTable[ItemID].Name; + + [CommandProperty(AccessLevel.Counselor)] + public TileFlag Flags => TileData.ItemTable[ItemID].Flags; + + [CommandProperty(AccessLevel.Counselor)] + public int ItemID { get; } + + [CommandProperty(AccessLevel.Counselor)] + public int X => m_Location.X; + + [CommandProperty(AccessLevel.Counselor)] + public int Y => m_Location.Y; + + [CommandProperty(AccessLevel.Counselor)] + public int Z => m_Location.Z; + } +} diff --git a/Projects/Server/Targeting/Target.cs b/Projects/Server/Targeting/Target.cs index ecd32183c..6e5523d1b 100644 --- a/Projects/Server/Targeting/Target.cs +++ b/Projects/Server/Targeting/Target.cs @@ -1,288 +1,289 @@ -/*************************************************************************** - * Target.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using Server.Network; - -namespace Server.Targeting -{ - public abstract class Target - { - private static int m_NextTargetID; - - private Timer m_TimeoutTimer; - - protected Target(int range, bool allowGround, TargetFlags flags) - { - TargetID = ++m_NextTargetID; - Range = range; - AllowGround = allowGround; - Flags = flags; - - CheckLOS = true; - } - - public DateTime TimeoutTime { get; private set; } - - public bool CheckLOS { get; set; } - - public bool DisallowMultis { get; set; } - - public bool AllowNonlocal { get; set; } - - public int TargetID { get; } - - public int Range { get; set; } - - public bool AllowGround { get; set; } - - public TargetFlags Flags { get; set; } - - public static void Cancel(Mobile m) - { - m.NetState?.Send(CancelTarget.Instance); - m.Target?.OnTargetCancel(m, TargetCancelType.Canceled); - } - - public void BeginTimeout(Mobile from, TimeSpan delay) - { - TimeoutTime = DateTime.UtcNow + delay; - - m_TimeoutTimer?.Stop(); - - m_TimeoutTimer = new TimeoutTimer(this, from, delay); - m_TimeoutTimer.Start(); - } - - public void CancelTimeout() - { - m_TimeoutTimer?.Stop(); - m_TimeoutTimer = null; - } - - public void Timeout(Mobile from) - { - CancelTimeout(); - from.ClearTarget(); - - Cancel(from); - - OnTargetCancel(from, TargetCancelType.Timeout); - OnTargetFinish(from); - } - - public virtual Packet GetPacketFor(NetState ns) => new TargetReq(this); - - public void Cancel(Mobile from, TargetCancelType type) - { - CancelTimeout(); - from.ClearTarget(); - - OnTargetCancel(from, type); - OnTargetFinish(from); - } - - public void Invoke(Mobile from, object targeted) - { - CancelTimeout(); - from.ClearTarget(); - - if (from.Deleted) - { - OnTargetCancel(from, TargetCancelType.Canceled); - OnTargetFinish(from); - return; - } - - Point3D loc; - Map map; - - var item = targeted as Item; - var mobile = targeted as Mobile; - - if (targeted is LandTarget target) - { - loc = target.Location; - map = from.Map; - } - else if (targeted is StaticTarget staticTarget) - { - loc = staticTarget.Location; - map = from.Map; - } - else if (mobile != null) - { - if (mobile.Deleted) - { - OnTargetDeleted(from, mobile); - OnTargetFinish(from); - return; - } - - if (!mobile.CanTarget) - { - OnTargetUntargetable(from, mobile); - OnTargetFinish(from); - return; - } - - loc = mobile.Location; - map = mobile.Map; - } - else if (item != null) - { - if (item.Deleted) - { - OnTargetDeleted(from, item); - OnTargetFinish(from); - return; - } - - if (!item.CanTarget) - { - OnTargetUntargetable(from, item); - OnTargetFinish(from); - return; - } - - if (!AllowNonlocal && item.RootParent is Mobile && item.RootParent != from && from.AccessLevel == AccessLevel.Player) - { - OnNonlocalTarget(from, item); - OnTargetFinish(from); - return; - } - - loc = item.GetWorldLocation(); - map = item.Map; - } - else - { - OnTargetCancel(from, TargetCancelType.Canceled); - OnTargetFinish(from); - return; - } - - if (map == null || map != from.Map || (Range != -1 && !from.InRange(loc, Range))) - { - OnTargetOutOfRange(from, targeted); - } - else - { - if (!from.CanSee(targeted)) - OnCantSeeTarget(from, targeted); - else if (CheckLOS && !from.InLOS(targeted)) - OnTargetOutOfLOS(from, targeted); - else if (item?.InSecureTrade == true) - OnTargetInSecureTrade(from, targeted); - else if (item?.IsAccessibleTo(from) == false) - OnTargetNotAccessible(from, targeted); - else if (item?.CheckTarget(from, this, targeted) == false) - OnTargetUntargetable(from, targeted); - else if (mobile?.CheckTarget(from, this, mobile) == false) - OnTargetUntargetable(from, mobile); - else if (from.Region.OnTarget(from, this, targeted)) - OnTarget(from, targeted); - } - - OnTargetFinish(from); - } - - protected virtual void OnTarget(Mobile from, object targeted) - { - } - - protected virtual void OnTargetNotAccessible(Mobile from, object targeted) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - protected virtual void OnTargetInSecureTrade(Mobile from, object targeted) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - protected virtual void OnNonlocalTarget(Mobile from, object targeted) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - protected virtual void OnCantSeeTarget(Mobile from, object targeted) - { - from.SendLocalizedMessage(500237); // Target can not be seen. - } - - protected virtual void OnTargetOutOfLOS(Mobile from, object targeted) - { - from.SendLocalizedMessage(500237); // Target can not be seen. - } - - protected virtual void OnTargetOutOfRange(Mobile from, object targeted) - { - from.SendLocalizedMessage(500446); // That is too far away. - } - - protected virtual void OnTargetDeleted(Mobile from, object targeted) - { - } - - protected virtual void OnTargetUntargetable(Mobile from, object targeted) - { - from.SendLocalizedMessage(500447); // That is not accessible. - } - - protected virtual void OnTargetCancel(Mobile from, TargetCancelType cancelType) - { - } - - protected virtual void OnTargetFinish(Mobile from) - { - } - - private class TimeoutTimer : Timer - { - private static readonly TimeSpan ThirtySeconds = TimeSpan.FromSeconds(30.0); - private static readonly TimeSpan TenSeconds = TimeSpan.FromSeconds(10.0); - private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1.0); - private readonly Mobile m_Mobile; - private readonly Target m_Target; - - public TimeoutTimer(Target target, Mobile m, TimeSpan delay) : base(delay) - { - m_Target = target; - m_Mobile = m; - - if (delay >= ThirtySeconds) - Priority = TimerPriority.FiveSeconds; - else if (delay >= TenSeconds) - Priority = TimerPriority.OneSecond; - else if (delay >= OneSecond) - Priority = TimerPriority.TwoFiftyMS; - else - Priority = TimerPriority.TwentyFiveMS; - } - - protected override void OnTick() - { - if (m_Mobile.Target == m_Target) - m_Target.Timeout(m_Mobile); - } - } - } -} +/*************************************************************************** + * Target.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using Server.Network; + +namespace Server.Targeting +{ + public abstract class Target + { + private static int m_NextTargetID; + + private Timer m_TimeoutTimer; + + protected Target(int range, bool allowGround, TargetFlags flags) + { + TargetID = ++m_NextTargetID; + Range = range; + AllowGround = allowGround; + Flags = flags; + + CheckLOS = true; + } + + public DateTime TimeoutTime { get; private set; } + + public bool CheckLOS { get; set; } + + public bool DisallowMultis { get; set; } + + public bool AllowNonlocal { get; set; } + + public int TargetID { get; } + + public int Range { get; set; } + + public bool AllowGround { get; set; } + + public TargetFlags Flags { get; set; } + + public static void Cancel(Mobile m) + { + m.NetState?.Send(CancelTarget.Instance); + m.Target?.OnTargetCancel(m, TargetCancelType.Canceled); + } + + public void BeginTimeout(Mobile from, TimeSpan delay) + { + TimeoutTime = DateTime.UtcNow + delay; + + m_TimeoutTimer?.Stop(); + + m_TimeoutTimer = new TimeoutTimer(this, from, delay); + m_TimeoutTimer.Start(); + } + + public void CancelTimeout() + { + m_TimeoutTimer?.Stop(); + m_TimeoutTimer = null; + } + + public void Timeout(Mobile from) + { + CancelTimeout(); + from.ClearTarget(); + + Cancel(from); + + OnTargetCancel(from, TargetCancelType.Timeout); + OnTargetFinish(from); + } + + public virtual Packet GetPacketFor(NetState ns) => new TargetReq(this); + + public void Cancel(Mobile from, TargetCancelType type) + { + CancelTimeout(); + from.ClearTarget(); + + OnTargetCancel(from, type); + OnTargetFinish(from); + } + + public void Invoke(Mobile from, object targeted) + { + CancelTimeout(); + from.ClearTarget(); + + if (from.Deleted) + { + OnTargetCancel(from, TargetCancelType.Canceled); + OnTargetFinish(from); + return; + } + + Point3D loc; + Map map; + + var item = targeted as Item; + var mobile = targeted as Mobile; + + if (targeted is LandTarget target) + { + loc = target.Location; + map = from.Map; + } + else if (targeted is StaticTarget staticTarget) + { + loc = staticTarget.Location; + map = from.Map; + } + else if (mobile != null) + { + if (mobile.Deleted) + { + OnTargetDeleted(from, mobile); + OnTargetFinish(from); + return; + } + + if (!mobile.CanTarget) + { + OnTargetUntargetable(from, mobile); + OnTargetFinish(from); + return; + } + + loc = mobile.Location; + map = mobile.Map; + } + else if (item != null) + { + if (item.Deleted) + { + OnTargetDeleted(from, item); + OnTargetFinish(from); + return; + } + + if (!item.CanTarget) + { + OnTargetUntargetable(from, item); + OnTargetFinish(from); + return; + } + + if (!AllowNonlocal && item.RootParent is Mobile && item.RootParent != from && + from.AccessLevel == AccessLevel.Player) + { + OnNonlocalTarget(from, item); + OnTargetFinish(from); + return; + } + + loc = item.GetWorldLocation(); + map = item.Map; + } + else + { + OnTargetCancel(from, TargetCancelType.Canceled); + OnTargetFinish(from); + return; + } + + if (map == null || map != from.Map || Range != -1 && !from.InRange(loc, Range)) + { + OnTargetOutOfRange(from, targeted); + } + else + { + if (!from.CanSee(targeted)) + OnCantSeeTarget(from, targeted); + else if (CheckLOS && !from.InLOS(targeted)) + OnTargetOutOfLOS(from, targeted); + else if (item?.InSecureTrade == true) + OnTargetInSecureTrade(from, targeted); + else if (item?.IsAccessibleTo(from) == false) + OnTargetNotAccessible(from, targeted); + else if (item?.CheckTarget(from, this, targeted) == false) + OnTargetUntargetable(from, targeted); + else if (mobile?.CheckTarget(from, this, mobile) == false) + OnTargetUntargetable(from, mobile); + else if (from.Region.OnTarget(from, this, targeted)) + OnTarget(from, targeted); + } + + OnTargetFinish(from); + } + + protected virtual void OnTarget(Mobile from, object targeted) + { + } + + protected virtual void OnTargetNotAccessible(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnTargetInSecureTrade(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnNonlocalTarget(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnCantSeeTarget(Mobile from, object targeted) + { + from.SendLocalizedMessage(500237); // Target can not be seen. + } + + protected virtual void OnTargetOutOfLOS(Mobile from, object targeted) + { + from.SendLocalizedMessage(500237); // Target can not be seen. + } + + protected virtual void OnTargetOutOfRange(Mobile from, object targeted) + { + from.SendLocalizedMessage(500446); // That is too far away. + } + + protected virtual void OnTargetDeleted(Mobile from, object targeted) + { + } + + protected virtual void OnTargetUntargetable(Mobile from, object targeted) + { + from.SendLocalizedMessage(500447); // That is not accessible. + } + + protected virtual void OnTargetCancel(Mobile from, TargetCancelType cancelType) + { + } + + protected virtual void OnTargetFinish(Mobile from) + { + } + + private class TimeoutTimer : Timer + { + private static readonly TimeSpan ThirtySeconds = TimeSpan.FromSeconds(30.0); + private static readonly TimeSpan TenSeconds = TimeSpan.FromSeconds(10.0); + private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1.0); + private readonly Mobile m_Mobile; + private readonly Target m_Target; + + public TimeoutTimer(Target target, Mobile m, TimeSpan delay) : base(delay) + { + m_Target = target; + m_Mobile = m; + + if (delay >= ThirtySeconds) + Priority = TimerPriority.FiveSeconds; + else if (delay >= TenSeconds) + Priority = TimerPriority.OneSecond; + else if (delay >= OneSecond) + Priority = TimerPriority.TwoFiftyMS; + else + Priority = TimerPriority.TwentyFiveMS; + } + + protected override void OnTick() + { + if (m_Mobile.Target == m_Target) + m_Target.Timeout(m_Mobile); + } + } + } +} diff --git a/Projects/Server/Targeting/TargetCancelType.cs b/Projects/Server/Targeting/TargetCancelType.cs index 4accc9265..65e99da20 100644 --- a/Projects/Server/Targeting/TargetCancelType.cs +++ b/Projects/Server/Targeting/TargetCancelType.cs @@ -1,30 +1,30 @@ -/*************************************************************************** - * TargetCancelType.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server.Targeting -{ - public enum TargetCancelType - { - Overridden, - Canceled, - Disconnected, - Timeout - } -} +/*************************************************************************** + * TargetCancelType.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server.Targeting +{ + public enum TargetCancelType + { + Overridden, + Canceled, + Disconnected, + Timeout + } +} diff --git a/Projects/Server/Targeting/TargetFlags.cs b/Projects/Server/Targeting/TargetFlags.cs index 95e31f5c6..efa420b20 100644 --- a/Projects/Server/Targeting/TargetFlags.cs +++ b/Projects/Server/Targeting/TargetFlags.cs @@ -1,32 +1,32 @@ -/*************************************************************************** - * TargetFlags.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; - -namespace Server.Targeting -{ - [Flags] - public enum TargetFlags : byte - { - None = 0x00, - Harmful = 0x01, - Beneficial = 0x02 - } -} +/*************************************************************************** + * TargetFlags.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server.Targeting +{ + [Flags] + public enum TargetFlags : byte + { + None = 0x00, + Harmful = 0x01, + Beneficial = 0x02 + } +} diff --git a/Projects/Server/TileData.cs b/Projects/Server/TileData.cs index 199c6e059..2916e27db 100644 --- a/Projects/Server/TileData.cs +++ b/Projects/Server/TileData.cs @@ -1,265 +1,274 @@ -/*************************************************************************** - * TileData.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.IO; -using System.Text; - -namespace Server -{ - public struct LandData - { - public LandData(string name, TileFlag flags) - { - Name = name; - Flags = flags; - } - - public string Name { get; set; } - - public TileFlag Flags { get; set; } - } - - public struct ItemData - { - private byte m_Weight; - private byte m_Quality; - private byte m_Quantity; - private byte m_Value; - private byte m_Height; - - public ItemData(string name, TileFlag flags, int weight, int quality, int quantity, int value, int height) - { - Name = name; - Flags = flags; - m_Weight = (byte)weight; - m_Quality = (byte)quality; - m_Quantity = (byte)quantity; - m_Value = (byte)value; - m_Height = (byte)height; - } - - public string Name { get; set; } - - public TileFlag Flags { get; set; } - - public bool Bridge - { - get => (Flags & TileFlag.Bridge) != 0; - set - { - if (value) - Flags |= TileFlag.Bridge; - else - Flags &= ~TileFlag.Bridge; - } - } - - public bool Impassable - { - get => (Flags & TileFlag.Impassable) != 0; - set - { - if (value) - Flags |= TileFlag.Impassable; - else - Flags &= ~TileFlag.Impassable; - } - } - - public bool Surface - { - get => (Flags & TileFlag.Surface) != 0; - set - { - if (value) - Flags |= TileFlag.Surface; - else - Flags &= ~TileFlag.Surface; - } - } - - public int Weight - { - get => m_Weight; - set => m_Weight = (byte)value; - } - - public int Quality - { - get => m_Quality; - set => m_Quality = (byte)value; - } - - public int Quantity - { - get => m_Quantity; - set => m_Quantity = (byte)value; - } - - public int Value - { - get => m_Value; - set => m_Value = (byte)value; - } - - public int Height - { - get => m_Height; - set => m_Height = (byte)value; - } - - public int CalcHeight - { - get - { - if ((Flags & TileFlag.Bridge) != 0) - return m_Height / 2; - return m_Height; - } - } - } - - [Flags] - public enum TileFlag : long - { - None = 0x00000000, - Background = 0x00000001, - Weapon = 0x00000002, - Transparent = 0x00000004, - Translucent = 0x00000008, - Wall = 0x00000010, - Damaging = 0x00000020, - Impassable = 0x00000040, - Wet = 0x00000080, - Unknown1 = 0x00000100, - Surface = 0x00000200, - Bridge = 0x00000400, - Generic = 0x00000800, - Window = 0x00001000, - NoShoot = 0x00002000, - ArticleA = 0x00004000, - ArticleAn = 0x00008000, - Internal = 0x00010000, - Foliage = 0x00020000, - PartialHue = 0x00040000, - Unknown2 = 0x00080000, - Map = 0x00100000, - Container = 0x00200000, - Wearable = 0x00400000, - LightSource = 0x00800000, - Animation = 0x01000000, - NoDiagonal = 0x02000000, - Unknown3 = 0x04000000, - Armor = 0x08000000, - Roof = 0x10000000, - Door = 0x20000000, - StairBack = 0x40000000, - StairRight = 0x80000000 - } - - public static class TileData - { - private static readonly byte[] m_StringBuffer = new byte[20]; - - static TileData() - { - ItemTable = new ItemData[0x10000]; - LandTable = new LandData[0x4000]; - - if (Core.IsRunningFromXUnit) return; - - var filePath = Core.FindDataFile("tiledata.mul"); - - using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); - var bin = new BinaryReader(fs); - - var is7090 = fs.Length == 3188736; - bool is7000 = fs.Length == 1644544; - - for (var i = 0; i < 0x4000; i++) - { - // header - if (is7090) - { - if (i == 1 || i > 0 && (i & 0x1F) == 0) - bin.ReadInt32(); - } - else if ((i & 0x1F) == 0) - bin.ReadInt32(); - - var flags = (TileFlag)(is7090 ? bin.ReadInt64() : bin.ReadInt32()); - bin.ReadInt16(); // skip 2 bytes -- textureID - - LandTable[i] = new LandData(ReadNameString(bin), flags); - } - - int length = is7090 ? 0x10000 : is7000 ? 0x8000 : 0x4000; - - for (var i = 0; i < length; i++) - { - if ((i & 0x1F) == 0) bin.ReadInt32(); // header - - var flags = (TileFlag)(is7090 ? bin.ReadInt64() : bin.ReadInt32()); - int weight = bin.ReadByte(); - int quality = bin.ReadByte(); - bin.ReadInt16(); - bin.ReadByte(); - int quantity = bin.ReadByte(); - bin.ReadInt32(); - bin.ReadByte(); - int value = bin.ReadByte(); - int height = bin.ReadByte(); - - ItemTable[i] = new ItemData( - ReadNameString(bin), flags, weight, quality, quantity, value, height - ); - } - - MaxLandValue = LandTable.Length - 1; - MaxItemValue = ItemTable.Length - 1; - } - - public static LandData[] LandTable { get; } - - public static ItemData[] ItemTable { get; } - - public static int MaxLandValue { get; } - - public static int MaxItemValue { get; } - - private static string ReadNameString(BinaryReader bin) - { - bin.Read(m_StringBuffer, 0, 20); - - int count = 0; - - while (count < 20) - { - if (m_StringBuffer[count] == 0) - break; - - count++; - } - - return Encoding.ASCII.GetString(new ReadOnlySpan(m_StringBuffer, 0, count)); - } - } -} +/*************************************************************************** + * TileData.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.IO; +using System.Text; + +namespace Server +{ + public struct LandData + { + public LandData(string name, TileFlag flags) + { + Name = name; + Flags = flags; + } + + public string Name { get; set; } + + public TileFlag Flags { get; set; } + } + + public struct ItemData + { + private byte m_Weight; + private byte m_Quality; + private byte m_Quantity; + private byte m_Value; + private byte m_Height; + + public ItemData(string name, TileFlag flags, int weight, int quality, int quantity, int value, int height) + { + Name = name; + Flags = flags; + m_Weight = (byte)weight; + m_Quality = (byte)quality; + m_Quantity = (byte)quantity; + m_Value = (byte)value; + m_Height = (byte)height; + } + + public string Name { get; set; } + + public TileFlag Flags { get; set; } + + public bool Bridge + { + get => (Flags & TileFlag.Bridge) != 0; + set + { + if (value) + Flags |= TileFlag.Bridge; + else + Flags &= ~TileFlag.Bridge; + } + } + + public bool Impassable + { + get => (Flags & TileFlag.Impassable) != 0; + set + { + if (value) + Flags |= TileFlag.Impassable; + else + Flags &= ~TileFlag.Impassable; + } + } + + public bool Surface + { + get => (Flags & TileFlag.Surface) != 0; + set + { + if (value) + Flags |= TileFlag.Surface; + else + Flags &= ~TileFlag.Surface; + } + } + + public int Weight + { + get => m_Weight; + set => m_Weight = (byte)value; + } + + public int Quality + { + get => m_Quality; + set => m_Quality = (byte)value; + } + + public int Quantity + { + get => m_Quantity; + set => m_Quantity = (byte)value; + } + + public int Value + { + get => m_Value; + set => m_Value = (byte)value; + } + + public int Height + { + get => m_Height; + set => m_Height = (byte)value; + } + + public int CalcHeight + { + get + { + if ((Flags & TileFlag.Bridge) != 0) + return m_Height / 2; + return m_Height; + } + } + } + + [Flags] + public enum TileFlag : long + { + None = 0x00000000, + Background = 0x00000001, + Weapon = 0x00000002, + Transparent = 0x00000004, + Translucent = 0x00000008, + Wall = 0x00000010, + Damaging = 0x00000020, + Impassable = 0x00000040, + Wet = 0x00000080, + Unknown1 = 0x00000100, + Surface = 0x00000200, + Bridge = 0x00000400, + Generic = 0x00000800, + Window = 0x00001000, + NoShoot = 0x00002000, + ArticleA = 0x00004000, + ArticleAn = 0x00008000, + Internal = 0x00010000, + Foliage = 0x00020000, + PartialHue = 0x00040000, + Unknown2 = 0x00080000, + Map = 0x00100000, + Container = 0x00200000, + Wearable = 0x00400000, + LightSource = 0x00800000, + Animation = 0x01000000, + NoDiagonal = 0x02000000, + Unknown3 = 0x04000000, + Armor = 0x08000000, + Roof = 0x10000000, + Door = 0x20000000, + StairBack = 0x40000000, + StairRight = 0x80000000 + } + + public static class TileData + { + private static readonly byte[] m_StringBuffer = new byte[20]; + + static TileData() + { + ItemTable = new ItemData[0x10000]; + LandTable = new LandData[0x4000]; + + if (Core.IsRunningFromXUnit) return; + + var filePath = Core.FindDataFile("tiledata.mul"); + + using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + var bin = new BinaryReader(fs); + + var is7090 = fs.Length == 3188736; + var is7000 = fs.Length == 1644544; + + for (var i = 0; i < 0x4000; i++) + { + // header + if (is7090) + { + if (i == 1 || i > 0 && (i & 0x1F) == 0) + bin.ReadInt32(); + } + else if ((i & 0x1F) == 0) + { + bin.ReadInt32(); + } + + var flags = (TileFlag)(is7090 ? bin.ReadInt64() : bin.ReadInt32()); + bin.ReadInt16(); // skip 2 bytes -- textureID + + LandTable[i] = new LandData(ReadNameString(bin), flags); + } + + var length = is7090 ? 0x10000 : + is7000 ? 0x8000 : 0x4000; + + for (var i = 0; i < length; i++) + { + if ((i & 0x1F) == 0) bin.ReadInt32(); // header + + var flags = (TileFlag)(is7090 ? bin.ReadInt64() : bin.ReadInt32()); + int weight = bin.ReadByte(); + int quality = bin.ReadByte(); + bin.ReadInt16(); + bin.ReadByte(); + int quantity = bin.ReadByte(); + bin.ReadInt32(); + bin.ReadByte(); + int value = bin.ReadByte(); + int height = bin.ReadByte(); + + ItemTable[i] = new ItemData( + ReadNameString(bin), + flags, + weight, + quality, + quantity, + value, + height + ); + } + + MaxLandValue = LandTable.Length - 1; + MaxItemValue = ItemTable.Length - 1; + } + + public static LandData[] LandTable { get; } + + public static ItemData[] ItemTable { get; } + + public static int MaxLandValue { get; } + + public static int MaxItemValue { get; } + + private static string ReadNameString(BinaryReader bin) + { + bin.Read(m_StringBuffer, 0, 20); + + var count = 0; + + while (count < 20) + { + if (m_StringBuffer[count] == 0) + break; + + count++; + } + + return Encoding.ASCII.GetString(new ReadOnlySpan(m_StringBuffer, 0, count)); + } + } +} diff --git a/Projects/Server/TileList.cs b/Projects/Server/TileList.cs index 96efd4afb..b4035c4b2 100644 --- a/Projects/Server/TileList.cs +++ b/Projects/Server/TileList.cs @@ -1,82 +1,84 @@ -/*************************************************************************** - * TileList.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server -{ - public class TileList - { - private static readonly StaticTile[] m_EmptyTiles = System.Array.Empty(); - private StaticTile[] m_Tiles; - - public TileList() - { - m_Tiles = new StaticTile[8]; - Count = 0; - } - - public int Count { get; private set; } - - public void AddRange(StaticTile[] tiles) - { - if (Count + tiles.Length > m_Tiles.Length) - { - var old = m_Tiles; - m_Tiles = new StaticTile[(Count + tiles.Length) * 2]; - - for (var i = 0; i < old.Length; ++i) - m_Tiles[i] = old[i]; - } - - for (var i = 0; i < tiles.Length; ++i) - m_Tiles[Count++] = tiles[i]; - } - - public void Add(ushort id, sbyte z) - { - if (Count + 1 > m_Tiles.Length) - { - var old = m_Tiles; - m_Tiles = new StaticTile[old.Length * 2]; - - for (var i = 0; i < old.Length; ++i) - m_Tiles[i] = old[i]; - } - - m_Tiles[Count].m_ID = id; - m_Tiles[Count].m_Z = z; - ++Count; - } - - public StaticTile[] ToArray() - { - if (Count == 0) - return m_EmptyTiles; - - var tiles = new StaticTile[Count]; - - for (var i = 0; i < Count; ++i) - tiles[i] = m_Tiles[i]; - - Count = 0; - - return tiles; - } - } -} +/*************************************************************************** + * TileList.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; + +namespace Server +{ + public class TileList + { + private static readonly StaticTile[] m_EmptyTiles = Array.Empty(); + private StaticTile[] m_Tiles; + + public TileList() + { + m_Tiles = new StaticTile[8]; + Count = 0; + } + + public int Count { get; private set; } + + public void AddRange(StaticTile[] tiles) + { + if (Count + tiles.Length > m_Tiles.Length) + { + var old = m_Tiles; + m_Tiles = new StaticTile[(Count + tiles.Length) * 2]; + + for (var i = 0; i < old.Length; ++i) + m_Tiles[i] = old[i]; + } + + for (var i = 0; i < tiles.Length; ++i) + m_Tiles[Count++] = tiles[i]; + } + + public void Add(ushort id, sbyte z) + { + if (Count + 1 > m_Tiles.Length) + { + var old = m_Tiles; + m_Tiles = new StaticTile[old.Length * 2]; + + for (var i = 0; i < old.Length; ++i) + m_Tiles[i] = old[i]; + } + + m_Tiles[Count].m_ID = id; + m_Tiles[Count].m_Z = z; + ++Count; + } + + public StaticTile[] ToArray() + { + if (Count == 0) + return m_EmptyTiles; + + var tiles = new StaticTile[Count]; + + for (var i = 0; i < Count; ++i) + tiles[i] = m_Tiles[i]; + + Count = 0; + + return tiles; + } + } +} diff --git a/Projects/Server/TileMatrix.cs b/Projects/Server/TileMatrix.cs index 82480939e..6e2c0a674 100644 --- a/Projects/Server/TileMatrix.cs +++ b/Projects/Server/TileMatrix.cs @@ -1,635 +1,641 @@ -/*************************************************************************** - * TileMatrix.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Server -{ - public class TileMatrix - { - private static readonly List m_Instances = new List(); - - private readonly int m_FileIndex; - private readonly List m_FileShare = new List(); - - private readonly FileStream m_MapStream; - - public FileStream IndexStream { get; } - - public FileStream DataStream { get; } - - private readonly BinaryReader m_IndexReader; - - private readonly LandTile[] m_InvalidLandBlock; - private readonly int[][] m_LandPatches; - private readonly LandTile[][][] m_LandTiles; - - private TileList[][] m_Lists; - - private readonly UOPIndex m_MapIndex; - private DateTime m_NextLandWarning; - - private DateTime m_NextStaticWarning; - - private readonly Map m_Owner; - - private readonly int[][] m_StaticPatches; - private readonly StaticTile[][][][][] m_StaticTiles; - - private StaticTile[] m_TileBuffer = new StaticTile[128]; - - private readonly TileList m_TilesList = new TileList(); - - public TileMatrix(Map owner, int fileIndex, int mapID, int width, int height) - { - lock (m_Instances) - { - for (var i = 0; i < m_Instances.Count; ++i) - { - var tm = m_Instances[i]; - - if (tm.m_FileIndex == fileIndex) - lock (m_FileShare) - { - lock (tm.m_FileShare) - { - tm.m_FileShare.Add(this); - m_FileShare.Add(tm); - } - } - } - - m_Instances.Add(this); - } - - m_FileIndex = fileIndex; - BlockWidth = width >> 3; - BlockHeight = height >> 3; - - m_Owner = owner; - - if (fileIndex != 0x7F) - { - var mapPath = Core.FindDataFile($"map{fileIndex}LegacyMUL.uop", false, true); - - if (mapPath != null) - { - m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - m_MapIndex = new UOPIndex(m_MapStream); - } - else - { - mapPath = Core.FindDataFile($"map{fileIndex}.mul", false, true); - - if (mapPath != null) - m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - } - - var indexPath = Core.FindDataFile($"staidx{fileIndex}.mul", false, true); - - if (indexPath != null) - { - IndexStream = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - m_IndexReader = new BinaryReader(IndexStream); - } - - var staticsPath = Core.FindDataFile($"statics{fileIndex}.mul", false, true); - - if (staticsPath != null) - DataStream = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - } - - EmptyStaticBlock = new StaticTile[8][][]; - - for (var i = 0; i < 8; ++i) - { - EmptyStaticBlock[i] = new StaticTile[8][]; - - for (var j = 0; j < 8; ++j) - EmptyStaticBlock[i][j] = Array.Empty(); - } - - m_InvalidLandBlock = new LandTile[196]; - - m_LandTiles = new LandTile[BlockWidth][][]; - m_StaticTiles = new StaticTile[BlockWidth][][][][]; - m_StaticPatches = new int[BlockWidth][]; - m_LandPatches = new int[BlockWidth][]; - - Patch = new TileMatrixPatch(this, mapID); - } - - public TileMatrixPatch Patch { get; } - - public int BlockWidth { get; } - - public int BlockHeight { get; } - - public StaticTile[][][] EmptyStaticBlock { get; } - - [MethodImpl(MethodImplOptions.Synchronized)] - public void SetStaticBlock(int x, int y, StaticTile[][][] value) - { - if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) - return; - - m_StaticTiles[x] ??= new StaticTile[BlockHeight][][][]; - m_StaticTiles[x][y] = value; - - m_StaticPatches[x] ??= new int[(BlockHeight + 31) >> 5]; - m_StaticPatches[x][y >> 5] |= 1 << (y & 0x1F); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public StaticTile[][][] GetStaticBlock(int x, int y) - { - if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || DataStream == null || IndexStream == null) - return EmptyStaticBlock; - - m_StaticTiles[x] ??= new StaticTile[BlockHeight][][][]; - - var tiles = m_StaticTiles[x][y]; - - if (tiles != null) - return tiles; - - lock (m_FileShare) - { - for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) - { - var shared = m_FileShare[i]; - - lock (shared) - { - if (x < shared.BlockWidth && y < shared.BlockHeight) - { - var theirTiles = shared.m_StaticTiles[x]; - - if (theirTiles != null) - tiles = theirTiles[y]; - - if (tiles != null) - { - var theirBits = shared.m_StaticPatches[x]; - - if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) - tiles = null; - } - } - } - } - } - - return m_StaticTiles[x][y] = tiles ?? ReadStaticBlock(x, y); - } - - public StaticTile[] GetStaticTiles(int x, int y) => GetStaticBlock(x >> 3, y >> 3)[x & 0x7][y & 0x7]; - - [MethodImpl(MethodImplOptions.Synchronized)] - public StaticTile[] GetStaticTiles(int x, int y, bool multis) - { - var tiles = GetStaticBlock(x >> 3, y >> 3); - - if (!multis) - return tiles[x & 0x7][y & 0x7]; - - var eable = m_Owner.GetMultiTilesAt(x, y); - - if (eable == Map.NullEnumerable.Instance) - return tiles[x & 0x7][y & 0x7]; - - var any = false; - - m_TilesList.AddRange(eable.SelectMany(t => - { - any = true; - return t; - }).ToArray()); - - eable.Free(); - - if (!any) - return tiles[x & 0x7][y & 0x7]; - - m_TilesList.AddRange(tiles[x & 0x7][y & 0x7]); - - return m_TilesList.ToArray(); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public void SetLandBlock(int x, int y, LandTile[] value) - { - if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) - return; - - m_LandTiles[x] ??= new LandTile[BlockHeight][]; - m_LandTiles[x][y] = value; - - m_LandPatches[x] ??= new int[(BlockHeight + 31) >> 5]; - m_LandPatches[x][y >> 5] |= 1 << (y & 0x1F); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public LandTile[] GetLandBlock(int x, int y) - { - if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || m_MapStream == null) - return m_InvalidLandBlock; - - m_LandTiles[x] ??= new LandTile[BlockHeight][]; - - var tiles = m_LandTiles[x][y]; - - if (tiles != null) - return tiles; - - lock (m_FileShare) - { - for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) - { - var shared = m_FileShare[i]; - - lock (shared) - { - if (x < shared.BlockWidth && y < shared.BlockHeight) - { - var theirTiles = shared.m_LandTiles[x]; - - if (theirTiles != null) - tiles = theirTiles[y]; - - if (tiles != null) - { - var theirBits = shared.m_LandPatches[x]; - - if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) - tiles = null; - } - } - } - } - } - - return m_LandTiles[x][y] = tiles ?? ReadLandBlock(x, y); - } - - public LandTile GetLandTile(int x, int y) => GetLandBlock(x >> 3, y >> 3)[((y & 0x7) << 3) + (x & 0x7)]; - - [MethodImpl(MethodImplOptions.Synchronized)] - private unsafe StaticTile[][][] ReadStaticBlock(int x, int y) - { - try - { - m_IndexReader.BaseStream.Seek((x * BlockHeight + y) * 12, SeekOrigin.Begin); - - var lookup = m_IndexReader.ReadInt32(); - var length = m_IndexReader.ReadInt32(); - - if (lookup < 0 || length <= 0) - return EmptyStaticBlock; - - var count = length / 7; - - DataStream.Seek(lookup, SeekOrigin.Begin); - - if (m_TileBuffer.Length < count) - m_TileBuffer = new StaticTile[count]; - - var staTiles = m_TileBuffer; // new StaticTile[tileCount]; - - fixed (StaticTile* pTiles = staTiles) - { - NativeReader.Read(DataStream.SafeFileHandle.DangerousGetHandle(), pTiles, length); - if (m_Lists == null) - { - m_Lists = new TileList[8][]; - - for (var i = 0; i < 8; ++i) - { - m_Lists[i] = new TileList[8]; - - for (var j = 0; j < 8; ++j) - m_Lists[i][j] = new TileList(); - } - } - - var lists = m_Lists; - - StaticTile* pCur = pTiles, pEnd = pTiles + count; - - while (pCur < pEnd) - { - lists[pCur->m_X & 0x7][pCur->m_Y & 0x7].Add(pCur->m_ID, pCur->m_Z); - pCur += 1; - } - - var tiles = new StaticTile[8][][]; - - for (var i = 0; i < 8; ++i) - { - tiles[i] = new StaticTile[8][]; - - for (var j = 0; j < 8; ++j) - tiles[i][j] = lists[i][j].ToArray(); - } - - return tiles; - } - } - catch (EndOfStreamException) - { - if (DateTime.UtcNow >= m_NextStaticWarning) - { - Console.WriteLine("Warning: Static EOS for {0} ({1}, {2})", m_Owner, x, y); - m_NextStaticWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); - } - - return EmptyStaticBlock; - } - } - - public void Force() - { - if ((AssemblyHandler.Assemblies?.Length ?? 0) == 0) - throw new Exception(); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private unsafe LandTile[] ReadLandBlock(int x, int y) - { - try - { - var offset = (x * BlockHeight + y) * 196 + 4; - - if (m_MapIndex != null) - offset = m_MapIndex.Lookup(offset); - - m_MapStream.Seek(offset, SeekOrigin.Begin); - - var tiles = new LandTile[64]; - - fixed (LandTile* pTiles = tiles) - { - NativeReader.Read(m_MapStream.SafeFileHandle.DangerousGetHandle(), pTiles, 192); - } - - return tiles; - } - catch - { - if (DateTime.UtcNow >= m_NextLandWarning) - { - Console.WriteLine("Warning: Land EOS for {0} ({1}, {2})", m_Owner, x, y); - m_NextLandWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); - } - - return m_InvalidLandBlock; - } - } - - public void Dispose() - { - m_MapIndex?.Close(); - m_MapStream?.Close(); - DataStream?.Close(); - m_IndexReader?.Close(); - } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct LandTile - { - internal short m_ID; - internal sbyte m_Z; - - public int ID => m_ID; - - public int Z - { - get => m_Z; - set => m_Z = (sbyte)value; - } - - public int Height => 0; - - public bool Ignored => m_ID == 2 || m_ID == 0x1DB || (m_ID >= 0x1AE && m_ID <= 0x1B5); - - public LandTile(short id, sbyte z) - { - m_ID = id; - m_Z = z; - } - - public void Set(short id, sbyte z) - { - m_ID = id; - m_Z = z; - } - } - - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct StaticTile - { - internal ushort m_ID; - internal byte m_X; - internal byte m_Y; - internal sbyte m_Z; - internal short m_Hue; - - public int ID => m_ID; - - public int X - { - get => m_X; - set => m_X = (byte)value; - } - - public int Y - { - get => m_Y; - set => m_Y = (byte)value; - } - - public int Z - { - get => m_Z; - set => m_Z = (sbyte)value; - } - - public int Hue - { - get => m_Hue; - set => m_Hue = (short)value; - } - - public int Height => TileData.ItemTable[m_ID & TileData.MaxItemValue].Height; - - public StaticTile(ushort id, sbyte z) - { - m_ID = id; - m_Z = z; - - m_X = 0; - m_Y = 0; - m_Hue = 0; - } - - public StaticTile(ushort id, byte x, byte y, sbyte z, short hue) - { - m_ID = id; - m_X = x; - m_Y = y; - m_Z = z; - m_Hue = hue; - } - - public void Set(ushort id, sbyte z) - { - m_ID = id; - m_Z = z; - } - - public void Set(ushort id, byte x, byte y, sbyte z, short hue) - { - m_ID = id; - m_X = x; - m_Y = y; - m_Z = z; - m_Hue = hue; - } - } - - public class UOPIndex - { - private readonly UOPEntry[] m_Entries; - private readonly int m_Length; - - private readonly BinaryReader m_Reader; - - public UOPIndex(FileStream stream) - { - m_Reader = new BinaryReader(stream); - m_Length = (int)stream.Length; - - if (m_Reader.ReadInt32() != 0x50594D) - throw new ArgumentException("Invalid UOP file."); - - Version = m_Reader.ReadInt32(); - m_Reader.ReadInt32(); - var nextTable = m_Reader.ReadInt32(); - - var entries = new List(); - - do - { - stream.Seek(nextTable, SeekOrigin.Begin); - var count = m_Reader.ReadInt32(); - nextTable = m_Reader.ReadInt32(); - m_Reader.ReadInt32(); - - for (var i = 0; i < count; ++i) - { - var offset = m_Reader.ReadInt32(); - - if (offset == 0) - { - stream.Seek(30, SeekOrigin.Current); - continue; - } - - m_Reader.ReadInt64(); - var length = m_Reader.ReadInt32(); - - entries.Add(new UOPEntry(offset, length)); - - stream.Seek(18, SeekOrigin.Current); - } - } while (nextTable != 0 && nextTable < m_Length); - - entries.Sort(OffsetComparer.Instance); - - for (var i = 0; i < entries.Count; ++i) - { - stream.Seek(entries[i].m_Offset + 2, SeekOrigin.Begin); - - int dataOffset = m_Reader.ReadInt16(); - entries[i].m_Offset += 4 + dataOffset; - - stream.Seek(dataOffset, SeekOrigin.Current); - entries[i].m_Order = m_Reader.ReadInt32(); - } - - entries.Sort(); - m_Entries = entries.ToArray(); - } - - public int Version { get; } - - public int Lookup(int offset) - { - var total = 0; - - for (var i = 0; i < m_Entries.Length; ++i) - { - var newTotal = total + m_Entries[i].m_Length; - - if (offset < newTotal) - return m_Entries[i].m_Offset + (offset - total); - - total = newTotal; - } - - return m_Length; - } - - public void Close() - { - m_Reader.Close(); - } - - private class UOPEntry : IComparable - { - public readonly int m_Length; - public int m_Offset; - public int m_Order; - - public UOPEntry(int offset, int length) - { - m_Offset = offset; - m_Length = length; - m_Order = 0; - } - - public int CompareTo(UOPEntry other) => m_Order.CompareTo(other.m_Order); - } - - private class OffsetComparer : IComparer - { - public static readonly IComparer Instance = new OffsetComparer(); - - public int Compare(UOPEntry x, UOPEntry y) => - x == null ? y == null ? 0 : 1 : y == null ? -1 : x.m_Offset.CompareTo(y.m_Offset); - } - } -} +/*************************************************************************** + * TileMatrix.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Server +{ + public class TileMatrix + { + private static readonly List m_Instances = new List(); + + private readonly int m_FileIndex; + private readonly List m_FileShare = new List(); + + private readonly BinaryReader m_IndexReader; + + private readonly LandTile[] m_InvalidLandBlock; + private readonly int[][] m_LandPatches; + private readonly LandTile[][][] m_LandTiles; + + private readonly UOPIndex m_MapIndex; + + private readonly FileStream m_MapStream; + + private readonly Map m_Owner; + + private readonly int[][] m_StaticPatches; + private readonly StaticTile[][][][][] m_StaticTiles; + + private readonly TileList m_TilesList = new TileList(); + + private TileList[][] m_Lists; + private DateTime m_NextLandWarning; + + private DateTime m_NextStaticWarning; + + private StaticTile[] m_TileBuffer = new StaticTile[128]; + + public TileMatrix(Map owner, int fileIndex, int mapID, int width, int height) + { + lock (m_Instances) + { + for (var i = 0; i < m_Instances.Count; ++i) + { + var tm = m_Instances[i]; + + if (tm.m_FileIndex == fileIndex) + lock (m_FileShare) + { + lock (tm.m_FileShare) + { + tm.m_FileShare.Add(this); + m_FileShare.Add(tm); + } + } + } + + m_Instances.Add(this); + } + + m_FileIndex = fileIndex; + BlockWidth = width >> 3; + BlockHeight = height >> 3; + + m_Owner = owner; + + if (fileIndex != 0x7F) + { + var mapPath = Core.FindDataFile($"map{fileIndex}LegacyMUL.uop", false, true); + + if (mapPath != null) + { + m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + m_MapIndex = new UOPIndex(m_MapStream); + } + else + { + mapPath = Core.FindDataFile($"map{fileIndex}.mul", false, true); + + if (mapPath != null) + m_MapStream = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + } + + var indexPath = Core.FindDataFile($"staidx{fileIndex}.mul", false, true); + + if (indexPath != null) + { + IndexStream = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + m_IndexReader = new BinaryReader(IndexStream); + } + + var staticsPath = Core.FindDataFile($"statics{fileIndex}.mul", false, true); + + if (staticsPath != null) + DataStream = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + } + + EmptyStaticBlock = new StaticTile[8][][]; + + for (var i = 0; i < 8; ++i) + { + EmptyStaticBlock[i] = new StaticTile[8][]; + + for (var j = 0; j < 8; ++j) + EmptyStaticBlock[i][j] = Array.Empty(); + } + + m_InvalidLandBlock = new LandTile[196]; + + m_LandTiles = new LandTile[BlockWidth][][]; + m_StaticTiles = new StaticTile[BlockWidth][][][][]; + m_StaticPatches = new int[BlockWidth][]; + m_LandPatches = new int[BlockWidth][]; + + Patch = new TileMatrixPatch(this, mapID); + } + + public FileStream IndexStream { get; } + + public FileStream DataStream { get; } + + public TileMatrixPatch Patch { get; } + + public int BlockWidth { get; } + + public int BlockHeight { get; } + + public StaticTile[][][] EmptyStaticBlock { get; } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetStaticBlock(int x, int y, StaticTile[][][] value) + { + if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) + return; + + m_StaticTiles[x] ??= new StaticTile[BlockHeight][][][]; + m_StaticTiles[x][y] = value; + + m_StaticPatches[x] ??= new int[(BlockHeight + 31) >> 5]; + m_StaticPatches[x][y >> 5] |= 1 << (y & 0x1F); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public StaticTile[][][] GetStaticBlock(int x, int y) + { + if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || DataStream == null || IndexStream == null) + return EmptyStaticBlock; + + m_StaticTiles[x] ??= new StaticTile[BlockHeight][][][]; + + var tiles = m_StaticTiles[x][y]; + + if (tiles != null) + return tiles; + + lock (m_FileShare) + { + for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) + { + var shared = m_FileShare[i]; + + lock (shared) + { + if (x < shared.BlockWidth && y < shared.BlockHeight) + { + var theirTiles = shared.m_StaticTiles[x]; + + if (theirTiles != null) + tiles = theirTiles[y]; + + if (tiles != null) + { + var theirBits = shared.m_StaticPatches[x]; + + if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) + tiles = null; + } + } + } + } + } + + return m_StaticTiles[x][y] = tiles ?? ReadStaticBlock(x, y); + } + + public StaticTile[] GetStaticTiles(int x, int y) => GetStaticBlock(x >> 3, y >> 3)[x & 0x7][y & 0x7]; + + [MethodImpl(MethodImplOptions.Synchronized)] + public StaticTile[] GetStaticTiles(int x, int y, bool multis) + { + var tiles = GetStaticBlock(x >> 3, y >> 3); + + if (!multis) + return tiles[x & 0x7][y & 0x7]; + + var eable = m_Owner.GetMultiTilesAt(x, y); + + if (eable == Map.NullEnumerable.Instance) + return tiles[x & 0x7][y & 0x7]; + + var any = false; + + m_TilesList.AddRange( + eable.SelectMany( + t => + { + any = true; + return t; + } + ) + .ToArray() + ); + + eable.Free(); + + if (!any) + return tiles[x & 0x7][y & 0x7]; + + m_TilesList.AddRange(tiles[x & 0x7][y & 0x7]); + + return m_TilesList.ToArray(); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public void SetLandBlock(int x, int y, LandTile[] value) + { + if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) + return; + + m_LandTiles[x] ??= new LandTile[BlockHeight][]; + m_LandTiles[x][y] = value; + + m_LandPatches[x] ??= new int[(BlockHeight + 31) >> 5]; + m_LandPatches[x][y >> 5] |= 1 << (y & 0x1F); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public LandTile[] GetLandBlock(int x, int y) + { + if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight || m_MapStream == null) + return m_InvalidLandBlock; + + m_LandTiles[x] ??= new LandTile[BlockHeight][]; + + var tiles = m_LandTiles[x][y]; + + if (tiles != null) + return tiles; + + lock (m_FileShare) + { + for (var i = 0; tiles == null && i < m_FileShare.Count; ++i) + { + var shared = m_FileShare[i]; + + lock (shared) + { + if (x < shared.BlockWidth && y < shared.BlockHeight) + { + var theirTiles = shared.m_LandTiles[x]; + + if (theirTiles != null) + tiles = theirTiles[y]; + + if (tiles != null) + { + var theirBits = shared.m_LandPatches[x]; + + if (theirBits != null && (theirBits[y >> 5] & (1 << (y & 0x1F))) != 0) + tiles = null; + } + } + } + } + } + + return m_LandTiles[x][y] = tiles ?? ReadLandBlock(x, y); + } + + public LandTile GetLandTile(int x, int y) => GetLandBlock(x >> 3, y >> 3)[((y & 0x7) << 3) + (x & 0x7)]; + + [MethodImpl(MethodImplOptions.Synchronized)] + private unsafe StaticTile[][][] ReadStaticBlock(int x, int y) + { + try + { + m_IndexReader.BaseStream.Seek((x * BlockHeight + y) * 12, SeekOrigin.Begin); + + var lookup = m_IndexReader.ReadInt32(); + var length = m_IndexReader.ReadInt32(); + + if (lookup < 0 || length <= 0) + return EmptyStaticBlock; + + var count = length / 7; + + DataStream.Seek(lookup, SeekOrigin.Begin); + + if (m_TileBuffer.Length < count) + m_TileBuffer = new StaticTile[count]; + + var staTiles = m_TileBuffer; // new StaticTile[tileCount]; + + fixed (StaticTile* pTiles = staTiles) + { + NativeReader.Read(DataStream.SafeFileHandle.DangerousGetHandle(), pTiles, length); + if (m_Lists == null) + { + m_Lists = new TileList[8][]; + + for (var i = 0; i < 8; ++i) + { + m_Lists[i] = new TileList[8]; + + for (var j = 0; j < 8; ++j) + m_Lists[i][j] = new TileList(); + } + } + + var lists = m_Lists; + + StaticTile* pCur = pTiles, pEnd = pTiles + count; + + while (pCur < pEnd) + { + lists[pCur->m_X & 0x7][pCur->m_Y & 0x7].Add(pCur->m_ID, pCur->m_Z); + pCur += 1; + } + + var tiles = new StaticTile[8][][]; + + for (var i = 0; i < 8; ++i) + { + tiles[i] = new StaticTile[8][]; + + for (var j = 0; j < 8; ++j) + tiles[i][j] = lists[i][j].ToArray(); + } + + return tiles; + } + } + catch (EndOfStreamException) + { + if (DateTime.UtcNow >= m_NextStaticWarning) + { + Console.WriteLine("Warning: Static EOS for {0} ({1}, {2})", m_Owner, x, y); + m_NextStaticWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); + } + + return EmptyStaticBlock; + } + } + + public void Force() + { + if ((AssemblyHandler.Assemblies?.Length ?? 0) == 0) + throw new Exception(); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private unsafe LandTile[] ReadLandBlock(int x, int y) + { + try + { + var offset = (x * BlockHeight + y) * 196 + 4; + + if (m_MapIndex != null) + offset = m_MapIndex.Lookup(offset); + + m_MapStream.Seek(offset, SeekOrigin.Begin); + + var tiles = new LandTile[64]; + + fixed (LandTile* pTiles = tiles) + { + NativeReader.Read(m_MapStream.SafeFileHandle.DangerousGetHandle(), pTiles, 192); + } + + return tiles; + } + catch + { + if (DateTime.UtcNow >= m_NextLandWarning) + { + Console.WriteLine("Warning: Land EOS for {0} ({1}, {2})", m_Owner, x, y); + m_NextLandWarning = DateTime.UtcNow + TimeSpan.FromMinutes(1.0); + } + + return m_InvalidLandBlock; + } + } + + public void Dispose() + { + m_MapIndex?.Close(); + m_MapStream?.Close(); + DataStream?.Close(); + m_IndexReader?.Close(); + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LandTile + { + internal short m_ID; + internal sbyte m_Z; + + public int ID => m_ID; + + public int Z + { + get => m_Z; + set => m_Z = (sbyte)value; + } + + public int Height => 0; + + public bool Ignored => m_ID == 2 || m_ID == 0x1DB || m_ID >= 0x1AE && m_ID <= 0x1B5; + + public LandTile(short id, sbyte z) + { + m_ID = id; + m_Z = z; + } + + public void Set(short id, sbyte z) + { + m_ID = id; + m_Z = z; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct StaticTile + { + internal ushort m_ID; + internal byte m_X; + internal byte m_Y; + internal sbyte m_Z; + internal short m_Hue; + + public int ID => m_ID; + + public int X + { + get => m_X; + set => m_X = (byte)value; + } + + public int Y + { + get => m_Y; + set => m_Y = (byte)value; + } + + public int Z + { + get => m_Z; + set => m_Z = (sbyte)value; + } + + public int Hue + { + get => m_Hue; + set => m_Hue = (short)value; + } + + public int Height => TileData.ItemTable[m_ID & TileData.MaxItemValue].Height; + + public StaticTile(ushort id, sbyte z) + { + m_ID = id; + m_Z = z; + + m_X = 0; + m_Y = 0; + m_Hue = 0; + } + + public StaticTile(ushort id, byte x, byte y, sbyte z, short hue) + { + m_ID = id; + m_X = x; + m_Y = y; + m_Z = z; + m_Hue = hue; + } + + public void Set(ushort id, sbyte z) + { + m_ID = id; + m_Z = z; + } + + public void Set(ushort id, byte x, byte y, sbyte z, short hue) + { + m_ID = id; + m_X = x; + m_Y = y; + m_Z = z; + m_Hue = hue; + } + } + + public class UOPIndex + { + private readonly UOPEntry[] m_Entries; + private readonly int m_Length; + + private readonly BinaryReader m_Reader; + + public UOPIndex(FileStream stream) + { + m_Reader = new BinaryReader(stream); + m_Length = (int)stream.Length; + + if (m_Reader.ReadInt32() != 0x50594D) + throw new ArgumentException("Invalid UOP file."); + + Version = m_Reader.ReadInt32(); + m_Reader.ReadInt32(); + var nextTable = m_Reader.ReadInt32(); + + var entries = new List(); + + do + { + stream.Seek(nextTable, SeekOrigin.Begin); + var count = m_Reader.ReadInt32(); + nextTable = m_Reader.ReadInt32(); + m_Reader.ReadInt32(); + + for (var i = 0; i < count; ++i) + { + var offset = m_Reader.ReadInt32(); + + if (offset == 0) + { + stream.Seek(30, SeekOrigin.Current); + continue; + } + + m_Reader.ReadInt64(); + var length = m_Reader.ReadInt32(); + + entries.Add(new UOPEntry(offset, length)); + + stream.Seek(18, SeekOrigin.Current); + } + } while (nextTable != 0 && nextTable < m_Length); + + entries.Sort(OffsetComparer.Instance); + + for (var i = 0; i < entries.Count; ++i) + { + stream.Seek(entries[i].m_Offset + 2, SeekOrigin.Begin); + + int dataOffset = m_Reader.ReadInt16(); + entries[i].m_Offset += 4 + dataOffset; + + stream.Seek(dataOffset, SeekOrigin.Current); + entries[i].m_Order = m_Reader.ReadInt32(); + } + + entries.Sort(); + m_Entries = entries.ToArray(); + } + + public int Version { get; } + + public int Lookup(int offset) + { + var total = 0; + + for (var i = 0; i < m_Entries.Length; ++i) + { + var newTotal = total + m_Entries[i].m_Length; + + if (offset < newTotal) + return m_Entries[i].m_Offset + (offset - total); + + total = newTotal; + } + + return m_Length; + } + + public void Close() + { + m_Reader.Close(); + } + + private class UOPEntry : IComparable + { + public readonly int m_Length; + public int m_Offset; + public int m_Order; + + public UOPEntry(int offset, int length) + { + m_Offset = offset; + m_Length = length; + m_Order = 0; + } + + public int CompareTo(UOPEntry other) => m_Order.CompareTo(other.m_Order); + } + + private class OffsetComparer : IComparer + { + public static readonly IComparer Instance = new OffsetComparer(); + + public int Compare(UOPEntry x, UOPEntry y) => + x == null ? y == null ? 0 : 1 : + y == null ? -1 : x.m_Offset.CompareTo(y.m_Offset); + } + } +} diff --git a/Projects/Server/TileMatrixPatch.cs b/Projects/Server/TileMatrixPatch.cs index 386a1b1b7..5f25c1e53 100644 --- a/Projects/Server/TileMatrixPatch.cs +++ b/Projects/Server/TileMatrixPatch.cs @@ -1,185 +1,185 @@ -/*************************************************************************** - * TileMatrixPatch.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System.IO; -using System.Runtime.CompilerServices; - -namespace Server -{ - public class TileMatrixPatch - { - private readonly int m_LandBlocks; - private readonly int m_StaticBlocks; - - private StaticTile[] m_TileBuffer = new StaticTile[128]; - - public TileMatrixPatch(TileMatrix matrix, int index) - { - if (!Enabled) - return; - - var mapDataPath = Core.FindDataFile($"mapdif{index}.mul", false); - var mapIndexPath = Core.FindDataFile($"mapdifl{index}.mul", false); - - if (File.Exists(mapDataPath) && File.Exists(mapIndexPath)) - m_LandBlocks = PatchLand(matrix, mapDataPath, mapIndexPath); - - var staDataPath = Core.FindDataFile($"stadif{index}.mul", false); - var staIndexPath = Core.FindDataFile($"stadifl{index}.mul", false); - var staLookupPath = Core.FindDataFile($"stadifi{index}.mul", false); - - if (File.Exists(staDataPath) && File.Exists(staIndexPath) && File.Exists(staLookupPath)) - m_StaticBlocks = PatchStatics(matrix, staDataPath, staIndexPath, staLookupPath); - } - - public static bool Enabled { get; set; } = true; - - public int LandBlocks - { - get - { - lock (this) - { - return m_LandBlocks; - } - } - } - - public int StaticBlocks - { - get - { - lock (this) - { - return m_StaticBlocks; - } - } - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private unsafe int PatchLand(TileMatrix matrix, string dataPath, string indexPath) - { - using var fsData = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var fsIndex = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var indexReader = new BinaryReader(fsIndex); - - var count = (int)(indexReader.BaseStream.Length / 4); - - for (var i = 0; i < count; ++i) - { - var blockID = indexReader.ReadInt32(); - var x = blockID / matrix.BlockHeight; - var y = blockID % matrix.BlockHeight; - - fsData.Seek(4, SeekOrigin.Current); - - var tiles = new LandTile[64]; - - fixed (LandTile* pTiles = tiles) - { - NativeReader.Read(fsData.SafeFileHandle.DangerousGetHandle(), pTiles, 192); - } - - matrix.SetLandBlock(x, y, tiles); - } - - indexReader.Close(); - - return count; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private unsafe int PatchStatics(TileMatrix matrix, string dataPath, string indexPath, string lookupPath) - { - using var fsData = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var fsIndex = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var fsLookup = new FileStream(lookupPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var indexReader = new BinaryReader(fsIndex); - var lookupReader = new BinaryReader(fsLookup); - - var count = (int)(indexReader.BaseStream.Length / 4); - - var lists = new TileList[8][]; - - for (var x = 0; x < 8; ++x) - { - lists[x] = new TileList[8]; - - for (var y = 0; y < 8; ++y) - lists[x][y] = new TileList(); - } - - for (var i = 0; i < count; ++i) - { - var blockID = indexReader.ReadInt32(); - var blockX = blockID / matrix.BlockHeight; - var blockY = blockID % matrix.BlockHeight; - - var offset = lookupReader.ReadInt32(); - var length = lookupReader.ReadInt32(); - lookupReader.ReadInt32(); // Extra - - if (offset < 0 || length <= 0) - { - matrix.SetStaticBlock(blockX, blockY, matrix.EmptyStaticBlock); - continue; - } - - fsData.Seek(offset, SeekOrigin.Begin); - - var tileCount = length / 7; - - if (m_TileBuffer.Length < tileCount) - m_TileBuffer = new StaticTile[tileCount]; - - var staTiles = m_TileBuffer; - - fixed (StaticTile* pTiles = staTiles) - { - NativeReader.Read(fsData.SafeFileHandle.DangerousGetHandle(), pTiles, length); - StaticTile* pCur = pTiles, pEnd = pTiles + tileCount; - - while (pCur < pEnd) - { - lists[pCur->m_X & 0x7][pCur->m_Y & 0x7].Add(pCur->m_ID, pCur->m_Z); - pCur = pCur + 1; - } - - var tiles = new StaticTile[8][][]; - - for (var x = 0; x < 8; ++x) - { - tiles[x] = new StaticTile[8][]; - - for (var y = 0; y < 8; ++y) - tiles[x][y] = lists[x][y].ToArray(); - } - - matrix.SetStaticBlock(blockX, blockY, tiles); - } - } - - indexReader.Close(); - lookupReader.Close(); - - return count; - } - } -} +/*************************************************************************** + * TileMatrixPatch.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System.IO; +using System.Runtime.CompilerServices; + +namespace Server +{ + public class TileMatrixPatch + { + private readonly int m_LandBlocks; + private readonly int m_StaticBlocks; + + private StaticTile[] m_TileBuffer = new StaticTile[128]; + + public TileMatrixPatch(TileMatrix matrix, int index) + { + if (!Enabled) + return; + + var mapDataPath = Core.FindDataFile($"mapdif{index}.mul", false); + var mapIndexPath = Core.FindDataFile($"mapdifl{index}.mul", false); + + if (File.Exists(mapDataPath) && File.Exists(mapIndexPath)) + m_LandBlocks = PatchLand(matrix, mapDataPath, mapIndexPath); + + var staDataPath = Core.FindDataFile($"stadif{index}.mul", false); + var staIndexPath = Core.FindDataFile($"stadifl{index}.mul", false); + var staLookupPath = Core.FindDataFile($"stadifi{index}.mul", false); + + if (File.Exists(staDataPath) && File.Exists(staIndexPath) && File.Exists(staLookupPath)) + m_StaticBlocks = PatchStatics(matrix, staDataPath, staIndexPath, staLookupPath); + } + + public static bool Enabled { get; set; } = true; + + public int LandBlocks + { + get + { + lock (this) + { + return m_LandBlocks; + } + } + } + + public int StaticBlocks + { + get + { + lock (this) + { + return m_StaticBlocks; + } + } + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private unsafe int PatchLand(TileMatrix matrix, string dataPath, string indexPath) + { + using var fsData = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var fsIndex = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var indexReader = new BinaryReader(fsIndex); + + var count = (int)(indexReader.BaseStream.Length / 4); + + for (var i = 0; i < count; ++i) + { + var blockID = indexReader.ReadInt32(); + var x = blockID / matrix.BlockHeight; + var y = blockID % matrix.BlockHeight; + + fsData.Seek(4, SeekOrigin.Current); + + var tiles = new LandTile[64]; + + fixed (LandTile* pTiles = tiles) + { + NativeReader.Read(fsData.SafeFileHandle.DangerousGetHandle(), pTiles, 192); + } + + matrix.SetLandBlock(x, y, tiles); + } + + indexReader.Close(); + + return count; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private unsafe int PatchStatics(TileMatrix matrix, string dataPath, string indexPath, string lookupPath) + { + using var fsData = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var fsIndex = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var fsLookup = new FileStream(lookupPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var indexReader = new BinaryReader(fsIndex); + var lookupReader = new BinaryReader(fsLookup); + + var count = (int)(indexReader.BaseStream.Length / 4); + + var lists = new TileList[8][]; + + for (var x = 0; x < 8; ++x) + { + lists[x] = new TileList[8]; + + for (var y = 0; y < 8; ++y) + lists[x][y] = new TileList(); + } + + for (var i = 0; i < count; ++i) + { + var blockID = indexReader.ReadInt32(); + var blockX = blockID / matrix.BlockHeight; + var blockY = blockID % matrix.BlockHeight; + + var offset = lookupReader.ReadInt32(); + var length = lookupReader.ReadInt32(); + lookupReader.ReadInt32(); // Extra + + if (offset < 0 || length <= 0) + { + matrix.SetStaticBlock(blockX, blockY, matrix.EmptyStaticBlock); + continue; + } + + fsData.Seek(offset, SeekOrigin.Begin); + + var tileCount = length / 7; + + if (m_TileBuffer.Length < tileCount) + m_TileBuffer = new StaticTile[tileCount]; + + var staTiles = m_TileBuffer; + + fixed (StaticTile* pTiles = staTiles) + { + NativeReader.Read(fsData.SafeFileHandle.DangerousGetHandle(), pTiles, length); + StaticTile* pCur = pTiles, pEnd = pTiles + tileCount; + + while (pCur < pEnd) + { + lists[pCur->m_X & 0x7][pCur->m_Y & 0x7].Add(pCur->m_ID, pCur->m_Z); + pCur = pCur + 1; + } + + var tiles = new StaticTile[8][][]; + + for (var x = 0; x < 8; ++x) + { + tiles[x] = new StaticTile[8][]; + + for (var y = 0; y < 8; ++y) + tiles[x][y] = lists[x][y].ToArray(); + } + + matrix.SetStaticBlock(blockX, blockY, tiles); + } + } + + indexReader.Close(); + lookupReader.Close(); + + return count; + } + } +} diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs index 5bc2fd697..ba98ee5d3 100644 --- a/Projects/Server/Timer/Timer.cs +++ b/Projects/Server/Timer/Timer.cs @@ -1,486 +1,492 @@ -/*************************************************************************** - * Timer.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Server.Diagnostics; - -namespace Server -{ - public enum TimerPriority - { - EveryTick, - TenMS, - TwentyFiveMS, - FiftyMS, - TwoFiftyMS, - OneSecond, - FiveSeconds, - OneMinute - } - - public partial class Timer - { - private static readonly Queue m_Queue = new Queue(); - - private static int m_QueueCountAtSlice; - private long m_Delay; - private int m_Index; - private readonly int m_Count; - private long m_Interval; - private List m_List; - private long m_Next; - private TimerPriority m_Priority; - private bool m_PrioritySet; - - private bool m_Queued; - private bool m_Running; - - public Timer(TimeSpan delay) : this(delay, TimeSpan.Zero, 1) - { - } - - public Timer(TimeSpan delay, TimeSpan interval, int count = 0) - { - m_Delay = (long)delay.TotalMilliseconds; - m_Interval = (long)interval.TotalMilliseconds; - m_Count = count; - - if (!m_PrioritySet) - { - m_Priority = ComputePriority(count == 1 ? delay : interval); - m_PrioritySet = true; - } - - if (DefRegCreation) - RegCreation(); - } - - public TimerPriority Priority - { - get => m_Priority; - set - { - if (!m_PrioritySet) - m_PrioritySet = true; - - if (m_Priority != value) - { - m_Priority = value; - - if (m_Running) - TimerThread.PriorityChange(this, (int)m_Priority); - } - } - } - - public DateTime Next => DateTime.UtcNow + TimeSpan.FromMilliseconds(m_Next - Core.TickCount); - - public TimeSpan Delay - { - get => TimeSpan.FromMilliseconds(m_Delay); - set => m_Delay = (long)value.TotalMilliseconds; - } - - public TimeSpan Interval - { - get => TimeSpan.FromMilliseconds(m_Interval); - set => m_Interval = (long)value.TotalMilliseconds; - } - - public bool Running - { - get => m_Running; - set - { - if (value) - Start(); - else - Stop(); - } - } - - public static int BreakCount { get; set; } = 20000; - - public virtual bool DefRegCreation => true; - - private static string FormatDelegate(Delegate callback) => - callback == null ? "null" : $"{callback.Method.DeclaringType?.FullName ?? ""}.{callback.Method.Name}"; - - public static void DumpInfo(TextWriter tw) - { - TimerThread.DumpInfo(tw); - } - - public TimerProfile GetProfile() - { - if (!Core.Profiling) return null; - - var name = ToString(); - - return TimerProfile.Acquire(name); - } - - public static void Slice() - { - lock (m_Queue) - { - m_QueueCountAtSlice = m_Queue.Count; - - var index = 0; - - while (index < BreakCount && m_Queue.Count != 0) - { - var t = m_Queue.Dequeue(); - var prof = t.GetProfile(); - - prof?.Start(); - - t.OnTick(); - t.m_Queued = false; - ++index; - - prof?.Finish(); - } - } - } - - public void RegCreation() - { - var prof = GetProfile(); - - if (prof != null) prof.Created++; - } - - public override string ToString() => GetType().FullName ?? ""; - - public static TimerPriority ComputePriority(TimeSpan ts) - { - if (ts >= TimeSpan.FromMinutes(1.0)) - return TimerPriority.FiveSeconds; - - if (ts >= TimeSpan.FromSeconds(10.0)) - return TimerPriority.OneSecond; - - if (ts >= TimeSpan.FromSeconds(5.0)) - return TimerPriority.TwoFiftyMS; - - if (ts >= TimeSpan.FromSeconds(2.5)) - return TimerPriority.FiftyMS; - - if (ts >= TimeSpan.FromSeconds(1.0)) - return TimerPriority.TwentyFiveMS; - - if (ts >= TimeSpan.FromSeconds(0.5)) - return TimerPriority.TenMS; - - return TimerPriority.EveryTick; - } - - public void Start() - { - if (!m_Running) - { - m_Running = true; - TimerThread.AddTimer(this); - - var prof = GetProfile(); - - if (prof != null) prof.Started++; - } - } - - public void Stop() - { - if (m_Running) - { - m_Running = false; - TimerThread.RemoveTimer(this); - - var prof = GetProfile(); - - if (prof != null) prof.Stopped++; - } - } - - protected virtual void OnTick() - { - } - - public class TimerThread - { - private static readonly Dictionary m_Changed = new Dictionary(); - - private static readonly long[] m_NextPriorities = new long[8]; - - private static readonly long[] m_PriorityDelays = - { - 0, - 10, - 25, - 50, - 250, - 1000, - 5000, - 60000 - }; - - private static readonly List[] m_Timers = - { - new List(), - new List(), - new List(), - new List(), - new List(), - new List(), - new List(), - new List() - }; - - private static readonly AutoResetEvent m_Signal = new AutoResetEvent(false); - - public static void DumpInfo(TextWriter tw) - { - for (var i = 0; i < 8; ++i) - { - tw.WriteLine("Priority: {0}", (TimerPriority)i); - tw.WriteLine(); - - var hash = new Dictionary>(); - - for (var j = 0; j < m_Timers[i].Count; ++j) - { - var t = m_Timers[i][j]; - - var key = t.ToString(); - - if (!hash.TryGetValue(key, out var list)) - hash[key] = list = new List(); - - list.Add(t); - } - - foreach (var kv in hash) - { - var key = kv.Key; - var list = kv.Value; - - tw.WriteLine("Type: {0}; Count: {1}; Percent: {2}%", key, list.Count, - (int)(100 * (list.Count / (double)m_Timers[i].Count))); - } - - tw.WriteLine(); - tw.WriteLine(); - } - } - - public static void Change(Timer t, int newIndex, bool isAdd) - { - lock (m_Changed) - { - m_Changed[t] = TimerChangeEntry.GetInstance(t, newIndex, isAdd); - } - - m_Signal.Set(); - } - - public static void AddTimer(Timer t) - { - Change(t, (int)t.Priority, true); - } - - public static void PriorityChange(Timer t, int newPrio) - { - Change(t, newPrio, false); - } - - public static void RemoveTimer(Timer t) - { - Change(t, -1, false); - } - - private static void ProcessChanged() - { - lock (m_Changed) - { - var curTicks = Core.TickCount; - - foreach (var tce in m_Changed.Values) - { - var timer = tce.m_Timer; - var newIndex = tce.m_NewIndex; - - timer.m_List?.Remove(timer); - - if (tce.m_IsAdd) - { - timer.m_Next = curTicks + timer.m_Delay; - timer.m_Index = 0; - } - - if (newIndex >= 0) - { - timer.m_List = m_Timers[newIndex]; - timer.m_List.Add(timer); - } - else - { - timer.m_List = null; - } - - tce.Free(); - } - - m_Changed.Clear(); - } - } - - public static void Set() - { - m_Signal.Set(); - } - - public void TimerMain() - { - while (!Core.Closing) - { - if (World.Loading || World.Saving) - { - m_Signal.WaitOne(1, false); - continue; - } - - ProcessChanged(); - - var loaded = false; - - for (var i = 0; i < m_Timers.Length; i++) - { - var now = Core.TickCount; - if (now < m_NextPriorities[i]) - break; - - m_NextPriorities[i] = now + m_PriorityDelays[i]; - - for (var j = 0; j < m_Timers[i].Count; j++) - { - var t = m_Timers[i][j]; - - if (!t.m_Queued && now > t.m_Next) - { - t.m_Queued = true; - - lock (m_Queue) - { - m_Queue.Enqueue(t); - } - - loaded = true; - - if (t.m_Count != 0 && ++t.m_Index >= t.m_Count) - t.Stop(); - else - t.m_Next = now + t.m_Interval; - } - } - } - - if (loaded) - Core.Set(); - - m_Signal.WaitOne(1, false); - } - } - - private class TimerChangeEntry - { - private static readonly Queue m_InstancePool = new Queue(); - public bool m_IsAdd; - public int m_NewIndex; - public Timer m_Timer; - - private TimerChangeEntry(Timer t, int newIndex, bool isAdd) - { - m_Timer = t; - m_NewIndex = newIndex; - m_IsAdd = isAdd; - } - - public void Free() - { - lock (m_InstancePool) - { - if (m_InstancePool.Count < 200) // Arbitrary - m_InstancePool.Enqueue(this); - } - } - - public static TimerChangeEntry GetInstance(Timer t, int newIndex, bool isAdd) - { - TimerChangeEntry e = null; - - lock (m_InstancePool) - { - if (m_InstancePool.Count > 0) e = m_InstancePool.Dequeue(); - } - - if (e != null) - { - e.m_Timer = t; - e.m_NewIndex = newIndex; - e.m_IsAdd = isAdd; - } - else - { - e = new TimerChangeEntry(t, newIndex, isAdd); - } - - return e; - } - } - } - - private class DelayTaskTimer : Timer - { - private readonly TaskCompletionSource m_TaskCompleter; - - public Task Task => m_TaskCompleter.Task; - - public DelayTaskTimer(TimeSpan delay) : base(delay) => m_TaskCompleter = new TaskCompletionSource(); - - protected override void OnTick() - { - m_TaskCompleter.SetResult(this); - } - } - - public static Task Pause(int ms) => Pause(TimeSpan.FromMilliseconds(ms)); - - public static Task Pause(TimeSpan ms) - { - var t = new DelayTaskTimer(ms); - t.Start(); - return t.Task; - } - } -} +/*************************************************************************** + * Timer.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Server.Diagnostics; + +namespace Server +{ + public enum TimerPriority + { + EveryTick, + TenMS, + TwentyFiveMS, + FiftyMS, + TwoFiftyMS, + OneSecond, + FiveSeconds, + OneMinute + } + + public partial class Timer + { + private static readonly Queue m_Queue = new Queue(); + + private static int m_QueueCountAtSlice; + private readonly int m_Count; + private long m_Delay; + private int m_Index; + private long m_Interval; + private List m_List; + private long m_Next; + private TimerPriority m_Priority; + private bool m_PrioritySet; + + private bool m_Queued; + private bool m_Running; + + public Timer(TimeSpan delay) : this(delay, TimeSpan.Zero, 1) + { + } + + public Timer(TimeSpan delay, TimeSpan interval, int count = 0) + { + m_Delay = (long)delay.TotalMilliseconds; + m_Interval = (long)interval.TotalMilliseconds; + m_Count = count; + + if (!m_PrioritySet) + { + m_Priority = ComputePriority(count == 1 ? delay : interval); + m_PrioritySet = true; + } + + if (DefRegCreation) + RegCreation(); + } + + public TimerPriority Priority + { + get => m_Priority; + set + { + if (!m_PrioritySet) + m_PrioritySet = true; + + if (m_Priority != value) + { + m_Priority = value; + + if (m_Running) + TimerThread.PriorityChange(this, (int)m_Priority); + } + } + } + + public DateTime Next => DateTime.UtcNow + TimeSpan.FromMilliseconds(m_Next - Core.TickCount); + + public TimeSpan Delay + { + get => TimeSpan.FromMilliseconds(m_Delay); + set => m_Delay = (long)value.TotalMilliseconds; + } + + public TimeSpan Interval + { + get => TimeSpan.FromMilliseconds(m_Interval); + set => m_Interval = (long)value.TotalMilliseconds; + } + + public bool Running + { + get => m_Running; + set + { + if (value) + Start(); + else + Stop(); + } + } + + public static int BreakCount { get; set; } = 20000; + + public virtual bool DefRegCreation => true; + + private static string FormatDelegate(Delegate callback) => + callback == null ? "null" : $"{callback.Method.DeclaringType?.FullName ?? ""}.{callback.Method.Name}"; + + public static void DumpInfo(TextWriter tw) + { + TimerThread.DumpInfo(tw); + } + + public TimerProfile GetProfile() + { + if (!Core.Profiling) return null; + + var name = ToString(); + + return TimerProfile.Acquire(name); + } + + public static void Slice() + { + lock (m_Queue) + { + m_QueueCountAtSlice = m_Queue.Count; + + var index = 0; + + while (index < BreakCount && m_Queue.Count != 0) + { + var t = m_Queue.Dequeue(); + var prof = t.GetProfile(); + + prof?.Start(); + + t.OnTick(); + t.m_Queued = false; + ++index; + + prof?.Finish(); + } + } + } + + public void RegCreation() + { + var prof = GetProfile(); + + if (prof != null) prof.Created++; + } + + public override string ToString() => GetType().FullName ?? ""; + + public static TimerPriority ComputePriority(TimeSpan ts) + { + if (ts >= TimeSpan.FromMinutes(1.0)) + return TimerPriority.FiveSeconds; + + if (ts >= TimeSpan.FromSeconds(10.0)) + return TimerPriority.OneSecond; + + if (ts >= TimeSpan.FromSeconds(5.0)) + return TimerPriority.TwoFiftyMS; + + if (ts >= TimeSpan.FromSeconds(2.5)) + return TimerPriority.FiftyMS; + + if (ts >= TimeSpan.FromSeconds(1.0)) + return TimerPriority.TwentyFiveMS; + + if (ts >= TimeSpan.FromSeconds(0.5)) + return TimerPriority.TenMS; + + return TimerPriority.EveryTick; + } + + public void Start() + { + if (!m_Running) + { + m_Running = true; + TimerThread.AddTimer(this); + + var prof = GetProfile(); + + if (prof != null) prof.Started++; + } + } + + public void Stop() + { + if (m_Running) + { + m_Running = false; + TimerThread.RemoveTimer(this); + + var prof = GetProfile(); + + if (prof != null) prof.Stopped++; + } + } + + protected virtual void OnTick() + { + } + + public static Task Pause(int ms) => Pause(TimeSpan.FromMilliseconds(ms)); + + public static Task Pause(TimeSpan ms) + { + var t = new DelayTaskTimer(ms); + t.Start(); + return t.Task; + } + + public class TimerThread + { + private static readonly Dictionary + m_Changed = new Dictionary(); + + private static readonly long[] m_NextPriorities = new long[8]; + + private static readonly long[] m_PriorityDelays = + { + 0, + 10, + 25, + 50, + 250, + 1000, + 5000, + 60000 + }; + + private static readonly List[] m_Timers = + { + new List(), + new List(), + new List(), + new List(), + new List(), + new List(), + new List(), + new List() + }; + + private static readonly AutoResetEvent m_Signal = new AutoResetEvent(false); + + public static void DumpInfo(TextWriter tw) + { + for (var i = 0; i < 8; ++i) + { + tw.WriteLine("Priority: {0}", (TimerPriority)i); + tw.WriteLine(); + + var hash = new Dictionary>(); + + for (var j = 0; j < m_Timers[i].Count; ++j) + { + var t = m_Timers[i][j]; + + var key = t.ToString(); + + if (!hash.TryGetValue(key, out var list)) + hash[key] = list = new List(); + + list.Add(t); + } + + foreach (var kv in hash) + { + var key = kv.Key; + var list = kv.Value; + + tw.WriteLine( + "Type: {0}; Count: {1}; Percent: {2}%", + key, + list.Count, + (int)(100 * (list.Count / (double)m_Timers[i].Count)) + ); + } + + tw.WriteLine(); + tw.WriteLine(); + } + } + + public static void Change(Timer t, int newIndex, bool isAdd) + { + lock (m_Changed) + { + m_Changed[t] = TimerChangeEntry.GetInstance(t, newIndex, isAdd); + } + + m_Signal.Set(); + } + + public static void AddTimer(Timer t) + { + Change(t, (int)t.Priority, true); + } + + public static void PriorityChange(Timer t, int newPrio) + { + Change(t, newPrio, false); + } + + public static void RemoveTimer(Timer t) + { + Change(t, -1, false); + } + + private static void ProcessChanged() + { + lock (m_Changed) + { + var curTicks = Core.TickCount; + + foreach (var tce in m_Changed.Values) + { + var timer = tce.m_Timer; + var newIndex = tce.m_NewIndex; + + timer.m_List?.Remove(timer); + + if (tce.m_IsAdd) + { + timer.m_Next = curTicks + timer.m_Delay; + timer.m_Index = 0; + } + + if (newIndex >= 0) + { + timer.m_List = m_Timers[newIndex]; + timer.m_List.Add(timer); + } + else + { + timer.m_List = null; + } + + tce.Free(); + } + + m_Changed.Clear(); + } + } + + public static void Set() + { + m_Signal.Set(); + } + + public void TimerMain() + { + while (!Core.Closing) + { + if (World.Loading || World.Saving) + { + m_Signal.WaitOne(1, false); + continue; + } + + ProcessChanged(); + + var loaded = false; + + for (var i = 0; i < m_Timers.Length; i++) + { + var now = Core.TickCount; + if (now < m_NextPriorities[i]) + break; + + m_NextPriorities[i] = now + m_PriorityDelays[i]; + + for (var j = 0; j < m_Timers[i].Count; j++) + { + var t = m_Timers[i][j]; + + if (!t.m_Queued && now > t.m_Next) + { + t.m_Queued = true; + + lock (m_Queue) + { + m_Queue.Enqueue(t); + } + + loaded = true; + + if (t.m_Count != 0 && ++t.m_Index >= t.m_Count) + t.Stop(); + else + t.m_Next = now + t.m_Interval; + } + } + } + + if (loaded) + Core.Set(); + + m_Signal.WaitOne(1, false); + } + } + + private class TimerChangeEntry + { + private static readonly Queue m_InstancePool = new Queue(); + public bool m_IsAdd; + public int m_NewIndex; + public Timer m_Timer; + + private TimerChangeEntry(Timer t, int newIndex, bool isAdd) + { + m_Timer = t; + m_NewIndex = newIndex; + m_IsAdd = isAdd; + } + + public void Free() + { + lock (m_InstancePool) + { + if (m_InstancePool.Count < 200) // Arbitrary + m_InstancePool.Enqueue(this); + } + } + + public static TimerChangeEntry GetInstance(Timer t, int newIndex, bool isAdd) + { + TimerChangeEntry e = null; + + lock (m_InstancePool) + { + if (m_InstancePool.Count > 0) e = m_InstancePool.Dequeue(); + } + + if (e != null) + { + e.m_Timer = t; + e.m_NewIndex = newIndex; + e.m_IsAdd = isAdd; + } + else + { + e = new TimerChangeEntry(t, newIndex, isAdd); + } + + return e; + } + } + } + + private class DelayTaskTimer : Timer + { + private readonly TaskCompletionSource m_TaskCompleter; + + public DelayTaskTimer(TimeSpan delay) : base(delay) => + m_TaskCompleter = new TaskCompletionSource(); + + public Task Task => m_TaskCompleter.Task; + + protected override void OnTick() + { + m_TaskCompleter.SetResult(this); + } + } + } +} diff --git a/Projects/Server/Timer/TimerDelayCalls.cs b/Projects/Server/Timer/TimerDelayCalls.cs index bab5e1036..2990e31b6 100644 --- a/Projects/Server/Timer/TimerDelayCalls.cs +++ b/Projects/Server/Timer/TimerDelayCalls.cs @@ -1,268 +1,302 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: TimerDelayCalls.cs - Created: 2020/07/31 - Updated: 2020/07/31 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; - -namespace Server -{ - public delegate void TimerCallback(); - public delegate void TimerStateCallback(T state); - public delegate void TimerStateCallback(T1 t1, T2 t2); - public delegate void TimerStateCallback(T1 t1, T2 t2, T3 t3); - public delegate void TimerStateCallback(T1 t1, T2 t2, T3 t3, T4 t4); - - public partial class Timer - { - public static Timer DelayCall(TimerCallback callback) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback); - - public static Timer DelayCall(TimeSpan delay, TimerCallback callback) => DelayCall(delay, TimeSpan.Zero, 1, callback); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerCallback callback) => - DelayCall(delay, interval, 0, callback); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, TimerCallback callback) - { - Timer t = new DelayCallTimer(delay, interval, count, callback); - - t.Priority = ComputePriority(count == 1 ? delay : interval); - t.Start(); - - return t; - } - - private class DelayCallTimer : Timer - { - public DelayCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerCallback callback) : base(delay, - interval, count) - { - Callback = callback; - RegCreation(); - } - - public TimerCallback Callback { get; } - - public override bool DefRegCreation => false; - - protected override void OnTick() - { - Callback?.Invoke(); - } - - public override string ToString() => $"DelayCallTimer[{FormatDelegate(Callback)}]"; - } - - public static Timer DelayCall(TimerStateCallback callback, T state) => - DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, state); - - public static Timer DelayCall(TimeSpan delay, TimerStateCallback callback, T state) => - DelayCall(delay, TimeSpan.Zero, 1, callback, state); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerStateCallback callback, T state) => - DelayCall(delay, interval, 0, callback, state); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, - T state) - { - Timer t = new DelayStateCallTimer(delay, interval, count, callback, state); - - t.Priority = ComputePriority(count == 1 ? delay : interval); - - t.Start(); - - return t; - } - - private class DelayStateCallTimer : Timer - { - private readonly T m_State; - - public DelayStateCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, T state) - : base(delay, interval, count) - { - Callback = callback; - m_State = state; - - RegCreation(); - } - - public TimerStateCallback Callback { get; } - - public override bool DefRegCreation => false; - - protected override void OnTick() - { - Callback?.Invoke(m_State); - } - - public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; - } - - public static Timer DelayCall(TimerStateCallback callback, T1 t1, T2 t2) => - DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2); - - public static Timer DelayCall(TimeSpan delay, TimerStateCallback callback, T1 t1, T2 t2) => - DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerStateCallback callback, - T1 t1, T2 t2) => DelayCall(delay, interval, 0, callback, t1, t2); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, - T1 t1, T2 t2) - { - Timer t = new DelayStateCallTimer(delay, interval, count, callback, t1, t2); - - t.Priority = ComputePriority(count == 1 ? delay : interval); - - t.Start(); - - return t; - } - - private class DelayStateCallTimer : Timer - { - private readonly T1 m_T1; - private readonly T2 m_T2; - - public DelayStateCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, - T1 t1, T2 t2) : base(delay, interval, count) - { - Callback = callback; - m_T1 = t1; - m_T2 = t2; - - RegCreation(); - } - - public TimerStateCallback Callback { get; } - - public override bool DefRegCreation => false; - - protected override void OnTick() - { - Callback?.Invoke(m_T1, m_T2); - } - - public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; - } - - public static Timer DelayCall(TimerStateCallback callback, T1 t1, T2 t2, T3 t3) => - DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3); - - public static Timer DelayCall(TimeSpan delay, TimerStateCallback callback, T1 t1, T2 t2, T3 t3) => - DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerStateCallback callback, - T1 t1, T2 t2, T3 t3) => DelayCall(delay, interval, 0, callback, t1, t2, t3); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, - TimerStateCallback callback, T1 t1, T2 t2, T3 t3) - { - Timer t = new DelayStateCallTimer(delay, interval, count, callback, t1, t2, t3); - - t.Priority = ComputePriority(count == 1 ? delay : interval); - - t.Start(); - - return t; - } - - private class DelayStateCallTimer : Timer - { - private readonly T1 m_T1; - private readonly T2 m_T2; - private readonly T3 m_T3; - - public DelayStateCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, - T1 t1, T2 t2, T3 t3) : base(delay, interval, count) - { - Callback = callback; - m_T1 = t1; - m_T2 = t2; - m_T3 = t3; - - RegCreation(); - } - - public TimerStateCallback Callback { get; } - - public override bool DefRegCreation => false; - - protected override void OnTick() - { - Callback?.Invoke(m_T1, m_T2, m_T3); - } - - public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; - } - - public static Timer DelayCall(TimerStateCallback callback, T1 t1, T2 t2, T3 t3, T4 t4) => - DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3, t4); - - public static Timer DelayCall(TimeSpan delay, TimerStateCallback callback, - T1 t1, T2 t2, T3 t3, T4 t4) => DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3, t4); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, - TimerStateCallback callback, T1 t1, T2 t2, T3 t3, T4 t4) => - DelayCall(delay, interval, 0, callback, t1, t2, t3, t4); - - public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, - TimerStateCallback callback, T1 t1, T2 t2, T3 t3, T4 t4) - { - Timer t = new DelayStateCallTimer(delay, interval, count, callback, t1, t2, t3, t4); - - t.Priority = ComputePriority(count == 1 ? delay : interval); - - t.Start(); - - return t; - } - - private class DelayStateCallTimer : Timer - { - private readonly T1 m_T1; - private readonly T2 m_T2; - private readonly T3 m_T3; - private readonly T4 m_T4; - - public DelayStateCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, - T1 t1, T2 t2, T3 t3, T4 t4) : base(delay, interval, count) - { - Callback = callback; - m_T1 = t1; - m_T2 = t2; - m_T3 = t3; - m_T4 = t4; - - RegCreation(); - } - - public TimerStateCallback Callback { get; } - - public override bool DefRegCreation => false; - - protected override void OnTick() - { - Callback?.Invoke(m_T1, m_T2, m_T3, m_T4); - } - - public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; - } - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: TimerDelayCalls.cs - Created: 2020/07/31 - Updated: 2020/07/31 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server +{ + public delegate void TimerCallback(); + + public delegate void TimerStateCallback(T state); + + public delegate void TimerStateCallback(T1 t1, T2 t2); + + public delegate void TimerStateCallback(T1 t1, T2 t2, T3 t3); + + public delegate void TimerStateCallback(T1 t1, T2 t2, T3 t3, T4 t4); + + public partial class Timer + { + public static Timer DelayCall(TimerCallback callback) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback); + + public static Timer DelayCall(TimeSpan delay, TimerCallback callback) => + DelayCall(delay, TimeSpan.Zero, 1, callback); + + public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerCallback callback) => + DelayCall(delay, interval, 0, callback); + + public static Timer DelayCall(TimeSpan delay, TimeSpan interval, int count, TimerCallback callback) + { + Timer t = new DelayCallTimer(delay, interval, count, callback); + + t.Priority = ComputePriority(count == 1 ? delay : interval); + t.Start(); + + return t; + } + + public static Timer DelayCall(TimerStateCallback callback, T state) => + DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, state); + + public static Timer DelayCall(TimeSpan delay, TimerStateCallback callback, T state) => + DelayCall(delay, TimeSpan.Zero, 1, callback, state); + + public static Timer DelayCall(TimeSpan delay, TimeSpan interval, TimerStateCallback callback, T state) => + DelayCall(delay, interval, 0, callback, state); + + public static Timer DelayCall( + TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, + T state + ) + { + Timer t = new DelayStateCallTimer(delay, interval, count, callback, state); + + t.Priority = ComputePriority(count == 1 ? delay : interval); + + t.Start(); + + return t; + } + + public static Timer DelayCall(TimerStateCallback callback, T1 t1, T2 t2) => + DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2); + + public static Timer DelayCall(TimeSpan delay, TimerStateCallback callback, T1 t1, T2 t2) => + DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2); + + public static Timer DelayCall( + TimeSpan delay, TimeSpan interval, TimerStateCallback callback, + T1 t1, T2 t2 + ) => DelayCall(delay, interval, 0, callback, t1, t2); + + public static Timer DelayCall( + TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, + T1 t1, T2 t2 + ) + { + Timer t = new DelayStateCallTimer(delay, interval, count, callback, t1, t2); + + t.Priority = ComputePriority(count == 1 ? delay : interval); + + t.Start(); + + return t; + } + + public static Timer DelayCall(TimerStateCallback callback, T1 t1, T2 t2, T3 t3) => + DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3); + + public static Timer DelayCall( + TimeSpan delay, TimerStateCallback callback, T1 t1, T2 t2, T3 t3 + ) => + DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3); + + public static Timer DelayCall( + TimeSpan delay, TimeSpan interval, TimerStateCallback callback, + T1 t1, T2 t2, T3 t3 + ) => DelayCall(delay, interval, 0, callback, t1, t2, t3); + + public static Timer DelayCall( + TimeSpan delay, TimeSpan interval, int count, + TimerStateCallback callback, T1 t1, T2 t2, T3 t3 + ) + { + Timer t = new DelayStateCallTimer(delay, interval, count, callback, t1, t2, t3); + + t.Priority = ComputePriority(count == 1 ? delay : interval); + + t.Start(); + + return t; + } + + public static Timer DelayCall( + TimerStateCallback callback, T1 t1, T2 t2, T3 t3, T4 t4 + ) => + DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3, t4); + + public static Timer DelayCall( + TimeSpan delay, TimerStateCallback callback, + T1 t1, T2 t2, T3 t3, T4 t4 + ) => DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3, t4); + + public static Timer DelayCall( + TimeSpan delay, TimeSpan interval, + TimerStateCallback callback, T1 t1, T2 t2, T3 t3, T4 t4 + ) => + DelayCall(delay, interval, 0, callback, t1, t2, t3, t4); + + public static Timer DelayCall( + TimeSpan delay, TimeSpan interval, int count, + TimerStateCallback callback, T1 t1, T2 t2, T3 t3, T4 t4 + ) + { + Timer t = new DelayStateCallTimer(delay, interval, count, callback, t1, t2, t3, t4); + + t.Priority = ComputePriority(count == 1 ? delay : interval); + + t.Start(); + + return t; + } + + private class DelayCallTimer : Timer + { + public DelayCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerCallback callback) : base( + delay, + interval, + count + ) + { + Callback = callback; + RegCreation(); + } + + public TimerCallback Callback { get; } + + public override bool DefRegCreation => false; + + protected override void OnTick() + { + Callback?.Invoke(); + } + + public override string ToString() => $"DelayCallTimer[{FormatDelegate(Callback)}]"; + } + + private class DelayStateCallTimer : Timer + { + private readonly T m_State; + + public DelayStateCallTimer(TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, T state) + : base(delay, interval, count) + { + Callback = callback; + m_State = state; + + RegCreation(); + } + + public TimerStateCallback Callback { get; } + + public override bool DefRegCreation => false; + + protected override void OnTick() + { + Callback?.Invoke(m_State); + } + + public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; + } + + private class DelayStateCallTimer : Timer + { + private readonly T1 m_T1; + private readonly T2 m_T2; + + public DelayStateCallTimer( + TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, + T1 t1, T2 t2 + ) : base(delay, interval, count) + { + Callback = callback; + m_T1 = t1; + m_T2 = t2; + + RegCreation(); + } + + public TimerStateCallback Callback { get; } + + public override bool DefRegCreation => false; + + protected override void OnTick() + { + Callback?.Invoke(m_T1, m_T2); + } + + public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; + } + + private class DelayStateCallTimer : Timer + { + private readonly T1 m_T1; + private readonly T2 m_T2; + private readonly T3 m_T3; + + public DelayStateCallTimer( + TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, + T1 t1, T2 t2, T3 t3 + ) : base(delay, interval, count) + { + Callback = callback; + m_T1 = t1; + m_T2 = t2; + m_T3 = t3; + + RegCreation(); + } + + public TimerStateCallback Callback { get; } + + public override bool DefRegCreation => false; + + protected override void OnTick() + { + Callback?.Invoke(m_T1, m_T2, m_T3); + } + + public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; + } + + private class DelayStateCallTimer : Timer + { + private readonly T1 m_T1; + private readonly T2 m_T2; + private readonly T3 m_T3; + private readonly T4 m_T4; + + public DelayStateCallTimer( + TimeSpan delay, TimeSpan interval, int count, TimerStateCallback callback, + T1 t1, T2 t2, T3 t3, T4 t4 + ) : base(delay, interval, count) + { + Callback = callback; + m_T1 = t1; + m_T2 = t2; + m_T3 = t3; + m_T4 = t4; + + RegCreation(); + } + + public TimerStateCallback Callback { get; } + + public override bool DefRegCreation => false; + + protected override void OnTick() + { + Callback?.Invoke(m_T1, m_T2, m_T3, m_T4); + } + + public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]"; + } + } +} diff --git a/Projects/Server/Utilities/ActivatorUtil.cs b/Projects/Server/Utilities/ActivatorUtil.cs index 958292f62..b43daa448 100644 --- a/Projects/Server/Utilities/ActivatorUtil.cs +++ b/Projects/Server/Utilities/ActivatorUtil.cs @@ -1,121 +1,132 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ActivatorUtil.cs - Created: 2020/02/19 - Updated: 2020/07/30 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Linq; -using System.Reflection; - -namespace Server.Utilities -{ - public static class ActivatorUtil - { - public static ConstructorInfo GetConstructor(Type type, Predicate predicate = null) - { - var emptyCtor = type.GetConstructor(Type.EmptyTypes); - - if (emptyCtor != null && predicate?.Invoke(emptyCtor) != false) return emptyCtor; - - var optionalCtor = type.GetConstructors().SingleOrDefault(info => - predicate?.Invoke(info) != false && info.GetParameters().All(x => x.IsOptional)); - - if (optionalCtor != null) return optionalCtor; - - throw new TypeInitializationException(type.ToString(), - new Exception($"There is no empty/default constructor for {type} that matches predicate.")); - } - - public static ConstructorInfo GetConstructor(Type type, Predicate predicate, params Type[] args) - { - try - { - ConstructorInfo ctor; - - if (args.All(x => x != null)) - { - ctor = type.GetConstructor(args); - - if (ctor != null && predicate?.Invoke(ctor) != false) return ctor; - } - else - { - ctor = type.GetConstructors().SingleOrDefault(info => - { - if (predicate?.Invoke(info) == false) return false; - - var paramList = info.GetParameters().ToList(); - - // If more args are given than parameters, skip. - if (args.Length > paramList.Count) return false; - - // check all given args map to params. - for (var i = 0; i < args.Length; i++) - // if a null reference is passed, but the type is not nullable - if (args[i] == null && paramList[i].ParameterType.IsValueType - // or if an arg is not null and is not assignable to the parameter type, skip. - || !(args[i] == null || paramList[i].ParameterType.IsAssignableFrom(args[i]))) - return false; - - // If there are more parameters, check if they any are not optional, if any are not, skip. - // Otherwise all checks have passed. We have found a match - return args.Length <= paramList.Count || paramList.GetRange(args.Length, paramList.Count - args.Length) - .All(x => x.IsOptional); - }); - - if (ctor != null) return ctor; - } - - throw new Exception($"There is no empty/default constructor for {type} that matches predicate."); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } - - public static object CreateInstance(Type type, Predicate constructorPredicate = null) - { - var cctor = GetConstructor(type, constructorPredicate); - var args = cctor.GetParameters(); - - if (args.Length == 0) return cctor.Invoke(Type.EmptyTypes); - - var argList = new object[args.Length]; - Array.Fill(argList, Type.Missing); - return cctor.Invoke(argList); - } - - public static object CreateInstance(Type type, Predicate constructorPredicate = null, - params object[] args) - { - if (args == null || args.Length == 0) return CreateInstance(type, constructorPredicate); - - var cctor = GetConstructor(type, constructorPredicate, args.Select(x => x?.GetType()).ToArray()); - return cctor.Invoke(args); - } - - public static object CreateInstance(Type type, params object[] args) => CreateInstance(type, null, args); - - public static T CreateInstance(Predicate constructorPredicate = null) => - (T)CreateInstance(typeof(T), constructorPredicate); - - public static T CreateInstance(params object[] args) => (T)CreateInstance(typeof(T), null, args); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ActivatorUtil.cs - Created: 2020/02/19 - Updated: 2020/07/30 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Linq; +using System.Reflection; + +namespace Server.Utilities +{ + public static class ActivatorUtil + { + public static ConstructorInfo GetConstructor(Type type, Predicate predicate = null) + { + var emptyCtor = type.GetConstructor(Type.EmptyTypes); + + if (emptyCtor != null && predicate?.Invoke(emptyCtor) != false) return emptyCtor; + + var optionalCtor = type.GetConstructors() + .SingleOrDefault( + info => + predicate?.Invoke(info) != false && info.GetParameters().All(x => x.IsOptional) + ); + + if (optionalCtor != null) return optionalCtor; + + throw new TypeInitializationException( + type.ToString(), + new Exception($"There is no empty/default constructor for {type} that matches predicate.") + ); + } + + public static ConstructorInfo GetConstructor(Type type, Predicate predicate, params Type[] args) + { + try + { + ConstructorInfo ctor; + + if (args.All(x => x != null)) + { + ctor = type.GetConstructor(args); + + if (ctor != null && predicate?.Invoke(ctor) != false) return ctor; + } + else + { + ctor = type.GetConstructors() + .SingleOrDefault( + info => + { + if (predicate?.Invoke(info) == false) return false; + + var paramList = info.GetParameters().ToList(); + + // If more args are given than parameters, skip. + if (args.Length > paramList.Count) return false; + + // check all given args map to params. + for (var i = 0; i < args.Length; i++) + // if a null reference is passed, but the type is not nullable + if (args[i] == null && paramList[i].ParameterType.IsValueType + // or if an arg is not null and is not assignable to the parameter type, skip. + || !(args[i] == null || paramList[i].ParameterType.IsAssignableFrom(args[i]))) + return false; + + // If there are more parameters, check if they any are not optional, if any are not, skip. + // Otherwise all checks have passed. We have found a match + return args.Length <= paramList.Count || paramList + .GetRange(args.Length, paramList.Count - args.Length) + .All(x => x.IsOptional); + } + ); + + if (ctor != null) return ctor; + } + + throw new Exception($"There is no empty/default constructor for {type} that matches predicate."); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + public static object CreateInstance(Type type, Predicate constructorPredicate = null) + { + var cctor = GetConstructor(type, constructorPredicate); + var args = cctor.GetParameters(); + + if (args.Length == 0) return cctor.Invoke(Type.EmptyTypes); + + var argList = new object[args.Length]; + Array.Fill(argList, Type.Missing); + return cctor.Invoke(argList); + } + + public static object CreateInstance( + Type type, Predicate constructorPredicate = null, + params object[] args + ) + { + if (args == null || args.Length == 0) return CreateInstance(type, constructorPredicate); + + var cctor = GetConstructor(type, constructorPredicate, args.Select(x => x?.GetType()).ToArray()); + return cctor.Invoke(args); + } + + public static object CreateInstance(Type type, params object[] args) => CreateInstance(type, null, args); + + public static T CreateInstance(Predicate constructorPredicate = null) => + (T)CreateInstance(typeof(T), constructorPredicate); + + public static T CreateInstance(params object[] args) => (T)CreateInstance(typeof(T), null, args); + } +} diff --git a/Projects/Server/Utilities/RefPool.cs b/Projects/Server/Utilities/RefPool.cs index 2192a72c7..079923c56 100644 --- a/Projects/Server/Utilities/RefPool.cs +++ b/Projects/Server/Utilities/RefPool.cs @@ -1,149 +1,156 @@ -/************************************************************************* - * ModernUO * - * Copyright (C) 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: RefPool.cs - Created: 2020/02/20 - Updated: 2020/07/30 * - * * - * This program is free software: you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation, either version 3 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program. If not, see . * - *************************************************************************/ - -using System; -using System.Collections.Generic; - -namespace Server.Utilities -{ - /// A resource reference object that can be disposed. - /// - /// Disposing the reference is expected to return itself back into the - /// original pool that created it. - /// - public interface IRef : IDisposable - { - } - - /// - /// Base implementation of the interface. - /// - /// - /// New implementations of should either derive from, or mirror - /// the functionality of this base implementation. - /// - /// - public abstract class BaseRef : IRef where TDerived : IRef - { - private readonly RefPool m_Pool; - public BaseRef(RefPool pool) => m_Pool = pool; - protected abstract void OnDispose(); - - public void Dispose() - { - OnDispose(); - m_Pool.Return((TDerived)(object)this); - } - } - - /// - /// A resource reference pool that manages a collection of reusable resources. - /// - /// The resource type the pool will contain. - public class RefPool where TRef : IRef - { - public delegate TRef Generator(RefPool targetPool); - - public const int DEFAULT_RESOURCE_RETENTION = 10; - - private readonly Stack m_Resources = new Stack(); - private readonly Generator m_Generator; - private int m_MaxRefrenceRetention; - - /// - /// The maximum number of unused resources to hold in the pool. - /// - public int MaxRefrenceRetention - { - get => m_MaxRefrenceRetention; - set - { - m_MaxRefrenceRetention = value; - while (m_Resources.Count > value) m_Resources.Pop(); - } - } - - /// The generator function for creating new resources. - /// An amount of resources that should be pre-generated during initialization of the resource pool. - public RefPool(Generator generator, int preGenerateCount = 0, int maxRefrenceRetention = DEFAULT_RESOURCE_RETENTION) - { - if (generator == null) - throw new ArgumentNullException(nameof(generator)); - if (preGenerateCount > maxRefrenceRetention) - throw new IndexOutOfRangeException($"{nameof(preGenerateCount)} greater than {nameof(maxRefrenceRetention)}"); - m_Generator = generator; - m_MaxRefrenceRetention = maxRefrenceRetention; - while (--preGenerateCount >= 0) m_Resources.Push(generator(this)); - } - - /// - /// Retrieves a resource reference that is managed by this . If the pool is has unused resources, - /// it will remove one from the pool and return it; otherwise, a new resource will be generated. - /// - /// Unused resource, or a new resource if no unused resources available. - public TRef Get() => m_Resources.TryPop(out var item) ? item : m_Generator(this); - - /// - /// Returns a resource reference to the pool of unused resources. - /// - /// Resource to be returned. - public void Return(TRef queueRef) - { - if (m_Resources.Count < MaxRefrenceRetention) - m_Resources.Push(queueRef); - } - } - - public class QueueRef : Queue, IRef - { - private readonly RefPool> m_Pool; - private QueueRef(RefPool> pool) => m_Pool = pool; - - /// Clears the queue and returns this resource to its parent resource pool. - public void Dispose() - { - Clear(); - m_Pool.Return(this); - } - - /// - /// Generator function for creating instances of the resource. - /// - public static RefPool>.Generator Generate = targetPool => new QueueRef(targetPool); - } - - public class StackRef : Stack, IRef - { - private readonly RefPool> m_Pool; - private StackRef(RefPool> pool) => m_Pool = pool; - - /// Clears the stack and returns this resource to its parent resource pool. - public void Dispose() - { - Clear(); - m_Pool.Return(this); - } - - /// - /// Generator function for creating instances of the resource. - /// - public static RefPool>.Generator Generate = (targetPool) => new StackRef(targetPool); - } -} +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: RefPool.cs - Created: 2020/02/20 - Updated: 2020/07/30 * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Generic; + +namespace Server.Utilities +{ + /// A resource reference object that can be disposed. + /// + /// Disposing the reference is expected to return itself back into the + /// original pool that created it. + /// + public interface IRef : IDisposable + { + } + + /// + /// Base implementation of the interface. + /// + /// + /// New implementations of should either derive from, or mirror + /// the functionality of this base implementation. + /// + /// + public abstract class BaseRef : IRef where TDerived : IRef + { + private readonly RefPool m_Pool; + public BaseRef(RefPool pool) => m_Pool = pool; + + public void Dispose() + { + OnDispose(); + m_Pool.Return((TDerived)(object)this); + } + + protected abstract void OnDispose(); + } + + /// + /// A resource reference pool that manages a collection of reusable resources. + /// + /// The resource type the pool will contain. + public class RefPool where TRef : IRef + { + public delegate TRef Generator(RefPool targetPool); + + public const int DEFAULT_RESOURCE_RETENTION = 10; + private readonly Generator m_Generator; + + private readonly Stack m_Resources = new Stack(); + private int m_MaxRefrenceRetention; + + /// The generator function for creating new resources. + /// + /// An amount of resources that should be pre-generated during initialization of the resource + /// pool. + /// + public RefPool(Generator generator, int preGenerateCount = 0, int maxRefrenceRetention = DEFAULT_RESOURCE_RETENTION) + { + if (generator == null) + throw new ArgumentNullException(nameof(generator)); + if (preGenerateCount > maxRefrenceRetention) + throw new IndexOutOfRangeException( + $"{nameof(preGenerateCount)} greater than {nameof(maxRefrenceRetention)}" + ); + m_Generator = generator; + m_MaxRefrenceRetention = maxRefrenceRetention; + while (--preGenerateCount >= 0) m_Resources.Push(generator(this)); + } + + /// + /// The maximum number of unused resources to hold in the pool. + /// + public int MaxRefrenceRetention + { + get => m_MaxRefrenceRetention; + set + { + m_MaxRefrenceRetention = value; + while (m_Resources.Count > value) m_Resources.Pop(); + } + } + + /// + /// Retrieves a resource reference that is managed by this . If the pool is has unused + /// resources, + /// it will remove one from the pool and return it; otherwise, a new resource will be generated. + /// + /// Unused resource, or a new resource if no unused resources available. + public TRef Get() => m_Resources.TryPop(out var item) ? item : m_Generator(this); + + /// + /// Returns a resource reference to the pool of unused resources. + /// + /// Resource to be returned. + public void Return(TRef queueRef) + { + if (m_Resources.Count < MaxRefrenceRetention) + m_Resources.Push(queueRef); + } + } + + public class QueueRef : Queue, IRef + { + /// + /// Generator function for creating instances of the resource. + /// + public static RefPool>.Generator Generate = targetPool => new QueueRef(targetPool); + + private readonly RefPool> m_Pool; + private QueueRef(RefPool> pool) => m_Pool = pool; + + /// Clears the queue and returns this resource to its parent resource pool. + public void Dispose() + { + Clear(); + m_Pool.Return(this); + } + } + + public class StackRef : Stack, IRef + { + /// + /// Generator function for creating instances of the resource. + /// + public static RefPool>.Generator Generate = targetPool => new StackRef(targetPool); + + private readonly RefPool> m_Pool; + private StackRef(RefPool> pool) => m_Pool = pool; + + /// Clears the stack and returns this resource to its parent resource pool. + public void Dispose() + { + Clear(); + m_Pool.Return(this); + } + } +} diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 3c8fa6513..5f61af941 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -1,1059 +1,1068 @@ -/*************************************************************************** - * Utility.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Runtime.CompilerServices; -using System.Text; -using System.Xml; -using Server.Random; - -namespace Server -{ - public static class Utility - { - private static Encoding m_UTF8, m_UTF8WithEncoding; - - private static Dictionary _ipAddressTable; - - private static readonly SkillName[] m_AllSkills = - { - SkillName.Alchemy, - SkillName.Anatomy, - SkillName.AnimalLore, - SkillName.ItemID, - SkillName.ArmsLore, - SkillName.Parry, - SkillName.Begging, - SkillName.Blacksmith, - SkillName.Fletching, - SkillName.Peacemaking, - SkillName.Camping, - SkillName.Carpentry, - SkillName.Cartography, - SkillName.Cooking, - SkillName.DetectHidden, - SkillName.Discordance, - SkillName.EvalInt, - SkillName.Healing, - SkillName.Fishing, - SkillName.Forensics, - SkillName.Herding, - SkillName.Hiding, - SkillName.Provocation, - SkillName.Inscribe, - SkillName.Lockpicking, - SkillName.Magery, - SkillName.MagicResist, - SkillName.Tactics, - SkillName.Snooping, - SkillName.Musicianship, - SkillName.Poisoning, - SkillName.Archery, - SkillName.SpiritSpeak, - SkillName.Stealing, - SkillName.Tailoring, - SkillName.AnimalTaming, - SkillName.TasteID, - SkillName.Tinkering, - SkillName.Tracking, - SkillName.Veterinary, - SkillName.Swords, - SkillName.Macing, - SkillName.Fencing, - SkillName.Wrestling, - SkillName.Lumberjacking, - SkillName.Mining, - SkillName.Meditation, - SkillName.Stealth, - SkillName.RemoveTrap, - SkillName.Necromancy, - SkillName.Focus, - SkillName.Chivalry, - SkillName.Bushido, - SkillName.Ninjitsu, - SkillName.Spellweaving - }; - - private static readonly SkillName[] m_CombatSkills = - { - SkillName.Archery, - SkillName.Swords, - SkillName.Macing, - SkillName.Fencing, - SkillName.Wrestling - }; - - private static readonly SkillName[] m_CraftSkills = - { - SkillName.Alchemy, - SkillName.Blacksmith, - SkillName.Fletching, - SkillName.Carpentry, - SkillName.Cartography, - SkillName.Cooking, - SkillName.Inscribe, - SkillName.Tailoring, - SkillName.Tinkering - }; - - private static readonly Stack m_ConsoleColors = new Stack(); - - public static Encoding UTF8 => m_UTF8 ??= new UTF8Encoding(false, false); - public static Encoding UTF8WithEncoding => m_UTF8WithEncoding ??= new UTF8Encoding(true, false); - - public static void Separate(StringBuilder sb, string value, string separator) - { - if (sb.Length > 0) - sb.Append(separator); - - sb.Append(value); - } - - public static string Intern(string str) => str?.Length > 0 ? string.Intern(str) : str; - - public static void Intern(ref string str) - { - str = Intern(str); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string IsNullOrDefault(this string value, string def) => value?.Length > 0 ? value : def; - - public static IPAddress Intern(IPAddress ipAddress) - { - _ipAddressTable ??= new Dictionary(); - - if (!_ipAddressTable.TryGetValue(ipAddress, out var interned)) - { - interned = ipAddress; - _ipAddressTable[ipAddress] = interned; - } - - return interned; - } - - public static void Intern(ref IPAddress ipAddress) - { - ipAddress = Intern(ipAddress); - } - - public static bool IsValidIP(string text) - { - IPMatch(text, IPAddress.None, out var valid); - - return valid; - } - - public static bool IPMatch(string val, IPAddress ip) => IPMatch(val, ip, out _); - - public static string FixHtml(string str) - { - if (str == null) - return ""; - - var hasOpen = str.IndexOf('<') >= 0; - var hasClose = str.IndexOf('>') >= 0; - var hasPound = str.IndexOf('#') >= 0; - - if (!hasOpen && !hasClose && !hasPound) - return str; - - var sb = new StringBuilder(str); - - if (hasOpen) - sb.Replace('<', '('); - - if (hasClose) - sb.Replace('>', ')'); - - if (hasPound) - sb.Replace('#', '-'); - - return sb.ToString(); - } - - public static bool IPMatchCIDR(string cidr, IPAddress ip) - { - if (ip == null || ip.AddressFamily == AddressFamily.InterNetworkV6) - return false; // Just worry about IPv4 for now - - var bytes = new byte[4]; - var split = cidr.Split('.'); - var cidrBits = false; - var cidrLength = 0; - - for (var i = 0; i < 4; i++) - { - var part = 0; - - var partBase = 10; - - var pattern = split[i]; - - for (var j = 0; j < pattern.Length; j++) - { - var c = pattern[j]; - - if (c == 'x' || c == 'X') - { - partBase = 16; - } - else if (c >= '0' && c <= '9') - { - var offset = c - '0'; - - if (cidrBits) - { - cidrLength *= partBase; - cidrLength += offset; - } - else - { - part *= partBase; - part += offset; - } - } - else if (c >= 'a' && c <= 'f') - { - var offset = 10 + (c - 'a'); - - if (cidrBits) - { - cidrLength *= partBase; - cidrLength += offset; - } - else - { - part *= partBase; - part += offset; - } - } - else if (c >= 'A' && c <= 'F') - { - var offset = 10 + (c - 'A'); - - if (cidrBits) - { - cidrLength *= partBase; - cidrLength += offset; - } - else - { - part *= partBase; - part += offset; - } - } - else if (c == '/') - { - if (cidrBits || i != 3) // If there's two '/' or the '/' isn't in the last byte - return false; - - partBase = 10; - cidrBits = true; - } - else - { - return false; - } - } - - bytes[i] = (byte)part; - } - - return IPMatchCIDR(OrderedAddressValue(bytes), ip, cidrLength); - } - - public static bool IPMatchCIDR(IPAddress cidrPrefix, IPAddress ip, int cidrLength) - { - // Ignore IPv6 for now - if (cidrPrefix == null || ip == null || cidrPrefix.AddressFamily == AddressFamily.InterNetworkV6) - return false; - - var cidrValue = SwapUnsignedInt((uint)GetLongAddressValue(cidrPrefix)); - var ipValue = SwapUnsignedInt((uint)GetLongAddressValue(ip)); - - return IPMatchCIDR(cidrValue, ipValue, cidrLength); - } - - public static bool IPMatchCIDR(uint cidrPrefixValue, IPAddress ip, int cidrLength) - { - if (ip == null || ip.AddressFamily == AddressFamily.InterNetworkV6) - return false; - - var ipValue = SwapUnsignedInt((uint)GetLongAddressValue(ip)); - - return IPMatchCIDR(cidrPrefixValue, ipValue, cidrLength); - } - - public static bool IPMatchCIDR(uint cidrPrefixValue, uint ipValue, int cidrLength) - { - if (cidrLength <= 0 || cidrLength >= 32) // if invalid cidr Length, just compare IPs - return cidrPrefixValue == ipValue; - - var mask = uint.MaxValue << (32 - cidrLength); - - return (cidrPrefixValue & mask) == (ipValue & mask); - } - - private static uint OrderedAddressValue(byte[] bytes) - { - if (bytes.Length != 4) - return 0; - - return (uint)((bytes[0] << 0x18) | (bytes[1] << 0x10) | (bytes[2] << 8) | bytes[3]) & 0xffffffff; - } - - private static uint SwapUnsignedInt(uint source) => - ((source & 0x000000FF) << 0x18) - | ((source & 0x0000FF00) << 8) - | ((source & 0x00FF0000) >> 8) - | ((source & 0xFF000000) >> 0x18); - - public static bool TryConvertIPv6toIPv4(ref IPAddress address) - { - if (!Socket.OSSupportsIPv6 || address.AddressFamily == AddressFamily.InterNetwork) - return true; - - var addr = address.GetAddressBytes(); - if (addr.Length == 16) // sanity 0 - 15 //10 11 //12 13 14 15 - { - if (addr[10] != 0xFF || addr[11] != 0xFF) - return false; - - for (var i = 0; i < 10; i++) - if (addr[i] != 0) - return false; - - var v4Addr = new byte[4]; - - for (var i = 0; i < 4; i++) v4Addr[i] = addr[12 + i]; - - address = new IPAddress(v4Addr); - return true; - } - - return false; - } - - public static bool IPMatch(string val, IPAddress ip, out bool valid) - { - valid = true; - - var split = val.Split('.'); - - for (var i = 0; i < 4; ++i) - { - int lowPart, highPart; - - if (i >= split.Length) - { - lowPart = 0; - highPart = 255; - } - else - { - var pattern = split[i]; - - if (pattern == "*") - { - lowPart = 0; - highPart = 255; - } - else - { - lowPart = 0; - highPart = 0; - - var highOnly = false; - var lowBase = 10; - var highBase = 10; - - for (var j = 0; j < pattern.Length; ++j) - { - var c = pattern[j]; - - if (c == '?') - { - if (!highOnly) - { - lowPart *= lowBase; - lowPart += 0; - } - - highPart *= highBase; - highPart += highBase - 1; - } - else if (c == '-') - { - highOnly = true; - highPart = 0; - } - else if (c == 'x' || c == 'X') - { - lowBase = 16; - highBase = 16; - } - else if (c >= '0' && c <= '9') - { - var offset = c - '0'; - - if (!highOnly) - { - lowPart *= lowBase; - lowPart += offset; - } - - highPart *= highBase; - highPart += offset; - } - else if (c >= 'a' && c <= 'f') - { - var offset = 10 + (c - 'a'); - - if (!highOnly) - { - lowPart *= lowBase; - lowPart += offset; - } - - highPart *= highBase; - highPart += offset; - } - else if (c >= 'A' && c <= 'F') - { - var offset = 10 + (c - 'A'); - - if (!highOnly) - { - lowPart *= lowBase; - lowPart += offset; - } - - highPart *= highBase; - highPart += offset; - } - else - { - valid = false; // high & lowp art would be 0 if it got to here. - } - } - } - } - - int b = (byte)(GetAddressValue(ip) >> (i * 8)); - - if (b < lowPart || b > highPart) - return false; - } - - return true; - } - - public static bool IPMatchClassC(IPAddress ip1, IPAddress ip2) => - (GetAddressValue(ip1) & 0xFFFFFF) == (GetAddressValue(ip2) & 0xFFFFFF); - - public static int InsensitiveCompare(string first, string second) => Insensitive.Compare(first, second); - - public static bool InsensitiveStartsWith(string first, string second) => Insensitive.StartsWith(first, second); - - public static Direction GetDirection(IPoint2D from, IPoint2D to) - { - var dx = to.X - from.X; - var dy = to.Y - from.Y; - - var adx = Math.Abs(dx); - var ady = Math.Abs(dy); - - if (adx >= ady * 3) return dx > 0 ? Direction.East : Direction.West; - - if (ady >= adx * 3) return dy > 0 ? Direction.South : Direction.North; - - if (dx > 0) return dy > 0 ? Direction.Down : Direction.Right; - - return dy > 0 ? Direction.Left : Direction.Up; - } - - public static object GetArrayCap(Array array, int index, object emptyValue = null) => - array.Length > 0 ? array.GetValue(Math.Clamp(index, 0, array.Length - 1)) : emptyValue; - - public static SkillName RandomSkill() => - m_AllSkills[Random(m_AllSkills.Length - (Core.ML ? 0 : Core.SE ? 1 : Core.AOS ? 3 : 6))]; - - public static SkillName RandomCombatSkill() => m_CombatSkills.RandomElement(); - - public static SkillName RandomCraftSkill() => m_CraftSkills.RandomElement(); - - public static void FixPoints(ref Point3D top, ref Point3D bottom) - { - if (bottom.m_X < top.m_X) - { - var swap = top.m_X; - top.m_X = bottom.m_X; - bottom.m_X = swap; - } - - if (bottom.m_Y < top.m_Y) - { - var swap = top.m_Y; - top.m_Y = bottom.m_Y; - bottom.m_Y = swap; - } - - if (bottom.m_Z < top.m_Z) - { - var swap = top.m_Z; - top.m_Z = bottom.m_Z; - bottom.m_Z = swap; - } - } - - public static bool RangeCheck(IPoint2D p1, IPoint2D p2, int range) => - p1.X >= p2.X - range - && p1.X <= p2.X + range - && p1.Y >= p2.Y - range - && p2.Y <= p2.Y + range; - - public static void FormatBuffer(TextWriter output, Stream input, int length) - { - output.WriteLine(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); - output.WriteLine(" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); - - var byteIndex = 0; - - var whole = length >> 4; - var rem = length & 0xF; - - for (var i = 0; i < whole; ++i, byteIndex += 16) - { - var bytes = new StringBuilder(49); - var chars = new StringBuilder(16); - - for (var j = 0; j < 16; ++j) - { - var c = input.ReadByte(); - - bytes.Append(c.ToString("X2")); - - if (j != 7) - bytes.Append(' '); - else - bytes.Append(" "); - - if (c >= 0x20 && c < 0x7F) - chars.Append((char)c); - else - chars.Append('.'); - } - - output.Write(byteIndex.ToString("X4")); - output.Write(" "); - output.Write(bytes.ToString()); - output.Write(" "); - output.WriteLine(chars.ToString()); - } - - if (rem != 0) - { - var bytes = new StringBuilder(49); - var chars = new StringBuilder(rem); - - for (var j = 0; j < 16; ++j) - if (j < rem) - { - var c = input.ReadByte(); - - bytes.Append(c.ToString("X2")); - - if (j != 7) - bytes.Append(' '); - else - bytes.Append(" "); - - if (c >= 0x20 && c < 0x7F) - chars.Append((char)c); - else - chars.Append('.'); - } - else - { - bytes.Append(" "); - } - - output.Write(byteIndex.ToString("X4")); - output.Write(" "); - output.Write(bytes.ToString()); - output.Write(" "); - output.WriteLine(chars.ToString()); - } - } - - public static void PushColor(ConsoleColor color) - { - try - { - m_ConsoleColors.Push(Console.ForegroundColor); - Console.ForegroundColor = color; - } - catch - { - // ignored - } - } - - public static void PopColor() - { - try - { - Console.ForegroundColor = m_ConsoleColors.Pop(); - } - catch - { - // ignored - } - } - - public static bool NumberBetween(double num, int bound1, int bound2, double allowance) - { - if (bound1 > bound2) - { - var i = bound1; - bound1 = bound2; - bound2 = i; - } - - return num < bound2 + allowance && num > bound1 - allowance; - } - - public static void AssignRandomHair(Mobile m, int hue) - { - m.HairItemID = m.Race.RandomHair(m); - m.HairHue = hue; - } - - public static void AssignRandomHair(Mobile m, bool randomHue = true) - { - m.HairItemID = m.Race.RandomHair(m); - - if (randomHue) - m.HairHue = m.Race.RandomHairHue(); - } - - public static void AssignRandomFacialHair(Mobile m, int hue) - { - m.FacialHairItemID = m.Race.RandomFacialHair(m); - m.FacialHairHue = hue; - } - - public static void AssignRandomFacialHair(Mobile m, bool randomHue = true) - { - m.FacialHairItemID = m.Race.RandomFacialHair(m); - - if (randomHue) - m.FacialHairHue = m.Race.RandomHairHue(); - } - - public static List CastListContravariant(List list) where TInput : TOutput => - list.ConvertAll(value => (TOutput)value); - - public static List CastListCovariant(List list) where TOutput : TInput => - list.ConvertAll(value => (TOutput)value); - - public static List SafeConvertList(List list) where TOutput : class - { - if ((list?.Capacity ?? 0) == 0) - return new List(); - - var output = new List(list.Capacity); - output.AddRange(list.OfType()); - - return output; - } - - public static bool ToBoolean(string value) - { -#pragma warning disable CA1806 // Do not ignore method results - bool.TryParse(value, out var b); -#pragma warning restore CA1806 // Do not ignore method results - - return b; - } - - public static double ToDouble(string value) - { -#pragma warning disable CA1806 // Do not ignore method results - double.TryParse(value, out var d); -#pragma warning restore CA1806 // Do not ignore method results - - return d; - } - - public static TimeSpan ToTimeSpan(string value) - { -#pragma warning disable CA1806 // Do not ignore method results - TimeSpan.TryParse(value, out var t); -#pragma warning restore CA1806 // Do not ignore method results - - return t; - } - - public static int ToInt32(string value) - { - int i; - -#pragma warning disable CA1806 // Do not ignore method results - if (value.StartsWith("0x")) - int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i); - else - int.TryParse(value, out i); -#pragma warning restore CA1806 // Do not ignore method results - - return i; - } - - public static uint ToUInt32(string value) - { - uint i; - -#pragma warning disable CA1806 // Do not ignore method results - if (value.StartsWith("0x")) - uint.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i); - else - uint.TryParse(value, out i); -#pragma warning restore CA1806 // Do not ignore method results - - return i; - } - - public static bool ToInt32(string value, out int i) => - value.StartsWith("0x") - ? int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i) - : int.TryParse(value, out i); - - public static bool ToUInt32(string value, out uint i) => - value.StartsWith("0x") - ? uint.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i) - : uint.TryParse(value, out i); - - public static int GetXMLInt32(string intString, int defaultValue) - { - try - { - return XmlConvert.ToInt32(intString); - } - catch - { - return int.TryParse(intString, out var val) ? val : defaultValue; - } - } - - public static uint GetXMLUInt32(string uintString, uint defaultValue) - { - try - { - return XmlConvert.ToUInt32(uintString); - } - catch - { - return uint.TryParse(uintString, out var val) ? val : defaultValue; - } - } - - public static DateTime GetXMLDateTime(string dateTimeString, DateTime defaultValue) - { - try - { - return XmlConvert.ToDateTime(dateTimeString, XmlDateTimeSerializationMode.Utc); - } - catch - { - return DateTime.TryParse(dateTimeString, out var d) ? d : defaultValue; - } - } - - public static TimeSpan GetXMLTimeSpan(string timeSpanString, TimeSpan defaultValue) - { - try - { - return XmlConvert.ToTimeSpan(timeSpanString); - } - catch - { - return defaultValue; - } - } - - public static string GetAttribute(XmlElement node, string attributeName, string defaultValue = null) => - node?.Attributes[attributeName]?.Value ?? defaultValue; - - public static string GetText(XmlElement node, string defaultValue) => node == null ? defaultValue : node.InnerText; - - public static int GetAddressValue(IPAddress address) => BitConverter.ToInt32(address.GetAddressBytes(), 0); - - public static long GetLongAddressValue(IPAddress address) => BitConverter.ToInt64(address.GetAddressBytes(), 0); - - public static bool InRange(Point3D p1, Point3D p2, int range) => - p1.m_X >= p2.m_X - range - && p1.m_X <= p2.m_X + range - && p1.m_Y >= p2.m_Y - range - && p1.m_Y <= p2.m_Y + range; - - public static bool InUpdateRange(Point3D p1, Point3D p2) => - p1.m_X >= p2.m_X - 18 - && p1.m_X <= p2.m_X + 18 - && p1.m_Y >= p2.m_Y - 18 - && p1.m_Y <= p2.m_Y + 18; - - public static bool InUpdateRange(Point2D p1, Point2D p2) => - p1.m_X >= p2.m_X - 18 - && p1.m_X <= p2.m_X + 18 - && p1.m_Y >= p2.m_Y - 18 - && p1.m_Y <= p2.m_Y + 18; - - public static bool InUpdateRange(IPoint2D p1, IPoint2D p2) => - p1.X >= p2.X - 18 - && p1.X <= p2.X + 18 - && p1.Y >= p2.Y - 18 - && p1.Y <= p2.Y + 18; - - // 4d6+8 would be: Utility.Dice( 4, 6, 8 ) - public static int Dice(uint amount, uint sides, int bonus) - { - var total = 0; - - for (var i = 0; i < amount; ++i) - total += (int)RandomSources.Source.Next(1, sides); - - return total + bonus; - } - - public static void Shuffle(this IList list) - { - var count = list.Count; - for (var i = 0; i < count; i++) - { - var r = RandomMinMax(i, count - 1); - var swap = list[r]; - list[r] = list[i]; - list[i] = swap; - } - } - - public static void Shuffle(this Span list) - { - var count = list.Length; - for (var i = 0; i < count; i++) - { - var r = RandomMinMax(i, count - 1); - var swap = list[r]; - list[r] = list[i]; - list[i] = swap; - } - } - - /** - * Gets a random sample from the source list. - * Not meant for unbounded lists. Does not shuffle or modify source. - */ - public static T[] RandomSample(this T[] source, int count) - { - if (count <= 0) return Array.Empty(); - - var length = source.Length; - Span list = stackalloc bool[length]; - var sampleList = new T[count]; - - int i = 0; - do - { - var rand = Random(length); - if (!(list[rand] && (list[rand] = true))) - sampleList[i++] = source[rand]; - } while (i < count); - - return sampleList; - } - - public static List RandomSample(this List source, int count) - { - if (count <= 0) return new List(); - - var length = source.Count; - Span list = stackalloc bool[length]; - var sampleList = new List(count); - - int i = 0; - do - { - var rand = Random(length); - if (!(list[rand] && (list[rand] = true))) - sampleList[i++] = source[rand]; - } while (i < count); - - return sampleList; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T RandomList(params T[] list) => list.RandomElement(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T RandomElement(this IList list) => list.RandomElement(default); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T RandomElement(this IList list, T valueIfZero) => list.Count == 0 ? valueIfZero : list[Random(list.Count)]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool RandomBool() => RandomSources.Source.NextBool(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int RandomMinMax(int min, int max) - { - if (min > max) - { - var copy = min; - min = max; - max = copy; - } - else if (min == max) - { - return min; - } - - return min + (int)RandomSources.Source.Next((uint)(max - min + 1)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Random(int from, int count) => RandomSources.Source.Next(from, count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Random(int count) => RandomSources.Source.Next(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint Random(uint count) => RandomSources.Source.Next(count); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void RandomBytes(Span buffer) => RandomSources.Source.NextBytes(buffer); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double RandomDouble() => RandomSources.Source.NextDouble(); - - /// - /// Random pink, blue, green, orange, red or yellow hue - /// - public static int RandomNondyedHue() - { - return Random(6) switch - { - 0 => RandomPinkHue(), - 1 => RandomBlueHue(), - 2 => RandomGreenHue(), - 3 => RandomOrangeHue(), - 4 => RandomRedHue(), - 5 => RandomYellowHue(), - _ => 0 - }; - } - - /// - /// Random hue in the range 1201-1254 - /// - public static int RandomPinkHue() => Random(1201, 54); - - /// - /// Random hue in the range 1301-1354 - /// - public static int RandomBlueHue() => Random(1301, 54); - - /// - /// Random hue in the range 1401-1454 - /// - public static int RandomGreenHue() => Random(1401, 54); - - /// - /// Random hue in the range 1501-1554 - /// - public static int RandomOrangeHue() => Random(1501, 54); - - /// - /// Random hue in the range 1601-1654 - /// - public static int RandomRedHue() => Random(1601, 54); - - /// - /// Random hue in the range 1701-1754 - /// - public static int RandomYellowHue() => Random(1701, 54); - - /// - /// Random hue in the range 1801-1908 - /// - public static int RandomNeutralHue() => Random(1801, 108); - - /// - /// Random hue in the range 2001-2018 - /// - public static int RandomSnakeHue() => Random(2001, 18); - - /// - /// Random hue in the range 2101-2130 - /// - public static int RandomBirdHue() => Random(2101, 30); - - /// - /// Random hue in the range 2201-2224 - /// - public static int RandomSlimeHue() => Random(2201, 24); - - /// - /// Random hue in the range 2301-2318 - /// - public static int RandomAnimalHue() => Random(2301, 18); - - /// - /// Random hue in the range 2401-2430 - /// - public static int RandomMetalHue() => Random(2401, 30); - - public static int ClipDyedHue(int hue) => hue < 2 ? 2 : hue > 1001 ? 1001 : hue; - - /// - /// Random hue in the range 2-1001 - /// - public static int RandomDyedHue() => Random(2, 1000); - - /// - /// Random hue from 0x62, 0x71, 0x03, 0x0D, 0x13, 0x1C, 0x21, 0x30, 0x37, 0x3A, 0x44, 0x59 - /// - public static int RandomBrightHue() => - RandomDouble() < 0.1 ? RandomList(0x62, 0x71) : RandomList(0x03, 0x0D, 0x13, 0x1C, 0x21, 0x30, 0x37, 0x3A, 0x44, 0x59); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Clamp(this T val, T min, T max) where T : IComparable => - val.CompareTo(min) < 0 ? min : val.CompareTo(max) > 0 ? max : val; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TimeSpan Max(this TimeSpan val, TimeSpan max) => val > max ? max : val; - } -} +/*************************************************************************** + * Utility.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Xml; +using Server.Random; + +namespace Server +{ + public static class Utility + { + private static Encoding m_UTF8, m_UTF8WithEncoding; + + private static Dictionary _ipAddressTable; + + private static readonly SkillName[] m_AllSkills = + { + SkillName.Alchemy, + SkillName.Anatomy, + SkillName.AnimalLore, + SkillName.ItemID, + SkillName.ArmsLore, + SkillName.Parry, + SkillName.Begging, + SkillName.Blacksmith, + SkillName.Fletching, + SkillName.Peacemaking, + SkillName.Camping, + SkillName.Carpentry, + SkillName.Cartography, + SkillName.Cooking, + SkillName.DetectHidden, + SkillName.Discordance, + SkillName.EvalInt, + SkillName.Healing, + SkillName.Fishing, + SkillName.Forensics, + SkillName.Herding, + SkillName.Hiding, + SkillName.Provocation, + SkillName.Inscribe, + SkillName.Lockpicking, + SkillName.Magery, + SkillName.MagicResist, + SkillName.Tactics, + SkillName.Snooping, + SkillName.Musicianship, + SkillName.Poisoning, + SkillName.Archery, + SkillName.SpiritSpeak, + SkillName.Stealing, + SkillName.Tailoring, + SkillName.AnimalTaming, + SkillName.TasteID, + SkillName.Tinkering, + SkillName.Tracking, + SkillName.Veterinary, + SkillName.Swords, + SkillName.Macing, + SkillName.Fencing, + SkillName.Wrestling, + SkillName.Lumberjacking, + SkillName.Mining, + SkillName.Meditation, + SkillName.Stealth, + SkillName.RemoveTrap, + SkillName.Necromancy, + SkillName.Focus, + SkillName.Chivalry, + SkillName.Bushido, + SkillName.Ninjitsu, + SkillName.Spellweaving + }; + + private static readonly SkillName[] m_CombatSkills = + { + SkillName.Archery, + SkillName.Swords, + SkillName.Macing, + SkillName.Fencing, + SkillName.Wrestling + }; + + private static readonly SkillName[] m_CraftSkills = + { + SkillName.Alchemy, + SkillName.Blacksmith, + SkillName.Fletching, + SkillName.Carpentry, + SkillName.Cartography, + SkillName.Cooking, + SkillName.Inscribe, + SkillName.Tailoring, + SkillName.Tinkering + }; + + private static readonly Stack m_ConsoleColors = new Stack(); + + public static Encoding UTF8 => m_UTF8 ??= new UTF8Encoding(false, false); + public static Encoding UTF8WithEncoding => m_UTF8WithEncoding ??= new UTF8Encoding(true, false); + + public static void Separate(StringBuilder sb, string value, string separator) + { + if (sb.Length > 0) + sb.Append(separator); + + sb.Append(value); + } + + public static string Intern(string str) => str?.Length > 0 ? string.Intern(str) : str; + + public static void Intern(ref string str) + { + str = Intern(str); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string IsNullOrDefault(this string value, string def) => value?.Length > 0 ? value : def; + + public static IPAddress Intern(IPAddress ipAddress) + { + _ipAddressTable ??= new Dictionary(); + + if (!_ipAddressTable.TryGetValue(ipAddress, out var interned)) + { + interned = ipAddress; + _ipAddressTable[ipAddress] = interned; + } + + return interned; + } + + public static void Intern(ref IPAddress ipAddress) + { + ipAddress = Intern(ipAddress); + } + + public static bool IsValidIP(string text) + { + IPMatch(text, IPAddress.None, out var valid); + + return valid; + } + + public static bool IPMatch(string val, IPAddress ip) => IPMatch(val, ip, out _); + + public static string FixHtml(string str) + { + if (str == null) + return ""; + + var hasOpen = str.IndexOf('<') >= 0; + var hasClose = str.IndexOf('>') >= 0; + var hasPound = str.IndexOf('#') >= 0; + + if (!hasOpen && !hasClose && !hasPound) + return str; + + var sb = new StringBuilder(str); + + if (hasOpen) + sb.Replace('<', '('); + + if (hasClose) + sb.Replace('>', ')'); + + if (hasPound) + sb.Replace('#', '-'); + + return sb.ToString(); + } + + public static bool IPMatchCIDR(string cidr, IPAddress ip) + { + if (ip == null || ip.AddressFamily == AddressFamily.InterNetworkV6) + return false; // Just worry about IPv4 for now + + var bytes = new byte[4]; + var split = cidr.Split('.'); + var cidrBits = false; + var cidrLength = 0; + + for (var i = 0; i < 4; i++) + { + var part = 0; + + var partBase = 10; + + var pattern = split[i]; + + for (var j = 0; j < pattern.Length; j++) + { + var c = pattern[j]; + + if (c == 'x' || c == 'X') + { + partBase = 16; + } + else if (c >= '0' && c <= '9') + { + var offset = c - '0'; + + if (cidrBits) + { + cidrLength *= partBase; + cidrLength += offset; + } + else + { + part *= partBase; + part += offset; + } + } + else if (c >= 'a' && c <= 'f') + { + var offset = 10 + (c - 'a'); + + if (cidrBits) + { + cidrLength *= partBase; + cidrLength += offset; + } + else + { + part *= partBase; + part += offset; + } + } + else if (c >= 'A' && c <= 'F') + { + var offset = 10 + (c - 'A'); + + if (cidrBits) + { + cidrLength *= partBase; + cidrLength += offset; + } + else + { + part *= partBase; + part += offset; + } + } + else if (c == '/') + { + if (cidrBits || i != 3) // If there's two '/' or the '/' isn't in the last byte + return false; + + partBase = 10; + cidrBits = true; + } + else + { + return false; + } + } + + bytes[i] = (byte)part; + } + + return IPMatchCIDR(OrderedAddressValue(bytes), ip, cidrLength); + } + + public static bool IPMatchCIDR(IPAddress cidrPrefix, IPAddress ip, int cidrLength) + { + // Ignore IPv6 for now + if (cidrPrefix == null || ip == null || cidrPrefix.AddressFamily == AddressFamily.InterNetworkV6) + return false; + + var cidrValue = SwapUnsignedInt((uint)GetLongAddressValue(cidrPrefix)); + var ipValue = SwapUnsignedInt((uint)GetLongAddressValue(ip)); + + return IPMatchCIDR(cidrValue, ipValue, cidrLength); + } + + public static bool IPMatchCIDR(uint cidrPrefixValue, IPAddress ip, int cidrLength) + { + if (ip == null || ip.AddressFamily == AddressFamily.InterNetworkV6) + return false; + + var ipValue = SwapUnsignedInt((uint)GetLongAddressValue(ip)); + + return IPMatchCIDR(cidrPrefixValue, ipValue, cidrLength); + } + + public static bool IPMatchCIDR(uint cidrPrefixValue, uint ipValue, int cidrLength) + { + if (cidrLength <= 0 || cidrLength >= 32) // if invalid cidr Length, just compare IPs + return cidrPrefixValue == ipValue; + + var mask = uint.MaxValue << (32 - cidrLength); + + return (cidrPrefixValue & mask) == (ipValue & mask); + } + + private static uint OrderedAddressValue(byte[] bytes) + { + if (bytes.Length != 4) + return 0; + + return (uint)((bytes[0] << 0x18) | (bytes[1] << 0x10) | (bytes[2] << 8) | bytes[3]) & 0xffffffff; + } + + private static uint SwapUnsignedInt(uint source) => + ((source & 0x000000FF) << 0x18) + | ((source & 0x0000FF00) << 8) + | ((source & 0x00FF0000) >> 8) + | ((source & 0xFF000000) >> 0x18); + + public static bool TryConvertIPv6toIPv4(ref IPAddress address) + { + if (!Socket.OSSupportsIPv6 || address.AddressFamily == AddressFamily.InterNetwork) + return true; + + var addr = address.GetAddressBytes(); + if (addr.Length == 16) // sanity 0 - 15 //10 11 //12 13 14 15 + { + if (addr[10] != 0xFF || addr[11] != 0xFF) + return false; + + for (var i = 0; i < 10; i++) + if (addr[i] != 0) + return false; + + var v4Addr = new byte[4]; + + for (var i = 0; i < 4; i++) v4Addr[i] = addr[12 + i]; + + address = new IPAddress(v4Addr); + return true; + } + + return false; + } + + public static bool IPMatch(string val, IPAddress ip, out bool valid) + { + valid = true; + + var split = val.Split('.'); + + for (var i = 0; i < 4; ++i) + { + int lowPart, highPart; + + if (i >= split.Length) + { + lowPart = 0; + highPart = 255; + } + else + { + var pattern = split[i]; + + if (pattern == "*") + { + lowPart = 0; + highPart = 255; + } + else + { + lowPart = 0; + highPart = 0; + + var highOnly = false; + var lowBase = 10; + var highBase = 10; + + for (var j = 0; j < pattern.Length; ++j) + { + var c = pattern[j]; + + if (c == '?') + { + if (!highOnly) + { + lowPart *= lowBase; + lowPart += 0; + } + + highPart *= highBase; + highPart += highBase - 1; + } + else if (c == '-') + { + highOnly = true; + highPart = 0; + } + else if (c == 'x' || c == 'X') + { + lowBase = 16; + highBase = 16; + } + else if (c >= '0' && c <= '9') + { + var offset = c - '0'; + + if (!highOnly) + { + lowPart *= lowBase; + lowPart += offset; + } + + highPart *= highBase; + highPart += offset; + } + else if (c >= 'a' && c <= 'f') + { + var offset = 10 + (c - 'a'); + + if (!highOnly) + { + lowPart *= lowBase; + lowPart += offset; + } + + highPart *= highBase; + highPart += offset; + } + else if (c >= 'A' && c <= 'F') + { + var offset = 10 + (c - 'A'); + + if (!highOnly) + { + lowPart *= lowBase; + lowPart += offset; + } + + highPart *= highBase; + highPart += offset; + } + else + { + valid = false; // high & lowp art would be 0 if it got to here. + } + } + } + } + + int b = (byte)(GetAddressValue(ip) >> (i * 8)); + + if (b < lowPart || b > highPart) + return false; + } + + return true; + } + + public static bool IPMatchClassC(IPAddress ip1, IPAddress ip2) => + (GetAddressValue(ip1) & 0xFFFFFF) == (GetAddressValue(ip2) & 0xFFFFFF); + + public static int InsensitiveCompare(string first, string second) => Insensitive.Compare(first, second); + + public static bool InsensitiveStartsWith(string first, string second) => Insensitive.StartsWith(first, second); + + public static Direction GetDirection(IPoint2D from, IPoint2D to) + { + var dx = to.X - from.X; + var dy = to.Y - from.Y; + + var adx = Math.Abs(dx); + var ady = Math.Abs(dy); + + if (adx >= ady * 3) return dx > 0 ? Direction.East : Direction.West; + + if (ady >= adx * 3) return dy > 0 ? Direction.South : Direction.North; + + if (dx > 0) return dy > 0 ? Direction.Down : Direction.Right; + + return dy > 0 ? Direction.Left : Direction.Up; + } + + public static object GetArrayCap(Array array, int index, object emptyValue = null) => + array.Length > 0 ? array.GetValue(Math.Clamp(index, 0, array.Length - 1)) : emptyValue; + + public static SkillName RandomSkill() => + m_AllSkills[Random( + m_AllSkills.Length - (Core.ML ? 0 : + Core.SE ? 1 : + Core.AOS ? 3 : 6) + )]; + + public static SkillName RandomCombatSkill() => m_CombatSkills.RandomElement(); + + public static SkillName RandomCraftSkill() => m_CraftSkills.RandomElement(); + + public static void FixPoints(ref Point3D top, ref Point3D bottom) + { + if (bottom.m_X < top.m_X) + { + var swap = top.m_X; + top.m_X = bottom.m_X; + bottom.m_X = swap; + } + + if (bottom.m_Y < top.m_Y) + { + var swap = top.m_Y; + top.m_Y = bottom.m_Y; + bottom.m_Y = swap; + } + + if (bottom.m_Z < top.m_Z) + { + var swap = top.m_Z; + top.m_Z = bottom.m_Z; + bottom.m_Z = swap; + } + } + + public static bool RangeCheck(IPoint2D p1, IPoint2D p2, int range) => + p1.X >= p2.X - range + && p1.X <= p2.X + range + && p1.Y >= p2.Y - range + && p2.Y <= p2.Y + range; + + public static void FormatBuffer(TextWriter output, Stream input, int length) + { + output.WriteLine(" 0 1 2 3 4 5 6 7 8 9 A B C D E F"); + output.WriteLine(" -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); + + var byteIndex = 0; + + var whole = length >> 4; + var rem = length & 0xF; + + for (var i = 0; i < whole; ++i, byteIndex += 16) + { + var bytes = new StringBuilder(49); + var chars = new StringBuilder(16); + + for (var j = 0; j < 16; ++j) + { + var c = input.ReadByte(); + + bytes.Append(c.ToString("X2")); + + if (j != 7) + bytes.Append(' '); + else + bytes.Append(" "); + + if (c >= 0x20 && c < 0x7F) + chars.Append((char)c); + else + chars.Append('.'); + } + + output.Write(byteIndex.ToString("X4")); + output.Write(" "); + output.Write(bytes.ToString()); + output.Write(" "); + output.WriteLine(chars.ToString()); + } + + if (rem != 0) + { + var bytes = new StringBuilder(49); + var chars = new StringBuilder(rem); + + for (var j = 0; j < 16; ++j) + if (j < rem) + { + var c = input.ReadByte(); + + bytes.Append(c.ToString("X2")); + + if (j != 7) + bytes.Append(' '); + else + bytes.Append(" "); + + if (c >= 0x20 && c < 0x7F) + chars.Append((char)c); + else + chars.Append('.'); + } + else + { + bytes.Append(" "); + } + + output.Write(byteIndex.ToString("X4")); + output.Write(" "); + output.Write(bytes.ToString()); + output.Write(" "); + output.WriteLine(chars.ToString()); + } + } + + public static void PushColor(ConsoleColor color) + { + try + { + m_ConsoleColors.Push(Console.ForegroundColor); + Console.ForegroundColor = color; + } + catch + { + // ignored + } + } + + public static void PopColor() + { + try + { + Console.ForegroundColor = m_ConsoleColors.Pop(); + } + catch + { + // ignored + } + } + + public static bool NumberBetween(double num, int bound1, int bound2, double allowance) + { + if (bound1 > bound2) + { + var i = bound1; + bound1 = bound2; + bound2 = i; + } + + return num < bound2 + allowance && num > bound1 - allowance; + } + + public static void AssignRandomHair(Mobile m, int hue) + { + m.HairItemID = m.Race.RandomHair(m); + m.HairHue = hue; + } + + public static void AssignRandomHair(Mobile m, bool randomHue = true) + { + m.HairItemID = m.Race.RandomHair(m); + + if (randomHue) + m.HairHue = m.Race.RandomHairHue(); + } + + public static void AssignRandomFacialHair(Mobile m, int hue) + { + m.FacialHairItemID = m.Race.RandomFacialHair(m); + m.FacialHairHue = hue; + } + + public static void AssignRandomFacialHair(Mobile m, bool randomHue = true) + { + m.FacialHairItemID = m.Race.RandomFacialHair(m); + + if (randomHue) + m.FacialHairHue = m.Race.RandomHairHue(); + } + + public static List CastListContravariant(List list) where TInput : TOutput => + list.ConvertAll(value => (TOutput)value); + + public static List CastListCovariant(List list) where TOutput : TInput => + list.ConvertAll(value => (TOutput)value); + + public static List SafeConvertList(List list) where TOutput : class + { + if ((list?.Capacity ?? 0) == 0) + return new List(); + + var output = new List(list.Capacity); + output.AddRange(list.OfType()); + + return output; + } + + public static bool ToBoolean(string value) + { +#pragma warning disable CA1806 // Do not ignore method results + bool.TryParse(value, out var b); +#pragma warning restore CA1806 // Do not ignore method results + + return b; + } + + public static double ToDouble(string value) + { +#pragma warning disable CA1806 // Do not ignore method results + double.TryParse(value, out var d); +#pragma warning restore CA1806 // Do not ignore method results + + return d; + } + + public static TimeSpan ToTimeSpan(string value) + { +#pragma warning disable CA1806 // Do not ignore method results + TimeSpan.TryParse(value, out var t); +#pragma warning restore CA1806 // Do not ignore method results + + return t; + } + + public static int ToInt32(string value) + { + int i; + +#pragma warning disable CA1806 // Do not ignore method results + if (value.StartsWith("0x")) + int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i); + else + int.TryParse(value, out i); +#pragma warning restore CA1806 // Do not ignore method results + + return i; + } + + public static uint ToUInt32(string value) + { + uint i; + +#pragma warning disable CA1806 // Do not ignore method results + if (value.StartsWith("0x")) + uint.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i); + else + uint.TryParse(value, out i); +#pragma warning restore CA1806 // Do not ignore method results + + return i; + } + + public static bool ToInt32(string value, out int i) => + value.StartsWith("0x") + ? int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i) + : int.TryParse(value, out i); + + public static bool ToUInt32(string value, out uint i) => + value.StartsWith("0x") + ? uint.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i) + : uint.TryParse(value, out i); + + public static int GetXMLInt32(string intString, int defaultValue) + { + try + { + return XmlConvert.ToInt32(intString); + } + catch + { + return int.TryParse(intString, out var val) ? val : defaultValue; + } + } + + public static uint GetXMLUInt32(string uintString, uint defaultValue) + { + try + { + return XmlConvert.ToUInt32(uintString); + } + catch + { + return uint.TryParse(uintString, out var val) ? val : defaultValue; + } + } + + public static DateTime GetXMLDateTime(string dateTimeString, DateTime defaultValue) + { + try + { + return XmlConvert.ToDateTime(dateTimeString, XmlDateTimeSerializationMode.Utc); + } + catch + { + return DateTime.TryParse(dateTimeString, out var d) ? d : defaultValue; + } + } + + public static TimeSpan GetXMLTimeSpan(string timeSpanString, TimeSpan defaultValue) + { + try + { + return XmlConvert.ToTimeSpan(timeSpanString); + } + catch + { + return defaultValue; + } + } + + public static string GetAttribute(XmlElement node, string attributeName, string defaultValue = null) => + node?.Attributes[attributeName]?.Value ?? defaultValue; + + public static string GetText(XmlElement node, string defaultValue) => node == null ? defaultValue : node.InnerText; + + public static int GetAddressValue(IPAddress address) => BitConverter.ToInt32(address.GetAddressBytes(), 0); + + public static long GetLongAddressValue(IPAddress address) => BitConverter.ToInt64(address.GetAddressBytes(), 0); + + public static bool InRange(Point3D p1, Point3D p2, int range) => + p1.m_X >= p2.m_X - range + && p1.m_X <= p2.m_X + range + && p1.m_Y >= p2.m_Y - range + && p1.m_Y <= p2.m_Y + range; + + public static bool InUpdateRange(Point3D p1, Point3D p2) => + p1.m_X >= p2.m_X - 18 + && p1.m_X <= p2.m_X + 18 + && p1.m_Y >= p2.m_Y - 18 + && p1.m_Y <= p2.m_Y + 18; + + public static bool InUpdateRange(Point2D p1, Point2D p2) => + p1.m_X >= p2.m_X - 18 + && p1.m_X <= p2.m_X + 18 + && p1.m_Y >= p2.m_Y - 18 + && p1.m_Y <= p2.m_Y + 18; + + public static bool InUpdateRange(IPoint2D p1, IPoint2D p2) => + p1.X >= p2.X - 18 + && p1.X <= p2.X + 18 + && p1.Y >= p2.Y - 18 + && p1.Y <= p2.Y + 18; + + // 4d6+8 would be: Utility.Dice( 4, 6, 8 ) + public static int Dice(uint amount, uint sides, int bonus) + { + var total = 0; + + for (var i = 0; i < amount; ++i) + total += (int)RandomSources.Source.Next(1, sides); + + return total + bonus; + } + + public static void Shuffle(this IList list) + { + var count = list.Count; + for (var i = 0; i < count; i++) + { + var r = RandomMinMax(i, count - 1); + var swap = list[r]; + list[r] = list[i]; + list[i] = swap; + } + } + + public static void Shuffle(this Span list) + { + var count = list.Length; + for (var i = 0; i < count; i++) + { + var r = RandomMinMax(i, count - 1); + var swap = list[r]; + list[r] = list[i]; + list[i] = swap; + } + } + + /** + * Gets a random sample from the source list. + * Not meant for unbounded lists. Does not shuffle or modify source. + */ + public static T[] RandomSample(this T[] source, int count) + { + if (count <= 0) return Array.Empty(); + + var length = source.Length; + Span list = stackalloc bool[length]; + var sampleList = new T[count]; + + var i = 0; + do + { + var rand = Random(length); + if (!(list[rand] && (list[rand] = true))) + sampleList[i++] = source[rand]; + } while (i < count); + + return sampleList; + } + + public static List RandomSample(this List source, int count) + { + if (count <= 0) return new List(); + + var length = source.Count; + Span list = stackalloc bool[length]; + var sampleList = new List(count); + + var i = 0; + do + { + var rand = Random(length); + if (!(list[rand] && (list[rand] = true))) + sampleList[i++] = source[rand]; + } while (i < count); + + return sampleList; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T RandomList(params T[] list) => list.RandomElement(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T RandomElement(this IList list) => list.RandomElement(default); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T RandomElement(this IList list, T valueIfZero) => + list.Count == 0 ? valueIfZero : list[Random(list.Count)]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool RandomBool() => RandomSources.Source.NextBool(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int RandomMinMax(int min, int max) + { + if (min > max) + { + var copy = min; + min = max; + max = copy; + } + else if (min == max) + { + return min; + } + + return min + (int)RandomSources.Source.Next((uint)(max - min + 1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Random(int from, int count) => RandomSources.Source.Next(from, count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Random(int count) => RandomSources.Source.Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Random(uint count) => RandomSources.Source.Next(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RandomBytes(Span buffer) => RandomSources.Source.NextBytes(buffer); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double RandomDouble() => RandomSources.Source.NextDouble(); + + /// + /// Random pink, blue, green, orange, red or yellow hue + /// + public static int RandomNondyedHue() + { + return Random(6) switch + { + 0 => RandomPinkHue(), + 1 => RandomBlueHue(), + 2 => RandomGreenHue(), + 3 => RandomOrangeHue(), + 4 => RandomRedHue(), + 5 => RandomYellowHue(), + _ => 0 + }; + } + + /// + /// Random hue in the range 1201-1254 + /// + public static int RandomPinkHue() => Random(1201, 54); + + /// + /// Random hue in the range 1301-1354 + /// + public static int RandomBlueHue() => Random(1301, 54); + + /// + /// Random hue in the range 1401-1454 + /// + public static int RandomGreenHue() => Random(1401, 54); + + /// + /// Random hue in the range 1501-1554 + /// + public static int RandomOrangeHue() => Random(1501, 54); + + /// + /// Random hue in the range 1601-1654 + /// + public static int RandomRedHue() => Random(1601, 54); + + /// + /// Random hue in the range 1701-1754 + /// + public static int RandomYellowHue() => Random(1701, 54); + + /// + /// Random hue in the range 1801-1908 + /// + public static int RandomNeutralHue() => Random(1801, 108); + + /// + /// Random hue in the range 2001-2018 + /// + public static int RandomSnakeHue() => Random(2001, 18); + + /// + /// Random hue in the range 2101-2130 + /// + public static int RandomBirdHue() => Random(2101, 30); + + /// + /// Random hue in the range 2201-2224 + /// + public static int RandomSlimeHue() => Random(2201, 24); + + /// + /// Random hue in the range 2301-2318 + /// + public static int RandomAnimalHue() => Random(2301, 18); + + /// + /// Random hue in the range 2401-2430 + /// + public static int RandomMetalHue() => Random(2401, 30); + + public static int ClipDyedHue(int hue) => hue < 2 ? 2 : + hue > 1001 ? 1001 : hue; + + /// + /// Random hue in the range 2-1001 + /// + public static int RandomDyedHue() => Random(2, 1000); + + /// + /// Random hue from 0x62, 0x71, 0x03, 0x0D, 0x13, 0x1C, 0x21, 0x30, 0x37, 0x3A, 0x44, 0x59 + /// + public static int RandomBrightHue() => + RandomDouble() < 0.1 + ? RandomList(0x62, 0x71) + : RandomList(0x03, 0x0D, 0x13, 0x1C, 0x21, 0x30, 0x37, 0x3A, 0x44, 0x59); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Clamp(this T val, T min, T max) where T : IComparable => + val.CompareTo(min) < 0 ? min : + val.CompareTo(max) > 0 ? max : val; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TimeSpan Max(this TimeSpan val, TimeSpan max) => val > max ? max : val; + } +} diff --git a/Projects/Server/VirtueInfo.cs b/Projects/Server/VirtueInfo.cs index f96bf3111..7b23eefeb 100644 --- a/Projects/Server/VirtueInfo.cs +++ b/Projects/Server/VirtueInfo.cs @@ -1,158 +1,158 @@ -/*************************************************************************** - * VirtueInfo.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -namespace Server -{ - [PropertyObject] - public class VirtueInfo - { - public VirtueInfo() - { - } - - public VirtueInfo(IGenericReader reader) - { - int version = reader.ReadByte(); - - switch (version) - { - case 1: // Changed the values throughout the virtue system - case 0: - { - int mask = reader.ReadByte(); - - if (mask != 0) - { - Values = new int[8]; - - for (var i = 0; i < 8; ++i) - if ((mask & (1 << i)) != 0) - Values[i] = reader.ReadInt(); - } - - break; - } - } - - if (version == 0) - { - Compassion *= 200; - Sacrifice *= 250; // Even though 40 (the max) only gives 10k, It's because it was formerly too easy - - // No direct conversion factor for Justice, this is just an approximation - Justice *= 500; - - // All the other virtues haven't been defined at 'version 0' point in time in the scripts. - } - } - - public int[] Values { get; private set; } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Humility - { - get => GetValue(0); - set => SetValue(0, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Sacrifice - { - get => GetValue(1); - set => SetValue(1, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Compassion - { - get => GetValue(2); - set => SetValue(2, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Spirituality - { - get => GetValue(3); - set => SetValue(3, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Valor - { - get => GetValue(4); - set => SetValue(4, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Honor - { - get => GetValue(5); - set => SetValue(5, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Justice - { - get => GetValue(6); - set => SetValue(6, value); - } - - [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] - public int Honesty - { - get => GetValue(7); - set => SetValue(7, value); - } - - public int GetValue(int index) => Values?[index] ?? 0; - - public void SetValue(int index, int value) - { - Values ??= new int[8]; - Values[index] = value; - } - - public override string ToString() => "..."; - - public static void Serialize(IGenericWriter writer, VirtueInfo info) - { - writer.Write((byte)1); // version - - if (info.Values == null) - { - writer.Write((byte)0); - } - else - { - var mask = 0; - - for (var i = 0; i < 8; ++i) - if (info.Values[i] != 0) - mask |= 1 << i; - - writer.Write((byte)mask); - - for (var i = 0; i < 8; ++i) - if (info.Values[i] != 0) - writer.Write(info.Values[i]); - } - } - } -} +/*************************************************************************** + * VirtueInfo.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +namespace Server +{ + [PropertyObject] + public class VirtueInfo + { + public VirtueInfo() + { + } + + public VirtueInfo(IGenericReader reader) + { + int version = reader.ReadByte(); + + switch (version) + { + case 1: // Changed the values throughout the virtue system + case 0: + { + int mask = reader.ReadByte(); + + if (mask != 0) + { + Values = new int[8]; + + for (var i = 0; i < 8; ++i) + if ((mask & (1 << i)) != 0) + Values[i] = reader.ReadInt(); + } + + break; + } + } + + if (version == 0) + { + Compassion *= 200; + Sacrifice *= 250; // Even though 40 (the max) only gives 10k, It's because it was formerly too easy + + // No direct conversion factor for Justice, this is just an approximation + Justice *= 500; + + // All the other virtues haven't been defined at 'version 0' point in time in the scripts. + } + } + + public int[] Values { get; private set; } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Humility + { + get => GetValue(0); + set => SetValue(0, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Sacrifice + { + get => GetValue(1); + set => SetValue(1, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Compassion + { + get => GetValue(2); + set => SetValue(2, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Spirituality + { + get => GetValue(3); + set => SetValue(3, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Valor + { + get => GetValue(4); + set => SetValue(4, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Honor + { + get => GetValue(5); + set => SetValue(5, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Justice + { + get => GetValue(6); + set => SetValue(6, value); + } + + [CommandProperty(AccessLevel.Counselor, AccessLevel.GameMaster)] + public int Honesty + { + get => GetValue(7); + set => SetValue(7, value); + } + + public int GetValue(int index) => Values?[index] ?? 0; + + public void SetValue(int index, int value) + { + Values ??= new int[8]; + Values[index] = value; + } + + public override string ToString() => "..."; + + public static void Serialize(IGenericWriter writer, VirtueInfo info) + { + writer.Write((byte)1); // version + + if (info.Values == null) + { + writer.Write((byte)0); + } + else + { + var mask = 0; + + for (var i = 0; i < 8; ++i) + if (info.Values[i] != 0) + mask |= 1 << i; + + writer.Write((byte)mask); + + for (var i = 0; i < 8; ++i) + if (info.Values[i] != 0) + writer.Write(info.Values[i]); + } + } + } +} diff --git a/Projects/Server/World.cs b/Projects/Server/World.cs index 4237e1f9c..b75bcd540 100644 --- a/Projects/Server/World.cs +++ b/Projects/Server/World.cs @@ -1,792 +1,801 @@ -/*************************************************************************** - * World.cs - * ------------------- - * begin : May 1, 2002 - * copyright : (C) The RunUO Software Team - * email : info@runuo.com - * - * $Id$ - * - ***************************************************************************/ - -/*************************************************************************** - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - ***************************************************************************/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Threading; -using Server.Guilds; -using Server.Network; - -namespace Server -{ - public static class World - { - private static readonly ManualResetEvent m_DiskWriteHandle = new ManualResetEvent(true); - - private static Queue _addQueue, _deleteQueue; - - public static readonly string MobileIndexPath = Path.Combine("Saves/Mobiles/", "Mobiles.idx"); - public static readonly string MobileTypesPath = Path.Combine("Saves/Mobiles/", "Mobiles.tdb"); - public static readonly string MobileDataPath = Path.Combine("Saves/Mobiles/", "Mobiles.bin"); - - public static readonly string ItemIndexPath = Path.Combine("Saves/Items/", "Items.idx"); - public static readonly string ItemTypesPath = Path.Combine("Saves/Items/", "Items.tdb"); - public static readonly string ItemDataPath = Path.Combine("Saves/Items/", "Items.bin"); - - public static readonly string GuildIndexPath = Path.Combine("Saves/Guilds/", "Guilds.idx"); - public static readonly string GuildDataPath = Path.Combine("Saves/Guilds/", "Guilds.bin"); - - private static readonly Type[] m_SerialTypeArray = { typeof(Serial) }; - - internal static int m_Saves; - - internal static List m_ItemTypes = new List(); - internal static List m_MobileTypes = new List(); - - public static bool Saving { get; private set; } - - public static bool Loaded { get; private set; } - - public static bool Loading { get; private set; } - - public static Dictionary Mobiles { get; private set; } - - public static Dictionary Items { get; private set; } - - public static string LoadingType { get; private set; } - - public static void NotifyDiskWriteComplete() - { - if (m_DiskWriteHandle.Set()) Console.WriteLine("Closing Save Files. "); - } - - public static void WaitForWriteCompletion() - { - m_DiskWriteHandle.WaitOne(); - } - - public static bool OnDelete(IEntity entity) - { - if (Saving || Loading) - { - if (Saving) AppendSafetyLog("delete", entity); - - _deleteQueue.Enqueue(entity); - - return false; - } - - return true; - } - - public static void Broadcast(int hue, bool ascii, string text) - { - Packet p; - - if (ascii) - p = new AsciiMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "System", text); - else - p = new UnicodeMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text); - - var list = TcpServer.Instances; - - p.Acquire(); - - for (var i = 0; i < list.Count; ++i) - if (list[i].Mobile != null) - list[i].Send(p); - - p.Release(); - } - - public static void Broadcast(int hue, bool ascii, string format, params object[] args) - { - Broadcast(hue, ascii, string.Format(format, args)); - } - - private static List> ReadTypes(BinaryReader tdbReader) - { - var count = tdbReader.ReadInt32(); - - var types = new List>(count); - - for (var i = 0; i < count; ++i) - { - var typeName = tdbReader.ReadString(); - - var t = AssemblyHandler.FindFirstTypeForName(typeName); - - if (t == null) - { - Console.WriteLine("failed"); - - Console.WriteLine("Error: Type '{0}' was not found. Delete all of those types? (y/n)", typeName); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - types.Add(null); - Console.Write("World: Loading..."); - continue; - } - - Console.WriteLine("Types will not be deleted. An exception will be thrown."); - - throw new Exception($"Bad type '{typeName}'"); - } - - var ctor = t.GetConstructor(m_SerialTypeArray); - - if (ctor != null) - types.Add(new Tuple(ctor, typeName)); - else - throw new Exception($"Type '{t}' does not have a serialization constructor"); - } - - return types; - } - - public static void Load() - { - if (Loaded) - return; - - Loaded = true; - LoadingType = null; - - Console.Write("World: Loading..."); - - var watch = Stopwatch.StartNew(); - - Loading = true; - - _addQueue = new Queue(); - _deleteQueue = new Queue(); - - int mobileCount, itemCount, guildCount; - - var ctorArgs = new object[1]; - - var items = new List(); - var mobiles = new List(); - var guilds = new List(); - - if (File.Exists(MobileIndexPath) && File.Exists(MobileTypesPath)) - { - using var idx = new FileStream(MobileIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var idxReader = new BinaryReader(idx); - using var tdb = new FileStream(MobileTypesPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var tdbReader = new BinaryReader(tdb); - var types = ReadTypes(tdbReader); - - mobileCount = idxReader.ReadInt32(); - - Mobiles = new Dictionary(mobileCount); - - for (var i = 0; i < mobileCount; ++i) - { - var typeID = idxReader.ReadInt32(); - var serial = idxReader.ReadUInt32(); - var pos = idxReader.ReadInt64(); - var length = idxReader.ReadInt32(); - - var objs = types[typeID]; - - if (objs == null) - continue; - - Mobile m = null; - var ctor = objs.Item1; - var typeName = objs.Item2; - - try - { - ctorArgs[0] = (Serial)serial; - m = (Mobile)ctor.Invoke(ctorArgs); - } - catch - { - // ignored - } - - if (m != null) - { - mobiles.Add(new MobileEntry(m, typeID, typeName, pos, length)); - AddMobile(m); - } - } - - tdbReader.Close(); - idxReader.Close(); - } - else - { - Mobiles = new Dictionary(); - } - - if (File.Exists(ItemIndexPath) && File.Exists(ItemTypesPath)) - { - using var idx = new FileStream(ItemIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var idxReader = new BinaryReader(idx); - - var tdb = new FileStream(ItemTypesPath, FileMode.Open, FileAccess.Read, FileShare.Read); - using var tdbReader = new BinaryReader(tdb); - - var types = ReadTypes(tdbReader); - - itemCount = idxReader.ReadInt32(); - - Items = new Dictionary(itemCount); - - for (var i = 0; i < itemCount; ++i) - { - var typeID = idxReader.ReadInt32(); - var serial = idxReader.ReadUInt32(); - var pos = idxReader.ReadInt64(); - var length = idxReader.ReadInt32(); - - var objs = types[typeID]; - - if (objs == null) - continue; - - Item item = null; - var ctor = objs.Item1; - var typeName = objs.Item2; - - try - { - ctorArgs[0] = (Serial)serial; - item = (Item)ctor.Invoke(ctorArgs); - } - catch - { - // ignored - } - - if (item != null) - { - items.Add(new ItemEntry(item, typeID, typeName, pos, length)); - AddItem(item); - } - } - - tdbReader.Close(); - idxReader.Close(); - } - else - { - Items = new Dictionary(); - } - - if (File.Exists(GuildIndexPath)) - { - using var idx = new FileStream(GuildIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var idxReader = new BinaryReader(idx); - - guildCount = idxReader.ReadInt32(); - - var createEventArgs = new CreateGuildEventArgs(0xFFFFFFFF); - for (var i = 0; i < guildCount; ++i) - { - idxReader.ReadInt32(); // no typeid for guilds - var id = idxReader.ReadUInt32(); - var pos = idxReader.ReadInt64(); - var length = idxReader.ReadInt32(); - - createEventArgs.Id = id; - EventSink.InvokeCreateGuild(createEventArgs); - var guild = createEventArgs.Guild; - if (guild != null) - guilds.Add(new GuildEntry(guild, pos, length)); - } - - idxReader.Close(); - } - - bool failedMobiles = false, failedItems = false, failedGuilds = false; - Type failedType = null; - var failedSerial = Serial.Zero; - Exception failed = null; - var failedTypeID = 0; - - if (File.Exists(MobileDataPath)) - { - using var bin = new FileStream(MobileDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var reader = new BinaryFileReader(new BinaryReader(bin)); - - for (var i = 0; i < mobiles.Count; ++i) - { - var entry = mobiles[i]; - var m = entry.Mobile; - - if (m != null) - { - reader.Seek(entry.Position, SeekOrigin.Begin); - - try - { - LoadingType = entry.TypeName; - m.Deserialize(reader); - - if (reader.Position != entry.Position + entry.Length) - throw new Exception($"***** Bad serialize on {m.GetType()} *****"); - } - catch (Exception e) - { - mobiles.RemoveAt(i); - - failed = e; - failedMobiles = true; - failedType = m.GetType(); - failedTypeID = entry.TypeID; - failedSerial = m.Serial; - - break; - } - } - } - - reader.Close(); - } - - if (!failedMobiles && File.Exists(ItemDataPath)) - { - using var bin = new FileStream(ItemDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var reader = new BinaryFileReader(new BinaryReader(bin)); - - for (var i = 0; i < items.Count; ++i) - { - var entry = items[i]; - var item = entry.Item; - - if (item != null) - { - reader.Seek(entry.Position, SeekOrigin.Begin); - - try - { - LoadingType = entry.TypeName; - item.Deserialize(reader); - - if (reader.Position != entry.Position + entry.Length) - throw new Exception($"***** Bad serialize on {item.GetType()} *****"); - } - catch (Exception e) - { - items.RemoveAt(i); - - failed = e; - failedItems = true; - failedType = item.GetType(); - failedTypeID = entry.TypeID; - failedSerial = item.Serial; - - break; - } - } - } - - reader.Close(); - } - - LoadingType = null; - - if (!failedMobiles && !failedItems && File.Exists(GuildDataPath)) - { - using var bin = new FileStream(GuildDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); - var reader = new BinaryFileReader(new BinaryReader(bin)); - - for (var i = 0; i < guilds.Count; ++i) - { - var entry = guilds[i]; - var g = entry.Guild; - - if (g != null) - { - reader.Seek(entry.Position, SeekOrigin.Begin); - - try - { - g.Deserialize(reader); - - if (reader.Position != entry.Position + entry.Length) - throw new Exception($"***** Bad serialize on Guild {g.Serial} *****"); - } - catch (Exception e) - { - guilds.RemoveAt(i); - - failed = e; - failedGuilds = true; - failedType = typeof(BaseGuild); - failedTypeID = g.Serial.ToInt32(); - failedSerial = g.Serial; - - break; - } - } - } - - reader.Close(); - } - - if (failedItems || failedMobiles || failedGuilds) - { - Console.WriteLine("An error was encountered while loading a saved object"); - - Console.WriteLine(" - Type: {0}", failedType); - Console.WriteLine(" - Serial: {0}", failedSerial); - - Console.WriteLine("Delete the object? (y/n)"); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - if (failedType != typeof(BaseGuild)) - { - Console.WriteLine("Delete all objects of that type? (y/n)"); - - if (Console.ReadKey(true).Key == ConsoleKey.Y) - { - if (failedMobiles) - for (var i = 0; i < mobiles.Count;) - if (mobiles[i].TypeID == failedTypeID) - mobiles.RemoveAt(i); - else - ++i; - else if (failedItems) - for (var i = 0; i < items.Count;) - if (items[i].TypeID == failedTypeID) - items.RemoveAt(i); - else - ++i; - } - } - - SaveIndex(mobiles, MobileIndexPath); - SaveIndex(items, ItemIndexPath); - SaveIndex(guilds, GuildIndexPath); - } - - Console.WriteLine("After pressing return an exception will be thrown and the server will terminate."); - Console.ReadLine(); - - throw new Exception( - $"Load failed (items={failedItems}, mobiles={failedMobiles}, guilds={failedGuilds}, type={failedType}, serial={failedSerial})", - failed); - } - - EventSink.InvokeWorldLoad(); - - Loading = false; - - ProcessSafetyQueues(); - - foreach (var item in Items.Values) - { - if (item.Parent == null) - item.UpdateTotals(); - - item.ClearProperties(); - } - - foreach (var m in Mobiles.Values) - { - m.UpdateRegion(); // Is this really needed? - m.UpdateTotals(); - - m.ClearProperties(); - } - - watch.Stop(); - - Console.WriteLine("done ({1} items, {2} mobiles) ({0:F2} seconds)", watch.Elapsed.TotalSeconds, Items.Count, - Mobiles.Count); - } - - private static void ProcessSafetyQueues() - { - while (_addQueue.Count > 0) - { - var entity = _addQueue.Dequeue(); - - if (entity is Item item) - AddItem(item); - else if (entity is Mobile mob) - AddMobile(mob); - } - - while (_deleteQueue.Count > 0) - { - var entity = _deleteQueue.Dequeue(); - - if (entity is Item item) - item.Delete(); - else if (entity is Mobile mob) - mob.Delete(); - } - } - - private static void AppendSafetyLog(string action, IEntity entity) - { - var message = - $"Warning: Attempted to {action} {entity} during world save.{Environment.NewLine}This action could cause inconsistent state.{Environment.NewLine}It is strongly advised that the offending scripts be corrected."; - - Console.WriteLine(message); - - try - { - using var op = new StreamWriter("world-save-errors.log", true); - op.WriteLine("{0}\t{1}", DateTime.UtcNow, message); - op.WriteLine(new StackTrace(2).ToString()); - op.WriteLine(); - } - catch - { - // ignored - } - } - - private static void SaveIndex(List list, string path) where T : IEntityEntry - { - if (!Directory.Exists("Saves/Mobiles/")) - Directory.CreateDirectory("Saves/Mobiles/"); - - if (!Directory.Exists("Saves/Items/")) - Directory.CreateDirectory("Saves/Items/"); - - if (!Directory.Exists("Saves/Guilds/")) - Directory.CreateDirectory("Saves/Guilds/"); - - using var idx = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); - var idxWriter = new BinaryWriter(idx); - - idxWriter.Write(list.Count); - - for (var i = 0; i < list.Count; ++i) - { - var e = list[i]; - - idxWriter.Write(e.TypeID); - idxWriter.Write(e.Serial); - idxWriter.Write(e.Position); - idxWriter.Write(e.Length); - } - - idxWriter.Close(); - } - - public static void Save() - { - Save(true, false); - } - - public static void Save(bool message, bool permitBackgroundWrite) - { - if (Saving) - return; - - ++m_Saves; - - NetState.Pause(); - - WaitForWriteCompletion(); // Blocks Save until current disk flush is done. - - Saving = true; - - m_DiskWriteHandle.Reset(); - - if (message) - Broadcast(0x35, true, "The world is saving, please wait."); - - var strategy = SaveStrategy.Acquire(); - Console.WriteLine("Core: Using {0} save strategy", strategy.Name.ToLower()); - - Console.Write($"[{DateTime.UtcNow.ToLongTimeString()}] World: Saving..."); - - var watch = Stopwatch.StartNew(); - - if (!Directory.Exists("Saves/Mobiles/")) - Directory.CreateDirectory("Saves/Mobiles/"); - if (!Directory.Exists("Saves/Items/")) - Directory.CreateDirectory("Saves/Items/"); - if (!Directory.Exists("Saves/Guilds/")) - Directory.CreateDirectory("Saves/Guilds/"); - - strategy.Save(permitBackgroundWrite); - - try - { - EventSink.InvokeWorldSave(message); - } - catch (Exception e) - { - throw new Exception("World Save event threw an exception. Save failed!", e); - } - - watch.Stop(); - - Saving = false; - - if (!permitBackgroundWrite) - NotifyDiskWriteComplete(); // Sets the DiskWriteHandle. If we allow background writes, we leave this upto the individual save strategies. - - ProcessSafetyQueues(); - - strategy.ProcessDecay(); - - Console.WriteLine("Save done in {0:F2} seconds.", watch.Elapsed.TotalSeconds); - - if (message) - Broadcast(0x35, true, "World save complete. The entire process took {0:F1} seconds.", - watch.Elapsed.TotalSeconds); - - NetState.Resume(); - } - - public static IEntity FindEntity(Serial serial) - { - if (serial.IsItem) - return FindItem(serial); - if (serial.IsMobile) - return FindMobile(serial); - - return null; - } - - public static Mobile FindMobile(Serial serial) - { - Mobiles.TryGetValue(serial, out var mob); - - return mob; - } - - public static void AddMobile(Mobile m) - { - if (Saving) - { - AppendSafetyLog("add", m); - _addQueue.Enqueue(m); - } - else - { - Mobiles.Add(m.Serial, m); - } - } - - public static Item FindItem(Serial serial) - { - Items.TryGetValue(serial, out var item); - - return item; - } - - public static void AddItem(Item item) - { - if (Saving) - { - AppendSafetyLog("add", item); - _addQueue.Enqueue(item); - } - else - { - Items.Add(item.Serial, item); - } - } - - public static void RemoveMobile(Mobile m) - { - Mobiles.Remove(m.Serial); - } - - public static void RemoveItem(Item item) - { - Items.Remove(item.Serial); - } - - private interface IEntityEntry - { - Serial Serial { get; } - int TypeID { get; } - long Position { get; } - int Length { get; } - } - - private sealed class GuildEntry : IEntityEntry - { - public GuildEntry(BaseGuild g, long pos, int length) - { - Guild = g; - Position = pos; - Length = length; - } - - public BaseGuild Guild { get; } - - public Serial Serial => Guild?.Serial ?? 0; - - public int TypeID => 0; - - public long Position { get; } - - public int Length { get; } - } - - private sealed class ItemEntry : IEntityEntry - { - public ItemEntry(Item item, int typeID, string typeName, long pos, int length) - { - Item = item; - TypeID = typeID; - TypeName = typeName; - Position = pos; - Length = length; - } - - public Item Item { get; } - - public string TypeName { get; } - - public Serial Serial => Item?.Serial ?? Serial.MinusOne; - - public int TypeID { get; } - - public long Position { get; } - - public int Length { get; } - } - - private sealed class MobileEntry : IEntityEntry - { - public MobileEntry(Mobile mobile, int typeID, string typeName, long pos, int length) - { - Mobile = mobile; - TypeID = typeID; - TypeName = typeName; - Position = pos; - Length = length; - } - - public Mobile Mobile { get; } - - public string TypeName { get; } - - public Serial Serial => Mobile?.Serial ?? Serial.MinusOne; - - public int TypeID { get; } - - public long Position { get; } - - public int Length { get; } - } - } -} +/*************************************************************************** + * World.cs + * ------------------- + * begin : May 1, 2002 + * copyright : (C) The RunUO Software Team + * email : info@runuo.com + * + * $Id$ + * + ***************************************************************************/ + +/*************************************************************************** + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + ***************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading; +using Server.Guilds; +using Server.Network; + +namespace Server +{ + public static class World + { + private static readonly ManualResetEvent m_DiskWriteHandle = new ManualResetEvent(true); + + private static Queue _addQueue, _deleteQueue; + + public static readonly string MobileIndexPath = Path.Combine("Saves/Mobiles/", "Mobiles.idx"); + public static readonly string MobileTypesPath = Path.Combine("Saves/Mobiles/", "Mobiles.tdb"); + public static readonly string MobileDataPath = Path.Combine("Saves/Mobiles/", "Mobiles.bin"); + + public static readonly string ItemIndexPath = Path.Combine("Saves/Items/", "Items.idx"); + public static readonly string ItemTypesPath = Path.Combine("Saves/Items/", "Items.tdb"); + public static readonly string ItemDataPath = Path.Combine("Saves/Items/", "Items.bin"); + + public static readonly string GuildIndexPath = Path.Combine("Saves/Guilds/", "Guilds.idx"); + public static readonly string GuildDataPath = Path.Combine("Saves/Guilds/", "Guilds.bin"); + + private static readonly Type[] m_SerialTypeArray = { typeof(Serial) }; + + internal static int m_Saves; + + internal static List m_ItemTypes = new List(); + internal static List m_MobileTypes = new List(); + + public static bool Saving { get; private set; } + + public static bool Loaded { get; private set; } + + public static bool Loading { get; private set; } + + public static Dictionary Mobiles { get; private set; } + + public static Dictionary Items { get; private set; } + + public static string LoadingType { get; private set; } + + public static void NotifyDiskWriteComplete() + { + if (m_DiskWriteHandle.Set()) Console.WriteLine("Closing Save Files. "); + } + + public static void WaitForWriteCompletion() + { + m_DiskWriteHandle.WaitOne(); + } + + public static bool OnDelete(IEntity entity) + { + if (Saving || Loading) + { + if (Saving) AppendSafetyLog("delete", entity); + + _deleteQueue.Enqueue(entity); + + return false; + } + + return true; + } + + public static void Broadcast(int hue, bool ascii, string text) + { + Packet p; + + if (ascii) + p = new AsciiMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "System", text); + else + p = new UnicodeMessage(Serial.MinusOne, -1, MessageType.Regular, hue, 3, "ENU", "System", text); + + var list = TcpServer.Instances; + + p.Acquire(); + + for (var i = 0; i < list.Count; ++i) + if (list[i].Mobile != null) + list[i].Send(p); + + p.Release(); + } + + public static void Broadcast(int hue, bool ascii, string format, params object[] args) + { + Broadcast(hue, ascii, string.Format(format, args)); + } + + private static List> ReadTypes(BinaryReader tdbReader) + { + var count = tdbReader.ReadInt32(); + + var types = new List>(count); + + for (var i = 0; i < count; ++i) + { + var typeName = tdbReader.ReadString(); + + var t = AssemblyHandler.FindFirstTypeForName(typeName); + + if (t == null) + { + Console.WriteLine("failed"); + + Console.WriteLine("Error: Type '{0}' was not found. Delete all of those types? (y/n)", typeName); + + if (Console.ReadKey(true).Key == ConsoleKey.Y) + { + types.Add(null); + Console.Write("World: Loading..."); + continue; + } + + Console.WriteLine("Types will not be deleted. An exception will be thrown."); + + throw new Exception($"Bad type '{typeName}'"); + } + + var ctor = t.GetConstructor(m_SerialTypeArray); + + if (ctor != null) + types.Add(new Tuple(ctor, typeName)); + else + throw new Exception($"Type '{t}' does not have a serialization constructor"); + } + + return types; + } + + public static void Load() + { + if (Loaded) + return; + + Loaded = true; + LoadingType = null; + + Console.Write("World: Loading..."); + + var watch = Stopwatch.StartNew(); + + Loading = true; + + _addQueue = new Queue(); + _deleteQueue = new Queue(); + + int mobileCount, itemCount, guildCount; + + var ctorArgs = new object[1]; + + var items = new List(); + var mobiles = new List(); + var guilds = new List(); + + if (File.Exists(MobileIndexPath) && File.Exists(MobileTypesPath)) + { + using var idx = new FileStream(MobileIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var idxReader = new BinaryReader(idx); + using var tdb = new FileStream(MobileTypesPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var tdbReader = new BinaryReader(tdb); + var types = ReadTypes(tdbReader); + + mobileCount = idxReader.ReadInt32(); + + Mobiles = new Dictionary(mobileCount); + + for (var i = 0; i < mobileCount; ++i) + { + var typeID = idxReader.ReadInt32(); + var serial = idxReader.ReadUInt32(); + var pos = idxReader.ReadInt64(); + var length = idxReader.ReadInt32(); + + var objs = types[typeID]; + + if (objs == null) + continue; + + Mobile m = null; + var ctor = objs.Item1; + var typeName = objs.Item2; + + try + { + ctorArgs[0] = (Serial)serial; + m = (Mobile)ctor.Invoke(ctorArgs); + } + catch + { + // ignored + } + + if (m != null) + { + mobiles.Add(new MobileEntry(m, typeID, typeName, pos, length)); + AddMobile(m); + } + } + + tdbReader.Close(); + idxReader.Close(); + } + else + { + Mobiles = new Dictionary(); + } + + if (File.Exists(ItemIndexPath) && File.Exists(ItemTypesPath)) + { + using var idx = new FileStream(ItemIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var idxReader = new BinaryReader(idx); + + var tdb = new FileStream(ItemTypesPath, FileMode.Open, FileAccess.Read, FileShare.Read); + using var tdbReader = new BinaryReader(tdb); + + var types = ReadTypes(tdbReader); + + itemCount = idxReader.ReadInt32(); + + Items = new Dictionary(itemCount); + + for (var i = 0; i < itemCount; ++i) + { + var typeID = idxReader.ReadInt32(); + var serial = idxReader.ReadUInt32(); + var pos = idxReader.ReadInt64(); + var length = idxReader.ReadInt32(); + + var objs = types[typeID]; + + if (objs == null) + continue; + + Item item = null; + var ctor = objs.Item1; + var typeName = objs.Item2; + + try + { + ctorArgs[0] = (Serial)serial; + item = (Item)ctor.Invoke(ctorArgs); + } + catch + { + // ignored + } + + if (item != null) + { + items.Add(new ItemEntry(item, typeID, typeName, pos, length)); + AddItem(item); + } + } + + tdbReader.Close(); + idxReader.Close(); + } + else + { + Items = new Dictionary(); + } + + if (File.Exists(GuildIndexPath)) + { + using var idx = new FileStream(GuildIndexPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var idxReader = new BinaryReader(idx); + + guildCount = idxReader.ReadInt32(); + + var createEventArgs = new CreateGuildEventArgs(0xFFFFFFFF); + for (var i = 0; i < guildCount; ++i) + { + idxReader.ReadInt32(); // no typeid for guilds + var id = idxReader.ReadUInt32(); + var pos = idxReader.ReadInt64(); + var length = idxReader.ReadInt32(); + + createEventArgs.Id = id; + EventSink.InvokeCreateGuild(createEventArgs); + var guild = createEventArgs.Guild; + if (guild != null) + guilds.Add(new GuildEntry(guild, pos, length)); + } + + idxReader.Close(); + } + + bool failedMobiles = false, failedItems = false, failedGuilds = false; + Type failedType = null; + var failedSerial = Serial.Zero; + Exception failed = null; + var failedTypeID = 0; + + if (File.Exists(MobileDataPath)) + { + using var bin = new FileStream(MobileDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var reader = new BinaryFileReader(new BinaryReader(bin)); + + for (var i = 0; i < mobiles.Count; ++i) + { + var entry = mobiles[i]; + var m = entry.Mobile; + + if (m != null) + { + reader.Seek(entry.Position, SeekOrigin.Begin); + + try + { + LoadingType = entry.TypeName; + m.Deserialize(reader); + + if (reader.Position != entry.Position + entry.Length) + throw new Exception($"***** Bad serialize on {m.GetType()} *****"); + } + catch (Exception e) + { + mobiles.RemoveAt(i); + + failed = e; + failedMobiles = true; + failedType = m.GetType(); + failedTypeID = entry.TypeID; + failedSerial = m.Serial; + + break; + } + } + } + + reader.Close(); + } + + if (!failedMobiles && File.Exists(ItemDataPath)) + { + using var bin = new FileStream(ItemDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var reader = new BinaryFileReader(new BinaryReader(bin)); + + for (var i = 0; i < items.Count; ++i) + { + var entry = items[i]; + var item = entry.Item; + + if (item != null) + { + reader.Seek(entry.Position, SeekOrigin.Begin); + + try + { + LoadingType = entry.TypeName; + item.Deserialize(reader); + + if (reader.Position != entry.Position + entry.Length) + throw new Exception($"***** Bad serialize on {item.GetType()} *****"); + } + catch (Exception e) + { + items.RemoveAt(i); + + failed = e; + failedItems = true; + failedType = item.GetType(); + failedTypeID = entry.TypeID; + failedSerial = item.Serial; + + break; + } + } + } + + reader.Close(); + } + + LoadingType = null; + + if (!failedMobiles && !failedItems && File.Exists(GuildDataPath)) + { + using var bin = new FileStream(GuildDataPath, FileMode.Open, FileAccess.Read, FileShare.Read); + var reader = new BinaryFileReader(new BinaryReader(bin)); + + for (var i = 0; i < guilds.Count; ++i) + { + var entry = guilds[i]; + var g = entry.Guild; + + if (g != null) + { + reader.Seek(entry.Position, SeekOrigin.Begin); + + try + { + g.Deserialize(reader); + + if (reader.Position != entry.Position + entry.Length) + throw new Exception($"***** Bad serialize on Guild {g.Serial} *****"); + } + catch (Exception e) + { + guilds.RemoveAt(i); + + failed = e; + failedGuilds = true; + failedType = typeof(BaseGuild); + failedTypeID = g.Serial.ToInt32(); + failedSerial = g.Serial; + + break; + } + } + } + + reader.Close(); + } + + if (failedItems || failedMobiles || failedGuilds) + { + Console.WriteLine("An error was encountered while loading a saved object"); + + Console.WriteLine(" - Type: {0}", failedType); + Console.WriteLine(" - Serial: {0}", failedSerial); + + Console.WriteLine("Delete the object? (y/n)"); + + if (Console.ReadKey(true).Key == ConsoleKey.Y) + { + if (failedType != typeof(BaseGuild)) + { + Console.WriteLine("Delete all objects of that type? (y/n)"); + + if (Console.ReadKey(true).Key == ConsoleKey.Y) + { + if (failedMobiles) + for (var i = 0; i < mobiles.Count;) + if (mobiles[i].TypeID == failedTypeID) + mobiles.RemoveAt(i); + else + ++i; + else if (failedItems) + for (var i = 0; i < items.Count;) + if (items[i].TypeID == failedTypeID) + items.RemoveAt(i); + else + ++i; + } + } + + SaveIndex(mobiles, MobileIndexPath); + SaveIndex(items, ItemIndexPath); + SaveIndex(guilds, GuildIndexPath); + } + + Console.WriteLine("After pressing return an exception will be thrown and the server will terminate."); + Console.ReadLine(); + + throw new Exception( + $"Load failed (items={failedItems}, mobiles={failedMobiles}, guilds={failedGuilds}, type={failedType}, serial={failedSerial})", + failed + ); + } + + EventSink.InvokeWorldLoad(); + + Loading = false; + + ProcessSafetyQueues(); + + foreach (var item in Items.Values) + { + if (item.Parent == null) + item.UpdateTotals(); + + item.ClearProperties(); + } + + foreach (var m in Mobiles.Values) + { + m.UpdateRegion(); // Is this really needed? + m.UpdateTotals(); + + m.ClearProperties(); + } + + watch.Stop(); + + Console.WriteLine( + "done ({1} items, {2} mobiles) ({0:F2} seconds)", + watch.Elapsed.TotalSeconds, + Items.Count, + Mobiles.Count + ); + } + + private static void ProcessSafetyQueues() + { + while (_addQueue.Count > 0) + { + var entity = _addQueue.Dequeue(); + + if (entity is Item item) + AddItem(item); + else if (entity is Mobile mob) + AddMobile(mob); + } + + while (_deleteQueue.Count > 0) + { + var entity = _deleteQueue.Dequeue(); + + if (entity is Item item) + item.Delete(); + else if (entity is Mobile mob) + mob.Delete(); + } + } + + private static void AppendSafetyLog(string action, IEntity entity) + { + var message = + $"Warning: Attempted to {action} {entity} during world save.{Environment.NewLine}This action could cause inconsistent state.{Environment.NewLine}It is strongly advised that the offending scripts be corrected."; + + Console.WriteLine(message); + + try + { + using var op = new StreamWriter("world-save-errors.log", true); + op.WriteLine("{0}\t{1}", DateTime.UtcNow, message); + op.WriteLine(new StackTrace(2).ToString()); + op.WriteLine(); + } + catch + { + // ignored + } + } + + private static void SaveIndex(List list, string path) where T : IEntityEntry + { + if (!Directory.Exists("Saves/Mobiles/")) + Directory.CreateDirectory("Saves/Mobiles/"); + + if (!Directory.Exists("Saves/Items/")) + Directory.CreateDirectory("Saves/Items/"); + + if (!Directory.Exists("Saves/Guilds/")) + Directory.CreateDirectory("Saves/Guilds/"); + + using var idx = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + var idxWriter = new BinaryWriter(idx); + + idxWriter.Write(list.Count); + + for (var i = 0; i < list.Count; ++i) + { + var e = list[i]; + + idxWriter.Write(e.TypeID); + idxWriter.Write(e.Serial); + idxWriter.Write(e.Position); + idxWriter.Write(e.Length); + } + + idxWriter.Close(); + } + + public static void Save() + { + Save(true, false); + } + + public static void Save(bool message, bool permitBackgroundWrite) + { + if (Saving) + return; + + ++m_Saves; + + NetState.Pause(); + + WaitForWriteCompletion(); // Blocks Save until current disk flush is done. + + Saving = true; + + m_DiskWriteHandle.Reset(); + + if (message) + Broadcast(0x35, true, "The world is saving, please wait."); + + var strategy = SaveStrategy.Acquire(); + Console.WriteLine("Core: Using {0} save strategy", strategy.Name.ToLower()); + + Console.Write($"[{DateTime.UtcNow.ToLongTimeString()}] World: Saving..."); + + var watch = Stopwatch.StartNew(); + + if (!Directory.Exists("Saves/Mobiles/")) + Directory.CreateDirectory("Saves/Mobiles/"); + if (!Directory.Exists("Saves/Items/")) + Directory.CreateDirectory("Saves/Items/"); + if (!Directory.Exists("Saves/Guilds/")) + Directory.CreateDirectory("Saves/Guilds/"); + + strategy.Save(permitBackgroundWrite); + + try + { + EventSink.InvokeWorldSave(message); + } + catch (Exception e) + { + throw new Exception("World Save event threw an exception. Save failed!", e); + } + + watch.Stop(); + + Saving = false; + + if (!permitBackgroundWrite) + NotifyDiskWriteComplete(); // Sets the DiskWriteHandle. If we allow background writes, we leave this upto the individual save strategies. + + ProcessSafetyQueues(); + + strategy.ProcessDecay(); + + Console.WriteLine("Save done in {0:F2} seconds.", watch.Elapsed.TotalSeconds); + + if (message) + Broadcast( + 0x35, + true, + "World save complete. The entire process took {0:F1} seconds.", + watch.Elapsed.TotalSeconds + ); + + NetState.Resume(); + } + + public static IEntity FindEntity(Serial serial) + { + if (serial.IsItem) + return FindItem(serial); + if (serial.IsMobile) + return FindMobile(serial); + + return null; + } + + public static Mobile FindMobile(Serial serial) + { + Mobiles.TryGetValue(serial, out var mob); + + return mob; + } + + public static void AddMobile(Mobile m) + { + if (Saving) + { + AppendSafetyLog("add", m); + _addQueue.Enqueue(m); + } + else + { + Mobiles.Add(m.Serial, m); + } + } + + public static Item FindItem(Serial serial) + { + Items.TryGetValue(serial, out var item); + + return item; + } + + public static void AddItem(Item item) + { + if (Saving) + { + AppendSafetyLog("add", item); + _addQueue.Enqueue(item); + } + else + { + Items.Add(item.Serial, item); + } + } + + public static void RemoveMobile(Mobile m) + { + Mobiles.Remove(m.Serial); + } + + public static void RemoveItem(Item item) + { + Items.Remove(item.Serial); + } + + private interface IEntityEntry + { + Serial Serial { get; } + int TypeID { get; } + long Position { get; } + int Length { get; } + } + + private sealed class GuildEntry : IEntityEntry + { + public GuildEntry(BaseGuild g, long pos, int length) + { + Guild = g; + Position = pos; + Length = length; + } + + public BaseGuild Guild { get; } + + public Serial Serial => Guild?.Serial ?? 0; + + public int TypeID => 0; + + public long Position { get; } + + public int Length { get; } + } + + private sealed class ItemEntry : IEntityEntry + { + public ItemEntry(Item item, int typeID, string typeName, long pos, int length) + { + Item = item; + TypeID = typeID; + TypeName = typeName; + Position = pos; + Length = length; + } + + public Item Item { get; } + + public string TypeName { get; } + + public Serial Serial => Item?.Serial ?? Serial.MinusOne; + + public int TypeID { get; } + + public long Position { get; } + + public int Length { get; } + } + + private sealed class MobileEntry : IEntityEntry + { + public MobileEntry(Mobile mobile, int typeID, string typeName, long pos, int length) + { + Mobile = mobile; + TypeID = typeID; + TypeName = typeName; + Position = pos; + Length = length; + } + + public Mobile Mobile { get; } + + public string TypeName { get; } + + public Serial Serial => Mobile?.Serial ?? Serial.MinusOne; + + public int TypeID { get; } + + public long Position { get; } + + public int Length { get; } + } + } +}