2024-05-05 02:58:49 -04:00
|
|
|
|
from copy import deepcopy
|
2025-03-16 21:46:34 -04:00
|
|
|
|
from typing import Dict, List, Tuple
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
2024-05-05 02:58:49 -04:00
|
|
|
|
from BaseClasses import ItemClassification, Location, Region, Tutorial
|
2024-03-05 18:55:56 -05:00
|
|
|
|
from worlds.AutoWorld import WebWorld, World
|
2025-03-16 21:46:34 -04:00
|
|
|
|
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table,\
|
|
|
|
|
checkpoint_item_data_table, item_table
|
2024-05-05 02:58:49 -04:00
|
|
|
|
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
|
2025-03-16 21:46:34 -04:00
|
|
|
|
sign_location_data_table, car_location_data_table, checkpoint_location_data_table,\
|
|
|
|
|
location_table
|
2024-05-05 02:58:49 -04:00
|
|
|
|
from .Names import ItemName, LocationName
|
2025-03-16 21:46:34 -04:00
|
|
|
|
from .Options import Celeste64Options, celeste_64_option_groups, resolve_options
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Celeste64WebWorld(WebWorld):
|
|
|
|
|
theme = "ice"
|
|
|
|
|
|
|
|
|
|
setup_en = Tutorial(
|
|
|
|
|
tutorial_name="Start Guide",
|
|
|
|
|
description="A guide to playing Celeste 64 in Archipelago.",
|
|
|
|
|
language="English",
|
|
|
|
|
file_name="guide_en.md",
|
|
|
|
|
link="guide/en",
|
|
|
|
|
authors=["PoryGone"]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
tutorials = [setup_en]
|
|
|
|
|
|
2024-05-21 18:22:39 -04:00
|
|
|
|
option_groups = celeste_64_option_groups
|
|
|
|
|
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
|
|
|
|
class Celeste64World(World):
|
|
|
|
|
"""Relive the magic of Celeste Mountain alongside Madeline in this small, heartfelt 3D platformer.
|
|
|
|
|
Created in a week(ish) by the Celeste team to celebrate the game’s sixth anniversary 🍓✨"""
|
|
|
|
|
|
2024-05-05 02:58:49 -04:00
|
|
|
|
# Class Data
|
2024-03-05 18:55:56 -05:00
|
|
|
|
game = "Celeste 64"
|
|
|
|
|
web = Celeste64WebWorld()
|
|
|
|
|
options_dataclass = Celeste64Options
|
|
|
|
|
options: Celeste64Options
|
|
|
|
|
location_name_to_id = location_table
|
|
|
|
|
item_name_to_id = item_table
|
|
|
|
|
|
2024-05-05 02:58:49 -04:00
|
|
|
|
# Instance Data
|
|
|
|
|
strawberries_required: int
|
|
|
|
|
active_logic_mapping: Dict[str, List[List[str]]]
|
2025-03-16 21:46:34 -04:00
|
|
|
|
active_region_logic_mapping: Dict[Tuple[str], List[List[str]]]
|
2024-05-05 02:58:49 -04:00
|
|
|
|
|
2025-03-16 21:46:34 -04:00
|
|
|
|
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)
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
|
|
|
|
def create_item(self, name: str) -> Celeste64Item:
|
|
|
|
|
# Only make required amount of strawberries be Progression
|
2024-05-05 02:58:49 -04:00
|
|
|
|
if getattr(self, "strawberries_required", None) and name == ItemName.strawberry:
|
2024-03-05 18:55:56 -05:00
|
|
|
|
classification: ItemClassification = ItemClassification.filler
|
|
|
|
|
self.prog_strawberries = getattr(self, "prog_strawberries", 0)
|
2024-05-05 02:58:49 -04:00
|
|
|
|
if self.prog_strawberries < self.strawberries_required:
|
2024-03-05 18:55:56 -05:00
|
|
|
|
classification = ItemClassification.progression_skip_balancing
|
|
|
|
|
self.prog_strawberries += 1
|
|
|
|
|
|
|
|
|
|
return Celeste64Item(name, classification, item_data_table[name].code, self.player)
|
|
|
|
|
else:
|
|
|
|
|
return Celeste64Item(name, item_data_table[name].type, item_data_table[name].code, self.player)
|
|
|
|
|
|
|
|
|
|
def create_items(self) -> None:
|
|
|
|
|
item_pool: List[Celeste64Item] = []
|
|
|
|
|
|
2024-05-05 02:58:49 -04:00
|
|
|
|
location_count: int = 30
|
|
|
|
|
|
|
|
|
|
if self.options.friendsanity:
|
|
|
|
|
location_count += 9
|
|
|
|
|
|
|
|
|
|
if self.options.signsanity:
|
|
|
|
|
location_count += 5
|
|
|
|
|
|
|
|
|
|
if self.options.carsanity:
|
|
|
|
|
location_count += 2
|
|
|
|
|
|
|
|
|
|
item_pool += [self.create_item(name)
|
|
|
|
|
for name in unlockable_item_data_table.keys()
|
|
|
|
|
if name not in self.options.start_inventory]
|
|
|
|
|
|
2025-03-16 21:46:34 -04:00
|
|
|
|
chosen_start_item: str = ""
|
2024-05-05 02:58:49 -04:00
|
|
|
|
|
2025-03-16 21:46:34 -04:00
|
|
|
|
if self.options.move_shuffle:
|
2024-05-05 02:58:49 -04:00
|
|
|
|
if self.options.logic_difficulty == "standard":
|
2025-03-16 21:46:34 -04:00
|
|
|
|
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)
|
2024-05-05 02:58:49 -04:00
|
|
|
|
|
|
|
|
|
if self.options.carsanity:
|
|
|
|
|
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player)
|
2025-03-16 21:46:34 -04:00
|
|
|
|
intro_car_loc.place_locked_item(self.create_item(chosen_start_item))
|
2024-05-05 02:58:49 -04:00
|
|
|
|
location_count -= 1
|
|
|
|
|
else:
|
2025-03-16 21:46:34 -04:00
|
|
|
|
self.multiworld.push_precollected(self.create_item(chosen_start_item))
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
2024-05-05 02:58:49 -04:00
|
|
|
|
item_pool += [self.create_item(name)
|
2025-03-16 21:46:34 -04:00
|
|
|
|
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))
|
2024-05-05 02:58:49 -04:00
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
item_pool += [self.create_item(ItemName.strawberry) for _ in range(real_total_strawberries)]
|
|
|
|
|
|
|
|
|
|
filler_item_count: int = location_count - len(item_pool)
|
|
|
|
|
item_pool += [self.create_item(ItemName.raspberry) for _ in range(filler_item_count)]
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
|
|
|
|
self.multiworld.itempool += item_pool
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_regions(self) -> None:
|
|
|
|
|
from .Regions import region_data_table
|
|
|
|
|
# Create regions.
|
|
|
|
|
for region_name in region_data_table.keys():
|
|
|
|
|
region = Region(region_name, self.player, self.multiworld)
|
|
|
|
|
self.multiworld.regions.append(region)
|
|
|
|
|
|
|
|
|
|
# Create locations.
|
|
|
|
|
for region_name, region_data in region_data_table.items():
|
|
|
|
|
region = self.multiworld.get_region(region_name, self.player)
|
|
|
|
|
region.add_locations({
|
2024-05-05 02:58:49 -04:00
|
|
|
|
location_name: location_data.address for location_name, location_data in strawberry_location_data_table.items()
|
2024-03-05 18:55:56 -05:00
|
|
|
|
if location_data.region == region_name
|
|
|
|
|
}, Celeste64Location)
|
2024-05-05 02:58:49 -04:00
|
|
|
|
|
|
|
|
|
if self.options.friendsanity:
|
|
|
|
|
region.add_locations({
|
|
|
|
|
location_name: location_data.address for location_name, location_data in friend_location_data_table.items()
|
|
|
|
|
if location_data.region == region_name
|
|
|
|
|
}, Celeste64Location)
|
|
|
|
|
|
|
|
|
|
if self.options.signsanity:
|
|
|
|
|
region.add_locations({
|
|
|
|
|
location_name: location_data.address for location_name, location_data in sign_location_data_table.items()
|
|
|
|
|
if location_data.region == region_name
|
|
|
|
|
}, Celeste64Location)
|
|
|
|
|
|
|
|
|
|
if self.options.carsanity:
|
|
|
|
|
region.add_locations({
|
|
|
|
|
location_name: location_data.address for location_name, location_data in car_location_data_table.items()
|
|
|
|
|
if location_data.region == region_name
|
|
|
|
|
}, Celeste64Location)
|
|
|
|
|
|
2025-03-16 21:46:34 -04:00
|
|
|
|
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)
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
2025-03-16 21:46:34 -04:00
|
|
|
|
from .Rules import connect_region
|
|
|
|
|
connect_region(self, region, region_data_table[region_name].connecting_regions)
|
2024-03-05 18:55:56 -05:00
|
|
|
|
|
2025-03-16 21:46:34 -04:00
|
|
|
|
# Have to do this here because of other games using State in a way that's bad
|
2024-03-05 18:55:56 -05:00
|
|
|
|
from .Rules import set_rules
|
|
|
|
|
set_rules(self)
|
|
|
|
|
|
|
|
|
|
|
2025-03-16 21:46:34 -04:00
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
|
|
|
return ItemName.raspberry
|
|
|
|
|
|
|
|
|
|
|
2024-03-05 18:55:56 -05:00
|
|
|
|
def fill_slot_data(self):
|
|
|
|
|
return {
|
|
|
|
|
"death_link": self.options.death_link.value,
|
|
|
|
|
"death_link_amnesty": self.options.death_link_amnesty.value,
|
2024-05-05 02:58:49 -04:00
|
|
|
|
"strawberries_required": self.strawberries_required,
|
|
|
|
|
"move_shuffle": self.options.move_shuffle.value,
|
|
|
|
|
"friendsanity": self.options.friendsanity.value,
|
|
|
|
|
"signsanity": self.options.signsanity.value,
|
|
|
|
|
"carsanity": self.options.carsanity.value,
|
2025-03-16 21:46:34 -04:00
|
|
|
|
"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,
|
2024-05-05 02:58:49 -04:00
|
|
|
|
"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,
|
2024-03-05 18:55:56 -05:00
|
|
|
|
}
|