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
821 lines
39 KiB
Python
821 lines
39 KiB
Python
import math
|
|
import threading
|
|
import typing
|
|
import os
|
|
import pkgutil
|
|
import settings
|
|
import random
|
|
from typing import List, Dict, Set, Any
|
|
|
|
from .Items import (
|
|
EOSItem,
|
|
item_table,
|
|
item_frequencies,
|
|
item_table_by_id,
|
|
item_table_by_groups,
|
|
filler_item_table,
|
|
filler_item_weights,
|
|
trap_item_table,
|
|
trap_item_weights,
|
|
exclusive_filler_item_table,
|
|
exclusive_filler_item_weights,
|
|
legendary_pool_dict,
|
|
filler_items,
|
|
exclusive_filler_items,
|
|
conditional_filler_useful_items,
|
|
)
|
|
from .Locations import EOS_location_table, EOSLocation, location_Dict_by_id, expanded_EOS_location_table
|
|
from .Options import EOSOptions
|
|
from .Rules import set_rules, ready_for_late_game, has_relic_shards
|
|
from BaseClasses import Tutorial, ItemClassification, Region, Location, LocationProgressType, Item
|
|
from worlds.AutoWorld import World, WebWorld
|
|
from worlds.generic.Rules import set_rule
|
|
from .Client import EoSClient, game_version
|
|
from .Rom import EOSProcedurePatch, write_tokens
|
|
|
|
|
|
class EOSWeb(WebWorld):
|
|
theme = "ocean"
|
|
game = "Pokemon Mystery Dungeon Explorers of Sky"
|
|
|
|
tutorials = [
|
|
Tutorial(
|
|
"Multiworld Setup Guide",
|
|
"A Guide to setting up Explorers of Sky for MultiWorld.",
|
|
"English",
|
|
"setup_en.md",
|
|
"setup/en",
|
|
["CrypticMonkey33", "Chesyon"],
|
|
)
|
|
]
|
|
|
|
|
|
class EOSSettings(settings.Group):
|
|
class RomFile(settings.UserFilePath):
|
|
"""File name of the EoS EU rom"""
|
|
|
|
copy_to = "POKEDUN_SORA_C2SP01_00.nds"
|
|
description = "Explorers of Sky (EU) ROM File"
|
|
md5s = ["6735749e060e002efd88e61560e45567"]
|
|
|
|
rom_file: RomFile = RomFile(RomFile.copy_to)
|
|
rom_start: bool = True
|
|
|
|
|
|
class EOSWorld(World):
|
|
"""
|
|
This is for Pokemon Mystery Dungeon Explorers of Sky, a game where you inhabit a pokemon and explore
|
|
through dungeons, solve quests, and help out other Pokemon in the colony
|
|
"""
|
|
|
|
game = "Pokemon Mystery Dungeon Explorers of Sky"
|
|
options: EOSOptions
|
|
options_dataclass = EOSOptions
|
|
web = EOSWeb()
|
|
settings: typing.ClassVar[EOSSettings]
|
|
|
|
item_name_to_id = {item.name: item.id for item in item_table.values()}
|
|
location_name_to_id = {location.name: location.id for location in expanded_EOS_location_table}
|
|
|
|
required_client_version = (0, 6, 1)
|
|
required_server_version = (0, 6, 1)
|
|
|
|
item_name_groups = item_table_by_groups
|
|
disabled_locations: Set[str] = []
|
|
extra_locations_added = 0
|
|
mission_start_id = 1000
|
|
excluded_locations = 0
|
|
dimensional_scream_list = []
|
|
dimensional_scream_list_ints: list[int] = []
|
|
starting_se: int = 0
|
|
slot_data_ready = threading.Event
|
|
excluded_tag_amount: int = 0
|
|
|
|
def get_filler_item_name(self) -> str:
|
|
"""Called when the item pool needs to be filled with additional items to match location count."""
|
|
# logging.warning(f"World {self} is generating a filler item without custom filler pool.")
|
|
return self.multiworld.random.choice(tuple(filler_item_table.keys()))
|
|
|
|
def generate_early(self) -> None:
|
|
self.slot_data_ready = threading.Event()
|
|
if self.options.bag_on_start.value:
|
|
item_name = "Bag Upgrade"
|
|
self.multiworld.push_precollected(self.create_item(item_name))
|
|
if self.options.hero_evolution.value:
|
|
item_name = "Hero Evolution"
|
|
self.multiworld.push_precollected(self.create_item(item_name))
|
|
if self.options.recruit_evo.value:
|
|
item_name = "Recruit Evolution"
|
|
self.multiworld.push_precollected(self.create_item(item_name))
|
|
if self.options.dojo_dungeons.value > 0:
|
|
dojo_amount = self.options.dojo_dungeons.value
|
|
dojo_table = item_table_by_groups["Dojo Dungeons"]
|
|
random_open_dungeons = self.random.sample(sorted(dojo_table), k=dojo_amount)
|
|
for item_name in random_open_dungeons:
|
|
self.multiworld.push_precollected(self.create_item(item_name))
|
|
if self.options.recruit.value:
|
|
item_name = "Recruitment"
|
|
self.multiworld.push_precollected(self.create_item(item_name))
|
|
if self.options.team_form.value:
|
|
item_name = "Formation Control"
|
|
self.multiworld.push_precollected(self.create_item(item_name))
|
|
if self.options.special_episode_sanity.value and not self.options.exclude_special.value:
|
|
possible_se = [
|
|
"Bidoof's Wish",
|
|
"Igglybuff the Prodigy",
|
|
'Today\'s "Oh My Gosh"',
|
|
"Here Comes Team Charm!",
|
|
"In the Future of Darkness",
|
|
]
|
|
se_num = self.random.randint(0, 4)
|
|
self.starting_se = se_num
|
|
item_name = possible_se[se_num]
|
|
self.multiworld.push_precollected(self.create_item(item_name))
|
|
else:
|
|
self.multiworld.push_precollected(self.create_item("Main Game Unlock"))
|
|
|
|
def create_regions(self) -> None:
|
|
menu_region = Region("Menu", self.player, self.multiworld)
|
|
self.multiworld.regions.append(menu_region)
|
|
|
|
early_dungeons_region = Region("Early Dungeons", self.player, self.multiworld)
|
|
self.multiworld.regions.append(early_dungeons_region)
|
|
|
|
# early_dungeons_region2 = Region("Early Dungeons2", self.player, self.multiworld)
|
|
# self.multiworld.regions.append(early_dungeons_region2)
|
|
|
|
late_dungeons_region = Region("Late Dungeons", self.player, self.multiworld)
|
|
self.multiworld.regions.append(late_dungeons_region)
|
|
|
|
end_game_region = Region("Boss Dungeons", self.player, self.multiworld)
|
|
self.multiworld.regions.append(end_game_region)
|
|
|
|
extra_items_region = Region("Extra Items", self.player, self.multiworld)
|
|
self.multiworld.regions.append(extra_items_region)
|
|
|
|
rule_dungeons_region = Region("Rule Dungeons", self.player, self.multiworld)
|
|
self.multiworld.regions.append(rule_dungeons_region)
|
|
|
|
for location in EOS_location_table:
|
|
if location.name == "Beach Cave":
|
|
menu_region.locations.append(EOSLocation(self.player, location.name, location.id, menu_region))
|
|
if "Mission" in location.group:
|
|
for j in range(self.options.early_mission_checks.value):
|
|
location_name: str = f"{location.name} Mission {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j
|
|
menu_region.locations.append(EOSLocation(self.player, location_name, location_id, menu_region))
|
|
|
|
self.extra_locations_added += 1
|
|
for j in range(self.options.early_outlaw_checks.value):
|
|
location_name = f"{location.name} Outlaw {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j + 50
|
|
menu_region.locations.append(EOSLocation(self.player, location_name, location_id, menu_region))
|
|
|
|
self.extra_locations_added += 1
|
|
elif location.classification == "Free":
|
|
menu_region.locations.append(EOSLocation(self.player, location.name, location.id, menu_region))
|
|
elif location.name == "Team Name Location":
|
|
menu_region.locations.append(EOSLocation(self.player, location.name, location.id, menu_region))
|
|
elif location.classification == "Rank":
|
|
rank_toid_dict = {
|
|
"Bronze Rank": 1,
|
|
"Silver Rank": 2,
|
|
"Gold Rank": 3,
|
|
"Diamond Rank": 4,
|
|
"Super Rank": 5,
|
|
"Ultra Rank": 6,
|
|
"Hyper Rank": 7,
|
|
"Master Rank": 8,
|
|
"Master ★ Rank": 9,
|
|
"Master ★★ Rank": 10,
|
|
"Master ★★★ Rank": 11,
|
|
"Guildmaster Rank": 12,
|
|
}
|
|
if rank_toid_dict[location.name] > self.options.max_rank:
|
|
# self.excluded_locations += 1
|
|
# continue
|
|
early_dungeon = EOSLocation(self.player, location.name, location.id, early_dungeons_region)
|
|
early_dungeon.progress_type = LocationProgressType.EXCLUDED
|
|
self.excluded_tag_amount += 1
|
|
early_dungeons_region.locations.append(early_dungeon)
|
|
|
|
# if dialga is the goal, we can't add master star rank+
|
|
elif self.options.goal.value == 0 and rank_toid_dict[location.name] > 8:
|
|
# self.excluded_locations += 1
|
|
# continue
|
|
|
|
late_dungeon = EOSLocation(self.player, location.name, location.id, late_dungeons_region)
|
|
late_dungeon.progress_type = LocationProgressType.EXCLUDED
|
|
self.excluded_tag_amount += 1
|
|
late_dungeons_region.locations.append(late_dungeon)
|
|
|
|
elif rank_toid_dict[location.name] <= 8:
|
|
early_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location.name, location.id, early_dungeons_region)
|
|
)
|
|
else:
|
|
late_dungeon = EOSLocation(self.player, location.name, location.id, late_dungeons_region)
|
|
late_dungeons_region.locations.append(late_dungeon)
|
|
|
|
elif location.classification in ["EarlyDungeonComplete", "EarlySubX"]:
|
|
early_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location.name, location.id, early_dungeons_region)
|
|
)
|
|
if "Mission" in location.group:
|
|
for j in range(self.options.early_mission_checks.value):
|
|
location_name = f"{location.name} Mission {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j
|
|
early_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location_name, location_id, early_dungeons_region)
|
|
)
|
|
|
|
set_rule(
|
|
self.multiworld.get_location(location_name, self.player),
|
|
lambda state, ln=location.name, p=self.player: state.has(ln, p),
|
|
)
|
|
self.extra_locations_added += 1
|
|
|
|
for j in range(self.options.early_outlaw_checks.value):
|
|
location_name = f"{location.name} Outlaw {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j + 50
|
|
early_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location_name, location_id, early_dungeons_region)
|
|
)
|
|
|
|
set_rule(
|
|
self.multiworld.get_location(location_name, self.player),
|
|
lambda state, ln=location.name, p=self.player: state.has(ln, p),
|
|
)
|
|
self.extra_locations_added += 1
|
|
|
|
elif location.classification == "SpecialDungeonComplete":
|
|
if not self.options.exclude_special.value:
|
|
early_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location.name, location.id, early_dungeons_region)
|
|
)
|
|
else:
|
|
self.excluded_locations += 1
|
|
continue
|
|
|
|
elif location.classification in ["LateDungeonComplete", "LateSubX"]:
|
|
late_dungeon = EOSLocation(self.player, location.name, location.id, late_dungeons_region)
|
|
if self.options.goal.value == 0: # if dialga is the goal, make the location excluded
|
|
self.excluded_locations += 1
|
|
continue
|
|
|
|
late_dungeons_region.locations.append(late_dungeon)
|
|
if self.options.goal.value == 1 and ("Mission" in location.group):
|
|
for j in range(self.options.late_mission_checks.value):
|
|
location_name = f"{location.name} Mission {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j
|
|
late_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location_name, location_id, late_dungeons_region)
|
|
)
|
|
|
|
set_rule(
|
|
self.multiworld.get_location(f"{location.name} Mission {j + 1}", self.player),
|
|
lambda state, ln=location.name, p=self.player: state.has(ln, p),
|
|
)
|
|
self.extra_locations_added += 1
|
|
|
|
for j in range(self.options.late_outlaw_checks.value):
|
|
location_name = f"{location.name} Outlaw {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j + 50
|
|
late_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location_name, location_id, late_dungeons_region)
|
|
)
|
|
|
|
set_rule(
|
|
self.multiworld.get_location(f"{location.name} Outlaw {j + 1}", self.player),
|
|
lambda state, ln=location.name, p=self.player: state.has(ln, p),
|
|
)
|
|
|
|
self.extra_locations_added += 1
|
|
elif location.classification in ["Manaphy", "SecretRank", "Legendary", "Instrument"] or location.name in [
|
|
"Bag Upgrade 5",
|
|
"Recycle Shop Dungeon #4",
|
|
"Recycle Shop Dungeon #5",
|
|
]:
|
|
late_dungeon = EOSLocation(self.player, location.name, location.id, late_dungeons_region)
|
|
if self.options.goal.value == 0: # if dialga is the goal, make the location excluded
|
|
self.excluded_locations += 1
|
|
continue
|
|
# late_dungeon.progress_type = LocationProgressType.EXCLUDED
|
|
# Manaphy's discovery can only be collected if manaphy is in the game
|
|
# if location.name == "Manaphy's Discovery":
|
|
# continue
|
|
late_dungeons_region.locations.append(late_dungeon)
|
|
elif location.classification == "SpindaDrinkEvent":
|
|
if int(location.name[-2:]) <= self.options.drink_events:
|
|
menu_region.locations.append(EOSLocation(self.player, location.name, location.id, menu_region))
|
|
else:
|
|
self.excluded_locations += 1
|
|
elif location.classification == "SpindaDrink":
|
|
if int(location.name[-2:]) <= self.options.spinda_drinks:
|
|
menu_region.locations.append(EOSLocation(self.player, location.name, location.id, menu_region))
|
|
else:
|
|
self.excluded_locations += 1
|
|
|
|
elif location.classification == "BossDungeonComplete":
|
|
location_data = EOSLocation(self.player, location.name, location.id, end_game_region)
|
|
if location.name == "Dark Crater" and self.options.goal.value == 0:
|
|
location_data.progress_type = LocationProgressType.EXCLUDED
|
|
|
|
end_game_region.locations.append(location_data)
|
|
if (self.options.goal.value == 1) and ("Mission" in location.group):
|
|
for j in range(self.options.late_mission_checks.value):
|
|
location_name = f"{location.name} Mission {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j
|
|
late_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location_name, location_id, late_dungeons_region)
|
|
)
|
|
|
|
set_rule(
|
|
self.multiworld.get_location(f"{location.name} Mission {j + 1}", self.player),
|
|
lambda state, ln=location.name, p=self.player: state.has(ln, p),
|
|
)
|
|
self.extra_locations_added += 1
|
|
|
|
for j in range(self.options.late_outlaw_checks.value):
|
|
location_name = f"{location.name} Outlaw {j + 1}"
|
|
location_id = location.id + self.mission_start_id + (100 * location.id) + j + 50
|
|
late_dungeons_region.locations.append(
|
|
EOSLocation(self.player, location_name, location_id, late_dungeons_region)
|
|
)
|
|
|
|
set_rule(
|
|
self.multiworld.get_location(f"{location.name} Outlaw {j + 1}", self.player),
|
|
lambda state, ln=location.name, p=self.player: state.has(ln, p),
|
|
)
|
|
|
|
self.extra_locations_added += 1
|
|
elif (
|
|
(location.classification == "ProgressiveBagUpgrade")
|
|
or (location.classification == "ShopItem")
|
|
or (location.classification == "DojoDungeonComplete")
|
|
or (location.classification == "SEDungeonUnlock")
|
|
):
|
|
extra_items_region.locations.append(
|
|
EOSLocation(self.player, location.name, location.id, extra_items_region)
|
|
)
|
|
elif location.classification in ["RuleDungeonComplete"]:
|
|
if self.options.long_location.value == 0:
|
|
self.excluded_locations += 1
|
|
continue
|
|
# location = EOSLocation(self.player, location.name, location.id, rule_dungeons_region)
|
|
# location.progress_type = LocationProgressType.EXCLUDED
|
|
# rule_dungeons_region.locations.append(location)
|
|
else:
|
|
location = EOSLocation(self.player, location.name, location.id, rule_dungeons_region)
|
|
rule_dungeons_region.locations.append(location)
|
|
|
|
elif location.classification in "OptionalSubX":
|
|
if self.options.long_location.value == 0:
|
|
location = EOSLocation(self.player, location.name, location.id, rule_dungeons_region)
|
|
location.progress_type = LocationProgressType.EXCLUDED
|
|
self.excluded_tag_amount += 1
|
|
rule_dungeons_region.locations.append(location)
|
|
else:
|
|
location = EOSLocation(self.player, location.name, location.id, rule_dungeons_region)
|
|
rule_dungeons_region.locations.append(location)
|
|
|
|
menu_region.connect(extra_items_region)
|
|
|
|
menu_region.connect(early_dungeons_region)
|
|
|
|
early_dungeons_region.connect(late_dungeons_region, "Late Game Door")
|
|
|
|
# early_dungeons_region.connect(early_dungeons_region2)
|
|
|
|
late_dungeons_region.connect(end_game_region, "Boss Door")
|
|
# lambda state: ready_for_final_boss(state, self.player))
|
|
|
|
boss_region = Region("Boss Room", self.player, self.multiworld)
|
|
|
|
boss_region.locations.append(EOSLocation(self.player, "Final Boss", None, boss_region))
|
|
|
|
end_game_region.connect(boss_region, "End Game")
|
|
# lambda state: state.has("Temporal Tower", self.player))
|
|
late_dungeons_region.connect(rule_dungeons_region, "Rule Dungeons")
|
|
|
|
self.get_location("Final Boss").place_locked_item(self.create_item("Victory"))
|
|
|
|
def create_item(self, name: str, classification: ItemClassification = None) -> EOSItem:
|
|
item_data = item_table[name]
|
|
if classification is not None:
|
|
return EOSItem(item_data.name, classification, item_data.id, self.player)
|
|
else:
|
|
return EOSItem(item_data.name, item_data.classification, item_data.id, self.player)
|
|
|
|
def fill_slot_data(self) -> Dict[str, Any]:
|
|
return {
|
|
"Goal": self.options.goal.value,
|
|
"ServerVersion": game_version,
|
|
"BagOnStart": self.options.bag_on_start.value,
|
|
"Recruitment": self.options.recruit.value,
|
|
"TeamFormation": self.options.team_form.value,
|
|
"LevelScaling": self.options.level_scale.value,
|
|
"RecruitmentEvolution": self.options.recruit_evo.value,
|
|
"DojoDungeonsRandomization": self.options.dojo_dungeons.value,
|
|
"ShardFragmentAmount": self.options.required_fragments.value,
|
|
"ExtraShardsAmount": self.options.total_shards.value,
|
|
"EarlyMissionsAmount": self.options.early_mission_checks.value,
|
|
"EarlyOutlawsAmount": self.options.early_outlaw_checks.value,
|
|
"LateMissionsAmount": self.options.late_mission_checks.value,
|
|
"LateOutlawsAmount": self.options.late_outlaw_checks.value,
|
|
"TypeSanity": self.options.type_sanity.value,
|
|
"StarterOption": self.options.starter_option.value,
|
|
"IQScaling": self.options.iq_scaling.value,
|
|
"XPScaling": self.options.xp_scaling.value,
|
|
"RequiredInstruments": self.options.req_instruments.value,
|
|
"ExtraInstruments": self.options.total_instruments.value,
|
|
"HeroEvolution": self.options.hero_evolution.value,
|
|
"Deathlink": self.options.deathlink.value,
|
|
"DeathlinkType": self.options.deathlink_type.value,
|
|
"LegendaryAmount": self.options.legendaries.value,
|
|
"AllowedLegendaries": self.options.allowed_legendaries.value,
|
|
"SkyPeakType": self.options.sky_peak_type.value,
|
|
"SpecialEpisodeSanity": self.options.special_episode_sanity.value,
|
|
"HintLocationList": self.dimensional_scream_list_ints,
|
|
"TrapsAllowed": self.options.allow_traps.value,
|
|
"InvisibleTraps": self.options.invisible_traps.value,
|
|
"TrapPercentage": self.options.trap_percent.value,
|
|
"LongLocations": self.options.long_location.value,
|
|
"CursedAegisCave": self.options.cursed_aegis_cave.value,
|
|
"DrinkEvents": self.options.drink_events.value,
|
|
"SpindaDrinks": self.options.spinda_drinks.value,
|
|
"ExcludeSpecial": self.options.exclude_special.value,
|
|
"AllowMissionsEarly": self.options.early_mission_floors.value,
|
|
"MaxRank": self.options.max_rank.value,
|
|
"GuestScaling": self.options.guest_scaling.value,
|
|
"MoveShortcuts": self.options.move_shortcuts.value,
|
|
"StartInventoryFromPool": self.options.start_inventory_from_pool.value,
|
|
}
|
|
|
|
def create_items(self) -> None:
|
|
required_items = []
|
|
filler_items_pool = []
|
|
exclusive_filler_pool = []
|
|
non_unique_trap_items = []
|
|
non_unique_trap_weights = []
|
|
unique_trap_items = []
|
|
unique_trap_weights = []
|
|
instruments = []
|
|
item_weights = []
|
|
conditional_count = 0
|
|
# conditional_item_table = []
|
|
if self.options.goal.value == 1:
|
|
instruments_to_add = 0
|
|
if self.options.total_instruments.value < self.options.req_instruments.value:
|
|
instruments_to_add = self.options.req_instruments.value
|
|
else:
|
|
instruments_to_add = self.options.total_instruments.value
|
|
|
|
instrument_table = item_table_by_groups["Instrument"]
|
|
instruments = self.random.sample(sorted(instrument_table), instruments_to_add)
|
|
for item in instruments:
|
|
required_items.append(self.create_item(item, ItemClassification.progression_skip_balancing))
|
|
|
|
precollected = [item.name for item in self.multiworld.precollected_items[self.player]]
|
|
|
|
precollected_from_pool = []
|
|
for item, value in self.options.start_inventory_from_pool.value.items():
|
|
for _ in range(value):
|
|
precollected_from_pool += [item]
|
|
|
|
precollected_added = []
|
|
for item, value in self.options.start_inventory.value.items():
|
|
for _ in range(value):
|
|
precollected_added += [item]
|
|
# precollected_from_pool = [item for item, value in self.multiworld.start_inventory_from_pool[self.player].value.items()]
|
|
# precollected_added = [item for item, value in self.multiworld.start_inventory[self.player].value.items()]
|
|
relics_to_add = 0
|
|
if self.options.required_fragments.value > self.options.total_shards.value:
|
|
relics_to_add = self.options.required_fragments.value
|
|
else:
|
|
relics_to_add = self.options.total_shards.value
|
|
|
|
for i in range(relics_to_add):
|
|
required_items.append(
|
|
self.create_item("Relic Fragment Shard", ItemClassification.progression_skip_balancing)
|
|
)
|
|
|
|
if self.options.goal == 1:
|
|
required_items.append(self.create_item("Manaphy", ItemClassification.progression))
|
|
required_items.append(self.create_item("Secret Rank", ItemClassification.progression))
|
|
else:
|
|
# self.excluded_locations += 1
|
|
test = 0
|
|
if self.options.goal.value == 1 and (
|
|
self.options.legendaries.value > len(self.options.allowed_legendaries.value)
|
|
):
|
|
for item in self.options.allowed_legendaries.value:
|
|
required_items.append(self.create_item(item, ItemClassification.filler))
|
|
elif self.options.goal.value == 1:
|
|
new_list = self.random.sample(
|
|
sorted(self.options.allowed_legendaries.value), self.options.legendaries.value
|
|
)
|
|
for item in new_list:
|
|
required_items.append(self.create_item(item, ItemClassification.filler))
|
|
|
|
for item_name in item_table:
|
|
# if (item_name == "Dark Crater") and (self.options.goal.value == 1):
|
|
# continue
|
|
if item_name in item_frequencies:
|
|
freq = 0
|
|
|
|
freq = item_frequencies.get(item_name, 1)
|
|
|
|
freq = max(
|
|
freq
|
|
- precollected.count(item_name)
|
|
+ precollected_added.count(item_name)
|
|
+ precollected_from_pool.count(item_name),
|
|
0,
|
|
)
|
|
required_items += [self.create_item(item_name) for _ in range(freq)]
|
|
elif "Special Dungeons" in item_table[item_name].group:
|
|
if self.options.exclude_special.value:
|
|
continue
|
|
else:
|
|
classification = ItemClassification.progression
|
|
required_items.append(self.create_item(item_name, classification))
|
|
elif item_table[item_name].name == "Main Game Unlock":
|
|
if not self.options.special_episode_sanity.value or self.options.exclude_special.value:
|
|
continue
|
|
else:
|
|
required_items.append(self.create_item(item_name, ItemClassification.progression))
|
|
elif item_table[item_name].name in ["Victory", "Relic Fragment Shard"]:
|
|
continue
|
|
elif "Legendary" in item_table[item_name].group:
|
|
continue
|
|
elif "Instrument" in item_table[item_name].group:
|
|
continue
|
|
elif "Rank" in item_table[item_name].group:
|
|
continue
|
|
elif item_table[item_name].name in conditional_filler_useful_items:
|
|
conditional_count += 1
|
|
# conditional_item_table.append(item_name)
|
|
continue
|
|
elif item_table[item_name].classification == ItemClassification.filler:
|
|
if item_name in ["Gold Ribbon"]:
|
|
continue
|
|
if "Exclusive" in item_table[item_name].group:
|
|
exclusive_filler_pool.append(self.create_item(item_name, ItemClassification.filler))
|
|
else:
|
|
filler_items_pool.append(self.create_item(item_name, ItemClassification.filler))
|
|
|
|
elif item_table[item_name].classification == ItemClassification.trap:
|
|
if "Late" in item_table[item_name].group and self.options.goal.value == 0:
|
|
continue
|
|
elif "Unique" in item_table[item_name].group:
|
|
unique_trap_items.append(self.create_item(item_name, ItemClassification.trap))
|
|
unique_trap_weights.append(item_table[item_name].start_number)
|
|
else:
|
|
non_unique_trap_items.append(self.create_item(item_name, ItemClassification.trap))
|
|
non_unique_trap_weights.append(item_table[item_name].start_number)
|
|
|
|
elif item_table[item_name].classification == ItemClassification.progression:
|
|
classification = ItemClassification.progression
|
|
if (self.options.goal.value == 0) and "LateDungeons" in item_table[item_name].group:
|
|
continue
|
|
# classification = ItemClassification.useful
|
|
|
|
if (self.options.long_location.value == 0) and "RuleDungeons" in item_table[item_name].group:
|
|
continue
|
|
|
|
if "Aegis" in item_table[item_name].group:
|
|
if self.options.goal.value == 0:
|
|
# classification = ItemClassification.useful
|
|
continue
|
|
else:
|
|
classification = ItemClassification.progression
|
|
if self.options.cursed_aegis_cave.value == 0:
|
|
if item_name == "Progressive Seal":
|
|
for i in range(3):
|
|
required_items.append(self.create_item(item_name, classification))
|
|
continue
|
|
else:
|
|
continue
|
|
else:
|
|
if item_name == "Progressive Seal":
|
|
continue
|
|
else:
|
|
required_items.append(self.create_item(item_name, classification))
|
|
continue
|
|
|
|
if "SkyPeak" in item_table[item_name].group:
|
|
if self.options.sky_peak_type.value == 1:
|
|
if item_name == "Progressive Sky Peak":
|
|
if self.options.goal.value == 0:
|
|
continue
|
|
# classification = ItemClassification.useful
|
|
else:
|
|
classification = ItemClassification.progression
|
|
for i in range(10):
|
|
required_items.append(self.create_item(item_name, classification))
|
|
continue
|
|
else:
|
|
continue
|
|
elif self.options.sky_peak_type.value == 2:
|
|
if item_name == "Progressive Sky Peak":
|
|
continue
|
|
else:
|
|
if self.options.goal.value == 0:
|
|
# classification = ItemClassification.useful
|
|
continue
|
|
else:
|
|
classification = ItemClassification.progression
|
|
elif self.options.sky_peak_type.value == 3:
|
|
if item_name == "1st Station Pass":
|
|
if self.options.goal.value == 0:
|
|
continue
|
|
# classification = ItemClassification.useful
|
|
else:
|
|
classification = ItemClassification.progression
|
|
|
|
else:
|
|
continue
|
|
|
|
required_items.append(self.create_item(item_name, classification))
|
|
|
|
else:
|
|
required_items.append(self.create_item(item_name, ItemClassification.useful))
|
|
|
|
remaining = (
|
|
len(EOS_location_table)
|
|
+ self.extra_locations_added
|
|
- len(required_items)
|
|
- conditional_count
|
|
- 1
|
|
- self.excluded_locations
|
|
) # subtracting 1 for the event check
|
|
for i in range(len(conditional_filler_useful_items)):
|
|
if (i <= (self.excluded_tag_amount - remaining)) and (remaining < self.excluded_tag_amount):
|
|
required_items.append(self.create_item(conditional_filler_useful_items[i], ItemClassification.filler))
|
|
else:
|
|
required_items.append(self.create_item(conditional_filler_useful_items[i], ItemClassification.useful))
|
|
|
|
self.multiworld.itempool += required_items
|
|
|
|
item_weights += filler_item_weights
|
|
for i in range(4):
|
|
filler_items_pool += filler_items_pool
|
|
non_unique_trap_items += non_unique_trap_items
|
|
item_weights += item_weights
|
|
non_unique_trap_weights += non_unique_trap_weights
|
|
filler_items_pool += exclusive_filler_pool
|
|
item_weights += exclusive_filler_item_weights
|
|
all_traps = []
|
|
all_traps += non_unique_trap_items
|
|
all_traps += unique_trap_items
|
|
all_trap_weights = []
|
|
all_trap_weights += non_unique_trap_weights
|
|
all_trap_weights += unique_trap_weights
|
|
|
|
if self.options.allow_traps.value in [1, 2]:
|
|
filler_items_toadd = math.ceil(remaining * (100 - self.options.trap_percent) / 100)
|
|
traps_toadd = math.floor(remaining * self.options.trap_percent / 100)
|
|
self.multiworld.itempool += [
|
|
self.create_item(filler_item.name)
|
|
for filler_item in self.random.sample(filler_items_pool, filler_items_toadd, counts=item_weights)
|
|
]
|
|
self.multiworld.itempool += [
|
|
self.create_item(trap.name)
|
|
for trap in self.random.sample(all_traps, traps_toadd, counts=all_trap_weights)
|
|
]
|
|
|
|
else:
|
|
self.multiworld.itempool += [
|
|
self.create_item(filler_item.name)
|
|
for filler_item in self.random.sample(filler_items_pool, remaining, counts=item_weights)
|
|
]
|
|
|
|
idek_var = self.excluded_tag_amount
|
|
wtf_items_list = [item.name for item in self.multiworld.itempool]
|
|
excluded_location_list = [
|
|
location.name
|
|
for location in self.multiworld.get_locations(self.player)
|
|
if location.progress_type is LocationProgressType.EXCLUDED
|
|
]
|
|
test = 0
|
|
|
|
def set_rules(self) -> None:
|
|
set_rules(self, self.disabled_locations)
|
|
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
|
|
|
def generate_output(self, output_directory: str) -> None:
|
|
try:
|
|
patch = EOSProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
|
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/archipelago-base.bsdiff"))
|
|
hint_item_list: list[Location] = []
|
|
self.dimensional_scream_list = self.hint_locations()
|
|
self.dimensional_scream_list_ints = [k.address for k in self.dimensional_scream_list]
|
|
except Exception:
|
|
raise
|
|
finally:
|
|
self.slot_data_ready.set()
|
|
|
|
for i in range(10):
|
|
hint_item_list += [self.multiworld.get_location(f"Shop Item {1 + i}", self.player)]
|
|
write_tokens(self, patch, hint_item_list, self.dimensional_scream_list, self.starting_se + 1)
|
|
rom_path = os.path.join(
|
|
output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}{patch.patch_file_ending}"
|
|
)
|
|
patch.write(rom_path)
|
|
|
|
def hint_locations(self) -> list[Location]:
|
|
hint_loc = []
|
|
filler = 5
|
|
useful = 6
|
|
progressive = 9
|
|
min_internal_progressive = 4
|
|
max_external_progressive = progressive - min_internal_progressive
|
|
sky_dungeons = []
|
|
external_items = []
|
|
extra_useful_items = []
|
|
extra_filler_items = []
|
|
important_sky_items = [
|
|
"Icy Flute",
|
|
"Fiery Drum",
|
|
"Terra Cymbal",
|
|
"Aqua-Monica",
|
|
"Rock Horn",
|
|
"Grass Cornet",
|
|
"Sky Melodica",
|
|
"Stellar Symphony",
|
|
"Null Bagpipes",
|
|
"Glimmer Harp",
|
|
"Toxic Sax",
|
|
"Biting Bass",
|
|
"Knockout Bell",
|
|
"Spectral Chimes",
|
|
"Liar's Lyre",
|
|
"Charge Synth",
|
|
"Norma-ccordion",
|
|
"Psychic Cello",
|
|
"Dragu-teki",
|
|
"Steel Guitar",
|
|
"Relic Fragment Shard",
|
|
]
|
|
location_list = list(self.multiworld.get_locations(self.player))
|
|
random.shuffle(location_list)
|
|
for location in location_list:
|
|
if location.address is not None and location.item:
|
|
if filler <= 0 and useful <= 0 and progressive <= 0:
|
|
break
|
|
elif progressive > 0 and location.item.advancement:
|
|
if location.item.player == self.player and location.item.name not in important_sky_items:
|
|
sky_dungeons.append(location)
|
|
continue
|
|
if location.item.player != self.player:
|
|
if max_external_progressive > 0:
|
|
hint_loc.append(location)
|
|
max_external_progressive -= 1
|
|
progressive -= 1
|
|
else:
|
|
external_items.append(location)
|
|
continue
|
|
if location.item.player == self.player and location.item.name in important_sky_items:
|
|
hint_loc.append(location)
|
|
progressive -= 1
|
|
elif filler > 0 and not (location.item.advancement or location.item.useful):
|
|
hint_loc.append(location)
|
|
filler -= 1
|
|
elif not (location.item.advancement or location.item.useful):
|
|
extra_filler_items.append(location)
|
|
elif useful > 0 and location.item.useful:
|
|
hint_loc.append(location)
|
|
useful -= 1
|
|
elif location.item.useful:
|
|
extra_useful_items.append(location)
|
|
if progressive > 0:
|
|
for i in range(progressive):
|
|
if i < len(sky_dungeons):
|
|
hint_loc.append(sky_dungeons[i])
|
|
else:
|
|
if i - len(sky_dungeons) < len(external_items):
|
|
hint_loc.append(external_items[i - len(sky_dungeons)])
|
|
else:
|
|
if i - len(sky_dungeons) - len(external_items) < len(extra_useful_items):
|
|
hint_loc.append(extra_useful_items[i - len(sky_dungeons) - len(external_items)])
|
|
else:
|
|
if i - len(sky_dungeons) - len(external_items) - len(extra_useful_items) < len(
|
|
extra_filler_items
|
|
):
|
|
hint_loc.append(
|
|
extra_filler_items[
|
|
i - len(sky_dungeons) - len(external_items) - len(extra_useful_items)
|
|
]
|
|
)
|
|
else:
|
|
break
|
|
|
|
return hint_loc
|
|
|
|
def modify_multidata(self, multidata: Dict[str, Any]) -> None:
|
|
self.slot_data_ready.wait()
|
|
slot = self.player
|
|
if self.dimensional_scream_list_ints:
|
|
multidata["slot_data"][slot]["HintLocationList"] = self.dimensional_scream_list_ints
|