Animate enemy flee results (#8748)

This commit is contained in:
2026-07-21 09:18:15 -07:00
committed by GitHub
parent 54283cbeb1
commit 68873cc219
3 changed files with 83 additions and 21 deletions
@@ -194,7 +194,10 @@ namespace eagle0.Tests {
new Dictionary<int, string>(),
new Dictionary<int, string>());
shardokModel.UnitsById[fleeingUnitId] = new UnitViewWithName(
new Net.Eagle0.Shardok.Api.UnitView { UnitId = fleeingUnitId },
new Net.Eagle0.Shardok.Api.UnitView {
UnitId = fleeingUnitId,
Location = new Net.Eagle0.Shardok.Common.Coords { Row = 3, Column = 4 }
},
null,
null,
null);
@@ -238,6 +241,8 @@ namespace eagle0.Tests {
Assert.AreEqual(1, shardokModel.History.Count);
Assert.IsFalse(shardokModel.UnitsById.ContainsKey(fleeingUnitId));
Assert.AreEqual(3, shardokModel.ActionSourceCoords[0].Row);
Assert.AreEqual(4, shardokModel.ActionSourceCoords[0].Column);
}
private static AvailableCommands
@@ -895,16 +895,15 @@ namespace Shardok {
} else if (
isOtherPlayer && animationType != AnimationType.None &&
historyEntry.Actor != null) {
// Other player's action with animation
if (Model.UnitsById.TryGetValue(
historyEntry.Actor.Value,
out var actorUnit)) {
// For Move actions, use stored source coords (unit's pre-move location)
// For other actions, use current unit location
int sourceIndex =
Model.MoveSourceCoords.TryGetValue(i, out var sourceCoords)
? MapCoordsToGridIndex(sourceCoords)
: MapCoordsToGridIndex(actorUnit.Location);
// Other player's action with animation. Prefer the actor's position from
// before the result diff, since the result may move or remove the unit.
Model.UnitsById.TryGetValue(historyEntry.Actor.Value, out var actorUnit);
bool hasSourceCoords =
Model.ActionSourceCoords.TryGetValue(i, out var sourceCoords);
if (hasSourceCoords || actorUnit != null) {
int sourceIndex = hasSourceCoords
? MapCoordsToGridIndex(sourceCoords)
: MapCoordsToGridIndex(actorUnit.Location);
// Get target - either coords (archery) or unit location (melee)
int targetIndex = -1;
@@ -920,6 +919,14 @@ namespace Shardok {
targetIndex = MapCoordsToGridIndex(targetUnit.Location);
}
if (targetIndex < 0 && (animationType == AnimationType.FleeSuccess ||
animationType == AnimationType.FleeCaptured)) {
targetIndex = FindNearestOpponentGridIndex(
hasSourceCoords ? sourceCoords : actorUnit.Location,
historyEntry.Player.Value);
if (targetIndex < 0) { targetIndex = sourceIndex; }
}
if (targetIndex >= 0) {
if (UsesTwoStageSounds(animationType)) {
StartCoroutine(PlayTwoStageSequence(
@@ -939,8 +946,8 @@ namespace Shardok {
}
}
} else {
// Unit removed from model (e.g. fled off map) — play sound
// without animation since we can't determine coordinates
// The actor was already unavailable before this result, so its
// animation position cannot be reconstructed.
if (soundManager.IsTwoStageActionType(type)) {
StartCoroutine(PlayTwoStageSoundSequence(animationType, type));
} else {
@@ -2280,6 +2287,52 @@ namespace Shardok {
return bestIndex;
}
/// <summary>
/// Finds the nearest visible unit opposed to the acting player. Hostility values are
/// relative to the local player, so self and allied players oppose enemies and vice
/// versa. Used to give untargeted flee results a believable direction.
/// </summary>
int FindNearestOpponentGridIndex(Coords sourceCoords, PlayerId actorPlayerId) {
var actorPlayer = Model.players.Find(player => player.playerId == actorPlayerId);
bool actorPlayerKnown =
Model.players.Exists(player => player.playerId == actorPlayerId);
int bestIndex = -1;
int bestDistance = int.MaxValue;
foreach (var unit in Model.UnitsById.Values) {
if (unit.PlayerId == actorPlayerId) continue;
bool isOpponent;
if (actorPlayerKnown) {
isOpponent = AreOpposingHostilities(
actorPlayer.hostility,
(Net.Eagle0.Common.Hostility)unit.HostilityStatus);
} else {
isOpponent = true;
}
if (!isOpponent) continue;
int dist = HexDistance(sourceCoords, unit.Location);
if (dist < bestDistance) {
bestDistance = dist;
bestIndex = MapCoordsToGridIndex(unit.Location);
}
}
return bestIndex;
}
static bool AreOpposingHostilities(
Net.Eagle0.Common.Hostility first,
Net.Eagle0.Common.Hostility second) {
bool firstIsFriendly = first == Net.Eagle0.Common.Hostility.SelfHostility ||
first == Net.Eagle0.Common.Hostility.AlliedHostility;
bool secondIsFriendly = second == Net.Eagle0.Common.Hostility.SelfHostility ||
second == Net.Eagle0.Common.Hostility.AlliedHostility;
return (firstIsFriendly && second == Net.Eagle0.Common.Hostility.EnemyHostility) ||
(secondIsFriendly && first == Net.Eagle0.Common.Hostility.EnemyHostility);
}
/// <summary>
/// Public entry point for testing animations with their correct sound flow.
/// Calls PlayAnimationAndSound with no unit info, so animations that need
@@ -106,10 +106,11 @@ public class ShardokGameModel : eagle.IShardokGameStateProvider {
public List<ActionResultView> History { get; private set; }
/// <summary>
/// Tracks the source coordinates for Move actions, keyed by history index.
/// Used for animations since the model is fully updated before animations play.
/// Tracks each action actor's coordinates before its diff is applied, keyed by history index.
/// Used for animations since the model is fully updated before animations play and an action
/// may remove its actor (for example, when a unit flees or is captured).
/// </summary>
public Dictionary<int, Coords> MoveSourceCoords { get; } = new();
public Dictionary<int, Coords> ActionSourceCoords { get; } = new();
public PlayerId PlayerId { get; private set; }
public GameStatus GameStatus { get; private set; }
@@ -175,7 +176,7 @@ public class ShardokGameModel : eagle.IShardokGameStateProvider {
UnitsById.Clear();
ReserveUnitsById.Clear();
UnitPlayers.Clear();
MoveSourceCoords.Clear();
ActionSourceCoords.Clear();
AvailableCommands = new List<CommandDescriptor>();
_recommendedStartingPositionsApplied = false;
GameStatus = new GameStatus { State = GameStatus.Types.State.SetUp };
@@ -489,11 +490,14 @@ public class ShardokGameModel : eagle.IShardokGameStateProvider {
}
private void HandleNewHistoryEntry(ActionResultView entry) {
// For Move actions, record the source coordinates BEFORE applying the diff
// This is needed for animations since all diffs are applied before animations play
if (entry.Type == ActionType.Move && entry.Actor != null) {
// Record the actor's coordinates BEFORE applying the diff. All diffs are applied before
// animations play, and some results remove the actor from UnitsById entirely.
if (entry.Actor != null) {
if (UnitsById.TryGetValue(entry.Actor.Value, out var actorUnit)) {
MoveSourceCoords[History.Count] = actorUnit.Location;
ActionSourceCoords[History.Count] = new Coords {
Row = actorUnit.Location.Row,
Column = actorUnit.Location.Column
};
}
}