## Summary Two small additive changes a derived spawner needs. - **`BaseSpawner.OnTick()` is now `virtual`.** A subclass that gates spawning on external state (time windows, event triggers) must gate *timer* spawns without gating the manual `Spawn()` API, and `OnTick` is the only place the two paths differ: it is the timer callback and `Spawn()` is both what it calls and what commands and scripts call. Cost: one virtual dispatch on the existing timer callback; no change to stock behaviour. - **`group` in the JSON DTO.** `BaseSpawner.Group` (all dead, then respawn) is binary-persisted but was missing from `SpawnerDto`, so it did not survive export/import. Added to the abstract record after `spawnLocationIsHome`, assigned in `ApplyDto` after `InitSpawn` (which resets it), and exported by the three stock `ToDto` implementations. ## Test plan - [x] A derived spawner overriding `OnTick` with a closed gate: `OnTick()` spawns nothing; manual `Spawn()` still spawns and does not pass through `OnTick`. - [x] DTO round trip with `Group = true` carries `group` and restores it on `ToSpawner()`. - [x] `UOContent.Tests` full suite green. - [ ] CI
78 lines
2.1 KiB
C#
78 lines
2.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text.Json;
|
|
using Server.Engines.Spawners;
|
|
using Xunit;
|
|
|
|
namespace Server.Tests.Engines.Spawners;
|
|
|
|
[Collection("Sequential UOContent Tests")]
|
|
public class SpawnerTickAndGroupDtoTests
|
|
{
|
|
private sealed class GatedSpawner : Spawner
|
|
{
|
|
public int Ticks;
|
|
public bool Gate = false;
|
|
|
|
public GatedSpawner() : base(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, default, "Rabbit")
|
|
{
|
|
}
|
|
|
|
public GatedSpawner(Serial serial) : base(serial)
|
|
{
|
|
}
|
|
|
|
public override void OnTick()
|
|
{
|
|
Ticks++;
|
|
if (Gate)
|
|
{
|
|
base.OnTick();
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void OnTick_IsVirtual_AndManualSpawnBypassesIt()
|
|
{
|
|
var spawner = new GatedSpawner();
|
|
spawner.MoveToWorld(new Point3D(1500, 1500, 0), Map.Felucca);
|
|
try
|
|
{
|
|
spawner.OnTick();
|
|
Assert.Equal(1, spawner.Ticks);
|
|
Assert.Empty(spawner.Spawned); // gate closed: base.OnTick not reached
|
|
|
|
spawner.Spawn(); // manual API does not go through OnTick
|
|
Assert.Equal(1, spawner.Ticks);
|
|
Assert.Single(spawner.Spawned);
|
|
}
|
|
finally
|
|
{
|
|
spawner.Delete();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Dto_RoundTrip_CarriesGroup()
|
|
{
|
|
var spawner = new Spawner(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, default, "Rabbit");
|
|
spawner.MoveToWorld(new Point3D(1500, 1500, 0), Map.Felucca);
|
|
Spawner loaded = null;
|
|
try
|
|
{
|
|
spawner.Group = true;
|
|
var json = SpawnerJsonSerializer.SerializeCompact(new List<SpawnerDto> { spawner.ToDto() });
|
|
Assert.Contains("\"group\"", json);
|
|
|
|
var dtos = JsonSerializer.Deserialize<List<SpawnerDto>>(json, SpawnerJsonSerializer.Options);
|
|
loaded = (Spawner)dtos[0].ToSpawner();
|
|
Assert.True(loaded.Group);
|
|
}
|
|
finally
|
|
{
|
|
loaded?.Delete();
|
|
spawner.Delete();
|
|
}
|
|
}
|
|
}
|