protected Move[] getMoves() { //todo deal with moves with no pp/disabled Move[] moves = new Move[4]; int waittime = 1; for (int i = 0; i < 4; i++) { if (!waitUntilElementExists(By.CssSelector("button[value='" + (i + 1).ToString() + "'][name='chooseMove']"), waittime)) { cwrite("Unavailable or bad move " + i.ToString(), "debug", COLOR_BOT); Move defal = new Move("error", types["error"]); moves[i] = defal; continue; } IWebElement b = browser.FindElement(By.CssSelector("button[value='" + (i + 1).ToString() + "'][name='chooseMove']")); string htmla = (string)((IJavaScriptExecutor)browser).ExecuteScript("return arguments[0].outerHTML;", b); string[] html = htmla.Split(new string[] { "data-move=\"" }, StringSplitOptions.None); //string[] html = b.GetAttribute("innerhtml").Split(new string[]{"data-move=\""},StringSplitOptions.None); var nametag = Array.Find(html, s => s.StartsWith("data-move")); string[] name = html[1].Split('"'); string[] temp = b.GetAttribute("class").Split('-'); string type = temp[1]; // moves [i] = Move m; //hidden power and frustration check if (name[0] == "Hidden Power") { string nname = "Hidden Power " + type; if (!Global.moves.ContainsKey(nname)) { m = new Move(nname, types[type.ToLower()], 60); m.group = "special"; Global.moves.Add(m.name, m); moves[i] = m; cwrite("Move " + i.ToString() + " " + m.name, COLOR_BOT); } else { m = Global.moveLookup("Hidden Power " + type); moves[i] = m; } } else if (Global.moveLookup(name[0]).type.value == "normal") { string nname = name[0]+" (" + type + ")"; if (!Global.moves.ContainsKey(nname)) { //This handles all normal type moves affected by -ate abilities. //I think it also handles Normalize as well. m = new Move(nname, types[type.ToLower()]); Move analog = Global.moveLookup(name[0]); m.group = analog.group; /* Check for -ate abilities by comparing the original type to the one we have. * Add the 30% boost to the base power so no need to calc it later. */ if(m.type != analog.type) m.bp = analog.bp + (analog.bp * 0.3f); Global.moves.Add(m.name, m); moves[i] = m; cwrite("Move " + i.ToString() + " " + m.name, COLOR_BOT); } else { m = Global.moveLookup(nname); moves[i] = m; } } else { if (Global.moves.ContainsKey(name[0])) m = Global.moves[name[0]]; else { cwrite("Unknown move " + name[0], COLOR_WARN); m = new Move(name[0], Global.types[type.ToLower()]); } moves[i] = m; cwrite("Move " + i.ToString() + " " + name[0], COLOR_BOT); } } return moves; }
/// <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; }
private void readJson() { if (!File.Exists(path)) { cwrite("Could not open movelist.txt", "error", COLOR_ERR); cwrite("Analytic mode will not work properly.", "[!]", COLOR_WARN); cwrite("Attempting to continue", COLOR_OK); return; } using (var reader = new StreamReader(path)) { string json; json = reader.ReadToEnd(); JObject jo = JsonConvert.DeserializeObject<JObject>(json); string allmoves = jo.First.ToString(); var current = jo.First; for (int i = 0; i< jo.Count;i++) { MoveJSONObj mv = JsonConvert.DeserializeObject<MoveJSONObj>(current.First.ToString()); Move move = new Move(mv); Global.moves.Add(move.name, move); current = current.Next; } } }
/// <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 }
public float heuristic(Move m, BattlePokemon p) { return heuristic(m.type, p, true); }
/// <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 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; }
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> /// /// </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> /// 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 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; }