* Movie ignore mobiles to Mobile class
* Allow necromancer familiars to ignore mobiles
* ChampionSpawn should not quietly fail when creating new spawn
* Fix client party crash bug: 2 people partied, the leader logs out, other client is hung
* Fix spellbooks creating with magery/meditation only and creating magery multiple times
* Fix BaseRunicTool.GetRandomSlayer() creating more undead slayers than intended
* Do not allow players to dismount each other whilst mounted
* Fix AOS onwards damage increase tooltip and wrong formula in AOS
* Fix monsters killing other monster revenants + animate dead should not attack player pets + other animates
* Fix fire steed having loot pack added twice
* Fix oaks spawn not able to create Unicorns + Kirins via Activator.CreateInstace() due to constructor having name as parametr
* Fix ignoreMobiles flag not propagated to client for non-players.
* Fix harrower tents not leeching from players
* Fix boats speedhacking around the map
* Fix bless, agility etc. not renewing duration
* Fix reveal always worked
* Fix empty constructor for steeds so they don't throw now.
## Changes
* Improves type hashing by introducing xxHash3 (64bit)
* Removes individual `tdb` files in favor of a single `SerializedTypes.db` file. This file is only used to identify a type that is being deserialized, which doesn't exist.
* Adds duplicate type alias detection
* Adds `AssemblyHandler.FindTypeByHash`
View changed files whitespaces: https://github.com/modernuo/ModernUO/pull/1172/files?diff=split&w=1
## SerializedTypes.db
The serialized types file is used to get back the original name of a type in case it no longer exists in code. This can easily be necessary if a class is renamed in code and no `TypeAlias` is provided.
### Format
byte[4] - version
byte[4] - count
--array--
byte[8] - xxHash
byte[1] - flag, 0 - null, 1 - not null
byte[n] - Full class name in UTF8
### Example
<img width="472" alt="SerializedTypes_Example" src="https://user-images.githubusercontent.com/3953314/195255429-31d24293-6bd1-419e-811b-07874dd0f78d.png">
## Benchmarks
Serialized 500 Type fields. The 8192bytes comes from the _ConcurrentQueue_ that would later be used for SerializedTypes.
Note that the queue is never cleared, so it's size grew considerably.
```cs
| Method | Mean | Error | StdDev | Allocated |
|--------------------- |---------:|---------:|---------:|----------:|
| BenchmarkXXHash | 18.44 us | 0.278 us | 0.260 us | 8192 B |
| BenchmarkTypeStrings | 25.09 us | 0.292 us | 0.259 us | - |
```
TODO:
* Add support in the Serialization Generator for `ReadType()` and `Write(Type)`
* Remove `SetTypeRef` from Serialization Generator
- [X] Fixes NPE from account tags.
- [X] Fixes bad skill check due to missing cast to double.
- [X] Fixes water elemental duration.
- [X] Standardizes spell summon duration by expansion.
## Breaking Changes (New API)
ObjectPropertyList supports the following API:
```cs
list.Add(500000);
list.Add(500001, stringArgument);
list.Add("Some text");
list.Add($"Some text with {argument}");
list.Add(500002, $"{arg1}\t{arg2}");
```
## Notes
1. All API uses that require a formatter like this:
```cs
list.Add(500002, "{0}\t{1}", arg1, arg2);
```
Should be changed to use string interpolation, for example:
```cs
list.Add(500002, $"{arg1}\t{arg2}");
```
2. The following paradigm should no longer be used:
```cs
list.Add(1061170, prop.ToString()); // strength requirement ~1_val~
```
The new string interpolation API will avoid having to convert the argument to a string before writing it to the packet. Instead use the following:
```cs
list.Add(1061170, $"{prop}"); // strength requirement ~1_val~
```
### Benchmarks
```cs
| Method | Mean | Error | StdDev | Gen 0 | Allocated |
|------------------------------- |---------:|--------:|--------:|-------:|----------:|
| BenchmarkOldOPL | 241.0 ns | 0.56 ns | 0.47 ns | 0.0105 | 88 B |
| BenchmarkStringInterpolatedOPL | 199.9 ns | 2.44 ns | 2.39 ns | - | - |
```
### Changes
- [X] Removes crash in STArray.Return when array is null.
- [X] Fixes NPE in OPL when entity is null. Serial in packet will be 0 when entity is null.
- [X] Fixes NPE in AosAttributes when Parent is null.
- [X] Changes OPL to use string interpolation.
- [X] Introduces `IPropertyList` to allow extending PropertyList for other uses.
* Removes scaled speed by dex for monsters
* Changes scaled speed by dex for pets (HS+ expansion) to be 400ms -> 100ms between 50 -> 200 dex
* Removes speed changes that were broken for various mobs
* Combines "Legacy" speed and renames the class to `NPCSpeeds`
* Creates speed "classes" (slow, medium, fast, very fast)
* Introduces a new overridable property `SpeedClass`. Register a new speed class using `NPCSpeeds.Register()` and then set the speed class to bulk/mass apply speeds.
Order of speed determination:
1. SpeedMod
2. SpeedClass property (not null)
3. SpeedClass entry in Data\npc-speeds.json for that type
4. "Fast" (200ms Active, 400ms Passive)
## Added Feature
Adds a single threaded array pool that works exactly the same as `ArrayPool<T>.Shared`.
The `STArrayPool<T>.Shared` can only be used on a single thread, the main game thread of the server.
Note: Unlike the built-in array pool, there is no hook into the _GC Gen 2_. This means to relieve potentially high memory pressure, `ArrayPool<T>.Shared.Trim()` must be called. The pool will only release arrays _after two successive calls within 10 seconds or longer_. If the server is at less than 70% total memory usage, or the server is not going to use this pool for something egregious, then don't bother ever calling Trim().
## Changes
- [X] Fixes ArrayPool calls that should be cleared due to references.
- [X] Benchmarks against ArrayPool with 4+ rented arrays deep of the same length.
- [x] Unit tests
- [X] Removes several `ToList()` uses with `PooledRefQueue`
- [X] Adds a `PeekRandom` to PooledRefQueue
- [X] Updates EV/BS so they dispel each other in a more efficient manner.
- [X] Fixes Firebomb so it works like a normal firefield.
- [X] Fixes field spells so they aren't unnecessarily using a Point3D ref more than necessary.
- [X] Removes extra allocation in campfire by using reverse loop.
- [X] Removes other LINQ calls that aren't needed.
* Adds the following configurations:
* `movement.delay.npcMinDelay` - 0.1 - Pets or Non-monster NPCs
* `movement.delay.npcMaxDelay` - 0.4 - Pets or Non-monster NPCs
* `movement.delay.monsterMinDelay` - 0.4 - Non-pet Monsters or NPC vs Player Combat
* `movement.delay.monsterMaxDelay` - 0.8 - Non-pet Monsters or NPC vs Player Combat
* `movement.delay.monsterMinDex` - 150 - Dex maximum for delay by dex
* `movement.delay.MinDex` - 190 - Dex maximum for delay by dex
* Now reads Prof.txt to get the professions
* The templated professions are based on profession `TrueName`
* Adds missing starting equipment
* Implements gargish starting equipment
* Consolidates some of the code
* Moves TC stuff to the TC file.
* Fixes some starting equipment that was wrong.
Note:
* Implementing the original T2A professions is possible on the client side, but requires moving the old clilocs to the current clilocs file. In the current file the entries are blank.
* Using the original LBR professions (they match AOS but without necromancy/chivalry) is kind of confusing because the profession indexes are ripped out of the UOTD/UOR ones and have non-sequential indexes. This will probably cause confusion and people will get no templated items when they select the wrong profession.
* Removes BodyValue
* Removes BodyPicker gump
* Fixes setting Body and BodyMod in props gump
* Props will now look for an `implicit cast` in order to find a way to allow you to modify values with the props gump.
* Fixed `[self set body 0x190` so it works too.
* Fixes pooled timer leaking
* Fixes `[dumptimers` command so it outputs properly, adds spacing, and stacktraces
* Adds `[Tidy]` for serializing Lists. This will remove deleted entities during world save before serializing the list.
* Adds helpers for managing Lists/Sets/Dictionaries
### New API
```cs
// Creates the list if it is null, then adds
Utility.Add(ref list, value);
Utility.Add(ref set, value);
Utility.Add(ref dict, key, value);
// Nulls the variable if the count is zero
Utility.Remove(ref list, value);
Utility.Remove(ref set, value);
Utility.Remove(ref dict, key);
// Marks entity as dirty in addition to doing the action
entity.Add(list, value);
// Marks entity as dirty, and will create list if it doesn't exist
entity.Add(ref list, value);
// Marks entity as dirty in addition to doing the action
entity.Remove(list, value);
// Marks entity as dirty, and will null the list count is zero
entity.Remove(ref list, value);
```
### Updates to [dumptimers
<img width="825" alt="Screen Shot 2021-08-14 at 2 55 10 AM" src="https://user-images.githubusercontent.com/3953314/129442449-ccf7fe14-29d6-4f3f-9366-c8eb7b9828a7.png">
### Changes/Fixes:
* Adds timer pooling.
* Allows pool to be configurable in ModernUO.json
* Pool replenishes itself asynchronously if depleted.
* Fixes an issue with barkeeps and town criers
* Fixes an issue with incognito buff icons not being removed
* Fixes an issue with polymorph name mod not being removed
* Fixes several places where timers go on forever even after an object is deleted, keeping a reference (memory leak)
* Eliminates the timer for MiningCart altogether.
* Deletes `AcidSlime` since it is a duplicate of `PoolOfAcid`
* Fixes HonorableExecution and standardizes the code for other Bushido moves.
## Changes to the Timer API:
```cs
public class Timer
{
// Creates a timer that will be returned to the pool once execution stops.
public static void StartTimer(Action callback);
public static void StartTimer(TimeSpan delay, Action callback);
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback);
public static void StartTimer(TimeSpan interval, int count, Action callback);
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback);
// Creates a timer and returns a token for more control. Requires manual cancellation in order for the timer to be returned to the pool.
// If the token is dereferenced, the timer will be dereferenced too. While not returning a timer to the pool is not considered hazardous, it does defeat the purpose of pooled timers.
public static void StartTimer(Action callback, out TimerExecutionToken token);
public static void StartTimer(TimeSpan delay, Action callback, out TimerExecutionToken token);
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback, out TimerExecutionToken token);
public static void StartTimer(TimeSpan interval, int count, Action callback, out TimerExecutionToken token);
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback, out TimerExecutionToken token);
// If you aren't sure how to use the API above, or you don't care about performance, then you can use the old RunUO Timer.DelayCall
public static DelayCallTimer DelayCall(Action callback);
public static DelayCallTimer DelayCall(TimeSpan delay, Action callback);
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, Action callback);
public static DelayCallTimer DelayCall(TimeSpan interval, int count, Action callback);
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, int count, Action callback);
}
public struct TimerExecutionToken
{
public bool Running { get; }
public int Index { get; }
public int RemainingCount { get; }
public DateTime Next { get; }
}
```
## When to use `TimerExecutionToken`?
Use tokens when you want to gain the performance benefit of using a pooled timer, but you need one of the following:
* Access to the next time the timer will tick:`token.Next`
* Access to which interval, how many intervals there are, or how many are remaining: `token.Index`, `token.Count`, and `token.RemainingCount`
* Stop a timer manually.
* Determine if the timer is running: `timer.Running`
* See notes below about requirements for using tokens!
## Notes about using the TimerExecutionToken:
When you opt-in to receive a token, you must call `Cancel()` to return the timer. This can be done inside of the callback, or outside of the callback at any time.
If this is not called and your timer is an infinite interval, then you will create a potential memory leak, or null pointer exception in your callback.
If the timer ends and is stopped, but cancel is not called, then the timer will never return to the pool and stay referenced until the token is deleted or cancel is called. (Memory leak)
## Is this thread safe?
No. The ModernUO timer system is not thread safe at all. If you require a thread safe timer system, contact me and I'll help adapt this system. Keep in mind that there is a massive performance hit to make this thread safe when there are literally no use cases for it.
If you need to synchronize execution, meaning you want to execute code from another thread on the core thread. Let's say you have a discord bot that is pushing commands to the game server. Then use `EventLoopContext.Post(SendOrPostCallback callback, object state);`.
- [X] Caches DateTime.NowUtc on the game loop (not other threads)
- [X] Replaces all locations where it makes sense
- [X] Adds Min/Max for `IComparable` (TimeSpan, DateTimes, etc)
Closes#261
SpanWriter now has an argument that allows it to be resizable.
```cs
public SpanWriter(Span<byte> initialBuffer, bool resize = false)
public SpanWriter(int initialCapacity, bool resize = false)
```
If a SpanWriter is set to be resizable, or given an initial capacity instead of a buffer, it must be disposed:
```cs
public static void SomeMethod()
{
using var writer = new SpanWriter(stackalloc byte[512], true);
// stuff
// Automatic Dispose of writer
}
```
This is because the SpanWriter uses `ArrayPool<byte>.Shared` _rented buffers_ to resize. If the writer is not disposed, those buffers will never be reused resulting in a _memory leak_.
It is possible that the SpanWriter will outright ditch the initial buffer if resize is set to true and growing is needed. To check/account for that we can do the following:
```cs
public static void SomeMethod()
{
Span<byte> span = stackalloc byte[512];
using var writer = new SpanWriter(span, true);
// write some stuff that causes the span to grow
span = writer.RawBuffer;
// Do stuff with the span
}
```
Make sure you don't accidentally `Dispose()` the writer or use the `RawBuffer` outside of the `using` block. If you do, bad things will happen! (NullPointerException, or a fresh SpanWriter with no buffer, depending on the situation).
SpanWriter can also now be used with a fixed statement since it has a `PinnableReference()` function.
- [X] Fixes an issue where a buffer smaller than 8 bytes would not double with enough space in some cases.
- [X] Fixes an issue with dupe copying the savebuffer reference (ugh).
- [X] Streamlines the IGenericWriter API to use better generics.
- [X] Streamlines the IGenericReader API to use better generics.
- [X] Forces `tidying` of a List/HashSet to be done externally since Writers/Readers should not have side effects.
- [X] Fixes an issue where Tidying a list didn't TrimExcess, causing memory leaks.
- [X] Reverted the meaning of `World.Running` to specifically refer to any world state post world loading.
- NOTE: Do not use this if you want to block on world saves. Instead use checks against `WorldState.Saving` states.
- [X] Fixes an issue with serializing negative DateTime deltas.
- [X] Fixes a potential issue with serializing non-UTC DateTime.
Bumps release version
- [X] Fixes an edge case where the cycle index goes negative and crashes
- [X] Fixes average CPS calculation
- [X] Fixes issue with logout delay
- [X] Bumps release version
- [X] Updates Movement packets
- [X] Adds OSI fastwalk packets. These aren't used on OSi anymore.
- [X] Adds new movement handling, but looks like the client doesn't use it. (Also leaving the 0x4000 Character List Flag off)
- [X] Adds time sync request handler, but also looks like the client doesn't use it.
- [X] Adds time sync response for time sync, just in case, but it isn't used, so not sure about the arguments.
- [X] Reimplements RunUO's fastwalk to use a circular array on the netstate instead of every mobile
- [X] Removes ClearFastwalkStack from RunUO implementation. This shouldn't be needed anymore
Changed fastwalk settings:
```cs
public static int WalkFootDelay { get; set; } = 440;
public static int RunFootDelay { get; set; } = 220;
public static int WalkMountDelay { get; set; } = 220;
public static int RunMountDelay { get; set; } = 110;
public static bool EnableFastwalkPrevention { get; set; } = true;
public static AccessLevel FastwalkExemptionLevel { get; set; } = AccessLevel.Counselor;
// If this is changed during runtime, then the steps array needs resizing.
public static int MaxSteps { get; private set; } = 4;
```
modernuo.json
```json
{
"settings": {
"movement.delay.runFoot": "220",
"movement.delay.runMount": "110",
"movement.delay.walkFoot": "440",
"movement.delay.walkMount": "220",
"movement.enableFastWalkPrevention": "True",
"movement.fastwalkExemptionLevel": "Counselor",
"movement.maxSteps": "4"
}
}
```
Notes about OSI fastwalk:
While it does work, I can't find a benefit in using it because of the variable speeds. If players moved at a single speed then we could refill the stack every X milliseconds with 6 keys and use a naive token bucket implementation.
Unfortunately variable speeds mount/run/walk/etc means we would have to use a leaky bucket algorithm.
If we are using a leaky bucket algorithm with a variable leak, then we don't need to send tokens because we are already tracking it on the server side.
ModernUO vs OSI Fastwalk:
When the fastwalk was implemented on OSI, it used up to 6 tokens. These tokens were probably distributed every 600-750 milliseconds. To get the same effect, the new fastwalk settings might need to be adjusted. I would tweak them and feel free to let me know what worked for you!
Bumps release version
- [X] Fixes several bugs
- [X] Updates more ordinal issues
- [X] Cleans up the code a bit
- [X] Turns classes static that should have been
- [X] Changes TcpServer.Instances to a HashSet
Bumps release version
- [X] Changes effect packets
- [X] Updates playing sounds
- [X] Updates Effects class to have more options so there are less direct calls to building the packets
- [X] Changes bonding status packet