From 3b413371dc16de943f5feddc720217ce8256c57c Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 2 Jan 2021 19:08:35 -0800 Subject: [PATCH] fix(core): Adds Gump component functionality (#377) - [X] Adds gump close packet - [X] Fixes gump tooltip - [X] Adds new compile/append functions for the new gump packets --- .../Collections/BenchmarkOrderedHashSet.cs | 123 ++++ .../FeatureFlags/BenchmarkFeatureFlags.cs | 0 .../FeatureFlags/FeatureFlag.cs | 0 .../Packets/BenchmarkPacketBroadcast.cs | 0 .../Packets/PacketTestUtilities.cs | 0 .../Utilities}/BenchmarkStringHelpers.cs | 0 Projects/Benchmarks/Program.cs | 3 +- .../Tests/Collections/OrderedHashSetTests.cs | 24 + Projects/Server/Collections/ArraySet.cs | 74 -- Projects/Server/Collections/HashHelpers.cs | 102 +++ .../OrderedHashSet.ValueCollection.cs | 144 ++++ Projects/Server/Collections/OrderedHashSet.cs | 661 ++++++++++++++++++ Projects/Server/Gumps/GumpAlphaRegion.cs | 18 + Projects/Server/Gumps/GumpBackground.cs | 20 + Projects/Server/Gumps/GumpButton.cs | 25 + Projects/Server/Gumps/GumpCheck.cs | 25 + Projects/Server/Gumps/GumpECHandleInput.cs | 8 + Projects/Server/Gumps/GumpEntry.cs | 7 + Projects/Server/Gumps/GumpGroup.cs | 11 + Projects/Server/Gumps/GumpHtml.cs | 25 + Projects/Server/Gumps/GumpHtmlLocalized.cs | 95 ++- Projects/Server/Gumps/GumpImage.cs | 47 +- Projects/Server/Gumps/GumpImageTileButton.cs | 80 ++- Projects/Server/Gumps/GumpImageTiled.cs | 19 + Projects/Server/Gumps/GumpItem.cs | 23 + Projects/Server/Gumps/GumpItemProperty.cs | 11 + Projects/Server/Gumps/GumpLabel.cs | 17 + Projects/Server/Gumps/GumpLabelCropped.cs | 23 + Projects/Server/Gumps/GumpMasterGump.cs | 11 + Projects/Server/Gumps/GumpPage.cs | 11 + Projects/Server/Gumps/GumpRadio.cs | 25 + Projects/Server/Gumps/GumpSpriteImage.cs | 24 + Projects/Server/Gumps/GumpTextEntry.cs | 27 + Projects/Server/Gumps/GumpTextEntryLimited.cs | 29 + Projects/Server/Gumps/GumpTooltip.cs | 30 +- Projects/Server/Mobiles/Mobile.cs | 5 +- .../Server/Network/Packets/GumpPackets.cs | 13 + .../Network/Packets/OutgoingGumpPackets.cs | 40 ++ Projects/Server/Serialization/BufferWriter.cs | 2 - .../UOContent/Engines/Virtues/VirtueGump.cs | 31 +- 40 files changed, 1696 insertions(+), 137 deletions(-) create mode 100644 Projects/Benchmarks/Benchmarks/Collections/BenchmarkOrderedHashSet.cs rename Projects/Benchmarks/{ => Benchmarks}/FeatureFlags/BenchmarkFeatureFlags.cs (100%) rename Projects/Benchmarks/{ => Benchmarks}/FeatureFlags/FeatureFlag.cs (100%) rename Projects/Benchmarks/{ => Benchmarks}/Packets/BenchmarkPacketBroadcast.cs (100%) rename Projects/Benchmarks/{ => Benchmarks}/Packets/PacketTestUtilities.cs (100%) rename Projects/Benchmarks/{BenchmarkUtilities => Benchmarks/Utilities}/BenchmarkStringHelpers.cs (100%) create mode 100644 Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs delete mode 100644 Projects/Server/Collections/ArraySet.cs create mode 100644 Projects/Server/Collections/HashHelpers.cs create mode 100644 Projects/Server/Collections/OrderedHashSet.ValueCollection.cs create mode 100644 Projects/Server/Collections/OrderedHashSet.cs create mode 100644 Projects/Server/Network/Packets/OutgoingGumpPackets.cs diff --git a/Projects/Benchmarks/Benchmarks/Collections/BenchmarkOrderedHashSet.cs b/Projects/Benchmarks/Benchmarks/Collections/BenchmarkOrderedHashSet.cs new file mode 100644 index 000000000..97a9adf66 --- /dev/null +++ b/Projects/Benchmarks/Benchmarks/Collections/BenchmarkOrderedHashSet.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using Server.Collections; + +namespace Benchmarks +{ + [MemoryDiagnoser] + [SimpleJob(RuntimeMoniker.NetCoreApp50)] + public class BenchmarkOrderedHashSet + { + private List _list; + private OrderedHashSet _ordered; + private HashSet _hashSet; + private const int iterations = 16; + + [IterationSetup] + public void IterationSetup() + { + _list = new List(); + _ordered = new OrderedHashSet(); + _hashSet = new HashSet(); + } + + [IterationCleanup] + public void IterationCleanup() + { + _list = null; + _ordered = null; + _hashSet = null; + } + + [Benchmark] + public int UsingList() + { + for (int i = 0; i < iterations / 2; i++) + { + AddIfNotPresent(_list, i.ToString()); + } + + for (int i = 0; i < iterations; i++) + { + AddIfNotPresent(_list, i.ToString()); + } + + for (int i = 0; i < iterations * 2; i++) + { + AddIfNotPresent(_list, i.ToString()); + } + + for (int i = 0; i < _list.Count; i++) + { + _list[i].ToString(); + } + + return _list.Count; + } + + private static int AddIfNotPresent(List list, T item) + { + var index = list.IndexOf(item); + if (index > -1) + { + return index; + } + + list.Add(item); + return list.Count - 1; + } + + [Benchmark] + public int UsingOrderedHashSet() + { + for (int i = 0; i < iterations / 2; i++) + { + _ordered.GetOrAdd(i.ToString()); + } + + for (int i = 0; i < iterations; i++) + { + _ordered.GetOrAdd(i.ToString()); + } + + for (int i = 0; i < iterations * 2; i++) + { + _ordered.GetOrAdd(i.ToString()); + } + + foreach (var str in _ordered) + { + str.ToString(); + } + + return _ordered.Count; + } + + [Benchmark] + public int UsingHashSet() + { + for (int i = 0; i < iterations / 2; i++) + { + _hashSet.Add(i.ToString()); + } + + for (int i = 0; i < iterations; i++) + { + _hashSet.Add(i.ToString()); + } + + for (int i = 0; i < iterations * 2; i++) + { + _hashSet.Add(i.ToString()); + } + + foreach (var str in _hashSet) + { + str.ToString(); + } + + return _hashSet.Count; + } + } +} diff --git a/Projects/Benchmarks/FeatureFlags/BenchmarkFeatureFlags.cs b/Projects/Benchmarks/Benchmarks/FeatureFlags/BenchmarkFeatureFlags.cs similarity index 100% rename from Projects/Benchmarks/FeatureFlags/BenchmarkFeatureFlags.cs rename to Projects/Benchmarks/Benchmarks/FeatureFlags/BenchmarkFeatureFlags.cs diff --git a/Projects/Benchmarks/FeatureFlags/FeatureFlag.cs b/Projects/Benchmarks/Benchmarks/FeatureFlags/FeatureFlag.cs similarity index 100% rename from Projects/Benchmarks/FeatureFlags/FeatureFlag.cs rename to Projects/Benchmarks/Benchmarks/FeatureFlags/FeatureFlag.cs diff --git a/Projects/Benchmarks/Packets/BenchmarkPacketBroadcast.cs b/Projects/Benchmarks/Benchmarks/Packets/BenchmarkPacketBroadcast.cs similarity index 100% rename from Projects/Benchmarks/Packets/BenchmarkPacketBroadcast.cs rename to Projects/Benchmarks/Benchmarks/Packets/BenchmarkPacketBroadcast.cs diff --git a/Projects/Benchmarks/Packets/PacketTestUtilities.cs b/Projects/Benchmarks/Benchmarks/Packets/PacketTestUtilities.cs similarity index 100% rename from Projects/Benchmarks/Packets/PacketTestUtilities.cs rename to Projects/Benchmarks/Benchmarks/Packets/PacketTestUtilities.cs diff --git a/Projects/Benchmarks/BenchmarkUtilities/BenchmarkStringHelpers.cs b/Projects/Benchmarks/Benchmarks/Utilities/BenchmarkStringHelpers.cs similarity index 100% rename from Projects/Benchmarks/BenchmarkUtilities/BenchmarkStringHelpers.cs rename to Projects/Benchmarks/Benchmarks/Utilities/BenchmarkStringHelpers.cs diff --git a/Projects/Benchmarks/Program.cs b/Projects/Benchmarks/Program.cs index 636bb412b..b9fba96aa 100644 --- a/Projects/Benchmarks/Program.cs +++ b/Projects/Benchmarks/Program.cs @@ -10,7 +10,8 @@ namespace Benchmarks // var featureFlags = BenchmarkRunner.Run(); // var packetConstruction = BenchmarkRunner.Run(); // var broadcast = BenchmarkRunner.Run(); - var stringHelpers = BenchmarkRunner.Run(); + // var stringHelpers = BenchmarkRunner.Run(); + var indexList = BenchmarkRunner.Run(); } } } diff --git a/Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs b/Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs new file mode 100644 index 000000000..133746876 --- /dev/null +++ b/Projects/Server.Tests/Tests/Collections/OrderedHashSetTests.cs @@ -0,0 +1,24 @@ +using System.Linq; +using Server.Collections; +using Xunit; + +namespace Server.Tests +{ + public class OrderedHashSetTests + { + [Fact] + public void TestOrderedHashSet() + { + var set = new OrderedHashSet(1) { "random string1", "another random string1", "another random string2", "another random string3" }; + set.Remove("another random string1"); + var list = set.ToList(); + + string[] arr = { "random string1", "another random string2", "another random string3" }; + int i = 0; + foreach (var entry in list) + { + Assert.Equal(entry, arr[i++]); + } + } + } +} diff --git a/Projects/Server/Collections/ArraySet.cs b/Projects/Server/Collections/ArraySet.cs deleted file mode 100644 index d0b03f9b0..000000000 --- a/Projects/Server/Collections/ArraySet.cs +++ /dev/null @@ -1,74 +0,0 @@ -/************************************************************************* - * ModernUO * - * Copyright 2019-2020 - ModernUO Development Team * - * Email: hi@modernuo.com * - * File: ArraySet.cs * - * * - * This program is free software: you can 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. * - * * - * 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(); - - 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/Collections/HashHelpers.cs b/Projects/Server/Collections/HashHelpers.cs new file mode 100644 index 000000000..98046027c --- /dev/null +++ b/Projects/Server/Collections/HashHelpers.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; + +namespace Microsoft.Collections.Extensions +{ + internal static class HashHelpers + { + + // must never be written to + internal static readonly int[] SizeOneIntArray = new int[1]; + + // This is the maximum prime smaller than Array.MaxArrayLength + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + public static readonly int[] primes = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; + + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if (candidate % divisor == 0) + { + return false; + } + } + return true; + } + return candidate == 2; + } + + public static int GetPrime(int min) + { + if (min < 0) + { + throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); + } + + for (int i = 0; i < primes.Length; i++) + { + int prime = primes[i]; + if (prime >= min) + { + return prime; + } + } + + //outside of our predefined table. + //compute the hard way. + for (int i = min | 1; i < int.MaxValue; i += 2) + { + if (IsPrime(i) && (i - 1) % HashPrime != 0) + { + return i; + } + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + } +} diff --git a/Projects/Server/Collections/OrderedHashSet.ValueCollection.cs b/Projects/Server/Collections/OrderedHashSet.ValueCollection.cs new file mode 100644 index 000000000..df48f0bfd --- /dev/null +++ b/Projects/Server/Collections/OrderedHashSet.ValueCollection.cs @@ -0,0 +1,144 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: OrderedHashSet.ValueCollection.cs * + * * + * This program is free software: you can 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. * + * * + * 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; +using System.Diagnostics; + +namespace Server.Collections +{ + public partial class OrderedHashSet + { + [DebuggerDisplay("Count = {Count}")] + public sealed class ValueCollection : IList, IReadOnlyList + { + private readonly OrderedHashSet _orderedHashSet; + + private const string NotSupported_ValueCollectionSet = + "Mutating a key collection derived from a hash set is not allowed."; + + public int Count => _orderedHashSet.Count; + + public TValue this[int index] => ((IList)_orderedHashSet)[index]; + + TValue IList.this[int index] + { + get => this[index]; + set => throw new NotSupportedException(NotSupported_ValueCollectionSet); + } + + bool ICollection.IsReadOnly => true; + + internal ValueCollection(OrderedHashSet OrderedHashSet) => + _orderedHashSet = OrderedHashSet; + + public Enumerator GetEnumerator() => new Enumerator(_orderedHashSet); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + int IList.IndexOf(TValue item) => _orderedHashSet.IndexOf(item); + + void IList.Insert(int index, TValue item) => throw new NotSupportedException(NotSupported_ValueCollectionSet); + + void IList.RemoveAt(int index) => throw new NotSupportedException(NotSupported_ValueCollectionSet); + + void ICollection.Add(TValue item) => throw new NotSupportedException(NotSupported_ValueCollectionSet); + + void ICollection.Clear() => throw new NotSupportedException(NotSupported_ValueCollectionSet); + + bool ICollection.Contains(TValue item) => _orderedHashSet.Contains(item); + + void ICollection.CopyTo(TValue[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + if ((uint)arrayIndex > (uint)array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), ArgumentOutOfRange_NeedNonNegNum); + } + int count = Count; + if (array.Length - arrayIndex < count) + { + throw new ArgumentException(Arg_ArrayPlusOffTooSmall); + } + + Entry[] entries = _orderedHashSet._entries; + for (int i = 0; i < count; ++i) + { + array[i + arrayIndex] = entries[i].Value; + } + } + + bool ICollection.Remove(TValue item) => throw new NotSupportedException(NotSupported_ValueCollectionSet); + + public struct Enumerator : IEnumerator + { + private readonly OrderedHashSet _orderedHashSet; + private readonly int _version; + private int _index; + private TValue _current; + + public TValue Current => _current; + + object IEnumerator.Current => _current; + + internal Enumerator(OrderedHashSet OrderedHashSet) + { + _orderedHashSet = OrderedHashSet; + _version = OrderedHashSet._version; + _index = 0; + _current = default; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_version != _orderedHashSet._version) + { + throw new InvalidOperationException(InvalidOperation_EnumFailedVersion); + } + + if (_index < _orderedHashSet.Count) + { + _current = _orderedHashSet._entries[_index].Value; + ++_index; + return true; + } + _current = default; + return false; + } + + void IEnumerator.Reset() + { + if (_version != _orderedHashSet._version) + { + throw new InvalidOperationException(InvalidOperation_EnumFailedVersion); + } + + _index = 0; + _current = default; + } + } + } + } +} diff --git a/Projects/Server/Collections/OrderedHashSet.cs b/Projects/Server/Collections/OrderedHashSet.cs new file mode 100644 index 000000000..29bc6efbf --- /dev/null +++ b/Projects/Server/Collections/OrderedHashSet.cs @@ -0,0 +1,661 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: OrderedHashSet.cs * + * * + * This program is free software: you can 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. * + * * + * 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.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.Collections.Extensions; + +namespace Server.Collections +{ + [DebuggerDisplay("Count = {Count}")] + public partial class OrderedHashSet : IList, IReadOnlyList, ISet, IReadOnlySet + { + private struct Entry + { + public uint HashCode; + public TValue Value; + public int Next; // the index of the next item in the same bucket, -1 if last + } + + private const string ArgumentOutOfRange_Index = + "Index was out of range. Must be non-negative and less than the size of the collection."; + + private const string ArgumentOutOfRange_NeedNonNegNum = + "Non-negative number required."; + + private const string Argument_InvalidOffLen = + "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."; + + private const string Argument_AddingDuplicate = "An item with the same value has already been added. Value: {0}"; + + private const string Arg_ArrayPlusOffTooSmall = + "Destination array is not long enough to copy all the items in the collection. Check array index and length."; + + private const string InvalidOperation_ConcurrentOperationsNotSupported = + "Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct."; + + private const string InvalidOperation_EnumFailedVersion = + "Collection was modified; enumeration operation may not execute."; + + private static readonly Entry[] InitialEntries = new Entry[1]; + private int[] _buckets = HashHelpers.SizeOneIntArray; + private Entry[] _entries = InitialEntries; + private int _count; + private int _version; + private readonly IEqualityComparer _comparer; + private ValueCollection _values; + + public int Count => _count; + public IEqualityComparer Comparer => _comparer ?? EqualityComparer.Default; + public ValueCollection Values => _values ??= new ValueCollection(this); + + public OrderedHashSet() + : this(0) + { + } + + public OrderedHashSet(IEqualityComparer comparer) + : this(0, comparer) + { + } + + public OrderedHashSet(int capacity, IEqualityComparer comparer = null) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + if (capacity > 0) + { + int newSize = HashHelpers.GetPrime(capacity); + _buckets = new int[newSize]; + _entries = new Entry[newSize]; + } + + if (comparer != EqualityComparer.Default) + { + _comparer = comparer; + } + } + + public OrderedHashSet(IEnumerable collection, IEqualityComparer comparer = null) + : this((collection as ICollection)?.Count ?? 0, comparer) + { + if (collection == null) + { + throw new ArgumentNullException(nameof(collection)); + } + + foreach (TValue value in collection) + { + Add(value); + } + } + + public void ExceptWith(IEnumerable other) + { + throw new NotImplementedException(); + } + + public void IntersectWith(IEnumerable other) + { + throw new NotImplementedException(); + } + + public bool Contains(TValue item) => TryGetValue(item, out var value) && EqualityComparer.Default.Equals(value); + + // TODO: Implement IReadOnlySet and ISet + + bool IReadOnlySet.IsProperSubsetOf(IEnumerable other) => throw new NotImplementedException(); + + bool IReadOnlySet.IsProperSupersetOf(IEnumerable other) => throw new NotImplementedException(); + + bool IReadOnlySet.IsSubsetOf(IEnumerable other) => throw new NotImplementedException(); + + bool IReadOnlySet.IsSupersetOf(IEnumerable other) => throw new NotImplementedException(); + + bool IReadOnlySet.Overlaps(IEnumerable other) => throw new NotImplementedException(); + + bool IReadOnlySet.SetEquals(IEnumerable other) => throw new NotImplementedException(); + + bool ISet.IsProperSubsetOf(IEnumerable other) => throw new NotImplementedException(); + + bool ISet.IsProperSupersetOf(IEnumerable other) => throw new NotImplementedException(); + + bool ISet.IsSubsetOf(IEnumerable other) => throw new NotImplementedException(); + + bool ISet.IsSupersetOf(IEnumerable other) => throw new NotImplementedException(); + + bool ISet.Overlaps(IEnumerable other) => throw new NotImplementedException(); + + bool ISet.SetEquals(IEnumerable other) => throw new NotImplementedException(); + + public void SymmetricExceptWith(IEnumerable other) => throw new NotImplementedException(); + + public void UnionWith(IEnumerable other) => throw new NotImplementedException(); + + public void Clear() + { + if (_count > 0) + { + Array.Clear(_buckets, 0, _buckets.Length); + Array.Clear(_entries, 0, _count); + _count = 0; + ++_version; + } + } + + public int EnsureCapacity(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + if (_entries.Length >= capacity) + { + return _entries.Length; + } + int newSize = HashHelpers.GetPrime(capacity); + Resize(newSize); + ++_version; + return newSize; + } + + public Enumerator GetEnumerator() => new(this); + + void ICollection.Add(TValue item) => TryAdd(item); + + public bool Add(TValue item) => TryAdd(item); + + public int GetOrAdd(TValue value) => TryInsert(null, value); + + public int IndexOf(TValue value) => IndexOf(value, out _); + + public void Insert(int index, TValue value) + { + if ((uint)index > (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(index), ArgumentOutOfRange_Index); + } + + TryInsert(index, value); + } + + public void Move(int fromIndex, int toIndex) + { + if ((uint)fromIndex >= (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(fromIndex), ArgumentOutOfRange_Index); + } + if ((uint)toIndex >= (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(toIndex), ArgumentOutOfRange_Index); + } + + if (fromIndex == toIndex) + { + return; + } + + Entry[] entries = _entries; + Entry temp = entries[fromIndex]; + RemoveEntryFromBucket(fromIndex); + int direction = fromIndex < toIndex ? 1 : -1; + for (int i = fromIndex; i != toIndex; i += direction) + { + entries[i] = entries[i + direction]; + UpdateBucketIndex(i + direction, -direction); + } + AddEntryToBucket(ref temp, toIndex, _buckets); + entries[toIndex] = temp; + ++_version; + } + + public void MoveRange(int fromIndex, int toIndex, int count) + { + if (count == 1) + { + Move(fromIndex, toIndex); + return; + } + + if ((uint)fromIndex >= (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(fromIndex), ArgumentOutOfRange_Index); + } + if ((uint)toIndex >= (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(toIndex), ArgumentOutOfRange_Index); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), ArgumentOutOfRange_NeedNonNegNum); + } + if (fromIndex + count > Count) + { + throw new ArgumentException(Argument_InvalidOffLen); + } + if (toIndex + count > Count) + { + throw new ArgumentException(Argument_InvalidOffLen); + } + + if (fromIndex == toIndex || count == 0) + { + return; + } + + Entry[] entries = _entries; + Entry[] entriesToMove = ArrayPool.Shared.Rent(count); + for (int i = 0; i < count; ++i) + { + entriesToMove[i] = entries[fromIndex + i]; + RemoveEntryFromBucket(fromIndex + i); + } + + // Move entries in between + int direction = 1; + int amount = count; + int start = fromIndex; + int end = toIndex; + if (fromIndex > toIndex) + { + direction = -1; + amount = -count; + start = fromIndex + count - 1; + end = toIndex + count - 1; + } + for (int i = start; i != end; i += direction) + { + entries[i] = entries[i + amount]; + UpdateBucketIndex(i + amount, -amount); + } + + int[] buckets = _buckets; + // Copy entries to destination + for (int i = 0; i < count; ++i) + { + Entry temp = entriesToMove[i]; + AddEntryToBucket(ref temp, toIndex + i, buckets); + entries[toIndex + i] = temp; + } + ++_version; + ArrayPool.Shared.Return(entriesToMove); + } + + public bool Remove(TValue value) + { + int index = IndexOf(value); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } + + public void RemoveAt(int index) + { + int count = Count; + if ((uint)index >= (uint)count) + { + throw new ArgumentOutOfRangeException(nameof(index), ArgumentOutOfRange_Index); + } + + // Remove the entry from the bucket + RemoveEntryFromBucket(index); + + // Decrement the indices > index + Entry[] entries = _entries; + for (int i = index + 1; i < count; ++i) + { + entries[i - 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: -1); + } + --_count; + entries[_count] = default; + ++_version; + } + + public void TrimExcess() => TrimExcess(Count); + + public void TrimExcess(int capacity) + { + if (capacity < Count) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + int newSize = HashHelpers.GetPrime(capacity); + if (newSize < _entries.Length) + { + Resize(newSize); + ++_version; + } + } + + public bool TryAdd(TValue value) => TryInsert(null, value) != _count - 1; + + public bool TryGetValue(TValue value, out TValue actualValue) + { + int index = IndexOf(value); + if (index >= 0) + { + actualValue = _entries[index].Value; + return true; + } + + actualValue = default; + return false; + } + + public TValue this[int index] + { + get + { + if ((uint)index >= (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(index), ArgumentOutOfRange_Index); + } + + return _entries[index].Value; + } + set + { + if ((uint)index >= (uint)Count) + { + throw new ArgumentOutOfRangeException(nameof(index), ArgumentOutOfRange_Index); + } + + TValue v = value; + int foundIndex = IndexOf(v, out uint hashCode); + if (foundIndex < 0) + { + RemoveEntryFromBucket(index); + Entry entry = new Entry { HashCode = hashCode, Value = value }; + AddEntryToBucket(ref entry, index, _buckets); + _entries[index] = entry; + ++_version; + } + else if (foundIndex == index) + { + ref Entry entry = ref _entries[index]; + entry.Value = value; + } + else + { + throw new ArgumentException(string.Format(Argument_AddingDuplicate, v.ToString())); + } + } + } + + public bool IsReadOnly => false; + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void CopyTo(TValue[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if ((uint)arrayIndex > (uint)array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), ArgumentOutOfRange_NeedNonNegNum); + } + + int count = Count; + if (array.Length - arrayIndex < count) + { + throw new ArgumentException(Arg_ArrayPlusOffTooSmall); + } + + Entry[] entries = _entries; + for (int i = 0; i < count; ++i) + { + Entry entry = entries[i]; + array[i + arrayIndex] = entry.Value; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Entry[] Resize(int newSize) + { + int[] newBuckets = new int[newSize]; + Entry[] newEntries = new Entry[newSize]; + + int count = Count; + Array.Copy(_entries, newEntries, count); + for (int i = 0; i < count; ++i) + { + AddEntryToBucket(ref newEntries[i], i, newBuckets); + } + + _buckets = newBuckets; + _entries = newEntries; + return newEntries; + } + + private int IndexOf(TValue value, out uint hashCode) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + IEqualityComparer comparer = _comparer; + hashCode = (uint)(comparer?.GetHashCode(value) ?? value.GetHashCode()); + int index = _buckets[(int)(hashCode % (uint)_buckets.Length)] - 1; + if (index >= 0) + { + comparer ??= EqualityComparer.Default; + Entry[] entries = _entries; + int collisionCount = 0; + do + { + Entry entry = entries[index]; + if (entry.HashCode == hashCode && comparer.Equals(entry.Value, value)) + { + break; + } + index = entry.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; + } while (index >= 0); + } + return index; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int TryInsert(int? index, TValue value) + { + int i = IndexOf(value, out uint hashCode); + return i >= 0 ? i : AddInternal(index, value, hashCode); + } + + private int AddInternal(int? index, TValue value, uint hashCode) + { + Entry[] entries = _entries; + // Check if resize is needed + int count = Count; + if (entries.Length == count || entries.Length == 1) + { + entries = Resize(HashHelpers.ExpandPrime(entries.Length)); + } + + // Increment indices >= index; + int actualIndex = index ?? count; + for (int i = count - 1; i >= actualIndex; --i) + { + entries[i + 1] = entries[i]; + UpdateBucketIndex(i, incrementAmount: 1); + } + + ref Entry entry = ref entries[actualIndex]; + entry.HashCode = hashCode; + entry.Value = value; + AddEntryToBucket(ref entry, actualIndex, _buckets); + ++_count; + ++_version; + return actualIndex; + } + + // Returns the index of the next entry in the bucket + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AddEntryToBucket(ref Entry entry, int entryIndex, int[] buckets) + { + ref int b = ref buckets[(int)(entry.HashCode % (uint)buckets.Length)]; + entry.Next = b - 1; + b = entryIndex + 1; + } + + private void RemoveEntryFromBucket(int entryIndex) + { + Entry[] entries = _entries; + Entry entry = entries[entryIndex]; + ref int b = ref _buckets[(int)(entry.HashCode % (uint)_buckets.Length)]; + // Bucket was pointing to removed entry. Update it to point to the next in the chain + if (b == entryIndex + 1) + { + b = entry.Next + 1; + } + else + { + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to remove, then fix the chain + int i = b - 1; + int collisionCount = 0; + while (true) + { + ref Entry e = ref entries[i]; + if (e.Next == entryIndex) + { + e.Next = entry.Next; + return; + } + i = e.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; + } + } + } + + private void UpdateBucketIndex(int entryIndex, int incrementAmount) + { + Entry[] entries = _entries; + Entry entry = entries[entryIndex]; + ref int b = ref _buckets[(int)(entry.HashCode % (uint)_buckets.Length)]; + // Bucket was pointing to entry. Increment the index by incrementAmount. + if (b == entryIndex + 1) + { + b += incrementAmount; + } + else + { + // Start at the entry the bucket points to, and walk the chain until we find the entry with the index we want to increment. + int i = b - 1; + int collisionCount = 0; + while (true) + { + ref Entry e = ref entries[i]; + if (e.Next == entryIndex) + { + e.Next += incrementAmount; + return; + } + i = e.Next; + if (collisionCount >= entries.Length) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(InvalidOperation_ConcurrentOperationsNotSupported); + } + ++collisionCount; + } + } + } + + public struct Enumerator : IEnumerator + { + private readonly OrderedHashSet _OrderedHashSet; + private readonly int _version; + private int _index; + private TValue _current; + + public TValue Current => _current; + + object IEnumerator.Current => _current; + + internal Enumerator(OrderedHashSet OrderedHashSet) + { + _OrderedHashSet = OrderedHashSet; + _version = OrderedHashSet._version; + _index = 0; + _current = default; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_version != _OrderedHashSet._version) + { + throw new InvalidOperationException(InvalidOperation_EnumFailedVersion); + } + + if (_index < _OrderedHashSet.Count) + { + Entry entry = _OrderedHashSet._entries[_index]; + _current = entry.Value; + ++_index; + return true; + } + _current = default; + return false; + } + + void IEnumerator.Reset() + { + if (_version != _OrderedHashSet._version) + { + throw new InvalidOperationException(InvalidOperation_EnumFailedVersion); + } + + _index = 0; + _current = default; + } + } + } +} diff --git a/Projects/Server/Gumps/GumpAlphaRegion.cs b/Projects/Server/Gumps/GumpAlphaRegion.cs index 85fe42cf0..7b917d492 100644 --- a/Projects/Server/Gumps/GumpAlphaRegion.cs +++ b/Projects/Server/Gumps/GumpAlphaRegion.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -24,6 +26,8 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ checkertrans {X} {Y} {Width} {Height} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ checkertrans {X} {Y} {Width} {Height} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -32,5 +36,19 @@ namespace Server.Gumps disp.AppendLayout(Width); disp.AppendLayout(Height); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpBackground.cs b/Projects/Server/Gumps/GumpBackground.cs index ed801c820..b793cddab 100644 --- a/Projects/Server/Gumps/GumpBackground.cs +++ b/Projects/Server/Gumps/GumpBackground.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -27,6 +29,8 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ resizepic {X} {Y} {GumpID} {Width} {Height} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ resizepic {X} {Y} {GumpID} {Width} {Height} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -36,5 +40,21 @@ namespace Server.Gumps disp.AppendLayout(Width); disp.AppendLayout(Height); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(GumpID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpButton.cs b/Projects/Server/Gumps/GumpButton.cs index 52e989b39..4068589c7 100644 --- a/Projects/Server/Gumps/GumpButton.cs +++ b/Projects/Server/Gumps/GumpButton.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -43,6 +45,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ button {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ button {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -54,5 +59,25 @@ namespace Server.Gumps disp.AppendLayout(Param); disp.AppendLayout(ButtonID); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(NormalID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(PressedID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(((int)Type).ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Param.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(ButtonID.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpCheck.cs b/Projects/Server/Gumps/GumpCheck.cs index 9c6e93c1d..0d4105c8f 100644 --- a/Projects/Server/Gumps/GumpCheck.cs +++ b/Projects/Server/Gumps/GumpCheck.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -31,6 +33,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ checkbox {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ checkbox {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -43,5 +48,25 @@ namespace Server.Gumps disp.Switches++; } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(InactiveID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(ActiveID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(InitialState ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(SwitchID.ToString()); + writer.Write((ushort)0x207D); // " }" + + switches++; + } } } diff --git a/Projects/Server/Gumps/GumpECHandleInput.cs b/Projects/Server/Gumps/GumpECHandleInput.cs index 21fa02df3..7981beb52 100644 --- a/Projects/Server/Gumps/GumpECHandleInput.cs +++ b/Projects/Server/Gumps/GumpECHandleInput.cs @@ -13,6 +13,8 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -21,10 +23,16 @@ namespace Server.Gumps { private static readonly byte[] m_LayoutName = Gump.StringToBuffer("echandleinput"); public override string Compile(NetState ns) => "{ echandleinput }"; + public override string Compile(OrderedHashSet strings) => "{ echandleinput }"; public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.WriteAscii("{ echandleinput }"); + } } } diff --git a/Projects/Server/Gumps/GumpEntry.cs b/Projects/Server/Gumps/GumpEntry.cs index 15d3d940b..e2d8701ed 100644 --- a/Projects/Server/Gumps/GumpEntry.cs +++ b/Projects/Server/Gumps/GumpEntry.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -23,6 +25,11 @@ namespace Server.Gumps } public abstract string Compile(NetState ns); + public abstract string Compile(OrderedHashSet strings); public abstract void AppendTo(NetState ns, IGumpWriter disp); + + // TODO: Replace OrderedHashSet with InsertOnlyHashSet, a copy of HashSet that is ReadOnly compatible, but includes + // a public AddIfNotPresent function that returns the index of the element + public abstract void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches); } } diff --git a/Projects/Server/Gumps/GumpGroup.cs b/Projects/Server/Gumps/GumpGroup.cs index b3e37cc06..85f0dffcc 100644 --- a/Projects/Server/Gumps/GumpGroup.cs +++ b/Projects/Server/Gumps/GumpGroup.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -11,11 +13,20 @@ namespace Server.Gumps public int Group { get; set; } public override string Compile(NetState ns) => $"{{ group {Group} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ group {Group} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); disp.AppendLayout(Group); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(Group.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpHtml.cs b/Projects/Server/Gumps/GumpHtml.cs index e2c4e3b2b..d17074c84 100644 --- a/Projects/Server/Gumps/GumpHtml.cs +++ b/Projects/Server/Gumps/GumpHtml.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -34,6 +36,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ htmlgump {X} {Y} {Width} {Height} {Parent.Intern(Text)} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ htmlgump {X} {Y} {Width} {Height} {strings.GetOrAdd(Text)} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -45,5 +50,25 @@ namespace Server.Gumps disp.AppendLayout(Background); disp.AppendLayout(Scrollbar); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(strings.GetOrAdd(Text).ToString()); + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Background ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Scrollbar ? 0x31 : 0x30)); // 1 or 0 + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpHtmlLocalized.cs b/Projects/Server/Gumps/GumpHtmlLocalized.cs index ddfd52391..cc79281ea 100644 --- a/Projects/Server/Gumps/GumpHtmlLocalized.cs +++ b/Projects/Server/Gumps/GumpHtmlLocalized.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -88,9 +90,19 @@ namespace Server.Gumps public GumpHtmlLocalizedType Type { get; set; } - public override string Compile(NetState ns) - { - return Type switch + public override string Compile(NetState ns) => + 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 string Compile(OrderedHashSet strings) => + Type switch { GumpHtmlLocalizedType.Plain => $"{{ xmfhtmlgump {X} {Y} {Width} {Height} {Number} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} }}", @@ -99,7 +111,6 @@ namespace Server.Gumps _ => $"{{ xmfhtmltok {X} {Y} {Width} {Height} {(Background ? 1 : 0)} {(Scrollbar ? 1 : 0)} {Color} {Number} @{Args}@ }}" }; - } public override void AppendTo(NetState ns, IGumpWriter disp) { @@ -154,5 +165,81 @@ namespace Server.Gumps } } } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + + switch (Type) + { + case GumpHtmlLocalizedType.Plain: + { + writer.Write(m_LayoutNamePlain); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Number.ToString()); + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Background ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Scrollbar ? 0x31 : 0x30)); // 1 or 0 + + break; + } + case GumpHtmlLocalizedType.Color: + { + writer.Write(m_LayoutNameColor); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Number.ToString()); + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Background ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Scrollbar ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Color.ToString()); + + break; + } + case GumpHtmlLocalizedType.Args: + { + writer.Write(m_LayoutNameArgs); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Background ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(Scrollbar ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Color.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Number.ToString()); + writer.Write((byte)0x20); // ' ' + writer.Write((byte)0x40); // '@' + writer.WriteAscii(Args ?? ""); + writer.Write((byte)0x40); // '@' + + break; + } + } + + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpImage.cs b/Projects/Server/Gumps/GumpImage.cs index c2696664b..63dd29253 100644 --- a/Projects/Server/Gumps/GumpImage.cs +++ b/Projects/Server/Gumps/GumpImage.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -6,13 +8,15 @@ namespace Server.Gumps { private static readonly byte[] m_LayoutName = Gump.StringToBuffer("gumppic"); private static readonly byte[] m_HueEquals = Gump.StringToBuffer(" hue="); + private static readonly byte[] m_ClassEquals = Gump.StringToBuffer(" class="); - public GumpImage(int x, int y, int gumpID, int hue = 0) + public GumpImage(int x, int y, int gumpID, int hue = 0, string cls = null) { X = x; Y = y; GumpID = gumpID; Hue = hue; + Class = cls; } public int X { get; set; } @@ -23,8 +27,13 @@ namespace Server.Gumps public int Hue { get; set; } + public string Class { get; set; } + public override string Compile(NetState ns) => - Hue == 0 ? $"{{ gumppic {X} {Y} {GumpID} }}" : $"{{ gumppic {X} {Y} {GumpID} hue={Hue} }}"; + $"{{ gumppic {X} {Y} {GumpID}{(Hue == 0 ? "" : $"hue={Hue}")}{(string.IsNullOrEmpty(Class) ? "" : $"class={Class}")} }}"; + + public override string Compile(OrderedHashSet strings) => + $"{{ gumppic {X} {Y} {GumpID}{(Hue == 0 ? "" : $"hue={Hue}")}{(string.IsNullOrEmpty(Class) ? "" : $"class={Class}")} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { @@ -38,6 +47,40 @@ namespace Server.Gumps disp.AppendLayout(m_HueEquals); disp.AppendLayoutNS(Hue); } + + if (!string.IsNullOrEmpty(Class)) + { + disp.AppendLayout(m_ClassEquals); + disp.AppendLayoutNS(Class); + } + } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(GumpID.ToString()); + writer.Write((byte)0x20); // ' ' + + if (Hue != 0) + { + writer.Write(m_HueEquals); + writer.WriteAscii(Hue.ToString()); + writer.Write((byte)0x20); // ' ' + } + + if (!string.IsNullOrWhiteSpace(Class)) + { + writer.Write(m_ClassEquals); + writer.WriteAscii(Class); + writer.Write((byte)0x20); // ' ' + } + + writer.Write((byte)0x7D); // '}' } } } diff --git a/Projects/Server/Gumps/GumpImageTileButton.cs b/Projects/Server/Gumps/GumpImageTileButton.cs index 411983c53..a2d2deb98 100644 --- a/Projects/Server/Gumps/GumpImageTileButton.cs +++ b/Projects/Server/Gumps/GumpImageTileButton.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -7,9 +9,8 @@ namespace Server.Gumps 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) + // 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, @@ -21,7 +22,7 @@ namespace Server.Gumps NormalID = normalID; PressedID = pressedID; ButtonID = buttonID; - m_Type = type; + Type = type; Param = param; ItemID = itemID; @@ -42,19 +43,7 @@ namespace Server.Gumps public int ButtonID { get; set; } - public GumpButtonType Type - { - get => m_Type; - set - { - if (m_Type != value) - { - m_Type = value; - - var parent = Parent; - } - } - } + public GumpButtonType Type { get; set; } public int Param { get; set; } @@ -68,17 +57,15 @@ namespace Server.Gumps 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} }}"; - } + public override string Compile(NetState ns) => + LocalizedTooltip > 0 ? + $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}{{ tooltip {LocalizedTooltip} }}" : + $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}"; - return - $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)m_Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}"; - } + public override string Compile(OrderedHashSet strings) => + LocalizedTooltip > 0 ? + $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}{{ tooltip {LocalizedTooltip} }}" : + $"{{ buttontileart {X} {Y} {NormalID} {PressedID} {(int)Type} {Param} {ButtonID} {ItemID} {Hue} {Width} {Height} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { @@ -87,7 +74,7 @@ namespace Server.Gumps disp.AppendLayout(Y); disp.AppendLayout(NormalID); disp.AppendLayout(PressedID); - disp.AppendLayout((int)m_Type); + disp.AppendLayout((int)Type); disp.AppendLayout(Param); disp.AppendLayout(ButtonID); @@ -102,5 +89,42 @@ namespace Server.Gumps disp.AppendLayout(LocalizedTooltip); } } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(NormalID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(PressedID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(((int)Type).ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Param.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(ButtonID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(ItemID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Hue.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + + if (LocalizedTooltip > 0) + { + writer.Write(m_LayoutTooltip); + writer.WriteAscii(LocalizedTooltip.ToString()); + writer.Write((byte)0x20); // ' ' + } + + writer.Write((byte)0x7D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpImageTiled.cs b/Projects/Server/Gumps/GumpImageTiled.cs index bc4b072b1..ea37a2b95 100644 --- a/Projects/Server/Gumps/GumpImageTiled.cs +++ b/Projects/Server/Gumps/GumpImageTiled.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -26,6 +28,7 @@ namespace Server.Gumps public int GumpID { get; set; } public override string Compile(NetState ns) => $"{{ gumppictiled {X} {Y} {Width} {Height} {GumpID} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ gumppictiled {X} {Y} {Width} {Height} {GumpID} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { @@ -36,5 +39,21 @@ namespace Server.Gumps disp.AppendLayout(Height); disp.AppendLayout(GumpID); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(GumpID.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpItem.cs b/Projects/Server/Gumps/GumpItem.cs index 996e930e3..983bd6405 100644 --- a/Projects/Server/Gumps/GumpItem.cs +++ b/Projects/Server/Gumps/GumpItem.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -26,6 +28,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => Hue == 0 ? $"{{ tilepic {X} {Y} {ItemID} }}" : $"{{ tilepichue {X} {Y} {ItemID} {Hue} }}"; + public override string Compile(OrderedHashSet strings) => + 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); @@ -38,5 +43,23 @@ namespace Server.Gumps disp.AppendLayout(Hue); } } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write(Hue == 0 ? m_LayoutName : m_LayoutNameHue); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(ItemID.ToString()); + + if (Hue != 0) + { + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Hue.ToString()); + } + + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpItemProperty.cs b/Projects/Server/Gumps/GumpItemProperty.cs index 8adefb5c3..e6eb2c8a9 100644 --- a/Projects/Server/Gumps/GumpItemProperty.cs +++ b/Projects/Server/Gumps/GumpItemProperty.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -11,11 +13,20 @@ namespace Server.Gumps public uint Serial { get; set; } public override string Compile(NetState ns) => $"{{ itemproperty {Serial} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ itemproperty {Serial} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); disp.AppendLayout(Serial); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(Serial.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpLabel.cs b/Projects/Server/Gumps/GumpLabel.cs index 702c6d84b..749560be7 100644 --- a/Projects/Server/Gumps/GumpLabel.cs +++ b/Projects/Server/Gumps/GumpLabel.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -23,6 +25,7 @@ namespace Server.Gumps public string Text { get; set; } public override string Compile(NetState ns) => $"{{ text {X} {Y} {Hue} {Parent.Intern(Text)} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ text {X} {Y} {Hue} {strings.GetOrAdd(Text)} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { @@ -32,5 +35,19 @@ namespace Server.Gumps disp.AppendLayout(Hue); disp.AppendLayout(Parent.Intern(Text)); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Hue.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(strings.GetOrAdd(Text).ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpLabelCropped.cs b/Projects/Server/Gumps/GumpLabelCropped.cs index 58ac425b7..053d83715 100644 --- a/Projects/Server/Gumps/GumpLabelCropped.cs +++ b/Projects/Server/Gumps/GumpLabelCropped.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -31,6 +33,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ croppedtext {X} {Y} {Width} {Height} {Hue} {Parent.Intern(Text)} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ croppedtext {X} {Y} {Width} {Height} {Hue} {strings.GetOrAdd(Text)} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -41,5 +46,23 @@ namespace Server.Gumps disp.AppendLayout(Hue); disp.AppendLayout(Parent.Intern(Text)); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Hue.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(strings.GetOrAdd(Text).ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpMasterGump.cs b/Projects/Server/Gumps/GumpMasterGump.cs index 9847abf21..7c422d5e8 100644 --- a/Projects/Server/Gumps/GumpMasterGump.cs +++ b/Projects/Server/Gumps/GumpMasterGump.cs @@ -13,6 +13,8 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -26,11 +28,20 @@ namespace Server.Gumps public int GumpID { get; set; } public override string Compile(NetState ns) => $"{{ mastergump {GumpID} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ mastergump {GumpID} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); disp.AppendLayout(GumpID); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(GumpID.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpPage.cs b/Projects/Server/Gumps/GumpPage.cs index e2d90b43e..c1a36f99a 100644 --- a/Projects/Server/Gumps/GumpPage.cs +++ b/Projects/Server/Gumps/GumpPage.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -11,11 +13,20 @@ namespace Server.Gumps public int Page { get; set; } public override string Compile(NetState ns) => $"{{ page {Page} }}"; + public override string Compile(OrderedHashSet strings) => $"{{ page {Page} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); disp.AppendLayout(Page); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(Page.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpRadio.cs b/Projects/Server/Gumps/GumpRadio.cs index 5c2d65a60..5aac49589 100644 --- a/Projects/Server/Gumps/GumpRadio.cs +++ b/Projects/Server/Gumps/GumpRadio.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -31,6 +33,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ radio {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ radio {X} {Y} {InactiveID} {ActiveID} {(InitialState ? 1 : 0)} {SwitchID} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -43,5 +48,25 @@ namespace Server.Gumps disp.Switches++; } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(InactiveID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(ActiveID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.Write((byte)(InitialState ? 0x31 : 0x30)); // 1 or 0 + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(SwitchID.ToString()); + writer.Write((ushort)0x207D); // " }" + + switches++; + } } } diff --git a/Projects/Server/Gumps/GumpSpriteImage.cs b/Projects/Server/Gumps/GumpSpriteImage.cs index f5a88f72f..45a6eb771 100644 --- a/Projects/Server/Gumps/GumpSpriteImage.cs +++ b/Projects/Server/Gumps/GumpSpriteImage.cs @@ -13,6 +13,8 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -47,6 +49,8 @@ namespace Server.Gumps public int SY { get; set; } public override string Compile(NetState ns) => $"{{ picinpic {X} {Y} {GumpID} {Width} {Height} {SX} {SY} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ picinpic {X} {Y} {GumpID} {Width} {Height} {SX} {SY} }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { @@ -59,5 +63,25 @@ namespace Server.Gumps disp.AppendLayout(SX); disp.AppendLayout(SY); } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(GumpID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(SX.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(SY.ToString()); + writer.Write((ushort)0x207D); // " }" + } } } diff --git a/Projects/Server/Gumps/GumpTextEntry.cs b/Projects/Server/Gumps/GumpTextEntry.cs index 3b6fa49ce..93f3b422a 100644 --- a/Projects/Server/Gumps/GumpTextEntry.cs +++ b/Projects/Server/Gumps/GumpTextEntry.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -34,6 +36,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ textentry {X} {Y} {Width} {Height} {Hue} {EntryID} {Parent.Intern(InitialText)} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ textentry {X} {Y} {Width} {Height} {Hue} {EntryID} {strings.GetOrAdd(InitialText)} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -47,5 +52,27 @@ namespace Server.Gumps disp.TextEntries++; } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Hue.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(EntryID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(strings.GetOrAdd(InitialText).ToString()); + writer.Write((ushort)0x207D); // " }" + + entries++; + } } } diff --git a/Projects/Server/Gumps/GumpTextEntryLimited.cs b/Projects/Server/Gumps/GumpTextEntryLimited.cs index f34f8bf3d..9d66ccc77 100644 --- a/Projects/Server/Gumps/GumpTextEntryLimited.cs +++ b/Projects/Server/Gumps/GumpTextEntryLimited.cs @@ -1,3 +1,5 @@ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -39,6 +41,9 @@ namespace Server.Gumps public override string Compile(NetState ns) => $"{{ textentrylimited {X} {Y} {Width} {Height} {Hue} {EntryID} {Parent.Intern(InitialText)} {Size} }}"; + public override string Compile(OrderedHashSet strings) => + $"{{ textentrylimited {X} {Y} {Width} {Height} {Hue} {EntryID} {strings.GetOrAdd(InitialText)} {Size} }}"; + public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); @@ -53,5 +58,29 @@ namespace Server.Gumps disp.TextEntries++; } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(X.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Y.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Width.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Height.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Hue.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(EntryID.ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(strings.GetOrAdd(InitialText).ToString()); + writer.Write((byte)0x20); // ' ' + writer.WriteAscii(Size.ToString()); + writer.Write((ushort)0x207D); // " }" + + entries++; + } } } diff --git a/Projects/Server/Gumps/GumpTooltip.cs b/Projects/Server/Gumps/GumpTooltip.cs index a91b73ea2..4b9278f40 100644 --- a/Projects/Server/Gumps/GumpTooltip.cs +++ b/Projects/Server/Gumps/GumpTooltip.cs @@ -13,6 +13,8 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Buffers; +using Server.Collections; using Server.Network; namespace Server.Gumps @@ -31,13 +33,37 @@ namespace Server.Gumps public string Args { get; set; } - public override string Compile(NetState ns) => $"{{ tooltip {Number} @{Args}@ }}"; + public override string Compile(NetState ns) => + string.IsNullOrEmpty(Args) ? $"{{ tooltip {Number} }}" : $"{{ tooltip {Number} @{Args}@ }}"; + public override string Compile(OrderedHashSet strings) => + string.IsNullOrEmpty(Args) ? $"{{ tooltip {Number} }}" : $"{{ tooltip {Number} @{Args}@ }}"; public override void AppendTo(NetState ns, IGumpWriter disp) { disp.AppendLayout(m_LayoutName); disp.AppendLayout(Number); - disp.AppendLayout(Args); + + if (!string.IsNullOrEmpty(Args)) + { + disp.AppendLayout(Args); + } + } + + public override void AppendTo(ref SpanWriter writer, OrderedHashSet strings, ref int entries, ref int switches) + { + writer.Write((ushort)0x7B20); // "{ " + writer.Write(m_LayoutName); + writer.WriteAscii(Number.ToString()); + + if (!string.IsNullOrEmpty(Args)) + { + writer.Write((byte)0x20); // ' ' + writer.Write((byte)0x40); // '@' + writer.WriteAscii(Args); + writer.Write((byte)0x40); // '@' + } + + writer.Write((ushort)0x207D); // " }" } } } diff --git a/Projects/Server/Mobiles/Mobile.cs b/Projects/Server/Mobiles/Mobile.cs index defa27f83..5b71a73b2 100644 --- a/Projects/Server/Mobiles/Mobile.cs +++ b/Projects/Server/Mobiles/Mobile.cs @@ -8454,8 +8454,7 @@ namespace Server if (gump != null) { - // TODO: Recycle CloseGump - m_NetState.Send(new CloseGump(gump.TypeID, 0)); + m_NetState.SendCloseGump(gump.TypeID, 0); m_NetState.RemoveGump(gump); gump.OnServerClose(m_NetState); } @@ -8478,7 +8477,7 @@ namespace Server foreach (var gump in gumps) { - ns.Send(new CloseGump(gump.TypeID, 0)); + ns.SendCloseGump(gump.TypeID, 0); gump.OnServerClose(ns); } diff --git a/Projects/Server/Network/Packets/GumpPackets.cs b/Projects/Server/Network/Packets/GumpPackets.cs index 92cb2fc8f..a4741f43f 100644 --- a/Projects/Server/Network/Packets/GumpPackets.cs +++ b/Projects/Server/Network/Packets/GumpPackets.cs @@ -44,6 +44,7 @@ namespace Server.Network void AppendLayout(uint val); void AppendLayoutNS(int val); void AppendLayout(string text); + void AppendLayoutNS(string text); void AppendLayout(byte[] buffer); void WriteStrings(List strings); void Flush(); @@ -110,6 +111,11 @@ namespace Server.Network m_Layout.Write(m_Buffer, 1, bytes); } + public void AppendLayoutNS(string text) + { + m_Layout.WriteAsciiFixed(text, text.Length); + } + public void AppendLayout(string text) { AppendLayout(m_BeginTextSeparator); @@ -248,6 +254,13 @@ namespace Server.Network m_LayoutLength += bytes; } + public void AppendLayoutNS(string text) + { + var length = text.Length; + Stream.WriteAsciiFixed(text, length); + m_LayoutLength += length; + } + public void AppendLayout(string text) { AppendLayout(m_BeginTextSeparator); diff --git a/Projects/Server/Network/Packets/OutgoingGumpPackets.cs b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs new file mode 100644 index 000000000..716aca4df --- /dev/null +++ b/Projects/Server/Network/Packets/OutgoingGumpPackets.cs @@ -0,0 +1,40 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2020 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: OutgoingGumpPackets.cs * + * * + * This program is free software: you can 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System.Buffers; + +namespace Server.Network +{ + public static class OutgoingGumpPackets + { + public static void SendCloseGump(this NetState ns, int typeId, int buttonId) + { + if (ns == null || !ns.GetSendBuffer(out var buffer)) + { + return; + } + + var writer = new CircularBufferWriter(buffer); + writer.Write((byte)0xBF); // Packet ID + writer.Write((ushort)13); + + writer.Write((short)0x04); + writer.Write(typeId); + writer.Write(buttonId); + + ns.Send(ref buffer, writer.Position); + } + } +} diff --git a/Projects/Server/Serialization/BufferWriter.cs b/Projects/Server/Serialization/BufferWriter.cs index 25d64826d..8e1ca729e 100644 --- a/Projects/Server/Serialization/BufferWriter.cs +++ b/Projects/Server/Serialization/BufferWriter.cs @@ -98,8 +98,6 @@ namespace Server var newBuffer = GC.AllocateUninitializedArray(size); _buffer.AsSpan(0, Math.Min(size, _buffer.Length)).CopyTo(newBuffer); _buffer = newBuffer; - - // Array.Resize(ref _buffer, size); } public virtual void Flush() diff --git a/Projects/UOContent/Engines/Virtues/VirtueGump.cs b/Projects/UOContent/Engines/Virtues/VirtueGump.cs index acc50f6f8..5f9bc903e 100644 --- a/Projects/UOContent/Engines/Virtues/VirtueGump.cs +++ b/Projects/UOContent/Engines/Virtues/VirtueGump.cs @@ -39,14 +39,14 @@ namespace Server AddPage(1); - Add(new InternalEntry(61, 71, 108, GetHueFor(0))); // Humility - Add(new InternalEntry(123, 46, 112, GetHueFor(4))); // Valor - Add(new InternalEntry(187, 70, 107, GetHueFor(5))); // Honor - Add(new InternalEntry(35, 135, 110, GetHueFor(1))); // Sacrifice - Add(new InternalEntry(211, 133, 105, GetHueFor(2))); // Compassion - Add(new InternalEntry(61, 195, 111, GetHueFor(3))); // Spiritulaity - Add(new InternalEntry(186, 195, 109, GetHueFor(6))); // Justice - Add(new InternalEntry(121, 221, 106, GetHueFor(7))); // Honesty + Add(new VirtueGumpItem(61, 71, 108, GetHueFor(0))); // Humility + Add(new VirtueGumpItem(123, 46, 112, GetHueFor(4))); // Valor + Add(new VirtueGumpItem(187, 70, 107, GetHueFor(5))); // Honor + Add(new VirtueGumpItem(35, 135, 110, GetHueFor(1))); // Sacrifice + Add(new VirtueGumpItem(211, 133, 105, GetHueFor(2))); // Compassion + Add(new VirtueGumpItem(61, 195, 111, GetHueFor(3))); // Spiritulaity + Add(new VirtueGumpItem(186, 195, 109, GetHueFor(6))); // Justice + Add(new VirtueGumpItem(121, 221, 106, GetHueFor(7))); // Honesty if (m_Beholder == m_Beheld) { @@ -171,22 +171,11 @@ namespace Server } } - private class InternalEntry : GumpImage + private class VirtueGumpItem : GumpImage { - private static readonly byte[] m_Class = StringToBuffer(" class=VirtueGumpItem"); - - public InternalEntry(int x, int y, int gumpID, int hue) : base(x, y, gumpID, hue) + public VirtueGumpItem(int x, int y, int gumpID, int hue) : base(x, y, gumpID, hue, "VirtueGumpItem") { } - - public override string Compile(NetState ns) => $"{{ gumppic {X} {Y} {GumpID} hue={Hue} class=VirtueGumpItem }}"; - - public override void AppendTo(NetState ns, IGumpWriter disp) - { - base.AppendTo(ns, disp); - - disp.AppendLayout(m_Class); - } } } }