diff options
Diffstat (limited to 'apworld/regions.py')
-rw-r--r-- | apworld/regions.py | 153 |
1 files changed, 141 insertions, 12 deletions
diff --git a/apworld/regions.py b/apworld/regions.py index fe2c99b..0c3858d 100644 --- a/apworld/regions.py +++ b/apworld/regions.py | |||
@@ -1,6 +1,8 @@ | |||
1 | from typing import TYPE_CHECKING | 1 | from typing import TYPE_CHECKING |
2 | 2 | ||
3 | import BaseClasses | ||
3 | from BaseClasses import Region, ItemClassification, Entrance | 4 | from BaseClasses import Region, ItemClassification, Entrance |
5 | from entrance_rando import randomize_entrances | ||
4 | from .items import Lingo2Item | 6 | from .items import Lingo2Item |
5 | from .locations import Lingo2Location | 7 | from .locations import Lingo2Location |
6 | from .player_logic import AccessRequirements | 8 | from .player_logic import AccessRequirements |
@@ -11,21 +13,42 @@ if TYPE_CHECKING: | |||
11 | 13 | ||
12 | 14 | ||
13 | def create_region(room, world: "Lingo2World") -> Region: | 15 | def create_region(room, world: "Lingo2World") -> Region: |
14 | new_region = Region(world.static_logic.get_room_region_name(room.id), world.player, world.multiworld) | 16 | return Region(world.static_logic.get_room_region_name(room.id), world.player, world.multiworld) |
15 | 17 | ||
18 | |||
19 | def create_locations(room, new_region: Region, world: "Lingo2World", regions: dict[str, Region]): | ||
16 | for location in world.player_logic.locations_by_room.get(room.id, {}): | 20 | for location in world.player_logic.locations_by_room.get(room.id, {}): |
21 | reqs = location.reqs.copy() | ||
22 | reqs.remove_room(new_region.name) | ||
23 | |||
17 | new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code], | 24 | new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code], |
18 | location.code, new_region) | 25 | location.code, new_region) |
19 | new_location.access_rule = make_location_lambda(location.reqs, world) | 26 | new_location.access_rule = make_location_lambda(reqs, world, regions) |
20 | new_region.locations.append(new_location) | 27 | new_region.locations.append(new_location) |
21 | 28 | ||
22 | for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): | 29 | for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): |
23 | new_location = Lingo2Location(world.player, event_name, None, new_region) | 30 | new_location = Lingo2Location(world.player, event_name, None, new_region) |
31 | if world.for_tracker and item_name == "Victory": | ||
32 | new_location.goal = True | ||
33 | |||
24 | event_item = Lingo2Item(item_name, ItemClassification.progression, None, world.player) | 34 | event_item = Lingo2Item(item_name, ItemClassification.progression, None, world.player) |
25 | new_location.place_locked_item(event_item) | 35 | new_location.place_locked_item(event_item) |
26 | new_region.locations.append(new_location) | 36 | new_region.locations.append(new_location) |
27 | 37 | ||
28 | return new_region | 38 | if world.for_tracker and world.options.shuffle_worldports: |
39 | for port_id in room.ports: | ||
40 | port = world.static_logic.objects.ports[port_id] | ||
41 | if port.no_shuffle: | ||
42 | continue | ||
43 | |||
44 | new_location = Lingo2Location(world.player, f"Worldport {port.id} Entered", None, new_region) | ||
45 | new_location.port_id = port.id | ||
46 | |||
47 | if port.HasField("required_door"): | ||
48 | new_location.access_rule = \ | ||
49 | make_location_lambda(world.player_logic.get_door_open_reqs(port.required_door), world, regions) | ||
50 | |||
51 | new_region.locations.append(new_location) | ||
29 | 52 | ||
30 | 53 | ||
31 | def create_regions(world: "Lingo2World"): | 54 | def create_regions(world: "Lingo2World"): |
@@ -33,16 +56,34 @@ def create_regions(world: "Lingo2World"): | |||
33 | "Menu": Region("Menu", world.player, world.multiworld) | 56 | "Menu": Region("Menu", world.player, world.multiworld) |
34 | } | 57 | } |
35 | 58 | ||
59 | region_and_room = [] | ||
60 | |||
61 | # Create the regions in two stages. First, make the actual region objects and memoize them. Then, add all of the | ||
62 | # locations. This allows us to reference the actual region objects in the access rules for the locations, which is | ||
63 | # faster than having to look them up during access checking. | ||
36 | for room in world.static_logic.objects.rooms: | 64 | for room in world.static_logic.objects.rooms: |
37 | region = create_region(room, world) | 65 | region = create_region(room, world) |
38 | regions[region.name] = region | 66 | regions[region.name] = region |
67 | region_and_room.append((region, room)) | ||
68 | |||
69 | for (region, room) in region_and_room: | ||
70 | create_locations(room, region, world, regions) | ||
39 | 71 | ||
40 | regions["Menu"].connect(regions["The Entry - Starting Room"], "Start Game") | 72 | regions["Menu"].connect(regions["The Entry - Starting Room"], "Start Game") |
41 | 73 | ||
42 | # TODO: The requirements of the opposite trigger also matter. | ||
43 | for connection in world.static_logic.objects.connections: | 74 | for connection in world.static_logic.objects.connections: |
75 | if connection.roof_access and not world.options.daedalus_roof_access: | ||
76 | continue | ||
77 | |||
78 | if connection.vanilla_only and world.options.shuffle_doors: | ||
79 | continue | ||
80 | |||
44 | from_region = world.static_logic.get_room_region_name(connection.from_room) | 81 | from_region = world.static_logic.get_room_region_name(connection.from_room) |
45 | to_region = world.static_logic.get_room_region_name(connection.to_room) | 82 | to_region = world.static_logic.get_room_region_name(connection.to_room) |
83 | |||
84 | if from_region not in regions or to_region not in regions: | ||
85 | continue | ||
86 | |||
46 | connection_name = f"{from_region} -> {to_region}" | 87 | connection_name = f"{from_region} -> {to_region}" |
47 | 88 | ||
48 | reqs = AccessRequirements() | 89 | reqs = AccessRequirements() |
@@ -56,7 +97,10 @@ def create_regions(world: "Lingo2World"): | |||
56 | 97 | ||
57 | if connection.HasField("port"): | 98 | if connection.HasField("port"): |
58 | port = world.static_logic.objects.ports[connection.port] | 99 | port = world.static_logic.objects.ports[connection.port] |
59 | connection_name = f"{connection_name} (via port {port.name})" | 100 | connection_name = f"{connection_name} (via {port.display_name})" |
101 | |||
102 | if world.options.shuffle_worldports and not port.no_shuffle: | ||
103 | continue | ||
60 | 104 | ||
61 | if port.HasField("required_door"): | 105 | if port.HasField("required_door"): |
62 | reqs.merge(world.player_logic.get_door_open_reqs(port.required_door)) | 106 | reqs.merge(world.player_logic.get_door_open_reqs(port.required_door)) |
@@ -79,14 +123,99 @@ def create_regions(world: "Lingo2World"): | |||
79 | else: | 123 | else: |
80 | connection_name = f"{connection_name} (via panel {panel.name})" | 124 | connection_name = f"{connection_name} (via panel {panel.name})" |
81 | 125 | ||
82 | if from_region in regions and to_region in regions: | 126 | if connection.HasField("purple_ending") and connection.purple_ending and world.options.strict_purple_ending: |
83 | connection = Entrance(world.player, connection_name, regions[from_region]) | 127 | world.player_logic.add_solution_reqs(reqs, "abcdefghijklmnopqrstuvwxyz") |
84 | connection.access_rule = make_location_lambda(reqs, world) | 128 | |
129 | if connection.HasField("cyan_ending") and connection.cyan_ending and world.options.strict_cyan_ending: | ||
130 | world.player_logic.add_solution_reqs(reqs, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz") | ||
131 | |||
132 | reqs.simplify() | ||
133 | reqs.remove_room(from_region) | ||
85 | 134 | ||
86 | regions[from_region].exits.append(connection) | 135 | if to_region in reqs.rooms: |
87 | connection.connect(regions[to_region]) | 136 | # This connection can't ever increase access because you're required to have access to the other side in |
137 | # order for it to be usable. We will just not create the connection at all, in order to help GER figure out | ||
138 | # what regions are dead ends. | ||
139 | continue | ||
88 | 140 | ||
89 | for region in reqs.rooms: | 141 | connection = Entrance(world.player, connection_name, regions[from_region]) |
90 | world.multiworld.register_indirect_condition(regions[region], connection) | 142 | connection.access_rule = make_location_lambda(reqs, world, regions) |
143 | |||
144 | regions[from_region].exits.append(connection) | ||
145 | connection.connect(regions[to_region]) | ||
146 | |||
147 | for region in reqs.get_referenced_rooms(): | ||
148 | world.multiworld.register_indirect_condition(regions[region], connection) | ||
91 | 149 | ||
92 | world.multiworld.regions += regions.values() | 150 | world.multiworld.regions += regions.values() |
151 | |||
152 | |||
153 | def shuffle_entrances(world: "Lingo2World"): | ||
154 | er_entrances: list[Entrance] = [] | ||
155 | er_exits: list[Entrance] = [] | ||
156 | |||
157 | port_id_by_name: dict[str, int] = {} | ||
158 | |||
159 | for port in world.static_logic.objects.ports: | ||
160 | if port.no_shuffle: | ||
161 | continue | ||
162 | |||
163 | port_region_name = world.static_logic.get_room_region_name(port.room_id) | ||
164 | port_region = world.multiworld.get_region(port_region_name, world.player) | ||
165 | |||
166 | connection_name = f"{port_region_name} - {port.display_name}" | ||
167 | port_id_by_name[connection_name] = port.id | ||
168 | |||
169 | entrance = port_region.create_er_target(connection_name) | ||
170 | entrance.randomization_type = BaseClasses.EntranceType.TWO_WAY | ||
171 | |||
172 | er_exit = port_region.create_exit(connection_name) | ||
173 | er_exit.randomization_type = BaseClasses.EntranceType.TWO_WAY | ||
174 | |||
175 | if port.HasField("required_door"): | ||
176 | door_reqs = world.player_logic.get_door_open_reqs(port.required_door) | ||
177 | er_exit.access_rule = make_location_lambda(door_reqs, world, None) | ||
178 | |||
179 | for region in door_reqs.get_referenced_rooms(): | ||
180 | world.multiworld.register_indirect_condition(world.multiworld.get_region(region, world.player), | ||
181 | er_exit) | ||
182 | |||
183 | er_entrances.append(entrance) | ||
184 | er_exits.append(er_exit) | ||
185 | |||
186 | result = randomize_entrances(world, True, {0:[0]}, False, er_entrances, | ||
187 | er_exits) | ||
188 | |||
189 | for (f, to) in result.pairings: | ||
190 | world.port_pairings[port_id_by_name[f]] = port_id_by_name[to] | ||
191 | |||
192 | |||
193 | def connect_ports_from_ut(port_pairings: dict[int, int], world: "Lingo2World"): | ||
194 | for fpid, tpid in port_pairings.items(): | ||
195 | from_port = world.static_logic.objects.ports[fpid] | ||
196 | to_port = world.static_logic.objects.ports[tpid] | ||
197 | |||
198 | from_region_name = world.static_logic.get_room_region_name(from_port.room_id) | ||
199 | to_region_name = world.static_logic.get_room_region_name(to_port.room_id) | ||
200 | |||
201 | from_region = world.multiworld.get_region(from_region_name, world.player) | ||
202 | to_region = world.multiworld.get_region(to_region_name, world.player) | ||
203 | |||
204 | connection = Entrance(world.player, f"{from_region_name} - {from_port.display_name}", from_region) | ||
205 | |||
206 | reqs = AccessRequirements() | ||
207 | if from_port.HasField("required_door"): | ||
208 | reqs = world.player_logic.get_door_open_reqs(from_port.required_door).copy() | ||
209 | |||
210 | if world.for_tracker: | ||
211 | reqs.items.add(f"Worldport {fpid} Entered") | ||
212 | |||
213 | if not reqs.is_empty(): | ||
214 | connection.access_rule = make_location_lambda(reqs, world, None) | ||
215 | |||
216 | for region in reqs.get_referenced_rooms(): | ||
217 | world.multiworld.register_indirect_condition(world.multiworld.get_region(region, world.player), | ||
218 | connection) | ||
219 | |||
220 | from_region.exits.append(connection) | ||
221 | connection.connect(to_region) | ||