feat(hs): all statues visible at fixed coords, guard-gated, any-order break with counter+killer message
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -87,9 +87,15 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
private const short GuardMonsterNumber = 580;
|
private const short GuardMonsterNumber = 580;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The (dx, dy) offsets, relative to a statue's position, at which its 4 guards spawn.
|
/// The (dx, dy) offsets, relative to a statue's position, at which its 3 guards spawn. A statue
|
||||||
|
/// cannot be damaged until all 3 of these guards are dead (see <see cref="IsStatueAttackBlocked"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly (int Dx, int Dy)[] GuardOffsets = { (-2, 0), (2, 0), (0, -2), (0, 2) };
|
private static readonly (int Dx, int Dy)[] GuardOffsets = { (-2, 0), (2, 0), (0, 2) };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of guard mobs which spawn next to (and protect) each statue.
|
||||||
|
/// </summary>
|
||||||
|
private const int GuardsPerStatue = 3;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a destroyed statue's index (0..5) to the <see cref="MUnique.OpenMU.DataModel.Configuration.MagicEffectDefinition.Number"/>
|
/// Maps a destroyed statue's index (0..5) to the <see cref="MUnique.OpenMU.DataModel.Configuration.MagicEffectDefinition.Number"/>
|
||||||
@@ -108,37 +114,35 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
private static readonly TimeSpan BuffDuration = TimeSpan.FromMinutes(6);
|
private static readonly TimeSpan BuffDuration = TimeSpan.FromMinutes(6);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PLACEHOLDER coordinates: the Heykel Savasi map terrain is not final yet, so these positions are an
|
/// The FIXED positions of the red team's 7 statues (attacked by the blue team). All 7 are spawned and
|
||||||
/// approximate straight line from mid-map toward the red base (20, 20). Index 0 is the outermost statue
|
/// visible at battle start and can be broken in ANY order; the index (0..6) is retained only for the
|
||||||
/// (attacked first by the blue team), index 6 is the final statue, closest to the red base (win condition
|
/// per-break buff mapping (see <see cref="StatueBuffEffectNumbers"/>), not for spawn sequencing.
|
||||||
/// for blue once broken).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Point[] RedStatuePositions =
|
private static readonly Point[] RedStatuePositions =
|
||||||
{
|
{
|
||||||
new(42, 51),
|
new(43, 20),
|
||||||
new(42, 45),
|
new(56, 26),
|
||||||
new(42, 38),
|
new(72, 43),
|
||||||
new(42, 31),
|
new(43, 28),
|
||||||
new(42, 25),
|
new(43, 44),
|
||||||
new(42, 15),
|
new(29, 26),
|
||||||
new(42, 8),
|
new(14, 43),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PLACEHOLDER coordinates: the Heykel Savasi map terrain is not final yet, so these positions are an
|
/// The FIXED positions of the blue team's 7 statues (attacked by the red team). All 7 are spawned and
|
||||||
/// approximate straight line from mid-map toward the blue base (220, 220). Index 0 is the outermost statue
|
/// visible at battle start and can be broken in ANY order; the index (0..6) is retained only for the
|
||||||
/// (attacked first by the red team), index 6 is the final statue, closest to the blue base (win condition
|
/// per-break buff mapping (see <see cref="StatueBuffEffectNumbers"/>), not for spawn sequencing.
|
||||||
/// for red once broken).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Point[] BlueStatuePositions =
|
private static readonly Point[] BlueStatuePositions =
|
||||||
{
|
{
|
||||||
new(42, 53),
|
new(16, 61),
|
||||||
new(42, 59),
|
new(31, 77),
|
||||||
new(42, 66),
|
new(43, 72),
|
||||||
new(42, 73),
|
new(43, 58),
|
||||||
new(42, 79),
|
new(72, 60),
|
||||||
new(42, 87),
|
new(57, 76),
|
||||||
new(42, 94),
|
new(43, 82),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -180,6 +184,19 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<AttackableNpcBase, (HeykelSavasiTeam Defender, int Index)> _statues = new();
|
private readonly ConcurrentDictionary<AttackableNpcBase, (HeykelSavasiTeam Defender, int Index)> _statues = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps each spawned statue NPC to the number of its guard mobs which are still alive. A statue starts at
|
||||||
|
/// <see cref="GuardsPerStatue"/> and can only be damaged once this reaches 0 (see
|
||||||
|
/// <see cref="IsStatueAttackBlocked"/>); decremented by <see cref="OnMonsterDied"/> as guards die.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ConcurrentDictionary<AttackableNpcBase, int> _statueGuardsAlive = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps each spawned guard mob to the statue it protects, so <see cref="OnMonsterDied"/> can decrement the
|
||||||
|
/// right statue's living-guard count when a guard dies. The guard is removed once its death is processed.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ConcurrentDictionary<AttackableNpcBase, AttackableNpcBase> _guardToStatue = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pure statue-break progress/winner state, extracted into its own dependency-free type so it can be
|
/// Pure statue-break progress/winner state, extracted into its own dependency-free type so it can be
|
||||||
/// unit-tested without constructing a full <see cref="HeykelSavasiContext"/> (whose constructor needs a
|
/// unit-tested without constructing a full <see cref="HeykelSavasiContext"/> (whose constructor needs a
|
||||||
@@ -247,12 +264,29 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
public HeykelSavasiTeam GetTeam(Player player) => this._teams.TryGetValue(player, out var team) ? team : HeykelSavasiTeam.None;
|
public HeykelSavasiTeam GetTeam(Player player) => this._teams.TryGetValue(player, out var team) ? team : HeykelSavasiTeam.None;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if <paramref name="npc"/> is a Heykel Savasi statue belonging to the
|
/// Returns <c>true</c> if a hit on <paramref name="npc"/> by <paramref name="attacker"/> must be blocked.
|
||||||
/// same team as <paramref name="attacker"/> — such statues must be immune to friendly damage,
|
/// A statue is protected (undamageable) while EITHER it belongs to the attacker's own team (a player may
|
||||||
/// otherwise a player could destroy their own team's statue and credit the enemy.
|
/// never break their own team's statue and credit the enemy) OR any of its 3 guard mobs are still alive
|
||||||
|
/// (the guards must be cleared first). If <paramref name="npc"/> is not a tracked statue, the hit is never
|
||||||
|
/// blocked here (normal damage). Called from <c>AttackableNpcBase.AttackByAsync</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsFriendlyStatue(AttackableNpcBase npc, Player attacker)
|
/// <param name="npc">The NPC being attacked.</param>
|
||||||
=> this._statues.TryGetValue(npc, out var info) && info.Defender == this.GetTeam(attacker);
|
/// <param name="attacker">The attacking player.</param>
|
||||||
|
/// <returns><c>true</c> if the hit should be blocked; otherwise, <c>false</c>.</returns>
|
||||||
|
public bool IsStatueAttackBlocked(AttackableNpcBase npc, Player attacker)
|
||||||
|
{
|
||||||
|
if (!this._statues.TryGetValue(npc, out var info))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.Defender == this.GetTeam(attacker))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._statueGuardsAlive.TryGetValue(npc, out var aliveGuards) && aliveGuards > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the base spawn gate for the given team.
|
/// Gets the base spawn gate for the given team.
|
||||||
@@ -421,9 +455,13 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
// Re-show the HUD panel promptly after the battle-start map change.
|
// Re-show the HUD panel promptly after the battle-start map change.
|
||||||
await this.BroadcastHudStateAsync().ConfigureAwait(false);
|
await this.BroadcastHudStateAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// Spawn the first (outermost) statue of each team's line; the opposing team attacks it.
|
// Spawn ALL 7 statues (plus each statue's 3 guards) of both teams at their fixed positions, all
|
||||||
await this.SpawnStatueAsync(HeykelSavasiTeam.Red, 0).ConfigureAwait(false);
|
// visible from battle start; the opposing team may break them in any order (guards first).
|
||||||
await this.SpawnStatueAsync(HeykelSavasiTeam.Blue, 0).ConfigureAwait(false);
|
for (var i = 0; i < StatuesToBreak; i++)
|
||||||
|
{
|
||||||
|
await this.SpawnStatueAsync(HeykelSavasiTeam.Red, i).ConfigureAwait(false);
|
||||||
|
await this.SpawnStatueAsync(HeykelSavasiTeam.Blue, i).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -498,12 +536,38 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var attacker = Opponent(info.Defender);
|
// The statue is gone; drop its guard bookkeeping too (its guards were already dead - a statue can
|
||||||
this.HandleStatueDestroyed(info.Defender, info.Index, attacker, spawnNext: true);
|
// only be hit once its 3 guards fall - but this keeps the maps from leaking a stale entry).
|
||||||
|
this._statueGuardsAlive.TryRemove(npc, out _);
|
||||||
|
|
||||||
// Fire-and-forget the async side effects (buff application, spawning the next statue, or
|
var attacker = Opponent(info.Defender);
|
||||||
// finishing the event); the death event handler itself must stay synchronous.
|
|
||||||
_ = this.OnStatueDestroyedSideEffectsAsync(info.Defender, info.Index, attacker);
|
// Any-order, count-based progress: this break increments the attacker's running total (0..7),
|
||||||
|
// independent of which statue index fell.
|
||||||
|
this.HandleStatueDestroyed(info.Defender, info.Index, attacker, spawnNext: false);
|
||||||
|
var count = this.GetProgress(attacker);
|
||||||
|
|
||||||
|
// Fire-and-forget the async side effects (break announcement, buff application, or finishing the
|
||||||
|
// event); the death event handler itself must stay synchronous.
|
||||||
|
_ = this.OnStatueDestroyedSideEffectsAsync(attacker, count, e.KillerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// ADAMU-CUSTOM (Heykel Savasi): guards spawned by <see cref="SpawnStatueAsync"/> are ordinary
|
||||||
|
/// <see cref="Monster"/>s, so their <c>Died</c> event is auto-subscribed to this handler by
|
||||||
|
/// <see cref="MiniGameContext.OnObjectAddedToMapAsync"/>. When a guard dies we decrement the living-guard
|
||||||
|
/// count of the statue it protected; only once that count reaches 0 does <see cref="IsStatueAttackBlocked"/>
|
||||||
|
/// let players damage the statue.
|
||||||
|
/// </remarks>
|
||||||
|
protected override void OnMonsterDied(object? sender, DeathInformation e)
|
||||||
|
{
|
||||||
|
base.OnMonsterDied(sender, e);
|
||||||
|
|
||||||
|
if (sender is AttackableNpcBase guard && this._guardToStatue.TryRemove(guard, out var statue))
|
||||||
|
{
|
||||||
|
this._statueGuardsAlive.AddOrUpdate(statue, 0, (_, alive) => alive > 0 ? alive - 1 : 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -599,40 +663,48 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles the async side effects of a statue's destruction: applying the destruction buff to the
|
/// Handles the async side effects of a statue's destruction: announcing the break (running counter +
|
||||||
/// attacking team, finishing the event if that statue was the winning one, or spawning the defender's
|
/// killer name) to all event players, applying the count-based destruction buff to the attacking team,
|
||||||
/// next statue (plus guards) otherwise.
|
/// and finishing the event once the attacker has broken all <see cref="StatuesToBreak"/> of the
|
||||||
|
/// opponent's statues. Statues are no longer spawned here - all of them are spawned up-front at battle
|
||||||
|
/// start (see <see cref="OnGameStartAsync"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="defender">The team whose statue was destroyed.</param>
|
|
||||||
/// <param name="index">The index (0..6) of the destroyed statue in <paramref name="defender"/>'s line.</param>
|
|
||||||
/// <param name="attacker">The team which destroyed the statue.</param>
|
/// <param name="attacker">The team which destroyed the statue.</param>
|
||||||
private async ValueTask OnStatueDestroyedSideEffectsAsync(HeykelSavasiTeam defender, int index, HeykelSavasiTeam attacker)
|
/// <param name="count">The attacker's running number of statues broken so far (1..<see cref="StatuesToBreak"/>).</param>
|
||||||
|
/// <param name="killerName">The character name of the player who dealt the killing blow.</param>
|
||||||
|
private async ValueTask OnStatueDestroyedSideEffectsAsync(HeykelSavasiTeam attacker, int count, string killerName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// One-shot HUD broadcast: the statue's progress counter was already updated synchronously by
|
// One-shot HUD broadcast: the progress counter was already updated synchronously by
|
||||||
// HandleStatueDestroyed (called from OnDestructibleDied before this method was scheduled), so every
|
// HandleStatueDestroyed (called from OnDestructibleDied before this method was scheduled), so every
|
||||||
// player sees the new progress immediately rather than waiting for the next periodic tick.
|
// player sees the new progress immediately rather than waiting for the next periodic tick.
|
||||||
await this.BroadcastHudStateAsync().ConfigureAwait(false);
|
await this.BroadcastHudStateAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
await this.ApplyStatueBuffToTeamAsync(attacker, index).ConfigureAwait(false);
|
// Yellow center announcement to every event player: running counter + the destroyer's name.
|
||||||
|
var message = $"{count} Heykel {killerName} tarafindan yok edildi";
|
||||||
|
foreach (var player in this.PlayersOf(HeykelSavasiTeam.Red).Concat(this.PlayersOf(HeykelSavasiTeam.Blue)).ToList())
|
||||||
|
{
|
||||||
|
await player.InvokeViewPlugInAsync<MUnique.OpenMU.GameLogic.Views.IShowMessagePlugIn>(
|
||||||
|
p => p.ShowMessageAsync(message, MUnique.OpenMU.Interfaces.MessageType.GoldenCenter)).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count-based buff: 1st break -> buff index 0, ... 6th -> index 5, 7th -> none (bounds guard).
|
||||||
|
await this.ApplyStatueBuffToTeamAsync(attacker, count - 1).ConfigureAwait(false);
|
||||||
|
|
||||||
if (this.GetWinner() == attacker)
|
if (this.GetWinner() == attacker)
|
||||||
{
|
{
|
||||||
this.FinishEvent();
|
this.FinishEvent();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.SpawnStatueAsync(defender, index + 1).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.Logger.LogError(ex, "{context}: Error handling statue-destroyed side effects for defender {defender}, index {index}.", this, defender, index);
|
this.Logger.LogError(ex, "{context}: Error handling statue-destroyed side effects for attacker {attacker}, count {count}.", this, attacker, count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spawns <paramref name="defender"/>'s statue at <paramref name="index"/> in its line, plus 4 guard
|
/// Spawns <paramref name="defender"/>'s statue at <paramref name="index"/> in its line, plus 3 guard
|
||||||
/// mobs around it, using the same runtime-spawn API the map initializer itself uses
|
/// mobs around it, using the same runtime-spawn API the map initializer itself uses
|
||||||
/// (<see cref="IMapInitializer.InitializeSpawnAsync"/>; see also Castle Siege's
|
/// (<see cref="IMapInitializer.InitializeSpawnAsync"/>; see also Castle Siege's
|
||||||
/// <c>CastleSiegeEventPlugIn.SpawnCastleDefensesAsync</c> for the reference pattern). The spawned statue
|
/// <c>CastleSiegeEventPlugIn.SpawnCastleDefensesAsync</c> for the reference pattern). The spawned statue
|
||||||
@@ -654,10 +726,9 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each statue (and its guards) spawns at a RANDOM walkable spot anywhere on the map, so
|
// Statues are placed at FIXED coordinates and all spawned up-front, so both teams' 7 statues
|
||||||
// after breaking one statue players must hunt for the next one across the arena. Falls back
|
// are visible from battle start and can be broken in any order.
|
||||||
// to the fixed line position if the terrain can't yield a random coordinate.
|
var pos = positions[index];
|
||||||
var pos = this.Map.Terrain.RandomWalkableCoordinate ?? positions[index];
|
|
||||||
|
|
||||||
var statueDefinition = this._gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == StatueMonsterNumber);
|
var statueDefinition = this._gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == StatueMonsterNumber);
|
||||||
if (statueDefinition is null)
|
if (statueDefinition is null)
|
||||||
@@ -666,8 +737,8 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distinct spawn-index ranges per (defender, index), so repeated calls across the event
|
// Distinct spawn-index ranges per (defender, index), so the up-front spawns never collide:
|
||||||
// never collide: e.g. Red index 0 -> 1000..1004, Blue index 3 -> 2030..2034.
|
// e.g. Red index 0 -> 1000..1003, Blue index 3 -> 2030..2033.
|
||||||
var spawnIndexBase = ((int)defender * 1000) + (index * 10);
|
var spawnIndexBase = ((int)defender * 1000) + (index * 10);
|
||||||
|
|
||||||
var statueSpawnArea = new MonsterSpawnArea
|
var statueSpawnArea = new MonsterSpawnArea
|
||||||
@@ -683,11 +754,16 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
};
|
};
|
||||||
|
|
||||||
var statueNpc = await this._mapInitializer.InitializeSpawnAsync(spawnIndexBase, this.Map, statueSpawnArea, this).ConfigureAwait(false);
|
var statueNpc = await this._mapInitializer.InitializeSpawnAsync(spawnIndexBase, this.Map, statueSpawnArea, this).ConfigureAwait(false);
|
||||||
if (statueNpc is AttackableNpcBase attackable)
|
if (statueNpc is not AttackableNpcBase statue)
|
||||||
{
|
{
|
||||||
this._statues[attackable] = (defender, index);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._statues[statue] = (defender, index);
|
||||||
|
|
||||||
|
// This statue starts fully guarded; it becomes damageable only once all 3 guards are dead.
|
||||||
|
this._statueGuardsAlive[statue] = GuardsPerStatue;
|
||||||
|
|
||||||
var guardDefinition = this._gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GuardMonsterNumber);
|
var guardDefinition = this._gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GuardMonsterNumber);
|
||||||
if (guardDefinition is null)
|
if (guardDefinition is null)
|
||||||
{
|
{
|
||||||
@@ -710,17 +786,14 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
SpawnTrigger = SpawnTrigger.OnceAtEventStart,
|
SpawnTrigger = SpawnTrigger.OnceAtEventStart,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this._mapInitializer.InitializeSpawnAsync(spawnIndexBase + 1 + i, this.Map, guardSpawnArea, this).ConfigureAwait(false);
|
var guardNpc = await this._mapInitializer.InitializeSpawnAsync(spawnIndexBase + 1 + i, this.Map, guardSpawnArea, this).ConfigureAwait(false);
|
||||||
}
|
|
||||||
|
|
||||||
// Statues spawn at random spots, so announce the new statue's coordinates to every event
|
// Guards are ordinary Monsters; their Died event is auto-subscribed to OnMonsterDied by
|
||||||
// player as a yellow center (GoldenCenter) message — only players inside the event see it.
|
// MiniGameContext.OnObjectAddedToMapAsync. We only need to remember which statue each guards.
|
||||||
var teamName = defender == HeykelSavasiTeam.Red ? "RED" : "BLUE";
|
if (guardNpc is AttackableNpcBase guard)
|
||||||
var coordMessage = $"{teamName} STATUE appeared! Coordinate: {pos.X} , {pos.Y}";
|
{
|
||||||
foreach (var announcePlayer in this.PlayersOf(HeykelSavasiTeam.Red).Concat(this.PlayersOf(HeykelSavasiTeam.Blue)).ToList())
|
this._guardToStatue[guard] = statue;
|
||||||
{
|
}
|
||||||
await announcePlayer.InvokeViewPlugInAsync<MUnique.OpenMU.GameLogic.Views.IShowMessagePlugIn>(
|
|
||||||
p => p.ShowMessageAsync(coordMessage, MUnique.OpenMU.Interfaces.MessageType.GoldenCenter)).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1017,21 +1090,28 @@ public class HeykelSavasiContext : MiniGameContext
|
|||||||
public HeykelSavasiTeam GetWinner() => this._winner;
|
public HeykelSavasiTeam GetWinner() => this._winner;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers that <paramref name="attacker"/> destroyed the statue at <paramref name="index"/> in
|
/// Registers that <paramref name="attacker"/> destroyed one of the opponent's statues. Progress is a
|
||||||
/// the opponent's line. Once a winner is set, further registrations are ignored.
|
/// running COUNT (statues can be broken in any order), so this simply increments the attacker's total
|
||||||
|
/// by one; once the count reaches <see cref="StatuesToBreak"/> the attacker is recorded as the winner.
|
||||||
|
/// Once a winner is set, further registrations are ignored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">The index (0..6) of the destroyed statue.</param>
|
/// <param name="index">
|
||||||
|
/// The index of the destroyed statue in the defender's line. No longer used for sequencing (breaks are
|
||||||
|
/// order-independent); retained for signature/call-site compatibility only.
|
||||||
|
/// </param>
|
||||||
/// <param name="attacker">The team which destroyed the statue.</param>
|
/// <param name="attacker">The team which destroyed the statue.</param>
|
||||||
public void RegisterDestroyed(int index, HeykelSavasiTeam attacker)
|
public void RegisterDestroyed(int index, HeykelSavasiTeam attacker)
|
||||||
{
|
{
|
||||||
|
_ = index;
|
||||||
if (this._winner != HeykelSavasiTeam.None)
|
if (this._winner != HeykelSavasiTeam.None)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._progress[attacker] = index + 1;
|
var newCount = this.GetProgress(attacker) + 1;
|
||||||
|
this._progress[attacker] = newCount;
|
||||||
|
|
||||||
if (index + 1 >= StatuesToBreak)
|
if (newCount >= StatuesToBreak)
|
||||||
{
|
{
|
||||||
this._winner = attacker;
|
this._winner = attacker;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,10 +109,11 @@ public abstract class AttackableNpcBase : NonPlayerCharacter, IAttackable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ADAMU-CUSTOM: Heykel Savasi -> a statue is immune to damage from its own team (only the enemy may break it).
|
// ADAMU-CUSTOM: Heykel Savasi -> a statue is immune to damage from its own team (only the enemy may
|
||||||
|
// break it) AND while any of its 3 guard mobs are still alive (guards must be cleared first).
|
||||||
if (attacker is Player heykelStatueAttacker
|
if (attacker is Player heykelStatueAttacker
|
||||||
&& heykelStatueAttacker.CurrentMiniGame is MiniGames.HeykelSavasiContext heykelStatueGame
|
&& heykelStatueAttacker.CurrentMiniGame is MiniGames.HeykelSavasiContext heykelStatueGame
|
||||||
&& heykelStatueGame.IsFriendlyStatue(this, heykelStatueAttacker))
|
&& heykelStatueGame.IsStatueAttackBlocked(this, heykelStatueAttacker))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user