From 06df072095deae83a41d67f3fb8b30053c2545ef Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:49:37 -0400 Subject: [PATCH 1/9] Core: Require excluded locations to be reachable with full/locations accessibility (#3802) * Make excludeds reachable * Update all_state tests --- BaseClasses.py | 3 +-- test/bases.py | 8 +++----- test/general/test_reachability.py | 6 ++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 95f24af26548..68b410010e9d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -616,8 +616,7 @@ def location_condition(location: Location) -> bool: def location_relevant(location: Location) -> bool: """Determine if this location is relevant to sweep.""" - return location.progress_type != LocationProgressType.EXCLUDED \ - and (location.player in players["full"] or location.advancement) + return location.player in players["full"] or location.advancement def all_done() -> bool: """Check if all access rules are fulfilled""" diff --git a/test/bases.py b/test/bases.py index 9fb223af2ac1..83461b158f4f 100644 --- a/test/bases.py +++ b/test/bases.py @@ -293,13 +293,11 @@ def test_all_state_can_reach_everything(self): if not (self.run_default_tests and self.constructed): return with self.subTest("Game", game=self.game, seed=self.multiworld.seed): - excluded = self.multiworld.worlds[self.player].options.exclude_locations.value state = self.multiworld.get_all_state(False) for location in self.multiworld.get_locations(): - if location.name not in excluded: - with self.subTest("Location should be reached", location=location.name): - reachable = location.can_reach(state) - self.assertTrue(reachable, f"{location.name} unreachable") + with self.subTest("Location should be reached", location=location.name): + reachable = location.can_reach(state) + self.assertTrue(reachable, f"{location.name} unreachable") with self.subTest("Beatable"): self.multiworld.state = state self.assertBeatable(True) diff --git a/test/general/test_reachability.py b/test/general/test_reachability.py index 4b71762f77fe..d50013cc4178 100644 --- a/test/general/test_reachability.py +++ b/test/general/test_reachability.py @@ -37,12 +37,10 @@ def test_default_all_state_can_reach_everything(self): unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set()) with self.subTest("Game", game=game_name): multiworld = setup_solo_multiworld(world_type) - excluded = multiworld.worlds[1].options.exclude_locations.value state = multiworld.get_all_state(False) for location in multiworld.get_locations(): - if location.name not in excluded: - with self.subTest("Location should be reached", location=location.name): - self.assertTrue(location.can_reach(state), f"{location.name} unreachable") + with self.subTest("Location should be reached", location=location.name): + self.assertTrue(location.can_reach(state), f"{location.name} unreachable") for region in multiworld.get_regions(): if region.name in unreachable_regions: From 4af6927e230144e7045f394efd6ea029b1339b05 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Fri, 16 Aug 2024 14:52:16 -0400 Subject: [PATCH 2/9] Lingo: Fixed Initiated-side Eight Door not opening (#3793) --- worlds/lingo/data/LL1.yaml | 30 ++++++++++++++++-------------- worlds/lingo/data/generated.dat | Bin 149055 -> 149166 bytes worlds/lingo/data/ids.yaml | 4 ++-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml index 950fd326743a..16a1573b1d56 100644 --- a/worlds/lingo/data/LL1.yaml +++ b/worlds/lingo/data/LL1.yaml @@ -2049,6 +2049,18 @@ panel: I - room: Elements Area panel: A + Eight Door (Outside The Initiated): + id: Red Blue Purple Room Area Doors/Door_a_strands2 + item_name: Outside The Initiated - Eight Door + item_group: Achievement Room Entrances + skip_location: True + panels: + - room: The Incomparable + panel: I (Seven) + - room: Courtyard + panel: I + - room: Elements Area + panel: A panel_doors: Giant Sevens: item_name: Giant Seven Panels @@ -2067,8 +2079,8 @@ room: The Incomparable door: Eight Door Outside The Initiated: - room: Outside The Initiated - door: Eight Door + room: The Incomparable + door: Eight Door (Outside The Initiated) paintings: - id: eight_painting2 orientation: north @@ -3310,7 +3322,8 @@ room: Art Gallery door: Exit Eight Alcove: - door: Eight Door + room: The Incomparable + door: Eight Door (Outside The Initiated) The Optimistic: True panels: SEVEN (1): @@ -3463,17 +3476,6 @@ panel: GREEN - room: Outside The Agreeable panel: PURPLE - Eight Door: - id: Red Blue Purple Room Area Doors/Door_a_strands2 - item_group: Achievement Room Entrances - skip_location: True - panels: - - room: The Incomparable - panel: I (Seven) - - room: Courtyard - panel: I - - room: Elements Area - panel: A panel_doors: UNCOVER: panels: diff --git a/worlds/lingo/data/generated.dat b/worlds/lingo/data/generated.dat index 9a49d3d9d4b9078c1d8244f5023cb1d49584241a..e2d3d06bec9642cf1b782cc3751ec542c322b558 100644 GIT binary patch delta 30860 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 IA;Y8pAA$hX*8l(j delta 30698 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 Date: Fri, 16 Aug 2024 14:53:54 -0400 Subject: [PATCH 3/9] TUNIC: Give the fox a gun (in logic) (very small PR) (#3790) * Add bomb wall logic * Remove option call from can_shop * Gun for the envoy blocking Quarry * has_sword -> can_shop on cube cave entrance region --- worlds/tunic/er_data.py | 9 ++++++++- worlds/tunic/er_rules.py | 22 +++++++++++++++++++--- worlds/tunic/items.py | 2 +- worlds/tunic/rules.py | 17 +++++++++++++++-- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py index e999026dec78..6316292e564e 100644 --- a/worlds/tunic/er_data.py +++ b/worlds/tunic/er_data.py @@ -75,7 +75,7 @@ def destination_scene(self) -> str: # the vanilla connection destination="Town Basement", tag="_beach"), Portal(name="Changing Room Entrance", region="Overworld", destination="Changing Room", tag="_"), - Portal(name="Cube Cave Entrance", region="Overworld", + Portal(name="Cube Cave Entrance", region="Cube Cave Entrance Region", destination="CubeRoom", tag="_"), Portal(name="Stairs from Overworld to Mountain", region="Upper Overworld", destination="Mountain", tag="_"), @@ -562,6 +562,7 @@ class DeadEnd(IntEnum): "Overworld Temple Door": RegionInfo("Overworld Redux"), # the small space betweeen the door and the portal "Overworld Town Portal": RegionInfo("Overworld Redux"), # being able to go to or come from the portal "Overworld Spawn Portal": RegionInfo("Overworld Redux"), # being able to go to or come from the portal + "Cube Cave Entrance Region": RegionInfo("Overworld Redux"), # other side of the bomb wall "Stick House": RegionInfo("Sword Cave", dead_end=DeadEnd.all_cats), "Windmill": RegionInfo("Windmill"), "Old House Back": RegionInfo("Overworld Interiors"), # part with the hc door @@ -775,6 +776,8 @@ class DeadEnd(IntEnum): [["UR"]], "Overworld Old House Door": [], + "Cube Cave Entrance Region": + [], }, "East Overworld": { "Above Ruined Passage": @@ -920,6 +923,10 @@ class DeadEnd(IntEnum): "Overworld": [], }, + "Cube Cave Entrance Region": { + "Overworld": + [], + }, "Old House Front": { "Old House Back": [], diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py index a54ea23bcc0a..3d1973beb375 100644 --- a/worlds/tunic/er_rules.py +++ b/worlds/tunic/er_rules.py @@ -1,6 +1,7 @@ from typing import Dict, Set, List, Tuple, TYPE_CHECKING from worlds.generic.Rules import set_rule, forbid_item -from .rules import has_ability, has_sword, has_stick, has_ice_grapple_logic, has_lantern, has_mask, can_ladder_storage +from .rules import (has_ability, has_sword, has_stick, has_ice_grapple_logic, has_lantern, has_mask, can_ladder_storage, + bomb_walls) from .er_data import Portal from BaseClasses import Region, CollectionState @@ -11,6 +12,7 @@ grapple = "Magic Orb" ice_dagger = "Magic Dagger" fire_wand = "Magic Wand" +gun = "Gun" lantern = "Lantern" fairies = "Fairy" coins = "Golden Coin" @@ -31,6 +33,10 @@ def has_ladder(ladder: str, state: CollectionState, world: "TunicWorld") -> bool return not world.options.shuffle_ladders or state.has(ladder, world.player) +def can_shop(state: CollectionState, world: "TunicWorld") -> bool: + return has_sword(state, world.player) and state.can_reach_region("Shop", world.player) + + def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_pairs: Dict[Portal, Portal]) -> None: player = world.player options = world.options @@ -217,12 +223,12 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_ regions["Overworld"].connect( connecting_region=regions["Overworld after Envoy"], - rule=lambda state: state.has_any({laurels, grapple}, player) + rule=lambda state: state.has_any({laurels, grapple, gun}, player) or state.has("Sword Upgrade", player, 4) or options.logic_rules) regions["Overworld after Envoy"].connect( connecting_region=regions["Overworld"], - rule=lambda state: state.has_any({laurels, grapple}, player) + rule=lambda state: state.has_any({laurels, grapple, gun}, player) or state.has("Sword Upgrade", player, 4) or options.logic_rules) @@ -329,6 +335,12 @@ def set_er_region_rules(world: "TunicWorld", regions: Dict[str, Region], portal_ connecting_region=regions["Overworld"], rule=lambda state: state.has_any({grapple, laurels}, player)) + regions["Overworld"].connect( + connecting_region=regions["Cube Cave Entrance Region"], + rule=lambda state: state.has(gun, player) or can_shop(state, world)) + regions["Cube Cave Entrance Region"].connect( + connecting_region=regions["Overworld"]) + # Overworld side areas regions["Old House Front"].connect( connecting_region=regions["Old House Back"]) @@ -1527,6 +1539,10 @@ def set_er_location_rules(world: "TunicWorld") -> None: set_rule(world.get_location("Library Fuse"), lambda state: has_ability(prayer, state, world)) + # Bombable Walls + for location_name in bomb_walls: + set_rule(world.get_location(location_name), lambda state: state.has(gun, player) or can_shop(state, world)) + # Shop set_rule(world.get_location("Shop - Potion 1"), lambda state: has_sword(state, player)) diff --git a/worlds/tunic/items.py b/worlds/tunic/items.py index a8aec9f74485..3e7f2c1a4382 100644 --- a/worlds/tunic/items.py +++ b/worlds/tunic/items.py @@ -43,7 +43,7 @@ class TunicItemData(NamedTuple): "Magic Orb": TunicItemData(ItemClassification.progression, 1, 27), "Hero's Laurels": TunicItemData(ItemClassification.progression, 1, 28), "Lantern": TunicItemData(ItemClassification.progression, 1, 29), - "Gun": TunicItemData(ItemClassification.useful, 1, 30, "Weapons"), + "Gun": TunicItemData(ItemClassification.progression, 1, 30, "Weapons"), "Shield": TunicItemData(ItemClassification.useful, 1, 31), "Dath Stone": TunicItemData(ItemClassification.useful, 1, 32), "Hourglass": TunicItemData(ItemClassification.useful, 1, 33), diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py index 2ff588da904d..68756869038d 100644 --- a/worlds/tunic/rules.py +++ b/worlds/tunic/rules.py @@ -1,7 +1,7 @@ from random import Random from typing import Dict, TYPE_CHECKING -from worlds.generic.Rules import set_rule, forbid_item +from worlds.generic.Rules import set_rule, forbid_item, add_rule from BaseClasses import CollectionState from .options import TunicOptions if TYPE_CHECKING: @@ -11,6 +11,7 @@ grapple = "Magic Orb" ice_dagger = "Magic Dagger" fire_wand = "Magic Wand" +gun = "Gun" lantern = "Lantern" fairies = "Fairy" coins = "Golden Coin" @@ -26,6 +27,11 @@ blue_hexagon = "Blue Questagon" gold_hexagon = "Gold Questagon" +bomb_walls = ["East Forest - Bombable Wall", "Eastern Vault Fortress - [East Wing] Bombable Wall", + "Overworld - [Central] Bombable Wall", "Overworld - [Southwest] Bombable Wall Near Fountain", + "Quarry - [West] Upper Area Bombable Wall", "Quarry - [East] Bombable Wall", + "Ruined Atoll - [Northwest] Bombable Wall"] + def randomize_ability_unlocks(random: Random, options: TunicOptions) -> Dict[str, int]: ability_requirement = [1, 1, 1] @@ -110,7 +116,7 @@ def set_region_rules(world: "TunicWorld") -> None: lambda state: state.has_any({grapple, laurels}, player) and has_ability(prayer, state, world) world.get_entrance("Overworld -> Quarry").access_rule = \ lambda state: (has_sword(state, player) or state.has(fire_wand, player)) \ - and (state.has_any({grapple, laurels}, player) or can_ladder_storage(state, world)) + and (state.has_any({grapple, laurels, gun}, player) or can_ladder_storage(state, world)) world.get_entrance("Quarry Back -> Quarry").access_rule = \ lambda state: has_sword(state, player) or state.has(fire_wand, player) world.get_entrance("Quarry -> Lower Quarry").access_rule = \ @@ -326,6 +332,13 @@ def set_location_rules(world: "TunicWorld") -> None: set_rule(world.get_location("Hero's Grave - Feathers Relic"), lambda state: state.has(laurels, player) and has_ability(prayer, state, world)) + # Bombable Walls + for location_name in bomb_walls: + # has_sword is there because you can buy bombs in the shop + set_rule(world.get_location(location_name), lambda state: state.has(gun, player) or has_sword(state, player)) + add_rule(world.get_location("Cube Cave - Holy Cross Chest"), + lambda state: state.has(gun, player) or has_sword(state, player)) + # Shop set_rule(world.get_location("Shop - Potion 1"), lambda state: has_sword(state, player)) From 7eda4c47f87fba03ae738811aadfd9c8fd863805 Mon Sep 17 00:00:00 2001 From: Mysteryem Date: Fri, 16 Aug 2024 19:57:04 +0100 Subject: [PATCH 4/9] TLOZ: Fix non-deterministic item pool generation (#3779) * TLOZ: Fix non-deterministic item pool generation The way the item pool was constructed involved iterating unions of sets. Sets are unordered, so the order of iteration of these combined sets would be non-deterministic, resulting in the items in the item pool being generated in a different order with the same seed. Rather than creating unions of sets at all, the original code has been replaced with using Counter objects. As a dict subclass, Counter maintains insertion order, and its update() method makes it simple to combine the separate item dictionaries into a single dictionary with the total count of each item across each of the separate item dictionaries. Fixes #3664 - After investigating more deeply, the only differences I could find between generations of the same seed was the order of items created by TLOZ, so this patch appears to fix the non-deterministic generation issue. I did manage to reproduce the non-deterministic behaviour with just TLOZ in the end, but it was very rare. I'm not entirely sure why generating with SMZ3 specifically would cause the non-deterministic behaviour in TLOZ to be frequently present, whereas generating with other games or multiple TLOZ yamls would not. * Change import order --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> --- worlds/tloz/ItemPool.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/worlds/tloz/ItemPool.py b/worlds/tloz/ItemPool.py index 4acda4ef41fc..7c57dc5a4b26 100644 --- a/worlds/tloz/ItemPool.py +++ b/worlds/tloz/ItemPool.py @@ -1,3 +1,5 @@ +from collections import Counter + from BaseClasses import ItemClassification from .Locations import level_locations, all_level_locations, standard_level_locations, shop_locations from .Options import TriforceLocations, StartingPosition @@ -58,11 +60,11 @@ "Small Key": 2, "Five Rupees": 2 } -basic_pool = { - item: overworld_items.get(item, 0) + shop_items.get(item, 0) - + major_dungeon_items.get(item, 0) + map_compass_replacements.get(item, 0) - for item in set(overworld_items) | set(shop_items) | set(major_dungeon_items) | set(map_compass_replacements) -} +basic_pool = Counter() +basic_pool.update(overworld_items) +basic_pool.update(shop_items) +basic_pool.update(major_dungeon_items) +basic_pool.update(map_compass_replacements) starting_weapons = ["Sword", "White Sword", "Magical Sword", "Magical Rod", "Red Candle"] guaranteed_shop_items = ["Small Key", "Bomb", "Water of Life (Red)", "Arrow"] @@ -135,10 +137,10 @@ def get_pool_core(world): # Finish Pool final_pool = basic_pool if world.options.ExpandedPool: - final_pool = { - item: basic_pool.get(item, 0) + minor_items.get(item, 0) + take_any_items.get(item, 0) - for item in set(basic_pool) | set(minor_items) | set(take_any_items) - } + final_pool = Counter() + final_pool.update(basic_pool) + final_pool.update(minor_items) + final_pool.update(take_any_items) final_pool["Five Rupees"] -= 1 for item in final_pool.keys(): for i in range(0, final_pool[item]): From e9c863dffd7bad83ca65ea68c6f9470d393d0b70 Mon Sep 17 00:00:00 2001 From: Emily <35015090+EmilyV99@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:04:23 -0400 Subject: [PATCH 5/9] Docs: Update 'tag' documentation (#3632) * Add tag docs for HintGame * Apply suggestions from code review Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * Make Tracker/TextOnly consistent with previous commit * Apply suggestion Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * fix spacing * Apply suggestion Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> * apply suggestion correcting footnotes Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- docs/network protocol.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/network protocol.md b/docs/network protocol.md index da5c41431501..f8080fecc879 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -702,14 +702,18 @@ GameData is a **dict** but contains these keys and values. It's broken out into | checksum | str | A checksum hash of this game's data. | ### Tags -Tags are represented as a list of strings, the common Client tags follow: - -| Name | Notes | -|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| AP | Signifies that this client is a reference client, its usefulness is mostly in debugging to compare client behaviours more easily. | -| DeathLink | Client participates in the DeathLink mechanic, therefore will send and receive DeathLink bounce packets | -| Tracker | Tells the server that this client will not send locations and is actually a Tracker. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. | -| TextOnly | Tells the server that this client will not send locations and is intended for chat. When specified and used with empty or null `game` in [Connect](#connect), game and game's version validation will be skipped. | +Tags are represented as a list of strings, the common client tags follow: + +| Name | Notes | +|-----------|--------------------------------------------------------------------------------------------------------------------------------------| +| AP | Signifies that this client is a reference client, its usefulness is mostly in debugging to compare client behaviours more easily. | +| DeathLink | Client participates in the DeathLink mechanic, therefore will send and receive DeathLink bounce packets. | +| HintGame | Indicates the client is a hint game, made to send hints instead of locations. Special join/leave message,¹ `game` is optional.² | +| Tracker | Indicates the client is a tracker, made to track instead of sending locations. Special join/leave message,¹ `game` is optional.² | +| TextOnly | Indicates the client is a basic client, made to chat instead of sending locations. Special join/leave message,¹ `game` is optional.² | + +¹: When connecting or disconnecting, the chat message shows e.g. "tracking".\ +²: Allows `game` to be empty or null in [Connect](#connect). Game and version validation will then be skipped. ### DeathLink A special kind of Bounce packet that can be supported by any AP game. It targets the tag "DeathLink" and carries the following data: From c014c5a54a8dcd83836f719d8ad06b945bfdea5e Mon Sep 17 00:00:00 2001 From: digiholic Date: Fri, 16 Aug 2024 14:10:30 -0600 Subject: [PATCH 6/9] [OSRS] Fixes Incorrect filler item names causing failures on tests. (#3768) * Updates filler item names to match the actual item names * Adds more descriptive error message in case this error comes back * Properly raises exception instead of just text * Replaces exception with assert --- worlds/osrs/Names.py | 6 +++--- worlds/osrs/__init__.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/worlds/osrs/Names.py b/worlds/osrs/Names.py index 95aed742b6f1..cc92439ef859 100644 --- a/worlds/osrs/Names.py +++ b/worlds/osrs/Names.py @@ -93,9 +93,9 @@ class ItemNames(str, Enum): Progressive_Armor = "Progressive Armor" Progressive_Weapons = "Progressive Weapons" Progressive_Tools = "Progressive Tools" - Progressive_Range_Armor = "Progressive Range Armor" - Progressive_Range_Weapon = "Progressive Range Weapon" - Progressive_Magic = "Progressive Magic Spell" + Progressive_Range_Armor = "Progressive Ranged Armor" + Progressive_Range_Weapon = "Progressive Ranged Weapons" + Progressive_Magic = "Progressive Magic" Lobsters = "10 Lobsters" Swordfish = "5 Swordfish" Energy_Potions = "10 Energy Potions" diff --git a/worlds/osrs/__init__.py b/worlds/osrs/__init__.py index f726b4b81bf2..d74dc7cfd9c2 100644 --- a/worlds/osrs/__init__.py +++ b/worlds/osrs/__init__.py @@ -524,7 +524,9 @@ def create_region(self, name: str) -> "Region": return region def create_item(self, item_name: str) -> "Item": - item = [item for item in item_rows if item.name == item_name][0] + items = [item for item in item_rows if item.name == item_name] + assert len(items) > 0, f"No matching item found for name {item_name} for player {self.player_name}" + item = items[0] index = item_rows.index(item) return OSRSItem(item.name, item.progression, self.base_id + index, self.player) From ca96e7e2941325c46035a5f69775b5baffbf18e0 Mon Sep 17 00:00:00 2001 From: Kaito Sinclaire Date: Fri, 16 Aug 2024 13:20:02 -0700 Subject: [PATCH 7/9] Fix !remaining for cross-world items (#3732) * Fix !remaining for other worlds * Typing fixes for the previous change * Update LocationStore test to match what get_remaining now returns --- MultiServer.py | 18 +++++++++--------- NetUtils.py | 8 ++++---- _speedups.pyx | 8 ++++---- test/netutils/test_location_store.py | 6 +++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index f59855fca6a4..b7c0e0f74555 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -991,7 +991,7 @@ def collect_player(ctx: Context, team: int, slot: int, is_group: bool = False): collect_player(ctx, team, group, True) -def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[int]: +def get_remaining(ctx: Context, team: int, slot: int) -> typing.List[typing.Tuple[int, int]]: return ctx.locations.get_remaining(ctx.location_checks, team, slot) @@ -1350,10 +1350,10 @@ def _cmd_collect(self) -> bool: def _cmd_remaining(self) -> bool: """List remaining items in your game, but not their location or recipient""" if self.ctx.remaining_mode == "enabled": - remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot) - if remaining_item_ids: - self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id] - for item_id in remaining_item_ids)) + rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot) + if rest_locations: + self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id] + for slot, item_id in rest_locations)) else: self.output("No remaining items found.") return True @@ -1363,10 +1363,10 @@ def _cmd_remaining(self) -> bool: return False else: # is goal if self.ctx.client_game_state[self.client.team, self.client.slot] == ClientStatus.CLIENT_GOAL: - remaining_item_ids = get_remaining(self.ctx, self.client.team, self.client.slot) - if remaining_item_ids: - self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[self.client.slot]][item_id] - for item_id in remaining_item_ids)) + rest_locations = get_remaining(self.ctx, self.client.team, self.client.slot) + if rest_locations: + self.output("Remaining items: " + ", ".join(self.ctx.item_names[self.ctx.games[slot]][item_id] + for slot, item_id in rest_locations)) else: self.output("No remaining items found.") return True diff --git a/NetUtils.py b/NetUtils.py index f8d698c74fcc..f79773728cd6 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -397,12 +397,12 @@ def get_missing(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int] location_id not in checked] def get_remaining(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]], team: int, slot: int - ) -> typing.List[int]: + ) -> typing.List[typing.Tuple[int, int]]: checked = state[team, slot] player_locations = self[slot] - return sorted([player_locations[location_id][0] for - location_id in player_locations if - location_id not in checked]) + return sorted([(player_locations[location_id][1], player_locations[location_id][0]) for + location_id in player_locations if + location_id not in checked]) if typing.TYPE_CHECKING: # type-check with pure python implementation until we have a typing stub diff --git a/_speedups.pyx b/_speedups.pyx index 4b083c2f9aef..dc039e336500 100644 --- a/_speedups.pyx +++ b/_speedups.pyx @@ -287,15 +287,15 @@ cdef class LocationStore: entry in self.entries[start:start + count] if entry.location not in checked] - def get_remaining(self, state: State, team: int, slot: int) -> List[int]: + def get_remaining(self, state: State, team: int, slot: int) -> List[Tuple[int, int]]: cdef LocationEntry* entry cdef ap_player_t sender = slot cdef size_t start = self.sender_index[sender].start cdef size_t count = self.sender_index[sender].count cdef set checked = state[team, slot] - return sorted([entry.item for - entry in self.entries[start:start+count] if - entry.location not in checked]) + return sorted([(entry.receiver, entry.item) for + entry in self.entries[start:start+count] if + entry.location not in checked]) @cython.auto_pickle(False) diff --git a/test/netutils/test_location_store.py b/test/netutils/test_location_store.py index f3e83989bea4..1b984015844d 100644 --- a/test/netutils/test_location_store.py +++ b/test/netutils/test_location_store.py @@ -130,9 +130,9 @@ def test_get_missing(self) -> None: def test_get_remaining(self) -> None: self.assertEqual(self.store.get_remaining(full_state, 0, 1), []) - self.assertEqual(self.store.get_remaining(one_state, 0, 1), [13, 21]) - self.assertEqual(self.store.get_remaining(empty_state, 0, 1), [13, 21, 22]) - self.assertEqual(self.store.get_remaining(empty_state, 0, 3), [99]) + self.assertEqual(self.store.get_remaining(one_state, 0, 1), [(1, 13), (2, 21)]) + self.assertEqual(self.store.get_remaining(empty_state, 0, 1), [(1, 13), (2, 21), (2, 22)]) + self.assertEqual(self.store.get_remaining(empty_state, 0, 3), [(4, 99)]) def test_location_set_intersection(self) -> None: locations = {10, 11, 12} From 81092247c65817dc5f7529ce0ffd934b8007d085 Mon Sep 17 00:00:00 2001 From: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> Date: Fri, 16 Aug 2024 16:20:20 -0400 Subject: [PATCH 8/9] Core: early_local != local_early #3780 --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index edae5d7b19b1..c931e22145a5 100644 --- a/Main.py +++ b/Main.py @@ -101,7 +101,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No multiworld.early_items[player][item_name] = max(0, early-count) remaining_count = count-early if remaining_count > 0: - local_early = multiworld.early_local_items[player].get(item_name, 0) + local_early = multiworld.local_early_items[player].get(item_name, 0) if local_early: multiworld.early_items[player][item_name] = max(0, local_early - remaining_count) del local_early From f5218faea73983172a593f2caae9058769c0b7db Mon Sep 17 00:00:00 2001 From: Bryce Wilson Date: Fri, 16 Aug 2024 13:23:47 -0700 Subject: [PATCH 9/9] Pokemon Emerald: Ensure dig tutor is always usable (#3660) * Pokemon Emerald: Ensure dig tutor is always usable * Pokemon Emerald: Clarify comment Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/pokemon_emerald/rom.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/worlds/pokemon_emerald/rom.py b/worlds/pokemon_emerald/rom.py index 968a103ccd25..75d7d575846d 100644 --- a/worlds/pokemon_emerald/rom.py +++ b/worlds/pokemon_emerald/rom.py @@ -817,6 +817,8 @@ def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", patch: Pokemon def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None: + FORTREE_MOVE_TUTOR_INDEX = 24 + if easter_egg[0] == 2: for i in range(30): patch.write_token( @@ -840,18 +842,26 @@ def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", patch: PokemonEmer # Always set Fortree move tutor to Dig patch.write_token( APTokenTypes.WRITE, - data.rom_addresses["gTutorMoves"] + (24 * 2), + data.rom_addresses["gTutorMoves"] + (FORTREE_MOVE_TUTOR_INDEX * 2), struct.pack("=50%) compatibility + if world.options.tm_tutor_compatibility.value < 50: + compatibility &= ~(1 << FORTREE_MOVE_TUTOR_INDEX) + if world.random.random() < 0.5: + compatibility |= 1 << FORTREE_MOVE_TUTOR_INDEX + patch.write_token( APTokenTypes.WRITE, data.rom_addresses["sTutorLearnsets"] + (species.species_id * 4), - struct.pack("