/// <summary> /// Calculates the furthest walkable point ot the given target mob. /// </summary> public AxialCoord?FurthestPointToTarget(CachedMob mob, CachedMob target) { var path = PrecomputedPathTo(mob.MobInstance.Coord, target.MobInstance.Coord); if (path.Count == 0 && mob.MobInstance.Coord.Distance(target.MobInstance.Coord) == 1) { return(null); } AxialCoord?furthestPoint = null; foreach (var coord in path) { int distance = Distance(mob.MobInstance.Coord, coord); var mobAtCoord = Game.State.AtCoord(coord, true); if (distance <= mob.MobInstance.Ap) { if (mobAtCoord == null) { furthestPoint = coord; } } else { break; } } return(furthestPoint); }
/// <summary> /// Generates possible direct ability use actions. /// </summary> public static bool GenerateDirectAbilityUse(GameInstance state, CachedMob mob, List <UctAction> result) { bool foundAbilityUse = false; var mobInfo = mob.MobInfo; var mobId = mob.MobId; foreach (var abilityId in mobInfo.Abilities) { if (!GameInvariants.IsAbilityUsableNoTarget(state, mobId, abilityId)) { continue; } foreach (var targetId in state.MobManager.Mobs) { if (GameInvariants.IsAbilityUsable(state, mob, state.CachedMob(targetId), abilityId)) { foundAbilityUse = true; var action = UctAction.AbilityUseAction(abilityId, mobId, targetId); GameInvariants.AssertValidAction(state, action); result.Add(action); } } } return(foundAbilityUse); }
/// <summary> /// Generates a number of defensive move actions based on a heatmap. /// </summary> public static void GenerateDefensiveMoveActions(GameInstance state, CachedMob mob, List <UctAction> result) { var heatmap = Heatmap.BuildHeatmap(state, null, false); var coords = new List <AxialCoord>(); var mobInstance = mob.MobInstance; var mobId = mob.MobId; foreach (var coord in heatmap.Map.AllCoords) { if (heatmap.Map[coord] != heatmap.MinValue) { continue; } if (state.Map[coord] == HexType.Wall) { continue; } if (state.State.AtCoord(coord, true).HasValue) { continue; } bool canMoveTo = state.Pathfinder.Distance(mobInstance.Coord, coord) <= mobInstance.Ap; if (!canMoveTo) { continue; } coords.Add(coord); } coords.Shuffle(); int maximumMoveActions = Math.Max(0, 3 - result.Count); for (int i = 0; i < Math.Min(coords.Count, maximumMoveActions); i++) { var action = UctAction.DefensiveMoveAction(mobId, coords[i]); GameInvariants.AssertValidAction(state, action); result.Add(action); } }
/// <summary> /// Calculates a move towards an enemy, or ends a turn if no such move is possible. /// </summary> private static UctAction PickMoveTowardsEnemyAction(GameInstance game, CachedMob mob, CachedMob possibleTarget) { foreach (var targetId in game.MobManager.Mobs) { var target = game.CachedMob(targetId); if (!GameInvariants.IsTargetableNoSource(game, mob, target)) { continue; } var action = FastMoveTowardsEnemy(game, mob, target); if (action.Type == UctActionType.Move) { return(action); } } return(UctAction.EndTurnAction()); }
public string BuildMessage(UctAction action, CachedMob mob, CachedMob target, AbilityInfo abilityInfo, int?moveCost) { if (action.Type == UctActionType.Null) { return(" "); } string str; switch (action.Type) { case UctActionType.AbilityUse: str = $"Did {abilityInfo.Dmg} damage for {abilityInfo.Cost} AP."; break; case UctActionType.AttackMove: str = $"Moved towards enemy for {moveCost} AP and did {abilityInfo.Dmg} damage for {abilityInfo.Cost} AP."; break; case UctActionType.Move: str = $"Moved from {mob.MobInstance.Coord} to {action.Coord} for {moveCost} AP."; break; case UctActionType.DefensiveMove: str = $"Is trying to hide at {action.Coord} for {moveCost} AP."; break; case UctActionType.EndTurn: throw new InvalidOperationException("End turn shouldn't be logged."); case UctActionType.Null: throw new InvalidOperationException("Null action should never be logged."); default: throw new ArgumentException($"Invalid action type ${action.Type}", nameof(action)); } return($"{string.Format("{0,3}", _actionIndex)}. {str}"); }
public void Log(TeamColor currentTeam, UctAction action, CachedMob mob, CachedMob target, AbilityInfo abilityInfo, int?moveCost) { GameManager.CurrentSynchronizationContext.Post(_ => { var entry = new HistoryLogEntry(ActionCount++, currentTeam, action, mob, target, abilityInfo, moveCost, _assetManager); _log.Add(entry); _childrenPlaceholder.AddChild(entry); if (_log.Count > MaxHistorySize) { _log.RemoveAt(0); _childrenPlaceholder.Children.RemoveAt(0); } }, null); }
/// <summary> /// Returns an action that moves towards an enemy as fast as possible. /// </summary> public static UctAction FastMoveTowardsEnemy(GameInstance state, CachedMob mob, CachedMob target) { var pathfinder = state.Pathfinder; var moveTarget = pathfinder.FurthestPointToTarget(mob, target); if (moveTarget != null && pathfinder.Distance(mob.MobInstance.Coord, moveTarget.Value) <= mob.MobInstance.Ap) { return(UctAction.MoveAction(mob.MobId, moveTarget.Value)); } else if (moveTarget == null) { // Intentionally doing nothing return(UctAction.EndTurnAction()); } else { Utils.Log(LogSeverity.Debug, nameof(AiRuleBasedController), $"Move failed since target is too close, source {mob.MobInstance.Coord}, target {target.MobInstance.Coord}"); return(UctAction.EndTurnAction()); } }
/// <summary> /// Generates possible attack move actions. /// </summary> public static void GenerateAttackMoveActions(GameInstance state, CachedMob mob, List <UctAction> result) { var mobInfo = mob.MobInfo; var mobInstance = mob.MobInstance; foreach (var enemyId in state.MobManager.Mobs) { var target = state.CachedMob(enemyId); if (!GameInvariants.IsTargetableNoSource(state, mob, target)) { continue; } AxialCoord myCoord = mobInstance.Coord; AxialCoord?closestCoord = null; int? distance = null; int? chosenAbilityId = null; foreach (var coord in state.Map.EmptyCoords) { if (!state.Map.IsVisible(coord, target.MobInstance.Coord)) { continue; } var possibleMoveAction = GameInvariants.CanMoveTo(state, mob, coord); if (possibleMoveAction.Type == UctActionType.Null) { continue; } Debug.Assert(possibleMoveAction.Type == UctActionType.Move); foreach (var abilityId in mobInfo.Abilities) { if (!GameInvariants.IsAbilityUsableFrom(state, mob, coord, target, abilityId)) { continue; } int myDistance = state.Pathfinder.Distance(myCoord, coord); if (!closestCoord.HasValue) { chosenAbilityId = abilityId; closestCoord = coord; distance = myDistance; } else if (distance.Value > myDistance) { chosenAbilityId = abilityId; closestCoord = coord; distance = myDistance; } } } if (closestCoord.HasValue) { if (Constants.AttackMoveEnabled) { var action = UctAction.AttackMoveAction(mob.MobId, closestCoord.Value, chosenAbilityId.Value, target.MobId); var after = ActionEvaluator.F(state, action.ToPureMove()); GameInvariants.AssertValidAbilityUseAction(after, action.ToPureAbilityUse()); GameInvariants.AssertValidAction(state, action); result.Add(action); } else { var action = UctAction.MoveAction(mob.MobId, closestCoord.Value); GameInvariants.AssertValidAction(state, action); result.Add(action); } } } }
public HistoryLogEntry(int actionIndex, TeamColor currentTeam, UctAction action, CachedMob mob, CachedMob target, AbilityInfo abilityInfo, int?moveCost, AssetManager assetManager) { _actionIndex = actionIndex; _assetManager = assetManager; Renderer = this; CurrentTeam = currentTeam; Message = BuildMessage(action, mob, target, abilityInfo, moveCost); }