// This function evaluates the game-field // It then creates and returns an ActionData object referring to the steps for executing the action public ActionData GetActionData() { // first, find the best ability in terms of significance. List <AbilityOption> options = myUnit.GetAbilityOptions(); float highestSignificance = float.NegativeInfinity; // for each possible action: for (int i = 0; i < options.Count; i++) { // evaluate the significance of this particular option SignifyAbility(options[i]); // Debug.Log("Ability: " + options[i].GetAbility() + " | Significance: " + options[i].GetSignificance()); // check if it has the highest significance if (options[i].GetSignificance() >= highestSignificance) { committedAbilityIndex = i; committedAbilityOption = options[i]; highestSignificance = options[i].GetSignificance(); } } committedMovement = CommitMovement(); return(new ActionData(committedMovement.Item1, committedMovement.Item2, committedAbilityIndex)); }
// Takes an AbilityOption object and evaluates/sets its significance // this function is abstract because way in which an ability is given significance is biased based on the type of EnemyAction that has been decided upon // for example, being Aggro will value different things when choosing an option as opposed to being Defensive protected abstract void SignifyAbility(AbilityOption option);
protected override void SignifyAbility(AbilityOption option) { // start at 0.0 significance option.SetSignificance(0.0f); // get variables Vector2Int bestTarget = new Vector2Int(int.MaxValue, int.MaxValue); Dictionary <Vector2Int, MutableTuple <Unit, float> > affectableUnits = option.GetAffectableUnits(); bool unitIsAffectable; foreach (EffectState effect in option.GetAbility().GetEffectState()) { foreach (Vector2Int key in affectableUnits.Keys) { unitIsAffectable = false; // go through each type of Effect that the ability deals, adding significance each time switch (effect) { case EffectState.DAMAGE: // we don't want to damage EnemyUnits if (!(affectableUnits[key].Item1 is EnemyUnit)) { unitIsAffectable = true; // if using this ability would reduce this Unit's HP to 0 or below, // definitely consider doing this. if ((option.GetAbility() as Attack).LethalAttack(affectableUnits[key].Item1)) { affectableUnits[key].Item2 = float.PositiveInfinity; } else { // the significance of this ability against this Unit is double the ratio of how much of the Unit's current health that the ability will deal, out of 10.0 float significance = ((float)((option.GetAbility() as Attack).GetDamage()) / (float)(affectableUnits[key].Item1.GetHealth() + affectableUnits[key].Item1.GetDamageReduction()) * 20.0f); affectableUnits[key].Item2 += significance; } } break; case EffectState.BUFF_DR: // viable units are (friendly) EnemyUnits that are not capped out of DR (max 5) if (affectableUnits[key].Item1 is EnemyUnit && affectableUnits[key].Item1.GetDamageReduction() < 5) { unitIsAffectable = true; // the significance of using this effect is greater the less DR the Unit already has (max 2.5) float significance = ((float)(affectableUnits[key].Item1.GetDamageReduction() - 5)) / -2.0f; affectableUnits[key].Item2 += significance; } break; case EffectState.BUFF_DMG: // viable units are (friendly) EnemyUnits that are not Buffed if (affectableUnits[key].Item1 is EnemyUnit && !affectableUnits[key].Item1.attackBuffed) { unitIsAffectable = true; affectableUnits[key].Item2 += 2.5f; } break; case EffectState.IMMOBILIZE: // viable units are (nonfriendly) non-EnemyUnits that are not stunned if (!(affectableUnits[key].Item1 is EnemyUnit) && !(affectableUnits[key].Item1.GetImmobilizedDuration() > 0)) { unitIsAffectable = true; affectableUnits[key].Item2 += 2.5f; } break; case EffectState.KNOCKBACK: // viable units are (nonfriendly) non-EnemyUnits if (!(affectableUnits[key].Item1 is EnemyUnit)) { unitIsAffectable = true; affectableUnits[key].Item2 += 1.5f; } break; case EffectState.DISABLE: // viable units are (nonfriendly) non-EnemyUnits if (!(affectableUnits[key].Item1 is EnemyUnit) && !(affectableUnits[key].Item1.GetDisabledDuration() > 0)) { unitIsAffectable = true; affectableUnits[key].Item2 += 3.5f; } break; default: break; } // We have a new Best Target IF: its viably affectable AND: there isn't one OR this significance is bigger than the current best if (unitIsAffectable && ((bestTarget.x == int.MaxValue && bestTarget.y == int.MaxValue) || (affectableUnits[key].Item2 >= affectableUnits[bestTarget].Item2))) { bestTarget = key; } } } // If this Ability cannot affect any meaningful Units: Do NOT use it // IF: This ability deals some sort of effect AND either: there is no bestTarget NOR affectable Unit if (option.GetAbility().GetEffectState().Count > 0 && ((bestTarget.x == int.MaxValue && bestTarget.y == int.MaxValue) || affectableUnits.Keys.Count <= 0)) { option.SetSignificance(float.NegativeInfinity); } else if (option.GetAbility() is Wait) { // wait should be zero, AKA it should only be chosen if everything else is an awful choice option.SetSignificance(0.0f); // a disabled Unit MUST wait if (myUnit.GetDisabledDuration() > 0) { option.SetSignificance(float.PositiveInfinity); } } else { option.SetSignificance(affectableUnits[bestTarget].Item2); } }