2023-03-22 14:25:55 +00:00
|
|
|
import base64
|
|
|
|
import copy
|
|
|
|
import itertools
|
|
|
|
import math
|
|
|
|
import os
|
|
|
|
from enum import IntFlag
|
|
|
|
from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple
|
|
|
|
|
|
|
|
from BaseClasses import Entrance, Item, ItemClassification, MultiWorld, Region, Tutorial, \
|
|
|
|
LocationProgressType
|
2023-05-20 17:21:39 +00:00
|
|
|
from Utils import __version__
|
2023-03-22 14:25:55 +00:00
|
|
|
from Options import AssembleOptions
|
|
|
|
from worlds.AutoWorld import WebWorld, World
|
|
|
|
from Fill import fill_restrictive
|
|
|
|
from worlds.generic.Rules import add_rule, set_rule
|
|
|
|
from .Options import adventure_option_definitions, DragonRandoType, DifficultySwitchA, DifficultySwitchB
|
|
|
|
from .Rom import get_base_rom_bytes, get_base_rom_path, AdventureDeltaPatch, apply_basepatch, \
|
|
|
|
AdventureAutoCollectLocation
|
|
|
|
from .Items import item_table, ItemData, nothing_item_id, event_table, AdventureItem, standard_item_max
|
|
|
|
from .Locations import location_table, base_location_id, LocationData, get_random_room_in_regions
|
|
|
|
from .Offsets import static_item_data_location, items_ram_start, static_item_element_size, item_position_table, \
|
|
|
|
static_first_dragon_index, connector_port_offset, yorgle_speed_data_location, grundle_speed_data_location, \
|
|
|
|
rhindle_speed_data_location, item_ram_addresses, start_castle_values, start_castle_offset
|
|
|
|
from .Regions import create_regions
|
|
|
|
from .Rules import set_rules
|
|
|
|
|
|
|
|
|
|
|
|
from worlds.LauncherComponents import Component, components, SuffixIdentifier
|
|
|
|
|
|
|
|
# Adventure
|
|
|
|
components.append(Component('Adventure Client', 'AdventureClient', file_identifier=SuffixIdentifier('.apadvn')))
|
|
|
|
|
|
|
|
|
|
|
|
class AdventureWeb(WebWorld):
|
2023-05-01 00:03:31 +00:00
|
|
|
theme = "dirt"
|
|
|
|
|
|
|
|
setup = Tutorial(
|
2023-03-22 14:25:55 +00:00
|
|
|
"Multiworld Setup Guide",
|
|
|
|
"A guide to setting up Adventure for MultiWorld.",
|
|
|
|
"English",
|
|
|
|
"setup_en.md",
|
|
|
|
"setup/en",
|
|
|
|
["JusticePS"]
|
2023-05-01 00:03:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
setup_fr = Tutorial(
|
|
|
|
"Guide de configuration Multimonde",
|
|
|
|
"Un guide pour configurer Adventure MultiWorld",
|
|
|
|
"Français",
|
|
|
|
"setup_fr.md",
|
|
|
|
"setup/fr",
|
|
|
|
["TheLynk"]
|
|
|
|
)
|
|
|
|
|
|
|
|
tutorials = [setup, setup_fr]
|
|
|
|
|
2023-03-22 14:25:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
def get_item_position_data_start(table_index: int):
|
2023-05-20 12:40:51 +00:00
|
|
|
item_ram_address = item_ram_addresses[table_index]
|
2023-03-22 14:25:55 +00:00
|
|
|
return item_position_table + item_ram_address - items_ram_start
|
|
|
|
|
|
|
|
|
|
|
|
class AdventureWorld(World):
|
|
|
|
"""
|
|
|
|
Adventure for the Atari 2600 is an early graphical adventure game.
|
|
|
|
Find the enchanted chalice and return it to the yellow castle,
|
|
|
|
using magic items to enter hidden rooms, retrieve out of
|
|
|
|
reach items, or defeat the three dragons. Beware the bat
|
|
|
|
who likes to steal your equipment!
|
|
|
|
"""
|
|
|
|
game: ClassVar[str] = "Adventure"
|
|
|
|
web: ClassVar[WebWorld] = AdventureWeb()
|
|
|
|
|
|
|
|
option_definitions: ClassVar[Dict[str, AssembleOptions]] = adventure_option_definitions
|
|
|
|
item_name_to_id: ClassVar[Dict[str, int]] = {name: data.id for name, data in item_table.items()}
|
|
|
|
location_name_to_id: ClassVar[Dict[str, int]] = {name: data.location_id for name, data in location_table.items()}
|
|
|
|
data_version: ClassVar[int] = 1
|
|
|
|
required_client_version: Tuple[int, int, int] = (0, 3, 9)
|
|
|
|
|
|
|
|
def __init__(self, world: MultiWorld, player: int):
|
|
|
|
super().__init__(world, player)
|
|
|
|
self.rom_name: Optional[bytearray] = bytearray("", "utf8" )
|
|
|
|
self.dragon_rooms: [int] = [0x14, 0x19, 0x4]
|
|
|
|
self.dragon_slay_check: Optional[int] = 0
|
|
|
|
self.connector_multi_slot: Optional[int] = 0
|
|
|
|
self.dragon_rando_type: Optional[int] = 0
|
|
|
|
self.yorgle_speed: Optional[int] = 2
|
|
|
|
self.yorgle_min_speed: Optional[int] = 2
|
|
|
|
self.grundle_speed: Optional[int] = 2
|
|
|
|
self.grundle_min_speed: Optional[int] = 2
|
|
|
|
self.rhindle_speed: Optional[int] = 3
|
|
|
|
self.rhindle_min_speed: Optional[int] = 3
|
|
|
|
self.difficulty_switch_a: Optional[int] = 0
|
|
|
|
self.difficulty_switch_b: Optional[int] = 0
|
|
|
|
self.start_castle: Optional[int] = 0
|
|
|
|
# dict of item names -> list of speed deltas
|
|
|
|
self.dragon_speed_reducer_info: {} = {}
|
|
|
|
self.created_items: int = 0
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def stage_assert_generate(cls, _multiworld: MultiWorld) -> None:
|
|
|
|
# don't need rom anymore
|
|
|
|
pass
|
|
|
|
|
|
|
|
def place_random_dragon(self, dragon_index: int):
|
|
|
|
region_list = ["Overworld", "YellowCastle", "BlackCastle", "WhiteCastle"]
|
|
|
|
self.dragon_rooms[dragon_index] = get_random_room_in_regions(region_list, self.multiworld.random)
|
|
|
|
|
|
|
|
def generate_early(self) -> None:
|
|
|
|
self.rom_name = \
|
|
|
|
bytearray(f"ADVENTURE{__version__.replace('.', '')[:3]}_{self.player}_{self.multiworld.seed}", "utf8")[:21]
|
|
|
|
self.rom_name.extend([0] * (21 - len(self.rom_name)))
|
|
|
|
|
|
|
|
self.dragon_rando_type = self.multiworld.dragon_rando_type[self.player].value
|
|
|
|
self.dragon_slay_check = self.multiworld.dragon_slay_check[self.player].value
|
|
|
|
self.connector_multi_slot = self.multiworld.connector_multi_slot[self.player].value
|
|
|
|
self.yorgle_speed = self.multiworld.yorgle_speed[self.player].value
|
|
|
|
self.yorgle_min_speed = self.multiworld.yorgle_min_speed[self.player].value
|
|
|
|
self.grundle_speed = self.multiworld.grundle_speed[self.player].value
|
|
|
|
self.grundle_min_speed = self.multiworld.grundle_min_speed[self.player].value
|
|
|
|
self.rhindle_speed = self.multiworld.rhindle_speed[self.player].value
|
|
|
|
self.rhindle_min_speed = self.multiworld.rhindle_min_speed[self.player].value
|
|
|
|
self.difficulty_switch_a = self.multiworld.difficulty_switch_a[self.player].value
|
|
|
|
self.difficulty_switch_b = self.multiworld.difficulty_switch_b[self.player].value
|
|
|
|
self.start_castle = self.multiworld.start_castle[self.player].value
|
|
|
|
self.created_items = 0
|
|
|
|
|
|
|
|
if self.dragon_slay_check == 0:
|
|
|
|
item_table["Sword"].classification = ItemClassification.useful
|
|
|
|
else:
|
|
|
|
item_table["Sword"].classification = ItemClassification.progression
|
|
|
|
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
|
|
|
item_table["Right Difficulty Switch"].classification = ItemClassification.progression
|
|
|
|
|
|
|
|
if self.dragon_rando_type == DragonRandoType.option_shuffle:
|
|
|
|
self.multiworld.random.shuffle(self.dragon_rooms)
|
|
|
|
elif self.dragon_rando_type == DragonRandoType.option_overworldplus:
|
|
|
|
dragon_indices = [0, 1, 2]
|
|
|
|
overworld_forced_index = self.multiworld.random.choice(dragon_indices)
|
|
|
|
dragon_indices.remove(overworld_forced_index)
|
|
|
|
region_list = ["Overworld"]
|
|
|
|
self.dragon_rooms[overworld_forced_index] = get_random_room_in_regions(region_list, self.multiworld.random)
|
|
|
|
self.place_random_dragon(dragon_indices[0])
|
|
|
|
self.place_random_dragon(dragon_indices[1])
|
|
|
|
elif self.dragon_rando_type == DragonRandoType.option_randomized:
|
|
|
|
self.place_random_dragon(0)
|
|
|
|
self.place_random_dragon(1)
|
|
|
|
self.place_random_dragon(2)
|
|
|
|
|
|
|
|
def create_items(self) -> None:
|
|
|
|
for event in map(self.create_item, event_table):
|
|
|
|
self.multiworld.itempool.append(event)
|
|
|
|
exclude = [item for item in self.multiworld.precollected_items[self.player]]
|
|
|
|
self.created_items = 0
|
|
|
|
for item in map(self.create_item, item_table):
|
|
|
|
if item.code == nothing_item_id:
|
|
|
|
continue
|
|
|
|
if item in exclude and item.code <= standard_item_max:
|
|
|
|
exclude.remove(item) # this is destructive. create unique list above
|
|
|
|
else:
|
|
|
|
if item.code <= standard_item_max:
|
|
|
|
self.multiworld.itempool.append(item)
|
|
|
|
self.created_items += 1
|
|
|
|
num_locations = len(location_table) - 1 # subtract out the chalice location
|
|
|
|
if self.dragon_slay_check == 0:
|
|
|
|
num_locations -= 3
|
|
|
|
|
|
|
|
if self.difficulty_switch_a == DifficultySwitchA.option_hard_with_unlock_item:
|
|
|
|
self.multiworld.itempool.append(self.create_item("Left Difficulty Switch"))
|
|
|
|
self.created_items += 1
|
|
|
|
if self.difficulty_switch_b == DifficultySwitchB.option_hard_with_unlock_item:
|
|
|
|
self.multiworld.itempool.append(self.create_item("Right Difficulty Switch"))
|
|
|
|
self.created_items += 1
|
|
|
|
|
|
|
|
extra_filler_count = num_locations - self.created_items
|
|
|
|
self.dragon_speed_reducer_info = {}
|
|
|
|
# make sure yorgle doesn't take 2 if there's not enough for the others to get at least one
|
|
|
|
if extra_filler_count <= 4:
|
|
|
|
extra_filler_count = 1
|
|
|
|
self.create_dragon_slow_items(self.yorgle_min_speed, self.yorgle_speed, "Slow Yorgle", extra_filler_count)
|
|
|
|
extra_filler_count = num_locations - self.created_items
|
|
|
|
|
|
|
|
if extra_filler_count <= 3:
|
|
|
|
extra_filler_count = 1
|
|
|
|
self.create_dragon_slow_items(self.grundle_min_speed, self.grundle_speed, "Slow Grundle", extra_filler_count)
|
|
|
|
extra_filler_count = num_locations - self.created_items
|
|
|
|
|
|
|
|
self.create_dragon_slow_items(self.rhindle_min_speed, self.rhindle_speed, "Slow Rhindle", extra_filler_count)
|
|
|
|
extra_filler_count = num_locations - self.created_items
|
|
|
|
|
|
|
|
# traps would probably go here, if enabled
|
|
|
|
freeincarnate_max = self.multiworld.freeincarnate_max[self.player].value
|
|
|
|
actual_freeincarnates = min(extra_filler_count, freeincarnate_max)
|
|
|
|
self.multiworld.itempool += [self.create_item("Freeincarnate") for _ in range(actual_freeincarnates)]
|
|
|
|
self.created_items += actual_freeincarnates
|
|
|
|
|
|
|
|
def create_dragon_slow_items(self, min_speed: int, speed: int, item_name: str, maximum_items: int):
|
|
|
|
if min_speed < speed:
|
|
|
|
delta = speed - min_speed
|
|
|
|
if delta > 2 and maximum_items >= 2:
|
|
|
|
self.multiworld.itempool.append(self.create_item(item_name))
|
|
|
|
self.multiworld.itempool.append(self.create_item(item_name))
|
|
|
|
speed_with_one = speed - math.floor(delta / 2)
|
|
|
|
self.dragon_speed_reducer_info[item_table[item_name].id] = [speed_with_one, min_speed]
|
|
|
|
self.created_items += 2
|
|
|
|
elif maximum_items >= 1:
|
|
|
|
self.multiworld.itempool.append(self.create_item(item_name))
|
|
|
|
self.dragon_speed_reducer_info[item_table[item_name].id] = [min_speed]
|
|
|
|
self.created_items += 1
|
|
|
|
|
|
|
|
def create_regions(self) -> None:
|
|
|
|
create_regions(self.multiworld, self.player, self.dragon_rooms)
|
|
|
|
|
|
|
|
set_rules = set_rules
|
|
|
|
|
|
|
|
def generate_basic(self) -> None:
|
|
|
|
self.multiworld.get_location("Chalice Home", self.player).place_locked_item(
|
|
|
|
self.create_event("Victory", ItemClassification.progression))
|
|
|
|
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
|
|
|
|
|
|
|
def pre_fill(self):
|
|
|
|
# Place empty items in filler locations here, to limit
|
|
|
|
# the number of exported empty items and the density of stuff in overworld.
|
|
|
|
max_location_count = len(location_table) - 1
|
|
|
|
if self.dragon_slay_check == 0:
|
|
|
|
max_location_count -= 3
|
|
|
|
|
|
|
|
force_empty_item_count = (max_location_count - self.created_items)
|
|
|
|
if force_empty_item_count <= 0:
|
|
|
|
return
|
|
|
|
overworld = self.multiworld.get_region("Overworld", self.player)
|
|
|
|
overworld_locations_copy = overworld.locations.copy()
|
|
|
|
all_locations = self.multiworld.get_locations(self.player)
|
|
|
|
|
|
|
|
locations_copy = all_locations.copy()
|
|
|
|
for loc in all_locations:
|
|
|
|
if loc.item is not None or loc.progress_type != LocationProgressType.DEFAULT:
|
|
|
|
locations_copy.remove(loc)
|
|
|
|
if loc in overworld_locations_copy:
|
|
|
|
overworld_locations_copy.remove(loc)
|
|
|
|
|
|
|
|
# guarantee at least one overworld location, so we can for sure get a key somewhere
|
|
|
|
# if too much stuff is plando'd though, just let it go
|
|
|
|
if len(overworld_locations_copy) >= 3:
|
|
|
|
saved_overworld_loc = self.multiworld.random.choice(overworld_locations_copy)
|
|
|
|
locations_copy.remove(saved_overworld_loc)
|
|
|
|
overworld_locations_copy.remove(saved_overworld_loc)
|
|
|
|
|
|
|
|
# if we have few items, enforce another overworld slot, fill a hard slot, and ensure we have
|
|
|
|
# at least one hard slot available
|
|
|
|
if self.created_items < 15:
|
|
|
|
hard_locations = []
|
|
|
|
for loc in locations_copy:
|
|
|
|
if "Vault" in loc.name or "Credits" in loc.name:
|
|
|
|
hard_locations.append(loc)
|
|
|
|
force_empty_item_count -= 1
|
|
|
|
loc = self.multiworld.random.choice(hard_locations)
|
|
|
|
loc.place_locked_item(self.create_item('nothing'))
|
|
|
|
hard_locations.remove(loc)
|
|
|
|
locations_copy.remove(loc)
|
|
|
|
|
|
|
|
loc = self.multiworld.random.choice(hard_locations)
|
|
|
|
locations_copy.remove(loc)
|
|
|
|
hard_locations.remove(loc)
|
|
|
|
|
|
|
|
saved_overworld_loc = self.multiworld.random.choice(overworld_locations_copy)
|
|
|
|
locations_copy.remove(saved_overworld_loc)
|
|
|
|
overworld_locations_copy.remove(saved_overworld_loc)
|
|
|
|
|
|
|
|
# if we have very few items, fill another two difficult slots
|
|
|
|
if self.created_items < 10:
|
|
|
|
for i in range(2):
|
|
|
|
force_empty_item_count -= 1
|
|
|
|
loc = self.multiworld.random.choice(hard_locations)
|
|
|
|
loc.place_locked_item(self.create_item('nothing'))
|
|
|
|
hard_locations.remove(loc)
|
|
|
|
locations_copy.remove(loc)
|
|
|
|
|
|
|
|
# for the absolute minimum number of items, enforce a third overworld slot
|
|
|
|
if self.created_items <= 7:
|
|
|
|
saved_overworld_loc = self.multiworld.random.choice(overworld_locations_copy)
|
|
|
|
locations_copy.remove(saved_overworld_loc)
|
|
|
|
overworld_locations_copy.remove(saved_overworld_loc)
|
|
|
|
|
|
|
|
# finally, place nothing items
|
|
|
|
while force_empty_item_count > 0 and locations_copy:
|
|
|
|
force_empty_item_count -= 1
|
|
|
|
# prefer somewhat to thin out the overworld.
|
|
|
|
if len(overworld_locations_copy) > 0 and self.multiworld.random.randint(0, 10) < 4:
|
|
|
|
loc = self.multiworld.random.choice(overworld_locations_copy)
|
|
|
|
else:
|
|
|
|
loc = self.multiworld.random.choice(locations_copy)
|
|
|
|
loc.place_locked_item(self.create_item('nothing'))
|
|
|
|
locations_copy.remove(loc)
|
|
|
|
if loc in overworld_locations_copy:
|
|
|
|
overworld_locations_copy.remove(loc)
|
|
|
|
|
|
|
|
def place_dragons(self, rom_deltas: {int, int}):
|
|
|
|
for i in range(3):
|
|
|
|
table_index = static_first_dragon_index + i
|
|
|
|
item_position_data_start = get_item_position_data_start(table_index)
|
|
|
|
rom_deltas[item_position_data_start] = self.dragon_rooms[i]
|
|
|
|
|
|
|
|
def set_dragon_speeds(self, rom_deltas: {int, int}):
|
|
|
|
rom_deltas[yorgle_speed_data_location] = self.yorgle_speed
|
|
|
|
rom_deltas[grundle_speed_data_location] = self.grundle_speed
|
|
|
|
rom_deltas[rhindle_speed_data_location] = self.rhindle_speed
|
|
|
|
|
|
|
|
def set_start_castle(self, rom_deltas):
|
|
|
|
start_castle_value = start_castle_values[self.start_castle]
|
|
|
|
rom_deltas[start_castle_offset] = start_castle_value
|
|
|
|
|
|
|
|
def generate_output(self, output_directory: str) -> None:
|
|
|
|
rom_path: str = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.bin")
|
|
|
|
foreign_item_locations: [LocationData] = []
|
|
|
|
auto_collect_locations: [AdventureAutoCollectLocation] = []
|
|
|
|
local_item_to_location: {int, int} = {}
|
|
|
|
bat_no_touch_locs: [LocationData] = []
|
|
|
|
bat_logic: int = self.multiworld.bat_logic[self.player].value
|
|
|
|
try:
|
|
|
|
rom_deltas: { int, int } = {}
|
|
|
|
self.place_dragons(rom_deltas)
|
|
|
|
self.set_dragon_speeds(rom_deltas)
|
|
|
|
self.set_start_castle(rom_deltas)
|
|
|
|
# start and stop indices are offsets in the ROM file, not Adventure ROM addresses (which start at f000)
|
|
|
|
|
|
|
|
# This places the local items (I still need to make it easy to inject the offset data)
|
|
|
|
unplaced_local_items = dict(filter(lambda x: nothing_item_id < x[1].id <= standard_item_max,
|
|
|
|
item_table.items()))
|
|
|
|
for location in self.multiworld.get_locations(self.player):
|
|
|
|
# 'nothing' items, which are autocollected when the room is entered
|
|
|
|
if location.item.player == self.player and \
|
|
|
|
location.item.name == "nothing":
|
|
|
|
location_data = location_table[location.name]
|
|
|
|
auto_collect_locations.append(AdventureAutoCollectLocation(location_data.short_location_id,
|
|
|
|
location_data.room_id))
|
|
|
|
# standard Adventure items, which are placed in the rom
|
|
|
|
elif location.item.player == self.player and \
|
|
|
|
location.item.name != "nothing" and \
|
|
|
|
location.item.code is not None and \
|
|
|
|
location.item.code <= standard_item_max:
|
|
|
|
# I need many of the intermediate values here.
|
|
|
|
item_table_offset = item_table[location.item.name].table_index * static_item_element_size
|
|
|
|
item_ram_address = item_ram_addresses[item_table[location.item.name].table_index]
|
|
|
|
item_position_data_start = item_position_table + item_ram_address - items_ram_start
|
|
|
|
location_data = location_table[location.name]
|
|
|
|
room_x, room_y = location_data.get_position(self.multiworld.per_slot_randoms[self.player])
|
|
|
|
if location_data.needs_bat_logic and bat_logic == 0x0:
|
|
|
|
copied_location = copy.copy(location_data)
|
|
|
|
copied_location.local_item = item_ram_address
|
|
|
|
bat_no_touch_locs.append(copied_location)
|
|
|
|
del unplaced_local_items[location.item.name]
|
|
|
|
|
|
|
|
rom_deltas[item_position_data_start] = location_data.room_id
|
|
|
|
rom_deltas[item_position_data_start + 1] = room_x
|
|
|
|
rom_deltas[item_position_data_start + 2] = room_y
|
|
|
|
local_item_to_location[item_table_offset] = self.location_name_to_id[location.name] \
|
|
|
|
- base_location_id
|
|
|
|
# items from other worlds, and non-standard Adventure items handled by script, like difficulty switches
|
|
|
|
elif location.item.code is not None:
|
|
|
|
if location.item.code != nothing_item_id:
|
|
|
|
location_data = location_table[location.name]
|
|
|
|
foreign_item_locations.append(location_data)
|
|
|
|
if location_data.needs_bat_logic and bat_logic == 0x0:
|
|
|
|
bat_no_touch_locs.append(location_data)
|
|
|
|
else:
|
|
|
|
location_data = location_table[location.name]
|
|
|
|
auto_collect_locations.append(AdventureAutoCollectLocation(location_data.short_location_id,
|
|
|
|
location_data.room_id))
|
|
|
|
# Adventure items that are in another world get put in an invalid room until needed
|
|
|
|
for unplaced_item_name, unplaced_item in unplaced_local_items.items():
|
|
|
|
item_position_data_start = get_item_position_data_start(unplaced_item.table_index)
|
|
|
|
rom_deltas[item_position_data_start] = 0xff
|
|
|
|
|
|
|
|
if self.multiworld.connector_multi_slot[self.player].value:
|
|
|
|
rom_deltas[connector_port_offset] = (self.player & 0xff)
|
|
|
|
else:
|
|
|
|
rom_deltas[connector_port_offset] = 0
|
|
|
|
except Exception as e:
|
|
|
|
raise e
|
|
|
|
else:
|
|
|
|
patch = AdventureDeltaPatch(os.path.splitext(rom_path)[0] + AdventureDeltaPatch.patch_file_ending,
|
|
|
|
player=self.player, player_name=self.multiworld.player_name[self.player],
|
|
|
|
locations=foreign_item_locations,
|
|
|
|
autocollect=auto_collect_locations, local_item_locations=local_item_to_location,
|
|
|
|
dragon_speed_reducer_info=self.dragon_speed_reducer_info,
|
|
|
|
diff_a_mode=self.difficulty_switch_a, diff_b_mode=self.difficulty_switch_b,
|
|
|
|
bat_logic=bat_logic, bat_no_touch_locations=bat_no_touch_locs,
|
|
|
|
rom_deltas=rom_deltas,
|
|
|
|
seed_name=bytes(self.multiworld.seed_name, encoding="ascii"))
|
|
|
|
patch.write()
|
|
|
|
finally:
|
|
|
|
if os.path.exists(rom_path):
|
|
|
|
os.unlink(rom_path)
|
|
|
|
|
|
|
|
# end of ordered Main.py calls
|
|
|
|
|
|
|
|
def create_item(self, name: str) -> Item:
|
|
|
|
item_data: ItemData = item_table.get(name)
|
|
|
|
return AdventureItem(name, item_data.classification, item_data.id, self.player)
|
|
|
|
|
|
|
|
def create_event(self, name: str, classification: ItemClassification) -> Item:
|
|
|
|
return AdventureItem(name, classification, None, self.player)
|