diff options
Diffstat (limited to 'apworld')
| -rw-r--r-- | apworld/__init__.py | 11 | ||||
| -rw-r--r-- | apworld/client/gamedata.gd | 4 | ||||
| -rw-r--r-- | apworld/client/manager.gd | 4 | ||||
| -rw-r--r-- | apworld/client/player.gd | 15 | ||||
| -rw-r--r-- | apworld/context.py | 31 | ||||
| -rw-r--r-- | apworld/options.py | 2 | ||||
| -rw-r--r-- | apworld/player_logic.py | 13 | ||||
| -rw-r--r-- | apworld/static_logic.py | 6 | ||||
| -rw-r--r-- | apworld/tracker.py | 5 |
9 files changed, 71 insertions, 20 deletions
| diff --git a/apworld/__init__.py b/apworld/__init__.py index f5774c6..3d2f075 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py | |||
| @@ -77,7 +77,10 @@ class Lingo2World(World): | |||
| 77 | if self.options.shuffle_worldports: | 77 | if self.options.shuffle_worldports: |
| 78 | if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough: | 78 | if hasattr(self.multiworld, "re_gen_passthrough") and "Lingo 2" in self.multiworld.re_gen_passthrough: |
| 79 | slot_value = self.multiworld.re_gen_passthrough["Lingo 2"]["port_pairings"] | 79 | slot_value = self.multiworld.re_gen_passthrough["Lingo 2"]["port_pairings"] |
| 80 | self.port_pairings = {int(fp): int(tp) for fp, tp in slot_value.items()} | 80 | self.port_pairings = { |
| 81 | self.static_logic.port_id_by_ap_id[int(fp)]: self.static_logic.port_id_by_ap_id[int(tp)] | ||
| 82 | for fp, tp in slot_value.items() | ||
| 83 | } | ||
| 81 | 84 | ||
| 82 | connect_ports_from_ut(self.port_pairings, self) | 85 | connect_ports_from_ut(self.port_pairings, self) |
| 83 | else: | 86 | else: |
| @@ -152,7 +155,11 @@ class Lingo2World(World): | |||
| 152 | } | 155 | } |
| 153 | 156 | ||
| 154 | if self.options.shuffle_worldports: | 157 | if self.options.shuffle_worldports: |
| 155 | slot_data["port_pairings"] = self.port_pairings | 158 | def get_port_ap_id(port_id): |
| 159 | return self.static_logic.objects.ports[port_id].ap_id | ||
| 160 | |||
| 161 | slot_data["port_pairings"] = {get_port_ap_id(from_id): get_port_ap_id(to_id) | ||
| 162 | for from_id, to_id in self.port_pairings.items()} | ||
| 156 | 163 | ||
| 157 | return slot_data | 164 | return slot_data |
| 158 | 165 | ||
| diff --git a/apworld/client/gamedata.gd b/apworld/client/gamedata.gd index 3a35125..d7e3136 100644 --- a/apworld/client/gamedata.gd +++ b/apworld/client/gamedata.gd | |||
| @@ -15,6 +15,7 @@ var symbol_item_ids = [] | |||
| 15 | var anti_trap_ids = {} | 15 | var anti_trap_ids = {} |
| 16 | var location_name_by_id = {} | 16 | var location_name_by_id = {} |
| 17 | var ending_display_name_by_name = {} | 17 | var ending_display_name_by_name = {} |
| 18 | var port_id_by_ap_id = {} | ||
| 18 | 19 | ||
| 19 | var kSYMBOL_ITEMS | 20 | var kSYMBOL_ITEMS |
| 20 | 21 | ||
| @@ -99,6 +100,9 @@ func load(data_bytes): | |||
| 99 | var map_data = port_id_by_map_node_path[map.get_name()] | 100 | var map_data = port_id_by_map_node_path[map.get_name()] |
| 100 | map_data[port.get_path()] = port.get_id() | 101 | map_data[port.get_path()] = port.get_id() |
| 101 | 102 | ||
| 103 | if port.has_ap_id(): | ||
| 104 | port_id_by_ap_id[port.get_ap_id()] = port.get_id() | ||
| 105 | |||
| 102 | for progressive in objects.get_progressives(): | 106 | for progressive in objects.get_progressives(): |
| 103 | progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id() | 107 | progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id() |
| 104 | 108 | ||
| diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index aa07559..727d17a 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd | |||
| @@ -472,7 +472,9 @@ func _client_connected(slot_data): | |||
| 472 | var raw_pp = slot_data.get("port_pairings") | 472 | var raw_pp = slot_data.get("port_pairings") |
| 473 | 473 | ||
| 474 | for p1 in raw_pp.keys(): | 474 | for p1 in raw_pp.keys(): |
| 475 | port_pairings[int(p1)] = int(raw_pp[p1]) | 475 | port_pairings[gamedata.port_id_by_ap_id[int(p1)]] = gamedata.port_id_by_ap_id[int( |
| 476 | raw_pp[p1] | ||
| 477 | )] | ||
| 476 | 478 | ||
| 477 | # Set up item locks. | 479 | # Set up item locks. |
| 478 | _item_locks = {} | 480 | _item_locks = {} |
| diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 712a59b..35fd793 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd | |||
| @@ -586,6 +586,12 @@ func _ready(): | |||
| 586 | saver.senderGroup.append(NodePath("/root/scene/Components/Collectables")) | 586 | saver.senderGroup.append(NodePath("/root/scene/Components/Collectables")) |
| 587 | get_node("/root/scene").add_child.call_deferred(saver) | 587 | get_node("/root/scene").add_child.call_deferred(saver) |
| 588 | 588 | ||
| 589 | # Shrink the painting trigger in The Unyielding. | ||
| 590 | if global.map == "the_unyielding": | ||
| 591 | var trigger_area = get_node("/root/scene/Components/PaintingUnlocker/triggerArea") | ||
| 592 | trigger_area.position = Vector3(0, 0, -6) | ||
| 593 | trigger_area.scale = Vector3(6, 1, 6) | ||
| 594 | |||
| 589 | ap.update_job_well_done_sign() | 595 | ap.update_job_well_done_sign() |
| 590 | 596 | ||
| 591 | # Set up door locations. | 597 | # Set up door locations. |
| @@ -598,9 +604,12 @@ func _ready(): | |||
| 598 | continue | 604 | continue |
| 599 | 605 | ||
| 600 | if ( | 606 | if ( |
| 601 | door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY | 607 | not (door.has_legacy_location() and door.get_legacy_location()) |
| 602 | or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING | 608 | and ( |
| 603 | or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR | 609 | door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY |
| 610 | or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING | ||
| 611 | or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR | ||
| 612 | ) | ||
| 604 | ): | 613 | ): |
| 605 | continue | 614 | continue |
| 606 | 615 | ||
| diff --git a/apworld/context.py b/apworld/context.py index e2d80cd..52b04ae 100644 --- a/apworld/context.py +++ b/apworld/context.py | |||
| @@ -30,6 +30,16 @@ KEY_STORAGE_MAPPING = { | |||
| 30 | REVERSE_KEY_STORAGE_MAPPING = {t: k for k, t in KEY_STORAGE_MAPPING.items()} | 30 | REVERSE_KEY_STORAGE_MAPPING = {t: k for k, t in KEY_STORAGE_MAPPING.items()} |
| 31 | 31 | ||
| 32 | 32 | ||
| 33 | # There is a distinction between an object's ID and its AP ID. The latter is stable between releases, whereas the former | ||
| 34 | # can change and is also namespaced based on the object type. We should only store AP IDs in multiworld state (such as | ||
| 35 | # slot data and data storage) to increase compatability between releases. The data we currently store is: | ||
| 36 | # - Port pairings for worldport shuffle (slot data) | ||
| 37 | # - Checked worldports for worldport shuffle (data storage) | ||
| 38 | # - Latched doors (data storage) | ||
| 39 | # The client generally deals in the actual object IDs rather than the stable IDs, although it does have to convert the | ||
| 40 | # port pairing IDs when reading them from slot data. The context (this file here) does the work of converting back and | ||
| 41 | # forth between the values. AP IDs are converted to IDs after reading them from data storage, and IDs are converted to | ||
| 42 | # AP IDs before sending them to data storage. | ||
| 33 | class Lingo2Manager: | 43 | class Lingo2Manager: |
| 34 | game_ctx: "Lingo2GameContext" | 44 | game_ctx: "Lingo2GameContext" |
| 35 | client_ctx: "Lingo2ClientContext" | 45 | client_ctx: "Lingo2ClientContext" |
| @@ -74,6 +84,7 @@ class Lingo2Manager: | |||
| 74 | 84 | ||
| 75 | return ret | 85 | return ret |
| 76 | 86 | ||
| 87 | # Input should be real IDs, not AP IDs | ||
| 77 | def update_worldports(self, new_worldports: set[int]) -> set[int]: | 88 | def update_worldports(self, new_worldports: set[int]) -> set[int]: |
| 78 | ret = new_worldports.difference(self.worldports) | 89 | ret = new_worldports.difference(self.worldports) |
| 79 | self.worldports.update(new_worldports) | 90 | self.worldports.update(new_worldports) |
| @@ -227,6 +238,7 @@ class Lingo2GameContext: | |||
| 227 | 238 | ||
| 228 | async_start(self.send_msgs([msg]), name="update keyboard") | 239 | async_start(self.send_msgs([msg]), name="update keyboard") |
| 229 | 240 | ||
| 241 | # Input should be real IDs, not AP IDs | ||
| 230 | def send_update_worldports(self, worldports): | 242 | def send_update_worldports(self, worldports): |
| 231 | if self.server is None: | 243 | if self.server is None: |
| 232 | return | 244 | return |
| @@ -441,13 +453,15 @@ class Lingo2ClientContext(CommonContext): | |||
| 441 | elif args["key"] == self.get_datastorage_key("keyboard2"): | 453 | elif args["key"] == self.get_datastorage_key("keyboard2"): |
| 442 | self.handle_keyboard_update(2, args) | 454 | self.handle_keyboard_update(2, args) |
| 443 | elif args["key"] == self.get_datastorage_key("worldports"): | 455 | elif args["key"] == self.get_datastorage_key("worldports"): |
| 444 | updates = self.manager.update_worldports(set(args["value"])) | 456 | port_ids = set(Lingo2World.static_logic.port_id_by_ap_id[ap_id] for ap_id in args["value"]) |
| 457 | updates = self.manager.update_worldports(port_ids) | ||
| 445 | if len(updates) > 0: | 458 | if len(updates) > 0: |
| 446 | self.manager.game_ctx.send_update_worldports(updates) | 459 | self.manager.game_ctx.send_update_worldports(updates) |
| 447 | elif args["key"] == self.victory_data_storage_key: | 460 | elif args["key"] == self.victory_data_storage_key: |
| 448 | self.handle_status_update(args["value"]) | 461 | self.handle_status_update(args["value"]) |
| 449 | elif args["key"] == self.get_datastorage_key("latches"): | 462 | elif args["key"] == self.get_datastorage_key("latches"): |
| 450 | updates = self.manager.update_latches(set(args["value"])) | 463 | door_ids = set(Lingo2World.static_logic.door_id_by_ap_id[ap_id] for ap_id in args["value"]) |
| 464 | updates = self.manager.update_latches(door_ids) | ||
| 451 | if len(updates) > 0: | 465 | if len(updates) > 0: |
| 452 | self.manager.game_ctx.send_update_latches(updates) | 466 | self.manager.game_ctx.send_update_latches(updates) |
| 453 | 467 | ||
| @@ -515,14 +529,16 @@ class Lingo2ClientContext(CommonContext): | |||
| 515 | if len(updates) > 0: | 529 | if len(updates) > 0: |
| 516 | self.manager.game_ctx.send_update_keyboard(updates) | 530 | self.manager.game_ctx.send_update_keyboard(updates) |
| 517 | 531 | ||
| 532 | # Input should be real IDs, not AP IDs | ||
| 518 | async def update_worldports(self, updates: set[int]): | 533 | async def update_worldports(self, updates: set[int]): |
| 534 | port_ap_ids = [Lingo2World.static_logic.objects.ports[port_id].ap_id for port_id in updates] | ||
| 519 | await self.send_msgs([{ | 535 | await self.send_msgs([{ |
| 520 | "cmd": "Set", | 536 | "cmd": "Set", |
| 521 | "key": self.get_datastorage_key("worldports"), | 537 | "key": self.get_datastorage_key("worldports"), |
| 522 | "want_reply": True, | 538 | "want_reply": True, |
| 523 | "operations": [{ | 539 | "operations": [{ |
| 524 | "operation": "update", | 540 | "operation": "update", |
| 525 | "value": updates | 541 | "value": port_ap_ids |
| 526 | }] | 542 | }] |
| 527 | }]) | 543 | }]) |
| 528 | 544 | ||
| @@ -532,13 +548,14 @@ class Lingo2ClientContext(CommonContext): | |||
| 532 | self.manager.game_ctx.send_accessible_locations() | 548 | self.manager.game_ctx.send_accessible_locations() |
| 533 | 549 | ||
| 534 | async def update_latches(self, updates: set[int]): | 550 | async def update_latches(self, updates: set[int]): |
| 551 | door_ap_ids = [Lingo2World.static_logic.objects.doors[door_id].ap_id for door_id in updates] | ||
| 535 | await self.send_msgs([{ | 552 | await self.send_msgs([{ |
| 536 | "cmd": "Set", | 553 | "cmd": "Set", |
| 537 | "key": self.get_datastorage_key("latches"), | 554 | "key": self.get_datastorage_key("latches"), |
| 538 | "want_reply": True, | 555 | "want_reply": True, |
| 539 | "operations": [{ | 556 | "operations": [{ |
| 540 | "operation": "update", | 557 | "operation": "update", |
| 541 | "value": updates | 558 | "value": door_ap_ids |
| 542 | }] | 559 | }] |
| 543 | }]) | 560 | }]) |
| 544 | 561 | ||
| @@ -590,12 +607,14 @@ async def process_game_cmd(manager: Lingo2Manager, args: dict): | |||
| 590 | async_start(manager.client_ctx.update_keyboard(updates), name="client update keyboard") | 607 | async_start(manager.client_ctx.update_keyboard(updates), name="client update keyboard") |
| 591 | elif cmd == "CheckWorldport": | 608 | elif cmd == "CheckWorldport": |
| 592 | port_id = args["port_id"] | 609 | port_id = args["port_id"] |
| 610 | port_ap_id = Lingo2World.static_logic.objects.ports[port_id].ap_id | ||
| 593 | worldports = {port_id} | 611 | worldports = {port_id} |
| 594 | 612 | ||
| 595 | # Also check the reverse port if it's a two-way connection. | 613 | # Also check the reverse port if it's a two-way connection. |
| 596 | port_pairings = manager.client_ctx.slot_data["port_pairings"] | 614 | port_pairings = manager.client_ctx.slot_data["port_pairings"] |
| 597 | if str(port_id) in port_pairings and port_pairings.get(str(port_pairings[str(port_id)]), None) == port_id: | 615 | if str(port_ap_id) in port_pairings and\ |
| 598 | worldports.add(port_pairings[str(port_id)]) | 616 | port_pairings.get(str(port_pairings[str(port_ap_id)]), None) == port_ap_id: |
| 617 | worldports.add(Lingo2World.static_logic.port_id_by_ap_id[port_pairings[str(port_ap_id)]]) | ||
| 599 | 618 | ||
| 600 | updates = manager.update_worldports(worldports) | 619 | updates = manager.update_worldports(worldports) |
| 601 | if len(updates) > 0: | 620 | if len(updates) > 0: |
| diff --git a/apworld/options.py b/apworld/options.py index a56b40d..f687434 100644 --- a/apworld/options.py +++ b/apworld/options.py | |||
| @@ -197,7 +197,7 @@ class MasteriesRequirement(Range): | |||
| 197 | the maximum.""" | 197 | the maximum.""" |
| 198 | display_name = "Masteries Requirement" | 198 | display_name = "Masteries Requirement" |
| 199 | range_start = 0 | 199 | range_start = 0 |
| 200 | range_end = 18 | 200 | range_end = 19 |
| 201 | default = 0 | 201 | default = 0 |
| 202 | 202 | ||
| 203 | 203 | ||
| diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 0cbcdec..57fb4f9 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py | |||
| @@ -389,14 +389,14 @@ class Lingo2PlayerLogic: | |||
| 389 | self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id, | 389 | self.locations_by_room.setdefault(ending.room_id, []).append(PlayerLocation(ending.ap_id, |
| 390 | AccessRequirements())) | 390 | AccessRequirements())) |
| 391 | 391 | ||
| 392 | event_name = f"{ending.name.capitalize()} Ending (Achieved)" | ||
| 393 | item_name = "Ending" | ||
| 394 | |||
| 395 | if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: | 392 | if world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: |
| 396 | item_name = "Victory" | 393 | event_name = f"{ending.name.capitalize()} Ending (Goal)" |
| 394 | self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = "Victory" | ||
| 397 | self.goal_room_id = ending.room_id | 395 | self.goal_room_id = ending.room_id |
| 398 | 396 | ||
| 399 | self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name | 397 | if ending.name != "WHITE": |
| 398 | event_name = f"{ending.name.capitalize()} Ending (Achieved)" | ||
| 399 | self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = "Ending" | ||
| 400 | 400 | ||
| 401 | if self.world.options.keyholder_sanity: | 401 | if self.world.options.keyholder_sanity: |
| 402 | for keyholder in world.static_logic.objects.keyholders: | 402 | for keyholder in world.static_logic.objects.keyholders: |
| @@ -416,6 +416,8 @@ class Lingo2PlayerLogic: | |||
| 416 | for symbol_name in SYMBOL_ITEMS.values(): | 416 | for symbol_name in SYMBOL_ITEMS.values(): |
| 417 | self.real_items.append(symbol_name) | 417 | self.real_items.append(symbol_name) |
| 418 | 418 | ||
| 419 | print("hi") | ||
| 420 | |||
| 419 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: | 421 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: |
| 420 | if answer is None: | 422 | if answer is None: |
| 421 | if panel_id not in self.panel_reqs: | 423 | if panel_id not in self.panel_reqs: |
| @@ -511,7 +513,6 @@ class Lingo2PlayerLogic: | |||
| 511 | reqs.possibilities.append(panel_reqs) | 513 | reqs.possibilities.append(panel_reqs) |
| 512 | 514 | ||
| 513 | if door.HasField("control_center_color"): | 515 | if door.HasField("control_center_color"): |
| 514 | # TODO: Logic for ensuring two CC states aren't needed at once. | ||
| 515 | reqs.rooms.add("Control Center - Main Area") | 516 | reqs.rooms.add("Control Center - Main Area") |
| 516 | self.add_solution_reqs(reqs, door.control_center_color) | 517 | self.add_solution_reqs(reqs, door.control_center_color) |
| 517 | 518 | ||
| diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 8e07b82..8a84111 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py | |||
| @@ -15,6 +15,9 @@ class Lingo2StaticLogic: | |||
| 15 | 15 | ||
| 16 | letter_weights: dict[str, int] | 16 | letter_weights: dict[str, int] |
| 17 | 17 | ||
| 18 | door_id_by_ap_id: dict[int, int] | ||
| 19 | port_id_by_ap_id: dict[int, int] | ||
| 20 | |||
| 18 | def __init__(self): | 21 | def __init__(self): |
| 19 | self.item_id_to_name = {} | 22 | self.item_id_to_name = {} |
| 20 | self.location_id_to_name = {} | 23 | self.location_id_to_name = {} |
| @@ -83,6 +86,9 @@ class Lingo2StaticLogic: | |||
| 83 | for letter in panel.answer.upper(): | 86 | for letter in panel.answer.upper(): |
| 84 | self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1 | 87 | self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1 |
| 85 | 88 | ||
| 89 | self.door_id_by_ap_id = {door.ap_id: door.id for door in self.objects.doors if door.HasField("ap_id")} | ||
| 90 | self.port_id_by_ap_id = {port.ap_id: port.id for port in self.objects.ports if port.HasField("ap_id")} | ||
| 91 | |||
| 86 | def get_door_item_name(self, door: data_pb2.Door) -> str: | 92 | def get_door_item_name(self, door: data_pb2.Door) -> str: |
| 87 | return f"{self.get_map_object_map_name(door)} - {door.name}" | 93 | return f"{self.get_map_object_map_name(door)} - {door.name}" |
| 88 | 94 | ||
| diff --git a/apworld/tracker.py b/apworld/tracker.py index c65317c..d473af4 100644 --- a/apworld/tracker.py +++ b/apworld/tracker.py | |||
| @@ -47,7 +47,10 @@ class Tracker: | |||
| 47 | self.world.create_regions() | 47 | self.world.create_regions() |
| 48 | 48 | ||
| 49 | if self.world.options.shuffle_worldports: | 49 | if self.world.options.shuffle_worldports: |
| 50 | port_pairings = {int(fp): int(tp) for fp, tp in slot_data["port_pairings"].items()} | 50 | port_pairings = { |
| 51 | self.world.static_logic.port_id_by_ap_id[int(fp)]: self.world.static_logic.port_id_by_ap_id[int(tp)] | ||
| 52 | for fp, tp in slot_data["port_pairings"].items() | ||
| 53 | } | ||
| 51 | connect_ports_from_ut(port_pairings, self.world) | 54 | connect_ports_from_ut(port_pairings, self.world) |
| 52 | 55 | ||
| 53 | self.refresh_state() | 56 | self.refresh_state() |
