forked from mirror/Archipelago
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
397 lines
21 KiB
Python
397 lines
21 KiB
Python
import json
|
|
import pkgutil
|
|
import typing
|
|
from typing import Mapping, Any, Optional
|
|
|
|
from BaseClasses import Region, LocationProgressType, Item, CollectionState, ItemClassification, Tutorial
|
|
from worlds.AutoWorld import World, WebWorld
|
|
from worlds.generic.Rules import set_rule
|
|
|
|
from .data import (NoruneGeoItems, MatatakiGeoItems, QueensGeoItems,
|
|
MuskaGeoItems, FactoryGeoItems, DHCGeoItems)
|
|
from . import Rules
|
|
from .Items import DarkCloudItem, ItemData
|
|
from .Location import DarkCloudLocation
|
|
from .Options import DarkCloudOptions
|
|
from .data.MiracleChest import MiracleChest
|
|
from .game_id import dc1_name
|
|
|
|
geo_funcs = [NoruneGeoItems.create_norune_atla, MatatakiGeoItems.create_matataki_atla,
|
|
QueensGeoItems.create_queens_atla, MuskaGeoItems.create_muska_atla,
|
|
FactoryGeoItems.create_factory_atla, DHCGeoItems.create_castle_atla]
|
|
geo_class = [NoruneGeoItems, MatatakiGeoItems, QueensGeoItems, MuskaGeoItems, FactoryGeoItems, DHCGeoItems]
|
|
|
|
dungeon_locations = json.loads(pkgutil.get_data(__name__, "data/atla_locations.json").decode())
|
|
|
|
# TODO webworld implementation as we get closer to completion.
|
|
class DarkCloudWeb(WebWorld):
|
|
theme = "jungle"
|
|
tutorials = [Tutorial(
|
|
"Multiworld Setup Guide",
|
|
"A guide to setting up the Super Mario Sunshine software on your computer. This guide covers single-player, "
|
|
"multiworld, and related software.",
|
|
"English",
|
|
"setup_en.md",
|
|
"setup/en",
|
|
["gyst"]
|
|
)]
|
|
bug_report_page = ""
|
|
|
|
|
|
class DarkCloudWorld(World):
|
|
"""
|
|
It is said superblal is still stuck in this game to this day.
|
|
"""
|
|
game = dc1_name
|
|
required_client_version = (0, 6, 1)
|
|
options_dataclass = Options.DarkCloudOptions
|
|
options: Options.DarkCloudOptions
|
|
topology_present = True
|
|
web = DarkCloudWeb()
|
|
|
|
item_name_to_id = {}
|
|
location_name_to_id = {}
|
|
item_name_to_classification = {}
|
|
filler_item_names = []
|
|
|
|
# Parse inventory item data
|
|
item_data = [[],[],[],[],[]]
|
|
reader = pkgutil.get_data(__name__, "data/item_data.csv").decode().splitlines()
|
|
for line in reader:
|
|
row = line.split(",")
|
|
item_name_to_id.update({row[0]: int(row[1])})
|
|
classification = ItemClassification(int(row[2]))
|
|
if classification == ItemClassification.filler:
|
|
filler_item_names.append(row[0])
|
|
|
|
# [3]-[7] are counts if an item should be added for a given town 0-5.
|
|
counts = []
|
|
for i in range(3, 8):
|
|
if row[i]:
|
|
counts.append(int(row[i]))
|
|
else:
|
|
counts.append(0)
|
|
|
|
item_data[0].append(ItemData(row[0], int(row[1]), classification, counts))
|
|
|
|
item_name_to_classification[row[0]] = classification
|
|
|
|
|
|
for i in geo_class:
|
|
item_name_to_id.update(i.ids)
|
|
item_name_to_classification.update(i.classifications)
|
|
|
|
for i in dungeon_locations:
|
|
location_name_to_id.update(i)
|
|
|
|
# Parse in the miracle chest data
|
|
mc_data = [[], [], [], [], []]
|
|
reader = pkgutil.get_data(__name__, "data/miracle_locations.csv").decode().splitlines()
|
|
for line in reader:
|
|
row = line.split(",")
|
|
mc_data[int(row[2])].append(MiracleChest(row[0], row[1], row[2], row[3], row[4]))
|
|
|
|
for i in mc_data:
|
|
for j in i:
|
|
location_name_to_id.update({str(j.name): int(j.ap_id)})
|
|
|
|
origin_region_name = "Norune"
|
|
|
|
def create_items(self):
|
|
for i in range(self.options.boss_goal):
|
|
self.multiworld.itempool.extend(geo_funcs[i](self.options, self.player))
|
|
|
|
if self.options.miracle_sanity:
|
|
for i in range(min(5, int(self.options.boss_goal))):
|
|
for item in self.item_data[i]:
|
|
self.multiworld.itempool.extend(item.to_items(self.player, self))
|
|
|
|
# Set up progressive items
|
|
def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]:
|
|
if not item.advancement:
|
|
return None
|
|
name = item.name
|
|
if name.startswith("Progressive "):
|
|
prog_table = Items.progressive_item_list[name]
|
|
if remove:
|
|
for item_name in reversed(prog_table):
|
|
if state.has(item_name, item.player):
|
|
return item_name
|
|
else:
|
|
for item_name in prog_table:
|
|
if not state.has(item_name, item.player):
|
|
return item_name
|
|
|
|
return super(DarkCloudWorld, self).collect_item(state, item, remove)
|
|
|
|
def create_regions(self):
|
|
regions: typing.Dict[str, Region] = {}
|
|
|
|
# Towns
|
|
norune = Region("Norune", self.player, self.multiworld)
|
|
matataki = Region("Matataki", self.player, self.multiworld)
|
|
queens = Region("Queens", self.player, self.multiworld)
|
|
muska = Region("Muska", self.player, self.multiworld)
|
|
factory = Region("Factory", self.player, self.multiworld)
|
|
dhc = Region("DHC", self.player, self.multiworld)
|
|
|
|
# Dungeons
|
|
dbc1 = Region("DBC1", self.player, self.multiworld)
|
|
dbc2 = Region("DBC2", self.player, self.multiworld)
|
|
wof1 = Region("WOF1", self.player, self.multiworld)
|
|
wof2 = Region("WOF2", self.player, self.multiworld)
|
|
sr1 = Region("SR1", self.player, self.multiworld)
|
|
sr2 = Region("SR2", self.player, self.multiworld)
|
|
smt1 = Region("SMT1", self.player, self.multiworld)
|
|
smt2 = Region("SMT2", self.player, self.multiworld)
|
|
ms1 = Region("MS1", self.player, self.multiworld)
|
|
ms2 = Region("MS2", self.player, self.multiworld)
|
|
got = Region("GOT", self.player, self.multiworld)
|
|
|
|
towns = [norune, matataki, queens, muska, factory, dhc]
|
|
dungeons = [dbc1, dbc2, wof1, wof2, sr1, sr2, smt1, smt2, ms1, ms2, got]
|
|
|
|
for town in towns:
|
|
regions[town.name] = town
|
|
|
|
for dungeon in dungeons:
|
|
regions[dungeon.name] = dungeon
|
|
|
|
# Only add locations for the relevant dungeons
|
|
for i in range(min(len(dungeons), self.options.boss_goal * 2)):
|
|
dun = dungeons[i]
|
|
dun_locs = dungeon_locations[i]
|
|
|
|
# Create locations, then add to the dungeons
|
|
for key in dun_locs:
|
|
loc = DarkCloudLocation(self.player, key, dun_locs[key], LocationProgressType.DEFAULT, dun)
|
|
dun.locations.append(loc)
|
|
|
|
if self.options.miracle_sanity:
|
|
for i in range(min(5, int(self.options.boss_goal))):
|
|
mcs = self.mc_data[i]
|
|
for chest in mcs:
|
|
loc = DarkCloudLocation(self.player, str(chest.name), int(chest.ap_id),
|
|
LocationProgressType.DEFAULT, towns[i])
|
|
loc.access_rule = lambda state, a=chest.req_char, b=chest.req_geo: Rules.chest_test(state,
|
|
self.player, a,
|
|
b)
|
|
towns[i].locations.append(loc)
|
|
|
|
# Connect Regions
|
|
def create_connection(from_region: str, to_region: str):
|
|
regions[from_region].connect(regions[to_region])
|
|
|
|
create_connection("Norune", "Matataki")
|
|
create_connection("Norune", "Queens")
|
|
create_connection("Norune", "Muska")
|
|
create_connection("Norune", "Factory")
|
|
create_connection("Norune", "DHC")
|
|
|
|
create_connection("Norune", "DBC1")
|
|
create_connection("Norune", "DBC2")
|
|
|
|
create_connection("Matataki", "WOF1")
|
|
create_connection("Matataki", "WOF2")
|
|
|
|
create_connection("Queens", "SR1")
|
|
create_connection("Queens", "SR2")
|
|
|
|
create_connection("Muska", "SMT1")
|
|
create_connection("Muska", "SMT2")
|
|
|
|
create_connection("Factory", "MS1")
|
|
create_connection("Factory", "MS2")
|
|
|
|
create_connection("DHC", "GOT")
|
|
|
|
self.multiworld.regions.extend(towns)
|
|
self.multiworld.regions.extend(dungeons)
|
|
|
|
def set_rules(self):
|
|
|
|
set_rule(self.multiworld.get_entrance("Norune -> DBC1", self.player), lambda state: True)
|
|
|
|
if hasattr(self.multiworld, "generation_is_fake"): # UT doesn't need the shop for its logic
|
|
set_rule(self.multiworld.get_entrance("Norune -> DBC2", self.player),
|
|
lambda state: Rules.xiao_available_ut(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Matataki", self.player),
|
|
lambda state: Rules.xiao_available_ut(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Matataki -> WOF1", self.player),
|
|
lambda state: Rules.xiao_available_ut(state, self.player))
|
|
else:
|
|
set_rule(self.multiworld.get_entrance("Norune -> DBC2", self.player),
|
|
lambda state: Rules.xiao_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Matataki", self.player),
|
|
lambda state: Rules.xiao_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Matataki -> WOF1", self.player),
|
|
lambda state: Rules.xiao_available(state, self.player))
|
|
|
|
if self.options.miracle_sanity and not hasattr(self.multiworld, "generation_is_fake"):
|
|
set_rule(self.multiworld.get_entrance("Matataki -> WOF2", self.player),
|
|
lambda state: Rules.goro_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Queens", self.player),
|
|
lambda state: Rules.goro_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Queens -> SR1", self.player),
|
|
lambda state: Rules.goro_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Queens -> SR2", self.player),
|
|
lambda state: Rules.ruby_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Muska", self.player),
|
|
lambda state: Rules.ruby_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Muska -> SMT1", self.player),
|
|
lambda state: Rules.ruby_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Muska -> SMT2", self.player),
|
|
lambda state: Rules.ungaga_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Factory", self.player),
|
|
lambda state: Rules.ungaga_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Factory -> MS1", self.player),
|
|
lambda state: Rules.ungaga_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Factory -> MS2", self.player),
|
|
lambda state: Rules.osmond_available_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> DHC", self.player),
|
|
lambda state: Rules.got_accessible_items(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("DHC -> GOT", self.player),
|
|
lambda state: Rules.got_accessible_items(state, self.player))
|
|
else:
|
|
set_rule(self.multiworld.get_entrance("Matataki -> WOF2", self.player),
|
|
lambda state: Rules.goro_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Queens", self.player),
|
|
lambda state: Rules.goro_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Queens -> SR1", self.player),
|
|
lambda state: Rules.goro_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Queens -> SR2", self.player),
|
|
lambda state: Rules.ruby_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Muska", self.player),
|
|
lambda state: Rules.ruby_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Muska -> SMT1", self.player),
|
|
lambda state: Rules.ruby_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Muska -> SMT2", self.player),
|
|
lambda state: Rules.ungaga_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> Factory", self.player),
|
|
lambda state: Rules.ungaga_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Factory -> MS1", self.player),
|
|
lambda state: Rules.ungaga_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Factory -> MS2", self.player),
|
|
lambda state: Rules.osmond_available(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("Norune -> DHC", self.player),
|
|
lambda state: Rules.got_accessible(state, self.player))
|
|
set_rule(self.multiworld.get_entrance("DHC -> GOT", self.player),
|
|
lambda state: Rules.got_accessible(state, self.player))
|
|
|
|
# Set up completion goal
|
|
match self.options.boss_goal:
|
|
case 2:
|
|
if self.options.all_bosses:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.two_bosses_items(state,
|
|
self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.two_bosses(state,
|
|
self.player)
|
|
else:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.utan_access(state,
|
|
self.player)\
|
|
and Rules.goro_available_items(state, self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.utan_access(state,
|
|
self.player)\
|
|
and Rules.goro_available(state, self.player)
|
|
case 3:
|
|
if self.options.all_bosses:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.three_bosses_items(state,
|
|
self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.three_bosses(state,
|
|
self.player)
|
|
else:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.saia_access(state,
|
|
self.player)\
|
|
and Rules.ruby_available_items(state, self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.saia_access(state,
|
|
self.player)\
|
|
and Rules.ruby_available(state, self.player)
|
|
case 4:
|
|
if self.options.all_bosses:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.four_bosses_items(state,
|
|
self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.four_bosses(state,
|
|
self.player)
|
|
else:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.curse_access(state,
|
|
self.player)\
|
|
and Rules.ungaga_available_items(state, self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.curse_access(state,
|
|
self.player)\
|
|
and Rules.ungaga_available(state, self.player)
|
|
case 5:
|
|
if self.options.all_bosses:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.five_bosses_items(state,
|
|
self.player)
|
|
else:
|
|
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.five_bosses(state,
|
|
self.player)
|
|
else:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.joe_access(state,
|
|
self.player)\
|
|
and Rules.osmond_available_items(
|
|
state, self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.joe_access(state,
|
|
self.player)\
|
|
and Rules.osmond_available(
|
|
state, self.player)
|
|
case 6:
|
|
if self.options.all_bosses:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.six_bosses_items(state,
|
|
self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.six_bosses(state,
|
|
self.player)
|
|
else:
|
|
if self.options.miracle_sanity:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.genie_access(state,
|
|
self.player) \
|
|
and Rules.osmond_available_items(
|
|
state, self.player)
|
|
else:
|
|
self.multiworld.completion_condition[self.player] = lambda state: Rules.genie_access(state,
|
|
self.player) \
|
|
and Rules.osmond_available(
|
|
state, self.player)
|
|
def create_item(self, name:str) -> DarkCloudItem:
|
|
classification = self.item_name_to_classification[name]
|
|
return DarkCloudItem(name, classification, self.item_name_to_id[name], self.player)
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
return self.filler_item_names[self.random.randint(0, len(self.filler_item_names) - 1)]
|
|
|
|
def fill_slot_data(self) -> Mapping[str, Any]:
|
|
slot_data = {
|
|
"options": {
|
|
"goal": self.options.boss_goal.value,
|
|
"all_bosses": self.options.all_bosses.value,
|
|
"open_dungeon": self.options.open_dungeon.value,
|
|
"starter_weapons": self.options.starter_weapons.value,
|
|
"abs_multiplier": self.options.abs_multiplier.value,
|
|
"attach_multiplier": self.options.attach_multiplier.value,
|
|
"attach_mult_config": self.options.attach_mult_config.value,
|
|
"auto_build": self.options.auto_build.value,
|
|
"miracle_sanity": self.options.miracle_sanity.value,
|
|
"death_link": self.options.death_link.value,
|
|
},
|
|
}
|
|
|
|
return slot_data
|