/// <summary> /// Figures out the best location to use this Ability at. Default implementation returns the enemy target, closest ally /// or the caster. /// </summary> /// <param name="abilityDef">Ability Def for the AI.</param> /// <param name="pawn">Pawn to take in account.</param> /// <returns>Targeting location or Pawn.</returns> public virtual LocalTargetInfo TargetAbilityFor(AbilityAIDef abilityDef, Pawn pawn) { if (abilityDef.usedOnCaster) { return(pawn); } if (abilityDef.canTargetAlly) { return(GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.OnCell, TraverseParms.For(TraverseMode.NoPassClosedDoors), abilityDef.maxRange, thing => AbilityUtility.AreAllies(pawn, thing))); } if (pawn.mindState.enemyTarget != null && pawn.mindState.enemyTarget is Pawn targetPawn) { if (!targetPawn.Dead) { return(pawn.mindState.enemyTarget); } } else { if (pawn.mindState.enemyTarget != null && !(pawn.mindState.enemyTarget is Corpse)) { return(pawn.mindState.enemyTarget); } } return(null); }
/// <summary> /// Picks the best candidate Pawn out of up to 10 other. /// </summary> /// <param name="abilityDef">Ability Def to optionally take in account.</param> /// <param name="pawn">Pawn using the Ability.</param> /// <returns>Candidate Pawn if found, null if not.</returns> public virtual Pawn PickBestClosestPawn(AbilityAIDef abilityDef, Pawn pawn) { Pawn bestPawn = null; var bestHealth = 1f; var checkedThings = new List <Thing>(); //Check 10 closest for optimal target. for (var i = 0; i < 10; i++) { var foundThing = GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), PathEndMode.OnCell, TraverseParms.For(TraverseMode.NoPassClosedDoors), abilityDef.maxRange, thing => pawn != thing && !checkedThings.Contains(thing) && AbilityUtility.AreAllies(pawn, thing)); //Found no valid candidate. if (foundThing == null) { break; } checkedThings.Add(foundThing); if (foundThing is Pawn foundPawn) { if (foundPawn.health.summaryHealth.SummaryHealthPercent < bestHealth) { bestPawn = foundPawn; bestHealth = foundPawn.health.summaryHealth.SummaryHealthPercent; } } } return(bestPawn); }
public override LocalTargetInfo TargetAbilityFor(AbilityAIDef abilityDef, Pawn pawn) { var bestPawn = PickBestClosestPawn(abilityDef, pawn); if (bestPawn == null) { return(base.TargetAbilityFor(abilityDef, pawn)); } return(bestPawn); }
public override float PowerScoreFor(AbilityAIDef abilityDef, Pawn pawn) { var baseScore = abilityDef.power; //Grab enemies \ allies var potentionalTargets = new List <Thing>(GrabTargets(abilityDef, pawn, CustomGrabTargetsPredicate(abilityDef, pawn), MaxTargetsToCheck)); //Add self if can target allies. if (abilityDef.canTargetAlly) { potentionalTargets.Add(pawn); } //Get the highest intersecting target. var targetInfos = new List <LocalTargetInfo>(); foreach (var target in potentionalTargets) { targetInfos.Add(new LocalTargetInfo(target)); } var bestTarget = AbilityMaths.PickMostRadialIntersectingTarget(targetInfos, abilityDef.abilityRadius); //If we found no valid target, return negative power. if (bestTarget == LocalTargetInfo.Invalid) { return(-abilityDef.power); } //Calculate final score from best target. var finalScore = baseScore; foreach (var targetPawn in AbilityUtility.GetPawnsInsideRadius(bestTarget, pawn.Map, abilityDef.abilityRadius, predPawn => abilityDef.abilityRadiusNeedSight && GenSight.LineOfSight(pawn.Position, predPawn.Position, pawn.Map, true) || abilityDef.abilityRadiusNeedSight == false)) { if (targetPawn.HostileTo(pawn) || targetPawn.AnimalOrWildMan() ) //Hostile pawns or animals increase score. { finalScore += abilityDef.power; } else //Friendly pawns decrease score. { finalScore -= abilityDef.power; } } //Log.Message("AbilityWorker_AreaOfEffect, finalScore=" + finalScore); return(finalScore); }
public override bool CanPawnUseThisAbility(AbilityAIDef abilityDef, Pawn pawn, LocalTargetInfo target) { //If no best pawn was found, then we should not bother using ability. var bestPawn = PickBestClosestPawn(abilityDef, pawn); if (bestPawn == null) { return(false); } return(base.CanPawnUseThisAbility(abilityDef, pawn, target)); }
/// <summary> /// Final check to say whether the Pawn can use this Ability on the target or self. Default implementation returns true /// for co-ordinates and true if the enemy is NOT Downed. /// </summary> /// <param name="abilityDef">Ability Def for the AI.</param> /// <param name="pawn">Pawn to take in account.</param> /// <param name="target">Target this ability is aiming at.</param> /// <returns>True if we can use this Ability. False if not.</returns> public virtual bool CanPawnUseThisAbility(AbilityAIDef abilityDef, Pawn pawn, LocalTargetInfo target) { if (target.HasThing) { var targetPawn = target.Thing as Pawn; if (!abilityDef.canTargetAlly) { return(!targetPawn.Downed); } } return(true); }
public override LocalTargetInfo TargetAbilityFor(AbilityAIDef abilityDef, Pawn pawn) { //Grab enemies \ allies List <Thing> potentionalTargets = new List <Thing>(GrabTargets(abilityDef, pawn, CustomGrabTargetsPredicate(abilityDef, pawn), MaxTargetsToCheck)); //Add self if can target allies. if (abilityDef.canTargetAlly) { potentionalTargets.Add(pawn); } //Get the highest intersecting target. List <LocalTargetInfo> targetInfos = new List <LocalTargetInfo>(); foreach (Thing target in potentionalTargets) { targetInfos.Add(new LocalTargetInfo(target)); } LocalTargetInfo bestTarget = AbilityMaths.PickMostRadialIntersectingTarget(targetInfos, abilityDef.abilityRadius); return(bestTarget); }
/// <summary> /// Recursively travels through this decision node sub nodes. Not stack friendly. /// </summary> /// <param name="caster">Caster to recurse for.</param> /// <returns>Ability if found. Null if none is found.</returns> public virtual AbilityAIDef RecursivelyGetAbility(Pawn caster) { AbilityAIDef result = TryPickAbility(caster); if (result != null) { return(result); } if (CanContinueTraversing(caster)) { foreach (AbilityDecisionNode node in subNodes) { result = node.RecursivelyGetAbility(caster); if (result != null) { return(result); } } } return(null); }
/// <summary> /// Grab all viable target candidates. /// </summary> /// <param name="abilityDef">Ability Def to take in account.</param> /// <param name="pawn">Caster Pawn.</param> /// <param name="customPredicate">If set it overrides the default predicate.</param> /// <param name="pawnsToTest">How many pawns to test at max before stopping. Default is 30.</param> /// <returns>Things that are viable.</returns> public virtual IEnumerable <Thing> GrabTargets(AbilityAIDef abilityDef, Pawn pawn, Predicate <Thing> customPredicate = null, int pawnsToTest = 30) { //Make a list of candidates. List <Thing> potentionalTargets = new List <Thing>(); Predicate <Thing> pawnPredicate = null; if (customPredicate != null) { pawnPredicate = customPredicate; } else if (abilityDef.canTargetAlly) { pawnPredicate = delegate(Thing thing) { //Count own faction and faction whose goodwill they got above 50% as allies. if (AbilityUtility.AreAllies(pawn, thing)) { return(true); } return(false); } } ; else { pawnPredicate = delegate(Thing thing) { Pawn thingPawn = thing as Pawn; //Count anything hostile as a target. if (thingPawn != null) { if (!thingPawn.Downed && GenHostility.HostileTo(thing, pawn)) { return(true); } else if (GenHostility.HostileTo(thing, pawn)) { return(true); } } return(false); } }; //Max 'pawnsToTest' shall we grab. for (int i = 0; i < pawnsToTest; i++) { Thing grabResult = GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), Verse.AI.PathEndMode.OnCell, TraverseParms.For(TraverseMode.NoPassClosedDoors), abilityDef.maxRange, thing => pawn != thing && !potentionalTargets.Contains(thing) && (thing.Position - pawn.Position).LengthHorizontal >= abilityDef.minRange && pawnPredicate(thing)); //If found nothing, then break. if (grabResult == null) { break; } potentionalTargets.Add(grabResult); yield return(grabResult); } } }
/// <summary> /// Custom overridable Predicate for classes expanding on this algorithm. /// </summary> /// <param name="abilityDef">Ability Def to take in account.</param> /// <param name="pawn">Caster Pawn.</param> /// <returns>Optional predicate.</returns> public virtual Predicate <Thing> CustomGrabTargetsPredicate(AbilityAIDef abilityDef, Pawn pawn) { return(null); }
protected override Job TryGiveJob(Pawn pawn) { //Do we have at least one elegible profile? var profiles = pawn.EligibleAIProfiles(); /*StringBuilder builder = new StringBuilder("profiles = "); * * foreach(AbilityUserAIProfileDef profile in profiles) * { * builder.Append(profile.defName + ", "); * } * * Log.Message(builder.ToString());*/ if (profiles != null && profiles.Count() > 0) { foreach (var profile in profiles) { if (profile != null) { //Traverse the decision tree. //List<AbilityDecisionNode> currentNodes = new List<AbilityDecisionNode>(); //List<AbilityDecisionNode> nextNodes = new List<AbilityDecisionNode>(); //Seed root. //nextNodes.Add(profile.decisionTree); //Result AbilityAIDef to use. AbilityAIDef useThisAbility = null; if (profile.decisionTree != null) { useThisAbility = profile.decisionTree.RecursivelyGetAbility(pawn); } //Debug /*int nodesTraversed = 0; * AbilityDecisionNode lastNode = null; * * //Flat recursive iteration * do * { * //Add from next list to current list. * currentNodes.AddRange(nextNodes); * nextNodes.Clear(); * * //Check if we can continue traversing on the current level. * foreach (AbilityDecisionNode currentNode in currentNodes) * { * nodesTraversed++; * * if (currentNode.CanContinueTraversing(pawn)) * nextNodes.AddRange(currentNode.subNodes); * * //Try picking an ability. * useThisAbility = currentNode.TryPickAbility(pawn); * * //Found ability to use. * if (useThisAbility != null) * { * lastNode = currentNode; * break; * } * } * * //Found ability to use. * if (useThisAbility != null) * break; * * //Clear current set. * currentNodes.Clear(); * } while (nextNodes.Count > 0);*/ //Debug //if (useThisAbility != null) // Log.Message("JobGiver_AIAbilityUser.TryGiveJob for '" + pawn.ThingID + "' with ability: " + useThisAbility.defName + ", while traversing " + nodesTraversed + " nodes."); //else // Log.Message("JobGiver_AIAbilityUser.TryGiveJob for '" + pawn.ThingID + "' with ability: No ability, while traversing " + nodesTraversed + " nodes."); if (useThisAbility != null) { //Debug /*Log.Message("Ability '" + useThisAbility.defName + "' picked for AI.\n" + * "lastNode=" + lastNode.GetType().Name + "\n" + * "lastNode.parent=" + lastNode?.parent?.GetType()?.Name);*/ //Get CompAbilityUser var thingComp = pawn.AllComps.First(comp => comp.GetType() == profile.compAbilityUserClass); var compAbilityUser = thingComp as CompAbilityUser; if (compAbilityUser != null) { //Get Ability from Pawn. var useAbility = compAbilityUser.AbilityData.AllPowers.First(ability => ability.Def == useThisAbility.ability); var reason = ""; //Give job. if (useAbility.CanCastPowerCheck(AbilityContext.AI, out reason)) { var target = useThisAbility.Worker.TargetAbilityFor(useThisAbility, pawn); if (target.IsValid) { return(useAbility.UseAbility(AbilityContext.AI, target)); } } } } } } } //No Job to give. //Report. //Log.Message("JobGiver_AIAbilityUser.TryGiveJob for '" + pawn.ThingID + "' is Invalid."); return(null); }
/// <summary> /// First check on whether a Ability can be used or not. Default implementation have no special criterias. /// </summary> /// <param name="profileDef">Profile Def to check for.</param> /// <param name="pawn">Pawn to check for.</param> /// <param name="abilityDef">Ability Def to check for.</param> /// <returns>True if Ability can be used. False if not.</returns> public virtual bool CanUseAbility(AbilityUserAIProfileDef profileDef, Pawn pawn, AbilityAIDef abilityDef) { return(true); }
/// <summary> /// Calculates the final power score for this Ability taking the condition of the Pawn in account. Default /// implementation just returns the power score. /// </summary> /// <param name="abilityDef">Ability Def for the AI.</param> /// <param name="pawn">Pawn to take in account.</param> /// <returns>Final calculated score.</returns> public virtual float PowerScoreFor(AbilityAIDef abilityDef, Pawn pawn) { return(abilityDef.power); }