### Summary
Refactors ScheduledEvent and EventScheduler API to use TimeOnly so recurrence offset is explicit.
API:
```cs
public ScheduledEvent(
DateTime startAfter,
DateTime endOn,
TimeOnly time,
IRecurrencePattern recurrence,
TimeZoneInfo timeZone = null
)
```
Example:
```cs
// Schedule a daily event at 8:00 AM UTC
EventScheduler.DailyAt(
new DateTime(2024, 6, 1, 8, 0, 0, DateTimeKind.Utc),
() => Console.WriteLine("Daily event triggered!")
);
// Schedule a custom recurring event at 3:30 PM UTC every Monday
var recurrence = new WeeklyRecurrencePattern(1, DaysOfWeek.Monday);
EventScheduler.Shared.ScheduleEvent(
DateTime.UtcNow,
new TimeOnly(15, 30),
() => Console.WriteLine("Weekly Monday event!"),
recurrence
);
```
### Summary
* Adds an event scheduler.
* Adds conveniences for hourly, daily, weekly, biweekly, monthly, ordinal monthly, and yearly recurrences
Example:
```cs
var tz = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
// Specify the time of the day, and the day of the week you want it to occur. Make sure it is translated into Utc.
// The next occurrence will be _after_ the specified date/time.
var scheduledEvent = EventScheduler.WeeklyAt(new DateTime(2025, 04, 26, 17, 00, 00), StartEvent, tz);
void StartEvent()
{
World.Broadcast(0x30, false, "The event has started!");
}
Console.WriteLine("Event starts on {0}", scheduledEvent.NextOccurrence);
```
In this example, on _Saturday, May 3rd, 2025 @ 5pm ET_, the message "The event has started!" will be broadcasted.
### Summary
Adds the command [TrackLeaks to enable tracking item/mobiles that have been deleted but still have dangling references. Requires adding the _TRACK_LEAKS_ define constant during build.
### Summary
* Optimized GetString by eliminating the intermediate string allocation.
** Note **: Encoding.GetChars() is still really inefficient, especially when strings are not aligned or have regular ascii/unicode characters. Thankfully we generally don't have to worry about these odd edge cases, but if they happen then GetChars can allocate hundreds of bytes.
This is a benchmark for just the related changes. NonSpecial are just ascii characters, while the other tests include control codes.
```cs
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|------------------------------- |---------:|--------:|--------:|-------:|----------:|
| GetString | 306.7 ns | 5.96 ns | 6.86 ns | 0.0124 | 200 B |
| GetStringNotSpecial | 257.0 ns | 4.83 ns | 4.52 ns | 0.0114 | 184 B |
| GetStringSpanHelpers | 166.4 ns | 3.26 ns | 3.48 ns | - | - |
| GetStringSpanHelpersNotSpecial | 137.3 ns | 1.57 ns | 1.22 ns | - | - |
```
### Summary
On some operating systems (like hosted Linux VMs), the `TimeStamp.GetTimeStamp()` CPU tick count will wrap around. This is generally not an issue, except for legacy reasons the TickCount is returned in milliseconds instead of ticks. This means when the values wrap around, they are already divided by the CPU Frequency (usually 1million) and then converted to milliseconds. That means the delta between the tick count before and after wrapping is off by a magnitude of (Frequency / 1000).
Example:
TimeStamp A = 9223372036654775807
Some time has passed:
TimeStamp B = -9223372036654775809
The raw delta is 400_000_000 (400ms) when you do `unchecked(A - B)`.
If we do the calculation AFTER converting it to milliseconds, then:
TickCount A = 9223372036654
TickCount B = -9223372036654
The raw delta is -18446744073308 instead of 400_000_000.
To fix this the calculation was changed so `long` -> `ulong`, then divided, then converted back to `long`, effectively bypassing wrap-around issue.
The new TickCount values in our example become:
TickCount A = 9223372037054
TickCount B = 9223372036654
The delta is 400 (in milliseconds). 🎉
### Summary
- Fixes infinite teleport from guard refactor for spawners
- Fixes guards not attacking Always Murderer creatures
- Fixes crash when calling guards due to map modification while enumeration
### Summary
* Fixes a race condition where the snapshot path isn't between the request snapshot being set on a background thread, and the main loop consuming that flag.
Closes#2102