Cache raid simulations results instead of storing results in local database
This commit is contained in:
parent
5ff176e1e2
commit
46a3df2aee
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
dist/
|
dist/
|
||||||
*.db
|
*.sqlite
|
||||||
*.json
|
|
||||||
|
@ -7,6 +7,7 @@ name = "pogo-scaled-estimators"
|
|||||||
version = "1.0a1"
|
version = "1.0a1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"requests",
|
"requests",
|
||||||
|
"requests-cache",
|
||||||
"rich",
|
"rich",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
@ -4,242 +4,70 @@
|
|||||||
# license that can be found in the LICENSE file or at
|
# license that can be found in the LICENSE file or at
|
||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import functools
|
|
||||||
import json
|
|
||||||
import operator
|
|
||||||
import sqlite3
|
|
||||||
import time
|
|
||||||
import urllib.parse
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import final
|
from typing import final
|
||||||
|
|
||||||
import requests
|
from rich.progress import Progress
|
||||||
from rich.progress import Progress, TaskID
|
|
||||||
|
|
||||||
from pogo_scaled_estimators.utilities import format_move_name, format_pokemon_name
|
from pogo_scaled_estimators.pokebattler_proxy import Moveset, PokebattlerProxy, Raid
|
||||||
|
from pogo_scaled_estimators.utilities import format_pokemon_name
|
||||||
WEAKNESS = 1.6
|
|
||||||
DOUBLE_WEAKNESS = WEAKNESS * WEAKNESS
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Calculator:
|
class Calculator:
|
||||||
def __init__(self, attacker_types: list[str]) -> None:
|
def __init__(self, attacker_types: list[str]) -> None:
|
||||||
self.db = sqlite3.connect("ase.db")
|
|
||||||
# db.set_trace_callback(print)
|
|
||||||
|
|
||||||
with contextlib.suppress(sqlite3.OperationalError):
|
|
||||||
_ = self.db.execute(
|
|
||||||
"CREATE TABLE estimators(defender, raid_tier, attacker, level, quick_move, charged_move, estimator, party)"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.raids = self._pokebattler_resource("raids")
|
|
||||||
self.pokemon = self._pokebattler_resource("pokemon")
|
|
||||||
self.resists = self._pokebattler_resource("resists")
|
|
||||||
self.moves = self._pokebattler_resource("moves")
|
|
||||||
|
|
||||||
self.attacker_types = attacker_types
|
self.attacker_types = attacker_types
|
||||||
|
self._pokebattler_proxy = PokebattlerProxy()
|
||||||
self.progress = Progress()
|
self._progress = Progress()
|
||||||
self._refresh_task: TaskID | None = None
|
|
||||||
|
|
||||||
def _pokebattler_resource(self, name: str) -> dict:
|
|
||||||
p = Path(f"./{name}.json")
|
|
||||||
if not p.exists():
|
|
||||||
response = requests.get(f"https://fight.pokebattler.com/{name}")
|
|
||||||
with p.open(mode="wb") as fp:
|
|
||||||
_ = fp.write(response.content)
|
|
||||||
|
|
||||||
with p.open() as fp:
|
|
||||||
return json.load(fp)
|
|
||||||
|
|
||||||
def calculate(self, level: int = 40, party: int = 1) -> None:
|
def calculate(self, level: int = 40, party: int = 1) -> None:
|
||||||
raid_bosses = self._raid_bosses()
|
raid_bosses = self._pokebattler_proxy.raid_bosses(self.attacker_types)
|
||||||
charged_moves = [
|
attackers = {
|
||||||
move["moveId"] for move in self.moves["move"] if "type" in move and move["type"] in self.attacker_types
|
attacker: {"RAID_LEVEL_3": [], "RAID_LEVEL_5": [], "RAID_LEVEL_MEGA": []}
|
||||||
]
|
for attacker in self._pokebattler_proxy.with_charged_moves(self.attacker_types)
|
||||||
res = self.db.execute(
|
}
|
||||||
f"""SELECT DISTINCT(attacker) FROM estimators WHERE charged_move IN ("{'","'.join(charged_moves)}")"""
|
|
||||||
)
|
|
||||||
attackers = {row[0]: {"RAID_LEVEL_3": [], "RAID_LEVEL_5": [], "RAID_LEVEL_MEGA": []} for row in res.fetchall()}
|
|
||||||
movesets = {}
|
|
||||||
|
|
||||||
defenders = functools.reduce(operator.iconcat, raid_bosses.values(), [])
|
with self._progress:
|
||||||
res = self.db.execute(
|
total_defenders = sum(len(defenders) for defenders in raid_bosses.values())
|
||||||
f"""
|
task = self._progress.add_task("Working...", total=(total_defenders * 30))
|
||||||
SELECT e.raid_tier, e.defender, e.attacker, e.estimator / m.min_estimator, e.quick_move, e.charged_move
|
|
||||||
FROM estimators e
|
for raid_tier, defenders in raid_bosses.items():
|
||||||
INNER JOIN (
|
|
||||||
SELECT defender, MIN(estimator) AS min_estimator
|
|
||||||
FROM estimators
|
|
||||||
WHERE party = ? AND level = 40
|
|
||||||
AND attacker IN ("{'","'.join(attackers.keys())}")
|
|
||||||
AND defender IN ("{'","'.join(defenders)}")
|
|
||||||
GROUP BY defender
|
|
||||||
) AS m ON e.defender = m.defender
|
|
||||||
INNER JOIN (
|
|
||||||
SELECT defender, attacker, MIN(estimator) as min_estimator
|
|
||||||
FROM estimators
|
|
||||||
WHERE party = ? AND level = ? AND charged_move IN ("{'","'.join(charged_moves)}")
|
|
||||||
GROUP BY defender, attacker
|
|
||||||
) AS ms ON e.defender = ms.defender AND e.attacker = ms.attacker AND e.estimator = ms.min_estimator
|
|
||||||
WHERE e.attacker IN ("{'","'.join(attackers.keys())}")
|
|
||||||
""",
|
|
||||||
(party, party, level),
|
|
||||||
)
|
|
||||||
for raid_tier, _, attacker, estimator, fast_move, charged_move in res.fetchall():
|
|
||||||
if raid_tier == "RAID_LEVEL_MEGA_5":
|
if raid_tier == "RAID_LEVEL_MEGA_5":
|
||||||
simplified_raid_tier = "RAID_LEVEL_MEGA"
|
simplified_raid_tier = "RAID_LEVEL_MEGA"
|
||||||
elif raid_tier == "RAID_LEVEL_ULTRA_BEAST":
|
elif raid_tier == "RAID_LEVEL_ULTRA_BEAST":
|
||||||
simplified_raid_tier = "RAID_LEVEL_5"
|
simplified_raid_tier = "RAID_LEVEL_5"
|
||||||
else:
|
else:
|
||||||
simplified_raid_tier = raid_tier
|
simplified_raid_tier = raid_tier
|
||||||
attackers[attacker][simplified_raid_tier].append(estimator)
|
for defender in defenders:
|
||||||
movesets[attacker] = (fast_move, charged_move)
|
self._progress.update(task, description=f"vs {format_pokemon_name(defender)}...")
|
||||||
|
raid = Raid(raid_tier, defender, level, party)
|
||||||
|
results = {
|
||||||
|
attacker: movesets
|
||||||
|
for attacker, movesets in self._pokebattler_proxy.simulate(raid).items()
|
||||||
|
if attacker in attackers
|
||||||
|
}
|
||||||
|
best_movesets = {
|
||||||
|
attacker: min(movesets, key=lambda moveset: moveset.estimator)
|
||||||
|
for attacker, movesets in results.items()
|
||||||
|
}
|
||||||
|
best_estimator = min(best_movesets.values(), key=lambda moveset: moveset.estimator).estimator
|
||||||
|
for attacker, moveset in best_movesets.items():
|
||||||
|
self._progress.update(task, advance=1)
|
||||||
|
attackers[attacker][simplified_raid_tier].append(moveset.scale(best_estimator))
|
||||||
|
|
||||||
ase = {}
|
ase = {}
|
||||||
for attacker, estimators in attackers.items():
|
for attacker, raid_tier_results in attackers.items():
|
||||||
if not estimators["RAID_LEVEL_3"] or not estimators["RAID_LEVEL_5"] or not estimators["RAID_LEVEL_MEGA"]:
|
if (
|
||||||
|
not raid_tier_results["RAID_LEVEL_3"]
|
||||||
|
or not raid_tier_results["RAID_LEVEL_5"]
|
||||||
|
or not raid_tier_results["RAID_LEVEL_MEGA"]
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
ase = (
|
ase = (
|
||||||
0.15 * sum(estimators["RAID_LEVEL_3"]) / len(estimators["RAID_LEVEL_3"])
|
0.15 * self._average_estimator(raid_tier_results["RAID_LEVEL_3"])
|
||||||
+ 0.50 * sum(estimators["RAID_LEVEL_5"]) / len(estimators["RAID_LEVEL_5"])
|
+ 0.50 * self._average_estimator(raid_tier_results["RAID_LEVEL_5"])
|
||||||
+ 0.35 * sum(estimators["RAID_LEVEL_MEGA"]) / len(estimators["RAID_LEVEL_MEGA"])
|
+ 0.35 * self._average_estimator(raid_tier_results["RAID_LEVEL_MEGA"])
|
||||||
)
|
)
|
||||||
fast_move, charged_move = movesets[attacker]
|
print(f"{attacker},{level},{ase}")
|
||||||
print(f"{attacker},{level},{ase},{fast_move},{charged_move}")
|
|
||||||
|
|
||||||
def _raid_bosses(self) -> dict:
|
def _average_estimator(self, movesets: list[Moveset]) -> float:
|
||||||
raid_tiers = []
|
return sum(moveset.estimator for moveset in movesets) / len(movesets)
|
||||||
raid_bosses = {}
|
|
||||||
|
|
||||||
for raid_level in ["3", "5", "MEGA", "MEGA_5", "ULTRA_BEAST"]:
|
|
||||||
tier = f"RAID_LEVEL_{raid_level}"
|
|
||||||
raid_tiers.extend(
|
|
||||||
[
|
|
||||||
tier,
|
|
||||||
f"{tier}_LEGACY",
|
|
||||||
f"{tier}_FUTURE",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
raid_bosses[tier] = []
|
|
||||||
|
|
||||||
for tier in filter(lambda tier: tier["tier"] in raid_tiers, self.raids["tiers"]):
|
|
||||||
for boss in (raid["pokemon"] for raid in tier["raids"]):
|
|
||||||
if boss.endswith("_FORM"):
|
|
||||||
continue
|
|
||||||
boss_pokemon = next(filter(lambda mon: mon["pokemonId"] == boss, self.pokemon["pokemon"]))
|
|
||||||
if ("candyToEvolve" in boss_pokemon or boss in ["SEADRA", "SEALEO"]) and boss not in [
|
|
||||||
"KELDEO",
|
|
||||||
"LUMINEON",
|
|
||||||
"MANAPHY",
|
|
||||||
"PHIONE",
|
|
||||||
"STUNFISK",
|
|
||||||
"TERRAKION",
|
|
||||||
]:
|
|
||||||
continue
|
|
||||||
boss_types = (
|
|
||||||
boss_pokemon["type"],
|
|
||||||
boss_pokemon.get("type2", "POKEMON_TYPE_NONE"),
|
|
||||||
)
|
|
||||||
if any(self._is_weak(attacker_type, boss_types) for attacker_type in self.attacker_types):
|
|
||||||
raid_bosses[tier["info"]["guessTier"]].append(boss)
|
|
||||||
|
|
||||||
return raid_bosses
|
|
||||||
|
|
||||||
def _is_weak(self, attacker_type: str, defender_types: tuple[str, str]) -> bool:
|
|
||||||
pokemon_types = list(self.resists.keys())
|
|
||||||
defender_type_indices = (
|
|
||||||
pokemon_types.index(defender_types[0]),
|
|
||||||
pokemon_types.index(defender_types[1]),
|
|
||||||
)
|
|
||||||
attack_resist = (
|
|
||||||
self.resists[attacker_type][defender_type_indices[0]]
|
|
||||||
* self.resists[attacker_type][defender_type_indices[1]]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check for double weakness.
|
|
||||||
if attack_resist >= DOUBLE_WEAKNESS:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Checkout for single weakness if not double weak to anything else.
|
|
||||||
any_double_weaknesses = any(
|
|
||||||
r[defender_type_indices[0]] * r[defender_type_indices[1]] >= DOUBLE_WEAKNESS for r in self.resists.values()
|
|
||||||
)
|
|
||||||
if not any_double_weaknesses and attack_resist >= WEAKNESS:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def load_estimators(self, tier: str, defender: str, level: int, party: int) -> None:
|
|
||||||
base_url = "https://fight.pokebattler.com"
|
|
||||||
query_string = {
|
|
||||||
"sort": "ESTIMATOR",
|
|
||||||
"weatherCondition": "NO_WEATHER",
|
|
||||||
"dodgeStrategy": "DODGE_REACTION_TIME",
|
|
||||||
"aggregation": "AVERAGE",
|
|
||||||
"includeLegendary": "true",
|
|
||||||
"includeShadow": "true",
|
|
||||||
"includeMegas": "true",
|
|
||||||
"primalAssistants": "",
|
|
||||||
"numParty": str(party),
|
|
||||||
}
|
|
||||||
if tier != "RAID_LEVEL_3":
|
|
||||||
query_string["friendshipLevel"] = "FRIENDSHIP_LEVEL_4"
|
|
||||||
url = f"{base_url}/raids/defenders/{defender}/levels/{tier}/attackers/levels/{level}/strategies/CINEMATIC_ATTACK_WHEN_POSSIBLE/DEFENSE_RANDOM_MC?{urllib.parse.urlencode(query_string, doseq=True)}"
|
|
||||||
response = requests.get(url)
|
|
||||||
for attacker in response.json()["attackers"][0]["randomMove"]["defenders"]:
|
|
||||||
for attacker_moves in attacker["byMove"]:
|
|
||||||
res = self.db.execute(
|
|
||||||
"SELECT estimator FROM estimators WHERE defender=? AND attacker=? AND level=? AND quick_move=? AND charged_move=? AND party=?",
|
|
||||||
(
|
|
||||||
defender,
|
|
||||||
attacker["pokemonId"],
|
|
||||||
level,
|
|
||||||
attacker_moves["move1"],
|
|
||||||
attacker_moves["move2"],
|
|
||||||
party,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
estimator = res.fetchone()
|
|
||||||
if estimator is not None:
|
|
||||||
self.progress.console.log(
|
|
||||||
f'{format_pokemon_name(attacker["pokemonId"])} ({format_move_name(attacker_moves["move1"])}/{format_move_name(attacker_moves["move2"])}): {estimator[0]:.2f} (cached)'
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
_ = self.db.execute(
|
|
||||||
"INSERT INTO estimators(defender, raid_tier, attacker, level, quick_move, charged_move, estimator, party) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
(
|
|
||||||
defender,
|
|
||||||
tier,
|
|
||||||
attacker["pokemonId"],
|
|
||||||
level,
|
|
||||||
attacker_moves["move1"],
|
|
||||||
attacker_moves["move2"],
|
|
||||||
attacker_moves["result"]["estimator"],
|
|
||||||
party,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.progress.console.log(
|
|
||||||
f'{format_pokemon_name(attacker["pokemonId"])} ({format_move_name(attacker_moves["move1"])}/{format_move_name(attacker_moves["move2"])}): {attacker_moves["result"]["estimator"]:.2f}'
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._refresh_task is not None:
|
|
||||||
self.progress.update(self._refresh_task, advance=1)
|
|
||||||
self.db.commit()
|
|
||||||
|
|
||||||
def refresh(self, level: int = 40, party: int = 1) -> None:
|
|
||||||
with self.progress:
|
|
||||||
total_defenders = sum(len(defenders) for defenders in self._raid_bosses().values())
|
|
||||||
self._refresh_task = self.progress.add_task("Working...", total=(total_defenders * 30))
|
|
||||||
for tier, defenders in self._raid_bosses().items():
|
|
||||||
for defender in defenders:
|
|
||||||
self.progress.update(self._refresh_task, description=f"vs {format_pokemon_name(defender)}...")
|
|
||||||
try:
|
|
||||||
self.load_estimators(tier, defender, level, party)
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
time.sleep(30)
|
|
||||||
self.load_estimators(tier, defender, level, party)
|
|
||||||
time.sleep(1)
|
|
||||||
|
@ -15,14 +15,9 @@ def main_cli():
|
|||||||
_ = parser.add_argument("type", nargs="+", help="an attacker type")
|
_ = parser.add_argument("type", nargs="+", help="an attacker type")
|
||||||
_ = parser.add_argument("--level", type=int, default=40)
|
_ = parser.add_argument("--level", type=int, default=40)
|
||||||
_ = parser.add_argument("--party", type=int, default=1)
|
_ = parser.add_argument("--party", type=int, default=1)
|
||||||
_ = parser.add_argument("--refresh", action="store_true", default=False)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
calculator = pogo_scaled_estimators.calculator.Calculator(args.type)
|
calculator = pogo_scaled_estimators.calculator.Calculator(args.type)
|
||||||
if args.refresh:
|
|
||||||
calculator.refresh(level=args.level, party=args.party)
|
|
||||||
else:
|
|
||||||
calculator.calculate(level=args.level, party=args.party)
|
calculator.calculate(level=args.level, party=args.party)
|
||||||
|
|
||||||
|
|
||||||
|
153
src/pogo_scaled_estimators/pokebattler_proxy.py
Normal file
153
src/pogo_scaled_estimators/pokebattler_proxy.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
import urllib.parse
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import cached_property
|
||||||
|
from typing import cast, final
|
||||||
|
|
||||||
|
import requests_cache
|
||||||
|
|
||||||
|
BASE_URL = "https://fight.pokebattler.com"
|
||||||
|
WEAKNESS = 1.6
|
||||||
|
DOUBLE_WEAKNESS = WEAKNESS * WEAKNESS
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Raid:
|
||||||
|
tier: str
|
||||||
|
defender: str
|
||||||
|
level: int = 40
|
||||||
|
party: int = 1
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Moveset:
|
||||||
|
fast_move: str
|
||||||
|
charged_move: str
|
||||||
|
estimator: float
|
||||||
|
|
||||||
|
def scale(self, factor: float):
|
||||||
|
return Moveset(self.fast_move, self.charged_move, self.estimator / factor)
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class PokebattlerProxy:
|
||||||
|
def __init__(self):
|
||||||
|
self._cached_session = requests_cache.CachedSession("pokebatter_cache", cache_control=True)
|
||||||
|
self._pokemon: dict | None = None
|
||||||
|
self._raids: dict | None = None
|
||||||
|
self._resists: dict | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cached_session(self):
|
||||||
|
return self._cached_session
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def moves(self) -> dict:
|
||||||
|
return self.cached_session.get(f"{BASE_URL}/moves").json()["move"]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def pokemon(self) -> dict:
|
||||||
|
return self.cached_session.get(f"{BASE_URL}/pokemon").json()["pokemon"]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def raids(self) -> dict:
|
||||||
|
return self.cached_session.get(f"{BASE_URL}/raids").json()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def resists(self) -> dict:
|
||||||
|
return self.cached_session.get(f"{BASE_URL}/resists").json()
|
||||||
|
|
||||||
|
def simulate(self, raid: Raid) -> dict[str, list[Moveset]]:
|
||||||
|
query_string = {
|
||||||
|
"sort": "ESTIMATOR",
|
||||||
|
"weatherCondition": "NO_WEATHER",
|
||||||
|
"dodgeStrategy": "DODGE_REACTION_TIME",
|
||||||
|
"aggregation": "AVERAGE",
|
||||||
|
"includeLegendary": "true",
|
||||||
|
"includeShadow": "true",
|
||||||
|
"includeMegas": "true",
|
||||||
|
"primalAssistants": "",
|
||||||
|
"numParty": str(raid.party),
|
||||||
|
}
|
||||||
|
if raid.tier != "RAID_LEVEL_3":
|
||||||
|
query_string["friendshipLevel"] = "FRIENDSHIP_LEVEL_4"
|
||||||
|
url = f"{BASE_URL}/raids/defenders/{raid.defender}/levels/{raid.tier}/attackers/levels/{raid.level}/strategies/CINEMATIC_ATTACK_WHEN_POSSIBLE/DEFENSE_RANDOM_MC?{urllib.parse.urlencode(query_string, doseq=True)}"
|
||||||
|
response = self._cached_session.get(url)
|
||||||
|
results: dict[str, list[Moveset]] = {}
|
||||||
|
for attacker in response.json()["attackers"][0]["randomMove"]["defenders"]:
|
||||||
|
results[attacker["pokemonId"]] = [
|
||||||
|
Moveset(
|
||||||
|
attacker_moves["move1"], attacker_moves["move2"], cast(float, attacker_moves["result"]["estimator"])
|
||||||
|
)
|
||||||
|
for attacker_moves in attacker["byMove"]
|
||||||
|
]
|
||||||
|
return results
|
||||||
|
|
||||||
|
def raid_bosses(self, attacker_types: list[str]) -> dict:
|
||||||
|
raid_tiers = []
|
||||||
|
raid_bosses = {}
|
||||||
|
|
||||||
|
for raid_level in ["3", "5", "MEGA", "MEGA_5", "ULTRA_BEAST"]:
|
||||||
|
tier = f"RAID_LEVEL_{raid_level}"
|
||||||
|
raid_tiers.extend(
|
||||||
|
[
|
||||||
|
tier,
|
||||||
|
f"{tier}_LEGACY",
|
||||||
|
f"{tier}_FUTURE",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
raid_bosses[tier] = []
|
||||||
|
|
||||||
|
for tier in filter(lambda tier: tier["tier"] in raid_tiers, self.raids["tiers"]):
|
||||||
|
for boss in (raid["pokemon"] for raid in tier["raids"]):
|
||||||
|
if boss.endswith("_FORM"):
|
||||||
|
continue
|
||||||
|
boss_pokemon = next(filter(lambda mon: mon["pokemonId"] == boss, self.pokemon))
|
||||||
|
if ("candyToEvolve" in boss_pokemon or boss in ["SEADRA", "SEALEO"]) and boss not in [
|
||||||
|
"KELDEO",
|
||||||
|
"LUMINEON",
|
||||||
|
"MANAPHY",
|
||||||
|
"PHIONE",
|
||||||
|
"STUNFISK",
|
||||||
|
"TERRAKION",
|
||||||
|
]:
|
||||||
|
continue
|
||||||
|
boss_types = (
|
||||||
|
boss_pokemon["type"],
|
||||||
|
boss_pokemon.get("type2", "POKEMON_TYPE_NONE"),
|
||||||
|
)
|
||||||
|
if any(self._is_weak(attacker_type, boss_types) for attacker_type in attacker_types):
|
||||||
|
raid_bosses[tier["info"]["guessTier"]].append(boss)
|
||||||
|
|
||||||
|
return raid_bosses
|
||||||
|
|
||||||
|
def _is_weak(self, attacker_type: str, defender_types: tuple[str, str]) -> bool:
|
||||||
|
pokemon_types = list(self.resists.keys())
|
||||||
|
defender_type_indices = (
|
||||||
|
pokemon_types.index(defender_types[0]),
|
||||||
|
pokemon_types.index(defender_types[1]),
|
||||||
|
)
|
||||||
|
attack_resist = (
|
||||||
|
self.resists[attacker_type][defender_type_indices[0]]
|
||||||
|
* self.resists[attacker_type][defender_type_indices[1]]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for double weakness.
|
||||||
|
if attack_resist >= DOUBLE_WEAKNESS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Checkout for single weakness if not double weak to anything else.
|
||||||
|
any_double_weaknesses = any(
|
||||||
|
r[defender_type_indices[0]] * r[defender_type_indices[1]] >= DOUBLE_WEAKNESS for r in self.resists.values()
|
||||||
|
)
|
||||||
|
if not any_double_weaknesses and attack_resist >= WEAKNESS:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def with_charged_moves(self, attacker_types: list[str]) -> list[str]:
|
||||||
|
charged_moves = [move["moveId"] for move in self.moves if "type" in move and move["type"] in attacker_types]
|
||||||
|
return [
|
||||||
|
mon["pokemonId"]
|
||||||
|
for mon in self.pokemon
|
||||||
|
if any(moveset["cinematicMove"] in charged_moves for moveset in mon["movesets"])
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user