From c87a5b3d82dbe22ff5bfa4f69adb604d0bb3b60f Mon Sep 17 00:00:00 2001 From: Tino Date: Tue, 12 Dec 2023 16:19:39 -0500 Subject: [PATCH 1/4] Fix event stream example page with non-full party, add EXP bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The event stream example page did not work properly if you didn't have a full party of 6 Pokémon, as the limits for a loop were badly chosen. This change also adds a little EXP bar to show how that can be done using the events API. --- modules/gui/create_profile_screen.py | 1 + modules/web/http_example.html | 28 +++++++++++++++++++++++----- modules/web/pokemon.d.ts | 2 +- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/modules/gui/create_profile_screen.py b/modules/gui/create_profile_screen.py index e865e66e..371c518b 100644 --- a/modules/gui/create_profile_screen.py +++ b/modules/gui/create_profile_screen.py @@ -190,6 +190,7 @@ def handle_selected_file(selection: list[str]) -> None: "*.ss7", "*.ss8", "*.ss9", + "*.sav", ] ], title="Load Existing Save", diff --git a/modules/web/http_example.html b/modules/web/http_example.html index 06e4ba89..2db781f9 100644 --- a/modules/web/http_example.html +++ b/modules/web/http_example.html @@ -274,7 +274,7 @@

Event Log

*/ function handlePartyChange(data) { - for (let index = 0; index < 6; index++) { + for (let index = 0; index < data.length; index++) { const listEntry = document.getElementById("party" + index); const nameParts = []; @@ -305,7 +305,14 @@

Event Log

hpBarColoured.style.backgroundColor = "#080"; } hpBar.append(hpBarColoured); - listEntry.append(hpBar); + + const expBar = document.createElement("div"); + expBar.className = "exp-bar"; + const expBarColoured = document.createElement("div"); + expBarColoured.style.width = `${data[index].exp_fraction_to_next_level * 100}%`; + expBar.append(expBarColoured); + + listEntry.append(hpBar, expBar); } } @@ -646,14 +653,25 @@

Current Encounter

margin-bottom: .5rem; } - .party-entry .hp-bar { - height: 3px; + .party-entry .hp-bar, .party-entry .exp-bar { width: 200px; background-color: #ddd; + border: 1px #888 solid; + } + + .party-entry .hp-bar { + height: 5px; + margin-top: 2px; + } + + .party-entry .exp-bar { + height: 3px; + margin-top: 1px; } - .party-entry .hp-bar div { + .party-entry .hp-bar div, .party-entry .exp-bar div { height: 100%; + background-color: #5ae; } .encounter-container { diff --git a/modules/web/pokemon.d.ts b/modules/web/pokemon.d.ts index ba72ffbc..7d1ce40f 100644 --- a/modules/web/pokemon.d.ts +++ b/modules/web/pokemon.d.ts @@ -45,7 +45,7 @@ export type Type = { index: number; // English name of this type. - name: string; + name: TypeName; kind: "???" | "Physical" | "Special"; } From dc3052a0d9c23af9e3b29f2a5531de3160a98616 Mon Sep 17 00:00:00 2001 From: Tino Date: Tue, 12 Dec 2023 17:05:12 -0500 Subject: [PATCH 2/4] Switch to manual mode when encountering a Roamer --- modules/encounter.py | 15 +++++++++++++-- modules/pokemon.py | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/modules/encounter.py b/modules/encounter.py index b9f52626..a0d036f7 100644 --- a/modules/encounter.py +++ b/modules/encounter.py @@ -3,7 +3,7 @@ from modules.files import save_pk3 from modules.gui.desktop_notification import desktop_notification from modules.pokemon_storage import get_pokemon_storage -from modules.pokemon import Pokemon +from modules.pokemon import Pokemon, get_battle_type_flags, BattleTypeFlag from modules.stats import total_stats @@ -29,8 +29,10 @@ def encounter_pokemon(pokemon: Pokemon) -> None: context.message = f"Encountered a {pokemon.species.name} with a shiny value of {pokemon.shiny_value:,}!" + battle_type_flags = get_battle_type_flags() + # TODO temporary until auto-catch is ready - if pokemon.is_shiny or custom_found: + if pokemon.is_shiny or custom_found or BattleTypeFlag.ROAMER in battle_type_flags: if pokemon.is_shiny: if not config.logging.save_pk3.all and config.logging.save_pk3.shiny: save_pk3(pokemon) @@ -50,6 +52,15 @@ def encounter_pokemon(pokemon: Pokemon) -> None: alert_title = "Custom filter triggered!" alert_message = f"Found a {pokemon.species.name} that matched one of your filters. ({custom_filter_result})" + + elif BattleTypeFlag.ROAMER in battle_type_flags: + state_tag = "roamer" + console.print("[bold pink]Roaming Pokemon found!") + context.message = f"Roaming Pokemon found! Bot has been switched to manual mode so you can catch it." + + alert_title = "Roaming Pokemon found!" + alert_message = f"Encountered a roaming {pokemon.species.name}." + else: state_tag = "" alert_title = None diff --git a/modules/pokemon.py b/modules/pokemon.py index 2f15e3c0..9f38289a 100644 --- a/modules/pokemon.py +++ b/modules/pokemon.py @@ -1511,7 +1511,42 @@ def get_opponent() -> Pokemon: class BattleTypeFlag(Flag, boundary=KEEP): - BATTLE_TYPE_TRAINER = 1 << 3 + DOUBLE = 1 << 0 + LINK = 1 << 1 + IS_MASTER = 1 << 2 + TRAINER = 1 << 3 + FIRST_BATTLE = 1 << 4 + LINK_IN_BATTLE = 1 << 5 + MULTI = 1 << 6 + SAFARI = 1 << 7 + BATTLE_TOWER = 1 << 8 + WALLY_TUTORIAL = 1 << 9 + ROAMER = 1 << 10 + EREADER_TRAINER = 1 << 11 + KYOGRE_GROUDON = 1 << 12 + LEGENDARY = 1 << 13 + REGI = 1 << 14 + TWO_OPPONENTS = 1 << 15 + DOME = 1 << 16 + PALACE = 1 << 17 + ARENA = 1 << 18 + FACTORY = 1 << 19 + PIKE = 1 << 20 + PYRAMID = 1 << 21 + INGAME_PARTNER = 1 << 22 + TOWER_LINK_MULTI = 1 << 23 + RECORDED = 1 << 24 + RECORDED_LINK = 1 << 25 + TRAINER_HILL = 1 << 26 + SECRET_BASE = 1 << 27 + GROUDON = 1 << 28 + KYOGRE = 1 << 29 + RAYQUAZA = 1 << 30 + RECORDED_IS_MASTER = 1 << 31 + + +def get_battle_type_flags() -> BattleTypeFlag: + return BattleTypeFlag(unpack_uint32(read_symbol("gBattleTypeFlags"))) def opponent_changed() -> bool: @@ -1524,11 +1559,10 @@ def opponent_changed() -> bool: try: global last_opid opponent_pid = read_symbol("gEnemyParty", size=4) - g_battle_type_flags = read_symbol("gBattleTypeFlags", size=4) if ( opponent_pid != last_opid and opponent_pid != b"\x00\x00\x00\x00" - and (BattleTypeFlag.BATTLE_TYPE_TRAINER not in BattleTypeFlag(unpack_uint32(g_battle_type_flags))) + and (BattleTypeFlag.TRAINER not in get_battle_type_flags()) ): last_opid = opponent_pid return True From 0dd8ed660dd3dcffe1c6a44479c043d0d34afe77 Mon Sep 17 00:00:00 2001 From: Tino Date: Tue, 12 Dec 2023 18:35:41 -0500 Subject: [PATCH 3/4] Fix item stack sizes for 'bag full' calculation It turns out, my assumption that items can always stack up to 99 is not quite correct: Berries and items in PC storage can stack up to 999, and on FR/LG items from other bags as well. --- modules/items.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/items.py b/modules/items.py index 2dcdb418..3224ba0b 100644 --- a/modules/items.py +++ b/modules/items.py @@ -171,8 +171,14 @@ def has_space_for(self, item: Item) -> bool: if len(pocket) < pocket_size: return True + # In FireRed/LeafGreen, you can always put 999 items in a stack. In RSE, this only works for berries. + if context.rom.game_title in ["POKEMON FIRE", "POKEMON LEAF"] or item.pocket == ItemPocket.Berries: + stack_size = 999 + else: + stack_size = 99 + for slot in pocket: - if slot.item == item and slot.quantity < 99: + if slot.item == item and slot.quantity < stack_size: return True return False @@ -243,7 +249,7 @@ def has_space_for(self, item: Item) -> bool: return True for slot in self.items: - if slot.item == item and slot.quantity < 99: + if slot.item == item and slot.quantity < 999: return True return False From 8eb53a44c4e0902f389fb9d31d9e7cbf41ca2e11 Mon Sep 17 00:00:00 2001 From: Tino Date: Tue, 12 Dec 2023 19:05:46 -0500 Subject: [PATCH 4/4] Fix how ROMs are looked up for profiles When loading a profile, the bot has two approaches to find the correct ROM: 1. Find a ROM with the exact name stated in the profile's `metadata.yml` 2. If that fails, find a ROM that matches in game title, version number and language code. Unfortunately, there was a bug with the second approach because the bot compared a string (from `metadata.yml`) with an Enum (from `load_rom_data()`) which caused that check to always fail. So effectively, unless the ROM file name matched the bot would always complain about the ROM missing. --- modules/profiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/profiles.py b/modules/profiles.py index 991a8921..4d9ed281 100644 --- a/modules/profiles.py +++ b/modules/profiles.py @@ -72,7 +72,7 @@ def load_profile(path: Path) -> Profile: [ rom.game_code == metadata.rom.game_code, rom.revision == metadata.rom.revision, - rom.language == metadata.rom.language, + rom.language.value == metadata.rom.language, ] ): return Profile(rom, path, last_played)