private IEnumerator resetRound() { if (PerceptorEvaluator != null && PerceptorEvaluator.isActiveAndEnabled) { yield return(new WaitUntil(() => PerceptorEvaluator.READY)); } // Reset all cards from the discard back into the deck foreach (CardController cc in DiscardPile.GetComponentsInChildren <CardController>()) { cc.MoveTo(Deck.transform); } // Reset all players foreach (PlayerController p in Players) { p.Reset(); } // Reset the discard pile counters (the deck is reset when Shuffle() is called during STAGE_INITIAL) DiscardPile.Reset(); TurnHistory.Clear(); // Display play statistics, if any if (PosterioriPerceptor.StatisticOfPlay != null) { AIUtil.DisplayMatrix("Statistics of play", PosterioriPerceptor.StatisticOfPlay); } // Wait untill all cards are back in the deck before reseting the overall game state yield return(new WaitUntil(() => (AllCardsDown))); CurrentStage = STAGE_INITIAL; }
bool findClosestWalkablePointToDestination(HexGrid grid, Vector3 destination, Vector3 startLoc, List <PathNode> pathNodes, float destinationRadius, out Vector3 foundPoint, out PathNode foundPathNode) { bool foundAny = false; foundPoint = Vector3.zero; foundPathNode = null; float closestDistanceToDestination = float.MaxValue; float closestDistanceToStart = float.MaxValue; // used for tie-breaking between nodes of equal distance to destination for (int nodeIndex = 0; nodeIndex < pathNodes.Count; ++nodeIndex) { PathNode node = pathNodes[nodeIndex]; float distToDest = AIUtil.Get2DDistanceBetweenVector3s(node.Position, destination); if (distToDest <= destinationRadius) { float distToStart = AIUtil.Get2DDistanceBetweenVector3s(node.Position, startLoc); if ((distToDest < closestDistanceToDestination) || ((distToDest == closestDistanceToDestination) && (distToStart < closestDistanceToStart))) { foundAny = true; foundPathNode = node; foundPoint = node.Position; closestDistanceToDestination = distToDest; closestDistanceToStart = distToStart; } } } return(foundAny); }
public static void Postfix(AIUtil __instance, ref bool __result, AbstractActor attacker, ICombatant target, Vector3 position, List <AbstractActor> allies) { //LowVisibility.Logger.Debug("AIUtil:UnitHasVisibilityToTargetFromPosition:post - entered."); bool alliesHaveVis = false; for (int i = 0; i < allies.Count; i++) { //if (allies[i].VisibilityCache.VisibilityToTarget(target).VisibilityLevel == VisibilityLevel.LOSFull) { if (allies[i].VisibilityCache.VisibilityToTarget(target).VisibilityLevel == VisibilityLevel.Blip0Minimum) { alliesHaveVis = true; } } if (alliesHaveVis) { VisibilityLevel visibilityToTargetWithPositionsAndRotations = attacker.Combat.LOS.GetVisibilityToTargetWithPositionsAndRotations(attacker, position, target); //__result = visibilityToTargetWithPositionsAndRotations >= VisibilityLevel.LOSFull; __result = visibilityToTargetWithPositionsAndRotations >= VisibilityLevel.Blip0Minimum; } else { __result = true; } //LowVisibility.Logger.Debug($"AIUtil:UnitHasVisibilityToTargetFromPosition:post - result is:{__result}"); }
public float EvaluateUnit(AbstractActor unit) { var hostiles = AIUtil.HostilesToUnit(unit); if (hostiles.Count == 0) { return(float.MinValue); } var minDistance = float.MaxValue; foreach (var hostile in hostiles) { if (hostile.IsDead) { continue; } var distance = Vector3.Distance(unit.CurrentPosition, hostile.CurrentPosition); minDistance = Mathf.Min(minDistance, distance); } // ReSharper disable once CompareOfFloatsByEqualityOperator if (minDistance == float.MaxValue) { return(float.MinValue); } return(minDistance); }
// On a knock-out, reduce the dimension of the joint distribution array and renormalize it public void KnockOutFilter(int SittingOrder, int DiscardedValue) { Debug.Assert(SittingOrder != MySittingOrder); Debug.Assert(DiscardedValue >= CardController.VALUE_GUARD && DiscardedValue <= CardController.VALUE_PRINCESS); int CardIndex = DiscardedValue - 1; int HiddenHandIndex = GetHiddenHandIndex(SittingOrder); // Account for the discared card AccountForCard(DiscardedValue); // Merge down the current matrix to one dimension lower, taking the slice that corresponds to the knocked-out player's actual hand if (CurrentOpponentCount == 3) { TwoOpponentsHandsDistribution = ThreeOpponentsHandsDistribution.GetSlice(HiddenHandIndex, CardIndex); TwoOpponentsHandsDistribution.Renormalize(); // Also, we move out the knocked-out player's sitting order to the last place of the HiddenHands array AIUtil.ShiftToLast(ref HiddenHands, HiddenHandIndex); } else if (CurrentOpponentCount == 2) { SingleOpponentHandDistribution = TwoOpponentsHandsDistribution.GetSlice(HiddenHandIndex, CardIndex); SingleOpponentHandDistribution.Renormalize(); AIUtil.ShiftToLast(ref HiddenHands, HiddenHandIndex); } // Update player situation CurrentOpponentCount -= 1; PlayerIsKnockedOut[SittingOrder] = true; }
protected override IEnumerator PickAMove() { // Wait until the perceptor has finished working yield return(new WaitUntil(() => MyPerceptor.READY)); // Get available moves List <MoveData> availableMoves = myHand.GetLegalMoves(Game, this); availableMoves.AddRange(justDrawn.GetLegalMoves(Game, this)); // Calculate utilities of every move MoveData.DualUtility[] moveUtility = new MoveData.DualUtility[availableMoves.Count]; MoveData.DualUtility highestUtility = MoveData.DualUtility.MinValue; for (int i = 0; i < availableMoves.Count; i++) { moveUtility[i] = GetMoveUtility(availableMoves[i]); if (moveUtility[i].CompareTo(highestUtility) > 0) { highestUtility = moveUtility[i]; } } // Calculate cutoff for random selection float cutoff = Mathf.Max(0, RELATIVE_CUTOFF_POINT * highestUtility.Utility); // Return a weighted random of the resulting moves myNextMove = availableMoves[AIUtil.GetWeightedRandom(moveUtility, highestUtility.Rank, cutoff)]; }
static void Postfix(AIUtil __instance, AbstractActor unit, AttackType attackType, List <Weapon> weaponList, ICombatant target, Vector3 attackPosition, Vector3 targetPosition, bool useRevengeBonus, AbstractActor unitForBVContext, float __result) { Mod.AILog.Info?.Write($"=== Expected damage of: {__result} for attacker {unit.DistinctId()} using attackType: " + $"{attackType} versus target: {target.DistinctId()}"); }
private float GetExpectedDamageForAllWeaponsVsTarget(AbstractActor attackingUnit, ICombatant targetUnit, bool targetIsEvasive) { if (!AIUtil.UnitHasVisibilityToTargetFromCurrentPosition(attackingUnit, targetUnit)) { // Our team can't see this hostile. return(0.0f); } float damage = 0; for (int weaponIndex = 0; weaponIndex < attackingUnit.Weapons.Count; ++weaponIndex) { Weapon weapon = attackingUnit.Weapons[weaponIndex]; if (weapon.CanFire && weapon.WillFireAtTarget(targetUnit)) { int numShots = weapon.ShotsWhenFired; float toHit = weapon.GetToHitFromPosition(targetUnit, 1, attackingUnit.CurrentPosition, targetUnit.CurrentPosition, true, targetIsEvasive); // TODO (DAVE) : 1 = attacking a single target. Once AI can multi-target, this should reflect the number of targets float damagePerShot = weapon.DamagePerShotFromPosition(MeleeAttackType.NotSet, attackingUnit.CurrentPosition, targetUnit); float heatDamagePerShot = 1 + (weapon.HeatDamagePerShot); // Factoring in heatDamagePerShot. +1, since most weapons deal 0 heat Dmg damage += numShots * toHit * damagePerShot * heatDamagePerShot; } } return(damage); }
// Duplication of HBS code, avoiding prefix=true for now. public static void Postfix(ref BehaviorTreeResults __result, string ___name, BehaviorTree ___tree, AbstractActor ___unit) { Mod.Log.Info?.Write("CJMCN:T - entered"); Mech mech = ___unit as Mech; if (mech != null && mech.WorkingJumpjets > 0) { string stayInsideRegionGUID = RegionUtil.GetStayInsideRegionGUID(___unit); float acceptableHeat = AIUtil.GetAcceptableHeatLevelForMech(mech); float currentHeat = (float)mech.CurrentHeat; Mod.Log.Info?.Write($"CJMCN:T - === actor:{CombatantUtils.Label(mech)} has currentHeat:{currentHeat} and acceptableHeat:{acceptableHeat}"); List <PathNode> sampledPathNodes = ___unit.JumpPathing.GetSampledPathNodes(); Mod.Log.Info?.Write($"CJMCN:T - calculating {sampledPathNodes.Count} nodes"); for (int i = 0; i < sampledPathNodes.Count; i++) { Vector3 candidatePos = sampledPathNodes[i].Position; float distanceBetween2D = AIUtil.Get2DDistanceBetweenVector3s(candidatePos, ___unit.CurrentPosition); float distanceBetween3D = Vector3.Distance(candidatePos, ___unit.CurrentPosition); Mod.Log.Info?.Write($"CJMCN:T - calculated distances 2D:'{distanceBetween2D}' 3D:'{distanceBetween3D} "); if (distanceBetween2D >= 1f) { float magnitude = (candidatePos - ___unit.CurrentPosition).magnitude; float jumpHeat = (float)mech.CalcJumpHeat(magnitude); Mod.Log.Info?.Write($"CJMCN:T - calculated jumpHeat:'{jumpHeat}' from magnitude:'{magnitude}. "); Mod.Log.Info?.Write($"CJMCN:T - comparing heat: [jumpHeat:'{jumpHeat}' + currentHeat:'{currentHeat}'] <= acceptableHeat:'{acceptableHeat}. "); if (jumpHeat + (float)mech.CurrentHeat <= acceptableHeat) { if (stayInsideRegionGUID != null) { MapTerrainDataCell cellAt = ___unit.Combat.MapMetaData.GetCellAt(candidatePos); if (cellAt != null) { MapEncounterLayerDataCell mapEncounterLayerDataCell = cellAt.MapEncounterLayerDataCell; if (mapEncounterLayerDataCell != null && mapEncounterLayerDataCell.regionGuidList != null && !mapEncounterLayerDataCell.regionGuidList.Contains(stayInsideRegionGUID)) { // Skip this loop iteration if Mod.Log.Info?.Write($"CJMCN:T - candidate outside of constraint region, ignoring."); goto CANDIDATE_OUTSIDE_REGION; } } } Mod.Log.Info?.Write($"CJMCN:T - adding candidate position:{candidatePos}"); ___tree.movementCandidateLocations.Add(new MoveDestination(sampledPathNodes[i], MoveType.Jumping)); } } CANDIDATE_OUTSIDE_REGION :; } } // Should already be set by prefix method //__result = BehaviorTreeResults(BehaviorNodeState.Success); }
/// <summary> /// Computes a partial hand distribution of the specified player, /// for an assumed world state and the observation of which card they played. /// </summary> /// <param name="PlayedCardIndex">The index of the card that was played by the opponent.</param> /// <param name="HandIndex">The index of the card that the other player is assumed to have in their hand.</param> /// <param name="OutDistribution">A reference to a probability distribution that containts return values.</param> protected void UpdatePartialHandDistribution(int PlayedCardIndex, int PlayerHand, ref Distribution1D OutDistribution) { // Don't continue if priori probability is zero float PrioriProbability = SingleOpponentHandDistribution[PlayerHand]; if (PrioriProbability <= 0) { return; } // Make a copy of the unaccounted card counters and update it with the "virtually" accounted-for cards Array.Copy(CountUnaccountedForCards, 1, virtualRemainingCards, 0, CARD_VECTOR_LENGTH); if (--virtualRemainingCards[PlayerHand] < 0) { return; // If a counter goes below zero, this is an impossible case, so return without an update } // Prepare computation int remainingDeckSize = AIUtil.SumUpArray(virtualRemainingCards); Debug.Assert(remainingDeckSize > 0); // Now loop through each deck card and see if the card that was played could have been played from hand or from deck for (int dc = 0; dc < CARD_VECTOR_LENGTH; dc++) { if (virtualRemainingCards[dc] > 0 && (PlayedCardIndex == PlayerHand || PlayedCardIndex == dc)) { // Compute which card the player has left in their hand, given PlayedCardIndex int otherCard = (PlayedCardIndex == PlayerHand) ? dc : PlayerHand; // Calculate the joint probability of such play OutDistribution[otherCard] += PrioriProbability * virtualRemainingCards[dc] / remainingDeckSize * PosterioriPerceptor.LikelihoodOfPlay[PlayedCardIndex + 1, otherCard + 1]; } } }
//public static List<AttackEvaluation> EvaluateAttacks(AbstractActor attacker, ICombatant target, // List<List<CondensedWeapon>>[] weaponSetListByAttack, Vector3 attackPosition, Vector3 targetPosition, // bool targetIsEvasive) { // ConcurrentBag<AttackEvaluation> allResults = new ConcurrentBag<AttackEvaluation>(); // // List 0 is ranged weapons, 1 is melee+support, 2 is DFA+support // for (int i = 0; i < 3; i++) { // List<List<CondensedWeapon>> weaponSetsByAttackType = weaponSetListByAttack[i]; // string attackLabel = "ranged attack"; // if (i == 1) { attackLabel = "melee attacks"; } // if (i == 2) { attackLabel = "DFA attacks"; } // Mod.Log.Debug?.Write($"Evaluating {weaponSetsByAttackType.Count} {attackLabel}"); // if (weaponSetsByAttackType != null) { // //ConcurrentQueue<List<CondensedWeapon>> workQueue = new ConcurrentQueue<List<CondensedWeapon>>(); // //for (int j = 0; j < weaponSetsByAttackType.Count; j++) { // // workQueue.Enqueue(weaponSetsByAttackType[j]); // //} // //void evaluateWeaponSet() { // // Mod.Log.Debug?.Write($" New action started."); // // SpinWait spin = new SpinWait(); // // while (true) { // // if (workQueue.TryDequeue(out List<Weapon> weaponSet)) { // // AttackEvaluator.AttackEvaluation attackEvaluation = new AttackEvaluator.AttackEvaluation(); // // attackEvaluation.WeaponList = weaponSet; // // attackEvaluation.AttackType = (AIUtil.AttackType)i; // // attackEvaluation.HeatGenerated = (float)AIUtil.HeatForAttack(weaponSet); // // if (unit is Mech mech) { // // attackEvaluation.HeatGenerated += (float)mech.TempHeat; // // attackEvaluation.HeatGenerated -= (float)mech.AdjustedHeatsinkCapacity; // // } // // attackEvaluation.ExpectedDamage = AIUtil.ExpectedDamageForAttack(unit, attackEvaluation.AttackType, weaponSet, target, attackPosition, targetPosition, true, unit); // // attackEvaluation.lowestHitChance = AIUtil.LowestHitChance(weaponSet, target, attackPosition, targetPosition, targetIsEvasive); // // allResults.Add(attackEvaluation); // // Mod.Log.Debug?.Write($"Processed a weaponSet, {workQueue.Count} remaining"); // // } else { // // Mod.Log.Debug?.Write($"Failed to dequeue, {workQueue.Count} remaining"); // // if (workQueue.Count == 0) { break; } else { spin.SpinOnce(); } // // } // // } // // Mod.Log.Debug?.Write($" New action ending."); // //}; // //Parallel.Invoke(evaluateWeaponSet, evaluateWeaponSet, evaluateWeaponSet); // for (int j = 0; j < weaponSetsByAttackType.Count; j++) { // List<CondensedWeapon> weaponList = weaponSetsByAttackType[j]; // Mod.Log.Debug?.Write($"Evaluating {weaponList?.Count} weapons for a {attackLabel}"); // AttackEvaluator.AttackEvaluation attackEvaluation = new AttackEvaluator.AttackEvaluation(); // attackEvaluation.AttackType = (AIUtil.AttackType)i; // attackEvaluation.HeatGenerated = (float)AIHelper.HeatForAttack(weaponList); // if (attacker is Mech mech) { // attackEvaluation.HeatGenerated += (float)mech.TempHeat; // attackEvaluation.HeatGenerated -= (float)mech.AdjustedHeatsinkCapacity; // } // attackEvaluation.ExpectedDamage = AIHelper.ExpectedDamageForAttack(attacker, attackEvaluation.AttackType, // weaponList, target, attackPosition, targetPosition, true, attacker); // attackEvaluation.lowestHitChance = AIHelper.LowestHitChance(weaponList, target, attackPosition, targetPosition, targetIsEvasive); // // Expand the list to all weaponDefs, not our condensed ones // Mod.Log.Debug?.Write($"Expanding weapon list for AttackEvaluation"); // List<Weapon> aeWeaponList = new List<Weapon>(); // foreach (CondensedWeapon cWeapon in weaponList) { // List<Weapon> cWeapons = cWeapon.condensedWeapons; // if (cWeapon.ammoAndMode != null) { // foreach (Weapon wep in cWeapons) { // Mod.Log.Debug?.Write($" -- Setting ammoMode to: {cWeapon.ammoAndMode.ammoId}_{cWeapon.ammoAndMode.modeId} for weapon: {wep.UIName}"); // CleverGirlHelper.ApplyAmmoMode(wep, cWeapon.ammoAndMode); // } // } // aeWeaponList.AddRange(cWeapon.condensedWeapons); // } // Mod.Log.Debug?.Write($"List size {weaponList?.Count} was expanded to: {aeWeaponList?.Count}"); // attackEvaluation.WeaponList = aeWeaponList; // allResults.Add(attackEvaluation); // } // } // } // List<AttackEvaluator.AttackEvaluation> sortedResults = new List<AttackEvaluator.AttackEvaluation>(); // sortedResults.AddRange(allResults); // sortedResults.Sort((AttackEvaluator.AttackEvaluation a, AttackEvaluator.AttackEvaluation b) => a.ExpectedDamage.CompareTo(b.ExpectedDamage)); // sortedResults.Reverse(); // return sortedResults; //} public static bool MeleeDamageOutweighsRisk(Mech attacker, ICombatant target) { float attackerMeleeDam = AIUtil.ExpectedDamageForMeleeAttackUsingUnitsBVs(attacker, target, attacker.CurrentPosition, target.CurrentPosition, false, attacker); if (attackerMeleeDam <= 0f) { Mod.Log.Debug?.Write("Attacker has no expected damage, melee is too risky."); return(false); } Mech targetMech = target as Mech; if (targetMech == null) { Mod.Log.Debug?.Write("Target has no expected damage, melee is safe."); return(true); } // Use the target mech's position, because if we melee the attacker they can probably get to us float targetMeleeDam = AIUtil.ExpectedDamageForMeleeAttackUsingUnitsBVs(targetMech, attacker, targetMech.CurrentPosition, targetMech.CurrentPosition, false, attacker); float meleeDamageRatio = attackerMeleeDam / targetMeleeDam; float meleeDamageRatioCap = AIHelper.GetBehaviorVariableValue(attacker.BehaviorTree, BehaviorVariableName.Float_MeleeDamageRatioCap).FloatVal; Mod.Log.Debug?.Write($" meleeDamageRatio: {meleeDamageRatio} = target: {targetMeleeDam} / attacker: {attackerMeleeDam} vs. cap: {meleeDamageRatioCap}"); return(meleeDamageRatio > meleeDamageRatioCap); }
public void moveAll() { foreach (Player player in playerList) { player.Move(); } if (projectiles.Count != 0) { foreach (Projectile proj in projectiles) { if (proj.Active) { proj.Move(); } else { removeProjList.Add(proj); } } } if (enemyList.Count != 0) { foreach (Enemy enemy in enemyList) { if (!enemy.TargetPlayer.Equals("")) { enemy.Rotation = AIUtil.getAnglePointingAt(enemy.Position, getPlayer(enemy.TargetPlayer).Position); enemy.Accelerate(); enemy.Move(); } } } }
public static void Postfix(AIUtil __instance, ref bool __result, AbstractActor attacker, ICombatant target) { //LowVisibility.Logger.Debug("AIUtil:UnitHasVisibilityToTargetFromCurrentPosition:post - entered."); //__result = attacker.VisibilityToTargetUnit(target) == VisibilityLevel.LOSFull; __result = attacker.VisibilityToTargetUnit(target) >= VisibilityLevel.Blip0Minimum; //LowVisibility.Logger.Debug($"AIUtil:UnitHasVisibilityToTargetFromCurrentPosition:post - result is:{__result}"); }
public override void FixedUpdate() { if (Patrol) { AIUtil.Patrol(true, this); } base.FixedUpdate(); }
private static Vector3?GetEstimatedPath(AbstractActor actor, out List <Vector3> path) { PathNode closestNode = null; var closestNodeDist = float.MaxValue; var destRadius = GetMinWeaponRange(actor); path = null; var enemies = actor.Combat.GetAllEnemiesOf(actor); if (enemies.Count == 0) { Main.HBSLog?.LogWarning("FilterToAdjacentSlowestMoveNode: no enemies"); } foreach (var enemy in enemies) { if (enemy.IsDead) { continue; } var clippedEnemyPos = RegionUtil.MaybeClipMovementDestinationToStayInsideRegion(actor, enemy.CurrentPosition); var curPath = DynamicLongRangePathfinder.GetPathToDestination(clippedEnemyPos, actor.MovementCaps.MaxWalkDistance, actor, false, destRadius); if (curPath == null || curPath.Count == 0) { Main.HBSLog?.LogWarning("FilterToAdjacentSlowestMoveNode: didn't get a path to unit"); continue; } var nodeOnCurPath = AIUtil.GetPrunedClosestValidPathNode(actor, actor.Pathing.CurrentGrid, actor.Combat, actor.MovementCaps.MaxWalkDistance, curPath); if (nodeOnCurPath == null) { Main.HBSLog?.LogWarning("FilterToAdjacentSlowestMoveNode: didn't get a node on the current path"); continue; } var distanceFromEnemy = Vector3.Distance(nodeOnCurPath.Position, clippedEnemyPos); if (distanceFromEnemy >= closestNodeDist) { continue; } path = curPath; closestNode = nodeOnCurPath; closestNodeDist = distanceFromEnemy; } return(closestNode?.Position); }
public static BTResult Swim(Animal agent, Vector2 direction, bool wandering, AnimalState state, bool surface, int tries = 10) { var targetPos = AIUtil.FindTargetSwimPosition(agent.Position, 5.0f, 20.0f, direction, 90, 360, tries, surface).XYZi; if (targetPos == agent.Position) { return(BTResult.Failure("target position is current position.")); } // Avoid fish swimming too close to coast line // TODO: Avoid building routes near coast, cache available points far away from coast if (!agent.Species.CanSwimNearCoast) { var waterHeight = World.World.GetWaterHeight(targetPos.XZ); var isNearCoast = ((WorldPosition3i)targetPos).SpiralOutXZIter(3).Any(groundPos => World.World.GetBlock((WrappedWorldPosition3i)groundPos.X_Z(waterHeight)).Is <Solid>()); if (isNearCoast) { return(BTResult.Failure("target position is too close to coast line")); } } var targetBlock = World.World.GetBlock(targetPos); // If an animal can't float on water surface - move it a block below highest water block TODO: make them move on underwater ground // TODO: Remove after pathfinder improvements if (!agent.Species.FloatOnSurface && targetBlock is WaterBlock && World.World.GetBlock(targetPos + Vector3i.Up).Is <Empty>()) { targetPos += Vector3i.Down; } //if (targetBlock.Is<Solid>()) targetPos += Vector3i.Up; if (targetBlock.Is <Empty>()) { targetPos += Vector3i.Down; // Fail if target position is too shallow if (World.World.GetBlock(targetPos).Is <Solid>()) { return(BTResult.Failure("target position is too thin")); } } // Clamp current position to ground or water, if can't float on water surface - stay below water height TODO: make them move on underwater ground var pos = World.World.ClampToWaterHeight(agent.Position.XYZi); // TODO: Remove after pathfinder improvements if (!agent.Species.FloatOnSurface && pos.y == World.World.GetWaterHeight(agent.Position.XZi)) { pos += Vector3i.Down; } var route = Route.Basic(agent.Species.GetTraversalData(wandering), agent.FacingDir, pos + Vector3i.Down, targetPos); //For fish, we need to compensate, since route is built from positions of the ground below var routeTime = route.IsValid ? agent.SetRoute(route, state, null) : 0f; return(routeTime < float.Epsilon ? BTResult.Failure("route not set") : BTResult.Success($"swimming path")); }
public float[] GetCardProbabilitiesInDeck() { float[] result = new float[CardController.VALUE_PRINCESS + 1]; float sum = 0; for (int i = CardController.VALUE_GUARD; i <= CardController.VALUE_PRINCESS; i++) { result[i] = GetCardProbabilityInDeck(i); sum += result[i]; } Debug.AssertFormat(sum <= 0 || AIUtil.Approx(sum, 1f) || MyController.Game.Deck.CountCardsLeft < 1, "{0}: Deck card probabilities don't sum up to 1! ({1})", MyController, sum); return(result); }
static bool ValidateMultiAttackOrder(MultiTargetAttackOrderInfo order, AbstractActor unit) { AIUtil.LogAI("Multiattack validation", unit); for (int subAttackIndex = 0; subAttackIndex < order.SubTargetOrders.Count; ++subAttackIndex) { AttackOrderInfo subOrder = order.SubTargetOrders[subAttackIndex]; AIUtil.LogAI(string.Format("SubAttack #{0}: target {1} {2}", subAttackIndex, subOrder.TargetUnit.GUID, subOrder.TargetUnit.DisplayName)); foreach (Weapon w in subOrder.Weapons) { AIUtil.LogAI(string.Format(" Weapon {0}", w.Name)); } } List <string> targetGUIDs = new List <string>(); foreach (AttackOrderInfo subOrder in order.SubTargetOrders) { string thisGUID = subOrder.TargetUnit.GUID; if (targetGUIDs.IndexOf(thisGUID) != -1) { // found duplicated target GUIDs AIUtil.LogAI("Multiattack error: Duplicated target GUIDs", unit); return(false); } foreach (Weapon w in subOrder.Weapons) { if (!w.CanFire) { AIUtil.LogAI("Multiattack error: weapon that cannot fire", unit); return(false); } ICombatant target = subOrder.TargetUnit; if (!unit.Combat.LOFCache.UnitHasLOFToTargetAtTargetPosition( unit, target, w.MaxRange, unit.CurrentPosition, unit.CurrentRotation, target.CurrentPosition, target.CurrentRotation, w.IndirectFireCapable)) { AIUtil.LogAI("Multiattack error: weapon that cannot fire", unit); return(false); } } } AIUtil.LogAI("Multiattack validates OK", unit); return(true); }
public static bool Prefix(BehaviorNode __instance, ref BehaviorTreeResults __result, ref BehaviorTree ___tree, ref AbstractActor ___unit) { try { List <AbstractActor> allAlliesOf = ___unit.Combat.GetAllAlliesOf(___unit); AuraBubble sensors = ___unit.sensorAura(); for (int index1 = 0; index1 < ___tree.enemyUnits.Count; ++index1) { ICombatant enemyUnit = ___tree.enemyUnits[index1]; AbstractActor abstractActor = enemyUnit as AbstractActor; float magnitude = (enemyUnit.CurrentPosition - ___unit.CurrentPosition).magnitude; if (AIUtil.UnitHasVisibilityToTargetFromPosition(___unit, enemyUnit, ___unit.CurrentPosition, allAlliesOf)) { if (___unit.CanEngageTarget(enemyUnit) || ___unit.CanDFATargetFromPosition(enemyUnit, ___unit.CurrentPosition)) { __result = new BehaviorTreeResults(BehaviorNodeState.Success); return(false); } if ((double)magnitude <= (double)___unit.MaxWalkDistance) { __result = new BehaviorTreeResults(BehaviorNodeState.Success); return(false); } if (abstractActor != null && abstractActor.IsGhosted) { float num = Mathf.Lerp(___unit.MaxWalkDistance, ___unit.MaxSprintDistance, ___unit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_SignalInWeapRngWhenEnemyGhostedWithinMoveDistance).FloatVal); float range = sensors.collider.radius; if ((double)Vector3.Distance(___unit.CurrentPosition, abstractActor.CurrentPosition) - (double)range >= (double)num) { continue; } } for (int index2 = 0; index2 < ___unit.Weapons.Count; ++index2) { Weapon weapon = ___unit.Weapons[index2]; if (weapon.CanFire && (double)weapon.MaxRange >= (double)magnitude) { __result = new BehaviorTreeResults(BehaviorNodeState.Success); return(false); } } } } __result = new BehaviorTreeResults(BehaviorNodeState.Failure); return(false); } catch (Exception e) { Log.Debug?.Write(e.ToString() + "\n"); __result = new BehaviorTreeResults(BehaviorNodeState.Failure); return(false); } }
float findDistanceFromGuardLance(Vector3 position, Lance guardLance) { float bestDistance = float.MaxValue; for (int i = 0; i < guardLance.unitGuids.Count; ++i) { string unitGUID = guardLance.unitGuids[i]; AbstractActor guardUnitActor = unit.Combat.ItemRegistry.GetItemByGUID <AbstractActor>(unitGUID); if ((guardUnitActor != null) && (!guardUnitActor.IsDead)) { bestDistance = Mathf.Min(bestDistance, AIUtil.Get2DDistanceBetweenVector3s(guardUnitActor.CurrentPosition, position)); } } return(bestDistance); }
RouteGameLogic getRoute() { BehaviorVariableValue variableValue = tree.GetBehaviorVariableValue(BehaviorVariableName.String_RouteGUID); if (variableValue == null) { // no route AIUtil.LogAI("No behavior variable for route GUID found"); return(null); } string routeGUID = variableValue.StringVal; return(RoutingUtil.FindRouteByGUID(tree, routeGUID)); }
private static List <WeaponAttackEval> FilterForHeatBudget(List <WeaponAttackEval> attacks, AttackDetails details) { if (!(details.Attacker is Mech)) { Mod.Log.Debug?.Write("Attacker is not a mech, returning all weapons from heat filter."); return(attacks); } // Build a state for the atacker defining what level of heat/damage is acceptable. Mech attackerMech = details.Attacker as Mech; float currentHeat = attackerMech == null ? 0f : (float)attackerMech.CurrentHeat; // TODO: Improve this / link to CBTBE float acceptableHeat = attackerMech == null ? float.MaxValue : AIUtil.GetAcceptableHeatLevelForMech(attackerMech); float heatBudget = acceptableHeat - currentHeat; Mod.Log.Debug?.Write($"Allowing up to {heatBudget} additional points of heat."); // TODO: Allow for BVars influence on this // Now start optimizing. First, sort the list by heatratio Mod.Log.Debug?.Write($"Sorting {attacks.Count} weapons by heatRatio"); attacks.Sort( (wae1, wae2) => wae1.DirectDmgPerHeat.CompareTo(wae2.DirectDmgPerHeat) ); Mod.Log.Debug?.Write($" ..Done."); // What's the highest damage solution we can get without overheating? List <WeaponAttackEval> filteredAttacks = new List <WeaponAttackEval>(); foreach (WeaponAttackEval wae in attacks) { Mod.Log.Debug?.Write($"Evaluating weapon {wae?.Weapon?.UIName} with generated heat: {wae?.Weapon?.HeatGenerated} versus budget"); if (wae.Weapon.HeatGenerated <= heatBudget) { Mod.Log.Debug?.Write($"Adding weapon {wae.Weapon.UIName}"); filteredAttacks.Add(wae); heatBudget -= wae.Weapon.HeatGenerated; } else { Mod.Log.Debug?.Write($"Skipping weapon {wae.Weapon.UIName}"); } } Mod.Log.Debug?.Write("Done budgeting weapons"); return(filteredAttacks); }
// Precomputes the static arrays for quicker access protected static void PrecomputeStaticArrays() { // Initialized base deck distribution BaseDeckDistribution = new Distribution1D(CARD_VECTOR_LENGTH); for (int CardIndex = 0; CardIndex < CARD_VECTOR_LENGTH; CardIndex++) { BaseDeckDistribution[CardIndex] = ((float)GameController.CARD_COUNT[CardIndex + 1]) / GameController.TOTAL_CARD_COUNT; } // Initialize base joint hand distribution BaseThreeOpponentsHandsDistribution = new Distribution3D(CARD_VECTOR_LENGTH, CARD_VECTOR_LENGTH, CARD_VECTOR_LENGTH); // Optimization for the following calculation: int[] tempRemainingCards = new int[CARD_VECTOR_LENGTH]; // We can't use virtualRemainingCards in a static method... Array.Copy(GameController.CARD_COUNT, 1, tempRemainingCards, 0, CARD_VECTOR_LENGTH); float SumOfArray = 0, ScalingFactor = 1f / GameController.TOTAL_CARD_COUNT / (GameController.TOTAL_CARD_COUNT - 1) / (GameController.TOTAL_CARD_COUNT - 2); // Calculate base joint hand distribution for (int Hand1Index = 0; Hand1Index < CARD_VECTOR_LENGTH; Hand1Index++) { // Account for this value of the first hand float Hand1Prob = ScalingFactor * tempRemainingCards[Hand1Index]; tempRemainingCards[Hand1Index] -= 1; // And loop through all possible second and third hidden hands for (int Hand2Index = 0; Hand2Index < CARD_VECTOR_LENGTH; Hand2Index++) { // Account for this value of the second hand float Hand2JointProb = Hand1Prob * tempRemainingCards[Hand2Index]; tempRemainingCards[Hand2Index] -= 1; // And loop through all possible third hidden hands for (int Hand3Index = 0; Hand3Index < CARD_VECTOR_LENGTH; Hand3Index++) { // If this is an impossible case, just jump to the next iteration if (Hand2JointProb <= 0 || tempRemainingCards[Hand3Index] <= 0) { continue; } // Otherwise, calculate the joint probability of this case and store it float jointProb = Hand2JointProb * tempRemainingCards[Hand3Index]; BaseThreeOpponentsHandsDistribution[Hand1Index, Hand2Index, Hand3Index] = jointProb; SumOfArray += jointProb; } // Reset card counts for the next iteration tempRemainingCards[Hand2Index] += 1; } // Reset card counts for the next iteration tempRemainingCards[Hand1Index] += 1; } Debug.Assert(AIUtil.Approx(SumOfArray, 1f)); }
public float[] GetCardProbabilitiesInHand(PlayerController Player) { float[] result = new float[CardController.VALUE_PRINCESS + 1]; // If no player is specified (when called for a non-targeted card), just return an empty array if (Player != null) { float sum = 0; for (int i = CardController.VALUE_GUARD; i <= CardController.VALUE_PRINCESS; i++) { result[i] = GetCardProbabilityInHand(Player, i); sum += result[i]; } Debug.AssertFormat(sum <= 0 || AIUtil.Approx(sum, 1f), "{0}: Hand card probabilities of {2} don't sum up to 1! ({1})", MyController, sum, Player); } return(result); }
// CLONE OF HBS CODE - LIKELY BRITTLE! public static bool ShouldUnitUseInspire(AbstractActor unit) { float num = AIUtil.CalcMaxInspirationDelta(unit, true); AITeam aiteam = unit.team as AITeam; if (aiteam == null || !unit.CanBeInspired) { return(false); } if (num < AIHelper.GetBehaviorVariableValue(unit.BehaviorTree, BehaviorVariableName.Float_MinimumInspirationDamage).FloatVal) { return(false); } float num2 = 1f - aiteam.GetInspirationWindow(); return(num > aiteam.GetInspirationTargetDamage() * num2); }
public static bool Prefix(AITeam __instance, List <AbstractActor> unusedUnits, ref AbstractActor __result) { try { if (!__instance.CanEntireEnemyTeamBeGhosted()) { __result = null; return(false); } AbstractActor result = (AbstractActor)null; float minDistance = float.MaxValue; for (int index1 = 0; index1 < unusedUnits.Count; ++index1) { AbstractActor unusedUnit = unusedUnits[index1]; List <AbstractActor> enemies = AIUtil.HostilesToUnit(unusedUnit); AuraBubble sensors = unusedUnit.sensorAura(); if (sensors == null) { continue; } ; for (int index2 = 0; index2 < enemies.Count; ++index2) { AbstractActor enemy = enemies[index2]; if (enemy.HasECMAbilityInstalled) { float floatVal = unusedUnit.BehaviorTree.GetBehaviorVariableValue(BehaviorVariableName.Float_SignalInWeapRngWhenEnemyGhostedWithinMoveDistance).FloatVal; float maxMoveDist = Mathf.Lerp(unusedUnit.MaxWalkDistance, unusedUnit.MaxSprintDistance, floatVal); float range = sensors.collider.radius; float needMoveDist = Vector3.Distance(unusedUnit.CurrentPosition, enemy.CurrentPosition) - range; if ((double)needMoveDist <= (double)maxMoveDist && (double)needMoveDist < (double)minDistance) { result = unusedUnit; minDistance = needMoveDist; } } } } __result = result; return(false); }catch (Exception e) { Log.WriteCritical(e.ToString() + "\n"); __result = null; return(false); } }
// If the animal is stuck somewhere it shouldn't be help it escape or die if its very far from walkable terrain public static BTResult LandAnimalUnStuckOrDie(Animal agent) { const int maxUnStuckDistance = 20; var nearestValid = RouteManager.NearestWalkableXYZ(agent.Position.XYZi, maxUnStuckDistance); if (nearestValid == agent.Position.WorldPosition3i) { agent.NextTick = WorldTime.Seconds + 10f; return(BTResult.Failure()); } if (!nearestValid.IsValid) { // cheating failed? time to die! agent.Kill(); return(BTResult.Success()); } // ignore terrain, path directly to a valid area, but only if noone is around *shy* or if he's really trying if (agent.TryGetMemory(Animal.TriesToUnStuckMemory, out int tries)) { tries += 1; } agent.SetMemory(Animal.TriesToUnStuckMemory, tries); if (!NetObjectManager.GetObjectsWithin(agent.Position.XZ, 20).OfType <Player>().Any() || tries >= 3) { var route = AIUtil.GetRoute(agent.FacingDir, agent.Position, (Vector3)nearestValid, agent.Species.GetTraversalData(true), null, null); if (!route.IsValid) { agent.NextTick = WorldTime.Seconds + 10; return(BTResult.Failure()); } // Proceed with route with time length more than 0 var timeToFinishRoute = agent.SetRoute(route, AnimalState.Wander); if (timeToFinishRoute > float.Epsilon) { agent.RemoveMemory(Animal.TriesToUnStuckMemory); return(BTResult.Success()); } } agent.NextTick = WorldTime.Seconds + 10; return(BTResult.Failure()); }
// Convenience function for easier deck distribution calculation // Note that because it is CUMULATIVE, DeckDistributionBuffer contains intermediary results and may not be cleared! protected void CumulativeUpdateDeckDistribution(float Probability, int[] RemainingCards, ref Distribution1D DeckDistributionBuffer) { Debug.Assert(RemainingCards.Length == CARD_VECTOR_LENGTH); Debug.Assert(DeckDistributionBuffer.Length == CARD_VECTOR_LENGTH); if (Probability > 0) { int sum = AIUtil.SumUpArray(RemainingCards); if (sum > 0) { for (int i = 0; i < CARD_VECTOR_LENGTH; i++) { if (RemainingCards[i] > 0) { DeckDistributionBuffer[i] += Probability * RemainingCards[i] / sum; } } } } }
/// <summary> /// Computes a partial hand distribution of the specified player, /// for an assumed world state and the observation of which card they played. /// </summary> /// <param name="TurnData">Complete data of the turn being analyzed.</param> /// <param name="Hand0">The index of the card the first of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand1">The index of the card the second of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand2">The index of the card the third of the three remaining opponents is asssumed to hold.</param> /// <param name="OutDistribution">A reference to a probability distribution that containts return values.</param> protected void UpdatePartialHandDistribution(MoveData TurnData, int Hand0, int Hand1, int Hand2, ref Distribution3D OutDistribution) { // Don't continue if priori probability is zero float PrioriProbability = ThreeOpponentsHandsDistribution[Hand0, Hand1, Hand2]; if (PrioriProbability <= 0) { return; } // Make a copy of the unaccounted card counters and update it with the "virtually" accounted-for cards Array.Copy(CountUnaccountedForCards, 1, virtualRemainingCards, 0, CARD_VECTOR_LENGTH); if (--virtualRemainingCards[Hand0] < 0 || --virtualRemainingCards[Hand1] < 0 || --virtualRemainingCards[Hand2] < 0) { return; // If a counter goes below zero, this is an impossible case, so return without an update } // Parse the turn data int PlayerIndex = GetHiddenHandIndex(TurnData.Player.SittingOrder), PlayedCardIndex = TurnData.Card.Value - 1, TargetIndex = (TurnData.Target == null || TurnData.Target.SittingOrder == MySittingOrder) ? -1 : GetHiddenHandIndex(TurnData.Target.SittingOrder); // Determine which card in the current data belongs to PlayerIndex int[] idx = new int[] { Hand0, Hand1, Hand2 }; int playerHand = idx[PlayerIndex], targetHand = TargetIndex != -1 ? idx[TargetIndex] : -1; // Prepare computation int remainingDeckSize = AIUtil.SumUpArray(virtualRemainingCards); Debug.Assert(remainingDeckSize > 0); // Now loop through each deck card and see if the card that was played could have been played from hand or from deck for (int dc = 0; dc < CARD_VECTOR_LENGTH; dc++) { if (virtualRemainingCards[dc] > 0 && (PlayedCardIndex == playerHand || PlayedCardIndex == dc)) { // Compute which card the player has left in their hand, given PlayedCardIndex int otherCard = (PlayedCardIndex == playerHand) ? dc : playerHand; // Prepare the index values for the update idx[PlayerIndex] = otherCard; // Calculate the joint probability of such play and increment the output array OutDistribution[idx[0], idx[1], idx[2]] += PrioriProbability * virtualRemainingCards[dc] / remainingDeckSize * GetLikelihoodOfPlay(TurnData, otherCard + 1, targetHand + 1); } } }
/// <summary> /// Computes a partial hand distribution of the specified player's new hand after being targeted by the Prince, /// for an assumed world state and the observation of which card they previously discarded. /// </summary> /// <param name="PlayerIndex">Index of the player for whom the computation is performed.</param> /// <param name="Hand0">The index of the card the first of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand1">The index of the card the second of the three remaining opponents is asssumed to hold.</param> /// <param name="Hand2">The index of the card the third of the three remaining opponents is asssumed to hold.</param> /// <param name="DiscardedIndex">The observed index of the card that the player had discarded.</param> /// <param name="OutDistribution">A reference to a probability distribution that containts return values.</param> protected void PrinceDiscardAndDrawPartialUpdate(int PlayerIndex, int Hand0, int Hand1, int Hand2, int DiscardedIndex, ref Distribution3D OutDistribution) { // Don't continue if priori probability is zero float PrioriProbability = ThreeOpponentsHandsDistribution[Hand0, Hand1, Hand2]; if (PrioriProbability <= 0) { return; } // Determine which card in the current data belongs to PlayerIndex int[] idx = new int[] { Hand0, Hand1, Hand2 }; int playerHand = idx[PlayerIndex]; if (playerHand != DiscardedIndex) { return; } // Make a copy of the unaccounted card counters and update it with the "virtually" accounted-for cards Array.Copy(CountUnaccountedForCards, 1, virtualRemainingCards, 0, CARD_VECTOR_LENGTH); if (--virtualRemainingCards[Hand0] < 0 || --virtualRemainingCards[Hand1] < 0 || --virtualRemainingCards[Hand2] < 0) { return; // If a counter goes below zero, this is an impossible case, so return without an update } // Prepare computation int remainingDeckSize = AIUtil.SumUpArray(virtualRemainingCards); if (remainingDeckSize < 1) { return; // The game is over, anyway. } Debug.Assert(remainingDeckSize > 0); // Now loop through each deck card and see if the card that was played could have been played from hand or from deck for (int dc = 0; dc < CARD_VECTOR_LENGTH; dc++) { if (virtualRemainingCards[dc] > 0) { // Calculate the joint probability of such play and increment the output array idx[PlayerIndex] = dc; OutDistribution[idx[0], idx[1], idx[2]] += PrioriProbability * virtualRemainingCards[dc] / remainingDeckSize; } } }