> [!Important]
> **Developer Note**
> This code change will **completely move gumps out of the core**
### Summary
- Adds `GetGumps()` convenience which exposes methods to Find/Close/Send multiple gumps. This helper is a performance improvement by eliminating the Dictionary<Player, List> lookup for gumps.
### Summary
* Added World.NewVirtual for creating virtual serial numbers
* Reserved range 0x7EEEEEEE to 0x7FFFFFFF for virtual serials
* Hair and Facial hair (for mobiles) now use virtual serials instead of FakeSerial() functions
* Consolidated virtual hair to a single `VirtualHairInfo` class.
Corpse hair and facial hair now persists across save/load and hair and facial hair no longer teleport to newest corpse.
## Summary
- Removes allocation of a `List<ContextMenuEntry>` every time a context menu is created.
- Moves packet/context menu creation logic out of the core
- Fixes tame entry
## BREAKING CHANGE
> [!Important]
> **Developer Note**
> ```cs
> public virtual void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
> ```
> and similar functions changed to
> ```cs
> public virtual void GetContextMenuEntries(Mobile from, ref PooledRefList<ContextMenuEntry> list)
> ```
### Summary
- Fixes an issue that causes CUO to crash due to bad string caching in static gumps
- Fixes wrong/bad 16bit html gump hues.
- Moves `C16232` (16-bit to 32-bit) and `C32216` (32-bit to 16-bit) to Utility class for broader use.
TODO:
- Some gumps have different 32bit (for string content) vs 16bit (for localized content) strings. Does this matter?
### Summary
- Adds some more checks in case a corpse's owner is somehow null. Would like to eventually allow null owner corpses, but more work is needed.
- Cleans up variable unboxing and reassignment in quests. Also flattens quest logic. Still more work needs to be done.
- Codegens more quest items/mobiles.
### Summary
- Works around a sneaky edge case bug in the JIT with stackalloc where sometimes the buffer is not zero'd.
- Fixes SendDisplayBoatHS
- Fixes sending health bars in the `SendEverything()` logic.
- Fixes a bug in sizing for some string helper functions.
### Developer Note
We are enabled `SkipLocalsInit` - do not rely on `stackalloc` to be zero'd. To zero the buffer, use `span.Clear();`
Closes#1606
### Summary
Modifying a ValueLinkList using one of the methods will bump the "version". This field is used by iterators (foreach loops) to determine if the link list was modified while iterating. The sector.Items (and in the future other lists), will no longer be safe to modify while iterating. The server will _CRASH_ if the ValueLinkList is modified.
Thanks to @stefanomerotta for help!
### Screenshots
<img width="588" alt="image" src="https://github.com/modernuo/ModernUO/assets/3953314/83ee0b6e-ff4f-4768-9e29-84456e04b1ec">
## MAJOR CHANGE (API BREAKING)
Moved the virtues to it's own system _VirtueSystem_. This should make it easier to extend or remove the virtue system. Virtues will be saved to a new folder called _Virtues_.
### Motivation
The motivation was also two-fold, performance/stability, and to fix bugs.
First, virtues is the second system (first is murders), that has a pre-world-save check on _every mobile in the game_ to atrophy virtue stats. This is taxing since it freexes the world and makes world saves take longer. Every mobile had Gain/Loss dates for each virtue, whether they needed them or not. Most players don't even use the virtue system, so this will increase performance considerably.
Second, when I tried to optimize/refactor the code, I found several bugs that needed to be fixed.
### Major API Changes
- [X] The properties on players related to virtues are gone. Use `pm.GetVirtues()?.<PropertyName>` instead.
- [X] Virtues were removed from non-player Mobiles.
- [X] `pm.JusticeProtectors` was removed. Use `JusticeVirtue.GetProtector()` or `JusticeVirtue.GetProtected()` instead.
### Screenshots
<img width="221" alt="Props-1" src="https://github.com/modernuo/ModernUO/assets/3953314/5c09cb83-b8d5-44a2-b899-a7ed7cd736ac">
<img width="220" alt="props-2" src="https://github.com/modernuo/ModernUO/assets/3953314/aeade4d3-8df0-47bd-955a-a864d0946cf7">
## Possible Breaking Change
* Reverted regions.json back to RunUO until newer regions are implemented and proper expansion checks are added.
### Other Changes
- [X] Adds `Region.IsPartOf<T1, T2>()` to check for multiple types.
- [X] Fixes regions.json being wrong
- [X] Adds lots of missing regions. **They are not implemented properly yet**
- [X] Adds regions.xml -> regions.json (check ModernUO Discord)
- [X] Adds expansion specific regions.
## Changes
- [X] Adds OPL convenience methods
- `opl.Add(cliloc, value)` and `opl.Add(value)` - value as an integer or string works just like `opl.Add(cliloc, $"{value}")`
- `opl.AddLocalized(cliloc, clilocValue)` - works the same as `opl.Add(cliloc, $"#{clilocValue}");`
- [X] Simplifies basic `list.Add()` situations
- [X] Changes cliloc as an argument so it works with custom IPropertyList implementations (HTML)
- [X] Fixes plants so they support the old localization and new (changed in 7.0.12.0+)
- [X] Exposes more methods to override for Item to make creating custom OPL possible.
## Important Notes
* Using a ternary as an argument, like this `opl.Add(number, showType ? $"{type}\t{value}" : $"{value}");` _will not use the correct string interpolation_. This means if you use a custom PropertyList (for HTML or some other purpose), the property list won't be localized properly.
* All localization values must be interpolated, even if they are literal strings, or integers. Example: `opl.Add(number, $"{"Charges"}\t{m_Charges}");` is correct. Using the following: `$"Charges\t{m_Charges}"` will not work for custom PropertyList implementations!
## 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)
- [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
### 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
- [X] Fixes some issues with the type caching
- [X] Changes type check default to ignore case
- [X] Splits out type caching of insensitive and sensitive lists. This means less stuff to iterate through when checking a type against the string.
- [X] Adds an ArrayEnumerator, mostly for reference purposes (copy/paste as needed)
- [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
### API Breaking Change
Combined `AsciiMessage` and `UnicodeMessage` into a single function that takes two arguments, `bool ascii` and `string lang`. Lang can be null (or anything) if ascii is true.
- [X] Changes message packets
- [X] Cleans up some code
- [X] Uses benchmarks to determine if the spanwriter + copyfrom
- [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