184 lines
7.8 KiB
Python
184 lines
7.8 KiB
Python
|
import os
|
||
|
import pkgutil
|
||
|
import typing
|
||
|
import settings
|
||
|
from BaseClasses import Tutorial, ItemClassification
|
||
|
from worlds.AutoWorld import WebWorld, World
|
||
|
from typing import List, Dict, Any
|
||
|
from .Locations import all_locations, location_table, bowsers, bowsersMini, hidden, coins
|
||
|
from .Options import MLSSOptions
|
||
|
from .Items import MLSSItem, itemList, item_frequencies, item_table
|
||
|
from .Names.LocationName import LocationName
|
||
|
from .Client import MLSSClient
|
||
|
from .Regions import create_regions, connect_regions
|
||
|
from .Rom import MLSSProcedurePatch, write_tokens
|
||
|
from .Rules import set_rules
|
||
|
|
||
|
|
||
|
class MLSSWebWorld(WebWorld):
|
||
|
theme = "partyTime"
|
||
|
bug_report_page = "https://github.com/jamesbrq/ArchipelagoMLSS/issues"
|
||
|
tutorials = [
|
||
|
Tutorial(
|
||
|
tutorial_name="Setup Guide",
|
||
|
description="A guide to setting up Mario & Luigi: Superstar Saga for Archipelago.",
|
||
|
language="English",
|
||
|
file_name="setup_en.md",
|
||
|
link="setup/en",
|
||
|
authors=["jamesbrq"],
|
||
|
)
|
||
|
]
|
||
|
|
||
|
|
||
|
class MLSSSettings(settings.Group):
|
||
|
class RomFile(settings.UserFilePath):
|
||
|
"""File name of the MLSS US rom"""
|
||
|
|
||
|
copy_to = "Mario & Luigi - Superstar Saga (U).gba"
|
||
|
description = "MLSS ROM File"
|
||
|
md5s = ["4b1a5897d89d9e74ec7f630eefdfd435"]
|
||
|
|
||
|
rom_file: RomFile = RomFile(RomFile.copy_to)
|
||
|
rom_start: bool = True
|
||
|
|
||
|
|
||
|
class MLSSWorld(World):
|
||
|
"""
|
||
|
Adventure with Mario and Luigi together in the Beanbean Kingdom
|
||
|
to stop the evil Cackletta and retrieve the Beanstar.
|
||
|
"""
|
||
|
|
||
|
game = "Mario & Luigi Superstar Saga"
|
||
|
web = MLSSWebWorld()
|
||
|
options_dataclass = MLSSOptions
|
||
|
options: MLSSOptions
|
||
|
settings: typing.ClassVar[MLSSSettings]
|
||
|
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||
|
location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations}
|
||
|
required_client_version = (0, 4, 5)
|
||
|
|
||
|
disabled_locations: List[str]
|
||
|
|
||
|
def generate_early(self) -> None:
|
||
|
self.disabled_locations = []
|
||
|
if self.options.chuckle_beans == 0:
|
||
|
self.disabled_locations += [location.name for location in all_locations if "Digspot" in location.name]
|
||
|
if self.options.castle_skip:
|
||
|
self.disabled_locations += [location.name for location in all_locations if "Bowser" in location.name]
|
||
|
if self.options.chuckle_beans == 1:
|
||
|
self.disabled_locations = [location.name for location in all_locations if location.id in hidden]
|
||
|
if self.options.skip_minecart:
|
||
|
self.disabled_locations += [LocationName.HoohooMountainBaseMinecartCaveDigspot]
|
||
|
if self.options.disable_surf:
|
||
|
self.disabled_locations += [LocationName.SurfMinigame]
|
||
|
if self.options.harhalls_pants:
|
||
|
self.disabled_locations += [LocationName.HarhallsPants]
|
||
|
if not self.options.coins:
|
||
|
self.disabled_locations += [location.name for location in all_locations if location in coins]
|
||
|
|
||
|
def create_regions(self) -> None:
|
||
|
create_regions(self, self.disabled_locations)
|
||
|
connect_regions(self)
|
||
|
|
||
|
item = self.create_item("Mushroom")
|
||
|
self.get_location(LocationName.ShopStartingFlag1).place_locked_item(item)
|
||
|
item = self.create_item("Syrup")
|
||
|
self.get_location(LocationName.ShopStartingFlag2).place_locked_item(item)
|
||
|
item = self.create_item("1-UP Mushroom")
|
||
|
self.get_location(LocationName.ShopStartingFlag3).place_locked_item(item)
|
||
|
item = self.create_item("Hoo Bean")
|
||
|
self.get_location(LocationName.PantsShopStartingFlag1).place_locked_item(item)
|
||
|
item = self.create_item("Chuckle Bean")
|
||
|
self.get_location(LocationName.PantsShopStartingFlag2).place_locked_item(item)
|
||
|
|
||
|
def fill_slot_data(self) -> Dict[str, Any]:
|
||
|
return {
|
||
|
"CastleSkip": self.options.castle_skip.value,
|
||
|
"SkipMinecart": self.options.skip_minecart.value,
|
||
|
"DisableSurf": self.options.disable_surf.value,
|
||
|
"HarhallsPants": self.options.harhalls_pants.value,
|
||
|
"ChuckleBeans": self.options.chuckle_beans.value,
|
||
|
"DifficultLogic": self.options.difficult_logic.value,
|
||
|
"Coins": self.options.coins.value,
|
||
|
}
|
||
|
|
||
|
def create_items(self) -> None:
|
||
|
# First add in all progression and useful items
|
||
|
required_items = []
|
||
|
precollected = [item for item in itemList if item in self.multiworld.precollected_items]
|
||
|
for item in itemList:
|
||
|
if item.classification != ItemClassification.filler and item.classification != ItemClassification.skip_balancing:
|
||
|
freq = item_frequencies.get(item.itemName, 1)
|
||
|
if item in precollected:
|
||
|
freq = max(freq - precollected.count(item), 0)
|
||
|
if self.options.harhalls_pants and "Harhall's" in item.itemName:
|
||
|
continue
|
||
|
required_items += [item.itemName for _ in range(freq)]
|
||
|
|
||
|
for itemName in required_items:
|
||
|
self.multiworld.itempool.append(self.create_item(itemName))
|
||
|
|
||
|
# Then, create our list of filler items
|
||
|
filler_items = []
|
||
|
for item in itemList:
|
||
|
if item.classification != ItemClassification.filler:
|
||
|
continue
|
||
|
if item.itemName == "5 Coins" and not self.options.coins:
|
||
|
continue
|
||
|
freq = item_frequencies.get(item.itemName, 1)
|
||
|
if self.options.chuckle_beans == 0:
|
||
|
if item.itemName == "Chuckle Bean":
|
||
|
continue
|
||
|
if self.options.chuckle_beans == 1:
|
||
|
if item.itemName == "Chuckle Bean":
|
||
|
freq -= 59
|
||
|
filler_items += [item.itemName for _ in range(freq)]
|
||
|
|
||
|
# And finally take as many fillers as we need to have the same amount of items and locations.
|
||
|
remaining = len(all_locations) - len(required_items) - 5
|
||
|
if self.options.castle_skip:
|
||
|
remaining -= len(bowsers) + len(bowsersMini) - (5 if self.options.chuckle_beans == 0 else 0)
|
||
|
if self.options.skip_minecart and self.options.chuckle_beans == 2:
|
||
|
remaining -= 1
|
||
|
if self.options.disable_surf:
|
||
|
remaining -= 1
|
||
|
if self.options.harhalls_pants:
|
||
|
remaining -= 1
|
||
|
if self.options.chuckle_beans == 0:
|
||
|
remaining -= 192
|
||
|
if self.options.chuckle_beans == 1:
|
||
|
remaining -= 59
|
||
|
if not self.options.coins:
|
||
|
remaining -= len(coins)
|
||
|
|
||
|
self.multiworld.itempool += [
|
||
|
self.create_item(filler_item_name) for filler_item_name in self.random.sample(filler_items, remaining)
|
||
|
]
|
||
|
|
||
|
def set_rules(self) -> None:
|
||
|
set_rules(self, self.disabled_locations)
|
||
|
if self.options.castle_skip:
|
||
|
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(
|
||
|
"PostJokes", "Region", self.player
|
||
|
)
|
||
|
else:
|
||
|
self.multiworld.completion_condition[self.player] = lambda state: state.can_reach(
|
||
|
"Bowser's Castle Mini", "Region", self.player
|
||
|
)
|
||
|
|
||
|
def create_item(self, name: str) -> MLSSItem:
|
||
|
item = item_table[name]
|
||
|
return MLSSItem(item.itemName, item.classification, item.code, self.player)
|
||
|
|
||
|
def get_filler_item_name(self) -> str:
|
||
|
return self.random.choice(list(filter(lambda item: item.classification == ItemClassification.filler, itemList)))
|
||
|
|
||
|
def generate_output(self, output_directory: str) -> None:
|
||
|
patch = MLSSProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
||
|
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/basepatch.bsdiff"))
|
||
|
write_tokens(self, patch)
|
||
|
rom_path = os.path.join(
|
||
|
output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}" f"{patch.patch_file_ending}"
|
||
|
)
|
||
|
patch.write(rom_path)
|