feat(tvt): 4 arena bosses at 3 statues, robust guard/boss spawn, reliable GM restart, English strings

- Bosses: 306/309/357/459 spawn once at (55,42)/(32,42)/(31,59)/(56,58) after 3 total statues broken.
- Guards/bosses spawn in a walkable BOX (retry) instead of a fixed point; several statue-guard
  offsets landed on non-walkable terrain and silently failed, leaving statues unbreakable.
- Guard-alive gate uses the ACTUAL spawned count so a statue can always reach 0 -> breakable.
- HeykelSavasiStartPlugIn.OnStartedAsync force-disposes a lingering finished context so GM /starths
  restarts the event immediately (no server restart).
- Statue-break + join messages translated to English.
This commit is contained in:
Acentech Dev
2026-07-21 15:40:45 +03:00
parent bd5cda12dd
commit a2119b272e
3 changed files with 142 additions and 14 deletions

View File

@@ -30,4 +30,31 @@ public sealed class HeykelSavasiStartPlugIn : MiniGameStartBasePlugIn<HeykelSava
{
return new HeykelSavasiGameServerState(gameContext);
}
/// <inheritdoc />
protected override async ValueTask OnStartedAsync(HeykelSavasiGameServerState state)
{
// A GM /starths must work even immediately after a previous run ended. When the event finishes, its
// MiniGameContext lingers in a terminal state (Ended/Closed) for its exit ceremony (~60s) BEFORE it
// disposes; during that window GetMiniGameAsync (which only skips disposed/disposing contexts) would
// return that stale, already-finished context instead of opening a fresh event - so nothing starts
// until a server restart clears it. Force-dispose any lingering non-running context here first, so the
// base implementation then creates a brand-new event. A genuinely running game (Open/Playing) is left
// untouched (the base call just reuses it - no double start).
var definitions = state.Context.Configuration.MiniGameDefinitions
.Where(d => d.Type == this.Key && d.MapCreationPolicy == MiniGameMapCreationPolicy.Shared)
.ToList();
foreach (var definition in definitions)
{
var existing = await state.Context.GetMiniGameAsync(definition, null!).ConfigureAwait(false);
if (existing is { IsDisposed: false, IsDisposing: false }
&& existing.State is not MiniGameState.Open and not MiniGameState.Playing)
{
await existing.DisposeAsync().ConfigureAwait(false);
}
}
await base.OnStartedAsync(state).ConfigureAwait(false);
}
}