From 5549c2bec757df26238de83d3c4a5f6320db12bf Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Wed, 15 Jul 2026 01:23:57 +0300 Subject: [PATCH] feat(CS-P3): siege PvP on Valley of Loren during siege (ADAMU-CUSTOM) + golden phase announcements --- .../specs/2026-07-14-castle-siege-design.md | 3 +- src/GameLogic/Player.cs | 10 ++++- .../PeriodicTasks/CastleSiegeEventPlugIn.cs | 42 +++++++++++++++---- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/docs/superpowers/specs/2026-07-14-castle-siege-design.md b/docs/superpowers/specs/2026-07-14-castle-siege-design.md index bb8efab..67f3dc0 100644 --- a/docs/superpowers/specs/2026-07-14-castle-siege-design.md +++ b/docs/superpowers/specs/2026-07-14-castle-siege-design.md @@ -166,7 +166,8 @@ docker push /adamu-openmu: | `GameLogic/PlayerActions/TalkNpcAction.cs` | 0b | remote-NPC: `TalkToNpcByNumberAsync` (additive metod) | ✅ Uygulandı, `// ADAMU-CUSTOM` işaretli | | `GameLogic/GameMap.cs` | 0b | remote-NPC: `GetNpcByNumber` helper (additive metod) | ✅ Uygulandı, `// ADAMU-CUSTOM` işaretli; test: `GameMapTest.GetNpcByNumberFindsSpawnedNpcAsync` | | `GameServer/MessageHandler/TalkNpcHandlerPlugInBase.cs` | 0b | remote-NPC: `0x8000` marker dalı (tek gerçek core-logic dokunuşu) | ✅ Uygulandı, `// ADAMU-CUSTOM` işaretli | -| DataModel + Initialization (CS entity'leri) | P1 | CS persistence | ⏳ Planlanacak (EF migration gerekir) | +| `GameLogic/Player.cs` | P3 | CS PvP: Siege fazında Valley of Loren'de (map 30) kill izni (`IsCastleSiegeBattleActive`) | ✅ Uygulandı, `// ADAMU-CUSTOM` | +| DataModel + Initialization (CS entity'leri) | P4 | CS persistence (sahiplik/vergi) | ⏳ Planlanacak (EF migration gerekir) | | *(fazlar ilerledikçe eklenecek)* | | | | ## 10. Test stratejisi diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs index cbc6732..765c12b 100644 --- a/src/GameLogic/Player.cs +++ b/src/GameLogic/Player.cs @@ -709,6 +709,11 @@ public class Player : AsyncDisposable, IBucketMapObserver, IAttackable, IAttacke return this.InvokeViewPlugInAsync(p => p.ShowMessageAsync(message, MessageType.BlueNormal)); } + // ADAMU-CUSTOM: Castle Siege PvP gate — true while the siege phase runs on Valley of Loren (map 30). + private bool IsCastleSiegeBattleActive() + => this.CurrentMap?.Definition.Number == 30 + && PlugIns.PeriodicTasks.CastleSiegeEventPlugIn.TryGetContext(this.GameContext)?.Phase == CastleSiege.CastleSiegePhase.Siege; + /// public async ValueTask AttackByAsync(IAttacker attacker, SkillEntry? skill, bool isCombo, double damageFactor = 1.0, bool? isFinalStreakHit = null) { @@ -723,7 +728,10 @@ public class Player : AsyncDisposable, IBucketMapObserver, IAttackable, IAttacke } if (!this.GameContext.PvpEnabled && this.CurrentMap?.Definition.BattleZone == null && - this.CurrentMiniGame?.AllowPlayerKilling is false) + this.CurrentMiniGame?.AllowPlayerKilling is false + + // ADAMU-CUSTOM: Castle Siege — enable PvP on Valley of Loren (30) while the siege phase runs. + && !this.IsCastleSiegeBattleActive()) { return null; } diff --git a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs index 1976d6d..4763452 100644 --- a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs +++ b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs @@ -8,6 +8,7 @@ using System.Collections.Concurrent; using System.Linq; using System.Runtime.InteropServices; using MUnique.OpenMU.GameLogic.CastleSiege; +using MUnique.OpenMU.GameLogic.Views; using MUnique.OpenMU.Interfaces; using MUnique.OpenMU.PlugIns; @@ -46,14 +47,8 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom { var created = new CastleSiegeContext(this.Configuration ?? new CastleSiegeConfiguration()); - // When the siege phase begins, pull all registered guild members onto the battle map. - created.PhaseChanged += phase => - { - if (phase == CastleSiegePhase.Siege) - { - _ = WarpRegisteredMembersToSiegeAsync(gc, created); - } - }; + // Announce phase changes to the whole server and, when the siege begins, warp registered members. + created.PhaseChanged += phase => _ = OnPhaseChangedAsync(gc, created, phase); return created; }); @@ -71,6 +66,37 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom } } + private static async Task OnPhaseChangedAsync(IGameContext gameContext, CastleSiegeContext context, CastleSiegePhase phase) + { + try + { + switch (phase) + { + case CastleSiegePhase.Registration: + 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 WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false); + break; + case CastleSiegePhase.Ownership when context.OwnerGuildName is { } owner: + await AnnounceAsync(gameContext, $"The Castle Siege has ended. The castle now belongs to the guild '{owner}'!").ConfigureAwait(false); + break; + default: + break; + } + } + catch (Exception ex) + { + gameContext.LoggerFactory.CreateLogger() + .LogError(ex, "Castle Siege: error handling phase change to {phase}.", phase); + } + } + + private static ValueTask AnnounceAsync(IGameContext gameContext, string message) + => gameContext.ForEachPlayerAsync(player => + player.InvokeViewPlugInAsync(p => p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()); + private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context) { try