diff --git a/src/GameLogic/MiniGames/HeykelSavasiContext.cs b/src/GameLogic/MiniGames/HeykelSavasiContext.cs
index ffe0578..2feb621 100644
--- a/src/GameLogic/MiniGames/HeykelSavasiContext.cs
+++ b/src/GameLogic/MiniGames/HeykelSavasiContext.cs
@@ -58,6 +58,19 @@ public class HeykelSavasiContext : MiniGameContext
///
public const int StatuesToBreak = 7;
+ ///
+ /// The fixed amount of Zen awarded to each member of the winning team when the event ends.
+ ///
+ ///
+ /// This is the authoritative definition of the reward amount. It is defined here (in GameLogic), not in
+ /// Persistence.Initialization.VersionSeasonSix.Events.HeykelSavasiInitializer where a same-named
+ /// constant currently also exists, because MUnique.OpenMU.GameLogic does not - and must not -
+ /// reference MUnique.OpenMU.Persistence.Initialization (the dependency only goes the other way;
+ /// same constraint that forced into this class). The initializer's constant
+ /// is unused by this class and will be reconciled/removed in a later cleanup pass.
+ ///
+ public const int WinnerZenReward = 10_000_000;
+
///
/// The of the destructible
/// statue NPC (see HeykelSavasiMap.CreateMonsters).
@@ -327,6 +340,49 @@ public class HeykelSavasiContext : MiniGameContext
await this.SpawnStatueAsync(HeykelSavasiTeam.Blue, 0).ConfigureAwait(false);
}
+ ///
+ ///
+ /// Awards Zen to every member of the winning team (see
+ /// ); no reward is given on a draw (). Mirrors
+ /// the exact fallback pattern the framework itself uses for money rewards
+ /// (MiniGameContext.GiveRewardAsync, MiniGameContext.cs:577-579): if a player's inventory can't
+ /// hold the money, returns and the player is
+ /// notified via . Deliberately does not announce the
+ /// result via MiniGameContext.ShowGoldenMessageAsync: that overload treats its argument as a
+ /// localization resource key (see e.g. BloodCastleContext.OnObjectRemovedFromMapAsync's
+ /// nameof(PlayerMessage.BloodCastleCrystalStatusDestroyed) usage), not literal text, so there is no
+ /// existing resource key for a Heykel Savasi win/draw announcement to pass here; adding one is out of
+ /// scope for this task. Does not warp players itself either: once the base call below returns, the
+ /// framework's own shutdown path calls MovePlayersToSafezoneAsync, which - because
+ /// is no longer at that point -
+ /// resolves every participant's respawn gate to the map's configured SafezoneMap (Lorencia).
+ ///
+ protected override async ValueTask GameEndedAsync(ICollection finishers)
+ {
+ var winner = this.GetWinner();
+ if (winner != HeykelSavasiTeam.None)
+ {
+ foreach (var player in this.PlayersOf(winner).ToList())
+ {
+ if (!player.TryAddMoney(WinnerZenReward))
+ {
+ await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.AwardMoneyFailByFullInventory)).ConfigureAwait(false);
+ }
+ }
+ }
+
+ await base.GameEndedAsync(finishers).ConfigureAwait(false);
+ }
+
+ ///
+ /// Gets the players which would receive the winner's Zen reward if the event ended right now, for unit
+ /// tests only (a full run needs a live with a working
+ /// /inventory, which isn't practical to construct in a fast unit test).
+ ///
+ /// The winning team's players, or an empty sequence if the event hasn't been decided yet.
+ internal IEnumerable GetRewardTargetsForTest()
+ => this.GetWinner() == HeykelSavasiTeam.None ? Enumerable.Empty() : this.PlayersOf(this.GetWinner());
+
///
///
/// Note: already subscribes every