public float heuristic(Move m, BattlePokemon p) { return heuristic(m.type, p, true); }
/// <summary> /// Gives a specialised value of the effectiveness /// of a move t from pokemon p towards the defender. /// </summary> /// <param name="t">Attacking move type</param> /// <param name="p">Pokemon attacking</param> /// <returns></returns> public float heuristic(Type t, BattlePokemon p, bool countStab) { float eff1 = damageCalc(t, this.type1); float eff2 = damageCalc(t, this.type2); float stab = 1; if (countStab) { if (t == p.type1 || t == p.type2) stab = 1.5f; } float immunityFromAbility = 1; if (immunityCheck(t, this)) immunityFromAbility = 0; return (eff1 * eff2 * stab * immunityFromAbility); }
/// <summary> /// Used to handle moves whose BP is unknown or varies. /// </summary> /// <param name="m"></param> /// <returns></returns> private float getRealBP(Move m, BattlePokemon enemy) { if (m.bp > -1) return m.bp; else if (m.bp == -2) { if (m.name == "Gyro Ball") { float np = 25 * (enemy.getStat("spe") / this.getStat("spe")); } } return 20; //assume default }
/// <summary> /// Gets the heuristic value of pokemon p vs. this pokemon. /// Returns a value between 0-8 /// </summary> /// <param name="p"></param> /// <returns></returns> public float matchup(BattlePokemon p) { //All of this sucks and it needs to be rewritten to be more clear and useful. if (Object.ReferenceEquals(p, null)) { //in a test case where volt-switch killed an opponent "p" was null //todo: return a more helpful indicator return 1; } return heuristic(p.type1, p,false) + heuristic(p.type2, p,false); }
public float heuristic(Move m, BattlePokemon p) { return(heuristic(m.type, p, true)); }
/// <summary> /// Returns the damage modifier for the held item. /// </summary> /// <returns></returns> private float itemDamageMod(Move m, BattlePokemon enemy) { if (item == "none") return 1; if (item == "choiceband" && m.group == "physical") return 1.5f; if (item == "choicespecs" && m.group == "special") return 1.5f; if (item == "lifeorb") return 1.3f; if (item == "expertbelt" && this.isSuperEffective(m, enemy)) return 1.2f; return 1; }
/// <summary> /// /// </summary> /// <param name="you"></param> /// <param name="e"></param> /// <param name="m"></param> /// <returns></returns> private int getStatusChance(BattlePokemon you, BattlePokemon e, Move m, List<BattlePokemon> enemyTeam) { int max = GlobalConstants.MAX_MOVE_RANK; if (e.status != Status.STATE_HEALTHY) return -max; if (e.mon.hasAbility("magic bounce")) return -max; if (e.mon.hasAbility("poison heal") && m.statuseffect == "tox") return -max; if (e.mon.hasAbility("limber") && m.statuseffect == "par") return -max; if (m.statuseffect == "tox" && (e.hasType(types["poison"]) || e.hasType(types["steel"]))) return -max; if (m.statuseffect == "par" && (e.hasType(types["ground"]) || e.hasType(types["electric"]))) return -max; int chance = 0; if (m.statuseffect == "brn" && e.mon.getRole().physical) chance += 2; else if (m.statuseffect == "par" && (e.getStat("spe") >= you.getStat("spe"))) chance += 2; else if (m.statuseffect == "slp" && (you.getStat("spe") >= e.getStat("spe"))) { foreach (BattlePokemon bp in enemyTeam) { if (bp.status == Status.STATE_SLP) return -max; //abide by sleep clause } chance += 2; } chance += (int)Math.Round(10 * e.checkKOChance(you)); //Increase chance if enemy is too strong and needs to be weakened. return chance; }
/// <summary> /// Simply returns whether the move is super-effective or better (4x) /// </summary> /// <param name="m"></param> /// <param name="enemy"></param> /// <returns></returns> public bool isSuperEffective(Move m, BattlePokemon enemy) { float a = damageCalc(m.type, enemy.type1); float b = 1; if (enemy.type1 != enemy.type2) b = damageCalc(m.type, enemy.type2); return (a * b > 2f) ? true : false; }
/// <summary> /// The damage formula used by the games / PS! /// This returns a fairly accurate representation of /// how much damage this pokemon will do to enemy with move m. /// Ignores critical chance and variance. /// </summary> /// <param name="m"></param> /// <param name="enemy"></param> /// <returns></returns> private int damageFormula(Move m, BattlePokemon enemy) { float first = (2f * this.level + 10f) / 250f; float second = 0; if (m.group == "physical") { second = (float)this.getStat("atk") / (float)enemy.getStat("def"); } else { second = (float)this.getStat("spa") / (float)enemy.getStat("spd"); } float third = getRealBP(m, enemy); float totaldmg = first * second * third + 2; //Calculate modifier. //We will assume no critical hit and disregard the random variance. float itemmod = 1; float stab = 1; float immunity = 1; float type = damageCalc(m.type, enemy.type1); if(enemy.type1 != enemy.type2) type = type * damageCalc(m.type, enemy.type2); if (m.type == this.type1 || m.type == this.type2) { stab = 1.5f; } itemmod = itemDamageMod(m, enemy); if (immunityCheck(m.type, enemy)) immunity = 0; float multiplier = type * stab * itemmod * immunity; return (int)Math.Floor(totaldmg * multiplier); }
/// <summary> /// Returns a rank from -4 to 7 /// </summary> /// <param name="e"></param> /// <param name="lastAction"></param> /// <returns></returns> private int getRecoverChance(BattlePokemon e, LastBattleAction lastAction) { BattlePokemon you = this; int hpThreshold = 40; //Percent of health at which to conisder recovering. int chance = 0; if (you.getHPPercentage() <= hpThreshold) chance += 3; else if (you.getHPPercentage() > (100 - hpThreshold)) chance -= 2; if (e.checkKOChance(you) < 0.3f) chance += 2; //heal if the opponent cannot ohko us. if (you.status != Status.STATE_HEALTHY && lastAction != LastBattleAction.ACTION_SLEEPTALK) chance += 2; if (lastAction == LastBattleAction.ACTION_RECOVER) chance -= 2; return chance; }
/// <summary> /// Returns how many times this pokemon will /// have to use move m to KO enemy. /// </summary> /// <param name="m"></param> /// <param name="enemy"></param> /// <returns></returns> public int hitsToKill(Move m, BattlePokemon enemy) { int totalDamage = damageFormula(m, enemy); //Compare the damage we will deal to the health of the enemy. int times = 0; //number of times it takes to use this move to KO the opponent. int health = enemy.getHealth(); for (;(health > 0); times++) { if (times > GlobalConstants.MAX_HKO) break; health -= totalDamage; } return times; }
/// <summary> /// Ranks a given move. /// Returns a rank >= 1 /// </summary> /// <param name="m"></param> /// <param name="enemy"></param> /// <returns></returns> public int rankMove(Move m, BattlePokemon enemy,List<BattlePokemon> enemyTeam, LastBattleAction lba) { int DEFAULT_RANK = 11; //(15 - 4) int rank = DEFAULT_RANK; // rank of move m if (m.group != "status") { rank = hitsToKill(m, enemy); //discourage the use of low accuracy moves if they're overkill if (m.accuracy != 1 && enemy.getHPPercentage() < 20) ++rank; if (m.priority > 0) rank -= m.priority; //To rank in ascending order (ie 1 is a poor rank) subtract the rank from the max. if (rank > GlobalConstants.MAX_MOVE_RANK) rank = GlobalConstants.MAX_MOVE_RANK; rank = GlobalConstants.MAX_MOVE_RANK - rank; } else { if (m.heal) { rank = getRecoverChance(enemy, lba); rank += ((100-getHPPercentage())/10); } else if (m.status) { rank += getStatusChance(this, enemy, m, enemyTeam); /* add the status rank to the default rank, meaning a good * status move will rank around the same as a 2HKO. This * will prevent cases where an easy OHKO is available, but * a turn is wasted on status. However it may need more * balancing. */ } else if (m.isBoost) { rank += getBoostChance(this, enemy, m, lba); } } return rank; }
public void testBattle() { BattlePokemon off = new BattlePokemon(Global.lookup("magcargo")); BattlePokemon def = new BattlePokemon(Global.lookup("scizor")); List<BattlePokemon> defteam = new List<BattlePokemon>(); defteam.Add(def); Move m1 = Global.moveLookup("Fire Blast"); Move m2 = Global.moveLookup("Flamethrower"); def.setHealth(10); int dmg1 = off.rankMove(m1, def,defteam, LastBattleAction.ACTION_ATTACK_SUCCESS); int dmg2 = off.rankMove(m2, def, defteam, LastBattleAction.ACTION_ATTACK_SUCCESS); cwrite(off.mon.name + "'s " + m1.name + " against " + def.mon.name+":"+dmg1, "debug", COLOR_BOT); cwrite(off.mon.name + "'s " + m2.name + " against " + def.mon.name + ":" + dmg2, "debug", COLOR_BOT); }
/// <summary> /// Predicts how well the pokemon matches up against /// the opponent. This only takes into account the pokemon's /// typing as compared to it's opponent's. /// </summary> /// <param name="enemy">the opposing pokemon</param> /// <returns>a float in range 0-8</returns> public float checkTypes(BattlePokemon defender) { float val = damageCalc(this.mon.type1, defender.type1); if (defender.type1.value != defender.type2.value || defender.type2.value != null) val = val * damageCalc(this.mon.type1, defender.type2); if (this.mon.type1 != this.mon.type2 || this.mon.type2 != null) { val = val * damageCalc(this.mon.type2, defender.type2); if (defender.type1.value != defender.type2.value || defender.type2.value != null) val = val * damageCalc(this.mon.type2, defender.type2); } return val; }
private int getBoostChance(BattlePokemon you, BattlePokemon e, Move m, LastBattleAction lastAction) { int chance = 0; int minHP = 30; float enemyTolerance = 0.5f; List<String> boosts = m.whatBoosts(); //lower rank if already maxed out. foreach (string s in boosts) { if (this.getBoostModifier(s) == 4f) chance -= 1; } if (you.getHPPercentage() <= minHP) chance -= 2; //too weak, should focus efforts elsewhere if (e.checkKOChance(you) < enemyTolerance) chance += 2; //enemy does not threaten us else if (e.checkKOChance(you) - 0.2f < enemyTolerance) chance += 2; //if boosting will make us survive, do it. else chance -= 2; //otherwise too risky if (you.mon.getRole().setup) chance += 4; //if the mon is a setup sweeper, etc, if (lastAction == LastBattleAction.ACTION_BOOST) chance -= 1; //Be careful not to boost forever. return chance; }
/// <summary> /// Checks if a move is immune against another Pokemon based on its abilities, etc. /// NOTE: Type-based immunities are covered within their own typings, not here. /// </summary> /// <param name="t"></param> /// <param name="p"></param> /// <returns></returns> public bool immunityCheck(Type t, BattlePokemon p) { if (t.value == "ground" && p.mon.hasAbility("levitate")) return true; if (t.value == "fire" && p.mon.hasAbility("flashfire")) return true; return false; }
/// <summary> /// Calculates the total damage a move will do to a particular pokemon, /// with respect to abilities, types, STAB, common items, etc. /// </summary> /// <param name="m">Move used by (this) pokemon.</param> /// <param name="enemy"></param> /// <returns></returns> public float damageCalcTotal(Move m, BattlePokemon enemy) { float mult = damageCalc(m.type, enemy.type1); mult = mult * damageCalc(m.type, enemy.type2); float dmg = getRealBP(m,enemy); float stab = 1; float ability = 1; float immunity = 1; if (immunityCheck(m.type, enemy)) immunity = 0; if (m.type == this.type1 || m.type == this.type2) { stab = 1.5f; } //do ability calculations float additional = itemDamageMod(m, enemy); return (dmg * mult * stab * ability * additional * immunity); }
/// <summary> /// Returns a number between 0,1 that determines /// the likelihood of this mon KOing the enemy /// </summary> /// <param name="enemy"></param> /// <returns></returns> public float checkKOChance(BattlePokemon enemy) { float chance = 0; //First check if we are faster. if (this.getStat("spe") > enemy.getStat("spe")) { chance += 0.1f; } //Now check if we are a suitable attacker Role role = mon.getRole(); DefenseType edeftype = enemy.mon.getDefType(); if (((role.physical) && (edeftype.special)) || ((role.special) && (edeftype.physical)) || (edeftype.any)) { chance += 0.4f; } if (enemy.getHPPercentage() < 50) chance += 0.2f; else chance -= 0.1f; if (checkTypes(enemy) >= 2.5f) chance += 0.2f; //Todo: add other parameters here. Decrement chance for unsuitable matchings, etc. //Todo also: adjust chance scale. do some calcs to find out what produces more favorable results. return chance; }
/// <summary> /// Ranks a given move. /// Returns a rank >= 1 /// </summary> /// <param name="m"></param> /// <param name="enemy"></param> /// <returns></returns> public int rankMove(Move m, BattlePokemon enemy, List <BattlePokemon> enemyTeam, LastBattleAction lba, Weather weather) { int DEFAULT_RANK = 11; //(15 - 4) int rank = DEFAULT_RANK; // rank of move m if (m.group != "status") { rank = hitsToKill(m, enemy, weather); /* * Set the rank to the maximum if this move is fake out, and it can be used. * This is isn't always the move we want to make, so further checks need * to be developed. Namely, prevent using it against ghost types and * rough skin/iron barbs, etc. */ if (m.name.Contains("Fake Out") && this.canUseFakeout) { rank = 0; //Rank order is reversed for damaging moves, 0 = Max Rank. } //discourage the use of low accuracy moves if they're overkill if (m.accuracy != 1 && enemy.getHPPercentage() < 20) { ++rank; } if (m.priority > 0) { rank -= m.priority; } //To rank in ascending order (ie 1 is a poor rank) subtract the rank from the max. if (rank > GlobalConstants.MAX_MOVE_RANK) { rank = GlobalConstants.MAX_MOVE_RANK; //Prevent negative ranks. } rank = GlobalConstants.MAX_MOVE_RANK - rank; } else { if (m.heal) { rank = getRecoverChance(enemy, lba); rank += ((100 - getHPPercentage()) / 10); } else if (m.status) { rank += getStatusChance(this, enemy, m, enemyTeam); /* add the status rank to the default rank, meaning a good * status move will rank around the same as a 2HKO. This * will prevent cases where an easy OHKO is available, but * a turn is wasted on status. However it may need more * balancing. */ } else if (m.isBoost) { rank += getBoostChance(this, enemy, m, lba); } else if (m.field) { /* * If the pokemon is a lead and hasn't used a hazard, * rank this highly. Otherwise give it a small boost, * or have it remain default for non-lead pokemon. * This needs to be refined more. */ if (this.mon.getRole().lead&& !this.hasUsedHazard) { rank = GlobalConstants.MAX_MOVE_RANK; } else if (this.mon.getRole().lead) { rank += 1; } } } return(rank); }