Merge branch 'main' into ror2
This commit is contained in:
@@ -114,6 +114,9 @@ class World(metaclass=AutoWorldRegister):
|
||||
item_names: Set[str] # set of all potential item names
|
||||
location_names: Set[str] # set of all potential location names
|
||||
|
||||
# If there is visibility in what is being sent, this is where it will be known.
|
||||
sending_visible: bool = False
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
self.world = world
|
||||
self.player = player
|
||||
|
||||
@@ -1315,9 +1315,7 @@ def patch_rom(world, rom, player, enemized):
|
||||
equip[0x37B] = 1
|
||||
equip[0x36E] = 0x80
|
||||
|
||||
for item in world.precollected_items:
|
||||
if item.player != player:
|
||||
continue
|
||||
for item in world.precollected_items[player]:
|
||||
|
||||
if item.name in {'Bow', 'Silver Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
|
||||
'Titans Mitts', 'Power Glove', 'Progressive Glove',
|
||||
|
||||
@@ -9,7 +9,7 @@ from .Technologies import base_tech_table, recipe_sources, base_technology_table
|
||||
get_science_pack_pools, Recipe, recipes, technology_table, tech_table, factorio_base_id, useless_technologies
|
||||
from .Shapes import get_shapes
|
||||
from .Mod import generate_mod
|
||||
from .Options import factorio_options, Silo
|
||||
from .Options import factorio_options, Silo, TechTreeInformation
|
||||
|
||||
import logging
|
||||
|
||||
@@ -66,6 +66,9 @@ class Factorio(World):
|
||||
if map_basic_settings.get("seed", None) is None: # allow seed 0
|
||||
map_basic_settings["seed"] = self.world.slot_seeds[player].randint(0, 2 ** 32 - 1) # 32 bit uint
|
||||
|
||||
self.sending_visible = self.world.tech_tree_information[player] == TechTreeInformation.option_full
|
||||
|
||||
|
||||
generate_output = generate_mod
|
||||
|
||||
def create_regions(self):
|
||||
|
||||
@@ -36,7 +36,6 @@ location_id_offset = 67000
|
||||
|
||||
# OoT's generate_output doesn't benefit from more than 2 threads, instead it uses a lot of memory.
|
||||
i_o_limiter = threading.Semaphore(2)
|
||||
hint_data_available = threading.Event()
|
||||
|
||||
|
||||
class OOTWorld(World):
|
||||
@@ -88,6 +87,10 @@ class OOTWorld(World):
|
||||
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self, world, player):
|
||||
self.hint_data_available = threading.Event()
|
||||
super(OOTWorld, self).__init__(world, player)
|
||||
|
||||
def generate_early(self):
|
||||
# Player name MUST be at most 16 bytes ascii-encoded, otherwise won't write to ROM correctly
|
||||
if len(bytes(self.world.get_player_name(self.player), 'ascii')) > 16:
|
||||
@@ -261,7 +264,7 @@ class OOTWorld(World):
|
||||
# Both two-handed swords can be required in glitch logic, so only consider them nonprogression in glitchless
|
||||
self.nonadvancement_items.add('Biggoron Sword')
|
||||
self.nonadvancement_items.add('Giants Knife')
|
||||
|
||||
|
||||
def load_regions_from_json(self, file_path):
|
||||
region_json = read_json(file_path)
|
||||
|
||||
@@ -456,9 +459,7 @@ class OOTWorld(World):
|
||||
junk_pool = get_junk_pool(self)
|
||||
removed_items = []
|
||||
# Determine starting items
|
||||
for item in self.world.precollected_items:
|
||||
if item.player != self.player:
|
||||
continue
|
||||
for item in self.world.precollected_items[self.player]:
|
||||
if item.name in self.remove_from_start_inventory:
|
||||
self.remove_from_start_inventory.remove(item.name)
|
||||
removed_items.append(item.name)
|
||||
@@ -586,14 +587,20 @@ class OOTWorld(World):
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), any_dungeon_locations,
|
||||
itempools['any_dungeon'], True, True)
|
||||
|
||||
# If anything is overworld-only, enforce them as local and not in the remaining dungeon locations
|
||||
if itempools['overworld'] or self.shuffle_fortresskeys == 'overworld':
|
||||
from worlds.generic.Rules import forbid_items_for_player
|
||||
fortresskeys = {'Small Key (Gerudo Fortress)'} if self.shuffle_fortresskeys == 'overworld' else set()
|
||||
local_overworld_items = set(map(lambda item: item.name, itempools['overworld'])).union(fortresskeys)
|
||||
for location in self.world.get_locations():
|
||||
if location.player != self.player or location in any_dungeon_locations:
|
||||
forbid_items_for_player(location, local_overworld_items, self.player)
|
||||
# If anything is overworld-only, fill into local non-dungeon locations
|
||||
if self.shuffle_fortresskeys == 'overworld':
|
||||
fortresskeys = filter(lambda item: item.player == self.player and item.type == 'FortressSmallKey', self.world.itempool)
|
||||
itempools['overworld'].extend(fortresskeys)
|
||||
if itempools['overworld']:
|
||||
for item in itempools['overworld']:
|
||||
self.world.itempool.remove(item)
|
||||
itempools['overworld'].sort(key=lambda item:
|
||||
{'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'FortressSmallKey': 1}.get(item.type, 0))
|
||||
non_dungeon_locations = [loc for loc in self.get_locations() if not loc.item and loc not in any_dungeon_locations
|
||||
and loc.type != 'Shop' and (loc.type != 'Song' or self.shuffle_song_items != 'song')]
|
||||
self.world.random.shuffle(non_dungeon_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), non_dungeon_locations,
|
||||
itempools['overworld'], True, True)
|
||||
|
||||
# Place songs
|
||||
# 5 built-in retries because this section can fail sometimes
|
||||
@@ -697,7 +704,7 @@ class OOTWorld(World):
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
if self.hints != 'none':
|
||||
hint_data_available.wait()
|
||||
self.hint_data_available.wait()
|
||||
|
||||
with i_o_limiter:
|
||||
# Make ice traps appear as other random items
|
||||
@@ -776,7 +783,8 @@ class OOTWorld(World):
|
||||
except Exception as e:
|
||||
raise e
|
||||
finally:
|
||||
hint_data_available.set()
|
||||
for autoworld in world.get_game_worlds("Ocarina of Time"):
|
||||
autoworld.hint_data_available.set()
|
||||
|
||||
def modify_multidata(self, multidata: dict):
|
||||
for item_name in self.remove_from_start_inventory:
|
||||
|
||||
@@ -5,10 +5,11 @@ class ItemData(NamedTuple):
|
||||
code: int
|
||||
count: int = 1
|
||||
progression: bool = False
|
||||
never_exclude: bool = False
|
||||
|
||||
# A lot of items arent normally dropped by the randomizer as they are mostly enemy drops, but they can be enabled if desired
|
||||
item_table: Dict[str, ItemData] = {
|
||||
'Eternal Crown': ItemData('Equipment', 1337000),
|
||||
'Eternal Crown': ItemData('Equipment', 1337000, never_exclude=True),
|
||||
'Security Visor': ItemData('Equipment', 1337001, 0),
|
||||
'Engineer Goggles': ItemData('Equipment', 1337002, 0),
|
||||
'Leather Helmet': ItemData('Equipment', 1337003, 0),
|
||||
@@ -39,24 +40,24 @@ item_table: Dict[str, ItemData] = {
|
||||
'Lab Coat': ItemData('Equipment', 1337028),
|
||||
'Empress Robe': ItemData('Equipment', 1337029),
|
||||
'Princess Dress': ItemData('Equipment', 1337030),
|
||||
'Eternal Coat': ItemData('Equipment', 1337031),
|
||||
'Eternal Coat': ItemData('Equipment', 1337031, never_exclude=True),
|
||||
'Synthetic Plume': ItemData('Equipment', 1337032, 0),
|
||||
'Cheveur Plume': ItemData('Equipment', 1337033, 0),
|
||||
'Metal Wristband': ItemData('Equipment', 1337034),
|
||||
'Nymph Hairband': ItemData('Equipment', 1337035, 0),
|
||||
'Mother o\' Pearl': ItemData('Equipment', 1337036, 0),
|
||||
'Bird Statue': ItemData('Equipment', 1337037),
|
||||
'Bird Statue': ItemData('Equipment', 1337037, never_exclude=True),
|
||||
'Chaos Stole': ItemData('Equipment', 1337038, 0),
|
||||
'Pendulum': ItemData('Equipment', 1337039),
|
||||
'Pendulum': ItemData('Equipment', 1337039, never_exclude=True),
|
||||
'Chaos Horn': ItemData('Equipment', 1337040, 0),
|
||||
'Filigree Clasp': ItemData('Equipment', 1337041),
|
||||
'Azure Stole': ItemData('Equipment', 1337042, 0),
|
||||
'Ancient Coin': ItemData('Equipment', 1337043),
|
||||
'Shiny Rock': ItemData('Equipment', 1337044, 0),
|
||||
'Galaxy Earrings': ItemData('Equipment', 1337045),
|
||||
'Selen\'s Bangle': ItemData('Equipment', 1337046),
|
||||
'Glass Pumpkin': ItemData('Equipment', 1337047),
|
||||
'Gilded Egg': ItemData('Equipment', 1337048),
|
||||
'Galaxy Earrings': ItemData('Equipment', 1337045, never_exclude=True),
|
||||
'Selen\'s Bangle': ItemData('Equipment', 1337046, never_exclude=True),
|
||||
'Glass Pumpkin': ItemData('Equipment', 1337047, never_exclude=True),
|
||||
'Gilded Egg': ItemData('Equipment', 1337048, never_exclude=True),
|
||||
'Meyef': ItemData('Familiar', 1337049),
|
||||
'Griffin': ItemData('Familiar', 1337050),
|
||||
'Merchant Crow': ItemData('Familiar', 1337051),
|
||||
@@ -134,7 +135,7 @@ item_table: Dict[str, ItemData] = {
|
||||
'Library Keycard V': ItemData('Relic', 1337123, progression=True),
|
||||
'Tablet': ItemData('Relic', 1337124, progression=True),
|
||||
'Elevator Keycard': ItemData('Relic', 1337125, progression=True),
|
||||
'Jewelry Box': ItemData('Relic', 1337126),
|
||||
'Jewelry Box': ItemData('Relic', 1337126, never_exclude=True),
|
||||
'Goddess Brooch': ItemData('Relic', 1337127),
|
||||
'Wyrm Brooch': ItemData('Relic', 1337128),
|
||||
'Greed Brooch': ItemData('Relic', 1337129),
|
||||
@@ -171,7 +172,7 @@ item_table: Dict[str, ItemData] = {
|
||||
'Bombardment': ItemData('Orb Spell', 1337160),
|
||||
'Corruption': ItemData('Orb Spell', 1337161),
|
||||
'Lightwall': ItemData('Orb Spell', 1337162, progression=True),
|
||||
'Bleak Ring': ItemData('Orb Passive', 1337163),
|
||||
'Bleak Ring': ItemData('Orb Passive', 1337163, never_exclude=True),
|
||||
'Scythe Ring': ItemData('Orb Passive', 1337164),
|
||||
'Pyro Ring': ItemData('Orb Passive', 1337165, progression=True),
|
||||
'Royal Ring': ItemData('Orb Passive', 1337166, progression=True),
|
||||
@@ -180,12 +181,12 @@ item_table: Dict[str, ItemData] = {
|
||||
'Tailwind Ring': ItemData('Orb Passive', 1337169),
|
||||
'Economizer Ring': ItemData('Orb Passive', 1337170),
|
||||
'Dusk Ring': ItemData('Orb Passive', 1337171),
|
||||
'Star of Lachiem': ItemData('Orb Passive', 1337172),
|
||||
'Star of Lachiem': ItemData('Orb Passive', 1337172, never_exclude=True),
|
||||
'Oculus Ring': ItemData('Orb Passive', 1337173, progression=True),
|
||||
'Sanguine Ring': ItemData('Orb Passive', 1337174),
|
||||
'Sun Ring': ItemData('Orb Passive', 1337175),
|
||||
'Silence Ring': ItemData('Orb Passive', 1337176),
|
||||
'Shadow Seal': ItemData('Orb Passive', 1337177),
|
||||
'Shadow Seal': ItemData('Orb Passive', 1337177, never_exclude=True),
|
||||
'Hope Ring': ItemData('Orb Passive', 1337178),
|
||||
'Max HP': ItemData('Stat', 1337179, 12),
|
||||
'Max Aura': ItemData('Stat', 1337180, 13),
|
||||
|
||||
@@ -10,7 +10,7 @@ class LocationData(NamedTuple):
|
||||
code: Optional[int]
|
||||
rule: Callable = lambda state: True
|
||||
|
||||
def get_locations(world: Optional[MultiWorld], player: Optional[int]):
|
||||
def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[LocationData, ...]:
|
||||
location_table: Tuple[LocationData, ...] = (
|
||||
# PresentItemLocations
|
||||
LocationData('Tutorial', 'Yo Momma 1', 1337000),
|
||||
|
||||
@@ -150,9 +150,9 @@ def create_regions(world: MultiWorld, player: int, locations: Tuple[LocationData
|
||||
connect(world, player, names, 'Space time continuum', 'Caves of Banishment (upper)', lambda state: pyramid_keys_unlock == "GateCavesOfBanishment")
|
||||
|
||||
|
||||
def create_location(player: int, name: str, id: Optional[int], region: Region, rule: Callable, location_cache: List[Location]) -> Location:
|
||||
location = Location(player, name, id, region)
|
||||
location.access_rule = rule
|
||||
def create_location(player: int, location_data: LocationData, region: Region, location_cache: List[Location]) -> Location:
|
||||
location = Location(player, location_data.name, location_data.code, region)
|
||||
location.access_rule = location_data.rule
|
||||
|
||||
if id is None:
|
||||
location.event = True
|
||||
@@ -169,7 +169,7 @@ def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str
|
||||
|
||||
if name in locations_per_region:
|
||||
for location_data in locations_per_region[name]:
|
||||
location = create_location(player, location_data.name, location_data.code, region, location_data.rule, location_cache)
|
||||
location = create_location(player, location_data, region, location_cache)
|
||||
region.locations.append(location)
|
||||
|
||||
return region
|
||||
|
||||
@@ -40,11 +40,11 @@ class TimespinnerWorld(World):
|
||||
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return create_item(name, self.player)
|
||||
return create_item_with_correct_settings(self.world, self.player, name)
|
||||
|
||||
|
||||
def set_rules(self):
|
||||
setup_events(self.world, self.player, self.locked_locations[self.player])
|
||||
setup_events(self.world, self.player, self.locked_locations[self.player], self.location_cache[self.player])
|
||||
|
||||
self.world.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player)
|
||||
|
||||
@@ -59,7 +59,7 @@ class TimespinnerWorld(World):
|
||||
|
||||
pool = get_item_pool(self.world, self.player, excluded_items)
|
||||
|
||||
fill_item_pool_with_dummy_items(self.world, self.player, self.locked_locations[self.player], pool)
|
||||
fill_item_pool_with_dummy_items(self.world, self.player, self.locked_locations[self.player], self.location_cache[self.player], pool)
|
||||
|
||||
self.world.itempool += pool
|
||||
|
||||
@@ -79,33 +79,28 @@ class TimespinnerWorld(World):
|
||||
return slot_data
|
||||
|
||||
|
||||
def create_item(name: str, player: int) -> Item:
|
||||
data = item_table[name]
|
||||
return Item(name, data.progression, data.code, player)
|
||||
|
||||
|
||||
def get_excluded_items_based_on_options(world: MultiWorld, player: int) -> List[str]:
|
||||
excluded_items: List[str] = []
|
||||
def get_excluded_items_based_on_options(world: MultiWorld, player: int) -> Set[str]:
|
||||
excluded_items: Set[str] = set()
|
||||
|
||||
if is_option_enabled(world, player, "StartWithJewelryBox"):
|
||||
excluded_items.append('Jewelry Box')
|
||||
excluded_items.add('Jewelry Box')
|
||||
if is_option_enabled(world, player, "StartWithMeyef"):
|
||||
excluded_items.append('Meyef')
|
||||
excluded_items.add('Meyef')
|
||||
if is_option_enabled(world, player, "QuickSeed"):
|
||||
excluded_items.append('Talaria Attachment')
|
||||
excluded_items.add('Talaria Attachment')
|
||||
|
||||
return excluded_items
|
||||
|
||||
|
||||
def assign_starter_items(world: MultiWorld, player: int, excluded_items: List[str], locked_locations: List[str]):
|
||||
def assign_starter_items(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
|
||||
melee_weapon = world.random.choice(starter_melee_weapons)
|
||||
spell = world.random.choice(starter_spells)
|
||||
|
||||
excluded_items.append(melee_weapon)
|
||||
excluded_items.append(spell)
|
||||
excluded_items.add(melee_weapon)
|
||||
excluded_items.add(spell)
|
||||
|
||||
melee_weapon_item = create_item(melee_weapon, player)
|
||||
spell_item = create_item(spell, player)
|
||||
melee_weapon_item = create_item_with_correct_settings(world, player, melee_weapon)
|
||||
spell_item = create_item_with_correct_settings(world, player, spell)
|
||||
|
||||
world.get_location('Yo Momma 1', player).place_locked_item(melee_weapon_item)
|
||||
world.get_location('Yo Momma 2', player).place_locked_item(spell_item)
|
||||
@@ -114,53 +109,57 @@ def assign_starter_items(world: MultiWorld, player: int, excluded_items: List[st
|
||||
locked_locations.append('Yo Momma 2')
|
||||
|
||||
|
||||
def get_item_pool(world: MultiWorld, player: int, excluded_items: List[str]) -> List[Item]:
|
||||
def get_item_pool(world: MultiWorld, player: int, excluded_items: Set[str]) -> List[Item]:
|
||||
pool: List[Item] = []
|
||||
|
||||
for name, data in item_table.items():
|
||||
if not name in excluded_items:
|
||||
for _ in range(data.count):
|
||||
item = update_progressive_state_based_flags(world, player, name, create_item(name, player))
|
||||
item = create_item_with_correct_settings(world, player, name)
|
||||
pool.append(item)
|
||||
|
||||
return pool
|
||||
|
||||
|
||||
def fill_item_pool_with_dummy_items(world: MultiWorld, player: int, locked_locations: List[str], pool: List[Item]):
|
||||
for _ in range(len(get_locations(world, player)) - len(locked_locations) - len(pool)):
|
||||
item = create_item(world.random.choice(filler_items), player)
|
||||
def fill_item_pool_with_dummy_items(world: MultiWorld, player: int, locked_locations: List[str],
|
||||
location_cache: List[Location], pool: List[Item]):
|
||||
for _ in range(len(location_cache) - len(locked_locations) - len(pool)):
|
||||
item = create_item_with_correct_settings(world, player, world.random.choice(filler_items))
|
||||
pool.append(item)
|
||||
|
||||
|
||||
def place_first_progression_item(world: MultiWorld, player: int, excluded_items: List[str],
|
||||
locked_locations: List[str]):
|
||||
def place_first_progression_item(world: MultiWorld, player: int, excluded_items: Set[str], locked_locations: List[str]):
|
||||
progression_item = world.random.choice(starter_progression_items)
|
||||
location = world.random.choice(starter_progression_locations)
|
||||
|
||||
excluded_items.append(progression_item)
|
||||
excluded_items.add(progression_item)
|
||||
locked_locations.append(location)
|
||||
|
||||
item = create_item(progression_item, player)
|
||||
item = create_item_with_correct_settings(world, player, progression_item)
|
||||
|
||||
world.get_location(location, player).place_locked_item(item)
|
||||
|
||||
|
||||
def update_progressive_state_based_flags(world: MultiWorld, player: int, name: str, data: Item) -> Item:
|
||||
if not data.advancement:
|
||||
return data
|
||||
def create_item_with_correct_settings(world: MultiWorld, player: int, name: str) -> Item:
|
||||
data = item_table[name]
|
||||
|
||||
item = Item(name, data.progression, data.code, player)
|
||||
item.never_exclude = data.never_exclude
|
||||
|
||||
if not item.advancement:
|
||||
return item
|
||||
|
||||
if (name == 'Tablet' or name == 'Library Keycard V') and not is_option_enabled(world, player, "DownloadableItems"):
|
||||
data.advancement = False
|
||||
item.advancement = False
|
||||
if name == 'Oculus Ring' and not is_option_enabled(world, player, "FacebookMode"):
|
||||
data.advancement = False
|
||||
item.advancement = False
|
||||
|
||||
return data
|
||||
return item
|
||||
|
||||
|
||||
def setup_events(world: MultiWorld, player: int, locked_locations: List[str]):
|
||||
for location in get_locations(world, player):
|
||||
if location.code == EventId:
|
||||
location = world.get_location(location.name, player)
|
||||
def setup_events(world: MultiWorld, player: int, locked_locations: List[str], location_cache: List[Location]):
|
||||
for location in location_cache:
|
||||
if location.address == EventId:
|
||||
item = Item(location.name, True, EventId, player)
|
||||
|
||||
locked_locations.append(location.name)
|
||||
|
||||
Reference in New Issue
Block a user