mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 07:03:44 -08:00
* Add the world * doc update * docs * Fix Blast/Missile not clearing Reflect * Update worlds/earthbound/__init__.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/__init__.py remove unused import Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/__init__.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/modules/dungeon_er.py make bool optional Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/modules/boss_shuffle.py typing update Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/modules/boss_shuffle.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Filter events out of item name to id * we call it a glorp * Update worlds/earthbound/Regions.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/__init__.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/Items.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/Regions.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Fix missing optional import * hint stuff * -Fix Apple Kid text being wrong -Fix Slimy Pile text being wrong * -Fix some sprite corruption if PSI was used when an enemy loaded another enemy -Fixed a visible artifact tile during some cutscenes * Update ver * Update docs * Fix some money scripting issues * Add argument to PSI fakeout attack * Updated monkey caves shop description * Remove closing markdown from doc * Add new flavors * Make flavors actually work * Update platforms * Fix common gear getting duplicated * Split region initialization * Condense checks for start inventory + some other junk * Fix some item groups - change receiver phone to warp pad * wow that one was really bad :glorp: * blah * Fix cutoff option text * switch start inventory concatenation to itertools * Fix sky runner scripting bug - added some new comm suggestions * Fix crash when generating with spoiler_only * Fix happy-happy teleport not unlocking after beating carpainter * Hint man hints can now use CreateHint packets to create hints in other games * Adjust some filler rarity * Update world to use CreateHints and deprecate old method * Fix epilogue skip being offset * Rearrange a couple regions * Fix tendapants getting deleted in battle * update doc * i got scared and forgot i had multiple none checks and am worried about this triggering but tested and it works * Fix mostly typing errors from silvris * More type checks * More typing * Typema * Type * Fix enemy levels overwriting music * Fix gihugic blunder * Fix Lumine Hall enabling OSS * del world * Rel 4.2.7 * Remove some debug logs * Fix vanilla bug with weird ambush detection * Fix Starman Junior having an unscaled Freeze * Change shop scaling * Fix shops using the wrong thankful script * Update some bosses in boss shuffle * Loc group adjustment * Update some boss shuffle stuff | Fix Enemizer attacks getting overwritten by Shuffle data | Fix flunkies not updating and still being used with enemizer * Get rid of some debug stuff * Get boss shuffle running, dont merge * Fix json and get boss shuffle no plando back up * Fix Magicant Boost not initializing to Ness if party count = 4 * Fix belch shop using wrong logic * Don't re-send goal status * EBitem * remove : * idk if this is whatvi wanted * All client messagesnow only send when relevant instead of constantly * Patch up the rest of boss plando * Fix Giygas being not excluded from enemizer * Fix epilogue again * adjust the sphere scaling name * add the things * Fix Ness being placed onto monotoli when monotoli was in sea of eden * Fix prefill properly * Fix boss shuffle on vanilla slots. * rename this, apparently * Update archipelago.json --------- Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
610 lines
26 KiB
Python
610 lines
26 KiB
Python
import os
|
|
import typing
|
|
import threading
|
|
import pkgutil
|
|
from typing import List, Set, Dict, TextIO, Tuple
|
|
|
|
from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification
|
|
from Fill import fill_restrictive
|
|
from worlds.AutoWorld import World, WebWorld
|
|
import itertools
|
|
import settings
|
|
from .Items import get_item_names_per_category, item_table
|
|
from .Locations import get_locations
|
|
from .Regions import init_areas, connect_area_exits
|
|
from .Options import EBOptions, eb_option_groups
|
|
from .setup_game import setup_gamevars, place_static_items
|
|
from .modules.enemy_data import initialize_enemies
|
|
from .modules.flavor_data import create_flavors
|
|
from .game_data.local_data import item_id_table, world_version
|
|
from .modules.hint_data import setup_hints
|
|
from .game_data.text_data import spoiler_psi, spoiler_starts, spoiler_badges
|
|
from .Client import EarthBoundClient
|
|
from .Rules import set_location_rules
|
|
from .Rom import patch_rom, EBProcPatch, valid_hashes
|
|
from .game_data.static_location_data import location_ids, location_groups
|
|
from .modules.equipamizer import EBArmor, EBWeapon
|
|
from .modules.boss_shuffle import BossData, SlotInfo
|
|
from worlds.generic.Rules import add_item_rule
|
|
from Options import OptionError
|
|
|
|
|
|
class EBSettings(settings.Group):
|
|
class RomFile(settings.SNESRomPath):
|
|
"""File name of the EarthBound US ROM"""
|
|
description = "EarthBound ROM File"
|
|
copy_to = "EarthBound.sfc"
|
|
md5s = valid_hashes
|
|
|
|
rom_file: RomFile = RomFile(RomFile.copy_to)
|
|
|
|
|
|
class EBWeb(WebWorld):
|
|
theme = "ocean"
|
|
|
|
setup_en = Tutorial(
|
|
"Multiworld Setup Guide",
|
|
"A guide to setting up the EarthBound randomizer"
|
|
"and connecting to an Archipelago server.",
|
|
"English",
|
|
"setup_en.md",
|
|
"setup/en",
|
|
["Pink Switch"]
|
|
)
|
|
|
|
|
|
tutorials = [setup_en]
|
|
|
|
option_groups = eb_option_groups
|
|
# option_presets = eb_option_presets
|
|
|
|
class EBItem(Item):
|
|
game: str = "EarthBound"
|
|
|
|
|
|
class EarthBoundWorld(World):
|
|
"""EarthBound is a contemporary-themed JRPG. Take four psychically-endowed children
|
|
across the world in search of 8 Melodies to defeat Giygas, the cosmic evil."""
|
|
|
|
game = "EarthBound"
|
|
option_definitions = EBOptions
|
|
data_version = 1
|
|
required_client_version = (0, 5, 0)
|
|
|
|
item_name_to_id = {item: data.code for item, data in item_table.items() if data.code}
|
|
location_name_to_id = location_ids
|
|
item_name_groups = get_item_names_per_category()
|
|
location_name_groups = location_groups
|
|
|
|
web = EBWeb()
|
|
settings: typing.ClassVar[EBSettings]
|
|
# topology_present = True
|
|
|
|
options_dataclass = EBOptions
|
|
options: EBOptions
|
|
|
|
locked_locations: List[str]
|
|
location_cache: List[Location]
|
|
|
|
def __init__(self, multiworld: MultiWorld, player: int):
|
|
self.rom_name_available_event = threading.Event()
|
|
super().__init__(multiworld, player)
|
|
|
|
self.locked_locations = []
|
|
self.location_cache = []
|
|
self.event_count = 8
|
|
self.progressive_filler_bats: int = 0
|
|
self.progressive_filler_pans: int = 0
|
|
self.progressive_filler_guns: int = 0
|
|
self.progressive_filler_bracelets: int = 0
|
|
self.progressive_filler_other: int = 0
|
|
self.world_version: str = world_version
|
|
self.armor_list = Dict[str, EBArmor]
|
|
self.weapon_list = Dict[str, EBWeapon]
|
|
self.boss_slots = Dict[str, SlotInfo]
|
|
self.boss_info = Dict[str, BossData]
|
|
self.starting_character: str | None = None
|
|
self.locals = []
|
|
self.rom_name = None
|
|
self.starting_area_teleport = None
|
|
self.common_gear = []
|
|
self.uncommon_gear = []
|
|
self.rare_gear = []
|
|
self.get_all_spheres = threading.Event()
|
|
self.boss_list: List[str] = []
|
|
self.starting_region = str
|
|
self.start_location = int
|
|
self.dungeon_connections: dict[str, str] = {}
|
|
self.has_generated_output: bool = False
|
|
self.hint_man_hints: list[tuple[int | str, player]] = []
|
|
|
|
self.common_items = [
|
|
"Cookie",
|
|
"Bag of Fries",
|
|
"Teddy Bear",
|
|
"Hamburger",
|
|
"Boiled Egg",
|
|
"Fresh Egg",
|
|
"Picnic Lunch",
|
|
"Croissant",
|
|
"Bread Roll",
|
|
"Can of Fruit Juice",
|
|
"Royal Iced Tea",
|
|
"Protein Drink",
|
|
"Bottle of Water",
|
|
"Cold Remedy",
|
|
"Vial of Serum",
|
|
"Ketchup Packet",
|
|
"Sugar Packet",
|
|
"Tin of Cocoa",
|
|
"Carton of Cream",
|
|
"Sprig of Parsley",
|
|
"Jar of Hot Sauce",
|
|
"Salt Packet",
|
|
"Wet Towel",
|
|
"Refreshing Herb",
|
|
"Ruler",
|
|
"Protractor",
|
|
"Insecticide Spray",
|
|
"Rust Promoter",
|
|
"Stag Beetle",
|
|
"Toothbrush",
|
|
"Handbag Strap",
|
|
"Chick",
|
|
"Chicken",
|
|
"Trout Yogurt",
|
|
"Banana",
|
|
"Calorie Stick",
|
|
"Gelato de Resort",
|
|
"Snake",
|
|
"Cup of Noodles",
|
|
"Cup of Coffee",
|
|
"Double Burger",
|
|
"Bean Croquette",
|
|
"Molokheiya Soup",
|
|
"Plain Roll",
|
|
"Magic Tart",
|
|
"PSI Caramel",
|
|
"Popsicle",
|
|
"Bottle Rocket"
|
|
]
|
|
|
|
self.common_gear = [
|
|
"Yo-yo",
|
|
"Slingshot",
|
|
"Travel Charm",
|
|
"Great Charm",
|
|
"Ribbon",
|
|
"Red Ribbon"
|
|
]
|
|
|
|
self.uncommon_items = [
|
|
"Pasta di Summers",
|
|
"Pizza",
|
|
"Chef's Special",
|
|
"Super Plush Bear",
|
|
"Jar of Delisauce",
|
|
"Secret Herb",
|
|
"Xterminator Spray",
|
|
"Snake Bag",
|
|
"Bomb",
|
|
"Rust Promoter DX",
|
|
"Pair of Dirty Socks",
|
|
"Mummy Wrap",
|
|
"Pharaoh's Curse",
|
|
"Sudden Guts Pill",
|
|
"Picture Postcard",
|
|
"Viper",
|
|
"Repel Sandwich",
|
|
"Lucky Sandwich",
|
|
"Peanut Cheese Bar",
|
|
"Bowl of Rice Gruel",
|
|
"Kabob",
|
|
"Plain Yogurt",
|
|
"Beef Jerky",
|
|
"Mammoth Burger",
|
|
"Bottle of DXwater",
|
|
"Magic Pudding",
|
|
"Big Bottle Rocket",
|
|
"Bazooka",
|
|
"Meteornium"
|
|
|
|
]
|
|
|
|
self.uncommon_gear = [
|
|
"Trick Yo-yo",
|
|
"Bionic Slingshot",
|
|
"Crystal Charm",
|
|
"Defense Ribbon",
|
|
"Earth Pendant",
|
|
"Flame Pendant",
|
|
"Rain Pendant",
|
|
"Night Pendant"
|
|
]
|
|
|
|
self.rare_items = [
|
|
"Large Pizza",
|
|
"Magic Truffle",
|
|
"Brain Food Lunch",
|
|
"Rock Candy",
|
|
"Kraken Soup",
|
|
"IQ Capsule",
|
|
"Guts Capsule",
|
|
"Speed Capsule",
|
|
"Vital Capsule",
|
|
"Luck Capsule",
|
|
"Horn of Life",
|
|
"Multi Bottle Rocket",
|
|
"Super Bomb",
|
|
"Bag of Dragonite",
|
|
"Meteotite",
|
|
"Repel Superwich",
|
|
"Piggy Jelly",
|
|
"Spicy Jerky",
|
|
"Luxury Jerky",
|
|
"Cup of Lifenoodles"
|
|
]
|
|
|
|
self.rare_gear = [
|
|
"Combat Yo-yo",
|
|
"Sword of Kings",
|
|
"Sea Pendant",
|
|
"Star Pendant",
|
|
"Goddess Ribbon"
|
|
]
|
|
|
|
self.money = [
|
|
"$10",
|
|
"$100",
|
|
"$1000"
|
|
]
|
|
|
|
def generate_early(self) -> None: # Todo: place locked items in generate_early
|
|
self.starting_character = self.options.starting_character.current_key.capitalize()
|
|
self.locals = []
|
|
local_space_count = 0
|
|
max_counts = {
|
|
"Ness": 12,
|
|
"Paula": 11,
|
|
"Jeff": 9,
|
|
"Poo": 12
|
|
}
|
|
|
|
max_count = max_counts[self.starting_character]
|
|
for item_name, amount in itertools.chain(self.options.start_inventory.items(), self.options.start_inventory_from_pool.items()):
|
|
if item_name in item_id_table:
|
|
local_space_count += amount
|
|
if local_space_count > max_count and not self.options.remote_items:
|
|
player = self.multiworld.get_player_name(self.player)
|
|
raise OptionError(f"{player}: starting inventory cannot place more than {max_count} items into 'Goods' for {self.starting_character}. Attempted to place {local_space_count} Goods items.")
|
|
|
|
setup_gamevars(self)
|
|
create_flavors(self)
|
|
initialize_enemies(self)
|
|
|
|
if not self.options.character_shuffle:
|
|
self.options.local_items.value.update(["Paula", "Jeff", "Poo", "Flying Man"])
|
|
self.event_count += 6
|
|
|
|
if self.options.local_teleports:
|
|
self.options.local_items.value |= self.item_name_groups["PSI"]
|
|
|
|
def create_regions(self) -> None:
|
|
init_areas(self, get_locations(self))
|
|
connect_area_exits(self)
|
|
place_static_items(self)
|
|
|
|
def create_items(self) -> None:
|
|
pool = self.get_item_pool(self.get_excluded_items())
|
|
self.fill_item_pool(pool)
|
|
|
|
self.multiworld.itempool += pool
|
|
|
|
def set_rules(self) -> None:
|
|
set_location_rules(self)
|
|
self.multiworld.completion_condition[self.player] = lambda state: state.has('Saved Earth', self.player)
|
|
|
|
def pre_fill(self) -> None:
|
|
prefill_locations = []
|
|
prefill_items = []
|
|
|
|
if not self.options.character_shuffle:
|
|
main_characters = ["Ness", "Paula", "Jeff", "Poo"]
|
|
for character in main_characters:
|
|
if character != self.starting_character:
|
|
prefill_items.append(self.create_item(character))
|
|
|
|
prefill_items.extend([
|
|
self.create_item("Flying Man"),
|
|
self.create_item("Teddy Bear"),
|
|
self.create_item("Super Plush Bear")
|
|
])
|
|
|
|
prefill_locations.extend([
|
|
self.multiworld.get_location("Happy-Happy Village - Prisoner", self.player),
|
|
self.multiworld.get_location("Threed - Zombie Prisoner", self.player),
|
|
self.multiworld.get_location("Snow Wood - Bedroom", self.player),
|
|
self.multiworld.get_location("Monotoli Building - Monotoli Character", self.player),
|
|
self.multiworld.get_location("Dalaam - Throne Character", self.player),
|
|
self.multiworld.get_location("Deep Darkness - Barf Character", self.player),
|
|
])
|
|
self.random.shuffle(prefill_locations)
|
|
add_item_rule(self.multiworld.get_location("Happy-Happy Village - Prisoner", self.player), lambda item: item.name in self.item_name_groups["Characters"])
|
|
add_item_rule(self.multiworld.get_location("Threed - Zombie Prisoner", self.player), lambda item: item.name in self.item_name_groups["Characters"])
|
|
add_item_rule(self.multiworld.get_location("Snow Wood - Bedroom", self.player), lambda item: item.name in self.item_name_groups["Characters"])
|
|
add_item_rule(self.multiworld.get_location("Monotoli Building - Monotoli Character", self.player), lambda item: item.name in self.item_name_groups["Characters"])
|
|
add_item_rule(self.multiworld.get_location("Dalaam - Throne Character", self.player), lambda item: item.name in self.item_name_groups["Characters"])
|
|
add_item_rule(self.multiworld.get_location("Deep Darkness - Barf Character", self.player), lambda item: item.name in self.item_name_groups["Characters"])
|
|
|
|
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False, collect_pre_fill_items=False), prefill_locations, prefill_items, True, True)
|
|
setup_hints(self)
|
|
|
|
def get_pre_fill_items(self) -> list[Item]:
|
|
characters = ["Ness", "Paula", "Jeff", "Poo"]
|
|
prefill_items = []
|
|
for character in characters:
|
|
if character != self.starting_character:
|
|
prefill_items.append(self.create_item(f"{character}"))
|
|
return prefill_items
|
|
|
|
@classmethod
|
|
def stage_generate_output(cls, multiworld: MultiWorld, output_directory: str) -> None:
|
|
try:
|
|
multiworld.earthbound_locations_by_sphere = list(multiworld.get_spheres())
|
|
except Exception:
|
|
raise
|
|
finally:
|
|
for world in multiworld.get_game_worlds("EarthBound"):
|
|
world.get_all_spheres.set()
|
|
|
|
def generate_output(self, output_directory: str) -> None:
|
|
self.has_generated_output = True # Make sure data defined in generate output doesn't get added to spoiler only mode
|
|
try:
|
|
patch = EBProcPatch(player=self.player, player_name=self.multiworld.player_name[self.player])
|
|
patch.write_file("earthbound_basepatch.bsdiff4", pkgutil.get_data(__name__, "src/earthbound_basepatch.bsdiff4"))
|
|
patch_rom(self, patch, self.player)
|
|
|
|
self.rom_name = patch.name
|
|
|
|
patch.write(os.path.join(output_directory,
|
|
f"{self.multiworld.get_out_file_name_base(self.player)}{patch.patch_file_ending}"))
|
|
except Exception:
|
|
raise
|
|
finally:
|
|
self.rom_name_available_event.set() # make sure threading continues and errors are collected
|
|
|
|
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]) -> None:
|
|
if self.options.dungeon_shuffle:
|
|
dungeon_entrances = {}
|
|
dungeon_mapping = {}
|
|
for dungeon in self.dungeon_connections:
|
|
dungeon_entrances[self.dungeon_connections[dungeon]] = dungeon
|
|
|
|
for dungeon in dungeon_entrances:
|
|
for location in self.get_region(dungeon).locations:
|
|
if location.address:
|
|
dungeon_mapping[location.address] = dungeon_entrances[dungeon]
|
|
|
|
hint_data[self.player] = dungeon_mapping
|
|
|
|
def fill_slot_data(self) -> Dict[str, typing.Any]:
|
|
return {
|
|
"starting_area": self.start_location,
|
|
"pizza_logic": self.options.monkey_caves_mode.value,
|
|
"free_sancs": self.options.no_free_sanctuaries.value,
|
|
"shopsanity": self.options.shop_randomizer.value,
|
|
"hint_man_hints": self.hint_man_hints
|
|
}
|
|
|
|
def modify_multidata(self, multidata: dict) -> None:
|
|
import base64
|
|
# wait for self.rom_name to be available.
|
|
self.rom_name_available_event.wait()
|
|
rom_name = getattr(self, "rom_name", None)
|
|
if rom_name:
|
|
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
|
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
|
|
|
def write_spoiler_header(self, spoiler_handle: TextIO) -> None:
|
|
spoiler_handle.write(f"\nStarting Location: {spoiler_starts[self.start_location]}\n")
|
|
spoiler_handle.write(f"Franklin Badge Protection: {spoiler_badges[self.franklin_protection]}\n")
|
|
if self.options.psi_shuffle:
|
|
spoiler_handle.write("\nPSI Shuffle:\n")
|
|
spoiler_handle.write(f" Favorite Thing PSI Slot: {spoiler_psi[self.offensive_psi_slots[0]]}\n")
|
|
spoiler_handle.write(f" Ness Offensive PSI Middle Slot: {spoiler_psi[self.offensive_psi_slots[1]]}\n")
|
|
spoiler_handle.write(f" Paula Offensive PSI Top Slot: {spoiler_psi[self.offensive_psi_slots[2]]}\n")
|
|
spoiler_handle.write(f" Paula/Poo Offensive PSI Middle Slot: {spoiler_psi[self.offensive_psi_slots[3]]}\n")
|
|
spoiler_handle.write(f" Paula/Poo Offensive PSI Bottom Slot: {spoiler_psi[self.offensive_psi_slots[4]]}\n")
|
|
spoiler_handle.write(f" Poo Progressive PSI Slot: {spoiler_psi[self.offensive_psi_slots[5]]}\n")
|
|
|
|
spoiler_handle.write(f" Ness/Poo Shield Slot: {spoiler_psi[self.shield_slots[0]]}\n")
|
|
spoiler_handle.write(f" Paula Shield Slot: {spoiler_psi[self.shield_slots[1]]}\n")
|
|
|
|
spoiler_handle.write(f" Ness Assist PSI Middle Slot: {spoiler_psi[self.assist_psi_slots[0]]}\n")
|
|
spoiler_handle.write(f" Ness Assist PSI Bottom Slot: {spoiler_psi[self.assist_psi_slots[1]]}\n")
|
|
spoiler_handle.write(f" Paula Assist PSI Middle Slot: {spoiler_psi[self.assist_psi_slots[2]]}\n")
|
|
spoiler_handle.write(f" Paula Assist PSI Bottom Slot: {spoiler_psi[self.assist_psi_slots[3]]}\n")
|
|
spoiler_handle.write(f" Poo Assist PSI Slot: {spoiler_psi[self.assist_psi_slots[4]]}\n")
|
|
if self.options.psi_shuffle == 2:
|
|
spoiler_handle.write(f" Bomb/Bazooka Slot: {spoiler_psi[self.jeff_offense_items[0]]}\n")
|
|
spoiler_handle.write(f" Bottle Rocket Slot: {spoiler_psi[self.jeff_offense_items[1]]}\n")
|
|
|
|
spoiler_handle.write(f" Spray Can Slot: {spoiler_psi[self.jeff_assist_items[0]]}\n")
|
|
spoiler_handle.write(f" Multi-Level Gadget Slot 1: {spoiler_psi[self.jeff_assist_items[1]]}\n")
|
|
spoiler_handle.write(f" Single-Level Gadget Slot 1: {spoiler_psi[self.jeff_assist_items[2]]}\n")
|
|
spoiler_handle.write(f" Single-Level Gadget Slot 2: {spoiler_psi[self.jeff_assist_items[3]]}\n")
|
|
spoiler_handle.write(f" Multi-Level Gadget Slot 2: {spoiler_psi[self.jeff_assist_items[4]]}\n")
|
|
|
|
if self.options.boss_shuffle:
|
|
spoiler_handle.write("\nBoss Randomization:\n" +
|
|
f" Frank => {self.boss_list[0]}\n" +
|
|
f" Frankystein Mark II => {self.boss_list[1]}\n" +
|
|
f" Titanic Ant => {self.boss_list[2]}\n" +
|
|
f" Captain Strong => {self.boss_list[3]}\n" +
|
|
f" Everdred => {self.boss_list[4]}\n" +
|
|
f" Mr. Carpainter => {self.boss_list[5]}\n" +
|
|
f" Mondo Mole => {self.boss_list[6]}\n" +
|
|
f" Boogey Tent => {self.boss_list[7]}\n" +
|
|
f" Mini Barf => {self.boss_list[8]}\n" +
|
|
f" Master Belch => {self.boss_list[9]}\n" +
|
|
f" Trillionage Sprout => {self.boss_list[10]}\n" +
|
|
f" Guardian Digger => {self.boss_list[11]}\n" +
|
|
f" Dept. Store Spook => {self.boss_list[12]}\n" +
|
|
f" Evil Mani-Mani => {self.boss_list[13]}\n" +
|
|
f" Clumsy Robot => {self.boss_list[14]}\n" +
|
|
f" Shrooom! => {self.boss_list[15]}\n" +
|
|
f" Plague Rat of Doom => {self.boss_list[16]}\n" +
|
|
f" Thunder and Storm => {self.boss_list[17]}\n" +
|
|
f" Kraken => {self.boss_list[18]}\n" +
|
|
f" Guardian General => {self.boss_list[19]}\n" +
|
|
f" Master Barf => {self.boss_list[20]}\n" +
|
|
f" Starman Deluxe => {self.boss_list[21]}\n" +
|
|
f" Electro Specter => {self.boss_list[22]}\n" +
|
|
f" Carbon Dog => {self.boss_list[23]}\n" +
|
|
f" Ness's Nightmare => {self.boss_list[24]}\n" +
|
|
f" Heavily Armed Pokey => {self.boss_list[25]}\n" +
|
|
f" Starman Junior => {self.boss_list[26]}\n" +
|
|
f" Diamond Dog => {self.boss_list[27]}\n" +
|
|
f" Giygas (Phase 2) => {self.boss_list[28]}\n")
|
|
|
|
if self.options.dungeon_shuffle:
|
|
spoiler_handle.write("\nDungeon Entrances:\n")
|
|
for dungeon in self.dungeon_connections:
|
|
spoiler_handle.write(
|
|
f" {dungeon} => {self.dungeon_connections[dungeon]}\n"
|
|
)
|
|
|
|
if self.has_generated_output:
|
|
spoiler_handle.write("\nArea Levels:\n")
|
|
spoiler_excluded_areas = ["Ness's Mind", "Global ATM Access", "Common Condiment Shop"]
|
|
for area in self.area_levels:
|
|
if area not in spoiler_excluded_areas:
|
|
spoiler_handle.write(f" {area}: Level {self.area_levels[area]}\n")
|
|
|
|
def create_item(self, name: str) -> EBItem:
|
|
data = item_table[name]
|
|
return EBItem(name, data.classification, data.code, self.player)
|
|
|
|
def get_filler_item_name(self) -> str: # Todo: make this suck less
|
|
weights = {"rare": self.options.rare_filler_weight.value, "uncommon": self.options.uncommon_filler_weight.value, "common": self.options.common_filler_weight.value,
|
|
"rare_gear": int(self.options.rare_filler_weight.value * 0.5), "uncommon_gear": int(self.options.uncommon_filler_weight.value * 0.5),
|
|
"common_gear": int(self.options.common_filler_weight.value * 0.5), "money": self.options.money_weight.value}
|
|
|
|
filler_type = self.random.choices(list(weights), weights=list(weights.values()), k=1)[0]
|
|
weight_table = {
|
|
"common": self.common_items,
|
|
"common_gear": self.common_gear,
|
|
"uncommon": self.uncommon_items,
|
|
"uncommon_gear": self.uncommon_gear,
|
|
"rare": self.rare_items,
|
|
"rare_gear": self.rare_gear,
|
|
"money": self.money
|
|
}
|
|
return self.random.choice(weight_table[filler_type])
|
|
|
|
def get_excluded_items(self) -> Set[str]:
|
|
excluded_items: Set[str] = set()
|
|
excluded_items.add(self.starting_character)
|
|
starting_area_to_teleport = ["Onett Teleport", "Onett Teleport", "Twoson Teleport", "Happy-Happy Village Teleport",
|
|
"Threed Teleport", "Saturn Valley Teleport", "Fourside Teleport", "Winters Teleport",
|
|
"Summers Teleport", "Dalaam Teleport", "Scaraba Teleport", "Deep Darkness Teleport",
|
|
"Tenda Village Teleport", "Lost Underworld Teleport", "Magicant Teleport"]
|
|
self.starting_area_teleport = starting_area_to_teleport[self.start_location]
|
|
excluded_items.add(self.starting_area_teleport)
|
|
if self.options.random_start_location:
|
|
excluded_items.add(self.starting_teleport)
|
|
|
|
if self.options.magicant_mode not in [0, 3]:
|
|
excluded_items.add("Magicant Teleport")
|
|
|
|
if not self.options.character_shuffle:
|
|
excluded_items.add("Ness")
|
|
excluded_items.add("Paula")
|
|
excluded_items.add("Jeff")
|
|
excluded_items.add("Poo")
|
|
excluded_items.add("Flying Man")
|
|
|
|
if self.options.progressive_weapons:
|
|
excluded_items.add("Magicant Bat")
|
|
excluded_items.add("Legendary Bat")
|
|
excluded_items.add("Pop Gun")
|
|
excluded_items.add("Stun Gun")
|
|
excluded_items.add("Death Ray")
|
|
excluded_items.add("Moon Beam Gun")
|
|
|
|
if self.options.progressive_armor:
|
|
excluded_items.add("Platinum Band")
|
|
excluded_items.add("Diamond Band")
|
|
excluded_items.add("Pixie's Bracelet")
|
|
excluded_items.add("Cherub's Band")
|
|
excluded_items.add("Goddess Band")
|
|
excluded_items.add("Coin of Slumber")
|
|
excluded_items.add("Souvenir Coin")
|
|
excluded_items.add("Mr. Saturn Coin")
|
|
|
|
if not self.options.no_free_sanctuaries:
|
|
excluded_items.add("Tiny Key")
|
|
excluded_items.add("Tenda Lavapants")
|
|
|
|
return excluded_items
|
|
|
|
def set_classifications(self, name: str) -> Item:
|
|
data = item_table[name]
|
|
item = Item(name, data.classification, data.code, self.player)
|
|
|
|
if name == "Magicant Teleport" and self.options.magicant_mode == 3:
|
|
item.classification = ItemClassification.useful
|
|
return item
|
|
|
|
def fill_item_pool(self, pool: List[Item]) -> None:
|
|
item_to_counts = {
|
|
"Progressive Bat": self.progressive_filler_bats,
|
|
"Progressive Fry Pan": self.progressive_filler_pans,
|
|
"Progressive Gun": self.progressive_filler_guns,
|
|
"Progressive Bracelet": self.progressive_filler_bracelets,
|
|
"Progressive Other": self.progressive_filler_other
|
|
}
|
|
|
|
max_filler_counts = {
|
|
"Progressive Bat": 8,
|
|
"Progressive Fry Pan": 9,
|
|
"Progressive Gun": 6,
|
|
"Progressive Bracelet": 6,
|
|
"Progressive Other": 10
|
|
}
|
|
|
|
for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(pool) - self.event_count): # Change to fix event count
|
|
item = self.set_classifications(self.get_filler_item_name())
|
|
if item.name in ["Progressive Bat", "Progressive Fry Pan", "Progressive Other",
|
|
"Progressive Gun", "Progressive Bracelet"]:
|
|
item_to_counts[item.name] += 1
|
|
|
|
if item_to_counts[item.name] >= max_filler_counts[item.name]:
|
|
self.common_gear = [x for x in self.common_gear if x != item.name]
|
|
self.uncommon_gear = [x for x in self.uncommon_gear if x != item.name]
|
|
self.rare_gear = [x for x in self.rare_gear if x != item.name]
|
|
pool.append(item)
|
|
|
|
def get_item_pool(self, excluded_items: Set[str]) -> List[Item]:
|
|
pool: List[Item] = []
|
|
|
|
for name, data in item_table.items():
|
|
if name not in excluded_items:
|
|
for _ in range(data.amount):
|
|
item = self.set_classifications(name)
|
|
pool.append(item)
|
|
|
|
if self.options.progressive_weapons:
|
|
for i in range(2):
|
|
pool.append(self.set_classifications("Progressive Bat"))
|
|
for i in range(4):
|
|
pool.append(self.set_classifications("Progressive Gun"))
|
|
|
|
if self.options.progressive_armor:
|
|
for i in range(5):
|
|
pool.append(self.set_classifications("Progressive Bracelet"))
|
|
for i in range(3):
|
|
pool.append(self.set_classifications("Progressive Other"))
|
|
|
|
return pool
|