feat(CS-P3): full siege objective chain - guardian statues (Destructible spawn) + dual Crown Switch hold + throne capture gating

This commit is contained in:
Acentech Dev
2026-07-15 01:52:19 +03:00
parent 5549c2bec7
commit c50af5e5c2
7 changed files with 234 additions and 15 deletions

View File

@@ -8,6 +8,7 @@ using System.Collections.Concurrent;
using System.Linq;
using System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.CastleSiege;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.PlugIns;
@@ -23,6 +24,8 @@ using MUnique.OpenMU.PlugIns;
public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustomConfiguration<CastleSiegeConfiguration>, ISupportDefaultCustomConfiguration
{
private const ushort ValleyOfLorenMapNumber = 30;
private const short GuardianStatueNumber = 132; // reuse the "Statue of Saint" destructible definition
private static readonly (byte X, byte Y)[] StatuePositions = { (170, 206), (182, 206) };
private static readonly ConcurrentDictionary<IGameContext, CastleSiegeContext> Contexts = new();
@@ -76,7 +79,8 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
await AnnounceAsync(gameContext, "Castle Siege registration is now open! Guild masters, register at the Guardsman in the Valley of Loren.").ConfigureAwait(false);
break;
case CastleSiegePhase.Siege:
await AnnounceAsync(gameContext, "The Castle Siege has begun! Fight your way to the throne and capture it to win the castle!").ConfigureAwait(false);
await AnnounceAsync(gameContext, "The Castle Siege has begun! Destroy the guardian statues, hold both Crown Switches, then take the throne!").ConfigureAwait(false);
await SpawnGuardianStatuesAsync(gameContext, context).ConfigureAwait(false);
await WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false);
break;
case CastleSiegePhase.Ownership when context.OwnerGuildName is { } owner:
@@ -97,6 +101,58 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
=> gameContext.ForEachPlayerAsync(player =>
player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask());
private static async Task SpawnGuardianStatuesAsync(IGameContext gameContext, CastleSiegeContext context)
{
try
{
if (gameContext is not GameContext concrete)
{
context.SetStatueCount(0);
return;
}
var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false);
var statueDef = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GuardianStatueNumber);
if (map is null || statueDef is null)
{
context.SetStatueCount(0);
return;
}
var spawned = 0;
for (var i = 0; i < StatuePositions.Length; i++)
{
var pos = StatuePositions[i];
var spawnArea = new MonsterSpawnArea
{
MonsterDefinition = statueDef,
Quantity = 1,
X1 = pos.X,
X2 = pos.X,
Y1 = pos.Y,
Y2 = pos.Y,
Direction = Direction.South,
SpawnTrigger = SpawnTrigger.OnceAtEventStart,
};
var npc = await concrete.MapInitializer.InitializeSpawnAsync(1000 + i, map, spawnArea).ConfigureAwait(false);
if (npc is AttackableNpcBase attackable)
{
attackable.Died += (_, _) => context.NotifyStatueDestroyed();
spawned++;
}
}
context.SetStatueCount(spawned);
}
catch (Exception ex)
{
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
.LogError(ex, "Castle Siege: error while spawning guardian statues.");
context.SetStatueCount(0);
}
}
private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context)
{
try