diff --git a/worlds/celeste64/CHANGELOG.md b/worlds/celeste64/CHANGELOG.md index 5e562e17f443..3c37b4fcfe5c 100644 --- a/worlds/celeste64/CHANGELOG.md +++ b/worlds/celeste64/CHANGELOG.md @@ -1,6 +1,31 @@ # Celeste 64 - Changelog +## v1.3 + +### Features: + +- New optional Location Checks + - Checkpointsanity +- Hair Color + - Allows for setting of Maddy's hair color in each of No Dash, One Dash, Two Dash, and Feather states +- Other Player Ghosts + - A game config option allows you to see ghosts of other Celeste 64 players in the multiworld + +### Quality of Life: + +- Checkpoint Warping + - Received Checkpoint items allow for warping to their respective checkpoint + - These items are on their respective checkpoint location if Checkpointsanity is disabled + - Logic accounts for being able to warp to otherwise inaccessible areas + - Checkpoints are a possible option for a starting item on Standard Logic + Move Shuffle + Checkpointsanity +- New Options toggle to enable/disable background input + +### Bug Fixes: + +- Traffic Blocks now correctly appear disabled within Cassettes + + ## v1.2 ### Features: diff --git a/worlds/celeste64/Items.py b/worlds/celeste64/Items.py index 36c9f670c798..b173ebc69118 100644 --- a/worlds/celeste64/Items.py +++ b/worlds/celeste64/Items.py @@ -39,6 +39,22 @@ class Celeste64ItemData(NamedTuple): ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, ItemClassification.progression), } -item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, **unlockable_item_data_table, **move_item_data_table} +checkpoint_item_data_table: Dict[str, Celeste64ItemData] = { + ItemName.checkpoint_1: Celeste64ItemData(celeste_64_base_id + 0x20, ItemClassification.progression), + ItemName.checkpoint_2: Celeste64ItemData(celeste_64_base_id + 0x21, ItemClassification.progression), + ItemName.checkpoint_3: Celeste64ItemData(celeste_64_base_id + 0x22, ItemClassification.progression), + ItemName.checkpoint_4: Celeste64ItemData(celeste_64_base_id + 0x23, ItemClassification.progression), + ItemName.checkpoint_5: Celeste64ItemData(celeste_64_base_id + 0x24, ItemClassification.progression), + ItemName.checkpoint_6: Celeste64ItemData(celeste_64_base_id + 0x25, ItemClassification.progression), + ItemName.checkpoint_7: Celeste64ItemData(celeste_64_base_id + 0x26, ItemClassification.progression), + ItemName.checkpoint_8: Celeste64ItemData(celeste_64_base_id + 0x27, ItemClassification.progression), + ItemName.checkpoint_9: Celeste64ItemData(celeste_64_base_id + 0x28, ItemClassification.progression), + ItemName.checkpoint_10: Celeste64ItemData(celeste_64_base_id + 0x29, ItemClassification.progression), +} + +item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table, + **unlockable_item_data_table, + **move_item_data_table, + **checkpoint_item_data_table} item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None} diff --git a/worlds/celeste64/Locations.py b/worlds/celeste64/Locations.py index 6341529da356..9e202eca1203 100644 --- a/worlds/celeste64/Locations.py +++ b/worlds/celeste64/Locations.py @@ -1,7 +1,7 @@ from typing import Dict, NamedTuple, Optional from BaseClasses import Location -from .Names import LocationName +from .Names import LocationName, RegionName celeste_64_base_id: int = 0xCA0000 @@ -17,66 +17,80 @@ class Celeste64LocationData(NamedTuple): strawberry_location_data_table: Dict[str, Celeste64LocationData] = { - LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00), - LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01), - LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02), - LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03), - LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04), - LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05), - LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06), - LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07), - LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08), - LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09), - LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A), - LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B), - LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C), - LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D), - LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E), - LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F), - LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10), - LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11), - LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12), - LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13), - LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14), - LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15), - LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16), - LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17), - LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18), - LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19), - LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A), - LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B), - LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C), - LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D), + LocationName.strawberry_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x00), + LocationName.strawberry_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x01), + LocationName.strawberry_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x02), + LocationName.strawberry_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x03), + LocationName.strawberry_5: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x04), + LocationName.strawberry_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x05), + LocationName.strawberry_7: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x06), + LocationName.strawberry_8: Celeste64LocationData(RegionName.nw_girders_island, celeste_64_base_id + 0x07), + LocationName.strawberry_9: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x08), + LocationName.strawberry_10: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x09), + LocationName.strawberry_11: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x0A), + LocationName.strawberry_12: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x0B), + LocationName.strawberry_13: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x0C), + LocationName.strawberry_14: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0D), + LocationName.strawberry_15: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0E), + LocationName.strawberry_16: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0F), + LocationName.strawberry_17: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x10), + LocationName.strawberry_18: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x11), + LocationName.strawberry_19: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x12), + LocationName.strawberry_20: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x13), + LocationName.strawberry_21: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x14), + LocationName.strawberry_22: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x15), + LocationName.strawberry_23: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x16), + LocationName.strawberry_24: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x17), + LocationName.strawberry_25: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x18), + LocationName.strawberry_26: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x19), + LocationName.strawberry_27: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1A), + LocationName.strawberry_28: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1B), + LocationName.strawberry_29: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x1C), + LocationName.strawberry_30: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x1D), } friend_location_data_table: Dict[str, Celeste64LocationData] = { - LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00), - LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01), - LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02), - LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03), - LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04), - LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05), - LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06), - LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07), - LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08), + LocationName.granny_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x00), + LocationName.granny_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x01), + LocationName.granny_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x02), + LocationName.theo_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x03), + LocationName.theo_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x04), + LocationName.theo_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x05), + LocationName.badeline_1: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x06), + LocationName.badeline_2: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x07), + LocationName.badeline_3: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x08), } sign_location_data_table: Dict[str, Celeste64LocationData] = { - LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00), - LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01), - LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02), - LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03), - LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04), + LocationName.sign_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x00), + LocationName.sign_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x01), + LocationName.sign_3: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x200 + 0x02), + LocationName.sign_4: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x200 + 0x03), + LocationName.sign_5: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x200 + 0x04), } car_location_data_table: Dict[str, Celeste64LocationData] = { - LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00), - LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01), + LocationName.car_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x300 + 0x00), + LocationName.car_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x300 + 0x01), +} + +checkpoint_location_data_table: Dict[str, Celeste64LocationData] = { + LocationName.checkpoint_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x400 + 0x00), + LocationName.checkpoint_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x01), + LocationName.checkpoint_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x02), + LocationName.checkpoint_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x03), + LocationName.checkpoint_5: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x04), + LocationName.checkpoint_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x05), + LocationName.checkpoint_7: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x400 + 0x06), + LocationName.checkpoint_8: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x400 + 0x07), + LocationName.checkpoint_9: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x400 + 0x08), + LocationName.checkpoint_10: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x400 + 0x09), } location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table, **friend_location_data_table, **sign_location_data_table, - **car_location_data_table} + **car_location_data_table, + **checkpoint_location_data_table} location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None} diff --git a/worlds/celeste64/Names/ItemName.py b/worlds/celeste64/Names/ItemName.py index d4fb9496007f..b4c02115cbba 100644 --- a/worlds/celeste64/Names/ItemName.py +++ b/worlds/celeste64/Names/ItemName.py @@ -15,3 +15,18 @@ air_dash = "Air Dash" skid_jump = "Skid Jump" climb = "Climb" + +# Checkpoint Items +checkpoint_1 = "Intro Checkpoint" +checkpoint_2 = "Granny Checkpoint" +checkpoint_3 = "South-East Tower Checkpoint" +checkpoint_4 = "Climb Sign Checkpoint" +checkpoint_5 = "Freeway Checkpoint" +checkpoint_6 = "Freeway Feather Checkpoint" +checkpoint_7 = "Feather Maze Checkpoint" +checkpoint_8 = "Double Dash House Checkpoint" +checkpoint_9 = "Badeline Tower Checkpoint" +checkpoint_10 = "Badeline Island Checkpoint" + +# Item used for logic definitions that are not possible with the given options +cannot_access = "CANNOT ACCESS" diff --git a/worlds/celeste64/Names/LocationName.py b/worlds/celeste64/Names/LocationName.py index 1b784f3875f1..784d2049cfa2 100644 --- a/worlds/celeste64/Names/LocationName.py +++ b/worlds/celeste64/Names/LocationName.py @@ -10,7 +10,7 @@ strawberry_9 = "South-West Dash Refills Strawberry" strawberry_10 = "South-East Tower Side Strawberry" strawberry_11 = "Girders Strawberry" -strawberry_12 = "North-East Tower Bottom Strawberry" +strawberry_12 = "Badeline Tower Bottom Strawberry" strawberry_13 = "Breakable Blocks Strawberry" strawberry_14 = "Feather Maze Strawberry" strawberry_15 = "Feather Chain Strawberry" @@ -18,7 +18,7 @@ strawberry_17 = "Double Dash Puzzle Strawberry" strawberry_18 = "Double Dash Spike Climb Strawberry" strawberry_19 = "Double Dash Spring Strawberry" -strawberry_20 = "North-East Tower Breakable Bottom Strawberry" +strawberry_20 = "Badeline Tower Breakable Bottom Strawberry" strawberry_21 = "Theo Tower Lower Cassette Strawberry" strawberry_22 = "Theo Tower Upper Cassette Strawberry" strawberry_23 = "South End of Bridge Cassette Strawberry" @@ -27,8 +27,8 @@ strawberry_26 = "North End of Bridge Cassette Strawberry" strawberry_27 = "Distant Feather Cassette Strawberry" strawberry_28 = "Feather Arches Cassette Strawberry" -strawberry_29 = "North-East Tower Cassette Strawberry" -strawberry_30 = "Badeline Cassette Strawberry" +strawberry_29 = "Badeline Tower Cassette Strawberry" +strawberry_30 = "Badeline Island Cassette Strawberry" # Friend Locations granny_1 = "Granny Conversation 1" @@ -51,3 +51,15 @@ # Car Locations car_1 = "Intro Car" car_2 = "Secret Car" + +# Checkpoint Locations +checkpoint_1 = "Intro Checkpoint" +checkpoint_2 = "Granny Checkpoint" +checkpoint_3 = "South-East Tower Checkpoint" +checkpoint_4 = "Climb Sign Checkpoint" +checkpoint_5 = "Freeway Checkpoint" +checkpoint_6 = "Freeway Feather Checkpoint" +checkpoint_7 = "Feather Maze Checkpoint" +checkpoint_8 = "Double Dash House Checkpoint" +checkpoint_9 = "Badeline Tower Checkpoint" +checkpoint_10 = "Badeline Island Checkpoint" diff --git a/worlds/celeste64/Names/RegionName.py b/worlds/celeste64/Names/RegionName.py new file mode 100644 index 000000000000..ddb07a5bdaeb --- /dev/null +++ b/worlds/celeste64/Names/RegionName.py @@ -0,0 +1,13 @@ +# Level Base Regions +forsaken_city = "Forsaken City" + +# Forsaken City Regions +intro_islands = "Intro Islands" +granny_island = "Granny Island" +highway_island = "Freeway Island" +nw_girders_island = "North-West Girders Island" +ne_feathers_island = "North-East Feathers Island" +se_house_island = "South-East House Island" +badeline_tower_lower = "Badeline Tower Lower" +badeline_tower_upper = "Badeline Tower Upper" +badeline_island = "Badeline Island" diff --git a/worlds/celeste64/Options.py b/worlds/celeste64/Options.py index 9a67e7d7d4f5..bb9932d47f0d 100644 --- a/worlds/celeste64/Options.py +++ b/worlds/celeste64/Options.py @@ -1,6 +1,8 @@ from dataclasses import dataclass +import random -from Options import Choice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions +from Options import Choice, TextChoice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions, OptionError +from worlds.AutoWorld import World class DeathLinkAmnesty(Range): @@ -18,7 +20,7 @@ class TotalStrawberries(Range): """ display_name = "Total Strawberries" range_start = 0 - range_end = 46 + range_end = 55 default = 20 class StrawberriesRequiredPercentage(Range): @@ -73,6 +75,93 @@ class Carsanity(Toggle): """ display_name = "Carsanity" +class Checkpointsanity(Toggle): + """ + Whether activating Checkpoints grants location checks + + Activating this will also shuffle items into the pool which allow usage and warping to each Checkpoint + """ + display_name = "Checkpointsanity" + + +class ColorChoice(TextChoice): + option_strawberry = 0xDB2C00 + option_empty = 0x6EC0FF + option_double = 0xFA91FF + option_golden = 0xF2D450 + option_baddy = 0x9B3FB5 + option_fire_red = 0xFF0000 + option_maroon = 0x800000 + option_salmon = 0xFF3A65 + option_orange = 0xD86E0A + option_lime_green = 0x8DF920 + option_bright_green = 0x0DAF05 + option_forest_green = 0x132818 + option_royal_blue = 0x0036BF + option_brown = 0xB78726 + option_black = 0x000000 + option_white = 0xFFFFFF + option_grey = 0x808080 + option_any_color = -1 + + @classmethod + def from_text(cls, text: str) -> Choice: + text = text.lower() + if text == "random": + choice_list = list(cls.name_lookup) + choice_list.remove(-1) + return cls(random.choice(choice_list)) + return super().from_text(text) + + +class MadelineOneDashHairColor(ColorChoice): + """ + What color Madeline's hair is when she has one dash + + The `any_color` option will choose a fully random color + + A custom color entry may be supplied as a 6-character RGB hex color code + e.g. F542C8 + """ + display_name = "Madeline One Dash Hair Color" + default = ColorChoice.option_strawberry + +class MadelineTwoDashHairColor(ColorChoice): + """ + What color Madeline's hair is when she has two dashes + + The `any_color` option will choose a fully random color + + A custom color entry may be supplied as a 6-character RGB hex color code + e.g. F542C8 + """ + display_name = "Madeline Two Dash Hair Color" + default = ColorChoice.option_double + +class MadelineNoDashHairColor(ColorChoice): + """ + What color Madeline's hair is when she has no dashes + + The `any_color` option will choose a fully random color + + A custom color entry may be supplied as a 6-character RGB hex color code + e.g. F542C8 + """ + display_name = "Madeline No Dash Hair Color" + default = ColorChoice.option_empty + +class MadelineFeatherHairColor(ColorChoice): + """ + What color Madeline's hair is when she has a feather + + The `any_color` option will choose a fully random color + + A custom color entry may be supplied as a 6-character RGB hex color code + e.g. F542C8 + """ + display_name = "Madeline Feather Hair Color" + default = ColorChoice.option_golden + class BadelineChaserSource(Choice): """ @@ -119,6 +208,13 @@ class BadelineChaserSpeed(Range): Friendsanity, Signsanity, Carsanity, + Checkpointsanity, + ]), + OptionGroup("Aesthetic Options", [ + MadelineOneDashHairColor, + MadelineTwoDashHairColor, + MadelineNoDashHairColor, + MadelineFeatherHairColor, ]), OptionGroup("Badeline Chasers", [ BadelineChaserSource, @@ -142,7 +238,68 @@ class Celeste64Options(PerGameCommonOptions): friendsanity: Friendsanity signsanity: Signsanity carsanity: Carsanity + checkpointsanity: Checkpointsanity + + madeline_one_dash_hair_color: MadelineOneDashHairColor + madeline_two_dash_hair_color: MadelineTwoDashHairColor + madeline_no_dash_hair_color: MadelineNoDashHairColor + madeline_feather_hair_color: MadelineFeatherHairColor badeline_chaser_source: BadelineChaserSource badeline_chaser_frequency: BadelineChaserFrequency badeline_chaser_speed: BadelineChaserSpeed + + +def resolve_options(world: World): + # One Dash Hair + if isinstance(world.options.madeline_one_dash_hair_color.value, str): + try: + world.madeline_one_dash_hair_color = int(world.options.madeline_one_dash_hair_color.value.strip("#")[:6], 16) + except ValueError: + raise OptionError(f"Invalid input for option `madeline_one_dash_hair_color`:" + f"{world.options.madeline_one_dash_hair_color.value} for " + f"{world.player_name}") + elif world.options.madeline_one_dash_hair_color.value == -1: + world.madeline_one_dash_hair_color = world.random.randint(0, 0xFFFFFF) + else: + world.madeline_one_dash_hair_color = world.options.madeline_one_dash_hair_color.value + + # Two Dash Hair + if isinstance(world.options.madeline_two_dash_hair_color.value, str): + try: + world.madeline_two_dash_hair_color = int(world.options.madeline_two_dash_hair_color.value.strip("#")[:6], 16) + except ValueError: + raise OptionError(f"Invalid input for option `madeline_two_dash_hair_color`:" + f"{world.options.madeline_two_dash_hair_color.value} for " + f"{world.player_name}") + elif world.options.madeline_two_dash_hair_color.value == -1: + world.madeline_two_dash_hair_color = world.random.randint(0, 0xFFFFFF) + else: + world.madeline_two_dash_hair_color = world.options.madeline_two_dash_hair_color.value + + # No Dash Hair + if isinstance(world.options.madeline_no_dash_hair_color.value, str): + try: + world.madeline_no_dash_hair_color = int(world.options.madeline_no_dash_hair_color.value.strip("#")[:6], 16) + except ValueError: + raise OptionError(f"Invalid input for option `madeline_no_dash_hair_color`:" + f"{world.options.madeline_no_dash_hair_color.value} for " + f"{world.player_name}") + elif world.options.madeline_no_dash_hair_color.value == -1: + world.madeline_no_dash_hair_color = world.random.randint(0, 0xFFFFFF) + else: + world.madeline_no_dash_hair_color = world.options.madeline_no_dash_hair_color.value + + # Feather Hair + if isinstance(world.options.madeline_feather_hair_color.value, str): + try: + world.madeline_feather_hair_color = int(world.options.madeline_feather_hair_color.value.strip("#")[:6], 16) + except ValueError: + raise OptionError(f"Invalid input for option `madeline_feather_hair_color`:" + f"{world.options.madeline_feather_hair_color.value} for " + f"{world.player_name}") + elif world.options.madeline_feather_hair_color.value == -1: + world.madeline_feather_hair_color = world.random.randint(0, 0xFFFFFF) + else: + world.madeline_feather_hair_color = world.options.madeline_feather_hair_color.value + diff --git a/worlds/celeste64/Regions.py b/worlds/celeste64/Regions.py index 6f01c873a4f9..7db683bbb925 100644 --- a/worlds/celeste64/Regions.py +++ b/worlds/celeste64/Regions.py @@ -1,11 +1,23 @@ from typing import Dict, List, NamedTuple +from .Names import RegionName class Celeste64RegionData(NamedTuple): connecting_regions: List[str] = [] region_data_table: Dict[str, Celeste64RegionData] = { - "Menu": Celeste64RegionData(["Forsaken City"]), - "Forsaken City": Celeste64RegionData(), + "Menu": Celeste64RegionData([RegionName.forsaken_city]), + + RegionName.forsaken_city: Celeste64RegionData([RegionName.intro_islands, RegionName.granny_island, RegionName.highway_island, RegionName.ne_feathers_island, RegionName.se_house_island, RegionName.badeline_tower_upper, RegionName.badeline_island]), + + RegionName.intro_islands: Celeste64RegionData([RegionName.granny_island]), + RegionName.granny_island: Celeste64RegionData([RegionName.highway_island, RegionName.nw_girders_island, RegionName.badeline_tower_lower, RegionName.se_house_island]), + RegionName.highway_island: Celeste64RegionData([RegionName.granny_island, RegionName.ne_feathers_island, RegionName.nw_girders_island]), + RegionName.nw_girders_island: Celeste64RegionData([RegionName.highway_island]), + RegionName.ne_feathers_island: Celeste64RegionData([RegionName.se_house_island, RegionName.highway_island, RegionName.badeline_tower_lower, RegionName.badeline_tower_upper]), + RegionName.se_house_island: Celeste64RegionData([RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_lower]), + RegionName.badeline_tower_lower: Celeste64RegionData([RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_upper]), + RegionName.badeline_tower_upper: Celeste64RegionData([RegionName.badeline_island, RegionName.badeline_tower_lower, RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island]), + RegionName.badeline_island: Celeste64RegionData([RegionName.badeline_tower_upper, RegionName.granny_island, RegionName.highway_island]), } diff --git a/worlds/celeste64/Rules.py b/worlds/celeste64/Rules.py index ebb47cca3093..3365a7cf9551 100644 --- a/worlds/celeste64/Rules.py +++ b/worlds/celeste64/Rules.py @@ -1,265 +1,85 @@ -from typing import Dict, List +from typing import Dict, List, Tuple, Callable -from BaseClasses import CollectionState +from BaseClasses import CollectionState, Region from worlds.generic.Rules import set_rule from . import Celeste64World -from .Names import ItemName, LocationName +from .Names import ItemName, LocationName, RegionName def set_rules(world: Celeste64World): if world.options.logic_difficulty == "standard": - if world.options.move_shuffle: - world.active_logic_mapping = location_standard_moves_logic - else: - world.active_logic_mapping = location_standard_logic + world.active_logic_mapping = location_standard_moves_logic + world.active_region_logic_mapping = region_standard_moves_logic else: - if world.options.move_shuffle: - world.active_logic_mapping = location_hard_moves_logic - else: - world.active_logic_mapping = location_hard_logic + world.active_logic_mapping = location_hard_moves_logic + world.active_region_logic_mapping = region_hard_moves_logic for location in world.multiworld.get_locations(world.player): set_rule(location, lambda state, location=location: location_rule(state, world, location.name)) - if world.options.logic_difficulty == "standard": - if world.options.move_shuffle: - world.goal_logic_mapping = goal_standard_moves_logic - else: - world.goal_logic_mapping = goal_standard_logic - else: - if world.options.move_shuffle: - world.goal_logic_mapping = goal_hard_moves_logic - else: - world.goal_logic_mapping = goal_hard_logic - # Completion condition. world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world) -goal_standard_logic: List[List[str]] = [[ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.double_dash_refill]] -goal_hard_logic: List[List[str]] = [[]] -goal_standard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]] -goal_hard_moves_logic: List[List[str]] = [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], - [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], - [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]] - - -location_standard_logic: Dict[str, List[List[str]]] = { - LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables]], - LocationName.strawberry_6: [[ItemName.dash_refill], - [ItemName.traffic_block]], - LocationName.strawberry_7: [[ItemName.dash_refill], - [ItemName.traffic_block]], - LocationName.strawberry_8: [[ItemName.traffic_block]], - LocationName.strawberry_9: [[ItemName.dash_refill]], - LocationName.strawberry_11: [[ItemName.dash_refill], - [ItemName.traffic_block]], - LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill], - [ItemName.traffic_block, ItemName.double_dash_refill]], - LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables], - [ItemName.traffic_block, ItemName.breakables]], - LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather], - [ItemName.traffic_block, ItemName.feather]], - LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather], - [ItemName.traffic_block, ItemName.feather]], - LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather], - [ItemName.traffic_block, ItemName.feather]], - LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]], - LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill], - [ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]], - LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring], - [ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring]], - LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables], - [ItemName.traffic_block, ItemName.feather, ItemName.breakables]], - - LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]], - LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables]], - LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin], - [ItemName.cassette, ItemName.traffic_block, ItemName.coin]], - LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block]], - LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill], - [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill]], - LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill], - [ItemName.cassette, ItemName.traffic_block]], - LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin], - [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]], - LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin], - [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin]], - LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin]], - LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables]], - - LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables]], - LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables]], - LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables]], - LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], - LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], - LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], - - LocationName.sign_2: [[ItemName.breakables]], - LocationName.sign_3: [[ItemName.dash_refill], - [ItemName.traffic_block]], - LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill], - [ItemName.dash_refill, ItemName.feather], - [ItemName.traffic_block, ItemName.feather]], - LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], - - LocationName.car_2: [[ItemName.breakables]], -} - -location_hard_logic: Dict[str, List[List[str]]] = { - LocationName.strawberry_13: [[ItemName.breakables]], - LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]], - LocationName.strawberry_20: [[ItemName.breakables]], - - LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables]], - LocationName.strawberry_22: [[ItemName.cassette]], - LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin]], - LocationName.strawberry_24: [[ItemName.cassette]], - LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill]], - LocationName.strawberry_26: [[ItemName.cassette]], - LocationName.strawberry_27: [[ItemName.cassette]], - LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather]], - LocationName.strawberry_29: [[ItemName.cassette]], - LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables]], - - LocationName.sign_2: [[ItemName.breakables]], - - LocationName.car_2: [[ItemName.breakables]], -} - location_standard_moves_logic: Dict[str, List[List[str]]] = { LocationName.strawberry_1: [[ItemName.ground_dash], [ItemName.air_dash], - [ItemName.skid_jump], - [ItemName.climb]], - LocationName.strawberry_2: [[ItemName.ground_dash], - [ItemName.air_dash], - [ItemName.skid_jump], [ItemName.climb]], + LocationName.strawberry_2: [[ItemName.air_dash], + [ItemName.skid_jump]], LocationName.strawberry_3: [[ItemName.air_dash], [ItemName.skid_jump]], LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], LocationName.strawberry_5: [[ItemName.air_dash]], - LocationName.strawberry_6: [[ItemName.dash_refill, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash], - [ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.skid_jump], - [ItemName.traffic_block, ItemName.climb]], - LocationName.strawberry_7: [[ItemName.dash_refill, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash], - [ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.skid_jump], - [ItemName.traffic_block, ItemName.climb]], - LocationName.strawberry_8: [[ItemName.traffic_block, ItemName.ground_dash], - [ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.skid_jump], - [ItemName.traffic_block, ItemName.climb]], LocationName.strawberry_9: [[ItemName.dash_refill, ItemName.air_dash]], LocationName.strawberry_10: [[ItemName.climb]], - LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.climb]], - LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash], - [ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]], - LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash], - [ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash], - [ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], - LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash], - [ItemName.traffic_block, ItemName.feather, ItemName.air_dash]], - LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.feather, ItemName.climb]], - LocationName.strawberry_16: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash], - [ItemName.traffic_block, ItemName.feather]], - LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.ground_dash], - [ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.air_dash], - [ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.skid_jump], - [ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.climb]], - LocationName.strawberry_18: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]], - LocationName.strawberry_19: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash], - [ItemName.traffic_block, ItemName.double_dash_refill, ItemName.feather, ItemName.spring, ItemName.air_dash]], - LocationName.strawberry_20: [[ItemName.dash_refill, ItemName.feather, ItemName.breakables, ItemName.air_dash], - [ItemName.traffic_block, ItemName.feather, ItemName.breakables, ItemName.air_dash]], + LocationName.strawberry_11: [[ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_13: [[ItemName.breakables, ItemName.air_dash], + [ItemName.breakables, ItemName.ground_dash]], + LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash]], + LocationName.strawberry_15: [[ItemName.feather, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_16: [[ItemName.feather]], + LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]], + LocationName.strawberry_18: [[ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_19: [[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash, ItemName.skid_jump]], + LocationName.strawberry_20: [[ItemName.feather, ItemName.breakables, ItemName.air_dash]], LocationName.strawberry_21: [[ItemName.cassette, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], LocationName.strawberry_22: [[ItemName.cassette, ItemName.dash_refill, ItemName.breakables, ItemName.air_dash]], - LocationName.strawberry_23: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.climb], - [ItemName.cassette, ItemName.traffic_block, ItemName.coin, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_23: [[ItemName.cassette, ItemName.coin, ItemName.air_dash, ItemName.climb]], LocationName.strawberry_24: [[ItemName.cassette, ItemName.dash_refill, ItemName.traffic_block, ItemName.air_dash]], - LocationName.strawberry_25: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb], - [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]], - LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb], - [ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]], - LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash], - [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash]], - LocationName.strawberry_28: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb], - [ItemName.cassette, ItemName.traffic_block, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]], - LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]], + LocationName.strawberry_25: [[ItemName.cassette, ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_26: [[ItemName.cassette, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_27: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]], + LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]], LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.spring, ItemName.breakables, ItemName.air_dash, ItemName.climb]], - LocationName.granny_1: [[ItemName.ground_dash], - [ItemName.air_dash], - [ItemName.skid_jump], - [ItemName.climb]], - LocationName.granny_2: [[ItemName.ground_dash], - [ItemName.air_dash], - [ItemName.skid_jump], - [ItemName.climb]], - LocationName.granny_3: [[ItemName.ground_dash], - [ItemName.air_dash], - [ItemName.skid_jump], - [ItemName.climb]], LocationName.theo_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], LocationName.theo_2: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], LocationName.theo_3: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], - LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], - LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], - LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], - - LocationName.sign_1: [[ItemName.ground_dash], - [ItemName.air_dash], - [ItemName.skid_jump], - [ItemName.climb]], + LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash], [ItemName.breakables, ItemName.air_dash]], - LocationName.sign_3: [[ItemName.dash_refill, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash], - [ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.skid_jump], - [ItemName.traffic_block, ItemName.climb]], - LocationName.sign_4: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash], - [ItemName.dash_refill, ItemName.feather, ItemName.air_dash], - [ItemName.traffic_block, ItemName.feather, ItemName.ground_dash], - [ItemName.traffic_block, ItemName.feather, ItemName.air_dash], - [ItemName.traffic_block, ItemName.feather, ItemName.skid_jump], - [ItemName.traffic_block, ItemName.feather, ItemName.climb]], - LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb]], - LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash], - [ItemName.breakables, ItemName.air_dash]], + LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash, ItemName.climb], + [ItemName.breakables, ItemName.air_dash, ItemName.climb]], } location_hard_moves_logic: Dict[str, List[List[str]]] = { - LocationName.strawberry_3: [[ItemName.air_dash], - [ItemName.skid_jump]], LocationName.strawberry_5: [[ItemName.ground_dash], [ItemName.air_dash]], - LocationName.strawberry_8: [[ItemName.traffic_block], - [ItemName.ground_dash, ItemName.air_dash]], LocationName.strawberry_10: [[ItemName.air_dash], [ItemName.climb]], LocationName.strawberry_11: [[ItemName.ground_dash], [ItemName.air_dash], [ItemName.skid_jump]], - LocationName.strawberry_12: [[ItemName.feather], - [ItemName.ground_dash], - [ItemName.air_dash]], LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash], [ItemName.breakables, ItemName.air_dash]], LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash], - [ItemName.air_dash, ItemName.climb]], + [ItemName.air_dash, ItemName.climb], + [ItemName.double_dash_refill, ItemName.air_dash]], LocationName.strawberry_15: [[ItemName.feather], [ItemName.ground_dash, ItemName.air_dash]], LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]], @@ -287,42 +107,94 @@ def set_rules(world: Celeste64World): [ItemName.cassette, ItemName.feather, ItemName.climb]], LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump], [ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]], - LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash, ItemName.air_dash, ItemName.climb, ItemName.skid_jump], - [ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.feather, ItemName.air_dash, ItemName.climb, ItemName.skid_jump], - [ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.ground_dash, ItemName.air_dash, ItemName.climb], - [ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.feather, ItemName.air_dash, ItemName.climb]], - - LocationName.badeline_1: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], - [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], - [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], - LocationName.badeline_2: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], - [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], - [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], - LocationName.badeline_3: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], - [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], - [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], + LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb, ItemName.skid_jump], + [ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, ItemName.spring, ItemName.air_dash, ItemName.climb]], LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash], [ItemName.breakables, ItemName.air_dash]], - LocationName.sign_5: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables, ItemName.air_dash, ItemName.climb], - [ItemName.traffic_block, ItemName.air_dash, ItemName.skid_jump], - [ItemName.ground_dash, ItemName.air_dash, ItemName.skid_jump], - [ItemName.feather, ItemName.traffic_block, ItemName.air_dash], - [ItemName.traffic_block, ItemName.ground_dash, ItemName.air_dash]], LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash], [ItemName.breakables, ItemName.air_dash]], } -def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool: +region_standard_moves_logic: Dict[Tuple[str], List[List[str]]] = { + (RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]], + (RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]], + (RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]], + (RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]], + (RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]], + (RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]], + + (RegionName.intro_islands, RegionName.granny_island): [[ItemName.ground_dash], + [ItemName.air_dash], + [ItemName.skid_jump], + [ItemName.climb]], + + (RegionName.granny_island, RegionName.highway_island): [[ItemName.air_dash, ItemName.dash_refill]], + (RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]], + (RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.climb, ItemName.dash_refill]], + (RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill]], + + (RegionName.highway_island, RegionName.granny_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.dash_refill]], + (RegionName.highway_island, RegionName.ne_feathers_island): [[ItemName.feather]], + (RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.cannot_access]], + + (RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block]], + + (RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather]], + (RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather]], + (RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.climb, ItemName.air_dash, ItemName.feather]], + + (RegionName.se_house_island, RegionName.granny_island): [[ItemName.air_dash, ItemName.traffic_block, ItemName.double_dash_refill]], + (RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.double_dash_refill]], + + (RegionName.badeline_tower_lower, RegionName.se_house_island): [[ItemName.cannot_access]], + (RegionName.badeline_tower_lower, RegionName.ne_feathers_island): [[ItemName.air_dash, ItemName.breakables, ItemName.feather]], + (RegionName.badeline_tower_lower, RegionName.granny_island): [[ItemName.cannot_access]], + (RegionName.badeline_tower_lower, RegionName.badeline_tower_upper): [[ItemName.cannot_access]], + + (RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]], + (RegionName.badeline_tower_upper, RegionName.se_house_island): [[ItemName.air_dash], [ItemName.ground_dash]], + (RegionName.badeline_tower_upper, RegionName.ne_feathers_island): [[ItemName.air_dash], [ItemName.ground_dash]], + (RegionName.badeline_tower_upper, RegionName.granny_island): [[ItemName.dash_refill]], + + (RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]], +} + +region_hard_moves_logic: Dict[Tuple[str], List[List[str]]] = { + (RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]], + (RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]], + (RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]], + (RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]], + (RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]], + (RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]], + + (RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]], + (RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]], + (RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.double_dash_refill], [ItemName.ground_dash]], + + (RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.air_dash, ItemName.ground_dash]], + + (RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.ground_dash]], + + (RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash], [ItemName.skid_jump]], + (RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash]], + (RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.feather]], + + (RegionName.se_house_island, RegionName.granny_island): [[ItemName.traffic_block]], + (RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]], + + (RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.traffic_block], + [ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.skid_jump], + [ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.traffic_block], + [ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.skid_jump]], + + (RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]], +} + +def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool: if loc not in world.active_logic_mapping: return True @@ -332,12 +204,28 @@ def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bo return False -def goal_rule(state: CollectionState, world: Celeste64World) -> bool: - if not state.has(ItemName.strawberry, world.player, world.strawberries_required): - return False +def region_connection_rule(state: CollectionState, world: Celeste64World, region_connection: Tuple[str]) -> bool: + if region_connection not in world.active_region_logic_mapping: + return True - for possible_access in world.goal_logic_mapping: + for possible_access in world.active_region_logic_mapping[region_connection]: if state.has_all(possible_access, world.player): return True return False + +def goal_rule(state: CollectionState, world: Celeste64World) -> bool: + if not state.has(ItemName.strawberry, world.player, world.strawberries_required): + return False + + goal_region: Region = world.multiworld.get_region(RegionName.badeline_island, world.player) + return state.can_reach(goal_region) + +def connect_region(world: Celeste64World, region: Region, dest_regions: List[str]): + rules: Dict[str, Callable[[CollectionState], bool]] = {} + + for dest_region in dest_regions: + region_connection: Tuple[str] = (region.name, dest_region) + rules[dest_region] = lambda state, region_connection=region_connection: region_connection_rule(state, world, region_connection) + + region.add_exits(dest_regions, rules) diff --git a/worlds/celeste64/__init__.py b/worlds/celeste64/__init__.py index 7786e381230a..04eae0c444a9 100644 --- a/worlds/celeste64/__init__.py +++ b/worlds/celeste64/__init__.py @@ -1,13 +1,15 @@ from copy import deepcopy -from typing import Dict, List +from typing import Dict, List, Tuple from BaseClasses import ItemClassification, Location, Region, Tutorial from worlds.AutoWorld import WebWorld, World -from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table +from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table,\ + checkpoint_item_data_table, item_table from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\ - sign_location_data_table, car_location_data_table, location_table + sign_location_data_table, car_location_data_table, checkpoint_location_data_table,\ + location_table from .Names import ItemName, LocationName -from .Options import Celeste64Options, celeste_64_option_groups +from .Options import Celeste64Options, celeste_64_option_groups, resolve_options class Celeste64WebWorld(WebWorld): @@ -42,8 +44,15 @@ class Celeste64World(World): # Instance Data strawberries_required: int active_logic_mapping: Dict[str, List[List[str]]] - goal_logic_mapping: Dict[str, List[List[str]]] + active_region_logic_mapping: Dict[Tuple[str], List[List[str]]] + madeline_one_dash_hair_color: int + madeline_two_dash_hair_color: int + madeline_no_dash_hair_color: int + madeline_feather_hair_color: int + + def generate_early(self) -> None: + resolve_options(self) def create_item(self, name: str) -> Celeste64Item: # Only make required amount of strawberries be Progression @@ -76,25 +85,49 @@ def create_items(self) -> None: for name in unlockable_item_data_table.keys() if name not in self.options.start_inventory] - if self.options.move_shuffle: - move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys())) + chosen_start_item: str = "" + if self.options.move_shuffle: if self.options.logic_difficulty == "standard": - # If the start_inventory already includes a move, don't worry about giving it one - if not [move for move in move_items_for_itempool if move in self.options.start_inventory]: - chosen_start_move = self.random.choice(move_items_for_itempool) - move_items_for_itempool.remove(chosen_start_move) + possible_unwalls: List[str] = [name for name in move_item_data_table.keys() + if name != ItemName.skid_jump] + + if self.options.checkpointsanity: + possible_unwalls.extend([name for name in checkpoint_item_data_table.keys() + if name != ItemName.checkpoint_1 and name != ItemName.checkpoint_10]) + + # If the start_inventory already includes a move or checkpoint, don't worry about giving it one + if not [item for item in possible_unwalls if item in self.multiworld.precollected_items[self.player]]: + chosen_start_item = self.random.choice(possible_unwalls) if self.options.carsanity: intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player) - intro_car_loc.place_locked_item(self.create_item(chosen_start_move)) + intro_car_loc.place_locked_item(self.create_item(chosen_start_item)) location_count -= 1 else: - self.multiworld.push_precollected(self.create_item(chosen_start_move)) + self.multiworld.push_precollected(self.create_item(chosen_start_item)) item_pool += [self.create_item(name) - for name in move_items_for_itempool - if name not in self.options.start_inventory] + for name in move_item_data_table.keys() + if name not in self.multiworld.precollected_items[self.player] + and name != chosen_start_item] + else: + for start_move in move_item_data_table.keys(): + self.multiworld.push_precollected(self.create_item(start_move)) + + if self.options.checkpointsanity: + location_count += 9 + goal_checkpoint_loc: Location = self.multiworld.get_location(LocationName.checkpoint_10, self.player) + goal_checkpoint_loc.place_locked_item(self.create_item(ItemName.checkpoint_10)) + item_pool += [self.create_item(name) + for name in checkpoint_item_data_table.keys() + if name not in self.multiworld.precollected_items[self.player] + and name != ItemName.checkpoint_10 + and name != chosen_start_item] + else: + for item_name in checkpoint_item_data_table.keys(): + checkpoint_loc: Location = self.multiworld.get_location(item_name, self.player) + checkpoint_loc.place_locked_item(self.create_item(item_name)) real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool)) self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100)) @@ -140,18 +173,23 @@ def create_regions(self) -> None: if location_data.region == region_name }, Celeste64Location) - region.add_exits(region_data_table[region_name].connecting_regions) - - - def get_filler_item_name(self) -> str: - return ItemName.raspberry + region.add_locations({ + location_name: location_data.address for location_name, location_data in checkpoint_location_data_table.items() + if location_data.region == region_name + }, Celeste64Location) + from .Rules import connect_region + connect_region(self, region, region_data_table[region_name].connecting_regions) - def set_rules(self) -> None: + # Have to do this here because of other games using State in a way that's bad from .Rules import set_rules set_rules(self) + def get_filler_item_name(self) -> str: + return ItemName.raspberry + + def fill_slot_data(self): return { "death_link": self.options.death_link.value, @@ -161,6 +199,11 @@ def fill_slot_data(self): "friendsanity": self.options.friendsanity.value, "signsanity": self.options.signsanity.value, "carsanity": self.options.carsanity.value, + "checkpointsanity": self.options.checkpointsanity.value, + "madeline_one_dash_hair_color": self.madeline_one_dash_hair_color, + "madeline_two_dash_hair_color": self.madeline_two_dash_hair_color, + "madeline_no_dash_hair_color": self.madeline_no_dash_hair_color, + "madeline_feather_hair_color": self.madeline_feather_hair_color, "badeline_chaser_source": self.options.badeline_chaser_source.value, "badeline_chaser_frequency": self.options.badeline_chaser_frequency.value, "badeline_chaser_speed": self.options.badeline_chaser_speed.value,