diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | apworld/README.md | 4 | ||||
-rw-r--r-- | apworld/__init__.py | 16 | ||||
-rw-r--r-- | apworld/docs/en_Lingo_2.md | 4 | ||||
-rw-r--r-- | apworld/items.py | 24 | ||||
-rw-r--r-- | apworld/options.py | 9 | ||||
-rw-r--r-- | apworld/player_logic.py | 60 | ||||
-rw-r--r-- | apworld/requirements.txt | 2 | ||||
-rw-r--r-- | apworld/rules.py | 2 | ||||
-rw-r--r-- | apworld/static_logic.py | 14 | ||||
-rw-r--r-- | client/Archipelago/gamedata.gd | 47 | ||||
-rw-r--r-- | client/Archipelago/manager.gd | 10 | ||||
-rw-r--r-- | client/Archipelago/panel.gd | 101 | ||||
-rw-r--r-- | client/Archipelago/player.gd | 7 | ||||
-rw-r--r-- | client/Archipelago/settings_screen.gd | 4 | ||||
-rw-r--r-- | client/README.md | 4 | ||||
-rw-r--r-- | data/connections.txtpb | 20 | ||||
-rw-r--r-- | data/ids.yaml | 19 | ||||
-rw-r--r-- | data/maps/the_entry/rooms/Starting Room.txtpb | 4 | ||||
-rw-r--r-- | data/maps/the_great/doors.txtpb | 5 | ||||
-rw-r--r-- | data/maps/the_great/rooms/West Side.txtpb | 1 | ||||
-rw-r--r-- | data/maps/the_owl/doors.txtpb | 2 | ||||
-rw-r--r-- | data/metadata.txtpb | 22 | ||||
-rw-r--r-- | proto/human.proto | 4 | ||||
-rw-r--r-- | tools/assign_ids/main.cpp | 18 |
25 files changed, 376 insertions, 33 deletions
diff --git a/README.md b/README.md index 144ee2b..60dc1b3 100644 --- a/README.md +++ b/README.md | |||
@@ -11,6 +11,6 @@ part of an Archipelago multiworld game. | |||
11 | 11 | ||
12 | There are multiple parts of this project: | 12 | There are multiple parts of this project: |
13 | 13 | ||
14 | - [Client](https://code.fourisland.com/lingo-archipelago2/about/client/README.md) | 14 | - [Client](https://code.fourisland.com/lingo2-archipelago/about/client/README.md) |
15 | - [Apworld](https://code.fourisland.com/lingo-archipelago2/about/apworld/README.md) | 15 | - [Apworld](https://code.fourisland.com/lingo2-archipelago/about/apworld/README.md) |
16 | - [Data](https://code.fourisland.com/lingo-archipelago2/about/data/README.md) | 16 | - [Data](https://code.fourisland.com/lingo2-archipelago/about/data/README.md) |
diff --git a/apworld/README.md b/apworld/README.md index 386a2a1..13374b2 100644 --- a/apworld/README.md +++ b/apworld/README.md | |||
@@ -6,7 +6,7 @@ Lingo 2. | |||
6 | ## Installation | 6 | ## Installation |
7 | 7 | ||
8 | 1. Download the Lingo 2 Apworld from | 8 | 1. Download the Lingo 2 Apworld from |
9 | [the releases page](https://code.fourisland.com/lingo-archipelago2/about/apworld/CHANGELOG.md). | 9 | [the releases page](https://code.fourisland.com/lingo2-archipelago/about/apworld/CHANGELOG.md). |
10 | 2. If you do not already have it, download and install the | 10 | 2. If you do not already have it, download and install the |
11 | [Archipelago software](https://github.com/ArchipelagoMW/Archipelago/releases/). | 11 | [Archipelago software](https://github.com/ArchipelagoMW/Archipelago/releases/). |
12 | 3. Double click on `lingo2.apworld` to install it, or copy it manually to the | 12 | 3. Double click on `lingo2.apworld` to install it, or copy it manually to the |
@@ -20,7 +20,7 @@ used. | |||
20 | 20 | ||
21 | The first file is `data.binpb`, the datafile containing the randomizer logic. | 21 | The first file is `data.binpb`, the datafile containing the randomizer logic. |
22 | You can read about how to generate it on | 22 | You can read about how to generate it on |
23 | [its own README page](https://code.fourisland.com/lingo-archipelago2/about/data/README.md). | 23 | [its own README page](https://code.fourisland.com/lingo2-archipelago/about/data/README.md). |
24 | Once you have it, put it in a subfolder of `apworld` called `generated`. | 24 | Once you have it, put it in a subfolder of `apworld` called `generated`. |
25 | 25 | ||
26 | The second generated file is `data_pb2.py`. This file allows Archipelago to read | 26 | The second generated file is `data_pb2.py`. This file allows Archipelago to read |
diff --git a/apworld/__init__.py b/apworld/__init__.py index c45e8b3..8051e0f 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py | |||
@@ -1,7 +1,7 @@ | |||
1 | """ | 1 | """ |
2 | Archipelago init file for Lingo 2 | 2 | Archipelago init file for Lingo 2 |
3 | """ | 3 | """ |
4 | from BaseClasses import ItemClassification, Item | 4 | from BaseClasses import ItemClassification, Item, Tutorial |
5 | from worlds.AutoWorld import WebWorld, World | 5 | from worlds.AutoWorld import WebWorld, World |
6 | from .items import Lingo2Item | 6 | from .items import Lingo2Item |
7 | from .options import Lingo2Options | 7 | from .options import Lingo2Options |
@@ -9,10 +9,20 @@ from .player_logic import Lingo2PlayerLogic | |||
9 | from .regions import create_regions | 9 | from .regions import create_regions |
10 | from .static_logic import Lingo2StaticLogic | 10 | from .static_logic import Lingo2StaticLogic |
11 | 11 | ||
12 | MAJOR_VERSION = 1 | ||
13 | MINOR_VERSION = 0 | ||
12 | 14 | ||
13 | class Lingo2WebWorld(WebWorld): | 15 | class Lingo2WebWorld(WebWorld): |
14 | rich_text_options_doc = True | 16 | rich_text_options_doc = True |
15 | theme = "grass" | 17 | theme = "grass" |
18 | tutorials = [Tutorial( | ||
19 | "Multiworld Setup Guide", | ||
20 | "A guide to playing Lingo 2 with Archipelago.", | ||
21 | "English", | ||
22 | "en_Lingo_2.md", | ||
23 | "setup/en", | ||
24 | ["hatkirby"] | ||
25 | )] | ||
16 | 26 | ||
17 | 27 | ||
18 | class Lingo2World(World): | 28 | class Lingo2World(World): |
@@ -32,6 +42,8 @@ class Lingo2World(World): | |||
32 | static_logic = Lingo2StaticLogic() | 42 | static_logic = Lingo2StaticLogic() |
33 | item_name_to_id = static_logic.item_name_to_id | 43 | item_name_to_id = static_logic.item_name_to_id |
34 | location_name_to_id = static_logic.location_name_to_id | 44 | location_name_to_id = static_logic.location_name_to_id |
45 | item_name_groups = static_logic.item_name_groups | ||
46 | location_name_groups = static_logic.location_name_groups | ||
35 | 47 | ||
36 | player_logic: Lingo2PlayerLogic | 48 | player_logic: Lingo2PlayerLogic |
37 | 49 | ||
@@ -72,11 +84,13 @@ class Lingo2World(World): | |||
72 | "shuffle_control_center_colors", | 84 | "shuffle_control_center_colors", |
73 | "shuffle_doors", | 85 | "shuffle_doors", |
74 | "shuffle_letters", | 86 | "shuffle_letters", |
87 | "shuffle_symbols", | ||
75 | "victory_condition", | 88 | "victory_condition", |
76 | ] | 89 | ] |
77 | 90 | ||
78 | slot_data = { | 91 | slot_data = { |
79 | **self.options.as_dict(*slot_options), | 92 | **self.options.as_dict(*slot_options), |
93 | "version": [MAJOR_VERSION, MINOR_VERSION], | ||
80 | } | 94 | } |
81 | 95 | ||
82 | return slot_data | 96 | return slot_data |
diff --git a/apworld/docs/en_Lingo_2.md b/apworld/docs/en_Lingo_2.md new file mode 100644 index 0000000..977795a --- /dev/null +++ b/apworld/docs/en_Lingo_2.md | |||
@@ -0,0 +1,4 @@ | |||
1 | # Lingo 2 | ||
2 | |||
3 | See [the project README](https://code.fourisland.com/lingo2-archipelago/about/) | ||
4 | for installation instructions and frequently asked questions. \ No newline at end of file | ||
diff --git a/apworld/items.py b/apworld/items.py index 971a709..32568a3 100644 --- a/apworld/items.py +++ b/apworld/items.py | |||
@@ -1,5 +1,29 @@ | |||
1 | from .generated import data_pb2 as data_pb2 | ||
1 | from BaseClasses import Item | 2 | from BaseClasses import Item |
2 | 3 | ||
3 | 4 | ||
4 | class Lingo2Item(Item): | 5 | class Lingo2Item(Item): |
5 | game: str = "Lingo 2" | 6 | game: str = "Lingo 2" |
7 | |||
8 | |||
9 | SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = { | ||
10 | data_pb2.PuzzleSymbol.SUN: "Sun Symbol", | ||
11 | data_pb2.PuzzleSymbol.SPARKLES: "Sparkles Symbol", | ||
12 | data_pb2.PuzzleSymbol.ZERO: "Zero Symbol", | ||
13 | data_pb2.PuzzleSymbol.EXAMPLE: "Example Symbol", | ||
14 | data_pb2.PuzzleSymbol.BOXES: "Boxes Symbol", | ||
15 | data_pb2.PuzzleSymbol.PLANET: "Planet Symbol", | ||
16 | data_pb2.PuzzleSymbol.PYRAMID: "Pyramid Symbol", | ||
17 | data_pb2.PuzzleSymbol.CROSS: "Cross Symbol", | ||
18 | data_pb2.PuzzleSymbol.SWEET: "Sweet Symbol", | ||
19 | data_pb2.PuzzleSymbol.GENDER: "Gender Symbol", | ||
20 | data_pb2.PuzzleSymbol.AGE: "Age Symbol", | ||
21 | data_pb2.PuzzleSymbol.SOUND: "Sound Symbol", | ||
22 | data_pb2.PuzzleSymbol.ANAGRAM: "Anagram Symbol", | ||
23 | data_pb2.PuzzleSymbol.JOB: "Job Symbol", | ||
24 | data_pb2.PuzzleSymbol.STARS: "Stars Symbol", | ||
25 | data_pb2.PuzzleSymbol.NULL: "Null Symbol", | ||
26 | data_pb2.PuzzleSymbol.EVAL: "Eval Symbol", | ||
27 | data_pb2.PuzzleSymbol.LINGO: "Lingo Symbol", | ||
28 | data_pb2.PuzzleSymbol.QUESTION: "Question Symbol", | ||
29 | } | ||
diff --git a/apworld/options.py b/apworld/options.py index 2197b0f..f72e826 100644 --- a/apworld/options.py +++ b/apworld/options.py | |||
@@ -39,6 +39,14 @@ class ShuffleLetters(Choice): | |||
39 | option_item_cyan = 4 | 39 | option_item_cyan = 4 |
40 | 40 | ||
41 | 41 | ||
42 | class ShuffleSymbols(Toggle): | ||
43 | """ | ||
44 | If enabled, 19 items will be added to the pool, representing the different symbols that can appear on a panel. | ||
45 | Players will be prevented from solving puzzles with symbols on them until all of the required symbols are unlocked. | ||
46 | """ | ||
47 | display_name = "Shuffle Symbols" | ||
48 | |||
49 | |||
42 | class KeyholderSanity(Toggle): | 50 | class KeyholderSanity(Toggle): |
43 | """ | 51 | """ |
44 | If enabled, 26 locations will be created for placing each key into its respective Green Ending keyholder. | 52 | If enabled, 26 locations will be created for placing each key into its respective Green Ending keyholder. |
@@ -102,6 +110,7 @@ class Lingo2Options(PerGameCommonOptions): | |||
102 | shuffle_doors: ShuffleDoors | 110 | shuffle_doors: ShuffleDoors |
103 | shuffle_control_center_colors: ShuffleControlCenterColors | 111 | shuffle_control_center_colors: ShuffleControlCenterColors |
104 | shuffle_letters: ShuffleLetters | 112 | shuffle_letters: ShuffleLetters |
113 | shuffle_symbols: ShuffleSymbols | ||
105 | keyholder_sanity: KeyholderSanity | 114 | keyholder_sanity: KeyholderSanity |
106 | cyan_door_behavior: CyanDoorBehavior | 115 | cyan_door_behavior: CyanDoorBehavior |
107 | daedalus_roof_access: DaedalusRoofAccess | 116 | daedalus_roof_access: DaedalusRoofAccess |
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index c94b809..42b36e6 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py | |||
@@ -1,6 +1,7 @@ | |||
1 | from enum import IntEnum, auto | 1 | from enum import IntEnum, auto |
2 | 2 | ||
3 | from .generated import data_pb2 as data_pb2 | 3 | from .generated import data_pb2 as data_pb2 |
4 | from .items import SYMBOL_ITEMS | ||
4 | from typing import TYPE_CHECKING, NamedTuple | 5 | from typing import TYPE_CHECKING, NamedTuple |
5 | 6 | ||
6 | from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior | 7 | from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior |
@@ -23,7 +24,6 @@ class AccessRequirements: | |||
23 | items: set[str] | 24 | items: set[str] |
24 | progressives: dict[str, int] | 25 | progressives: dict[str, int] |
25 | rooms: set[str] | 26 | rooms: set[str] |
26 | symbols: set[str] | ||
27 | letters: dict[str, int] | 27 | letters: dict[str, int] |
28 | cyans: bool | 28 | cyans: bool |
29 | 29 | ||
@@ -34,7 +34,6 @@ class AccessRequirements: | |||
34 | self.items = set() | 34 | self.items = set() |
35 | self.progressives = dict() | 35 | self.progressives = dict() |
36 | self.rooms = set() | 36 | self.rooms = set() |
37 | self.symbols = set() | ||
38 | self.letters = dict() | 37 | self.letters = dict() |
39 | self.cyans = False | 38 | self.cyans = False |
40 | self.or_logic = list() | 39 | self.or_logic = list() |
@@ -49,9 +48,6 @@ class AccessRequirements: | |||
49 | for room in other.rooms: | 48 | for room in other.rooms: |
50 | self.rooms.add(room) | 49 | self.rooms.add(room) |
51 | 50 | ||
52 | for symbol in other.symbols: | ||
53 | self.symbols.add(symbol) | ||
54 | |||
55 | for letter, level in other.letters.items(): | 51 | for letter, level in other.letters.items(): |
56 | self.letters[letter] = max(self.letters.get(letter, 0), level) | 52 | self.letters[letter] = max(self.letters.get(letter, 0), level) |
57 | 53 | ||
@@ -60,6 +56,10 @@ class AccessRequirements: | |||
60 | for disjunction in other.or_logic: | 56 | for disjunction in other.or_logic: |
61 | self.or_logic.append(disjunction) | 57 | self.or_logic.append(disjunction) |
62 | 58 | ||
59 | def is_empty(self) -> bool: | ||
60 | return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0 | ||
61 | and not self.cyans and len(self.or_logic) == 0) | ||
62 | |||
63 | def __repr__(self): | 63 | def __repr__(self): |
64 | parts = [] | 64 | parts = [] |
65 | if len(self.items) > 0: | 65 | if len(self.items) > 0: |
@@ -68,8 +68,6 @@ class AccessRequirements: | |||
68 | parts.append(f"progressives={self.progressives}") | 68 | parts.append(f"progressives={self.progressives}") |
69 | if len(self.rooms) > 0: | 69 | if len(self.rooms) > 0: |
70 | parts.append(f"rooms={self.rooms}") | 70 | parts.append(f"rooms={self.rooms}") |
71 | if len(self.symbols) > 0: | ||
72 | parts.append(f"symbols={self.symbols}") | ||
73 | if len(self.letters) > 0: | 71 | if len(self.letters) > 0: |
74 | parts.append(f"letters={self.letters}") | 72 | parts.append(f"letters={self.letters}") |
75 | if self.cyans: | 73 | if self.cyans: |
@@ -231,6 +229,10 @@ class Lingo2PlayerLogic: | |||
231 | self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id, | 229 | self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id, |
232 | reqs)) | 230 | reqs)) |
233 | 231 | ||
232 | if self.world.options.shuffle_symbols: | ||
233 | for symbol_name in SYMBOL_ITEMS.values(): | ||
234 | self.real_items.append(symbol_name) | ||
235 | |||
234 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: | 236 | def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: |
235 | if answer is None: | 237 | if answer is None: |
236 | if panel_id not in self.panel_reqs: | 238 | if panel_id not in self.panel_reqs: |
@@ -253,25 +255,35 @@ class Lingo2PlayerLogic: | |||
253 | self.add_solution_reqs(reqs, answer) | 255 | self.add_solution_reqs(reqs, answer) |
254 | elif len(panel.proxies) > 0: | 256 | elif len(panel.proxies) > 0: |
255 | possibilities = [] | 257 | possibilities = [] |
258 | already_filled = False | ||
256 | 259 | ||
257 | for proxy in panel.proxies: | 260 | for proxy in panel.proxies: |
258 | proxy_reqs = AccessRequirements() | 261 | proxy_reqs = AccessRequirements() |
259 | self.add_solution_reqs(proxy_reqs, proxy.answer) | 262 | self.add_solution_reqs(proxy_reqs, proxy.answer) |
260 | 263 | ||
261 | possibilities.append(proxy_reqs) | 264 | if not proxy_reqs.is_empty(): |
265 | possibilities.append(proxy_reqs) | ||
266 | else: | ||
267 | already_filled = True | ||
268 | break | ||
262 | 269 | ||
263 | if not any(proxy.answer == panel.answer for proxy in panel.proxies): | 270 | if not already_filled and not any(proxy.answer == panel.answer for proxy in panel.proxies): |
264 | proxy_reqs = AccessRequirements() | 271 | proxy_reqs = AccessRequirements() |
265 | self.add_solution_reqs(proxy_reqs, panel.answer) | 272 | self.add_solution_reqs(proxy_reqs, panel.answer) |
266 | 273 | ||
267 | possibilities.append(proxy_reqs) | 274 | if not proxy_reqs.is_empty(): |
275 | possibilities.append(proxy_reqs) | ||
276 | else: | ||
277 | already_filled = True | ||
268 | 278 | ||
269 | reqs.or_logic.append(possibilities) | 279 | if not already_filled: |
280 | reqs.or_logic.append(possibilities) | ||
270 | else: | 281 | else: |
271 | self.add_solution_reqs(reqs, panel.answer) | 282 | self.add_solution_reqs(reqs, panel.answer) |
272 | 283 | ||
273 | for symbol in panel.symbols: | 284 | if self.world.options.shuffle_symbols: |
274 | reqs.symbols.add(symbol) | 285 | for symbol in panel.symbols: |
286 | reqs.items.add(SYMBOL_ITEMS.get(symbol)) | ||
275 | 287 | ||
276 | if panel.HasField("required_door"): | 288 | if panel.HasField("required_door"): |
277 | door_reqs = self.get_door_open_reqs(panel.required_door) | 289 | door_reqs = self.get_door_open_reqs(panel.required_door) |
@@ -300,9 +312,16 @@ class Lingo2PlayerLogic: | |||
300 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) | 312 | panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) |
301 | reqs.merge(panel_reqs) | 313 | reqs.merge(panel_reqs) |
302 | elif door.complete_at == 1: | 314 | elif door.complete_at == 1: |
303 | reqs.or_logic.append([self.get_panel_reqs(proxy.panel, | 315 | disjunction = [] |
304 | proxy.answer if proxy.HasField("answer") else None) | 316 | for proxy in door.panels: |
305 | for proxy in door.panels]) | 317 | proxy_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) |
318 | if proxy_reqs.is_empty(): | ||
319 | disjunction.clear() | ||
320 | break | ||
321 | else: | ||
322 | disjunction.append(proxy_reqs) | ||
323 | if len(disjunction) > 0: | ||
324 | reqs.or_logic.append(disjunction) | ||
306 | else: | 325 | else: |
307 | # TODO: Handle complete_at > 1 | 326 | # TODO: Handle complete_at > 1 |
308 | pass | 327 | pass |
@@ -316,7 +335,8 @@ class Lingo2PlayerLogic: | |||
316 | if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2: | 335 | if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2: |
317 | reqs.rooms.add("The Repetitive - Main Room") | 336 | reqs.rooms.add("The Repetitive - Main Room") |
318 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter: | 337 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter: |
319 | reqs.cyans = True | 338 | if self.world.options.shuffle_letters != ShuffleLetters.option_unlocked: |
339 | reqs.cyans = True | ||
320 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item: | 340 | elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item: |
321 | # There shouldn't be any locations that are cyan doors. | 341 | # There shouldn't be any locations that are cyan doors. |
322 | pass | 342 | pass |
@@ -335,7 +355,11 @@ class Lingo2PlayerLogic: | |||
335 | 355 | ||
336 | for ending_id in door.endings: | 356 | for ending_id in door.endings: |
337 | ending = self.world.static_logic.objects.endings[ending_id] | 357 | ending = self.world.static_logic.objects.endings[ending_id] |
338 | reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)") | 358 | |
359 | if self.world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name: | ||
360 | reqs.items.add("Victory") | ||
361 | else: | ||
362 | reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)") | ||
339 | 363 | ||
340 | for sub_door_id in door.doors: | 364 | for sub_door_id in door.doors: |
341 | sub_reqs = self.get_door_open_reqs(sub_door_id) | 365 | sub_reqs = self.get_door_open_reqs(sub_door_id) |
diff --git a/apworld/requirements.txt b/apworld/requirements.txt index 49ca0a7..dbc395b 100644 --- a/apworld/requirements.txt +++ b/apworld/requirements.txt | |||
@@ -1 +1 @@ | |||
protobuf==3.20.3 \ No newline at end of file | protobuf==3.20.3 | ||
diff --git a/apworld/rules.py b/apworld/rules.py index 56486fa..0bff056 100644 --- a/apworld/rules.py +++ b/apworld/rules.py | |||
@@ -18,8 +18,6 @@ def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirem | |||
18 | if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms): | 18 | if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms): |
19 | return False | 19 | return False |
20 | 20 | ||
21 | # TODO: symbols | ||
22 | |||
23 | for letter_key, letter_level in reqs.letters.items(): | 21 | for letter_key, letter_level in reqs.letters.items(): |
24 | if not state.has(letter_key, world.player, letter_level): | 22 | if not state.has(letter_key, world.player, letter_level): |
25 | return False | 23 | return False |
diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 3f6cdea..c112d8e 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py | |||
@@ -1,4 +1,5 @@ | |||
1 | from .generated import data_pb2 as data_pb2 | 1 | from .generated import data_pb2 as data_pb2 |
2 | from .items import SYMBOL_ITEMS | ||
2 | import pkgutil | 3 | import pkgutil |
3 | 4 | ||
4 | class Lingo2StaticLogic: | 5 | class Lingo2StaticLogic: |
@@ -8,9 +9,14 @@ class Lingo2StaticLogic: | |||
8 | item_name_to_id: dict[str, int] | 9 | item_name_to_id: dict[str, int] |
9 | location_name_to_id: dict[str, int] | 10 | location_name_to_id: dict[str, int] |
10 | 11 | ||
12 | item_name_groups: dict[str, list[str]] | ||
13 | location_name_groups: dict[str, list[str]] | ||
14 | |||
11 | def __init__(self): | 15 | def __init__(self): |
12 | self.item_id_to_name = {} | 16 | self.item_id_to_name = {} |
13 | self.location_id_to_name = {} | 17 | self.location_id_to_name = {} |
18 | self.item_name_groups = {} | ||
19 | self.location_name_groups = {} | ||
14 | 20 | ||
15 | file = pkgutil.get_data(__name__, "generated/data.binpb") | 21 | file = pkgutil.get_data(__name__, "generated/data.binpb") |
16 | self.objects = data_pb2.AllObjects() | 22 | self.objects = data_pb2.AllObjects() |
@@ -29,17 +35,21 @@ class Lingo2StaticLogic: | |||
29 | letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}" | 35 | letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}" |
30 | location_name = f"{self.get_room_object_map_name(letter)} - {letter_name}" | 36 | location_name = f"{self.get_room_object_map_name(letter)} - {letter_name}" |
31 | self.location_id_to_name[letter.ap_id] = location_name | 37 | self.location_id_to_name[letter.ap_id] = location_name |
38 | self.location_name_groups.setdefault("Letters", []).append(location_name) | ||
32 | 39 | ||
33 | if not letter.level2: | 40 | if not letter.level2: |
34 | self.item_id_to_name[letter.ap_id] = letter.key.upper() | 41 | self.item_id_to_name[letter.ap_id] = letter.key.upper() |
42 | self.item_name_groups.setdefault("Letters", []).append(letter.key.upper()) | ||
35 | 43 | ||
36 | for mastery in self.objects.masteries: | 44 | for mastery in self.objects.masteries: |
37 | location_name = f"{self.get_room_object_map_name(mastery)} - Mastery" | 45 | location_name = f"{self.get_room_object_map_name(mastery)} - Mastery" |
38 | self.location_id_to_name[mastery.ap_id] = location_name | 46 | self.location_id_to_name[mastery.ap_id] = location_name |
47 | self.location_name_groups.setdefault("Masteries", []).append(location_name) | ||
39 | 48 | ||
40 | for ending in self.objects.endings: | 49 | for ending in self.objects.endings: |
41 | location_name = f"{self.get_room_object_map_name(ending)} - {ending.name.title()} Ending" | 50 | location_name = f"{self.get_room_object_map_name(ending)} - {ending.name.title()} Ending" |
42 | self.location_id_to_name[ending.ap_id] = location_name | 51 | self.location_id_to_name[ending.ap_id] = location_name |
52 | self.location_name_groups.setdefault("Endings", []).append(location_name) | ||
43 | 53 | ||
44 | for progressive in self.objects.progressives: | 54 | for progressive in self.objects.progressives: |
45 | self.item_id_to_name[progressive.ap_id] = progressive.name | 55 | self.item_id_to_name[progressive.ap_id] = progressive.name |
@@ -51,9 +61,13 @@ class Lingo2StaticLogic: | |||
51 | if keyholder.HasField("key"): | 61 | if keyholder.HasField("key"): |
52 | location_name = f"{self.get_room_object_location_prefix(keyholder)} - {keyholder.key.upper()} Keyholder" | 62 | location_name = f"{self.get_room_object_location_prefix(keyholder)} - {keyholder.key.upper()} Keyholder" |
53 | self.location_id_to_name[keyholder.ap_id] = location_name | 63 | self.location_id_to_name[keyholder.ap_id] = location_name |
64 | self.location_name_groups.setdefault("Keyholders", []).append(location_name) | ||
54 | 65 | ||
55 | self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" | 66 | self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" |
56 | 67 | ||
68 | for symbol_name in SYMBOL_ITEMS.values(): | ||
69 | self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name | ||
70 | |||
57 | self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()} | 71 | self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()} |
58 | self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()} | 72 | self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()} |
59 | 73 | ||
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd index f7a5d90..d8d16ed 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd | |||
@@ -5,15 +5,41 @@ var SCRIPT_proto | |||
5 | var objects | 5 | var objects |
6 | var door_id_by_map_node_path = {} | 6 | var door_id_by_map_node_path = {} |
7 | var painting_id_by_map_node_path = {} | 7 | var painting_id_by_map_node_path = {} |
8 | var panel_id_by_map_node_path = {} | ||
8 | var door_id_by_ap_id = {} | 9 | var door_id_by_ap_id = {} |
9 | var map_id_by_name = {} | 10 | var map_id_by_name = {} |
10 | var progressive_id_by_ap_id = {} | 11 | var progressive_id_by_ap_id = {} |
11 | var letter_id_by_ap_id = {} | 12 | var letter_id_by_ap_id = {} |
13 | var symbol_item_ids = [] | ||
14 | |||
15 | var kSYMBOL_ITEMS | ||
12 | 16 | ||
13 | 17 | ||
14 | func _init(proto_script): | 18 | func _init(proto_script): |
15 | SCRIPT_proto = proto_script | 19 | SCRIPT_proto = proto_script |
16 | 20 | ||
21 | kSYMBOL_ITEMS = { | ||
22 | SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol", | ||
23 | SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol", | ||
24 | SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol", | ||
25 | SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol", | ||
26 | SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol", | ||
27 | SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol", | ||
28 | SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol", | ||
29 | SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol", | ||
30 | SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol", | ||
31 | SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol", | ||
32 | SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol", | ||
33 | SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol", | ||
34 | SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol", | ||
35 | SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol", | ||
36 | SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol", | ||
37 | SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol", | ||
38 | SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol", | ||
39 | SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol", | ||
40 | SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol", | ||
41 | } | ||
42 | |||
17 | 43 | ||
18 | func load(data_bytes): | 44 | func load(data_bytes): |
19 | objects = SCRIPT_proto.AllObjects.new() | 45 | objects = SCRIPT_proto.AllObjects.new() |
@@ -58,6 +84,19 @@ func load(data_bytes): | |||
58 | for letter in objects.get_letters(): | 84 | for letter in objects.get_letters(): |
59 | letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() | 85 | letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id() |
60 | 86 | ||
87 | for panel in objects.get_panels(): | ||
88 | var room = objects.get_rooms()[panel.get_room_id()] | ||
89 | var map = objects.get_maps()[room.get_map_id()] | ||
90 | |||
91 | if not map.get_name() in panel_id_by_map_node_path: | ||
92 | panel_id_by_map_node_path[map.get_name()] = {} | ||
93 | |||
94 | var map_data = panel_id_by_map_node_path[map.get_name()] | ||
95 | map_data[panel.get_path()] = panel.get_id() | ||
96 | |||
97 | for symbol_name in kSYMBOL_ITEMS.values(): | ||
98 | symbol_item_ids.append(objects.get_special_ids()[symbol_name]) | ||
99 | |||
61 | 100 | ||
62 | func get_door_for_map_node_path(map_name, node_path): | 101 | func get_door_for_map_node_path(map_name, node_path): |
63 | if not door_id_by_map_node_path.has(map_name): | 102 | if not door_id_by_map_node_path.has(map_name): |
@@ -67,6 +106,14 @@ func get_door_for_map_node_path(map_name, node_path): | |||
67 | return map_data.get(node_path, null) | 106 | return map_data.get(node_path, null) |
68 | 107 | ||
69 | 108 | ||
109 | func get_panel_for_map_node_path(map_name, node_path): | ||
110 | if not panel_id_by_map_node_path.has(map_name): | ||
111 | return null | ||
112 | |||
113 | var map_data = panel_id_by_map_node_path[map_name] | ||
114 | return map_data.get(node_path, null) | ||
115 | |||
116 | |||
70 | func get_door_ap_id(door_id): | 117 | func get_door_ap_id(door_id): |
71 | var door = objects.get_doors()[door_id] | 118 | var door = objects.get_doors()[door_id] |
72 | if door.has_ap_id(): | 119 | if door.has_ap_id(): |
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index cd0654f..383be1f 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd | |||
@@ -1,6 +1,7 @@ | |||
1 | extends Node | 1 | extends Node |
2 | 2 | ||
3 | const my_version = "0.1.0" | 3 | const MAJOR_VERSION = 1 |
4 | const MINOR_VERSION = 0 | ||
4 | 5 | ||
5 | var SCRIPT_client | 6 | var SCRIPT_client |
6 | var SCRIPT_keyboard | 7 | var SCRIPT_keyboard |
@@ -47,6 +48,7 @@ var keyholder_sanity = false | |||
47 | var shuffle_control_center_colors = false | 48 | var shuffle_control_center_colors = false |
48 | var shuffle_doors = false | 49 | var shuffle_doors = false |
49 | var shuffle_letters = kSHUFFLE_LETTERS_VANILLA | 50 | var shuffle_letters = kSHUFFLE_LETTERS_VANILLA |
51 | var shuffle_symbols = false | ||
50 | var victory_condition = -1 | 52 | var victory_condition = -1 |
51 | 53 | ||
52 | signal could_not_connect | 54 | signal could_not_connect |
@@ -183,6 +185,11 @@ func _process_item(item, index, from, flags, amount): | |||
183 | if not letter.has_level2() or not letter.get_level2(): | 185 | if not letter.has_level2() or not letter.get_level2(): |
184 | _process_key_item(letter.get_key(), amount) | 186 | _process_key_item(letter.get_key(), amount) |
185 | 187 | ||
188 | if gamedata.symbol_item_ids.has(item): | ||
189 | var player = get_tree().get_root().get_node_or_null("scene/player") | ||
190 | if player != null: | ||
191 | player.emit_signal("evaluate_solvability") | ||
192 | |||
186 | # Show a message about the item if it's new. | 193 | # Show a message about the item if it's new. |
187 | if index != null and index > _last_new_item: | 194 | if index != null and index > _last_new_item: |
188 | _last_new_item = index | 195 | _last_new_item = index |
@@ -356,6 +363,7 @@ func _client_connected(slot_data): | |||
356 | shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) | 363 | shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false)) |
357 | shuffle_doors = bool(slot_data.get("shuffle_doors", false)) | 364 | shuffle_doors = bool(slot_data.get("shuffle_doors", false)) |
358 | shuffle_letters = int(slot_data.get("shuffle_letters", 0)) | 365 | shuffle_letters = int(slot_data.get("shuffle_letters", 0)) |
366 | shuffle_symbols = bool(slot_data.get("shuffle_symbols", false)) | ||
359 | victory_condition = int(slot_data.get("victory_condition", 0)) | 367 | victory_condition = int(slot_data.get("victory_condition", 0)) |
360 | 368 | ||
361 | # Set up item locks. | 369 | # Set up item locks. |
diff --git a/client/Archipelago/panel.gd b/client/Archipelago/panel.gd new file mode 100644 index 0000000..fdaaf0e --- /dev/null +++ b/client/Archipelago/panel.gd | |||
@@ -0,0 +1,101 @@ | |||
1 | extends "res://scripts/nodes/panel.gd" | ||
2 | |||
3 | var panel_logic = null | ||
4 | var symbol_solvable = true | ||
5 | |||
6 | var black = load("res://assets/materials/black.material") | ||
7 | |||
8 | |||
9 | func _ready(): | ||
10 | super._ready() | ||
11 | |||
12 | var node_path = String( | ||
13 | get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() | ||
14 | ) | ||
15 | |||
16 | var gamedata = global.get_node("Gamedata") | ||
17 | var panel_id = gamedata.get_panel_for_map_node_path(global.map, node_path) | ||
18 | if panel_id != null: | ||
19 | var ap = global.get_node("Archipelago") | ||
20 | if ap.shuffle_symbols: | ||
21 | if global.map == "the_entry" and node_path == "Panels/Entry/front_1": | ||
22 | clue = "i" | ||
23 | symbol = "" | ||
24 | |||
25 | setField("clue", clue) | ||
26 | setField("symbol", symbol) | ||
27 | |||
28 | panel_logic = gamedata.objects.get_panels()[panel_id] | ||
29 | checkSymbolSolvable() | ||
30 | |||
31 | if not symbol_solvable: | ||
32 | get_tree().get_root().get_node("scene/player").connect( | ||
33 | "evaluate_solvability", evaluateSolvability | ||
34 | ) | ||
35 | |||
36 | |||
37 | func checkSymbolSolvable(): | ||
38 | var old_solvable = symbol_solvable | ||
39 | symbol_solvable = true | ||
40 | |||
41 | if panel_logic == null: | ||
42 | # There's no logic for this panel. | ||
43 | return | ||
44 | |||
45 | var ap = global.get_node("Archipelago") | ||
46 | if not ap.shuffle_symbols: | ||
47 | # Symbols aren't item-locked. | ||
48 | return | ||
49 | |||
50 | var gamedata = global.get_node("Gamedata") | ||
51 | for symbol in panel_logic.get_symbols(): | ||
52 | var item_name = gamedata.kSYMBOL_ITEMS.get(symbol) | ||
53 | var item_id = gamedata.objects.get_special_ids()[item_name] | ||
54 | if ap.client.getItemAmount(item_id) < 1: | ||
55 | symbol_solvable = false | ||
56 | break | ||
57 | |||
58 | if symbol_solvable != old_solvable: | ||
59 | if symbol_solvable: | ||
60 | setField("clue", clue) | ||
61 | setField("symbol", symbol) | ||
62 | setField("answer", answer) | ||
63 | else: | ||
64 | quad_mesh.surface_set_material(0, black) | ||
65 | get_node("Hinge/clue").text = "missing" | ||
66 | get_node("Hinge/answer").text = "symbols" | ||
67 | |||
68 | |||
69 | func checkSolvable(key): | ||
70 | checkSymbolSolvable() | ||
71 | if not symbol_solvable: | ||
72 | return false | ||
73 | |||
74 | return super.checkSolvable(key) | ||
75 | |||
76 | |||
77 | func evaluateSolvability(): | ||
78 | checkSolvable("") | ||
79 | |||
80 | |||
81 | func passedInput(key, skip_focus_check = false): | ||
82 | if not symbol_solvable: | ||
83 | return | ||
84 | |||
85 | super.passedInput(key, skip_focus_check) | ||
86 | |||
87 | |||
88 | func focus(): | ||
89 | if not symbol_solvable: | ||
90 | has_focus = false | ||
91 | return | ||
92 | |||
93 | super.focus() | ||
94 | |||
95 | |||
96 | func unfocus(): | ||
97 | if not symbol_solvable: | ||
98 | has_focus = false | ||
99 | return | ||
100 | |||
101 | super.unfocus() | ||
diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd index dd6aa2b..4b995bc 100644 --- a/client/Archipelago/player.gd +++ b/client/Archipelago/player.gd | |||
@@ -16,6 +16,8 @@ const kEndingNameByVictoryValue = { | |||
16 | 12: "WHITE", | 16 | 12: "WHITE", |
17 | } | 17 | } |
18 | 18 | ||
19 | signal evaluate_solvability | ||
20 | |||
19 | 21 | ||
20 | func _ready(): | 22 | func _ready(): |
21 | var khl_script = load("res://scripts/nodes/keyHolderListener.gd") | 23 | var khl_script = load("res://scripts/nodes/keyHolderListener.gd") |
@@ -188,6 +190,11 @@ func _ready(): | |||
188 | warp_enter.rotation_degrees.y = 90 | 190 | warp_enter.rotation_degrees.y = 90 |
189 | get_parent().add_child.call_deferred(warp_enter) | 191 | get_parent().add_child.call_deferred(warp_enter) |
190 | 192 | ||
193 | # Remove door behind X1. | ||
194 | if global.map == "the_entry": | ||
195 | var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1") | ||
196 | door_node.handleTriggered() | ||
197 | |||
191 | super._ready() | 198 | super._ready() |
192 | 199 | ||
193 | await get_tree().process_frame | 200 | await get_tree().process_frame |
diff --git a/client/Archipelago/settings_screen.gd b/client/Archipelago/settings_screen.gd index aaaf72a..a3bc25e 100644 --- a/client/Archipelago/settings_screen.gd +++ b/client/Archipelago/settings_screen.gd | |||
@@ -40,6 +40,7 @@ func _ready(): | |||
40 | ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd") | 40 | ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd") |
41 | ) | 41 | ) |
42 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd")) | 42 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/painting.gd")) |
43 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/panel.gd")) | ||
43 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd")) | 44 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/pauseMenu.gd")) |
44 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd")) | 45 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/player.gd")) |
45 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd")) | 46 | installScriptExtension(ResourceLoader.load("user://maps/Archipelago/saver.gd")) |
@@ -89,7 +90,7 @@ func _ready(): | |||
89 | history_box.get_popup().connect("id_pressed", historySelected) | 90 | history_box.get_popup().connect("id_pressed", historySelected) |
90 | 91 | ||
91 | # Show client version. | 92 | # Show client version. |
92 | $Panel/title.text = "ARCHIPELAGO (%s)" % ap.my_version | 93 | $Panel/title.text = "ARCHIPELAGO (%d.%d)" % [ap.MAJOR_VERSION, ap.MINOR_VERSION] |
93 | 94 | ||
94 | # Increase font size in text boxes. | 95 | # Increase font size in text boxes. |
95 | $Panel/server_box.add_theme_font_size_override("font_size", 36) | 96 | $Panel/server_box.add_theme_font_size_override("font_size", 36) |
@@ -159,6 +160,7 @@ func connectionSuccessful(): | |||
159 | clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") | 160 | clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn") |
160 | clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") | 161 | clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") |
161 | clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") | 162 | clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") |
163 | clearResourceCache("res://objects/nodes/panel.tscn") | ||
162 | clearResourceCache("res://objects/nodes/player.tscn") | 164 | clearResourceCache("res://objects/nodes/player.tscn") |
163 | clearResourceCache("res://objects/nodes/saver.tscn") | 165 | clearResourceCache("res://objects/nodes/saver.tscn") |
164 | clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") | 166 | clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") |
diff --git a/client/README.md b/client/README.md index 07c5459..1e94bdb 100644 --- a/client/README.md +++ b/client/README.md | |||
@@ -6,7 +6,7 @@ to an Archipelago Multiworld and randomize your game. | |||
6 | ## Installation | 6 | ## Installation |
7 | 7 | ||
8 | 1. Download the Lingo 2 Archipelago Randomizer from | 8 | 1. Download the Lingo 2 Archipelago Randomizer from |
9 | [the releases page](https://code.fourisland.com/lingo-archipelago2/about/client/CHANGELOG.md). | 9 | [the releases page](https://code.fourisland.com/lingo2-archipelago/about/client/CHANGELOG.md). |
10 | 2. Open up Lingo 2, go to settings, and click View Game Data. This should open | 10 | 2. Open up Lingo 2, go to settings, and click View Game Data. This should open |
11 | up a folder in Windows Explorer. | 11 | up a folder in Windows Explorer. |
12 | 3. Unzip the randomizer into the "maps" folder. Ensure that archipelago.tscn and | 12 | 3. Unzip the randomizer into the "maps" folder. Ensure that archipelago.tscn and |
@@ -44,7 +44,7 @@ need to be generated before the client can be run. | |||
44 | 44 | ||
45 | The first file is `data.binpb`, the datafile containing the randomizer logic. | 45 | The first file is `data.binpb`, the datafile containing the randomizer logic. |
46 | You can read about how to generate it on | 46 | You can read about how to generate it on |
47 | [its own README page](https://code.fourisland.com/lingo-archipelago2/about/data/README.md). | 47 | [its own README page](https://code.fourisland.com/lingo2-archipelago/about/data/README.md). |
48 | Once you have it, put it in a subfolder of `client` called `generated`. | 48 | Once you have it, put it in a subfolder of `client` called `generated`. |
49 | 49 | ||
50 | The second generated file is `proto.gd`. This file allows Lingo 2 to read the | 50 | The second generated file is `proto.gd`. This file allows Lingo 2 to read the |
diff --git a/data/connections.txtpb b/data/connections.txtpb index a79778f..d718c96 100644 --- a/data/connections.txtpb +++ b/data/connections.txtpb | |||
@@ -841,6 +841,8 @@ connections { | |||
841 | } | 841 | } |
842 | oneway: true | 842 | oneway: true |
843 | } | 843 | } |
844 | # Two one-way connections because the CLUE panel only needs to be solved to | ||
845 | # go from The Great to The Partial. | ||
844 | connections { | 846 | connections { |
845 | from { | 847 | from { |
846 | port { | 848 | port { |
@@ -856,6 +858,24 @@ connections { | |||
856 | name: "GREAT" | 858 | name: "GREAT" |
857 | } | 859 | } |
858 | } | 860 | } |
861 | oneway: true | ||
862 | } | ||
863 | connections { | ||
864 | from { | ||
865 | port { | ||
866 | map: "the_partial" | ||
867 | room: "Obverse Side" | ||
868 | name: "GREAT" | ||
869 | } | ||
870 | } | ||
871 | to { | ||
872 | port { | ||
873 | map: "the_great" | ||
874 | room: "West Side" | ||
875 | name: "PARTIAL" | ||
876 | } | ||
877 | } | ||
878 | oneway: true | ||
859 | } | 879 | } |
860 | connections { | 880 | connections { |
861 | from { | 881 | from { |
diff --git a/data/ids.yaml b/data/ids.yaml index e2ec985..30a400b 100644 --- a/data/ids.yaml +++ b/data/ids.yaml | |||
@@ -3836,6 +3836,25 @@ endings: | |||
3836 | YELLOW: 1206 | 3836 | YELLOW: 1206 |
3837 | special: | 3837 | special: |
3838 | A Job Well Done: 1160 | 3838 | A Job Well Done: 1160 |
3839 | Age Symbol: 2791 | ||
3840 | Anagram Symbol: 2792 | ||
3841 | Boxes Symbol: 2793 | ||
3842 | Cross Symbol: 2794 | ||
3843 | Eval Symbol: 2795 | ||
3844 | Example Symbol: 2796 | ||
3845 | Gender Symbol: 2797 | ||
3846 | Job Symbol: 2798 | ||
3847 | Lingo Symbol: 2799 | ||
3848 | Null Symbol: 2800 | ||
3849 | Planet Symbol: 2801 | ||
3850 | Pyramid Symbol: 2802 | ||
3851 | Question Symbol: 2803 | ||
3852 | Sound Symbol: 2804 | ||
3853 | Sparkles Symbol: 2805 | ||
3854 | Stars Symbol: 2806 | ||
3855 | Sun Symbol: 2807 | ||
3856 | Sweet Symbol: 2808 | ||
3857 | Zero Symbol: 2809 | ||
3839 | progressives: | 3858 | progressives: |
3840 | Progressive Gold Ending: 2753 | 3859 | Progressive Gold Ending: 2753 |
3841 | door_groups: | 3860 | door_groups: |
diff --git a/data/maps/the_entry/rooms/Starting Room.txtpb b/data/maps/the_entry/rooms/Starting Room.txtpb index bc77e6d..8e8373b 100644 --- a/data/maps/the_entry/rooms/Starting Room.txtpb +++ b/data/maps/the_entry/rooms/Starting Room.txtpb | |||
@@ -24,7 +24,9 @@ panels { | |||
24 | path: "Panels/Entry/front_1" | 24 | path: "Panels/Entry/front_1" |
25 | clue: "eye" | 25 | clue: "eye" |
26 | answer: "i" | 26 | answer: "i" |
27 | symbols: ZERO | 27 | #symbols: ZERO |
28 | # This panel blocks getting N1 and T1. We will mod it to be I/I with no symbol | ||
29 | # when symbol shuffle is on. | ||
28 | } | 30 | } |
29 | panels { | 31 | panels { |
30 | name: "HINT" | 32 | name: "HINT" |
diff --git a/data/maps/the_great/doors.txtpb b/data/maps/the_great/doors.txtpb index f0f2fde..5d0e90d 100644 --- a/data/maps/the_great/doors.txtpb +++ b/data/maps/the_great/doors.txtpb | |||
@@ -508,3 +508,8 @@ doors { | |||
508 | receivers: "Panels/General/entry_7/teleportListener" | 508 | receivers: "Panels/General/entry_7/teleportListener" |
509 | double_letters: true | 509 | double_letters: true |
510 | } | 510 | } |
511 | doors { | ||
512 | name: "Partial Entrance" | ||
513 | type: EVENT | ||
514 | panels { room: "West Side" name: "CLUE" } | ||
515 | } | ||
diff --git a/data/maps/the_great/rooms/West Side.txtpb b/data/maps/the_great/rooms/West Side.txtpb index daf1718..8279e16 100644 --- a/data/maps/the_great/rooms/West Side.txtpb +++ b/data/maps/the_great/rooms/West Side.txtpb | |||
@@ -76,4 +76,5 @@ ports { | |||
76 | path: "Meshes/Blocks/Warps/worldport7" | 76 | path: "Meshes/Blocks/Warps/worldport7" |
77 | orientation: "east" | 77 | orientation: "east" |
78 | # ER with this is weird; make sure to place on the surface | 78 | # ER with this is weird; make sure to place on the surface |
79 | required_door { name: "Partial Entrance" } | ||
79 | } | 80 | } |
diff --git a/data/maps/the_owl/doors.txtpb b/data/maps/the_owl/doors.txtpb index 5ec34c6..9254c2a 100644 --- a/data/maps/the_owl/doors.txtpb +++ b/data/maps/the_owl/doors.txtpb | |||
@@ -235,7 +235,7 @@ doors { | |||
235 | type: EVENT | 235 | type: EVENT |
236 | #receivers: "Panels/Colors/owl_2/animationListener2" | 236 | #receivers: "Panels/Colors/owl_2/animationListener2" |
237 | panels { room: "Connected Area" name: "RANGE" } | 237 | panels { room: "Connected Area" name: "RANGE" } |
238 | panels { room: "R2C3 Bottom" name: "BLACK" } | 238 | panels { room: "Connected Area" name: "WHITE" } |
239 | panels { room: "Blue Room" name: "SKY" } | 239 | panels { room: "Blue Room" name: "SKY" } |
240 | } | 240 | } |
241 | doors { | 241 | doors { |
diff --git a/data/metadata.txtpb b/data/metadata.txtpb new file mode 100644 index 0000000..ef66622 --- /dev/null +++ b/data/metadata.txtpb | |||
@@ -0,0 +1,22 @@ | |||
1 | # Filler item. | ||
2 | special_names: "A Job Well Done" | ||
3 | # Symbol items. | ||
4 | special_names: "Age Symbol" | ||
5 | special_names: "Anagram Symbol" | ||
6 | special_names: "Boxes Symbol" | ||
7 | special_names: "Cross Symbol" | ||
8 | special_names: "Eval Symbol" | ||
9 | special_names: "Example Symbol" | ||
10 | special_names: "Gender Symbol" | ||
11 | special_names: "Job Symbol" | ||
12 | special_names: "Lingo Symbol" | ||
13 | special_names: "Null Symbol" | ||
14 | special_names: "Planet Symbol" | ||
15 | special_names: "Pyramid Symbol" | ||
16 | special_names: "Question Symbol" | ||
17 | special_names: "Sound Symbol" | ||
18 | special_names: "Sparkles Symbol" | ||
19 | special_names: "Stars Symbol" | ||
20 | special_names: "Sun Symbol" | ||
21 | special_names: "Sweet Symbol" | ||
22 | special_names: "Zero Symbol" | ||
diff --git a/proto/human.proto b/proto/human.proto index d48f687..1c5b463 100644 --- a/proto/human.proto +++ b/proto/human.proto | |||
@@ -212,6 +212,10 @@ message HumanDoorGroups { | |||
212 | repeated HumanDoorGroup door_groups = 1; | 212 | repeated HumanDoorGroup door_groups = 1; |
213 | } | 213 | } |
214 | 214 | ||
215 | message HumanGlobalMetadata { | ||
216 | repeated string special_names = 1; | ||
217 | } | ||
218 | |||
215 | message IdMappings { | 219 | message IdMappings { |
216 | message RoomIds { | 220 | message RoomIds { |
217 | map<string, uint64> panels = 1; | 221 | map<string, uint64> panels = 1; |
diff --git a/tools/assign_ids/main.cpp b/tools/assign_ids/main.cpp index ee55338..3e16f78 100644 --- a/tools/assign_ids/main.cpp +++ b/tools/assign_ids/main.cpp | |||
@@ -44,6 +44,7 @@ class AssignIds { | |||
44 | ProcessSpecialIds(); | 44 | ProcessSpecialIds(); |
45 | ProcessProgressivesFile(datadir_path / "progressives.txtpb"); | 45 | ProcessProgressivesFile(datadir_path / "progressives.txtpb"); |
46 | ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb"); | 46 | ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb"); |
47 | ProcessGlobalMetadataFile(datadir_path / "metadata.txtpb"); | ||
47 | 48 | ||
48 | WriteIds(ids_path); | 49 | WriteIds(ids_path); |
49 | 50 | ||
@@ -288,6 +289,23 @@ class AssignIds { | |||
288 | } | 289 | } |
289 | } | 290 | } |
290 | 291 | ||
292 | void ProcessGlobalMetadataFile(std::filesystem::path path) { | ||
293 | if (!std::filesystem::exists(path)) { | ||
294 | return; | ||
295 | } | ||
296 | |||
297 | auto h_metadata = ReadMessageFromFile<HumanGlobalMetadata>(path.string()); | ||
298 | auto& specials = *output_.mutable_special(); | ||
299 | |||
300 | for (const std::string& h_special : h_metadata.special_names()) { | ||
301 | if (!id_mappings_.special().contains(h_special)) { | ||
302 | specials[h_special] = next_id_++; | ||
303 | } else { | ||
304 | specials[h_special] = id_mappings_.special().at(h_special); | ||
305 | } | ||
306 | } | ||
307 | } | ||
308 | |||
291 | private: | 309 | private: |
292 | void UpdateNextId(const google::protobuf::Map<std::string, uint64_t>& ids) { | 310 | void UpdateNextId(const google::protobuf::Map<std::string, uint64_t>& ids) { |
293 | for (const auto& [_, id] : ids) { | 311 | for (const auto& [_, id] : ids) { |