about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--README.md104
-rw-r--r--apworld/CHANGELOG.md13
-rw-r--r--apworld/README.md48
-rw-r--r--apworld/__init__.py49
-rw-r--r--apworld/docs/en_Lingo_2.md4
-rw-r--r--apworld/items.py26
-rw-r--r--apworld/options.py122
-rw-r--r--apworld/player_logic.py344
-rw-r--r--apworld/regions.py31
-rw-r--r--apworld/requirements.txt2
-rw-r--r--apworld/rules.py43
-rw-r--r--apworld/static_logic.py63
-rw-r--r--apworld/version.py1
-rw-r--r--client/Archipelago/animationListener.gd14
-rw-r--r--client/Archipelago/client.gd55
-rw-r--r--client/Archipelago/collectable.gd16
-rw-r--r--client/Archipelago/door.gd14
-rw-r--r--client/Archipelago/gamedata.gd62
-rw-r--r--client/Archipelago/keyHolder.gd38
-rw-r--r--client/Archipelago/keyHolderChecker.gd24
-rw-r--r--client/Archipelago/keyHolderResetterListener.gd8
-rw-r--r--client/Archipelago/keyboard.gd199
-rw-r--r--client/Archipelago/manager.gd319
-rw-r--r--client/Archipelago/messages.gd18
-rw-r--r--client/Archipelago/painting.gd14
-rw-r--r--client/Archipelago/panel.gd101
-rw-r--r--client/Archipelago/pauseMenu.gd7
-rw-r--r--client/Archipelago/player.gd82
-rw-r--r--client/Archipelago/saver.gd6
-rw-r--r--client/Archipelago/settings_screen.gd77
-rw-r--r--client/Archipelago/teleport.gd38
-rw-r--r--client/Archipelago/teleportListener.gd23
-rw-r--r--client/Archipelago/textclient.gd2
-rw-r--r--client/Archipelago/victoryListener.gd2
-rw-r--r--client/Archipelago/visibilityListener.gd38
-rw-r--r--client/Archipelago/worldport.gd10
-rw-r--r--client/Archipelago/worldportListener.gd4
-rw-r--r--client/CHANGELOG.md21
-rw-r--r--client/README.md90
-rw-r--r--client/archipelago.tscn5
-rw-r--r--data/README.md13
-rw-r--r--data/connections.txtpb59
-rw-r--r--data/door_groups.txtpb160
-rw-r--r--data/ids.yaml156
-rw-r--r--data/maps/control_center/rooms/Main Area.txtpb4
-rw-r--r--data/maps/daedalus/connections.txtpb323
-rw-r--r--data/maps/daedalus/doors.txtpb132
-rw-r--r--data/maps/daedalus/rooms/C Keyholder.txtpb1
-rw-r--r--data/maps/daedalus/rooms/D Keyholder.txtpb1
-rw-r--r--data/maps/daedalus/rooms/F Keyholder.txtpb1
-rw-r--r--data/maps/daedalus/rooms/Number Paintings Area.txtpb1
-rw-r--r--data/maps/daedalus/rooms/Orange Room Hallway.txtpb4
-rw-r--r--data/maps/daedalus/rooms/Outside House.txtpb1
-rw-r--r--data/maps/daedalus/rooms/Wonderland.txtpb1
-rw-r--r--data/maps/daedalus/rooms/Yellow Color Door.txtpb2
-rw-r--r--data/maps/four_rooms/rooms/Keyholder Room.txtpb1
-rw-r--r--data/maps/the_ancient/doors.txtpb3
-rw-r--r--data/maps/the_ancient/rooms/Inside.txtpb1
-rw-r--r--data/maps/the_bearer/connections.txtpb5
-rw-r--r--data/maps/the_bearer/rooms/Back Area.txtpb6
-rw-r--r--data/maps/the_bearer/rooms/Tree Entrance.txtpb6
-rw-r--r--data/maps/the_congruent/doors.txtpb8
-rw-r--r--data/maps/the_congruent/rooms/T Keyholder.txtpb1
-rw-r--r--data/maps/the_darkroom/connections.txtpb15
-rw-r--r--data/maps/the_darkroom/doors.txtpb4
-rw-r--r--data/maps/the_darkroom/rooms/Congruent Entrance.txtpb7
-rw-r--r--data/maps/the_darkroom/rooms/Cyan Hallway.txtpb7
-rw-r--r--data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb7
-rw-r--r--data/maps/the_darkroom/rooms/First Room.txtpb12
-rw-r--r--data/maps/the_darkroom/rooms/Second Room.txtpb6
-rw-r--r--data/maps/the_entry/connections.txtpb17
-rw-r--r--data/maps/the_entry/doors.txtpb6
-rw-r--r--data/maps/the_entry/rooms/Flipped Second Room.txtpb7
-rw-r--r--data/maps/the_entry/rooms/Four Rooms Entrance.txtpb7
-rw-r--r--data/maps/the_entry/rooms/Liberated Entrance.txtpb6
-rw-r--r--data/maps/the_entry/rooms/Link Area.txtpb12
-rw-r--r--data/maps/the_entry/rooms/Literate Entrance.txtpb6
-rw-r--r--data/maps/the_entry/rooms/Starting Room.txtpb4
-rw-r--r--data/maps/the_extravagant/rooms/X Plus.txtpb1
-rw-r--r--data/maps/the_gallery/doors.txtpb34
-rw-r--r--data/maps/the_gallery/rooms/Main Area.txtpb1
-rw-r--r--data/maps/the_graveyard/doors.txtpb2
-rw-r--r--data/maps/the_great/doors.txtpb22
-rw-r--r--data/maps/the_great/rooms/Main Area.txtpb1
-rw-r--r--data/maps/the_great/rooms/North Landscape.txtpb1
-rw-r--r--data/maps/the_great/rooms/Question Room What.txtpb10
-rw-r--r--data/maps/the_great/rooms/West Side.txtpb1
-rw-r--r--data/maps/the_hive/rooms/Main Area.txtpb1
-rw-r--r--data/maps/the_jubilant/rooms/Side Area.txtpb1
-rw-r--r--data/maps/the_linear/doors.txtpb1
-rw-r--r--data/maps/the_nuanced/rooms/Main Room.txtpb1
-rw-r--r--data/maps/the_owl/connections.txtpb25
-rw-r--r--data/maps/the_owl/doors.txtpb20
-rw-r--r--data/maps/the_parthenon/doors.txtpb14
-rw-r--r--data/maps/the_parthenon/rooms/U Keyholder.txtpb1
-rw-r--r--data/maps/the_partial/rooms/Obverse Side.txtpb1
-rw-r--r--data/maps/the_quiet/rooms/Keyholder Room.txtpb1
-rw-r--r--data/maps/the_repetitive/connections.txtpb2
-rw-r--r--data/maps/the_repetitive/doors.txtpb8
-rw-r--r--data/maps/the_repetitive/metadata.txtpb4
-rw-r--r--data/maps/the_repetitive/rooms/Anti Room.txtpb11
-rw-r--r--data/maps/the_shop/rooms/Main Area.txtpb1
-rw-r--r--data/maps/the_symbolic/doors.txtpb43
-rw-r--r--data/maps/the_talented/rooms/Main Area.txtpb1
-rw-r--r--data/maps/the_tenacious/rooms/Main Area.txtpb1
-rw-r--r--data/maps/the_three_doors/doors.txtpb1
-rw-r--r--data/maps/the_tree/doors.txtpb1
-rw-r--r--data/maps/the_unkempt/doors.txtpb2
-rw-r--r--data/maps/the_unkempt/rooms/Main Area.txtpb1
-rw-r--r--data/maps/the_unkempt/rooms/Right Area.txtpb3
-rw-r--r--data/maps/the_unkempt/rooms/V Keyholder.txtpb1
-rw-r--r--data/maps/the_unkempt/rooms/W Keyholder.txtpb1
-rw-r--r--data/maps/the_unyielding/doors.txtpb3
-rw-r--r--data/maps/the_unyielding/rooms/Yellow Left.txtpb1
-rw-r--r--data/maps/the_unyielding/rooms/Yellow Right.txtpb1
-rw-r--r--data/metadata.txtpb50
-rw-r--r--data/progressives.txtpb11
-rw-r--r--proto/data.proto48
-rw-r--r--proto/human.proto47
-rw-r--r--tools/assign_ids/main.cpp193
-rw-r--r--tools/datapacker/container.cpp34
-rw-r--r--tools/datapacker/container.h6
-rw-r--r--tools/datapacker/main.cpp95
-rw-r--r--tools/util/godot_scene.cpp6
-rw-r--r--tools/util/ids_yaml_format.cpp39
-rw-r--r--tools/validator/human_processor.cpp153
-rw-r--r--tools/validator/structs.h26
-rw-r--r--tools/validator/validator.cpp172
-rw-r--r--vcpkg.json2
-rw-r--r--vendor/godobuf/LICENSE29
-rw-r--r--vendor/godobuf/README4
-rw-r--r--vendor/godobuf/addons/protobuf/parser.gd2254
-rw-r--r--vendor/godobuf/addons/protobuf/plugin.cfg7
-rw-r--r--vendor/godobuf/addons/protobuf/protobuf_cmdln.gd66
-rw-r--r--vendor/godobuf/addons/protobuf/protobuf_core.gd668
-rw-r--r--vendor/godobuf/addons/protobuf/protobuf_util.gd46
-rw-r--r--vendor/godobuf/default_env.tres7
-rw-r--r--vendor/godobuf/logo.pngbin0 -> 19026 bytes
-rw-r--r--vendor/godobuf/logo.png.import35
-rw-r--r--vendor/godobuf/project.godot26
140 files changed, 6907 insertions, 492 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa81210 --- /dev/null +++ b/README.md
@@ -0,0 +1,104 @@
1# lingo2-archipelago
2
3[Archipelago](https://archipelago.gg/) is an open-source project that supports
4randomizing a number of different games and combining them into one cooperative
5experience. Items from each game are hidden in other games. For more information
6about Archipelago, you can look at their website.
7
8This is a project that modifies the game
9[Lingo 2](https://www.lingothegame.com/lingo2.html) so that it can be played as
10part of an Archipelago multiworld game.
11
12## How To Play
13
14Here are the components needed in order to play:
15
16- **Apworld**: This is used by Archipelago to generate randomized Lingo 2
17 worlds.
18 - [Installation & FAQ](https://code.fourisland.com/lingo2-archipelago/about/apworld/README.md)
19 - [Downloads](https://code.fourisland.com/lingo2-archipelago/about/apworld/CHANGELOG.md)
20- **Client**: This is how Lingo 2 connects to an Archipelago multiworld.
21 - [Installation & FAQ](https://code.fourisland.com/lingo2-archipelago/about/client/README.md)
22 - [Downloads](https://code.fourisland.com/lingo2-archipelago/about/client/CHANGELOG.md)
23
24## Frequently Asked Questions
25
26### Why aren't the starting room letters shuffled?
27
28The letter requirements for solving puzzles are very restrictive, especially in
29the early game. It is possible for the generator to find some subset of letters
30and doors to place in the starting room such that you are not trapped, but this
31places a lot of strain on generation and leads to significantly more generation
32failures.
33
34As a result, the starting room letters (H1, I1, N1, and T1) are always present
35in the starting room, even when remote letter shuffle is enabled. These letters
36will _also_ count as clearing a check, so you will send out another item at the
37same time as collecting the letter.
38
39### What areas are randomized?
40
41Almost all maps that you can access from the base game are randomized. The
42exceptions are:
43
44- Icarus (this will be randomized at some point, although it will be optional)
45- Demo
46- The Hinterlands (this will probably be repurposed)
47- The beta tester gift maps
48
49### What about wall snipes?
50
51"Wall sniping" refers to the fact that you are able to solve puzzles on the
52other side of opaque walls. The player is never expected to or required to do
53this in normal gameplay. This randomizer does not change how wall snipes work,
54but it will likewise never require the use of them.
55
56### How do cyan doors work?
57
58In the base game, there are a number of cyan-colored doors that ordinarily open
59once you collect H2 in The Repetitive. There are also a handful of panels that
60only appear upon getting H2 as well, which the apworld treats the same as the
61cyan doors.
62
63There is an option that lets you choose how these doors and panels behave. By
64default, they act the same as in the base game: they only open or appear after
65collecting H2. Note that this means the actual H2 collectable in The Repetitive.
66Receiving H2 via remote letter shuffle does not count for this requirement.
67However, you can also make cyan doors activate upon collecting or receiving your
68first double letter, regardless of what it is or if it's remote. Finally, you
69can lock cyan doors behind an item called "Cyan Doors".
70
71It is important to note, however, that the Cyan Door Behavior option only
72applies to cyan doors that are not already affected by another type of
73shuffling. When door shuffle is on, the following cyan doors are activated by
74individual items and are not impacted by your choice of Cyan Door Behavior:
75
76- The entrance to The Tower from The Great (The Great - Tower Entrance)
77- The entrance to The Butterfly from The Bearer (The Bearer - Butterfly
78 Entrance)
79- The entrance to The Repetitive from The Entry (The Entry - Repetitive
80 Entrance)
81- The eye painting near the yellow color hallway in Daedalus (Daedalus - Eye
82 Painting)
83- The Red I room in The Repetitive (The Repetitive - Anti Collectable Room)
84
85Additionally, when control center color shuffle is enabled, the orange door in
86The Unkempt (which ordinarily doubles as a cyan door) opens upon receiving the
87Control Center Orange Doors item, instead of following the Cyan Door Behavior
88option.
89
90### Help! I lost C/G in The Congruent!
91
92If you place C or G into the relevant keyholders in The Congruent, the keyholder
93disappears. You can retrieve your letter immediately by pressing C or G again
94while standing in the same position, as the keyholder is still there, just
95invisible. If you have already left the room, though, there is an easier way to
96get your letters back: just use the Key Return in The Entry.
97
98## Project Details
99
100There are multiple parts of this project:
101
102- [Client](https://code.fourisland.com/lingo2-archipelago/about/client/README.md)
103- [Apworld](https://code.fourisland.com/lingo2-archipelago/about/apworld/README.md)
104- [Data](https://code.fourisland.com/lingo2-archipelago/about/data/README.md)
diff --git a/apworld/CHANGELOG.md b/apworld/CHANGELOG.md new file mode 100644 index 0000000..7db040c --- /dev/null +++ b/apworld/CHANGELOG.md
@@ -0,0 +1,13 @@
1# lingo2-archipelago Apworld Releases
2
3## v3.2 - 2025-09-12
4
5- Initial release for testing. Features include door shuffle, letter shuffle,
6 and symbol shuffle.
7
8Download:
9[lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v3.2/lingo2.apworld)<br/>
10Template YAML:
11[Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v3.2/Lingo%202.yaml)<br/>
12Source:
13[v3.2](https://code.fourisland.com/lingo2-archipelago/tag/?h=apworld-v3.2)
diff --git a/apworld/README.md b/apworld/README.md new file mode 100644 index 0000000..13374b2 --- /dev/null +++ b/apworld/README.md
@@ -0,0 +1,48 @@
1# Lingo 2 Apworld
2
3The Lingo 2 Apworld allows you to generate Archipelago Multiworlds containing
4Lingo 2.
5
6## Installation
7
81. Download the Lingo 2 Apworld from
9 [the releases page](https://code.fourisland.com/lingo2-archipelago/about/apworld/CHANGELOG.md).
102. If you do not already have it, download and install the
11 [Archipelago software](https://github.com/ArchipelagoMW/Archipelago/releases/).
123. Double click on `lingo2.apworld` to install it, or copy it manually to the
13 `custom_worlds` folder of your Archipelago installation.
14
15## Running from source
16
17The apworld is mostly written in Python, which does not need to be compiled.
18However, there are two files that need to be generated before the apworld can be
19used.
20
21The first file is `data.binpb`, the datafile containing the randomizer logic.
22You can read about how to generate it on
23[its own README page](https://code.fourisland.com/lingo2-archipelago/about/data/README.md).
24Once you have it, put it in a subfolder of `apworld` called `generated`.
25
26The second generated file is `data_pb2.py`. This file allows Archipelago to read
27the datafile. We use `protoc`, the Protocol Buffer compiler, to generate it. As
28of 0.6.3, Archipelago has protobuf 3.20.3 packaged with it, which means we need
29to compile our proto file with a similar version.
30
31If you followed the steps to generate `data.binpb` and compiled the `datapacker`
32tool yourself, you will already have protobuf version 3.21.12 installed through
33vcpkg. You can then run a command similar to this in order to generate the
34python file.
35
36```shell
37.\out\build\x64-Debug\vcpkg_installed\x64-windows\tools\protobuf\protoc.exe -Iproto\ ^
38 --python_out=apworld\generated\ .\proto\data.proto
39```
40
41The exact path to `protoc.exe` is going to depend on where vcpkg installed its
42packages. The above location is where Visual Studio will probably put it.
43
44After generating those two files, the apworld should be functional. You can copy
45it into an Archipelago source tree (rename the folder `apworld` to `lingo2` if
46you do so) if you want to edit/debug the code. Otherwise, you can zip up the
47folder and rename it to `lingo2.apworld` in order to package it for
48distribution.
diff --git a/apworld/__init__.py b/apworld/__init__.py index 4e5777a..8b2e42e 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py
@@ -1,18 +1,27 @@
1""" 1"""
2Archipelago init file for Lingo 2 2Archipelago init file for Lingo 2
3""" 3"""
4from BaseClasses import ItemClassification, Item 4from BaseClasses import ItemClassification, Item, Tutorial
5from worlds.AutoWorld import WebWorld, World 5from worlds.AutoWorld import WebWorld, World
6from .items import Lingo2Item 6from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS
7from .options import Lingo2Options 7from .options import Lingo2Options
8from .player_logic import Lingo2PlayerLogic 8from .player_logic import Lingo2PlayerLogic
9from .regions import create_regions 9from .regions import create_regions
10from .static_logic import Lingo2StaticLogic 10from .static_logic import Lingo2StaticLogic
11from .version import APWORLD_VERSION
11 12
12 13
13class Lingo2WebWorld(WebWorld): 14class Lingo2WebWorld(WebWorld):
14 rich_text_options_doc = True 15 rich_text_options_doc = True
15 theme = "grass" 16 theme = "grass"
17 tutorials = [Tutorial(
18 "Multiworld Setup Guide",
19 "A guide to playing Lingo 2 with Archipelago.",
20 "English",
21 "en_Lingo_2.md",
22 "setup/en",
23 ["hatkirby"]
24 )]
16 25
17 26
18class Lingo2World(World): 27class Lingo2World(World):
@@ -32,6 +41,8 @@ class Lingo2World(World):
32 static_logic = Lingo2StaticLogic() 41 static_logic = Lingo2StaticLogic()
33 item_name_to_id = static_logic.item_name_to_id 42 item_name_to_id = static_logic.item_name_to_id
34 location_name_to_id = static_logic.location_name_to_id 43 location_name_to_id = static_logic.location_name_to_id
44 item_name_groups = static_logic.item_name_groups
45 location_name_groups = static_logic.location_name_groups
35 46
36 player_logic: Lingo2PlayerLogic 47 player_logic: Lingo2PlayerLogic
37 48
@@ -51,13 +62,29 @@ class Lingo2World(World):
51 total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values()) 62 total_locations = sum(len(locs) for locs in self.player_logic.locations_by_room.values())
52 63
53 item_difference = total_locations - len(pool) 64 item_difference = total_locations - len(pool)
65
66 if self.options.trap_percentage > 0:
67 num_traps = int(item_difference * self.options.trap_percentage / 100)
68 item_difference = item_difference - num_traps
69
70 trap_names = []
71 trap_weights = []
72 for letter_name, weight in self.static_logic.letter_weights.items():
73 trap_names.append(f"Anti {letter_name}")
74 trap_weights.append(weight)
75
76 bad_letters = self.random.choices(trap_names, weights=trap_weights, k=num_traps)
77 pool += [self.create_item(trap_name) for trap_name in bad_letters]
78
54 for i in range(0, item_difference): 79 for i in range(0, item_difference):
55 pool.append(self.create_item("Nothing")) 80 pool.append(self.create_item(self.get_filler_item_name()))
56 81
57 self.multiworld.itempool += pool 82 self.multiworld.itempool += pool
58 83
59 def create_item(self, name: str) -> Item: 84 def create_item(self, name: str) -> Item:
60 return Lingo2Item(name, ItemClassification.filler if name == "Nothing" else ItemClassification.progression, 85 return Lingo2Item(name, ItemClassification.filler if name == self.get_filler_item_name() else
86 ItemClassification.trap if name in ANTI_COLLECTABLE_TRAPS else
87 ItemClassification.progression,
61 self.item_name_to_id.get(name), self.player) 88 self.item_name_to_id.get(name), self.player)
62 89
63 def set_rules(self): 90 def set_rules(self):
@@ -65,11 +92,23 @@ class Lingo2World(World):
65 92
66 def fill_slot_data(self): 93 def fill_slot_data(self):
67 slot_options = [ 94 slot_options = [
68 "victory_condition", "shuffle_doors", 95 "cyan_door_behavior",
96 "daedalus_roof_access",
97 "keyholder_sanity",
98 "shuffle_control_center_colors",
99 "shuffle_doors",
100 "shuffle_gallery_paintings",
101 "shuffle_letters",
102 "shuffle_symbols",
103 "victory_condition",
69 ] 104 ]
70 105
71 slot_data = { 106 slot_data = {
72 **self.options.as_dict(*slot_options), 107 **self.options.as_dict(*slot_options),
108 "version": [self.static_logic.get_data_version(), APWORLD_VERSION],
73 } 109 }
74 110
75 return slot_data 111 return slot_data
112
113 def get_filler_item_name(self) -> str:
114 return "A Job Well Done"
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
3See [the project README](https://code.fourisland.com/lingo2-archipelago/about/)
4for installation instructions and frequently asked questions. \ No newline at end of file
diff --git a/apworld/items.py b/apworld/items.py index 971a709..28158c3 100644 --- a/apworld/items.py +++ b/apworld/items.py
@@ -1,5 +1,31 @@
1from .generated import data_pb2 as data_pb2
1from BaseClasses import Item 2from BaseClasses import Item
2 3
3 4
4class Lingo2Item(Item): 5class Lingo2Item(Item):
5 game: str = "Lingo 2" 6 game: str = "Lingo 2"
7
8
9SYMBOL_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}
30
31ANTI_COLLECTABLE_TRAPS: list[str] = [f"Anti {letter}" for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"]
diff --git a/apworld/options.py b/apworld/options.py index d984beb..52d2034 100644 --- a/apworld/options.py +++ b/apworld/options.py
@@ -1,15 +1,115 @@
1from dataclasses import dataclass 1from dataclasses import dataclass
2 2
3from Options import PerGameCommonOptions, Toggle, Choice 3from Options import PerGameCommonOptions, Toggle, Choice, DefaultOnToggle, Range
4 4
5 5
6class ShuffleDoors(Toggle): 6class ShuffleDoors(DefaultOnToggle):
7 """If enabled, most doors will open from receiving an item rather than fulfilling the in-game requirements.""" 7 """If enabled, most doors will open from receiving an item rather than fulfilling the in-game requirements."""
8 display_name = "Shuffle Doors" 8 display_name = "Shuffle Doors"
9 9
10 10
11class ShuffleControlCenterColors(Toggle):
12 """
13 Some doors open after solving the COLOR panel in the Control Center. If this option is enabled, these doors will
14 instead open upon receiving an item.
15 """
16 display_name = "Shuffle Control Center Colors"
17
18
19class ShuffleGalleryPaintings(Toggle):
20 """If enabled, gallery paintings will appear from receiving an item rather than by triggering them normally."""
21 display_name = "Shuffle Gallery Paintings"
22
23
24class ShuffleLetters(Choice):
25 """
26 Controls how letter unlocks are handled. Note that H1, I1, N1, and T1 will always be present at their vanilla
27 locations in the starting room, even if letters are shuffled remotely.
28
29 - **Vanilla**: All letters will be present at their vanilla locations.
30 - **Unlocked**: Players will start with their keyboards fully unlocked.
31 - **Progressive**: Two items will be added to the pool for every letter (one for H, I, N, and T). Receiving the
32 first item gives you the corresponding level 1 letter, and the second item gives you the corresponding level 2
33 letter.
34 - **Vanilla Cyan**: Players will start with all level 1 (purple) letters unlocked. Level 2 (cyan) letters will be
35 present at their vanilla locations.
36 - **Item Cyan**: Players will start with all level 1 (purple) letters unlocked. One item will be added to the pool
37 for every level 2 (cyan) letter.
38 """
39 display_name = "Shuffle Letters"
40 option_vanilla = 0
41 option_unlocked = 1
42 option_progressive = 2
43 option_vanilla_cyan = 3
44 option_item_cyan = 4
45
46
47class ShuffleSymbols(Toggle):
48 """
49 If enabled, 19 items will be added to the pool, representing the different symbols that can appear on a panel.
50 Players will be prevented from solving puzzles with symbols on them until all of the required symbols are unlocked.
51 """
52 display_name = "Shuffle Symbols"
53
54
55class KeyholderSanity(Toggle):
56 """
57 If enabled, 26 locations will be created for placing each key into its respective Green Ending keyholder.
58
59 NOTE: This does not apply to the two disappearing keyholders in The Congruent, as they are not part of Green Ending.
60 """
61 display_name = "Keyholder Sanity"
62
63
64class CyanDoorBehavior(Choice):
65 """
66 Cyan-colored doors usually only open upon unlocking double letters. Some panels also only appear upon unlocking
67 double letters. This option determines how these unlocks should behave.
68
69 - **Collect H2**: In the base game, H2 is the first double letter you are intended to collect, so cyan doors only
70 open when you collect the H2 pickup in The Repetitive. Collecting the actual pickup is still required even with
71 remote letter shuffle enabled.
72 - **Any Double Letter**: Cyan doors will open when you have unlocked any cyan letter on your keyboard. In letter
73 shuffle, this means receiving a cyan letter, not picking up a cyan letter collectable.
74 - **Item**: Cyan doors will be grouped together in a single item.
75
76 Note that some cyan doors are impacted by door shuffle (e.g. the entrance to The Tower). When door shuffle is
77 enabled, these doors won't be affected by the value of this option.
78 """
79 display_name = "Cyan Door Behavior"
80 option_collect_h2 = 0
81 option_any_double_letter = 1
82 option_item = 2
83
84
85class DaedalusRoofAccess(Toggle):
86 """
87 If enabled, the player will be logically expected to be able to go from the castle entrance to any part of Daedalus
88 that is open to the air. If disabled, the player will only be expected to be able to enter the castle, the moat,
89 Icarus, and the area at the bottom of the stairs. Invisible walls that become opaque as you approach them are added
90 to the level to prevent the player from accidentally breaking logic.
91 """
92 display_name = "Allow Daedalus Roof Access"
93
94
11class VictoryCondition(Choice): 95class VictoryCondition(Choice):
12 """Victory condition.""" 96 """
97 This option determines what your goal is.
98
99 - **Gray Ending** (The Colorful)
100 - **Purple Ending** (The Sun Temple). This ordinarily requires all level 1 (purple) letters.
101 - **Mint Ending** (typing EXIT into the keyholders in Control Center)
102 - **Black Ending** (The Graveyard)
103 - **Blue Ending** (The Words)
104 - **Cyan Ending** (The Parthenon). This ordinarily requires almost all level 2 (cyan) letters.
105 - **Red Ending** (The Tower)
106 - **Plum Ending** (The Wondrous / The Door)
107 - **Orange Ending** (the castle in Daedalus)
108 - **Gold Ending** (The Gold). This involves going through the color rooms in Daedalus.
109 - **Yellow Ending** (The Gallery). This requires unlocking all gallery paintings.
110 - **Green Ending** (The Ancient). This requires filling all keyholders with specific letters.
111 - **White Ending** (Control Center). This combines every other ending.
112 """
13 display_name = "Victory Condition" 113 display_name = "Victory Condition"
14 option_gray_ending = 0 114 option_gray_ending = 0
15 option_purple_ending = 1 115 option_purple_ending = 1
@@ -26,7 +126,23 @@ class VictoryCondition(Choice):
26 option_white_ending = 12 126 option_white_ending = 12
27 127
28 128
129class TrapPercentage(Range):
130 """Replaces junk items with traps, at the specified rate."""
131 display_name = "Trap Percentage"
132 range_start = 0
133 range_end = 100
134 default = 0
135
136
29@dataclass 137@dataclass
30class Lingo2Options(PerGameCommonOptions): 138class Lingo2Options(PerGameCommonOptions):
31 shuffle_doors: ShuffleDoors 139 shuffle_doors: ShuffleDoors
140 shuffle_control_center_colors: ShuffleControlCenterColors
141 shuffle_gallery_paintings: ShuffleGalleryPaintings
142 shuffle_letters: ShuffleLetters
143 shuffle_symbols: ShuffleSymbols
144 keyholder_sanity: KeyholderSanity
145 cyan_door_behavior: CyanDoorBehavior
146 daedalus_roof_access: DaedalusRoofAccess
32 victory_condition: VictoryCondition 147 victory_condition: VictoryCondition
148 trap_percentage: TrapPercentage
diff --git a/apworld/player_logic.py b/apworld/player_logic.py index c6465f6..17af77f 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py
@@ -1,7 +1,10 @@
1from enum import IntEnum, auto
2
1from .generated import data_pb2 as data_pb2 3from .generated import data_pb2 as data_pb2
4from .items import SYMBOL_ITEMS
2from typing import TYPE_CHECKING, NamedTuple 5from typing import TYPE_CHECKING, NamedTuple
3 6
4from .options import VictoryCondition 7from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior
5 8
6if TYPE_CHECKING: 9if TYPE_CHECKING:
7 from . import Lingo2World 10 from . import Lingo2World
@@ -14,64 +17,147 @@ def calculate_letter_histogram(solution: str) -> dict[str, int]:
14 real_l = l.upper() 17 real_l = l.upper()
15 histogram[real_l] = min(histogram.get(real_l, 0) + 1, 2) 18 histogram[real_l] = min(histogram.get(real_l, 0) + 1, 2)
16 19
17 for free_letter in "HINT":
18 if histogram.get(free_letter, 0) == 1:
19 del histogram[free_letter]
20
21 return histogram 20 return histogram
22 21
23 22
24class AccessRequirements: 23class AccessRequirements:
25 items: set[str] 24 items: set[str]
25 progressives: dict[str, int]
26 rooms: set[str] 26 rooms: set[str]
27 symbols: set[str]
28 letters: dict[str, int] 27 letters: dict[str, int]
28 cyans: bool
29 29
30 # This is an AND of ORs. 30 # This is an AND of ORs.
31 or_logic: list[list["AccessRequirements"]] 31 or_logic: list[list["AccessRequirements"]]
32 32
33 # When complete_at is set, at least that many of the requirements in possibilities must be accessible. This should
34 # only be used for doors with complete_at > 1, as or_logic is more efficient for complete_at == 1.
35 complete_at: int | None
36 possibilities: list["AccessRequirements"]
37
33 def __init__(self): 38 def __init__(self):
34 self.items = set() 39 self.items = set()
40 self.progressives = dict()
35 self.rooms = set() 41 self.rooms = set()
36 self.symbols = set()
37 self.letters = dict() 42 self.letters = dict()
43 self.cyans = False
38 self.or_logic = list() 44 self.or_logic = list()
45 self.complete_at = None
46 self.possibilities = list()
39 47
40 def add_solution(self, solution: str): 48 def copy(self) -> "AccessRequirements":
41 histogram = calculate_letter_histogram(solution) 49 reqs = AccessRequirements()
42 50 reqs.items = self.items.copy()
43 for l, a in histogram.items(): 51 reqs.progressives = self.progressives.copy()
44 self.letters[l] = max(self.letters.get(l, 0), histogram.get(l)) 52 reqs.rooms = self.rooms.copy()
53 reqs.letters = self.letters.copy()
54 reqs.cyans = self.cyans
55 reqs.or_logic = [[other_req.copy() for other_req in disjunction] for disjunction in self.or_logic]
56 reqs.complete_at = self.complete_at
57 reqs.possibilities = self.possibilities.copy()
58 return reqs
45 59
46 def merge(self, other: "AccessRequirements"): 60 def merge(self, other: "AccessRequirements"):
47 for item in other.items: 61 for item in other.items:
48 self.items.add(item) 62 self.items.add(item)
49 63
64 for item, amount in other.progressives.items():
65 self.progressives[item] = max(amount, self.progressives.get(item, 0))
66
50 for room in other.rooms: 67 for room in other.rooms:
51 self.rooms.add(room) 68 self.rooms.add(room)
52 69
53 for symbol in other.symbols:
54 self.symbols.add(symbol)
55
56 for letter, level in other.letters.items(): 70 for letter, level in other.letters.items():
57 self.letters[letter] = max(self.letters.get(letter, 0), level) 71 self.letters[letter] = max(self.letters.get(letter, 0), level)
58 72
73 self.cyans = self.cyans or other.cyans
74
59 for disjunction in other.or_logic: 75 for disjunction in other.or_logic:
60 self.or_logic.append(disjunction) 76 self.or_logic.append(disjunction)
61 77
78 if other.complete_at is not None:
79 # Merging multiple requirements that use complete_at sucks, and is part of why we want to minimize use of
80 # it. If both requirements use complete_at, we will cheat by using the or_logic field, which supports
81 # conjunctions of requirements.
82 if self.complete_at is not None:
83 print("Merging requirements with complete_at > 1. This is messy and should be avoided!")
84
85 left_req = AccessRequirements()
86 left_req.complete_at = self.complete_at
87 left_req.possibilities = self.possibilities
88 self.or_logic.append([left_req])
89
90 self.complete_at = None
91 self.possibilities = list()
92
93 right_req = AccessRequirements()
94 right_req.complete_at = other.complete_at
95 right_req.possibilities = other.possibilities
96 self.or_logic.append([right_req])
97 else:
98 self.complete_at = other.complete_at
99 self.possibilities = other.possibilities
100
101 def is_empty(self) -> bool:
102 return (len(self.items) == 0 and len(self.progressives) == 0 and len(self.rooms) == 0 and len(self.letters) == 0
103 and not self.cyans and len(self.or_logic) == 0 and self.complete_at is None)
104
105 def __eq__(self, other: "AccessRequirements"):
106 return (self.items == other.items and self.progressives == other.progressives and self.rooms == other.rooms and
107 self.letters == other.letters and self.cyans == other.cyans and self.or_logic == other.or_logic and
108 self.complete_at == other.complete_at and self.possibilities == other.possibilities)
109
110 def simplify(self):
111 resimplify = False
112
113 if len(self.or_logic) > 0:
114 old_or_logic = self.or_logic
115
116 def remove_redundant(sub_reqs: "AccessRequirements"):
117 sub_reqs.letters = {l: v for l, v in sub_reqs.letters.items() if self.letters.get(l, 0) < v}
118
119 self.or_logic = []
120 for disjunction in old_or_logic:
121 new_disjunction = []
122 for ssr in disjunction:
123 remove_redundant(ssr)
124 if not ssr.is_empty():
125 new_disjunction.append(ssr)
126 else:
127 new_disjunction.clear()
128 break
129 if len(new_disjunction) == 1:
130 self.merge(new_disjunction[0])
131 resimplify = True
132 elif len(new_disjunction) > 1:
133 if all(cjr == new_disjunction[0] for cjr in new_disjunction):
134 self.merge(new_disjunction[0])
135 resimplify = True
136 else:
137 self.or_logic.append(new_disjunction)
138
139 if resimplify:
140 self.simplify()
141
62 def __repr__(self): 142 def __repr__(self):
63 parts = [] 143 parts = []
64 if len(self.items) > 0: 144 if len(self.items) > 0:
65 parts.append(f"items={self.items}") 145 parts.append(f"items={self.items}")
146 if len(self.progressives) > 0:
147 parts.append(f"progressives={self.progressives}")
66 if len(self.rooms) > 0: 148 if len(self.rooms) > 0:
67 parts.append(f"rooms={self.rooms}") 149 parts.append(f"rooms={self.rooms}")
68 if len(self.symbols) > 0:
69 parts.append(f"symbols={self.symbols}")
70 if len(self.letters) > 0: 150 if len(self.letters) > 0:
71 parts.append(f"letters={self.letters}") 151 parts.append(f"letters={self.letters}")
152 if self.cyans:
153 parts.append(f"cyans=True")
72 if len(self.or_logic) > 0: 154 if len(self.or_logic) > 0:
73 parts.append(f"or_logic={self.or_logic}") 155 parts.append(f"or_logic={self.or_logic}")
74 return f"AccessRequirements({", ".join(parts)})" 156 if self.complete_at is not None:
157 parts.append(f"complete_at={self.complete_at}")
158 if len(self.possibilities) > 0:
159 parts.append(f"possibilities={self.possibilities}")
160 return "AccessRequirements(" + ", ".join(parts) + ")"
75 161
76 162
77class PlayerLocation(NamedTuple): 163class PlayerLocation(NamedTuple):
@@ -79,13 +165,19 @@ class PlayerLocation(NamedTuple):
79 reqs: AccessRequirements 165 reqs: AccessRequirements
80 166
81 167
168class LetterBehavior(IntEnum):
169 VANILLA = auto()
170 ITEM = auto()
171 UNLOCKED = auto()
172
173
82class Lingo2PlayerLogic: 174class Lingo2PlayerLogic:
83 world: "Lingo2World" 175 world: "Lingo2World"
84 176
85 locations_by_room: dict[int, list[PlayerLocation]] 177 locations_by_room: dict[int, list[PlayerLocation]]
86 event_loc_item_by_room: dict[int, dict[str, str]] 178 event_loc_item_by_room: dict[int, dict[str, str]]
87 179
88 item_by_door: dict[int, str] 180 item_by_door: dict[int, tuple[str, int]]
89 181
90 panel_reqs: dict[int, AccessRequirements] 182 panel_reqs: dict[int, AccessRequirements]
91 proxy_reqs: dict[int, dict[str, AccessRequirements]] 183 proxy_reqs: dict[int, dict[str, AccessRequirements]]
@@ -93,6 +185,8 @@ class Lingo2PlayerLogic:
93 185
94 real_items: list[str] 186 real_items: list[str]
95 187
188 double_letter_amount: dict[str, int]
189
96 def __init__(self, world: "Lingo2World"): 190 def __init__(self, world: "Lingo2World"):
97 self.world = world 191 self.world = world
98 self.locations_by_room = {} 192 self.locations_by_room = {}
@@ -102,14 +196,68 @@ class Lingo2PlayerLogic:
102 self.proxy_reqs = dict() 196 self.proxy_reqs = dict()
103 self.door_reqs = dict() 197 self.door_reqs = dict()
104 self.real_items = list() 198 self.real_items = list()
199 self.double_letter_amount = dict()
200
201 if self.world.options.shuffle_doors:
202 for progressive in world.static_logic.objects.progressives:
203 for i in range(0, len(progressive.doors)):
204 self.item_by_door[progressive.doors[i]] = (progressive.name, i + 1)
205 self.real_items.append(progressive.name)
206
207 for door_group in world.static_logic.objects.door_groups:
208 if door_group.type == data_pb2.DoorGroupType.CONNECTOR:
209 if not self.world.options.shuffle_doors:
210 continue
211 elif door_group.type == data_pb2.DoorGroupType.COLOR_CONNECTOR:
212 if not self.world.options.shuffle_control_center_colors:
213 continue
214 elif door_group.type == data_pb2.DoorGroupType.SHUFFLE_GROUP:
215 if not self.world.options.shuffle_doors:
216 continue
217 else:
218 continue
219
220 for door in door_group.doors:
221 self.item_by_door[door] = (door_group.name, 1)
222
223 self.real_items.append(door_group.name)
105 224
106 # We iterate through the doors in two parts because it is essential that we determine which doors are shuffled 225 # We iterate through the doors in two parts because it is essential that we determine which doors are shuffled
107 # before we calculate any access requirements. 226 # before we calculate any access requirements.
108 for door in world.static_logic.objects.doors: 227 for door in world.static_logic.objects.doors:
109 if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and self.world.options.shuffle_doors: 228 if door.type in [data_pb2.DoorType.EVENT, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]:
110 door_item_name = self.world.static_logic.get_door_item_name(door) 229 continue
111 self.item_by_door[door.id] = door_item_name 230
112 self.real_items.append(door_item_name) 231 if door.id in self.item_by_door:
232 continue
233
234 if (door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.ITEM_ONLY] and
235 not self.world.options.shuffle_doors):
236 continue
237
238 if (door.type == data_pb2.DoorType.CONTROL_CENTER_COLOR and
239 not self.world.options.shuffle_control_center_colors):
240 continue
241
242 if door.type == data_pb2.DoorType.GALLERY_PAINTING and not self.world.options.shuffle_gallery_paintings:
243 continue
244
245 door_item_name = self.world.static_logic.get_door_item_name(door)
246 self.item_by_door[door.id] = (door_item_name, 1)
247 self.real_items.append(door_item_name)
248
249 # We handle cyan_door_behavior = Item after door shuffle, because cyan doors that are impacted by door shuffle
250 # should be exempt from cyan_door_behavior.
251 if world.options.cyan_door_behavior == CyanDoorBehavior.option_item:
252 for door_group in world.static_logic.objects.door_groups:
253 if door_group.type != data_pb2.DoorGroupType.CYAN_DOORS:
254 continue
255
256 for door in door_group.doors:
257 if not door in self.item_by_door:
258 self.item_by_door[door] = (door_group.name, 1)
259
260 self.real_items.append(door_group.name)
113 261
114 for door in world.static_logic.objects.doors: 262 for door in world.static_logic.objects.doors:
115 if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]: 263 if door.type in [data_pb2.DoorType.STANDARD, data_pb2.DoorType.LOCATION_ONLY, data_pb2.DoorType.GRAVESTONE]:
@@ -119,14 +267,20 @@ class Lingo2PlayerLogic:
119 for letter in world.static_logic.objects.letters: 267 for letter in world.static_logic.objects.letters:
120 self.locations_by_room.setdefault(letter.room_id, []).append(PlayerLocation(letter.ap_id, 268 self.locations_by_room.setdefault(letter.room_id, []).append(PlayerLocation(letter.ap_id,
121 AccessRequirements())) 269 AccessRequirements()))
270 behavior = self.get_letter_behavior(letter.key, letter.level2)
271 if behavior == LetterBehavior.VANILLA:
272 letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}"
273 event_name = f"{letter_name} (Collected)"
274 self.event_loc_item_by_room.setdefault(letter.room_id, {})[event_name] = letter.key.upper()
122 275
123 letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}" 276 if letter.level2:
124 event_name = f"{letter_name} (Collected)" 277 event_name = f"{letter_name} (Double Collected)"
125 self.event_loc_item_by_room.setdefault(letter.room_id, {})[event_name] = letter.key.upper() 278 self.event_loc_item_by_room.setdefault(letter.room_id, {})[event_name] = letter.key.upper()
279 elif behavior == LetterBehavior.ITEM:
280 self.real_items.append(letter.key.upper())
126 281
127 if letter.level2: 282 if behavior != LetterBehavior.UNLOCKED:
128 event_name = f"{letter_name} (Double Collected)" 283 self.double_letter_amount[letter.key.upper()] = self.double_letter_amount.get(letter.key.upper(), 0) + 1
129 self.event_loc_item_by_room.setdefault(letter.room_id, {})[event_name] = letter.key.upper()
130 284
131 for mastery in world.static_logic.objects.masteries: 285 for mastery in world.static_logic.objects.masteries:
132 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id, 286 self.locations_by_room.setdefault(mastery.room_id, []).append(PlayerLocation(mastery.ap_id,
@@ -150,6 +304,21 @@ class Lingo2PlayerLogic:
150 304
151 self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name 305 self.event_loc_item_by_room.setdefault(ending.room_id, {})[event_name] = item_name
152 306
307 if self.world.options.keyholder_sanity:
308 for keyholder in world.static_logic.objects.keyholders:
309 if keyholder.HasField("key"):
310 reqs = AccessRequirements()
311
312 if self.get_letter_behavior(keyholder.key, False) != LetterBehavior.UNLOCKED:
313 reqs.letters[keyholder.key.upper()] = 1
314
315 self.locations_by_room.setdefault(keyholder.room_id, []).append(PlayerLocation(keyholder.ap_id,
316 reqs))
317
318 if self.world.options.shuffle_symbols:
319 for symbol_name in SYMBOL_ITEMS.values():
320 self.real_items.append(symbol_name)
321
153 def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements: 322 def get_panel_reqs(self, panel_id: int, answer: str | None) -> AccessRequirements:
154 if answer is None: 323 if answer is None:
155 if panel_id not in self.panel_reqs: 324 if panel_id not in self.panel_reqs:
@@ -169,28 +338,38 @@ class Lingo2PlayerLogic:
169 reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.room_id)) 338 reqs.rooms.add(self.world.static_logic.get_room_region_name(panel.room_id))
170 339
171 if answer is not None: 340 if answer is not None:
172 reqs.add_solution(answer) 341 self.add_solution_reqs(reqs, answer)
173 elif len(panel.proxies) > 0: 342 elif len(panel.proxies) > 0:
174 possibilities = [] 343 possibilities = []
344 already_filled = False
175 345
176 for proxy in panel.proxies: 346 for proxy in panel.proxies:
177 proxy_reqs = AccessRequirements() 347 proxy_reqs = AccessRequirements()
178 proxy_reqs.add_solution(proxy.answer) 348 self.add_solution_reqs(proxy_reqs, proxy.answer)
179 349
180 possibilities.append(proxy_reqs) 350 if not proxy_reqs.is_empty():
351 possibilities.append(proxy_reqs)
352 else:
353 already_filled = True
354 break
181 355
182 if not any(proxy.answer == panel.answer for proxy in panel.proxies): 356 if not already_filled and not any(proxy.answer == panel.answer for proxy in panel.proxies):
183 proxy_reqs = AccessRequirements() 357 proxy_reqs = AccessRequirements()
184 proxy_reqs.add_solution(panel.answer) 358 self.add_solution_reqs(proxy_reqs, panel.answer)
185 359
186 possibilities.append(proxy_reqs) 360 if not proxy_reqs.is_empty():
361 possibilities.append(proxy_reqs)
362 else:
363 already_filled = True
187 364
188 reqs.or_logic.append(possibilities) 365 if not already_filled:
366 reqs.or_logic.append(possibilities)
189 else: 367 else:
190 reqs.add_solution(panel.answer) 368 self.add_solution_reqs(reqs, panel.answer)
191 369
192 for symbol in panel.symbols: 370 if self.world.options.shuffle_symbols:
193 reqs.symbols.add(symbol) 371 for symbol in panel.symbols:
372 reqs.items.add(SYMBOL_ITEMS.get(symbol))
194 373
195 if panel.HasField("required_door"): 374 if panel.HasField("required_door"):
196 door_reqs = self.get_door_open_reqs(panel.required_door) 375 door_reqs = self.get_door_open_reqs(panel.required_door)
@@ -213,31 +392,46 @@ class Lingo2PlayerLogic:
213 door = self.world.static_logic.objects.doors[door_id] 392 door = self.world.static_logic.objects.doors[door_id]
214 reqs = AccessRequirements() 393 reqs = AccessRequirements()
215 394
216 # TODO: lavender_cubes, endings
217 if not door.HasField("complete_at") or door.complete_at == 0: 395 if not door.HasField("complete_at") or door.complete_at == 0:
218 for proxy in door.panels: 396 for proxy in door.panels:
219 panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None) 397 panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
220 reqs.merge(panel_reqs) 398 reqs.merge(panel_reqs)
221 elif door.complete_at == 1: 399 elif door.complete_at == 1:
222 reqs.or_logic.append([self.get_panel_reqs(proxy.panel, 400 disjunction = []
223 proxy.answer if proxy.HasField("answer") else None) 401 for proxy in door.panels:
224 for proxy in door.panels]) 402 proxy_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
403 if proxy_reqs.is_empty():
404 disjunction.clear()
405 break
406 else:
407 disjunction.append(proxy_reqs)
408 if len(disjunction) > 0:
409 reqs.or_logic.append(disjunction)
225 else: 410 else:
226 # TODO: Handle complete_at > 1 411 reqs.complete_at = door.complete_at
227 pass 412 for proxy in door.panels:
413 panel_reqs = self.get_panel_reqs(proxy.panel, proxy.answer if proxy.HasField("answer") else None)
414 reqs.possibilities.append(panel_reqs)
228 415
229 if door.HasField("control_center_color"): 416 if door.HasField("control_center_color"):
230 # TODO: Logic for ensuring two CC states aren't needed at once. 417 # TODO: Logic for ensuring two CC states aren't needed at once.
231 reqs.rooms.add("Control Center - Main Area") 418 reqs.rooms.add("Control Center - Main Area")
232 reqs.add_solution(door.control_center_color) 419 self.add_solution_reqs(reqs, door.control_center_color)
233 420
234 if door.double_letters: 421 if door.double_letters:
235 # TODO: When letter shuffle is on, change this to require any double letter instead. 422 if self.world.options.cyan_door_behavior == CyanDoorBehavior.option_collect_h2:
236 reqs.rooms.add("The Repetitive - Main Room") 423 reqs.rooms.add("The Repetitive - Main Room")
424 elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_any_double_letter:
425 if self.world.options.shuffle_letters != ShuffleLetters.option_unlocked:
426 reqs.cyans = True
427 elif self.world.options.cyan_door_behavior == CyanDoorBehavior.option_item:
428 # There shouldn't be any locations that are cyan doors.
429 pass
237 430
238 for keyholder_uses in door.keyholders: 431 for keyholder_uses in door.keyholders:
239 key_name = keyholder_uses.key.upper() 432 key_name = keyholder_uses.key.upper()
240 if key_name not in reqs.letters: 433 if (self.get_letter_behavior(keyholder_uses.key, False) != LetterBehavior.UNLOCKED
434 and key_name not in reqs.letters):
241 reqs.letters[key_name] = 1 435 reqs.letters[key_name] = 1
242 436
243 keyholder = self.world.static_logic.objects.keyholders[keyholder_uses.keyholder] 437 keyholder = self.world.static_logic.objects.keyholders[keyholder_uses.keyholder]
@@ -248,12 +442,18 @@ class Lingo2PlayerLogic:
248 442
249 for ending_id in door.endings: 443 for ending_id in door.endings:
250 ending = self.world.static_logic.objects.endings[ending_id] 444 ending = self.world.static_logic.objects.endings[ending_id]
251 reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)") 445
446 if self.world.options.victory_condition.current_key.removesuffix("_ending").upper() == ending.name:
447 reqs.items.add("Victory")
448 else:
449 reqs.items.add(f"{ending.name.capitalize()} Ending (Achieved)")
252 450
253 for sub_door_id in door.doors: 451 for sub_door_id in door.doors:
254 sub_reqs = self.get_door_open_reqs(sub_door_id) 452 sub_reqs = self.get_door_open_reqs(sub_door_id)
255 reqs.merge(sub_reqs) 453 reqs.merge(sub_reqs)
256 454
455 reqs.simplify()
456
257 return reqs 457 return reqs
258 458
259 # This gets the requirements to open a door within the world. When a door is shuffled, this means having the item 459 # This gets the requirements to open a door within the world. When a door is shuffled, this means having the item
@@ -261,8 +461,50 @@ class Lingo2PlayerLogic:
261 def get_door_open_reqs(self, door_id: int) -> AccessRequirements: 461 def get_door_open_reqs(self, door_id: int) -> AccessRequirements:
262 if door_id in self.item_by_door: 462 if door_id in self.item_by_door:
263 reqs = AccessRequirements() 463 reqs = AccessRequirements()
264 reqs.items.add(self.item_by_door.get(door_id)) 464
465 item_name, amount = self.item_by_door.get(door_id)
466 if amount == 1:
467 reqs.items.add(item_name)
468 else:
469 reqs.progressives[item_name] = amount
265 470
266 return reqs 471 return reqs
267 else: 472 else:
268 return self.get_door_reqs(door_id) 473 return self.get_door_reqs(door_id)
474
475 def get_letter_behavior(self, letter: str, level2: bool) -> LetterBehavior:
476 if self.world.options.shuffle_letters == ShuffleLetters.option_unlocked:
477 return LetterBehavior.UNLOCKED
478
479 if self.world.options.shuffle_letters in [ShuffleLetters.option_vanilla_cyan, ShuffleLetters.option_item_cyan]:
480 if level2:
481 if self.world.options.shuffle_letters == ShuffleLetters.option_vanilla_cyan:
482 return LetterBehavior.VANILLA
483 else:
484 return LetterBehavior.ITEM
485 else:
486 return LetterBehavior.UNLOCKED
487
488 if not level2 and letter in ["h", "i", "n", "t"]:
489 return LetterBehavior.UNLOCKED
490
491 if self.world.options.shuffle_letters == ShuffleLetters.option_progressive:
492 return LetterBehavior.ITEM
493
494 return LetterBehavior.VANILLA
495
496 def add_solution_reqs(self, reqs: AccessRequirements, solution: str):
497 histogram = calculate_letter_histogram(solution)
498
499 for l, a in histogram.items():
500 needed = min(a, 2)
501 level2 = (needed == 2)
502
503 if level2 and self.get_letter_behavior(l.lower(), True) == LetterBehavior.UNLOCKED:
504 needed = 1
505
506 if self.get_letter_behavior(l.lower(), False) == LetterBehavior.UNLOCKED:
507 needed = needed - 1
508
509 if needed > 0:
510 reqs.letters[l] = max(reqs.letters.get(l, 0), needed)
diff --git a/apworld/regions.py b/apworld/regions.py index fe2c99b..4f1dd55 100644 --- a/apworld/regions.py +++ b/apworld/regions.py
@@ -11,12 +11,18 @@ if TYPE_CHECKING:
11 11
12 12
13def create_region(room, world: "Lingo2World") -> Region: 13def create_region(room, world: "Lingo2World") -> Region:
14 new_region = Region(world.static_logic.get_room_region_name(room.id), world.player, world.multiworld) 14 return Region(world.static_logic.get_room_region_name(room.id), world.player, world.multiworld)
15 15
16
17def 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, {}): 18 for location in world.player_logic.locations_by_room.get(room.id, {}):
19 reqs = location.reqs.copy()
20 if new_region.name in reqs.rooms:
21 reqs.rooms.remove(new_region.name)
22
17 new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code], 23 new_location = Lingo2Location(world.player, world.static_logic.location_id_to_name[location.code],
18 location.code, new_region) 24 location.code, new_region)
19 new_location.access_rule = make_location_lambda(location.reqs, world) 25 new_location.access_rule = make_location_lambda(reqs, world, regions)
20 new_region.locations.append(new_location) 26 new_region.locations.append(new_location)
21 27
22 for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items(): 28 for event_name, item_name in world.player_logic.event_loc_item_by_room.get(room.id, {}).items():
@@ -25,22 +31,31 @@ def create_region(room, world: "Lingo2World") -> Region:
25 new_location.place_locked_item(event_item) 31 new_location.place_locked_item(event_item)
26 new_region.locations.append(new_location) 32 new_region.locations.append(new_location)
27 33
28 return new_region
29
30
31def create_regions(world: "Lingo2World"): 34def create_regions(world: "Lingo2World"):
32 regions = { 35 regions = {
33 "Menu": Region("Menu", world.player, world.multiworld) 36 "Menu": Region("Menu", world.player, world.multiworld)
34 } 37 }
35 38
39 region_and_room = []
40
41 # Create the regions in two stages. First, make the actual region objects and memoize them. Then, add all of the
42 # locations. This allows us to reference the actual region objects in the access rules for the locations, which is
43 # faster than having to look them up during access checking.
36 for room in world.static_logic.objects.rooms: 44 for room in world.static_logic.objects.rooms:
37 region = create_region(room, world) 45 region = create_region(room, world)
38 regions[region.name] = region 46 regions[region.name] = region
47 region_and_room.append((region, room))
48
49 for (region, room) in region_and_room:
50 create_locations(room, region, world, regions)
39 51
40 regions["Menu"].connect(regions["The Entry - Starting Room"], "Start Game") 52 regions["Menu"].connect(regions["The Entry - Starting Room"], "Start Game")
41 53
42 # TODO: The requirements of the opposite trigger also matter. 54 # TODO: The requirements of the opposite trigger also matter.
43 for connection in world.static_logic.objects.connections: 55 for connection in world.static_logic.objects.connections:
56 if connection.roof_access and not world.options.daedalus_roof_access:
57 continue
58
44 from_region = world.static_logic.get_room_region_name(connection.from_room) 59 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) 60 to_region = world.static_logic.get_room_region_name(connection.to_room)
46 connection_name = f"{from_region} -> {to_region}" 61 connection_name = f"{from_region} -> {to_region}"
@@ -79,14 +94,18 @@ def create_regions(world: "Lingo2World"):
79 else: 94 else:
80 connection_name = f"{connection_name} (via panel {panel.name})" 95 connection_name = f"{connection_name} (via panel {panel.name})"
81 96
97 reqs.simplify()
98
82 if from_region in regions and to_region in regions: 99 if from_region in regions and to_region in regions:
83 connection = Entrance(world.player, connection_name, regions[from_region]) 100 connection = Entrance(world.player, connection_name, regions[from_region])
84 connection.access_rule = make_location_lambda(reqs, world) 101 connection.access_rule = make_location_lambda(reqs, world, regions)
85 102
86 regions[from_region].exits.append(connection) 103 regions[from_region].exits.append(connection)
87 connection.connect(regions[to_region]) 104 connection.connect(regions[to_region])
88 105
89 for region in reqs.rooms: 106 for region in reqs.rooms:
107 if region == from_region:
108 continue
90 world.multiworld.register_indirect_condition(regions[region], connection) 109 world.multiworld.register_indirect_condition(regions[region], connection)
91 110
92 world.multiworld.regions += regions.values() 111 world.multiworld.regions += regions.values()
diff --git a/apworld/requirements.txt b/apworld/requirements.txt index b701d11..dbc395b 100644 --- a/apworld/requirements.txt +++ b/apworld/requirements.txt
@@ -1 +1 @@
protobuf>=5.29.3 \ No newline at end of file protobuf==3.20.3
diff --git a/apworld/rules.py b/apworld/rules.py index 4a84acf..c077858 100644 --- a/apworld/rules.py +++ b/apworld/rules.py
@@ -1,32 +1,63 @@
1from collections.abc import Callable 1from collections.abc import Callable
2from typing import TYPE_CHECKING 2from typing import TYPE_CHECKING
3 3
4from BaseClasses import CollectionState 4from BaseClasses import CollectionState, Region
5from .player_logic import AccessRequirements 5from .player_logic import AccessRequirements
6 6
7if TYPE_CHECKING: 7if TYPE_CHECKING:
8 from . import Lingo2World 8 from . import Lingo2World
9 9
10 10
11def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirements, world: "Lingo2World") -> bool: 11def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirements, regions: list[Region],
12 world: "Lingo2World") -> bool:
12 if not all(state.has(item, world.player) for item in reqs.items): 13 if not all(state.has(item, world.player) for item in reqs.items):
13 return False 14 return False
14 15
16 if not all(state.has(item, world.player, amount) for item, amount in reqs.progressives.items()):
17 return False
18
15 if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms): 19 if not all(state.can_reach_region(region_name, world.player) for region_name in reqs.rooms):
16 return False 20 return False
17 21
18 # TODO: symbols 22 if not all(state.can_reach(region) for region in regions):
23 return False
19 24
20 for letter_key, letter_level in reqs.letters.items(): 25 for letter_key, letter_level in reqs.letters.items():
21 if not state.has(letter_key, world.player, letter_level): 26 if not state.has(letter_key, world.player, letter_level):
22 return False 27 return False
23 28
29 if reqs.cyans:
30 if not any(state.has(letter, world.player, amount)
31 for letter, amount in world.player_logic.double_letter_amount.items()):
32 return False
33
24 if len(reqs.or_logic) > 0: 34 if len(reqs.or_logic) > 0:
25 if not all(any(lingo2_can_satisfy_requirements(state, sub_reqs, world) for sub_reqs in subjunction) 35 if not all(any(lingo2_can_satisfy_requirements(state, sub_reqs, [], world) for sub_reqs in subjunction)
26 for subjunction in reqs.or_logic): 36 for subjunction in reqs.or_logic):
27 return False 37 return False
28 38
39 if reqs.complete_at is not None:
40 completed = 0
41 checked = 0
42 for possibility in reqs.possibilities:
43 checked += 1
44 if lingo2_can_satisfy_requirements(state, possibility, [], world):
45 completed += 1
46 if completed >= reqs.complete_at:
47 break
48 elif len(reqs.possibilities) - checked + completed < reqs.complete_at:
49 # There aren't enough remaining possibilities for the check to pass.
50 return False
51 if completed < reqs.complete_at:
52 return False
53
29 return True 54 return True
30 55
31def make_location_lambda(reqs: AccessRequirements, world: "Lingo2World") -> Callable[[CollectionState], bool]: 56def make_location_lambda(reqs: AccessRequirements, world: "Lingo2World",
32 return lambda state: lingo2_can_satisfy_requirements(state, reqs, world) 57 regions: dict[str, Region]) -> Callable[[CollectionState], bool]:
58 # Replace required rooms with regions for the top level requirement, which saves looking up the regions during rule
59 # checking.
60 required_regions = [regions[room_name] for room_name in reqs.rooms]
61 new_reqs = reqs.copy()
62 new_reqs.rooms.clear()
63 return lambda state: lingo2_can_satisfy_requirements(state, new_reqs, required_regions, world)
diff --git a/apworld/static_logic.py b/apworld/static_logic.py index ff1f17d..2700601 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py
@@ -1,4 +1,5 @@
1from .generated import data_pb2 as data_pb2 1from .generated import data_pb2 as data_pb2
2from .items import SYMBOL_ITEMS, ANTI_COLLECTABLE_TRAPS
2import pkgutil 3import pkgutil
3 4
4class Lingo2StaticLogic: 5class Lingo2StaticLogic:
@@ -8,9 +9,17 @@ 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
15 letter_weights: dict[str, int]
16
11 def __init__(self): 17 def __init__(self):
12 self.item_id_to_name = {} 18 self.item_id_to_name = {}
13 self.location_id_to_name = {} 19 self.location_id_to_name = {}
20 self.item_name_groups = {}
21 self.location_name_groups = {}
22 self.letter_weights = {}
14 23
15 file = pkgutil.get_data(__name__, "generated/data.binpb") 24 file = pkgutil.get_data(__name__, "generated/data.binpb")
16 self.objects = data_pb2.AllObjects() 25 self.objects = data_pb2.AllObjects()
@@ -29,23 +38,49 @@ class Lingo2StaticLogic:
29 letter_name = f"{letter.key.upper()}{'2' if letter.level2 else '1'}" 38 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}" 39 location_name = f"{self.get_room_object_map_name(letter)} - {letter_name}"
31 self.location_id_to_name[letter.ap_id] = location_name 40 self.location_id_to_name[letter.ap_id] = location_name
41 self.location_name_groups.setdefault("Letters", []).append(location_name)
32 42
33 if not letter.level2: 43 if not letter.level2:
34 self.item_id_to_name[letter.ap_id] = letter_name 44 self.item_id_to_name[letter.ap_id] = letter.key.upper()
45 self.item_name_groups.setdefault("Letters", []).append(letter.key.upper())
35 46
36 for mastery in self.objects.masteries: 47 for mastery in self.objects.masteries:
37 location_name = f"{self.get_room_object_map_name(mastery)} - Mastery" 48 location_name = f"{self.get_room_object_map_name(mastery)} - Mastery"
38 self.location_id_to_name[mastery.ap_id] = location_name 49 self.location_id_to_name[mastery.ap_id] = location_name
50 self.location_name_groups.setdefault("Masteries", []).append(location_name)
39 51
40 for ending in self.objects.endings: 52 for ending in self.objects.endings:
41 location_name = f"{self.get_room_object_map_name(ending)} - {ending.name.title()} Ending" 53 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 54 self.location_id_to_name[ending.ap_id] = location_name
55 self.location_name_groups.setdefault("Endings", []).append(location_name)
56
57 for progressive in self.objects.progressives:
58 self.item_id_to_name[progressive.ap_id] = progressive.name
59
60 for door_group in self.objects.door_groups:
61 self.item_id_to_name[door_group.ap_id] = door_group.name
62
63 for keyholder in self.objects.keyholders:
64 if keyholder.HasField("key"):
65 location_name = f"{self.get_room_object_location_prefix(keyholder)} - {keyholder.key.upper()} Keyholder"
66 self.location_id_to_name[keyholder.ap_id] = location_name
67 self.location_name_groups.setdefault("Keyholders", []).append(location_name)
43 68
44 self.item_id_to_name[self.objects.special_ids["Nothing"]] = "Nothing" 69 self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done"
70
71 for symbol_name in SYMBOL_ITEMS.values():
72 self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name
73
74 for trap_name in ANTI_COLLECTABLE_TRAPS:
75 self.item_id_to_name[self.objects.special_ids[trap_name]] = trap_name
45 76
46 self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()} 77 self.item_name_to_id = {name: ap_id for ap_id, name in self.item_id_to_name.items()}
47 self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()} 78 self.location_name_to_id = {name: ap_id for ap_id, name in self.location_id_to_name.items()}
48 79
80 for panel in self.objects.panels:
81 for letter in panel.answer.upper():
82 self.letter_weights[letter] = self.letter_weights.get(letter, 0) + 1
83
49 def get_door_item_name(self, door: data_pb2.Door) -> str: 84 def get_door_item_name(self, door: data_pb2.Door) -> str:
50 return f"{self.get_map_object_map_name(door)} - {door.name}" 85 return f"{self.get_map_object_map_name(door)} - {door.name}"
51 86
@@ -54,13 +89,7 @@ class Lingo2StaticLogic:
54 return self.get_door_item_name(door_id) 89 return self.get_door_item_name(door_id)
55 90
56 def get_door_location_name(self, door: data_pb2.Door) -> str: 91 def get_door_location_name(self, door: data_pb2.Door) -> str:
57 game_map = self.objects.maps[door.map_id] 92 map_part = self.get_room_object_location_prefix(door)
58 room = self.objects.rooms[door.room_id]
59
60 if room.HasField("panel_display_name"):
61 map_part = f"{game_map.display_name} ({room.panel_display_name})"
62 else:
63 map_part = game_map.display_name
64 93
65 if door.HasField("location_name"): 94 if door.HasField("location_name"):
66 return f"{map_part} - {door.location_name}" 95 return f"{map_part} - {door.location_name}"
@@ -75,7 +104,7 @@ class Lingo2StaticLogic:
75 if door.type != data_pb2.DoorType.STANDARD: 104 if door.type != data_pb2.DoorType.STANDARD:
76 return None 105 return None
77 106
78 if len(door.keyholders) > 0 or len(door.endings) > 0: 107 if len(door.keyholders) > 0 or len(door.endings) > 0 or not door.HasField("complete_at"):
79 return None 108 return None
80 109
81 if len(door.panels) > 4: 110 if len(door.panels) > 4:
@@ -111,7 +140,7 @@ class Lingo2StaticLogic:
111 for panel_id in door.panels] 140 for panel_id in door.panels]
112 panel_names.sort() 141 panel_names.sort()
113 142
114 return f"{map_part} - {", ".join(panel_names)}" 143 return map_part + " - " + ", ".join(panel_names)
115 144
116 def get_door_location_name_by_id(self, door_id: int) -> str: 145 def get_door_location_name_by_id(self, door_id: int) -> str:
117 door = self.objects.doors[door_id] 146 door = self.objects.doors[door_id]
@@ -126,3 +155,15 @@ class Lingo2StaticLogic:
126 155
127 def get_room_object_map_name(self, obj) -> str: 156 def get_room_object_map_name(self, obj) -> str:
128 return self.get_map_object_map_name(self.objects.rooms[obj.room_id]) 157 return self.get_map_object_map_name(self.objects.rooms[obj.room_id])
158
159 def get_room_object_location_prefix(self, obj) -> str:
160 room = self.objects.rooms[obj.room_id]
161 game_map = self.objects.maps[room.map_id]
162
163 if room.HasField("panel_display_name"):
164 return f"{game_map.display_name} ({room.panel_display_name})"
165 else:
166 return game_map.display_name
167
168 def get_data_version(self) -> int:
169 return self.objects.version
diff --git a/apworld/version.py b/apworld/version.py new file mode 100644 index 0000000..1b62c0a --- /dev/null +++ b/apworld/version.py
@@ -0,0 +1 @@
APWORLD_VERSION = 3
diff --git a/client/Archipelago/animationListener.gd b/client/Archipelago/animationListener.gd index f1fb5fb..c3b26db 100644 --- a/client/Archipelago/animationListener.gd +++ b/client/Archipelago/animationListener.gd
@@ -1,6 +1,7 @@
1extends "res://scripts/nodes/listeners/animationListener.gd" 1extends "res://scripts/nodes/listeners/animationListener.gd"
2 2
3var item_id 3var item_id
4var item_amount
4 5
5 6
6func _ready(): 7func _ready():
@@ -8,17 +9,16 @@ func _ready():
8 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() 9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
9 ) 10 )
10 11
11 print("node: %s" % node_path)
12
13 var gamedata = global.get_node("Gamedata") 12 var gamedata = global.get_node("Gamedata")
14 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
15 if door_id != null: 14 if door_id != null:
16 print("door_id: %d" % door_id)
17
18 var ap = global.get_node("Archipelago") 15 var ap = global.get_node("Archipelago")
19 item_id = ap.get_item_id_for_door(door_id) 16 var item_lock = ap.get_item_id_for_door(door_id)
17
18 if item_lock != null:
19 item_id = item_lock[0]
20 item_amount = item_lock[1]
20 21
21 if item_id != null:
22 self.senders = [] 22 self.senders = []
23 self.senderGroup = [] 23 self.senderGroup = []
24 self.nested = false 24 self.nested = false
@@ -34,5 +34,5 @@ func _ready():
34func _readier(): 34func _readier():
35 var ap = global.get_node("Archipelago") 35 var ap = global.get_node("Archipelago")
36 36
37 if ap.has_item(item_id): 37 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered() 38 handleTriggered()
diff --git a/client/Archipelago/client.gd b/client/Archipelago/client.gd index f0f36d7..843647d 100644 --- a/client/Archipelago/client.gd +++ b/client/Archipelago/client.gd
@@ -32,17 +32,23 @@ var _players = []
32var _player_name_by_slot = {} 32var _player_name_by_slot = {}
33var _game_by_player = {} 33var _game_by_player = {}
34var _checked_locations = [] 34var _checked_locations = []
35var _received_items = [] 35var _received_indexes = []
36var _received_items = {}
36var _slot_data = {} 37var _slot_data = {}
37 38
38signal could_not_connect 39signal could_not_connect
39signal connect_status 40signal connect_status
40signal client_connected(slot_data) 41signal client_connected(slot_data)
41signal item_received(item_id, index, player, flags) 42signal item_received(item_id, index, player, flags, amount)
42signal message_received(message) 43signal message_received(message)
44signal location_scout_received(item_id, location_id, player, flags)
43 45
44 46
45func _init(): 47func _init():
48 set_process_mode(Node.PROCESS_MODE_ALWAYS)
49
50 _ws.inbound_buffer_size = 8388608
51
46 global._print("Instantiated APClient") 52 global._print("Instantiated APClient")
47 53
48 # Read AP datapackages from file, if there are any 54 # Read AP datapackages from file, if there are any
@@ -74,6 +80,8 @@ func _reset_state():
74 _authenticated = false 80 _authenticated = false
75 _try_wss = false 81 _try_wss = false
76 _has_connected = false 82 _has_connected = false
83 _received_items = {}
84 _received_indexes = []
77 85
78 86
79func _errored(): 87func _errored():
@@ -219,7 +227,7 @@ func _process(_delta):
219 error_message = "Unknown error." 227 error_message = "Unknown error."
220 228
221 _initiated_disconnect = true 229 _initiated_disconnect = true
222 _ws.disconnect_from_host() 230 _ws.close()
223 231
224 emit_signal("could_not_connect", error_message) 232 emit_signal("could_not_connect", error_message)
225 global._print("Connection to AP refused") 233 global._print("Connection to AP refused")
@@ -228,21 +236,40 @@ func _process(_delta):
228 elif cmd == "ReceivedItems": 236 elif cmd == "ReceivedItems":
229 var i = 0 237 var i = 0
230 for item in message["items"]: 238 for item in message["items"]:
231 if not _received_items.has(int(item["item"])): 239 var index = int(message["index"] + i)
232 _received_items.append(int(item["item"])) 240 i += 1
241
242 if _received_indexes.has(index):
243 # Do not re-process items.
244 continue
245
246 _received_indexes.append(index)
247
248 var item_id = int(item["item"])
249 _received_items[item_id] = _received_items.get(item_id, 0) + 1
233 250
234 emit_signal( 251 emit_signal(
235 "item_received", 252 "item_received",
236 int(item["item"]), 253 item_id,
237 int(message["index"]) + i, 254 index,
238 int(item["player"]), 255 int(item["player"]),
239 int(item["flags"]) 256 int(item["flags"]),
257 _received_items[item_id]
240 ) 258 )
241 i += 1
242 259
243 elif cmd == "PrintJSON": 260 elif cmd == "PrintJSON":
244 emit_signal("message_received", message) 261 emit_signal("message_received", message)
245 262
263 elif cmd == "LocationInfo":
264 for loc in message["locations"]:
265 emit_signal(
266 "location_scout_received",
267 int(loc["item"]),
268 int(loc["location"]),
269 int(loc["player"]),
270 int(loc["flags"])
271 )
272
246 elif state == WebSocketPeer.STATE_CLOSED: 273 elif state == WebSocketPeer.STATE_CLOSED:
247 if _has_connected: 274 if _has_connected:
248 _closed() 275 _closed()
@@ -284,7 +311,7 @@ func connectToServer(server, un, pw):
284 % err 311 % err
285 ) 312 )
286 ) 313 )
287 global._print("Could not connect to AP: " + err) 314 global._print("Could not connect to AP: %d" % err)
288 return 315 return
289 _should_process = true 316 _should_process = true
290 317
@@ -378,5 +405,13 @@ func completedGoal():
378 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL 405 sendMessage([{"cmd": "StatusUpdate", "status": 30}]) # CLIENT_GOAL
379 406
380 407
408func scoutLocations(loc_ids):
409 sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}])
410
411
381func hasItem(item_id): 412func hasItem(item_id):
382 return _received_items.has(item_id) 413 return _received_items.has(item_id)
414
415
416func getItemAmount(item_id):
417 return _received_items.get(item_id, 0)
diff --git a/client/Archipelago/collectable.gd b/client/Archipelago/collectable.gd new file mode 100644 index 0000000..4a17a2a --- /dev/null +++ b/client/Archipelago/collectable.gd
@@ -0,0 +1,16 @@
1extends "res://scripts/nodes/collectable.gd"
2
3
4func pickedUp():
5 if unlock_type == "key":
6 var ap = global.get_node("Archipelago")
7 if ap.get_letter_behavior(unlock_key, level == 2) == ap.kLETTER_BEHAVIOR_VANILLA:
8 ap.keyboard.collect_local_letter(unlock_key, level)
9 else:
10 ap.keyboard.update_unlocks()
11
12 super.pickedUp()
13
14
15func setScoutedText(text):
16 get_node("MeshInstance3D").mesh.text = text.replace(" ", "\n")
diff --git a/client/Archipelago/door.gd b/client/Archipelago/door.gd index 731eca4..fead818 100644 --- a/client/Archipelago/door.gd +++ b/client/Archipelago/door.gd
@@ -1,6 +1,7 @@
1extends "res://scripts/nodes/door.gd" 1extends "res://scripts/nodes/door.gd"
2 2
3var item_id 3var item_id
4var item_amount
4 5
5 6
6func _ready(): 7func _ready():
@@ -8,17 +9,16 @@ func _ready():
8 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() 9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
9 ) 10 )
10 11
11 print("node: %s" % node_path)
12
13 var gamedata = global.get_node("Gamedata") 12 var gamedata = global.get_node("Gamedata")
14 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
15 if door_id != null: 14 if door_id != null:
16 print("door_id: %d" % door_id)
17
18 var ap = global.get_node("Archipelago") 15 var ap = global.get_node("Archipelago")
19 item_id = ap.get_item_id_for_door(door_id) 16 var item_lock = ap.get_item_id_for_door(door_id)
17
18 if item_lock != null:
19 item_id = item_lock[0]
20 item_amount = item_lock[1]
20 21
21 if item_id != null:
22 self.senders = [] 22 self.senders = []
23 self.senderGroup = [] 23 self.senderGroup = []
24 self.nested = false 24 self.nested = false
@@ -34,5 +34,5 @@ func _ready():
34func _readier(): 34func _readier():
35 var ap = global.get_node("Archipelago") 35 var ap = global.get_node("Archipelago")
36 36
37 if ap.has_item(item_id): 37 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered() 38 handleTriggered()
diff --git a/client/Archipelago/gamedata.gd b/client/Archipelago/gamedata.gd index 16368a9..41d966a 100644 --- a/client/Archipelago/gamedata.gd +++ b/client/Archipelago/gamedata.gd
@@ -5,13 +5,42 @@ var SCRIPT_proto
5var objects 5var objects
6var door_id_by_map_node_path = {} 6var door_id_by_map_node_path = {}
7var painting_id_by_map_node_path = {} 7var painting_id_by_map_node_path = {}
8var panel_id_by_map_node_path = {}
8var door_id_by_ap_id = {} 9var door_id_by_ap_id = {}
9var map_id_by_name = {} 10var map_id_by_name = {}
11var progressive_id_by_ap_id = {}
12var letter_id_by_ap_id = {}
13var symbol_item_ids = []
14var anti_trap_ids = {}
15
16var kSYMBOL_ITEMS
10 17
11 18
12func _init(proto_script): 19func _init(proto_script):
13 SCRIPT_proto = proto_script 20 SCRIPT_proto = proto_script
14 21
22 kSYMBOL_ITEMS = {
23 SCRIPT_proto.PuzzleSymbol.SUN: "Sun Symbol",
24 SCRIPT_proto.PuzzleSymbol.SPARKLES: "Sparkles Symbol",
25 SCRIPT_proto.PuzzleSymbol.ZERO: "Zero Symbol",
26 SCRIPT_proto.PuzzleSymbol.EXAMPLE: "Example Symbol",
27 SCRIPT_proto.PuzzleSymbol.BOXES: "Boxes Symbol",
28 SCRIPT_proto.PuzzleSymbol.PLANET: "Planet Symbol",
29 SCRIPT_proto.PuzzleSymbol.PYRAMID: "Pyramid Symbol",
30 SCRIPT_proto.PuzzleSymbol.CROSS: "Cross Symbol",
31 SCRIPT_proto.PuzzleSymbol.SWEET: "Sweet Symbol",
32 SCRIPT_proto.PuzzleSymbol.GENDER: "Gender Symbol",
33 SCRIPT_proto.PuzzleSymbol.AGE: "Age Symbol",
34 SCRIPT_proto.PuzzleSymbol.SOUND: "Sound Symbol",
35 SCRIPT_proto.PuzzleSymbol.ANAGRAM: "Anagram Symbol",
36 SCRIPT_proto.PuzzleSymbol.JOB: "Job Symbol",
37 SCRIPT_proto.PuzzleSymbol.STARS: "Stars Symbol",
38 SCRIPT_proto.PuzzleSymbol.NULL: "Null Symbol",
39 SCRIPT_proto.PuzzleSymbol.EVAL: "Eval Symbol",
40 SCRIPT_proto.PuzzleSymbol.LINGO: "Lingo Symbol",
41 SCRIPT_proto.PuzzleSymbol.QUESTION: "Question Symbol",
42 }
43
15 44
16func load(data_bytes): 45func load(data_bytes):
17 objects = SCRIPT_proto.AllObjects.new() 46 objects = SCRIPT_proto.AllObjects.new()
@@ -50,6 +79,31 @@ func load(data_bytes):
50 79
51 var _map_data = painting_id_by_map_node_path[map.get_name()] 80 var _map_data = painting_id_by_map_node_path[map.get_name()]
52 81
82 for progressive in objects.get_progressives():
83 progressive_id_by_ap_id[progressive.get_ap_id()] = progressive.get_id()
84
85 for letter in objects.get_letters():
86 letter_id_by_ap_id[letter.get_ap_id()] = letter.get_id()
87
88 for panel in objects.get_panels():
89 var room = objects.get_rooms()[panel.get_room_id()]
90 var map = objects.get_maps()[room.get_map_id()]
91
92 if not map.get_name() in panel_id_by_map_node_path:
93 panel_id_by_map_node_path[map.get_name()] = {}
94
95 var map_data = panel_id_by_map_node_path[map.get_name()]
96 map_data[panel.get_path()] = panel.get_id()
97
98 for symbol_name in kSYMBOL_ITEMS.values():
99 symbol_item_ids.append(objects.get_special_ids()[symbol_name])
100
101 for special_name in objects.get_special_ids().keys():
102 if special_name.begins_with("Anti "):
103 anti_trap_ids[objects.get_special_ids()[special_name]] = (
104 special_name.substr(5).to_lower()
105 )
106
53 107
54func get_door_for_map_node_path(map_name, node_path): 108func get_door_for_map_node_path(map_name, node_path):
55 if not door_id_by_map_node_path.has(map_name): 109 if not door_id_by_map_node_path.has(map_name):
@@ -59,6 +113,14 @@ func get_door_for_map_node_path(map_name, node_path):
59 return map_data.get(node_path, null) 113 return map_data.get(node_path, null)
60 114
61 115
116func get_panel_for_map_node_path(map_name, node_path):
117 if not panel_id_by_map_node_path.has(map_name):
118 return null
119
120 var map_data = panel_id_by_map_node_path[map_name]
121 return map_data.get(node_path, null)
122
123
62func get_door_ap_id(door_id): 124func get_door_ap_id(door_id):
63 var door = objects.get_doors()[door_id] 125 var door = objects.get_doors()[door_id]
64 if door.has_ap_id(): 126 if door.has_ap_id():
diff --git a/client/Archipelago/keyHolder.gd b/client/Archipelago/keyHolder.gd new file mode 100644 index 0000000..3c037ff --- /dev/null +++ b/client/Archipelago/keyHolder.gd
@@ -0,0 +1,38 @@
1extends "res://scripts/nodes/keyHolder.gd"
2
3
4func setFromAp(key, level):
5 if level > 0:
6 has_key = true
7 is_complete = "%s%d" % [key, level]
8 held_key = key
9 held_level = level
10 get_node("Hinge/Letter").mesh.text = held_key
11 get_node("Hinge/Letter2").mesh.text = held_key
12 setMaterial()
13 emit_signal("trigger")
14 else:
15 has_key = false
16 held_key = ""
17 held_level = 0
18 setMaterial()
19 get_node("Hinge/Letter").mesh.text = "-"
20 get_node("Hinge/Letter2").mesh.text = "-"
21 is_complete = ""
22 emit_signal("untrigger")
23
24
25func addKey(key):
26 var node_path = String(
27 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
28 )
29 var ap = global.get_node("Archipelago")
30 ap.keyboard.put_in_keyholder(key, global.map, node_path)
31
32
33func removeKey():
34 var node_path = String(
35 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
36 )
37 var ap = global.get_node("Archipelago")
38 ap.keyboard.remove_from_keyholder(held_key, global.map, node_path)
diff --git a/client/Archipelago/keyHolderChecker.gd b/client/Archipelago/keyHolderChecker.gd new file mode 100644 index 0000000..a75a9e4 --- /dev/null +++ b/client/Archipelago/keyHolderChecker.gd
@@ -0,0 +1,24 @@
1extends "res://scripts/nodes/listeners/keyHolderChecker.gd"
2
3
4func check():
5 var ap = global.get_node("Archipelago")
6 var matches = []
7 for map in ap.keyboard.keyholder_state.keys():
8 var nodes = ap.keyboard.keyholder_state[map]
9 for node in nodes.keys():
10 matches.append([nodes[node], 1, map, "/root/scene/%s" % node])
11
12 var count = 0
13 for key_match in matches:
14 var active = (
15 key_match[2] + String(key_match[3]).replace("/root/scene/Components/KeyHolders/", ".")
16 )
17 if map[active] == key_match[0]:
18 emit_signal("trigger_letter", key_match[0], true)
19 count += 1
20 else:
21 emit_signal("trigger_letter", key_match[0], false)
22
23 if count > 25:
24 emit_signal("trigger")
diff --git a/client/Archipelago/keyHolderResetterListener.gd b/client/Archipelago/keyHolderResetterListener.gd new file mode 100644 index 0000000..d5300f3 --- /dev/null +++ b/client/Archipelago/keyHolderResetterListener.gd
@@ -0,0 +1,8 @@
1extends "res://scripts/nodes/listeners/keyHolderResetterListener.gd"
2
3
4func reset():
5 var ap = global.get_node("Archipelago")
6 var was_removed = ap.keyboard.reset_keyholders()
7 if was_removed:
8 sfxPlayer.sfx_play("pickup")
diff --git a/client/Archipelago/keyboard.gd b/client/Archipelago/keyboard.gd new file mode 100644 index 0000000..450566d --- /dev/null +++ b/client/Archipelago/keyboard.gd
@@ -0,0 +1,199 @@
1extends Node
2
3const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz"
4
5var letters_saved = {}
6var letters_in_keyholders = []
7var letters_blocked = []
8var letters_dynamic = {}
9var keyholder_state = {}
10
11var filename = ""
12
13
14func _init():
15 reset()
16
17
18func reset():
19 letters_saved.clear()
20 letters_in_keyholders.clear()
21 letters_blocked.clear()
22 letters_dynamic.clear()
23 keyholder_state.clear()
24
25
26func load_seed():
27 var ap = global.get_node("Archipelago")
28
29 reset()
30
31 filename = "user://archipelago_keys/%s_%d" % [ap.client._seed, ap.client._slot]
32
33 if FileAccess.file_exists(filename):
34 var ap_file = FileAccess.open(filename, FileAccess.READ)
35 var localdata = []
36 if ap_file != null:
37 localdata = ap_file.get_var(true)
38 ap_file.close()
39
40 if typeof(localdata) != TYPE_ARRAY:
41 print("AP keyboard file is corrupted")
42 localdata = []
43
44 if localdata.size() > 0:
45 letters_saved = localdata[0]
46 if localdata.size() > 1:
47 letters_in_keyholders = localdata[1]
48 if localdata.size() > 2:
49 keyholder_state = localdata[2]
50
51 for k in kALL_LETTERS:
52 var level = 0
53
54 if ap.get_letter_behavior(k, false) == ap.kLETTER_BEHAVIOR_UNLOCKED:
55 level += 1
56 if ap.get_letter_behavior(k, true) == ap.kLETTER_BEHAVIOR_UNLOCKED:
57 level += 1
58
59 letters_dynamic[k] = level
60
61 update_unlocks()
62
63
64func save():
65 var dir = DirAccess.open("user://")
66 var folder = "archipelago_keys"
67 if not dir.dir_exists(folder):
68 dir.make_dir(folder)
69
70 var file = FileAccess.open(filename, FileAccess.WRITE)
71
72 var data = [
73 letters_saved,
74 letters_in_keyholders,
75 keyholder_state,
76 ]
77 file.store_var(data, true)
78 file.close()
79
80
81func update_unlocks():
82 unlocks.resetKeys()
83
84 var has_doubles = false
85
86 for k in kALL_LETTERS:
87 var level = 0
88
89 if not letters_in_keyholders.has(k):
90 level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0)
91
92 if level >= 2:
93 level = 2
94 has_doubles = true
95
96 if letters_blocked.has(k):
97 level = 0
98
99 unlocks.unlockKey(k, level)
100
101 if has_doubles and unlocks.data["double_letters"] != "unlocked":
102 var ap = global.get_node("Archipelago")
103 if ap.cyan_door_behavior == ap.kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER:
104 unlocks.setData("double_letters", "unlocked")
105
106
107func collect_local_letter(key, level):
108 if level < 0 or level > 2 or level < letters_saved.get(key, 0):
109 return
110
111 letters_saved[key] = level
112
113 if letters_blocked.has(key):
114 letters_blocked.erase(key)
115
116 update_unlocks()
117 save()
118
119
120func collect_remote_letter(key, level):
121 if level < 0 or level > 2 or level < letters_dynamic.get(key, 0):
122 return
123
124 letters_dynamic[key] = level
125
126 if letters_blocked.has(key):
127 letters_blocked.erase(key)
128
129 update_unlocks()
130 save()
131
132
133func put_in_keyholder(key, map, kh_path):
134 if not keyholder_state.has(map):
135 keyholder_state[map] = {}
136
137 keyholder_state[map][kh_path] = key
138 letters_in_keyholders.append(key)
139
140 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(
141 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
142 )
143
144 update_unlocks()
145 save()
146
147
148func remove_from_keyholder(key, map, kh_path):
149 if not keyholder_state.has(map):
150 # This... shouldn't happen.
151 keyholder_state[map] = {}
152
153 keyholder_state[map].erase(kh_path)
154 letters_in_keyholders.erase(key)
155
156 get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(key, 0)
157
158 update_unlocks()
159 save()
160
161
162func block_letter(key):
163 if not letters_blocked.has(key):
164 letters_blocked.append(key)
165
166 update_unlocks()
167
168
169func load_keyholders(map):
170 if keyholder_state.has(map):
171 var khs = keyholder_state[map]
172
173 for path in khs.keys():
174 var key = khs[path]
175 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
176 key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
177 )
178
179
180func reset_keyholders():
181 if letters_in_keyholders.is_empty() and letters_blocked.is_empty():
182 return false
183
184 var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty()
185
186 if keyholder_state.has(global.map):
187 for path in keyholder_state[global.map]:
188 get_tree().get_root().get_node("scene").get_node(path).setFromAp(
189 keyholder_state[global.map][path], 0
190 )
191
192 keyholder_state.clear()
193 letters_in_keyholders.clear()
194 letters_blocked.clear()
195
196 update_unlocks()
197 save()
198
199 return cleared_anything
diff --git a/client/Archipelago/manager.gd b/client/Archipelago/manager.gd index 97c556a..95f8e1a 100644 --- a/client/Archipelago/manager.gd +++ b/client/Archipelago/manager.gd
@@ -1,8 +1,9 @@
1extends Node 1extends Node
2 2
3const my_version = "0.1.0" 3const MOD_VERSION = 4
4 4
5var SCRIPT_client 5var SCRIPT_client
6var SCRIPT_keyboard
6var SCRIPT_locationListener 7var SCRIPT_locationListener
7var SCRIPT_uuid 8var SCRIPT_uuid
8var SCRIPT_victoryListener 9var SCRIPT_victoryListener
@@ -13,13 +14,42 @@ var ap_pass = ""
13var connection_history = [] 14var connection_history = []
14 15
15var client 16var client
17var keyboard
16 18
17var _localdata_file = "" 19var _localdata_file = ""
18var _received_indexes = []
19var _last_new_item = -1 20var _last_new_item = -1
20var _batch_locations = false 21var _batch_locations = false
21var _held_locations = [] 22var _held_locations = []
22 23var _held_location_scouts = []
24var _location_scouts = {}
25var _item_locks = {}
26var _inverse_item_locks = {}
27var _held_letters = {}
28var _letters_setup = false
29
30const kSHUFFLE_LETTERS_VANILLA = 0
31const kSHUFFLE_LETTERS_UNLOCKED = 1
32const kSHUFFLE_LETTERS_PROGRESSIVE = 2
33const kSHUFFLE_LETTERS_VANILLA_CYAN = 3
34const kSHUFFLE_LETTERS_ITEM_CYAN = 4
35
36const kLETTER_BEHAVIOR_VANILLA = 0
37const kLETTER_BEHAVIOR_ITEM = 1
38const kLETTER_BEHAVIOR_UNLOCKED = 2
39
40const kCYAN_DOOR_BEHAVIOR_H2 = 0
41const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1
42const kCYAN_DOOR_BEHAVIOR_ITEM = 2
43
44var apworld_version = [0, 0]
45var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2
46var daedalus_roof_access = false
47var keyholder_sanity = false
48var shuffle_control_center_colors = false
49var shuffle_doors = false
50var shuffle_gallery_paintings = false
51var shuffle_letters = kSHUFFLE_LETTERS_VANILLA
52var shuffle_symbols = false
23var victory_condition = -1 53var victory_condition = -1
24 54
25signal could_not_connect 55signal could_not_connect
@@ -57,12 +87,16 @@ func _ready():
57 87
58 client.connect("item_received", _process_item) 88 client.connect("item_received", _process_item)
59 client.connect("message_received", _process_message) 89 client.connect("message_received", _process_message)
90 client.connect("location_scout_received", _process_location_scout)
60 client.connect("could_not_connect", _client_could_not_connect) 91 client.connect("could_not_connect", _client_could_not_connect)
61 client.connect("connect_status", _client_connect_status) 92 client.connect("connect_status", _client_connect_status)
62 client.connect("client_connected", _client_connected) 93 client.connect("client_connected", _client_connected)
63 94
64 add_child(client) 95 add_child(client)
65 96
97 keyboard = SCRIPT_keyboard.new()
98 add_child(keyboard)
99
66 100
67func saveSettings(): 101func saveSettings():
68 # Save the AP settings to disk. 102 # Save the AP settings to disk.
@@ -96,8 +130,13 @@ func saveLocaldata():
96 130
97 131
98func connectToServer(): 132func connectToServer():
99 _received_indexes = []
100 _last_new_item = -1 133 _last_new_item = -1
134 _batch_locations = false
135 _held_locations = []
136 _held_location_scouts = []
137 _location_scouts = {}
138 _letters_setup = false
139 _held_letters = {}
101 140
102 client.connectToServer(ap_server, ap_user, ap_pass) 141 client.connectToServer(ap_server, ap_user, ap_pass)
103 142
@@ -111,48 +150,46 @@ func disconnect_from_ap():
111 150
112 151
113func get_item_id_for_door(door_id): 152func get_item_id_for_door(door_id):
114 var gamedata = global.get_node("Gamedata") 153 return _item_locks.get(door_id, null)
115 var door = gamedata.objects.get_doors()[door_id]
116 if (
117 door.get_type() == gamedata.SCRIPT_proto.DoorType.EVENT
118 or door.get_type() == gamedata.SCRIPT_proto.DoorType.LOCATION_ONLY
119 or door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR
120 ):
121 return null
122 return gamedata.get_door_ap_id(door_id)
123
124 154
125func has_item(item_id):
126 return client.hasItem(item_id)
127
128
129func _process_item(item, index, from, flags):
130 if index != null:
131 if _received_indexes.has(index):
132 # Do not re-process items.
133 return
134
135 _received_indexes.append(index)
136 155
156func _process_item(item, index, from, flags, amount):
137 var item_name = "Unknown" 157 var item_name = "Unknown"
138 if client._item_id_to_name["Lingo 2"].has(item): 158 if client._item_id_to_name["Lingo 2"].has(item):
139 item_name = client._item_id_to_name["Lingo 2"][item] 159 item_name = client._item_id_to_name["Lingo 2"][item]
140 160
141 var gamedata = global.get_node("Gamedata") 161 var gamedata = global.get_node("Gamedata")
142 var door_id = gamedata.door_id_by_ap_id.get(item, null) 162
143 if door_id != null and gamedata.get_door_map_name(door_id) == global.map: 163 var prog_id = null
144 var receivers = gamedata.get_door_receivers(door_id) 164 if _inverse_item_locks.has(item):
145 var scene = get_tree().get_root().get_node_or_null("scene") 165 for lock in _inverse_item_locks.get(item):
146 if scene != null: 166 if lock[1] != amount:
147 for receiver in receivers: 167 continue
148 var rnode = scene.get_node_or_null(receiver) 168
149 if rnode != null: 169 if gamedata.progressive_id_by_ap_id.has(item):
150 rnode.handleTriggered() 170 prog_id = lock[0]
151 #for painting_id in gamedata.objects.get_doors()[door_id].get_move_paintings(): 171
152 # var painting = gamedata.objects.get_paintings()[painting_id] 172 if gamedata.get_door_map_name(lock[0]) != global.map:
153 # var pnode = scene.get_node_or_null(painting.get_path() + "/teleportListener") 173 continue
154 # if pnode != null: 174
155 # pnode.handleTriggered() 175 var receivers = gamedata.get_door_receivers(lock[0])
176 var scene = get_tree().get_root().get_node_or_null("scene")
177 if scene != null:
178 for receiver in receivers:
179 var rnode = scene.get_node_or_null(receiver)
180 if rnode != null:
181 rnode.handleTriggered()
182
183 var letter_id = gamedata.letter_id_by_ap_id.get(item, null)
184 if letter_id != null:
185 var letter = gamedata.objects.get_letters()[letter_id]
186 if not letter.has_level2() or not letter.get_level2():
187 _process_key_item(letter.get_key(), amount)
188
189 if gamedata.symbol_item_ids.has(item):
190 var player = get_tree().get_root().get_node_or_null("scene/player")
191 if player != null:
192 player.emit_signal("evaluate_solvability")
156 193
157 # Show a message about the item if it's new. 194 # Show a message about the item if it's new.
158 if index != null and index > _last_new_item: 195 if index != null and index > _last_new_item:
@@ -160,16 +197,26 @@ func _process_item(item, index, from, flags):
160 saveLocaldata() 197 saveLocaldata()
161 198
162 var player_name = "Unknown" 199 var player_name = "Unknown"
163 if client._player_name_by_slot.has(from): 200 if client._player_name_by_slot.has(float(from)):
164 player_name = client._player_name_by_slot[from] 201 player_name = client._player_name_by_slot[float(from)]
165 202
166 var item_color = colorForItemType(flags) 203 var item_color = colorForItemType(flags)
167 204
205 var full_item_name = item_name
206 if prog_id != null:
207 var door = gamedata.objects.get_doors()[prog_id]
208 full_item_name = "%s (%s)" % [item_name, door.get_name()]
209
168 var message 210 var message
169 if from == client._slot: 211 if from == client._slot:
170 message = "Found [color=%s]%s[/color]" % [item_color, item_name] 212 message = "Found [color=%s]%s[/color]" % [item_color, full_item_name]
171 else: 213 else:
172 message = "Received [color=%s]%s[/color] from %s" % [item_color, item_name, player_name] 214 message = (
215 "Received [color=%s]%s[/color] from %s" % [item_color, full_item_name, player_name]
216 )
217
218 if gamedata.anti_trap_ids.has(item):
219 keyboard.block_letter(gamedata.anti_trap_ids[item])
173 220
174 global._print(message) 221 global._print(message)
175 222
@@ -188,15 +235,15 @@ func _process_message(message):
188 235
189 var item_name = "Unknown" 236 var item_name = "Unknown"
190 var item_player_game = client._game_by_player[message["receiving"]] 237 var item_player_game = client._game_by_player[message["receiving"]]
191 if client._item_id_to_name[item_player_game].has(message["item"]["item"]): 238 if client._item_id_to_name[item_player_game].has(int(message["item"]["item"])):
192 item_name = client._item_id_to_name[item_player_game][message["item"]["item"]] 239 item_name = client._item_id_to_name[item_player_game][int(message["item"]["item"])]
193 240
194 var location_name = "Unknown" 241 var location_name = "Unknown"
195 var location_player_game = client._game_by_player[message["item"]["player"]] 242 var location_player_game = client._game_by_player[message["item"]["player"]]
196 if client._location_id_to_name[location_player_game].has(message["item"]["location"]): 243 if client._location_id_to_name[location_player_game].has(int(message["item"]["location"])):
197 location_name = ( 244 location_name = (client._location_id_to_name[location_player_game][int(
198 client._location_id_to_name[location_player_game][message["item"]["location"]] 245 message["item"]["location"]
199 ) 246 )])
200 247
201 var player_name = "Unknown" 248 var player_name = "Unknown"
202 if client._player_name_by_slot.has(message["receiving"]): 249 if client._player_name_by_slot.has(message["receiving"]):
@@ -262,8 +309,35 @@ func parse_printjson_for_textclient(message):
262 textclient_node.parse_printjson("".join(parts)) 309 textclient_node.parse_printjson("".join(parts))
263 310
264 311
265func _client_could_not_connect(): 312func _process_location_scout(item_id, location_id, player, flags):
266 emit_signal("could_not_connect") 313 _location_scouts[location_id] = {"item": item_id, "player": player, "flags": flags}
314
315 if player == client._slot and flags & 4 != 0:
316 # This is a trap for us, so let's not display it.
317 return
318
319 var gamedata = global.get_node("Gamedata")
320 var map_id = gamedata.map_id_by_name.get(global.map)
321
322 var item_name = "Unknown"
323 var item_player_game = client._game_by_player[float(player)]
324 if client._item_id_to_name[item_player_game].has(item_id):
325 item_name = client._item_id_to_name[item_player_game][item_id]
326
327 var letter_id = gamedata.letter_id_by_ap_id.get(location_id, null)
328 if letter_id != null:
329 var letter = gamedata.objects.get_letters()[letter_id]
330 var room = gamedata.objects.get_rooms()[letter.get_room_id()]
331 if room.get_map_id() == map_id:
332 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
333 letter.get_path()
334 )
335 if collectable != null:
336 collectable.setScoutedText(item_name)
337
338
339func _client_could_not_connect(message):
340 emit_signal("could_not_connect", message)
267 341
268 342
269func _client_connect_status(message): 343func _client_connect_status(message):
@@ -271,6 +345,8 @@ func _client_connect_status(message):
271 345
272 346
273func _client_connected(slot_data): 347func _client_connected(slot_data):
348 var gamedata = global.get_node("Gamedata")
349
274 _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot] 350 _localdata_file = "user://archipelago_data/%s_%d" % [client._seed, client._slot]
275 _last_new_item = -1 351 _last_new_item = -1
276 352
@@ -288,8 +364,76 @@ func _client_connected(slot_data):
288 if localdata.size() > 0: 364 if localdata.size() > 0:
289 _last_new_item = localdata[0] 365 _last_new_item = localdata[0]
290 366
291 if slot_data.has("victory_condition"): 367 # Read slot data.
292 victory_condition = int(slot_data["victory_condition"]) 368 cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0))
369 daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false))
370 keyholder_sanity = bool(slot_data.get("keyholder_sanity", false))
371 shuffle_control_center_colors = bool(slot_data.get("shuffle_control_center_colors", false))
372 shuffle_doors = bool(slot_data.get("shuffle_doors", false))
373 shuffle_gallery_paintings = bool(slot_data.get("shuffle_gallery_paintings", false))
374 shuffle_letters = int(slot_data.get("shuffle_letters", 0))
375 shuffle_symbols = bool(slot_data.get("shuffle_symbols", false))
376 victory_condition = int(slot_data.get("victory_condition", 0))
377
378 if slot_data.has("version"):
379 apworld_version = [int(slot_data["version"][0]), int(slot_data["version"][1])]
380
381 # Set up item locks.
382 _item_locks = {}
383
384 if shuffle_doors:
385 for door in gamedata.objects.get_doors():
386 if (
387 door.get_type() == gamedata.SCRIPT_proto.DoorType.STANDARD
388 or door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
389 ):
390 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
391
392 for progressive in gamedata.objects.get_progressives():
393 for i in range(0, progressive.get_doors().size()):
394 var door = gamedata.objects.get_doors()[progressive.get_doors()[i]]
395 _item_locks[door.get_id()] = [progressive.get_ap_id(), i + 1]
396
397 for door_group in gamedata.objects.get_door_groups():
398 if (
399 door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CONNECTOR
400 or door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.SHUFFLE_GROUP
401 ):
402 for door in door_group.get_doors():
403 _item_locks[door] = [door_group.get_ap_id(), 1]
404
405 if shuffle_control_center_colors:
406 for door in gamedata.objects.get_doors():
407 if door.get_type() == gamedata.SCRIPT_proto.DoorType.CONTROL_CENTER_COLOR:
408 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
409
410 for door_group in gamedata.objects.get_door_groups():
411 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.COLOR_CONNECTOR:
412 for door in door_group.get_doors():
413 _item_locks[door] = [door_group.get_ap_id(), 1]
414
415 if shuffle_gallery_paintings:
416 for door in gamedata.objects.get_doors():
417 if door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING:
418 _item_locks[door.get_id()] = [door.get_ap_id(), 1]
419
420 if cyan_door_behavior == kCYAN_DOOR_BEHAVIOR_ITEM:
421 for door_group in gamedata.objects.get_door_groups():
422 if door_group.get_type() == gamedata.SCRIPT_proto.DoorGroupType.CYAN_DOORS:
423 for door in door_group.get_doors():
424 if not _item_locks.has(door):
425 _item_locks[door] = [door_group.get_ap_id(), 1]
426
427 # Create a reverse item locks map for processing items.
428 _inverse_item_locks = {}
429
430 for door_id in _item_locks.keys():
431 var lock = _item_locks.get(door_id)
432
433 if not _inverse_item_locks.has(lock[0]):
434 _inverse_item_locks[lock[0]] = []
435
436 _inverse_item_locks[lock[0]].append([door_id, lock[1]])
293 437
294 emit_signal("ap_connected") 438 emit_signal("ap_connected")
295 439
@@ -305,10 +449,28 @@ func send_location(loc_id):
305 client.sendLocation(loc_id) 449 client.sendLocation(loc_id)
306 450
307 451
452func scout_location(loc_id):
453 if _location_scouts.has(loc_id):
454 return _location_scouts.get(loc_id)
455
456 if _batch_locations:
457 _held_location_scouts.append(loc_id)
458 else:
459 client.scoutLocation(loc_id)
460
461 return null
462
463
308func stop_batching_locations(): 464func stop_batching_locations():
309 _batch_locations = false 465 _batch_locations = false
310 client.sendLocations(_held_locations) 466
311 _held_locations.clear() 467 if not _held_locations.is_empty():
468 client.sendLocations(_held_locations)
469 _held_locations.clear()
470
471 if not _held_location_scouts.is_empty():
472 client.scoutLocations(_held_location_scouts)
473 _held_location_scouts.clear()
312 474
313 475
314func colorForItemType(flags): 476func colorForItemType(flags):
@@ -324,3 +486,50 @@ func colorForItemType(flags):
324 return "#d63a22" 486 return "#d63a22"
325 else: # filler 487 else: # filler
326 return "#14de9e" 488 return "#14de9e"
489
490
491func get_letter_behavior(key, level2):
492 if shuffle_letters == kSHUFFLE_LETTERS_UNLOCKED:
493 return kLETTER_BEHAVIOR_UNLOCKED
494
495 if [kSHUFFLE_LETTERS_VANILLA_CYAN, kSHUFFLE_LETTERS_ITEM_CYAN].has(shuffle_letters):
496 if level2:
497 if shuffle_letters == kSHUFFLE_LETTERS_VANILLA_CYAN:
498 return kLETTER_BEHAVIOR_VANILLA
499 else:
500 return kLETTER_BEHAVIOR_ITEM
501 else:
502 return kLETTER_BEHAVIOR_UNLOCKED
503
504 if not level2 and ["h", "i", "n", "t"].has(key):
505 # This differs from the equivalent function in the apworld. Logically it is
506 # the same as UNLOCKED since they are in the starting room, but VANILLA
507 # means the player still has to actually pick up the letters.
508 return kLETTER_BEHAVIOR_VANILLA
509
510 if shuffle_letters == kSHUFFLE_LETTERS_PROGRESSIVE:
511 return kLETTER_BEHAVIOR_ITEM
512
513 return kLETTER_BEHAVIOR_VANILLA
514
515
516func setup_keys():
517 keyboard.load_seed()
518
519 _letters_setup = true
520
521 for k in _held_letters.keys():
522 _process_key_item(k, _held_letters[k])
523
524 _held_letters.clear()
525
526
527func _process_key_item(key, level):
528 if not _letters_setup:
529 _held_letters[key] = max(_held_letters.get(key, 0), level)
530 return
531
532 if shuffle_letters == kSHUFFLE_LETTERS_ITEM_CYAN:
533 level += 1
534
535 keyboard.collect_remote_letter(key, level)
diff --git a/client/Archipelago/messages.gd b/client/Archipelago/messages.gd index 52f38b9..82fdbc4 100644 --- a/client/Archipelago/messages.gd +++ b/client/Archipelago/messages.gd
@@ -48,10 +48,11 @@ func showMessage(text):
48 while !_ordered_labels.is_empty(): 48 while !_ordered_labels.is_empty():
49 await get_tree().create_timer(timeout).timeout 49 await get_tree().create_timer(timeout).timeout
50 50
51 var to_remove = _ordered_labels.pop_front() 51 if !_ordered_labels.is_empty():
52 var to_tween = get_tree().create_tween().bind_node(to_remove) 52 var to_remove = _ordered_labels.pop_front()
53 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5) 53 var to_tween = get_tree().create_tween().bind_node(to_remove)
54 to_tween.tween_callback(to_remove.queue_free) 54 to_tween.tween_property(to_remove, "modulate:a", 0.0, 0.5)
55 to_tween.tween_callback(to_remove.queue_free)
55 56
56 if !_message_queue.is_empty(): 57 if !_message_queue.is_empty():
57 var next_msg = _message_queue.pop_front() 58 var next_msg = _message_queue.pop_front()
@@ -59,3 +60,12 @@ func showMessage(text):
59 60
60 if timeout > 4: 61 if timeout > 4:
61 timeout -= 3 62 timeout -= 3
63
64
65func clear():
66 _message_queue.clear()
67
68 for message_label in _ordered_labels:
69 message_label.queue_free()
70
71 _ordered_labels.clear()
diff --git a/client/Archipelago/painting.gd b/client/Archipelago/painting.gd index 6b3de0b..276d4eb 100644 --- a/client/Archipelago/painting.gd +++ b/client/Archipelago/painting.gd
@@ -1,6 +1,7 @@
1extends "res://scripts/nodes/painting.gd" 1extends "res://scripts/nodes/painting.gd"
2 2
3var item_id 3var item_id
4var item_amount
4 5
5 6
6func _ready(): 7func _ready():
@@ -8,17 +9,16 @@ func _ready():
8 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() 9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
9 ) 10 )
10 11
11 print("node: %s" % node_path)
12
13 var gamedata = global.get_node("Gamedata") 12 var gamedata = global.get_node("Gamedata")
14 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
15 if door_id != null: 14 if door_id != null:
16 print("door_id: %d" % door_id)
17
18 var ap = global.get_node("Archipelago") 15 var ap = global.get_node("Archipelago")
19 item_id = ap.get_item_id_for_door(door_id) 16 var item_lock = ap.get_item_id_for_door(door_id)
17
18 if item_lock != null:
19 item_id = item_lock[0]
20 item_amount = item_lock[1]
20 21
21 if item_id != null:
22 self.senders = [] 22 self.senders = []
23 self.senderGroup = [] 23 self.senderGroup = []
24 self.nested = false 24 self.nested = false
@@ -34,5 +34,5 @@ func _ready():
34func _readier(): 34func _readier():
35 var ap = global.get_node("Archipelago") 35 var ap = global.get_node("Archipelago")
36 36
37 if ap.has_item(item_id): 37 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered() 38 handleTriggered()
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 @@
1extends "res://scripts/nodes/panel.gd"
2
3var panel_logic = null
4var symbol_solvable = true
5
6var black = load("res://assets/materials/black.material")
7
8
9func _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
37func 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
69func checkSolvable(key):
70 checkSymbolSolvable()
71 if not symbol_solvable:
72 return false
73
74 return super.checkSolvable(key)
75
76
77func evaluateSolvability():
78 checkSolvable("")
79
80
81func passedInput(key, skip_focus_check = false):
82 if not symbol_solvable:
83 return
84
85 super.passedInput(key, skip_focus_check)
86
87
88func focus():
89 if not symbol_solvable:
90 has_focus = false
91 return
92
93 super.focus()
94
95
96func unfocus():
97 if not symbol_solvable:
98 has_focus = false
99 return
100
101 super.unfocus()
diff --git a/client/Archipelago/pauseMenu.gd b/client/Archipelago/pauseMenu.gd index 6c013a5..df4bfd1 100644 --- a/client/Archipelago/pauseMenu.gd +++ b/client/Archipelago/pauseMenu.gd
@@ -4,3 +4,10 @@ extends "res://scripts/ui/pauseMenu.gd"
4func _pause_game(): 4func _pause_game():
5 global.get_node("Textclient").dismiss() 5 global.get_node("Textclient").dismiss()
6 super._pause_game() 6 super._pause_game()
7
8
9func _main_menu():
10 global.loaded = false
11 global.get_node("Archipelago").disconnect_from_ap()
12 global.get_node("Messages").clear()
13 super._main_menu()
diff --git a/client/Archipelago/player.gd b/client/Archipelago/player.gd index 7a1f5db..f0b214f 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
19signal evaluate_solvability
20
19 21
20func _ready(): 22func _ready():
21 var khl_script = load("res://scripts/nodes/keyHolderListener.gd") 23 var khl_script = load("res://scripts/nodes/keyHolderListener.gd")
@@ -34,7 +36,10 @@ func _ready():
34 if not door.has_ap_id(): 36 if not door.has_ap_id():
35 continue 37 continue
36 38
37 if door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY: 39 if (
40 door.get_type() == gamedata.SCRIPT_proto.DoorType.ITEM_ONLY
41 or door.get_type() == gamedata.SCRIPT_proto.DoorType.GALLERY_PAINTING
42 ):
38 continue 43 continue
39 44
40 var locationListener = ap.SCRIPT_locationListener.new() 45 var locationListener = ap.SCRIPT_locationListener.new()
@@ -66,6 +71,12 @@ func _ready():
66 71
67 locationListener.senders.append(NodePath("../" + khl.name)) 72 locationListener.senders.append(NodePath("../" + khl.name))
68 73
74 for sender in door.get_senders():
75 locationListener.senders.append(NodePath("/root/scene/" + sender))
76
77 if door.has_complete_at():
78 locationListener.complete_at = door.get_complete_at()
79
69 get_parent().add_child.call_deferred(locationListener) 80 get_parent().add_child.call_deferred(locationListener)
70 81
71 # Set up letter locations. 82 # Set up letter locations.
@@ -81,6 +92,26 @@ func _ready():
81 92
82 get_parent().add_child.call_deferred(locationListener) 93 get_parent().add_child.call_deferred(locationListener)
83 94
95 if (
96 ap.get_letter_behavior(letter.get_key(), letter.has_level2() and letter.get_level2())
97 != ap.kLETTER_BEHAVIOR_VANILLA
98 ):
99 var scout = ap.scout_location(letter.get_ap_id())
100 if (
101 scout != null
102 and not (scout["player"] == ap.client._slot and scout["flags"] & 4 != 0)
103 ):
104 var item_name = "Unknown"
105 var item_player_game = ap.client._game_by_player[float(scout["player"])]
106 if ap.client._item_id_to_name[item_player_game].has(scout["item"]):
107 item_name = ap.client._item_id_to_name[item_player_game][scout["item"]]
108
109 var collectable = get_tree().get_root().get_node("scene").get_node_or_null(
110 letter.get_path()
111 )
112 if collectable != null:
113 collectable.setScoutedText.call_deferred(item_name)
114
84 # Set up mastery locations. 115 # Set up mastery locations.
85 for mastery in gamedata.objects.get_masteries(): 116 for mastery in gamedata.objects.get_masteries():
86 var room = gamedata.objects.get_rooms()[mastery.get_room_id()] 117 var room = gamedata.objects.get_rooms()[mastery.get_room_id()]
@@ -114,8 +145,32 @@ func _ready():
114 145
115 get_parent().add_child.call_deferred(victoryListener) 146 get_parent().add_child.call_deferred(victoryListener)
116 147
148 # Set up keyholder locations, in keyholder sanity.
149 if ap.keyholder_sanity:
150 for keyholder in gamedata.objects.get_keyholders():
151 if not keyholder.has_key():
152 continue
153
154 var room = gamedata.objects.get_rooms()[keyholder.get_room_id()]
155 if room.get_map_id() != map_id:
156 continue
157
158 var locationListener = ap.SCRIPT_locationListener.new()
159 locationListener.location_id = keyholder.get_ap_id()
160 locationListener.name = "locationListener_%d" % keyholder.get_ap_id()
161
162 var khl = khl_script.new()
163 khl.name = "location_%d_keyholder" % keyholder.get_ap_id()
164 khl.answer = keyholder.get_key()
165 khl.senders.append(NodePath("/root/scene/" + keyholder.get_path()))
166 get_parent().add_child.call_deferred(khl)
167
168 locationListener.senders.append(NodePath("../" + khl.name))
169
170 get_parent().add_child.call_deferred(locationListener)
171
117 # Block off roof access in Daedalus. 172 # Block off roof access in Daedalus.
118 if global.map == "daedalus": 173 if global.map == "daedalus" and not ap.daedalus_roof_access:
119 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49) 174 _set_up_invis_wall(75.5, 11, -24.5, 1, 10, 49)
120 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1) 175 _set_up_invis_wall(51.5, 11, -17, 16, 10, 1)
121 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10) 176 _set_up_invis_wall(46, 10, -9.5, 1, 10, 10)
@@ -147,6 +202,29 @@ func _ready():
147 warp_enter.rotation_degrees.y = 90 202 warp_enter.rotation_degrees.y = 90
148 get_parent().add_child.call_deferred(warp_enter) 203 get_parent().add_child.call_deferred(warp_enter)
149 204
205 if global.map == "the_entry":
206 # Remove door behind X1.
207 var door_node = get_tree().get_root().get_node("/root/scene/Components/Doors/exit_1")
208 door_node.handleTriggered()
209
210 # Display win condition.
211 var sign_prefab = preload("res://objects/nodes/sign.tscn")
212 var sign1 = sign_prefab.instantiate()
213 sign1.position = Vector3(-7, 5, -15.01)
214 sign1.text = "victory"
215 get_parent().add_child.call_deferred(sign1)
216
217 var sign2 = sign_prefab.instantiate()
218 sign2.position = Vector3(-7, 4, -15.01)
219 sign2.text = "%s ending" % kEndingNameByVictoryValue.get(ap.victory_condition, "?")
220
221 var sign2_color = kEndingNameByVictoryValue.get(ap.victory_condition, "coral").to_lower()
222 if sign2_color == "white":
223 sign2_color = "silver"
224
225 sign2.material = load("res://assets/materials/%s.material" % sign2_color)
226 get_parent().add_child.call_deferred(sign2)
227
150 super._ready() 228 super._ready()
151 229
152 await get_tree().process_frame 230 await get_tree().process_frame
diff --git a/client/Archipelago/saver.gd b/client/Archipelago/saver.gd index 7e788a8..0fba9e7 100644 --- a/client/Archipelago/saver.gd +++ b/client/Archipelago/saver.gd
@@ -2,4 +2,8 @@ extends "res://scripts/nodes/saver.gd"
2 2
3 3
4func levelLoaded(): 4func levelLoaded():
5 reload.call_deferred() 5 if type == "keyholders":
6 var ap = global.get_node("Archipelago")
7 ap.keyboard.load_keyholders.call_deferred(global.map)
8 else:
9 reload.call_deferred()
diff --git a/client/Archipelago/settings_screen.gd b/client/Archipelago/settings_screen.gd index 624b1eb..140b4f4 100644 --- a/client/Archipelago/settings_screen.gd +++ b/client/Archipelago/settings_screen.gd
@@ -22,14 +22,8 @@ func _ready():
22 var ap_instance = ap_script.new() 22 var ap_instance = ap_script.new()
23 ap_instance.name = "Archipelago" 23 ap_instance.name = "Archipelago"
24 24
25 #apclient_instance.SCRIPT_doorControl = load("user://maps/Archipelago/doorControl.gd")
26 #apclient_instance.SCRIPT_effects = load("user://maps/Archipelago/effects.gd")
27 #apclient_instance.SCRIPT_location = load("user://maps/Archipelago/location.gd")
28 #apclient_instance.SCRIPT_mypainting = load("user://maps/Archipelago/mypainting.gd")
29 #apclient_instance.SCRIPT_panel = load("user://maps/Archipelago/panel.gd")
30 #apclient_instance.SCRIPT_textclient = load("user://maps/Archipelago/textclient.gd")
31
32 ap_instance.SCRIPT_client = load("user://maps/Archipelago/client.gd") 25 ap_instance.SCRIPT_client = load("user://maps/Archipelago/client.gd")
26 ap_instance.SCRIPT_keyboard = load("user://maps/Archipelago/keyboard.gd")
33 ap_instance.SCRIPT_locationListener = load("user://maps/Archipelago/locationListener.gd") 27 ap_instance.SCRIPT_locationListener = load("user://maps/Archipelago/locationListener.gd")
34 ap_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd") 28 ap_instance.SCRIPT_uuid = load("user://maps/Archipelago/vendor/uuid.gd")
35 ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd") 29 ap_instance.SCRIPT_victoryListener = load("user://maps/Archipelago/victoryListener.gd")
@@ -38,12 +32,22 @@ func _ready():
38 32
39 # Let's also inject any scripts we need to inject now. 33 # Let's also inject any scripts we need to inject now.
40 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd")) 34 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/animationListener.gd"))
35 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/collectable.gd"))
41 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/door.gd")) 36 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/door.gd"))
37 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolder.gd"))
38 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/keyHolderChecker.gd"))
39 installScriptExtension(
40 ResourceLoader.load("user://maps/Archipelago/keyHolderResetterListener.gd")
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"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleport.gd"))
46 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd")) 48 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/teleportListener.gd"))
49 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/visibilityListener.gd"))
50 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldport.gd"))
47 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd")) 51 installScriptExtension(ResourceLoader.load("user://maps/Archipelago/worldportListener.gd"))
48 52
49 var proto_script = load("user://maps/Archipelago/generated/proto.gd") 53 var proto_script = load("user://maps/Archipelago/generated/proto.gd")
@@ -66,6 +70,7 @@ func _ready():
66 global.add_child(textclient_instance) 70 global.add_child(textclient_instance)
67 71
68 var ap = global.get_node("Archipelago") 72 var ap = global.get_node("Archipelago")
73 var gamedata = global.get_node("Gamedata")
69 ap.connect("ap_connected", connectionSuccessful) 74 ap.connect("ap_connected", connectionSuccessful)
70 ap.connect("could_not_connect", connectionUnsuccessful) 75 ap.connect("could_not_connect", connectionUnsuccessful)
71 ap.connect("connect_status", connectionStatus) 76 ap.connect("connect_status", connectionStatus)
@@ -89,13 +94,17 @@ func _ready():
89 history_box.get_popup().connect("id_pressed", historySelected) 94 history_box.get_popup().connect("id_pressed", historySelected)
90 95
91 # Show client version. 96 # Show client version.
92 $Panel/title.text = "ARCHIPELAGO (%s)" % ap.my_version 97 $Panel/title.text = "ARCHIPELAGO (%d.%d)" % [gamedata.objects.get_version(), ap.MOD_VERSION]
93 98
94 # Increase font size in text boxes. 99 # Increase font size in text boxes.
95 $Panel/server_box.add_theme_font_size_override("font_size", 36) 100 $Panel/server_box.add_theme_font_size_override("font_size", 36)
96 $Panel/player_box.add_theme_font_size_override("font_size", 36) 101 $Panel/player_box.add_theme_font_size_override("font_size", 36)
97 $Panel/password_box.add_theme_font_size_override("font_size", 36) 102 $Panel/password_box.add_theme_font_size_override("font_size", 36)
98 103
104 # Set up version mismatch dialog.
105 $Panel/VersionMismatch.connect("confirmed", startGame)
106 $Panel/VersionMismatch.get_cancel_button().pressed.connect(versionMismatchDeclined)
107
99 108
100# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd 109# Adapted from https://gitlab.com/Delta-V-Modding/Mods/-/blob/main/game/ModLoader.gd
101func installScriptExtension(childScript: Resource): 110func installScriptExtension(childScript: Resource):
@@ -125,6 +134,33 @@ func connectionStatus(message):
125 134
126func connectionSuccessful(): 135func connectionSuccessful():
127 var ap = global.get_node("Archipelago") 136 var ap = global.get_node("Archipelago")
137 var gamedata = global.get_node("Gamedata")
138
139 # Check for major version mismatch.
140 if ap.apworld_version[0] != gamedata.objects.get_version():
141 $Panel/AcceptDialog.exclusive = false
142
143 var popup = self.get_node("Panel/VersionMismatch")
144 popup.title = "Version Mismatch!"
145 popup.dialog_text = (
146 "This slot was generated using v%d.%d of the Lingo 2 apworld,\nwhich has a different major version than this client (v%d.%d).\nIt is highly recommended to play using the correct version of the client.\nYou may experience bugs or logic issues if you continue."
147 % [
148 ap.apworld_version[0],
149 ap.apworld_version[1],
150 gamedata.objects.get_version(),
151 ap.MOD_VERSION
152 ]
153 )
154 popup.exclusive = true
155 popup.popup_centered()
156
157 return
158
159 startGame()
160
161
162func startGame():
163 var ap = global.get_node("Archipelago")
128 164
129 # Save connection details 165 # Save connection details
130 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass] 166 var connection_details = [ap.ap_server, ap.ap_user, ap.ap_pass]
@@ -141,21 +177,30 @@ func connectionSuccessful():
141 global.universe = "lingo" 177 global.universe = "lingo"
142 global.map = "the_entry" 178 global.map = "the_entry"
143 179
144 unlocks.resetKeys()
145 unlocks.resetCollectables() 180 unlocks.resetCollectables()
146 unlocks.resetData() 181 unlocks.resetData()
147 unlocks.loadKeys() 182
183 ap.setup_keys()
184
148 unlocks.loadCollectables() 185 unlocks.loadCollectables()
149 unlocks.loadData() 186 unlocks.loadData()
150 unlocks.unlockKey("capslock", 1) 187 unlocks.unlockKey("capslock", 1)
151 188
152 clearResourceCache("res://objects/meshes/gridDoor.tscn") 189 clearResourceCache("res://objects/meshes/gridDoor.tscn")
190 clearResourceCache("res://objects/nodes/collectable.tscn")
153 clearResourceCache("res://objects/nodes/door.tscn") 191 clearResourceCache("res://objects/nodes/door.tscn")
192 clearResourceCache("res://objects/nodes/keyHolder.tscn")
154 clearResourceCache("res://objects/nodes/listeners/animationListener.tscn") 193 clearResourceCache("res://objects/nodes/listeners/animationListener.tscn")
194 clearResourceCache("res://objects/nodes/listeners/keyHolderChecker.tscn")
195 clearResourceCache("res://objects/nodes/listeners/keyHolderResetterListener.tscn")
155 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn") 196 clearResourceCache("res://objects/nodes/listeners/teleportListener.tscn")
197 clearResourceCache("res://objects/nodes/listeners/visibilityListener.tscn")
156 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn") 198 clearResourceCache("res://objects/nodes/listeners/worldportListener.tscn")
199 clearResourceCache("res://objects/nodes/panel.tscn")
157 clearResourceCache("res://objects/nodes/player.tscn") 200 clearResourceCache("res://objects/nodes/player.tscn")
158 clearResourceCache("res://objects/nodes/saver.tscn") 201 clearResourceCache("res://objects/nodes/saver.tscn")
202 clearResourceCache("res://objects/nodes/teleport.tscn")
203 clearResourceCache("res://objects/nodes/worldport.tscn")
159 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn") 204 clearResourceCache("res://objects/scenes/menus/pause_menu.tscn")
160 205
161 var paintings_dir = DirAccess.open("res://objects/meshes/paintings") 206 var paintings_dir = DirAccess.open("res://objects/meshes/paintings")
@@ -167,7 +212,7 @@ func connectionSuccessful():
167 clearResourceCache("res://objects/meshes/paintings/" + file_name) 212 clearResourceCache("res://objects/meshes/paintings/" + file_name)
168 file_name = paintings_dir.get_next() 213 file_name = paintings_dir.get_next()
169 214
170 switcher.switch_map("res://objects/scenes/the_entry.tscn") 215 switcher.switch_map.call_deferred("res://objects/scenes/the_entry.tscn")
171 216
172 217
173func connectionUnsuccessful(error_message): 218func connectionUnsuccessful(error_message):
@@ -180,6 +225,13 @@ func connectionUnsuccessful(error_message):
180 popup.get_ok_button().visible = true 225 popup.get_ok_button().visible = true
181 popup.popup_centered() 226 popup.popup_centered()
182 227
228 $Panel/connect_button.disabled = false
229
230
231func versionMismatchDeclined():
232 $Panel/AcceptDialog.hide()
233 $Panel/connect_button.disabled = false
234
183 235
184func historySelected(index): 236func historySelected(index):
185 var ap = global.get_node("Archipelago") 237 var ap = global.get_node("Archipelago")
@@ -191,5 +243,4 @@ func historySelected(index):
191 243
192 244
193func clearResourceCache(path): 245func clearResourceCache(path):
194 ResourceLoader.load_threaded_request(path, "", false, ResourceLoader.CACHE_MODE_REPLACE) 246 ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_REPLACE)
195 ResourceLoader.load_threaded_get(path)
diff --git a/client/Archipelago/teleport.gd b/client/Archipelago/teleport.gd new file mode 100644 index 0000000..428d50b --- /dev/null +++ b/client/Archipelago/teleport.gd
@@ -0,0 +1,38 @@
1extends "res://scripts/nodes/teleport.gd"
2
3var item_id
4var item_amount
5
6
7func _ready():
8 var node_path = String(
9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
10 )
11
12 var gamedata = global.get_node("Gamedata")
13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
14 if door_id != null:
15 var ap = global.get_node("Archipelago")
16 var item_lock = ap.get_item_id_for_door(door_id)
17
18 if item_lock != null:
19 item_id = item_lock[0]
20 item_amount = item_lock[1]
21
22 self.senders = []
23 self.senderGroup = []
24 self.nested = false
25 self.complete_at = 0
26 self.max_length = 0
27 self.excludeSenders = []
28
29 call_deferred("_readier")
30
31 super._ready()
32
33
34func _readier():
35 var ap = global.get_node("Archipelago")
36
37 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered()
diff --git a/client/Archipelago/teleportListener.gd b/client/Archipelago/teleportListener.gd index 4bb08c9..6f363af 100644 --- a/client/Archipelago/teleportListener.gd +++ b/client/Archipelago/teleportListener.gd
@@ -1,6 +1,7 @@
1extends "res://scripts/nodes/listeners/teleportListener.gd" 1extends "res://scripts/nodes/listeners/teleportListener.gd"
2 2
3var item_id 3var item_id
4var item_amount
4 5
5 6
6func _ready(): 7func _ready():
@@ -8,17 +9,27 @@ func _ready():
8 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() 9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
9 ) 10 )
10 11
11 print("node: %s" % node_path) 12 if (
13 global.map == "daedalus"
14 and (
15 node_path == "Components/Triggers/teleportListenerConnections"
16 or node_path == "Components/Triggers/teleportListenerConnections2"
17 )
18 ):
19 # Effectively disable these.
20 teleport_point = target_path.position
21 return
12 22
13 var gamedata = global.get_node("Gamedata") 23 var gamedata = global.get_node("Gamedata")
14 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path) 24 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
15 if door_id != null: 25 if door_id != null:
16 print("door_id: %d" % door_id)
17
18 var ap = global.get_node("Archipelago") 26 var ap = global.get_node("Archipelago")
19 item_id = ap.get_item_id_for_door(door_id) 27 var item_lock = ap.get_item_id_for_door(door_id)
28
29 if item_lock != null:
30 item_id = item_lock[0]
31 item_amount = item_lock[1]
20 32
21 if item_id != null:
22 self.senders = [] 33 self.senders = []
23 self.senderGroup = [] 34 self.senderGroup = []
24 self.nested = false 35 self.nested = false
@@ -34,5 +45,5 @@ func _ready():
34func _readier(): 45func _readier():
35 var ap = global.get_node("Archipelago") 46 var ap = global.get_node("Archipelago")
36 47
37 if ap.has_item(item_id): 48 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered() 49 handleTriggered()
diff --git a/client/Archipelago/textclient.gd b/client/Archipelago/textclient.gd index 4b03151..85cc6d2 100644 --- a/client/Archipelago/textclient.gd +++ b/client/Archipelago/textclient.gd
@@ -50,7 +50,7 @@ func _ready():
50 50
51 51
52func _input(event): 52func _input(event):
53 if event is InputEventKey and event.pressed: 53 if global.loaded and event is InputEventKey and event.pressed:
54 if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT): 54 if event.keycode == KEY_TAB and !Input.is_key_pressed(KEY_SHIFT):
55 if !get_tree().paused: 55 if !get_tree().paused:
56 is_open = true 56 is_open = true
diff --git a/client/Archipelago/victoryListener.gd b/client/Archipelago/victoryListener.gd index 4b85d3a..e9089d7 100644 --- a/client/Archipelago/victoryListener.gd +++ b/client/Archipelago/victoryListener.gd
@@ -11,6 +11,8 @@ func handleTriggered():
11 var ap = global.get_node("Archipelago") 11 var ap = global.get_node("Archipelago")
12 ap.client.completedGoal() 12 ap.client.completedGoal()
13 13
14 global.get_node("Messages").showMessage("You have completed your goal!")
15
14 16
15func handleUntriggered(): 17func handleUntriggered():
16 triggered -= 1 18 triggered -= 1
diff --git a/client/Archipelago/visibilityListener.gd b/client/Archipelago/visibilityListener.gd new file mode 100644 index 0000000..5ea17a0 --- /dev/null +++ b/client/Archipelago/visibilityListener.gd
@@ -0,0 +1,38 @@
1extends "res://scripts/nodes/listeners/visibilityListener.gd"
2
3var item_id
4var item_amount
5
6
7func _ready():
8 var node_path = String(
9 get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names()
10 )
11
12 var gamedata = global.get_node("Gamedata")
13 var door_id = gamedata.get_door_for_map_node_path(global.map, node_path)
14 if door_id != null:
15 var ap = global.get_node("Archipelago")
16 var item_lock = ap.get_item_id_for_door(door_id)
17
18 if item_lock != null:
19 item_id = item_lock[0]
20 item_amount = item_lock[1]
21
22 self.senders = []
23 self.senderGroup = []
24 self.nested = false
25 self.complete_at = 0
26 self.max_length = 0
27 self.excludeSenders = []
28
29 call_deferred("_readier")
30
31 super._ready()
32
33
34func _readier():
35 var ap = global.get_node("Archipelago")
36
37 if ap.client.getItemAmount(item_id) >= item_amount:
38 handleTriggered()
diff --git a/client/Archipelago/worldport.gd b/client/Archipelago/worldport.gd new file mode 100644 index 0000000..d0fb6c9 --- /dev/null +++ b/client/Archipelago/worldport.gd
@@ -0,0 +1,10 @@
1extends "res://scripts/nodes/worldport.gd"
2
3
4func _ready():
5 if global.map == "icarus" and exit == "daedalus":
6 var ap = global.get_node("Archipelago")
7 if not ap.daedalus_roof_access:
8 entry_point = Vector3(58, 10, 0)
9
10 super._ready()
diff --git a/client/Archipelago/worldportListener.gd b/client/Archipelago/worldportListener.gd index c31c825..5c2faff 100644 --- a/client/Archipelago/worldportListener.gd +++ b/client/Archipelago/worldportListener.gd
@@ -1,8 +1,8 @@
1extends "res://scripts/nodes/listeners/worldportListener.gd" 1extends "res://scripts/nodes/listeners/worldportListener.gd"
2 2
3 3
4func changeScene(): 4func handleTriggered():
5 if exit == "menus/credits": 5 if exit == "menus/credits":
6 return 6 return
7 7
8 super.changeScene() 8 super.handleTriggered()
diff --git a/client/CHANGELOG.md b/client/CHANGELOG.md new file mode 100644 index 0000000..89d9873 --- /dev/null +++ b/client/CHANGELOG.md
@@ -0,0 +1,21 @@
1# lingo2-archipelago Client Releases
2
3## v3.3 - 2025-09-12
4
5- Fixed issue downloading large datapackages (such as TUNIC's).
6- Connection failures now show error messages.
7
8Download:
9[lingo2-archipelago-client-v3.3.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.3.zip)<br/>
10Source:
11[v3.3](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v3.3)
12
13## v3.2 - 2025-09-12
14
15- Initial release for testing. Features include door shuffle, letter shuffle,
16 and symbol shuffle.
17
18Download:
19[lingo2-archipelago-client-v3.2.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.2.zip)<br/>
20Source:
21[v3.2](https://code.fourisland.com/lingo2-archipelago/tag/?h=client-v3.2)
diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..99589c5 --- /dev/null +++ b/client/README.md
@@ -0,0 +1,90 @@
1# Lingo 2 Archipelago Client
2
3The Lingo 2 Archipelago Client is a mod for Lingo 2 that allows you to connect
4to an Archipelago Multiworld and randomize your game.
5
6## Installation
7
81. Download the Lingo 2 Archipelago Randomizer from
9 [the releases page](https://code.fourisland.com/lingo2-archipelago/about/client/CHANGELOG.md).
102. Open up Lingo 2, go to settings, and click View Game Data. This should open
11 up a folder in Windows Explorer.
123. Unzip the randomizer into the "maps" folder. Ensure that archipelago.tscn and
13 the Archipelago folder are both within the maps folder.
14
15**NOTE**: It is important that the major version number of your client matches
16the major version number of the apworld you generated with.
17
18## Joining a Multiworld game
19
201. Launch Lingo 2.
212. Click on Level Selection, and choose Archipelago from the list.
223. The selected player is generally ignored by the mod, and you don't even need
23 to ensure you use the same player between connections. However, if your
24 player name has a gift map associated with it, Lingo 2 will prioritize the
25 gift map over loading the mod, so in that case you should choose another
26 player.
274. Press Play.
285. Enter the Archipelago address, slot name, and password into the fields.
296. Press Connect.
307. Enjoy!
31
32To continue an earlier game, you can perform the exact same steps as above. You
33will probably have to re-select Archipelago from the Level Selection screen, as
34the game does not remember which level you were playing.
35
36**Note**: Running the randomizer modifies the game's memory. If you want to play
37the base game after playing the randomizer, you need to restart Lingo 2 first.
38
39## Running from source
40
41The mod is mostly written in GDScript, which is parsed and executed by Lingo 2
42itself, and thus does not need to be compiled. However, there are two files that
43need to be generated before the client can be run.
44
45The first file is `data.binpb`, the datafile containing the randomizer logic.
46You can read about how to generate it on
47[its own README page](https://code.fourisland.com/lingo2-archipelago/about/data/README.md).
48Once you have it, put it in a subfolder of `client` called `generated`.
49
50The second generated file is `proto.gd`. This file allows Lingo 2 to read the
51datafile. We use a Godot script to generate it, which means
52[the Godot Editor](https://godotengine.org/download/) is required. From the root
53of the repository:
54
55```shell
56cd vendor\godobuf
57godot --headless -s addons\protobuf\protobuf_cmdln.gd --input=..\..\proto\data.proto ^
58 --output=..\..\client\Archipelago\generated\proto.gd
59```
60
61If you are not on Windows, replace the forward slashes with backslashes as
62appropriate (and the caret with a forward slash). You will also probably need to
63replace "godot" at the start of the second line with a path to a Godot Editor
64executable.
65
66After generating those two files, the contents of the `client` folder (minus
67this README) can be pasted into the Lingo 2 maps directory as described above.
68
69## Frequently Asked Questions
70
71### Is my progress saved locally?
72
73Lingo 2 autosaves your progress every time you solve a puzzle, get a
74collectable, or interact with a keyholder. The randomizer generates a savefile
75name based on your Multiworld seed and slot number, so you should be able to
76seamlessly switch between multiworlds and even slots within a multiworld.
77
78The exception to this is different rooms created from the same multiworld seed.
79The client is unable to tell rooms in a seed apart (this is a limitation of the
80Archipelago API), so the client will use the same save file for the same slot in
81different rooms on the same seed. You can work around this by manually moving or
82removing the save file from the level1 save file directory.
83
84If you play the base game again, you will see one or more save files with a long
85name that begins with "zzAP\_". These are the saves for your multiworlds. They
86can be safely deleted after you have completed the associated multiworld. It is
87not recommended to load these save files outside of the randomizer.
88
89A connection to Archipelago is required to resume playing a multiworld. This is
90because the set of items you have received is not stored locally.
diff --git a/client/archipelago.tscn b/client/archipelago.tscn index 40dd46f..da83b23 100644 --- a/client/archipelago.tscn +++ b/client/archipelago.tscn
@@ -40,6 +40,7 @@ offset_right = 1920.0
40offset_bottom = 225.0 40offset_bottom = 225.0
41text = "ARCHIPELAGO" 41text = "ARCHIPELAGO"
42valign = 1 42valign = 1
43horizontal_alignment = 1
43theme = ExtResource("2_g4bvn") 44theme = ExtResource("2_g4bvn")
44 45
45[node name="credit" parent="Panel" type="Label"] 46[node name="credit" parent="Panel" type="Label"]
@@ -150,6 +151,10 @@ caret_blink = true
150offset_right = 83.0 151offset_right = 83.0
151offset_bottom = 58.0 152offset_bottom = 58.0
152 153
154[node name="VersionMismatch" type="ConfirmationDialog" parent="Panel"]
155offset_right = 83.0
156offset_bottom = 58.0
157
153[node name="connection_history" type="MenuButton" parent="Panel"] 158[node name="connection_history" type="MenuButton" parent="Panel"]
154offset_left = 1239.0 159offset_left = 1239.0
155offset_top = 276.0 160offset_top = 276.0
diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000..bf0a51b --- /dev/null +++ b/data/README.md
@@ -0,0 +1,13 @@
1# Lingo 2 Randomizer Data
2
3This folder contains the logic for the Lingo 2 randomizer in a human-readable
4format. This data is compiled into a single file and used in the various parts
5of the randomizer project (client, apworld, etc).
6
7The data is structured using [Protocol Buffers](https://protobuf.dev/). The
8schema for the human-readable format is
9[located in the repository](https://code.fourisland.com/lingo2-archipelago/tree/proto/human.proto).
10
11## Compiling
12
13Hi.
diff --git a/data/connections.txtpb b/data/connections.txtpb index f1b81d5..35e7ef8 100644 --- a/data/connections.txtpb +++ b/data/connections.txtpb
@@ -20,7 +20,7 @@ connections {
20 from { 20 from {
21 port { 21 port {
22 map: "the_entry" 22 map: "the_entry"
23 room: "Flipped Second Room" 23 room: "Four Rooms Entrance"
24 name: "FOUR" 24 name: "FOUR"
25 } 25 }
26 } 26 }
@@ -141,7 +141,7 @@ connections {
141 to { 141 to {
142 port { 142 port {
143 map: "the_darkroom" 143 map: "the_darkroom"
144 room: "First Room" 144 room: "Cyan Hallway"
145 name: "COLORFUL" 145 name: "COLORFUL"
146 } 146 }
147 } 147 }
@@ -157,7 +157,7 @@ connections {
157 to { 157 to {
158 port { 158 port {
159 map: "the_darkroom" 159 map: "the_darkroom"
160 room: "Second Room" 160 room: "Congruent Entrance"
161 name: "CONGRUENT" 161 name: "CONGRUENT"
162 } 162 }
163 } 163 }
@@ -233,7 +233,7 @@ connections {
233 from { 233 from {
234 port { 234 port {
235 map: "the_darkroom" 235 map: "the_darkroom"
236 room: "First Room" 236 room: "Double Sided Entrance"
237 name: "DOUBLESIDED" 237 name: "DOUBLESIDED"
238 } 238 }
239 } 239 }
@@ -308,6 +308,23 @@ connections {
308 name: "GALLERY" 308 name: "GALLERY"
309 } 309 }
310 } 310 }
311 oneway: true
312}
313connections {
314 from {
315 port {
316 map: "the_butterfly"
317 room: "Main Area"
318 name: "GALLERY"
319 }
320 }
321 to {
322 room {
323 map: "the_gallery"
324 name: "Main Area"
325 }
326 }
327 oneway: true
311} 328}
312connections { 329connections {
313 from { 330 from {
@@ -618,7 +635,7 @@ connections {
618 from { 635 from {
619 port { 636 port {
620 map: "the_entry" 637 map: "the_entry"
621 room: "Link Area" 638 room: "Liberated Entrance"
622 name: "BLUE" 639 name: "BLUE"
623 } 640 }
624 } 641 }
@@ -666,7 +683,7 @@ connections {
666 from { 683 from {
667 port { 684 port {
668 map: "the_entry" 685 map: "the_entry"
669 room: "Link Area" 686 room: "Literate Entrance"
670 name: "BROWN" 687 name: "BROWN"
671 } 688 }
672 } 689 }
@@ -841,6 +858,8 @@ connections {
841 } 858 }
842 oneway: true 859 oneway: true
843} 860}
861# Two one-way connections because the CLUE panel only needs to be solved to
862# go from The Great to The Partial.
844connections { 863connections {
845 from { 864 from {
846 port { 865 port {
@@ -856,6 +875,25 @@ connections {
856 name: "GREAT" 875 name: "GREAT"
857 } 876 }
858 } 877 }
878 oneway: true
879}
880connections {
881 from {
882 port {
883 map: "the_partial"
884 room: "Obverse Side"
885 name: "GREAT"
886 }
887 }
888 to {
889 port {
890 map: "the_great"
891 room: "West Side"
892 name: "PARTIAL"
893 }
894 }
895 oneway: true
896 bypass_target_door: true
859} 897}
860connections { 898connections {
861 from { 899 from {
@@ -1193,6 +1231,7 @@ connections {
1193 } 1231 }
1194 } 1232 }
1195 oneway: true 1233 oneway: true
1234 roof_access: true
1196} 1235}
1197connections { 1236connections {
1198 from { 1237 from {
@@ -1434,7 +1473,6 @@ connections {
1434 name: "GREAT" 1473 name: "GREAT"
1435 } 1474 }
1436 } 1475 }
1437 door { map: "the_great" name: "Daedalus Entrance" }
1438 oneway: true 1476 oneway: true
1439} 1477}
1440connections { 1478connections {
@@ -1453,6 +1491,7 @@ connections {
1453 } 1491 }
1454 } 1492 }
1455 oneway: true 1493 oneway: true
1494 bypass_target_door: true
1456} 1495}
1457connections { 1496connections {
1458 from { 1497 from {
@@ -1748,12 +1787,13 @@ connections {
1748 } 1787 }
1749 } 1788 }
1750 oneway: true 1789 oneway: true
1790 bypass_target_door: true
1751} 1791}
1752connections { 1792connections {
1753 from { 1793 from {
1754 port { 1794 port {
1755 map: "the_bearer" 1795 map: "the_bearer"
1756 room: "Back Area" 1796 room: "Tree Entrance"
1757 name: "TREE" 1797 name: "TREE"
1758 } 1798 }
1759 } 1799 }
@@ -1830,7 +1870,6 @@ connections {
1830 } 1870 }
1831} 1871}
1832connections { 1872connections {
1833 # Two one-way connections because the door only blocks one direction.
1834 from { 1873 from {
1835 port { 1874 port {
1836 map: "the_great" 1875 map: "the_great"
@@ -1847,6 +1886,7 @@ connections {
1847 } 1886 }
1848} 1887}
1849connections { 1888connections {
1889 # Two one-way connections because the door only blocks one direction.
1850 from { 1890 from {
1851 port { 1891 port {
1852 map: "the_unkempt" 1892 map: "the_unkempt"
@@ -1879,6 +1919,7 @@ connections {
1879 } 1919 }
1880 } 1920 }
1881 oneway: true 1921 oneway: true
1922 bypass_target_door: true
1882} 1923}
1883connections { 1924connections {
1884 from { 1925 from {
diff --git a/data/door_groups.txtpb b/data/door_groups.txtpb new file mode 100644 index 0000000..1a75c45 --- /dev/null +++ b/data/door_groups.txtpb
@@ -0,0 +1,160 @@
1door_groups {
2 name: "The Entry - Repetitive Entrance"
3 type: CONNECTOR
4 doors {
5 map: "the_entry"
6 name: "Starting Room West Wall North Door"
7 }
8 doors {
9 map: "the_repetitive"
10 name: "Entry Entrance"
11 }
12}
13door_groups {
14 name: "The Repetitive - Plaza Entrance"
15 type: CONNECTOR
16 doors {
17 map: "the_repetitive"
18 name: "Black Hallway"
19 }
20 doors {
21 map: "the_plaza"
22 name: "Repetitive Entrance"
23 }
24}
25door_groups {
26 name: "Control Center White Doors"
27 type: COLOR_CONNECTOR
28 doors {
29 map: "daedalus"
30 name: "White Hallway From Entry"
31 }
32 doors {
33 map: "the_entry"
34 name: "Control Center White Door"
35 }
36}
37door_groups {
38 name: "Control Center Purple Doors"
39 type: COLOR_CONNECTOR
40 doors {
41 map: "daedalus"
42 name: "Purple Hallway From Great"
43 }
44 doors {
45 map: "the_great"
46 name: "Control Center Purple Door"
47 }
48}
49door_groups {
50 name: "Control Center Orange Doors"
51 type: COLOR_CONNECTOR
52 doors {
53 map: "daedalus"
54 name: "Control Center Orange Door"
55 }
56 doors {
57 map: "the_unkempt"
58 name: "Control Center Orange Door"
59 }
60}
61door_groups {
62 name: "Control Center Brown Doors"
63 type: COLOR_CONNECTOR
64 doors {
65 map: "the_bearer"
66 name: "Control Center Brown Door"
67 }
68 doors {
69 map: "the_tree"
70 name: "Control Center Brown Door"
71 }
72}
73door_groups {
74 name: "Control Center Blue Doors"
75 type: COLOR_CONNECTOR
76 doors {
77 map: "the_digital"
78 name: "Control Center Blue Door"
79 }
80 doors {
81 map: "the_unyielding"
82 name: "Digital Entrance"
83 }
84}
85door_groups {
86 name: "Cyan Doors"
87 type: CYAN_DOORS
88 doors {
89 map: "daedalus"
90 name: "Eye Painting"
91 }
92 doors {
93 map: "the_bearer"
94 name: "Butterfly Entrance"
95 }
96 doors {
97 map: "the_darkroom"
98 name: "Double Letter Panel Blockers"
99 }
100 doors {
101 map: "the_entry"
102 name: "Starting Room West Wall North Door"
103 }
104 doors {
105 map: "the_entry"
106 name: "Flipped Pyramid Area Entrance"
107 }
108 doors {
109 map: "the_entry"
110 name: "Near D Room Painting"
111 }
112 doors {
113 map: "the_graveyard"
114 name: "Double Letters"
115 }
116 doors {
117 map: "the_great"
118 name: "Tower Entrance"
119 }
120 doors {
121 map: "the_great"
122 name: "Cyan Doors"
123 }
124 doors {
125 map: "the_parthenon"
126 name: "Double Letters"
127 }
128 doors {
129 map: "the_unkempt"
130 name: "Cyan Doors"
131 }
132 doors {
133 map: "the_unkempt"
134 name: "Control Center Orange Door"
135 }
136 doors {
137 map: "the_unyielding"
138 name: "Cyan Doors"
139 }
140}
141door_groups {
142 name: "Lavender Cubes"
143 type: SHUFFLE_GROUP
144 doors {
145 map: "daedalus"
146 name: "C Keyholder Blocker"
147 }
148 doors {
149 map: "the_congruent"
150 name: "T Keyholder Blocker"
151 }
152 doors {
153 map: "the_great"
154 name: "Lavender Cube"
155 }
156 doors {
157 map: "the_parthenon"
158 name: "Lavender Cubes"
159 }
160}
diff --git a/data/ids.yaml b/data/ids.yaml index 09430f2..2358b67 100644 --- a/data/ids.yaml +++ b/data/ids.yaml
@@ -20,6 +20,11 @@ maps:
20 panels: 20 panels:
21 COLOR: 2726 21 COLOR: 2726
22 Letters: 2727 22 Letters: 2727
23 keyholders:
24 1: 2760
25 2: 2761
26 3: 2762
27 4: 2763
23 Partial Entrance: 28 Partial Entrance:
24 panels: 29 panels:
25 PARTIAL: 2729 30 PARTIAL: 2729
@@ -155,6 +160,9 @@ maps:
155 Brown Smiley: 160 Brown Smiley:
156 panels: 161 panels:
157 OTHERS: 1667 162 OTHERS: 1667
163 C Keyholder:
164 keyholders:
165 C: 2755
158 Castle: 166 Castle:
159 panels: 167 panels:
160 FIVE (Blue): 1673 168 FIVE (Blue): 1673
@@ -265,6 +273,9 @@ maps:
265 SUMMER: 1754 273 SUMMER: 1754
266 WORD: 1753 274 WORD: 1753
267 WORDWORD: 1761 275 WORDWORD: 1761
276 D Keyholder:
277 keyholders:
278 D: 2759
268 Dark Light Exit: 279 Dark Light Exit:
269 panels: 280 panels:
270 GASKET: 1763 281 GASKET: 1763
@@ -287,6 +298,9 @@ maps:
287 Eye Painting: 298 Eye Painting:
288 panels: 299 panels:
289 REVILED: 1777 300 REVILED: 1777
301 F Keyholder:
302 keyholders:
303 F: 2756
290 F2 Room: 304 F2 Room:
291 panels: 305 panels:
292 CAST: 1782 306 CAST: 1782
@@ -480,6 +494,8 @@ maps:
480 panels: 494 panels:
481 GOING: 1934 495 GOING: 1934
482 TURN: 1935 496 TURN: 1935
497 keyholders:
498 G: 2757
483 Nursery: 499 Nursery:
484 panels: 500 panels:
485 "?": 1937 501 "?": 1937
@@ -547,6 +563,8 @@ maps:
547 WALLS: 1986 563 WALLS: 1986
548 WHISPER: 1978 564 WHISPER: 1978
549 WING: 1979 565 WING: 1979
566 keyholders:
567 H: 2758
550 Outside Magic Room: 568 Outside Magic Room:
551 panels: 569 panels:
552 WIZARD: 1988 570 WIZARD: 1988
@@ -879,7 +897,6 @@ maps:
879 REDACTED: 2274 897 REDACTED: 2274
880 STRINGS: 2205 898 STRINGS: 2205
881 WINDS: 2204 899 WINDS: 2204
882 "[REDACTED]": 2207
883 Yellow Color Door: 900 Yellow Color Door:
884 panels: 901 panels:
885 Paintings: 2210 902 Paintings: 2210
@@ -998,6 +1015,7 @@ maps:
998 Dark Light Room Exit: 1570 1015 Dark Light Room Exit: 1570
999 Dark Light Room Exit Panel: 1571 1016 Dark Light Room Exit Panel: 1571
1000 Entry Shortcut Secret Exit: 1437 1017 Entry Shortcut Secret Exit: 1437
1018 Eye Painting: 2751
1001 Eye Painting Exit: 1446 1019 Eye Painting Exit: 1446
1002 F Keyholder Door: 1551 1020 F Keyholder Door: 1551
1003 F2 Room Back Left Door: 1491 1021 F2 Room Back Left Door: 1491
@@ -1026,6 +1044,7 @@ maps:
1026 House Entrance: 1495 1044 House Entrance: 1495
1027 House Side Door: 1566 1045 House Side Door: 1566
1028 Intense Room Entrance: 1522 1046 Intense Room Entrance: 1522
1047 Lime Hexes: 2810
1029 Magenta Hexes: 2272 1048 Magenta Hexes: 2272
1030 Magic Room Entrance: 1500 1049 Magic Room Entrance: 1500
1031 Magic Room Panels: 1499 1050 Magic Room Panels: 1499
@@ -1106,6 +1125,7 @@ maps:
1106 Starting Room West Wall South Door: 1433 1125 Starting Room West Wall South Door: 1433
1107 Sticks And Stones Door: 1593 1126 Sticks And Stones Door: 1593
1108 Temple of the Eyes Entrance: 1444 1127 Temple of the Eyes Entrance: 1444
1128 Theo Panels: 2811
1109 U2 Room Back Door: 1497 1129 U2 Room Back Door: 1497
1110 U2 Room Back Right Door: 1496 1130 U2 Room Back Right Door: 1496
1111 U2 Room Entrance: 1498 1131 U2 Room Entrance: 1498
@@ -1159,6 +1179,9 @@ maps:
1159 SWAY: 24 1179 SWAY: 24
1160 TERROR: 20 1180 TERROR: 20
1161 TURN: 22 1181 TURN: 22
1182 Keyholder Room:
1183 keyholders:
1184 A: 2773
1162 Synonyms Room: 1185 Synonyms Room:
1163 panels: 1186 panels:
1164 ADORE: 26 1187 ADORE: 26
@@ -1195,7 +1218,6 @@ maps:
1195 panels: 1218 panels:
1196 THIS: 45 1219 THIS: 45
1197 doors: 1220 doors:
1198 End Door: 42
1199 Front Door: 41 1221 Front Door: 41
1200 Lavender Cubes: 43 1222 Lavender Cubes: 43
1201 the_bearer: 1223 the_bearer:
@@ -1276,7 +1298,6 @@ maps:
1276 Control Center Brown Door: 49 1298 Control Center Brown Door: 49
1277 Exit Door: 47 1299 Exit Door: 47
1278 Overlook Door: 46 1300 Overlook Door: 46
1279 Q2 Door: 48
1280 the_between: 1301 the_between:
1281 rooms: 1302 rooms:
1282 Control Center Side: 1303 Control Center Side:
@@ -1451,6 +1472,9 @@ maps:
1451 panels: 1472 panels:
1452 CIVIL: 216 1473 CIVIL: 216
1453 CRABS: 217 1474 CRABS: 217
1475 T Keyholder:
1476 keyholders:
1477 T: 2754
1454 doors: 1478 doors:
1455 C Keyholder Blocker: 176 1479 C Keyholder Blocker: 176
1456 C2 Door: 177 1480 C2 Door: 177
@@ -1462,7 +1486,6 @@ maps:
1462 Obverse Magenta Door: 173 1486 Obverse Magenta Door: 173
1463 Obverse Yellow Door: 178 1487 Obverse Yellow Door: 178
1464 Obverse Yellow Puzzles: 179 1488 Obverse Yellow Puzzles: 179
1465 T Keyholder Blocker: 183
1466 the_darkroom: 1489 the_darkroom:
1467 rooms: 1490 rooms:
1468 First Room: 1491 First Room:
@@ -1493,9 +1516,7 @@ maps:
1493 doors: 1516 doors:
1494 Colorful Entrance: 222 1517 Colorful Entrance: 222
1495 Congruent Entrance: 223 1518 Congruent Entrance: 223
1496 Double Letter Panel Blockers: 218
1497 Double Sided Entrance: 224 1519 Double Sided Entrance: 224
1498 S1 Door: 221
1499 Second Room Entrance: 219 1520 Second Room Entrance: 219
1500 Third Room Entrance: 220 1521 Third Room Entrance: 220
1501 the_digital: 1522 the_digital:
@@ -1695,8 +1716,6 @@ maps:
1695 panels: 1716 panels:
1696 CORN: 393 1717 CORN: 393
1697 DICE: 392 1718 DICE: 392
1698 HOLE: 390
1699 RABBIT: 389
1700 WREATH: 391 1719 WREATH: 391
1701 doors: 1720 doors:
1702 Blue Alcove Entrance: 297 1721 Blue Alcove Entrance: 297
@@ -1708,7 +1727,6 @@ maps:
1708 D Room Entrance: 319 1727 D Room Entrance: 319
1709 Daedalus Entrance: 311 1728 Daedalus Entrance: 311
1710 Flip Area Entrance: 310 1729 Flip Area Entrance: 310
1711 Flipped Pyramid Area Entrance: 315
1712 Flipped Second Room Left Door: 300 1730 Flipped Second Room Left Door: 300
1713 Flipped Second Room Right Door: 299 1731 Flipped Second Room Right Door: 299
1714 Gallery Entrance: 321 1732 Gallery Entrance: 321
@@ -1726,13 +1744,13 @@ maps:
1726 Red Blue Area Left Door: 302 1744 Red Blue Area Left Door: 302
1727 Red Blue Area Right Door: 303 1745 Red Blue Area Right Door: 303
1728 Red Room Painting: 323 1746 Red Room Painting: 323
1729 Repetitive Entrance: 312
1730 Revitalized Entrance: 306 1747 Revitalized Entrance: 306
1731 Right Eye Entrance: 301 1748 Right Eye Entrance: 301
1732 Scarf Door: 296 1749 Scarf Door: 296
1733 Second Room Left Door: 298 1750 Second Room Left Door: 298
1734 Second Room Right Door: 290 1751 Second Room Right Door: 290
1735 Shop Entrance: 313 1752 Shop Entrance: 313
1753 Starting Room West Wall North Door: 2781
1736 Third Eye Painting: 324 1754 Third Eye Painting: 324
1737 Trick Door: 287 1755 Trick Door: 287
1738 Trick To Shop Door: 289 1756 Trick To Shop Door: 289
@@ -1759,6 +1777,8 @@ maps:
1759 X Plus: 1777 X Plus:
1760 panels: 1778 panels:
1761 ROSE: 405 1779 ROSE: 405
1780 keyholders:
1781 M: 2766
1762 X Plus Middle Leg: 1782 X Plus Middle Leg:
1763 panels: 1783 panels:
1764 COLONY: 403 1784 COLONY: 403
@@ -1788,6 +1808,9 @@ maps:
1788 Daedalus Extension: 1808 Daedalus Extension:
1789 panels: 1809 panels:
1790 WHERE: 433 1810 WHERE: 433
1811 Main Area:
1812 keyholders:
1813 P: 2765
1791 doors: 1814 doors:
1792 Ancient Painting: 428 1815 Ancient Painting: 428
1793 Between Painting: 414 1816 Between Painting: 414
@@ -1972,6 +1995,8 @@ maps:
1972 LAUGH FINISHED: 573 1995 LAUGH FINISHED: 573
1973 PLANTS: 570 1996 PLANTS: 570
1974 WEATHER: 568 1997 WEATHER: 568
1998 keyholders:
1999 X: 2770
1975 Outside Jail: 2000 Outside Jail:
1976 panels: 2001 panels:
1977 GUT: 575 2002 GUT: 575
@@ -2060,7 +2085,6 @@ maps:
2060 Into The Mouth Gravestone: 457 2085 Into The Mouth Gravestone: 457
2061 Invisible Entrance: 465 2086 Invisible Entrance: 465
2062 Jail Entrance: 451 2087 Jail Entrance: 451
2063 Lavender Cube: 469
2064 Magnet Room Entrance: 449 2088 Magnet Room Entrance: 449
2065 Nature Room Door: 466 2089 Nature Room Door: 466
2066 Nature Room Panels: 467 2090 Nature Room Panels: 467
@@ -2119,6 +2143,8 @@ maps:
2119 WAS: 631 2143 WAS: 631
2120 WINGS: 662 2144 WINGS: 662
2121 YELL: 636 2145 YELL: 636
2146 keyholders:
2147 B: 2769
2122 Mastery Room: 2148 Mastery Room:
2123 masteries: 2149 masteries:
2124 MASTERY: 666 2150 MASTERY: 666
@@ -2185,6 +2211,8 @@ maps:
2185 FLASHBACK: 705 2211 FLASHBACK: 705
2186 PUSH: 703 2212 PUSH: 703
2187 PUSHBACK: 702 2213 PUSHBACK: 702
2214 keyholders:
2215 J: 2772
2188 doors: 2216 doors:
2189 Side Door: 687 2217 Side Door: 687
2190 the_keen: 2218 the_keen:
@@ -2296,10 +2324,11 @@ maps:
2296 NAY: 774 2324 NAY: 774
2297 NIGH: 781 2325 NIGH: 781
2298 TORE: 787 2326 TORE: 787
2327 keyholders:
2328 S: 2767
2299 doors: 2329 doors:
2300 Left Room Puzzles: 763 2330 Left Room Puzzles: 763
2301 Main Room Door: 2750 2331 Main Room Door: 2750
2302 Main Room Puzzles: 765
2303 Right Room Puzzles: 764 2332 Right Room Puzzles: 764
2304 the_orb: 2333 the_orb:
2305 rooms: 2334 rooms:
@@ -2360,11 +2389,6 @@ maps:
2360 panels: 2389 panels:
2361 CRUSH: 845 2390 CRUSH: 845
2362 RAY: 846 2391 RAY: 846
2363 R2C2.5 Bottom:
2364 panels:
2365 BLACK: 849
2366 FIGMENT: 848
2367 FIZZLE: 847
2368 R2C3 Bottom: 2392 R2C3 Bottom:
2369 panels: 2393 panels:
2370 BLACK: 2747 2394 BLACK: 2747
@@ -2403,6 +2427,9 @@ maps:
2403 CLEOPATRA: 859 2427 CLEOPATRA: 859
2404 NAPOLEON: 860 2428 NAPOLEON: 860
2405 XERXES: 857 2429 XERXES: 857
2430 U Keyholder:
2431 keyholders:
2432 U: 2777
2406 doors: 2433 doors:
2407 K2 Door: 852 2434 K2 Door: 852
2408 the_partial: 2435 the_partial:
@@ -2426,6 +2453,8 @@ maps:
2426 TON: 878 2453 TON: 878
2427 TURN: 875 2454 TURN: 875
2428 UP: 870 2455 UP: 870
2456 keyholders:
2457 L: 2771
2429 Reverse Side: 2458 Reverse Side:
2430 panels: 2459 panels:
2431 BRO: 884 2460 BRO: 884
@@ -2544,6 +2573,9 @@ maps:
2544 Turtle Entrance: 891 2573 Turtle Entrance: 891
2545 the_quiet: 2574 the_quiet:
2546 rooms: 2575 rooms:
2576 Keyholder Room:
2577 keyholders:
2578 Q: 2778
2547 Main Area: 2579 Main Area:
2548 panels: 2580 panels:
2549 BEE: 979 2581 BEE: 979
@@ -2620,7 +2652,8 @@ maps:
2620 rooms: 2652 rooms:
2621 Anti Room: 2653 Anti Room:
2622 panels: 2654 panels:
2623 EYE: 1041 2655 EYE (1): 1041
2656 EYE (2): 2813
2624 HA (1): 1035 2657 HA (1): 1035
2625 HA (2): 1036 2658 HA (2): 1036
2626 HA (3): 1037 2659 HA (3): 1037
@@ -2727,7 +2760,9 @@ maps:
2727 W: 1117 2760 W: 1117
2728 ZEROING: 1118 2761 ZEROING: 1118
2729 doors: 2762 doors:
2763 Anti-Collectable: 2812
2730 Anti-Collectable Room: 1025 2764 Anti-Collectable Room: 1025
2765 Black Hallway: 2780
2731 Cyan Door: 1028 2766 Cyan Door: 1028
2732 Cyan Puzzles: 1032 2767 Cyan Puzzles: 1032
2733 Dot Area Entrance: 1026 2768 Dot Area Entrance: 1026
@@ -2736,7 +2771,6 @@ maps:
2736 Lime Puzzles: 1031 2771 Lime Puzzles: 1031
2737 Magenta Door: 1029 2772 Magenta Door: 1029
2738 Magenta Puzzles: 1033 2773 Magenta Puzzles: 1033
2739 Plaza Entrance: 1024
2740 Yellow Door: 1030 2774 Yellow Door: 1030
2741 Yellow Puzzles: 1034 2775 Yellow Puzzles: 1034
2742 the_revitalized: 2776 the_revitalized:
@@ -2790,6 +2824,8 @@ maps:
2790 STIM: 1148 2824 STIM: 1148
2791 STONE: 1142 2825 STONE: 1142
2792 TADPOLES: 1159 2826 TADPOLES: 1159
2827 keyholders:
2828 N: 2779
2793 doors: 2829 doors:
2794 Books Puzzles: 1136 2830 Books Puzzles: 1136
2795 Games Puzzles: 1137 2831 Games Puzzles: 1137
@@ -3100,22 +3136,15 @@ maps:
3100 panels: 3136 panels:
3101 WHOLE: 2426 3137 WHOLE: 2426
3102 doors: 3138 doors:
3103 Black Door: 2276
3104 Blue Door: 2278
3105 Green Door: 2279
3106 Main Area Fifth Row: 2290 3139 Main Area Fifth Row: 2290
3107 Main Area First Row: 2286 3140 Main Area First Row: 2286
3108 Main Area Fourth Row: 2289 3141 Main Area Fourth Row: 2289
3109 Main Area Second Row: 2287 3142 Main Area Second Row: 2287
3110 Main Area Third Row: 2288 3143 Main Area Third Row: 2288
3111 Orange Door: 2282
3112 Poetry Room Panels: 2285 3144 Poetry Room Panels: 2285
3113 Purple Door: 2281 3145 Tutorial Door: 2754
3114 Red Door: 2277
3115 Tutorial Panels: 2283 3146 Tutorial Panels: 2283
3116 Whirred Room Panels: 2284 3147 Whirred Room Panels: 2284
3117 White Door: 2275
3118 Yellow Door: 2280
3119 the_talented: 3148 the_talented:
3120 rooms: 3149 rooms:
3121 Back Room: 3150 Back Room:
@@ -3144,6 +3173,8 @@ maps:
3144 SWINE (Brown): 2446 3173 SWINE (Brown): 2446
3145 WIFE (Black): 2440 3174 WIFE (Black): 2440
3146 WIFE (Brown): 2447 3175 WIFE (Brown): 2447
3176 keyholders:
3177 Y: 2764
3147 doors: 3178 doors:
3148 Black Side Panels: 2427 3179 Black Side Panels: 2427
3149 Brown Side Panels: 2428 3180 Brown Side Panels: 2428
@@ -3156,6 +3187,9 @@ maps:
3156 Control Center Entrance: 3187 Control Center Entrance:
3157 panels: 3188 panels:
3158 ZERO: 2455 3189 ZERO: 2455
3190 Main Area:
3191 keyholders:
3192 K: 2768
3159 Mastery: 3193 Mastery:
3160 masteries: 3194 masteries:
3161 MASTERY: 2456 3195 MASTERY: 2456
@@ -3360,6 +3394,8 @@ maps:
3360 WAYS: 2621 3394 WAYS: 2621
3361 WHILE: 2613 3395 WHILE: 2613
3362 ZOO: 2615 3396 ZOO: 2615
3397 keyholders:
3398 I: 2775
3363 Middle Room: 3399 Middle Room:
3364 panels: 3400 panels:
3365 FELLOW: 2624 3401 FELLOW: 2624
@@ -3407,6 +3443,12 @@ maps:
3407 UNINTERESTED: 2650 3443 UNINTERESTED: 2650
3408 UNIRONIC: 2656 3444 UNIRONIC: 2656
3409 UNLUCKY: 2654 3445 UNLUCKY: 2654
3446 V Keyholder:
3447 keyholders:
3448 V: 2776
3449 W Keyholder:
3450 keyholders:
3451 W: 2774
3410 doors: 3452 doors:
3411 Cog Rhino Hug Rug: 2586 3453 Cog Rhino Hug Rug: 2586
3412 Control Center Orange Door: 2582 3454 Control Center Orange Door: 2582
@@ -3652,7 +3694,6 @@ maps:
3652 HEALTH: 1428 3694 HEALTH: 1428
3653 doors: 3695 doors:
3654 Bearer Entrance: 1259 3696 Bearer Entrance: 1259
3655 Black Alcove: 2265
3656 Brown Alcove: 1255 3697 Brown Alcove: 1255
3657 Digital Entrance: 1257 3698 Digital Entrance: 1257
3658 East Room 1: 2740 3699 East Room 1: 2740
@@ -3798,4 +3839,61 @@ endings:
3798 WHITE: 2738 3839 WHITE: 2738
3799 YELLOW: 1206 3840 YELLOW: 1206
3800special: 3841special:
3801 Nothing: 1160 3842 A Job Well Done: 1160
3843 Age Symbol: 2791
3844 Anagram Symbol: 2792
3845 Anti A: 2814
3846 Anti B: 2815
3847 Anti C: 2816
3848 Anti D: 2817
3849 Anti E: 2818
3850 Anti F: 2819
3851 Anti G: 2820
3852 Anti H: 2821
3853 Anti I: 2822
3854 Anti J: 2823
3855 Anti K: 2824
3856 Anti L: 2825
3857 Anti M: 2826
3858 Anti N: 2827
3859 Anti O: 2828
3860 Anti P: 2829
3861 Anti Q: 2830
3862 Anti R: 2831
3863 Anti S: 2832
3864 Anti T: 2833
3865 Anti U: 2834
3866 Anti V: 2835
3867 Anti W: 2836
3868 Anti X: 2837
3869 Anti Y: 2838
3870 Anti Z: 2839
3871 Boxes Symbol: 2793
3872 Cross Symbol: 2794
3873 Eval Symbol: 2795
3874 Example Symbol: 2796
3875 Gender Symbol: 2797
3876 Job Symbol: 2798
3877 Lingo Symbol: 2799
3878 Null Symbol: 2800
3879 Planet Symbol: 2801
3880 Pyramid Symbol: 2802
3881 Question Symbol: 2803
3882 Sound Symbol: 2804
3883 Sparkles Symbol: 2805
3884 Stars Symbol: 2806
3885 Sun Symbol: 2807
3886 Sweet Symbol: 2808
3887 Zero Symbol: 2809
3888progressives:
3889 Progressive Gold Ending: 2753
3890door_groups:
3891 Control Center Blue Doors: 2788
3892 Control Center Brown Doors: 2787
3893 Control Center Orange Doors: 2786
3894 Control Center Purple Doors: 2785
3895 Control Center White Doors: 2784
3896 Cyan Doors: 2789
3897 Lavender Cubes: 2790
3898 The Entry - Repetitive Entrance: 2782
3899 The Repetitive - Plaza Entrance: 2783
diff --git a/data/maps/control_center/rooms/Main Area.txtpb b/data/maps/control_center/rooms/Main Area.txtpb index 44b0f79..bf81e26 100644 --- a/data/maps/control_center/rooms/Main Area.txtpb +++ b/data/maps/control_center/rooms/Main Area.txtpb
@@ -30,18 +30,22 @@ panels {
30keyholders { 30keyholders {
31 name: "1" 31 name: "1"
32 path: "Components/KeyHolders/keyHolder" 32 path: "Components/KeyHolders/keyHolder"
33 key: "z"
33} 34}
34keyholders { 35keyholders {
35 name: "2" 36 name: "2"
36 path: "Components/KeyHolders/keyHolder2" 37 path: "Components/KeyHolders/keyHolder2"
38 key: "e"
37} 39}
38keyholders { 40keyholders {
39 name: "3" 41 name: "3"
40 path: "Components/KeyHolders/keyHolder3" 42 path: "Components/KeyHolders/keyHolder3"
43 key: "r"
41} 44}
42keyholders { 45keyholders {
43 name: "4" 46 name: "4"
44 path: "Components/KeyHolders/keyHolder4" 47 path: "Components/KeyHolders/keyHolder4"
48 key: "o"
45} 49}
46ports { 50ports {
47 name: "RIGHT" 51 name: "RIGHT"
diff --git a/data/maps/daedalus/connections.txtpb b/data/maps/daedalus/connections.txtpb index 223710a..07b6513 100644 --- a/data/maps/daedalus/connections.txtpb +++ b/data/maps/daedalus/connections.txtpb
@@ -100,6 +100,11 @@ connections {
100 oneway: true 100 oneway: true
101} 101}
102connections { 102connections {
103 from_room: "Outside House"
104 to_room: "Blue Hallway Tall Side"
105 door { name: "House Side Door" }
106}
107connections {
103 from_room: "Purple SE Vestibule" 108 from_room: "Purple SE Vestibule"
104 to_room: "Welcome Back Area" 109 to_room: "Welcome Back Area"
105 oneway: true 110 oneway: true
@@ -535,6 +540,11 @@ connections {
535} 540}
536connections { 541connections {
537 from_room: "Z2 Room" 542 from_room: "Z2 Room"
543 to_room: "Orange Room Hallway"
544 door { name: "Z2 Room Southeast Door" }
545}
546connections {
547 from_room: "Orange Room Hallway"
538 to_room: "Orange Room" 548 to_room: "Orange Room"
539 door { name: "Z2 Room Southeast Door" } 549 door { name: "Z2 Room Southeast Door" }
540} 550}
@@ -1545,3 +1555,316 @@ connections {
1545 to_room: "Pyramid Top" 1555 to_room: "Pyramid Top"
1546 door { name: "Pyramid Third Floor Door" } 1556 door { name: "Pyramid Third Floor Door" }
1547} 1557}
1558connections {
1559 from_room: "Roof"
1560 to_room: "After Bee Room"
1561 oneway: true
1562 roof_access: true
1563}
1564connections {
1565 from_room: "Roof"
1566 to_room: "Amber North 2"
1567 oneway: true
1568 roof_access: true
1569}
1570connections {
1571 from_room: "Roof"
1572 to_room: "Black Hex"
1573 oneway: true
1574 roof_access: true
1575}
1576connections {
1577 from_room: "Roof"
1578 to_room: "Blue Hallway Tall Side"
1579 oneway: true
1580 roof_access: true
1581}
1582connections {
1583 from_room: "Roof"
1584 to_room: "Blue Hallway"
1585 oneway: true
1586 roof_access: true
1587}
1588# Blue Hallway Cut Side is inside.
1589connections {
1590 from_room: "Roof"
1591 to_room: "Eye Painting"
1592 oneway: true
1593 roof_access: true
1594}
1595connections {
1596 from_room: "Roof"
1597 to_room: "Globe Room"
1598 oneway: true
1599 roof_access: true
1600}
1601connections {
1602 from_room: "Roof"
1603 to_room: "Gray Color Door"
1604 oneway: true
1605 roof_access: true
1606}
1607connections {
1608 from_room: "Roof"
1609 to_room: "Green Color Door"
1610 oneway: true
1611 roof_access: true
1612}
1613connections {
1614 from_room: "Roof"
1615 to_room: "Green Smiley"
1616 oneway: true
1617 roof_access: true
1618}
1619connections {
1620 from_room: "Roof"
1621 to_room: "Hedges"
1622 oneway: true
1623 roof_access: true
1624}
1625connections {
1626 from_room: "Roof"
1627 to_room: "Maze Paintings Area"
1628 oneway: true
1629 roof_access: true
1630}
1631connections {
1632 from_room: "Roof"
1633 to_room: "Maze"
1634 oneway: true
1635 roof_access: true
1636}
1637connections {
1638 from_room: "Roof"
1639 to_room: "North Castle Area"
1640 oneway: true
1641 roof_access: true
1642}
1643connections {
1644 from_room: "Roof"
1645 to_room: "Number Paintings Area"
1646 oneway: true
1647 roof_access: true
1648}
1649connections {
1650 from_room: "Roof"
1651 to_room: "Orange Room Hallway"
1652 oneway: true
1653 roof_access: true
1654}
1655connections {
1656 from_room: "Roof"
1657 to_room: "Outside Book Room"
1658 oneway: true
1659 roof_access: true
1660}
1661connections {
1662 from_room: "Roof"
1663 to_room: "Outside Eye Temple"
1664 oneway: true
1665 roof_access: true
1666}
1667connections {
1668 from_room: "Roof"
1669 to_room: "Outside Hedges"
1670 oneway: true
1671 roof_access: true
1672}
1673connections {
1674 from_room: "Roof"
1675 to_room: "Outside Hotel"
1676 oneway: true
1677 roof_access: true
1678}
1679connections {
1680 from_room: "Roof"
1681 to_room: "Outside House"
1682 oneway: true
1683 roof_access: true
1684}
1685connections {
1686 from_room: "Roof"
1687 to_room: "Outside Magic Room"
1688 oneway: true
1689 roof_access: true
1690}
1691connections {
1692 from_room: "Roof"
1693 to_room: "Outside Orange Room"
1694 oneway: true
1695 roof_access: true
1696}
1697connections {
1698 from_room: "Roof"
1699 to_room: "Outside Pyramid"
1700 oneway: true
1701 roof_access: true
1702}
1703connections {
1704 from_room: "Roof"
1705 to_room: "Outside Red Room"
1706 oneway: true
1707 roof_access: true
1708}
1709connections {
1710 from_room: "Roof"
1711 to_room: "Outside Salt Room"
1712 oneway: true
1713 roof_access: true
1714}
1715connections {
1716 from_room: "Roof"
1717 to_room: "Outside Snake Room"
1718 oneway: true
1719 roof_access: true
1720}
1721connections {
1722 from_room: "Roof"
1723 to_room: "Post Orange Smiley Three Way"
1724 oneway: true
1725 roof_access: true
1726}
1727connections {
1728 from_room: "Roof"
1729 to_room: "Purple NW Vestibule"
1730 oneway: true
1731 roof_access: true
1732}
1733connections {
1734 from_room: "Roof"
1735 to_room: "Purple Room East"
1736 oneway: true
1737 roof_access: true
1738}
1739connections {
1740 from_room: "Roof"
1741 to_room: "Purple Room South"
1742 oneway: true
1743 roof_access: true
1744}
1745connections {
1746 from_room: "Roof"
1747 to_room: "Purple Room West"
1748 oneway: true
1749 roof_access: true
1750}
1751connections {
1752 from_room: "Roof"
1753 to_room: "Purple SE Vestibule"
1754 oneway: true
1755 roof_access: true
1756}
1757connections {
1758 from_room: "Roof"
1759 to_room: "Pyramid Second Floor"
1760 oneway: true
1761 roof_access: true
1762}
1763connections {
1764 from_room: "Roof"
1765 to_room: "Pyramid Top"
1766 oneway: true
1767 roof_access: true
1768}
1769connections {
1770 from_room: "Roof"
1771 to_room: "Quiet Entrance"
1772 oneway: true
1773 roof_access: true
1774}
1775connections {
1776 from_room: "Roof"
1777 to_room: "Red Color Door"
1778 oneway: true
1779 roof_access: true
1780}
1781connections {
1782 from_room: "Roof"
1783 to_room: "South Castle Area"
1784 oneway: true
1785 roof_access: true
1786}
1787connections {
1788 from_room: "Roof"
1789 to_room: "Starting Room"
1790 oneway: true
1791 roof_access: true
1792}
1793connections {
1794 from_room: "Roof"
1795 to_room: "Sweet Foyer"
1796 oneway: true
1797 roof_access: true
1798}
1799connections {
1800 from_room: "Roof"
1801 to_room: "Tree Entrance"
1802 oneway: true
1803 roof_access: true
1804}
1805connections {
1806 from_room: "Roof"
1807 to_room: "West Castle Area"
1808 oneway: true
1809 roof_access: true
1810}
1811connections {
1812 from_room: "Roof"
1813 to_room: "West Spire"
1814 oneway: true
1815 roof_access: true
1816}
1817connections {
1818 from_room: "Roof"
1819 to_room: "Yellow Color Door"
1820 oneway: true
1821 roof_access: true
1822}
1823connections {
1824 from_room: "Roof"
1825 to_room: "Z2 Room"
1826 oneway: true
1827 roof_access: true
1828}
1829connections {
1830 from_room: "Roof"
1831 to_room: "Zoo Center"
1832 oneway: true
1833 roof_access: true
1834}
1835connections {
1836 from_room: "Roof"
1837 to_room: "Zoo E"
1838 oneway: true
1839 roof_access: true
1840}
1841connections {
1842 from_room: "Roof"
1843 to_room: "Zoo N"
1844 oneway: true
1845 roof_access: true
1846}
1847connections {
1848 from_room: "Roof"
1849 to_room: "Zoo NE"
1850 oneway: true
1851 roof_access: true
1852}
1853connections {
1854 from_room: "Roof"
1855 to_room: "Zoo S"
1856 oneway: true
1857 roof_access: true
1858}
1859connections {
1860 from_room: "Roof"
1861 to_room: "Zoo SE"
1862 oneway: true
1863 roof_access: true
1864}
1865connections {
1866 from_room: "Roof"
1867 to_room: "F Keyholder"
1868 oneway: true
1869 roof_access: true
1870}
diff --git a/data/maps/daedalus/doors.txtpb b/data/maps/daedalus/doors.txtpb index 4e35de2..ace15a1 100644 --- a/data/maps/daedalus/doors.txtpb +++ b/data/maps/daedalus/doors.txtpb
@@ -195,8 +195,8 @@ doors {
195} 195}
196doors { 196doors {
197 name: "Welcome Back Door" 197 name: "Welcome Back Door"
198 type: STANDARD 198 type: LOCATION_ONLY
199 receivers: "Components/Doors/Entry/entry_14" 199 #receivers: "Components/Doors/Entry/entry_14"
200 panels { room: "Welcome Back Area" name: "GREETINGS OLD FRIEND" } 200 panels { room: "Welcome Back Area" name: "GREETINGS OLD FRIEND" }
201 location_room: "Welcome Back Area" 201 location_room: "Welcome Back Area"
202} 202}
@@ -493,7 +493,6 @@ doors {
493 panels { room: "Outside House" name: "WALLS" } 493 panels { room: "Outside House" name: "WALLS" }
494 panels { room: "Outside House" name: "LOCK" } 494 panels { room: "Outside House" name: "LOCK" }
495 location_room: "Outside House" 495 location_room: "Outside House"
496 location_name: "North Purple Vestibules"
497} 496}
498doors { 497doors {
499 name: "Purple NW Vestibule" 498 name: "Purple NW Vestibule"
@@ -892,16 +891,12 @@ doors {
892} 891}
893doors { 892doors {
894 name: "White Hallway From Entry" 893 name: "White Hallway From Entry"
895 # TODO: This should be combined with the corresponding door in the_entry, at
896 # least when connections are not shuffled.
897 type: CONTROL_CENTER_COLOR 894 type: CONTROL_CENTER_COLOR
898 receivers: "Components/Doors/Halls/froom_6" 895 receivers: "Components/Doors/Halls/froom_6"
899 control_center_color: "white" 896 control_center_color: "white"
900} 897}
901doors { 898doors {
902 name: "Purple Hallway From Great" 899 name: "Purple Hallway From Great"
903 # TODO: This should be combined with the corresponding door in the_great, at
904 # least when connections are not shuffled.
905 type: CONTROL_CENTER_COLOR 900 type: CONTROL_CENTER_COLOR
906 receivers: "Components/Doors/Halls/froom_7" 901 receivers: "Components/Doors/Halls/froom_7"
907 control_center_color: "purple" 902 control_center_color: "purple"
@@ -1080,6 +1075,7 @@ doors {
1080 panels { room: "Outside Snake Room" name: "SONG (South)" } 1075 panels { room: "Outside Snake Room" name: "SONG (South)" }
1081 panels { room: "West Castle Area" name: "SONG (2)" } 1076 panels { room: "West Castle Area" name: "SONG (2)" }
1082 location_room: "West Castle Area" 1077 location_room: "West Castle Area"
1078 location_name: "South SONGs"
1083} 1079}
1084doors { 1080doors {
1085 name: "Amber North Door" 1081 name: "Amber North Door"
@@ -1088,6 +1084,7 @@ doors {
1088 panels { room: "Outside Snake Room" name: "SONG (North)" } 1084 panels { room: "Outside Snake Room" name: "SONG (North)" }
1089 panels { room: "Amber North 2" name: "SONG" } 1085 panels { room: "Amber North 2" name: "SONG" }
1090 location_room: "Amber North 2" 1086 location_room: "Amber North 2"
1087 location_name: "North SONGs"
1091} 1088}
1092doors { 1089doors {
1093 name: "Amber East Doors" 1090 name: "Amber East Doors"
@@ -1210,70 +1207,37 @@ doors {
1210 type: ITEM_ONLY 1207 type: ITEM_ONLY
1211 receivers: "Components/Doors/Halls/connections_1" 1208 receivers: "Components/Doors/Halls/connections_1"
1212 receivers: "Components/Doors/Halls/connections_3" 1209 receivers: "Components/Doors/Halls/connections_3"
1210 # These have the same effect as the above, but including them here prevents
1211 # them from opening in door shuffle when the J2 door opens.
1212 receivers: "Components/Triggers/teleportListenerConnections3"
1213 receivers: "Components/Triggers/teleportListenerConnections4"
1214 # This door can open from either solving all panels, or just the smiley ones,
1215 # and the latter is obviously a subset of the former so let's just check for
1216 # that.
1213 panels { room: "Hotel" name: "PARKA" } 1217 panels { room: "Hotel" name: "PARKA" }
1214 panels { room: "Hotel" name: "MARLIN" }
1215 panels { room: "Hotel" name: "WHO" }
1216 panels { room: "Hotel" name: "CLOAK" } 1218 panels { room: "Hotel" name: "CLOAK" }
1217 panels { room: "Hotel" name: "MANE" }
1218 panels { room: "Hotel" name: "WHAT" }
1219 panels { room: "Hotel" name: "BLAZER" }
1220 panels { room: "Hotel" name: "WHERE" }
1221 panels { room: "Hotel" name: "DOROTHY" } 1219 panels { room: "Hotel" name: "DOROTHY" }
1222 panels { room: "Hotel" name: "JACKET" }
1223 panels { room: "Hotel" name: "TAIL" }
1224 panels { room: "Hotel" name: "JAWS" } 1220 panels { room: "Hotel" name: "JAWS" }
1225 panels { room: "Hotel" name: "FLOUNDER" }
1226 panels { room: "Hotel" name: "WHEN" } 1221 panels { room: "Hotel" name: "WHEN" }
1227 panels { room: "Hotel" name: "CLAWS" } 1222 panels { room: "Hotel" name: "CLAWS" }
1228 panels { room: "Hotel" name: "BRUCE" }
1229 panels { room: "Hotel" name: "POTATO" } 1223 panels { room: "Hotel" name: "POTATO" }
1230 panels { room: "Hotel" name: "SALAD" }
1231 panels { room: "Hotel" name: "BATHING" }
1232 panels { room: "Hotel" name: "MICRO" } 1224 panels { room: "Hotel" name: "MICRO" }
1233 panels { room: "Hotel" name: "BUSINESS" }
1234 panels { room: "Hotel" name: "WEDDING" }
1235 panels { room: "Hotel" name: "TREE" }
1236 panels { room: "Hotel" name: "RIVER" }
1237 panels { room: "Hotel" name: "TUNING" } 1225 panels { room: "Hotel" name: "TUNING" }
1238 panels { room: "Hotel" name: "BOXING" }
1239 panels { room: "Hotel" name: "TELEPHONE" }
1240 panels { room: "Hotel" name: "LAW" } 1226 panels { room: "Hotel" name: "LAW" }
1241 panels { room: "Hotel" name: "POKER" }
1242 panels { room: "Hotel" name: "CARD" } 1227 panels { room: "Hotel" name: "CARD" }
1243 panels { room: "Hotel" name: "ROAD" } 1228 panels { room: "Hotel" name: "ROAD" }
1244 panels { room: "Hotel" name: "CHOCOLATE" }
1245 panels { room: "Hotel" name: "DEPART" } 1229 panels { room: "Hotel" name: "DEPART" }
1246 panels { room: "Hotel" name: "WITHDRAW" }
1247 panels { room: "Hotel" name: "QUIT" }
1248 panels { room: "Hotel" name: "LEAVE" } 1230 panels { room: "Hotel" name: "LEAVE" }
1249 panels { room: "Hotel" name: "PALE" }
1250 panels { room: "Hotel" name: "JUST" }
1251 panels { room: "Hotel" name: "NEW" }
1252 panels { room: "Hotel" name: "UNTALENTED" }
1253 panels { room: "Hotel" name: "SERVICE" } 1231 panels { room: "Hotel" name: "SERVICE" }
1254 panels { room: "Hotel" name: "FULL" }
1255 panels { room: "Hotel" name: "EVIL" }
1256 panels { room: "Hotel" name: "HONEY" } 1232 panels { room: "Hotel" name: "HONEY" }
1257 panels { room: "Hotel" name: "CRESCENT" }
1258 panels { room: "Hotel" name: "INVALID" } 1233 panels { room: "Hotel" name: "INVALID" }
1259 panels { room: "Hotel" name: "FESTIVAL" } 1234 panels { room: "Hotel" name: "FESTIVAL" }
1260 panels { room: "Hotel" name: "BEAUTIFUL" }
1261 panels { room: "Hotel" name: "WILTED" } 1235 panels { room: "Hotel" name: "WILTED" }
1262 panels { room: "Hotel" name: "DROOPED" }
1263 panels { room: "Hotel" name: "FADED" }
1264 panels { room: "Hotel" name: "WANED" } 1236 panels { room: "Hotel" name: "WANED" }
1265 panels { room: "Hotel" name: "TALL" }
1266 panels { room: "Hotel" name: "CANVAS" }
1267 panels { room: "Hotel" name: "LEVER" }
1268 panels { room: "Hotel" name: "SCULPTURE" }
1269 panels { room: "Hotel" name: "RAGE" } 1237 panels { room: "Hotel" name: "RAGE" }
1270 panels { room: "Hotel" name: "BALL" }
1271 panels { room: "Hotel" name: "FOOL" }
1272 panels { room: "Hotel" name: "VERGE" } 1238 panels { room: "Hotel" name: "VERGE" }
1273 panels { room: "Hotel" name: "ART" }
1274 panels { room: "Hotel" name: "EVER" } 1239 panels { room: "Hotel" name: "EVER" }
1275 panels { room: "Hotel" name: "PAIN" } 1240 panels { room: "Hotel" name: "PAIN" }
1276 panels { room: "Hotel" name: "FOOT" }
1277} 1241}
1278doors { 1242doors {
1279 name: "J2 Door 1" 1243 name: "J2 Door 1"
@@ -1502,87 +1466,87 @@ doors {
1502} 1466}
1503doors { 1467doors {
1504 name: "Red Rainbow Room" 1468 name: "Red Rainbow Room"
1505 type: ITEM_ONLY 1469 type: STANDARD
1506 receivers: "Components/Doors/Color Reading/door_3" 1470 receivers: "Components/Doors/Color Reading/door_3"
1507 panels { room: "Rainbow Start" name: "PAINTING" } 1471 panels { room: "Rainbow Start" name: "PAINTING" }
1508 panels { room: "Red Smiley" name: "SMILE" } 1472 location_room: "Rainbow Start"
1509} 1473}
1510doors { 1474doors {
1511 name: "Orange Rainbow Room" 1475 name: "Orange Rainbow Room"
1512 type: ITEM_ONLY 1476 type: ITEM_ONLY
1513 receivers: "Components/Doors/Color Reading/door_4" 1477 receivers: "Components/Doors/Color Reading/door_4"
1514 panels { room: "Rainbow Red" name: "THEME" } 1478 panels { room: "Rainbow Red" name: "THEME" }
1515 panels { room: "Outside Orange Room" name: "SMILE" } 1479 panels { room: "Red Smiley" name: "SMILE" }
1516} 1480}
1517doors { 1481doors {
1518 name: "Yellow Rainbow Room" 1482 name: "Yellow Rainbow Room"
1519 type: ITEM_ONLY 1483 type: ITEM_ONLY
1520 receivers: "Components/Doors/Color Reading/door_17" 1484 receivers: "Components/Doors/Color Reading/door_17"
1521 panels { room: "Rainbow Orange" name: "THEME" } 1485 panels { room: "Rainbow Orange" name: "THEME" }
1522 panels { room: "Hedges" name: "SMILE" } 1486 panels { room: "Outside Orange Room" name: "SMILE" }
1523} 1487}
1524doors { 1488doors {
1525 name: "Green Rainbow Room" 1489 name: "Green Rainbow Room"
1526 type: ITEM_ONLY 1490 type: ITEM_ONLY
1527 receivers: "Components/Doors/Color Reading/door_5" 1491 receivers: "Components/Doors/Color Reading/door_5"
1528 panels { room: "Rainbow Yellow" name: "THEME" } 1492 panels { room: "Rainbow Yellow" name: "THEME" }
1529 panels { room: "Green Smiley" name: "SMILE" } 1493 panels { room: "Hedges" name: "SMILE" }
1530} 1494}
1531doors { 1495doors {
1532 name: "Blue Rainbow Room" 1496 name: "Blue Rainbow Room"
1533 type: ITEM_ONLY 1497 type: ITEM_ONLY
1534 receivers: "Components/Doors/Color Reading/door_6" 1498 receivers: "Components/Doors/Color Reading/door_6"
1535 panels { room: "Rainbow Green" name: "THEME" } 1499 panels { room: "Rainbow Green" name: "THEME" }
1536 panels { room: "Blue Smiley" name: "SMILE" } 1500 panels { room: "Green Smiley" name: "SMILE" }
1537} 1501}
1538doors { 1502doors {
1539 name: "Purple Rainbow Room" 1503 name: "Purple Rainbow Room"
1540 type: ITEM_ONLY 1504 type: ITEM_ONLY
1541 receivers: "Components/Doors/Color Reading/door_7" 1505 receivers: "Components/Doors/Color Reading/door_7"
1542 panels { room: "Rainbow Blue" name: "THEME" } 1506 panels { room: "Rainbow Blue" name: "THEME" }
1543 panels { room: "Purple Smiley" name: "SMILE" } 1507 panels { room: "Blue Smiley" name: "SMILE" }
1544} 1508}
1545doors { 1509doors {
1546 name: "Red Rainbow Panel" 1510 name: "Red Rainbow Panel"
1547 type: LOCATION_ONLY 1511 type: LOCATION_ONLY
1548 panels { room: "Rainbow Start" name: "PAINTING" }
1549 location_room: "Rainbow Start"
1550}
1551doors {
1552 name: "Orange Rainbow Panel"
1553 type: LOCATION_ONLY
1554 panels { room: "Rainbow Red" name: "THEME" } 1512 panels { room: "Rainbow Red" name: "THEME" }
1555 location_room: "Rainbow Red" 1513 location_room: "Rainbow Red"
1556} 1514}
1557doors { 1515doors {
1558 name: "Yellow Rainbow Panel" 1516 name: "Orange Rainbow Panel"
1559 type: LOCATION_ONLY 1517 type: LOCATION_ONLY
1560 panels { room: "Rainbow Orange" name: "THEME" } 1518 panels { room: "Rainbow Orange" name: "THEME" }
1561 location_room: "Rainbow Orange" 1519 location_room: "Rainbow Orange"
1562} 1520}
1563doors { 1521doors {
1564 name: "Green Rainbow Panel" 1522 name: "Yellow Rainbow Panel"
1565 type: LOCATION_ONLY 1523 type: LOCATION_ONLY
1566 panels { room: "Rainbow Yellow" name: "THEME" } 1524 panels { room: "Rainbow Yellow" name: "THEME" }
1567 location_room: "Rainbow Yellow" 1525 location_room: "Rainbow Yellow"
1568} 1526}
1569doors { 1527doors {
1570 name: "Blue Rainbow Panel" 1528 name: "Green Rainbow Panel"
1571 type: LOCATION_ONLY 1529 type: LOCATION_ONLY
1572 panels { room: "Rainbow Green" name: "THEME" } 1530 panels { room: "Rainbow Green" name: "THEME" }
1573 location_room: "Rainbow Green" 1531 location_room: "Rainbow Green"
1574} 1532}
1575doors { 1533doors {
1576 name: "Purple Rainbow Panel" 1534 name: "Blue Rainbow Panel"
1577 type: LOCATION_ONLY 1535 type: LOCATION_ONLY
1578 panels { room: "Rainbow Blue" name: "THEME" } 1536 panels { room: "Rainbow Blue" name: "THEME" }
1579 location_room: "Rainbow Blue" 1537 location_room: "Rainbow Blue"
1580} 1538}
1581doors { 1539doors {
1582 name: "Cyan Rainbow Room" 1540 name: "Cyan Rainbow Room"
1583 type: STANDARD 1541 type: ITEM_ONLY
1584 receivers: "Components/Doors/Color Reading/door_18" 1542 receivers: "Components/Doors/Color Reading/door_18"
1585 panels { room: "Rainbow Purple" name: "THEME" } 1543 panels { room: "Rainbow Purple" name: "THEME" }
1544 panels { room: "Purple Smiley" name: "SMILE" }
1545}
1546doors {
1547 name: "Purple Rainbow Panel"
1548 type: LOCATION_ONLY
1549 panels { room: "Rainbow Purple" name: "THEME" }
1586 location_room: "Rainbow Purple" 1550 location_room: "Rainbow Purple"
1587} 1551}
1588doors { 1552doors {
@@ -1591,6 +1555,7 @@ doors {
1591 receivers: "Components/Doors/Color Reading/door_8" 1555 receivers: "Components/Doors/Color Reading/door_8"
1592 panels { room: "Rainbow Cyan" name: "THEME" } 1556 panels { room: "Rainbow Cyan" name: "THEME" }
1593 location_room: "Rainbow Cyan" 1557 location_room: "Rainbow Cyan"
1558 location_name: "Cyan Rainbow Panel"
1594} 1559}
1595doors { 1560doors {
1596 name: "Pepper Room Entrance" 1561 name: "Pepper Room Entrance"
@@ -1920,6 +1885,7 @@ doors {
1920 type: LOCATION_ONLY 1885 type: LOCATION_ONLY
1921 panels { room: "Dark Light Exit" name: "GASKET" } 1886 panels { room: "Dark Light Exit" name: "GASKET" }
1922 location_room: "Dark Light Exit" 1887 location_room: "Dark Light Exit"
1888 location_name: "GASKET"
1923} 1889}
1924doors { 1890doors {
1925 name: "Dark Light Room Divider" 1891 name: "Dark Light Room Divider"
@@ -2112,8 +2078,12 @@ doors {
2112doors { 2078doors {
2113 name: "C Keyholder Blocker" 2079 name: "C Keyholder Blocker"
2114 type: EVENT 2080 type: EVENT
2115 # Components/Doors/Unincorporated/temple_foyer_7 2081 receivers: "Components/Doors/Unincorporated/temple_foyer_7"
2116 switches: "lavender_cubes" 2082 panels {
2083 map: "the_ancient"
2084 room: "Inside"
2085 name: "COLOR"
2086 }
2117} 2087}
2118doors { 2088doors {
2119 name: "Computer Room Back Door" 2089 name: "Computer Room Back Door"
@@ -2183,6 +2153,7 @@ doors {
2183 receivers: "Components/Doors/Unincorporated/temple_foyer_6" 2153 receivers: "Components/Doors/Unincorporated/temple_foyer_6"
2184 panels { room: "Globe Room" name: "WORD" } 2154 panels { room: "Globe Room" name: "WORD" }
2185 location_room: "Globe Room" 2155 location_room: "Globe Room"
2156 location_name: "Sticks and Stones"
2186} 2157}
2187doors { 2158doors {
2188 name: "Castle Numbers Puzzle" 2159 name: "Castle Numbers Puzzle"
@@ -2296,3 +2267,30 @@ doors {
2296 panels { room: "South Castle Area" name: "COLOR (3)" answer: "purple" } 2267 panels { room: "South Castle Area" name: "COLOR (3)" answer: "purple" }
2297 panels { room: "South Castle Area" name: "COLOR (4)" answer: "green" } 2268 panels { room: "South Castle Area" name: "COLOR (4)" answer: "green" }
2298} 2269}
2270doors {
2271 name: "Eye Painting"
2272 type: ITEM_ONLY
2273 receivers: "Components/Paintings/Temple of the Eyes/eyeRedStart/teleportListener"
2274 double_letters: true
2275}
2276doors {
2277 name: "Lime Hexes"
2278 type: LOCATION_ONLY
2279 panels { room: "Tree Entrance" name: "RAT" }
2280 panels { room: "Tree Entrance" name: "DIFFERENCE" }
2281 panels { room: "Tree Entrance" name: "LEANS" }
2282 panels { room: "Tree Entrance" name: "QUESTION" }
2283 panels { room: "Tree Entrance" name: "WHERE" }
2284 panels { room: "Tree Entrance" name: "SUNDER" }
2285 location_room: "Tree Entrance"
2286}
2287doors {
2288 name: "Theo Panels"
2289 type: LOCATION_ONLY
2290 panels { room: "House" name: "GOAT" }
2291 panels { room: "House" name: "AMAZE" }
2292 panels { room: "House" name: "SKINNYHIM" }
2293 panels { room: "House" name: "THEO" }
2294 location_room: "House"
2295 location_name: "All Puzzles"
2296}
diff --git a/data/maps/daedalus/rooms/C Keyholder.txtpb b/data/maps/daedalus/rooms/C Keyholder.txtpb index cc8548c..ef10a90 100644 --- a/data/maps/daedalus/rooms/C Keyholder.txtpb +++ b/data/maps/daedalus/rooms/C Keyholder.txtpb
@@ -3,4 +3,5 @@ panel_display_name: "North Area"
3keyholders { 3keyholders {
4 name: "C" 4 name: "C"
5 path: "Components/KeyHolders/keyHolderC" 5 path: "Components/KeyHolders/keyHolderC"
6 key: "c"
6} 7}
diff --git a/data/maps/daedalus/rooms/D Keyholder.txtpb b/data/maps/daedalus/rooms/D Keyholder.txtpb index 2521ab2..a5852be 100644 --- a/data/maps/daedalus/rooms/D Keyholder.txtpb +++ b/data/maps/daedalus/rooms/D Keyholder.txtpb
@@ -3,4 +3,5 @@ panel_display_name: "Plum Room"
3keyholders { 3keyholders {
4 name: "D" 4 name: "D"
5 path: "Components/KeyHolders/keyHolderD" 5 path: "Components/KeyHolders/keyHolderD"
6 key: "d"
6} 7}
diff --git a/data/maps/daedalus/rooms/F Keyholder.txtpb b/data/maps/daedalus/rooms/F Keyholder.txtpb index 662f76d..b424c6a 100644 --- a/data/maps/daedalus/rooms/F Keyholder.txtpb +++ b/data/maps/daedalus/rooms/F Keyholder.txtpb
@@ -3,4 +3,5 @@ panel_display_name: "West Area"
3keyholders { 3keyholders {
4 name: "F" 4 name: "F"
5 path: "Components/KeyHolders/keyHolderF" 5 path: "Components/KeyHolders/keyHolderF"
6 key: "f"
6} 7}
diff --git a/data/maps/daedalus/rooms/Number Paintings Area.txtpb b/data/maps/daedalus/rooms/Number Paintings Area.txtpb index 15c8875..c89bfcf 100644 --- a/data/maps/daedalus/rooms/Number Paintings Area.txtpb +++ b/data/maps/daedalus/rooms/Number Paintings Area.txtpb
@@ -17,6 +17,7 @@ panels {
17keyholders { 17keyholders {
18 name: "G" 18 name: "G"
19 path: "Components/KeyHolders/keyHolderG" 19 path: "Components/KeyHolders/keyHolderG"
20 key: "g"
20} 21}
21paintings { 22paintings {
22 name: "WON" 23 name: "WON"
diff --git a/data/maps/daedalus/rooms/Orange Room Hallway.txtpb b/data/maps/daedalus/rooms/Orange Room Hallway.txtpb new file mode 100644 index 0000000..915e698 --- /dev/null +++ b/data/maps/daedalus/rooms/Orange Room Hallway.txtpb
@@ -0,0 +1,4 @@
1name: "Orange Room Hallway"
2panel_display_name: "Orange Room"
3# This has the same door at both sides, and mainly just connects Z2 Room and
4# Orange Room. It's separate because you can also get here from the Roof.
diff --git a/data/maps/daedalus/rooms/Outside House.txtpb b/data/maps/daedalus/rooms/Outside House.txtpb index fd3f5f0..fed9dda 100644 --- a/data/maps/daedalus/rooms/Outside House.txtpb +++ b/data/maps/daedalus/rooms/Outside House.txtpb
@@ -75,6 +75,7 @@ panels {
75keyholders { 75keyholders {
76 name: "H" 76 name: "H"
77 path: "Components/KeyHolders/keyHolderH" 77 path: "Components/KeyHolders/keyHolderH"
78 key: "h"
78} 79}
79paintings { 80paintings {
80 name: "CASTLE2" 81 name: "CASTLE2"
diff --git a/data/maps/daedalus/rooms/Wonderland.txtpb b/data/maps/daedalus/rooms/Wonderland.txtpb index 4b69e99..ae9b3f1 100644 --- a/data/maps/daedalus/rooms/Wonderland.txtpb +++ b/data/maps/daedalus/rooms/Wonderland.txtpb
@@ -1,6 +1,5 @@
1name: "Wonderland" 1name: "Wonderland"
2panel_display_name: "Northwest Area" 2panel_display_name: "Northwest Area"
3# TODO: There's a warp from The Entry into here.
4panels { 3panels {
5 name: "APRIL" 4 name: "APRIL"
6 path: "Panels/Wonderland/wonderland_1" 5 path: "Panels/Wonderland/wonderland_1"
diff --git a/data/maps/daedalus/rooms/Yellow Color Door.txtpb b/data/maps/daedalus/rooms/Yellow Color Door.txtpb index f22c954..e44658c 100644 --- a/data/maps/daedalus/rooms/Yellow Color Door.txtpb +++ b/data/maps/daedalus/rooms/Yellow Color Door.txtpb
@@ -26,7 +26,7 @@ paintings {
26 path: "Components/Paintings/Temple of the Eyes/eyeRedStart" 26 path: "Components/Paintings/Temple of the Eyes/eyeRedStart"
27 move: true 27 move: true
28 enter_only: true 28 enter_only: true
29 # TODO: requires double letters 29 required_door { name: "Eye Painting" }
30} 30}
31ports { 31ports {
32 name: "FOURROOMS" 32 name: "FOURROOMS"
diff --git a/data/maps/four_rooms/rooms/Keyholder Room.txtpb b/data/maps/four_rooms/rooms/Keyholder Room.txtpb index e7c7fa6..13c3dce 100644 --- a/data/maps/four_rooms/rooms/Keyholder Room.txtpb +++ b/data/maps/four_rooms/rooms/Keyholder Room.txtpb
@@ -2,4 +2,5 @@ name: "Keyholder Room"
2keyholders { 2keyholders {
3 name: "A" 3 name: "A"
4 path: "Components/KeyHolders/keyHolderA" 4 path: "Components/KeyHolders/keyHolderA"
5 key: "a"
5} 6}
diff --git a/data/maps/the_ancient/doors.txtpb b/data/maps/the_ancient/doors.txtpb index 5dc062e..e550306 100644 --- a/data/maps/the_ancient/doors.txtpb +++ b/data/maps/the_ancient/doors.txtpb
@@ -38,7 +38,8 @@ doors {
38} 38}
39doors { 39doors {
40 name: "Lavender Cubes" 40 name: "Lavender Cubes"
41 type: STANDARD 41 type: LOCATION_ONLY
42 panels { room: "Inside" name: "COLOR" } 42 panels { room: "Inside" name: "COLOR" }
43 location_room: "Inside" 43 location_room: "Inside"
44 location_name: "COLOR"
44} 45}
diff --git a/data/maps/the_ancient/rooms/Inside.txtpb b/data/maps/the_ancient/rooms/Inside.txtpb index d6e8575..3723b2d 100644 --- a/data/maps/the_ancient/rooms/Inside.txtpb +++ b/data/maps/the_ancient/rooms/Inside.txtpb
@@ -5,5 +5,4 @@ panels {
5 clue: "color" 5 clue: "color"
6 answer: "lavender" 6 answer: "lavender"
7 symbols: EXAMPLE 7 symbols: EXAMPLE
8 # TODO: how does this connect to the "lavender_cubes" switch?
9} 8}
diff --git a/data/maps/the_bearer/connections.txtpb b/data/maps/the_bearer/connections.txtpb index 23410f0..ba14d83 100644 --- a/data/maps/the_bearer/connections.txtpb +++ b/data/maps/the_bearer/connections.txtpb
@@ -263,3 +263,8 @@ connections {
263 to_room: "Butterfly Room" 263 to_room: "Butterfly Room"
264 door { name: "Butterfly Entrance" } 264 door { name: "Butterfly Entrance" }
265} 265}
266connections {
267 from_room: "Back Area"
268 to_room: "Tree Entrance"
269 door { name: "Control Center Brown Door" }
270}
diff --git a/data/maps/the_bearer/rooms/Back Area.txtpb b/data/maps/the_bearer/rooms/Back Area.txtpb index 27e175c..b1860de 100644 --- a/data/maps/the_bearer/rooms/Back Area.txtpb +++ b/data/maps/the_bearer/rooms/Back Area.txtpb
@@ -7,12 +7,6 @@ panels {
7 symbols: EXAMPLE 7 symbols: EXAMPLE
8} 8}
9ports { 9ports {
10 name: "TREE"
11 path: "Components/Warps/worldport3"
12 orientation: "north"
13 required_door { name: "Control Center Brown Door" }
14}
15ports {
16 name: "DAEDALUS" 10 name: "DAEDALUS"
17 path: "Components/Warps/worldport2" 11 path: "Components/Warps/worldport2"
18 orientation: "north" 12 orientation: "north"
diff --git a/data/maps/the_bearer/rooms/Tree Entrance.txtpb b/data/maps/the_bearer/rooms/Tree Entrance.txtpb new file mode 100644 index 0000000..97a07da --- /dev/null +++ b/data/maps/the_bearer/rooms/Tree Entrance.txtpb
@@ -0,0 +1,6 @@
1name: "Tree Entrance"
2ports {
3 name: "TREE"
4 path: "Components/Warps/worldport3"
5 orientation: "north"
6}
diff --git a/data/maps/the_congruent/doors.txtpb b/data/maps/the_congruent/doors.txtpb index 7c79c00..a714eba 100644 --- a/data/maps/the_congruent/doors.txtpb +++ b/data/maps/the_congruent/doors.txtpb
@@ -119,7 +119,11 @@ doors {
119} 119}
120doors { 120doors {
121 name: "T Keyholder Blocker" 121 name: "T Keyholder Blocker"
122 type: ITEM_ONLY 122 type: EVENT
123 receivers: "Components/Doors/magenta_enterer3" 123 receivers: "Components/Doors/magenta_enterer3"
124 switches: "lavender_cubes" 124 panels {
125 map: "the_ancient"
126 room: "Inside"
127 name: "COLOR"
128 }
125} 129}
diff --git a/data/maps/the_congruent/rooms/T Keyholder.txtpb b/data/maps/the_congruent/rooms/T Keyholder.txtpb index 360b030..143ea53 100644 --- a/data/maps/the_congruent/rooms/T Keyholder.txtpb +++ b/data/maps/the_congruent/rooms/T Keyholder.txtpb
@@ -2,4 +2,5 @@ name: "T Keyholder"
2keyholders { 2keyholders {
3 name: "T" 3 name: "T"
4 path: "Components/KeyHolders/keyHolderT" 4 path: "Components/KeyHolders/keyHolderT"
5 key: "t"
5} 6}
diff --git a/data/maps/the_darkroom/connections.txtpb b/data/maps/the_darkroom/connections.txtpb index 4093585..1b7ad05 100644 --- a/data/maps/the_darkroom/connections.txtpb +++ b/data/maps/the_darkroom/connections.txtpb
@@ -33,3 +33,18 @@ connections {
33 to_room: "S Room" 33 to_room: "S Room"
34 door { name: "S1 Door" } 34 door { name: "S1 Door" }
35} 35}
36connections {
37 from_room: "First Room"
38 to_room: "Cyan Hallway"
39 door { name: "Colorful Entrance" }
40}
41connections {
42 from_room: "Second Room"
43 to_room: "Congruent Entrance"
44 door { name: "Congruent Entrance" }
45}
46connections {
47 from_room: "First Room"
48 to_room: "Double Sided Entrance"
49 door { name: "Double Sided Entrance" }
50}
diff --git a/data/maps/the_darkroom/doors.txtpb b/data/maps/the_darkroom/doors.txtpb index d7094ae..047c7d0 100644 --- a/data/maps/the_darkroom/doors.txtpb +++ b/data/maps/the_darkroom/doors.txtpb
@@ -2,8 +2,8 @@
2doors { 2doors {
3 name: "Double Letter Panel Blockers" 3 name: "Double Letter Panel Blockers"
4 type: EVENT 4 type: EVENT
5 #receivers: "Panels/Room 1/panel_3/visibilityListener" 5 receivers: "Panels/Room 1/panel_3/visibilityListener"
6 #receivers: "Panels/Room 2/panel_3/visibilityListener" 6 receivers: "Panels/Room 2/panel_3/visibilityListener"
7 double_letters: true 7 double_letters: true
8} 8}
9doors { 9doors {
diff --git a/data/maps/the_darkroom/rooms/Congruent Entrance.txtpb b/data/maps/the_darkroom/rooms/Congruent Entrance.txtpb new file mode 100644 index 0000000..7ea1286 --- /dev/null +++ b/data/maps/the_darkroom/rooms/Congruent Entrance.txtpb
@@ -0,0 +1,7 @@
1name: "Congruent Entrance"
2panel_display_name: "Second Room"
3ports {
4 name: "CONGRUENT"
5 path: "Components/Warps/worldport7"
6 orientation: "east"
7}
diff --git a/data/maps/the_darkroom/rooms/Cyan Hallway.txtpb b/data/maps/the_darkroom/rooms/Cyan Hallway.txtpb new file mode 100644 index 0000000..308efb1 --- /dev/null +++ b/data/maps/the_darkroom/rooms/Cyan Hallway.txtpb
@@ -0,0 +1,7 @@
1name: "Cyan Hallway"
2panel_display_name: "First Room"
3ports {
4 name: "COLORFUL"
5 path: "Components/Warps/worldport8"
6 orientation: "north"
7}
diff --git a/data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb b/data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb new file mode 100644 index 0000000..9d25108 --- /dev/null +++ b/data/maps/the_darkroom/rooms/Double Sided Entrance.txtpb
@@ -0,0 +1,7 @@
1name: "Double Sided Entrance"
2panel_display_name: "First Room"
3ports {
4 name: "DOUBLESIDED"
5 path: "Components/Warps/worldport6"
6 orientation: "east"
7}
diff --git a/data/maps/the_darkroom/rooms/First Room.txtpb b/data/maps/the_darkroom/rooms/First Room.txtpb index c93f5b4..c635757 100644 --- a/data/maps/the_darkroom/rooms/First Room.txtpb +++ b/data/maps/the_darkroom/rooms/First Room.txtpb
@@ -42,15 +42,3 @@ ports {
42 orientation: "north" 42 orientation: "north"
43 required_door { name: "Second Room Entrance" } 43 required_door { name: "Second Room Entrance" }
44} 44}
45ports {
46 name: "COLORFUL"
47 path: "Components/Warps/worldport8"
48 orientation: "north"
49 required_door { name: "Colorful Entrance" }
50}
51ports {
52 name: "DOUBLESIDED"
53 path: "Components/Warps/worldport6"
54 orientation: "east"
55 required_door { name: "Double Sided Entrance" }
56}
diff --git a/data/maps/the_darkroom/rooms/Second Room.txtpb b/data/maps/the_darkroom/rooms/Second Room.txtpb index baeea12..a3964ea 100644 --- a/data/maps/the_darkroom/rooms/Second Room.txtpb +++ b/data/maps/the_darkroom/rooms/Second Room.txtpb
@@ -47,9 +47,3 @@ ports {
47 orientation: "north" 47 orientation: "north"
48 required_door { name: "Third Room Entrance" } 48 required_door { name: "Third Room Entrance" }
49} 49}
50ports {
51 name: "CONGRUENT"
52 path: "Components/Warps/worldport7"
53 orientation: "east"
54 required_door { name: "Congruent Entrance" }
55}
diff --git a/data/maps/the_entry/connections.txtpb b/data/maps/the_entry/connections.txtpb index 6f847da..9813f85 100644 --- a/data/maps/the_entry/connections.txtpb +++ b/data/maps/the_entry/connections.txtpb
@@ -192,10 +192,25 @@ connections {
192connections { 192connections {
193 from_room: "Starting Room" 193 from_room: "Starting Room"
194 to_room: "Repetitive Entrance" 194 to_room: "Repetitive Entrance"
195 door { name: "Repetitive Entrance" } 195 door { name: "Starting Room West Wall North Door" }
196} 196}
197connections { 197connections {
198 from_room: "Lime Room" 198 from_room: "Lime Room"
199 to_room: "White Hallway To Daedalus" 199 to_room: "White Hallway To Daedalus"
200 door { name: "Control Center White Door" } 200 door { name: "Control Center White Door" }
201} 201}
202connections {
203 from_room: "Flipped Second Room"
204 to_room: "Four Rooms Entrance"
205 door { name: "Flipped Second Room Right Door" }
206}
207connections {
208 from_room: "Link Area"
209 to_room: "Liberated Entrance"
210 door { name: "Liberated Entrance" }
211}
212connections {
213 from_room: "Link Area"
214 to_room: "Literate Entrance"
215 door { name: "Literate Entrance" }
216}
diff --git a/data/maps/the_entry/doors.txtpb b/data/maps/the_entry/doors.txtpb index 78ffa52..466f5ce 100644 --- a/data/maps/the_entry/doors.txtpb +++ b/data/maps/the_entry/doors.txtpb
@@ -137,8 +137,10 @@ doors {
137 type: STANDARD 137 type: STANDARD
138 receivers: "Components/Doors/back_left_2" 138 receivers: "Components/Doors/back_left_2"
139 panels { room: "Colored Doors Area" name: "OPEN" answer: "orange" } 139 panels { room: "Colored Doors Area" name: "OPEN" answer: "orange" }
140 # "wall" is supposed to also work. idk man 140 panels { room: "Colored Doors Area" name: "OPEN" answer: "wall" }
141 complete_at: 1
141 location_room: "Colored Doors Area" 142 location_room: "Colored Doors Area"
143 location_name: "OPEN"
142} 144}
143doors { 145doors {
144 name: "Lime Room Entrance" 146 name: "Lime Room Entrance"
@@ -193,7 +195,7 @@ doors {
193 location_room: "Starting Room" 195 location_room: "Starting Room"
194} 196}
195doors { 197doors {
196 name: "Repetitive Entrance" 198 name: "Starting Room West Wall North Door"
197 type: ITEM_ONLY 199 type: ITEM_ONLY
198 receivers: "Components/Doors/Entry/entry_proxied_9" 200 receivers: "Components/Doors/Entry/entry_proxied_9"
199 double_letters: true 201 double_letters: true
diff --git a/data/maps/the_entry/rooms/Flipped Second Room.txtpb b/data/maps/the_entry/rooms/Flipped Second Room.txtpb index 5841ca1..0d518bb 100644 --- a/data/maps/the_entry/rooms/Flipped Second Room.txtpb +++ b/data/maps/the_entry/rooms/Flipped Second Room.txtpb
@@ -21,10 +21,3 @@ paintings {
21 gravity: Y_PLUS 21 gravity: Y_PLUS
22 display_name: "Eye Painting" 22 display_name: "Eye Painting"
23} 23}
24ports {
25 name: "FOUR"
26 path: "Components/Warps/worldport9"
27 orientation: "south"
28 gravity: Y_PLUS
29 required_door { name: "Flipped Second Room Right Door" }
30} \ No newline at end of file
diff --git a/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb b/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb new file mode 100644 index 0000000..689d23e --- /dev/null +++ b/data/maps/the_entry/rooms/Four Rooms Entrance.txtpb
@@ -0,0 +1,7 @@
1name: "Four Rooms Entrance"
2ports {
3 name: "FOUR"
4 path: "Components/Warps/worldport9"
5 orientation: "south"
6 gravity: Y_PLUS
7}
diff --git a/data/maps/the_entry/rooms/Liberated Entrance.txtpb b/data/maps/the_entry/rooms/Liberated Entrance.txtpb new file mode 100644 index 0000000..f0176a0 --- /dev/null +++ b/data/maps/the_entry/rooms/Liberated Entrance.txtpb
@@ -0,0 +1,6 @@
1name: "Liberated Entrance"
2ports {
3 name: "BLUE"
4 path: "worldport8"
5 orientation: "west"
6}
diff --git a/data/maps/the_entry/rooms/Link Area.txtpb b/data/maps/the_entry/rooms/Link Area.txtpb index 689f57a..5b68279 100644 --- a/data/maps/the_entry/rooms/Link Area.txtpb +++ b/data/maps/the_entry/rooms/Link Area.txtpb
@@ -26,15 +26,3 @@ paintings {
26 orientation: "south" 26 orientation: "south"
27 display_name: "Center Painting" 27 display_name: "Center Painting"
28} 28}
29ports {
30 name: "BLUE"
31 path: "worldport8"
32 orientation: "west"
33 required_door { name: "Liberated Entrance" }
34}
35ports {
36 name: "BROWN"
37 path: "worldport9"
38 orientation: "east"
39 required_door { name: "Literate Entrance" }
40} \ No newline at end of file
diff --git a/data/maps/the_entry/rooms/Literate Entrance.txtpb b/data/maps/the_entry/rooms/Literate Entrance.txtpb new file mode 100644 index 0000000..4ec402f --- /dev/null +++ b/data/maps/the_entry/rooms/Literate Entrance.txtpb
@@ -0,0 +1,6 @@
1name: "Literate Entrance"
2ports {
3 name: "BROWN"
4 path: "worldport9"
5 orientation: "east"
6}
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}
29panels { 31panels {
30 name: "HINT" 32 name: "HINT"
diff --git a/data/maps/the_extravagant/rooms/X Plus.txtpb b/data/maps/the_extravagant/rooms/X Plus.txtpb index 89b6da7..a1c4b9d 100644 --- a/data/maps/the_extravagant/rooms/X Plus.txtpb +++ b/data/maps/the_extravagant/rooms/X Plus.txtpb
@@ -23,4 +23,5 @@ paintings {
23keyholders { 23keyholders {
24 name: "M" 24 name: "M"
25 path: "Components/KeyHolders/keyHolderM" 25 path: "Components/KeyHolders/keyHolderM"
26 key: "m"
26} 27}
diff --git a/data/maps/the_gallery/doors.txtpb b/data/maps/the_gallery/doors.txtpb index a7a5d85..adbc766 100644 --- a/data/maps/the_gallery/doors.txtpb +++ b/data/maps/the_gallery/doors.txtpb
@@ -1,7 +1,7 @@
1# The Gallery is interesting because there's so many cross-map requirements. 1# The Gallery is interesting because there's so many cross-map requirements.
2doors { 2doors {
3 name: "Darkroom Painting" 3 name: "Darkroom Painting"
4 type: ITEM_ONLY 4 type: GALLERY_PAINTING
5 #move_paintings { room: "Main Area" name: "DARKROOM" } 5 #move_paintings { room: "Main Area" name: "DARKROOM" }
6 receivers: "Components/Paintings/darkroom/teleportListener" 6 receivers: "Components/Paintings/darkroom/teleportListener"
7 panels { map: "the_darkroom" room: "First Room" name: "BISON" } 7 panels { map: "the_darkroom" room: "First Room" name: "BISON" }
@@ -27,14 +27,14 @@ doors {
27} 27}
28doors { 28doors {
29 name: "Butterfly Painting" 29 name: "Butterfly Painting"
30 type: ITEM_ONLY 30 type: GALLERY_PAINTING
31 #move_paintings { room: "Main Area" name: "BUTTERFLY" } 31 #move_paintings { room: "Main Area" name: "BUTTERFLY" }
32 receivers: "Components/Paintings/butterfly/teleportListener" 32 receivers: "Components/Paintings/butterfly/teleportListener"
33 rooms { map: "the_butterfly" name: "Main Area" } 33 rooms { map: "the_butterfly" name: "Main Area" }
34} 34}
35doors { 35doors {
36 name: "Between Painting" 36 name: "Between Painting"
37 type: ITEM_ONLY 37 type: GALLERY_PAINTING
38 #move_paintings { room: "Main Area" name: "BETWEEN" } 38 #move_paintings { room: "Main Area" name: "BETWEEN" }
39 receivers: "Components/Paintings/between/teleportListener" 39 receivers: "Components/Paintings/between/teleportListener"
40 panels { map: "the_between" room: "Main Area" name: "SUN" } 40 panels { map: "the_between" room: "Main Area" name: "SUN" }
@@ -70,14 +70,14 @@ doors {
70} 70}
71doors { 71doors {
72 name: "Entry Painting" 72 name: "Entry Painting"
73 type: ITEM_ONLY 73 type: GALLERY_PAINTING
74 #move_paintings { room: "Main Area" name: "ENTRY" } 74 #move_paintings { room: "Main Area" name: "ENTRY" }
75 receivers: "Components/Paintings/eyes/teleportListener" 75 receivers: "Components/Paintings/eyes/teleportListener"
76 panels { map: "the_entry" room: "Eye Room" name: "I" } 76 panels { map: "the_entry" room: "Eye Room" name: "I" }
77} 77}
78doors { 78doors {
79 name: "Wise Painting" 79 name: "Wise Painting"
80 type: ITEM_ONLY 80 type: GALLERY_PAINTING
81 #move_paintings { room: "Main Area" name: "WISE" } 81 #move_paintings { room: "Main Area" name: "WISE" }
82 receivers: "Components/Paintings/triangle/teleportListener" 82 receivers: "Components/Paintings/triangle/teleportListener"
83 panels { map: "the_wise" room: "Entry" name: "INK" } 83 panels { map: "the_wise" room: "Entry" name: "INK" }
@@ -105,7 +105,7 @@ doors {
105} 105}
106doors { 106doors {
107 name: "Tree Painting" 107 name: "Tree Painting"
108 type: ITEM_ONLY 108 type: GALLERY_PAINTING
109 #move_paintings { room: "Main Area" name: "TREE" } 109 #move_paintings { room: "Main Area" name: "TREE" }
110 receivers: "Components/Paintings/Clue Maps/tree/teleportListener" 110 receivers: "Components/Paintings/Clue Maps/tree/teleportListener"
111 panels { map: "the_tree" room: "Main Area" name: "COLOR" } 111 panels { map: "the_tree" room: "Main Area" name: "COLOR" }
@@ -142,35 +142,35 @@ doors {
142} 142}
143doors { 143doors {
144 name: "Unyielding Painting" 144 name: "Unyielding Painting"
145 type: ITEM_ONLY 145 type: GALLERY_PAINTING
146 #move_paintings { room: "Main Area" name: "UNYIELDING" } 146 #move_paintings { room: "Main Area" name: "UNYIELDING" }
147 receivers: "Components/Paintings/Clue Maps/unyielding/teleportListener" 147 receivers: "Components/Paintings/Clue Maps/unyielding/teleportListener"
148 rooms { map: "the_unyielding" name: "Digital Entrance" } 148 rooms { map: "the_unyielding" name: "Digital Entrance" }
149} 149}
150doors { 150doors {
151 name: "Graveyard Painting" 151 name: "Graveyard Painting"
152 type: ITEM_ONLY 152 type: GALLERY_PAINTING
153 #move_paintings { room: "Main Area" name: "GRAVEYARD" } 153 #move_paintings { room: "Main Area" name: "GRAVEYARD" }
154 receivers: "Components/Paintings/Endings/grave/teleportListener" 154 receivers: "Components/Paintings/Endings/grave/teleportListener"
155 rooms { map: "the_graveyard" name: "Outside" } 155 rooms { map: "the_graveyard" name: "Outside" }
156} 156}
157doors { 157doors {
158 name: "Control Center Painting" 158 name: "Control Center Painting"
159 type: ITEM_ONLY 159 type: GALLERY_PAINTING
160 #move_paintings { room: "Main Area" name: "CC" } 160 #move_paintings { room: "Main Area" name: "CC" }
161 receivers: "Components/Paintings/Endings/desert/teleportListener" 161 receivers: "Components/Paintings/Endings/desert/teleportListener"
162 rooms { map: "the_impressive" name: "M2 Room" } 162 rooms { map: "the_impressive" name: "M2 Room" }
163} 163}
164doors { 164doors {
165 name: "Tower Painting" 165 name: "Tower Painting"
166 type: ITEM_ONLY 166 type: GALLERY_PAINTING
167 #move_paintings { room: "Main Area" name: "TOWER" } 167 #move_paintings { room: "Main Area" name: "TOWER" }
168 receivers: "Components/Paintings/Endings/red/teleportListener" 168 receivers: "Components/Paintings/Endings/red/teleportListener"
169 rooms { map: "the_tower" name: "First Floor" } 169 rooms { map: "the_tower" name: "First Floor" }
170} 170}
171doors { 171doors {
172 name: "Wondrous Painting" 172 name: "Wondrous Painting"
173 type: ITEM_ONLY 173 type: GALLERY_PAINTING
174 #move_paintings { room: "Main Area" name: "WONDROUS" } 174 #move_paintings { room: "Main Area" name: "WONDROUS" }
175 receivers: "Components/Paintings/Endings/window/teleportListener" 175 receivers: "Components/Paintings/Endings/window/teleportListener"
176 panels { map: "the_wondrous" room: "Entry" name: "WONDER" } 176 panels { map: "the_wondrous" room: "Entry" name: "WONDER" }
@@ -187,42 +187,42 @@ doors {
187} 187}
188doors { 188doors {
189 name: "Rainbow Painting" 189 name: "Rainbow Painting"
190 type: ITEM_ONLY 190 type: GALLERY_PAINTING
191 #move_paintings { room: "Main Area" name: "RAINBOW" } 191 #move_paintings { room: "Main Area" name: "RAINBOW" }
192 receivers: "Components/Paintings/Endings/rainbow/teleportListener" 192 receivers: "Components/Paintings/Endings/rainbow/teleportListener"
193 rooms { map: "daedalus" name: "Rainbow Start" } 193 rooms { map: "daedalus" name: "Rainbow Start" }
194} 194}
195doors { 195doors {
196 name: "Words Painting" 196 name: "Words Painting"
197 type: ITEM_ONLY 197 type: GALLERY_PAINTING
198 #move_paintings { room: "Main Area" name: "WORDS" } 198 #move_paintings { room: "Main Area" name: "WORDS" }
199 receivers: "Components/Paintings/Endings/words/teleportListener" 199 receivers: "Components/Paintings/Endings/words/teleportListener"
200 rooms { map: "the_words" name: "Main Area" } 200 rooms { map: "the_words" name: "Main Area" }
201} 201}
202doors { 202doors {
203 name: "Colorful Painting" 203 name: "Colorful Painting"
204 type: ITEM_ONLY 204 type: GALLERY_PAINTING
205 #move_paintings { room: "Main Area" name: "COLORFUL" } 205 #move_paintings { room: "Main Area" name: "COLORFUL" }
206 receivers: "Components/Paintings/Endings/colorful/teleportListener" 206 receivers: "Components/Paintings/Endings/colorful/teleportListener"
207 rooms { map: "the_colorful" name: "White Room" } 207 rooms { map: "the_colorful" name: "White Room" }
208} 208}
209doors { 209doors {
210 name: "Castle Painting" 210 name: "Castle Painting"
211 type: ITEM_ONLY 211 type: GALLERY_PAINTING
212 #move_paintings { room: "Main Area" name: "CASTLE" } 212 #move_paintings { room: "Main Area" name: "CASTLE" }
213 receivers: "Components/Paintings/Endings/castle/teleportListener" 213 receivers: "Components/Paintings/Endings/castle/teleportListener"
214 rooms { map: "daedalus" name: "Castle" } 214 rooms { map: "daedalus" name: "Castle" }
215} 215}
216doors { 216doors {
217 name: "Sun Temple Painting" 217 name: "Sun Temple Painting"
218 type: ITEM_ONLY 218 type: GALLERY_PAINTING
219 #move_paintings { room: "Main Area" name: "SUNTEMPLE" } 219 #move_paintings { room: "Main Area" name: "SUNTEMPLE" }
220 receivers: "Components/Paintings/Endings/temple/teleportListener" 220 receivers: "Components/Paintings/Endings/temple/teleportListener"
221 rooms { map: "the_sun_temple" name: "Entrance" } 221 rooms { map: "the_sun_temple" name: "Entrance" }
222} 222}
223doors { 223doors {
224 name: "Ancient Painting" 224 name: "Ancient Painting"
225 type: ITEM_ONLY 225 type: GALLERY_PAINTING
226 #move_paintings { room: "Main Area" name: "ANCIENT" } 226 #move_paintings { room: "Main Area" name: "ANCIENT" }
227 receivers: "Components/Paintings/Endings/cubes/teleportListener" 227 receivers: "Components/Paintings/Endings/cubes/teleportListener"
228 rooms { map: "the_ancient" name: "Outside" } 228 rooms { map: "the_ancient" name: "Outside" }
diff --git a/data/maps/the_gallery/rooms/Main Area.txtpb b/data/maps/the_gallery/rooms/Main Area.txtpb index 5ba6b25..bc1606d 100644 --- a/data/maps/the_gallery/rooms/Main Area.txtpb +++ b/data/maps/the_gallery/rooms/Main Area.txtpb
@@ -2,6 +2,7 @@ name: "Main Area"
2keyholders { 2keyholders {
3 name: "P" 3 name: "P"
4 path: "Components/KeyHolders/keyHolderP" 4 path: "Components/KeyHolders/keyHolderP"
5 key: "p"
5} 6}
6paintings { 7paintings {
7 name: "OWL" 8 name: "OWL"
diff --git a/data/maps/the_graveyard/doors.txtpb b/data/maps/the_graveyard/doors.txtpb index 5e5e929..a10d8f6 100644 --- a/data/maps/the_graveyard/doors.txtpb +++ b/data/maps/the_graveyard/doors.txtpb
@@ -19,5 +19,7 @@ doors {
19doors { 19doors {
20 name: "Double Letters" 20 name: "Double Letters"
21 type: EVENT 21 type: EVENT
22 receivers: "Panels/panel_3/teleportListener"
23 receivers: "Components/Paintings/omrt/teleportListener"
22 double_letters: true 24 double_letters: true
23} 25}
diff --git a/data/maps/the_great/doors.txtpb b/data/maps/the_great/doors.txtpb index dc25128..abbbc11 100644 --- a/data/maps/the_great/doors.txtpb +++ b/data/maps/the_great/doors.txtpb
@@ -49,6 +49,7 @@ doors {
49 type: LOCATION_ONLY 49 type: LOCATION_ONLY
50 panels { room: "West Side" name: "ERASE" } 50 panels { room: "West Side" name: "ERASE" }
51 location_room: "West Side" 51 location_room: "West Side"
52 location_name: "ERASE"
52} 53}
53doors { 54doors {
54 name: "Control Center Purple Door" 55 name: "Control Center Purple Door"
@@ -65,7 +66,7 @@ doors {
65doors { 66doors {
66 name: "Control Center Red Door" 67 name: "Control Center Red Door"
67 type: CONTROL_CENTER_COLOR 68 type: CONTROL_CENTER_COLOR
68 receivers: "Components/Doors/Gates/Gate" 69 receivers: "Components/Doors/entry_18"
69 control_center_color: "red" 70 control_center_color: "red"
70} 71}
71doors { 72doors {
@@ -470,9 +471,13 @@ doors {
470} 471}
471doors { 472doors {
472 name: "Lavender Cube" 473 name: "Lavender Cube"
473 type: ITEM_ONLY 474 type: EVENT
474 receivers: "Components/Doors/entry_28" 475 receivers: "Components/Doors/entry_28"
475 switches: "lavender_cubes" 476 panels {
477 map: "the_ancient"
478 room: "Inside"
479 name: "COLOR"
480 }
476} 481}
477doors { 482doors {
478 name: "Zero Entrance" 483 name: "Zero Entrance"
@@ -497,3 +502,14 @@ doors {
497 panels { room: "Back Area" name: "PAINTING" } 502 panels { room: "Back Area" name: "PAINTING" }
498 location_room: "Back Area" 503 location_room: "Back Area"
499} 504}
505doors {
506 name: "Cyan Doors"
507 type: EVENT
508 receivers: "Panels/General/entry_7/teleportListener"
509 double_letters: true
510}
511doors {
512 name: "Partial Entrance"
513 type: EVENT
514 panels { room: "West Side" name: "CLUE" }
515}
diff --git a/data/maps/the_great/rooms/Main Area.txtpb b/data/maps/the_great/rooms/Main Area.txtpb index 18bcf9b..82ec48c 100644 --- a/data/maps/the_great/rooms/Main Area.txtpb +++ b/data/maps/the_great/rooms/Main Area.txtpb
@@ -111,6 +111,7 @@ panels {
111 answer: "high" 111 answer: "high"
112 symbols: SUN 112 symbols: SUN
113 symbols: ZERO 113 symbols: ZERO
114 required_door { name: "Cyan Doors" }
114} 115}
115panels { 116panels {
116 name: "CURT" 117 name: "CURT"
diff --git a/data/maps/the_great/rooms/North Landscape.txtpb b/data/maps/the_great/rooms/North Landscape.txtpb index f0fde77..fb11c42 100644 --- a/data/maps/the_great/rooms/North Landscape.txtpb +++ b/data/maps/the_great/rooms/North Landscape.txtpb
@@ -52,6 +52,7 @@ panels {
52keyholders { 52keyholders {
53 name: "X" 53 name: "X"
54 path: "Components/KeyHolders/keyHolderX" 54 path: "Components/KeyHolders/keyHolderX"
55 key: "x"
55} 56}
56ports { 57ports {
57 name: "INVISIBLE" 58 name: "INVISIBLE"
diff --git a/data/maps/the_great/rooms/Question Room What.txtpb b/data/maps/the_great/rooms/Question Room What.txtpb index a806366..a61ccef 100644 --- a/data/maps/the_great/rooms/Question Room What.txtpb +++ b/data/maps/the_great/rooms/Question Room What.txtpb
@@ -6,11 +6,11 @@ panels {
6 clue: "question" 6 clue: "question"
7 answer: "what" 7 answer: "what"
8 symbols: EXAMPLE 8 symbols: EXAMPLE
9 proxies { answer: "why" path: "Panels/Question Proxies/question_4_proxied" } 9 proxies { answer: "what" path: "Panels/Question Proxies/question_4_proxied" }
10 proxies { answer: "who" path: "Panels/Question Proxies/question_4_proxied2" } 10 proxies { answer: "why" path: "Panels/Question Proxies/question_4_proxied2" }
11 proxies { answer: "where" path: "Panels/Question Proxies/question_4_proxied3" } 11 proxies { answer: "when" path: "Panels/Question Proxies/question_4_proxied3" }
12 proxies { answer: "how" path: "Panels/Question Proxies/question_4_proxied4" } 12 proxies { answer: "how" path: "Panels/Question Proxies/question_4_proxied4" }
13 proxies { answer: "what" path: "Panels/Question Proxies/question_4_proxied5" } 13 proxies { answer: "who" path: "Panels/Question Proxies/question_4_proxied5" }
14 proxies { answer: "when" path: "Panels/Question Proxies/question_4_proxied6" } 14 proxies { answer: "where" path: "Panels/Question Proxies/question_4_proxied6" }
15 display_name: "QUESTION (What)" 15 display_name: "QUESTION (What)"
16} 16}
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_hive/rooms/Main Area.txtpb b/data/maps/the_hive/rooms/Main Area.txtpb index 0f73682..013390a 100644 --- a/data/maps/the_hive/rooms/Main Area.txtpb +++ b/data/maps/the_hive/rooms/Main Area.txtpb
@@ -268,6 +268,7 @@ panels {
268keyholders { 268keyholders {
269 name: "B" 269 name: "B"
270 path: "Components/KeyHolders/keyHolderB" 270 path: "Components/KeyHolders/keyHolderB"
271 key: "b"
271} 272}
272ports { 273ports {
273 name: "DAED1" 274 name: "DAED1"
diff --git a/data/maps/the_jubilant/rooms/Side Area.txtpb b/data/maps/the_jubilant/rooms/Side Area.txtpb index e924762..807f044 100644 --- a/data/maps/the_jubilant/rooms/Side Area.txtpb +++ b/data/maps/the_jubilant/rooms/Side Area.txtpb
@@ -38,4 +38,5 @@ panels {
38keyholders { 38keyholders {
39 name: "J" 39 name: "J"
40 path: "Components/KeyHolders/keyHolderJ" 40 path: "Components/KeyHolders/keyHolderJ"
41 key: "j"
41} 42}
diff --git a/data/maps/the_linear/doors.txtpb b/data/maps/the_linear/doors.txtpb index 63d8ae8..9a57158 100644 --- a/data/maps/the_linear/doors.txtpb +++ b/data/maps/the_linear/doors.txtpb
@@ -10,4 +10,5 @@ doors {
10 panels { room: "Room" name: "INTO" } 10 panels { room: "Room" name: "INTO" }
11 panels { room: "Room" name: "NOR" } 11 panels { room: "Room" name: "NOR" }
12 location_room: "Room" 12 location_room: "Room"
13 location_name: "Gravestone"
13} 14}
diff --git a/data/maps/the_nuanced/rooms/Main Room.txtpb b/data/maps/the_nuanced/rooms/Main Room.txtpb index 8da3b5f..da89bd8 100644 --- a/data/maps/the_nuanced/rooms/Main Room.txtpb +++ b/data/maps/the_nuanced/rooms/Main Room.txtpb
@@ -112,4 +112,5 @@ ports {
112keyholders { 112keyholders {
113 name: "S" 113 name: "S"
114 path: "Components/KeyHolders/keyHolderS" 114 path: "Components/KeyHolders/keyHolderS"
115 key: "s"
115} 116}
diff --git a/data/maps/the_owl/connections.txtpb b/data/maps/the_owl/connections.txtpb index 2bd2380..cb4bee3 100644 --- a/data/maps/the_owl/connections.txtpb +++ b/data/maps/the_owl/connections.txtpb
@@ -10,10 +10,20 @@ connections {
10} 10}
11connections { 11connections {
12 from_room: "R2C2 Bottom" 12 from_room: "R2C2 Bottom"
13 to_room: "R2C2 Top"
14 door { name: "Sky Owl" }
15}
16connections {
17 from_room: "R2C2 Bottom"
13 to_room: "Connected Area" 18 to_room: "Connected Area"
14 door { name: "Gray Owl" } 19 door { name: "Gray Owl" }
15} 20}
16connections { 21connections {
22 from_room: "R2C2 Bottom"
23 to_room: "Connected Area"
24 door { name: "Sky Owl" }
25}
26connections {
17 from_room: "R2C3 Bottom" 27 from_room: "R2C3 Bottom"
18 to_room: "Connected Area" 28 to_room: "Connected Area"
19 oneway: true 29 oneway: true
@@ -45,6 +55,11 @@ connections {
45} 55}
46connections { 56connections {
47 from_room: "Connected Area" 57 from_room: "Connected Area"
58 to_room: "R2C3 Bottom"
59 door { name: "Sky Owl" }
60}
61connections {
62 from_room: "Connected Area"
48 to_room: "Magenta Hallway" 63 to_room: "Magenta Hallway"
49 door { name: "Control Center Magenta Door" } 64 door { name: "Control Center Magenta Door" }
50} 65}
@@ -70,6 +85,11 @@ connections {
70} 85}
71connections { 86connections {
72 from_room: "Connected Area" 87 from_room: "Connected Area"
88 to_room: "R1C4 Left"
89 door { name: "Sky Owl" }
90}
91connections {
92 from_room: "Connected Area"
73 to_room: "R2C1 Left" 93 to_room: "R2C1 Left"
74 door { name: "Sky Top Doors" } 94 door { name: "Sky Top Doors" }
75} 95}
@@ -84,6 +104,11 @@ connections {
84 door { name: "Gray Owl" } 104 door { name: "Gray Owl" }
85} 105}
86connections { 106connections {
107 from_room: "Connected Area"
108 to_room: "R2C1 Left"
109 door { name: "Sky Owl" }
110}
111connections {
87 from { 112 from {
88 painting { 113 painting {
89 room: "Connected Area" 114 room: "Connected Area"
diff --git a/data/maps/the_owl/doors.txtpb b/data/maps/the_owl/doors.txtpb index 3d2d055..9254c2a 100644 --- a/data/maps/the_owl/doors.txtpb +++ b/data/maps/the_owl/doors.txtpb
@@ -233,20 +233,20 @@ doors {
233doors { 233doors {
234 name: "Gray Panel" 234 name: "Gray Panel"
235 type: EVENT 235 type: EVENT
236 # TODO: Is it okay to have an event with an in-game effect? 236 #receivers: "Panels/Colors/owl_2/animationListener2"
237 receivers: "Panels/Colors/owl_2/animationListener2" 237 panels { room: "Connected Area" name: "RANGE" }
238 doors { name: "Orange Owl" } 238 panels { room: "Connected Area" name: "WHITE" }
239 doors { name: "Black Owl" } 239 panels { room: "Blue Room" name: "SKY" }
240 doors { name: "Blue Owl" }
241} 240}
242doors { 241doors {
243 name: "Owl Painting" 242 name: "Owl Painting"
244 type: EVENT 243 type: EVENT
245 #move_paintings { room: "Connected Area" name: "OWL" } 244 #move_paintings { room: "Connected Area" name: "OWL" }
246 #receivers: "Components/Paintings/owl/teleportListener" 245 #receivers: "Components/Paintings/owl/teleportListener"
247 doors { name: "Orange Owl" } 246 panels { room: "R2C1 Left" name: "DUSKY" }
248 doors { name: "Black Owl" } 247 panels { room: "R2C2 Top" name: "RAY" }
249 doors { name: "Blue Owl" } 248 panels { room: "Connected Area" name: "RANGE" }
250 doors { name: "White Owl" } 249 panels { room: "R2C3 Bottom" name: "BLACK" }
251 doors { name: "Sky Owl" } 250 panels { room: "Connected Area" name: "WHITE" }
251 panels { room: "Blue Room" name: "SKY" }
252} 252}
diff --git a/data/maps/the_parthenon/doors.txtpb b/data/maps/the_parthenon/doors.txtpb index bb57d12..1161917 100644 --- a/data/maps/the_parthenon/doors.txtpb +++ b/data/maps/the_parthenon/doors.txtpb
@@ -1,12 +1,24 @@
1doors { 1doors {
2 name: "Double Letters" 2 name: "Double Letters"
3 type: EVENT 3 type: EVENT
4 receivers: "Components/Doors/entry_11"
5 receivers: "Components/Doors/entry_5"
6 receivers: "Components/Doors/entry_6"
7 receivers: "Components/Doors/entry_7"
8 receivers: "Components/Doors/entry_8"
9 receivers: "Components/Doors/entry_9"
10 receivers: "Components/Doors/entry_10"
4 double_letters: true 11 double_letters: true
5} 12}
6doors { 13doors {
7 name: "Lavender Cubes" 14 name: "Lavender Cubes"
8 type: EVENT 15 type: EVENT
9 switches: "lavender_cubes" 16 receivers: "Components/Doors/entry_3"
17 panels {
18 map: "the_ancient"
19 room: "Inside"
20 name: "COLOR"
21 }
10} 22}
11doors { 23doors {
12 name: "K2 Door" 24 name: "K2 Door"
diff --git a/data/maps/the_parthenon/rooms/U Keyholder.txtpb b/data/maps/the_parthenon/rooms/U Keyholder.txtpb index 8248df8..0a5c31b 100644 --- a/data/maps/the_parthenon/rooms/U Keyholder.txtpb +++ b/data/maps/the_parthenon/rooms/U Keyholder.txtpb
@@ -2,4 +2,5 @@ name: "U Keyholder"
2keyholders { 2keyholders {
3 name: "U" 3 name: "U"
4 path: "Components/KeyHolders/keyHolderU" 4 path: "Components/KeyHolders/keyHolderU"
5 key: "u"
5} 6}
diff --git a/data/maps/the_partial/rooms/Obverse Side.txtpb b/data/maps/the_partial/rooms/Obverse Side.txtpb index 75cd9bb..c0ce04b 100644 --- a/data/maps/the_partial/rooms/Obverse Side.txtpb +++ b/data/maps/the_partial/rooms/Obverse Side.txtpb
@@ -106,6 +106,7 @@ keyholders {
106 # This is one of the ones that's misnamed within the game. 106 # This is one of the ones that's misnamed within the game.
107 name: "L" 107 name: "L"
108 path: "Components/KeyHolders/keyHolderI" 108 path: "Components/KeyHolders/keyHolderI"
109 key: "l"
109} 110}
110paintings { 111paintings {
111 name: "F" 112 name: "F"
diff --git a/data/maps/the_quiet/rooms/Keyholder Room.txtpb b/data/maps/the_quiet/rooms/Keyholder Room.txtpb index d0f2677..d3cab73 100644 --- a/data/maps/the_quiet/rooms/Keyholder Room.txtpb +++ b/data/maps/the_quiet/rooms/Keyholder Room.txtpb
@@ -2,4 +2,5 @@ name: "Keyholder Room"
2keyholders { 2keyholders {
3 name: "Q" 3 name: "Q"
4 path: "Components/KeyHolders/keyHolderQ" 4 path: "Components/KeyHolders/keyHolderQ"
5 key: "q"
5} 6}
diff --git a/data/maps/the_repetitive/connections.txtpb b/data/maps/the_repetitive/connections.txtpb index 2b115a9..0afe72d 100644 --- a/data/maps/the_repetitive/connections.txtpb +++ b/data/maps/the_repetitive/connections.txtpb
@@ -6,7 +6,7 @@ connections {
6connections { 6connections {
7 from_room: "Main Room" 7 from_room: "Main Room"
8 to_room: "Plaza Connector" 8 to_room: "Plaza Connector"
9 door { name: "Plaza Entrance" } 9 door { name: "Black Hallway" }
10 oneway: true 10 oneway: true
11} 11}
12connections { 12connections {
diff --git a/data/maps/the_repetitive/doors.txtpb b/data/maps/the_repetitive/doors.txtpb index 9e63c1d..d964928 100644 --- a/data/maps/the_repetitive/doors.txtpb +++ b/data/maps/the_repetitive/doors.txtpb
@@ -6,7 +6,7 @@ doors {
6 location_room: "Main Room" 6 location_room: "Main Room"
7} 7}
8doors { 8doors {
9 name: "Plaza Entrance" 9 name: "Black Hallway"
10 type: STANDARD 10 type: STANDARD
11 receivers: "Components/Doors/Door12" 11 receivers: "Components/Doors/Door12"
12 panels { room: "Main Room" name: "I" } 12 panels { room: "Main Room" name: "I" }
@@ -194,3 +194,9 @@ doors {
194 panels { room: "Yellow Room" name: "ASSESSES" } 194 panels { room: "Yellow Room" name: "ASSESSES" }
195 panels { room: "Yellow Room" name: "TINTING" } 195 panels { room: "Yellow Room" name: "TINTING" }
196} 196}
197doors {
198 name: "Anti-Collectable"
199 type: LOCATION_ONLY
200 senders: "Components/Collectables/anticollectable"
201 location_room: "Anti Room"
202}
diff --git a/data/maps/the_repetitive/metadata.txtpb b/data/maps/the_repetitive/metadata.txtpb index 6f5c459..76a0f50 100644 --- a/data/maps/the_repetitive/metadata.txtpb +++ b/data/maps/the_repetitive/metadata.txtpb
@@ -1,10 +1,6 @@
1display_name: "The Repetitive" 1display_name: "The Repetitive"
2# The anti-collectable doesn't fit into our system right now so let's ignore it.
3excluded_nodes: "Components/Collectables/anticollectable"
4# These paintings are directly above/behind panels and thus can't be entered. 2# These paintings are directly above/behind panels and thus can't be entered.
5excluded_nodes: "Meshes/eyeRed3" 3excluded_nodes: "Meshes/eyeRed3"
6excluded_nodes: "Meshes/eyeRed4" 4excluded_nodes: "Meshes/eyeRed4"
7# I do not know what this is.
8excluded_nodes: "Components/Doors/Door3/Hinge/panel_i"
9# This has something to do with the magenta room entrance proxy panel. 5# This has something to do with the magenta room entrance proxy panel.
10excluded_nodes: "Panels/Eval/panel_26_proxyied_fake" 6excluded_nodes: "Panels/Eval/panel_26_proxyied_fake"
diff --git a/data/maps/the_repetitive/rooms/Anti Room.txtpb b/data/maps/the_repetitive/rooms/Anti Room.txtpb index 641fede..65a99ff 100644 --- a/data/maps/the_repetitive/rooms/Anti Room.txtpb +++ b/data/maps/the_repetitive/rooms/Anti Room.txtpb
@@ -1,5 +1,4 @@
1name: "Anti Room" 1name: "Anti Room"
2# Ignore the collectible. The mod should remove it and the back wall too.
3panels { 2panels {
4 name: "HA (1)" 3 name: "HA (1)"
5 path: "Panels/Entry/panel_7" 4 path: "Panels/Entry/panel_7"
@@ -38,9 +37,17 @@ panels {
38 symbols: EXAMPLE 37 symbols: EXAMPLE
39} 38}
40panels { 39panels {
41 name: "EYE" 40 name: "EYE (1)"
42 path: "Panels/Entry/panel4" 41 path: "Panels/Entry/panel4"
43 clue: "eye" 42 clue: "eye"
44 answer: "iris" 43 answer: "iris"
45 symbols: BOXES 44 symbols: BOXES
46} 45}
46panels {
47 # This appears after grabbing the anti-collectable.
48 name: "EYE (2)"
49 path: "Components/Doors/Door3/Hinge/panel_i"
50 clue: "eye"
51 answer: "i"
52 symbols: ZERO
53}
diff --git a/data/maps/the_shop/rooms/Main Area.txtpb b/data/maps/the_shop/rooms/Main Area.txtpb index d45e0f0..db93fe1 100644 --- a/data/maps/the_shop/rooms/Main Area.txtpb +++ b/data/maps/the_shop/rooms/Main Area.txtpb
@@ -160,4 +160,5 @@ ports {
160keyholders { 160keyholders {
161 name: "N" 161 name: "N"
162 path: "Components/KeyHolders/keyHolderN" 162 path: "Components/KeyHolders/keyHolderN"
163 key: "n"
163} 164}
diff --git a/data/maps/the_symbolic/doors.txtpb b/data/maps/the_symbolic/doors.txtpb index e84811e..5a443e7 100644 --- a/data/maps/the_symbolic/doors.txtpb +++ b/data/maps/the_symbolic/doors.txtpb
@@ -1,62 +1,55 @@
1doors { 1doors {
2 name: "White Door" 2 name: "White Door"
3 type: STANDARD 3 type: EVENT
4 receivers: "Components/Doors/Door18" 4 #receivers: "Components/Doors/Door18"
5 panels { room: "White Room" name: "WRITE" } 5 panels { room: "White Room" name: "WRITE" }
6 location_room: "White Room"
7} 6}
8doors { 7doors {
9 name: "Black Door" 8 name: "Black Door"
10 type: STANDARD 9 type: EVENT
11 receivers: "Components/Doors/Door19" 10 #receivers: "Components/Doors/Door19"
12 panels { room: "Black Room" name: "HERE" } 11 panels { room: "Black Room" name: "HERE" }
13 location_room: "Black Room"
14} 12}
15doors { 13doors {
16 name: "Red Door" 14 name: "Red Door"
17 type: STANDARD 15 type: EVENT
18 receivers: "Components/Doors/Door20" 16 #receivers: "Components/Doors/Door20"
19 panels { room: "Red Room" name: "SYNONYM" } 17 panels { room: "Red Room" name: "SYNONYM" }
20 location_room: "Red Room"
21} 18}
22doors { 19doors {
23 name: "Blue Door" 20 name: "Blue Door"
24 type: STANDARD 21 type: EVENT
25 receivers: "Components/Doors/Door21" 22 #receivers: "Components/Doors/Door21"
26 panels { room: "Blue Room" name: "DEPLETE" } 23 panels { room: "Blue Room" name: "DEPLETE" }
27 location_room: "Blue Room"
28} 24}
29doors { 25doors {
30 name: "Green Door" 26 name: "Green Door"
31 type: STANDARD 27 type: EVENT
32 receivers: "Components/Doors/Door22" 28 #receivers: "Components/Doors/Door22"
33 panels { room: "Green Room" name: "INERT" } 29 panels { room: "Green Room" name: "INERT" }
34 location_room: "Green Room"
35} 30}
36doors { 31doors {
37 name: "Yellow Door" 32 name: "Yellow Door"
38 type: STANDARD 33 type: EVENT
39 receivers: "Components/Doors/Door23" 34 #receivers: "Components/Doors/Door23"
40 panels { room: "Yellow Room" name: "WHOLE" } 35 panels { room: "Yellow Room" name: "WHOLE" }
41 location_room: "Yellow Room"
42} 36}
43doors { 37doors {
44 name: "Purple Door" 38 name: "Purple Door"
45 type: STANDARD 39 type: EVENT
46 receivers: "Components/Doors/Door24" 40 #receivers: "Components/Doors/Door24"
47 panels { room: "Purple Room" name: "TIME" } 41 panels { room: "Purple Room" name: "TIME" }
48 location_room: "Purple Room"
49} 42}
50doors { 43doors {
51 name: "Orange Door" 44 name: "Orange Door"
52 type: STANDARD 45 type: EVENT
53 receivers: "Components/Doors/Door25" 46 #receivers: "Components/Doors/Door25"
54 panels { room: "Orange Room" name: "YOUNG" } 47 panels { room: "Orange Room" name: "YOUNG" }
55 location_room: "Orange Room"
56} 48}
57doors { 49doors {
58 name: "Tutorial Door" 50 name: "Tutorial Door"
59 type: EVENT 51 type: ITEM_ONLY
52 receivers: "Components/Doors/Door"
60 panels { room: "Tutorial" name: "<- (1)" } 53 panels { room: "Tutorial" name: "<- (1)" }
61 panels { room: "Tutorial" name: "<- (2)" } 54 panels { room: "Tutorial" name: "<- (2)" }
62 panels { room: "Tutorial" name: "<- (3)" } 55 panels { room: "Tutorial" name: "<- (3)" }
diff --git a/data/maps/the_talented/rooms/Main Area.txtpb b/data/maps/the_talented/rooms/Main Area.txtpb index cc3e222..f99be48 100644 --- a/data/maps/the_talented/rooms/Main Area.txtpb +++ b/data/maps/the_talented/rooms/Main Area.txtpb
@@ -107,6 +107,7 @@ panels {
107keyholders { 107keyholders {
108 name: "Y" 108 name: "Y"
109 path: "Components/KeyHolders/keyHolderY" 109 path: "Components/KeyHolders/keyHolderY"
110 key: "y"
110} 111}
111ports { 112ports {
112 name: "GREAT" 113 name: "GREAT"
diff --git a/data/maps/the_tenacious/rooms/Main Area.txtpb b/data/maps/the_tenacious/rooms/Main Area.txtpb index 8190827..18356e7 100644 --- a/data/maps/the_tenacious/rooms/Main Area.txtpb +++ b/data/maps/the_tenacious/rooms/Main Area.txtpb
@@ -2,4 +2,5 @@ name: "Main Area"
2keyholders { 2keyholders {
3 name: "K" 3 name: "K"
4 path: "Components/KeyHolders/keyHolderK" 4 path: "Components/KeyHolders/keyHolderK"
5 key: "k"
5} 6}
diff --git a/data/maps/the_three_doors/doors.txtpb b/data/maps/the_three_doors/doors.txtpb index 99fbcee..5ae9d90 100644 --- a/data/maps/the_three_doors/doors.txtpb +++ b/data/maps/the_three_doors/doors.txtpb
@@ -50,4 +50,5 @@ doors {
50 panels { room: "Dead End Room" name: "DEAD" } 50 panels { room: "Dead End Room" name: "DEAD" }
51 panels { room: "Dead End Room" name: "END" } 51 panels { room: "Dead End Room" name: "END" }
52 location_room: "Loose Strings Room" 52 location_room: "Loose Strings Room"
53 location_name: "Gravestone"
53} 54}
diff --git a/data/maps/the_tree/doors.txtpb b/data/maps/the_tree/doors.txtpb index b62a881..6cb4086 100644 --- a/data/maps/the_tree/doors.txtpb +++ b/data/maps/the_tree/doors.txtpb
@@ -38,4 +38,5 @@ doors {
38 panels { room: "Main Area" name: "SMALL (3)" } 38 panels { room: "Main Area" name: "SMALL (3)" }
39 panels { room: "Main Area" name: "SPRINKLE" } 39 panels { room: "Main Area" name: "SPRINKLE" }
40 location_room: "Main Area" 40 location_room: "Main Area"
41 location_name: "Gravestone"
41} 42}
diff --git a/data/maps/the_unkempt/doors.txtpb b/data/maps/the_unkempt/doors.txtpb index 9a13c82..2349913 100644 --- a/data/maps/the_unkempt/doors.txtpb +++ b/data/maps/the_unkempt/doors.txtpb
@@ -21,6 +21,7 @@ doors {
21doors { 21doors {
22 name: "Cyan Doors" 22 name: "Cyan Doors"
23 type: EVENT 23 type: EVENT
24 receivers: "Components/Doors/entry_12"
24 double_letters: true 25 double_letters: true
25} 26}
26doors { 27doors {
@@ -67,6 +68,7 @@ doors {
67 type: CONTROL_CENTER_COLOR 68 type: CONTROL_CENTER_COLOR
68 receivers: "Components/Doors/entry_6" 69 receivers: "Components/Doors/entry_6"
69 receivers: "Components/Doors/entry_13" 70 receivers: "Components/Doors/entry_13"
71 receivers: "Panels/Assorted/panel_1/teleportListener"
70 control_center_color: "orange" 72 control_center_color: "orange"
71 double_letters: true 73 double_letters: true
72} 74}
diff --git a/data/maps/the_unkempt/rooms/Main Area.txtpb b/data/maps/the_unkempt/rooms/Main Area.txtpb index ed3ce21..b5d29c4 100644 --- a/data/maps/the_unkempt/rooms/Main Area.txtpb +++ b/data/maps/the_unkempt/rooms/Main Area.txtpb
@@ -212,6 +212,7 @@ panels {
212keyholders { 212keyholders {
213 name: "I" 213 name: "I"
214 path: "Components/KeyHolders/keyHolderL" 214 path: "Components/KeyHolders/keyHolderL"
215 key: "i"
215} 216}
216ports { 217ports {
217 name: "GREAT" 218 name: "GREAT"
diff --git a/data/maps/the_unkempt/rooms/Right Area.txtpb b/data/maps/the_unkempt/rooms/Right Area.txtpb index 1475fb0..03d7cea 100644 --- a/data/maps/the_unkempt/rooms/Right Area.txtpb +++ b/data/maps/the_unkempt/rooms/Right Area.txtpb
@@ -159,6 +159,5 @@ panels {
159 clue: "color" 159 clue: "color"
160 answer: "orange" 160 answer: "orange"
161 symbols: EXAMPLE 161 symbols: EXAMPLE
162 # TODO: This is hidden in-game until double letters are unlocked AND "orange" 162 required_door { name: "Control Center Orange Door" }
163 # is entered in the control center.
164} 163}
diff --git a/data/maps/the_unkempt/rooms/V Keyholder.txtpb b/data/maps/the_unkempt/rooms/V Keyholder.txtpb index 0906b2e..8a4941d 100644 --- a/data/maps/the_unkempt/rooms/V Keyholder.txtpb +++ b/data/maps/the_unkempt/rooms/V Keyholder.txtpb
@@ -2,4 +2,5 @@ name: "V Keyholder"
2keyholders { 2keyholders {
3 name: "V" 3 name: "V"
4 path: "Components/KeyHolders/keyHolderV" 4 path: "Components/KeyHolders/keyHolderV"
5 key: "v"
5} 6}
diff --git a/data/maps/the_unkempt/rooms/W Keyholder.txtpb b/data/maps/the_unkempt/rooms/W Keyholder.txtpb index ae367b2..e16f997 100644 --- a/data/maps/the_unkempt/rooms/W Keyholder.txtpb +++ b/data/maps/the_unkempt/rooms/W Keyholder.txtpb
@@ -2,4 +2,5 @@ name: "W Keyholder"
2keyholders { 2keyholders {
3 name: "W" 3 name: "W"
4 path: "Components/KeyHolders/keyHolderW" 4 path: "Components/KeyHolders/keyHolderW"
5 key: "w"
5} 6}
diff --git a/data/maps/the_unyielding/doors.txtpb b/data/maps/the_unyielding/doors.txtpb index b9d0d77..a3c3999 100644 --- a/data/maps/the_unyielding/doors.txtpb +++ b/data/maps/the_unyielding/doors.txtpb
@@ -499,5 +499,8 @@ doors {
499doors { 499doors {
500 name: "Cyan Doors" 500 name: "Cyan Doors"
501 type: EVENT 501 type: EVENT
502 receivers: "Components/Doors/entry_4"
503 receivers: "Panels/Miscellaneous/entry_2/teleportListener"
504 receivers: "Panels/Miscellaneous/entry_3/teleportListener"
502 double_letters: true 505 double_letters: true
503} 506}
diff --git a/data/maps/the_unyielding/rooms/Yellow Left.txtpb b/data/maps/the_unyielding/rooms/Yellow Left.txtpb index 9c7d023..192d901 100644 --- a/data/maps/the_unyielding/rooms/Yellow Left.txtpb +++ b/data/maps/the_unyielding/rooms/Yellow Left.txtpb
@@ -5,4 +5,5 @@ panels {
5 clue: "sickness" 5 clue: "sickness"
6 answer: "health" 6 answer: "health"
7 symbols: SUN 7 symbols: SUN
8 required_door { name: "Cyan Doors" }
8} 9}
diff --git a/data/maps/the_unyielding/rooms/Yellow Right.txtpb b/data/maps/the_unyielding/rooms/Yellow Right.txtpb index 0599f29..d554c73 100644 --- a/data/maps/the_unyielding/rooms/Yellow Right.txtpb +++ b/data/maps/the_unyielding/rooms/Yellow Right.txtpb
@@ -5,4 +5,5 @@ panels {
5 clue: "health" 5 clue: "health"
6 answer: "sickness" 6 answer: "sickness"
7 symbols: SUN 7 symbols: SUN
8 required_door { name: "Cyan Doors" }
8} 9}
diff --git a/data/metadata.txtpb b/data/metadata.txtpb new file mode 100644 index 0000000..3f2c08d --- /dev/null +++ b/data/metadata.txtpb
@@ -0,0 +1,50 @@
1version: 4
2# Filler item.
3special_names: "A Job Well Done"
4# Symbol items.
5special_names: "Age Symbol"
6special_names: "Anagram Symbol"
7special_names: "Boxes Symbol"
8special_names: "Cross Symbol"
9special_names: "Eval Symbol"
10special_names: "Example Symbol"
11special_names: "Gender Symbol"
12special_names: "Job Symbol"
13special_names: "Lingo Symbol"
14special_names: "Null Symbol"
15special_names: "Planet Symbol"
16special_names: "Pyramid Symbol"
17special_names: "Question Symbol"
18special_names: "Sound Symbol"
19special_names: "Sparkles Symbol"
20special_names: "Stars Symbol"
21special_names: "Sun Symbol"
22special_names: "Sweet Symbol"
23special_names: "Zero Symbol"
24# Anti collectable traps
25special_names: "Anti A"
26special_names: "Anti B"
27special_names: "Anti C"
28special_names: "Anti D"
29special_names: "Anti E"
30special_names: "Anti F"
31special_names: "Anti G"
32special_names: "Anti H"
33special_names: "Anti I"
34special_names: "Anti J"
35special_names: "Anti K"
36special_names: "Anti L"
37special_names: "Anti M"
38special_names: "Anti N"
39special_names: "Anti O"
40special_names: "Anti P"
41special_names: "Anti Q"
42special_names: "Anti R"
43special_names: "Anti S"
44special_names: "Anti T"
45special_names: "Anti U"
46special_names: "Anti V"
47special_names: "Anti W"
48special_names: "Anti X"
49special_names: "Anti Y"
50special_names: "Anti Z"
diff --git a/data/progressives.txtpb b/data/progressives.txtpb new file mode 100644 index 0000000..0eba2bf --- /dev/null +++ b/data/progressives.txtpb
@@ -0,0 +1,11 @@
1progressives {
2 name: "Progressive Gold Ending"
3 doors { map: "daedalus" name: "Red Rainbow Room" }
4 doors { map: "daedalus" name: "Orange Rainbow Room" }
5 doors { map: "daedalus" name: "Yellow Rainbow Room" }
6 doors { map: "daedalus" name: "Green Rainbow Room" }
7 doors { map: "daedalus" name: "Blue Rainbow Room" }
8 doors { map: "daedalus" name: "Purple Rainbow Room" }
9 doors { map: "daedalus" name: "Cyan Rainbow Room" }
10 doors { map: "daedalus" name: "Brown Rainbow Room" }
11}
diff --git a/proto/data.proto b/proto/data.proto index 60b603b..7a1918a 100644 --- a/proto/data.proto +++ b/proto/data.proto
@@ -27,6 +27,29 @@ enum DoorType {
27 27
28 // This door is an item if gravestone shuffle is enabled, and is a location as long as panelsanity is not on. 28 // This door is an item if gravestone shuffle is enabled, and is a location as long as panelsanity is not on.
29 GRAVESTONE = 6; 29 GRAVESTONE = 6;
30
31 // This door is never a location, and is an item as long as gallery painting shuffle is on.
32 GALLERY_PAINTING = 7;
33}
34
35enum DoorGroupType {
36 DOOR_GROUP_TYPE_UNKNOWN = 0;
37
38 // These doors border a worldport. They should be grouped when connections are
39 // not shuffled.
40 CONNECTOR = 1;
41
42 // Similar to CONNECTOR, but these doors are also ordinarily opened by solving
43 // the COLOR panel in the Control Center. These should be grouped when
44 // connections are not shuffled, but are not items at all when control center
45 // colors are not shuffled.
46 COLOR_CONNECTOR = 2;
47
48 // Groups with this type become an item if cyan door behavior is set to item.
49 CYAN_DOORS = 3;
50
51 // Groups with this type always become an item if door shuffle is on.
52 SHUFFLE_GROUP = 4;
30} 53}
31 54
32enum AxisDirection { 55enum AxisDirection {
@@ -84,6 +107,8 @@ message Connection {
84 uint64 painting = 5; 107 uint64 painting = 5;
85 ProxyIdentifier panel = 6; 108 ProxyIdentifier panel = 6;
86 } 109 }
110
111 optional bool roof_access = 7;
87} 112}
88 113
89message Door { 114message Door {
@@ -100,12 +125,12 @@ message Door {
100 optional uint64 complete_at = 12; 125 optional uint64 complete_at = 12;
101 126
102 optional string control_center_color = 6; 127 optional string control_center_color = 6;
103 repeated string switches = 7;
104 repeated KeyholderAnswer keyholders = 13; 128 repeated KeyholderAnswer keyholders = 13;
105 repeated uint64 rooms = 14; 129 repeated uint64 rooms = 14;
106 repeated uint64 doors = 15; 130 repeated uint64 doors = 15;
107 repeated uint64 endings = 16; 131 repeated uint64 endings = 16;
108 optional bool double_letters = 18; 132 optional bool double_letters = 18;
133 repeated string senders = 19;
109 134
110 optional DoorType type = 8; 135 optional DoorType type = 8;
111 136
@@ -162,10 +187,12 @@ message Port {
162 187
163message KeyholderData { 188message KeyholderData {
164 optional uint64 id = 1; 189 optional uint64 id = 1;
190 optional uint64 ap_id = 6;
165 optional uint64 room_id = 2; 191 optional uint64 room_id = 2;
166 192
167 optional string name = 3; 193 optional string name = 3;
168 optional string path = 4; 194 optional string path = 4;
195 optional string key = 5;
169} 196}
170 197
171message Letter { 198message Letter {
@@ -221,7 +248,24 @@ message Map {
221 optional string display_name = 3; 248 optional string display_name = 3;
222} 249}
223 250
251message Progressive {
252 optional uint64 id = 1;
253 optional string name = 2;
254 optional uint64 ap_id = 3;
255 repeated uint64 doors = 4;
256}
257
258message DoorGroup {
259 optional uint64 id = 1;
260 optional string name = 2;
261 optional uint64 ap_id = 3;
262 optional DoorGroupType type = 4;
263 repeated uint64 doors = 5;
264}
265
224message AllObjects { 266message AllObjects {
267 optional uint64 version = 15;
268
225 repeated Map maps = 7; 269 repeated Map maps = 7;
226 repeated Room rooms = 1; 270 repeated Room rooms = 1;
227 repeated Door doors = 2; 271 repeated Door doors = 2;
@@ -233,5 +277,7 @@ message AllObjects {
233 repeated Mastery masteries = 10; 277 repeated Mastery masteries = 10;
234 repeated Ending endings = 12; 278 repeated Ending endings = 12;
235 repeated Connection connections = 6; 279 repeated Connection connections = 6;
280 repeated Progressive progressives = 13;
281 repeated DoorGroup door_groups = 14;
236 map<string, uint64> special_ids = 8; 282 map<string, uint64> special_ids = 8;
237} 283}
diff --git a/proto/human.proto b/proto/human.proto index c7e2f5d..f9517bd 100644 --- a/proto/human.proto +++ b/proto/human.proto
@@ -62,6 +62,14 @@ message HumanConnection {
62 62
63 optional bool oneway = 3; 63 optional bool oneway = 3;
64 optional DoorIdentifier door = 4; 64 optional DoorIdentifier door = 4;
65
66 // If true, this connection will only be logically allowed if the Daedalus
67 // Roof Access option is enabled.
68 optional bool roof_access = 7;
69
70 // This means that the connection intentionally skips the target object's
71 // required door.
72 optional bool bypass_target_door = 8;
65} 73}
66 74
67message HumanConnections { 75message HumanConnections {
@@ -82,13 +90,16 @@ message HumanDoor {
82 optional uint64 complete_at = 9; 90 optional uint64 complete_at = 9;
83 91
84 optional string control_center_color = 6; 92 optional string control_center_color = 6;
85 repeated string switches = 7;
86 repeated KeyholderIdentifier keyholders = 10; 93 repeated KeyholderIdentifier keyholders = 10;
87 repeated RoomIdentifier rooms = 11; 94 repeated RoomIdentifier rooms = 11;
88 repeated DoorIdentifier doors = 12; 95 repeated DoorIdentifier doors = 12;
89 repeated string endings = 13; 96 repeated string endings = 13;
90 optional bool double_letters = 15; 97 optional bool double_letters = 15;
91 98
99 // Sender nodes to be added to the list of requirements for triggering the
100 // location. Only for senders that have no logic requirements.
101 repeated string senders = 16;
102
92 optional DoorType type = 4; 103 optional DoorType type = 4;
93 optional string location_room = 5; 104 optional string location_room = 5;
94 optional string location_name = 14; 105 optional string location_name = 14;
@@ -142,6 +153,13 @@ message HumanPort {
142message HumanKeyholder { 153message HumanKeyholder {
143 optional string name = 1; 154 optional string name = 1;
144 optional string path = 2; 155 optional string path = 2;
156
157 // If this is set, the keyholder will become a location when keyholder shuffle
158 // is enabled. This value specifies the key that is required to clear the
159 // location. It should be the same as the key needed for Green Ending. The
160 // only cases when this shouldn't be set is the two disappearing keyholders in
161 // The Congruent.
162 optional string key = 3;
145} 163}
146 164
147message HumanLetter { 165message HumanLetter {
@@ -183,10 +201,35 @@ message HumanMap {
183 repeated string excluded_nodes = 2; 201 repeated string excluded_nodes = 2;
184} 202}
185 203
204message HumanProgressive {
205 optional string name = 1;
206 repeated DoorIdentifier doors = 2;
207}
208
209message HumanProgressives {
210 repeated HumanProgressive progressives = 1;
211}
212
213message HumanDoorGroup {
214 optional string name = 1;
215 optional DoorGroupType type = 2;
216 repeated DoorIdentifier doors = 3;
217}
218
219message HumanDoorGroups {
220 repeated HumanDoorGroup door_groups = 1;
221}
222
223message HumanGlobalMetadata {
224 repeated string special_names = 1;
225 optional uint64 version = 2;
226}
227
186message IdMappings { 228message IdMappings {
187 message RoomIds { 229 message RoomIds {
188 map<string, uint64> panels = 1; 230 map<string, uint64> panels = 1;
189 map<string, uint64> masteries = 2; 231 map<string, uint64> masteries = 2;
232 map<string, uint64> keyholders = 3;
190 } 233 }
191 234
192 message MapIds { 235 message MapIds {
@@ -198,4 +241,6 @@ message IdMappings {
198 map<string, uint64> special = 2; 241 map<string, uint64> special = 2;
199 map<string, uint64> letters = 3; 242 map<string, uint64> letters = 3;
200 map<string, uint64> endings = 4; 243 map<string, uint64> endings = 4;
244 map<string, uint64> progressives = 5;
245 map<string, uint64> door_groups = 6;
201} 246}
diff --git a/tools/assign_ids/main.cpp b/tools/assign_ids/main.cpp index e65e5e4..3e16f78 100644 --- a/tools/assign_ids/main.cpp +++ b/tools/assign_ids/main.cpp
@@ -2,6 +2,7 @@
2#include <google/protobuf/text_format.h> 2#include <google/protobuf/text_format.h>
3 3
4#include <cstdint> 4#include <cstdint>
5#include <filesystem>
5#include <fstream> 6#include <fstream>
6#include <iostream> 7#include <iostream>
7#include <map> 8#include <map>
@@ -40,6 +41,10 @@ class AssignIds {
40 ReadIds(ids_path); 41 ReadIds(ids_path);
41 42
42 ProcessMaps(datadir_path); 43 ProcessMaps(datadir_path);
44 ProcessSpecialIds();
45 ProcessProgressivesFile(datadir_path / "progressives.txtpb");
46 ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb");
47 ProcessGlobalMetadataFile(datadir_path / "metadata.txtpb");
43 48
44 WriteIds(ids_path); 49 WriteIds(ids_path);
45 50
@@ -54,50 +59,26 @@ class AssignIds {
54 id_mappings_ = ReadIdsFromYaml(path.string()); 59 id_mappings_ = ReadIdsFromYaml(path.string());
55 60
56 for (const auto& [_, map] : id_mappings_.maps()) { 61 for (const auto& [_, map] : id_mappings_.maps()) {
57 for (const auto& [_, id] : map.doors()) { 62 UpdateNextId(map.doors());
58 if (id > next_id_) {
59 next_id_ = id;
60 }
61 }
62 63
63 for (const auto& [_, room] : map.rooms()) { 64 for (const auto& [_, room] : map.rooms()) {
64 for (const auto& [_, id] : room.panels()) { 65 UpdateNextId(room.panels());
65 if (id > next_id_) { 66 UpdateNextId(room.masteries());
66 next_id_ = id; 67 UpdateNextId(room.keyholders());
67 }
68 }
69
70 for (const auto& [_, id] : room.masteries()) {
71 if (id > next_id_) {
72 next_id_ = id;
73 }
74 }
75 }
76 }
77
78 for (const auto& [_, id] : id_mappings_.special()) {
79 if (id > next_id_) {
80 next_id_ = id;
81 }
82 }
83
84 for (const auto& [_, id] : id_mappings_.letters()) {
85 if (id > next_id_) {
86 next_id_ = id;
87 } 68 }
88 } 69 }
89 70
90 for (const auto& [_, id] : id_mappings_.endings()) { 71 UpdateNextId(id_mappings_.special());
91 if (id > next_id_) { 72 UpdateNextId(id_mappings_.letters());
92 next_id_ = id; 73 UpdateNextId(id_mappings_.endings());
93 } 74 UpdateNextId(id_mappings_.progressives());
94 } 75 UpdateNextId(id_mappings_.door_groups());
95 76
96 next_id_++; 77 next_id_++;
97 } 78 }
98 79
99 void WriteIds(std::filesystem::path path) { 80 void WriteIds(std::filesystem::path path) {
100 WriteIdsAsYaml(id_mappings_, path.string()); 81 WriteIdsAsYaml(output_, path.string());
101 } 82 }
102 83
103 void ProcessMaps(std::filesystem::path path) { 84 void ProcessMaps(std::filesystem::path path) {
@@ -134,14 +115,18 @@ class AssignIds {
134 return; 115 return;
135 } 116 }
136 117
118 auto& maps = *output_.mutable_maps();
119 auto& doors = *maps[current_map_name].mutable_doors();
120
137 if (!id_mappings_.maps().contains(current_map_name) || 121 if (!id_mappings_.maps().contains(current_map_name) ||
138 !id_mappings_.maps() 122 !id_mappings_.maps()
139 .at(current_map_name) 123 .at(current_map_name)
140 .doors() 124 .doors()
141 .contains(h_door.name())) { 125 .contains(h_door.name())) {
142 auto& maps = *id_mappings_.mutable_maps();
143 auto& doors = *maps[current_map_name].mutable_doors();
144 doors[h_door.name()] = next_id_++; 126 doors[h_door.name()] = next_id_++;
127 } else {
128 doors[h_door.name()] =
129 id_mappings_.maps().at(current_map_name).doors().at(h_door.name());
145 } 130 }
146 } 131 }
147 132
@@ -156,6 +141,10 @@ class AssignIds {
156 void ProcessRoom(const HumanRoom& h_room, 141 void ProcessRoom(const HumanRoom& h_room,
157 const std::string& current_map_name) { 142 const std::string& current_map_name) {
158 for (const HumanPanel& h_panel : h_room.panels()) { 143 for (const HumanPanel& h_panel : h_room.panels()) {
144 auto& maps = *output_.mutable_maps();
145 auto& rooms = *maps[current_map_name].mutable_rooms();
146 auto& panels = *rooms[h_room.name()].mutable_panels();
147
159 if (!id_mappings_.maps().contains(current_map_name) || 148 if (!id_mappings_.maps().contains(current_map_name) ||
160 !id_mappings_.maps() 149 !id_mappings_.maps()
161 .at(current_map_name) 150 .at(current_map_name)
@@ -167,23 +156,33 @@ class AssignIds {
167 .at(h_room.name()) 156 .at(h_room.name())
168 .panels() 157 .panels()
169 .contains(h_panel.name())) { 158 .contains(h_panel.name())) {
170 auto& maps = *id_mappings_.mutable_maps();
171 auto& rooms = *maps[current_map_name].mutable_rooms();
172 auto& panels = *rooms[h_room.name()].mutable_panels();
173 panels[h_panel.name()] = next_id_++; 159 panels[h_panel.name()] = next_id_++;
160 } else {
161 panels[h_panel.name()] = id_mappings_.maps()
162 .at(current_map_name)
163 .rooms()
164 .at(h_room.name())
165 .panels()
166 .at(h_panel.name());
174 } 167 }
175 } 168 }
176 169
177 for (const HumanLetter& h_letter : h_room.letters()) { 170 for (const HumanLetter& h_letter : h_room.letters()) {
178 std::string lettername = GetLetterName(h_letter.key(), h_letter.level2()); 171 std::string lettername = GetLetterName(h_letter.key(), h_letter.level2());
179 172
173 auto& letters = *output_.mutable_letters();
180 if (!id_mappings_.letters().contains(lettername)) { 174 if (!id_mappings_.letters().contains(lettername)) {
181 auto& letters = *id_mappings_.mutable_letters();
182 letters[lettername] = next_id_++; 175 letters[lettername] = next_id_++;
176 } else {
177 letters[lettername] = id_mappings_.letters().at(lettername);
183 } 178 }
184 } 179 }
185 180
186 for (const HumanMastery& h_mastery : h_room.masteries()) { 181 for (const HumanMastery& h_mastery : h_room.masteries()) {
182 auto& maps = *output_.mutable_maps();
183 auto& rooms = *maps[current_map_name].mutable_rooms();
184 auto& masteries = *rooms[h_room.name()].mutable_masteries();
185
187 if (!id_mappings_.maps().contains(current_map_name) || 186 if (!id_mappings_.maps().contains(current_map_name) ||
188 !id_mappings_.maps() 187 !id_mappings_.maps()
189 .at(current_map_name) 188 .at(current_map_name)
@@ -195,27 +194,133 @@ class AssignIds {
195 .at(h_room.name()) 194 .at(h_room.name())
196 .masteries() 195 .masteries()
197 .contains(h_mastery.name())) { 196 .contains(h_mastery.name())) {
198 auto& maps = *id_mappings_.mutable_maps();
199 auto& rooms = *maps[current_map_name].mutable_rooms();
200 auto& masteries = *rooms[h_room.name()].mutable_masteries();
201 masteries[h_mastery.name()] = next_id_++; 197 masteries[h_mastery.name()] = next_id_++;
198 } else {
199 masteries[h_mastery.name()] = id_mappings_.maps()
200 .at(current_map_name)
201 .rooms()
202 .at(h_room.name())
203 .masteries()
204 .at(h_mastery.name());
202 } 205 }
203 } 206 }
204 207
205 for (const HumanEnding& h_ending : h_room.endings()) { 208 for (const HumanEnding& h_ending : h_room.endings()) {
209 auto& endings = *output_.mutable_endings();
210
206 if (!id_mappings_.endings().contains(h_ending.name())) { 211 if (!id_mappings_.endings().contains(h_ending.name())) {
207 auto& endings = *id_mappings_.mutable_endings();
208 endings[h_ending.name()] = next_id_++; 212 endings[h_ending.name()] = next_id_++;
213 } else {
214 endings[h_ending.name()] = id_mappings_.endings().at(h_ending.name());
215 }
216 }
217
218 for (const HumanKeyholder& h_keyholder : h_room.keyholders()) {
219 if (!h_keyholder.has_key()) {
220 continue;
221 }
222
223 auto& maps = *output_.mutable_maps();
224 auto& rooms = *maps[current_map_name].mutable_rooms();
225 auto& keyholders = *rooms[h_room.name()].mutable_keyholders();
226
227 if (!id_mappings_.maps().contains(current_map_name) ||
228 !id_mappings_.maps()
229 .at(current_map_name)
230 .rooms()
231 .contains(h_room.name()) ||
232 !id_mappings_.maps()
233 .at(current_map_name)
234 .rooms()
235 .at(h_room.name())
236 .keyholders()
237 .contains(h_keyholder.name())) {
238 keyholders[h_keyholder.name()] = next_id_++;
239 } else {
240 keyholders[h_keyholder.name()] = id_mappings_.maps()
241 .at(current_map_name)
242 .rooms()
243 .at(h_room.name())
244 .keyholders()
245 .at(h_keyholder.name());
246 }
247 }
248 }
249
250 void ProcessSpecialIds() {
251 auto& specials = *output_.mutable_special();
252
253 for (const auto& [special_name, ap_id] : id_mappings_.special()) {
254 specials[special_name] = ap_id;
255 }
256 }
257
258 void ProcessProgressivesFile(std::filesystem::path path) {
259 if (!std::filesystem::exists(path)) {
260 return;
261 }
262
263 auto h_progs = ReadMessageFromFile<HumanProgressives>(path.string());
264 auto& progs = *output_.mutable_progressives();
265
266 for (const HumanProgressive& h_prog : h_progs.progressives()) {
267 if (!id_mappings_.progressives().contains(h_prog.name())) {
268 progs[h_prog.name()] = next_id_++;
269 } else {
270 progs[h_prog.name()] = id_mappings_.progressives().at(h_prog.name());
271 }
272 }
273 }
274
275 void ProcessDoorGroupsFile(std::filesystem::path path) {
276 if (!std::filesystem::exists(path)) {
277 return;
278 }
279
280 auto h_groups = ReadMessageFromFile<HumanDoorGroups>(path.string());
281 auto& groups = *output_.mutable_door_groups();
282
283 for (const HumanDoorGroup& h_group : h_groups.door_groups()) {
284 if (!id_mappings_.door_groups().contains(h_group.name())) {
285 groups[h_group.name()] = next_id_++;
286 } else {
287 groups[h_group.name()] = id_mappings_.door_groups().at(h_group.name());
288 }
289 }
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);
209 } 305 }
210 } 306 }
211 } 307 }
212 308
213 private: 309 private:
310 void UpdateNextId(const google::protobuf::Map<std::string, uint64_t>& ids) {
311 for (const auto& [_, id] : ids) {
312 if (id > next_id_) {
313 next_id_ = id;
314 }
315 }
316 }
317
214 std::string mapdir_; 318 std::string mapdir_;
215 319
216 uint64_t next_id_ = 1; 320 uint64_t next_id_ = 1;
217 321
218 IdMappings id_mappings_; 322 IdMappings id_mappings_;
323 IdMappings output_;
219}; 324};
220 325
221} // namespace 326} // namespace
diff --git a/tools/datapacker/container.cpp b/tools/datapacker/container.cpp index 2c68552..4a656b3 100644 --- a/tools/datapacker/container.cpp +++ b/tools/datapacker/container.cpp
@@ -331,6 +331,40 @@ uint64_t Container::FindOrAddDoor(std::optional<std::string> map_name,
331 } 331 }
332} 332}
333 333
334uint64_t Container::FindOrAddProgressive(std::string prog_name) {
335 auto it = progressive_id_by_name_.find(prog_name);
336
337 if (it == progressive_id_by_name_.end()) {
338 uint64_t new_id = all_objects_.progressives_size();
339 Progressive* progressive = all_objects_.add_progressives();
340 progressive->set_id(new_id);
341 progressive->set_name(prog_name);
342
343 progressive_id_by_name_[prog_name] = new_id;
344
345 return new_id;
346 } else {
347 return it->second;
348 }
349}
350
351uint64_t Container::FindOrAddDoorGroup(std::string group_name) {
352 auto it = door_group_id_by_name_.find(group_name);
353
354 if (it == door_group_id_by_name_.end()) {
355 uint64_t new_id = all_objects_.door_groups_size();
356 DoorGroup* door_group = all_objects_.add_door_groups();
357 door_group->set_id(new_id);
358 door_group->set_name(group_name);
359
360 door_group_id_by_name_[group_name] = new_id;
361
362 return new_id;
363 } else {
364 return it->second;
365 }
366}
367
334void Container::AddConnection(const Connection& connection) { 368void Container::AddConnection(const Connection& connection) {
335 *all_objects_.add_connections() = connection; 369 *all_objects_.add_connections() = connection;
336} 370}
diff --git a/tools/datapacker/container.h b/tools/datapacker/container.h index 68f5875..bc02ba4 100644 --- a/tools/datapacker/container.h +++ b/tools/datapacker/container.h
@@ -60,6 +60,10 @@ class Container {
60 60
61 void AddConnection(const Connection& connection); 61 void AddConnection(const Connection& connection);
62 62
63 uint64_t FindOrAddProgressive(std::string prog_name);
64
65 uint64_t FindOrAddDoorGroup(std::string group_name);
66
63 AllObjects& all_objects() { return all_objects_; } 67 AllObjects& all_objects() { return all_objects_; }
64 68
65 private: 69 private:
@@ -82,6 +86,8 @@ class Container {
82 std::map<std::string, std::map<std::string, uint64_t>> 86 std::map<std::string, std::map<std::string, uint64_t>>
83 door_id_by_map_door_names_; 87 door_id_by_map_door_names_;
84 std::map<std::string, uint64_t> ending_id_by_name_; 88 std::map<std::string, uint64_t> ending_id_by_name_;
89 std::map<std::string, uint64_t> progressive_id_by_name_;
90 std::map<std::string, uint64_t> door_group_id_by_name_;
85}; 91};
86 92
87} // namespace com::fourisland::lingo2_archipelago 93} // namespace com::fourisland::lingo2_archipelago
diff --git a/tools/datapacker/main.cpp b/tools/datapacker/main.cpp index 4923fce..c13a4df 100644 --- a/tools/datapacker/main.cpp +++ b/tools/datapacker/main.cpp
@@ -43,6 +43,9 @@ class DataPacker {
43 43
44 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt); 44 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt);
45 ProcessMaps(datadir_path); 45 ProcessMaps(datadir_path);
46 ProcessProgressivesFile(datadir_path / "progressives.txtpb");
47 ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb");
48 ProcessGlobalMetadataFile(datadir_path / "metadata.txtpb");
46 ProcessIdsFile(datadir_path / "ids.yaml"); 49 ProcessIdsFile(datadir_path / "ids.yaml");
47 50
48 { 51 {
@@ -104,7 +107,7 @@ class DataPacker {
104 container_.FindOrAddRoom(current_map_name, h_room.name(), std::nullopt); 107 container_.FindOrAddRoom(current_map_name, h_room.name(), std::nullopt);
105 Room& room = *container_.all_objects().mutable_rooms(room_id); 108 Room& room = *container_.all_objects().mutable_rooms(room_id);
106 109
107 //room.set_display_name(h_room.display_name()); 110 // room.set_display_name(h_room.display_name());
108 111
109 if (h_room.has_panel_display_name()) { 112 if (h_room.has_panel_display_name()) {
110 room.set_panel_display_name(h_room.panel_display_name()); 113 room.set_panel_display_name(h_room.panel_display_name());
@@ -292,6 +295,10 @@ class DataPacker {
292 295
293 keyholder.set_path(h_keyholder.path()); 296 keyholder.set_path(h_keyholder.path());
294 297
298 if (h_keyholder.has_key()) {
299 keyholder.set_key(h_keyholder.key());
300 }
301
295 return keyholder_id; 302 return keyholder_id;
296 } 303 }
297 304
@@ -339,8 +346,8 @@ class DataPacker {
339 h_door.receivers().begin(), h_door.receivers().end(), 346 h_door.receivers().begin(), h_door.receivers().end(),
340 google::protobuf::RepeatedFieldBackInserter(door.mutable_receivers())); 347 google::protobuf::RepeatedFieldBackInserter(door.mutable_receivers()));
341 std::copy( 348 std::copy(
342 h_door.switches().begin(), h_door.switches().end(), 349 h_door.senders().begin(), h_door.senders().end(),
343 google::protobuf::RepeatedFieldBackInserter(door.mutable_switches())); 350 google::protobuf::RepeatedFieldBackInserter(door.mutable_senders()));
344 351
345 for (const PaintingIdentifier& pi : h_door.move_paintings()) { 352 for (const PaintingIdentifier& pi : h_door.move_paintings()) {
346 std::optional<std::string> map_name = 353 std::optional<std::string> map_name =
@@ -388,7 +395,7 @@ class DataPacker {
388 door.add_doors( 395 door.add_doors(
389 container_.FindOrAddDoor(map_name, di.name(), current_map_name)); 396 container_.FindOrAddDoor(map_name, di.name(), current_map_name));
390 } 397 }
391 398
392 for (const std::string& ending_name : h_door.endings()) { 399 for (const std::string& ending_name : h_door.endings()) {
393 door.add_endings(container_.FindOrAddEnding(ending_name)); 400 door.add_endings(container_.FindOrAddEnding(ending_name));
394 } 401 }
@@ -461,6 +468,11 @@ class DataPacker {
461 r_connection.set_required_door(door_id); 468 r_connection.set_required_door(door_id);
462 } 469 }
463 470
471 if (human_connection.has_roof_access()) {
472 f_connection.set_roof_access(human_connection.roof_access());
473 r_connection.set_roof_access(human_connection.roof_access());
474 }
475
464 container_.AddConnection(f_connection); 476 container_.AddConnection(f_connection);
465 if (!human_connection.oneway()) { 477 if (!human_connection.oneway()) {
466 container_.AddConnection(r_connection); 478 container_.AddConnection(r_connection);
@@ -544,6 +556,63 @@ class DataPacker {
544 } 556 }
545 } 557 }
546 558
559 void ProcessProgressivesFile(std::filesystem::path path) {
560 if (!std::filesystem::exists(path)) {
561 return;
562 }
563
564 auto h_progs = ReadMessageFromFile<HumanProgressives>(path.string());
565
566 for (const HumanProgressive& h_prog : h_progs.progressives()) {
567 ProcessProgressive(h_prog);
568 }
569 }
570
571 void ProcessProgressive(const HumanProgressive& h_prog) {
572 uint64_t prog_id = container_.FindOrAddProgressive(h_prog.name());
573 Progressive& prog = *container_.all_objects().mutable_progressives(prog_id);
574
575 for (const DoorIdentifier& di : h_prog.doors()) {
576 uint64_t door_id =
577 container_.FindOrAddDoor(di.map(), di.name(), std::nullopt);
578 prog.add_doors(door_id);
579 }
580 }
581
582 void ProcessDoorGroupsFile(std::filesystem::path path) {
583 if (!std::filesystem::exists(path)) {
584 return;
585 }
586
587 auto h_groups = ReadMessageFromFile<HumanDoorGroups>(path.string());
588
589 for (const HumanDoorGroup& h_group : h_groups.door_groups()) {
590 ProcessDoorGroup(h_group);
591 }
592 }
593
594 void ProcessDoorGroup(const HumanDoorGroup& h_group) {
595 uint64_t group_id = container_.FindOrAddDoorGroup(h_group.name());
596 DoorGroup& group = *container_.all_objects().mutable_door_groups(group_id);
597
598 group.set_type(h_group.type());
599
600 for (const DoorIdentifier& di : h_group.doors()) {
601 uint64_t door_id =
602 container_.FindOrAddDoor(di.map(), di.name(), std::nullopt);
603 group.add_doors(door_id);
604 }
605 }
606
607 void ProcessGlobalMetadataFile(std::filesystem::path path) {
608 if (!std::filesystem::exists(path)) {
609 return;
610 }
611
612 auto h_metadata = ReadMessageFromFile<HumanGlobalMetadata>(path.string());
613 container_.all_objects().set_version(h_metadata.version());
614 }
615
547 void ProcessIdsFile(std::filesystem::path path) { 616 void ProcessIdsFile(std::filesystem::path path) {
548 auto ids = ReadIdsFromYaml(path.string()); 617 auto ids = ReadIdsFromYaml(path.string());
549 618
@@ -568,6 +637,14 @@ class DataPacker {
568 .mutable_masteries(mastery_id) 637 .mutable_masteries(mastery_id)
569 ->set_ap_id(ap_id); 638 ->set_ap_id(ap_id);
570 } 639 }
640
641 for (const auto& [keyholder_name, ap_id] : room.keyholders()) {
642 uint64_t keyholder_id = container_.FindOrAddKeyholder(
643 map_name, room_name, keyholder_name, std::nullopt, std::nullopt);
644 container_.all_objects()
645 .mutable_keyholders(keyholder_id)
646 ->set_ap_id(ap_id);
647 }
571 } 648 }
572 } 649 }
573 650
@@ -585,6 +662,16 @@ class DataPacker {
585 uint64_t ending_id = container_.FindOrAddEnding(ending_name); 662 uint64_t ending_id = container_.FindOrAddEnding(ending_name);
586 container_.all_objects().mutable_endings(ending_id)->set_ap_id(ap_id); 663 container_.all_objects().mutable_endings(ending_id)->set_ap_id(ap_id);
587 } 664 }
665
666 for (const auto& [prog_name, ap_id] : ids.progressives()) {
667 uint64_t prog_id = container_.FindOrAddProgressive(prog_name);
668 container_.all_objects().mutable_progressives(prog_id)->set_ap_id(ap_id);
669 }
670
671 for (const auto& [group_name, ap_id] : ids.door_groups()) {
672 uint64_t group_id = container_.FindOrAddDoorGroup(group_name);
673 container_.all_objects().mutable_door_groups(group_id)->set_ap_id(ap_id);
674 }
588 } 675 }
589 676
590 std::string mapdir_; 677 std::string mapdir_;
diff --git a/tools/util/godot_scene.cpp b/tools/util/godot_scene.cpp index 1e77c9e..f788d21 100644 --- a/tools/util/godot_scene.cpp +++ b/tools/util/godot_scene.cpp
@@ -1,10 +1,8 @@
1#include "godot_scene.h" 1#include "godot_scene.h"
2 2
3#include <absl/strings/str_split.h>
4#include <absl/strings/string_view.h>
5
6#include <fstream> 3#include <fstream>
7#include <sstream> 4#include <sstream>
5#include <string_view>
8#include <variant> 6#include <variant>
9 7
10namespace com::fourisland::lingo2_archipelago { 8namespace com::fourisland::lingo2_archipelago {
@@ -23,7 +21,7 @@ struct Heading {
23 GodotInstanceType instance_type; 21 GodotInstanceType instance_type;
24}; 22};
25 23
26Heading ParseTscnHeading(absl::string_view line) { 24Heading ParseTscnHeading(std::string_view line) {
27 std::string original_line(line); 25 std::string original_line(line);
28 Heading heading; 26 Heading heading;
29 27
diff --git a/tools/util/ids_yaml_format.cpp b/tools/util/ids_yaml_format.cpp index f72f60e..71bfd63 100644 --- a/tools/util/ids_yaml_format.cpp +++ b/tools/util/ids_yaml_format.cpp
@@ -56,6 +56,14 @@ IdMappings ReadIdsFromYaml(const std::string& filename) {
56 mastery_it.second.as<uint64_t>(); 56 mastery_it.second.as<uint64_t>();
57 } 57 }
58 } 58 }
59
60 if (room_it.second["keyholders"]) {
61 for (const auto& keyholder_it : room_it.second["keyholders"]) {
62 (*room_ids.mutable_keyholders())[keyholder_it.first
63 .as<std::string>()] =
64 keyholder_it.second.as<uint64_t>();
65 }
66 }
59 } 67 }
60 } 68 }
61 69
@@ -89,6 +97,20 @@ IdMappings ReadIdsFromYaml(const std::string& filename) {
89 } 97 }
90 } 98 }
91 99
100 if (document["progressives"]) {
101 for (const auto& prog_it : document["progressives"]) {
102 (*result.mutable_progressives())[prog_it.first.as<std::string>()] =
103 prog_it.second.as<uint64_t>();
104 }
105 }
106
107 if (document["door_groups"]) {
108 for (const auto& group_it : document["door_groups"]) {
109 (*result.mutable_door_groups())[group_it.first.as<std::string>()] =
110 group_it.second.as<uint64_t>();
111 }
112 }
113
92 return result; 114 return result;
93} 115}
94 116
@@ -117,6 +139,13 @@ void WriteIdsAsYaml(const IdMappings& ids, const std::string& filename) {
117 mastery_id; 139 mastery_id;
118 }); 140 });
119 141
142 OperateOnSortedMap(room_ids.keyholders(),
143 [&room_node](const std::string& keyholder_name,
144 uint64_t keyholder_id) {
145 room_node["keyholders"][keyholder_name] =
146 keyholder_id;
147 });
148
120 map_node["rooms"][room_name] = std::move(room_node); 149 map_node["rooms"][room_name] = std::move(room_node);
121 }); 150 });
122 151
@@ -144,6 +173,16 @@ void WriteIdsAsYaml(const IdMappings& ids, const std::string& filename) {
144 result["special"][special_name] = special_id; 173 result["special"][special_name] = special_id;
145 }); 174 });
146 175
176 OperateOnSortedMap(ids.progressives(),
177 [&result](const std::string& prog_name, uint64_t prog_id) {
178 result["progressives"][prog_name] = prog_id;
179 });
180
181 OperateOnSortedMap(ids.door_groups(), [&result](const std::string& group_name,
182 uint64_t group_id) {
183 result["door_groups"][group_name] = group_id;
184 });
185
147 std::ofstream output_stream(filename); 186 std::ofstream output_stream(filename);
148 output_stream << result << std::endl; 187 output_stream << result << std::endl;
149} 188}
diff --git a/tools/validator/human_processor.cpp b/tools/validator/human_processor.cpp index 0f63936..2c978bf 100644 --- a/tools/validator/human_processor.cpp +++ b/tools/validator/human_processor.cpp
@@ -13,6 +13,7 @@
13#include <string> 13#include <string>
14 14
15#include "structs.h" 15#include "structs.h"
16#include "util/ids_yaml_format.h"
16 17
17namespace com::fourisland::lingo2_archipelago { 18namespace com::fourisland::lingo2_archipelago {
18namespace { 19namespace {
@@ -41,7 +42,9 @@ class HumanProcessor {
41 42
42 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt); 43 ProcessConnectionsFile(datadir_path / "connections.txtpb", std::nullopt);
43 ProcessMaps(datadir_path); 44 ProcessMaps(datadir_path);
44 ProcessIdsFile(datadir_path / "ids.txtpb"); 45 ProcessProgressivesFile(datadir_path / "progressives.txtpb");
46 ProcessDoorGroupsFile(datadir_path / "door_groups.txtpb");
47 ProcessIdsFile(datadir_path / "ids.yaml");
45 } 48 }
46 49
47 private: 50 private:
@@ -391,7 +394,9 @@ class HumanProcessor {
391 } 394 }
392 } else if (human_connection.has_from()) { 395 } else if (human_connection.has_from()) {
393 ProcessSingleConnection(human_connection, human_connection.from(), 396 ProcessSingleConnection(human_connection, human_connection.from(),
394 current_map_name); 397 current_map_name,
398 /*is_target=*/!human_connection.oneway() &&
399 !human_connection.bypass_target_door());
395 } 400 }
396 401
397 if (human_connection.has_to_room()) { 402 if (human_connection.has_to_room()) {
@@ -407,8 +412,9 @@ class HumanProcessor {
407 std::cout << "A global connection used to_room." << std::endl; 412 std::cout << "A global connection used to_room." << std::endl;
408 } 413 }
409 } else if (human_connection.has_to()) { 414 } else if (human_connection.has_to()) {
410 ProcessSingleConnection(human_connection, human_connection.to(), 415 ProcessSingleConnection(
411 current_map_name); 416 human_connection, human_connection.to(), current_map_name,
417 /*is_target=*/!human_connection.bypass_target_door());
412 } 418 }
413 419
414 if (human_connection.has_door()) { 420 if (human_connection.has_door()) {
@@ -429,7 +435,7 @@ class HumanProcessor {
429 void ProcessSingleConnection( 435 void ProcessSingleConnection(
430 const HumanConnection& human_connection, 436 const HumanConnection& human_connection,
431 const HumanConnection::Endpoint& endpoint, 437 const HumanConnection::Endpoint& endpoint,
432 const std::optional<std::string>& current_map_name) { 438 const std::optional<std::string>& current_map_name, bool is_target) {
433 if (endpoint.has_room()) { 439 if (endpoint.has_room()) {
434 auto room_identifier = 440 auto room_identifier =
435 GetCompleteRoomIdentifier(endpoint.room(), current_map_name); 441 GetCompleteRoomIdentifier(endpoint.room(), current_map_name);
@@ -448,6 +454,11 @@ class HumanProcessor {
448 if (painting_identifier) { 454 if (painting_identifier) {
449 PaintingInfo& painting_info = info_.paintings[*painting_identifier]; 455 PaintingInfo& painting_info = info_.paintings[*painting_identifier];
450 painting_info.connections_referenced_by.push_back(human_connection); 456 painting_info.connections_referenced_by.push_back(human_connection);
457
458 if (is_target) {
459 painting_info.target_connections_referenced_by.push_back(
460 human_connection);
461 }
451 } else { 462 } else {
452 // Not sure where else to store this right now. 463 // Not sure where else to store this right now.
453 std::cout 464 std::cout
@@ -460,6 +471,11 @@ class HumanProcessor {
460 if (port_identifier) { 471 if (port_identifier) {
461 PortInfo& port_info = info_.ports[*port_identifier]; 472 PortInfo& port_info = info_.ports[*port_identifier];
462 port_info.connections_referenced_by.push_back(human_connection); 473 port_info.connections_referenced_by.push_back(human_connection);
474
475 if (is_target) {
476 port_info.target_connections_referenced_by.push_back(
477 human_connection);
478 }
463 } else { 479 } else {
464 // Not sure where else to store this right now. 480 // Not sure where else to store this right now.
465 std::cout 481 std::cout
@@ -477,12 +493,137 @@ class HumanProcessor {
477 panel_info.proxies[endpoint.panel().answer()] 493 panel_info.proxies[endpoint.panel().answer()]
478 .connections_referenced_by.push_back(human_connection); 494 .connections_referenced_by.push_back(human_connection);
479 } 495 }
496
497 if (is_target) {
498 panel_info.target_connections_referenced_by.push_back(
499 human_connection);
500 }
501 }
502 }
503 }
504
505 void ProcessProgressivesFile(std::filesystem::path path) {
506 if (!std::filesystem::exists(path)) {
507 return;
508 }
509
510 auto h_progs = ReadMessageFromFile<HumanProgressives>(path.string());
511
512 for (const HumanProgressive& h_prog : h_progs.progressives()) {
513 ProcessProgressive(h_prog);
514 }
515 }
516
517 void ProcessProgressive(const HumanProgressive& h_prog) {
518 ProgressiveInfo& prog_info = info_.progressives[h_prog.name()];
519 prog_info.definitions.push_back(h_prog);
520
521 for (const DoorIdentifier& di : h_prog.doors()) {
522 if (!di.has_map()) {
523 prog_info.malformed_doors.push_back(di);
524 continue;
525 }
526
527 DoorInfo& door_info = info_.doors[di];
528 door_info.progressives_referenced_by.push_back(h_prog.name());
529 }
530 }
531
532 void ProcessDoorGroupsFile(std::filesystem::path path) {
533 if (!std::filesystem::exists(path)) {
534 return;
535 }
536
537 auto h_groups = ReadMessageFromFile<HumanDoorGroups>(path.string());
538
539 for (const HumanDoorGroup& h_group : h_groups.door_groups()) {
540 ProcessDoorGroup(h_group);
541 }
542 }
543
544 void ProcessDoorGroup(const HumanDoorGroup& h_group) {
545 DoorGroupInfo& group_info = info_.door_groups[h_group.name()];
546 group_info.definitions.push_back(h_group);
547
548 for (const DoorIdentifier& di : h_group.doors()) {
549 if (!di.has_map()) {
550 group_info.malformed_doors.push_back(di);
551 continue;
480 } 552 }
553
554 DoorInfo& door_info = info_.doors[di];
555 door_info.door_groups_referenced_by.push_back(h_group.name());
481 } 556 }
482 } 557 }
483 558
484 void ProcessIdsFile(std::filesystem::path path) { 559 void ProcessIdsFile(std::filesystem::path path) {
485 // Ignore this for now. 560 auto ids = ReadIdsFromYaml(path.string());
561
562 DoorIdentifier di;
563 PanelIdentifier pi;
564 KeyholderIdentifier ki;
565
566 for (const auto& [map_name, map] : ids.maps()) {
567 di.set_map(map_name);
568 pi.set_map(map_name);
569 ki.set_map(map_name);
570
571 for (const auto& [door_name, ap_id] : map.doors()) {
572 di.set_name(door_name);
573
574 DoorInfo& door_info = info_.doors[di];
575 door_info.has_id = true;
576 }
577
578 for (const auto& [room_name, room] : map.rooms()) {
579 pi.set_room(room_name);
580 ki.set_room(room_name);
581
582 for (const auto& [panel_name, ap_id] : room.panels()) {
583 pi.set_name(panel_name);
584
585 PanelInfo& panel_info = info_.panels[pi];
586 panel_info.has_id = true;
587 }
588
589 for (const auto& [mastery_name, ap_id] : room.masteries()) {
590 // TODO: Mastery
591 }
592
593 for (const auto& [keyholder_name, ap_id] : room.keyholders()) {
594 ki.set_name(keyholder_name);
595
596 KeyholderInfo& keyholder_info = info_.keyholders[ki];
597 keyholder_info.has_id = true;
598 }
599 }
600 }
601
602 for (const auto& [tag, id] : ids.special()) {
603 // TODO: Specials
604 }
605
606 for (const auto& [letter_name, ap_id] : ids.letters()) {
607 LetterIdentifier li =
608 std::make_tuple(letter_name[0], letter_name[1] == '2');
609 LetterInfo& letter_info = info_.letters[li];
610 letter_info.has_id = true;
611 }
612
613 for (const auto& [ending_name, ap_id] : ids.endings()) {
614 EndingInfo& ending_info = info_.endings[ending_name];
615 ending_info.has_id = true;
616 }
617
618 for (const auto& [prog_name, ap_id] : ids.progressives()) {
619 ProgressiveInfo& prog_info = info_.progressives[prog_name];
620 prog_info.has_id = true;
621 }
622
623 for (const auto& [group_name, ap_id] : ids.door_groups()) {
624 DoorGroupInfo& group_info = info_.door_groups[group_name];
625 group_info.has_id = true;
626 }
486 } 627 }
487 628
488 std::string mapdir_; 629 std::string mapdir_;
diff --git a/tools/validator/structs.h b/tools/validator/structs.h index 0ca96fe..d1d45f2 100644 --- a/tools/validator/structs.h +++ b/tools/validator/structs.h
@@ -39,12 +39,15 @@ struct RoomInfo {
39 39
40struct DoorInfo { 40struct DoorInfo {
41 std::vector<HumanDoor> definitions; 41 std::vector<HumanDoor> definitions;
42 bool has_id = false;
42 43
43 std::vector<HumanConnection> connections_referenced_by; 44 std::vector<HumanConnection> connections_referenced_by;
44 std::vector<DoorIdentifier> doors_referenced_by; 45 std::vector<DoorIdentifier> doors_referenced_by;
45 std::vector<PanelIdentifier> panels_referenced_by; 46 std::vector<PanelIdentifier> panels_referenced_by;
46 std::vector<PaintingIdentifier> paintings_referenced_by; 47 std::vector<PaintingIdentifier> paintings_referenced_by;
47 std::vector<PortIdentifier> ports_referenced_by; 48 std::vector<PortIdentifier> ports_referenced_by;
49 std::vector<std::string> progressives_referenced_by;
50 std::vector<std::string> door_groups_referenced_by;
48 51
49 MalformedIdentifiers malformed_identifiers; 52 MalformedIdentifiers malformed_identifiers;
50}; 53};
@@ -53,12 +56,14 @@ struct PortInfo {
53 std::vector<HumanPort> definitions; 56 std::vector<HumanPort> definitions;
54 57
55 std::vector<HumanConnection> connections_referenced_by; 58 std::vector<HumanConnection> connections_referenced_by;
59 std::vector<HumanConnection> target_connections_referenced_by;
56}; 60};
57 61
58struct PaintingInfo { 62struct PaintingInfo {
59 std::vector<HumanPainting> definitions; 63 std::vector<HumanPainting> definitions;
60 64
61 std::vector<HumanConnection> connections_referenced_by; 65 std::vector<HumanConnection> connections_referenced_by;
66 std::vector<HumanConnection> target_connections_referenced_by;
62 std::vector<DoorIdentifier> doors_referenced_by; 67 std::vector<DoorIdentifier> doors_referenced_by;
63}; 68};
64 69
@@ -71,10 +76,12 @@ struct ProxyInfo {
71 76
72struct PanelInfo { 77struct PanelInfo {
73 std::vector<HumanPanel> definitions; 78 std::vector<HumanPanel> definitions;
79 bool has_id = false;
74 80
75 std::string map_area_name; 81 std::string map_area_name;
76 82
77 std::vector<HumanConnection> connections_referenced_by; 83 std::vector<HumanConnection> connections_referenced_by;
84 std::vector<HumanConnection> target_connections_referenced_by;
78 std::vector<DoorIdentifier> doors_referenced_by; 85 std::vector<DoorIdentifier> doors_referenced_by;
79 86
80 std::map<std::string, ProxyInfo> proxies; 87 std::map<std::string, ProxyInfo> proxies;
@@ -82,6 +89,7 @@ struct PanelInfo {
82 89
83struct KeyholderInfo { 90struct KeyholderInfo {
84 std::vector<HumanKeyholder> definitions; 91 std::vector<HumanKeyholder> definitions;
92 bool has_id = false;
85 93
86 std::vector<DoorIdentifier> doors_referenced_by; 94 std::vector<DoorIdentifier> doors_referenced_by;
87}; 95};
@@ -90,10 +98,12 @@ using LetterIdentifier = std::tuple<char, bool>;
90 98
91struct LetterInfo { 99struct LetterInfo {
92 std::vector<RoomIdentifier> defined_in; 100 std::vector<RoomIdentifier> defined_in;
101 bool has_id = false;
93}; 102};
94 103
95struct EndingInfo { 104struct EndingInfo {
96 std::vector<RoomIdentifier> defined_in; 105 std::vector<RoomIdentifier> defined_in;
106 bool has_id = false;
97 107
98 std::vector<DoorIdentifier> doors_referenced_by; 108 std::vector<DoorIdentifier> doors_referenced_by;
99}; 109};
@@ -102,6 +112,20 @@ struct PanelNameInfo {
102 std::vector<PanelIdentifier> panels_used_by; 112 std::vector<PanelIdentifier> panels_used_by;
103}; 113};
104 114
115struct ProgressiveInfo {
116 std::vector<HumanProgressive> definitions;
117 bool has_id = false;
118
119 std::vector<DoorIdentifier> malformed_doors;
120};
121
122struct DoorGroupInfo {
123 std::vector<HumanDoorGroup> definitions;
124 bool has_id = false;
125
126 std::vector<DoorIdentifier> malformed_doors;
127};
128
105struct CollectedInfo { 129struct CollectedInfo {
106 std::map<std::string, MapInfo> maps; 130 std::map<std::string, MapInfo> maps;
107 std::map<RoomIdentifier, RoomInfo, RoomIdentifierLess> rooms; 131 std::map<RoomIdentifier, RoomInfo, RoomIdentifierLess> rooms;
@@ -114,6 +138,8 @@ struct CollectedInfo {
114 std::map<LetterIdentifier, LetterInfo> letters; 138 std::map<LetterIdentifier, LetterInfo> letters;
115 std::map<std::string, EndingInfo> endings; 139 std::map<std::string, EndingInfo> endings;
116 std::map<std::string, PanelNameInfo> panel_names; 140 std::map<std::string, PanelNameInfo> panel_names;
141 std::map<std::string, ProgressiveInfo> progressives;
142 std::map<std::string, DoorGroupInfo> door_groups;
117}; 143};
118 144
119} // namespace com::fourisland::lingo2_archipelago 145} // namespace com::fourisland::lingo2_archipelago
diff --git a/tools/validator/validator.cpp b/tools/validator/validator.cpp index 9c66e09..dd41f5c 100644 --- a/tools/validator/validator.cpp +++ b/tools/validator/validator.cpp
@@ -45,6 +45,12 @@ class Validator {
45 for (const auto& [panel_name, panel_info] : info_.panel_names) { 45 for (const auto& [panel_name, panel_info] : info_.panel_names) {
46 ValidatePanelName(panel_name, panel_info); 46 ValidatePanelName(panel_name, panel_info);
47 } 47 }
48 for (const auto& [prog_name, prog_info] : info_.progressives) {
49 ValidateProgressive(prog_name, prog_info);
50 }
51 for (const auto& [group_name, group_info] : info_.door_groups) {
52 ValidateDoorGroup(group_name, group_info);
53 }
48 } 54 }
49 55
50 private: 56 private:
@@ -100,7 +106,8 @@ class Validator {
100 return false; 106 return false;
101 } 107 }
102 108
103 if (h_door.keyholders_size() > 0 || h_door.endings_size() > 0) { 109 if (h_door.keyholders_size() > 0 || h_door.endings_size() > 0 ||
110 h_door.complete_at() > 0) {
104 return true; 111 return true;
105 } 112 }
106 113
@@ -164,6 +171,20 @@ class Validator {
164 std::cout << " CONNECTION " << connection.ShortDebugString() 171 std::cout << " CONNECTION " << connection.ShortDebugString()
165 << std::endl; 172 << std::endl;
166 } 173 }
174
175 for (const std::string& prog_name :
176 door_info.progressives_referenced_by) {
177 std::cout << " PROGRESSIVE " << prog_name << std::endl;
178 }
179
180 for (const std::string& group_name :
181 door_info.door_groups_referenced_by) {
182 std::cout << " DOOR GROUP " << group_name << std::endl;
183 }
184
185 if (door_info.has_id) {
186 std::cout << " An AP ID is present." << std::endl;
187 }
167 } else if (door_info.definitions.size() > 1) { 188 } else if (door_info.definitions.size() > 1) {
168 std::cout << "Door " << door_identifier.ShortDebugString() 189 std::cout << "Door " << door_identifier.ShortDebugString()
169 << " was defined multiple times." << std::endl; 190 << " was defined multiple times." << std::endl;
@@ -207,6 +228,17 @@ class Validator {
207 << " is a location that depends on double_letters." 228 << " is a location that depends on double_letters."
208 << std::endl; 229 << std::endl;
209 } 230 }
231
232 bool needs_id = (h_door.type() != DoorType::EVENT);
233 if (door_info.has_id != needs_id) {
234 if (needs_id) {
235 std::cout << "Door " << door_identifier.ShortDebugString()
236 << " is missing an AP ID." << std::endl;
237 } else {
238 std::cout << "Door " << door_identifier.ShortDebugString()
239 << " should not have an AP ID." << std::endl;
240 }
241 }
210 } 242 }
211 } 243 }
212 244
@@ -225,6 +257,22 @@ class Validator {
225 std::cout << "Port " << port_identifier.ShortDebugString() 257 std::cout << "Port " << port_identifier.ShortDebugString()
226 << " was defined multiple times." << std::endl; 258 << " was defined multiple times." << std::endl;
227 } 259 }
260
261 if (!port_info.target_connections_referenced_by.empty()) {
262 for (const HumanPort& port : port_info.definitions) {
263 if (port.has_required_door()) {
264 std::cout << "Port " << port_identifier.ShortDebugString()
265 << " has a required door but is the target of a connection:"
266 << std::endl;
267
268 for (const HumanConnection& connection :
269 port_info.target_connections_referenced_by) {
270 std::cout << " CONNECTION " << connection.ShortDebugString()
271 << std::endl;
272 }
273 }
274 }
275 }
228 } 276 }
229 277
230 void ValidatePainting(const PaintingIdentifier& painting_identifier, 278 void ValidatePainting(const PaintingIdentifier& painting_identifier,
@@ -248,6 +296,22 @@ class Validator {
248 std::cout << "Painting " << painting_identifier.ShortDebugString() 296 std::cout << "Painting " << painting_identifier.ShortDebugString()
249 << " was defined multiple times." << std::endl; 297 << " was defined multiple times." << std::endl;
250 } 298 }
299
300 if (!painting_info.target_connections_referenced_by.empty()) {
301 for (const HumanPainting& painting : painting_info.definitions) {
302 if (painting.has_required_door()) {
303 std::cout << "Painting " << painting_identifier.ShortDebugString()
304 << " has a required door but is the target of a connection:"
305 << std::endl;
306
307 for (const HumanConnection& connection :
308 painting_info.target_connections_referenced_by) {
309 std::cout << " CONNECTION " << connection.ShortDebugString()
310 << std::endl;
311 }
312 }
313 }
314 }
251 } 315 }
252 316
253 void ValidatePanel(const PanelIdentifier& panel_identifier, 317 void ValidatePanel(const PanelIdentifier& panel_identifier,
@@ -272,6 +336,10 @@ class Validator {
272 std::cout << " CONNECTION " << connection.ShortDebugString() 336 std::cout << " CONNECTION " << connection.ShortDebugString()
273 << std::endl; 337 << std::endl;
274 } 338 }
339
340 if (panel_info.has_id) {
341 std::cout << " An AP ID is present." << std::endl;
342 }
275 } else if (panel_info.definitions.size() > 1) { 343 } else if (panel_info.definitions.size() > 1) {
276 std::cout << "Panel " << panel_identifier.ShortDebugString() 344 std::cout << "Panel " << panel_identifier.ShortDebugString()
277 << " was defined multiple times." << std::endl; 345 << " was defined multiple times." << std::endl;
@@ -300,6 +368,27 @@ class Validator {
300 << "\" was defined multiple times." << std::endl; 368 << "\" was defined multiple times." << std::endl;
301 } 369 }
302 } 370 }
371
372 if (!panel_info.has_id) {
373 std::cout << "Panel " << panel_identifier.ShortDebugString()
374 << " is missing an AP ID." << std::endl;
375 }
376
377 if (!panel_info.target_connections_referenced_by.empty()) {
378 for (const HumanPanel& panel : panel_info.definitions) {
379 if (panel.has_required_door()) {
380 std::cout << "Panel " << panel_identifier.ShortDebugString()
381 << " has a required door but is the target of a connection:"
382 << std::endl;
383
384 for (const HumanConnection& connection :
385 panel_info.target_connections_referenced_by) {
386 std::cout << " CONNECTION " << connection.ShortDebugString()
387 << std::endl;
388 }
389 }
390 }
391 }
303 } 392 }
304 393
305 void ValidateKeyholder(const KeyholderIdentifier& keyholder_identifier, 394 void ValidateKeyholder(const KeyholderIdentifier& keyholder_identifier,
@@ -313,10 +402,28 @@ class Validator {
313 std::cout << " DOOR " << door_identifier.ShortDebugString() 402 std::cout << " DOOR " << door_identifier.ShortDebugString()
314 << std::endl; 403 << std::endl;
315 } 404 }
405
406 if (keyholder_info.has_id) {
407 std::cout << " An AP ID is present." << std::endl;
408 }
316 } else if (keyholder_info.definitions.size() > 1) { 409 } else if (keyholder_info.definitions.size() > 1) {
317 std::cout << "Keyholder " << keyholder_identifier.ShortDebugString() 410 std::cout << "Keyholder " << keyholder_identifier.ShortDebugString()
318 << " was defined multiple times." << std::endl; 411 << " was defined multiple times." << std::endl;
319 } 412 }
413
414 for (const HumanKeyholder& h_keyholder : keyholder_info.definitions) {
415 bool needs_id = (h_keyholder.has_key());
416
417 if (needs_id != keyholder_info.has_id) {
418 if (needs_id) {
419 std::cout << "Keyholder " << keyholder_identifier.ShortDebugString()
420 << " is missing an AP ID." << std::endl;
421 } else {
422 std::cout << "Keyholder " << keyholder_identifier.ShortDebugString()
423 << " should not have an AP ID." << std::endl;
424 }
425 }
426 }
320 } 427 }
321 428
322 void ValidateLetter(const LetterIdentifier& letter_identifier, 429 void ValidateLetter(const LetterIdentifier& letter_identifier,
@@ -324,7 +431,14 @@ class Validator {
324 std::string letter_name = std::string(1, std::get<0>(letter_identifier)) + 431 std::string letter_name = std::string(1, std::get<0>(letter_identifier)) +
325 (std::get<1>(letter_identifier) ? "2" : "1"); 432 (std::get<1>(letter_identifier) ? "2" : "1");
326 433
327 if (letter_info.defined_in.size() > 1) { 434 if (letter_info.defined_in.empty()) {
435 std::cout << "Letter " << letter_name
436 << " has no definition, but was referenced:" << std::endl;
437
438 if (letter_info.has_id) {
439 std::cout << " An AP ID is present." << std::endl;
440 }
441 } else if (letter_info.defined_in.size() > 1) {
328 std::cout << "Letter " << letter_name 442 std::cout << "Letter " << letter_name
329 << " was defined in multiple places:" << std::endl; 443 << " was defined in multiple places:" << std::endl;
330 444
@@ -332,6 +446,11 @@ class Validator {
332 std::cout << " " << room_identifier.ShortDebugString() << std::endl; 446 std::cout << " " << room_identifier.ShortDebugString() << std::endl;
333 } 447 }
334 } 448 }
449
450 if (!letter_info.has_id) {
451 std::cout << "Letter " << letter_name << " is missing an AP ID."
452 << std::endl;
453 }
335 } 454 }
336 455
337 void ValidateEnding(const std::string& ending_name, 456 void ValidateEnding(const std::string& ending_name,
@@ -345,6 +464,10 @@ class Validator {
345 std::cout << " DOOR " << door_identifier.ShortDebugString() 464 std::cout << " DOOR " << door_identifier.ShortDebugString()
346 << std::endl; 465 << std::endl;
347 } 466 }
467
468 if (ending_info.has_id) {
469 std::cout << " An AP ID is present." << std::endl;
470 }
348 } else if (ending_info.defined_in.size() > 1) { 471 } else if (ending_info.defined_in.size() > 1) {
349 std::cout << "Ending " << ending_name 472 std::cout << "Ending " << ending_name
350 << " was defined in multiple places:" << std::endl; 473 << " was defined in multiple places:" << std::endl;
@@ -353,6 +476,11 @@ class Validator {
353 std::cout << " " << room_identifier.ShortDebugString() << std::endl; 476 std::cout << " " << room_identifier.ShortDebugString() << std::endl;
354 } 477 }
355 } 478 }
479
480 if (!ending_info.has_id) {
481 std::cout << "Ending " << ending_name << " is missing an AP ID."
482 << std::endl;
483 }
356 } 484 }
357 485
358 void ValidatePanelName(const std::string& panel_name, 486 void ValidatePanelName(const std::string& panel_name,
@@ -369,6 +497,46 @@ class Validator {
369 } 497 }
370 } 498 }
371 499
500 void ValidateProgressive(const std::string& prog_name,
501 const ProgressiveInfo& prog_info) const {
502 if (prog_info.definitions.empty()) {
503 std::cout << "Progressive \"" << prog_name
504 << "\" has no definition, but was referenced:" << std::endl;
505
506 if (prog_info.has_id) {
507 std::cout << " An AP ID is present." << std::endl;
508 }
509 } else if (prog_info.definitions.size() > 1) {
510 std::cout << "Progressive \"" << prog_name
511 << "\" has multiple definitions." << std::endl;
512 }
513
514 if (!prog_info.has_id) {
515 std::cout << "Progressive \"" << prog_name << "\" is missing an AP ID."
516 << std::endl;
517 }
518 }
519
520 void ValidateDoorGroup(const std::string& group_name,
521 const DoorGroupInfo& group_info) const {
522 if (group_info.definitions.empty()) {
523 std::cout << "Door group \"" << group_name
524 << "\" has no definition, but was referenced:" << std::endl;
525
526 if (group_info.has_id) {
527 std::cout << " An AP ID is present." << std::endl;
528 }
529 } else if (group_info.definitions.size() > 1) {
530 std::cout << "Door group \"" << group_name
531 << "\" has multiple definitions." << std::endl;
532 }
533
534 if (!group_info.has_id) {
535 std::cout << "Door group \"" << group_name << "\" is missing an AP ID."
536 << std::endl;
537 }
538 }
539
372 const CollectedInfo& info_; 540 const CollectedInfo& info_;
373}; 541};
374 542
diff --git a/vcpkg.json b/vcpkg.json index 5a1975d..ba6833f 100644 --- a/vcpkg.json +++ b/vcpkg.json
@@ -7,7 +7,7 @@
7 "overrides": [ 7 "overrides": [
8 { 8 {
9 "name": "protobuf", 9 "name": "protobuf",
10 "version": "5.29.3" 10 "version": "3.21.12"
11 } 11 }
12 ] 12 ]
13} \ No newline at end of file 13} \ No newline at end of file
diff --git a/vendor/godobuf/LICENSE b/vendor/godobuf/LICENSE new file mode 100644 index 0000000..5d473d8 --- /dev/null +++ b/vendor/godobuf/LICENSE
@@ -0,0 +1,29 @@
1BSD 3-Clause License
2
3Copyright (c) 2018, oniksan
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions are met:
8
9* Redistributions of source code must retain the above copyright notice, this
10 list of conditions and the following disclaimer.
11
12* Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16* Neither the name of the copyright holder nor the names of its
17 contributors may be used to endorse or promote products derived from
18 this software without specific prior written permission.
19
20THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/godobuf/README b/vendor/godobuf/README new file mode 100644 index 0000000..ce716bb --- /dev/null +++ b/vendor/godobuf/README
@@ -0,0 +1,4 @@
1This is a fork of https://github.com/oniksan/godobuf with some minor changes so
2that it is able to compile the Lingo 2 randomizer proto files. The plugin parts
3of the project have also been removed since we only need the command line
4script.
diff --git a/vendor/godobuf/addons/protobuf/parser.gd b/vendor/godobuf/addons/protobuf/parser.gd new file mode 100644 index 0000000..dfc0bdd --- /dev/null +++ b/vendor/godobuf/addons/protobuf/parser.gd
@@ -0,0 +1,2254 @@
1#
2# BSD 3-Clause License
3#
4# Copyright (c) 2018 - 2023, Oleg Malyavkin
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9#
10# * Redistributions of source code must retain the above copyright notice, this
11# list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright notice,
14# this list of conditions and the following disclaimer in the documentation
15# and/or other materials provided with the distribution.
16#
17# * Neither the name of the copyright holder nor the names of its
18# contributors may be used to endorse or promote products derived from
19# this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32extends Node
33
34const PROTO_VERSION_CONST : String = "const PROTO_VERSION = "
35const PROTO_VERSION_DEFAULT : String = PROTO_VERSION_CONST + "0"
36
37class Document:
38
39 func _init(doc_name : String, doc_text : String):
40 name = doc_name
41 text = doc_text
42
43 var name : String
44 var text : String
45
46class TokenPosition:
47 func _init(b : int, e : int):
48 begin = b
49 end = e
50 var begin : int = 0
51 var end : int = 0
52
53class Helper:
54
55 class StringPosition:
56 func _init(s : int, c : int, l : int):
57 str_num = s
58 column = c
59 length = l
60 var str_num : int
61 var column : int
62 var length : int
63
64 static func str_pos(text : String, position : TokenPosition) -> StringPosition:
65 var cur_str : int = 1
66 var cur_col : int = 1
67 var res_str : int = 0
68 var res_col : int = 0
69 var res_length : int = 0
70 for i in range(text.length()):
71 if text[i] == "\n":
72 cur_str += 1
73 cur_col = 0
74 if position.begin == i:
75 res_str = cur_str
76 res_col = cur_col
77 res_length = position.end - position.begin + 1
78 break
79 cur_col += 1
80 return StringPosition.new(res_str, res_col, res_length)
81
82 static func text_pos(tokens : Array, index : int) -> TokenPosition:
83 var res_begin : int = 0
84 var res_end : int = 0
85 if index < tokens.size() && index >= 0:
86 res_begin = tokens[index].position.begin
87 res_end = tokens[index].position.end
88 return TokenPosition.new(res_begin, res_end)
89
90 static func error_string(file_name, col, row, error_text):
91 return file_name + ":" + str(col) + ":" + str(row) + ": error: " + error_text
92
93class AnalyzeResult:
94 var classes : Array = []
95 var fields : Array = []
96 var groups : Array = []
97 var version : int = 0
98 var state : bool = false
99 var tokens : Array = []
100 var syntax : Analysis.TranslationResult
101 var imports : Array = []
102 var doc : Document
103
104 func soft_copy() -> AnalyzeResult:
105 var res : AnalyzeResult = AnalyzeResult.new()
106 res.classes = classes
107 res.fields = fields
108 res.groups = groups
109 res.version = version
110 res.state = state
111 res.tokens = tokens
112 res.syntax = syntax
113 res.imports = imports
114 res.doc = doc
115 return res
116
117class Analysis:
118
119 func _init(path : String, doc : Document):
120 path_dir = path
121 document = doc
122
123 var document : Document
124 var path_dir : String
125
126 const LEX = {
127 LETTER = "[A-Za-z]",
128 DIGIT_DEC = "[0-9]",
129 DIGIT_OCT = "[0-7]",
130 DIGIT_HEX = "[0-9]|[A-F]|[a-f]",
131 BRACKET_ROUND_LEFT = "\\(",
132 BRACKET_ROUND_RIGHT = "\\)",
133 BRACKET_CURLY_LEFT = "\\{",
134 BRACKET_CURLY_RIGHT = "\\}",
135 BRACKET_SQUARE_LEFT = "\\[",
136 BRACKET_SQUARE_RIGHT = "\\]",
137 BRACKET_ANGLE_LEFT = "\\<",
138 BRACKET_ANGLE_RIGHT = "\\>",
139 SEMICOLON = ";",
140 COMMA = ",",
141 EQUAL = "=",
142 SIGN = "\\+|\\-",
143 SPACE = "\\s",
144 QUOTE_SINGLE = "'",
145 QUOTE_DOUBLE = "\"",
146 }
147
148 const TOKEN_IDENT : String = "(" + LEX.LETTER + "+" + "(" + LEX.LETTER + "|" + LEX.DIGIT_DEC + "|" + "_)*)"
149 const TOKEN_FULL_IDENT : String = TOKEN_IDENT + "{0,1}(\\." + TOKEN_IDENT + ")+"
150 const TOKEN_BRACKET_ROUND_LEFT : String = "(" + LEX.BRACKET_ROUND_LEFT + ")"
151 const TOKEN_BRACKET_ROUND_RIGHT : String = "(" + LEX.BRACKET_ROUND_RIGHT + ")"
152 const TOKEN_BRACKET_CURLY_LEFT : String = "(" + LEX.BRACKET_CURLY_LEFT + ")"
153 const TOKEN_BRACKET_CURLY_RIGHT : String = "(" + LEX.BRACKET_CURLY_RIGHT + ")"
154 const TOKEN_BRACKET_SQUARE_LEFT : String = "(" + LEX.BRACKET_SQUARE_LEFT + ")"
155 const TOKEN_BRACKET_SQUARE_RIGHT : String = "(" + LEX.BRACKET_SQUARE_RIGHT + ")"
156 const TOKEN_BRACKET_ANGLE_LEFT : String = "(" + LEX.BRACKET_ANGLE_LEFT + ")"
157 const TOKEN_BRACKET_ANGLE_RIGHT : String = "(" + LEX.BRACKET_ANGLE_RIGHT + ")"
158 const TOKEN_SEMICOLON : String = "(" + LEX.SEMICOLON + ")"
159 const TOKEN_EUQAL : String = "(" + LEX.EQUAL + ")"
160 const TOKEN_SIGN : String = "(" + LEX.SIGN + ")"
161 const TOKEN_LITERAL_DEC : String = "(([1-9])" + LEX.DIGIT_DEC +"*)"
162 const TOKEN_LITERAL_OCT : String = "(0" + LEX.DIGIT_OCT +"*)"
163 const TOKEN_LITERAL_HEX : String = "(0(x|X)(" + LEX.DIGIT_HEX +")+)"
164 const TOKEN_LITERAL_INT : String = "((\\+|\\-){0,1}" + TOKEN_LITERAL_DEC + "|" + TOKEN_LITERAL_OCT + "|" + TOKEN_LITERAL_HEX + ")"
165 const TOKEN_LITERAL_FLOAT_DEC : String = "(" + LEX.DIGIT_DEC + "+)"
166 const TOKEN_LITERAL_FLOAT_EXP : String = "((e|E)(\\+|\\-)?" + TOKEN_LITERAL_FLOAT_DEC + "+)"
167 const TOKEN_LITERAL_FLOAT : String = "((\\+|\\-){0,1}(" + TOKEN_LITERAL_FLOAT_DEC + "\\." + TOKEN_LITERAL_FLOAT_DEC + "?" + TOKEN_LITERAL_FLOAT_EXP + "?)|(" + TOKEN_LITERAL_FLOAT_DEC + TOKEN_LITERAL_FLOAT_EXP + ")|(\\." + TOKEN_LITERAL_FLOAT_DEC + TOKEN_LITERAL_FLOAT_EXP + "?))"
168 const TOKEN_SPACE : String = "(" + LEX.SPACE + ")+"
169 const TOKEN_COMMA : String = "(" + LEX.COMMA + ")"
170 const TOKEN_CHAR_ESC : String = "[\\\\(a|b|f|n|r|t|v|\\\\|'|\")]"
171 const TOKEN_OCT_ESC : String = "[\\\\" + LEX.DIGIT_OCT + "{3}]"
172 const TOKEN_HEX_ESC : String = "[\\\\(x|X)" + LEX.DIGIT_HEX + "{2}]"
173 const TOKEN_CHAR_EXCLUDE : String = "[^\\0\\n\\\\]"
174 const TOKEN_CHAR_VALUE : String = "(" + TOKEN_HEX_ESC + "|" + TOKEN_OCT_ESC + "|" + TOKEN_CHAR_ESC + "|" + TOKEN_CHAR_EXCLUDE + ")"
175 const TOKEN_STRING_SINGLE : String = "('" + TOKEN_CHAR_VALUE + "*?')"
176 const TOKEN_STRING_DOUBLE : String = "(\"" + TOKEN_CHAR_VALUE + "*?\")"
177 const TOKEN_COMMENT_SINGLE : String = "((//[^\\n\\r]*[^\\s])|//)"
178 const TOKEN_COMMENT_MULTI : String = "/\\*(.|[\\n\\r])*?\\*/"
179
180 const TOKEN_SECOND_MESSAGE : String = "^message$"
181 const TOKEN_SECOND_SIMPLE_DATA_TYPE : String = "^(double|float|int32|int64|uint32|uint64|sint32|sint64|fixed32|fixed64|sfixed32|sfixed64|bool|string|bytes)$"
182 const TOKEN_SECOND_ENUM : String = "^enum$"
183 const TOKEN_SECOND_MAP : String = "^map$"
184 const TOKEN_SECOND_ONEOF : String = "^oneof$"
185 const TOKEN_SECOND_LITERAL_BOOL : String = "^(true|false)$"
186 const TOKEN_SECOND_SYNTAX : String = "^syntax$"
187 const TOKEN_SECOND_IMPORT : String = "^import$"
188 const TOKEN_SECOND_PACKAGE : String = "^package$"
189 const TOKEN_SECOND_OPTION : String = "^option$"
190 const TOKEN_SECOND_SERVICE : String = "^service$"
191 const TOKEN_SECOND_RESERVED : String = "^reserved$"
192 const TOKEN_SECOND_IMPORT_QUALIFICATION : String = "^(weak|public)$"
193 const TOKEN_SECOND_FIELD_QUALIFICATION : String = "^(repeated|required|optional)$"
194 const TOKEN_SECOND_ENUM_OPTION : String = "^allow_alias$"
195 const TOKEN_SECOND_QUALIFICATION : String = "^(custom_option|extensions)$"
196 const TOKEN_SECOND_FIELD_OPTION : String = "^packed$"
197
198 class TokenEntrance:
199 func _init(i : int, b : int, e : int, t : String):
200 position = TokenPosition.new(b, e)
201 text = t
202 id = i
203 var position : TokenPosition
204 var text : String
205 var id : int
206
207 enum RANGE_STATE {
208 INCLUDE = 0,
209 EXCLUDE_LEFT = 1,
210 EXCLUDE_RIGHT = 2,
211 OVERLAY = 3,
212 EQUAL = 4,
213 ENTERS = 5
214 }
215
216 class TokenRange:
217 func _init(b : int, e : int, s):
218 position = TokenPosition.new(b, e)
219 state = s
220 var position : TokenPosition
221 var state
222
223 class Token:
224 var _regex : RegEx
225 var _entrance : TokenEntrance = null
226 var _entrances : Array = []
227 var _entrance_index : int = 0
228 var _id : int
229 var _ignore : bool
230 var _clarification : String
231
232 func _init(id : int, clarification : String, regex_str : String, ignore = false):
233 _id = id
234 _regex = RegEx.new()
235 _regex.compile(regex_str)
236 _clarification = clarification
237 _ignore = ignore
238
239 func find(text : String, start : int) -> TokenEntrance:
240 _entrance = null
241 if !_regex.is_valid():
242 return null
243 var match_result : RegExMatch = _regex.search(text, start)
244 if match_result != null:
245 var capture
246 capture = match_result.get_string(0)
247 if capture.is_empty():
248 return null
249 _entrance = TokenEntrance.new(_id, match_result.get_start(0), capture.length() - 1 + match_result.get_start(0), capture)
250 return _entrance
251
252 func find_all(text : String) -> Array:
253 var pos : int = 0
254 clear()
255 while find(text, pos) != null:
256 _entrances.append(_entrance)
257 pos = _entrance.position.end + 1
258 return _entrances
259
260 func add_entrance(entrance) -> void:
261 _entrances.append(entrance)
262
263 func clear() -> void:
264 _entrance = null
265 _entrances = []
266 _entrance_index = 0
267
268 func get_entrances() -> Array:
269 return _entrances
270
271 func remove_entrance(index) -> void:
272 if index < _entrances.size():
273 _entrances.remove_at(index)
274
275 func get_index() -> int:
276 return _entrance_index
277
278 func set_index(index : int) -> void:
279 if index < _entrances.size():
280 _entrance_index = index
281 else:
282 _entrance_index = 0
283
284 func is_ignore() -> bool:
285 return _ignore
286
287 func get_clarification() -> String:
288 return _clarification
289
290 class TokenResult:
291 var tokens : Array = []
292 var errors : Array = []
293
294 enum TOKEN_ID {
295 UNDEFINED = -1,
296 IDENT = 0,
297 FULL_IDENT = 1,
298 BRACKET_ROUND_LEFT = 2,
299 BRACKET_ROUND_RIGHT = 3,
300 BRACKET_CURLY_LEFT = 4,
301 BRACKET_CURLY_RIGHT = 5,
302 BRACKET_SQUARE_LEFT = 6,
303 BRACKET_SQUARE_RIGHT = 7,
304 BRACKET_ANGLE_LEFT = 8,
305 BRACKET_ANGLE_RIGHT = 9,
306 SEMICOLON = 10,
307 EUQAL = 11,
308 SIGN = 12,
309 INT = 13,
310 FLOAT = 14,
311 SPACE = 15,
312 COMMA = 16,
313 STRING_SINGLE = 17,
314 STRING_DOUBLE = 18,
315 COMMENT_SINGLE = 19,
316 COMMENT_MULTI = 20,
317
318 MESSAGE = 21,
319 SIMPLE_DATA_TYPE = 22,
320 ENUM = 23,
321 MAP = 24,
322 ONEOF = 25,
323 LITERAL_BOOL = 26,
324 SYNTAX = 27,
325 IMPORT = 28,
326 PACKAGE = 29,
327 OPTION = 30,
328 SERVICE = 31,
329 RESERVED = 32,
330 IMPORT_QUALIFICATION = 33,
331 FIELD_QUALIFICATION = 34,
332 ENUM_OPTION = 35,
333 QUALIFICATION = 36,
334 FIELD_OPTION = 37,
335
336 STRING = 38
337 }
338
339 var TOKEN = {
340 TOKEN_ID.IDENT: Token.new(TOKEN_ID.IDENT, "Identifier", TOKEN_IDENT),
341 TOKEN_ID.FULL_IDENT: Token.new(TOKEN_ID.FULL_IDENT, "Full identifier", TOKEN_FULL_IDENT),
342 TOKEN_ID.BRACKET_ROUND_LEFT: Token.new(TOKEN_ID.BRACKET_ROUND_LEFT, "(", TOKEN_BRACKET_ROUND_LEFT),
343 TOKEN_ID.BRACKET_ROUND_RIGHT: Token.new(TOKEN_ID.BRACKET_ROUND_RIGHT, ")", TOKEN_BRACKET_ROUND_RIGHT),
344 TOKEN_ID.BRACKET_CURLY_LEFT: Token.new(TOKEN_ID.BRACKET_CURLY_LEFT, "{", TOKEN_BRACKET_CURLY_LEFT),
345 TOKEN_ID.BRACKET_CURLY_RIGHT: Token.new(TOKEN_ID.BRACKET_CURLY_RIGHT, "}", TOKEN_BRACKET_CURLY_RIGHT),
346 TOKEN_ID.BRACKET_SQUARE_LEFT: Token.new(TOKEN_ID.BRACKET_SQUARE_LEFT, "[", TOKEN_BRACKET_SQUARE_LEFT),
347 TOKEN_ID.BRACKET_SQUARE_RIGHT: Token.new(TOKEN_ID.BRACKET_SQUARE_RIGHT, "]", TOKEN_BRACKET_SQUARE_RIGHT),
348 TOKEN_ID.BRACKET_ANGLE_LEFT: Token.new(TOKEN_ID.BRACKET_ANGLE_LEFT, "<", TOKEN_BRACKET_ANGLE_LEFT),
349 TOKEN_ID.BRACKET_ANGLE_RIGHT: Token.new(TOKEN_ID.BRACKET_ANGLE_RIGHT, ">", TOKEN_BRACKET_ANGLE_RIGHT),
350 TOKEN_ID.SEMICOLON: Token.new(TOKEN_ID.SEMICOLON, ";", TOKEN_SEMICOLON),
351 TOKEN_ID.EUQAL: Token.new(TOKEN_ID.EUQAL, "=", TOKEN_EUQAL),
352 TOKEN_ID.INT: Token.new(TOKEN_ID.INT, "Integer", TOKEN_LITERAL_INT),
353 TOKEN_ID.FLOAT: Token.new(TOKEN_ID.FLOAT, "Float", TOKEN_LITERAL_FLOAT),
354 TOKEN_ID.SPACE: Token.new(TOKEN_ID.SPACE, "Space", TOKEN_SPACE),
355 TOKEN_ID.COMMA: Token.new(TOKEN_ID.COMMA, ",", TOKEN_COMMA),
356 TOKEN_ID.STRING_SINGLE: Token.new(TOKEN_ID.STRING_SINGLE, "'String'", TOKEN_STRING_SINGLE),
357 TOKEN_ID.STRING_DOUBLE: Token.new(TOKEN_ID.STRING_DOUBLE, "\"String\"", TOKEN_STRING_DOUBLE),
358 TOKEN_ID.COMMENT_SINGLE: Token.new(TOKEN_ID.COMMENT_SINGLE, "//Comment", TOKEN_COMMENT_SINGLE),
359 TOKEN_ID.COMMENT_MULTI: Token.new(TOKEN_ID.COMMENT_MULTI, "/*Comment*/", TOKEN_COMMENT_MULTI),
360
361 TOKEN_ID.MESSAGE: Token.new(TOKEN_ID.MESSAGE, "Message", TOKEN_SECOND_MESSAGE, true),
362 TOKEN_ID.SIMPLE_DATA_TYPE: Token.new(TOKEN_ID.SIMPLE_DATA_TYPE, "Data type", TOKEN_SECOND_SIMPLE_DATA_TYPE, true),
363 TOKEN_ID.ENUM: Token.new(TOKEN_ID.ENUM, "Enum", TOKEN_SECOND_ENUM, true),
364 TOKEN_ID.MAP: Token.new(TOKEN_ID.MAP, "Map", TOKEN_SECOND_MAP, true),
365 TOKEN_ID.ONEOF: Token.new(TOKEN_ID.ONEOF, "OneOf", TOKEN_SECOND_ONEOF, true),
366 TOKEN_ID.LITERAL_BOOL: Token.new(TOKEN_ID.LITERAL_BOOL, "Bool literal", TOKEN_SECOND_LITERAL_BOOL, true),
367 TOKEN_ID.SYNTAX: Token.new(TOKEN_ID.SYNTAX, "Syntax", TOKEN_SECOND_SYNTAX, true),
368 TOKEN_ID.IMPORT: Token.new(TOKEN_ID.IMPORT, "Import", TOKEN_SECOND_IMPORT, true),
369 TOKEN_ID.PACKAGE: Token.new(TOKEN_ID.PACKAGE, "Package", TOKEN_SECOND_PACKAGE, true),
370 TOKEN_ID.OPTION: Token.new(TOKEN_ID.OPTION, "Option", TOKEN_SECOND_OPTION, true),
371 TOKEN_ID.SERVICE: Token.new(TOKEN_ID.SERVICE, "Service", TOKEN_SECOND_SERVICE, true),
372 TOKEN_ID.RESERVED: Token.new(TOKEN_ID.RESERVED, "Reserved", TOKEN_SECOND_RESERVED, true),
373 TOKEN_ID.IMPORT_QUALIFICATION: Token.new(TOKEN_ID.IMPORT_QUALIFICATION, "Import qualification", TOKEN_SECOND_IMPORT_QUALIFICATION, true),
374 TOKEN_ID.FIELD_QUALIFICATION: Token.new(TOKEN_ID.FIELD_QUALIFICATION, "Field qualification", TOKEN_SECOND_FIELD_QUALIFICATION, true),
375 TOKEN_ID.ENUM_OPTION: Token.new(TOKEN_ID.ENUM_OPTION, "Enum option", TOKEN_SECOND_ENUM_OPTION, true),
376 TOKEN_ID.QUALIFICATION: Token.new(TOKEN_ID.QUALIFICATION, "Qualification", TOKEN_SECOND_QUALIFICATION, true),
377 TOKEN_ID.FIELD_OPTION: Token.new(TOKEN_ID.FIELD_OPTION, "Field option", TOKEN_SECOND_FIELD_OPTION, true),
378
379 TOKEN_ID.STRING: Token.new(TOKEN_ID.STRING, "String", "", true)
380 }
381
382 static func check_range(main : TokenEntrance, current : TokenEntrance) -> TokenRange:
383 if main.position.begin > current.position.begin:
384 if main.position.end > current.position.end:
385 if main.position.begin >= current.position.end:
386 return TokenRange.new(current.position.begin, current.position.end, RANGE_STATE.EXCLUDE_LEFT)
387 else:
388 return TokenRange.new(main.position.begin, current.position.end, RANGE_STATE.OVERLAY)
389 else:
390 return TokenRange.new(current.position.begin, current.position.end, RANGE_STATE.ENTERS)
391 elif main.position.begin < current.position.begin:
392 if main.position.end >= current.position.end:
393 return TokenRange.new(main.position.begin, main.position.end, RANGE_STATE.INCLUDE)
394 else:
395 if main.position.end < current.position.begin:
396 return TokenRange.new(main.position.begin, main.position.end, RANGE_STATE.EXCLUDE_RIGHT)
397 else:
398 return TokenRange.new(main.position.begin, current.position.end, RANGE_STATE.OVERLAY)
399 else:
400 if main.position.end == current.position.end:
401 return TokenRange.new(main.position.begin, main.position.end, RANGE_STATE.EQUAL)
402 elif main.position.end > current.position.end:
403 return TokenRange.new(main.position.begin, main.position.end, RANGE_STATE.INCLUDE)
404 else:
405 return TokenRange.new(current.position.begin, current.position.end, RANGE_STATE.ENTERS)
406
407 func tokenizer() -> TokenResult:
408 for k in TOKEN:
409 if !TOKEN[k].is_ignore():
410 TOKEN[k].find_all(document.text)
411 var second_tokens : Array = []
412 second_tokens.append(TOKEN[TOKEN_ID.MESSAGE])
413 second_tokens.append(TOKEN[TOKEN_ID.SIMPLE_DATA_TYPE])
414 second_tokens.append(TOKEN[TOKEN_ID.ENUM])
415 second_tokens.append(TOKEN[TOKEN_ID.MAP])
416 second_tokens.append(TOKEN[TOKEN_ID.ONEOF])
417 second_tokens.append(TOKEN[TOKEN_ID.LITERAL_BOOL])
418 second_tokens.append(TOKEN[TOKEN_ID.SYNTAX])
419 second_tokens.append(TOKEN[TOKEN_ID.IMPORT])
420 second_tokens.append(TOKEN[TOKEN_ID.PACKAGE])
421 second_tokens.append(TOKEN[TOKEN_ID.OPTION])
422 second_tokens.append(TOKEN[TOKEN_ID.SERVICE])
423 second_tokens.append(TOKEN[TOKEN_ID.RESERVED])
424 second_tokens.append(TOKEN[TOKEN_ID.IMPORT_QUALIFICATION])
425 second_tokens.append(TOKEN[TOKEN_ID.FIELD_QUALIFICATION])
426 second_tokens.append(TOKEN[TOKEN_ID.ENUM_OPTION])
427 second_tokens.append(TOKEN[TOKEN_ID.QUALIFICATION])
428 second_tokens.append(TOKEN[TOKEN_ID.FIELD_OPTION])
429
430 var ident_token : Token = TOKEN[TOKEN_ID.IDENT]
431 for sec_token in second_tokens:
432 var remove_indexes : Array = []
433 for i in range(ident_token.get_entrances().size()):
434 var entrance : TokenEntrance = sec_token.find(ident_token.get_entrances()[i].text, 0)
435 if entrance != null:
436 entrance.position.begin = ident_token.get_entrances()[i].position.begin
437 entrance.position.end = ident_token.get_entrances()[i].position.end
438 sec_token.add_entrance(entrance)
439 remove_indexes.append(i)
440 for i in range(remove_indexes.size()):
441 ident_token.remove_entrance(remove_indexes[i] - i)
442 for v in TOKEN[TOKEN_ID.STRING_DOUBLE].get_entrances():
443 v.id = TOKEN_ID.STRING
444 TOKEN[TOKEN_ID.STRING].add_entrance(v)
445 TOKEN[TOKEN_ID.STRING_DOUBLE].clear()
446 for v in TOKEN[TOKEN_ID.STRING_SINGLE].get_entrances():
447 v.id = TOKEN_ID.STRING
448 TOKEN[TOKEN_ID.STRING].add_entrance(v)
449 TOKEN[TOKEN_ID.STRING_SINGLE].clear()
450 var main_token : TokenEntrance
451 var cur_token : TokenEntrance
452 var main_index : int = -1
453 var token_index_flag : bool = false
454 var result : TokenResult = TokenResult.new()
455 var check : TokenRange
456 var end : bool = false
457 var all : bool = false
458 var repeat : bool = false
459 while true:
460 all = true
461 for k in TOKEN:
462 if main_index == k:
463 continue
464 repeat = false
465 while TOKEN[k].get_entrances().size() > 0:
466 all = false
467 if !token_index_flag:
468 main_index = k
469 main_token = TOKEN[main_index].get_entrances()[0]
470 token_index_flag = true
471 break
472 else:
473 cur_token = TOKEN[k].get_entrances()[0]
474 check = check_range(main_token, cur_token)
475 if check.state == RANGE_STATE.INCLUDE:
476 TOKEN[k].remove_entrance(0)
477 end = true
478 elif check.state == RANGE_STATE.EXCLUDE_LEFT:
479 main_token = cur_token
480 main_index = k
481 end = false
482 repeat = true
483 break
484 elif check.state == RANGE_STATE.EXCLUDE_RIGHT:
485 end = true
486 break
487 elif check.state == RANGE_STATE.OVERLAY || check.state == RANGE_STATE.EQUAL:
488 result.errors.append(check)
489 TOKEN[main_index].remove_entrance(0)
490 TOKEN[k].remove_entrance(0)
491 token_index_flag = false
492 end = false
493 repeat = true
494 break
495 elif check.state == RANGE_STATE.ENTERS:
496 TOKEN[main_index].remove_entrance(0)
497 main_token = cur_token
498 main_index = k
499 end = false
500 repeat = true
501 break
502 if repeat:
503 break
504 if end:
505 if TOKEN[main_index].get_entrances().size() > 0:
506 result.tokens.append(main_token)
507 TOKEN[main_index].remove_entrance(0)
508 token_index_flag = false
509 if all:
510 break
511 return result
512
513 static func check_tokens_integrity(tokens : Array, end : int) -> Array:
514 var cur_index : int = 0
515 var result : Array = []
516 for v in tokens:
517 if v.position.begin > cur_index:
518 result.append(TokenPosition.new(cur_index, v.position.begin))
519 cur_index = v.position.end + 1
520 if cur_index < end:
521 result.append(TokenPosition.new(cur_index, end))
522 return result
523
524 static func comment_space_processing(tokens : Array) -> void:
525 var remove_indexes : Array = []
526 for i in range(tokens.size()):
527 if tokens[i].id == TOKEN_ID.COMMENT_SINGLE || tokens[i].id == TOKEN_ID.COMMENT_MULTI:
528 tokens[i].id = TOKEN_ID.SPACE
529 var space_index : int = -1
530 for i in range(tokens.size()):
531 if tokens[i].id == TOKEN_ID.SPACE:
532 if space_index >= 0:
533 tokens[space_index].position.end = tokens[i].position.end
534 tokens[space_index].text = tokens[space_index].text + tokens[i].text
535 remove_indexes.append(i)
536 else:
537 space_index = i
538 else:
539 space_index = -1
540 for i in range(remove_indexes.size()):
541 tokens.remove_at(remove_indexes[i] - i)
542
543 #Analysis rule
544 enum AR {
545 MAYBE = 1,
546 MUST_ONE = 2,
547 ANY = 3,
548 OR = 4,
549 MAYBE_BEGIN = 5,
550 MAYBE_END = 6,
551 ANY_BEGIN = 7,
552 ANY_END = 8
553 }
554
555 #Space rule (space after token)
556 enum SP {
557 MAYBE = 1,
558 MUST = 2,
559 NO = 3
560 }
561
562 #Analysis Syntax Description
563 class ASD:
564 func _init(t, s : int = SP.MAYBE, r : int = AR.MUST_ONE, i : bool = false):
565 token = t
566 space = s
567 rule = r
568 importance = i
569 var token
570 var space : int
571 var rule : int
572 var importance : bool
573
574 var TEMPLATE_SYNTAX : Array = [
575 Callable(self, "desc_syntax"),
576 ASD.new(TOKEN_ID.SYNTAX),
577 ASD.new(TOKEN_ID.EUQAL),
578 ASD.new(TOKEN_ID.STRING, SP.MAYBE, AR.MUST_ONE, true),
579 ASD.new(TOKEN_ID.SEMICOLON)
580 ]
581
582 var TEMPLATE_IMPORT : Array = [
583 Callable(self, "desc_import"),
584 ASD.new(TOKEN_ID.IMPORT, SP.MUST),
585 ASD.new(TOKEN_ID.IMPORT_QUALIFICATION, SP.MUST, AR.MAYBE, true),
586 ASD.new(TOKEN_ID.STRING, SP.MAYBE, AR.MUST_ONE, true),
587 ASD.new(TOKEN_ID.SEMICOLON)
588 ]
589
590 var TEMPLATE_PACKAGE : Array = [
591 Callable(self, "desc_package"),
592 ASD.new(TOKEN_ID.PACKAGE, SP.MUST),
593 ASD.new([TOKEN_ID.IDENT, TOKEN_ID.FULL_IDENT], SP.MAYBE, AR.OR, true),
594 ASD.new(TOKEN_ID.SEMICOLON)
595 ]
596
597 var TEMPLATE_OPTION : Array = [
598 Callable(self, "desc_option"),
599 ASD.new(TOKEN_ID.OPTION, SP.MUST),
600 ASD.new([TOKEN_ID.IDENT, TOKEN_ID.FULL_IDENT], SP.MAYBE, AR.OR, true),
601 ASD.new(TOKEN_ID.EUQAL),
602 ASD.new([TOKEN_ID.STRING, TOKEN_ID.INT, TOKEN_ID.FLOAT, TOKEN_ID.LITERAL_BOOL], SP.MAYBE, AR.OR, true),
603 ASD.new(TOKEN_ID.SEMICOLON)
604 ]
605
606 var TEMPLATE_FIELD : Array = [
607 Callable(self, "desc_field"),
608 ASD.new(TOKEN_ID.FIELD_QUALIFICATION, SP.MUST, AR.MAYBE, true),
609 ASD.new([TOKEN_ID.SIMPLE_DATA_TYPE, TOKEN_ID.IDENT, TOKEN_ID.FULL_IDENT], SP.MAYBE, AR.OR, true),
610 ASD.new(TOKEN_ID.IDENT, SP.MAYBE, AR.MUST_ONE, true),
611 ASD.new(TOKEN_ID.EUQAL),
612 ASD.new(TOKEN_ID.INT, SP.MAYBE, AR.MUST_ONE, true),
613 ASD.new(TOKEN_ID.BRACKET_SQUARE_LEFT, SP.MAYBE, AR.MAYBE_BEGIN),
614 ASD.new(TOKEN_ID.FIELD_OPTION, SP.MAYBE, AR.MUST_ONE, true),
615 ASD.new(TOKEN_ID.EUQAL),
616 ASD.new(TOKEN_ID.LITERAL_BOOL, SP.MAYBE, AR.MUST_ONE, true),
617 ASD.new(TOKEN_ID.BRACKET_SQUARE_RIGHT, SP.MAYBE, AR.MAYBE_END),
618 ASD.new(TOKEN_ID.SEMICOLON)
619 ]
620
621 var TEMPLATE_FIELD_ONEOF : Array = TEMPLATE_FIELD
622
623 var TEMPLATE_MAP_FIELD : Array = [
624 Callable(self, "desc_map_field"),
625 ASD.new(TOKEN_ID.MAP),
626 ASD.new(TOKEN_ID.BRACKET_ANGLE_LEFT),
627 ASD.new(TOKEN_ID.SIMPLE_DATA_TYPE, SP.MAYBE, AR.MUST_ONE, true),
628 ASD.new(TOKEN_ID.COMMA),
629 ASD.new([TOKEN_ID.SIMPLE_DATA_TYPE, TOKEN_ID.IDENT, TOKEN_ID.FULL_IDENT], SP.MAYBE, AR.OR, true),
630 ASD.new(TOKEN_ID.BRACKET_ANGLE_RIGHT, SP.MUST),
631 ASD.new(TOKEN_ID.IDENT, SP.MAYBE, AR.MUST_ONE, true),
632 ASD.new(TOKEN_ID.EUQAL),
633 ASD.new(TOKEN_ID.INT, SP.MAYBE, AR.MUST_ONE, true),
634 ASD.new(TOKEN_ID.BRACKET_SQUARE_LEFT, SP.MAYBE, AR.MAYBE_BEGIN),
635 ASD.new(TOKEN_ID.FIELD_OPTION, SP.MAYBE, AR.MUST_ONE, true),
636 ASD.new(TOKEN_ID.EUQAL),
637 ASD.new(TOKEN_ID.LITERAL_BOOL, SP.MAYBE, AR.MUST_ONE, true),
638 ASD.new(TOKEN_ID.BRACKET_SQUARE_RIGHT, SP.MAYBE, AR.MAYBE_END),
639 ASD.new(TOKEN_ID.SEMICOLON)
640 ]
641
642 var TEMPLATE_MAP_FIELD_ONEOF : Array = TEMPLATE_MAP_FIELD
643
644 var TEMPLATE_ENUM : Array = [
645 Callable(self, "desc_enum"),
646 ASD.new(TOKEN_ID.ENUM, SP.MUST),
647 ASD.new(TOKEN_ID.IDENT, SP.MAYBE, AR.MUST_ONE, true),
648 ASD.new(TOKEN_ID.BRACKET_CURLY_LEFT),
649 ASD.new(TOKEN_ID.OPTION, SP.MUST, AR.MAYBE_BEGIN),
650 ASD.new(TOKEN_ID.ENUM_OPTION, SP.MAYBE, AR.MUST_ONE, true),
651 ASD.new(TOKEN_ID.EUQAL),
652 ASD.new(TOKEN_ID.LITERAL_BOOL, SP.MAYBE, AR.MUST_ONE, true),
653 ASD.new(TOKEN_ID.SEMICOLON, SP.MAYBE, AR.MAYBE_END),
654 ASD.new(TOKEN_ID.IDENT, SP.MAYBE, AR.ANY_BEGIN, true),
655 ASD.new(TOKEN_ID.EUQAL),
656 ASD.new(TOKEN_ID.INT, SP.MAYBE, AR.MUST_ONE, true),
657 ASD.new(TOKEN_ID.SEMICOLON, SP.MAYBE, AR.ANY_END),
658 ASD.new(TOKEN_ID.BRACKET_CURLY_RIGHT)
659 ]
660
661 var TEMPLATE_MESSAGE_HEAD : Array = [
662 Callable(self, "desc_message_head"),
663 ASD.new(TOKEN_ID.MESSAGE, SP.MUST),
664 ASD.new(TOKEN_ID.IDENT, SP.MAYBE, AR.MUST_ONE, true),
665 ASD.new(TOKEN_ID.BRACKET_CURLY_LEFT)
666 ]
667
668 var TEMPLATE_MESSAGE_TAIL : Array = [
669 Callable(self, "desc_message_tail"),
670 ASD.new(TOKEN_ID.BRACKET_CURLY_RIGHT)
671 ]
672
673 var TEMPLATE_ONEOF_HEAD : Array = [
674 Callable(self, "desc_oneof_head"),
675 ASD.new(TOKEN_ID.ONEOF, SP.MUST),
676 ASD.new(TOKEN_ID.IDENT, SP.MAYBE, AR.MUST_ONE, true),
677 ASD.new(TOKEN_ID.BRACKET_CURLY_LEFT),
678 ]
679
680 var TEMPLATE_ONEOF_TAIL : Array = [
681 Callable(self, "desc_oneof_tail"),
682 ASD.new(TOKEN_ID.BRACKET_CURLY_RIGHT)
683 ]
684
685 var TEMPLATE_BEGIN : Array = [
686 null,
687 ASD.new(TOKEN_ID.SPACE, SP.NO, AR.MAYBE)
688 ]
689
690 var TEMPLATE_END : Array = [
691 null
692 ]
693
694 func get_token_id(tokens : Array, index : int) -> int:
695 if index < tokens.size():
696 return tokens[index].id
697 return TOKEN_ID.UNDEFINED
698
699 enum COMPARE_STATE {
700 DONE = 0,
701 MISMATCH = 1,
702 INCOMPLETE = 2,
703 ERROR_VALUE = 3
704 }
705
706 class TokenCompare:
707 func _init(s : int, i : int, d : String = ""):
708 state = s
709 index = i
710 description = d
711 var state : int
712 var index : int
713 var description : String
714
715 func check_space(tokens : Array, index : int, space) -> int:
716 if get_token_id(tokens, index) == TOKEN_ID.SPACE:
717 if space == SP.MAYBE:
718 return 1
719 elif space == SP.MUST:
720 return 1
721 elif space == SP.NO:
722 return -1
723 else:
724 if space == SP.MUST:
725 return -2
726 return 0
727
728 class IndexedToken:
729 func _init(t : TokenEntrance, i : int):
730 token = t
731 index = i
732 var token : TokenEntrance
733 var index : int
734
735 func token_importance_checkadd(template : ASD, token : TokenEntrance, index : int, importance : Array) -> void:
736 if template.importance:
737 importance.append(IndexedToken.new(token, index))
738
739 class CompareSettings:
740 func _init(ci : int, n : int, pi : int, pn : String = ""):
741 construction_index = ci
742 nesting = n
743 parent_index = pi
744 parent_name = pn
745
746 var construction_index : int
747 var nesting : int
748 var parent_index : int
749 var parent_name : String
750
751 func description_compare(template : Array, tokens : Array, index : int, settings : CompareSettings) -> TokenCompare:
752 var j : int = index
753 var space : int
754 var rule : int
755 var rule_flag : bool
756 var cont : bool
757 var check : int
758 var maybe_group_skip : bool = false
759 var any_group_index : int = -1
760 var any_end_group_index : int = -1
761 var i : int = 0
762 var importance : Array = []
763 while true:
764 i += 1
765 if i >= template.size():
766 break
767 rule_flag = false
768 cont = false
769 rule = template[i].rule
770 space = template[i].space
771 if rule == AR.MAYBE_END && maybe_group_skip:
772 maybe_group_skip = false
773 continue
774 if maybe_group_skip:
775 continue
776 if rule == AR.MAYBE:
777 if template[i].token == get_token_id(tokens, j):
778 token_importance_checkadd(template[i], tokens[j], j, importance)
779 rule_flag = true
780 else:
781 continue
782 elif rule == AR.MUST_ONE || rule == AR.MAYBE_END || rule == AR.ANY_END:
783 if template[i].token == get_token_id(tokens, j):
784 token_importance_checkadd(template[i], tokens[j], j, importance)
785 rule_flag = true
786 elif rule == AR.ANY:
787 var find_any : bool = false
788 while true:
789 if template[i].token == get_token_id(tokens, j):
790 token_importance_checkadd(template[i], tokens[j], j, importance)
791 find_any = true
792 j += 1
793 check = check_space(tokens, j, space)
794 if check < 0:
795 return TokenCompare.new(COMPARE_STATE.INCOMPLETE, j)
796 else:
797 j += check
798 else:
799 if find_any:
800 cont = true
801 break
802 elif rule == AR.OR:
803 var or_tokens = template[i].token
804 for v in or_tokens:
805 if v == get_token_id(tokens, j):
806 token_importance_checkadd(template[i], tokens[j], j, importance)
807 j += 1
808 check = check_space(tokens, j, space)
809 if check < 0:
810 return TokenCompare.new(COMPARE_STATE.INCOMPLETE, j)
811 else:
812 j += check
813 cont = true
814 break
815 elif rule == AR.MAYBE_BEGIN:
816 if template[i].token == get_token_id(tokens, j):
817 token_importance_checkadd(template[i], tokens[j], j, importance)
818 rule_flag = true
819 else:
820 maybe_group_skip = true
821 continue
822 elif rule == AR.ANY_BEGIN:
823 if template[i].token == get_token_id(tokens, j):
824 token_importance_checkadd(template[i], tokens[j], j, importance)
825 rule_flag = true
826 any_group_index = i
827 else:
828 if any_end_group_index > 0:
829 any_group_index = -1
830 i = any_end_group_index
831 any_end_group_index = -1
832 continue
833 if cont:
834 continue
835 if rule_flag:
836 j += 1
837 check = check_space(tokens, j, space)
838 if check < 0:
839 return TokenCompare.new(COMPARE_STATE.INCOMPLETE, j)
840 else:
841 j += check
842 else:
843 if j > index:
844 return TokenCompare.new(COMPARE_STATE.INCOMPLETE, j)
845 else:
846 return TokenCompare.new(COMPARE_STATE.MISMATCH, j)
847 if any_group_index >= 0 && rule == AR.ANY_END:
848 any_end_group_index = i
849 i = any_group_index - 1
850 if template[0] != null:
851 var result : DescriptionResult = template[0].call(importance, settings)
852 if !result.success:
853 return TokenCompare.new(COMPARE_STATE.ERROR_VALUE, result.error, result.description)
854 return TokenCompare.new(COMPARE_STATE.DONE, j)
855
856 var DESCRIPTION : Array = [
857 TEMPLATE_BEGIN, #0
858 TEMPLATE_SYNTAX, #1
859 TEMPLATE_IMPORT, #2
860 TEMPLATE_PACKAGE, #3
861 TEMPLATE_OPTION, #4
862 TEMPLATE_FIELD, #5
863 TEMPLATE_FIELD_ONEOF, #6
864 TEMPLATE_MAP_FIELD, #7
865 TEMPLATE_MAP_FIELD_ONEOF, #8
866 TEMPLATE_ENUM, #9
867 TEMPLATE_MESSAGE_HEAD, #10
868 TEMPLATE_MESSAGE_TAIL, #11
869 TEMPLATE_ONEOF_HEAD, #12
870 TEMPLATE_ONEOF_TAIL, #13
871 TEMPLATE_END #14
872 ]
873
874 enum JUMP {
875 NOTHING = 0, #nothing
876 SIMPLE = 1, #simple jump
877 NESTED_INCREMENT = 2, #nested increment
878 NESTED_DECREMENT = 3, #nested decrement
879 MUST_NESTED_SIMPLE = 4, #check: must be nested > 0
880 MUST_NESTED_INCREMENT = 5, #check: must be nested > 0, then nested increment
881 MUST_NESTED_DECREMENT = 6, #nested decrement, then check: must be nested > 0
882 }
883
884 var TRANSLATION_TABLE : Array = [
885 # BEGIN SYNTAX IMPORT PACKAGE OPTION FIELD FIELD_O MAP_F MAP_F_O ENUM MES_H MES_T ONEOF_H ONEOF_T END
886 [ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], #BEGIN
887 [ 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 1], #SYNTAX
888 [ 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 1], #IMPORT
889 [ 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 1], #PACKAGE
890 [ 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 2, 0, 0, 0, 1], #OPTION
891 [ 0, 0, 0, 0, 0, 4, 0, 4, 0, 1, 2, 3, 5, 0, 0], #FIELD
892 [ 0, 0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 6, 0], #FIELD_ONEOF
893 [ 0, 0, 0, 0, 0, 4, 0, 4, 0, 1, 2, 3, 5, 0, 0], #MAP_F
894 [ 0, 0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 6, 0], #MAP_F_ONEOF
895 [ 0, 0, 0, 0, 0, 4, 0, 4, 0, 1, 2, 3, 5, 0, 1], #ENUM
896 [ 0, 0, 0, 0, 0, 4, 0, 4, 0, 1, 2, 3, 5, 0, 0], #MES_H
897 [ 0, 0, 0, 0, 0, 4, 0, 4, 0, 1, 2, 3, 5, 0, 1], #MES_T
898 [ 0, 0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 0, 0], #ONEOF_H
899 [ 0, 0, 0, 0, 0, 4, 0, 4, 0, 1, 2, 3, 5, 0, 1], #ONEOF_T
900 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] #END
901 ]
902
903 class Construction:
904 func _init(b : int, e : int, d : int):
905 begin_token_index = b
906 end_token_index = e
907 description = d
908 var begin_token_index : int
909 var end_token_index : int
910 var description : int
911
912 class TranslationResult:
913 var constructions : Array = []
914 var done : bool = false
915 var error_description_id : int = -1
916 var error_description_text : String = ""
917 var parse_token_index : int = 0
918 var error_token_index : int = 0
919
920 func analyze_tokens(tokens : Array) -> TranslationResult:
921 var i : int = 0
922 var result : TranslationResult = TranslationResult.new()
923 var comp : TokenCompare
924 var cur_template_id : int = 0
925 var error : bool = false
926 var template_index : int
927 var comp_set : CompareSettings = CompareSettings.new(result.constructions.size(), 0, -1)
928 comp = description_compare(DESCRIPTION[cur_template_id], tokens, i, comp_set)
929 if comp.state == COMPARE_STATE.DONE:
930 i = comp.index
931 while true:
932 var end : bool = true
933 var find : bool = false
934 for j in range(TRANSLATION_TABLE[cur_template_id].size()):
935 template_index = j
936 if j == DESCRIPTION.size() - 1 && i < tokens.size():
937 end = false
938 if result.error_description_id < 0:
939 error = true
940 break
941 if TRANSLATION_TABLE[cur_template_id][j] > 0:
942 end = false
943 comp_set.construction_index = result.constructions.size()
944 comp = description_compare(DESCRIPTION[j], tokens, i, comp_set)
945 if comp.state == COMPARE_STATE.DONE:
946 if TRANSLATION_TABLE[cur_template_id][j] == JUMP.NESTED_INCREMENT:
947 comp_set.nesting += 1
948 elif TRANSLATION_TABLE[cur_template_id][j] == JUMP.NESTED_DECREMENT:
949 comp_set.nesting -= 1
950 if comp_set.nesting < 0:
951 error = true
952 break
953 elif TRANSLATION_TABLE[cur_template_id][j] == JUMP.MUST_NESTED_SIMPLE:
954 if comp_set.nesting <= 0:
955 error = true
956 break
957 elif TRANSLATION_TABLE[cur_template_id][j] == JUMP.MUST_NESTED_INCREMENT:
958 if comp_set.nesting <= 0:
959 error = true
960 break
961 comp_set.nesting += 1
962 elif TRANSLATION_TABLE[cur_template_id][j] == JUMP.MUST_NESTED_DECREMENT:
963 comp_set.nesting -= 1
964 if comp_set.nesting <= 0:
965 error = true
966 break
967 result.constructions.append(Construction.new(i, comp.index, j))
968 find = true
969 i = comp.index
970 cur_template_id = j
971 if i == tokens.size():
972 if TRANSLATION_TABLE[cur_template_id][DESCRIPTION.size() - 1] == JUMP.SIMPLE:
973 if comp_set.nesting == 0:
974 end = true
975 else:
976 error = true
977 else:
978 error = true
979 elif i > tokens.size():
980 error = true
981 break
982 elif comp.state == COMPARE_STATE.INCOMPLETE:
983 error = true
984 break
985 elif comp.state == COMPARE_STATE.ERROR_VALUE:
986 error = true
987 break
988 if error:
989 result.error_description_text = comp.description
990 result.error_description_id = template_index
991 result.parse_token_index = i
992 if comp.index >= tokens.size():
993 result.error_token_index = tokens.size() - 1
994 else:
995 result.error_token_index = comp.index
996 if end:
997 result.done = true
998 result.error_description_id = -1
999 break
1000 if !find:
1001 break
1002 return result
1003
1004 enum CLASS_TYPE {
1005 ENUM = 0,
1006 MESSAGE = 1,
1007 MAP = 2
1008 }
1009
1010 enum FIELD_TYPE {
1011 UNDEFINED = -1,
1012 INT32 = 0,
1013 SINT32 = 1,
1014 UINT32 = 2,
1015 INT64 = 3,
1016 SINT64 = 4,
1017 UINT64 = 5,
1018 BOOL = 6,
1019 ENUM = 7,
1020 FIXED32 = 8,
1021 SFIXED32 = 9,
1022 FLOAT = 10,
1023 FIXED64 = 11,
1024 SFIXED64 = 12,
1025 DOUBLE = 13,
1026 STRING = 14,
1027 BYTES = 15,
1028 MESSAGE = 16,
1029 MAP = 17
1030 }
1031
1032 enum FIELD_QUALIFICATOR {
1033 OPTIONAL = 0,
1034 REQUIRED = 1,
1035 REPEATED = 2,
1036 RESERVED = 3
1037 }
1038
1039 enum FIELD_OPTION {
1040 PACKED = 0,
1041 NOT_PACKED = 1
1042 }
1043
1044 class ASTClass:
1045 func _init(n : String, t : int, p : int, pn : String, o : String, ci : int):
1046 name = n
1047 type = t
1048 parent_index = p
1049 parent_name = pn
1050 option = o
1051 construction_index = ci
1052 values = []
1053
1054 var name : String
1055 var type : int
1056 var parent_index : int
1057 var parent_name : String
1058 var option : String
1059 var construction_index
1060 var values : Array
1061
1062 func copy() -> ASTClass:
1063 var res : ASTClass = ASTClass.new(name, type, parent_index, parent_name, option, construction_index)
1064 for v in values:
1065 res.values.append(v.copy())
1066 return res
1067
1068 class ASTEnumValue:
1069 func _init(n : String, v : String):
1070 name = n
1071 value = v
1072
1073 var name : String
1074 var value : String
1075
1076 func copy() -> ASTEnumValue:
1077 return ASTEnumValue.new(name, value)
1078
1079 class ASTField:
1080 func _init(t, n : String, tn : String, p : int, q : int, o : int, ci : int, mf : bool):
1081 tag = t
1082 name = n
1083 type_name = tn
1084 parent_class_id = p
1085 qualificator = q
1086 option = o
1087 construction_index = ci
1088 is_map_field = mf
1089
1090 var tag
1091 var name : String
1092 var type_name : String
1093 var parent_class_id : int
1094 var qualificator : int
1095 var option : int
1096 var construction_index : int
1097 var is_map_field : bool
1098 var field_type : int = FIELD_TYPE.UNDEFINED
1099 var type_class_id : int = -1
1100
1101 func copy() -> ASTField:
1102 var res : ASTField = ASTField.new(tag, name, type_name, parent_class_id, qualificator, option, construction_index, is_map_field)
1103 res.field_type = field_type
1104 res.type_class_id = type_class_id
1105 return res
1106
1107 enum AST_GROUP_RULE {
1108 ONEOF = 0,
1109 ALL = 1
1110 }
1111
1112 class ASTFieldGroup:
1113 func _init(n : String, pi : int, r : int):
1114 name = n
1115 parent_class_id = pi
1116 rule = r
1117 opened = true
1118
1119 var name : String
1120 var parent_class_id : int
1121 var rule : int
1122 var field_indexes : Array = []
1123 var opened : bool
1124
1125 func copy() -> ASTFieldGroup:
1126 var res : ASTFieldGroup = ASTFieldGroup.new(name, parent_class_id, rule)
1127 res.opened = opened
1128 for fi in field_indexes:
1129 res.field_indexes.append(fi)
1130 return res
1131
1132 class ASTImport:
1133 func _init(a_path : String, a_public : bool, sha : String):
1134 path = a_path
1135 public = a_public
1136 sha256 = sha
1137
1138 var path : String
1139 var public : bool
1140 var sha256 : String
1141
1142 var class_table : Array = []
1143 var field_table : Array = []
1144 var group_table : Array = []
1145 var import_table : Array = []
1146 var proto_version : int = 0
1147
1148 class DescriptionResult:
1149 func _init(s : bool = true, e = null, d : String = ""):
1150 success = s
1151 error = e
1152 description = d
1153 var success : bool
1154 var error
1155 var description : String
1156
1157 static func get_text_from_token(string_token : TokenEntrance) -> String:
1158 return string_token.text.substr(1, string_token.text.length() - 2)
1159
1160 func desc_syntax(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1161 var result : DescriptionResult = DescriptionResult.new()
1162 var s : String = get_text_from_token(indexed_tokens[0].token)
1163 if s == "proto2":
1164 proto_version = 2
1165 elif s == "proto3":
1166 proto_version = 3
1167 else:
1168 result.success = false
1169 result.error = indexed_tokens[0].index
1170 result.description = "Unspecified version of the protocol. Use \"proto2\" or \"proto3\" syntax string."
1171 return result
1172
1173 func desc_import(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1174 var result : DescriptionResult = DescriptionResult.new()
1175 var offset : int = 0
1176 var public : bool = false
1177 if indexed_tokens[offset].token.id == TOKEN_ID.IMPORT_QUALIFICATION:
1178 if indexed_tokens[offset].token.text == "public":
1179 public = true
1180 offset += 1
1181 var f_name : String = path_dir + get_text_from_token(indexed_tokens[offset].token)
1182 var sha : String = FileAccess.get_sha256(f_name)
1183 if FileAccess.file_exists(f_name):
1184 for i in import_table:
1185 if i.path == f_name:
1186 result.success = false
1187 result.error = indexed_tokens[offset].index
1188 result.description = "File '" + f_name + "' already imported."
1189 return result
1190 if i.sha256 == sha:
1191 result.success = false
1192 result.error = indexed_tokens[offset].index
1193 result.description = "File '" + f_name + "' with matching SHA256 already imported."
1194 return result
1195 import_table.append(ASTImport.new(f_name, public, sha))
1196 else:
1197 result.success = false
1198 result.error = indexed_tokens[offset].index
1199 result.description = "Import file '" + f_name + "' not found."
1200 return result
1201
1202 func desc_package(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1203 printerr("UNRELEASED desc_package: ", indexed_tokens.size(), ", nesting: ", settings.nesting)
1204 var result : DescriptionResult = DescriptionResult.new()
1205 return result
1206
1207 func desc_option(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1208 printerr("UNRELEASED desc_option: ", indexed_tokens.size(), ", nesting: ", settings.nesting)
1209 var result : DescriptionResult = DescriptionResult.new()
1210 return result
1211
1212 func desc_field(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1213 var result : DescriptionResult = DescriptionResult.new()
1214 var qualifcator : int = FIELD_QUALIFICATOR.OPTIONAL
1215 var option : int
1216 var offset : int = 0
1217
1218 if proto_version == 3:
1219 option = FIELD_OPTION.PACKED
1220 if indexed_tokens[offset].token.id == TOKEN_ID.FIELD_QUALIFICATION:
1221 if indexed_tokens[offset].token.text == "repeated":
1222 qualifcator = FIELD_QUALIFICATOR.REPEATED
1223 elif indexed_tokens[offset].token.text == "required" || indexed_tokens[offset].token.text == "optional":
1224 result.success = false
1225 result.error = indexed_tokens[offset].index
1226 result.description = "Using the 'required' or 'optional' qualificator is unacceptable in Protobuf v3."
1227 return result
1228 offset += 1
1229 if proto_version == 2:
1230 option = FIELD_OPTION.NOT_PACKED
1231 if !(group_table.size() > 0 && group_table[group_table.size() - 1].opened):
1232 if indexed_tokens[offset].token.id == TOKEN_ID.FIELD_QUALIFICATION:
1233 if indexed_tokens[offset].token.text == "repeated":
1234 qualifcator = FIELD_QUALIFICATOR.REPEATED
1235 elif indexed_tokens[offset].token.text == "required":
1236 qualifcator = FIELD_QUALIFICATOR.REQUIRED
1237 elif indexed_tokens[offset].token.text == "optional":
1238 qualifcator = FIELD_QUALIFICATOR.OPTIONAL
1239 offset += 1
1240 else:
1241 if class_table[settings.parent_index].type == CLASS_TYPE.MESSAGE:
1242 result.success = false
1243 result.error = indexed_tokens[offset].index
1244 result.description = "Using the 'required', 'optional' or 'repeated' qualificator necessarily in Protobuf v2."
1245 return result
1246 var type_name : String = indexed_tokens[offset].token.text; offset += 1
1247 var field_name : String = indexed_tokens[offset].token.text; offset += 1
1248 var tag : String = indexed_tokens[offset].token.text; offset += 1
1249
1250 if indexed_tokens.size() == offset + 2:
1251 if indexed_tokens[offset].token.text == "packed":
1252 offset += 1
1253 if indexed_tokens[offset].token.text == "true":
1254 option = FIELD_OPTION.PACKED
1255 else:
1256 option = FIELD_OPTION.NOT_PACKED
1257 else:
1258 result.success = false
1259 result.error = indexed_tokens[offset].index
1260 result.description = "Undefined field option."
1261 return result
1262
1263 if group_table.size() > 0:
1264 if group_table[group_table.size() - 1].opened:
1265 if indexed_tokens[0].token.id == TOKEN_ID.FIELD_QUALIFICATION:
1266 result.success = false
1267 result.error = indexed_tokens[0].index
1268 result.description = "Using the 'required', 'optional' or 'repeated' qualificator is unacceptable in 'OneOf' field."
1269 return result
1270 group_table[group_table.size() - 1].field_indexes.append(field_table.size())
1271 field_table.append(ASTField.new(tag, field_name, type_name, settings.parent_index, qualifcator, option, settings.construction_index, false))
1272 return result
1273
1274 func desc_map_field(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1275 var result : DescriptionResult = DescriptionResult.new()
1276 var qualifcator : int = FIELD_QUALIFICATOR.REPEATED
1277 var option : int
1278 var offset : int = 0
1279
1280 if proto_version == 3:
1281 option = FIELD_OPTION.PACKED
1282 if proto_version == 2:
1283 option = FIELD_OPTION.NOT_PACKED
1284
1285 var key_type_name : String = indexed_tokens[offset].token.text; offset += 1
1286 if key_type_name == "float" || key_type_name == "double" || key_type_name == "bytes":
1287 result.success = false
1288 result.error = indexed_tokens[offset - 1].index
1289 result.description = "Map 'key_type' can't be floating point types and bytes."
1290 var type_name : String = indexed_tokens[offset].token.text; offset += 1
1291 var field_name : String = indexed_tokens[offset].token.text; offset += 1
1292 var tag : String = indexed_tokens[offset].token.text; offset += 1
1293
1294 if indexed_tokens.size() == offset + 2:
1295 if indexed_tokens[offset].token.text == "packed":
1296 offset += 1
1297 if indexed_tokens[offset] == "true":
1298 option = FIELD_OPTION.PACKED
1299 else:
1300 option = FIELD_OPTION.NOT_PACKED
1301 else:
1302 result.success = false
1303 result.error = indexed_tokens[offset].index
1304 result.description = "Undefined field option."
1305
1306 if group_table.size() > 0:
1307 if group_table[group_table.size() - 1].opened:
1308 group_table[group_table.size() - 1].field_indexes.append(field_table.size())
1309
1310 class_table.append(ASTClass.new("map_type_" + field_name, CLASS_TYPE.MAP, settings.parent_index, settings.parent_name, "", settings.construction_index))
1311 field_table.append(ASTField.new(tag, field_name, "map_type_" + field_name, settings.parent_index, qualifcator, option, settings.construction_index, false))
1312
1313 field_table.append(ASTField.new(1, "key", key_type_name, class_table.size() - 1, FIELD_QUALIFICATOR.OPTIONAL, option, settings.construction_index, true))
1314 field_table.append(ASTField.new(2, "value", type_name, class_table.size() - 1, FIELD_QUALIFICATOR.OPTIONAL, option, settings.construction_index, true))
1315
1316 return result
1317
1318 func desc_enum(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1319 var result : DescriptionResult = DescriptionResult.new()
1320 var option : String = ""
1321 var offset : int = 0
1322 var type_name : String = indexed_tokens[offset].token.text; offset += 1
1323 if indexed_tokens[offset].token.id == TOKEN_ID.ENUM_OPTION:
1324 if indexed_tokens[offset].token.text == "allow_alias" && indexed_tokens[offset + 1].token.text == "true":
1325 option = "allow_alias"
1326 offset += 2
1327 var value : ASTEnumValue
1328 var enum_class : ASTClass = ASTClass.new(type_name, CLASS_TYPE.ENUM, settings.parent_index, settings.parent_name, option, settings.construction_index)
1329 var first_value : bool = true
1330 while offset < indexed_tokens.size():
1331 if first_value:
1332 if indexed_tokens[offset + 1].token.text != "0":
1333 result.success = false
1334 result.error = indexed_tokens[offset + 1].index
1335 result.description = "For Enums, the default value is the first defined enum value, which must be 0."
1336 break
1337 first_value = false
1338 #if indexed_tokens[offset + 1].token.text[0] == "+" || indexed_tokens[offset + 1].token.text[0] == "-":
1339 # result.success = false
1340 # result.error = indexed_tokens[offset + 1].index
1341 # result.description = "For Enums, signed values are not allowed."
1342 # break
1343 value = ASTEnumValue.new(indexed_tokens[offset].token.text, indexed_tokens[offset + 1].token.text)
1344 enum_class.values.append(value)
1345 offset += 2
1346
1347 class_table.append(enum_class)
1348 return result
1349
1350 func desc_message_head(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1351 var result : DescriptionResult = DescriptionResult.new()
1352 class_table.append(ASTClass.new(indexed_tokens[0].token.text, CLASS_TYPE.MESSAGE, settings.parent_index, settings.parent_name, "", settings.construction_index))
1353 settings.parent_index = class_table.size() - 1
1354 settings.parent_name = settings.parent_name + "." + indexed_tokens[0].token.text
1355 return result
1356
1357 func desc_message_tail(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1358 settings.parent_index = class_table[settings.parent_index].parent_index
1359 settings.parent_name = class_table[settings.parent_index + 1].parent_name
1360 var result : DescriptionResult = DescriptionResult.new()
1361 return result
1362
1363 func desc_oneof_head(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1364 var result : DescriptionResult = DescriptionResult.new()
1365 for g in group_table:
1366 if g.parent_class_id == settings.parent_index && g.name == indexed_tokens[0].token.text:
1367 result.success = false
1368 result.error = indexed_tokens[0].index
1369 result.description = "OneOf name must be unique."
1370 return result
1371 group_table.append(ASTFieldGroup.new(indexed_tokens[0].token.text, settings.parent_index, AST_GROUP_RULE.ONEOF))
1372 return result
1373
1374 func desc_oneof_tail(indexed_tokens : Array, settings : CompareSettings) -> DescriptionResult:
1375 group_table[group_table.size() - 1].opened = false
1376 var result : DescriptionResult = DescriptionResult.new()
1377 return result
1378
1379 func analyze() -> AnalyzeResult:
1380 var analyze_result : AnalyzeResult = AnalyzeResult.new()
1381 analyze_result.doc = document
1382 analyze_result.classes = class_table
1383 analyze_result.fields = field_table
1384 analyze_result.groups = group_table
1385 analyze_result.state = false
1386 var result : TokenResult = tokenizer()
1387 if result.errors.size() > 0:
1388 for v in result.errors:
1389 var spos : Helper.StringPosition = Helper.str_pos(document.text, v.position)
1390 var err_text : String = "Unexpected token intersection " + "'" + document.text.substr(v.position.begin, spos.length) + "'"
1391 printerr(Helper.error_string(document.name, spos.str_num, spos.column, err_text))
1392 else:
1393 var integrity = check_tokens_integrity(result.tokens, document.text.length() - 1)
1394 if integrity.size() > 0:
1395 for v in integrity:
1396 var spos: Helper.StringPosition = Helper.str_pos(document.text, TokenPosition.new(v.begin, v.end))
1397 var err_text : String = "Unexpected token " + "'" + document.text.substr(v.begin, spos.length) + "'"
1398 printerr(Helper.error_string(document.name, spos.str_num, spos.column, err_text))
1399 else:
1400 analyze_result.tokens = result.tokens
1401 comment_space_processing(result.tokens)
1402 var syntax : TranslationResult = analyze_tokens(result.tokens)
1403 if !syntax.done:
1404 var pos_main : TokenPosition = Helper.text_pos(result.tokens, syntax.parse_token_index)
1405 var pos_inner : TokenPosition = Helper.text_pos(result.tokens, syntax.error_token_index)
1406 var spos_main : Helper.StringPosition = Helper.str_pos(document.text, pos_main)
1407 var spos_inner : Helper.StringPosition = Helper.str_pos(document.text, pos_inner)
1408 var err_text : String = "Syntax error in construction '" + result.tokens[syntax.parse_token_index].text + "'. "
1409 err_text += "Unacceptable use '" + result.tokens[syntax.error_token_index].text + "' at:" + str(spos_inner.str_num) + ":" + str(spos_inner.column)
1410 err_text += "\n" + syntax.error_description_text
1411 printerr(Helper.error_string(document.name, spos_main.str_num, spos_main.column, err_text))
1412 else:
1413 analyze_result.version = proto_version
1414 analyze_result.imports = import_table
1415 analyze_result.syntax = syntax
1416 analyze_result.state = true
1417 return analyze_result
1418
1419class Semantic:
1420
1421 var class_table : Array
1422 var field_table : Array
1423 var group_table : Array
1424 var syntax : Analysis.TranslationResult
1425 var tokens : Array
1426 var document : Document
1427
1428 func _init(analyze_result : AnalyzeResult):
1429 class_table = analyze_result.classes
1430 field_table = analyze_result.fields
1431 group_table = analyze_result.groups
1432 syntax = analyze_result.syntax
1433 tokens = analyze_result.tokens
1434 document = analyze_result.doc
1435
1436
1437 enum CHECK_SUBJECT {
1438 CLASS_NAME = 0,
1439 FIELD_NAME = 1,
1440 FIELD_TAG_NUMBER = 2,
1441 FIELD_TYPE = 3
1442 }
1443
1444 var STRING_FIELD_TYPE = {
1445 "int32": Analysis.FIELD_TYPE.INT32,
1446 "sint32": Analysis.FIELD_TYPE.SINT32,
1447 "uint32": Analysis.FIELD_TYPE.UINT32,
1448 "int64": Analysis.FIELD_TYPE.INT64,
1449 "sint64": Analysis.FIELD_TYPE.SINT64,
1450 "uint64": Analysis.FIELD_TYPE.UINT64,
1451 "bool": Analysis.FIELD_TYPE.BOOL,
1452 "fixed32": Analysis.FIELD_TYPE.FIXED32,
1453 "sfixed32": Analysis.FIELD_TYPE.SFIXED32,
1454 "float": Analysis.FIELD_TYPE.FLOAT,
1455 "fixed64": Analysis.FIELD_TYPE.FIXED64,
1456 "sfixed64": Analysis.FIELD_TYPE.SFIXED64,
1457 "double": Analysis.FIELD_TYPE.DOUBLE,
1458 "string": Analysis.FIELD_TYPE.STRING,
1459 "bytes": Analysis.FIELD_TYPE.BYTES,
1460 "map": Analysis.FIELD_TYPE.MAP
1461 }
1462
1463 class CheckResult:
1464 func _init(mci : int, aci : int, ti : int, s : int):
1465 main_construction_index = mci
1466 associated_construction_index = aci
1467 table_index = ti
1468 subject = s
1469
1470 var main_construction_index: int = -1
1471 var associated_construction_index: int = -1
1472 var table_index: int = -1
1473 var subject : int
1474
1475 func check_class_names() -> Array:
1476 var result : Array = []
1477 for i in range(class_table.size()):
1478 var the_class_name : String = class_table[i].parent_name + "." + class_table[i].name
1479 for j in range(i + 1, class_table.size(), 1):
1480 var inner_name : String = class_table[j].parent_name + "." + class_table[j].name
1481 if inner_name == the_class_name:
1482 var check : CheckResult = CheckResult.new(class_table[j].construction_index, class_table[i].construction_index, j, CHECK_SUBJECT.CLASS_NAME)
1483 result.append(check)
1484 break
1485 return result
1486
1487 func check_field_names() -> Array:
1488 var result : Array = []
1489 for i in range(field_table.size()):
1490 var the_class_name : String = class_table[field_table[i].parent_class_id].parent_name + "." + class_table[field_table[i].parent_class_id].name
1491 for j in range(i + 1, field_table.size(), 1):
1492 var inner_name : String = class_table[field_table[j].parent_class_id].parent_name + "." + class_table[field_table[j].parent_class_id].name
1493 if inner_name == the_class_name:
1494 if field_table[i].name == field_table[j].name:
1495 var check : CheckResult = CheckResult.new(field_table[j].construction_index, field_table[i].construction_index, j, CHECK_SUBJECT.FIELD_NAME)
1496 result.append(check)
1497 break
1498 if field_table[i].tag == field_table[j].tag:
1499 var check : CheckResult = CheckResult.new(field_table[j].construction_index, field_table[i].construction_index, j, CHECK_SUBJECT.FIELD_TAG_NUMBER)
1500 result.append(check)
1501 break
1502 return result
1503
1504 func find_full_class_name(the_class_name : String) -> int:
1505 for i in range(class_table.size()):
1506 if the_class_name == class_table[i].parent_name + "." + class_table[i].name:
1507 return i
1508 return -1
1509
1510 func find_class_name(the_class_name : String) -> int:
1511 for i in range(class_table.size()):
1512 if the_class_name == class_table[i].name:
1513 return i
1514 return -1
1515
1516 func get_class_childs(class_index : int) -> Array:
1517 var result : Array = []
1518 for i in range(class_table.size()):
1519 if class_table[i].parent_index == class_index:
1520 result.append(i)
1521 return result
1522
1523 func find_in_childs(the_class_name : String, child_indexes : Array) -> int:
1524 for c in child_indexes:
1525 if the_class_name == class_table[c].name:
1526 return c
1527 return -1
1528
1529 func determine_field_types() -> Array:
1530 var result : Array = []
1531 for f in field_table:
1532 if STRING_FIELD_TYPE.has(f.type_name):
1533 f.field_type = STRING_FIELD_TYPE[f.type_name]
1534 else:
1535 if f.type_name[0] == ".":
1536 f.type_class_id = find_full_class_name(f.type_name)
1537 else:
1538 # Reset result from previous assignment, that can be incorrect because of merging of imports
1539 f.type_class_id = -1
1540 var splited_name : Array = f.type_name.split(".", false)
1541 var cur_class_index : int = f.parent_class_id
1542 var exit : bool = false
1543 while(true):
1544 var find : bool = false
1545 if cur_class_index == -1:
1546 break
1547 for n in splited_name:
1548 var childs_and_parent : Array = get_class_childs(cur_class_index)
1549 var res_index : int = find_in_childs(n, childs_and_parent)
1550 if res_index >= 0:
1551 find = true
1552 cur_class_index = res_index
1553 else:
1554 if find:
1555 exit = true
1556 else:
1557 cur_class_index = class_table[cur_class_index].parent_index
1558 break
1559 if exit:
1560 break
1561 if find:
1562 f.type_class_id = cur_class_index
1563 break
1564 if f.type_class_id == -1:
1565 f.type_class_id = find_full_class_name("." + f.type_name)
1566 for i in range(field_table.size()):
1567 if field_table[i].field_type == Analysis.FIELD_TYPE.UNDEFINED:
1568 if field_table[i].type_class_id == -1:
1569 result.append(CheckResult.new(field_table[i].construction_index, field_table[i].construction_index, i, CHECK_SUBJECT.FIELD_TYPE))
1570 else:
1571 if class_table[field_table[i].type_class_id].type == Analysis.CLASS_TYPE.ENUM:
1572 field_table[i].field_type = Analysis.FIELD_TYPE.ENUM
1573 elif class_table[field_table[i].type_class_id].type == Analysis.CLASS_TYPE.MESSAGE:
1574 field_table[i].field_type = Analysis.FIELD_TYPE.MESSAGE
1575 elif class_table[field_table[i].type_class_id].type == Analysis.CLASS_TYPE.MAP:
1576 field_table[i].field_type = Analysis.FIELD_TYPE.MAP
1577 else:
1578 result.append(CheckResult.new(field_table[i].construction_index, field_table[i].construction_index, i, CHECK_SUBJECT.FIELD_TYPE))
1579 return result
1580
1581 func check_constructions() -> Array:
1582 var cl : Array = check_class_names()
1583 var fl : Array = check_field_names()
1584 var ft : Array = determine_field_types()
1585 return cl + fl + ft
1586
1587 func check() -> bool:
1588 var check_result : Array = check_constructions()
1589 if check_result.size() == 0:
1590 return true
1591 else:
1592 for v in check_result:
1593 var main_tok : int = syntax.constructions[v.main_construction_index].begin_token_index
1594 var assoc_tok : int = syntax.constructions[v.associated_construction_index].begin_token_index
1595 var main_err_pos : Helper.StringPosition = Helper.str_pos(document.text, Helper.text_pos(tokens, main_tok))
1596 var assoc_err_pos : Helper.StringPosition = Helper.str_pos(document.text, Helper.text_pos(tokens, assoc_tok))
1597 var err_text : String
1598 if v.subject == CHECK_SUBJECT.CLASS_NAME:
1599 var class_type = "Undefined"
1600 if class_table[v.table_index].type == Analysis.CLASS_TYPE.ENUM:
1601 class_type = "Enum"
1602 elif class_table[v.table_index].type == Analysis.CLASS_TYPE.MESSAGE:
1603 class_type = "Message"
1604 elif class_table[v.table_index].type == Analysis.CLASS_TYPE.MAP:
1605 class_type = "Map"
1606 err_text = class_type + " name '" + class_table[v.table_index].name + "' is already defined at:" + str(assoc_err_pos.str_num) + ":" + str(assoc_err_pos.column)
1607 elif v.subject == CHECK_SUBJECT.FIELD_NAME:
1608 err_text = "Field name '" + field_table[v.table_index].name + "' is already defined at:" + str(assoc_err_pos.str_num) + ":" + str(assoc_err_pos.column)
1609 elif v.subject == CHECK_SUBJECT.FIELD_TAG_NUMBER:
1610 err_text = "Tag number '" + field_table[v.table_index].tag + "' is already defined at:" + str(assoc_err_pos.str_num) + ":" + str(assoc_err_pos.column)
1611 elif v.subject == CHECK_SUBJECT.FIELD_TYPE:
1612 err_text = "Type '" + field_table[v.table_index].type_name + "' of the '" + field_table[v.table_index].name + "' field undefined"
1613 else:
1614 err_text = "Undefined error"
1615 printerr(Helper.error_string(document.name, main_err_pos.str_num, main_err_pos.column, err_text))
1616 return false
1617
1618class Translator:
1619
1620 var class_table : Array
1621 var field_table : Array
1622 var group_table : Array
1623 var proto_version : int
1624
1625 func _init(analyzer_result : AnalyzeResult):
1626 class_table = analyzer_result.classes
1627 field_table = analyzer_result.fields
1628 group_table = analyzer_result.groups
1629 proto_version = analyzer_result.version
1630
1631 func tabulate(text : String, nesting : int) -> String:
1632 var tab : String = ""
1633 for i in range(nesting):
1634 tab += "\t"
1635 return tab + text
1636
1637 func default_dict_text() -> String:
1638 if proto_version == 2:
1639 return "DEFAULT_VALUES_2"
1640 elif proto_version == 3:
1641 return "DEFAULT_VALUES_3"
1642 return "TRANSLATION_ERROR"
1643
1644 func generate_field_type(field : Analysis.ASTField) -> String:
1645 var text : String = "PB_DATA_TYPE."
1646 if field.field_type == Analysis.FIELD_TYPE.INT32:
1647 return text + "INT32"
1648 elif field.field_type == Analysis.FIELD_TYPE.SINT32:
1649 return text + "SINT32"
1650 elif field.field_type == Analysis.FIELD_TYPE.UINT32:
1651 return text + "UINT32"
1652 elif field.field_type == Analysis.FIELD_TYPE.INT64:
1653 return text + "INT64"
1654 elif field.field_type == Analysis.FIELD_TYPE.SINT64:
1655 return text + "SINT64"
1656 elif field.field_type == Analysis.FIELD_TYPE.UINT64:
1657 return text + "UINT64"
1658 elif field.field_type == Analysis.FIELD_TYPE.BOOL:
1659 return text + "BOOL"
1660 elif field.field_type == Analysis.FIELD_TYPE.ENUM:
1661 return text + "ENUM"
1662 elif field.field_type == Analysis.FIELD_TYPE.FIXED32:
1663 return text + "FIXED32"
1664 elif field.field_type == Analysis.FIELD_TYPE.SFIXED32:
1665 return text + "SFIXED32"
1666 elif field.field_type == Analysis.FIELD_TYPE.FLOAT:
1667 return text + "FLOAT"
1668 elif field.field_type == Analysis.FIELD_TYPE.FIXED64:
1669 return text + "FIXED64"
1670 elif field.field_type == Analysis.FIELD_TYPE.SFIXED64:
1671 return text + "SFIXED64"
1672 elif field.field_type == Analysis.FIELD_TYPE.DOUBLE:
1673 return text + "DOUBLE"
1674 elif field.field_type == Analysis.FIELD_TYPE.STRING:
1675 return text + "STRING"
1676 elif field.field_type == Analysis.FIELD_TYPE.BYTES:
1677 return text + "BYTES"
1678 elif field.field_type == Analysis.FIELD_TYPE.MESSAGE:
1679 return text + "MESSAGE"
1680 elif field.field_type == Analysis.FIELD_TYPE.MAP:
1681 return text + "MAP"
1682 return text
1683
1684 func generate_field_rule(field : Analysis.ASTField) -> String:
1685 var text : String = "PB_RULE."
1686 if field.qualificator == Analysis.FIELD_QUALIFICATOR.OPTIONAL:
1687 return text + "OPTIONAL"
1688 elif field.qualificator == Analysis.FIELD_QUALIFICATOR.REQUIRED:
1689 return text + "REQUIRED"
1690 elif field.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1691 return text + "REPEATED"
1692 elif field.qualificator == Analysis.FIELD_QUALIFICATOR.RESERVED:
1693 return text + "RESERVED"
1694 return text
1695
1696 func generate_gdscript_type(field : Analysis.ASTField) -> String:
1697 if field.field_type == Analysis.FIELD_TYPE.MESSAGE:
1698 var type_name : String = class_table[field.type_class_id].parent_name + "." + class_table[field.type_class_id].name
1699 return type_name.substr(1, type_name.length() - 1)
1700 return generate_gdscript_simple_type(field)
1701
1702 func generate_gdscript_simple_type(field : Analysis.ASTField) -> String:
1703 if field.field_type == Analysis.FIELD_TYPE.INT32:
1704 return "int"
1705 elif field.field_type == Analysis.FIELD_TYPE.SINT32:
1706 return "int"
1707 elif field.field_type == Analysis.FIELD_TYPE.UINT32:
1708 return "int"
1709 elif field.field_type == Analysis.FIELD_TYPE.INT64:
1710 return "int"
1711 elif field.field_type == Analysis.FIELD_TYPE.SINT64:
1712 return "int"
1713 elif field.field_type == Analysis.FIELD_TYPE.UINT64:
1714 return "int"
1715 elif field.field_type == Analysis.FIELD_TYPE.BOOL:
1716 return "bool"
1717 elif field.field_type == Analysis.FIELD_TYPE.ENUM:
1718 return ""
1719 elif field.field_type == Analysis.FIELD_TYPE.FIXED32:
1720 return "int"
1721 elif field.field_type == Analysis.FIELD_TYPE.SFIXED32:
1722 return "int"
1723 elif field.field_type == Analysis.FIELD_TYPE.FLOAT:
1724 return "float"
1725 elif field.field_type == Analysis.FIELD_TYPE.FIXED64:
1726 return "int"
1727 elif field.field_type == Analysis.FIELD_TYPE.SFIXED64:
1728 return "int"
1729 elif field.field_type == Analysis.FIELD_TYPE.DOUBLE:
1730 return "float"
1731 elif field.field_type == Analysis.FIELD_TYPE.STRING:
1732 return "String"
1733 elif field.field_type == Analysis.FIELD_TYPE.BYTES:
1734 return "PackedByteArray"
1735 return ""
1736
1737 func generate_field_constructor(field_index : int, nesting : int) -> String:
1738 var text : String = ""
1739 var f : Analysis.ASTField = field_table[field_index]
1740 var field_name : String = "__" + f.name
1741 var pbfield_text : String
1742 var default_var_name := field_name + "_default"
1743 if f.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1744 var type_name := generate_gdscript_type(f)
1745 if type_name:
1746 text = tabulate("var %s: Array[%s] = []\n" % [default_var_name, type_name], nesting)
1747 else:
1748 text = tabulate("var %s: Array = []\n" % [default_var_name], nesting)
1749 pbfield_text += field_name + " = PBField.new("
1750 pbfield_text += "\"" + f.name + "\", "
1751 pbfield_text += generate_field_type(f) + ", "
1752 pbfield_text += generate_field_rule(f) + ", "
1753 pbfield_text += str(f.tag) + ", "
1754 if f.option == Analysis.FIELD_OPTION.PACKED:
1755 pbfield_text += "true"
1756 elif f.option == Analysis.FIELD_OPTION.NOT_PACKED:
1757 pbfield_text += "false"
1758 if f.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1759 pbfield_text += ", " + default_var_name
1760 else:
1761 pbfield_text += ", " + default_dict_text() + "[" + generate_field_type(f) + "]"
1762 pbfield_text += ")\n"
1763 text += tabulate(pbfield_text, nesting)
1764 if f.is_map_field:
1765 text += tabulate(field_name + ".is_map_field = true\n", nesting)
1766 text += tabulate("service = PBServiceField.new()\n", nesting)
1767 text += tabulate("service.field = " + field_name + "\n", nesting)
1768 if f.field_type == Analysis.FIELD_TYPE.MESSAGE:
1769 if f.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1770 text += tabulate("service.func_ref = Callable(self, \"add_" + f.name + "\")\n", nesting)
1771 else:
1772 text += tabulate("service.func_ref = Callable(self, \"new_" + f.name + "\")\n", nesting)
1773 elif f.field_type == Analysis.FIELD_TYPE.MAP:
1774 text += tabulate("service.func_ref = Callable(self, \"add_empty_" + f.name + "\")\n", nesting)
1775 text += tabulate("data[" + field_name + ".tag] = service\n", nesting)
1776
1777 return text
1778
1779 func generate_group_clear(field_index : int, nesting : int) -> String:
1780 for g in group_table:
1781 var text : String = ""
1782 var find : bool = false
1783 if g.parent_class_id == field_table[field_index].parent_class_id:
1784 for i in g.field_indexes:
1785 if field_index == i:
1786 find = true
1787 text += tabulate("data[" + field_table[i].tag + "].state = PB_SERVICE_STATE.FILLED\n", nesting)
1788 else:
1789 text += tabulate("__" + field_table[i].name + ".value = " + default_dict_text() + "[" + generate_field_type(field_table[i]) + "]\n", nesting)
1790 text += tabulate("data[" + field_table[i].tag + "].state = PB_SERVICE_STATE.UNFILLED\n", nesting)
1791 if find:
1792 return text
1793 return ""
1794
1795 func generate_has_oneof(field_index : int, nesting : int) -> String:
1796 for g in group_table:
1797 var text : String = ""
1798 if g.parent_class_id == field_table[field_index].parent_class_id:
1799 for i in g.field_indexes:
1800 if field_index == i:
1801 text += tabulate("func has_" + field_table[i].name + "() -> bool:\n", nesting)
1802 nesting += 1
1803 text += tabulate("return data[" + field_table[i].tag + "].state == PB_SERVICE_STATE.FILLED\n", nesting)
1804 return text
1805 return ""
1806
1807 func generate_field(field_index : int, nesting : int) -> String:
1808 var text : String = ""
1809 var f : Analysis.ASTField = field_table[field_index]
1810 var varname : String = "__" + f.name
1811 text += tabulate("var " + varname + ": PBField\n", nesting)
1812 if f.field_type == Analysis.FIELD_TYPE.MESSAGE:
1813 var the_class_name : String = class_table[f.type_class_id].parent_name + "." + class_table[f.type_class_id].name
1814 the_class_name = the_class_name.substr(1, the_class_name.length() - 1)
1815 if f.qualificator != Analysis.FIELD_QUALIFICATOR.OPTIONAL:
1816 text += generate_has_oneof(field_index, nesting)
1817 if f.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1818 text += tabulate("func get_" + f.name + "() -> Array[" + the_class_name + "]:\n", nesting)
1819 else:
1820 if f.qualificator == Analysis.FIELD_QUALIFICATOR.OPTIONAL:
1821 text += tabulate("func has_" + f.name + "() -> bool:\n", nesting)
1822 nesting += 1
1823 text += tabulate("if " + varname + ".value != null:\n", nesting)
1824 nesting += 1
1825 text += tabulate("return true\n", nesting)
1826 nesting -= 1
1827 text += tabulate("return false\n", nesting)
1828 nesting -= 1
1829 text += tabulate("func get_" + f.name + "() -> " + the_class_name + ":\n", nesting)
1830 nesting += 1
1831 text += tabulate("return " + varname + ".value\n", nesting)
1832 nesting -= 1
1833 text += tabulate("func clear_" + f.name + "() -> void:\n", nesting)
1834 nesting += 1
1835 text += tabulate("data[" + str(f.tag) + "].state = PB_SERVICE_STATE.UNFILLED\n", nesting)
1836 if f.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1837 text += tabulate(varname + ".value.clear()\n", nesting)
1838 nesting -= 1
1839 text += tabulate("func add_" + f.name + "() -> " + the_class_name + ":\n", nesting)
1840 nesting += 1
1841 text += tabulate("var element = " + the_class_name + ".new()\n", nesting)
1842 text += tabulate(varname + ".value.append(element)\n", nesting)
1843 text += tabulate("return element\n", nesting)
1844 else:
1845 text += tabulate(varname + ".value = " + default_dict_text() + "[" + generate_field_type(f) + "]\n", nesting)
1846 nesting -= 1
1847 text += tabulate("func new_" + f.name + "() -> " + the_class_name + ":\n", nesting)
1848 nesting += 1
1849 text += generate_group_clear(field_index, nesting)
1850 text += tabulate(varname + ".value = " + the_class_name + ".new()\n", nesting)
1851 text += tabulate("return " + varname + ".value\n", nesting)
1852 elif f.field_type == Analysis.FIELD_TYPE.MAP:
1853 var the_parent_class_name : String = class_table[f.type_class_id].parent_name
1854 the_parent_class_name = the_parent_class_name.substr(1, the_parent_class_name.length() - 1)
1855 var the_class_name : String = the_parent_class_name + "." + class_table[f.type_class_id].name
1856
1857 text += generate_has_oneof(field_index, nesting)
1858 text += tabulate("func get_raw_" + f.name + "():\n", nesting)
1859 nesting += 1
1860 text += tabulate("return " + varname + ".value\n", nesting)
1861 nesting -= 1
1862 text += tabulate("func get_" + f.name + "():\n", nesting)
1863 nesting += 1
1864 text += tabulate("return PBPacker.construct_map(" + varname + ".value)\n", nesting)
1865 nesting -= 1
1866 text += tabulate("func clear_" + f.name + "():\n", nesting)
1867 nesting += 1
1868 text += tabulate("data[" + str(f.tag) + "].state = PB_SERVICE_STATE.UNFILLED\n", nesting)
1869 text += tabulate(varname + ".value = " + default_dict_text() + "[" + generate_field_type(f) + "]\n", nesting)
1870 nesting -= 1
1871 for i in range(field_table.size()):
1872 if field_table[i].parent_class_id == f.type_class_id && field_table[i].name == "value":
1873 var gd_type : String = generate_gdscript_simple_type(field_table[i])
1874 var return_type : String = " -> " + the_class_name
1875 var value_return_type : String = ""
1876 if gd_type != "":
1877 value_return_type = return_type
1878 elif field_table[i].field_type == Analysis.FIELD_TYPE.MESSAGE:
1879 value_return_type = " -> " + the_parent_class_name + "." + field_table[i].type_name
1880 text += tabulate("func add_empty_" + f.name + "()" + return_type + ":\n", nesting)
1881 nesting += 1
1882 text += generate_group_clear(field_index, nesting)
1883 text += tabulate("var element = " + the_class_name + ".new()\n", nesting)
1884 text += tabulate(varname + ".value.append(element)\n", nesting)
1885 text += tabulate("return element\n", nesting)
1886 nesting -= 1
1887 if field_table[i].field_type == Analysis.FIELD_TYPE.MESSAGE:
1888 text += tabulate("func add_" + f.name + "(a_key)" + value_return_type + ":\n", nesting)
1889 nesting += 1
1890 text += generate_group_clear(field_index, nesting)
1891 text += tabulate("var idx = -1\n", nesting)
1892 text += tabulate("for i in range(" + varname + ".value.size()):\n", nesting)
1893 nesting += 1
1894 text += tabulate("if " + varname + ".value[i].get_key() == a_key:\n", nesting)
1895 nesting += 1
1896 text += tabulate("idx = i\n", nesting)
1897 text += tabulate("break\n", nesting)
1898 nesting -= 2
1899 text += tabulate("var element = " + the_class_name + ".new()\n", nesting)
1900 text += tabulate("element.set_key(a_key)\n", nesting)
1901 text += tabulate("if idx != -1:\n", nesting)
1902 nesting += 1
1903 text += tabulate(varname + ".value[idx] = element\n", nesting)
1904 nesting -= 1
1905 text += tabulate("else:\n", nesting)
1906 nesting += 1
1907 text += tabulate(varname + ".value.append(element)\n", nesting)
1908 nesting -= 1
1909 text += tabulate("return element.new_value()\n", nesting)
1910 else:
1911 text += tabulate("func add_" + f.name + "(a_key, a_value) -> void:\n", nesting)
1912 nesting += 1
1913 text += generate_group_clear(field_index, nesting)
1914 text += tabulate("var idx = -1\n", nesting)
1915 text += tabulate("for i in range(" + varname + ".value.size()):\n", nesting)
1916 nesting += 1
1917 text += tabulate("if " + varname + ".value[i].get_key() == a_key:\n", nesting)
1918 nesting += 1
1919 text += tabulate("idx = i\n", nesting)
1920 text += tabulate("break\n", nesting)
1921 nesting -= 2
1922 text += tabulate("var element = " + the_class_name + ".new()\n", nesting)
1923 text += tabulate("element.set_key(a_key)\n", nesting)
1924 text += tabulate("element.set_value(a_value)\n", nesting)
1925 text += tabulate("if idx != -1:\n", nesting)
1926 nesting += 1
1927 text += tabulate(varname + ".value[idx] = element\n", nesting)
1928 nesting -= 1
1929 text += tabulate("else:\n", nesting)
1930 nesting += 1
1931 text += tabulate(varname + ".value.append(element)\n", nesting)
1932 nesting -= 1
1933 break
1934 else:
1935 var gd_type : String = generate_gdscript_simple_type(f)
1936 var return_type : String = ""
1937 var argument_type : String = ""
1938 if gd_type != "":
1939 return_type = " -> " + gd_type
1940 argument_type = " : " + gd_type
1941 if f.qualificator != Analysis.FIELD_QUALIFICATOR.OPTIONAL:
1942 text += generate_has_oneof(field_index, nesting)
1943 if f.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1944 var array_type := "[" + gd_type + "]" if gd_type else ""
1945 text += tabulate("func get_" + f.name + "() -> Array" + array_type + ":\n", nesting)
1946 else:
1947 if f.qualificator == Analysis.FIELD_QUALIFICATOR.OPTIONAL:
1948 text += tabulate("func has_" + f.name + "() -> bool:\n", nesting)
1949 nesting += 1
1950 text += tabulate("if " + varname + ".value != null:\n", nesting)
1951 nesting += 1
1952 text += tabulate("return true\n", nesting)
1953 nesting -= 1
1954 text += tabulate("return false\n", nesting)
1955 nesting -= 1
1956 text += tabulate("func get_" + f.name + "()" + return_type + ":\n", nesting)
1957 nesting += 1
1958 text += tabulate("return " + varname + ".value\n", nesting)
1959 nesting -= 1
1960 text += tabulate("func clear_" + f.name + "() -> void:\n", nesting)
1961 nesting += 1
1962 text += tabulate("data[" + str(f.tag) + "].state = PB_SERVICE_STATE.UNFILLED\n", nesting)
1963 if f.qualificator == Analysis.FIELD_QUALIFICATOR.REPEATED:
1964 text += tabulate(varname + ".value.clear()\n", nesting)
1965 nesting -= 1
1966 text += tabulate("func add_" + f.name + "(value" + argument_type + ") -> void:\n", nesting)
1967 nesting += 1
1968 text += tabulate(varname + ".value.append(value)\n", nesting)
1969 else:
1970 text += tabulate(varname + ".value = " + default_dict_text() + "[" + generate_field_type(f) + "]\n", nesting)
1971 nesting -= 1
1972 text += tabulate("func set_" + f.name + "(value" + argument_type + ") -> void:\n", nesting)
1973 nesting += 1
1974 text += generate_group_clear(field_index, nesting)
1975 text += tabulate(varname + ".value = value\n", nesting)
1976 return text
1977
1978 func generate_class(class_index : int, nesting : int) -> String:
1979 var text : String = ""
1980 if class_table[class_index].type == Analysis.CLASS_TYPE.MESSAGE || class_table[class_index].type == Analysis.CLASS_TYPE.MAP:
1981 var cls_pref : String = ""
1982 cls_pref += tabulate("class " + class_table[class_index].name + ":\n", nesting)
1983 nesting += 1
1984 cls_pref += tabulate("func _init():\n", nesting)
1985 text += cls_pref
1986 nesting += 1
1987 text += tabulate("var service\n", nesting)
1988 text += tabulate("\n", nesting)
1989 var field_text : String = ""
1990 for i in range(field_table.size()):
1991 if field_table[i].parent_class_id == class_index:
1992 text += generate_field_constructor(i, nesting)
1993 text += tabulate("\n", nesting)
1994 field_text += generate_field(i, nesting - 1)
1995 field_text += tabulate("\n", nesting - 1)
1996 nesting -= 1
1997 text += tabulate("var data = {}\n", nesting)
1998 text += tabulate("\n", nesting)
1999 text += field_text
2000 for j in range(class_table.size()):
2001 if class_table[j].parent_index == class_index:
2002 var cl_text = generate_class(j, nesting)
2003 text += cl_text
2004 if class_table[j].type == Analysis.CLASS_TYPE.MESSAGE || class_table[j].type == Analysis.CLASS_TYPE.MAP:
2005 text += generate_class_services(nesting + 1)
2006 text += tabulate("\n", nesting + 1)
2007 elif class_table[class_index].type == Analysis.CLASS_TYPE.ENUM:
2008 text += tabulate("enum " + class_table[class_index].name + " {\n", nesting)
2009 nesting += 1
2010
2011 var expected_prefix = class_table[class_index].name.to_snake_case().to_upper() + "_"
2012 var all_have_prefix = true
2013 for en in range(class_table[class_index].values.size()):
2014 var value_name = class_table[class_index].values[en].name
2015 all_have_prefix = all_have_prefix and value_name.begins_with(expected_prefix) and value_name != expected_prefix
2016
2017 for en in range(class_table[class_index].values.size()):
2018 var value_name = class_table[class_index].values[en].name
2019 if all_have_prefix:
2020 value_name = value_name.substr(expected_prefix.length())
2021 var enum_val = value_name + " = " + class_table[class_index].values[en].value
2022 if en == class_table[class_index].values.size() - 1:
2023 text += tabulate(enum_val + "\n", nesting)
2024 else:
2025 text += tabulate(enum_val + ",\n", nesting)
2026 nesting -= 1
2027 text += tabulate("}\n", nesting)
2028 text += tabulate("\n", nesting)
2029
2030 return text
2031
2032 func generate_class_services(nesting : int) -> String:
2033 var text : String = ""
2034 text += tabulate("func _to_string() -> String:\n", nesting)
2035 nesting += 1
2036 text += tabulate("return PBPacker.message_to_string(data)\n", nesting)
2037 text += tabulate("\n", nesting)
2038 nesting -= 1
2039 text += tabulate("func to_bytes() -> PackedByteArray:\n", nesting)
2040 nesting += 1
2041 text += tabulate("return PBPacker.pack_message(data)\n", nesting)
2042 text += tabulate("\n", nesting)
2043 nesting -= 1
2044 text += tabulate("func from_bytes(bytes : PackedByteArray, offset : int = 0, limit : int = -1) -> int:\n", nesting)
2045 nesting += 1
2046 text += tabulate("var cur_limit = bytes.size()\n", nesting)
2047 text += tabulate("if limit != -1:\n", nesting)
2048 nesting += 1
2049 text += tabulate("cur_limit = limit\n", nesting)
2050 nesting -= 1
2051 text += tabulate("var result = PBPacker.unpack_message(data, bytes, offset, cur_limit)\n", nesting)
2052 text += tabulate("if result == cur_limit:\n", nesting)
2053 nesting += 1
2054 text += tabulate("if PBPacker.check_required(data):\n", nesting)
2055 nesting += 1
2056 text += tabulate("if limit == -1:\n", nesting)
2057 nesting += 1
2058 text += tabulate("return PB_ERR.NO_ERRORS\n", nesting)
2059 nesting -= 2
2060 text += tabulate("else:\n", nesting)
2061 nesting += 1
2062 text += tabulate("return PB_ERR.REQUIRED_FIELDS\n", nesting)
2063 nesting -= 2
2064 text += tabulate("elif limit == -1 && result > 0:\n", nesting)
2065 nesting += 1
2066 text += tabulate("return PB_ERR.PARSE_INCOMPLETE\n", nesting)
2067 nesting -= 1
2068 text += tabulate("return result\n", nesting)
2069 return text
2070
2071 func translate(file_name : String, core_file_name : String) -> bool:
2072
2073 var file : FileAccess = FileAccess.open(file_name, FileAccess.WRITE)
2074 if file == null:
2075 printerr("File: '", file_name, "' save error.")
2076 return false
2077
2078 if !FileAccess.file_exists(core_file_name):
2079 printerr("File: '", core_file_name, "' not found.")
2080 return false
2081
2082 var core_file : FileAccess = FileAccess.open(core_file_name, FileAccess.READ)
2083 if core_file == null:
2084 printerr("File: '", core_file_name, "' read error.")
2085 return false
2086 var core_text : String = core_file.get_as_text()
2087 core_file.close()
2088
2089 var text : String = ""
2090 var nesting : int = 0
2091 core_text = core_text.replace(PROTO_VERSION_DEFAULT, PROTO_VERSION_CONST + str(proto_version))
2092 text += core_text + "\n\n\n"
2093 text += "############### USER DATA BEGIN ################\n"
2094 var cls_user : String = ""
2095 for i in range(class_table.size()):
2096 if class_table[i].parent_index == -1:
2097 var cls_text = generate_class(i, nesting)
2098 cls_user += cls_text
2099 if class_table[i].type == Analysis.CLASS_TYPE.MESSAGE:
2100 nesting += 1
2101 cls_user += generate_class_services(nesting)
2102 cls_user += tabulate("\n", nesting)
2103 nesting -= 1
2104 text += "\n\n"
2105 text += cls_user
2106 text += "################ USER DATA END #################\n"
2107 file.store_string(text)
2108 file.close()
2109 if !FileAccess.file_exists(file_name):
2110 printerr("File: '", file_name, "' save error.")
2111 return false
2112 return true
2113
2114
2115class ImportFile:
2116 func _init(sha : String, a_path : String, a_parent : int):
2117 sha256 = sha
2118 path = a_path
2119 parent_index = a_parent
2120
2121 var sha256 : String
2122 var path : String
2123 var parent_index : int
2124
2125func parse_all(analyzes : Dictionary, imports : Array, path : String, full_name : String, parent_index : int) -> bool:
2126
2127 if !FileAccess.file_exists(full_name):
2128 printerr(full_name, ": not found.")
2129 return false
2130
2131 var file : FileAccess = FileAccess.open(full_name, FileAccess.READ)
2132 if file == null:
2133 printerr(full_name, ": read error.")
2134 return false
2135 var doc : Document = Document.new(full_name, file.get_as_text())
2136 var sha : String = file.get_sha256(full_name)
2137 file.close()
2138
2139 if !analyzes.has(sha):
2140 print(full_name, ": parsing.")
2141 var analysis : Analysis = Analysis.new(path, doc)
2142 var an_result : AnalyzeResult = analysis.analyze()
2143 if an_result.state:
2144 analyzes[sha] = an_result
2145 var parent : int = imports.size()
2146 imports.append(ImportFile.new(sha, doc.name, parent_index))
2147 for im in an_result.imports:
2148 if !parse_all(analyzes, imports, path, im.path, parent):
2149 return false
2150 else:
2151 printerr(doc.name + ": parsing error.")
2152 return false
2153 else:
2154 print(full_name, ": retrieving data from cache.")
2155 imports.append(ImportFile.new(sha, doc.name, parent_index))
2156 return true
2157
2158func union_analyses(a1 : AnalyzeResult, a2 : AnalyzeResult, only_classes : bool = true) -> void:
2159 var class_offset : int = a1.classes.size()
2160 var field_offset = a1.fields.size()
2161 for cl in a2.classes:
2162 var cur_class : Analysis.ASTClass = cl.copy()
2163 if cur_class.parent_index != -1:
2164 cur_class.parent_index += class_offset
2165 a1.classes.append(cur_class)
2166 if only_classes:
2167 return
2168 for fl in a2.fields:
2169 var cur_field : Analysis.ASTField = fl.copy()
2170 cur_field.parent_class_id += class_offset
2171 cur_field.type_class_id = -1
2172 a1.fields.append(cur_field)
2173 for gr in a2.groups:
2174 var cur_group : Analysis.ASTFieldGroup = gr.copy()
2175 cur_group.parent_class_id += class_offset
2176 var indexes : Array = []
2177 for i in cur_group.field_indexes:
2178 indexes.append(i + field_offset)
2179 cur_group.field_indexes = indexes
2180 a1.groups.append(cur_group)
2181
2182func union_imports(analyzes : Dictionary, key : String, result : AnalyzeResult, keys : Array, nesting : int, use_public : bool = true, only_classes : bool = true) -> void:
2183 nesting += 1
2184 for im in analyzes[key].imports:
2185 var find : bool = false
2186 for k in keys:
2187 if im.sha256 == k:
2188 find = true
2189 break
2190 if find:
2191 continue
2192 if (!use_public) || (use_public && ((im.public && nesting > 1) || nesting < 2)):
2193 keys.append(im.sha256)
2194 union_analyses(result, analyzes[im.sha256], only_classes)
2195 union_imports(analyzes, im.sha256, result, keys, nesting, use_public, only_classes)
2196
2197func semantic_all(analyzes : Dictionary, imports : Array)-> bool:
2198 for k in analyzes.keys():
2199 print(analyzes[k].doc.name, ": analysis.")
2200 var keys : Array = []
2201 var analyze : AnalyzeResult = analyzes[k].soft_copy()
2202 keys.append(k)
2203 analyze.classes = []
2204 for cl in analyzes[k].classes:
2205 analyze.classes.append(cl.copy())
2206 union_imports(analyzes, k, analyze, keys, 0)
2207 var semantic : Semantic = Semantic.new(analyze)
2208 if !semantic.check():
2209 printerr(analyzes[k].doc.name, ": analysis error.")
2210 return false
2211 return true
2212
2213func translate_all(analyzes : Dictionary, file_name : String, core_file_name : String) -> bool:
2214 var first_key : String = analyzes.keys()[0]
2215 var analyze : AnalyzeResult = analyzes[first_key]
2216 var keys : Array = []
2217 keys.append(first_key)
2218 union_imports(analyzes, first_key, analyze, keys, 0, false, false)
2219 print("Performing full semantic analysis.")
2220 var semantic : Semantic = Semantic.new(analyze)
2221 if !semantic.check():
2222 return false
2223 print("Performing translation.")
2224 var translator : Translator = Translator.new(analyze)
2225 if !translator.translate(file_name, core_file_name):
2226 return false
2227 var first : bool = true
2228 return true
2229
2230func work(path : String, in_file : String, out_file : String, core_file : String) -> bool:
2231 var in_full_name : String = path + in_file
2232 var imports : Array = []
2233 var analyzes : Dictionary = {}
2234
2235 print("Compiling source: '", in_full_name, "', output: '", out_file, "'.")
2236 print("\n1. Parsing:")
2237 if parse_all(analyzes, imports, path, in_full_name, -1):
2238 print("* Parsing completed successfully. *")
2239 else:
2240 return false
2241 print("\n2. Perfoming semantic analysis:")
2242 if semantic_all(analyzes, imports):
2243 print("* Semantic analysis completed successfully. *")
2244 else:
2245 return false
2246 print("\n3. Output file creating:")
2247 if translate_all(analyzes, out_file, core_file):
2248 print("* Output file was created successfully. *")
2249 else:
2250 return false
2251 return true
2252
2253func _ready():
2254 pass
diff --git a/vendor/godobuf/addons/protobuf/plugin.cfg b/vendor/godobuf/addons/protobuf/plugin.cfg new file mode 100644 index 0000000..6456a11 --- /dev/null +++ b/vendor/godobuf/addons/protobuf/plugin.cfg
@@ -0,0 +1,7 @@
1[plugin]
2
3name="Godobuf"
4description="Google Protobuf implementation for Godot/GDScript"
5author="oniksan"
6version="0.6.1 for Godot 4.x.y"
7script="protobuf_ui.gd"
diff --git a/vendor/godobuf/addons/protobuf/protobuf_cmdln.gd b/vendor/godobuf/addons/protobuf/protobuf_cmdln.gd new file mode 100644 index 0000000..97d7ba4 --- /dev/null +++ b/vendor/godobuf/addons/protobuf/protobuf_cmdln.gd
@@ -0,0 +1,66 @@
1#
2# BSD 3-Clause License
3#
4# Copyright (c) 2018, Oleg Malyavkin
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9#
10# * Redistributions of source code must retain the above copyright notice, this
11# list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright notice,
14# this list of conditions and the following disclaimer in the documentation
15# and/or other materials provided with the distribution.
16#
17# * Neither the name of the copyright holder nor the names of its
18# contributors may be used to endorse or promote products derived from
19# this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32extends SceneTree
33
34var Parser = preload("res://addons/protobuf/parser.gd")
35var Util = preload("res://addons/protobuf/protobuf_util.gd")
36
37func error(msg : String):
38 push_error(msg)
39 quit()
40
41func _init():
42 var arguments = {}
43 for argument in OS.get_cmdline_args():
44 if argument.find("=") > -1:
45 var key_value = argument.split("=")
46 arguments[key_value[0].lstrip("--")] = key_value[1]
47
48 if !arguments.has("input") || !arguments.has("output"):
49 error("Expected 2 Parameters: input and output")
50
51 var input_file_name = arguments["input"]
52 var output_file_name = arguments["output"]
53
54 var file = FileAccess.open(input_file_name, FileAccess.READ)
55 if file == null:
56 error("File: '" + input_file_name + "' not found.")
57
58 var parser = Parser.new()
59
60 if parser.work(Util.extract_dir(input_file_name), Util.extract_filename(input_file_name), \
61 output_file_name, "res://addons/protobuf/protobuf_core.gd"):
62 print("Compiled '", input_file_name, "' to '", output_file_name, "'.")
63 else:
64 error("Compilation failed.")
65
66 quit()
diff --git a/vendor/godobuf/addons/protobuf/protobuf_core.gd b/vendor/godobuf/addons/protobuf/protobuf_core.gd new file mode 100644 index 0000000..7098413 --- /dev/null +++ b/vendor/godobuf/addons/protobuf/protobuf_core.gd
@@ -0,0 +1,668 @@
1#
2# BSD 3-Clause License
3#
4# Copyright (c) 2018 - 2023, Oleg Malyavkin
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9#
10# * Redistributions of source code must retain the above copyright notice, this
11# list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright notice,
14# this list of conditions and the following disclaimer in the documentation
15# and/or other materials provided with the distribution.
16#
17# * Neither the name of the copyright holder nor the names of its
18# contributors may be used to endorse or promote products derived from
19# this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32# DEBUG_TAB redefine this " " if you need, example: const DEBUG_TAB = "\t"
33
34const PROTO_VERSION = 0
35
36const DEBUG_TAB : String = " "
37
38enum PB_ERR {
39 NO_ERRORS = 0,
40 VARINT_NOT_FOUND = -1,
41 REPEATED_COUNT_NOT_FOUND = -2,
42 REPEATED_COUNT_MISMATCH = -3,
43 LENGTHDEL_SIZE_NOT_FOUND = -4,
44 LENGTHDEL_SIZE_MISMATCH = -5,
45 PACKAGE_SIZE_MISMATCH = -6,
46 UNDEFINED_STATE = -7,
47 PARSE_INCOMPLETE = -8,
48 REQUIRED_FIELDS = -9
49}
50
51enum PB_DATA_TYPE {
52 INT32 = 0,
53 SINT32 = 1,
54 UINT32 = 2,
55 INT64 = 3,
56 SINT64 = 4,
57 UINT64 = 5,
58 BOOL = 6,
59 ENUM = 7,
60 FIXED32 = 8,
61 SFIXED32 = 9,
62 FLOAT = 10,
63 FIXED64 = 11,
64 SFIXED64 = 12,
65 DOUBLE = 13,
66 STRING = 14,
67 BYTES = 15,
68 MESSAGE = 16,
69 MAP = 17
70}
71
72const DEFAULT_VALUES_2 = {
73 PB_DATA_TYPE.INT32: null,
74 PB_DATA_TYPE.SINT32: null,
75 PB_DATA_TYPE.UINT32: null,
76 PB_DATA_TYPE.INT64: null,
77 PB_DATA_TYPE.SINT64: null,
78 PB_DATA_TYPE.UINT64: null,
79 PB_DATA_TYPE.BOOL: null,
80 PB_DATA_TYPE.ENUM: null,
81 PB_DATA_TYPE.FIXED32: null,
82 PB_DATA_TYPE.SFIXED32: null,
83 PB_DATA_TYPE.FLOAT: null,
84 PB_DATA_TYPE.FIXED64: null,
85 PB_DATA_TYPE.SFIXED64: null,
86 PB_DATA_TYPE.DOUBLE: null,
87 PB_DATA_TYPE.STRING: null,
88 PB_DATA_TYPE.BYTES: null,
89 PB_DATA_TYPE.MESSAGE: null,
90 PB_DATA_TYPE.MAP: null
91}
92
93const DEFAULT_VALUES_3 = {
94 PB_DATA_TYPE.INT32: 0,
95 PB_DATA_TYPE.SINT32: 0,
96 PB_DATA_TYPE.UINT32: 0,
97 PB_DATA_TYPE.INT64: 0,
98 PB_DATA_TYPE.SINT64: 0,
99 PB_DATA_TYPE.UINT64: 0,
100 PB_DATA_TYPE.BOOL: false,
101 PB_DATA_TYPE.ENUM: 0,
102 PB_DATA_TYPE.FIXED32: 0,
103 PB_DATA_TYPE.SFIXED32: 0,
104 PB_DATA_TYPE.FLOAT: 0.0,
105 PB_DATA_TYPE.FIXED64: 0,
106 PB_DATA_TYPE.SFIXED64: 0,
107 PB_DATA_TYPE.DOUBLE: 0.0,
108 PB_DATA_TYPE.STRING: "",
109 PB_DATA_TYPE.BYTES: [],
110 PB_DATA_TYPE.MESSAGE: null,
111 PB_DATA_TYPE.MAP: []
112}
113
114enum PB_TYPE {
115 VARINT = 0,
116 FIX64 = 1,
117 LENGTHDEL = 2,
118 STARTGROUP = 3,
119 ENDGROUP = 4,
120 FIX32 = 5,
121 UNDEFINED = 8
122}
123
124enum PB_RULE {
125 OPTIONAL = 0,
126 REQUIRED = 1,
127 REPEATED = 2,
128 RESERVED = 3
129}
130
131enum PB_SERVICE_STATE {
132 FILLED = 0,
133 UNFILLED = 1
134}
135
136class PBField:
137 func _init(a_name : String, a_type : int, a_rule : int, a_tag : int, packed : bool, a_value = null):
138 name = a_name
139 type = a_type
140 rule = a_rule
141 tag = a_tag
142 option_packed = packed
143 value = a_value
144
145 var name : String
146 var type : int
147 var rule : int
148 var tag : int
149 var option_packed : bool
150 var value
151 var is_map_field : bool = false
152 var option_default : bool = false
153
154class PBTypeTag:
155 var ok : bool = false
156 var type : int
157 var tag : int
158 var offset : int
159
160class PBServiceField:
161 var field : PBField
162 var func_ref = null
163 var state : int = PB_SERVICE_STATE.UNFILLED
164
165class PBPacker:
166 static func convert_signed(n : int) -> int:
167 if n < -2147483648:
168 return (n << 1) ^ (n >> 63)
169 else:
170 return (n << 1) ^ (n >> 31)
171
172 static func deconvert_signed(n : int) -> int:
173 if n & 0x01:
174 return ~(n >> 1)
175 else:
176 return (n >> 1)
177
178 static func pack_varint(value) -> PackedByteArray:
179 var varint : PackedByteArray = PackedByteArray()
180 if typeof(value) == TYPE_BOOL:
181 if value:
182 value = 1
183 else:
184 value = 0
185 for _i in range(9):
186 var b = value & 0x7F
187 value >>= 7
188 if value:
189 varint.append(b | 0x80)
190 else:
191 varint.append(b)
192 break
193 if varint.size() == 9 && (varint[8] & 0x80 != 0):
194 varint.append(0x01)
195 return varint
196
197 static func pack_bytes(value, count : int, data_type : int) -> PackedByteArray:
198 var bytes : PackedByteArray = PackedByteArray()
199 if data_type == PB_DATA_TYPE.FLOAT:
200 var spb : StreamPeerBuffer = StreamPeerBuffer.new()
201 spb.put_float(value)
202 bytes = spb.get_data_array()
203 elif data_type == PB_DATA_TYPE.DOUBLE:
204 var spb : StreamPeerBuffer = StreamPeerBuffer.new()
205 spb.put_double(value)
206 bytes = spb.get_data_array()
207 else:
208 for _i in range(count):
209 bytes.append(value & 0xFF)
210 value >>= 8
211 return bytes
212
213 static func unpack_bytes(bytes : PackedByteArray, index : int, count : int, data_type : int):
214 if data_type == PB_DATA_TYPE.FLOAT:
215 return bytes.decode_float(index)
216 elif data_type == PB_DATA_TYPE.DOUBLE:
217 return bytes.decode_double(index)
218 else:
219 # Convert to big endian
220 var slice: PackedByteArray = bytes.slice(index, index + count)
221 slice.reverse()
222 return slice
223
224 static func unpack_varint(varint_bytes) -> int:
225 var value : int = 0
226 var i: int = varint_bytes.size() - 1
227 while i > -1:
228 value = (value << 7) | (varint_bytes[i] & 0x7F)
229 i -= 1
230 return value
231
232 static func pack_type_tag(type : int, tag : int) -> PackedByteArray:
233 return pack_varint((tag << 3) | type)
234
235 static func isolate_varint(bytes : PackedByteArray, index : int) -> PackedByteArray:
236 var i: int = index
237 while i <= index + 10: # Protobuf varint max size is 10 bytes
238 if !(bytes[i] & 0x80):
239 return bytes.slice(index, i + 1)
240 i += 1
241 return [] # Unreachable
242
243 static func unpack_type_tag(bytes : PackedByteArray, index : int) -> PBTypeTag:
244 var varint_bytes : PackedByteArray = isolate_varint(bytes, index)
245 var result : PBTypeTag = PBTypeTag.new()
246 if varint_bytes.size() != 0:
247 result.ok = true
248 result.offset = varint_bytes.size()
249 var unpacked : int = unpack_varint(varint_bytes)
250 result.type = unpacked & 0x07
251 result.tag = unpacked >> 3
252 return result
253
254 static func pack_length_delimeted(type : int, tag : int, bytes : PackedByteArray) -> PackedByteArray:
255 var result : PackedByteArray = pack_type_tag(type, tag)
256 result.append_array(pack_varint(bytes.size()))
257 result.append_array(bytes)
258 return result
259
260 static func pb_type_from_data_type(data_type : int) -> int:
261 if data_type == PB_DATA_TYPE.INT32 || data_type == PB_DATA_TYPE.SINT32 || data_type == PB_DATA_TYPE.UINT32 || data_type == PB_DATA_TYPE.INT64 || data_type == PB_DATA_TYPE.SINT64 || data_type == PB_DATA_TYPE.UINT64 || data_type == PB_DATA_TYPE.BOOL || data_type == PB_DATA_TYPE.ENUM:
262 return PB_TYPE.VARINT
263 elif data_type == PB_DATA_TYPE.FIXED32 || data_type == PB_DATA_TYPE.SFIXED32 || data_type == PB_DATA_TYPE.FLOAT:
264 return PB_TYPE.FIX32
265 elif data_type == PB_DATA_TYPE.FIXED64 || data_type == PB_DATA_TYPE.SFIXED64 || data_type == PB_DATA_TYPE.DOUBLE:
266 return PB_TYPE.FIX64
267 elif data_type == PB_DATA_TYPE.STRING || data_type == PB_DATA_TYPE.BYTES || data_type == PB_DATA_TYPE.MESSAGE || data_type == PB_DATA_TYPE.MAP:
268 return PB_TYPE.LENGTHDEL
269 else:
270 return PB_TYPE.UNDEFINED
271
272 static func pack_field(field : PBField) -> PackedByteArray:
273 var type : int = pb_type_from_data_type(field.type)
274 var type_copy : int = type
275 if field.rule == PB_RULE.REPEATED && field.option_packed:
276 type = PB_TYPE.LENGTHDEL
277 var head : PackedByteArray = pack_type_tag(type, field.tag)
278 var data : PackedByteArray = PackedByteArray()
279 if type == PB_TYPE.VARINT:
280 var value
281 if field.rule == PB_RULE.REPEATED:
282 for v in field.value:
283 data.append_array(head)
284 if field.type == PB_DATA_TYPE.SINT32 || field.type == PB_DATA_TYPE.SINT64:
285 value = convert_signed(v)
286 else:
287 value = v
288 data.append_array(pack_varint(value))
289 return data
290 else:
291 if field.type == PB_DATA_TYPE.SINT32 || field.type == PB_DATA_TYPE.SINT64:
292 value = convert_signed(field.value)
293 else:
294 value = field.value
295 data = pack_varint(value)
296 elif type == PB_TYPE.FIX32:
297 if field.rule == PB_RULE.REPEATED:
298 for v in field.value:
299 data.append_array(head)
300 data.append_array(pack_bytes(v, 4, field.type))
301 return data
302 else:
303 data.append_array(pack_bytes(field.value, 4, field.type))
304 elif type == PB_TYPE.FIX64:
305 if field.rule == PB_RULE.REPEATED:
306 for v in field.value:
307 data.append_array(head)
308 data.append_array(pack_bytes(v, 8, field.type))
309 return data
310 else:
311 data.append_array(pack_bytes(field.value, 8, field.type))
312 elif type == PB_TYPE.LENGTHDEL:
313 if field.rule == PB_RULE.REPEATED:
314 if type_copy == PB_TYPE.VARINT:
315 if field.type == PB_DATA_TYPE.SINT32 || field.type == PB_DATA_TYPE.SINT64:
316 var signed_value : int
317 for v in field.value:
318 signed_value = convert_signed(v)
319 data.append_array(pack_varint(signed_value))
320 else:
321 for v in field.value:
322 data.append_array(pack_varint(v))
323 return pack_length_delimeted(type, field.tag, data)
324 elif type_copy == PB_TYPE.FIX32:
325 for v in field.value:
326 data.append_array(pack_bytes(v, 4, field.type))
327 return pack_length_delimeted(type, field.tag, data)
328 elif type_copy == PB_TYPE.FIX64:
329 for v in field.value:
330 data.append_array(pack_bytes(v, 8, field.type))
331 return pack_length_delimeted(type, field.tag, data)
332 elif field.type == PB_DATA_TYPE.STRING:
333 for v in field.value:
334 var obj = v.to_utf8_buffer()
335 data.append_array(pack_length_delimeted(type, field.tag, obj))
336 return data
337 elif field.type == PB_DATA_TYPE.BYTES:
338 for v in field.value:
339 data.append_array(pack_length_delimeted(type, field.tag, v))
340 return data
341 elif typeof(field.value[0]) == TYPE_OBJECT:
342 for v in field.value:
343 var obj : PackedByteArray = v.to_bytes()
344 data.append_array(pack_length_delimeted(type, field.tag, obj))
345 return data
346 else:
347 if field.type == PB_DATA_TYPE.STRING:
348 var str_bytes : PackedByteArray = field.value.to_utf8_buffer()
349 if PROTO_VERSION == 2 || (PROTO_VERSION == 3 && str_bytes.size() > 0):
350 data.append_array(str_bytes)
351 return pack_length_delimeted(type, field.tag, data)
352 if field.type == PB_DATA_TYPE.BYTES:
353 if PROTO_VERSION == 2 || (PROTO_VERSION == 3 && field.value.size() > 0):
354 data.append_array(field.value)
355 return pack_length_delimeted(type, field.tag, data)
356 elif typeof(field.value) == TYPE_OBJECT:
357 var obj : PackedByteArray = field.value.to_bytes()
358 if obj.size() > 0:
359 data.append_array(obj)
360 return pack_length_delimeted(type, field.tag, data)
361 else:
362 pass
363 if data.size() > 0:
364 head.append_array(data)
365 return head
366 else:
367 return data
368
369 static func skip_unknown_field(bytes : PackedByteArray, offset : int, type : int) -> int:
370 if type == PB_TYPE.VARINT:
371 return offset + isolate_varint(bytes, offset).size()
372 if type == PB_TYPE.FIX64:
373 return offset + 8
374 if type == PB_TYPE.LENGTHDEL:
375 var length_bytes : PackedByteArray = isolate_varint(bytes, offset)
376 var length : int = unpack_varint(length_bytes)
377 return offset + length_bytes.size() + length
378 if type == PB_TYPE.FIX32:
379 return offset + 4
380 return PB_ERR.UNDEFINED_STATE
381
382 static func unpack_field(bytes : PackedByteArray, offset : int, field : PBField, type : int, message_func_ref) -> int:
383 if field.rule == PB_RULE.REPEATED && type != PB_TYPE.LENGTHDEL && field.option_packed:
384 var count = isolate_varint(bytes, offset)
385 if count.size() > 0:
386 offset += count.size()
387 count = unpack_varint(count)
388 if type == PB_TYPE.VARINT:
389 var val
390 var counter = offset + count
391 while offset < counter:
392 val = isolate_varint(bytes, offset)
393 if val.size() > 0:
394 offset += val.size()
395 val = unpack_varint(val)
396 if field.type == PB_DATA_TYPE.SINT32 || field.type == PB_DATA_TYPE.SINT64:
397 val = deconvert_signed(val)
398 elif field.type == PB_DATA_TYPE.BOOL:
399 if val:
400 val = true
401 else:
402 val = false
403 field.value.append(val)
404 else:
405 return PB_ERR.REPEATED_COUNT_MISMATCH
406 return offset
407 elif type == PB_TYPE.FIX32 || type == PB_TYPE.FIX64:
408 var type_size
409 if type == PB_TYPE.FIX32:
410 type_size = 4
411 else:
412 type_size = 8
413 var val
414 var counter = offset + count
415 while offset < counter:
416 if (offset + type_size) > bytes.size():
417 return PB_ERR.REPEATED_COUNT_MISMATCH
418 val = unpack_bytes(bytes, offset, type_size, field.type)
419 offset += type_size
420 field.value.append(val)
421 return offset
422 else:
423 return PB_ERR.REPEATED_COUNT_NOT_FOUND
424 else:
425 if type == PB_TYPE.VARINT:
426 var val = isolate_varint(bytes, offset)
427 if val.size() > 0:
428 offset += val.size()
429 val = unpack_varint(val)
430 if field.type == PB_DATA_TYPE.SINT32 || field.type == PB_DATA_TYPE.SINT64:
431 val = deconvert_signed(val)
432 elif field.type == PB_DATA_TYPE.BOOL:
433 if val:
434 val = true
435 else:
436 val = false
437 if field.rule == PB_RULE.REPEATED:
438 field.value.append(val)
439 else:
440 field.value = val
441 else:
442 return PB_ERR.VARINT_NOT_FOUND
443 return offset
444 elif type == PB_TYPE.FIX32 || type == PB_TYPE.FIX64:
445 var type_size
446 if type == PB_TYPE.FIX32:
447 type_size = 4
448 else:
449 type_size = 8
450 var val
451 if (offset + type_size) > bytes.size():
452 return PB_ERR.REPEATED_COUNT_MISMATCH
453 val = unpack_bytes(bytes, offset, type_size, field.type)
454 offset += type_size
455 if field.rule == PB_RULE.REPEATED:
456 field.value.append(val)
457 else:
458 field.value = val
459 return offset
460 elif type == PB_TYPE.LENGTHDEL:
461 var inner_size = isolate_varint(bytes, offset)
462 if inner_size.size() > 0:
463 offset += inner_size.size()
464 inner_size = unpack_varint(inner_size)
465 if inner_size >= 0:
466 if inner_size + offset > bytes.size():
467 return PB_ERR.LENGTHDEL_SIZE_MISMATCH
468 if message_func_ref != null:
469 var message = message_func_ref.call()
470 if inner_size > 0:
471 var sub_offset = message.from_bytes(bytes, offset, inner_size + offset)
472 if sub_offset > 0:
473 if sub_offset - offset >= inner_size:
474 offset = sub_offset
475 return offset
476 else:
477 return PB_ERR.LENGTHDEL_SIZE_MISMATCH
478 return sub_offset
479 else:
480 return offset
481 elif field.type == PB_DATA_TYPE.STRING:
482 var str_bytes : PackedByteArray = bytes.slice(offset, inner_size + offset)
483 if field.rule == PB_RULE.REPEATED:
484 field.value.append(str_bytes.get_string_from_utf8())
485 else:
486 field.value = str_bytes.get_string_from_utf8()
487 return offset + inner_size
488 elif field.type == PB_DATA_TYPE.BYTES:
489 var val_bytes : PackedByteArray = bytes.slice(offset, inner_size + offset)
490 if field.rule == PB_RULE.REPEATED:
491 field.value.append(val_bytes)
492 else:
493 field.value = val_bytes
494 return offset + inner_size
495 else:
496 return PB_ERR.LENGTHDEL_SIZE_NOT_FOUND
497 else:
498 return PB_ERR.LENGTHDEL_SIZE_NOT_FOUND
499 return PB_ERR.UNDEFINED_STATE
500
501 static func unpack_message(data, bytes : PackedByteArray, offset : int, limit : int) -> int:
502 while true:
503 var tt : PBTypeTag = unpack_type_tag(bytes, offset)
504 if tt.ok:
505 offset += tt.offset
506 if data.has(tt.tag):
507 var service : PBServiceField = data[tt.tag]
508 var type : int = pb_type_from_data_type(service.field.type)
509 if type == tt.type || (tt.type == PB_TYPE.LENGTHDEL && service.field.rule == PB_RULE.REPEATED && service.field.option_packed):
510 var res : int = unpack_field(bytes, offset, service.field, type, service.func_ref)
511 if res > 0:
512 service.state = PB_SERVICE_STATE.FILLED
513 offset = res
514 if offset == limit:
515 return offset
516 elif offset > limit:
517 return PB_ERR.PACKAGE_SIZE_MISMATCH
518 elif res < 0:
519 return res
520 else:
521 break
522 else:
523 var res : int = skip_unknown_field(bytes, offset, tt.type)
524 if res > 0:
525 offset = res
526 if offset == limit:
527 return offset
528 elif offset > limit:
529 return PB_ERR.PACKAGE_SIZE_MISMATCH
530 elif res < 0:
531 return res
532 else:
533 break
534 else:
535 return offset
536 return PB_ERR.UNDEFINED_STATE
537
538 static func pack_message(data) -> PackedByteArray:
539 var DEFAULT_VALUES
540 if PROTO_VERSION == 2:
541 DEFAULT_VALUES = DEFAULT_VALUES_2
542 elif PROTO_VERSION == 3:
543 DEFAULT_VALUES = DEFAULT_VALUES_3
544 var result : PackedByteArray = PackedByteArray()
545 var keys : Array = data.keys()
546 keys.sort()
547 for i in keys:
548 if data[i].field.value != null:
549 if data[i].state == PB_SERVICE_STATE.UNFILLED \
550 && !data[i].field.is_map_field \
551 && typeof(data[i].field.value) == typeof(DEFAULT_VALUES[data[i].field.type]) \
552 && data[i].field.value == DEFAULT_VALUES[data[i].field.type]:
553 continue
554 elif data[i].field.rule == PB_RULE.REPEATED && data[i].field.value.size() == 0:
555 continue
556 result.append_array(pack_field(data[i].field))
557 elif data[i].field.rule == PB_RULE.REQUIRED:
558 print("Error: required field is not filled: Tag:", data[i].field.tag)
559 return PackedByteArray()
560 return result
561
562 static func check_required(data) -> bool:
563 var keys : Array = data.keys()
564 for i in keys:
565 if data[i].field.rule == PB_RULE.REQUIRED && data[i].state == PB_SERVICE_STATE.UNFILLED:
566 return false
567 return true
568
569 static func construct_map(key_values):
570 var result = {}
571 for kv in key_values:
572 result[kv.get_key()] = kv.get_value()
573 return result
574
575 static func tabulate(text : String, nesting : int) -> String:
576 var tab : String = ""
577 for _i in range(nesting):
578 tab += DEBUG_TAB
579 return tab + text
580
581 static func value_to_string(value, field : PBField, nesting : int) -> String:
582 var result : String = ""
583 var text : String
584 if field.type == PB_DATA_TYPE.MESSAGE:
585 result += "{"
586 nesting += 1
587 text = message_to_string(value.data, nesting)
588 if text != "":
589 result += "\n" + text
590 nesting -= 1
591 result += tabulate("}", nesting)
592 else:
593 nesting -= 1
594 result += "}"
595 elif field.type == PB_DATA_TYPE.BYTES:
596 result += "<"
597 for i in range(value.size()):
598 result += str(value[i])
599 if i != (value.size() - 1):
600 result += ", "
601 result += ">"
602 elif field.type == PB_DATA_TYPE.STRING:
603 result += "\"" + value + "\""
604 elif field.type == PB_DATA_TYPE.ENUM:
605 result += "ENUM::" + str(value)
606 else:
607 result += str(value)
608 return result
609
610 static func field_to_string(field : PBField, nesting : int) -> String:
611 var result : String = tabulate(field.name + ": ", nesting)
612 if field.type == PB_DATA_TYPE.MAP:
613 if field.value.size() > 0:
614 result += "(\n"
615 nesting += 1
616 for i in range(field.value.size()):
617 var local_key_value = field.value[i].data[1].field
618 result += tabulate(value_to_string(local_key_value.value, local_key_value, nesting), nesting) + ": "
619 local_key_value = field.value[i].data[2].field
620 result += value_to_string(local_key_value.value, local_key_value, nesting)
621 if i != (field.value.size() - 1):
622 result += ","
623 result += "\n"
624 nesting -= 1
625 result += tabulate(")", nesting)
626 else:
627 result += "()"
628 elif field.rule == PB_RULE.REPEATED:
629 if field.value.size() > 0:
630 result += "[\n"
631 nesting += 1
632 for i in range(field.value.size()):
633 result += tabulate(str(i) + ": ", nesting)
634 result += value_to_string(field.value[i], field, nesting)
635 if i != (field.value.size() - 1):
636 result += ","
637 result += "\n"
638 nesting -= 1
639 result += tabulate("]", nesting)
640 else:
641 result += "[]"
642 else:
643 result += value_to_string(field.value, field, nesting)
644 result += ";\n"
645 return result
646
647 static func message_to_string(data, nesting : int = 0) -> String:
648 var DEFAULT_VALUES
649 if PROTO_VERSION == 2:
650 DEFAULT_VALUES = DEFAULT_VALUES_2
651 elif PROTO_VERSION == 3:
652 DEFAULT_VALUES = DEFAULT_VALUES_3
653 var result : String = ""
654 var keys : Array = data.keys()
655 keys.sort()
656 for i in keys:
657 if data[i].field.value != null:
658 if data[i].state == PB_SERVICE_STATE.UNFILLED \
659 && !data[i].field.is_map_field \
660 && typeof(data[i].field.value) == typeof(DEFAULT_VALUES[data[i].field.type]) \
661 && data[i].field.value == DEFAULT_VALUES[data[i].field.type]:
662 continue
663 elif data[i].field.rule == PB_RULE.REPEATED && data[i].field.value.size() == 0:
664 continue
665 result += field_to_string(data[i].field, nesting)
666 elif data[i].field.rule == PB_RULE.REQUIRED:
667 result += data[i].field.name + ": " + "error"
668 return result
diff --git a/vendor/godobuf/addons/protobuf/protobuf_util.gd b/vendor/godobuf/addons/protobuf/protobuf_util.gd new file mode 100644 index 0000000..5941cb8 --- /dev/null +++ b/vendor/godobuf/addons/protobuf/protobuf_util.gd
@@ -0,0 +1,46 @@
1#
2# BSD 3-Clause License
3#
4# Copyright (c) 2018, Oleg Malyavkin
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9#
10# * Redistributions of source code must retain the above copyright notice, this
11# list of conditions and the following disclaimer.
12#
13# * Redistributions in binary form must reproduce the above copyright notice,
14# this list of conditions and the following disclaimer in the documentation
15# and/or other materials provided with the distribution.
16#
17# * Neither the name of the copyright holder nor the names of its
18# contributors may be used to endorse or promote products derived from
19# this software without specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32static func extract_dir(file_path):
33 var parts = file_path.split("/", false)
34 parts.remove_at(parts.size() - 1)
35 var path
36 if file_path.begins_with("/"):
37 path = "/"
38 else:
39 path = ""
40 for part in parts:
41 path += part + "/"
42 return path
43
44static func extract_filename(file_path):
45 var parts = file_path.split("/", false)
46 return parts[parts.size() - 1]
diff --git a/vendor/godobuf/default_env.tres b/vendor/godobuf/default_env.tres new file mode 100644 index 0000000..20207a4 --- /dev/null +++ b/vendor/godobuf/default_env.tres
@@ -0,0 +1,7 @@
1[gd_resource type="Environment" load_steps=2 format=2]
2
3[sub_resource type="ProceduralSky" id=1]
4
5[resource]
6background_mode = 2
7background_sky = SubResource( 1 )
diff --git a/vendor/godobuf/logo.png b/vendor/godobuf/logo.png new file mode 100644 index 0000000..4ff9029 --- /dev/null +++ b/vendor/godobuf/logo.png
Binary files differ
diff --git a/vendor/godobuf/logo.png.import b/vendor/godobuf/logo.png.import new file mode 100644 index 0000000..43df7a6 --- /dev/null +++ b/vendor/godobuf/logo.png.import
@@ -0,0 +1,35 @@
1[remap]
2
3importer="texture"
4type="StreamTexture"
5path="res://.import/logo.png-cca8726399059c8d4f806e28e356b14d.stex"
6metadata={
7"vram_texture": false
8}
9
10[deps]
11
12source_file="res://logo.png"
13dest_files=[ "res://.import/logo.png-cca8726399059c8d4f806e28e356b14d.stex" ]
14
15[params]
16
17compress/mode=0
18compress/lossy_quality=0.7
19compress/hdr_mode=0
20compress/bptc_ldr=0
21compress/normal_map=0
22flags/repeat=0
23flags/filter=true
24flags/mipmaps=false
25flags/anisotropic=false
26flags/srgb=2
27process/fix_alpha_border=true
28process/premult_alpha=false
29process/HDR_as_SRGB=false
30process/invert_color=false
31process/normal_map_invert_y=false
32stream=false
33size_limit=0
34detect_3d=true
35svg/scale=1.0
diff --git a/vendor/godobuf/project.godot b/vendor/godobuf/project.godot new file mode 100644 index 0000000..8cef0a4 --- /dev/null +++ b/vendor/godobuf/project.godot
@@ -0,0 +1,26 @@
1; Engine configuration file.
2; It's best edited using the editor UI and not directly,
3; since the parameters that go here are not all obvious.
4;
5; Format:
6; [section] ; section goes between []
7; param=value ; assign values to parameters
8
9config_version=4
10
11_global_script_classes=[ ]
12_global_script_class_icons={
13}
14
15[application]
16
17config/name="Protobuf Plugin"
18config/icon="res://logo.png"
19
20[editor_plugins]
21
22enabled=PoolStringArray( "res://addons/protobuf/plugin.cfg" )
23
24[rendering]
25
26environment/default_environment="res://default_env.tres"