diff options
| -rw-r--r-- | CHANGELOG.md | 59 | ||||
| -rw-r--r-- | apworld/__init__.py | 3 | ||||
| -rw-r--r-- | apworld/client/manager.gd | 13 | ||||
| -rw-r--r-- | apworld/client/maps/control_center.gd | 35 | ||||
| -rw-r--r-- | apworld/client/player.gd | 11 | ||||
| -rw-r--r-- | apworld/options.py | 20 | ||||
| -rw-r--r-- | apworld/player_logic.py | 8 | ||||
| -rw-r--r-- | apworld/regions.py | 4 | ||||
| -rw-r--r-- | apworld/static_logic.py | 2 | ||||
| -rw-r--r-- | data/maps/control_center/connections.txtpb | 1 | ||||
| -rw-r--r-- | data/metadata.txtpb | 2 | ||||
| -rw-r--r-- | proto/data.proto | 1 | ||||
| -rw-r--r-- | proto/human.proto | 4 | ||||
| -rw-r--r-- | tools/datapacker/main.cpp | 5 |
14 files changed, 165 insertions, 3 deletions
| diff --git a/CHANGELOG.md b/CHANGELOG.md index 3740ed5..d6ca532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
| @@ -1,5 +1,64 @@ | |||
| 1 | # lingo2-archipelago Releases | 1 | # lingo2-archipelago Releases |
| 2 | 2 | ||
| 3 | ## v9.1.0 - 2026-02-08 | ||
| 4 | |||
| 5 | - Added "Custom Mint Ending" option. This creates a panel in the Control Center | ||
| 6 | after putting EXIT into the keyholders, the answer to which is the value of | ||
| 7 | the option. The player must solve the panel to spawn Mint Ending. | ||
| 8 | - Added "Shuffle Music" option. This randomizes the music track that plays on | ||
| 9 | each map. | ||
| 10 | - Added an item group for symbols. | ||
| 11 | - Fix the message on the Control Center wall overcounting the number of endings | ||
| 12 | by 1. | ||
| 13 | |||
| 14 | Compatibility notes: | ||
| 15 | |||
| 16 | - This client should be completely compatible with worlds generated on v9.0.0. | ||
| 17 | |||
| 18 | Download: | ||
| 19 | [lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v9.1.0/lingo2.apworld)<br/> | ||
| 20 | Template YAML: | ||
| 21 | [Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v9.1.0/Lingo%202.yaml)<br/> | ||
| 22 | Source: [v9.1.0](https://code.fourisland.com/lingo2-archipelago/tag/?h=v9.1.0) | ||
| 23 | |||
| 24 | ## v9.0.0 - 2026-02-07 | ||
| 25 | |||
| 26 | - Added "Shuffle Fast Travel" option. This allows you to randomize the | ||
| 27 | destinations of the fast travel buttons on the pause menu to almost any map | ||
| 28 | (except for The Entry, which is always the center button). | ||
| 29 | - Added "Fast Travel Access" option. By default, fast travel is unlocked by | ||
| 30 | entering the destination area manually. You can instead set to have all fast | ||
| 31 | travel destinations unlocked from the start, or have them locked behind items | ||
| 32 | (apart from The Entry). | ||
| 33 | - Added "Restrict Letter Placements" option. When enabled, letter items will | ||
| 34 | only be placed in your local world, in letter locations. This only has an | ||
| 35 | effect when Shuffle Letters is set to Item Cyan or Progressive. This is | ||
| 36 | experimental, and may slow down generation. | ||
| 37 | - The values of the Endings Requirement and Masteries Requirement options are | ||
| 38 | now shown in the Control Center, as well as how many endings and masteries you | ||
| 39 | currently have. | ||
| 40 | - Previously, vanilla doors logic expected you to have to solve every panel in | ||
| 41 | the Daedalus Computer Room in order to use the back exit door. This has been | ||
| 42 | fixed to only require solving one panel. | ||
| 43 | - Previously, shuffled doors + shuffled worldports logic expected you to be able | ||
| 44 | to enter Control Center from the Perceptive Entrance without the door item by | ||
| 45 | solving the PART panel (as you would in vanilla doors). This has been fixed so | ||
| 46 | that the door item is required in both directions. | ||
| 47 | |||
| 48 | Compatibility notes: | ||
| 49 | |||
| 50 | - The change to the door in the Control Center required creating a new item with | ||
| 51 | a new ID, which is not present in worlds generated on older versions of the | ||
| 52 | apworld. Therefore, if you have an older world using door shuffle, you may not | ||
| 53 | be able to access the checks in the Perceptive Entrance or the worldport (if | ||
| 54 | worldports are shuffled). | ||
| 55 | |||
| 56 | Download: | ||
| 57 | [lingo2.apworld](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v9.0.0/lingo2.apworld)<br/> | ||
| 58 | Template YAML: | ||
| 59 | [Lingo 2.yaml](https://files.fourisland.com/releases/lingo2-archipelago/apworld/v9.0.0/Lingo%202.yaml)<br/> | ||
| 60 | Source: [v9.0.0](https://code.fourisland.com/lingo2-archipelago/tag/?h=v9.0.0) | ||
| 61 | |||
| 3 | ## v8.1.1 - 2026-02-05 | 62 | ## v8.1.1 - 2026-02-05 |
| 4 | 63 | ||
| 5 | - Fixed issue in Daedalus Only mode where the Lavender Cubes and Rainbow Rooms | 64 | - Fixed issue in Daedalus Only mode where the Lavender Cubes and Rainbow Rooms |
| diff --git a/apworld/__init__.py b/apworld/__init__.py index ba5d7ea..6b5338e 100644 --- a/apworld/__init__.py +++ b/apworld/__init__.py | |||
| @@ -157,6 +157,7 @@ class Lingo2World(World): | |||
| 157 | "shuffle_doors", | 157 | "shuffle_doors", |
| 158 | "shuffle_gallery_paintings", | 158 | "shuffle_gallery_paintings", |
| 159 | "shuffle_letters", | 159 | "shuffle_letters", |
| 160 | "shuffle_music", | ||
| 160 | "shuffle_symbols", | 161 | "shuffle_symbols", |
| 161 | "shuffle_worldports", | 162 | "shuffle_worldports", |
| 162 | "strict_cyan_ending", | 163 | "strict_cyan_ending", |
| @@ -166,7 +167,9 @@ class Lingo2World(World): | |||
| 166 | 167 | ||
| 167 | slot_data: dict[str, object] = { | 168 | slot_data: dict[str, object] = { |
| 168 | **self.options.as_dict(*slot_options), | 169 | **self.options.as_dict(*slot_options), |
| 170 | "custom_mint_ending": self.player_logic.custom_mint_ending or "", | ||
| 169 | "rte": [self.static_logic.objects.maps[map_id].name for map_id in self.player_logic.rte_mapping], | 171 | "rte": [self.static_logic.objects.maps[map_id].name for map_id in self.player_logic.rte_mapping], |
| 172 | "seed": self.random.randint(0, 1000000), | ||
| 170 | "version": self.static_logic.get_data_version(), | 173 | "version": self.static_logic.get_data_version(), |
| 171 | } | 174 | } |
| 172 | 175 | ||
| diff --git a/apworld/client/manager.gd b/apworld/client/manager.gd index 00f03ea..f10a0b7 100644 --- a/apworld/client/manager.gd +++ b/apworld/client/manager.gd | |||
| @@ -67,6 +67,7 @@ const kEndingNameByVictoryValue = { | |||
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | var apworld_version = [0, 0, 0] | 69 | var apworld_version = [0, 0, 0] |
| 70 | var custom_mint_ending = "" | ||
| 70 | var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 | 71 | var cyan_door_behavior = kCYAN_DOOR_BEHAVIOR_H2 |
| 71 | var daedalus_only = false | 72 | var daedalus_only = false |
| 72 | var daedalus_roof_access = false | 73 | var daedalus_roof_access = false |
| @@ -76,6 +77,7 @@ var endings_requirement = 0 | |||
| 76 | var fast_travel_access = 0 | 77 | var fast_travel_access = 0 |
| 77 | var keyholder_sanity = false | 78 | var keyholder_sanity = false |
| 78 | var masteries_requirement = 0 | 79 | var masteries_requirement = 0 |
| 80 | var music_mapping = {} | ||
| 79 | var port_pairings = {} | 81 | var port_pairings = {} |
| 80 | var rte_mapping = [] | 82 | var rte_mapping = [] |
| 81 | var shuffle_control_center_colors = false | 83 | var shuffle_control_center_colors = false |
| @@ -84,6 +86,7 @@ var shuffle_gallery_paintings = false | |||
| 84 | var shuffle_letters = kSHUFFLE_LETTERS_VANILLA | 86 | var shuffle_letters = kSHUFFLE_LETTERS_VANILLA |
| 85 | var shuffle_symbols = false | 87 | var shuffle_symbols = false |
| 86 | var shuffle_worldports = false | 88 | var shuffle_worldports = false |
| 89 | var slot_rng = null | ||
| 87 | var strict_cyan_ending = false | 90 | var strict_cyan_ending = false |
| 88 | var strict_purple_ending = false | 91 | var strict_purple_ending = false |
| 89 | var victory_condition = -1 | 92 | var victory_condition = -1 |
| @@ -476,6 +479,7 @@ func _client_connected(slot_data): | |||
| 476 | _last_new_item = localdata[0] | 479 | _last_new_item = localdata[0] |
| 477 | 480 | ||
| 478 | # Read slot data. | 481 | # Read slot data. |
| 482 | custom_mint_ending = slot_data.get("custom_mint_ending", "") | ||
| 479 | cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0)) | 483 | cyan_door_behavior = int(slot_data.get("cyan_door_behavior", 0)) |
| 480 | daedalus_only = bool(slot_data.get("daedalus_only", false)) | 484 | daedalus_only = bool(slot_data.get("daedalus_only", false)) |
| 481 | daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) | 485 | daedalus_roof_access = bool(slot_data.get("daedalus_roof_access", false)) |
| @@ -514,6 +518,15 @@ func _client_connected(slot_data): | |||
| 514 | if slot_data.has("rte"): | 518 | if slot_data.has("rte"): |
| 515 | rte_mapping = slot_data.get("rte") | 519 | rte_mapping = slot_data.get("rte") |
| 516 | 520 | ||
| 521 | slot_rng = RandomNumberGenerator.new() | ||
| 522 | slot_rng.seed = int(slot_data.get("seed", 0)) | ||
| 523 | |||
| 524 | music_mapping.clear() | ||
| 525 | if bool(slot_data.get("shuffle_music", false)): | ||
| 526 | for map_name in global.reserved_scenes: | ||
| 527 | var track_index = slot_rng.randi_range(0, musicPlayer.all_tracks.size() - 1) | ||
| 528 | music_mapping[map_name] = musicPlayer.all_tracks.keys()[track_index] | ||
| 529 | |||
| 517 | # Set up item locks. | 530 | # Set up item locks. |
| 518 | _item_locks = {} | 531 | _item_locks = {} |
| 519 | 532 | ||
| diff --git a/apworld/client/maps/control_center.gd b/apworld/client/maps/control_center.gd index 92999d3..8e919ab 100644 --- a/apworld/client/maps/control_center.gd +++ b/apworld/client/maps/control_center.gd | |||
| @@ -79,7 +79,7 @@ func on_map_load(root): | |||
| 79 | var mastery_count = 0 | 79 | var mastery_count = 0 |
| 80 | for key in unlocks.data: | 80 | for key in unlocks.data: |
| 81 | if unlocks.data[key] == "unlocked": | 81 | if unlocks.data[key] == "unlocked": |
| 82 | if key.ends_with("_ending"): | 82 | if key.ends_with("_ending") and key != "free_ending": |
| 83 | ending_count += 1 | 83 | ending_count += 1 |
| 84 | elif key.ends_with("_mastery"): | 84 | elif key.ends_with("_mastery"): |
| 85 | mastery_count += 1 | 85 | mastery_count += 1 |
| @@ -96,6 +96,39 @@ func on_map_load(root): | |||
| 96 | sign2.text = "Masteries: %d/%d" % [mastery_count, ap.masteries_requirement] | 96 | sign2.text = "Masteries: %d/%d" % [mastery_count, ap.masteries_requirement] |
| 97 | root.get_node("/root/scene").add_child.call_deferred(sign2) | 97 | root.get_node("/root/scene").add_child.call_deferred(sign2) |
| 98 | 98 | ||
| 99 | # Handle custom Mint Ending. | ||
| 100 | if ap.custom_mint_ending != "": | ||
| 101 | var panel_prefab = preload("res://objects/nodes/panel.tscn") | ||
| 102 | var tpl_prefab = preload("res://objects/nodes/listeners/teleportListener.tscn") | ||
| 103 | |||
| 104 | var mint_ending = root.get_node("/root/scene/Components/Endings/mint_ending") | ||
| 105 | |||
| 106 | var mint_panel = panel_prefab.instantiate() | ||
| 107 | mint_panel.name = "mint_panel" | ||
| 108 | mint_panel.clue = ap.custom_mint_ending | ||
| 109 | mint_panel.symbol = "" | ||
| 110 | mint_panel.answer = ap.custom_mint_ending | ||
| 111 | mint_panel.position = Vector3(-63, 3, -29) | ||
| 112 | mint_panel.rotation_degrees = Vector3(-45, 90, 0) | ||
| 113 | root.get_node("/root/scene").add_child.call_deferred(mint_panel) | ||
| 114 | |||
| 115 | var mint_tpl = tpl_prefab.instantiate() | ||
| 116 | mint_tpl.name = "mint_tpl" | ||
| 117 | mint_tpl.teleport_point = mint_ending.position | ||
| 118 | mint_tpl.teleport_rotate = mint_ending.rotation_degrees | ||
| 119 | mint_tpl.target_path = mint_ending | ||
| 120 | mint_tpl.senders.append(NodePath("/root/scene/mint_panel")) | ||
| 121 | root.get_node("/root/scene").add_child.call_deferred(mint_tpl) | ||
| 122 | |||
| 123 | var mint_tpl2 = tpl_prefab.instantiate() | ||
| 124 | mint_tpl2.name = "mint_tpl2" | ||
| 125 | mint_tpl2.teleport_point = Vector3(0, -1000, 0) | ||
| 126 | mint_tpl2.target_path = mint_panel | ||
| 127 | mint_tpl2.senders.append(NodePath("/root/scene/mint_panel")) | ||
| 128 | root.get_node("/root/scene").add_child.call_deferred(mint_tpl2) | ||
| 129 | |||
| 130 | mint_ending.position.y = -1000 | ||
| 131 | |||
| 99 | 132 | ||
| 100 | func _set_up_mastery_listener(root, name): | 133 | func _set_up_mastery_listener(root, name): |
| 101 | var prefab = preload("res://objects/nodes/listeners/unlockReaderListener.tscn") | 134 | var prefab = preload("res://objects/nodes/listeners/unlockReaderListener.tscn") |
| diff --git a/apworld/client/player.gd b/apworld/client/player.gd index 95c05d7..dabc15d 100644 --- a/apworld/client/player.gd +++ b/apworld/client/player.gd | |||
| @@ -196,6 +196,17 @@ func _ready(): | |||
| 196 | minimap.visible = ap.show_minimap | 196 | minimap.visible = ap.show_minimap |
| 197 | get_parent().add_child.call_deferred(minimap) | 197 | get_parent().add_child.call_deferred(minimap) |
| 198 | 198 | ||
| 199 | if ap.music_mapping.has(global.map): | ||
| 200 | var song_setter = get_node_or_null("/root/scene/songSetter") | ||
| 201 | if song_setter: | ||
| 202 | song_setter.song_name = ap.music_mapping[global.map] | ||
| 203 | else: | ||
| 204 | var song_setter_prefab = preload("res://objects/nodes/songSetter.tscn") | ||
| 205 | song_setter = song_setter_prefab.instantiate() | ||
| 206 | song_setter.name = "songSetter" | ||
| 207 | song_setter.song_name = ap.music_mapping[global.map] | ||
| 208 | get_parent().add_child.call_deferred(song_setter) | ||
| 209 | |||
| 199 | super._ready() | 210 | super._ready() |
| 200 | 211 | ||
| 201 | await get_tree().process_frame | 212 | await get_tree().process_frame |
| diff --git a/apworld/options.py b/apworld/options.py index 6fe6d8d..c1eab33 100644 --- a/apworld/options.py +++ b/apworld/options.py | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | from dataclasses import dataclass | 1 | from dataclasses import dataclass |
| 2 | 2 | ||
| 3 | from Options import PerGameCommonOptions, Toggle, Choice, DefaultOnToggle, Range, OptionSet | 3 | from Options import PerGameCommonOptions, Toggle, Choice, DefaultOnToggle, Range, OptionSet, FreeText |
| 4 | 4 | ||
| 5 | 5 | ||
| 6 | class ShuffleDoors(DefaultOnToggle): | 6 | class ShuffleDoors(DefaultOnToggle): |
| @@ -178,6 +178,15 @@ class DaedalusRoofAccess(Toggle): | |||
| 178 | display_name = "Allow Daedalus Roof Access" | 178 | display_name = "Allow Daedalus Roof Access" |
| 179 | 179 | ||
| 180 | 180 | ||
| 181 | class CustomMintEnding(FreeText): | ||
| 182 | """ | ||
| 183 | If not blank, this will add a new panel that must be solved before collecting Mint Ending (EXIT in the Control | ||
| 184 | Center). The panel will only require typing the text provided for this option, which means the choice of letters | ||
| 185 | here has an impact on logic. | ||
| 186 | """ | ||
| 187 | display_name = "Custom Mint Ending" | ||
| 188 | |||
| 189 | |||
| 181 | class StrictPurpleEnding(DefaultOnToggle): | 190 | class StrictPurpleEnding(DefaultOnToggle): |
| 182 | """ | 191 | """ |
| 183 | If enabled, the player will be required to have all purple (level 1) letters in order to get Purple Ending. | 192 | If enabled, the player will be required to have all purple (level 1) letters in order to get Purple Ending. |
| @@ -256,6 +265,13 @@ class TrapPercentage(Range): | |||
| 256 | default = 0 | 265 | default = 0 |
| 257 | 266 | ||
| 258 | 267 | ||
| 268 | class ShuffleMusic(Toggle): | ||
| 269 | """ | ||
| 270 | If enabled, every map will be assigned a random music track. | ||
| 271 | """ | ||
| 272 | display_name = "Shuffle Music" | ||
| 273 | |||
| 274 | |||
| 259 | @dataclass | 275 | @dataclass |
| 260 | class Lingo2Options(PerGameCommonOptions): | 276 | class Lingo2Options(PerGameCommonOptions): |
| 261 | shuffle_doors: ShuffleDoors | 277 | shuffle_doors: ShuffleDoors |
| @@ -273,9 +289,11 @@ class Lingo2Options(PerGameCommonOptions): | |||
| 273 | enable_gift_maps: EnableGiftMaps | 289 | enable_gift_maps: EnableGiftMaps |
| 274 | daedalus_only: DaedalusOnly | 290 | daedalus_only: DaedalusOnly |
| 275 | daedalus_roof_access: DaedalusRoofAccess | 291 | daedalus_roof_access: DaedalusRoofAccess |
| 292 | custom_mint_ending: CustomMintEnding | ||
| 276 | strict_purple_ending: StrictPurpleEnding | 293 | strict_purple_ending: StrictPurpleEnding |
| 277 | strict_cyan_ending: StrictCyanEnding | 294 | strict_cyan_ending: StrictCyanEnding |
| 278 | victory_condition: VictoryCondition | 295 | victory_condition: VictoryCondition |
| 279 | endings_requirement: EndingsRequirement | 296 | endings_requirement: EndingsRequirement |
| 280 | masteries_requirement: MasteriesRequirement | 297 | masteries_requirement: MasteriesRequirement |
| 281 | trap_percentage: TrapPercentage | 298 | trap_percentage: TrapPercentage |
| 299 | shuffle_music: ShuffleMusic | ||
| diff --git a/apworld/player_logic.py b/apworld/player_logic.py index 7bfd49f..ea74266 100644 --- a/apworld/player_logic.py +++ b/apworld/player_logic.py | |||
| @@ -223,6 +223,7 @@ class Lingo2PlayerLogic: | |||
| 223 | double_letter_amount: dict[str, int] | 223 | double_letter_amount: dict[str, int] |
| 224 | goal_room_id: int | 224 | goal_room_id: int |
| 225 | rte_mapping: list[int] | 225 | rte_mapping: list[int] |
| 226 | custom_mint_ending: str | None | ||
| 226 | 227 | ||
| 227 | def __init__(self, world: "Lingo2World"): | 228 | def __init__(self, world: "Lingo2World"): |
| 228 | self.world = world | 229 | self.world = world |
| @@ -237,6 +238,7 @@ class Lingo2PlayerLogic: | |||
| 237 | self.real_items = list() | 238 | self.real_items = list() |
| 238 | self.starting_items = list() | 239 | self.starting_items = list() |
| 239 | self.double_letter_amount = dict() | 240 | self.double_letter_amount = dict() |
| 241 | self.custom_mint_ending = None | ||
| 240 | 242 | ||
| 241 | def should_shuffle_map(game_map) -> bool | set[int]: | 243 | def should_shuffle_map(game_map) -> bool | set[int]: |
| 242 | if world.options.daedalus_only: | 244 | if world.options.daedalus_only: |
| @@ -302,6 +304,12 @@ class Lingo2PlayerLogic: | |||
| 302 | raise OptionError(f"When Restrict Letter Placements is enabled and Shuffle Letters is set to Progressive, " | 304 | raise OptionError(f"When Restrict Letter Placements is enabled and Shuffle Letters is set to Progressive, " |
| 303 | f"both Shuffle Doors and Shuffle Symbols must be disabled (Player {world.player}).") | 305 | f"both Shuffle Doors and Shuffle Symbols must be disabled (Player {world.player}).") |
| 304 | 306 | ||
| 307 | if world.options.custom_mint_ending.value != "": | ||
| 308 | self.custom_mint_ending = ''.join(filter(str.isalpha, world.options.custom_mint_ending.value)).lower() | ||
| 309 | |||
| 310 | if len(self.custom_mint_ending) > 52: | ||
| 311 | raise OptionError(f"Custom Mint Ending should not be greater than 52 letters (Player {world.player}).") | ||
| 312 | |||
| 305 | maximum_masteries = 13 + len(world.options.enable_gift_maps.value) | 313 | maximum_masteries = 13 + len(world.options.enable_gift_maps.value) |
| 306 | if world.options.enable_icarus: | 314 | if world.options.enable_icarus: |
| 307 | maximum_masteries += 1 | 315 | maximum_masteries += 1 |
| diff --git a/apworld/regions.py b/apworld/regions.py index 076c143..3996153 100644 --- a/apworld/regions.py +++ b/apworld/regions.py | |||
| @@ -141,6 +141,10 @@ def create_regions(world: "Lingo2World"): | |||
| 141 | if connection.HasField("cyan_ending") and connection.cyan_ending and world.options.strict_cyan_ending: | 141 | if connection.HasField("cyan_ending") and connection.cyan_ending and world.options.strict_cyan_ending: |
| 142 | world.player_logic.add_solution_reqs(reqs, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz") | 142 | world.player_logic.add_solution_reqs(reqs, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz") |
| 143 | 143 | ||
| 144 | if (connection.HasField("mint_ending") and connection.mint_ending | ||
| 145 | and world.player_logic.custom_mint_ending is not None): | ||
| 146 | world.player_logic.add_solution_reqs(reqs, world.player_logic.custom_mint_ending) | ||
| 147 | |||
| 144 | reqs.simplify() | 148 | reqs.simplify() |
| 145 | reqs.remove_room(from_region) | 149 | reqs.remove_room(from_region) |
| 146 | 150 | ||
| diff --git a/apworld/static_logic.py b/apworld/static_logic.py index 672ae5a..48ad78e 100644 --- a/apworld/static_logic.py +++ b/apworld/static_logic.py | |||
| @@ -75,8 +75,10 @@ class Lingo2StaticLogic: | |||
| 75 | self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" | 75 | self.item_id_to_name[self.objects.special_ids["A Job Well Done"]] = "A Job Well Done" |
| 76 | self.item_id_to_name[self.objects.special_ids["Numbers"]] = "Numbers" | 76 | self.item_id_to_name[self.objects.special_ids["Numbers"]] = "Numbers" |
| 77 | 77 | ||
| 78 | self.item_name_groups["Symbols"] = [] | ||
| 78 | for symbol_name in SYMBOL_ITEMS.values(): | 79 | for symbol_name in SYMBOL_ITEMS.values(): |
| 79 | self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name | 80 | self.item_id_to_name[self.objects.special_ids[symbol_name]] = symbol_name |
| 81 | self.item_name_groups["Symbols"].append(symbol_name) | ||
| 80 | 82 | ||
| 81 | for trap_name in ANTI_COLLECTABLE_TRAPS: | 83 | for trap_name in ANTI_COLLECTABLE_TRAPS: |
| 82 | self.item_id_to_name[self.objects.special_ids[trap_name]] = trap_name | 84 | self.item_id_to_name[self.objects.special_ids[trap_name]] = trap_name |
| diff --git a/data/maps/control_center/connections.txtpb b/data/maps/control_center/connections.txtpb index 432d39d..5dc2890 100644 --- a/data/maps/control_center/connections.txtpb +++ b/data/maps/control_center/connections.txtpb | |||
| @@ -17,6 +17,7 @@ connections { | |||
| 17 | from_room: "Main Area" | 17 | from_room: "Main Area" |
| 18 | to_room: "Mint Ending" | 18 | to_room: "Mint Ending" |
| 19 | door { name: "Mint Ending Door" } | 19 | door { name: "Mint Ending Door" } |
| 20 | mint_ending: true | ||
| 20 | } | 21 | } |
| 21 | connections { | 22 | connections { |
| 22 | from_room: "Main Area" | 23 | from_room: "Main Area" |
| diff --git a/data/metadata.txtpb b/data/metadata.txtpb index c362be0..99d3021 100644 --- a/data/metadata.txtpb +++ b/data/metadata.txtpb | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | version { | 1 | version { |
| 2 | major: 9 | 2 | major: 9 |
| 3 | minor: 0 | 3 | minor: 1 |
| 4 | patch: 0 | 4 | patch: 0 |
| 5 | } | 5 | } |
| 6 | # Filler item. | 6 | # Filler item. |
| diff --git a/proto/data.proto b/proto/data.proto index e053942..619b3d3 100644 --- a/proto/data.proto +++ b/proto/data.proto | |||
| @@ -136,6 +136,7 @@ message Connection { | |||
| 136 | optional bool roof_access = 7; | 136 | optional bool roof_access = 7; |
| 137 | optional bool purple_ending = 8; | 137 | optional bool purple_ending = 8; |
| 138 | optional bool cyan_ending = 9; | 138 | optional bool cyan_ending = 9; |
| 139 | optional bool mint_ending = 11; | ||
| 139 | optional bool vanilla_only = 10; | 140 | optional bool vanilla_only = 10; |
| 140 | } | 141 | } |
| 141 | 142 | ||
| diff --git a/proto/human.proto b/proto/human.proto index 6c98d3f..5cd8ce7 100644 --- a/proto/human.proto +++ b/proto/human.proto | |||
| @@ -79,6 +79,10 @@ message HumanConnection { | |||
| 79 | // when the Strict Cyan Ending option is on. | 79 | // when the Strict Cyan Ending option is on. |
| 80 | optional bool cyan_ending = 10; | 80 | optional bool cyan_ending = 10; |
| 81 | 81 | ||
| 82 | // This means that the connection should additionally require being able to | ||
| 83 | // type a specific text string when Custom Mint Ending is on. | ||
| 84 | optional bool mint_ending = 12; | ||
| 85 | |||
| 82 | // This means that the connection only exists when doors are not shuffled. | 86 | // This means that the connection only exists when doors are not shuffled. |
| 83 | optional bool vanilla_only = 11; | 87 | optional bool vanilla_only = 11; |
| 84 | } | 88 | } |
| diff --git a/tools/datapacker/main.cpp b/tools/datapacker/main.cpp index 7313fee..4ecde74 100644 --- a/tools/datapacker/main.cpp +++ b/tools/datapacker/main.cpp | |||
| @@ -533,6 +533,11 @@ class DataPacker { | |||
| 533 | r_connection.set_cyan_ending(human_connection.cyan_ending()); | 533 | r_connection.set_cyan_ending(human_connection.cyan_ending()); |
| 534 | } | 534 | } |
| 535 | 535 | ||
| 536 | if (human_connection.has_mint_ending()) { | ||
| 537 | f_connection.set_mint_ending(human_connection.mint_ending()); | ||
| 538 | r_connection.set_mint_ending(human_connection.mint_ending()); | ||
| 539 | } | ||
| 540 | |||
| 536 | if (human_connection.has_vanilla_only()) { | 541 | if (human_connection.has_vanilla_only()) { |
| 537 | f_connection.set_vanilla_only(human_connection.vanilla_only()); | 542 | f_connection.set_vanilla_only(human_connection.vanilla_only()); |
| 538 | r_connection.set_vanilla_only(human_connection.vanilla_only()); | 543 | r_connection.set_vanilla_only(human_connection.vanilla_only()); |
