mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-05 04:38:16 -07:00
Merge branch 'main' into player-tracker
This commit is contained in:
@@ -129,6 +129,8 @@ def getItemGenericName(item):
|
||||
|
||||
|
||||
def isRestrictedDungeonItem(dungeon, item):
|
||||
if not isinstance(item, OOTItem):
|
||||
return False
|
||||
if (item.map or item.compass) and dungeon.world.shuffle_mapcompass == 'dungeon':
|
||||
return item in dungeon.dungeon_items
|
||||
if item.type == 'SmallKey' and dungeon.world.shuffle_smallkeys == 'dungeon':
|
||||
|
||||
@@ -1388,6 +1388,10 @@ def get_pool_core(world):
|
||||
remove_junk_pool = list(remove_junk_pool) + ['Recovery Heart', 'Bombs (20)', 'Arrows (30)', 'Ice Trap']
|
||||
|
||||
junk_candidates = [item for item in pool if item in remove_junk_pool]
|
||||
if len(pending_junk_pool) > len(junk_candidates):
|
||||
excess = len(pending_junk_pool) - len(junk_candidates)
|
||||
if world.triforce_hunt:
|
||||
raise RuntimeError(f"Items in the pool for player {world.player} exceed locations. Add {excess} location(s) or remove {excess} triforce piece(s).")
|
||||
while pending_junk_pool:
|
||||
pending_item = pending_junk_pool.pop()
|
||||
if not junk_candidates:
|
||||
|
||||
@@ -22,6 +22,12 @@ def ap_id_to_oot_data(ap_id):
|
||||
raise Exception(f'Could not find desired item ID: {ap_id}')
|
||||
|
||||
|
||||
def oot_is_item_of_type(item, item_type):
|
||||
if not isinstance(item, OOTItem):
|
||||
return False
|
||||
return item.type == item_type
|
||||
|
||||
|
||||
class OOTItem(Item):
|
||||
game: str = "Ocarina of Time"
|
||||
type: str
|
||||
@@ -43,7 +49,6 @@ class OOTItem(Item):
|
||||
self.type = type
|
||||
self.index = index
|
||||
self.special = special or {}
|
||||
self.looks_like_item = None
|
||||
self.price = special.get('price', None) if special else None
|
||||
self.internal = False
|
||||
|
||||
|
||||
@@ -101,7 +101,6 @@ class InteriorEntrances(Choice):
|
||||
option_off = 0
|
||||
option_simple = 1
|
||||
option_all = 2
|
||||
alias_false = 0
|
||||
alias_true = 2
|
||||
|
||||
|
||||
@@ -141,7 +140,6 @@ class MixEntrancePools(Choice):
|
||||
option_off = 0
|
||||
option_indoor = 1
|
||||
option_all = 2
|
||||
alias_false = 0
|
||||
|
||||
|
||||
class DecoupleEntrances(Toggle):
|
||||
@@ -158,12 +156,12 @@ class TriforceGoal(Range):
|
||||
"""Number of Triforce pieces required to complete the game."""
|
||||
display_name = "Required Triforce Pieces"
|
||||
range_start = 1
|
||||
range_end = 100
|
||||
range_end = 80
|
||||
default = 20
|
||||
|
||||
|
||||
class ExtraTriforces(Range):
|
||||
"""Percentage of additional Triforce pieces in the pool, separate from the item pool setting."""
|
||||
"""Percentage of additional Triforce pieces in the pool. With high numbers, you may need to randomize additional locations to have enough items."""
|
||||
display_name = "Percentage of Extra Triforce Pieces"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
@@ -308,7 +306,6 @@ class ShopShuffle(Choice):
|
||||
option_off = 0
|
||||
option_fixed_number = 1
|
||||
option_random_number = 2
|
||||
alias_false = 0
|
||||
|
||||
|
||||
class ShopSlots(Range):
|
||||
@@ -326,7 +323,6 @@ class TokenShuffle(Choice):
|
||||
option_dungeons = 1
|
||||
option_overworld = 2
|
||||
option_all = 3
|
||||
alias_false = 0
|
||||
|
||||
|
||||
class ScrubShuffle(Choice):
|
||||
@@ -336,7 +332,6 @@ class ScrubShuffle(Choice):
|
||||
option_low = 1
|
||||
option_regular = 2
|
||||
option_random_prices = 3
|
||||
alias_false = 0
|
||||
alias_affordable = 1
|
||||
alias_expensive = 2
|
||||
|
||||
@@ -569,7 +564,6 @@ class Hints(Choice):
|
||||
option_agony = 2
|
||||
option_always = 3
|
||||
default = 3
|
||||
alias_false = 0
|
||||
|
||||
|
||||
class MiscHints(DefaultOnToggle):
|
||||
@@ -673,8 +667,6 @@ class IceTraps(Choice):
|
||||
option_mayhem = 3
|
||||
option_onslaught = 4
|
||||
default = 1
|
||||
alias_false = 0
|
||||
alias_true = 2
|
||||
alias_extra = 2
|
||||
|
||||
|
||||
@@ -742,7 +734,6 @@ class Music(Choice):
|
||||
option_normal = 0
|
||||
option_off = 1
|
||||
option_randomized = 2
|
||||
alias_false = 1
|
||||
|
||||
|
||||
class BackgroundMusic(Music):
|
||||
|
||||
@@ -1844,7 +1844,7 @@ def write_rom_item(rom, item_id, item):
|
||||
|
||||
|
||||
def get_override_table(world):
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world.player), world.world.get_filled_locations(world.player))))
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world), world.world.get_filled_locations(world.player))))
|
||||
|
||||
|
||||
override_struct = struct.Struct('>xBBBHBB') # match override_t in get_items.c
|
||||
@@ -1852,10 +1852,10 @@ def get_override_table_bytes(override_table):
|
||||
return b''.join(sorted(itertools.starmap(override_struct.pack, override_table)))
|
||||
|
||||
|
||||
def get_override_entry(player_id, location):
|
||||
def get_override_entry(ootworld, location):
|
||||
scene = location.scene
|
||||
default = location.default
|
||||
player_id = 0 if player_id == location.item.player else min(location.item.player, 255)
|
||||
player_id = 0 if ootworld.player == location.item.player else min(location.item.player, 255)
|
||||
if location.item.game != 'Ocarina of Time':
|
||||
# This is an AP sendable. It's guaranteed to not be None.
|
||||
if location.item.advancement:
|
||||
@@ -1869,7 +1869,7 @@ def get_override_entry(player_id, location):
|
||||
|
||||
if location.item.trap:
|
||||
item_id = 0x7C # Ice Trap ID, to get "X is a fool" message
|
||||
looks_like_item_id = location.item.looks_like_item.index
|
||||
looks_like_item_id = ootworld.trap_appearances[location.address].index
|
||||
else:
|
||||
looks_like_item_id = 0
|
||||
|
||||
@@ -2091,7 +2091,8 @@ def get_locked_doors(rom, world):
|
||||
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
|
||||
|
||||
# If boss door, set the door's unlock flag
|
||||
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A):
|
||||
if (world.shuffle_bosskeys == 'remove' and scene != 0x0A) or (
|
||||
world.shuffle_ganon_bosskey == 'remove' and scene == 0x0A and not world.triforce_hunt):
|
||||
if actor_id == 0x002E and actor_type == 0x05:
|
||||
return [0x00D4 + scene * 0x1C + 0x04 + flag_byte, flag_bits]
|
||||
|
||||
@@ -2109,23 +2110,20 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
|
||||
rom.write_int16(location.address1, location.item.index)
|
||||
else:
|
||||
if location.item.trap:
|
||||
item_display = location.item.looks_like_item
|
||||
elif location.item.game != "Ocarina of Time":
|
||||
item_display = location.item
|
||||
if location.item.advancement:
|
||||
item_display.index = 0xCB
|
||||
else:
|
||||
item_display.index = 0xCC
|
||||
item_display.special = {}
|
||||
item_display = world.trap_appearances[location.address]
|
||||
else:
|
||||
item_display = location.item
|
||||
|
||||
# bottles in shops should look like empty bottles
|
||||
# so that that are different than normal shop refils
|
||||
if 'shop_object' in item_display.special:
|
||||
rom_item = read_rom_item(rom, item_display.special['shop_object'])
|
||||
if location.item.trap or location.item.game == "Ocarina of Time":
|
||||
if 'shop_object' in item_display.special:
|
||||
rom_item = read_rom_item(rom, item_display.special['shop_object'])
|
||||
else:
|
||||
rom_item = read_rom_item(rom, item_display.index)
|
||||
else:
|
||||
rom_item = read_rom_item(rom, item_display.index)
|
||||
display_index = 0xCB if location.item.advancement else 0xCC
|
||||
rom_item = read_rom_item(rom, display_index)
|
||||
|
||||
shop_objs.add(rom_item['object_id'])
|
||||
shop_id = world.current_shop_id
|
||||
|
||||
@@ -3,6 +3,7 @@ import logging
|
||||
|
||||
from .SaveContext import SaveContext
|
||||
from .Regions import TimeOfDay
|
||||
from .Items import oot_is_item_of_type
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from worlds.generic.Rules import set_rule, add_rule, add_item_rule, forbid_item
|
||||
@@ -138,7 +139,7 @@ def set_rules(ootworld):
|
||||
# Sheik in Ice Cavern is the only song location in a dungeon; need to ensure that it cannot be anything else.
|
||||
# This is required if map/compass included, or any_dungeon shuffle.
|
||||
location = world.get_location('Sheik in Ice Cavern', player)
|
||||
add_item_rule(location, lambda item: item.player == player and item.type == 'Song')
|
||||
add_item_rule(location, lambda item: item.player == player and oot_is_item_of_type(item, 'Song'))
|
||||
|
||||
if ootworld.skip_child_zelda:
|
||||
# If skip child zelda is on, the item at Song from Impa must be giveable by the save context.
|
||||
@@ -181,7 +182,7 @@ def set_shop_rules(ootworld):
|
||||
wallet = ootworld.parser.parse_rule('Progressive_Wallet')
|
||||
wallet2 = ootworld.parser.parse_rule('(Progressive_Wallet, 2)')
|
||||
|
||||
for location in filter(lambda location: location.item and location.item.type == 'Shop', ootworld.get_locations()):
|
||||
for location in filter(lambda location: location.item and oot_is_item_of_type(location.item, 'Shop'), ootworld.get_locations()):
|
||||
# Add wallet requirements
|
||||
if location.item.name in ['Buy Arrows (50)', 'Buy Fish', 'Buy Goron Tunic', 'Buy Bombchu (20)', 'Buy Bombs (30)']:
|
||||
add_rule(location, wallet)
|
||||
|
||||
@@ -8,7 +8,7 @@ logger = logging.getLogger("Ocarina of Time")
|
||||
from .Location import OOTLocation, LocationFactory, location_name_to_id
|
||||
from .Entrance import OOTEntrance
|
||||
from .EntranceShuffle import shuffle_random_entrances, entrance_shuffle_table, EntranceShuffleError
|
||||
from .Items import OOTItem, item_table, oot_data_to_ap_id
|
||||
from .Items import OOTItem, item_table, oot_data_to_ap_id, oot_is_item_of_type
|
||||
from .ItemPool import generate_itempool, add_dungeon_items, get_junk_item, get_junk_pool, normal_bottles
|
||||
from .Regions import OOTRegion, TimeOfDay
|
||||
from .Rules import set_rules, set_shop_rules, set_entrances_based_rules
|
||||
@@ -305,6 +305,10 @@ class OOTWorld(World):
|
||||
if self.skip_child_zelda:
|
||||
self.shuffle_weird_egg = False
|
||||
|
||||
# Ganon boss key should not be in itempool in triforce hunt
|
||||
if self.triforce_hunt:
|
||||
self.shuffle_ganon_bosskey = 'remove'
|
||||
|
||||
# Determine skipped trials in GT
|
||||
# This needs to be done before the logic rules in GT are parsed
|
||||
trial_list = ['Forest', 'Fire', 'Water', 'Spirit', 'Shadow', 'Light']
|
||||
@@ -313,7 +317,11 @@ class OOTWorld(World):
|
||||
|
||||
# Determine which dungeons are MQ
|
||||
# Possible future plan: allow user to pick which dungeons are MQ
|
||||
mq_dungeons = self.world.random.sample(dungeon_table, self.mq_dungeons)
|
||||
if self.logic_rules == 'glitchless':
|
||||
mq_dungeons = self.world.random.sample(dungeon_table, self.mq_dungeons)
|
||||
else:
|
||||
self.mq_dungeons = 0
|
||||
mq_dungeons = []
|
||||
self.dungeon_mq = {item['name']: (item in mq_dungeons) for item in dungeon_table}
|
||||
|
||||
# Determine tricks in logic
|
||||
@@ -920,7 +928,7 @@ class OOTWorld(World):
|
||||
# This includes all locations for which show_in_spoiler is false, and shuffled shop items.
|
||||
for loc in self.get_locations():
|
||||
if loc.address is not None and (
|
||||
not loc.show_in_spoiler or (loc.item is not None and loc.item.type == 'Shop')
|
||||
not loc.show_in_spoiler or oot_is_item_of_type(loc.item, 'Shop')
|
||||
or (self.skip_child_zelda and loc.name in ['HC Zeldas Letter', 'Song from Impa'])):
|
||||
loc.address = None
|
||||
|
||||
@@ -930,9 +938,10 @@ class OOTWorld(World):
|
||||
|
||||
with i_o_limiter:
|
||||
# Make traps appear as other random items
|
||||
ice_traps = [loc.item for loc in self.get_locations() if loc.item.trap]
|
||||
for trap in ice_traps:
|
||||
trap.looks_like_item = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
|
||||
trap_location_ids = [loc.address for loc in self.get_locations() if loc.item.trap]
|
||||
self.trap_appearances = {}
|
||||
for loc_id in trap_location_ids:
|
||||
self.trap_appearances[loc_id] = self.create_item(self.world.slot_seeds[self.player].choice(self.fake_items).name)
|
||||
|
||||
# Seed hint RNG, used for ganon text lines also
|
||||
self.hint_rng = self.world.slot_seeds[self.player]
|
||||
@@ -996,11 +1005,11 @@ class OOTWorld(World):
|
||||
autoworld.major_item_locations.append(loc)
|
||||
|
||||
if loc.game == "Ocarina of Time" and loc.item.code and (not loc.locked or
|
||||
(loc.item.type == 'Song' or
|
||||
(loc.item.type == 'SmallKey' and world.worlds[loc.player].shuffle_smallkeys == 'any_dungeon') or
|
||||
(loc.item.type == 'HideoutSmallKey' and world.worlds[loc.player].shuffle_fortresskeys == 'any_dungeon') or
|
||||
(loc.item.type == 'BossKey' and world.worlds[loc.player].shuffle_bosskeys == 'any_dungeon') or
|
||||
(loc.item.type == 'GanonBossKey' and world.worlds[loc.player].shuffle_ganon_bosskey == 'any_dungeon'))):
|
||||
(oot_is_item_of_type(loc.item, 'Song') or
|
||||
(oot_is_item_of_type(loc.item, 'SmallKey') and world.worlds[loc.player].shuffle_smallkeys == 'any_dungeon') or
|
||||
(oot_is_item_of_type(loc.item, 'HideoutSmallKey') and world.worlds[loc.player].shuffle_fortresskeys == 'any_dungeon') or
|
||||
(oot_is_item_of_type(loc.item, 'BossKey') and world.worlds[loc.player].shuffle_bosskeys == 'any_dungeon') or
|
||||
(oot_is_item_of_type(loc.item, 'GanonBossKey') and world.worlds[loc.player].shuffle_ganon_bosskey == 'any_dungeon'))):
|
||||
if loc.player in barren_hint_players:
|
||||
hint_area = get_hint_area(loc)
|
||||
items_by_region[loc.player][hint_area]['weight'] += 1
|
||||
@@ -1015,7 +1024,7 @@ class OOTWorld(World):
|
||||
elif barren_hint_players or woth_hint_players: # Check only relevant oot locations for barren/woth
|
||||
for player in (barren_hint_players | woth_hint_players):
|
||||
for loc in world.worlds[player].get_locations():
|
||||
if loc.item.code and (not loc.locked or loc.item.type == 'Song'):
|
||||
if loc.item.code and (not loc.locked or oot_is_item_of_type(loc.item, 'Song')):
|
||||
if player in barren_hint_players:
|
||||
hint_area = get_hint_area(loc)
|
||||
items_by_region[player][hint_area]['weight'] += 1
|
||||
|
||||
Reference in New Issue
Block a user