diff options
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 | ||
| 4 | randomizing a number of different games and combining them into one cooperative | ||
| 5 | experience. Items from each game are hidden in other games. For more information | ||
| 6 | about Archipelago, you can look at their website. | ||
| 7 | |||
| 8 | This is a project that modifies the game | ||
| 9 | [Lingo 2](https://www.lingothegame.com/lingo2.html) so that it can be played as | ||
| 10 | part of an Archipelago multiworld game. | ||
| 11 | |||
| 12 | ## How To Play | ||
| 13 | |||
| 14 | Here 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 | |||
| 28 | The letter requirements for solving puzzles are very restrictive, especially in | ||
| 29 | the early game. It is possible for the generator to find some subset of letters | ||
| 30 | and doors to place in the starting room such that you are not trapped, but this | ||
| 31 | places a lot of strain on generation and leads to significantly more generation | ||
| 32 | failures. | ||
| 33 | |||
| 34 | As a result, the starting room letters (H1, I1, N1, and T1) are always present | ||
| 35 | in the starting room, even when remote letter shuffle is enabled. These letters | ||
| 36 | will _also_ count as clearing a check, so you will send out another item at the | ||
| 37 | same time as collecting the letter. | ||
| 38 | |||
| 39 | ### What areas are randomized? | ||
| 40 | |||
| 41 | Almost all maps that you can access from the base game are randomized. The | ||
| 42 | exceptions 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 | ||
| 52 | other side of opaque walls. The player is never expected to or required to do | ||
| 53 | this in normal gameplay. This randomizer does not change how wall snipes work, | ||
| 54 | but it will likewise never require the use of them. | ||
| 55 | |||
| 56 | ### How do cyan doors work? | ||
| 57 | |||
| 58 | In the base game, there are a number of cyan-colored doors that ordinarily open | ||
| 59 | once you collect H2 in The Repetitive. There are also a handful of panels that | ||
| 60 | only appear upon getting H2 as well, which the apworld treats the same as the | ||
| 61 | cyan doors. | ||
| 62 | |||
| 63 | There is an option that lets you choose how these doors and panels behave. By | ||
| 64 | default, they act the same as in the base game: they only open or appear after | ||
| 65 | collecting H2. Note that this means the actual H2 collectable in The Repetitive. | ||
| 66 | Receiving H2 via remote letter shuffle does not count for this requirement. | ||
| 67 | However, you can also make cyan doors activate upon collecting or receiving your | ||
| 68 | first double letter, regardless of what it is or if it's remote. Finally, you | ||
| 69 | can lock cyan doors behind an item called "Cyan Doors". | ||
| 70 | |||
| 71 | It is important to note, however, that the Cyan Door Behavior option only | ||
| 72 | applies to cyan doors that are not already affected by another type of | ||
| 73 | shuffling. When door shuffle is on, the following cyan doors are activated by | ||
| 74 | individual 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 | |||
| 85 | Additionally, when control center color shuffle is enabled, the orange door in | ||
| 86 | The Unkempt (which ordinarily doubles as a cyan door) opens upon receiving the | ||
| 87 | Control Center Orange Doors item, instead of following the Cyan Door Behavior | ||
| 88 | option. | ||
| 89 | |||
| 90 | ### Help! I lost C/G in The Congruent! | ||
| 91 | |||
| 92 | If you place C or G into the relevant keyholders in The Congruent, the keyholder | ||
| 93 | disappears. You can retrieve your letter immediately by pressing C or G again | ||
| 94 | while standing in the same position, as the keyholder is still there, just | ||
| 95 | invisible. If you have already left the room, though, there is an easier way to | ||
| 96 | get your letters back: just use the Key Return in The Entry. | ||
| 97 | |||
| 98 | ## Project Details | ||
| 99 | |||
| 100 | There 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 | |||
| 8 | Download: | ||
| 9 | [lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v3.2/lingo2.apworld)<br/> | ||
| 10 | Template YAML: | ||
| 11 | [Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v3.2/Lingo%202.yaml)<br/> | ||
| 12 | Source: | ||
| 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 | |||
| 3 | The Lingo 2 Apworld allows you to generate Archipelago Multiworlds containing | ||
| 4 | Lingo 2. | ||
| 5 | |||
| 6 | ## Installation | ||
| 7 | |||
| 8 | 1. Download the Lingo 2 Apworld from | ||
| 9 | [the releases page](https://code.fourisland.com/lingo2-archipelago/about/apworld/CHANGELOG.md). | ||
| 10 | 2. If you do not already have it, download and install the | ||
| 11 | [Archipelago software](https://github.com/ArchipelagoMW/Archipelago/releases/). | ||
| 12 | 3. Double click on `lingo2.apworld` to install it, or copy it manually to the | ||
| 13 | `custom_worlds` folder of your Archipelago installation. | ||
| 14 | |||
| 15 | ## Running from source | ||
| 16 | |||
| 17 | The apworld is mostly written in Python, which does not need to be compiled. | ||
| 18 | However, there are two files that need to be generated before the apworld can be | ||
| 19 | used. | ||
| 20 | |||
| 21 | The first file is `data.binpb`, the datafile containing the randomizer logic. | ||
| 22 | You can read about how to generate it on | ||
| 23 | [its own README page](https://code.fourisland.com/lingo2-archipelago/about/data/README.md). | ||
| 24 | Once you have it, put it in a subfolder of `apworld` called `generated`. | ||
| 25 | |||
| 26 | The second generated file is `data_pb2.py`. This file allows Archipelago to read | ||
| 27 | the datafile. We use `protoc`, the Protocol Buffer compiler, to generate it. As | ||
| 28 | of 0.6.3, Archipelago has protobuf 3.20.3 packaged with it, which means we need | ||
| 29 | to compile our proto file with a similar version. | ||
| 30 | |||
| 31 | If you followed the steps to generate `data.binpb` and compiled the `datapacker` | ||
| 32 | tool yourself, you will already have protobuf version 3.21.12 installed through | ||
| 33 | vcpkg. You can then run a command similar to this in order to generate the | ||
| 34 | python 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 | |||
| 41 | The exact path to `protoc.exe` is going to depend on where vcpkg installed its | ||
| 42 | packages. The above location is where Visual Studio will probably put it. | ||
| 43 | |||
| 44 | After generating those two files, the apworld should be functional. You can copy | ||
| 45 | it into an Archipelago source tree (rename the folder `apworld` to `lingo2` if | ||
| 46 | you do so) if you want to edit/debug the code. Otherwise, you can zip up the | ||
| 47 | folder and rename it to `lingo2.apworld` in order to package it for | ||
| 48 | distribution. | ||
| 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 | """ |
| 2 | Archipelago init file for Lingo 2 | 2 | Archipelago init file for Lingo 2 |
| 3 | """ | 3 | """ |
| 4 | from BaseClasses import ItemClassification, Item | 4 | from BaseClasses import ItemClassification, Item, Tutorial |
| 5 | from worlds.AutoWorld import WebWorld, World | 5 | from worlds.AutoWorld import WebWorld, World |
| 6 | from .items import Lingo2Item | 6 | from .items import Lingo2Item, ANTI_COLLECTABLE_TRAPS |
| 7 | from .options import Lingo2Options | 7 | from .options import Lingo2Options |
| 8 | from .player_logic import Lingo2PlayerLogic | 8 | from .player_logic import Lingo2PlayerLogic |
| 9 | from .regions import create_regions | 9 | from .regions import create_regions |
| 10 | from .static_logic import Lingo2StaticLogic | 10 | from .static_logic import Lingo2StaticLogic |
| 11 | from .version import APWORLD_VERSION | ||
| 11 | 12 | ||
| 12 | 13 | ||
| 13 | class Lingo2WebWorld(WebWorld): | 14 | class 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 | ||
| 18 | class Lingo2World(World): | 27 | class 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 | |||
| 3 | See [the project README](https://code.fourisland.com/lingo2-archipelago/about/) | ||
| 4 | for installation instructions and frequently asked questions. \ No newline at end of file | ||
| diff --git a/apworld/items.py b/apworld/items.py index 971a709..28158c3 100644 --- a/apworld/items.py +++ b/apworld/items.py | |||
| @@ -1,5 +1,31 @@ | |||
| 1 | from .generated import data_pb2 as data_pb2 | ||
| 1 | from BaseClasses import Item | 2 | from BaseClasses import Item |
| 2 | 3 | ||
| 3 | 4 | ||
| 4 | class Lingo2Item(Item): | 5 | class Lingo2Item(Item): |
| 5 | game: str = "Lingo 2" | 6 | game: str = "Lingo 2" |
| 7 | |||
| 8 | |||
| 9 | SYMBOL_ITEMS: dict[data_pb2.PuzzleSymbol, str] = { | ||
| 10 | data_pb2.PuzzleSymbol.SUN: "Sun Symbol", | ||
| 11 | data_pb2.PuzzleSymbol.SPARKLES: "Sparkles Symbol", | ||
| 12 | data_pb2.PuzzleSymbol.ZERO: "Zero Symbol", | ||
| 13 | data_pb2.PuzzleSymbol.EXAMPLE: "Example Symbol", | ||
| 14 | data_pb2.PuzzleSymbol.BOXES: "Boxes Symbol", | ||
| 15 | data_pb2.PuzzleSymbol.PLANET: "Planet Symbol", | ||
| 16 | data_pb2.PuzzleSymbol.PYRAMID: "Pyramid Symbol", | ||
| 17 | data_pb2.PuzzleSymbol.CROSS: "Cross Symbol", | ||
| 18 | data_pb2.PuzzleSymbol.SWEET: "Sweet Symbol", | ||
| 19 | data_pb2.PuzzleSymbol.GENDER: "Gender Symbol", | ||
| 20 | data_pb2.PuzzleSymbol.AGE: "Age Symbol", | ||
| 21 | data_pb2.PuzzleSymbol.SOUND: "Sound Symbol", | ||
| 22 | data_pb2.PuzzleSymbol.ANAGRAM: "Anagram Symbol", | ||
| 23 | data_pb2.PuzzleSymbol.JOB: "Job Symbol", | ||
| 24 | data_pb2.PuzzleSymbol.STARS: "Stars Symbol", | ||
| 25 | data_pb2.PuzzleSymbol.NULL: "Null Symbol", | ||
| 26 | data_pb2.PuzzleSymbol.EVAL: "Eval Symbol", | ||
| 27 | data_pb2.PuzzleSymbol.LINGO: "Lingo Symbol", | ||
| 28 | data_pb2.PuzzleSymbol.QUESTION: "Question Symbol", | ||
| 29 | } | ||
| 30 | |||
| 31 | ANTI_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 @@ | |||
| 1 | from dataclasses import dataclass | 1 | from dataclasses import dataclass |
| 2 | 2 | ||
| 3 | from Options import PerGameCommonOptions, Toggle, Choice | 3 | from Options import PerGameCommonOptions, Toggle, Choice, DefaultOnToggle, Range |
| 4 | 4 | ||
| 5 | 5 | ||
| 6 | class ShuffleDoors(Toggle): | 6 | class 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 | ||
| 11 | class 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 | |||
| 19 | class 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 | |||
| 24 | class 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 | |||
| 47 | class 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 | |||
| 55 | class 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 | |||
| 64 | class 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 | |||
| 85 | class 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 | |||
| 11 | class VictoryCondition(Choice): | 95 | class 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 | ||
| 129 | class 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 |
| 30 | class Lingo2Options(PerGameCommonOptions): | 138 | class 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 @@ | |||
| 1 | from enum import IntEnum, auto | ||
| 2 | |||
| 1 | from .generated import data_pb2 as data_pb2 | 3 | from .generated import data_pb2 as data_pb2 |
| 4 | from .items import SYMBOL_ITEMS | ||
| 2 | from typing import TYPE_CHECKING, NamedTuple | 5 | from typing import TYPE_CHECKING, NamedTuple |
| 3 | 6 | ||
| 4 | from .options import VictoryCondition | 7 | from .options import VictoryCondition, ShuffleLetters, CyanDoorBehavior |
| 5 | 8 | ||
| 6 | if TYPE_CHECKING: | 9 | if 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 | ||
| 24 | class AccessRequirements: | 23 | class 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 | ||
| 77 | class PlayerLocation(NamedTuple): | 163 | class PlayerLocation(NamedTuple): |
| @@ -79,13 +165,19 @@ class PlayerLocation(NamedTuple): | |||
| 79 | reqs: AccessRequirements | 165 | reqs: AccessRequirements |
| 80 | 166 | ||
| 81 | 167 | ||
| 168 | class LetterBehavior(IntEnum): | ||
| 169 | VANILLA = auto() | ||
| 170 | ITEM = auto() | ||
| 171 | UNLOCKED = auto() | ||
| 172 | |||
| 173 | |||
| 82 | class Lingo2PlayerLogic: | 174 | class 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 | ||
| 13 | def create_region(room, world: "Lingo2World") -> Region: | 13 | def 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 | |||
| 17 | def create_locations(room, new_region: Region, world: "Lingo2World", regions: dict[str, Region]): | ||
| 16 | for location in world.player_logic.locations_by_room.get(room.id, {}): | 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 | |||
| 31 | def create_regions(world: "Lingo2World"): | 34 | def 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 @@ | |||
| 1 | from collections.abc import Callable | 1 | from collections.abc import Callable |
| 2 | from typing import TYPE_CHECKING | 2 | from typing import TYPE_CHECKING |
| 3 | 3 | ||
| 4 | from BaseClasses import CollectionState | 4 | from BaseClasses import CollectionState, Region |
| 5 | from .player_logic import AccessRequirements | 5 | from .player_logic import AccessRequirements |
| 6 | 6 | ||
| 7 | if TYPE_CHECKING: | 7 | if TYPE_CHECKING: |
| 8 | from . import Lingo2World | 8 | from . import Lingo2World |
| 9 | 9 | ||
| 10 | 10 | ||
| 11 | def lingo2_can_satisfy_requirements(state: CollectionState, reqs: AccessRequirements, world: "Lingo2World") -> bool: | 11 | def 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 | ||
| 31 | def make_location_lambda(reqs: AccessRequirements, world: "Lingo2World") -> Callable[[CollectionState], bool]: | 56 | def 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 @@ | |||
| 1 | from .generated import data_pb2 as data_pb2 | 1 | from .generated import data_pb2 as data_pb2 |
| 2 | from .items import SYMBOL_ITEMS, ANTI_COLLECTABLE_TRAPS | ||
| 2 | import pkgutil | 3 | import pkgutil |
| 3 | 4 | ||
| 4 | class Lingo2StaticLogic: | 5 | class 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 @@ | |||
| 1 | extends "res://scripts/nodes/listeners/animationListener.gd" | 1 | extends "res://scripts/nodes/listeners/animationListener.gd" |
| 2 | 2 | ||
| 3 | var item_id | 3 | var item_id |
| 4 | var item_amount | ||
| 4 | 5 | ||
| 5 | 6 | ||
| 6 | func _ready(): | 7 | func _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(): | |||
| 34 | func _readier(): | 34 | func _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 = [] | |||
| 32 | var _player_name_by_slot = {} | 32 | var _player_name_by_slot = {} |
| 33 | var _game_by_player = {} | 33 | var _game_by_player = {} |
| 34 | var _checked_locations = [] | 34 | var _checked_locations = [] |
| 35 | var _received_items = [] | 35 | var _received_indexes = [] |
| 36 | var _received_items = {} | ||
| 36 | var _slot_data = {} | 37 | var _slot_data = {} |
| 37 | 38 | ||
| 38 | signal could_not_connect | 39 | signal could_not_connect |
| 39 | signal connect_status | 40 | signal connect_status |
| 40 | signal client_connected(slot_data) | 41 | signal client_connected(slot_data) |
| 41 | signal item_received(item_id, index, player, flags) | 42 | signal item_received(item_id, index, player, flags, amount) |
| 42 | signal message_received(message) | 43 | signal message_received(message) |
| 44 | signal location_scout_received(item_id, location_id, player, flags) | ||
| 43 | 45 | ||
| 44 | 46 | ||
| 45 | func _init(): | 47 | func _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 | ||
| 79 | func _errored(): | 87 | func _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 | ||
| 408 | func scoutLocations(loc_ids): | ||
| 409 | sendMessage([{"cmd": "LocationScouts", "locations": loc_ids}]) | ||
| 410 | |||
| 411 | |||
| 381 | func hasItem(item_id): | 412 | func hasItem(item_id): |
| 382 | return _received_items.has(item_id) | 413 | return _received_items.has(item_id) |
| 414 | |||
| 415 | |||
| 416 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/collectable.gd" | ||
| 2 | |||
| 3 | |||
| 4 | func 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 | |||
| 15 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/door.gd" | 1 | extends "res://scripts/nodes/door.gd" |
| 2 | 2 | ||
| 3 | var item_id | 3 | var item_id |
| 4 | var item_amount | ||
| 4 | 5 | ||
| 5 | 6 | ||
| 6 | func _ready(): | 7 | func _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(): | |||
| 34 | func _readier(): | 34 | func _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 | |||
| 5 | var objects | 5 | var objects |
| 6 | var door_id_by_map_node_path = {} | 6 | var door_id_by_map_node_path = {} |
| 7 | var painting_id_by_map_node_path = {} | 7 | var painting_id_by_map_node_path = {} |
| 8 | var panel_id_by_map_node_path = {} | ||
| 8 | var door_id_by_ap_id = {} | 9 | var door_id_by_ap_id = {} |
| 9 | var map_id_by_name = {} | 10 | var map_id_by_name = {} |
| 11 | var progressive_id_by_ap_id = {} | ||
| 12 | var letter_id_by_ap_id = {} | ||
| 13 | var symbol_item_ids = [] | ||
| 14 | var anti_trap_ids = {} | ||
| 15 | |||
| 16 | var kSYMBOL_ITEMS | ||
| 10 | 17 | ||
| 11 | 18 | ||
| 12 | func _init(proto_script): | 19 | func _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 | ||
| 16 | func load(data_bytes): | 45 | func 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 | ||
| 54 | func get_door_for_map_node_path(map_name, node_path): | 108 | func 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 | ||
| 116 | func 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 | |||
| 62 | func get_door_ap_id(door_id): | 124 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/keyHolder.gd" | ||
| 2 | |||
| 3 | |||
| 4 | func 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 | |||
| 25 | func 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 | |||
| 33 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/listeners/keyHolderChecker.gd" | ||
| 2 | |||
| 3 | |||
| 4 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/listeners/keyHolderResetterListener.gd" | ||
| 2 | |||
| 3 | |||
| 4 | func 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 @@ | |||
| 1 | extends Node | ||
| 2 | |||
| 3 | const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz" | ||
| 4 | |||
| 5 | var letters_saved = {} | ||
| 6 | var letters_in_keyholders = [] | ||
| 7 | var letters_blocked = [] | ||
| 8 | var letters_dynamic = {} | ||
| 9 | var keyholder_state = {} | ||
| 10 | |||
| 11 | var filename = "" | ||
| 12 | |||
| 13 | |||
| 14 | func _init(): | ||
| 15 | reset() | ||
| 16 | |||
| 17 | |||
| 18 | func reset(): | ||
| 19 | letters_saved.clear() | ||
| 20 | letters_in_keyholders.clear() | ||
| 21 | letters_blocked.clear() | ||
| 22 | letters_dynamic.clear() | ||
| 23 | keyholder_state.clear() | ||
| 24 | |||
| 25 | |||
| 26 | func 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 | |||
| 64 | func 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 | |||
| 81 | func 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 | |||
| 107 | func 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 | |||
| 120 | func 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 | |||
| 133 | func 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 | |||
| 148 | func 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 | |||
| 162 | func block_letter(key): | ||
| 163 | if not letters_blocked.has(key): | ||
| 164 | letters_blocked.append(key) | ||
| 165 | |||
| 166 | update_unlocks() | ||
| 167 | |||
| 168 | |||
| 169 | func 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 | |||
| 180 | func 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 @@ | |||
| 1 | extends Node | 1 | extends Node |
| 2 | 2 | ||
| 3 | const my_version = "0.1.0" | 3 | const MOD_VERSION = 4 |
| 4 | 4 | ||
| 5 | var SCRIPT_client | 5 | var SCRIPT_client |
| 6 | var SCRIPT_keyboard | ||
| 6 | var SCRIPT_locationListener | 7 | var SCRIPT_locationListener |
| 7 | var SCRIPT_uuid | 8 | var SCRIPT_uuid |
| 8 | var SCRIPT_victoryListener | 9 | var SCRIPT_victoryListener |
| @@ -13,13 +14,42 @@ var ap_pass = "" | |||
| 13 | var connection_history = [] | 14 | var connection_history = [] |
| 14 | 15 | ||
| 15 | var client | 16 | var client |
| 17 | var keyboard | ||
| 16 | 18 | ||
| 17 | var _localdata_file = "" | 19 | var _localdata_file = "" |
| 18 | var _received_indexes = [] | ||
| 19 | var _last_new_item = -1 | 20 | var _last_new_item = -1 |
| 20 | var _batch_locations = false | 21 | var _batch_locations = false |
| 21 | var _held_locations = [] | 22 | var _held_locations = [] |
| 22 | 23 | var _held_location_scouts = [] | |
| 24 | var _location_scouts = {} | ||
| 25 | var _item_locks = {} | ||
| 26 | var _inverse_item_locks = {} | ||
| 27 | var _held_letters = {} | ||
| 28 | var _letters_setup = false | ||
| 29 | |||
| 30 | const kSHUFFLE_LETTERS_VANILLA = 0 | ||
| 31 | const kSHUFFLE_LETTERS_UNLOCKED = 1 | ||
| 32 | const kSHUFFLE_LETTERS_PROGRESSIVE = 2 | ||
| 33 | const kSHUFFLE_LETTERS_VANILLA_CYAN = 3 | ||
| 34 | const kSHUFFLE_LETTERS_ITEM_CYAN = 4 | ||
| 35 | |||
| 36 | const kLETTER_BEHAVIOR_VANILLA = 0 | ||
| 37 | const kLETTER_BEHAVIOR_ITEM = 1 | ||
| 38 | const kLETTER_BEHAVIOR_UNLOCKED = 2 | ||
| 39 | |||
| 40 | const kCYAN_DOOR_BEHAVIOR_H2 = 0 | ||
| 41 | const kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER = 1 | ||
| 42 | const kCYAN_DOOR_BEHAVIOR_ITEM = 2 | ||
| 43 | |||
| 44 | var apworld_version = [0, 0] | ||
| 45 | var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 | ||
| 46 | var daedalus_roof_access = false | ||
| 47 | var keyholder_sanity = false | ||
| 48 | var shuffle_control_center_colors = false | ||
| 49 | var shuffle_doors = false | ||
| 50 | var shuffle_gallery_paintings = false | ||
| 51 | var shuffle_letters = kSHUFFLE_LETTERS_VANILLA | ||
| 52 | var shuffle_symbols = false | ||
| 23 | var victory_condition = -1 | 53 | var victory_condition = -1 |
| 24 | 54 | ||
| 25 | signal could_not_connect | 55 | signal 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 | ||
| 67 | func saveSettings(): | 101 | func 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 | ||
| 98 | func connectToServer(): | 132 | func 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 | ||
| 113 | func get_item_id_for_door(door_id): | 152 | func 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 | ||
| 125 | func has_item(item_id): | ||
| 126 | return client.hasItem(item_id) | ||
| 127 | |||
| 128 | |||
| 129 | func _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 | ||
| 156 | func _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 | ||
| 265 | func _client_could_not_connect(): | 312 | func _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 | |||
| 339 | func _client_could_not_connect(message): | ||
| 340 | emit_signal("could_not_connect", message) | ||
| 267 | 341 | ||
| 268 | 342 | ||
| 269 | func _client_connect_status(message): | 343 | func _client_connect_status(message): |
| @@ -271,6 +345,8 @@ func _client_connect_status(message): | |||
| 271 | 345 | ||
| 272 | 346 | ||
| 273 | func _client_connected(slot_data): | 347 | func _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 | ||
| 452 | func 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 | |||
| 308 | func stop_batching_locations(): | 464 | func 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 | ||
| 314 | func colorForItemType(flags): | 476 | func 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 | |||
| 491 | func 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 | |||
| 516 | func 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 | |||
| 527 | func _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 | |||
| 65 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/painting.gd" | 1 | extends "res://scripts/nodes/painting.gd" |
| 2 | 2 | ||
| 3 | var item_id | 3 | var item_id |
| 4 | var item_amount | ||
| 4 | 5 | ||
| 5 | 6 | ||
| 6 | func _ready(): | 7 | func _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(): | |||
| 34 | func _readier(): | 34 | func _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 @@ | |||
| 1 | extends "res://scripts/nodes/panel.gd" | ||
| 2 | |||
| 3 | var panel_logic = null | ||
| 4 | var symbol_solvable = true | ||
| 5 | |||
| 6 | var black = load("res://assets/materials/black.material") | ||
| 7 | |||
| 8 | |||
| 9 | func _ready(): | ||
| 10 | super._ready() | ||
| 11 | |||
| 12 | var node_path = String( | ||
| 13 | get_tree().get_root().get_node("scene").get_path_to(self).get_concatenated_names() | ||
| 14 | ) | ||
| 15 | |||
| 16 | var gamedata = global.get_node("Gamedata") | ||
| 17 | var panel_id = gamedata.get_panel_for_map_node_path(global.map, node_path) | ||
| 18 | if panel_id != null: | ||
| 19 | var ap = global.get_node("Archipelago") | ||
| 20 | if ap.shuffle_symbols: | ||
| 21 | if global.map == "the_entry" and node_path == "Panels/Entry/front_1": | ||
| 22 | clue = "i" | ||
| 23 | symbol = "" | ||
| 24 | |||
| 25 | setField("clue", clue) | ||
| 26 | setField("symbol", symbol) | ||
| 27 | |||
| 28 | panel_logic = gamedata.objects.get_panels()[panel_id] | ||
| 29 | checkSymbolSolvable() | ||
| 30 | |||
| 31 | if not symbol_solvable: | ||
| 32 | get_tree().get_root().get_node("scene/player").connect( | ||
| 33 | "evaluate_solvability", evaluateSolvability | ||
| 34 | ) | ||
| 35 | |||
| 36 | |||
| 37 | func checkSymbolSolvable(): | ||
| 38 | var old_solvable = symbol_solvable | ||
| 39 | symbol_solvable = true | ||
| 40 | |||
| 41 | if panel_logic == null: | ||
| 42 | # There's no logic for this panel. | ||
| 43 | return | ||
| 44 | |||
| 45 | var ap = global.get_node("Archipelago") | ||
| 46 | if not ap.shuffle_symbols: | ||
| 47 | # Symbols aren't item-locked. | ||
| 48 | return | ||
| 49 | |||
| 50 | var gamedata = global.get_node("Gamedata") | ||
| 51 | for symbol in panel_logic.get_symbols(): | ||
| 52 | var item_name = gamedata.kSYMBOL_ITEMS.get(symbol) | ||
| 53 | var item_id = gamedata.objects.get_special_ids()[item_name] | ||
| 54 | if ap.client.getItemAmount(item_id) < 1: | ||
| 55 | symbol_solvable = false | ||
| 56 | break | ||
| 57 | |||
| 58 | if symbol_solvable != old_solvable: | ||
| 59 | if symbol_solvable: | ||
| 60 | setField("clue", clue) | ||
| 61 | setField("symbol", symbol) | ||
| 62 | setField("answer", answer) | ||
| 63 | else: | ||
| 64 | quad_mesh.surface_set_material(0, black) | ||
| 65 | get_node("Hinge/clue").text = "missing" | ||
| 66 | get_node("Hinge/answer").text = "symbols" | ||
| 67 | |||
| 68 | |||
| 69 | func checkSolvable(key): | ||
| 70 | checkSymbolSolvable() | ||
| 71 | if not symbol_solvable: | ||
| 72 | return false | ||
| 73 | |||
| 74 | return super.checkSolvable(key) | ||
| 75 | |||
| 76 | |||
| 77 | func evaluateSolvability(): | ||
| 78 | checkSolvable("") | ||
| 79 | |||
| 80 | |||
| 81 | func passedInput(key, skip_focus_check = false): | ||
| 82 | if not symbol_solvable: | ||
| 83 | return | ||
| 84 | |||
| 85 | super.passedInput(key, skip_focus_check) | ||
| 86 | |||
| 87 | |||
| 88 | func focus(): | ||
| 89 | if not symbol_solvable: | ||
| 90 | has_focus = false | ||
| 91 | return | ||
| 92 | |||
| 93 | super.focus() | ||
| 94 | |||
| 95 | |||
| 96 | func unfocus(): | ||
| 97 | if not symbol_solvable: | ||
| 98 | has_focus = false | ||
| 99 | return | ||
| 100 | |||
| 101 | super.unfocus() | ||
| diff --git a/client/Archipelago/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" | |||
| 4 | func _pause_game(): | 4 | func _pause_game(): |
| 5 | global.get_node("Textclient").dismiss() | 5 | global.get_node("Textclient").dismiss() |
| 6 | super._pause_game() | 6 | super._pause_game() |
| 7 | |||
| 8 | |||
| 9 | func _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 | ||
| 19 | signal evaluate_solvability | ||
| 20 | |||
| 19 | 21 | ||
| 20 | func _ready(): | 22 | func _ready(): |
| 21 | var khl_script = load("res://scripts/nodes/keyHolderListener.gd") | 23 | var khl_script = load("res://scripts/nodes/keyHolderListener.gd") |
| @@ -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 | ||
| 4 | func levelLoaded(): | 4 | func 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 |
| 101 | func installScriptExtension(childScript: Resource): | 110 | func installScriptExtension(childScript: Resource): |
| @@ -125,6 +134,33 @@ func connectionStatus(message): | |||
| 125 | 134 | ||
| 126 | func connectionSuccessful(): | 135 | func 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 | |||
| 162 | func 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 | ||
| 173 | func connectionUnsuccessful(error_message): | 218 | func 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 | |||
| 231 | func versionMismatchDeclined(): | ||
| 232 | $Panel/AcceptDialog.hide() | ||
| 233 | $Panel/connect_button.disabled = false | ||
| 234 | |||
| 183 | 235 | ||
| 184 | func historySelected(index): | 236 | func 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 | ||
| 193 | func clearResourceCache(path): | 245 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/teleport.gd" | ||
| 2 | |||
| 3 | var item_id | ||
| 4 | var item_amount | ||
| 5 | |||
| 6 | |||
| 7 | func _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 | |||
| 34 | func _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 @@ | |||
| 1 | extends "res://scripts/nodes/listeners/teleportListener.gd" | 1 | extends "res://scripts/nodes/listeners/teleportListener.gd" |
| 2 | 2 | ||
| 3 | var item_id | 3 | var item_id |
| 4 | var item_amount | ||
| 4 | 5 | ||
| 5 | 6 | ||
| 6 | func _ready(): | 7 | func _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(): | |||
| 34 | func _readier(): | 45 | func _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 | ||
| 52 | func _input(event): | 52 | func _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 | ||
| 15 | func handleUntriggered(): | 17 | func 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 @@ | |||
| 1 | extends "res://scripts/nodes/listeners/visibilityListener.gd" | ||
| 2 | |||
| 3 | var item_id | ||
| 4 | var item_amount | ||
| 5 | |||
| 6 | |||
| 7 | func _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 | |||
| 34 | func _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 @@ | |||
| 1 | extends "res://scripts/nodes/worldport.gd" | ||
| 2 | |||
| 3 | |||
| 4 | func _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 @@ | |||
| 1 | extends "res://scripts/nodes/listeners/worldportListener.gd" | 1 | extends "res://scripts/nodes/listeners/worldportListener.gd" |
| 2 | 2 | ||
| 3 | 3 | ||
| 4 | func changeScene(): | 4 | func 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 | |||
| 8 | Download: | ||
| 9 | [lingo2-archipelago-client-v3.3.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.3.zip)<br/> | ||
| 10 | Source: | ||
| 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 | |||
| 18 | Download: | ||
| 19 | [lingo2-archipelago-client-v3.2.zip](https://files.fourisland.com/releases/lingo2-archipelago/client/lingo2-archipelago-client-v3.2.zip)<br/> | ||
| 20 | Source: | ||
| 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 | |||
| 3 | The Lingo 2 Archipelago Client is a mod for Lingo 2 that allows you to connect | ||
| 4 | to an Archipelago Multiworld and randomize your game. | ||
| 5 | |||
| 6 | ## Installation | ||
| 7 | |||
| 8 | 1. Download the Lingo 2 Archipelago Randomizer from | ||
| 9 | [the releases page](https://code.fourisland.com/lingo2-archipelago/about/client/CHANGELOG.md). | ||
| 10 | 2. Open up Lingo 2, go to settings, and click View Game Data. This should open | ||
| 11 | up a folder in Windows Explorer. | ||
| 12 | 3. 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 | ||
| 16 | the major version number of the apworld you generated with. | ||
| 17 | |||
| 18 | ## Joining a Multiworld game | ||
| 19 | |||
| 20 | 1. Launch Lingo 2. | ||
| 21 | 2. Click on Level Selection, and choose Archipelago from the list. | ||
| 22 | 3. 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. | ||
| 27 | 4. Press Play. | ||
| 28 | 5. Enter the Archipelago address, slot name, and password into the fields. | ||
| 29 | 6. Press Connect. | ||
| 30 | 7. Enjoy! | ||
| 31 | |||
| 32 | To continue an earlier game, you can perform the exact same steps as above. You | ||
| 33 | will probably have to re-select Archipelago from the Level Selection screen, as | ||
| 34 | the 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 | ||
| 37 | the base game after playing the randomizer, you need to restart Lingo 2 first. | ||
| 38 | |||
| 39 | ## Running from source | ||
| 40 | |||
| 41 | The mod is mostly written in GDScript, which is parsed and executed by Lingo 2 | ||
| 42 | itself, and thus does not need to be compiled. However, there are two files that | ||
| 43 | need to be generated before the client can be run. | ||
| 44 | |||
| 45 | The first file is `data.binpb`, the datafile containing the randomizer logic. | ||
| 46 | You can read about how to generate it on | ||
| 47 | [its own README page](https://code.fourisland.com/lingo2-archipelago/about/data/README.md). | ||
| 48 | Once you have it, put it in a subfolder of `client` called `generated`. | ||
| 49 | |||
| 50 | The second generated file is `proto.gd`. This file allows Lingo 2 to read the | ||
| 51 | datafile. We use a Godot script to generate it, which means | ||
| 52 | [the Godot Editor](https://godotengine.org/download/) is required. From the root | ||
| 53 | of the repository: | ||
| 54 | |||
| 55 | ```shell | ||
| 56 | cd vendor\godobuf | ||
| 57 | godot --headless -s addons\protobuf\protobuf_cmdln.gd --input=..\..\proto\data.proto ^ | ||
| 58 | --output=..\..\client\Archipelago\generated\proto.gd | ||
| 59 | ``` | ||
| 60 | |||
| 61 | If you are not on Windows, replace the forward slashes with backslashes as | ||
| 62 | appropriate (and the caret with a forward slash). You will also probably need to | ||
| 63 | replace "godot" at the start of the second line with a path to a Godot Editor | ||
| 64 | executable. | ||
| 65 | |||
| 66 | After generating those two files, the contents of the `client` folder (minus | ||
| 67 | this 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 | |||
| 73 | Lingo 2 autosaves your progress every time you solve a puzzle, get a | ||
| 74 | collectable, or interact with a keyholder. The randomizer generates a savefile | ||
| 75 | name based on your Multiworld seed and slot number, so you should be able to | ||
| 76 | seamlessly switch between multiworlds and even slots within a multiworld. | ||
| 77 | |||
| 78 | The exception to this is different rooms created from the same multiworld seed. | ||
| 79 | The client is unable to tell rooms in a seed apart (this is a limitation of the | ||
| 80 | Archipelago API), so the client will use the same save file for the same slot in | ||
| 81 | different rooms on the same seed. You can work around this by manually moving or | ||
| 82 | removing the save file from the level1 save file directory. | ||
| 83 | |||
| 84 | If you play the base game again, you will see one or more save files with a long | ||
| 85 | name that begins with "zzAP\_". These are the saves for your multiworlds. They | ||
| 86 | can be safely deleted after you have completed the associated multiworld. It is | ||
| 87 | not recommended to load these save files outside of the randomizer. | ||
| 88 | |||
| 89 | A connection to Archipelago is required to resume playing a multiworld. This is | ||
| 90 | because 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 | |||
| 40 | offset_bottom = 225.0 | 40 | offset_bottom = 225.0 |
| 41 | text = "ARCHIPELAGO" | 41 | text = "ARCHIPELAGO" |
| 42 | valign = 1 | 42 | valign = 1 |
| 43 | horizontal_alignment = 1 | ||
| 43 | theme = ExtResource("2_g4bvn") | 44 | theme = 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 | |||
| 150 | offset_right = 83.0 | 151 | offset_right = 83.0 |
| 151 | offset_bottom = 58.0 | 152 | offset_bottom = 58.0 |
| 152 | 153 | ||
| 154 | [node name="VersionMismatch" type="ConfirmationDialog" parent="Panel"] | ||
| 155 | offset_right = 83.0 | ||
| 156 | offset_bottom = 58.0 | ||
| 157 | |||
| 153 | [node name="connection_history" type="MenuButton" parent="Panel"] | 158 | [node name="connection_history" type="MenuButton" parent="Panel"] |
| 154 | offset_left = 1239.0 | 159 | offset_left = 1239.0 |
| 155 | offset_top = 276.0 | 160 | offset_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 | |||
| 3 | This folder contains the logic for the Lingo 2 randomizer in a human-readable | ||
| 4 | format. This data is compiled into a single file and used in the various parts | ||
| 5 | of the randomizer project (client, apworld, etc). | ||
| 6 | |||
| 7 | The data is structured using [Protocol Buffers](https://protobuf.dev/). The | ||
| 8 | schema 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 | |||
| 13 | Hi. | ||
| 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 | } | ||
| 313 | connections { | ||
| 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 | } |
| 312 | connections { | 329 | connections { |
| 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. | ||
| 844 | connections { | 863 | connections { |
| 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 | } | ||
| 880 | connections { | ||
| 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 | } |
| 860 | connections { | 898 | connections { |
| 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 | } |
| 1197 | connections { | 1236 | connections { |
| 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 | } |
| 1440 | connections { | 1478 | connections { |
| @@ -1453,6 +1491,7 @@ connections { | |||
| 1453 | } | 1491 | } |
| 1454 | } | 1492 | } |
| 1455 | oneway: true | 1493 | oneway: true |
| 1494 | bypass_target_door: true | ||
| 1456 | } | 1495 | } |
| 1457 | connections { | 1496 | connections { |
| 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 | } |
| 1752 | connections { | 1792 | connections { |
| 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 | } |
| 1832 | connections { | 1872 | connections { |
| 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 | } |
| 1849 | connections { | 1888 | connections { |
| 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 | } |
| 1883 | connections { | 1924 | connections { |
| 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 @@ | |||
| 1 | door_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 | } | ||
| 13 | door_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 | } | ||
| 25 | door_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 | } | ||
| 37 | door_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 | } | ||
| 49 | door_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 | } | ||
| 61 | door_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 | } | ||
| 73 | door_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 | } | ||
| 85 | door_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 | } | ||
| 141 | door_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 |
| 3800 | special: | 3841 | special: |
| 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 | ||
| 3888 | progressives: | ||
| 3889 | Progressive Gold Ending: 2753 | ||
| 3890 | door_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 { | |||
| 30 | keyholders { | 30 | keyholders { |
| 31 | name: "1" | 31 | name: "1" |
| 32 | path: "Components/KeyHolders/keyHolder" | 32 | path: "Components/KeyHolders/keyHolder" |
| 33 | key: "z" | ||
| 33 | } | 34 | } |
| 34 | keyholders { | 35 | keyholders { |
| 35 | name: "2" | 36 | name: "2" |
| 36 | path: "Components/KeyHolders/keyHolder2" | 37 | path: "Components/KeyHolders/keyHolder2" |
| 38 | key: "e" | ||
| 37 | } | 39 | } |
| 38 | keyholders { | 40 | keyholders { |
| 39 | name: "3" | 41 | name: "3" |
| 40 | path: "Components/KeyHolders/keyHolder3" | 42 | path: "Components/KeyHolders/keyHolder3" |
| 43 | key: "r" | ||
| 41 | } | 44 | } |
| 42 | keyholders { | 45 | keyholders { |
| 43 | name: "4" | 46 | name: "4" |
| 44 | path: "Components/KeyHolders/keyHolder4" | 47 | path: "Components/KeyHolders/keyHolder4" |
| 48 | key: "o" | ||
| 45 | } | 49 | } |
| 46 | ports { | 50 | ports { |
| 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 | } |
| 102 | connections { | 102 | connections { |
| 103 | from_room: "Outside House" | ||
| 104 | to_room: "Blue Hallway Tall Side" | ||
| 105 | door { name: "House Side Door" } | ||
| 106 | } | ||
| 107 | connections { | ||
| 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 | } |
| 536 | connections { | 541 | connections { |
| 537 | from_room: "Z2 Room" | 542 | from_room: "Z2 Room" |
| 543 | to_room: "Orange Room Hallway" | ||
| 544 | door { name: "Z2 Room Southeast Door" } | ||
| 545 | } | ||
| 546 | connections { | ||
| 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 | } |
| 1558 | connections { | ||
| 1559 | from_room: "Roof" | ||
| 1560 | to_room: "After Bee Room" | ||
| 1561 | oneway: true | ||
| 1562 | roof_access: true | ||
| 1563 | } | ||
| 1564 | connections { | ||
| 1565 | from_room: "Roof" | ||
| 1566 | to_room: "Amber North 2" | ||
| 1567 | oneway: true | ||
| 1568 | roof_access: true | ||
| 1569 | } | ||
| 1570 | connections { | ||
| 1571 | from_room: "Roof" | ||
| 1572 | to_room: "Black Hex" | ||
| 1573 | oneway: true | ||
| 1574 | roof_access: true | ||
| 1575 | } | ||
| 1576 | connections { | ||
| 1577 | from_room: "Roof" | ||
| 1578 | to_room: "Blue Hallway Tall Side" | ||
| 1579 | oneway: true | ||
| 1580 | roof_access: true | ||
| 1581 | } | ||
| 1582 | connections { | ||
| 1583 | from_room: "Roof" | ||
| 1584 | to_room: "Blue Hallway" | ||
| 1585 | oneway: true | ||
| 1586 | roof_access: true | ||
| 1587 | } | ||
| 1588 | # Blue Hallway Cut Side is inside. | ||
| 1589 | connections { | ||
| 1590 | from_room: "Roof" | ||
| 1591 | to_room: "Eye Painting" | ||
| 1592 | oneway: true | ||
| 1593 | roof_access: true | ||
| 1594 | } | ||
| 1595 | connections { | ||
| 1596 | from_room: "Roof" | ||
| 1597 | to_room: "Globe Room" | ||
| 1598 | oneway: true | ||
| 1599 | roof_access: true | ||
| 1600 | } | ||
| 1601 | connections { | ||
| 1602 | from_room: "Roof" | ||
| 1603 | to_room: "Gray Color Door" | ||
| 1604 | oneway: true | ||
| 1605 | roof_access: true | ||
| 1606 | } | ||
| 1607 | connections { | ||
| 1608 | from_room: "Roof" | ||
| 1609 | to_room: "Green Color Door" | ||
| 1610 | oneway: true | ||
| 1611 | roof_access: true | ||
| 1612 | } | ||
| 1613 | connections { | ||
| 1614 | from_room: "Roof" | ||
| 1615 | to_room: "Green Smiley" | ||
| 1616 | oneway: true | ||
| 1617 | roof_access: true | ||
| 1618 | } | ||
| 1619 | connections { | ||
| 1620 | from_room: "Roof" | ||
| 1621 | to_room: "Hedges" | ||
| 1622 | oneway: true | ||
| 1623 | roof_access: true | ||
| 1624 | } | ||
| 1625 | connections { | ||
| 1626 | from_room: "Roof" | ||
| 1627 | to_room: "Maze Paintings Area" | ||
| 1628 | oneway: true | ||
| 1629 | roof_access: true | ||
| 1630 | } | ||
| 1631 | connections { | ||
| 1632 | from_room: "Roof" | ||
| 1633 | to_room: "Maze" | ||
| 1634 | oneway: true | ||
| 1635 | roof_access: true | ||
| 1636 | } | ||
| 1637 | connections { | ||
| 1638 | from_room: "Roof" | ||
| 1639 | to_room: "North Castle Area" | ||
| 1640 | oneway: true | ||
| 1641 | roof_access: true | ||
| 1642 | } | ||
| 1643 | connections { | ||
| 1644 | from_room: "Roof" | ||
| 1645 | to_room: "Number Paintings Area" | ||
| 1646 | oneway: true | ||
| 1647 | roof_access: true | ||
| 1648 | } | ||
| 1649 | connections { | ||
| 1650 | from_room: "Roof" | ||
| 1651 | to_room: "Orange Room Hallway" | ||
| 1652 | oneway: true | ||
| 1653 | roof_access: true | ||
| 1654 | } | ||
| 1655 | connections { | ||
| 1656 | from_room: "Roof" | ||
| 1657 | to_room: "Outside Book Room" | ||
| 1658 | oneway: true | ||
| 1659 | roof_access: true | ||
| 1660 | } | ||
| 1661 | connections { | ||
| 1662 | from_room: "Roof" | ||
| 1663 | to_room: "Outside Eye Temple" | ||
| 1664 | oneway: true | ||
| 1665 | roof_access: true | ||
| 1666 | } | ||
| 1667 | connections { | ||
| 1668 | from_room: "Roof" | ||
| 1669 | to_room: "Outside Hedges" | ||
| 1670 | oneway: true | ||
| 1671 | roof_access: true | ||
| 1672 | } | ||
| 1673 | connections { | ||
| 1674 | from_room: "Roof" | ||
| 1675 | to_room: "Outside Hotel" | ||
| 1676 | oneway: true | ||
| 1677 | roof_access: true | ||
| 1678 | } | ||
| 1679 | connections { | ||
| 1680 | from_room: "Roof" | ||
| 1681 | to_room: "Outside House" | ||
| 1682 | oneway: true | ||
| 1683 | roof_access: true | ||
| 1684 | } | ||
| 1685 | connections { | ||
| 1686 | from_room: "Roof" | ||
| 1687 | to_room: "Outside Magic Room" | ||
| 1688 | oneway: true | ||
| 1689 | roof_access: true | ||
| 1690 | } | ||
| 1691 | connections { | ||
| 1692 | from_room: "Roof" | ||
| 1693 | to_room: "Outside Orange Room" | ||
| 1694 | oneway: true | ||
| 1695 | roof_access: true | ||
| 1696 | } | ||
| 1697 | connections { | ||
| 1698 | from_room: "Roof" | ||
| 1699 | to_room: "Outside Pyramid" | ||
| 1700 | oneway: true | ||
| 1701 | roof_access: true | ||
| 1702 | } | ||
| 1703 | connections { | ||
| 1704 | from_room: "Roof" | ||
| 1705 | to_room: "Outside Red Room" | ||
| 1706 | oneway: true | ||
| 1707 | roof_access: true | ||
| 1708 | } | ||
| 1709 | connections { | ||
| 1710 | from_room: "Roof" | ||
| 1711 | to_room: "Outside Salt Room" | ||
| 1712 | oneway: true | ||
| 1713 | roof_access: true | ||
| 1714 | } | ||
| 1715 | connections { | ||
| 1716 | from_room: "Roof" | ||
| 1717 | to_room: "Outside Snake Room" | ||
| 1718 | oneway: true | ||
| 1719 | roof_access: true | ||
| 1720 | } | ||
| 1721 | connections { | ||
| 1722 | from_room: "Roof" | ||
| 1723 | to_room: "Post Orange Smiley Three Way" | ||
| 1724 | oneway: true | ||
| 1725 | roof_access: true | ||
| 1726 | } | ||
| 1727 | connections { | ||
| 1728 | from_room: "Roof" | ||
| 1729 | to_room: "Purple NW Vestibule" | ||
| 1730 | oneway: true | ||
| 1731 | roof_access: true | ||
| 1732 | } | ||
| 1733 | connections { | ||
| 1734 | from_room: "Roof" | ||
| 1735 | to_room: "Purple Room East" | ||
| 1736 | oneway: true | ||
| 1737 | roof_access: true | ||
| 1738 | } | ||
| 1739 | connections { | ||
| 1740 | from_room: "Roof" | ||
| 1741 | to_room: "Purple Room South" | ||
| 1742 | oneway: true | ||
| 1743 | roof_access: true | ||
| 1744 | } | ||
| 1745 | connections { | ||
| 1746 | from_room: "Roof" | ||
| 1747 | to_room: "Purple Room West" | ||
| 1748 | oneway: true | ||
| 1749 | roof_access: true | ||
| 1750 | } | ||
| 1751 | connections { | ||
| 1752 | from_room: "Roof" | ||
| 1753 | to_room: "Purple SE Vestibule" | ||
| 1754 | oneway: true | ||
| 1755 | roof_access: true | ||
| 1756 | } | ||
| 1757 | connections { | ||
| 1758 | from_room: "Roof" | ||
| 1759 | to_room: "Pyramid Second Floor" | ||
| 1760 | oneway: true | ||
| 1761 | roof_access: true | ||
| 1762 | } | ||
| 1763 | connections { | ||
| 1764 | from_room: "Roof" | ||
| 1765 | to_room: "Pyramid Top" | ||
| 1766 | oneway: true | ||
| 1767 | roof_access: true | ||
| 1768 | } | ||
| 1769 | connections { | ||
| 1770 | from_room: "Roof" | ||
| 1771 | to_room: "Quiet Entrance" | ||
| 1772 | oneway: true | ||
| 1773 | roof_access: true | ||
| 1774 | } | ||
| 1775 | connections { | ||
| 1776 | from_room: "Roof" | ||
| 1777 | to_room: "Red Color Door" | ||
| 1778 | oneway: true | ||
| 1779 | roof_access: true | ||
| 1780 | } | ||
| 1781 | connections { | ||
| 1782 | from_room: "Roof" | ||
| 1783 | to_room: "South Castle Area" | ||
| 1784 | oneway: true | ||
| 1785 | roof_access: true | ||
| 1786 | } | ||
| 1787 | connections { | ||
| 1788 | from_room: "Roof" | ||
| 1789 | to_room: "Starting Room" | ||
| 1790 | oneway: true | ||
| 1791 | roof_access: true | ||
| 1792 | } | ||
| 1793 | connections { | ||
| 1794 | from_room: "Roof" | ||
| 1795 | to_room: "Sweet Foyer" | ||
| 1796 | oneway: true | ||
| 1797 | roof_access: true | ||
| 1798 | } | ||
| 1799 | connections { | ||
| 1800 | from_room: "Roof" | ||
| 1801 | to_room: "Tree Entrance" | ||
| 1802 | oneway: true | ||
| 1803 | roof_access: true | ||
| 1804 | } | ||
| 1805 | connections { | ||
| 1806 | from_room: "Roof" | ||
| 1807 | to_room: "West Castle Area" | ||
| 1808 | oneway: true | ||
| 1809 | roof_access: true | ||
| 1810 | } | ||
| 1811 | connections { | ||
| 1812 | from_room: "Roof" | ||
| 1813 | to_room: "West Spire" | ||
| 1814 | oneway: true | ||
| 1815 | roof_access: true | ||
| 1816 | } | ||
| 1817 | connections { | ||
| 1818 | from_room: "Roof" | ||
| 1819 | to_room: "Yellow Color Door" | ||
| 1820 | oneway: true | ||
| 1821 | roof_access: true | ||
| 1822 | } | ||
| 1823 | connections { | ||
| 1824 | from_room: "Roof" | ||
| 1825 | to_room: "Z2 Room" | ||
| 1826 | oneway: true | ||
| 1827 | roof_access: true | ||
| 1828 | } | ||
| 1829 | connections { | ||
| 1830 | from_room: "Roof" | ||
| 1831 | to_room: "Zoo Center" | ||
| 1832 | oneway: true | ||
| 1833 | roof_access: true | ||
| 1834 | } | ||
| 1835 | connections { | ||
| 1836 | from_room: "Roof" | ||
| 1837 | to_room: "Zoo E" | ||
| 1838 | oneway: true | ||
| 1839 | roof_access: true | ||
| 1840 | } | ||
| 1841 | connections { | ||
| 1842 | from_room: "Roof" | ||
| 1843 | to_room: "Zoo N" | ||
| 1844 | oneway: true | ||
| 1845 | roof_access: true | ||
| 1846 | } | ||
| 1847 | connections { | ||
| 1848 | from_room: "Roof" | ||
| 1849 | to_room: "Zoo NE" | ||
| 1850 | oneway: true | ||
| 1851 | roof_access: true | ||
| 1852 | } | ||
| 1853 | connections { | ||
| 1854 | from_room: "Roof" | ||
| 1855 | to_room: "Zoo S" | ||
| 1856 | oneway: true | ||
| 1857 | roof_access: true | ||
| 1858 | } | ||
| 1859 | connections { | ||
| 1860 | from_room: "Roof" | ||
| 1861 | to_room: "Zoo SE" | ||
| 1862 | oneway: true | ||
| 1863 | roof_access: true | ||
| 1864 | } | ||
| 1865 | connections { | ||
| 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 | } |
| 196 | doors { | 196 | doors { |
| 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 | } |
| 498 | doors { | 497 | doors { |
| 499 | name: "Purple NW Vestibule" | 498 | name: "Purple NW Vestibule" |
| @@ -892,16 +891,12 @@ doors { | |||
| 892 | } | 891 | } |
| 893 | doors { | 892 | doors { |
| 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 | } |
| 901 | doors { | 898 | doors { |
| 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 | } |
| 1084 | doors { | 1080 | doors { |
| 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 | } |
| 1092 | doors { | 1089 | doors { |
| 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 | } |
| 1278 | doors { | 1242 | doors { |
| 1279 | name: "J2 Door 1" | 1243 | name: "J2 Door 1" |
| @@ -1502,87 +1466,87 @@ doors { | |||
| 1502 | } | 1466 | } |
| 1503 | doors { | 1467 | doors { |
| 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 | } |
| 1510 | doors { | 1474 | doors { |
| 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 | } |
| 1517 | doors { | 1481 | doors { |
| 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 | } |
| 1524 | doors { | 1488 | doors { |
| 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 | } |
| 1531 | doors { | 1495 | doors { |
| 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 | } |
| 1538 | doors { | 1502 | doors { |
| 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 | } |
| 1545 | doors { | 1509 | doors { |
| 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 | } | ||
| 1551 | doors { | ||
| 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 | } |
| 1557 | doors { | 1515 | doors { |
| 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 | } |
| 1563 | doors { | 1521 | doors { |
| 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 | } |
| 1569 | doors { | 1527 | doors { |
| 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 | } |
| 1575 | doors { | 1533 | doors { |
| 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 | } |
| 1581 | doors { | 1539 | doors { |
| 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 | } | ||
| 1546 | doors { | ||
| 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 | } |
| 1588 | doors { | 1552 | doors { |
| @@ -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 | } |
| 1595 | doors { | 1560 | doors { |
| 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 | } |
| 1924 | doors { | 1890 | doors { |
| 1925 | name: "Dark Light Room Divider" | 1891 | name: "Dark Light Room Divider" |
| @@ -2112,8 +2078,12 @@ doors { | |||
| 2112 | doors { | 2078 | doors { |
| 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 | } |
| 2118 | doors { | 2088 | doors { |
| 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 | } |
| 2187 | doors { | 2158 | doors { |
| 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 | } |
| 2270 | doors { | ||
| 2271 | name: "Eye Painting" | ||
| 2272 | type: ITEM_ONLY | ||
| 2273 | receivers: "Components/Paintings/Temple of the Eyes/eyeRedStart/teleportListener" | ||
| 2274 | double_letters: true | ||
| 2275 | } | ||
| 2276 | doors { | ||
| 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 | } | ||
| 2287 | doors { | ||
| 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" | |||
| 3 | keyholders { | 3 | keyholders { |
| 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" | |||
| 3 | keyholders { | 3 | keyholders { |
| 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" | |||
| 3 | keyholders { | 3 | keyholders { |
| 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 { | |||
| 17 | keyholders { | 17 | keyholders { |
| 18 | name: "G" | 18 | name: "G" |
| 19 | path: "Components/KeyHolders/keyHolderG" | 19 | path: "Components/KeyHolders/keyHolderG" |
| 20 | key: "g" | ||
| 20 | } | 21 | } |
| 21 | paintings { | 22 | paintings { |
| 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 @@ | |||
| 1 | name: "Orange Room Hallway" | ||
| 2 | panel_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 { | |||
| 75 | keyholders { | 75 | keyholders { |
| 76 | name: "H" | 76 | name: "H" |
| 77 | path: "Components/KeyHolders/keyHolderH" | 77 | path: "Components/KeyHolders/keyHolderH" |
| 78 | key: "h" | ||
| 78 | } | 79 | } |
| 79 | paintings { | 80 | paintings { |
| 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 @@ | |||
| 1 | name: "Wonderland" | 1 | name: "Wonderland" |
| 2 | panel_display_name: "Northwest Area" | 2 | panel_display_name: "Northwest Area" |
| 3 | # TODO: There's a warp from The Entry into here. | ||
| 4 | panels { | 3 | panels { |
| 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 | } |
| 31 | ports { | 31 | ports { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 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 | } |
| 39 | doors { | 39 | doors { |
| 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 | } |
| 266 | connections { | ||
| 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 | } |
| 9 | ports { | 9 | ports { |
| 10 | name: "TREE" | ||
| 11 | path: "Components/Warps/worldport3" | ||
| 12 | orientation: "north" | ||
| 13 | required_door { name: "Control Center Brown Door" } | ||
| 14 | } | ||
| 15 | ports { | ||
| 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 @@ | |||
| 1 | name: "Tree Entrance" | ||
| 2 | ports { | ||
| 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 | } |
| 120 | doors { | 120 | doors { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 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 | } |
| 36 | connections { | ||
| 37 | from_room: "First Room" | ||
| 38 | to_room: "Cyan Hallway" | ||
| 39 | door { name: "Colorful Entrance" } | ||
| 40 | } | ||
| 41 | connections { | ||
| 42 | from_room: "Second Room" | ||
| 43 | to_room: "Congruent Entrance" | ||
| 44 | door { name: "Congruent Entrance" } | ||
| 45 | } | ||
| 46 | connections { | ||
| 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 @@ | |||
| 2 | doors { | 2 | doors { |
| 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 | } |
| 9 | doors { | 9 | doors { |
| 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 @@ | |||
| 1 | name: "Congruent Entrance" | ||
| 2 | panel_display_name: "Second Room" | ||
| 3 | ports { | ||
| 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 @@ | |||
| 1 | name: "Cyan Hallway" | ||
| 2 | panel_display_name: "First Room" | ||
| 3 | ports { | ||
| 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 @@ | |||
| 1 | name: "Double Sided Entrance" | ||
| 2 | panel_display_name: "First Room" | ||
| 3 | ports { | ||
| 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 | } |
| 45 | ports { | ||
| 46 | name: "COLORFUL" | ||
| 47 | path: "Components/Warps/worldport8" | ||
| 48 | orientation: "north" | ||
| 49 | required_door { name: "Colorful Entrance" } | ||
| 50 | } | ||
| 51 | ports { | ||
| 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 | } |
| 50 | ports { | ||
| 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 { | |||
| 192 | connections { | 192 | connections { |
| 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 | } |
| 197 | connections { | 197 | connections { |
| 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 | } |
| 202 | connections { | ||
| 203 | from_room: "Flipped Second Room" | ||
| 204 | to_room: "Four Rooms Entrance" | ||
| 205 | door { name: "Flipped Second Room Right Door" } | ||
| 206 | } | ||
| 207 | connections { | ||
| 208 | from_room: "Link Area" | ||
| 209 | to_room: "Liberated Entrance" | ||
| 210 | door { name: "Liberated Entrance" } | ||
| 211 | } | ||
| 212 | connections { | ||
| 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 | } |
| 143 | doors { | 145 | doors { |
| 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 | } |
| 195 | doors { | 197 | doors { |
| 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 | } |
| 24 | ports { | ||
| 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 @@ | |||
| 1 | name: "Four Rooms Entrance" | ||
| 2 | ports { | ||
| 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 @@ | |||
| 1 | name: "Liberated Entrance" | ||
| 2 | ports { | ||
| 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 | } |
| 29 | ports { | ||
| 30 | name: "BLUE" | ||
| 31 | path: "worldport8" | ||
| 32 | orientation: "west" | ||
| 33 | required_door { name: "Liberated Entrance" } | ||
| 34 | } | ||
| 35 | ports { | ||
| 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 @@ | |||
| 1 | name: "Literate Entrance" | ||
| 2 | ports { | ||
| 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 | } |
| 29 | panels { | 31 | panels { |
| 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 { | |||
| 23 | keyholders { | 23 | keyholders { |
| 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. |
| 2 | doors { | 2 | doors { |
| 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 | } |
| 28 | doors { | 28 | doors { |
| 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 | } |
| 35 | doors { | 35 | doors { |
| 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 | } |
| 71 | doors { | 71 | doors { |
| 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 | } |
| 78 | doors { | 78 | doors { |
| 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 | } |
| 106 | doors { | 106 | doors { |
| 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 | } |
| 143 | doors { | 143 | doors { |
| 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 | } |
| 150 | doors { | 150 | doors { |
| 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 | } |
| 157 | doors { | 157 | doors { |
| 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 | } |
| 164 | doors { | 164 | doors { |
| 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 | } |
| 171 | doors { | 171 | doors { |
| 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 | } |
| 188 | doors { | 188 | doors { |
| 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 | } |
| 195 | doors { | 195 | doors { |
| 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 | } |
| 202 | doors { | 202 | doors { |
| 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 | } |
| 209 | doors { | 209 | doors { |
| 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 | } |
| 216 | doors { | 216 | doors { |
| 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 | } |
| 223 | doors { | 223 | doors { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 3 | name: "P" | 3 | name: "P" |
| 4 | path: "Components/KeyHolders/keyHolderP" | 4 | path: "Components/KeyHolders/keyHolderP" |
| 5 | key: "p" | ||
| 5 | } | 6 | } |
| 6 | paintings { | 7 | paintings { |
| 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 { | |||
| 19 | doors { | 19 | doors { |
| 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 | } |
| 53 | doors { | 54 | doors { |
| 54 | name: "Control Center Purple Door" | 55 | name: "Control Center Purple Door" |
| @@ -65,7 +66,7 @@ doors { | |||
| 65 | doors { | 66 | doors { |
| 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 | } |
| 71 | doors { | 72 | doors { |
| @@ -470,9 +471,13 @@ doors { | |||
| 470 | } | 471 | } |
| 471 | doors { | 472 | doors { |
| 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 | } |
| 477 | doors { | 482 | doors { |
| 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 | } |
| 505 | doors { | ||
| 506 | name: "Cyan Doors" | ||
| 507 | type: EVENT | ||
| 508 | receivers: "Panels/General/entry_7/teleportListener" | ||
| 509 | double_letters: true | ||
| 510 | } | ||
| 511 | doors { | ||
| 512 | name: "Partial Entrance" | ||
| 513 | type: EVENT | ||
| 514 | panels { room: "West Side" name: "CLUE" } | ||
| 515 | } | ||
| diff --git a/data/maps/the_great/rooms/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 | } |
| 115 | panels { | 116 | panels { |
| 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 { | |||
| 52 | keyholders { | 52 | keyholders { |
| 53 | name: "X" | 53 | name: "X" |
| 54 | path: "Components/KeyHolders/keyHolderX" | 54 | path: "Components/KeyHolders/keyHolderX" |
| 55 | key: "x" | ||
| 55 | } | 56 | } |
| 56 | ports { | 57 | ports { |
| 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 { | |||
| 268 | keyholders { | 268 | keyholders { |
| 269 | name: "B" | 269 | name: "B" |
| 270 | path: "Components/KeyHolders/keyHolderB" | 270 | path: "Components/KeyHolders/keyHolderB" |
| 271 | key: "b" | ||
| 271 | } | 272 | } |
| 272 | ports { | 273 | ports { |
| 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 { | |||
| 38 | keyholders { | 38 | keyholders { |
| 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 { | |||
| 112 | keyholders { | 112 | keyholders { |
| 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 | } |
| 11 | connections { | 11 | connections { |
| 12 | from_room: "R2C2 Bottom" | 12 | from_room: "R2C2 Bottom" |
| 13 | to_room: "R2C2 Top" | ||
| 14 | door { name: "Sky Owl" } | ||
| 15 | } | ||
| 16 | connections { | ||
| 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 | } |
| 16 | connections { | 21 | connections { |
| 22 | from_room: "R2C2 Bottom" | ||
| 23 | to_room: "Connected Area" | ||
| 24 | door { name: "Sky Owl" } | ||
| 25 | } | ||
| 26 | connections { | ||
| 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 | } |
| 46 | connections { | 56 | connections { |
| 47 | from_room: "Connected Area" | 57 | from_room: "Connected Area" |
| 58 | to_room: "R2C3 Bottom" | ||
| 59 | door { name: "Sky Owl" } | ||
| 60 | } | ||
| 61 | connections { | ||
| 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 | } |
| 71 | connections { | 86 | connections { |
| 72 | from_room: "Connected Area" | 87 | from_room: "Connected Area" |
| 88 | to_room: "R1C4 Left" | ||
| 89 | door { name: "Sky Owl" } | ||
| 90 | } | ||
| 91 | connections { | ||
| 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 | } |
| 86 | connections { | 106 | connections { |
| 107 | from_room: "Connected Area" | ||
| 108 | to_room: "R2C1 Left" | ||
| 109 | door { name: "Sky Owl" } | ||
| 110 | } | ||
| 111 | connections { | ||
| 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 { | |||
| 233 | doors { | 233 | doors { |
| 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 | } |
| 242 | doors { | 241 | doors { |
| 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 @@ | |||
| 1 | doors { | 1 | doors { |
| 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 | } |
| 6 | doors { | 13 | doors { |
| 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 | } |
| 11 | doors { | 23 | doors { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 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 | } |
| 110 | paintings { | 111 | paintings { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 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 { | |||
| 6 | connections { | 6 | connections { |
| 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 | } |
| 12 | connections { | 12 | connections { |
| 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 | } |
| 8 | doors { | 8 | doors { |
| 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 | } |
| 197 | doors { | ||
| 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 @@ | |||
| 1 | display_name: "The Repetitive" | 1 | display_name: "The Repetitive" |
| 2 | # The anti-collectable doesn't fit into our system right now so let's ignore it. | ||
| 3 | excluded_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. |
| 5 | excluded_nodes: "Meshes/eyeRed3" | 3 | excluded_nodes: "Meshes/eyeRed3" |
| 6 | excluded_nodes: "Meshes/eyeRed4" | 4 | excluded_nodes: "Meshes/eyeRed4" |
| 7 | # I do not know what this is. | ||
| 8 | excluded_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. |
| 10 | excluded_nodes: "Panels/Eval/panel_26_proxyied_fake" | 6 | excluded_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 @@ | |||
| 1 | name: "Anti Room" | 1 | name: "Anti Room" |
| 2 | # Ignore the collectible. The mod should remove it and the back wall too. | ||
| 3 | panels { | 2 | panels { |
| 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 | } |
| 40 | panels { | 39 | panels { |
| 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 | } |
| 46 | panels { | ||
| 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 { | |||
| 160 | keyholders { | 160 | keyholders { |
| 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 @@ | |||
| 1 | doors { | 1 | doors { |
| 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 | } |
| 8 | doors { | 7 | doors { |
| 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 | } |
| 15 | doors { | 13 | doors { |
| 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 | } |
| 22 | doors { | 19 | doors { |
| 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 | } |
| 29 | doors { | 25 | doors { |
| 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 | } |
| 36 | doors { | 31 | doors { |
| 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 | } |
| 43 | doors { | 37 | doors { |
| 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 | } |
| 50 | doors { | 43 | doors { |
| 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 | } |
| 57 | doors { | 49 | doors { |
| 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 { | |||
| 107 | keyholders { | 107 | keyholders { |
| 108 | name: "Y" | 108 | name: "Y" |
| 109 | path: "Components/KeyHolders/keyHolderY" | 109 | path: "Components/KeyHolders/keyHolderY" |
| 110 | key: "y" | ||
| 110 | } | 111 | } |
| 111 | ports { | 112 | ports { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 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 { | |||
| 21 | doors { | 21 | doors { |
| 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 | } |
| 26 | doors { | 27 | doors { |
| @@ -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 { | |||
| 212 | keyholders { | 212 | keyholders { |
| 213 | name: "I" | 213 | name: "I" |
| 214 | path: "Components/KeyHolders/keyHolderL" | 214 | path: "Components/KeyHolders/keyHolderL" |
| 215 | key: "i" | ||
| 215 | } | 216 | } |
| 216 | ports { | 217 | ports { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 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" | |||
| 2 | keyholders { | 2 | keyholders { |
| 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 { | |||
| 499 | doors { | 499 | doors { |
| 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 @@ | |||
| 1 | version: 4 | ||
| 2 | # Filler item. | ||
| 3 | special_names: "A Job Well Done" | ||
| 4 | # Symbol items. | ||
| 5 | special_names: "Age Symbol" | ||
| 6 | special_names: "Anagram Symbol" | ||
| 7 | special_names: "Boxes Symbol" | ||
| 8 | special_names: "Cross Symbol" | ||
| 9 | special_names: "Eval Symbol" | ||
| 10 | special_names: "Example Symbol" | ||
| 11 | special_names: "Gender Symbol" | ||
| 12 | special_names: "Job Symbol" | ||
| 13 | special_names: "Lingo Symbol" | ||
| 14 | special_names: "Null Symbol" | ||
| 15 | special_names: "Planet Symbol" | ||
| 16 | special_names: "Pyramid Symbol" | ||
| 17 | special_names: "Question Symbol" | ||
| 18 | special_names: "Sound Symbol" | ||
| 19 | special_names: "Sparkles Symbol" | ||
| 20 | special_names: "Stars Symbol" | ||
| 21 | special_names: "Sun Symbol" | ||
| 22 | special_names: "Sweet Symbol" | ||
| 23 | special_names: "Zero Symbol" | ||
| 24 | # Anti collectable traps | ||
| 25 | special_names: "Anti A" | ||
| 26 | special_names: "Anti B" | ||
| 27 | special_names: "Anti C" | ||
| 28 | special_names: "Anti D" | ||
| 29 | special_names: "Anti E" | ||
| 30 | special_names: "Anti F" | ||
| 31 | special_names: "Anti G" | ||
| 32 | special_names: "Anti H" | ||
| 33 | special_names: "Anti I" | ||
| 34 | special_names: "Anti J" | ||
| 35 | special_names: "Anti K" | ||
| 36 | special_names: "Anti L" | ||
| 37 | special_names: "Anti M" | ||
| 38 | special_names: "Anti N" | ||
| 39 | special_names: "Anti O" | ||
| 40 | special_names: "Anti P" | ||
| 41 | special_names: "Anti Q" | ||
| 42 | special_names: "Anti R" | ||
| 43 | special_names: "Anti S" | ||
| 44 | special_names: "Anti T" | ||
| 45 | special_names: "Anti U" | ||
| 46 | special_names: "Anti V" | ||
| 47 | special_names: "Anti W" | ||
| 48 | special_names: "Anti X" | ||
| 49 | special_names: "Anti Y" | ||
| 50 | special_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 @@ | |||
| 1 | progressives { | ||
| 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 | |||
| 35 | enum 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 | ||
| 32 | enum AxisDirection { | 55 | enum 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 | ||
| 89 | message Door { | 114 | message 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 | ||
| 163 | message KeyholderData { | 188 | message 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 | ||
| 171 | message Letter { | 198 | message Letter { |
| @@ -221,7 +248,24 @@ message Map { | |||
| 221 | optional string display_name = 3; | 248 | optional string display_name = 3; |
| 222 | } | 249 | } |
| 223 | 250 | ||
| 251 | message Progressive { | ||
| 252 | optional uint64 id = 1; | ||
| 253 | optional string name = 2; | ||
| 254 | optional uint64 ap_id = 3; | ||
| 255 | repeated uint64 doors = 4; | ||
| 256 | } | ||
| 257 | |||
| 258 | message 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 | |||
| 224 | message AllObjects { | 266 | message 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 | ||
| 67 | message HumanConnections { | 75 | message 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 { | |||
| 142 | message HumanKeyholder { | 153 | message 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 | ||
| 147 | message HumanLetter { | 165 | message HumanLetter { |
| @@ -183,10 +201,35 @@ message HumanMap { | |||
| 183 | repeated string excluded_nodes = 2; | 201 | repeated string excluded_nodes = 2; |
| 184 | } | 202 | } |
| 185 | 203 | ||
| 204 | message HumanProgressive { | ||
| 205 | optional string name = 1; | ||
| 206 | repeated DoorIdentifier doors = 2; | ||
| 207 | } | ||
| 208 | |||
| 209 | message HumanProgressives { | ||
| 210 | repeated HumanProgressive progressives = 1; | ||
| 211 | } | ||
| 212 | |||
| 213 | message HumanDoorGroup { | ||
| 214 | optional string name = 1; | ||
| 215 | optional DoorGroupType type = 2; | ||
| 216 | repeated DoorIdentifier doors = 3; | ||
| 217 | } | ||
| 218 | |||
| 219 | message HumanDoorGroups { | ||
| 220 | repeated HumanDoorGroup door_groups = 1; | ||
| 221 | } | ||
| 222 | |||
| 223 | message HumanGlobalMetadata { | ||
| 224 | repeated string special_names = 1; | ||
| 225 | optional uint64 version = 2; | ||
| 226 | } | ||
| 227 | |||
| 186 | message IdMappings { | 228 | message 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 | ||
| 334 | uint64_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 | |||
| 351 | uint64_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 | |||
| 334 | void Container::AddConnection(const Connection& connection) { | 368 | void 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 | ||
| 10 | namespace com::fourisland::lingo2_archipelago { | 8 | namespace com::fourisland::lingo2_archipelago { |
| @@ -23,7 +21,7 @@ struct Heading { | |||
| 23 | GodotInstanceType instance_type; | 21 | GodotInstanceType instance_type; |
| 24 | }; | 22 | }; |
| 25 | 23 | ||
| 26 | Heading ParseTscnHeading(absl::string_view line) { | 24 | Heading 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 | ||
| 17 | namespace com::fourisland::lingo2_archipelago { | 18 | namespace com::fourisland::lingo2_archipelago { |
| 18 | namespace { | 19 | namespace { |
| @@ -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 | ||
| 40 | struct DoorInfo { | 40 | struct 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 | ||
| 58 | struct PaintingInfo { | 62 | struct 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 | ||
| 72 | struct PanelInfo { | 77 | struct 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 | ||
| 83 | struct KeyholderInfo { | 90 | struct 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 | ||
| 91 | struct LetterInfo { | 99 | struct LetterInfo { |
| 92 | std::vector<RoomIdentifier> defined_in; | 100 | std::vector<RoomIdentifier> defined_in; |
| 101 | bool has_id = false; | ||
| 93 | }; | 102 | }; |
| 94 | 103 | ||
| 95 | struct EndingInfo { | 104 | struct 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 | ||
| 115 | struct ProgressiveInfo { | ||
| 116 | std::vector<HumanProgressive> definitions; | ||
| 117 | bool has_id = false; | ||
| 118 | |||
| 119 | std::vector<DoorIdentifier> malformed_doors; | ||
| 120 | }; | ||
| 121 | |||
| 122 | struct DoorGroupInfo { | ||
| 123 | std::vector<HumanDoorGroup> definitions; | ||
| 124 | bool has_id = false; | ||
| 125 | |||
| 126 | std::vector<DoorIdentifier> malformed_doors; | ||
| 127 | }; | ||
| 128 | |||
| 105 | struct CollectedInfo { | 129 | struct 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 @@ | |||
| 1 | BSD 3-Clause License | ||
| 2 | |||
| 3 | Copyright (c) 2018, oniksan | ||
| 4 | All rights reserved. | ||
| 5 | |||
| 6 | Redistribution and use in source and binary forms, with or without | ||
| 7 | modification, 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 | |||
| 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
| 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
| 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
| 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
| 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
| 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
| 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| 29 | OF 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 @@ | |||
| 1 | This is a fork of https://github.com/oniksan/godobuf with some minor changes so | ||
| 2 | that it is able to compile the Lingo 2 randomizer proto files. The plugin parts | ||
| 3 | of the project have also been removed since we only need the command line | ||
| 4 | script. | ||
| 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 | |||
| 32 | extends Node | ||
| 33 | |||
| 34 | const PROTO_VERSION_CONST : String = "const PROTO_VERSION = " | ||
| 35 | const PROTO_VERSION_DEFAULT : String = PROTO_VERSION_CONST + "0" | ||
| 36 | |||
| 37 | class 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 | |||
| 46 | class 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 | |||
| 53 | class 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 | |||
| 93 | class 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 | |||
| 117 | class 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 | |||
| 1419 | class 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 | |||
| 1618 | class 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 | |||
| 2115 | class 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 | |||
| 2125 | func 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 | |||
| 2158 | func 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 | |||
| 2182 | func 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 | |||
| 2197 | func 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 | |||
| 2213 | func 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 | |||
| 2230 | func 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 | |||
| 2253 | func _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 | |||
| 3 | name="Godobuf" | ||
| 4 | description="Google Protobuf implementation for Godot/GDScript" | ||
| 5 | author="oniksan" | ||
| 6 | version="0.6.1 for Godot 4.x.y" | ||
| 7 | script="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 | |||
| 32 | extends SceneTree | ||
| 33 | |||
| 34 | var Parser = preload("res://addons/protobuf/parser.gd") | ||
| 35 | var Util = preload("res://addons/protobuf/protobuf_util.gd") | ||
| 36 | |||
| 37 | func error(msg : String): | ||
| 38 | push_error(msg) | ||
| 39 | quit() | ||
| 40 | |||
| 41 | func _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 | |||
| 34 | const PROTO_VERSION = 0 | ||
| 35 | |||
| 36 | const DEBUG_TAB : String = " " | ||
| 37 | |||
| 38 | enum 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 | |||
| 51 | enum 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 | |||
| 72 | const 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 | |||
| 93 | const 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 | |||
| 114 | enum 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 | |||
| 124 | enum PB_RULE { | ||
| 125 | OPTIONAL = 0, | ||
| 126 | REQUIRED = 1, | ||
| 127 | REPEATED = 2, | ||
| 128 | RESERVED = 3 | ||
| 129 | } | ||
| 130 | |||
| 131 | enum PB_SERVICE_STATE { | ||
| 132 | FILLED = 0, | ||
| 133 | UNFILLED = 1 | ||
| 134 | } | ||
| 135 | |||
| 136 | class 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 | |||
| 154 | class PBTypeTag: | ||
| 155 | var ok : bool = false | ||
| 156 | var type : int | ||
| 157 | var tag : int | ||
| 158 | var offset : int | ||
| 159 | |||
| 160 | class PBServiceField: | ||
| 161 | var field : PBField | ||
| 162 | var func_ref = null | ||
| 163 | var state : int = PB_SERVICE_STATE.UNFILLED | ||
| 164 | |||
| 165 | class 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 | |||
| 32 | static 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 | |||
| 44 | static 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] | ||
| 6 | background_mode = 2 | ||
| 7 | background_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 | |||
| 3 | importer="texture" | ||
| 4 | type="StreamTexture" | ||
| 5 | path="res://.import/logo.png-cca8726399059c8d4f806e28e356b14d.stex" | ||
| 6 | metadata={ | ||
| 7 | "vram_texture": false | ||
| 8 | } | ||
| 9 | |||
| 10 | [deps] | ||
| 11 | |||
| 12 | source_file="res://logo.png" | ||
| 13 | dest_files=[ "res://.import/logo.png-cca8726399059c8d4f806e28e356b14d.stex" ] | ||
| 14 | |||
| 15 | [params] | ||
| 16 | |||
| 17 | compress/mode=0 | ||
| 18 | compress/lossy_quality=0.7 | ||
| 19 | compress/hdr_mode=0 | ||
| 20 | compress/bptc_ldr=0 | ||
| 21 | compress/normal_map=0 | ||
| 22 | flags/repeat=0 | ||
| 23 | flags/filter=true | ||
| 24 | flags/mipmaps=false | ||
| 25 | flags/anisotropic=false | ||
| 26 | flags/srgb=2 | ||
| 27 | process/fix_alpha_border=true | ||
| 28 | process/premult_alpha=false | ||
| 29 | process/HDR_as_SRGB=false | ||
| 30 | process/invert_color=false | ||
| 31 | process/normal_map_invert_y=false | ||
| 32 | stream=false | ||
| 33 | size_limit=0 | ||
| 34 | detect_3d=true | ||
| 35 | svg/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 | |||
| 9 | config_version=4 | ||
| 10 | |||
| 11 | _global_script_classes=[ ] | ||
| 12 | _global_script_class_icons={ | ||
| 13 | } | ||
| 14 | |||
| 15 | [application] | ||
| 16 | |||
| 17 | config/name="Protobuf Plugin" | ||
| 18 | config/icon="res://logo.png" | ||
| 19 | |||
| 20 | [editor_plugins] | ||
| 21 | |||
| 22 | enabled=PoolStringArray( "res://addons/protobuf/plugin.cfg" ) | ||
| 23 | |||
| 24 | [rendering] | ||
| 25 | |||
| 26 | environment/default_environment="res://default_env.tres" | ||
