mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-04-12 13:38:13 -07:00
Merge branch 'main' into feature/ds3_use_slotdata
# Conflicts: # worlds/dark_souls_3/__init__.py
This commit is contained in:
@@ -182,7 +182,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
web: WebWorld = WebWorld()
|
||||
|
||||
# autoset on creation:
|
||||
world: "MultiWorld"
|
||||
multiworld: "MultiWorld"
|
||||
player: int
|
||||
|
||||
# automatically generated
|
||||
@@ -196,7 +196,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
__file__: str # path it was loaded from
|
||||
|
||||
def __init__(self, world: "MultiWorld", player: int):
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.player = player
|
||||
|
||||
# overridable methods that get called by Main.py, sorted by execution order
|
||||
@@ -287,7 +287,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
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.world.random.choice(tuple(self.item_name_to_id.keys()))
|
||||
return self.multiworld.random.choice(tuple(self.item_name_to_id.keys()))
|
||||
|
||||
# decent place to implement progressive items, in most cases can stay as-is
|
||||
def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]:
|
||||
|
||||
@@ -81,7 +81,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
|
||||
state.has('Fire Rod', player) or
|
||||
(
|
||||
state.has('Bombos', player) and
|
||||
(state.has_sword(player) or state.world.swordless[player])
|
||||
(state.has_sword(player) or state.multiworld.swordless[player])
|
||||
)
|
||||
) and
|
||||
(
|
||||
@@ -90,7 +90,7 @@ def KholdstareDefeatRule(state, player: int) -> bool:
|
||||
(
|
||||
state.has('Fire Rod', player) and
|
||||
state.has('Bombos', player) and
|
||||
state.world.swordless[player] and
|
||||
state.multiworld.swordless[player] and
|
||||
state.can_extend_magic(player, 16)
|
||||
)
|
||||
)
|
||||
@@ -114,7 +114,7 @@ def AgahnimDefeatRule(state, player: int) -> bool:
|
||||
|
||||
|
||||
def GanonDefeatRule(state, player: int) -> bool:
|
||||
if state.world.swordless[player]:
|
||||
if state.multiworld.swordless[player]:
|
||||
return state.has('Hammer', player) and \
|
||||
state.has_fire_source(player) and \
|
||||
state.has('Silver Bow', player) and \
|
||||
@@ -123,7 +123,7 @@ def GanonDefeatRule(state, player: int) -> bool:
|
||||
can_hurt = state.has_beam_sword(player)
|
||||
common = can_hurt and state.has_fire_source(player)
|
||||
# silverless ganon may be needed in anything higher than no glitches
|
||||
if state.world.logic[player] != 'noglitches':
|
||||
if state.multiworld.logic[player] != 'noglitches':
|
||||
# need to light torch a sufficient amount of times
|
||||
return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (
|
||||
state.has('Silver Bow', player) and state.can_shoot_arrows(player)) or
|
||||
|
||||
@@ -18,7 +18,7 @@ def create_dungeons(world, player):
|
||||
dungeon.boss = BossFactory(default_boss, player) if default_boss else None
|
||||
for region in dungeon.regions:
|
||||
world.get_region(region, player).dungeon = dungeon
|
||||
dungeon.world = world
|
||||
dungeon.multiworld = world
|
||||
return dungeon
|
||||
|
||||
ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'],
|
||||
|
||||
@@ -224,7 +224,7 @@ for diff in {'easy', 'normal', 'hard', 'expert'}:
|
||||
|
||||
def generate_itempool(world):
|
||||
player = world.player
|
||||
world = world.world
|
||||
world = world.multiworld
|
||||
|
||||
if world.difficulty[player] not in difficulties:
|
||||
raise NotImplementedError(f"Diffulty {world.difficulty[player]}")
|
||||
@@ -286,7 +286,7 @@ def generate_itempool(world):
|
||||
region = world.get_region('Light World', player)
|
||||
|
||||
loc = ALttPLocation(player, "Murahdahla", parent=region)
|
||||
loc.access_rule = lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player)
|
||||
loc.access_rule = lambda state: state.has_triforce_pieces(state.multiworld.treasure_hunt_count[player], player)
|
||||
|
||||
region.locations.append(loc)
|
||||
world.clear_location_cache()
|
||||
|
||||
@@ -13,7 +13,7 @@ from worlds.alttp.Options import smallkey_shuffle
|
||||
|
||||
def set_rules(world):
|
||||
player = world.player
|
||||
world = world.world
|
||||
world = world.multiworld
|
||||
if world.logic[player] == 'nologic':
|
||||
if player == next(player_id for player_id in world.get_game_players("A Link to the Past")
|
||||
if world.logic[player_id] == 'nologic'): # only warn one time
|
||||
@@ -81,7 +81,7 @@ def set_rules(world):
|
||||
set_big_bomb_rules(world, player)
|
||||
if world.logic[player] in {'owglitches', 'hybridglitches', 'nologic'} and world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}:
|
||||
path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player)
|
||||
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.world.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or')
|
||||
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or')
|
||||
else:
|
||||
set_inverted_big_bomb_rules(world, player)
|
||||
|
||||
@@ -97,9 +97,9 @@ def set_rules(world):
|
||||
|
||||
set_trock_key_rules(world, player)
|
||||
|
||||
set_rule(ganons_tower, lambda state: state.has_crystals(state.world.crystals_needed_for_gt[player], player))
|
||||
set_rule(ganons_tower, lambda state: state.has_crystals(state.multiworld.crystals_needed_for_gt[player], player))
|
||||
if world.mode[player] != 'inverted' and world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
|
||||
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
||||
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.multiworld.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
||||
|
||||
set_bunny_rules(world, player, world.mode[player] == 'inverted')
|
||||
|
||||
@@ -204,7 +204,7 @@ def global_rules(world, player):
|
||||
((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
|
||||
(state.has('Cane of Byrna', player) and
|
||||
(state.can_extend_magic(player, 12, True) or
|
||||
(state.world.can_take_damage[player] and (state.has('Pegasus Boots', player) or state.has_hearts(player, 4))))))
|
||||
(state.multiworld.can_take_damage[player] and (state.has('Pegasus Boots', player) or state.has_hearts(player, 4))))))
|
||||
)
|
||||
|
||||
set_rule(world.get_location('Hookshot Cave - Top Right', player), lambda state: state.has('Hookshot', player))
|
||||
@@ -242,12 +242,12 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player))
|
||||
set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player))
|
||||
set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player))
|
||||
set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.world.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.world.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and state.has_fire_source(player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
|
||||
# logic patch to prevent placing a crystal in Desert that's required to reach the required keys
|
||||
if not (world.smallkey_shuffle[player] and world.bigkey_shuffle[player]):
|
||||
add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.world.get_region('Desert Palace Main (Outer)', player).can_reach(state))
|
||||
add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state))
|
||||
|
||||
set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Tower of Hera)', player) or item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))
|
||||
set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player))
|
||||
@@ -286,12 +286,12 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
|
||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 1))))
|
||||
set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or (
|
||||
item_in_locations(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state._lttp_has_key('Small Key (Ice Palace)', player))) and (state.world.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
|
||||
item_in_locations(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state._lttp_has_key('Small Key (Ice Palace)', player))) and (state.multiworld.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
|
||||
set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: state.can_lift_rocks(player) and state.has('Hammer', player))
|
||||
|
||||
set_rule(world.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has('Pegasus Boots', player) or state.has('Hookshot', player)) and (state.has_sword(player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player))) # need to defeat wizzrobes, bombs don't work ...
|
||||
set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage[player] and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.multiworld.can_take_damage[player] and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||
set_rule(world.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
|
||||
# big key gives backdoor access to that from the teleporter in the north west
|
||||
@@ -377,11 +377,11 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player),
|
||||
lambda state: state.world.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Chest', player),
|
||||
lambda state: state.world.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
lambda state: state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player),
|
||||
lambda state: state.world.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
|
||||
if world.enemy_shuffle[player]:
|
||||
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
|
||||
lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
@@ -389,22 +389,22 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
|
||||
lambda state: state.has('Big Key (Ganons Tower)', player) and state.can_shoot_arrows(player))
|
||||
set_rule(world.get_entrance('Ganons Tower Torch Rooms', player),
|
||||
lambda state: state.has_fire_source(player) and state.world.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state))
|
||||
lambda state: state.has_fire_source(player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state))
|
||||
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 3))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Door', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 4))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Gap', player),
|
||||
lambda state: state.has('Hookshot', player) and state.world.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state))
|
||||
lambda state: state.has('Hookshot', player) and state.multiworld.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
||||
ganon = world.get_location('Ganon', player)
|
||||
set_rule(ganon, lambda state: GanonDefeatRule(state, player))
|
||||
if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
|
||||
add_rule(ganon, lambda state: state.has_triforce_pieces(state.world.treasure_hunt_count[player], player))
|
||||
add_rule(ganon, lambda state: state.has_triforce_pieces(state.multiworld.treasure_hunt_count[player], player))
|
||||
elif world.goal[player] == 'ganonpedestal':
|
||||
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player))
|
||||
else:
|
||||
add_rule(ganon, lambda state: state.has_crystals(state.world.crystals_needed_for_ganon[player], player))
|
||||
add_rule(ganon, lambda state: state.has_crystals(state.multiworld.crystals_needed_for_ganon[player], player))
|
||||
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has_beam_sword(player)) # need to damage ganon to get tiles to drop
|
||||
|
||||
set_rule(world.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player))
|
||||
@@ -942,7 +942,7 @@ def set_trock_key_rules(world, player):
|
||||
|
||||
if world.accessibility[player] != 'locations':
|
||||
set_always_allow(world.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player
|
||||
and state.can_reach(state.world.get_region('Turtle Rock (Second Section)', player)))
|
||||
and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player)))
|
||||
|
||||
|
||||
def set_big_bomb_rules(world, player):
|
||||
|
||||
@@ -163,7 +163,7 @@ class ALTTPWorld(World):
|
||||
check_enemizer(self.enemizer_path)
|
||||
|
||||
player = self.player
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
|
||||
# system for sharing ER layouts
|
||||
self.er_seed = str(world.random.randint(0, 2 ** 64))
|
||||
@@ -195,7 +195,7 @@ class ALTTPWorld(World):
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
|
||||
world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player],
|
||||
world.triforce_pieces_required[player])
|
||||
@@ -219,21 +219,21 @@ class ALTTPWorld(World):
|
||||
link_entrances(world, player)
|
||||
mark_light_world_regions(world, player)
|
||||
for region_name, entrance_name in indirect_connections_not_inverted.items():
|
||||
world.register_indirect_condition(self.world.get_region(region_name, player),
|
||||
self.world.get_entrance(entrance_name, player))
|
||||
world.register_indirect_condition(world.get_region(region_name, player),
|
||||
world.get_entrance(entrance_name, player))
|
||||
else:
|
||||
link_inverted_entrances(world, player)
|
||||
mark_dark_world_regions(world, player)
|
||||
for region_name, entrance_name in indirect_connections_inverted.items():
|
||||
world.register_indirect_condition(self.world.get_region(region_name, player),
|
||||
self.world.get_entrance(entrance_name, player))
|
||||
world.register_indirect_condition(world.get_region(region_name, player),
|
||||
world.get_entrance(entrance_name, player))
|
||||
|
||||
world.random = old_random
|
||||
plando_connect(world, player)
|
||||
|
||||
for region_name, entrance_name in indirect_connections.items():
|
||||
world.register_indirect_condition(self.world.get_region(region_name, player),
|
||||
self.world.get_entrance(entrance_name, player))
|
||||
world.register_indirect_condition(world.get_region(region_name, player),
|
||||
world.get_entrance(entrance_name, player))
|
||||
|
||||
|
||||
def collect_item(self, state: CollectionState, item: Item, remove=False):
|
||||
@@ -278,15 +278,15 @@ class ALTTPWorld(World):
|
||||
if 'Sword' in item_name:
|
||||
if state.has('Golden Sword', item.player):
|
||||
pass
|
||||
elif state.has('Tempered Sword', item.player) and self.world.difficulty_requirements[
|
||||
elif state.has('Tempered Sword', item.player) and self.multiworld.difficulty_requirements[
|
||||
item.player].progressive_sword_limit >= 4:
|
||||
return 'Golden Sword'
|
||||
elif state.has('Master Sword', item.player) and self.world.difficulty_requirements[
|
||||
elif state.has('Master Sword', item.player) and self.multiworld.difficulty_requirements[
|
||||
item.player].progressive_sword_limit >= 3:
|
||||
return 'Tempered Sword'
|
||||
elif state.has('Fighter Sword', item.player) and self.world.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||
elif state.has('Fighter Sword', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 2:
|
||||
return 'Master Sword'
|
||||
elif self.world.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||
elif self.multiworld.difficulty_requirements[item.player].progressive_sword_limit >= 1:
|
||||
return 'Fighter Sword'
|
||||
elif 'Glove' in item_name:
|
||||
if state.has('Titans Mitts', item.player):
|
||||
@@ -298,20 +298,20 @@ class ALTTPWorld(World):
|
||||
elif 'Shield' in item_name:
|
||||
if state.has('Mirror Shield', item.player):
|
||||
return
|
||||
elif state.has('Red Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||
elif state.has('Red Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 3:
|
||||
return 'Mirror Shield'
|
||||
elif state.has('Blue Shield', item.player) and self.world.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||
elif state.has('Blue Shield', item.player) and self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 2:
|
||||
return 'Red Shield'
|
||||
elif self.world.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||
elif self.multiworld.difficulty_requirements[item.player].progressive_shield_limit >= 1:
|
||||
return 'Blue Shield'
|
||||
elif 'Bow' in item_name:
|
||||
if state.has('Silver Bow', item.player):
|
||||
return
|
||||
elif state.has('Bow', item.player) and (self.world.difficulty_requirements[item.player].progressive_bow_limit >= 2
|
||||
or self.world.logic[item.player] == 'noglitches'
|
||||
or self.world.swordless[item.player]): # modes where silver bow is always required for ganon
|
||||
elif state.has('Bow', item.player) and (self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 2
|
||||
or self.multiworld.logic[item.player] == 'noglitches'
|
||||
or self.multiworld.swordless[item.player]): # modes where silver bow is always required for ganon
|
||||
return 'Silver Bow'
|
||||
elif self.world.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
||||
elif self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
||||
return 'Bow'
|
||||
elif item.advancement:
|
||||
return item_name
|
||||
@@ -319,7 +319,7 @@ class ALTTPWorld(World):
|
||||
def pre_fill(self):
|
||||
from Fill import fill_restrictive, FillError
|
||||
attempts = 5
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
all_state = world.get_all_state(use_cache=True)
|
||||
crystals = [self.create_item(name) for name in ['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']]
|
||||
@@ -362,7 +362,7 @@ class ALTTPWorld(World):
|
||||
ShopSlotFill(world)
|
||||
|
||||
def use_enemizer(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
return (world.boss_shuffle[player] or world.enemy_shuffle[player]
|
||||
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
|
||||
@@ -370,7 +370,7 @@ class ALTTPWorld(World):
|
||||
or world.killable_thieves[player])
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
try:
|
||||
use_enemizer = self.use_enemizer()
|
||||
@@ -409,7 +409,7 @@ class ALTTPWorld(World):
|
||||
deathlink=world.death_link[player],
|
||||
allowcollect=world.allow_collect[player])
|
||||
|
||||
rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc")
|
||||
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc")
|
||||
rom.write_to_file(rompath)
|
||||
patch = LttPDeltaPatch(os.path.splitext(rompath)[0]+LttPDeltaPatch.patch_file_ending, player=player,
|
||||
player_name=world.player_name[player], patched_path=rompath)
|
||||
@@ -443,7 +443,7 @@ class ALTTPWorld(World):
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if rom_name:
|
||||
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return ALttPItem(name, self.player, **item_init_table[name])
|
||||
@@ -510,16 +510,16 @@ class ALTTPWorld(World):
|
||||
logging.warning(f"Could not trash fill Ganon's Tower for player {player}.")
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
if self.world.goal[self.player] == "icerodhunt":
|
||||
if self.multiworld.goal[self.player] == "icerodhunt":
|
||||
item = "Nothing"
|
||||
else:
|
||||
item = self.world.random.choice(extras_list)
|
||||
return GetBeemizerItem(self.world, self.player, item)
|
||||
item = self.multiworld.random.choice(extras_list)
|
||||
return GetBeemizerItem(self.multiworld, self.player, item)
|
||||
|
||||
def get_pre_fill_items(self):
|
||||
res = []
|
||||
if self.dungeon_local_item_names:
|
||||
for (name, player), dungeon in self.world.dungeons.items():
|
||||
for (name, player), dungeon in self.multiworld.dungeons.items():
|
||||
if player == self.player:
|
||||
for item in dungeon.all_items:
|
||||
if item.name in self.dungeon_local_item_names:
|
||||
@@ -538,8 +538,8 @@ def get_same_seed(world, seed_def: tuple) -> str:
|
||||
|
||||
class ALttPLogic(LogicMixin):
|
||||
def _lttp_has_key(self, item, player, count: int = 1):
|
||||
if self.world.logic[player] == 'nologic':
|
||||
if self.multiworld.logic[player] == 'nologic':
|
||||
return True
|
||||
if self.world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
if self.multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||
return self.prog_items[item, player] >= count
|
||||
|
||||
@@ -43,7 +43,7 @@ class ArchipIDLEWorld(World):
|
||||
|
||||
def generate_basic(self):
|
||||
item_table_copy = list(item_table)
|
||||
self.world.random.shuffle(item_table_copy)
|
||||
self.multiworld.random.shuffle(item_table_copy)
|
||||
|
||||
item_pool = []
|
||||
for i in range(100):
|
||||
@@ -55,31 +55,31 @@ class ArchipIDLEWorld(World):
|
||||
)
|
||||
item_pool.append(item)
|
||||
|
||||
self.world.itempool += item_pool
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return Item(name, ItemClassification.progression, self.item_name_to_id[name], self.player)
|
||||
|
||||
def create_regions(self):
|
||||
self.world.regions += [
|
||||
create_region(self.world, self.player, 'Menu', None, ['Entrance to IDLE Zone']),
|
||||
create_region(self.world, self.player, 'IDLE Zone', self.location_name_to_id)
|
||||
self.multiworld.regions += [
|
||||
create_region(self.multiworld, self.player, 'Menu', None, ['Entrance to IDLE Zone']),
|
||||
create_region(self.multiworld, self.player, 'IDLE Zone', self.location_name_to_id)
|
||||
]
|
||||
|
||||
# link up our region with the entrance we just made
|
||||
self.world.get_entrance('Entrance to IDLE Zone', self.player)\
|
||||
.connect(self.world.get_region('IDLE Zone', self.player))
|
||||
self.multiworld.get_entrance('Entrance to IDLE Zone', self.player)\
|
||||
.connect(self.multiworld.get_region('IDLE Zone', self.player))
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(item_table)
|
||||
return self.multiworld.random.choice(item_table)
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
region = Region(name, RegionType.Generic, name, player)
|
||||
region.world = world
|
||||
region.multiworld = world
|
||||
if locations:
|
||||
for location_name in locations.keys():
|
||||
location = ArchipIDLELocation(player, location_name, locations[location_name], region)
|
||||
|
||||
@@ -38,12 +38,12 @@ class ChecksFinderWorld(World):
|
||||
|
||||
def _get_checksfinder_data(self):
|
||||
return {
|
||||
'world_seed': self.world.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.world.seed_name,
|
||||
'player_name': self.world.get_player_name(self.player),
|
||||
'world_seed': self.multiworld.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.multiworld.seed_name,
|
||||
'player_name': self.multiworld.get_player_name(self.player),
|
||||
'player_id': self.player,
|
||||
'client_version': client_version,
|
||||
'race': self.world.is_race,
|
||||
'race': self.multiworld.is_race,
|
||||
}
|
||||
|
||||
def generate_basic(self):
|
||||
@@ -61,15 +61,15 @@ class ChecksFinderWorld(World):
|
||||
# Convert itempool into real items
|
||||
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_completion_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
set_completion_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
def ChecksFinderRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, RegionType.Generic, region_name, self.player, self.world)
|
||||
ret = Region(region_name, RegionType.Generic, region_name, self.player, self.multiworld)
|
||||
ret.locations = [ChecksFinderAdvancement(self.player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name]
|
||||
@@ -77,13 +77,13 @@ class ChecksFinderWorld(World):
|
||||
ret.exits.append(Entrance(self.player, exit, ret))
|
||||
return ret
|
||||
|
||||
self.world.regions += [ChecksFinderRegion(*r) for r in checksfinder_regions]
|
||||
link_checksfinder_structures(self.world, self.player)
|
||||
self.multiworld.regions += [ChecksFinderRegion(*r) for r in checksfinder_regions]
|
||||
link_checksfinder_structures(self.multiworld, self.player)
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = self._get_checksfinder_data()
|
||||
for option_name in checksfinder_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
@@ -80,7 +80,7 @@ class DarkSouls3World(World):
|
||||
|
||||
def create_regions(self):
|
||||
menu_region = Region("Menu", RegionType.Generic, "Menu", self.player)
|
||||
self.world.regions.append(menu_region)
|
||||
self.multiworld.regions.append(menu_region)
|
||||
|
||||
# Create all Vanilla regions of Dark Souls III
|
||||
firelink_shrine_region = self.create_region("Firelink Shrine", fire_link_shrine_table)
|
||||
@@ -107,71 +107,71 @@ class DarkSouls3World(World):
|
||||
|
||||
# Create the entrance to connect those regions
|
||||
menu_region.exits.append(Entrance(self.player, "New Game", menu_region))
|
||||
self.world.get_entrance("New Game", self.player).connect(firelink_shrine_region)
|
||||
self.multiworld.get_entrance("New Game", self.player).connect(firelink_shrine_region)
|
||||
firelink_shrine_region.exits.append(Entrance(self.player, "Goto High Wall of Lothric",
|
||||
firelink_shrine_region))
|
||||
firelink_shrine_region.exits.append(Entrance(self.player, "Goto Kiln Of The First Flame",
|
||||
firelink_shrine_region))
|
||||
firelink_shrine_region.exits.append(Entrance(self.player, "Goto Bell Tower",
|
||||
firelink_shrine_region))
|
||||
self.world.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region)
|
||||
self.world.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region)
|
||||
self.world.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region)
|
||||
self.multiworld.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region)
|
||||
self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region)
|
||||
self.multiworld.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region)
|
||||
high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Undead Settlement",
|
||||
high_wall_of_lothric_region))
|
||||
high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Lothric Castle",
|
||||
high_wall_of_lothric_region))
|
||||
self.world.get_entrance("Goto Undead Settlement", self.player).connect(undead_settlement_region)
|
||||
self.world.get_entrance("Goto Lothric Castle", self.player).connect(lothric_castle_region)
|
||||
self.multiworld.get_entrance("Goto Undead Settlement", self.player).connect(undead_settlement_region)
|
||||
self.multiworld.get_entrance("Goto Lothric Castle", self.player).connect(lothric_castle_region)
|
||||
undead_settlement_region.exits.append(Entrance(self.player, "Goto Road Of Sacrifices",
|
||||
undead_settlement_region))
|
||||
self.world.get_entrance("Goto Road Of Sacrifices", self.player).connect(road_of_sacrifices_region)
|
||||
self.multiworld.get_entrance("Goto Road Of Sacrifices", self.player).connect(road_of_sacrifices_region)
|
||||
road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Cathedral", road_of_sacrifices_region))
|
||||
road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Farron keep", road_of_sacrifices_region))
|
||||
self.world.get_entrance("Goto Cathedral", self.player).connect(cathedral_of_the_deep_region)
|
||||
self.world.get_entrance("Goto Farron keep", self.player).connect(farron_keep_region)
|
||||
self.multiworld.get_entrance("Goto Cathedral", self.player).connect(cathedral_of_the_deep_region)
|
||||
self.multiworld.get_entrance("Goto Farron keep", self.player).connect(farron_keep_region)
|
||||
farron_keep_region.exits.append(Entrance(self.player, "Goto Carthus catacombs", farron_keep_region))
|
||||
self.world.get_entrance("Goto Carthus catacombs", self.player).connect(catacombs_of_carthus_region)
|
||||
self.multiworld.get_entrance("Goto Carthus catacombs", self.player).connect(catacombs_of_carthus_region)
|
||||
catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Irithyll of the boreal",
|
||||
catacombs_of_carthus_region))
|
||||
catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Smouldering Lake",
|
||||
catacombs_of_carthus_region))
|
||||
self.world.get_entrance("Goto Irithyll of the boreal", self.player).\
|
||||
self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player).\
|
||||
connect(irithyll_of_the_boreal_valley_region)
|
||||
self.world.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region)
|
||||
self.multiworld.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region)
|
||||
irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Irithyll dungeon",
|
||||
irithyll_of_the_boreal_valley_region))
|
||||
irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Anor Londo",
|
||||
irithyll_of_the_boreal_valley_region))
|
||||
self.world.get_entrance("Goto Irithyll dungeon", self.player).connect(irithyll_dungeon_region)
|
||||
self.world.get_entrance("Goto Anor Londo", self.player).connect(anor_londo_region)
|
||||
self.multiworld.get_entrance("Goto Irithyll dungeon", self.player).connect(irithyll_dungeon_region)
|
||||
self.multiworld.get_entrance("Goto Anor Londo", self.player).connect(anor_londo_region)
|
||||
irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Archdragon peak", irithyll_dungeon_region))
|
||||
irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Profaned capital", irithyll_dungeon_region))
|
||||
self.world.get_entrance("Goto Archdragon peak", self.player).connect(archdragon_peak_region)
|
||||
self.world.get_entrance("Goto Profaned capital", self.player).connect(profaned_capital_region)
|
||||
self.multiworld.get_entrance("Goto Archdragon peak", self.player).connect(archdragon_peak_region)
|
||||
self.multiworld.get_entrance("Goto Profaned capital", self.player).connect(profaned_capital_region)
|
||||
lothric_castle_region.exits.append(Entrance(self.player, "Goto Consumed King Garden", lothric_castle_region))
|
||||
lothric_castle_region.exits.append(Entrance(self.player, "Goto Grand Archives", lothric_castle_region))
|
||||
self.world.get_entrance("Goto Consumed King Garden", self.player).connect(consumed_king_garden_region)
|
||||
self.world.get_entrance("Goto Grand Archives", self.player).connect(grand_archives_region)
|
||||
self.multiworld.get_entrance("Goto Consumed King Garden", self.player).connect(consumed_king_garden_region)
|
||||
self.multiworld.get_entrance("Goto Grand Archives", self.player).connect(grand_archives_region)
|
||||
consumed_king_garden_region.exits.append(Entrance(self.player, "Goto Untended Graves",
|
||||
consumed_king_garden_region))
|
||||
self.world.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region)
|
||||
self.multiworld.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region)
|
||||
|
||||
# For each region, add the associated locations retrieved from the corresponding location_table
|
||||
def create_region(self, region_name, location_table) -> Region:
|
||||
new_region = Region(region_name, RegionType.Generic, region_name, self.player, self.world)
|
||||
new_region = Region(region_name, RegionType.Generic, region_name, self.player, self.multiworld)
|
||||
if location_table:
|
||||
for name, address in location_table.items():
|
||||
location = DarkSouls3Location(self.player, name, self.location_name_to_id[name], new_region)
|
||||
new_region.locations.append(location)
|
||||
self.world.regions.append(new_region)
|
||||
self.multiworld.regions.append(new_region)
|
||||
return new_region
|
||||
|
||||
def create_items(self):
|
||||
for name, address in self.item_name_to_id.items():
|
||||
# Specific items will be included in the item pool under certain conditions. See generate_basic
|
||||
if name != "Basin of Vows":
|
||||
self.world.itempool += [self.create_item(name)]
|
||||
self.multiworld.itempool += [self.create_item(name)]
|
||||
|
||||
def generate_early(self):
|
||||
pass
|
||||
@@ -179,46 +179,46 @@ class DarkSouls3World(World):
|
||||
def set_rules(self) -> None:
|
||||
|
||||
# Define the access rules to the entrances
|
||||
set_rule(self.world.get_entrance("Goto Bell Tower", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Bell Tower", self.player),
|
||||
lambda state: state.has("Tower Key", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Undead Settlement", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Undead Settlement", self.player),
|
||||
lambda state: state.has("Small Lothric Banner", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Lothric Castle", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Lothric Castle", self.player),
|
||||
lambda state: state.has("Basin of Vows", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Irithyll of the boreal", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Irithyll of the boreal", self.player),
|
||||
lambda state: state.has("Small Doll", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Archdragon peak", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Archdragon peak", self.player),
|
||||
lambda state: state.can_reach("CKG: Soul of Consumed Oceiros", "Location", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Profaned capital", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Profaned capital", self.player),
|
||||
lambda state: state.has("Storm Ruler", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Grand Archives", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Grand Archives", self.player),
|
||||
lambda state: state.has("Grand Archives Key", self.player))
|
||||
set_rule(self.world.get_entrance("Goto Kiln Of The First Flame", self.player),
|
||||
set_rule(self.multiworld.get_entrance("Goto Kiln Of The First Flame", self.player),
|
||||
lambda state: state.has("Cinders of a Lord - Abyss Watcher", self.player) and
|
||||
state.has("Cinders of a Lord - Yhorm the Giant", self.player) and
|
||||
state.has("Cinders of a Lord - Aldrich", self.player) and
|
||||
state.has("Cinders of a Lord - Lothric Prince", self.player))
|
||||
|
||||
# Define the access rules to some specific locations
|
||||
set_rule(self.world.get_location("HWL: Soul of the Dancer", self.player),
|
||||
set_rule(self.multiworld.get_location("HWL: Soul of the Dancer", self.player),
|
||||
lambda state: state.has("Basin of Vows", self.player))
|
||||
set_rule(self.world.get_location("HWL: Greirat's Ashes", self.player),
|
||||
set_rule(self.multiworld.get_location("HWL: Greirat's Ashes", self.player),
|
||||
lambda state: state.has("Cell Key", self.player))
|
||||
set_rule(self.world.get_location("ID: Bellowing Dragoncrest Ring", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Bellowing Dragoncrest Ring", self.player),
|
||||
lambda state: state.has("Jailbreaker's Key", self.player))
|
||||
set_rule(self.world.get_location("ID: Prisoner Chief's Ashes", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Prisoner Chief's Ashes", self.player),
|
||||
lambda state: state.has("Jailer's Key Ring", self.player))
|
||||
set_rule(self.world.get_location("ID: Covetous Gold Serpent Ring", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Covetous Gold Serpent Ring", self.player),
|
||||
lambda state: state.has("Old Cell Key", self.player))
|
||||
set_rule(self.world.get_location("ID: Karla's Ashes", self.player),
|
||||
set_rule(self.multiworld.get_location("ID: Karla's Ashes", self.player),
|
||||
lambda state: state.has("Jailer's Key Ring", self.player))
|
||||
black_hand_gotthard_corpse_rule = lambda state: \
|
||||
(state.can_reach("AL: Cinders of a Lord - Aldrich", "Location", self.player) and
|
||||
state.can_reach("PC: Cinders of a Lord - Yhorm the Giant", "Location", self.player))
|
||||
set_rule(self.world.get_location("LC: Grand Archives Key", self.player), black_hand_gotthard_corpse_rule)
|
||||
set_rule(self.world.get_location("LC: Gotthard Twinswords", self.player), black_hand_gotthard_corpse_rule)
|
||||
set_rule(self.multiworld.get_location("LC: Grand Archives Key", self.player), black_hand_gotthard_corpse_rule)
|
||||
set_rule(self.multiworld.get_location("LC: Gotthard Twinswords", self.player), black_hand_gotthard_corpse_rule)
|
||||
|
||||
self.world.completion_condition[self.player] = lambda state: \
|
||||
self.multiworld.completion_condition[self.player] = lambda state: \
|
||||
state.has("Cinders of a Lord - Abyss Watcher", self.player) and \
|
||||
state.has("Cinders of a Lord - Yhorm the Giant", self.player) and \
|
||||
state.has("Cinders of a Lord - Aldrich", self.player) and \
|
||||
@@ -227,32 +227,32 @@ class DarkSouls3World(World):
|
||||
def generate_basic(self):
|
||||
# Depending on the specified option, add the Basin of Vows to a specific location or to the item pool
|
||||
item = self.create_item("Basin of Vows")
|
||||
if self.world.late_basin_of_vows[self.player]:
|
||||
self.world.get_location("IBV: Soul of Pontiff Sulyvahn", self.player).place_locked_item(item)
|
||||
if self.multiworld.late_basin_of_vows[self.player]:
|
||||
self.multiworld.get_location("IBV: Soul of Pontiff Sulyvahn", self.player).place_locked_item(item)
|
||||
else:
|
||||
self.world.itempool += [item]
|
||||
self.multiworld.itempool += [item]
|
||||
|
||||
# Fill item pool with additional items
|
||||
item_pool_len = self.item_name_to_id.__len__()
|
||||
total_required_locations = self.location_name_to_id.__len__()
|
||||
for i in range(item_pool_len, total_required_locations):
|
||||
self.world.itempool += [self.create_item("Soul of an Intrepid Hero")]
|
||||
self.multiworld.itempool += [self.create_item("Soul of an Intrepid Hero")]
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, object]:
|
||||
slot_data: Dict[str, object] = {}
|
||||
|
||||
# Depending on the specified option, modify items hexadecimal value to add an upgrade level
|
||||
item_dictionary_copy = item_dictionary.copy()
|
||||
if self.world.randomize_weapons_level[self.player]:
|
||||
if self.multiworld.randomize_weapons_level[self.player]:
|
||||
# Randomize some weapons upgrades
|
||||
for name in weapons_upgrade_5_table.keys():
|
||||
if self.world.random.randint(0, 100) < 33:
|
||||
value = self.world.random.randint(1, 5)
|
||||
if self.multiworld.random.randint(0, 100) < 33:
|
||||
value = self.multiworld.random.randint(1, 5)
|
||||
item_dictionary_copy[name] += value
|
||||
|
||||
for name in weapons_upgrade_10_table.keys():
|
||||
if self.world.random.randint(0, 100) < 33:
|
||||
value = self.world.random.randint(1, 10)
|
||||
if self.multiworld.random.randint(0, 100) < 33:
|
||||
value = self.multiworld.random.randint(1, 10)
|
||||
item_dictionary_copy[name] += value
|
||||
|
||||
# Create the mandatory lists to generate the player's output file
|
||||
@@ -261,7 +261,7 @@ class DarkSouls3World(World):
|
||||
locations_id = []
|
||||
locations_address = []
|
||||
locations_target = []
|
||||
for location in self.world.get_filled_locations():
|
||||
for location in self.multiworld.get_filled_locations():
|
||||
if location.item.player == self.player:
|
||||
items_id.append(location.item.code)
|
||||
items_address.append(item_dictionary[location.item.name])
|
||||
@@ -276,12 +276,12 @@ class DarkSouls3World(World):
|
||||
|
||||
slot_data = {
|
||||
"options": {
|
||||
"auto_equip": self.world.auto_equip[self.player].value,
|
||||
"lock_equip": self.world.lock_equip[self.player].value,
|
||||
"no_weapon_requirements": self.world.no_weapon_requirements[self.player].value,
|
||||
"auto_equip": self.multiworld.auto_equip[self.player].value,
|
||||
"lock_equip": self.multiworld.lock_equip[self.player].value,
|
||||
"no_weapon_requirements": self.multiworld.no_weapon_requirements[self.player].value,
|
||||
},
|
||||
"seed": self.world.seed_name, # to verify the server's multiworld
|
||||
"slot": self.world.player_name[self.player], # to connect to server
|
||||
"seed": self.multiworld.seed_name, # to verify the server's multiworld
|
||||
"slot": self.multiworld.player_name[self.player], # to connect to server
|
||||
"base_id": self.base_id, # to merge location and items lists
|
||||
"locationsId": locations_id,
|
||||
"locationsAddress": locations_address,
|
||||
@@ -291,6 +291,3 @@ class DarkSouls3World(World):
|
||||
}
|
||||
|
||||
return slot_data
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
pass
|
||||
|
||||
@@ -913,7 +913,7 @@ def connect_regions(world, player, level_list):
|
||||
def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None):
|
||||
# Shamelessly stolen from the ROR2 definition
|
||||
ret = Region(name, None, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for locationName, locationData in locations.items():
|
||||
loc_id = active_locations.get(locationName, 0)
|
||||
|
||||
@@ -69,13 +69,13 @@ class DKC3World(World):
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = self._get_slot_data()
|
||||
for option_name in dkc3_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
|
||||
return slot_data
|
||||
|
||||
def generate_basic(self):
|
||||
self.topology_present = self.world.level_shuffle[self.player].value
|
||||
self.topology_present = self.multiworld.level_shuffle[self.player].value
|
||||
itempool: typing.List[DKC3Item] = []
|
||||
|
||||
# Levels
|
||||
@@ -85,14 +85,14 @@ class DKC3World(World):
|
||||
# Rocket Rush Cog
|
||||
total_required_locations -= 1
|
||||
number_of_cogs = 4
|
||||
self.world.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog))
|
||||
self.multiworld.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog))
|
||||
number_of_bosses = 8
|
||||
if self.world.goal[self.player] == "knautilus":
|
||||
self.world.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
if self.multiworld.goal[self.player] == "knautilus":
|
||||
self.multiworld.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
number_of_bosses = 7
|
||||
else:
|
||||
self.world.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
number_of_banana_birds = self.world.number_of_banana_birds[self.player]
|
||||
self.multiworld.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
number_of_banana_birds = self.multiworld.number_of_banana_birds[self.player]
|
||||
|
||||
# Bosses
|
||||
total_required_locations += number_of_bosses
|
||||
@@ -100,15 +100,15 @@ class DKC3World(World):
|
||||
# Secret Caves
|
||||
total_required_locations += 13
|
||||
|
||||
if self.world.kongsanity[self.player]:
|
||||
if self.multiworld.kongsanity[self.player]:
|
||||
total_required_locations += 39
|
||||
|
||||
## Brothers Bear
|
||||
if False:#self.world.include_trade_sequence[self.player]:
|
||||
total_required_locations += 10
|
||||
|
||||
number_of_bonus_coins = (self.world.krematoa_bonus_coin_cost[self.player] * 5)
|
||||
number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.world.percentage_of_extra_bonus_coins[self.player] / 100)
|
||||
number_of_bonus_coins = (self.multiworld.krematoa_bonus_coin_cost[self.player] * 5)
|
||||
number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.multiworld.percentage_of_extra_bonus_coins[self.player] / 100)
|
||||
|
||||
itempool += [self.create_item(ItemName.bonus_coin) for _ in range(number_of_bonus_coins)]
|
||||
itempool += [self.create_item(ItemName.dk_coin) for _ in range(41)]
|
||||
@@ -119,27 +119,27 @@ class DKC3World(World):
|
||||
total_junk_count = total_required_locations - len(itempool)
|
||||
|
||||
junk_pool = []
|
||||
for item_name in self.world.random.choices(list(junk_table.keys()), k=total_junk_count):
|
||||
for item_name in self.multiworld.random.choices(list(junk_table.keys()), k=total_junk_count):
|
||||
junk_pool.append(self.create_item(item_name))
|
||||
|
||||
itempool += junk_pool
|
||||
|
||||
self.active_level_list = level_list.copy()
|
||||
|
||||
if self.world.level_shuffle[self.player]:
|
||||
self.world.random.shuffle(self.active_level_list)
|
||||
if self.multiworld.level_shuffle[self.player]:
|
||||
self.multiworld.random.shuffle(self.active_level_list)
|
||||
|
||||
connect_regions(self.world, self.player, self.active_level_list)
|
||||
connect_regions(self.multiworld, self.player, self.active_level_list)
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
try:
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
rom = LocalRom(get_base_rom_path())
|
||||
patch_rom(self.world, rom, self.player, self.active_level_list)
|
||||
patch_rom(self.multiworld, rom, self.player, self.active_level_list)
|
||||
|
||||
self.active_level_list.append(LocationName.rocket_rush_region)
|
||||
|
||||
@@ -165,7 +165,7 @@ class DKC3World(World):
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if rom_name:
|
||||
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
|
||||
if self.topology_present:
|
||||
world_names = [
|
||||
@@ -181,14 +181,14 @@ class DKC3World(World):
|
||||
er_hint_data = {}
|
||||
for world_index in range(len(world_names)):
|
||||
for level_index in range(5):
|
||||
level_region = self.world.get_region(self.active_level_list[world_index * 5 + level_index], self.player)
|
||||
level_region = self.multiworld.get_region(self.active_level_list[world_index * 5 + level_index], self.player)
|
||||
for location in level_region.locations:
|
||||
er_hint_data[location.address] = world_names[world_index]
|
||||
multidata['er_hint_data'][self.player] = er_hint_data
|
||||
|
||||
def create_regions(self):
|
||||
location_table = setup_locations(self.world, self.player)
|
||||
create_regions(self.world, self.player, location_table)
|
||||
location_table = setup_locations(self.multiworld, self.player)
|
||||
create_regions(self.multiworld, self.player, location_table)
|
||||
|
||||
def create_item(self, name: str, force_non_progression=False) -> Item:
|
||||
data = item_table[name]
|
||||
@@ -205,4 +205,4 @@ class DKC3World(World):
|
||||
return created_item
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
@@ -77,7 +77,7 @@ class FactorioModFile(worlds.Files.APContainer):
|
||||
|
||||
def generate_mod(world: "Factorio", output_directory: str):
|
||||
player = world.player
|
||||
multiworld = world.world
|
||||
multiworld = world.multiworld
|
||||
global data_final_template, locale_template, control_template, data_template, settings_template
|
||||
with template_load_lock:
|
||||
if not data_final_template:
|
||||
|
||||
@@ -20,7 +20,7 @@ def _sorter(location: "FactorioScienceLocation"):
|
||||
|
||||
|
||||
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
|
||||
world = factorio_world.world
|
||||
world = factorio_world.multiworld
|
||||
player = factorio_world.player
|
||||
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
|
||||
layout = world.tech_tree_layout[player].value
|
||||
|
||||
@@ -73,31 +73,32 @@ class Factorio(World):
|
||||
generate_output = generate_mod
|
||||
|
||||
def generate_early(self) -> None:
|
||||
self.world.max_tech_cost[self.player] = max(self.world.max_tech_cost[self.player],
|
||||
self.world.min_tech_cost[self.player])
|
||||
self.tech_mix = self.world.tech_cost_mix[self.player]
|
||||
self.skip_silo = self.world.silo[self.player].value == Silo.option_spawn
|
||||
self.multiworld.max_tech_cost[self.player] = max(self.multiworld.max_tech_cost[self.player],
|
||||
self.multiworld.min_tech_cost[self.player])
|
||||
self.tech_mix = self.multiworld.tech_cost_mix[self.player]
|
||||
self.skip_silo = self.multiworld.silo[self.player].value == Silo.option_spawn
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
random = self.world.random
|
||||
menu = Region("Menu", RegionType.Generic, "Menu", player, self.world)
|
||||
random = self.multiworld.random
|
||||
menu = Region("Menu", RegionType.Generic, "Menu", player, self.multiworld)
|
||||
crash = Entrance(player, "Crash Land", menu)
|
||||
menu.exits.append(crash)
|
||||
nauvis = Region("Nauvis", RegionType.Generic, "Nauvis", player, self.world)
|
||||
nauvis = Region("Nauvis", RegionType.Generic, "Nauvis", player, self.multiworld)
|
||||
|
||||
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
|
||||
self.world.evolution_traps[player].value + self.world.attack_traps[player].value
|
||||
self.multiworld.evolution_traps[player].value + self.multiworld.attack_traps[player].value
|
||||
|
||||
location_pool = []
|
||||
|
||||
for pack in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
location_pool.extend(location_pools[pack])
|
||||
location_names = self.world.random.sample(location_pool, location_count)
|
||||
|
||||
location_names = self.multiworld.random.sample(location_pool, location_count)
|
||||
self.locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
|
||||
for loc_name in location_names]
|
||||
rand_values = sorted(random.randint(self.world.min_tech_cost[self.player],
|
||||
self.world.max_tech_cost[self.player]) for _ in self.locations)
|
||||
rand_values = sorted(random.randint(self.multiworld.min_tech_cost[self.player],
|
||||
self.multiworld.max_tech_cost[self.player]) for _ in self.locations)
|
||||
for i, location in enumerate(sorted(self.locations, key=lambda loc: loc.rel_cost)):
|
||||
location.count = rand_values[i]
|
||||
del rand_values
|
||||
@@ -107,27 +108,27 @@ class Factorio(World):
|
||||
event = FactorioItem("Victory", ItemClassification.progression, None, player)
|
||||
location.place_locked_item(event)
|
||||
|
||||
for ingredient in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
for ingredient in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis)
|
||||
nauvis.locations.append(location)
|
||||
event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player)
|
||||
location.place_locked_item(event)
|
||||
|
||||
crash.connect(nauvis)
|
||||
self.world.regions += [menu, nauvis]
|
||||
self.multiworld.regions += [menu, nauvis]
|
||||
|
||||
def set_rules(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
self.custom_technologies = self.set_custom_technologies()
|
||||
self.set_custom_recipes()
|
||||
shapes = get_shapes(self)
|
||||
if world.logic[player] != 'nologic':
|
||||
from worlds.generic import Rules
|
||||
for ingredient in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
for ingredient in self.multiworld.max_science_pack[self.player].get_allowed_packs():
|
||||
location = world.get_location(f"Automate {ingredient}", player)
|
||||
|
||||
if self.world.recipe_ingredients[self.player]:
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
custom_recipe = self.custom_recipes[ingredient]
|
||||
|
||||
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
|
||||
@@ -147,12 +148,12 @@ class Factorio(World):
|
||||
prerequisites: all(state.can_reach(loc) for loc in locations))
|
||||
|
||||
silo_recipe = None
|
||||
if self.world.silo[self.player] == Silo.option_spawn:
|
||||
if self.multiworld.silo[self.player] == Silo.option_spawn:
|
||||
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("rocket-silo")))
|
||||
part_recipe = self.custom_recipes["rocket-part"]
|
||||
satellite_recipe = None
|
||||
if self.world.goal[self.player] == Goal.option_satellite:
|
||||
if self.multiworld.goal[self.player] == Goal.option_satellite:
|
||||
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \
|
||||
else next(iter(all_product_sources.get("satellite")))
|
||||
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe)
|
||||
@@ -164,12 +165,12 @@ class Factorio(World):
|
||||
|
||||
def generate_basic(self):
|
||||
player = self.player
|
||||
want_progressives = collections.defaultdict(lambda: self.world.progressive[player].
|
||||
want_progressives(self.world.random))
|
||||
self.world.itempool.extend(self.create_item("Evolution Trap") for _ in
|
||||
range(self.world.evolution_traps[player].value))
|
||||
self.world.itempool.extend(self.create_item("Attack Trap") for _ in
|
||||
range(self.world.attack_traps[player].value))
|
||||
want_progressives = collections.defaultdict(lambda: self.multiworld.progressive[player].
|
||||
want_progressives(self.multiworld.random))
|
||||
self.multiworld.itempool.extend(self.create_item("Evolution Trap") for _ in
|
||||
range(self.multiworld.evolution_traps[player].value))
|
||||
self.multiworld.itempool.extend(self.create_item("Attack Trap") for _ in
|
||||
range(self.multiworld.attack_traps[player].value))
|
||||
|
||||
cost_sorted_locations = sorted(self.locations, key=lambda location: location.name)
|
||||
special_index = {"automation": 0,
|
||||
@@ -188,19 +189,19 @@ class Factorio(World):
|
||||
tech_item = self.create_item(item_name)
|
||||
index = special_index.get(tech_name, None)
|
||||
if index is None:
|
||||
self.world.itempool.append(tech_item)
|
||||
self.multiworld.itempool.append(tech_item)
|
||||
else:
|
||||
loc = cost_sorted_locations[index]
|
||||
loc.place_locked_item(tech_item)
|
||||
loc.revealed = True
|
||||
|
||||
map_basic_settings = self.world.world_gen[player].value["basic"]
|
||||
map_basic_settings = self.multiworld.world_gen[player].value["basic"]
|
||||
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
|
||||
map_basic_settings["seed"] = self.multiworld.slot_seeds[player].randint(0, 2 ** 32 - 1) # 32 bit uint
|
||||
|
||||
if self.world.tech_tree_information[player] == TechTreeInformation.option_full:
|
||||
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full:
|
||||
# mark all locations as pre-hinted
|
||||
self.world.start_location_hints[self.player].value.update(base_tech_table)
|
||||
self.multiworld.start_location_hints[self.player].value.update(base_tech_table)
|
||||
for loc in self.locations:
|
||||
loc.revealed = True
|
||||
|
||||
@@ -258,7 +259,7 @@ class Factorio(World):
|
||||
# have to first sort for determinism, while filtering out non-stacking items
|
||||
pool: typing.List[str] = sorted(pool & valid_ingredients)
|
||||
# then sort with random data to shuffle
|
||||
self.world.random.shuffle(pool)
|
||||
self.multiworld.random.shuffle(pool)
|
||||
target_raw = int(sum((count for ingredient, count in original.base_cost.items())) * factor)
|
||||
target_energy = original.total_energy * factor
|
||||
target_num_ingredients = len(original.ingredients)
|
||||
@@ -302,7 +303,7 @@ class Factorio(World):
|
||||
if min_num > max_num:
|
||||
fallback_pool.append(ingredient)
|
||||
continue # can't use that ingredient
|
||||
num = self.world.random.randint(min_num, max_num)
|
||||
num = self.multiworld.random.randint(min_num, max_num)
|
||||
new_ingredients[ingredient] = num
|
||||
remaining_raw -= num * ingredient_raw
|
||||
remaining_energy -= num * ingredient_energy
|
||||
@@ -346,58 +347,58 @@ class Factorio(World):
|
||||
|
||||
def set_custom_technologies(self):
|
||||
custom_technologies = {}
|
||||
allowed_packs = self.world.max_science_pack[self.player].get_allowed_packs()
|
||||
allowed_packs = self.multiworld.max_science_pack[self.player].get_allowed_packs()
|
||||
for technology_name, technology in base_technology_table.items():
|
||||
custom_technologies[technology_name] = technology.get_custom(self.world, allowed_packs, self.player)
|
||||
custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player)
|
||||
return custom_technologies
|
||||
|
||||
def set_custom_recipes(self):
|
||||
original_rocket_part = recipes["rocket-part"]
|
||||
science_pack_pools = get_science_pack_pools()
|
||||
valid_pool = sorted(science_pack_pools[self.world.max_science_pack[self.player].get_max_pack()] & valid_ingredients)
|
||||
self.world.random.shuffle(valid_pool)
|
||||
valid_pool = sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_max_pack()] & valid_ingredients)
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
|
||||
{valid_pool[x]: 10 for x in range(3)},
|
||||
original_rocket_part.products,
|
||||
original_rocket_part.energy)}
|
||||
|
||||
if self.world.recipe_ingredients[self.player]:
|
||||
if self.multiworld.recipe_ingredients[self.player]:
|
||||
valid_pool = []
|
||||
for pack in self.world.max_science_pack[self.player].get_ordered_science_packs():
|
||||
for pack in self.multiworld.max_science_pack[self.player].get_ordered_science_packs():
|
||||
valid_pool += sorted(science_pack_pools[pack])
|
||||
self.world.random.shuffle(valid_pool)
|
||||
self.multiworld.random.shuffle(valid_pool)
|
||||
if pack in recipes: # skips over space science pack
|
||||
new_recipe = self.make_quick_recipe(recipes[pack], valid_pool)
|
||||
self.custom_recipes[pack] = new_recipe
|
||||
|
||||
if self.world.silo[self.player].value == Silo.option_randomize_recipe \
|
||||
or self.world.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe \
|
||||
or self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
valid_pool = set()
|
||||
for pack in sorted(self.world.max_science_pack[self.player].get_allowed_packs()):
|
||||
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()):
|
||||
valid_pool |= science_pack_pools[pack]
|
||||
|
||||
if self.world.silo[self.player].value == Silo.option_randomize_recipe:
|
||||
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(recipes["rocket-silo"], valid_pool,
|
||||
factor=(self.world.max_science_pack[self.player].value + 1) / 7)
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7)
|
||||
self.custom_recipes["rocket-silo"] = new_recipe
|
||||
|
||||
if self.world.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
if self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe:
|
||||
new_recipe = self.make_balanced_recipe(recipes["satellite"], valid_pool,
|
||||
factor=(self.world.max_science_pack[self.player].value + 1) / 7)
|
||||
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7)
|
||||
self.custom_recipes["satellite"] = new_recipe
|
||||
bridge = "ap-energy-bridge"
|
||||
new_recipe = self.make_quick_recipe(
|
||||
Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1},
|
||||
{bridge: 1}, 10),
|
||||
sorted(science_pack_pools[self.world.max_science_pack[self.player].get_ordered_science_packs()[0]]))
|
||||
sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_ordered_science_packs()[0]]))
|
||||
for ingredient_name in new_recipe.ingredients:
|
||||
new_recipe.ingredients[ingredient_name] = self.world.random.randint(10, 100)
|
||||
new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(10, 100)
|
||||
self.custom_recipes[bridge] = new_recipe
|
||||
|
||||
needed_recipes = self.world.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"}
|
||||
if self.world.silo[self.player] != Silo.option_spawn:
|
||||
needed_recipes = self.multiworld.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"}
|
||||
if self.multiworld.silo[self.player] != Silo.option_spawn:
|
||||
needed_recipes |= {"rocket-silo"}
|
||||
if self.world.goal[self.player].value == Goal.option_satellite:
|
||||
if self.multiworld.goal[self.player].value == Goal.option_satellite:
|
||||
needed_recipes |= {"satellite"}
|
||||
|
||||
for recipe in needed_recipes:
|
||||
@@ -447,10 +448,10 @@ class FactorioScienceLocation(FactorioLocation):
|
||||
|
||||
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
|
||||
for complexity in range(self.complexity):
|
||||
if parent.world.tech_cost_mix[self.player] > parent.world.random.randint(0, 99):
|
||||
if parent.multiworld.tech_cost_mix[self.player] > parent.multiworld.random.randint(0, 99):
|
||||
self.ingredients[Factorio.ordered_science_packs[complexity]] = 1
|
||||
self.count = parent.world.random.randint(parent.world.min_tech_cost[self.player],
|
||||
parent.world.max_tech_cost[self.player])
|
||||
self.count = parent.multiworld.random.randint(parent.multiworld.min_tech_cost[self.player],
|
||||
parent.multiworld.max_tech_cost[self.player])
|
||||
|
||||
@property
|
||||
def factorio_ingredients(self) -> typing.List[typing.Tuple[str, int]]:
|
||||
|
||||
@@ -51,15 +51,15 @@ class FF1World(World):
|
||||
return
|
||||
|
||||
def create_regions(self):
|
||||
locations = get_options(self.world, 'locations', self.player)
|
||||
rules = get_options(self.world, 'rules', self.player)
|
||||
locations = get_options(self.multiworld, 'locations', self.player)
|
||||
rules = get_options(self.multiworld, 'rules', self.player)
|
||||
menu_region = self.ff1_locations.create_menu_region(self.player, locations, rules)
|
||||
menu_region.world = self.world
|
||||
menu_region.multiworld = self.multiworld
|
||||
terminated_event = Location(self.player, CHAOS_TERMINATED_EVENT, EventId, menu_region)
|
||||
terminated_item = Item(CHAOS_TERMINATED_EVENT, ItemClassification.progression, EventId, self.player)
|
||||
terminated_event.place_locked_item(terminated_item)
|
||||
|
||||
items = get_options(self.world, 'items', self.player)
|
||||
items = get_options(self.multiworld, 'items', self.player)
|
||||
goal_rule = generate_rule([[name for name in items.keys() if name in FF1_PROGRESSION_LIST and name != "Shard"]],
|
||||
self.player)
|
||||
if "Shard" in items.keys():
|
||||
@@ -71,22 +71,22 @@ class FF1World(World):
|
||||
raise Exception("FFR Noverworld seeds must be generated on an older version of FFR. Please ensure you generated the settings using "
|
||||
"4-4-0.finalfantasyrandomizer.com")
|
||||
menu_region.locations.append(terminated_event)
|
||||
self.world.regions += [menu_region]
|
||||
self.multiworld.regions += [menu_region]
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return self.ff1_items.generate_item(name, self.player)
|
||||
|
||||
def set_rules(self):
|
||||
self.world.completion_condition[self.player] = lambda state: state.has(CHAOS_TERMINATED_EVENT, self.player)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(CHAOS_TERMINATED_EVENT, self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
items = get_options(self.world, 'items', self.player)
|
||||
items = get_options(self.multiworld, 'items', self.player)
|
||||
if FF1_BRIDGE in items.keys():
|
||||
self._place_locked_item_in_sphere0(FF1_BRIDGE)
|
||||
if items:
|
||||
possible_early_items = [name for name in FF1_STARTER_ITEMS if name in items.keys()]
|
||||
if possible_early_items:
|
||||
progression_item = self.world.random.choice(possible_early_items)
|
||||
progression_item = self.multiworld.random.choice(possible_early_items)
|
||||
self._place_locked_item_in_sphere0(progression_item)
|
||||
else:
|
||||
# Fail generation if there are no items in the pool
|
||||
@@ -96,16 +96,16 @@ class FF1World(World):
|
||||
items = [self.create_item(name) for name, data in items.items() for x in range(data['count']) if name not in
|
||||
self.locked_items]
|
||||
|
||||
self.world.itempool += items
|
||||
self.multiworld.itempool += items
|
||||
|
||||
def _place_locked_item_in_sphere0(self, progression_item: str):
|
||||
if progression_item:
|
||||
rules = get_options(self.world, 'rules', self.player)
|
||||
rules = get_options(self.multiworld, 'rules', self.player)
|
||||
sphere_0_locations = [name for name, rules in rules.items()
|
||||
if rules and len(rules[0]) == 0 and name not in self.locked_locations]
|
||||
if sphere_0_locations:
|
||||
initial_location = self.world.random.choice(sphere_0_locations)
|
||||
locked_location = self.world.get_location(initial_location, self.player)
|
||||
initial_location = self.multiworld.random.choice(sphere_0_locations)
|
||||
locked_location = self.multiworld.get_location(initial_location, self.player)
|
||||
locked_location.place_locked_item(self.create_item(progression_item))
|
||||
self.locked_items.append(progression_item)
|
||||
self.locked_locations.append(locked_location.name)
|
||||
@@ -116,7 +116,7 @@ class FF1World(World):
|
||||
return slot_data
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(["Heal", "Pure", "Soft", "Tent", "Cabin", "House"])
|
||||
return self.multiworld.random.choice(["Heal", "Pure", "Soft", "Tent", "Cabin", "House"])
|
||||
|
||||
|
||||
def get_options(world: MultiWorld, name: str, player: int):
|
||||
|
||||
@@ -151,7 +151,7 @@ def item_in_locations(state: "BaseClasses.CollectionState", item: str, player: i
|
||||
|
||||
def item_name(state: "BaseClasses.CollectionState", location: str, player: int) -> \
|
||||
typing.Optional[typing.Tuple[str, int]]:
|
||||
location = state.world.get_location(location, player)
|
||||
location = state.multiworld.get_location(location, player)
|
||||
if location.item is None:
|
||||
return None
|
||||
return location.item.name, location.item.player
|
||||
|
||||
@@ -44,7 +44,7 @@ class GenericWorld(World):
|
||||
web = GenericWeb()
|
||||
|
||||
def generate_early(self):
|
||||
self.world.player_types[self.player] = SlotType.spectator # mark as spectator
|
||||
self.multiworld.player_types[self.player] = SlotType.spectator # mark as spectator
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
if name == "Nothing":
|
||||
|
||||
@@ -29,7 +29,7 @@ def hk_set_rule(hk_world: World, location: str, rule):
|
||||
locations = hk_world.created_multi_locations.get(location)
|
||||
if locations is None:
|
||||
try:
|
||||
locations = [hk_world.world.get_location(location, player)]
|
||||
locations = [hk_world.multiworld.get_location(location, player)]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
@@ -39,7 +39,7 @@ def hk_set_rule(hk_world: World, location: str, rule):
|
||||
|
||||
def set_rules(hk_world: World):
|
||||
player = hk_world.player
|
||||
world = hk_world.world
|
||||
world = hk_world.multiworld
|
||||
set_generated_rules(hk_world, hk_set_rule)
|
||||
|
||||
# Shop costs
|
||||
|
||||
@@ -166,7 +166,7 @@ class HKWorld(World):
|
||||
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
||||
|
||||
def generate_early(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
charm_costs = world.RandomCharmCosts[self.player].get_costs(world.random)
|
||||
self.charm_costs = world.PlandoCharmCosts[self.player].get_costs(charm_costs)
|
||||
# world.exclude_locations[self.player].value.update(white_palace_locations)
|
||||
@@ -182,22 +182,22 @@ class HKWorld(World):
|
||||
|
||||
def white_palace_exclusions(self):
|
||||
exclusions = set()
|
||||
wp = self.world.WhitePalace[self.player]
|
||||
wp = self.multiworld.WhitePalace[self.player]
|
||||
if wp <= WhitePalace.option_nopathofpain:
|
||||
exclusions.update(path_of_pain_locations)
|
||||
if wp <= WhitePalace.option_kingfragment:
|
||||
exclusions.update(white_palace_checks)
|
||||
if wp == WhitePalace.option_exclude:
|
||||
exclusions.add("King_Fragment")
|
||||
if self.world.RandomizeCharms[self.player]:
|
||||
if self.multiworld.RandomizeCharms[self.player]:
|
||||
# If charms are randomized, this will be junk-filled -- so transitions and events are not progression
|
||||
exclusions.update(white_palace_transitions)
|
||||
exclusions.update(white_palace_events)
|
||||
return exclusions
|
||||
|
||||
def create_regions(self):
|
||||
menu_region: Region = create_region(self.world, self.player, 'Menu')
|
||||
self.world.regions.append(menu_region)
|
||||
menu_region: Region = create_region(self.multiworld, self.player, 'Menu')
|
||||
self.multiworld.regions.append(menu_region)
|
||||
# wp_exclusions = self.white_palace_exclusions()
|
||||
|
||||
# Link regions
|
||||
@@ -226,12 +226,12 @@ class HKWorld(World):
|
||||
pool: typing.List[HKItem] = []
|
||||
wp_exclusions = self.white_palace_exclusions()
|
||||
junk_replace: typing.Set[str] = set()
|
||||
if self.world.RemoveSpellUpgrades[self.player]:
|
||||
if self.multiworld.RemoveSpellUpgrades[self.player]:
|
||||
junk_replace.update(("Abyss_Shriek", "Shade_Soul", "Descending_Dark"))
|
||||
|
||||
randomized_starting_items = set()
|
||||
for attr, items in randomizable_starting_items.items():
|
||||
if getattr(self.world, attr)[self.player]:
|
||||
if getattr(self.multiworld, attr)[self.player]:
|
||||
randomized_starting_items.update(items)
|
||||
|
||||
# noinspection PyShadowingNames
|
||||
@@ -262,7 +262,7 @@ class HKWorld(World):
|
||||
unfilled_locations += 1
|
||||
pool.append(item)
|
||||
else:
|
||||
self.world.push_precollected(item)
|
||||
self.multiworld.push_precollected(item)
|
||||
return
|
||||
|
||||
if vanilla:
|
||||
@@ -277,49 +277,49 @@ class HKWorld(World):
|
||||
location.progress_type = LocationProgressType.EXCLUDED
|
||||
|
||||
for option_key, option in hollow_knight_randomize_options.items():
|
||||
randomized = getattr(self.world, option_key)[self.player]
|
||||
randomized = getattr(self.multiworld, option_key)[self.player]
|
||||
for item_name, location_name in zip(option.items, option.locations):
|
||||
if item_name in junk_replace:
|
||||
item_name = self.get_filler_item_name()
|
||||
|
||||
if (item_name == "Crystal_Heart" and self.world.SplitCrystalHeart[self.player]) or \
|
||||
(item_name == "Mothwing_Cloak" and self.world.SplitMothwingCloak[self.player]):
|
||||
if (item_name == "Crystal_Heart" and self.multiworld.SplitCrystalHeart[self.player]) or \
|
||||
(item_name == "Mothwing_Cloak" and self.multiworld.SplitMothwingCloak[self.player]):
|
||||
_add("Left_" + item_name, location_name)
|
||||
_add("Right_" + item_name, "Split_" + location_name)
|
||||
continue
|
||||
if item_name == "Mantis_Claw" and self.world.SplitMantisClaw[self.player]:
|
||||
if item_name == "Mantis_Claw" and self.multiworld.SplitMantisClaw[self.player]:
|
||||
_add("Left_" + item_name, "Left_" + location_name)
|
||||
_add("Right_" + item_name, "Right_" + location_name)
|
||||
continue
|
||||
if item_name == "Shade_Cloak" and self.world.SplitMothwingCloak[self.player]:
|
||||
if self.world.random.randint(0, 1):
|
||||
if item_name == "Shade_Cloak" and self.multiworld.SplitMothwingCloak[self.player]:
|
||||
if self.multiworld.random.randint(0, 1):
|
||||
item_name = "Left_Mothwing_Cloak"
|
||||
else:
|
||||
item_name = "Right_Mothwing_Cloak"
|
||||
|
||||
_add(item_name, location_name)
|
||||
|
||||
if self.world.RandomizeElevatorPass[self.player]:
|
||||
if self.multiworld.RandomizeElevatorPass[self.player]:
|
||||
randomized = True
|
||||
_add("Elevator_Pass", "Elevator_Pass")
|
||||
|
||||
for shop, locations in self.created_multi_locations.items():
|
||||
for _ in range(len(locations), getattr(self.world, shop_to_option[shop])[self.player].value):
|
||||
for _ in range(len(locations), getattr(self.multiworld, shop_to_option[shop])[self.player].value):
|
||||
loc = self.create_location(shop)
|
||||
unfilled_locations += 1
|
||||
|
||||
# Balance the pool
|
||||
item_count = len(pool)
|
||||
additional_shop_items = max(item_count - unfilled_locations, self.world.ExtraShopSlots[self.player].value)
|
||||
additional_shop_items = max(item_count - unfilled_locations, self.multiworld.ExtraShopSlots[self.player].value)
|
||||
|
||||
# Add additional shop items, as needed.
|
||||
if additional_shop_items > 0:
|
||||
shops = list(shop for shop, locations in self.created_multi_locations.items() if len(locations) < 16)
|
||||
if not self.world.EggShopSlots[self.player].value: # No eggshop, so don't place items there
|
||||
if not self.multiworld.EggShopSlots[self.player].value: # No eggshop, so don't place items there
|
||||
shops.remove('Egg_Shop')
|
||||
|
||||
for _ in range(additional_shop_items):
|
||||
shop = self.world.random.choice(shops)
|
||||
shop = self.multiworld.random.choice(shops)
|
||||
loc = self.create_location(shop)
|
||||
unfilled_locations += 1
|
||||
if len(self.created_multi_locations[shop]) >= 16:
|
||||
@@ -330,7 +330,7 @@ class HKWorld(World):
|
||||
# Create filler items, if needed
|
||||
if item_count < unfilled_locations:
|
||||
pool.extend(self.create_item(self.get_filler_item_name()) for _ in range(unfilled_locations - item_count))
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
self.apply_costsanity()
|
||||
self.sort_shops_by_cost()
|
||||
|
||||
@@ -345,24 +345,24 @@ class HKWorld(World):
|
||||
loc.costs = costs
|
||||
|
||||
def apply_costsanity(self):
|
||||
setting = self.world.CostSanity[self.player].value
|
||||
setting = self.multiworld.CostSanity[self.player].value
|
||||
if not setting:
|
||||
return # noop
|
||||
|
||||
def _compute_weights(weights: dict, desc: str) -> typing.Dict[str, int]:
|
||||
if all(x == 0 for x in weights.values()):
|
||||
logger.warning(
|
||||
f"All {desc} weights were zero for {self.world.player_name[self.player]}."
|
||||
f"All {desc} weights were zero for {self.multiworld.player_name[self.player]}."
|
||||
f" Setting them to one instead."
|
||||
)
|
||||
weights = {k: 1 for k in weights}
|
||||
|
||||
return {k: v for k, v in weights.items() if v}
|
||||
|
||||
random = self.world.random
|
||||
hybrid_chance = getattr(self.world, f"CostSanityHybridChance")[self.player].value
|
||||
random = self.multiworld.random
|
||||
hybrid_chance = getattr(self.multiworld, f"CostSanityHybridChance")[self.player].value
|
||||
weights = {
|
||||
data.term: getattr(self.world, f"CostSanity{data.option}Weight")[self.player].value
|
||||
data.term: getattr(self.multiworld, f"CostSanity{data.option}Weight")[self.player].value
|
||||
for data in cost_terms.values()
|
||||
}
|
||||
weights_geoless = dict(weights)
|
||||
@@ -374,16 +374,16 @@ class HKWorld(World):
|
||||
if hybrid_chance > 0:
|
||||
if len(weights) == 1:
|
||||
logger.warning(
|
||||
f"Only one cost type is available for CostSanity in {self.world.player_name[self.player]}'s world."
|
||||
f"Only one cost type is available for CostSanity in {self.multiworld.player_name[self.player]}'s world."
|
||||
f" CostSanityHybridChance will not trigger."
|
||||
)
|
||||
if len(weights_geoless) == 1:
|
||||
logger.warning(
|
||||
f"Only one cost type is available for CostSanity in {self.world.player_name[self.player]}'s world."
|
||||
f"Only one cost type is available for CostSanity in {self.multiworld.player_name[self.player]}'s world."
|
||||
f" CostSanityHybridChance will not trigger in geoless locations."
|
||||
)
|
||||
|
||||
for region in self.world.get_regions(self.player):
|
||||
for region in self.multiworld.get_regions(self.player):
|
||||
for location in region.locations:
|
||||
if location.vanilla:
|
||||
continue
|
||||
@@ -417,7 +417,7 @@ class HKWorld(World):
|
||||
location.sort_costs()
|
||||
|
||||
def set_rules(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
if world.logic[player] != 'nologic':
|
||||
goal = world.Goal[player]
|
||||
@@ -436,7 +436,7 @@ class HKWorld(World):
|
||||
|
||||
options = slot_data["options"] = {}
|
||||
for option_name in self.option_definitions:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
try:
|
||||
optionvalue = int(option.value)
|
||||
except TypeError:
|
||||
@@ -445,10 +445,10 @@ class HKWorld(World):
|
||||
options[option_name] = optionvalue
|
||||
|
||||
# 32 bit int
|
||||
slot_data["seed"] = self.world.slot_seeds[self.player].randint(-2147483647, 2147483646)
|
||||
slot_data["seed"] = self.multiworld.slot_seeds[self.player].randint(-2147483647, 2147483646)
|
||||
|
||||
# Backwards compatibility for shop cost data (HKAP < 0.1.0)
|
||||
if not self.world.CostSanity[self.player]:
|
||||
if not self.multiworld.CostSanity[self.player]:
|
||||
for shop, terms in shop_cost_types.items():
|
||||
unit = cost_terms[next(iter(terms))].option
|
||||
if unit == "Geo":
|
||||
@@ -460,7 +460,7 @@ class HKWorld(World):
|
||||
|
||||
# HKAP 0.1.0 and later cost data.
|
||||
location_costs = {}
|
||||
for region in self.world.get_regions(self.player):
|
||||
for region in self.multiworld.get_regions(self.player):
|
||||
for location in region.locations:
|
||||
if location.costs:
|
||||
location_costs[location.name] = location.costs
|
||||
@@ -479,7 +479,7 @@ class HKWorld(World):
|
||||
basename = name
|
||||
if name in shop_cost_types:
|
||||
costs = {
|
||||
term: self.world.random.randint(*self.ranges[term])
|
||||
term: self.multiworld.random.randint(*self.ranges[term])
|
||||
for term in shop_cost_types[name]
|
||||
}
|
||||
elif name in vanilla_location_costs:
|
||||
@@ -491,7 +491,7 @@ class HKWorld(World):
|
||||
i = len(multi) + 1
|
||||
name = f"{name}_{i}"
|
||||
|
||||
region = self.world.get_region("Menu", self.player)
|
||||
region = self.multiworld.get_region("Menu", self.player)
|
||||
loc = HKLocation(self.player, name,
|
||||
self.location_name_to_id[name], region, costs=costs, vanilla=vanilla,
|
||||
basename=basename)
|
||||
@@ -577,16 +577,16 @@ class HKWorld(World):
|
||||
'RandomizeGeoRocks', 'RandomizeSoulTotems', 'RandomizeLoreTablets', 'RandomizeJunkPitChests',
|
||||
'RandomizeRancidEggs'
|
||||
):
|
||||
if getattr(self.world, group):
|
||||
if getattr(self.multiworld, group):
|
||||
fillers.extend(item for item in hollow_knight_randomize_options[group].items if item not in
|
||||
exclusions)
|
||||
self.cached_filler_items[self.player] = fillers
|
||||
return self.world.random.choice(self.cached_filler_items[self.player])
|
||||
return self.multiworld.random.choice(self.cached_filler_items[self.player])
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, location_names=None, exits=None) -> Region:
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if location_names:
|
||||
for location in location_names:
|
||||
loc_id = HKWorld.location_name_to_id.get(location, None)
|
||||
@@ -654,16 +654,16 @@ class HKItem(Item):
|
||||
|
||||
|
||||
class HKLogicMixin(LogicMixin):
|
||||
world: MultiWorld
|
||||
multiworld: MultiWorld
|
||||
|
||||
def _hk_notches(self, player: int, *notches: int) -> int:
|
||||
return sum(self.world.worlds[player].charm_costs[notch] for notch in notches)
|
||||
return sum(self.multiworld.worlds[player].charm_costs[notch] for notch in notches)
|
||||
|
||||
def _hk_option(self, player: int, option_name: str) -> int:
|
||||
return getattr(self.world, option_name)[player].value
|
||||
return getattr(self.multiworld, option_name)[player].value
|
||||
|
||||
def _hk_start(self, player, start_location: str) -> bool:
|
||||
return self.world.StartLocation[player] == start_location
|
||||
return self.multiworld.StartLocation[player] == start_location
|
||||
|
||||
def _hk_nail_combat(self, player: int) -> bool:
|
||||
return self.has_any({'LFFTSLASH', 'RIGHTSLASH', 'UPSLASH'}, player)
|
||||
|
||||
@@ -89,7 +89,7 @@ class Hylics2Logic(LogicMixin):
|
||||
|
||||
|
||||
def set_rules(hylics2world):
|
||||
world = hylics2world.world
|
||||
world = hylics2world.multiworld
|
||||
player = hylics2world.player
|
||||
|
||||
# Afterlife
|
||||
|
||||
@@ -64,8 +64,8 @@ class Hylics2World(World):
|
||||
|
||||
# set random starting location if option is enabled
|
||||
def generate_early(self):
|
||||
if self.world.random_start[self.player]:
|
||||
i = self.world.random.randint(0, 3)
|
||||
if self.multiworld.random_start[self.player]:
|
||||
i = self.multiworld.random.randint(0, 3)
|
||||
if i == 0:
|
||||
self.start_location = "Waynehouse"
|
||||
elif i == 1:
|
||||
@@ -77,12 +77,12 @@ class Hylics2World(World):
|
||||
|
||||
def generate_basic(self):
|
||||
# create location for beating the game and place Victory event there
|
||||
loc = Location(self.player, "Defeat Gibby", None, self.world.get_region("Hylemxylem", self.player))
|
||||
loc = Location(self.player, "Defeat Gibby", None, self.multiworld.get_region("Hylemxylem", self.player))
|
||||
loc.place_locked_item(self.create_event("Victory"))
|
||||
set_rule(loc, lambda state: state._hylics2_has_upper_chamber_key(self.player)
|
||||
and state._hylics2_has_vessel_room_key(self.player))
|
||||
self.world.get_region("Hylemxylem", self.player).locations.append(loc)
|
||||
self.world.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
self.multiworld.get_region("Hylemxylem", self.player).locations.append(loc)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
# create item pool
|
||||
pool = []
|
||||
@@ -94,53 +94,53 @@ class Hylics2World(World):
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# add party members if option is enabled
|
||||
if self.world.party_shuffle[self.player]:
|
||||
if self.multiworld.party_shuffle[self.player]:
|
||||
for i, data in Items.party_item_table.items():
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# handle gesture shuffle options
|
||||
if self.world.gesture_shuffle[self.player] == 2: # vanilla locations
|
||||
if self.multiworld.gesture_shuffle[self.player] == 2: # vanilla locations
|
||||
gestures = Items.gesture_item_table
|
||||
self.world.get_location("Waynehouse: TV", self.player)\
|
||||
self.multiworld.get_location("Waynehouse: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200678]["name"], gestures[200678]["classification"], 200678))
|
||||
self.world.get_location("Afterlife: TV", self.player)\
|
||||
self.multiworld.get_location("Afterlife: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200683]["name"], gestures[200683]["classification"], 200683))
|
||||
self.world.get_location("New Muldul: TV", self.player)\
|
||||
self.multiworld.get_location("New Muldul: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200679]["name"], gestures[200679]["classification"], 200679))
|
||||
self.world.get_location("Viewax's Edifice: TV", self.player)\
|
||||
self.multiworld.get_location("Viewax's Edifice: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200680]["name"], gestures[200680]["classification"], 200680))
|
||||
self.world.get_location("TV Island: TV", self.player)\
|
||||
self.multiworld.get_location("TV Island: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200681]["name"], gestures[200681]["classification"], 200681))
|
||||
self.world.get_location("Juice Ranch: TV", self.player)\
|
||||
self.multiworld.get_location("Juice Ranch: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200682]["name"], gestures[200682]["classification"], 200682))
|
||||
self.world.get_location("Foglast: TV", self.player)\
|
||||
self.multiworld.get_location("Foglast: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200684]["name"], gestures[200684]["classification"], 200684))
|
||||
self.world.get_location("Drill Castle: TV", self.player)\
|
||||
self.multiworld.get_location("Drill Castle: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200688]["name"], gestures[200688]["classification"], 200688))
|
||||
self.world.get_location("Sage Airship: TV", self.player)\
|
||||
self.multiworld.get_location("Sage Airship: TV", self.player)\
|
||||
.place_locked_item(self.add_item(gestures[200685]["name"], gestures[200685]["classification"], 200685))
|
||||
|
||||
elif self.world.gesture_shuffle[self.player] == 1: # TVs only
|
||||
elif self.multiworld.gesture_shuffle[self.player] == 1: # TVs only
|
||||
gestures = list(Items.gesture_item_table.items())
|
||||
tvs = list(Locations.tv_location_table.items())
|
||||
|
||||
# if Extra Items in Logic is enabled place CHARGE UP first and make sure it doesn't get
|
||||
# placed at Sage Airship: TV
|
||||
if self.world.extra_items_in_logic[self.player]:
|
||||
tv = self.world.random.choice(tvs)
|
||||
if self.multiworld.extra_items_in_logic[self.player]:
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
gest = gestures.index((200681, Items.gesture_item_table[200681]))
|
||||
while tv[1]["name"] == "Sage Airship: TV":
|
||||
tv = self.world.random.choice(tvs)
|
||||
self.world.get_location(tv[1]["name"], self.player)\
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
self.multiworld.get_location(tv[1]["name"], self.player)\
|
||||
.place_locked_item(self.add_item(gestures[gest][1]["name"], gestures[gest][1]["classification"],
|
||||
gestures[gest]))
|
||||
gestures.remove(gestures[gest])
|
||||
tvs.remove(tv)
|
||||
|
||||
for i in range(len(gestures)):
|
||||
gest = self.world.random.choice(gestures)
|
||||
tv = self.world.random.choice(tvs)
|
||||
self.world.get_location(tv[1]["name"], self.player)\
|
||||
gest = self.multiworld.random.choice(gestures)
|
||||
tv = self.multiworld.random.choice(tvs)
|
||||
self.multiworld.get_location(tv[1]["name"], self.player)\
|
||||
.place_locked_item(self.add_item(gest[1]["name"], gest[1]["classification"], gest[1]))
|
||||
gestures.remove(gest)
|
||||
tvs.remove(tv)
|
||||
@@ -150,22 +150,22 @@ class Hylics2World(World):
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# add '10 Bones' items if medallion shuffle is enabled
|
||||
if self.world.medallion_shuffle[self.player]:
|
||||
if self.multiworld.medallion_shuffle[self.player]:
|
||||
for i, data in Items.medallion_item_table.items():
|
||||
for j in range(data["count"]):
|
||||
pool.append(self.add_item(data["name"], data["classification"], i))
|
||||
|
||||
# add to world's pool
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
slot_data: Dict[str, Any] = {
|
||||
"party_shuffle": self.world.party_shuffle[self.player].value,
|
||||
"medallion_shuffle": self.world.medallion_shuffle[self.player].value,
|
||||
"random_start" : self.world.random_start[self.player].value,
|
||||
"party_shuffle": self.multiworld.party_shuffle[self.player].value,
|
||||
"medallion_shuffle": self.multiworld.medallion_shuffle[self.player].value,
|
||||
"random_start" : self.multiworld.random_start[self.player].value,
|
||||
"start_location" : self.start_location,
|
||||
"death_link": self.world.death_link[self.player].value
|
||||
"death_link": self.multiworld.death_link[self.player].value
|
||||
}
|
||||
return slot_data
|
||||
|
||||
@@ -173,29 +173,29 @@ class Hylics2World(World):
|
||||
def create_regions(self) -> None:
|
||||
|
||||
region_table: Dict[int, Region] = {
|
||||
0: Region("Menu", RegionType.Generic, "Menu", self.player, self.world),
|
||||
1: Region("Afterlife", RegionType.Generic, "Afterlife", self.player, self.world),
|
||||
2: Region("Waynehouse", RegionType.Generic, "Waynehouse", self.player, self.world),
|
||||
3: Region("World", RegionType.Generic, "World", self.player, self.world),
|
||||
4: Region("New Muldul", RegionType.Generic, "New Muldul", self.player, self.world),
|
||||
5: Region("New Muldul Vault", RegionType.Generic, "New Muldul Vault", self.player, self.world),
|
||||
6: Region("Viewax", RegionType.Generic, "Viewax's Edifice", self.player, self.world),
|
||||
7: Region("Airship", RegionType.Generic, "Airship", self.player, self.world),
|
||||
8: Region("Arcade Island", RegionType.Generic, "Arcade Island", self.player, self.world),
|
||||
9: Region("TV Island", RegionType.Generic, "TV Island", self.player, self.world),
|
||||
10: Region("Juice Ranch", RegionType.Generic, "Juice Ranch", self.player, self.world),
|
||||
11: Region("Shield Facility", RegionType.Generic, "Shield Facility", self.player, self.world),
|
||||
12: Region("Worm Pod", RegionType.Generic, "Worm Pod", self.player, self.world),
|
||||
13: Region("Foglast", RegionType.Generic, "Foglast", self.player, self.world),
|
||||
14: Region("Drill Castle", RegionType.Generic, "Drill Castle", self.player, self.world),
|
||||
15: Region("Sage Labyrinth", RegionType.Generic, "Sage Labyrinth", self.player, self.world),
|
||||
16: Region("Sage Airship", RegionType.Generic, "Sage Airship", self.player, self.world),
|
||||
17: Region("Hylemxylem", RegionType.Generic, "Hylemxylem", self.player, self.world)
|
||||
0: Region("Menu", RegionType.Generic, "Menu", self.player, self.multiworld),
|
||||
1: Region("Afterlife", RegionType.Generic, "Afterlife", self.player, self.multiworld),
|
||||
2: Region("Waynehouse", RegionType.Generic, "Waynehouse", self.player, self.multiworld),
|
||||
3: Region("World", RegionType.Generic, "World", self.player, self.multiworld),
|
||||
4: Region("New Muldul", RegionType.Generic, "New Muldul", self.player, self.multiworld),
|
||||
5: Region("New Muldul Vault", RegionType.Generic, "New Muldul Vault", self.player, self.multiworld),
|
||||
6: Region("Viewax", RegionType.Generic, "Viewax's Edifice", self.player, self.multiworld),
|
||||
7: Region("Airship", RegionType.Generic, "Airship", self.player, self.multiworld),
|
||||
8: Region("Arcade Island", RegionType.Generic, "Arcade Island", self.player, self.multiworld),
|
||||
9: Region("TV Island", RegionType.Generic, "TV Island", self.player, self.multiworld),
|
||||
10: Region("Juice Ranch", RegionType.Generic, "Juice Ranch", self.player, self.multiworld),
|
||||
11: Region("Shield Facility", RegionType.Generic, "Shield Facility", self.player, self.multiworld),
|
||||
12: Region("Worm Pod", RegionType.Generic, "Worm Pod", self.player, self.multiworld),
|
||||
13: Region("Foglast", RegionType.Generic, "Foglast", self.player, self.multiworld),
|
||||
14: Region("Drill Castle", RegionType.Generic, "Drill Castle", self.player, self.multiworld),
|
||||
15: Region("Sage Labyrinth", RegionType.Generic, "Sage Labyrinth", self.player, self.multiworld),
|
||||
16: Region("Sage Airship", RegionType.Generic, "Sage Airship", self.player, self.multiworld),
|
||||
17: Region("Hylemxylem", RegionType.Generic, "Hylemxylem", self.player, self.multiworld)
|
||||
}
|
||||
|
||||
# create regions from table
|
||||
for i, reg in region_table.items():
|
||||
self.world.regions.append(reg)
|
||||
self.multiworld.regions.append(reg)
|
||||
# get all exits per region
|
||||
for j, exits in Exits.region_exit_table.items():
|
||||
if j == i:
|
||||
@@ -203,7 +203,7 @@ class Hylics2World(World):
|
||||
# create entrance and connect it to parent and destination regions
|
||||
ent = Entrance(self.player, k, reg)
|
||||
reg.exits.append(ent)
|
||||
if k == "New Game" and self.world.random_start[self.player]:
|
||||
if k == "New Game" and self.multiworld.random_start[self.player]:
|
||||
if self.start_location == "Waynehouse":
|
||||
ent.connect(region_table[2])
|
||||
elif self.start_location == "Viewax's Edifice":
|
||||
@@ -226,13 +226,13 @@ class Hylics2World(World):
|
||||
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
|
||||
|
||||
# add party member locations if option is enabled
|
||||
if self.world.party_shuffle[self.player]:
|
||||
if self.multiworld.party_shuffle[self.player]:
|
||||
for i, data in Locations.party_location_table.items():
|
||||
region_table[data["region"]].locations\
|
||||
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
|
||||
|
||||
# add medallion locations if option is enabled
|
||||
if self.world.medallion_shuffle[self.player]:
|
||||
if self.multiworld.medallion_shuffle[self.player]:
|
||||
for i, data in Locations.medallion_location_table.items():
|
||||
region_table[data["region"]].locations\
|
||||
.append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
|
||||
|
||||
@@ -88,7 +88,7 @@ class MeritousWorld(World):
|
||||
return crystal_pool
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
rand_crystals = self.world.random.randrange(0, 32)
|
||||
rand_crystals = self.multiworld.random.randrange(0, 32)
|
||||
if rand_crystals < 16:
|
||||
return "Crystals x500"
|
||||
elif rand_crystals < 28:
|
||||
@@ -97,14 +97,14 @@ class MeritousWorld(World):
|
||||
return "Crystals x2000"
|
||||
|
||||
def generate_early(self):
|
||||
self.goal = self.world.goal[self.player].value
|
||||
self.include_evolution_traps = self.world.include_evolution_traps[self.player].value
|
||||
self.include_psi_keys = self.world.include_psi_keys[self.player].value
|
||||
self.item_cache_cost = self.world.item_cache_cost[self.player].value
|
||||
self.death_link = self.world.death_link[self.player].value
|
||||
self.goal = self.multiworld.goal[self.player].value
|
||||
self.include_evolution_traps = self.multiworld.include_evolution_traps[self.player].value
|
||||
self.include_psi_keys = self.multiworld.include_psi_keys[self.player].value
|
||||
self.item_cache_cost = self.multiworld.item_cache_cost[self.player].value
|
||||
self.death_link = self.multiworld.death_link[self.player].value
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def create_items(self):
|
||||
frequencies = [0, # Nothing [0]
|
||||
@@ -133,22 +133,22 @@ class MeritousWorld(World):
|
||||
if len(item_pool) < location_count:
|
||||
item_pool += self._make_crystals(location_count - len(item_pool))
|
||||
|
||||
self.world.itempool += item_pool
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
self.world.get_location("Place of Power", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Place of Power", self.player).place_locked_item(
|
||||
self.create_item("Cursed Seal"))
|
||||
self.world.get_location("The Last Place You'll Look", self.player).place_locked_item(
|
||||
self.multiworld.get_location("The Last Place You'll Look", self.player).place_locked_item(
|
||||
self.create_item("Agate Knife"))
|
||||
self.world.get_location("Wervyn Anixil", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Wervyn Anixil", self.player).place_locked_item(
|
||||
self.create_event("Victory"))
|
||||
self.world.get_location("Wervyn Anixil?", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Wervyn Anixil?", self.player).place_locked_item(
|
||||
self.create_event("Full Victory"))
|
||||
for boss in ["Meridian", "Ataraxia", "Merodach"]:
|
||||
self.world.get_location(f"{boss} Defeat", self.player).place_locked_item(
|
||||
self.multiworld.get_location(f"{boss} Defeat", self.player).place_locked_item(
|
||||
self.create_event(f"{boss} Defeated"))
|
||||
|
||||
if not self.include_psi_keys:
|
||||
@@ -156,22 +156,22 @@ class MeritousWorld(World):
|
||||
psi_key_storage = []
|
||||
for i in range(0, 3):
|
||||
psi_keys += [self.create_item(f"PSI Key {i + 1}")]
|
||||
psi_key_storage += [self.world.get_location(
|
||||
psi_key_storage += [self.multiworld.get_location(
|
||||
f"PSI Key Storage {i + 1}", self.player)]
|
||||
|
||||
fill_restrictive(self.world, self.world.get_all_state(
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(
|
||||
False), psi_key_storage, psi_keys)
|
||||
|
||||
if not self.include_evolution_traps:
|
||||
for boss in ["Meridian", "Ataraxia", "Merodach"]:
|
||||
self.world.get_location(boss, self.player).place_locked_item(
|
||||
self.multiworld.get_location(boss, self.player).place_locked_item(
|
||||
self.create_item("Evolution Trap"))
|
||||
|
||||
if self.goal == 0:
|
||||
self.world.completion_condition[self.player] = lambda state: state.has_any(
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has_any(
|
||||
["Victory", "Full Victory"], self.player)
|
||||
else:
|
||||
self.world.completion_condition[self.player] = lambda state: state.has(
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(
|
||||
"Full Victory", self.player)
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
|
||||
@@ -45,7 +45,7 @@ class MinecraftLogic(LogicMixin):
|
||||
self.can_reach('Bastion Remnant', 'Region', player))
|
||||
|
||||
def _mc_overworld_villager(self, player: int):
|
||||
village_region = self.world.get_region('Village', player).entrances[0].parent_region.name
|
||||
village_region = self.multiworld.get_region('Village', player).entrances[0].parent_region.name
|
||||
if village_region == 'The Nether': # 2 options: cure zombie villager or build portal in village
|
||||
return (self.can_reach('Zombie Doctor', 'Location', player) or
|
||||
(self._mc_has_diamond_pickaxe(player) and self.can_reach('Village', 'Region', player)))
|
||||
@@ -58,10 +58,10 @@ class MinecraftLogic(LogicMixin):
|
||||
|
||||
# Difficulty-dependent functions
|
||||
def _mc_combat_difficulty(self, player: int):
|
||||
return self.world.combat_difficulty[player].current_key
|
||||
return self.multiworld.combat_difficulty[player].current_key
|
||||
|
||||
def _mc_can_adventure(self, player: int):
|
||||
death_link_check = not self.world.death_link[player] or self.has('Bed', player)
|
||||
death_link_check = not self.multiworld.death_link[player] or self.has('Bed', player)
|
||||
if self._mc_combat_difficulty(player) == 'easy':
|
||||
return self.has('Progressive Weapons', player, 2) and self._mc_has_iron_ingots(player) and death_link_check
|
||||
elif self._mc_combat_difficulty(player) == 'hard':
|
||||
@@ -112,9 +112,9 @@ class MinecraftLogic(LogicMixin):
|
||||
return self.has('Progressive Weapons', player, 2) and self.has('Progressive Armor', player) and self.has('Archery', player)
|
||||
|
||||
def _mc_has_structure_compass(self, entrance_name: str, player: int):
|
||||
if not self.world.structure_compasses[player]:
|
||||
if not self.multiworld.structure_compasses[player]:
|
||||
return True
|
||||
return self.has(f"Structure Compass ({self.world.get_entrance(entrance_name, player).connected_region.name})", player)
|
||||
return self.has(f"Structure Compass ({self.multiworld.get_entrance(entrance_name, player).connected_region.name})", player)
|
||||
|
||||
# Sets rules on entrances and advancements that are always applied
|
||||
def set_advancement_rules(world: MultiWorld, player: int):
|
||||
|
||||
@@ -70,21 +70,21 @@ class MinecraftWorld(World):
|
||||
def _get_mc_data(self):
|
||||
exits = [connection[0] for connection in default_connections]
|
||||
return {
|
||||
'world_seed': self.world.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.world.seed_name,
|
||||
'player_name': self.world.get_player_name(self.player),
|
||||
'world_seed': self.multiworld.slot_seeds[self.player].getrandbits(32),
|
||||
'seed_name': self.multiworld.seed_name,
|
||||
'player_name': self.multiworld.get_player_name(self.player),
|
||||
'player_id': self.player,
|
||||
'client_version': client_version,
|
||||
'structures': {exit: self.world.get_entrance(exit, self.player).connected_region.name for exit in exits},
|
||||
'advancement_goal': self.world.advancement_goal[self.player].value,
|
||||
'egg_shards_required': min(self.world.egg_shards_required[self.player].value,
|
||||
self.world.egg_shards_available[self.player].value),
|
||||
'egg_shards_available': self.world.egg_shards_available[self.player].value,
|
||||
'required_bosses': self.world.required_bosses[self.player].current_key,
|
||||
'MC35': bool(self.world.send_defeated_mobs[self.player].value),
|
||||
'death_link': bool(self.world.death_link[self.player].value),
|
||||
'starting_items': str(self.world.starting_items[self.player].value),
|
||||
'race': self.world.is_race,
|
||||
'structures': {exit: self.multiworld.get_entrance(exit, self.player).connected_region.name for exit in exits},
|
||||
'advancement_goal': self.multiworld.advancement_goal[self.player].value,
|
||||
'egg_shards_required': min(self.multiworld.egg_shards_required[self.player].value,
|
||||
self.multiworld.egg_shards_available[self.player].value),
|
||||
'egg_shards_available': self.multiworld.egg_shards_available[self.player].value,
|
||||
'required_bosses': self.multiworld.required_bosses[self.player].current_key,
|
||||
'MC35': bool(self.multiworld.send_defeated_mobs[self.player].value),
|
||||
'death_link': bool(self.multiworld.death_link[self.player].value),
|
||||
'starting_items': str(self.multiworld.starting_items[self.player].value),
|
||||
'race': self.multiworld.is_race,
|
||||
}
|
||||
|
||||
def generate_basic(self):
|
||||
@@ -96,18 +96,18 @@ class MinecraftWorld(World):
|
||||
for (name, num) in required_items.items():
|
||||
itempool += [name] * num
|
||||
# Add structure compasses if desired
|
||||
if self.world.structure_compasses[self.player]:
|
||||
if self.multiworld.structure_compasses[self.player]:
|
||||
structures = [connection[1] for connection in default_connections]
|
||||
for struct_name in structures:
|
||||
itempool.append(f"Structure Compass ({struct_name})")
|
||||
# Add dragon egg shards
|
||||
if self.world.egg_shards_required[self.player] > 0:
|
||||
itempool += ["Dragon Egg Shard"] * self.world.egg_shards_available[self.player]
|
||||
if self.multiworld.egg_shards_required[self.player] > 0:
|
||||
itempool += ["Dragon Egg Shard"] * self.multiworld.egg_shards_available[self.player]
|
||||
# Add bee traps if desired
|
||||
bee_trap_quantity = ceil(self.world.bee_traps[self.player] * (len(self.location_names)-len(itempool)) * 0.01)
|
||||
bee_trap_quantity = ceil(self.multiworld.bee_traps[self.player] * (len(self.location_names) - len(itempool)) * 0.01)
|
||||
itempool += ["Bee Trap"] * bee_trap_quantity
|
||||
# Fill remaining items with randomly generated junk
|
||||
itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()), k=len(self.location_names)-len(itempool))
|
||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()), k=len(self.location_names) - len(itempool))
|
||||
# Convert itempool into real items
|
||||
itempool = [item for item in map(lambda name: self.create_item(name), itempool)]
|
||||
|
||||
@@ -115,29 +115,29 @@ class MinecraftWorld(World):
|
||||
exclusion_pool = set()
|
||||
exclusion_types = ['hard', 'unreasonable']
|
||||
for key in exclusion_types:
|
||||
if not getattr(self.world, f"include_{key}_advancements")[self.player]:
|
||||
if not getattr(self.multiworld, f"include_{key}_advancements")[self.player]:
|
||||
exclusion_pool.update(exclusion_table[key])
|
||||
# For postgame advancements, check with the boss goal
|
||||
exclusion_pool.update(get_postgame_advancements(self.world.required_bosses[self.player].current_key))
|
||||
exclusion_rules(self.world, self.player, exclusion_pool)
|
||||
exclusion_pool.update(get_postgame_advancements(self.multiworld.required_bosses[self.player].current_key))
|
||||
exclusion_rules(self.multiworld, self.player, exclusion_pool)
|
||||
|
||||
# Prefill event locations with their events
|
||||
self.world.get_location("Blaze Spawner", self.player).place_locked_item(self.create_item("Blaze Rods"))
|
||||
self.world.get_location("Ender Dragon", self.player).place_locked_item(self.create_item("Defeat Ender Dragon"))
|
||||
self.world.get_location("Wither", self.player).place_locked_item(self.create_item("Defeat Wither"))
|
||||
self.multiworld.get_location("Blaze Spawner", self.player).place_locked_item(self.create_item("Blaze Rods"))
|
||||
self.multiworld.get_location("Ender Dragon", self.player).place_locked_item(self.create_item("Defeat Ender Dragon"))
|
||||
self.multiworld.get_location("Wither", self.player).place_locked_item(self.create_item("Defeat Wither"))
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choices(list(junk_weights.keys()), weights=list(junk_weights.values()))[0]
|
||||
return self.multiworld.random.choices(list(junk_weights.keys()), weights=list(junk_weights.values()))[0]
|
||||
|
||||
def set_rules(self):
|
||||
set_advancement_rules(self.world, self.player)
|
||||
set_completion_rules(self.world, self.player)
|
||||
set_advancement_rules(self.multiworld, self.player)
|
||||
set_completion_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
def MCRegion(region_name: str, exits=[]):
|
||||
ret = Region(region_name, None, region_name, self.player, self.world)
|
||||
ret = Region(region_name, None, region_name, self.player, self.multiworld)
|
||||
ret.locations = [MinecraftAdvancement(self.player, loc_name, loc_data.id, ret)
|
||||
for loc_name, loc_data in advancement_table.items()
|
||||
if loc_data.region == region_name]
|
||||
@@ -145,19 +145,19 @@ class MinecraftWorld(World):
|
||||
ret.exits.append(Entrance(self.player, exit, ret))
|
||||
return ret
|
||||
|
||||
self.world.regions += [MCRegion(*r) for r in mc_regions]
|
||||
link_minecraft_structures(self.world, self.player)
|
||||
self.multiworld.regions += [MCRegion(*r) for r in mc_regions]
|
||||
link_minecraft_structures(self.multiworld, self.player)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
data = self._get_mc_data()
|
||||
filename = f"{self.world.get_out_file_name_base(self.player)}.apmc"
|
||||
filename = f"AP_{self.multiworld.get_out_file_name_base(self.player)}.apmc"
|
||||
with open(os.path.join(output_directory, filename), 'wb') as f:
|
||||
f.write(b64encode(bytes(json.dumps(data), 'utf-8')))
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = self._get_mc_data()
|
||||
for option_name in minecraft_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if slot_data.get(option_name, None) is None and type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
@@ -768,7 +768,7 @@ patch_sets = {
|
||||
|
||||
def patch_cosmetics(ootworld, rom):
|
||||
# Use the world's slot seed for cosmetics
|
||||
random.seed(ootworld.world.slot_seeds[ootworld.player])
|
||||
random.seed(ootworld.multiworld.slot_seeds[ootworld.player])
|
||||
|
||||
# try to detect the cosmetic patch data format
|
||||
versioned_patch_set = None
|
||||
|
||||
@@ -9,7 +9,7 @@ class Dungeon(object):
|
||||
else:
|
||||
return [obj]
|
||||
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.name = name
|
||||
self.hint_text = hint
|
||||
self.font_color = font_color
|
||||
@@ -18,7 +18,7 @@ class Dungeon(object):
|
||||
self.small_keys = to_array(small_keys)
|
||||
self.dungeon_items = to_array(dungeon_items)
|
||||
|
||||
for region in world.world.regions:
|
||||
for region in world.multiworld.regions:
|
||||
if region.player == world.player and region.dungeon == self.name:
|
||||
region.dungeon = self
|
||||
self.regions.append(region)
|
||||
|
||||
@@ -7,7 +7,7 @@ class OOTEntrance(Entrance):
|
||||
|
||||
def __init__(self, player, world, name='', parent=None):
|
||||
super(OOTEntrance, self).__init__(player, name, parent)
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.access_rules = []
|
||||
self.reverse = None
|
||||
self.replaces = None
|
||||
@@ -30,8 +30,8 @@ class OOTEntrance(Entrance):
|
||||
return previously_connected
|
||||
|
||||
def get_new_target(self):
|
||||
root = self.world.get_region('Root Exits', self.player)
|
||||
target_entrance = OOTEntrance(self.player, self.world, 'Root -> ' + self.connected_region.name, root)
|
||||
root = self.multiworld.get_region('Root Exits', self.player)
|
||||
target_entrance = OOTEntrance(self.player, self.multiworld, 'Root -> ' + self.connected_region.name, root)
|
||||
target_entrance.connect(self.connected_region)
|
||||
target_entrance.replaces = self
|
||||
root.exits.append(target_entrance)
|
||||
|
||||
@@ -369,7 +369,7 @@ class EntranceShuffleError(Exception):
|
||||
|
||||
|
||||
def shuffle_random_entrances(ootworld):
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
# Gather locations to keep reachable for validation
|
||||
@@ -593,7 +593,7 @@ def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, al
|
||||
all_state, none_state, one_way_entrance_pools, one_way_target_entrance_pools):
|
||||
|
||||
avail_pool = list(chain.from_iterable(one_way_entrance_pools[t] for t in allowed_types if t in one_way_entrance_pools))
|
||||
ootworld.world.random.shuffle(avail_pool)
|
||||
ootworld.multiworld.random.shuffle(avail_pool)
|
||||
|
||||
for entrance in avail_pool:
|
||||
if entrance.replaces:
|
||||
@@ -643,11 +643,11 @@ def shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrances,
|
||||
raise EntranceShuffleError(f'Entrance placement attempt count exceeded for world {ootworld.player}')
|
||||
|
||||
def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state):
|
||||
ootworld.world.random.shuffle(entrances)
|
||||
ootworld.multiworld.random.shuffle(entrances)
|
||||
for entrance in entrances:
|
||||
if entrance.connected_region != None:
|
||||
continue
|
||||
ootworld.world.random.shuffle(target_entrances)
|
||||
ootworld.multiworld.random.shuffle(target_entrances)
|
||||
# Here we deliberately introduce bias by prioritizing certain interiors, i.e. the ones most likely to cause problems.
|
||||
# success rate over randomization
|
||||
if pool_type in {'InteriorSoft', 'MixedSoft'}:
|
||||
@@ -662,7 +662,7 @@ def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollback
|
||||
|
||||
|
||||
def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entrances):
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
# Disconnect all root assumed entrances and save original connections
|
||||
@@ -704,7 +704,7 @@ def split_entrances_by_requirements(ootworld, entrances_to_split, assumed_entran
|
||||
# TODO: improve this function
|
||||
def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all_state_orig, none_state_orig):
|
||||
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
all_state = all_state_orig.copy()
|
||||
@@ -827,7 +827,7 @@ def same_hint_area(first, second):
|
||||
return False
|
||||
|
||||
def get_entrance_replacing(region, entrance_name, player):
|
||||
original_entrance = region.world.get_entrance(entrance_name, player)
|
||||
original_entrance = region.multiworld.get_entrance(entrance_name, player)
|
||||
if not original_entrance.shuffled:
|
||||
return original_entrance
|
||||
|
||||
@@ -842,14 +842,14 @@ def get_entrance_replacing(region, entrance_name, player):
|
||||
def change_connections(entrance, target):
|
||||
entrance.connect(target.disconnect())
|
||||
entrance.replaces = target.replaces
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
if entrance.reverse and not entrance.multiworld.worlds[entrance.player].decouple_entrances:
|
||||
target.replaces.reverse.connect(entrance.reverse.assumed.disconnect())
|
||||
target.replaces.reverse.replaces = entrance.reverse
|
||||
|
||||
def restore_connections(entrance, target):
|
||||
target.connect(entrance.disconnect())
|
||||
entrance.replaces = None
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
if entrance.reverse and not entrance.multiworld.worlds[entrance.player].decouple_entrances:
|
||||
entrance.reverse.assumed.connect(target.replaces.reverse.disconnect())
|
||||
target.replaces.reverse.replaces = None
|
||||
|
||||
@@ -866,7 +866,7 @@ def check_entrances_compatibility(entrance, target, rollbacks):
|
||||
def confirm_replacement(entrance, target):
|
||||
delete_target_entrance(target)
|
||||
logging.getLogger('').debug(f'Connected {entrance} to {entrance.connected_region}')
|
||||
if entrance.reverse and not entrance.world.worlds[entrance.player].decouple_entrances:
|
||||
if entrance.reverse and not entrance.multiworld.worlds[entrance.player].decouple_entrances:
|
||||
replaced_reverse = target.replaces.reverse
|
||||
delete_target_entrance(entrance.reverse.assumed)
|
||||
logging.getLogger('').debug(f'Connected {replaced_reverse} to {replaced_reverse.connected_region}')
|
||||
|
||||
@@ -131,13 +131,13 @@ 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':
|
||||
if (item.map or item.compass) and dungeon.multiworld.shuffle_mapcompass == 'dungeon':
|
||||
return item in dungeon.dungeon_items
|
||||
if item.type == 'SmallKey' and dungeon.world.shuffle_smallkeys == 'dungeon':
|
||||
if item.type == 'SmallKey' and dungeon.multiworld.shuffle_smallkeys == 'dungeon':
|
||||
return item in dungeon.small_keys
|
||||
if item.type == 'BossKey' and dungeon.world.shuffle_bosskeys == 'dungeon':
|
||||
if item.type == 'BossKey' and dungeon.multiworld.shuffle_bosskeys == 'dungeon':
|
||||
return item in dungeon.boss_key
|
||||
if item.type == 'GanonBossKey' and dungeon.world.shuffle_ganon_bosskey == 'dungeon':
|
||||
if item.type == 'GanonBossKey' and dungeon.multiworld.shuffle_ganon_bosskey == 'dungeon':
|
||||
return item in dungeon.boss_key
|
||||
return False
|
||||
|
||||
@@ -148,7 +148,7 @@ def isRestrictedDungeonItem(dungeon, item):
|
||||
def attach_name(text, hinted_object, world):
|
||||
if hinted_object.player == world.player:
|
||||
return text
|
||||
return f"{text} for {world.world.get_player_name(hinted_object.player)}"
|
||||
return f"{text} for {world.multiworld.get_player_name(hinted_object.player)}"
|
||||
|
||||
|
||||
def add_hint(world, groups, gossip_text, count, location=None, force_reachable=False):
|
||||
@@ -439,7 +439,7 @@ def get_specific_item_hint(world, checked):
|
||||
itemname = world.named_item_pool.pop(0)
|
||||
if itemname == "Bottle" and world.hint_dist == "bingo":
|
||||
locations = [
|
||||
location for location in world.world.get_filled_locations()
|
||||
location for location in world.multiworld.get_filled_locations()
|
||||
if (is_not_checked(location, checked)
|
||||
and location.name not in world.hint_exclusions
|
||||
and location.item.name in bingoBottlesForHints
|
||||
@@ -448,7 +448,7 @@ def get_specific_item_hint(world, checked):
|
||||
]
|
||||
else:
|
||||
locations = [
|
||||
location for location in world.world.get_filled_locations()
|
||||
location for location in world.multiworld.get_filled_locations()
|
||||
if (is_not_checked(location, checked)
|
||||
and location.name not in world.hint_exclusions
|
||||
and location.item.name == itemname
|
||||
@@ -489,7 +489,7 @@ def get_random_location_hint(world, checked):
|
||||
and location.name not in world.hint_exclusions
|
||||
and location.name not in world.hint_type_overrides['item']
|
||||
and location.item.name not in world.item_hint_type_overrides['item'],
|
||||
world.world.get_filled_locations(world.player)))
|
||||
world.multiworld.get_filled_locations(world.player)))
|
||||
if not locations:
|
||||
return None
|
||||
|
||||
@@ -639,13 +639,13 @@ def buildWorldGossipHints(world, checkedLocations=None):
|
||||
world.woth_dungeon = 0
|
||||
|
||||
if checkedLocations is None:
|
||||
checkedLocations = {player: set() for player in world.world.get_all_ids()}
|
||||
checkedLocations = {player: set() for player in world.multiworld.get_all_ids()}
|
||||
|
||||
# If Ganondorf hints Light Arrows and is reachable without them, add to checkedLocations to prevent extra hinting
|
||||
# Can only be forced with vanilla bridge or trials
|
||||
if world.bridge != 'vanilla' and world.trials == 0 and world.misc_hints:
|
||||
try:
|
||||
light_arrow_location = world.world.find_item("Light Arrows", world.player)
|
||||
light_arrow_location = world.multiworld.find_item("Light Arrows", world.player)
|
||||
checkedLocations[light_arrow_location.player].add(light_arrow_location.name)
|
||||
except StopIteration: # start with them
|
||||
pass
|
||||
@@ -885,7 +885,7 @@ def buildAltarHints(world, messages, include_rewards=True, include_wincons=True)
|
||||
|
||||
# pulls text string from hintlist for reward after sending the location to hintlist.
|
||||
def buildBossString(reward, color, world):
|
||||
for location in world.world.get_filled_locations(world.player):
|
||||
for location in world.multiworld.get_filled_locations(world.player):
|
||||
if location.item.name == reward:
|
||||
item_icon = chr(location.item.special['item_id'])
|
||||
location_text = getHint(location.name, world.clearer_hints).text
|
||||
@@ -956,18 +956,18 @@ def buildGanonText(world, messages):
|
||||
text += "\x05\x42your pocket\x05\x40"
|
||||
else:
|
||||
try:
|
||||
find_light_arrows = world.world.find_item('Light Arrows', world.player)
|
||||
find_light_arrows = world.multiworld.find_item('Light Arrows', world.player)
|
||||
text = get_raw_text(getHint('Light Arrow Location', world.clearer_hints).text)
|
||||
location = find_light_arrows
|
||||
location_hint = get_hint_area(location)
|
||||
if world.player != location.player:
|
||||
text += "\x05\x42%s's\x05\x40 %s" % (world.world.get_player_name(location.player), get_raw_text(location_hint))
|
||||
text += "\x05\x42%s's\x05\x40 %s" % (world.multiworld.get_player_name(location.player), get_raw_text(location_hint))
|
||||
else:
|
||||
location_hint = location_hint.replace('Ganon\'s Castle', 'my castle')
|
||||
text += get_raw_text(location_hint)
|
||||
except StopIteration:
|
||||
text = get_raw_text(getHint('Validation Line', world.clearer_hints).text)
|
||||
for location in world.world.get_filled_locations(world.player):
|
||||
for location in world.multiworld.get_filled_locations(world.player):
|
||||
if location.name == 'Ganons Tower Boss Key Chest':
|
||||
text += get_raw_text(getHint(getItemGenericName(location.item), world.clearer_hints).text)
|
||||
break
|
||||
|
||||
@@ -748,7 +748,7 @@ def replace_max_item(items, item, max):
|
||||
|
||||
|
||||
def generate_itempool(ootworld):
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
global random
|
||||
random = world.random
|
||||
@@ -1280,32 +1280,32 @@ def get_pool_core(world):
|
||||
|
||||
if world.free_scarecrow:
|
||||
item = world.create_item('Scarecrow Song')
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
|
||||
if world.no_epona_race:
|
||||
item = world.create_item('Epona')
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
|
||||
if world.shuffle_mapcompass == 'remove' or world.shuffle_mapcompass == 'startwith':
|
||||
for item in [item for dungeon in world.dungeons for item in dungeon.dungeon_items]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
if world.shuffle_smallkeys == 'remove':
|
||||
for item in [item for dungeon in world.dungeons for item in dungeon.small_keys]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
if world.shuffle_bosskeys == 'remove':
|
||||
for item in [item for dungeon in world.dungeons if dungeon.name != 'Ganons Castle' for item in dungeon.boss_key]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
if world.shuffle_ganon_bosskey in ['remove', 'triforce']:
|
||||
for item in [item for dungeon in world.dungeons if dungeon.name == 'Ganons Castle' for item in dungeon.boss_key]:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
pool.extend(get_junk_item())
|
||||
|
||||
@@ -1331,7 +1331,7 @@ def get_pool_core(world):
|
||||
# Yes somehow you need 3 keys. This dungeon is bonkers
|
||||
items = [world.create_item('Small Key (Spirit Temple)') for i in range(3)]
|
||||
for item in items:
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
#if not world.dungeon_mq['Fire Temple']:
|
||||
# world.state.collect(ItemFactory('Small Key (Fire Temple)'))
|
||||
@@ -1346,7 +1346,7 @@ def get_pool_core(world):
|
||||
|
||||
if not world.keysanity and not world.dungeon_mq['Fire Temple']:
|
||||
item = world.create_item('Small Key (Fire Temple)')
|
||||
world.world.push_precollected(item)
|
||||
world.multiworld.push_precollected(item)
|
||||
world.remove_from_start_inventory.append(item.name)
|
||||
|
||||
if world.triforce_hunt:
|
||||
|
||||
@@ -882,7 +882,7 @@ def make_player_message(text):
|
||||
def update_item_messages(messages, world):
|
||||
new_item_messages = {**ITEM_MESSAGES, **KEYSANITY_MESSAGES}
|
||||
for id, text in new_item_messages.items():
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
update_message_by_id(messages, id, make_player_message(text), 0x23)
|
||||
else:
|
||||
update_message_by_id(messages, id, text, 0x23)
|
||||
@@ -1020,7 +1020,7 @@ def update_warp_song_text(messages, ootworld):
|
||||
}
|
||||
|
||||
for id, entr in msg_list.items():
|
||||
destination = ootworld.world.get_entrance(entr, ootworld.player).connected_region
|
||||
destination = ootworld.multiworld.get_entrance(entr, ootworld.player).connected_region
|
||||
|
||||
if destination.pretty_name:
|
||||
destination_name = destination.pretty_name
|
||||
|
||||
@@ -1326,7 +1326,7 @@ def patch_rom(world, rom):
|
||||
override_table = get_override_table(world)
|
||||
rom.write_bytes(rom.sym('cfg_item_overrides'), get_override_table_bytes(override_table))
|
||||
rom.write_byte(rom.sym('PLAYER_ID'), min(world.player, 255)) # Write player ID
|
||||
rom.write_bytes(rom.sym('AP_PLAYER_NAME'), bytearray(world.world.get_player_name(world.player), 'ascii'))
|
||||
rom.write_bytes(rom.sym('AP_PLAYER_NAME'), bytearray(world.multiworld.get_player_name(world.player), 'ascii'))
|
||||
|
||||
if world.death_link:
|
||||
rom.write_byte(rom.sym('DEATH_LINK'), 0x01)
|
||||
@@ -1359,7 +1359,7 @@ def patch_rom(world, rom):
|
||||
rom.write_byte(rom.sym('CFG_DAMAGE_MULTIPLYER'), 3)
|
||||
|
||||
# Patch songs and boss rewards
|
||||
for location in world.world.get_filled_locations(world.player):
|
||||
for location in world.multiworld.get_filled_locations(world.player):
|
||||
item = location.item
|
||||
special = item.special if item.game == 'Ocarina of Time' else {} # this shouldn't matter hopefully
|
||||
locationaddress = location.address1
|
||||
@@ -1686,7 +1686,7 @@ def patch_rom(world, rom):
|
||||
pass
|
||||
elif dungeon in ['Bottom of the Well', 'Ice Cavern']:
|
||||
dungeon_name, boss_name, compass_id, map_id = dungeon_list[dungeon]
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
map_message = "\x13\x76\x08\x05\x42\x0F\x05\x40 found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x09" % (dungeon_name)
|
||||
else:
|
||||
map_message = "\x13\x76\x08You found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x01It\'s %s!\x09" % (dungeon_name, "masterful" if world.dungeon_mq[dungeon] else "ordinary")
|
||||
@@ -1696,13 +1696,13 @@ def patch_rom(world, rom):
|
||||
else:
|
||||
dungeon_name, boss_name, compass_id, map_id = dungeon_list[dungeon]
|
||||
dungeon_reward = reward_list[world.get_location(boss_name).item.name]
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
compass_message = "\x13\x75\x08\x05\x42\x0F\x05\x40 found the \x05\x41Compass\x05\x40\x01for %s\x05\x40!\x09" % (dungeon_name)
|
||||
else:
|
||||
compass_message = "\x13\x75\x08You found the \x05\x41Compass\x05\x40\x01for %s\x05\x40!\x01It holds the %s!\x09" % (dungeon_name, dungeon_reward)
|
||||
update_message_by_id(messages, compass_id, compass_message)
|
||||
if world.mq_dungeons_random or world.mq_dungeons != 0 and world.mq_dungeons != 12:
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
map_message = "\x13\x76\x08\x05\x42\x0F\x05\x40 found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x09" % (dungeon_name)
|
||||
else:
|
||||
map_message = "\x13\x76\x08You found the \x05\x41Dungeon Map\x05\x40\x01for %s\x05\x40!\x01It\'s %s!\x09" % (dungeon_name, "masterful" if world.dungeon_mq[dungeon] else "ordinary")
|
||||
@@ -1730,7 +1730,7 @@ def patch_rom(world, rom):
|
||||
rom.write_int16(0xB6D57E, 0x0003)
|
||||
rom.write_int16(0xB6EC52, 999)
|
||||
tycoon_message = "\x08\x13\x57You got a \x05\x43Tycoon's Wallet\x05\x40!\x01Now you can hold\x01up to \x05\x46999\x05\x40 \x05\x46Rupees\x05\x40."
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
tycoon_message = make_player_message(tycoon_message)
|
||||
update_message_by_id(messages, 0x00F8, tycoon_message, 0x23)
|
||||
|
||||
@@ -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), world.world.get_filled_locations(world.player))))
|
||||
return list(filter(lambda val: val != None, map(partial(get_override_entry, world), world.multiworld.get_filled_locations(world.player))))
|
||||
|
||||
|
||||
override_struct = struct.Struct('>xBBBHBB') # match override_t in get_items.c
|
||||
@@ -2154,8 +2154,8 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
|
||||
if location.item.name == 'Ice Trap':
|
||||
split_item_name[0] = create_fake_name(split_item_name[0])
|
||||
|
||||
if len(world.world.worlds) > 1: # OOTWorld.MultiWorld.AutoWorld[]
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01%s\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (split_item_name[0], location.price, split_item_name[1], world.world.get_player_name(location.item.player))
|
||||
if len(world.multiworld.worlds) > 1: # OOTWorld.MultiWorld.AutoWorld[]
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01%s\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (split_item_name[0], location.price, split_item_name[1], world.multiworld.get_player_name(location.item.player))
|
||||
else:
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01%s\x01\x05\x40Special deal! ONE LEFT!\x01Get it while it lasts!\x09\x0A\x02' % (split_item_name[0], location.price, split_item_name[1])
|
||||
purchase_text = '\x08%s %d Rupees\x09\x01%s\x01\x1B\x05\x42Buy\x01Don\'t buy\x05\x40\x02' % (split_item_name[0], location.price, split_item_name[1])
|
||||
@@ -2168,10 +2168,10 @@ def place_shop_items(rom, world, shop_items, messages, locations, init_shop_id=F
|
||||
if location.item.trap:
|
||||
shop_item_name = create_fake_name(shop_item_name)
|
||||
|
||||
if len(world.world.worlds) > 1:
|
||||
if len(world.multiworld.worlds) > 1:
|
||||
shop_item_name = ''.join(filter(lambda char: char in character_table, shop_item_name))
|
||||
do_line_break = sum(character_table[char] for char in f"{shop_item_name} {location.price} Rupees") > NORMAL_LINE_WIDTH
|
||||
description_text = '\x08\x05\x41%s%s%d Rupees\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (shop_item_name, '\x01' if do_line_break else ' ', location.price, world.world.get_player_name(location.item.player))
|
||||
description_text = '\x08\x05\x41%s%s%d Rupees\x01\x05\x42%s\x05\x40\x01Special deal! ONE LEFT!\x09\x0A\x02' % (shop_item_name, '\x01' if do_line_break else ' ', location.price, world.multiworld.get_player_name(location.item.player))
|
||||
else:
|
||||
description_text = '\x08\x05\x41%s %d Rupees\x01\x05\x40Special deal! ONE LEFT!\x01Get it while it lasts!\x09\x0A\x02' % (shop_item_name, location.price)
|
||||
purchase_text = '\x08%s %d Rupees\x09\x01\x01\x1B\x05\x42Buy\x01Don\'t buy\x05\x40\x02' % (shop_item_name, location.price)
|
||||
|
||||
@@ -53,7 +53,7 @@ def isliteral(expr):
|
||||
class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
|
||||
def __init__(self, world, player):
|
||||
self.world = world
|
||||
self.multiworld = world
|
||||
self.player = player
|
||||
self.events = set()
|
||||
# map Region -> rule ast string -> item name
|
||||
@@ -86,9 +86,9 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
ctx=ast.Load()),
|
||||
args=[ast.Str(escaped_items[node.id]), ast.Constant(self.player)],
|
||||
keywords=[])
|
||||
elif node.id in self.world.__dict__:
|
||||
elif node.id in self.multiworld.__dict__:
|
||||
# Settings are constant
|
||||
return ast.parse('%r' % self.world.__dict__[node.id], mode='eval').body
|
||||
return ast.parse('%r' % self.multiworld.__dict__[node.id], mode='eval').body
|
||||
elif node.id in State.__dict__:
|
||||
return self.make_call(node, node.id, [], [])
|
||||
elif node.id in self.kwarg_defaults or node.id in allowed_globals:
|
||||
@@ -137,7 +137,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
|
||||
if isinstance(count, ast.Name):
|
||||
# Must be a settings constant
|
||||
count = ast.parse('%r' % self.world.__dict__[count.id], mode='eval').body
|
||||
count = ast.parse('%r' % self.multiworld.__dict__[count.id], mode='eval').body
|
||||
|
||||
if iname in escaped_items:
|
||||
iname = escaped_items[iname]
|
||||
@@ -182,7 +182,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
new_args = []
|
||||
for child in node.args:
|
||||
if isinstance(child, ast.Name):
|
||||
if child.id in self.world.__dict__:
|
||||
if child.id in self.multiworld.__dict__:
|
||||
# child = ast.Attribute(
|
||||
# value=ast.Attribute(
|
||||
# value=ast.Name(id='state', ctx=ast.Load()),
|
||||
@@ -190,7 +190,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# ctx=ast.Load()),
|
||||
# attr=child.id,
|
||||
# ctx=ast.Load())
|
||||
child = ast.Constant(getattr(self.world, child.id))
|
||||
child = ast.Constant(getattr(self.multiworld, child.id))
|
||||
elif child.id in rule_aliases:
|
||||
child = self.visit(child)
|
||||
elif child.id in escaped_items:
|
||||
@@ -217,7 +217,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
value=ast.Attribute(
|
||||
value=ast.Attribute(
|
||||
value=ast.Name(id='state', ctx=ast.Load()),
|
||||
attr='world',
|
||||
attr='multiworld',
|
||||
ctx=ast.Load()),
|
||||
attr='worlds',
|
||||
ctx=ast.Load()),
|
||||
@@ -242,7 +242,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# Fast check for json can_use
|
||||
if (len(node.ops) == 1 and isinstance(node.ops[0], ast.Eq)
|
||||
and isinstance(node.left, ast.Name) and isinstance(node.comparators[0], ast.Name)
|
||||
and node.left.id not in self.world.__dict__ and node.comparators[0].id not in self.world.__dict__):
|
||||
and node.left.id not in self.multiworld.__dict__ and node.comparators[0].id not in self.multiworld.__dict__):
|
||||
return ast.NameConstant(node.left.id == node.comparators[0].id)
|
||||
|
||||
node.left = escape_or_string(node.left)
|
||||
@@ -378,7 +378,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
# Requires the target regions have been defined in the world.
|
||||
def create_delayed_rules(self):
|
||||
for region_name, node, subrule_name in self.delayed_rules:
|
||||
region = self.world.world.get_region(region_name, self.player)
|
||||
region = self.multiworld.multiworld.get_region(region_name, self.player)
|
||||
event = OOTLocation(self.player, subrule_name, type='Event', parent=region, internal=True)
|
||||
event.show_in_spoiler = False
|
||||
|
||||
@@ -395,7 +395,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
set_rule(event, access_rule)
|
||||
region.locations.append(event)
|
||||
|
||||
self.world.make_event_item(subrule_name, event)
|
||||
self.multiworld.make_event_item(subrule_name, event)
|
||||
# Safeguard in case this is called multiple times per world
|
||||
self.delayed_rules.clear()
|
||||
|
||||
@@ -448,7 +448,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
## Handlers for compile-time optimizations (former State functions)
|
||||
|
||||
def at_day(self, node):
|
||||
if self.world.ensure_tod_access:
|
||||
if self.multiworld.ensure_tod_access:
|
||||
# tod has DAY or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -456,7 +456,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_dampe_time(self, node):
|
||||
if self.world.ensure_tod_access:
|
||||
if self.multiworld.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -464,10 +464,10 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.NameConstant(True)
|
||||
|
||||
def at_night(self, node):
|
||||
if self.current_spot.type == 'GS Token' and self.world.logic_no_night_tokens_without_suns_song:
|
||||
if self.current_spot.type == 'GS Token' and self.multiworld.logic_no_night_tokens_without_suns_song:
|
||||
# Using visit here to resolve 'can_play' rule
|
||||
return self.visit(ast.parse('can_play(Suns_Song)', mode='eval').body)
|
||||
if self.world.ensure_tod_access:
|
||||
if self.multiworld.ensure_tod_access:
|
||||
# tod has DAMPE or (tod == NONE and (ss or find a path from a provider))
|
||||
# parsing is better than constructing this expression by hand
|
||||
r = self.current_spot if type(self.current_spot) == OOTRegion else self.current_spot.parent_region
|
||||
@@ -501,7 +501,7 @@ class Rule_AST_Transformer(ast.NodeTransformer):
|
||||
return ast.parse(f"state._oot_reach_as_age('{r.name}', 'adult', {self.player})", mode='eval').body
|
||||
|
||||
def current_spot_starting_age_access(self, node):
|
||||
return self.current_spot_child_access(node) if self.world.starting_age == 'child' else self.current_spot_adult_access(node)
|
||||
return self.current_spot_child_access(node) if self.multiworld.starting_age == 'child' else self.current_spot_adult_access(node)
|
||||
|
||||
def has_bottle(self, node):
|
||||
return ast.parse(f"state._oot_has_bottle({self.player})", mode='eval').body
|
||||
|
||||
@@ -26,7 +26,7 @@ class OOTLogic(LogicMixin):
|
||||
|
||||
# Used for fall damage and other situations where damage is unavoidable
|
||||
def _oot_can_live_dmg(self, player, hearts):
|
||||
mult = self.world.worlds[player].damage_multiplier
|
||||
mult = self.multiworld.worlds[player].damage_multiplier
|
||||
if hearts*4 >= 3:
|
||||
return mult != 'ohko' and mult != 'quadruple'
|
||||
else:
|
||||
@@ -39,7 +39,7 @@ class OOTLogic(LogicMixin):
|
||||
def _oot_reach_as_age(self, regionname, age, player):
|
||||
if self.age[player] is None:
|
||||
self.age[player] = age
|
||||
can_reach = self.world.get_region(regionname, player).can_reach(self)
|
||||
can_reach = self.multiworld.get_region(regionname, player).can_reach(self)
|
||||
self.age[player] = None
|
||||
return can_reach
|
||||
return self.age[player] == age
|
||||
@@ -52,7 +52,7 @@ class OOTLogic(LogicMixin):
|
||||
}
|
||||
if regionname in name_map[tod]:
|
||||
return True
|
||||
region = self.world.get_region(regionname, player)
|
||||
region = self.multiworld.get_region(regionname, player)
|
||||
if region.provides_time == TimeOfDay.ALL or regionname == 'Root':
|
||||
self.day_reachable_regions[player].add(regionname)
|
||||
self.dampe_reachable_regions[player].add(regionname)
|
||||
@@ -82,7 +82,7 @@ class OOTLogic(LogicMixin):
|
||||
rrp = getattr(self, f'{age}_reachable_regions')[player]
|
||||
bc = getattr(self, f'{age}_blocked_connections')[player]
|
||||
queue = deque(getattr(self, f'{age}_blocked_connections')[player])
|
||||
start = self.world.get_region('Menu', player)
|
||||
start = self.multiworld.get_region('Menu', player)
|
||||
|
||||
# init on first call - this can't be done on construction since the regions don't exist yet
|
||||
if not start in rrp:
|
||||
@@ -110,7 +110,7 @@ class OOTLogic(LogicMixin):
|
||||
def set_rules(ootworld):
|
||||
logger = logging.getLogger('')
|
||||
|
||||
world = ootworld.world
|
||||
world = ootworld.multiworld
|
||||
player = ootworld.player
|
||||
|
||||
if ootworld.logic_rules != 'no_logic':
|
||||
@@ -213,10 +213,10 @@ def set_shop_rules(ootworld):
|
||||
# The goal is to automatically set item rules based on age requirements in case entrances were shuffled
|
||||
def set_entrances_based_rules(ootworld):
|
||||
|
||||
if ootworld.world.accessibility == 'beatable':
|
||||
if ootworld.multiworld.accessibility == 'beatable':
|
||||
return
|
||||
|
||||
all_state = ootworld.world.get_all_state(False)
|
||||
all_state = ootworld.multiworld.get_all_state(False)
|
||||
|
||||
for location in filter(lambda location: location.type == 'Shop', ootworld.get_locations()):
|
||||
# If a shop is not reachable as adult, it can't have Goron Tunic or Zora Tunic as child can't buy these
|
||||
|
||||
@@ -118,14 +118,14 @@ class OOTWorld(World):
|
||||
|
||||
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:
|
||||
if len(bytes(self.multiworld.get_player_name(self.player), 'ascii')) > 16:
|
||||
raise Exception(
|
||||
f"OoT: Player {self.player}'s name ({self.world.get_player_name(self.player)}) must be ASCII-compatible")
|
||||
f"OoT: Player {self.player}'s name ({self.multiworld.get_player_name(self.player)}) must be ASCII-compatible")
|
||||
|
||||
self.parser = Rule_AST_Transformer(self, self.player)
|
||||
|
||||
for (option_name, option) in oot_options.items():
|
||||
result = getattr(self.world, option_name)[self.player]
|
||||
result = getattr(self.multiworld, option_name)[self.player]
|
||||
if isinstance(result, Range):
|
||||
option_value = int(result)
|
||||
elif isinstance(result, Toggle):
|
||||
@@ -141,7 +141,7 @@ class OOTWorld(World):
|
||||
self.remove_from_start_inventory = [] # some items will be precollected but not in the inventory
|
||||
self.starting_items = Counter()
|
||||
self.starting_songs = False # whether starting_items contains a song
|
||||
self.file_hash = [self.world.random.randint(0, 31) for i in range(5)]
|
||||
self.file_hash = [self.multiworld.random.randint(0, 31) for i in range(5)]
|
||||
|
||||
self.item_name_groups = {
|
||||
"medallions": {"Light Medallion", "Forest Medallion", "Fire Medallion", "Water Medallion",
|
||||
@@ -185,13 +185,13 @@ class OOTWorld(World):
|
||||
# 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']
|
||||
chosen_trials = self.world.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
chosen_trials = self.multiworld.random.sample(trial_list, self.trials) # chooses a list of trials to NOT skip
|
||||
self.skipped_trials = {trial: (trial not in chosen_trials) for trial in trial_list}
|
||||
|
||||
# Determine which dungeons are MQ
|
||||
# Possible future plan: allow user to pick which dungeons are MQ
|
||||
if self.logic_rules == 'glitchless':
|
||||
mq_dungeons = self.world.random.sample(dungeon_table, self.mq_dungeons)
|
||||
mq_dungeons = self.multiworld.random.sample(dungeon_table, self.mq_dungeons)
|
||||
else:
|
||||
self.mq_dungeons = 0
|
||||
mq_dungeons = []
|
||||
@@ -208,8 +208,8 @@ class OOTWorld(World):
|
||||
|
||||
# No Logic forces all tricks on, prog balancing off and beatable-only
|
||||
elif self.logic_rules == 'no_logic':
|
||||
self.world.progression_balancing[self.player].value = False
|
||||
self.world.accessibility[self.player] = self.world.accessibility[self.player].from_text("minimal")
|
||||
self.multiworld.progression_balancing[self.player].value = False
|
||||
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
|
||||
for trick in normalized_name_tricks.values():
|
||||
setattr(self, trick['name'], True)
|
||||
|
||||
@@ -310,7 +310,7 @@ class OOTWorld(World):
|
||||
|
||||
for region in region_json:
|
||||
new_region = OOTRegion(region['region_name'], RegionType.Generic, None, self.player)
|
||||
new_region.world = self.world
|
||||
new_region.multiworld = self.multiworld
|
||||
if 'pretty_name' in region:
|
||||
new_region.pretty_name = region['pretty_name']
|
||||
if 'font_color' in region:
|
||||
@@ -355,19 +355,19 @@ class OOTWorld(World):
|
||||
new_location.show_in_spoiler = False
|
||||
if 'exits' in region:
|
||||
for exit, rule in region['exits'].items():
|
||||
new_exit = OOTEntrance(self.player, self.world, '%s -> %s' % (new_region.name, exit), new_region)
|
||||
new_exit = OOTEntrance(self.player, self.multiworld, '%s -> %s' % (new_region.name, exit), new_region)
|
||||
new_exit.vanilla_connected_region = exit
|
||||
new_exit.rule_string = rule
|
||||
if self.world.logic_rules != 'none':
|
||||
if self.multiworld.logic_rules != 'none':
|
||||
self.parser.parse_spot_rule(new_exit)
|
||||
if new_exit.never:
|
||||
logger.debug('Dropping unreachable exit: %s', new_exit.name)
|
||||
else:
|
||||
new_region.exits.append(new_exit)
|
||||
|
||||
self.world.regions.append(new_region)
|
||||
self.multiworld.regions.append(new_region)
|
||||
self.regions.append(new_region)
|
||||
self.world._recache()
|
||||
self.multiworld._recache()
|
||||
|
||||
def set_scrub_prices(self):
|
||||
# Get Deku Scrub Locations
|
||||
@@ -387,7 +387,7 @@ class OOTWorld(World):
|
||||
elif self.shuffle_scrubs == 'random':
|
||||
# this is a random value between 0-99
|
||||
# average value is ~33 rupees
|
||||
price = int(self.world.random.betavariate(1, 2) * 99)
|
||||
price = int(self.multiworld.random.betavariate(1, 2) * 99)
|
||||
|
||||
# Set price in the dictionary as well as the location.
|
||||
self.scrub_prices[scrub_item] = price
|
||||
@@ -402,14 +402,14 @@ class OOTWorld(World):
|
||||
self.shop_prices = {}
|
||||
for region in self.regions:
|
||||
if self.shopsanity == 'random':
|
||||
shop_item_count = self.world.random.randint(0, 4)
|
||||
shop_item_count = self.multiworld.random.randint(0, 4)
|
||||
else:
|
||||
shop_item_count = int(self.shopsanity)
|
||||
|
||||
for location in region.locations:
|
||||
if location.type == 'Shop':
|
||||
if location.name[-1:] in shop_item_indexes[:shop_item_count]:
|
||||
self.shop_prices[location.name] = int(self.world.random.betavariate(1.5, 2) * 60) * 5
|
||||
self.shop_prices[location.name] = int(self.multiworld.random.betavariate(1.5, 2) * 60) * 5
|
||||
|
||||
def fill_bosses(self, bossCount=9):
|
||||
boss_location_names = (
|
||||
@@ -424,7 +424,7 @@ class OOTWorld(World):
|
||||
'Links Pocket'
|
||||
)
|
||||
boss_rewards = [item for item in self.itempool if item.type == 'DungeonReward']
|
||||
boss_locations = [self.world.get_location(loc, self.player) for loc in boss_location_names]
|
||||
boss_locations = [self.multiworld.get_location(loc, self.player) for loc in boss_location_names]
|
||||
|
||||
placed_prizes = [loc.item.name for loc in boss_locations if loc.item is not None]
|
||||
prizepool = [item for item in boss_rewards if item.name not in placed_prizes]
|
||||
@@ -432,12 +432,12 @@ class OOTWorld(World):
|
||||
|
||||
while bossCount:
|
||||
bossCount -= 1
|
||||
self.world.random.shuffle(prizepool)
|
||||
self.world.random.shuffle(prize_locs)
|
||||
self.multiworld.random.shuffle(prizepool)
|
||||
self.multiworld.random.shuffle(prize_locs)
|
||||
item = prizepool.pop()
|
||||
loc = prize_locs.pop()
|
||||
loc.place_locked_item(item)
|
||||
self.world.itempool.remove(item)
|
||||
self.multiworld.itempool.remove(item)
|
||||
|
||||
def create_item(self, name: str):
|
||||
if name in item_table:
|
||||
@@ -449,7 +449,7 @@ class OOTWorld(World):
|
||||
def make_event_item(self, name, location, item=None):
|
||||
if item is None:
|
||||
item = self.create_item(name)
|
||||
self.world.push_item(location, item, collect=False)
|
||||
self.multiworld.push_item(location, item, collect=False)
|
||||
location.locked = True
|
||||
location.event = True
|
||||
if name not in item_table:
|
||||
@@ -463,11 +463,11 @@ class OOTWorld(World):
|
||||
world_type = 'Glitched World'
|
||||
overworld_data_path = data_path(world_type, 'Overworld.json')
|
||||
menu = OOTRegion('Menu', None, None, self.player)
|
||||
start = OOTEntrance(self.player, self.world, 'New Game', menu)
|
||||
start = OOTEntrance(self.player, self.multiworld, 'New Game', menu)
|
||||
menu.exits.append(start)
|
||||
self.world.regions.append(menu)
|
||||
self.multiworld.regions.append(menu)
|
||||
self.load_regions_from_json(overworld_data_path)
|
||||
start.connect(self.world.get_region('Root', self.player))
|
||||
start.connect(self.multiworld.get_region('Root', self.player))
|
||||
create_dungeons(self)
|
||||
self.parser.create_delayed_rules()
|
||||
|
||||
@@ -478,7 +478,7 @@ class OOTWorld(World):
|
||||
# Bind entrances to vanilla
|
||||
for region in self.regions:
|
||||
for exit in region.exits:
|
||||
exit.connect(self.world.get_region(exit.vanilla_connected_region, self.player))
|
||||
exit.connect(self.multiworld.get_region(exit.vanilla_connected_region, self.player))
|
||||
|
||||
def create_items(self):
|
||||
# Generate itempool
|
||||
@@ -491,7 +491,7 @@ class OOTWorld(World):
|
||||
junk_pool = get_junk_pool(self)
|
||||
removed_items = []
|
||||
# Determine starting items
|
||||
for item in self.world.precollected_items[self.player]:
|
||||
for item in self.multiworld.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)
|
||||
@@ -512,7 +512,7 @@ class OOTWorld(World):
|
||||
if self.start_with_rupees:
|
||||
self.starting_items['Rupees'] = 999
|
||||
|
||||
self.world.itempool += self.itempool
|
||||
self.multiworld.itempool += self.itempool
|
||||
self.remove_from_start_inventory.extend(removed_items)
|
||||
|
||||
def set_rules(self):
|
||||
@@ -533,7 +533,7 @@ class OOTWorld(World):
|
||||
for entrance in self.get_shuffled_entrances():
|
||||
if entrance.connected_region is not None:
|
||||
entrance.disconnect()
|
||||
entrance.connect(self.world.get_region(entrance.vanilla_connected_region, self.player))
|
||||
entrance.connect(self.multiworld.get_region(entrance.vanilla_connected_region, self.player))
|
||||
if entrance.assumed:
|
||||
assumed_entrance = entrance.assumed
|
||||
if assumed_entrance.connected_region is not None:
|
||||
@@ -570,27 +570,27 @@ class OOTWorld(World):
|
||||
|
||||
# Kill unreachable events that can't be gotten even with all items
|
||||
# Make sure to only kill actual internal events, not in-game "events"
|
||||
all_state = self.world.get_all_state(False)
|
||||
all_state = self.multiworld.get_all_state(False)
|
||||
all_locations = self.get_locations()
|
||||
reachable = self.world.get_reachable_locations(all_state, self.player)
|
||||
reachable = self.multiworld.get_reachable_locations(all_state, self.player)
|
||||
unreachable = [loc for loc in all_locations if
|
||||
(loc.internal or loc.type == 'Drop') and loc.event and loc.locked and loc not in reachable]
|
||||
for loc in unreachable:
|
||||
loc.parent_region.locations.remove(loc)
|
||||
# Exception: Sell Big Poe is an event which is only reachable if Bottle with Big Poe is in the item pool.
|
||||
# We allow it to be removed only if Bottle with Big Poe is not in the itempool.
|
||||
bigpoe = self.world.get_location('Sell Big Poe from Market Guard House', self.player)
|
||||
bigpoe = self.multiworld.get_location('Sell Big Poe from Market Guard House', self.player)
|
||||
if not all_state.has('Bottle with Big Poe', self.player) and bigpoe not in reachable:
|
||||
bigpoe.parent_region.locations.remove(bigpoe)
|
||||
self.world.clear_location_cache()
|
||||
self.multiworld.clear_location_cache()
|
||||
|
||||
# If fast scarecrow then we need to kill the Pierre location as it will be unreachable
|
||||
if self.free_scarecrow:
|
||||
loc = self.world.get_location("Pierre", self.player)
|
||||
loc = self.multiworld.get_location("Pierre", self.player)
|
||||
loc.parent_region.locations.remove(loc)
|
||||
# If open zora's domain then we need to kill Deliver Rutos Letter
|
||||
if self.zora_fountain == 'open':
|
||||
loc = self.world.get_location("Deliver Rutos Letter", self.player)
|
||||
loc = self.multiworld.get_location("Deliver Rutos Letter", self.player)
|
||||
loc.parent_region.locations.remove(loc)
|
||||
|
||||
def pre_fill(self):
|
||||
@@ -641,11 +641,11 @@ class OOTWorld(World):
|
||||
if loc.item is None and (
|
||||
self.shuffle_song_items != 'dungeon' or loc.name not in dungeon_song_locations)]
|
||||
if itempools['dungeon']: # only do this if there's anything to shuffle
|
||||
dungeon_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['dungeon']]
|
||||
dungeon_itempool = [item for item in self.multiworld.itempool if item.player == self.player and item.name in itempools['dungeon']]
|
||||
for item in dungeon_itempool:
|
||||
self.world.itempool.remove(item)
|
||||
self.world.random.shuffle(dungeon_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), dungeon_locations,
|
||||
self.multiworld.itempool.remove(item)
|
||||
self.multiworld.random.shuffle(dungeon_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), dungeon_locations,
|
||||
dungeon_itempool, True, True)
|
||||
any_dungeon_locations.extend(dungeon_locations) # adds only the unfilled locations
|
||||
|
||||
@@ -653,22 +653,22 @@ class OOTWorld(World):
|
||||
if self.shuffle_fortresskeys == 'any_dungeon':
|
||||
itempools['any_dungeon'].add('Small Key (Thieves Hideout)')
|
||||
if itempools['any_dungeon']:
|
||||
any_dungeon_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['any_dungeon']]
|
||||
any_dungeon_itempool = [item for item in self.multiworld.itempool if item.player == self.player and item.name in itempools['any_dungeon']]
|
||||
for item in any_dungeon_itempool:
|
||||
self.world.itempool.remove(item)
|
||||
self.multiworld.itempool.remove(item)
|
||||
any_dungeon_itempool.sort(key=lambda item:
|
||||
{'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
|
||||
self.world.random.shuffle(any_dungeon_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), any_dungeon_locations,
|
||||
self.multiworld.random.shuffle(any_dungeon_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), any_dungeon_locations,
|
||||
any_dungeon_itempool, True, True)
|
||||
|
||||
# If anything is overworld-only, fill into local non-dungeon locations
|
||||
if self.shuffle_fortresskeys == 'overworld':
|
||||
itempools['overworld'].add('Small Key (Thieves Hideout)')
|
||||
if itempools['overworld']:
|
||||
overworld_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['overworld']]
|
||||
overworld_itempool = [item for item in self.multiworld.itempool if item.player == self.player and item.name in itempools['overworld']]
|
||||
for item in overworld_itempool:
|
||||
self.world.itempool.remove(item)
|
||||
self.multiworld.itempool.remove(item)
|
||||
overworld_itempool.sort(key=lambda item:
|
||||
{'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
|
||||
non_dungeon_locations = [loc for loc in self.get_locations() if
|
||||
@@ -676,8 +676,8 @@ class OOTWorld(World):
|
||||
(loc.type != 'Shop' or loc.name in self.shop_prices) and
|
||||
(loc.type != 'Song' or self.shuffle_song_items != 'song') and
|
||||
(loc.name not in dungeon_song_locations or self.shuffle_song_items != 'dungeon')]
|
||||
self.world.random.shuffle(non_dungeon_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), non_dungeon_locations,
|
||||
self.multiworld.random.shuffle(non_dungeon_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), non_dungeon_locations,
|
||||
overworld_itempool, True, True)
|
||||
|
||||
# Place songs
|
||||
@@ -686,16 +686,16 @@ class OOTWorld(World):
|
||||
tries = 5
|
||||
if self.shuffle_song_items == 'song':
|
||||
song_locations = list(filter(lambda location: location.type == 'Song',
|
||||
self.world.get_unfilled_locations(player=self.player)))
|
||||
self.multiworld.get_unfilled_locations(player=self.player)))
|
||||
elif self.shuffle_song_items == 'dungeon':
|
||||
song_locations = list(filter(lambda location: location.name in dungeon_song_locations,
|
||||
self.world.get_unfilled_locations(player=self.player)))
|
||||
self.multiworld.get_unfilled_locations(player=self.player)))
|
||||
else:
|
||||
raise Exception(f"Unknown song shuffle type: {self.shuffle_song_items}")
|
||||
|
||||
songs = list(filter(lambda item: item.player == self.player and item.type == 'Song', self.world.itempool))
|
||||
songs = list(filter(lambda item: item.player == self.player and item.type == 'Song', self.multiworld.itempool))
|
||||
for song in songs:
|
||||
self.world.itempool.remove(song)
|
||||
self.multiworld.itempool.remove(song)
|
||||
|
||||
important_warps = (self.shuffle_special_interior_entrances or self.shuffle_overworld_entrances or
|
||||
self.warp_songs or self.spawn_positions)
|
||||
@@ -717,8 +717,8 @@ class OOTWorld(World):
|
||||
|
||||
while tries:
|
||||
try:
|
||||
self.world.random.shuffle(song_locations)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), song_locations[:], songs[:],
|
||||
self.multiworld.random.shuffle(song_locations)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), song_locations[:], songs[:],
|
||||
True, True)
|
||||
logger.debug(f"Successfully placed songs for player {self.player} after {6 - tries} attempt(s)")
|
||||
except FillError as e:
|
||||
@@ -741,33 +741,33 @@ class OOTWorld(World):
|
||||
# fast fill will fail because there is some logic on the shop items. we'll gather them up and place the shop items
|
||||
if self.shopsanity != 'off':
|
||||
shop_items = list(
|
||||
filter(lambda item: item.player == self.player and item.type == 'Shop', self.world.itempool))
|
||||
filter(lambda item: item.player == self.player and item.type == 'Shop', self.multiworld.itempool))
|
||||
shop_locations = list(
|
||||
filter(lambda location: location.type == 'Shop' and location.name not in self.shop_prices,
|
||||
self.world.get_unfilled_locations(player=self.player)))
|
||||
self.multiworld.get_unfilled_locations(player=self.player)))
|
||||
shop_items.sort(key=lambda item: {
|
||||
'Buy Deku Shield': 3 * int(self.open_forest == 'closed'),
|
||||
'Buy Goron Tunic': 2,
|
||||
'Buy Zora Tunic': 2
|
||||
}.get(item.name,
|
||||
int(item.advancement))) # place Deku Shields if needed, then tunics, then other advancement, then junk
|
||||
self.world.random.shuffle(shop_locations)
|
||||
self.multiworld.random.shuffle(shop_locations)
|
||||
for item in shop_items:
|
||||
self.world.itempool.remove(item)
|
||||
fill_restrictive(self.world, self.world.get_all_state(False), shop_locations, shop_items, True, True)
|
||||
self.multiworld.itempool.remove(item)
|
||||
fill_restrictive(self.multiworld, self.multiworld.get_all_state(False), shop_locations, shop_items, True, True)
|
||||
set_shop_rules(self) # sets wallet requirements on shop items, must be done after they are filled
|
||||
|
||||
# If skip child zelda is active and Song from Impa is unfilled, put a local giveable item into it.
|
||||
impa = self.world.get_location("Song from Impa", self.player)
|
||||
impa = self.multiworld.get_location("Song from Impa", self.player)
|
||||
if self.skip_child_zelda:
|
||||
if impa.item is None:
|
||||
item_to_place = self.world.random.choice(list(item for item in self.world.itempool if
|
||||
item.player == self.player and item.name in SaveContext.giveable_items))
|
||||
item_to_place = self.multiworld.random.choice(list(item for item in self.multiworld.itempool if
|
||||
item.player == self.player and item.name in SaveContext.giveable_items))
|
||||
impa.place_locked_item(item_to_place)
|
||||
self.world.itempool.remove(item_to_place)
|
||||
self.multiworld.itempool.remove(item_to_place)
|
||||
# Give items to startinventory
|
||||
self.world.push_precollected(impa.item)
|
||||
self.world.push_precollected(self.create_item("Zeldas Letter"))
|
||||
self.multiworld.push_precollected(impa.item)
|
||||
self.multiworld.push_precollected(self.create_item("Zeldas Letter"))
|
||||
|
||||
# Exclude locations in Ganon's Castle proportional to the number of items required to make the bridge
|
||||
# Check for dungeon ER later
|
||||
@@ -789,8 +789,8 @@ class OOTWorld(World):
|
||||
|
||||
gc = next(filter(lambda dungeon: dungeon.name == 'Ganons Castle', self.dungeons))
|
||||
locations = [loc.name for region in gc.regions for loc in region.locations if loc.item is None]
|
||||
junk_fill_locations = self.world.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
exclusion_rules(self.world, self.player, junk_fill_locations)
|
||||
junk_fill_locations = self.multiworld.random.sample(locations, round(len(locations) * ganon_junk_fill))
|
||||
exclusion_rules(self.multiworld, self.player, junk_fill_locations)
|
||||
|
||||
# Locations which are not sendable must be converted to events
|
||||
# This includes all locations for which show_in_spoiler is false, and shuffled shop items.
|
||||
@@ -813,12 +813,12 @@ class OOTWorld(World):
|
||||
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)
|
||||
self.trap_appearances[loc_id] = self.create_item(self.multiworld.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]
|
||||
self.hint_rng = self.multiworld.slot_seeds[self.player]
|
||||
|
||||
outfile_name = self.world.get_out_file_name_base(self.player)
|
||||
outfile_name = self.multiworld.get_out_file_name_base(self.player)
|
||||
rom = Rom(file=get_options()['oot_options']['rom_file'])
|
||||
if self.hints != 'none':
|
||||
buildWorldGossipHints(self)
|
||||
@@ -841,17 +841,17 @@ class OOTWorld(World):
|
||||
else:
|
||||
entrance = loadzone.reverse
|
||||
if entrance.reverse is not None:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player)
|
||||
self.multiworld.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player)
|
||||
else:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
else:
|
||||
reverse = loadzone.replaces.reverse
|
||||
if reverse in all_entrances:
|
||||
all_entrances.remove(reverse)
|
||||
self.world.spoiler.set_entrance(loadzone, reverse, 'both', self.player)
|
||||
self.multiworld.spoiler.set_entrance(loadzone, reverse, 'both', self.player)
|
||||
else:
|
||||
for entrance in all_entrances:
|
||||
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
self.multiworld.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
|
||||
|
||||
# Gathers hint data for OoT. Loops over all world locations for woth, barren, and major item locations.
|
||||
@classmethod
|
||||
@@ -979,9 +979,9 @@ class OOTWorld(World):
|
||||
|
||||
# Helper functions
|
||||
def get_shufflable_entrances(self, type=None, only_primary=False):
|
||||
return [entrance for entrance in self.world.get_entrances() if (entrance.player == self.player and
|
||||
(type == None or entrance.type == type) and
|
||||
(not only_primary or entrance.primary))]
|
||||
return [entrance for entrance in self.multiworld.get_entrances() if (entrance.player == self.player and
|
||||
(type == None or entrance.type == type) and
|
||||
(not only_primary or entrance.primary))]
|
||||
|
||||
def get_shuffled_entrances(self, type=None, only_primary=False):
|
||||
return [entrance for entrance in self.get_shufflable_entrances(type=type, only_primary=only_primary) if
|
||||
@@ -993,13 +993,13 @@ class OOTWorld(World):
|
||||
yield loc
|
||||
|
||||
def get_location(self, location):
|
||||
return self.world.get_location(location, self.player)
|
||||
return self.multiworld.get_location(location, self.player)
|
||||
|
||||
def get_region(self, region):
|
||||
return self.world.get_region(region, self.player)
|
||||
return self.multiworld.get_region(region, self.player)
|
||||
|
||||
def get_entrance(self, entrance):
|
||||
return self.world.get_entrance(entrance, self.player)
|
||||
return self.multiworld.get_entrance(entrance, self.player)
|
||||
|
||||
def is_major_item(self, item: OOTItem):
|
||||
if item.type == 'Token':
|
||||
@@ -1030,7 +1030,7 @@ class OOTWorld(World):
|
||||
# Specifically ensures that only real items are gotten, not any events.
|
||||
# In particular, ensures that Time Travel needs to be found.
|
||||
def get_state_with_complete_itempool(self):
|
||||
all_state = self.world.get_all_state(use_cache=False)
|
||||
all_state = self.multiworld.get_all_state(use_cache=False)
|
||||
# Remove event progression items
|
||||
for item, player in all_state.prog_items:
|
||||
if player == self.player and (item not in item_table or (item_table[item][2] is None and item_table[item][0] != 'DungeonReward')):
|
||||
|
||||
@@ -6,9 +6,9 @@ from BaseClasses import Location
|
||||
# TODO: implement Mapstone counting, Open, OpenWorld, connection rules
|
||||
|
||||
def set_rules(world):
|
||||
temp_base_rule(world.world, world.player)
|
||||
temp_base_rule(world.multiworld, world.player)
|
||||
for logicset in world.logic_sets:
|
||||
apply_or_ruleset(world.world, world.player, logicset)
|
||||
apply_or_ruleset(world.multiworld, world.player, logicset)
|
||||
|
||||
|
||||
def tautology(state):
|
||||
|
||||
@@ -24,17 +24,17 @@ class OriBlindForest(World):
|
||||
def generate_early(self):
|
||||
logic_sets = {"casual-core"}
|
||||
for logic_set in location_rules:
|
||||
if logic_set != "casual-core" and getattr(self.world, logic_set.replace("-", "_")):
|
||||
if logic_set != "casual-core" and getattr(self.multiworld, logic_set.replace("-", "_")):
|
||||
logic_sets.add(logic_set)
|
||||
self.logic_sets = logic_sets
|
||||
|
||||
set_rules = set_rules
|
||||
|
||||
def create_region(self, name: str):
|
||||
return Region(name, RegionType.Generic, name, self.player, self.world)
|
||||
return Region(name, RegionType.Generic, name, self.player, self.multiworld)
|
||||
|
||||
def create_regions(self):
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
menu = self.create_region("Menu")
|
||||
world.regions.append(menu)
|
||||
start = Entrance(self.player, "Start Game", menu)
|
||||
@@ -62,7 +62,7 @@ class OriBlindForest(World):
|
||||
|
||||
def generate_basic(self):
|
||||
for item_name, count in default_pool.items():
|
||||
self.world.itempool.extend([self.create_item(item_name) for _ in range(count)])
|
||||
self.multiworld.itempool.extend([self.create_item(item_name) for _ in range(count)])
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return Item(name,
|
||||
|
||||
@@ -159,7 +159,7 @@ class Overcooked2Level:
|
||||
sublevel: int
|
||||
|
||||
def __init__(self):
|
||||
self.world = Overcooked2GameWorld.ONE
|
||||
self.multiworld = Overcooked2GameWorld.ONE
|
||||
self.sublevel = 0
|
||||
|
||||
def __iter__(self):
|
||||
@@ -167,21 +167,21 @@ class Overcooked2Level:
|
||||
|
||||
def __next__(self):
|
||||
self.sublevel += 1
|
||||
if self.sublevel > self.world.sublevel_count:
|
||||
if self.world == Overcooked2GameWorld.KEVIN:
|
||||
if self.sublevel > self.multiworld.sublevel_count:
|
||||
if self.multiworld == Overcooked2GameWorld.KEVIN:
|
||||
raise StopIteration
|
||||
self.world = Overcooked2GameWorld(self.world.value + 1)
|
||||
self.multiworld = Overcooked2GameWorld(self.multiworld.value + 1)
|
||||
self.sublevel = 1
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def level_id(self) -> int:
|
||||
return self.world.base_id + (self.sublevel - 1)
|
||||
return self.multiworld.base_id + (self.sublevel - 1)
|
||||
|
||||
@property
|
||||
def level_name(self) -> str:
|
||||
return self.world.as_str + "-" + str(self.sublevel)
|
||||
return self.multiworld.as_str + "-" + str(self.sublevel)
|
||||
|
||||
@property
|
||||
def location_name_item(self) -> str:
|
||||
|
||||
@@ -79,7 +79,7 @@ class Overcooked2World(World):
|
||||
|
||||
def place_event(self, location_name: str, item_name: str,
|
||||
classification: ItemClassification = ItemClassification.progression_skip_balancing):
|
||||
location: Location = self.world.get_location(location_name, self.player)
|
||||
location: Location = self.multiworld.get_location(location_name, self.player)
|
||||
location.place_locked_item(self.create_event(item_name, classification))
|
||||
|
||||
def add_region(self, region_name: str):
|
||||
@@ -88,13 +88,13 @@ class Overcooked2World(World):
|
||||
RegionType.Generic,
|
||||
region_name,
|
||||
self.player,
|
||||
self.world,
|
||||
self.multiworld,
|
||||
)
|
||||
self.world.regions.append(region)
|
||||
self.multiworld.regions.append(region)
|
||||
|
||||
def connect_regions(self, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None):
|
||||
sourceRegion = self.world.get_region(source, self.player)
|
||||
targetRegion = self.world.get_region(target, self.player)
|
||||
sourceRegion = self.multiworld.get_region(source, self.player)
|
||||
targetRegion = self.multiworld.get_region(target, self.player)
|
||||
|
||||
connection = Entrance(self.player, '', sourceRegion)
|
||||
if rule:
|
||||
@@ -117,7 +117,7 @@ class Overcooked2World(World):
|
||||
else:
|
||||
location_id = level_id
|
||||
|
||||
region = self.world.get_region(region_name, self.player)
|
||||
region = self.multiworld.get_region(region_name, self.player)
|
||||
location = Overcooked2Location(
|
||||
self.player,
|
||||
location_name,
|
||||
@@ -145,8 +145,8 @@ class Overcooked2World(World):
|
||||
)
|
||||
|
||||
def get_options(self) -> Dict[str, Any]:
|
||||
return OC2Options({option.__name__: getattr(self.world, name)[self.player].result
|
||||
if issubclass(option, OC2OnToggle) else getattr(self.world, name)[self.player].value
|
||||
return OC2Options({option.__name__: getattr(self.multiworld, name)[self.player].result
|
||||
if issubclass(option, OC2OnToggle) else getattr(self.multiworld, name)[self.player].value
|
||||
for name, option in overcooked_options.items()})
|
||||
|
||||
# Helper Data
|
||||
@@ -170,7 +170,7 @@ class Overcooked2World(World):
|
||||
if self.options["ShuffleLevelOrder"]:
|
||||
self.level_mapping = \
|
||||
level_shuffle_factory(
|
||||
self.world.random,
|
||||
self.multiworld.random,
|
||||
self.options["PrepLevels"] != PrepLevelMode.excluded.value,
|
||||
self.options["IncludeHordeLevels"],
|
||||
)
|
||||
@@ -246,7 +246,7 @@ class Overcooked2World(World):
|
||||
|
||||
completion_condition: Callable[[CollectionState], bool] = lambda state: \
|
||||
state.has("Victory", self.player)
|
||||
self.world.completion_condition[self.player] = completion_condition
|
||||
self.multiworld.completion_condition[self.player] = completion_condition
|
||||
|
||||
def create_items(self):
|
||||
self.itempool = []
|
||||
@@ -298,7 +298,7 @@ class Overcooked2World(World):
|
||||
while len(self.itempool) < pool_count:
|
||||
self.itempool.append(self.create_item("Bonus Star", ItemClassification.useful))
|
||||
|
||||
self.world.itempool += self.itempool
|
||||
self.multiworld.itempool += self.itempool
|
||||
|
||||
def set_rules(self):
|
||||
pass
|
||||
@@ -324,7 +324,7 @@ class Overcooked2World(World):
|
||||
# Items get distributed to locations
|
||||
|
||||
def fill_json_data(self) -> Dict[str, Any]:
|
||||
mod_name = f"AP-{self.world.seed_name}-P{self.player}-{self.world.player_name[self.player]}"
|
||||
mod_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}"
|
||||
|
||||
# Serialize Level Order
|
||||
story_level_order = dict()
|
||||
@@ -363,7 +363,7 @@ class Overcooked2World(World):
|
||||
# Set Kevin Unlock Requirements
|
||||
if self.options["KevinLevels"]:
|
||||
def kevin_level_to_keyholder_level_id(level_id: int) -> Optional[int]:
|
||||
location = self.world.find_item(f"Kevin-{level_id-36}", self.player)
|
||||
location = self.multiworld.find_item(f"Kevin-{level_id-36}", self.player)
|
||||
if location.player != self.player:
|
||||
return None # This kevin level will be unlocked by the server at runtime
|
||||
level_id = oc2_location_name_to_id[location.name]
|
||||
@@ -376,7 +376,7 @@ class Overcooked2World(World):
|
||||
|
||||
# Place Items at Level Completion Screens (local only)
|
||||
on_level_completed: Dict[str, list[Dict[str, str]]] = dict()
|
||||
regions = self.world.get_regions(self.player)
|
||||
regions = self.multiworld.get_regions(self.player)
|
||||
for region in regions:
|
||||
for location in region.locations:
|
||||
if location.item is None:
|
||||
|
||||
@@ -73,22 +73,25 @@ class PokemonRedBlueWorld(World):
|
||||
def encode_name(name, t):
|
||||
try:
|
||||
if len(encode_text(name)) > 7:
|
||||
raise IndexError(f"{t} name too long for player {self.world.player_name[self.player]}. Must be 7 characters or fewer.")
|
||||
raise IndexError(f"{t} name too long for player {self.multiworld.player_name[self.player]}. Must be 7 characters or fewer.")
|
||||
return encode_text(name, length=8, whitespace="@", safety=True)
|
||||
except KeyError as e:
|
||||
raise KeyError(f"Invalid character(s) in {t} name for player {self.world.player_name[self.player]}") from e
|
||||
self.trainer_name = encode_name(self.world.trainer_name[self.player].value, "Player")
|
||||
self.rival_name = encode_name(self.world.rival_name[self.player].value, "Rival")
|
||||
raise KeyError(f"Invalid character(s) in {t} name for player {self.multiworld.player_name[self.player]}") from e
|
||||
self.trainer_name = encode_name(self.multiworld.trainer_name[self.player].value, "Player")
|
||||
self.rival_name = encode_name(self.multiworld.rival_name[self.player].value, "Rival")
|
||||
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value >= 2:
|
||||
if len(self.multiworld.player_name[self.player].encode()) > 16:
|
||||
raise Exception(f"Player name too long for {self.multiworld.get_player_name(self.player)}. Player name cannot exceed 16 bytes for Pokémon Red and Blue.")
|
||||
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value >= 2:
|
||||
badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value == 3:
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 3:
|
||||
badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
|
||||
"Soul Badge", "Volcano Badge", "Earth Badge"]
|
||||
self.world.random.shuffle(badges)
|
||||
self.multiworld.random.shuffle(badges)
|
||||
badges_to_add += [badges.pop(), badges.pop()]
|
||||
hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
|
||||
self.world.random.shuffle(hm_moves)
|
||||
self.multiworld.random.shuffle(hm_moves)
|
||||
self.extra_badges = {}
|
||||
for badge in badges_to_add:
|
||||
self.extra_badges[hm_moves.pop()] = badge
|
||||
@@ -96,42 +99,47 @@ class PokemonRedBlueWorld(World):
|
||||
process_pokemon_data(self)
|
||||
|
||||
def create_items(self) -> None:
|
||||
start_inventory = self.multiworld.start_inventory[self.player].value.copy()
|
||||
locations = [location for location in location_data if location.type == "Item"]
|
||||
item_pool = []
|
||||
for location in locations:
|
||||
if "Hidden" in location.name and not self.world.randomize_hidden_items[self.player].value:
|
||||
if "Hidden" in location.name and not self.multiworld.randomize_hidden_items[self.player].value:
|
||||
continue
|
||||
if "Rock Tunnel B1F" in location.region and not self.world.extra_key_items[self.player].value:
|
||||
if "Rock Tunnel B1F" in location.region and not self.multiworld.extra_key_items[self.player].value:
|
||||
continue
|
||||
if location.name == "Celadon City - Mansion Lady" and not self.world.tea[self.player].value:
|
||||
if location.name == "Celadon City - Mansion Lady" and not self.multiworld.tea[self.player].value:
|
||||
continue
|
||||
item = self.create_item(location.original_item)
|
||||
if location.original_item in self.multiworld.start_inventory[self.player].value and \
|
||||
location.original_item in item_groups["Unique"]:
|
||||
start_inventory[location.original_item] -= 1
|
||||
item = self.create_filler()
|
||||
else:
|
||||
item = self.create_item(location.original_item)
|
||||
if location.event:
|
||||
self.world.get_location(location.name, self.player).place_locked_item(item)
|
||||
elif ("Badge" not in item.name or self.world.badgesanity[self.player].value) and \
|
||||
(item.name != "Oak's Parcel" or self.world.old_man[self.player].value != 1):
|
||||
self.multiworld.get_location(location.name, self.player).place_locked_item(item)
|
||||
elif ("Badge" not in item.name or self.multiworld.badgesanity[self.player].value) and \
|
||||
(item.name != "Oak's Parcel" or self.multiworld.old_man[self.player].value != 1):
|
||||
item_pool.append(item)
|
||||
self.world.random.shuffle(item_pool)
|
||||
self.multiworld.random.shuffle(item_pool)
|
||||
|
||||
self.world.itempool += item_pool
|
||||
self.multiworld.itempool += item_pool
|
||||
|
||||
|
||||
def pre_fill(self):
|
||||
def generate_basic(self) -> None:
|
||||
|
||||
process_wild_pokemon(self)
|
||||
process_static_pokemon(self)
|
||||
|
||||
if self.world.old_man[self.player].value == 1:
|
||||
if self.multiworld.old_man[self.player].value == 1:
|
||||
item = self.create_item("Oak's Parcel")
|
||||
locations = []
|
||||
for location in self.world.get_locations():
|
||||
if location.player == self.player and location.item is None and location.can_reach(self.world.state) \
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.player == self.player and location.item is None and location.can_reach(self.multiworld.state) \
|
||||
and location.item_rule(item):
|
||||
locations.append(location)
|
||||
self.world.random.choice(locations).place_locked_item(item)
|
||||
self.multiworld.random.choice(locations).place_locked_item(item)
|
||||
|
||||
if not self.world.badgesanity[self.player].value:
|
||||
self.world.non_local_items[self.player].value -= self.item_name_groups["Badges"]
|
||||
if not self.multiworld.badgesanity[self.player].value:
|
||||
self.multiworld.non_local_items[self.player].value -= self.item_name_groups["Badges"]
|
||||
for i in range(5):
|
||||
try:
|
||||
badges = []
|
||||
@@ -142,11 +150,11 @@ class PokemonRedBlueWorld(World):
|
||||
for loc in ["Pewter Gym - Brock 1", "Cerulean Gym - Misty 1", "Vermilion Gym - Lt. Surge 1",
|
||||
"Celadon Gym - Erika 1", "Fuchsia Gym - Koga 1", "Saffron Gym - Sabrina 1",
|
||||
"Cinnabar Gym - Blaine 1", "Viridian Gym - Giovanni 1"]:
|
||||
badgelocs.append(self.world.get_location(loc, self.player))
|
||||
state = self.world.get_all_state(False)
|
||||
self.world.random.shuffle(badges)
|
||||
self.world.random.shuffle(badgelocs)
|
||||
fill_restrictive(self.world, state, badgelocs.copy(), badges, True, True)
|
||||
badgelocs.append(self.multiworld.get_location(loc, self.player))
|
||||
state = self.multiworld.get_all_state(False)
|
||||
self.multiworld.random.shuffle(badges)
|
||||
self.multiworld.random.shuffle(badgelocs)
|
||||
fill_restrictive(self.multiworld, state, badgelocs.copy(), badges, True, True)
|
||||
except FillError:
|
||||
for location in badgelocs:
|
||||
location.item = None
|
||||
@@ -155,36 +163,36 @@ class PokemonRedBlueWorld(World):
|
||||
else:
|
||||
raise FillError(f"Failed to place badges for player {self.player}")
|
||||
|
||||
locs = [self.world.get_location("Fossil - Choice A", self.player),
|
||||
self.world.get_location("Fossil - Choice B", self.player)]
|
||||
locs = [self.multiworld.get_location("Fossil - Choice A", self.player),
|
||||
self.multiworld.get_location("Fossil - Choice B", self.player)]
|
||||
for loc in locs:
|
||||
add_item_rule(loc, lambda i: i.advancement or i.name in self.item_name_groups["Unique"]
|
||||
or i.name == "Master Ball")
|
||||
|
||||
loc = self.world.get_location("Pallet Town - Player's PC", self.player)
|
||||
loc = self.multiworld.get_location("Pallet Town - Player's PC", self.player)
|
||||
if loc.item is None:
|
||||
locs.append(loc)
|
||||
|
||||
for loc in locs:
|
||||
unplaced_items = []
|
||||
if loc.name in self.world.priority_locations[self.player].value:
|
||||
if loc.name in self.multiworld.priority_locations[self.player].value:
|
||||
add_item_rule(loc, lambda i: i.advancement)
|
||||
for item in self.world.itempool:
|
||||
for item in reversed(self.multiworld.itempool):
|
||||
if item.player == self.player and loc.item_rule(item):
|
||||
self.world.itempool.remove(item)
|
||||
state = sweep_from_pool(self.world.state, self.world.itempool + unplaced_items)
|
||||
self.multiworld.itempool.remove(item)
|
||||
state = sweep_from_pool(self.multiworld.state, self.multiworld.itempool + unplaced_items)
|
||||
if state.can_reach(loc, "Location", self.player):
|
||||
loc.place_locked_item(item)
|
||||
break
|
||||
else:
|
||||
unplaced_items.append(item)
|
||||
self.world.itempool += unplaced_items
|
||||
self.multiworld.itempool += unplaced_items
|
||||
|
||||
intervene = False
|
||||
test_state = self.world.get_all_state(False)
|
||||
test_state = self.multiworld.get_all_state(False)
|
||||
if not test_state.pokemon_rb_can_surf(self.player) or not test_state.pokemon_rb_can_strength(self.player):
|
||||
intervene = True
|
||||
elif self.world.accessibility[self.player].current_key != "minimal":
|
||||
elif self.multiworld.accessibility[self.player].current_key != "minimal":
|
||||
if not test_state.pokemon_rb_can_cut(self.player) or not test_state.pokemon_rb_can_flash(self.player):
|
||||
intervene = True
|
||||
if intervene:
|
||||
@@ -192,27 +200,30 @@ class PokemonRedBlueWorld(World):
|
||||
# let you choose the exact weights for HM compatibility
|
||||
logging.warning(
|
||||
f"HM-compatible Pokémon possibly missing, placing Mew on Route 1 for player {self.player}")
|
||||
loc = self.world.get_location("Route 1 - Wild Pokemon - 1", self.player)
|
||||
loc = self.multiworld.get_location("Route 1 - Wild Pokemon - 1", self.player)
|
||||
loc.item = self.create_item("Mew")
|
||||
|
||||
def create_regions(self):
|
||||
if self.world.free_fly_location[self.player].value:
|
||||
fly_map_code = self.world.random.randint(5, 9)
|
||||
if self.multiworld.free_fly_location[self.player].value:
|
||||
if self.multiworld.old_man[self.player].value == 0:
|
||||
fly_map_code = self.multiworld.random.randint(1, 9)
|
||||
else:
|
||||
fly_map_code = self.multiworld.random.randint(5, 9)
|
||||
if fly_map_code == 5:
|
||||
fly_map_code = 4
|
||||
if fly_map_code == 9:
|
||||
fly_map_code = 10
|
||||
if fly_map_code == 5:
|
||||
fly_map_code = 4
|
||||
else:
|
||||
fly_map_code = 0
|
||||
self.fly_map = ["Pallet Town", "Viridian City", "Pewter City", "Cerulean City", "Lavender Town",
|
||||
"Vermilion City", "Celadon City", "Fuchsia City", "Cinnabar Island", "Indigo Plateau",
|
||||
"Saffron City"][fly_map_code]
|
||||
self.fly_map_code = fly_map_code
|
||||
create_regions(self.world, self.player)
|
||||
self.world.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
self.multiworld.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return PokemonRBItem(name, self.player)
|
||||
@@ -221,22 +232,44 @@ class PokemonRedBlueWorld(World):
|
||||
generate_output(self, output_directory)
|
||||
|
||||
def write_spoiler_header(self, spoiler_handle: TextIO):
|
||||
if self.world.free_fly_location[self.player].value:
|
||||
if self.multiworld.free_fly_location[self.player].value:
|
||||
spoiler_handle.write('Fly unlocks: %s\n' % self.fly_map)
|
||||
if self.extra_badges:
|
||||
for hm_move, badge in self.extra_badges.items():
|
||||
spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n")
|
||||
|
||||
def write_spoiler(self, spoiler_handle):
|
||||
if self.world.randomize_type_matchup_types[self.player].value or \
|
||||
self.world.randomize_type_matchup_type_effectiveness[self.player].value:
|
||||
spoiler_handle.write(f"\n\nType matchups ({self.world.player_name[self.player]}):\n\n")
|
||||
if self.multiworld.randomize_type_matchup_types[self.player].value or \
|
||||
self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value:
|
||||
spoiler_handle.write(f"\n\nType matchups ({self.multiworld.player_name[self.player]}):\n\n")
|
||||
for matchup in self.type_chart:
|
||||
spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n")
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice([item for item in item_table if item_table[item].classification in
|
||||
[ItemClassification.filler, ItemClassification.trap]])
|
||||
return self.multiworld.random.choice([item for item in item_table if item_table[item].classification in
|
||||
[ItemClassification.filler, ItemClassification.trap] and item not in
|
||||
item_groups["Vending Machine Drinks"]])
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
# for trackers
|
||||
return {
|
||||
"second_fossil_check_condition": self.multiworld.second_fossil_check_condition[self.player].value,
|
||||
"require_item_finder": self.multiworld.require_item_finder[self.player].value,
|
||||
"randomize_hidden_items": self.multiworld.randomize_hidden_items[self.player].value,
|
||||
"badges_needed_for_hm_moves": self.multiworld.badges_needed_for_hm_moves[self.player].value,
|
||||
"oaks_aide_rt_2": self.multiworld.oaks_aide_rt_2[self.player].value,
|
||||
"oaks_aide_rt_11": self.multiworld.oaks_aide_rt_11[self.player].value,
|
||||
"oaks_aide_rt_15": self.multiworld.oaks_aide_rt_15[self.player].value,
|
||||
"extra_key_items": self.multiworld.extra_key_items[self.player].value,
|
||||
"extra_strength_boulders": self.multiworld.extra_strength_boulders[self.player].value,
|
||||
"tea": self.multiworld.tea[self.player].value,
|
||||
"old_man": self.multiworld.old_man[self.player].value,
|
||||
"elite_four_condition": self.multiworld.elite_four_condition[self.player].value,
|
||||
"victory_road_condition": self.multiworld.victory_road_condition[self.player].value,
|
||||
"viridian_gym_condition": self.multiworld.viridian_gym_condition[self.player].value,
|
||||
"free_fly_map": self.fly_map_code,
|
||||
"extra_badges": self.extra_badges
|
||||
}
|
||||
|
||||
|
||||
class PokemonRBItem(Item):
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -14,6 +14,11 @@ As we are using Bizhawk, this guide is only applicable to Windows and Linux syst
|
||||
(select `Pokemon Client` during installation).
|
||||
- Pokémon Red and/or Blue ROM files. The Archipelago community cannot provide these.
|
||||
|
||||
## Optional Software
|
||||
|
||||
- [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/j-imbo/pkmnrb_jim/releases/latest), for use with [PopTracker](https://github.com/black-sliver/PopTracker/releases)
|
||||
|
||||
|
||||
## Configuring Bizhawk
|
||||
|
||||
Once Bizhawk has been installed, open Bizhawk and change the following settings:
|
||||
@@ -82,3 +87,14 @@ To connect the client to the multiserver simply put `<address>:<port>` on the te
|
||||
server uses password, type in the bottom textfield `/connect <address>:<port> [password]`)
|
||||
|
||||
Now you are ready to start your adventure in Kanto.
|
||||
|
||||
## Auto-Tracking
|
||||
|
||||
Pokémon Red and Blue has a fully functional map tracker that supports auto-tracking.
|
||||
|
||||
1. Download [Pokémon Red and Blue Archipelago Map Tracker](https://github.com/j-imbo/pkmnrb_jim/releases/latest) and [PopTracker](https://github.com/black-sliver/PopTracker/releases).
|
||||
2. Open PopTracker, and load the Pokémon Red and Blue pack.
|
||||
3. Click on the "AP" symbol at the top.
|
||||
4. Enter the AP address, slot name and password.
|
||||
|
||||
The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It will hide checks & adjust logic accordingly.
|
||||
|
||||
@@ -66,9 +66,9 @@ item_table = {
|
||||
"Max Repel": ItemData(57, ItemClassification.filler, ["Consumables"]),
|
||||
"Dire Hit": ItemData(58, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
#"Coin": ItemData(59, ItemClassification.filler),
|
||||
"Fresh Water": ItemData(60, ItemClassification.filler, ["Consumables"]),
|
||||
"Soda Pop": ItemData(61, ItemClassification.filler, ["Consumables"]),
|
||||
"Lemonade": ItemData(62, ItemClassification.filler, ["Consumables"]),
|
||||
"Fresh Water": ItemData(60, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
|
||||
"Soda Pop": ItemData(61, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
|
||||
"Lemonade": ItemData(62, ItemClassification.filler, ["Consumables", "Vending Machine Drinks"]),
|
||||
"S.S. Ticket": ItemData(63, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"Gold Teeth": ItemData(64, ItemClassification.progression, ["Unique", "Key Items"]),
|
||||
"X Attack": ItemData(65, ItemClassification.filler, ["Consumables", "Battle Items"]),
|
||||
|
||||
@@ -321,21 +321,21 @@ location_data = [
|
||||
LocationData("Viridian Forest", "Hidden Item Entrance Tree", "Antidote", rom_addresses['Hidden_Item_Viridian_Forest_2'], Hidden(1)),
|
||||
LocationData("Mt Moon B2F", "Hidden Item Dead End Before Fossils", "Moon Stone", rom_addresses['Hidden_Item_MtMoonB2F_1'], Hidden(2)),
|
||||
LocationData("Route 25", "Hidden Item Fence Outside Bill's House", "Ether", rom_addresses['Hidden_Item_Route_25_1'], Hidden(3)),
|
||||
LocationData("Route 9", "Hidden Item Rock By Grass", "Ether", rom_addresses['Hidden_Item_Route_9'], Hidden(4)),
|
||||
LocationData("Route 9", "Hidden Item Bush By Grass", "Ether", rom_addresses['Hidden_Item_Route_9'], Hidden(4)),
|
||||
LocationData("S.S. Anne 1F", "Hidden Item Kitchen Trash", "Great Ball", rom_addresses['Hidden_Item_SS_Anne_Kitchen'], Hidden(5)),
|
||||
LocationData("S.S. Anne B1F", "Hidden Item Under Pillow", "Hyper Potion", rom_addresses['Hidden_Item_SS_Anne_B1F'], Hidden(6)),
|
||||
LocationData("Route 10 North", "Hidden Item Behind Rock Tunnel Entrance Tree", "Super Potion", rom_addresses['Hidden_Item_Route_10_1'], Hidden(7)),
|
||||
LocationData("Route 10 South", "Hidden Item Rock", "Max Ether", rom_addresses['Hidden_Item_Route_10_2'], Hidden(8)),
|
||||
LocationData("Route 10 North", "Hidden Item Behind Rock Tunnel Entrance Cuttable Tree", "Super Potion", rom_addresses['Hidden_Item_Route_10_1'], Hidden(7)),
|
||||
LocationData("Route 10 South", "Hidden Item Bush", "Max Ether", rom_addresses['Hidden_Item_Route_10_2'], Hidden(8)),
|
||||
LocationData("Rocket Hideout B1F", "Hidden Item Pot Plant", "PP Up", rom_addresses['Hidden_Item_Rocket_Hideout_B1F'], Hidden(9)),
|
||||
LocationData("Rocket Hideout B3F", "Hidden Item Near East Item", "Nugget", rom_addresses['Hidden_Item_Rocket_Hideout_B3F'], Hidden(10)),
|
||||
LocationData("Rocket Hideout B4F", "Hidden Item Behind Giovanni", "Super Potion", rom_addresses['Hidden_Item_Rocket_Hideout_B4F'], Hidden(11)),
|
||||
LocationData("Pokemon Tower 5F", "Hidden Item Near West Staircase", "Elixir", rom_addresses['Hidden_Item_Pokemon_Tower_5F'], Hidden(12)),
|
||||
LocationData("Route 13", "Hidden Item Dead End Boulder", "PP Up", rom_addresses['Hidden_Item_Route_13_1'], Hidden(13)),
|
||||
LocationData("Route 13", "Hidden Item Dead End Bush", "PP Up", rom_addresses['Hidden_Item_Route_13_1'], Hidden(13)),
|
||||
LocationData("Route 13", "Hidden Item Dead End By Water Corner", "Calcium", rom_addresses['Hidden_Item_Route_13_2'], Hidden(14)),
|
||||
LocationData("Pokemon Mansion B1F", "Hidden Item Secret Key Room Corner", "Rare Candy", rom_addresses['Hidden_Item_Pokemon_Mansion_B1F'], Hidden(15)),
|
||||
LocationData("Safari Zone West", "Hidden Item Secret House Statue", "Revive", rom_addresses['Hidden_Item_Safari_Zone_West'], Hidden(17)),
|
||||
LocationData("Silph Co 5F", "Hidden Item Pot Plant", "Elixir", rom_addresses['Hidden_Item_Silph_Co_5F'], Hidden(18)),
|
||||
LocationData("Silph Co 9F", "Hidden Item Nurse Bed", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19)),
|
||||
LocationData("Silph Co 9F", "Hidden Item Nurse Bed (Card Key)", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19)),
|
||||
LocationData("Copycat's House", "Hidden Item Desk", "Nugget", rom_addresses['Hidden_Item_Copycats_House'], Hidden(20)),
|
||||
LocationData("Cerulean Cave 1F", "Hidden Item Center Rocks", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_Cave_1F'], Hidden(21)),
|
||||
LocationData("Cerulean Cave B1F", "Hidden Item Northeast Rocks", "Ultra Ball", rom_addresses['Hidden_Item_Cerulean_Cave_B1F'], Hidden(22)),
|
||||
@@ -345,15 +345,15 @@ location_data = [
|
||||
LocationData("Seafoam Islands B4F", "Hidden Item Corner Island", "Ultra Ball", rom_addresses['Hidden_Item_Seafoam_Islands_B4F'], Hidden(26)),
|
||||
LocationData("Pokemon Mansion 1F", "Hidden Item Block Near Entrance Carpet", "Moon Stone", rom_addresses['Hidden_Item_Pokemon_Mansion_1F'], Hidden(27)),
|
||||
LocationData("Pokemon Mansion 3F", "Hidden Item Behind Burglar", "Max Revive", rom_addresses['Hidden_Item_Pokemon_Mansion_3F'], Hidden(28)),
|
||||
LocationData("Route 23 North", "Hidden Item Rocks Before Final Guard", "Full Restore", rom_addresses['Hidden_Item_Route_23_1'], Hidden(29)),
|
||||
LocationData("Route 23 North", "Hidden Item East Tree After Water", "Ultra Ball", rom_addresses['Hidden_Item_Route_23_2'], Hidden(30)),
|
||||
LocationData("Route 23 South", "Hidden Item On Island", "Max Ether", rom_addresses['Hidden_Item_Route_23_3'], Hidden(31)),
|
||||
LocationData("Route 23", "Hidden Item Rocks Before Final Guard", "Full Restore", rom_addresses['Hidden_Item_Route_23_1'], Hidden(29)),
|
||||
LocationData("Route 23", "Hidden Item East Bush After Water", "Ultra Ball", rom_addresses['Hidden_Item_Route_23_2'], Hidden(30)),
|
||||
LocationData("Route 23", "Hidden Item On Island", "Max Ether", rom_addresses['Hidden_Item_Route_23_3'], Hidden(31)),
|
||||
LocationData("Victory Road 2F", "Hidden Item Rock Before Moltres", "Ultra Ball", rom_addresses['Hidden_Item_Victory_Road_2F_1'], Hidden(32)),
|
||||
LocationData("Victory Road 2F", "Hidden Item Rock In Final Room", "Full Restore", rom_addresses['Hidden_Item_Victory_Road_2F_2'], Hidden(33)),
|
||||
#LocationData("Vermilion City", "Hidden Item The Truck", "Max Elixir", rom_addresses['Hidden_Item_Unused_6F'], Hidden(34)),
|
||||
|
||||
LocationData("Viridian City", "Hidden Item Cuttable Tree", "Potion", rom_addresses['Hidden_Item_Viridian_City'], Hidden(35)),
|
||||
LocationData("Route 11", "Hidden Item Isolated Tree Near Gate", "Potion", rom_addresses['Hidden_Item_Route_11'], Hidden(36)),
|
||||
LocationData("Route 12 West", "Hidden Item Tree Near Gate", "Hyper Potion", rom_addresses['Hidden_Item_Route_12'], Hidden(37)),
|
||||
LocationData("Route 11", "Hidden Item Isolated Bush Near Gate", "Potion", rom_addresses['Hidden_Item_Route_11'], Hidden(36)),
|
||||
LocationData("Route 12 West", "Hidden Item Bush Near Gate", "Hyper Potion", rom_addresses['Hidden_Item_Route_12'], Hidden(37)),
|
||||
LocationData("Route 17", "Hidden Item In Grass", "Rare Candy", rom_addresses['Hidden_Item_Route_17_1'], Hidden(38)),
|
||||
LocationData("Route 17", "Hidden Item Near Northernmost Sign", "Full Restore", rom_addresses['Hidden_Item_Route_17_2'], Hidden(39)),
|
||||
LocationData("Route 17", "Hidden Item East Center", "PP Up", rom_addresses['Hidden_Item_Route_17_3'], Hidden(40)),
|
||||
@@ -421,13 +421,13 @@ location_data = [
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 19", "Super Rod Pokemon - 4", "Goldeen", rom_addresses["Wild_Super_Rod_H"] + 7,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 1", "Slowbro", rom_addresses["Wild_Super_Rod_I"] + 1,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 1", "Slowbro", rom_addresses["Wild_Super_Rod_I"] + 1,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 2", "Seaking", rom_addresses["Wild_Super_Rod_I"] + 3,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 2", "Seaking", rom_addresses["Wild_Super_Rod_I"] + 3,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 3", "Kingler", rom_addresses["Wild_Super_Rod_I"] + 5,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 3", "Kingler", rom_addresses["Wild_Super_Rod_I"] + 5,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 South", "Super Rod Pokemon - 4", "Seadra", rom_addresses["Wild_Super_Rod_I"] + 7,
|
||||
LocationData("Route 23", "Super Rod Pokemon - 4", "Seadra", rom_addresses["Wild_Super_Rod_I"] + 7,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Fuchsia City", "Super Rod Pokemon - 1", "Seaking", rom_addresses["Wild_Super_Rod_J"] + 1,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
@@ -1480,25 +1480,25 @@ location_data = [
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Power Plant", "Wild Pokemon - 10", ["Electabuzz", "Raichu"],
|
||||
rom_addresses["Wild_PowerPlant"] + 19, None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 1", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route23"] + 1,
|
||||
LocationData("Route 23", "Wild Pokemon - 1", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route23"] + 1,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 2", "Ditto", rom_addresses["Wild_Route23"] + 3, None, event=True,
|
||||
LocationData("Route 23", "Wild Pokemon - 2", "Ditto", rom_addresses["Wild_Route23"] + 3, None, event=True,
|
||||
type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 3", "Spearow", rom_addresses["Wild_Route23"] + 5, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 3", "Spearow", rom_addresses["Wild_Route23"] + 5, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 4", "Fearow", rom_addresses["Wild_Route23"] + 7, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 4", "Fearow", rom_addresses["Wild_Route23"] + 7, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 5", "Ditto", rom_addresses["Wild_Route23"] + 9, None, event=True,
|
||||
LocationData("Route 23", "Wild Pokemon - 5", "Ditto", rom_addresses["Wild_Route23"] + 9, None, event=True,
|
||||
type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 6", "Fearow", rom_addresses["Wild_Route23"] + 11, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 6", "Fearow", rom_addresses["Wild_Route23"] + 11, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 7", ["Arbok", "Sandslash"], rom_addresses["Wild_Route23"] + 13,
|
||||
LocationData("Route 23", "Wild Pokemon - 7", ["Arbok", "Sandslash"], rom_addresses["Wild_Route23"] + 13,
|
||||
None, event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 8", "Ditto", rom_addresses["Wild_Route23"] + 15, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 8", "Ditto", rom_addresses["Wild_Route23"] + 15, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 9", "Fearow", rom_addresses["Wild_Route23"] + 17, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 9", "Fearow", rom_addresses["Wild_Route23"] + 17, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Route 23 North", "Wild Pokemon - 10", "Fearow", rom_addresses["Wild_Route23"] + 19, None,
|
||||
LocationData("Route 23", "Wild Pokemon - 10", "Fearow", rom_addresses["Wild_Route23"] + 19, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
LocationData("Victory Road 2F", "Wild Pokemon - 1", "Machop", rom_addresses["Wild_VictoryRoad2F"] + 1, None,
|
||||
event=True, type="Wild Encounter"),
|
||||
|
||||
@@ -6,39 +6,39 @@ class PokemonLogic(LogicMixin):
|
||||
def pokemon_rb_can_surf(self, player):
|
||||
return (((self.has("HM03 Surf", player) and self.can_learn_hm("10000", player))
|
||||
or self.has("Flippers", player)) and (self.has("Soul Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Surf"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
self.has(self.multiworld.worlds[player].extra_badges.get("Surf"), player)
|
||||
or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_cut(self, player):
|
||||
return ((self.has("HM01 Cut", player) and self.can_learn_hm("100", player) or self.has("Master Sword", player))
|
||||
and (self.has("Cascade Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Cut"), player) or
|
||||
self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
self.has(self.multiworld.worlds[player].extra_badges.get("Cut"), player) or
|
||||
self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_fly(self, player):
|
||||
return (((self.has("HM02 Fly", player) and self.can_learn_hm("1000", player)) or self.has("Flute", player)) and
|
||||
(self.has("Thunder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Fly"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
(self.has("Thunder Badge", player) or self.has(self.multiworld.worlds[player].extra_badges.get("Fly"), player)
|
||||
or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def pokemon_rb_can_strength(self, player):
|
||||
return ((self.has("HM04 Strength", player) and self.can_learn_hm("100000", player)) or
|
||||
self.has("Titan's Mitt", player)) and (self.has("Rainbow Badge", player) or
|
||||
self.has(self.world.worlds[player].extra_badges.get("Strength"), player)
|
||||
or self.world.badges_needed_for_hm_moves[player].value == 0)
|
||||
self.has(self.multiworld.worlds[player].extra_badges.get("Strength"), player)
|
||||
or self.multiworld.badges_needed_for_hm_moves[player].value == 0)
|
||||
|
||||
def pokemon_rb_can_flash(self, player):
|
||||
return (((self.has("HM05 Flash", player) and self.can_learn_hm("1000000", player)) or self.has("Lamp", player))
|
||||
and (self.has("Boulder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Flash"),
|
||||
player) or self.world.badges_needed_for_hm_moves[player].value == 0))
|
||||
and (self.has("Boulder Badge", player) or self.has(self.multiworld.worlds[player].extra_badges.get("Flash"),
|
||||
player) or self.multiworld.badges_needed_for_hm_moves[player].value == 0))
|
||||
|
||||
def can_learn_hm(self, move, player):
|
||||
for pokemon, data in self.world.worlds[player].local_poke_data.items():
|
||||
for pokemon, data in self.multiworld.worlds[player].local_poke_data.items():
|
||||
if self.has(pokemon, player) and data["tms"][6] & int(move, 2):
|
||||
return True
|
||||
return False
|
||||
|
||||
def pokemon_rb_can_get_hidden_items(self, player):
|
||||
return self.has("Item Finder", player) or not self.world.require_item_finder[player].value
|
||||
return self.has("Item Finder", player) or not self.multiworld.require_item_finder[player].value
|
||||
|
||||
def pokemon_rb_cerulean_cave(self, count, player):
|
||||
return len([item for item in
|
||||
@@ -49,7 +49,7 @@ class PokemonLogic(LogicMixin):
|
||||
"HM04 Strength", "HM05 Flash"] if self.has(item, player)]) >= count
|
||||
|
||||
def pokemon_rb_can_pass_guards(self, player):
|
||||
if self.world.tea[player].value:
|
||||
if self.multiworld.tea[player].value:
|
||||
return self.has("Tea", player)
|
||||
else:
|
||||
# this could just be "True", but you never know what weird options I might introduce later ;)
|
||||
|
||||
@@ -83,9 +83,9 @@ class BadgeSanity(Toggle):
|
||||
|
||||
|
||||
class BadgesNeededForHMMoves(Choice):
|
||||
"""Off will remove the requirement for badges to use HM moves. Extra will give the Marsh, Volcano, and Earth
|
||||
Badges a random HM move to enable. Extra Plus will additionally pick two random badges to enable a second HM move.
|
||||
A man in Cerulean City will reveal the moves enabled by each Badge."""
|
||||
"""Off will remove the requirement for badges to use HM moves. Extra will give the Marsh, Volcano, and Earth Badges
|
||||
a random HM move to enable. Extra Plus will additionally pick two random badges to enable a second HM move.
|
||||
You will only need one of the required badges to use the HM move."""
|
||||
display_name = "Badges Needed For HM Moves"
|
||||
default = 1
|
||||
option_on = 1
|
||||
|
||||
@@ -41,8 +41,7 @@ def create_regions(world: MultiWorld, player: int):
|
||||
create_region(world, player, "Route 2 East", locations_per_region),
|
||||
create_region(world, player, "Diglett's Cave", locations_per_region),
|
||||
create_region(world, player, "Route 22", locations_per_region),
|
||||
create_region(world, player, "Route 23 South", locations_per_region),
|
||||
create_region(world, player, "Route 23 North", locations_per_region),
|
||||
create_region(world, player, "Route 23", locations_per_region),
|
||||
create_region(world, player, "Viridian Forest", locations_per_region),
|
||||
create_region(world, player, "Pewter City", locations_per_region),
|
||||
create_region(world, player, "Pewter Gym", locations_per_region),
|
||||
@@ -150,15 +149,15 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Menu", "Anywhere", one_way=True)
|
||||
connect(world, player, "Menu", "Pallet Town", one_way=True)
|
||||
connect(world, player, "Menu", "Fossil", lambda state: state.pokemon_rb_fossil_checks(
|
||||
state.world.second_fossil_check_condition[player].value, player), one_way=True)
|
||||
state.multiworld.second_fossil_check_condition[player].value, player), one_way=True)
|
||||
connect(world, player, "Pallet Town", "Route 1")
|
||||
connect(world, player, "Route 1", "Viridian City")
|
||||
connect(world, player, "Viridian City", "Route 22")
|
||||
connect(world, player, "Route 22", "Route 23 South",
|
||||
lambda state: state.pokemon_rb_has_badges(state.world.victory_road_condition[player].value, player))
|
||||
connect(world, player, "Route 23 South", "Route 23 North", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 22", "Route 23",
|
||||
lambda state: state.pokemon_rb_has_badges(state.multiworld.victory_road_condition[player].value, player) and
|
||||
state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Viridian City North", "Viridian Gym", lambda state:
|
||||
state.pokemon_rb_has_badges(state.world.viridian_gym_condition[player].value, player), one_way=True)
|
||||
state.pokemon_rb_has_badges(state.multiworld.viridian_gym_condition[player].value, player), one_way=True)
|
||||
connect(world, player, "Route 2", "Route 2 East", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 2 East", "Diglett's Cave", lambda state: state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 2", "Viridian City North")
|
||||
@@ -178,7 +177,7 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Route 9", "Route 10 North")
|
||||
connect(world, player, "Route 10 North", "Rock Tunnel 1F", lambda state: state.pokemon_rb_can_flash(player))
|
||||
connect(world, player, "Route 10 North", "Power Plant", lambda state: state.pokemon_rb_can_surf(player) and
|
||||
(state.has("Plant Key", player) or not state.world.extra_key_items[player].value), one_way=True)
|
||||
(state.has("Plant Key", player) or not state.multiworld.extra_key_items[player].value), one_way=True)
|
||||
connect(world, player, "Rock Tunnel 1F", "Route 10 South", lambda state: state.pokemon_rb_can_flash(player))
|
||||
connect(world, player, "Rock Tunnel 1F", "Rock Tunnel B1F")
|
||||
connect(world, player, "Lavender Town", "Pokemon Tower 1F", one_way=True)
|
||||
@@ -205,7 +204,7 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "S.S. Anne 1F", "S.S. Anne B1F", one_way=True)
|
||||
connect(world, player, "Vermilion City", "Route 11")
|
||||
connect(world, player, "Vermilion City", "Diglett's Cave")
|
||||
connect(world, player, "Route 12 West", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player) or not state.world.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Route 12 West", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player) or not state.multiworld.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Route 12 North", "Route 12 South", lambda state: state.has("Poke Flute", player) or state.pokemon_rb_can_surf( player))
|
||||
connect(world, player, "Route 12 West", "Route 12 North", lambda state: state.has("Poke Flute", player))
|
||||
connect(world, player, "Route 12 West", "Route 12 South", lambda state: state.has("Poke Flute", player))
|
||||
@@ -227,25 +226,25 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Fuchsia City", "Fuchsia Gym", one_way=True)
|
||||
connect(world, player, "Fuchsia City", "Route 18")
|
||||
connect(world, player, "Fuchsia City", "Safari Zone Gate", one_way=True)
|
||||
connect(world, player, "Safari Zone Gate", "Safari Zone Center", lambda state: state.has("Safari Pass", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Safari Zone Gate", "Safari Zone Center", lambda state: state.has("Safari Pass", player) or not state.multiworld.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone East", one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone West", one_way=True)
|
||||
connect(world, player, "Safari Zone Center", "Safari Zone North", one_way=True)
|
||||
connect(world, player, "Fuchsia City", "Route 15")
|
||||
connect(world, player, "Route 15", "Route 14")
|
||||
connect(world, player, "Route 14", "Route 13")
|
||||
connect(world, player, "Route 13", "Route 12 South", lambda state: state.pokemon_rb_can_strength(player) or state.pokemon_rb_can_surf(player) or not state.world.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Route 13", "Route 12 South", lambda state: state.pokemon_rb_can_strength(player) or state.pokemon_rb_can_surf(player) or not state.multiworld.extra_strength_boulders[player].value)
|
||||
connect(world, player, "Fuchsia City", "Route 19", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 20 East", "Route 19")
|
||||
connect(world, player, "Route 20 West", "Cinnabar Island", lambda state: state.pokemon_rb_can_surf(player))
|
||||
connect(world, player, "Route 20 West", "Seafoam Islands 1F")
|
||||
connect(world, player, "Route 20 East", "Seafoam Islands 1F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands 1F", "Route 20 East", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Viridian City", "Viridian City North", lambda state: state.has("Oak's Parcel", player) or state.world.old_man[player].value == 2 or state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Viridian City", "Viridian City North", lambda state: state.has("Oak's Parcel", player) or state.multiworld.old_man[player].value == 2 or state.pokemon_rb_can_cut(player))
|
||||
connect(world, player, "Route 3", "Mt Moon 1F", one_way=True)
|
||||
connect(world, player, "Route 11", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player))
|
||||
connect(world, player, "Cinnabar Island", "Cinnabar Gym", lambda state: state.has("Secret Key", player), one_way=True)
|
||||
connect(world, player, "Cinnabar Island", "Pokemon Mansion 1F", lambda state: state.has("Mansion Key", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Cinnabar Island", "Pokemon Mansion 1F", lambda state: state.has("Mansion Key", player) or not state.multiworld.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands B1F", "Seafoam Islands B2F", one_way=True)
|
||||
connect(world, player, "Seafoam Islands B2F", "Seafoam Islands B3F", one_way=True)
|
||||
@@ -263,19 +262,19 @@ def create_regions(world: MultiWorld, player: int):
|
||||
connect(world, player, "Silph Co 8F", "Silph Co 9F", one_way=True)
|
||||
connect(world, player, "Silph Co 9F", "Silph Co 10F", one_way=True)
|
||||
connect(world, player, "Silph Co 10F", "Silph Co 11F", one_way=True)
|
||||
connect(world, player, "Celadon City", "Rocket Hideout B1F", lambda state: state.has("Hideout Key", player) or not state.world.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Celadon City", "Rocket Hideout B1F", lambda state: state.has("Hideout Key", player) or not state.multiworld.extra_key_items[player].value, one_way=True)
|
||||
connect(world, player, "Rocket Hideout B1F", "Rocket Hideout B2F", one_way=True)
|
||||
connect(world, player, "Rocket Hideout B2F", "Rocket Hideout B3F", one_way=True)
|
||||
connect(world, player, "Rocket Hideout B3F", "Rocket Hideout B4F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion 2F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 2F", "Pokemon Mansion 3F", one_way=True)
|
||||
connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion B1F", one_way=True)
|
||||
connect(world, player, "Route 23 North", "Victory Road 1F", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Route 23", "Victory Road 1F", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
|
||||
connect(world, player, "Victory Road 1F", "Victory Road 2F", one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Victory Road 3F", one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Indigo Plateau", lambda state: state.pokemon_rb_has_badges(state.world.elite_four_condition[player], player), one_way=True)
|
||||
connect(world, player, "Victory Road 2F", "Indigo Plateau", lambda state: state.pokemon_rb_has_badges(state.multiworld.elite_four_condition[player], player), one_way=True)
|
||||
connect(world, player, "Cerulean City", "Cerulean Cave 1F", lambda state:
|
||||
state.pokemon_rb_cerulean_cave(state.world.cerulean_cave_condition[player].value + (state.world.extra_key_items[player].value * 4), player) and
|
||||
state.pokemon_rb_cerulean_cave(state.multiworld.cerulean_cave_condition[player].value + (state.multiworld.extra_key_items[player].value * 4), player) and
|
||||
state.pokemon_rb_can_surf(player), one_way=True)
|
||||
connect(world, player, "Cerulean Cave 1F", "Cerulean Cave 2F", one_way=True)
|
||||
connect(world, player, "Cerulean Cave 1F", "Cerulean Cave B1F", lambda state: state.pokemon_rb_can_surf(player), one_way=True)
|
||||
|
||||
@@ -43,7 +43,7 @@ def get_encounter_slots(self):
|
||||
|
||||
for location in encounter_slots:
|
||||
if isinstance(location.original_item, list):
|
||||
location.original_item = location.original_item[not self.world.game_version[self.player].value]
|
||||
location.original_item = location.original_item[not self.multiworld.game_version[self.player].value]
|
||||
return encounter_slots
|
||||
|
||||
|
||||
@@ -64,19 +64,19 @@ def randomize_pokemon(self, mon, mons_list, randomize_type):
|
||||
if randomize_type == 3:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
type_mons.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
||||
mon = type_mons[round(self.world.random.triangular(0, len(type_mons) - 1, 0))]
|
||||
mon = type_mons[round(self.multiworld.random.triangular(0, len(type_mons) - 1, 0))]
|
||||
if randomize_type == 2:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
mons_list.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
|
||||
mon = mons_list[round(self.world.random.triangular(0, 50, 0))]
|
||||
mon = mons_list[round(self.multiworld.random.triangular(0, 50, 0))]
|
||||
elif randomize_type == 4:
|
||||
mon = self.world.random.choice(mons_list)
|
||||
mon = self.multiworld.random.choice(mons_list)
|
||||
return mon
|
||||
|
||||
|
||||
def process_trainer_data(self, data):
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.trainer_legendaries[self.player].value]
|
||||
or self.multiworld.trainer_legendaries[self.player].value]
|
||||
address = rom_addresses["Trainer_Data"]
|
||||
while address < rom_addresses["Trainer_Data_End"]:
|
||||
if data[address] == 255:
|
||||
@@ -93,14 +93,14 @@ def process_trainer_data(self, data):
|
||||
for i in range(1, 4):
|
||||
for l in ["A", "B", "C", "D", "E", "F", "G", "H"]:
|
||||
if rom_addresses[f"Rival_Starter{i}_{l}"] == address:
|
||||
mon = " ".join(self.world.get_location(f"Pallet Town - Starter {i}", self.player).item.name.split()[1:])
|
||||
mon = " ".join(self.multiworld.get_location(f"Pallet Town - Starter {i}", self.player).item.name.split()[1:])
|
||||
if l in ["D", "E", "F", "G", "H"] and mon in poke_data.evolves_to:
|
||||
mon = poke_data.evolves_to[mon]
|
||||
if l in ["F", "G", "H"] and mon in poke_data.evolves_to:
|
||||
mon = poke_data.evolves_to[mon]
|
||||
if mon is None and self.world.randomize_trainer_parties[self.player].value:
|
||||
if mon is None and self.multiworld.randomize_trainer_parties[self.player].value:
|
||||
mon = poke_data.id_to_mon[data[address]]
|
||||
mon = randomize_pokemon(self, mon, mons_list, self.world.randomize_trainer_parties[self.player].value)
|
||||
mon = randomize_pokemon(self, mon, mons_list, self.multiworld.randomize_trainer_parties[self.player].value)
|
||||
if mon is not None:
|
||||
data[address] = poke_data.pokemon_data[mon]["id"]
|
||||
|
||||
@@ -114,22 +114,22 @@ def process_static_pokemon(self):
|
||||
|
||||
tower_6F_mons = set()
|
||||
for i in range(1, 11):
|
||||
tower_6F_mons.add(self.world.get_location(f"Pokemon Tower 6F - Wild Pokemon - {i}", self.player).item.name)
|
||||
tower_6F_mons.add(self.multiworld.get_location(f"Pokemon Tower 6F - Wild Pokemon - {i}", self.player).item.name)
|
||||
|
||||
mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.randomize_legendary_pokemon[self.player].value == 3]
|
||||
if self.world.randomize_legendary_pokemon[self.player].value == 0:
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
||||
if self.multiworld.randomize_legendary_pokemon[self.player].value == 0:
|
||||
for slot in legendary_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item("Missable " + slot.original_item))
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 1:
|
||||
self.world.random.shuffle(legendary_mons)
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 1:
|
||||
self.multiworld.random.shuffle(legendary_mons)
|
||||
for slot in legendary_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item("Missable " + legendary_mons.pop()))
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 2:
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 2:
|
||||
static_slots = static_slots + legendary_slots
|
||||
self.world.random.shuffle(static_slots)
|
||||
self.multiworld.random.shuffle(static_slots)
|
||||
static_slots.sort(key=lambda s: 0 if s.name == "Pokemon Tower 6F - Restless Soul" else 1)
|
||||
while legendary_slots:
|
||||
swap_slot = legendary_slots.pop()
|
||||
@@ -137,15 +137,15 @@ def process_static_pokemon(self):
|
||||
slot_type = slot.type.split()[0]
|
||||
if slot_type == "Legendary":
|
||||
slot_type = "Missable"
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
|
||||
swap_slot.original_item = slot.original_item
|
||||
elif self.world.randomize_legendary_pokemon[self.player].value == 3:
|
||||
elif self.multiworld.randomize_legendary_pokemon[self.player].value == 3:
|
||||
static_slots = static_slots + legendary_slots
|
||||
|
||||
for slot in static_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
randomize_type = self.world.randomize_static_pokemon[self.player].value
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
randomize_type = self.multiworld.randomize_static_pokemon[self.player].value
|
||||
slot_type = slot.type.split()[0]
|
||||
if slot_type == "Legendary":
|
||||
slot_type = "Missable"
|
||||
@@ -160,8 +160,8 @@ def process_static_pokemon(self):
|
||||
location.place_locked_item(mon)
|
||||
|
||||
for slot in starter_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
randomize_type = self.world.randomize_starter_pokemon[self.player].value
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
randomize_type = self.multiworld.randomize_starter_pokemon[self.player].value
|
||||
slot_type = "Missable"
|
||||
if not randomize_type:
|
||||
location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
|
||||
@@ -175,21 +175,21 @@ def process_wild_pokemon(self):
|
||||
encounter_slots = get_encounter_slots(self)
|
||||
|
||||
placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
|
||||
if self.world.randomize_wild_pokemon[self.player].value:
|
||||
if self.multiworld.randomize_wild_pokemon[self.player].value:
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.world.randomize_legendary_pokemon[self.player].value == 3]
|
||||
self.world.random.shuffle(encounter_slots)
|
||||
or self.multiworld.randomize_legendary_pokemon[self.player].value == 3]
|
||||
self.multiworld.random.shuffle(encounter_slots)
|
||||
locations = []
|
||||
for slot in encounter_slots:
|
||||
mon = randomize_pokemon(self, slot.original_item, mons_list, self.world.randomize_wild_pokemon[self.player].value)
|
||||
mon = randomize_pokemon(self, slot.original_item, mons_list, self.multiworld.randomize_wild_pokemon[self.player].value)
|
||||
# if static Pokemon are not randomized, we make sure nothing on Pokemon Tower 6F is a Marowak
|
||||
# if static Pokemon are randomized we deal with that during static encounter randomization
|
||||
while (self.world.randomize_static_pokemon[self.player].value == 0 and mon == "Marowak"
|
||||
while (self.multiworld.randomize_static_pokemon[self.player].value == 0 and mon == "Marowak"
|
||||
and "Pokemon Tower 6F" in slot.name):
|
||||
# to account for the possibility that only one ground type Pokemon exists, match only stats for this fix
|
||||
mon = randomize_pokemon(self, slot.original_item, mons_list, 2)
|
||||
placed_mons[mon] += 1
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.item = self.create_item(mon)
|
||||
location.event = True
|
||||
location.locked = True
|
||||
@@ -198,33 +198,33 @@ def process_wild_pokemon(self):
|
||||
|
||||
mons_to_add = []
|
||||
remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
if self.world.catch_em_all[self.player].value == 1:
|
||||
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
if self.multiworld.catch_em_all[self.player].value == 1:
|
||||
mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and
|
||||
(pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
elif self.world.catch_em_all[self.player].value == 2:
|
||||
(pokemon not in poke_data.legendary_pokemon or self.multiworld.randomize_legendary_pokemon[self.player].value == 3)]
|
||||
elif self.multiworld.catch_em_all[self.player].value == 2:
|
||||
mons_to_add = remaining_pokemon.copy()
|
||||
logic_needed_mons = max(self.world.oaks_aide_rt_2[self.player].value,
|
||||
self.world.oaks_aide_rt_11[self.player].value,
|
||||
self.world.oaks_aide_rt_15[self.player].value)
|
||||
if self.world.accessibility[self.player] == "minimal":
|
||||
logic_needed_mons = max(self.multiworld.oaks_aide_rt_2[self.player].value,
|
||||
self.multiworld.oaks_aide_rt_11[self.player].value,
|
||||
self.multiworld.oaks_aide_rt_15[self.player].value)
|
||||
if self.multiworld.accessibility[self.player] == "minimal":
|
||||
logic_needed_mons = 0
|
||||
|
||||
self.world.random.shuffle(remaining_pokemon)
|
||||
self.multiworld.random.shuffle(remaining_pokemon)
|
||||
while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
|
||||
+ len(mons_to_add) < logic_needed_mons):
|
||||
mons_to_add.append(remaining_pokemon.pop())
|
||||
for mon in mons_to_add:
|
||||
stat_base = get_base_stat_total(mon)
|
||||
candidate_locations = get_encounter_slots(self)
|
||||
if self.world.randomize_wild_pokemon[self.player].value in [1, 3]:
|
||||
if self.multiworld.randomize_wild_pokemon[self.player].value in [1, 3]:
|
||||
candidate_locations = [slot for slot in candidate_locations if any([poke_data.pokemon_data[slot.original_item][
|
||||
"type1"] in [self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]],
|
||||
poke_data.pokemon_data[slot.original_item]["type2"] in [self.local_poke_data[mon]["type1"],
|
||||
self.local_poke_data[mon]["type2"]]])]
|
||||
if not candidate_locations:
|
||||
candidate_locations = location_data
|
||||
candidate_locations = [self.world.get_location(location.name, self.player) for location in candidate_locations]
|
||||
candidate_locations = [self.multiworld.get_location(location.name, self.player) for location in candidate_locations]
|
||||
candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.item.name) - stat_base))
|
||||
for location in candidate_locations:
|
||||
if placed_mons[location.item.name] > 1 or location.item.name not in poke_data.first_stage_pokemon:
|
||||
@@ -236,7 +236,7 @@ def process_wild_pokemon(self):
|
||||
|
||||
else:
|
||||
for slot in encounter_slots:
|
||||
location = self.world.get_location(slot.name, self.player)
|
||||
location = self.multiworld.get_location(slot.name, self.player)
|
||||
location.item = self.create_item(slot.original_item)
|
||||
location.event = True
|
||||
location.locked = True
|
||||
@@ -250,19 +250,19 @@ def process_pokemon_data(self):
|
||||
learnsets = deepcopy(poke_data.learnsets)
|
||||
|
||||
for mon, mon_data in local_poke_data.items():
|
||||
if self.world.randomize_pokemon_stats[self.player].value == 1:
|
||||
if self.multiworld.randomize_pokemon_stats[self.player].value == 1:
|
||||
stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
|
||||
self.world.random.shuffle(stats)
|
||||
self.multiworld.random.shuffle(stats)
|
||||
mon_data["hp"] = stats[0]
|
||||
mon_data["atk"] = stats[1]
|
||||
mon_data["def"] = stats[2]
|
||||
mon_data["spd"] = stats[3]
|
||||
mon_data["spc"] = stats[4]
|
||||
elif self.world.randomize_pokemon_stats[self.player].value == 2:
|
||||
elif self.multiworld.randomize_pokemon_stats[self.player].value == 2:
|
||||
old_stats = mon_data["hp"] + mon_data["atk"] + mon_data["def"] + mon_data["spd"] + mon_data["spc"] - 5
|
||||
stats = [1, 1, 1, 1, 1]
|
||||
while old_stats > 0:
|
||||
stat = self.world.random.randint(0, 4)
|
||||
stat = self.multiworld.random.randint(0, 4)
|
||||
if stats[stat] < 255:
|
||||
old_stats -= 1
|
||||
stats[stat] += 1
|
||||
@@ -271,30 +271,30 @@ def process_pokemon_data(self):
|
||||
mon_data["def"] = stats[2]
|
||||
mon_data["spd"] = stats[3]
|
||||
mon_data["spc"] = stats[4]
|
||||
if self.world.randomize_pokemon_types[self.player].value:
|
||||
if self.world.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
|
||||
if self.multiworld.randomize_pokemon_types[self.player].value:
|
||||
if self.multiworld.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
|
||||
type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
|
||||
type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
|
||||
if type1 == type2:
|
||||
if self.world.secondary_type_chance[self.player].value == -1:
|
||||
if self.multiworld.secondary_type_chance[self.player].value == -1:
|
||||
if mon_data["type1"] != mon_data["type2"]:
|
||||
while type2 == type1:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
elif self.world.random.randint(1, 100) <= self.world.secondary_type_chance[self.player].value:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
elif self.multiworld.random.randint(1, 100) <= self.multiworld.secondary_type_chance[self.player].value:
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
else:
|
||||
type1 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
type1 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = type1
|
||||
if ((self.world.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
|
||||
!= mon_data["type2"]) or self.world.random.randint(1, 100)
|
||||
<= self.world.secondary_type_chance[self.player].value):
|
||||
if ((self.multiworld.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
|
||||
!= mon_data["type2"]) or self.multiworld.random.randint(1, 100)
|
||||
<= self.multiworld.secondary_type_chance[self.player].value):
|
||||
while type2 == type1:
|
||||
type2 = self.world.random.choice(list(poke_data.type_names.values()))
|
||||
type2 = self.multiworld.random.choice(list(poke_data.type_names.values()))
|
||||
|
||||
mon_data["type1"] = type1
|
||||
mon_data["type2"] = type2
|
||||
if self.world.randomize_pokemon_movesets[self.player].value:
|
||||
if self.world.randomize_pokemon_movesets[self.player].value == 1:
|
||||
if self.multiworld.randomize_pokemon_movesets[self.player].value:
|
||||
if self.multiworld.randomize_pokemon_movesets[self.player].value == 1:
|
||||
if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
|
||||
chances = [[75, "Normal"]]
|
||||
elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
|
||||
@@ -312,30 +312,30 @@ def process_pokemon_data(self):
|
||||
moves = list(poke_data.moves.keys())
|
||||
for move in ["No Move"] + poke_data.hm_moves:
|
||||
moves.remove(move)
|
||||
mon_data["start move 1"] = get_move(moves, chances, self.world.random, True)
|
||||
mon_data["start move 1"] = get_move(moves, chances, self.multiworld.random, True)
|
||||
for i in range(2, 5):
|
||||
if mon_data[f"start move {i}"] != "No Move" or self.world.start_with_four_moves[
|
||||
if mon_data[f"start move {i}"] != "No Move" or self.multiworld.start_with_four_moves[
|
||||
self.player].value == 1:
|
||||
mon_data[f"start move {i}"] = get_move(moves, chances, self.world.random)
|
||||
mon_data[f"start move {i}"] = get_move(moves, chances, self.multiworld.random)
|
||||
if mon in learnsets:
|
||||
for move_num in range(0, len(learnsets[mon])):
|
||||
learnsets[mon][move_num] = get_move(moves, chances, self.world.random)
|
||||
if self.world.randomize_pokemon_catch_rates[self.player].value:
|
||||
mon_data["catch rate"] = self.world.random.randint(self.world.minimum_catch_rate[self.player], 255)
|
||||
learnsets[mon][move_num] = get_move(moves, chances, self.multiworld.random)
|
||||
if self.multiworld.randomize_pokemon_catch_rates[self.player].value:
|
||||
mon_data["catch rate"] = self.multiworld.random.randint(self.multiworld.minimum_catch_rate[self.player], 255)
|
||||
else:
|
||||
mon_data["catch rate"] = max(self.world.minimum_catch_rate[self.player], mon_data["catch rate"])
|
||||
mon_data["catch rate"] = max(self.multiworld.minimum_catch_rate[self.player], mon_data["catch rate"])
|
||||
|
||||
if mon in poke_data.evolves_from.keys() and mon_data["type1"] == local_poke_data[poke_data.evolves_from[mon]]["type1"] and mon_data["type2"] == local_poke_data[poke_data.evolves_from[mon]]["type2"]:
|
||||
mon_data["tms"] = local_poke_data[poke_data.evolves_from[mon]]["tms"]
|
||||
elif mon != "Mew":
|
||||
tms_hms = poke_data.tm_moves + poke_data.hm_moves
|
||||
for flag, tm_move in enumerate(tms_hms):
|
||||
if (flag < 50 and self.world.tm_compatibility[self.player].value == 1) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 1):
|
||||
if (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 1) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 1):
|
||||
type_match = poke_data.moves[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]]
|
||||
bit = int(self.world.random.randint(1, 100) < [[90, 50, 25], [100, 75, 25]][flag >= 50][0 if type_match else 1 if poke_data.moves[tm_move]["type"] == "Normal" else 2])
|
||||
elif (flag < 50 and self.world.tm_compatibility[self.player].value == 2) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 2):
|
||||
bit = [0, 1][self.world.random.randint(0, 1)]
|
||||
elif (flag < 50 and self.world.tm_compatibility[self.player].value == 3) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 3):
|
||||
bit = int(self.multiworld.random.randint(1, 100) < [[90, 50, 25], [100, 75, 25]][flag >= 50][0 if type_match else 1 if poke_data.moves[tm_move]["type"] == "Normal" else 2])
|
||||
elif (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 2) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 2):
|
||||
bit = [0, 1][self.multiworld.random.randint(0, 1)]
|
||||
elif (flag < 50 and self.multiworld.tm_compatibility[self.player].value == 3) or (flag >= 50 and self.multiworld.hm_compatibility[self.player].value == 3):
|
||||
bit = 1
|
||||
else:
|
||||
continue
|
||||
@@ -350,14 +350,14 @@ def process_pokemon_data(self):
|
||||
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
random = self.world.slot_seeds[self.player]
|
||||
game_version = self.world.game_version[self.player].current_key
|
||||
random = self.multiworld.slot_seeds[self.player]
|
||||
game_version = self.multiworld.game_version[self.player].current_key
|
||||
data = bytearray(get_base_rom_bytes(game_version))
|
||||
|
||||
basemd5 = hashlib.md5()
|
||||
basemd5.update(data)
|
||||
|
||||
for location in self.world.get_locations():
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.player != self.player or location.rom_address is None:
|
||||
continue
|
||||
if location.item and location.item.player == self.player:
|
||||
@@ -377,42 +377,44 @@ def generate_output(self, output_directory: str):
|
||||
data[location.rom_address] = 0x2C # AP Item
|
||||
data[rom_addresses['Fly_Location']] = self.fly_map_code
|
||||
|
||||
if self.world.tea[self.player].value:
|
||||
if self.multiworld.tea[self.player].value:
|
||||
data[rom_addresses["Option_Tea"]] = 1
|
||||
data[rom_addresses["Guard_Drink_List"]] = 0x54
|
||||
data[rom_addresses["Guard_Drink_List"] + 1] = 0
|
||||
data[rom_addresses["Guard_Drink_List"] + 2] = 0
|
||||
|
||||
if self.world.extra_key_items[self.player].value:
|
||||
if self.multiworld.extra_key_items[self.player].value:
|
||||
data[rom_addresses['Options']] |= 4
|
||||
data[rom_addresses["Option_Blind_Trainers"]] = round(self.world.blind_trainers[self.player].value * 2.55)
|
||||
data[rom_addresses['Option_Cerulean_Cave_Condition']] = self.world.cerulean_cave_condition[self.player].value
|
||||
data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.world.minimum_steps_between_encounters[self.player].value
|
||||
data[rom_addresses['Option_Victory_Road_Badges']] = self.world.victory_road_condition[self.player].value
|
||||
data[rom_addresses['Option_Pokemon_League_Badges']] = self.world.elite_four_condition[self.player].value
|
||||
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.world.viridian_gym_condition[self.player].value
|
||||
data[rom_addresses['Option_EXP_Modifier']] = self.world.exp_modifier[self.player].value
|
||||
if not self.world.require_item_finder[self.player].value:
|
||||
data[rom_addresses["Option_Blind_Trainers"]] = round(self.multiworld.blind_trainers[self.player].value * 2.55)
|
||||
data[rom_addresses['Option_Cerulean_Cave_Condition']] = self.multiworld.cerulean_cave_condition[self.player].value
|
||||
data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.multiworld.minimum_steps_between_encounters[self.player].value
|
||||
data[rom_addresses['Option_Victory_Road_Badges']] = self.multiworld.victory_road_condition[self.player].value
|
||||
data[rom_addresses['Option_Pokemon_League_Badges']] = self.multiworld.elite_four_condition[self.player].value
|
||||
data[rom_addresses['Option_Viridian_Gym_Badges']] = self.multiworld.viridian_gym_condition[self.player].value
|
||||
data[rom_addresses['Option_EXP_Modifier']] = self.multiworld.exp_modifier[self.player].value
|
||||
if not self.multiworld.require_item_finder[self.player].value:
|
||||
data[rom_addresses['Option_Itemfinder']] = 0
|
||||
if self.world.extra_strength_boulders[self.player].value:
|
||||
if self.multiworld.extra_strength_boulders[self.player].value:
|
||||
for i in range(0, 3):
|
||||
data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15
|
||||
if self.world.extra_key_items[self.player].value:
|
||||
if self.multiworld.extra_key_items[self.player].value:
|
||||
for i in range(0, 4):
|
||||
data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15
|
||||
if self.world.old_man[self.player].value == 2:
|
||||
if self.multiworld.old_man[self.player].value == 2:
|
||||
data[rom_addresses['Option_Old_Man']] = 0x11
|
||||
data[rom_addresses['Option_Old_Man_Lying']] = 0x15
|
||||
money = str(self.world.starting_money[self.player].value)
|
||||
money = str(self.multiworld.starting_money[self.player].value)
|
||||
while len(money) < 6:
|
||||
money = "0" + money
|
||||
data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16)
|
||||
data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16)
|
||||
data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16)
|
||||
data[rom_addresses["Text_Badges_Needed_Viridian_Gym"]] = encode_text(
|
||||
str(self.multiworld.viridian_gym_condition[self.player].value))[0]
|
||||
data[rom_addresses["Text_Badges_Needed"]] = encode_text(
|
||||
str(max(self.world.victory_road_condition[self.player].value,
|
||||
self.world.elite_four_condition[self.player].value)))[0]
|
||||
if self.world.badges_needed_for_hm_moves[self.player].value == 0:
|
||||
str(max(self.multiworld.victory_road_condition[self.player].value,
|
||||
self.multiworld.elite_four_condition[self.player].value)))[0]
|
||||
if self.multiworld.badges_needed_for_hm_moves[self.player].value == 0:
|
||||
for hm_move in poke_data.hm_moves:
|
||||
write_bytes(data, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
|
||||
rom_addresses["HM_" + hm_move + "_Badge_a"])
|
||||
@@ -437,7 +439,7 @@ def generate_output(self, output_directory: str):
|
||||
write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
|
||||
|
||||
chart = deepcopy(poke_data.type_chart)
|
||||
if self.world.randomize_type_matchup_types[self.player].value == 1:
|
||||
if self.multiworld.randomize_type_matchup_types[self.player].value == 1:
|
||||
attacking_types = []
|
||||
defending_types = []
|
||||
for matchup in chart:
|
||||
@@ -458,7 +460,7 @@ def generate_output(self, output_directory: str):
|
||||
for matchup, chart_row in zip(matchups, chart):
|
||||
chart_row[0] = matchup[0]
|
||||
chart_row[1] = matchup[1]
|
||||
elif self.world.randomize_type_matchup_types[self.player].value == 2:
|
||||
elif self.multiworld.randomize_type_matchup_types[self.player].value == 2:
|
||||
used_matchups = []
|
||||
for matchup in chart:
|
||||
matchup[0] = random.choice(list(poke_data.type_names.values()))
|
||||
@@ -467,17 +469,17 @@ def generate_output(self, output_directory: str):
|
||||
matchup[0] = random.choice(list(poke_data.type_names.values()))
|
||||
matchup[1] = random.choice(list(poke_data.type_names.values()))
|
||||
used_matchups.append([matchup[0], matchup[1]])
|
||||
if self.world.randomize_type_matchup_type_effectiveness[self.player].value == 1:
|
||||
if self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value == 1:
|
||||
effectiveness_list = []
|
||||
for matchup in chart:
|
||||
effectiveness_list.append(matchup[2])
|
||||
random.shuffle(effectiveness_list)
|
||||
for (matchup, effectiveness) in zip(chart, effectiveness_list):
|
||||
matchup[2] = effectiveness
|
||||
elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 2:
|
||||
elif self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value == 2:
|
||||
for matchup in chart:
|
||||
matchup[2] = random.choice([0] + ([5, 20] * 5))
|
||||
elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 3:
|
||||
elif self.multiworld.randomize_type_matchup_type_effectiveness[self.player].value == 3:
|
||||
for matchup in chart:
|
||||
matchup[2] = random.choice([i for i in range(0, 21) if i != 10])
|
||||
type_loc = rom_addresses["Type_Chart"]
|
||||
@@ -492,7 +494,7 @@ def generate_output(self, output_directory: str):
|
||||
# to the way effectiveness messages are generated.
|
||||
self.type_chart = sorted(chart, key=lambda matchup: 0 - matchup[2])
|
||||
|
||||
if self.world.normalize_encounter_chances[self.player].value:
|
||||
if self.multiworld.normalize_encounter_chances[self.player].value:
|
||||
chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
|
||||
for i, chance in enumerate(chances):
|
||||
data[rom_addresses['Encounter_Chances'] + (i * 2)] = chance
|
||||
@@ -520,14 +522,14 @@ def generate_output(self, output_directory: str):
|
||||
for i, move in enumerate(self.learnsets[mon]):
|
||||
data[(address + 1) + i * 2] = poke_data.moves[move]["id"]
|
||||
|
||||
data[rom_addresses["Option_Aide_Rt2"]] = self.world.oaks_aide_rt_2[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt11"]] = self.world.oaks_aide_rt_11[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt15"]] = self.world.oaks_aide_rt_15[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt2"]] = self.multiworld.oaks_aide_rt_2[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt11"]] = self.multiworld.oaks_aide_rt_11[self.player]
|
||||
data[rom_addresses["Option_Aide_Rt15"]] = self.multiworld.oaks_aide_rt_15[self.player]
|
||||
|
||||
if self.world.safari_zone_normal_battles[self.player].value == 1:
|
||||
if self.multiworld.safari_zone_normal_battles[self.player].value == 1:
|
||||
data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255
|
||||
|
||||
if self.world.reusable_tms[self.player].value:
|
||||
if self.multiworld.reusable_tms[self.player].value:
|
||||
data[rom_addresses["Option_Reusable_TMs"]] = 0xC9
|
||||
|
||||
process_trainer_data(self, data)
|
||||
@@ -537,17 +539,17 @@ def generate_output(self, output_directory: str):
|
||||
data[rom_addresses['Title_Mon_First']] = mons.pop()
|
||||
for mon in range(0, 16):
|
||||
data[rom_addresses['Title_Mons'] + mon] = mons.pop()
|
||||
if self.world.game_version[self.player].value:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name
|
||||
else 1 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name else
|
||||
2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
if self.multiworld.game_version[self.player].value:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Pallet Town - Starter 1", self.player).item.name
|
||||
else 1 if mon == self.multiworld.get_location("Pallet Town - Starter 2", self.player).item.name else
|
||||
2 if mon == self.multiworld.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
else:
|
||||
mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name
|
||||
else 1 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name else
|
||||
2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
write_bytes(data, encode_text(self.world.seed_name, 20, True), rom_addresses['Title_Seed'])
|
||||
mons.sort(key=lambda mon: 0 if mon == self.multiworld.get_location("Pallet Town - Starter 2", self.player).item.name
|
||||
else 1 if mon == self.multiworld.get_location("Pallet Town - Starter 1", self.player).item.name else
|
||||
2 if mon == self.multiworld.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
|
||||
write_bytes(data, encode_text(self.multiworld.seed_name[-20:], 20, True), rom_addresses['Title_Seed'])
|
||||
|
||||
slot_name = self.world.player_name[self.player]
|
||||
slot_name = self.multiworld.player_name[self.player]
|
||||
slot_name.replace("@", " ")
|
||||
slot_name.replace("<", " ")
|
||||
slot_name.replace(">", " ")
|
||||
@@ -556,24 +558,24 @@ def generate_output(self, output_directory: str):
|
||||
write_bytes(data, self.trainer_name, rom_addresses['Player_Name'])
|
||||
write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
|
||||
|
||||
write_bytes(data, basemd5.digest(), 0xFFCC)
|
||||
write_bytes(data, self.world.seed_name.encode(), 0xFFDC)
|
||||
write_bytes(data, self.world.player_name[self.player].encode(), 0xFFF0)
|
||||
write_bytes(data, basemd5.digest(), 0xFFCB)
|
||||
write_bytes(data, self.multiworld.seed_name.encode(), 0xFFDB)
|
||||
write_bytes(data, self.multiworld.player_name[self.player].encode(), 0xFFF0)
|
||||
|
||||
|
||||
|
||||
outfilepname = f'_P{self.player}'
|
||||
outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" \
|
||||
if self.world.player_name[self.player] != 'Player%d' % self.player else ''
|
||||
rompath = os.path.join(output_directory, f'AP_{self.world.seed_name}{outfilepname}.gb')
|
||||
outfilepname += f"_{self.multiworld.get_file_safe_player_name(self.player).replace(' ', '_')}" \
|
||||
if self.multiworld.player_name[self.player] != 'Player%d' % self.player else ''
|
||||
rompath = os.path.join(output_directory, f'AP_{self.multiworld.seed_name}{outfilepname}.gb')
|
||||
with open(rompath, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
if self.world.game_version[self.player].current_key == "red":
|
||||
if self.multiworld.game_version[self.player].current_key == "red":
|
||||
patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=rompath)
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
|
||||
else:
|
||||
patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=rompath)
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=rompath)
|
||||
|
||||
patch.write()
|
||||
os.unlink(rompath)
|
||||
|
||||
@@ -327,9 +327,9 @@ rom_addresses = {
|
||||
"Learnset_Bellsprout": 0x3b9dc,
|
||||
"Learnset_Weepinbell": 0x3b9f0,
|
||||
"Learnset_Victreebel": 0x3ba00,
|
||||
"Type_Chart": 0x3e4b6,
|
||||
"Type_Chart_Divider": 0x3e5ac,
|
||||
"Ghost_Battle3": 0x3efd9,
|
||||
"Type_Chart": 0x3e4b0,
|
||||
"Type_Chart_Divider": 0x3e5a6,
|
||||
"Ghost_Battle3": 0x3efd3,
|
||||
"Missable_Pokemon_Mansion_1F_Item_1": 0x443d6,
|
||||
"Missable_Pokemon_Mansion_1F_Item_2": 0x443dd,
|
||||
"Map_Rock_TunnelF": 0x44676,
|
||||
@@ -585,4 +585,5 @@ rom_addresses = {
|
||||
"Badge_Text_Marsh_Badge": 0x9918c,
|
||||
"Badge_Text_Volcano_Badge": 0x991d6,
|
||||
"Badge_Text_Earth_Badge": 0x991f3,
|
||||
"Text_Badges_Needed_Viridian_Gym": 0xa49f2,
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ def set_rules(world, player):
|
||||
"Pallet Town - Rival's Sister": lambda state: state.has("Oak's Parcel", player),
|
||||
"Pallet Town - Oak's Post-Route-22-Rival Gift": lambda state: state.has("Oak's Parcel", player),
|
||||
"Viridian City - Sleepy Guy": lambda state: state.pokemon_rb_can_cut(player) or state.pokemon_rb_can_surf(player),
|
||||
"Route 2 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.world.oaks_aide_rt_2[player].value + 5, player),
|
||||
"Route 2 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.multiworld.oaks_aide_rt_2[player].value + 5, player),
|
||||
"Pewter City - Museum": lambda state: state.pokemon_rb_can_cut(player),
|
||||
"Cerulean City - Bicycle Shop": lambda state: state.has("Bike Voucher", player),
|
||||
"Lavender Town - Mr. Fuji": lambda state: state.has("Fuji Saved", player),
|
||||
"Vermilion Gym - Lt. Surge 1": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
|
||||
"Vermilion Gym - Lt. Surge 2": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
|
||||
"Route 11 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.world.oaks_aide_rt_11[player].value + 5, player),
|
||||
"Route 11 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.multiworld.oaks_aide_rt_11[player].value + 5, player),
|
||||
"Celadon City - Stranded Man": lambda state: state.pokemon_rb_can_surf(player),
|
||||
"Silph Co 11F - Silph Co President": lambda state: state.has("Card Key", player),
|
||||
"Fuchsia City - Safari Zone Warden": lambda state: state.has("Gold Teeth", player),
|
||||
@@ -73,12 +73,12 @@ def set_rules(world, player):
|
||||
"Anywhere - Good Rod Pokemon - 1": lambda state: state.has("Good Rod", player),
|
||||
"Anywhere - Good Rod Pokemon - 2": lambda state: state.has("Good Rod", player),
|
||||
"Anywhere - Old Rod Pokemon": lambda state: state.has("Old Rod", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player),
|
||||
"Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player),
|
||||
"Cinnabar Island - Old Amber Pokemon": lambda state: state.has("Old Amber", player),
|
||||
"Cinnabar Island - Helix Fossil Pokemon": lambda state: state.has("Helix Fossil", player),
|
||||
"Cinnabar Island - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", player),
|
||||
@@ -96,10 +96,10 @@ def set_rules(world, player):
|
||||
player),
|
||||
"Route 25 - Hidden Item Fence Outside Bill's House": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 9 - Hidden Item Rock By Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 9 - Hidden Item Bush By Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"S.S. Anne 1F - Hidden Item Kitchen Trash": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"S.S. Anne B1F - Hidden Item Under Pillow": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Tree": lambda
|
||||
"Route 10 - Hidden Item Behind Rock Tunnel Entrance Cuttable Tree": lambda
|
||||
state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 10 - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
@@ -107,14 +107,15 @@ def set_rules(world, player):
|
||||
"Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 13 - Hidden Item Dead End Boulder": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 13 - Hidden Item Dead End Bush": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 13 - Hidden Item Dead End By Water Corner": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Safari Zone West - Hidden Item Secret House Statue": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Silph Co 5F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Silph Co 9F - Hidden Item Nurse Bed": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Silph Co 9F - Hidden Item Nurse Bed (Card Key)": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player) and state.has("Card Key", player),
|
||||
"Copycat's House - Hidden Item Desk": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
@@ -127,15 +128,15 @@ def set_rules(world, player):
|
||||
"Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 23 - Hidden Item Rocks Before Final Guard": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 23 - Hidden Item East Tree After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
"Route 23 - Hidden Item East Bush After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 23 - Hidden Item On Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Victory Road 2F - Hidden Item Rock In Final Room": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Viridian City - Hidden Item Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 11 - Hidden Item Isolated Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 12 - Hidden Item Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 11 - Hidden Item Isolated Bush Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 12 - Hidden Item Bush Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item In Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item Near Northernmost Sign": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Route 17 - Hidden Item East Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
@@ -153,7 +154,7 @@ def set_rules(world, player):
|
||||
"Mt Moon B2F - Hidden Item Lone Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Seafoam Islands B3F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
"Vermilion City - Hidden Item In Water Near Fan Club": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
player) and state.pokemon_rb_can_surf(player),
|
||||
"Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: state.pokemon_rb_can_get_hidden_items(
|
||||
player),
|
||||
"Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: state.pokemon_rb_can_get_hidden_items(player),
|
||||
|
||||
@@ -5,10 +5,10 @@ from ..AutoWorld import LogicMixin
|
||||
|
||||
class RaftLogic(LogicMixin):
|
||||
def raft_paddleboard_mode_enabled(self, player):
|
||||
return self.world.paddleboard_mode[player].value
|
||||
return self.multiworld.paddleboard_mode[player].value
|
||||
|
||||
def raft_big_islands_available(self, player):
|
||||
return self.world.big_island_early_crafting[player].value or self.raft_can_access_radio_tower(player)
|
||||
return self.multiworld.big_island_early_crafting[player].value or self.raft_can_access_radio_tower(player)
|
||||
|
||||
def raft_can_smelt_items(self, player):
|
||||
return self.has("Smelter", player)
|
||||
|
||||
@@ -43,8 +43,8 @@ class RaftWorld(World):
|
||||
required_client_version = (0, 3, 4)
|
||||
|
||||
def generate_basic(self):
|
||||
minRPSpecified = self.world.minimum_resource_pack_amount[self.player].value
|
||||
maxRPSpecified = self.world.maximum_resource_pack_amount[self.player].value
|
||||
minRPSpecified = self.multiworld.minimum_resource_pack_amount[self.player].value
|
||||
maxRPSpecified = self.multiworld.maximum_resource_pack_amount[self.player].value
|
||||
minimumResourcePackAmount = min(minRPSpecified, maxRPSpecified)
|
||||
maximumResourcePackAmount = max(minRPSpecified, maxRPSpecified)
|
||||
# Generate item pool
|
||||
@@ -56,21 +56,21 @@ class RaftWorld(World):
|
||||
extraItemNamePool = []
|
||||
extras = len(location_table) - len(item_table) - 1 # Victory takes up 1 unaccounted-for slot
|
||||
if extras > 0:
|
||||
if (self.world.filler_item_types[self.player].value != 1): # Use resource packs
|
||||
if (self.multiworld.filler_item_types[self.player].value != 1): # Use resource packs
|
||||
for packItem in resourcePackItems:
|
||||
for i in range(minimumResourcePackAmount, maximumResourcePackAmount + 1):
|
||||
extraItemNamePool.append(createResourcePackName(i, packItem))
|
||||
|
||||
if self.world.filler_item_types[self.player].value != 0: # Use duplicate items
|
||||
if self.multiworld.filler_item_types[self.player].value != 0: # Use duplicate items
|
||||
dupeItemPool = item_table.copy()
|
||||
# Remove frequencies if necessary
|
||||
if self.world.island_frequency_locations[self.player].value != 5: # Not completely random locations
|
||||
if self.multiworld.island_frequency_locations[self.player].value != 5: # Not completely random locations
|
||||
dupeItemPool = (itm for itm in dupeItemPool if "Frequency" not in itm["name"])
|
||||
|
||||
# Remove progression or non-progression items if necessary
|
||||
if (self.world.duplicate_items[self.player].value == 0): # Progression only
|
||||
if (self.multiworld.duplicate_items[self.player].value == 0): # Progression only
|
||||
dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == True)
|
||||
elif (self.world.duplicate_items[self.player].value == 1): # Non-progression only
|
||||
elif (self.multiworld.duplicate_items[self.player].value == 1): # Non-progression only
|
||||
dupeItemPool = (itm for itm in dupeItemPool if itm["progression"] == False)
|
||||
|
||||
dupeItemPool = list(dupeItemPool)
|
||||
@@ -84,23 +84,23 @@ class RaftWorld(World):
|
||||
raft_item = self.create_item_replaceAsNecessary(randomItem)
|
||||
pool.append(raft_item)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def get_pre_fill_items(self):
|
||||
if self.world.island_frequency_locations[self.player] in [0, 1, 2, 3]:
|
||||
return [loc.item for loc in self.world.get_filled_locations()]
|
||||
if self.multiworld.island_frequency_locations[self.player] in [0, 1, 2, 3]:
|
||||
return [loc.item for loc in self.multiworld.get_filled_locations()]
|
||||
return []
|
||||
|
||||
def create_item_replaceAsNecessary(self, name: str) -> Item:
|
||||
isFrequency = "Frequency" in name
|
||||
shouldUseProgressive = ((isFrequency and self.world.island_frequency_locations[self.player].value == 4)
|
||||
or (not isFrequency and self.world.progressive_items[self.player].value))
|
||||
shouldUseProgressive = ((isFrequency and self.multiworld.island_frequency_locations[self.player].value == 4)
|
||||
or (not isFrequency and self.multiworld.progressive_items[self.player].value))
|
||||
if shouldUseProgressive and name in progressive_table:
|
||||
name = progressive_table[name]
|
||||
return self.create_item(name)
|
||||
@@ -128,7 +128,7 @@ class RaftWorld(World):
|
||||
return super(RaftWorld, self).collect_item(state, item, remove)
|
||||
|
||||
def pre_fill(self):
|
||||
if self.world.island_frequency_locations[self.player] == 0:
|
||||
if self.multiworld.island_frequency_locations[self.player] == 0:
|
||||
self.setLocationItem("Radio Tower Frequency to Vasagatan", "Vasagatan Frequency")
|
||||
self.setLocationItem("Vasagatan Frequency to Balboa", "Balboa Island Frequency")
|
||||
self.setLocationItem("Relay Station quest", "Caravan Island Frequency")
|
||||
@@ -136,7 +136,7 @@ class RaftWorld(World):
|
||||
self.setLocationItem("Tangaroa Frequency to Varuna Point", "Varuna Point Frequency")
|
||||
self.setLocationItem("Varuna Point Frequency to Temperance", "Temperance Frequency")
|
||||
self.setLocationItem("Temperance Frequency to Utopia", "Utopia Frequency")
|
||||
elif self.world.island_frequency_locations[self.player] == 1:
|
||||
elif self.multiworld.island_frequency_locations[self.player] == 1:
|
||||
self.setLocationItemFromRegion("RadioTower", "Vasagatan Frequency")
|
||||
self.setLocationItemFromRegion("Vasagatan", "Balboa Island Frequency")
|
||||
self.setLocationItemFromRegion("BalboaIsland", "Caravan Island Frequency")
|
||||
@@ -144,7 +144,7 @@ class RaftWorld(World):
|
||||
self.setLocationItemFromRegion("Tangaroa", "Varuna Point Frequency")
|
||||
self.setLocationItemFromRegion("Varuna Point", "Temperance Frequency")
|
||||
self.setLocationItemFromRegion("Temperance", "Utopia Frequency")
|
||||
elif self.world.island_frequency_locations[self.player] in [2, 3]:
|
||||
elif self.multiworld.island_frequency_locations[self.player] in [2, 3]:
|
||||
locationToFrequencyItemMap = {
|
||||
"Vasagatan": "Vasagatan Frequency",
|
||||
"BalboaIsland": "Balboa Island Frequency",
|
||||
@@ -172,37 +172,37 @@ class RaftWorld(World):
|
||||
else:
|
||||
currentLocation = availableLocationList[0] # Utopia (only one left in list)
|
||||
availableLocationList.remove(currentLocation)
|
||||
if self.world.island_frequency_locations[self.player] == 2:
|
||||
if self.multiworld.island_frequency_locations[self.player] == 2:
|
||||
self.setLocationItem(locationToVanillaFrequencyLocationMap[previousLocation], locationToFrequencyItemMap[currentLocation])
|
||||
elif self.world.island_frequency_locations[self.player] == 3:
|
||||
elif self.multiworld.island_frequency_locations[self.player] == 3:
|
||||
self.setLocationItemFromRegion(previousLocation, locationToFrequencyItemMap[currentLocation])
|
||||
previousLocation = currentLocation
|
||||
|
||||
# Victory item
|
||||
self.world.get_location("Utopia Complete", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Utopia Complete", self.player).place_locked_item(
|
||||
RaftItem("Victory", ItemClassification.progression, None, player=self.player))
|
||||
|
||||
def setLocationItem(self, location: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.world.itempool))
|
||||
self.world.itempool.remove(itemToUse)
|
||||
self.world.get_location(location, self.player).place_locked_item(itemToUse)
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.multiworld.itempool))
|
||||
self.multiworld.itempool.remove(itemToUse)
|
||||
self.multiworld.get_location(location, self.player).place_locked_item(itemToUse)
|
||||
|
||||
def setLocationItemFromRegion(self, region: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.world.itempool))
|
||||
self.world.itempool.remove(itemToUse)
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.multiworld.itempool))
|
||||
self.multiworld.itempool.remove(itemToUse)
|
||||
location = random.choice(list(loc for loc in location_table if loc["region"] == region))
|
||||
self.world.get_location(location["name"], self.player).place_locked_item(itemToUse)
|
||||
self.multiworld.get_location(location["name"], self.player).place_locked_item(itemToUse)
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"IslandGenerationDistance": self.world.island_generation_distance[self.player].value,
|
||||
"ExpensiveResearch": bool(self.world.expensive_research[self.player].value),
|
||||
"DeathLink": bool(self.world.death_link[self.player].value)
|
||||
"IslandGenerationDistance": self.multiworld.island_generation_distance[self.player].value,
|
||||
"ExpensiveResearch": bool(self.multiworld.expensive_research[self.player].value),
|
||||
"DeathLink": bool(self.multiworld.death_link[self.player].value)
|
||||
}
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = locations_lookup_name_to_id.get(location, 0)
|
||||
|
||||
@@ -1,136 +1,115 @@
|
||||
import typing
|
||||
from typing import Dict, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import Item
|
||||
from .Names import ItemName
|
||||
from BaseClasses import Item, ItemClassification
|
||||
|
||||
|
||||
class ItemData(typing.NamedTuple):
|
||||
code: typing.Optional[int]
|
||||
progression: bool
|
||||
quantity: int = 1
|
||||
event: bool = False
|
||||
|
||||
|
||||
class LegacyItem(Item):
|
||||
class RLItem(Item):
|
||||
game: str = "Rogue Legacy"
|
||||
|
||||
|
||||
# Separate tables for each type of item.
|
||||
vendors_table = {
|
||||
ItemName.blacksmith: ItemData(90000, True),
|
||||
ItemName.enchantress: ItemData(90001, True),
|
||||
ItemName.architect: ItemData(90002, False),
|
||||
class RLItemData(NamedTuple):
|
||||
category: str
|
||||
code: Optional[int] = None
|
||||
classification: ItemClassification = ItemClassification.filler
|
||||
max_quantity: int = 1
|
||||
weight: int = 1
|
||||
|
||||
@property
|
||||
def is_event_item(self):
|
||||
return self.code is None
|
||||
|
||||
|
||||
def get_items_by_category(category: str) -> Dict[str, RLItemData]:
|
||||
item_dict: Dict[str, RLItemData] = {}
|
||||
for name, data in item_table.items():
|
||||
if data.category == category:
|
||||
item_dict.setdefault(name, data)
|
||||
|
||||
return item_dict
|
||||
|
||||
|
||||
item_table: Dict[str, RLItemData] = {
|
||||
# Vendors
|
||||
"Blacksmith": RLItemData("Vendors", 90_000, ItemClassification.useful),
|
||||
"Enchantress": RLItemData("Vendors", 90_001, ItemClassification.progression),
|
||||
"Architect": RLItemData("Vendors", 90_002, ItemClassification.useful),
|
||||
|
||||
# Classes
|
||||
"Progressive Knights": RLItemData("Classes", 90_003, ItemClassification.useful, 2),
|
||||
"Progressive Mages": RLItemData("Classes", 90_004, ItemClassification.useful, 2),
|
||||
"Progressive Barbarians": RLItemData("Classes", 90_005, ItemClassification.useful, 2),
|
||||
"Progressive Knaves": RLItemData("Classes", 90_006, ItemClassification.useful, 2),
|
||||
"Progressive Shinobis": RLItemData("Classes", 90_007, ItemClassification.useful, 2),
|
||||
"Progressive Miners": RLItemData("Classes", 90_008, ItemClassification.useful, 2),
|
||||
"Progressive Liches": RLItemData("Classes", 90_009, ItemClassification.useful, 2),
|
||||
"Progressive Spellthieves": RLItemData("Classes", 90_010, ItemClassification.useful, 2),
|
||||
"Dragons": RLItemData("Classes", 90_096, ItemClassification.progression),
|
||||
"Traitors": RLItemData("Classes", 90_097, ItemClassification.useful),
|
||||
|
||||
# Skills
|
||||
"Health Up": RLItemData("Skills", 90_013, ItemClassification.progression_skip_balancing, 15),
|
||||
"Mana Up": RLItemData("Skills", 90_014, ItemClassification.progression_skip_balancing, 15),
|
||||
"Attack Up": RLItemData("Skills", 90_015, ItemClassification.progression_skip_balancing, 15),
|
||||
"Magic Damage Up": RLItemData("Skills", 90_016, ItemClassification.progression_skip_balancing, 15),
|
||||
"Armor Up": RLItemData("Skills", 90_017, ItemClassification.useful, 15),
|
||||
"Equip Up": RLItemData("Skills", 90_018, ItemClassification.useful, 5),
|
||||
"Crit Chance Up": RLItemData("Skills", 90_019, ItemClassification.useful, 5),
|
||||
"Crit Damage Up": RLItemData("Skills", 90_020, ItemClassification.useful, 5),
|
||||
"Down Strike Up": RLItemData("Skills", 90_021),
|
||||
"Gold Gain Up": RLItemData("Skills", 90_022),
|
||||
"Potion Efficiency Up": RLItemData("Skills", 90_023),
|
||||
"Invulnerability Time Up": RLItemData("Skills", 90_024),
|
||||
"Mana Cost Down": RLItemData("Skills", 90_025),
|
||||
"Death Defiance": RLItemData("Skills", 90_026, ItemClassification.useful),
|
||||
"Haggling": RLItemData("Skills", 90_027, ItemClassification.useful),
|
||||
"Randomize Children": RLItemData("Skills", 90_028, ItemClassification.useful),
|
||||
|
||||
# Blueprints
|
||||
"Progressive Blueprints": RLItemData("Blueprints", 90_055, ItemClassification.useful, 15),
|
||||
"Squire Blueprints": RLItemData("Blueprints", 90_040, ItemClassification.useful),
|
||||
"Silver Blueprints": RLItemData("Blueprints", 90_041, ItemClassification.useful),
|
||||
"Guardian Blueprints": RLItemData("Blueprints", 90_042, ItemClassification.useful),
|
||||
"Imperial Blueprints": RLItemData("Blueprints", 90_043, ItemClassification.useful),
|
||||
"Royal Blueprints": RLItemData("Blueprints", 90_044, ItemClassification.useful),
|
||||
"Knight Blueprints": RLItemData("Blueprints", 90_045, ItemClassification.useful),
|
||||
"Ranger Blueprints": RLItemData("Blueprints", 90_046, ItemClassification.useful),
|
||||
"Sky Blueprints": RLItemData("Blueprints", 90_047, ItemClassification.useful),
|
||||
"Dragon Blueprints": RLItemData("Blueprints", 90_048, ItemClassification.useful),
|
||||
"Slayer Blueprints": RLItemData("Blueprints", 90_049, ItemClassification.useful),
|
||||
"Blood Blueprints": RLItemData("Blueprints", 90_050, ItemClassification.useful),
|
||||
"Sage Blueprints": RLItemData("Blueprints", 90_051, ItemClassification.useful),
|
||||
"Retribution Blueprints": RLItemData("Blueprints", 90_052, ItemClassification.useful),
|
||||
"Holy Blueprints": RLItemData("Blueprints", 90_053, ItemClassification.useful),
|
||||
"Dark Blueprints": RLItemData("Blueprints", 90_054, ItemClassification.useful),
|
||||
|
||||
# Runes
|
||||
"Vault Runes": RLItemData("Runes", 90_060, ItemClassification.progression),
|
||||
"Sprint Runes": RLItemData("Runes", 90_061, ItemClassification.progression),
|
||||
"Vampire Runes": RLItemData("Runes", 90_062, ItemClassification.useful),
|
||||
"Sky Runes": RLItemData("Runes", 90_063, ItemClassification.progression),
|
||||
"Siphon Runes": RLItemData("Runes", 90_064, ItemClassification.useful),
|
||||
"Retaliation Runes": RLItemData("Runes", 90_065),
|
||||
"Bounty Runes": RLItemData("Runes", 90_066),
|
||||
"Haste Runes": RLItemData("Runes", 90_067),
|
||||
"Curse Runes": RLItemData("Runes", 90_068),
|
||||
"Grace Runes": RLItemData("Runes", 90_069),
|
||||
"Balance Runes": RLItemData("Runes", 90_070, ItemClassification.useful),
|
||||
|
||||
# Junk
|
||||
"Triple Stat Increase": RLItemData("Filler", 90_030, weight=6),
|
||||
"1000 Gold": RLItemData("Filler", 90_031, weight=3),
|
||||
"3000 Gold": RLItemData("Filler", 90_032, weight=2),
|
||||
"5000 Gold": RLItemData("Filler", 90_033, weight=1),
|
||||
}
|
||||
|
||||
static_classes_table = {
|
||||
ItemName.knight: ItemData(90080, False),
|
||||
ItemName.paladin: ItemData(90081, False),
|
||||
ItemName.mage: ItemData(90082, False),
|
||||
ItemName.archmage: ItemData(90083, False),
|
||||
ItemName.barbarian: ItemData(90084, False),
|
||||
ItemName.barbarian_king: ItemData(90085, False),
|
||||
ItemName.knave: ItemData(90086, False),
|
||||
ItemName.assassin: ItemData(90087, False),
|
||||
ItemName.shinobi: ItemData(90088, False),
|
||||
ItemName.hokage: ItemData(90089, False),
|
||||
ItemName.miner: ItemData(90090, False),
|
||||
ItemName.spelunker: ItemData(90091, False),
|
||||
ItemName.lich: ItemData(90092, False),
|
||||
ItemName.lich_king: ItemData(90093, False),
|
||||
ItemName.spellthief: ItemData(90094, False),
|
||||
ItemName.spellsword: ItemData(90095, False),
|
||||
ItemName.dragon: ItemData(90096, False),
|
||||
ItemName.traitor: ItemData(90097, False),
|
||||
event_item_table: Dict[str, RLItemData] = {
|
||||
"Defeat Khidr": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Alexander": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Ponce de Leon": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Herodotus": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Neo Khidr": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Alexander IV": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Ponce de Freon": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat Astrodotus": RLItemData("Event", classification=ItemClassification.progression),
|
||||
"Defeat The Fountain": RLItemData("Event", classification=ItemClassification.progression),
|
||||
}
|
||||
|
||||
progressive_classes_table = {
|
||||
ItemName.progressive_knight: ItemData(90003, False, 2),
|
||||
ItemName.progressive_mage: ItemData(90004, False, 2),
|
||||
ItemName.progressive_barbarian: ItemData(90005, False, 2),
|
||||
ItemName.progressive_knave: ItemData(90006, False, 2),
|
||||
ItemName.progressive_shinobi: ItemData(90007, False, 2),
|
||||
ItemName.progressive_miner: ItemData(90008, False, 2),
|
||||
ItemName.progressive_lich: ItemData(90009, False, 2),
|
||||
ItemName.progressive_spellthief: ItemData(90010, False, 2),
|
||||
}
|
||||
|
||||
configurable_skill_unlocks_table = {
|
||||
ItemName.health: ItemData(90013, True, 15),
|
||||
ItemName.mana: ItemData(90014, True, 15),
|
||||
ItemName.attack: ItemData(90015, True, 15),
|
||||
ItemName.magic_damage: ItemData(90016, True, 15),
|
||||
ItemName.armor: ItemData(90017, True, 10),
|
||||
ItemName.equip: ItemData(90018, True, 10),
|
||||
ItemName.crit_chance: ItemData(90019, False, 5),
|
||||
ItemName.crit_damage: ItemData(90020, False, 5),
|
||||
}
|
||||
|
||||
skill_unlocks_table = {
|
||||
ItemName.down_strike: ItemData(90021, False),
|
||||
ItemName.gold_gain: ItemData(90022, False),
|
||||
ItemName.potion_efficiency: ItemData(90023, False),
|
||||
ItemName.invulnerability_time: ItemData(90024, False),
|
||||
ItemName.mana_cost_down: ItemData(90025, False),
|
||||
ItemName.death_defiance: ItemData(90026, False),
|
||||
ItemName.haggling: ItemData(90027, False),
|
||||
ItemName.random_children: ItemData(90028, False),
|
||||
}
|
||||
|
||||
blueprints_table = {
|
||||
ItemName.squire_blueprints: ItemData(90040, False),
|
||||
ItemName.silver_blueprints: ItemData(90041, False),
|
||||
ItemName.guardian_blueprints: ItemData(90042, False),
|
||||
ItemName.imperial_blueprints: ItemData(90043, False),
|
||||
ItemName.royal_blueprints: ItemData(90044, False),
|
||||
ItemName.knight_blueprints: ItemData(90045, False),
|
||||
ItemName.ranger_blueprints: ItemData(90046, False),
|
||||
ItemName.sky_blueprints: ItemData(90047, False),
|
||||
ItemName.dragon_blueprints: ItemData(90048, False),
|
||||
ItemName.slayer_blueprints: ItemData(90049, False),
|
||||
ItemName.blood_blueprints: ItemData(90050, False),
|
||||
ItemName.sage_blueprints: ItemData(90051, False),
|
||||
ItemName.retribution_blueprints: ItemData(90052, False),
|
||||
ItemName.holy_blueprints: ItemData(90053, False),
|
||||
ItemName.dark_blueprints: ItemData(90054, False),
|
||||
}
|
||||
|
||||
progressive_blueprint_table = {
|
||||
ItemName.progressive_blueprints: ItemData(90055, False),
|
||||
}
|
||||
|
||||
runes_table = {
|
||||
ItemName.vault_runes: ItemData(90060, False),
|
||||
ItemName.sprint_runes: ItemData(90061, False),
|
||||
ItemName.vampire_runes: ItemData(90062, False),
|
||||
ItemName.sky_runes: ItemData(90063, False),
|
||||
ItemName.siphon_runes: ItemData(90064, False),
|
||||
ItemName.retaliation_runes: ItemData(90065, False),
|
||||
ItemName.bounty_runes: ItemData(90066, False),
|
||||
ItemName.haste_runes: ItemData(90067, False),
|
||||
ItemName.curse_runes: ItemData(90068, False),
|
||||
ItemName.grace_runes: ItemData(90069, False),
|
||||
ItemName.balance_runes: ItemData(90070, False),
|
||||
}
|
||||
|
||||
misc_items_table = {
|
||||
ItemName.trip_stat_increase: ItemData(90030, False),
|
||||
ItemName.gold_1000: ItemData(90031, False),
|
||||
ItemName.gold_3000: ItemData(90032, False),
|
||||
ItemName.gold_5000: ItemData(90033, False),
|
||||
# ItemName.rage_trap: ItemData(90034, False),
|
||||
}
|
||||
|
||||
# Complete item table.
|
||||
item_table = {
|
||||
**vendors_table,
|
||||
**static_classes_table,
|
||||
**progressive_classes_table,
|
||||
**configurable_skill_unlocks_table,
|
||||
**skill_unlocks_table,
|
||||
**blueprints_table,
|
||||
**progressive_blueprint_table,
|
||||
**runes_table,
|
||||
**misc_items_table,
|
||||
}
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code}
|
||||
|
||||
@@ -1,90 +1,98 @@
|
||||
import typing
|
||||
from typing import Dict, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import Location
|
||||
from .Names import LocationName
|
||||
|
||||
|
||||
class LegacyLocation(Location):
|
||||
class RLLocation(Location):
|
||||
game: str = "Rogue Legacy"
|
||||
|
||||
|
||||
base_location_table = {
|
||||
# Manor Renovations
|
||||
LocationName.manor_ground_base: 91000,
|
||||
LocationName.manor_main_base: 91001,
|
||||
LocationName.manor_main_bottom_window: 91002,
|
||||
LocationName.manor_main_top_window: 91003,
|
||||
LocationName.manor_main_roof: 91004,
|
||||
LocationName.manor_left_wing_base: 91005,
|
||||
LocationName.manor_left_wing_window: 91006,
|
||||
LocationName.manor_left_wing_roof: 91007,
|
||||
LocationName.manor_left_big_base: 91008,
|
||||
LocationName.manor_left_big_upper1: 91009,
|
||||
LocationName.manor_left_big_upper2: 91010,
|
||||
LocationName.manor_left_big_windows: 91011,
|
||||
LocationName.manor_left_big_roof: 91012,
|
||||
LocationName.manor_left_far_base: 91013,
|
||||
LocationName.manor_left_far_roof: 91014,
|
||||
LocationName.manor_left_extension: 91015,
|
||||
LocationName.manor_left_tree1: 91016,
|
||||
LocationName.manor_left_tree2: 91017,
|
||||
LocationName.manor_right_wing_base: 91018,
|
||||
LocationName.manor_right_wing_window: 91019,
|
||||
LocationName.manor_right_wing_roof: 91020,
|
||||
LocationName.manor_right_big_base: 91021,
|
||||
LocationName.manor_right_big_upper: 91022,
|
||||
LocationName.manor_right_big_roof: 91023,
|
||||
LocationName.manor_right_high_base: 91024,
|
||||
LocationName.manor_right_high_upper: 91025,
|
||||
LocationName.manor_right_high_tower: 91026,
|
||||
LocationName.manor_right_extension: 91027,
|
||||
LocationName.manor_right_tree: 91028,
|
||||
LocationName.manor_observatory_base: 91029,
|
||||
LocationName.manor_observatory_scope: 91030,
|
||||
class RLLocationData(NamedTuple):
|
||||
category: str
|
||||
code: Optional[int] = None
|
||||
|
||||
@property
|
||||
def is_event_location(self):
|
||||
return self.code is None
|
||||
|
||||
|
||||
def get_locations_by_category(category: str) -> Dict[str, RLLocationData]:
|
||||
location_dict: Dict[str, RLLocationData] = {}
|
||||
for name, data in location_table.items():
|
||||
if data.category == category:
|
||||
location_dict.setdefault(name, data)
|
||||
|
||||
return location_dict
|
||||
|
||||
|
||||
location_table: Dict[str, RLLocationData] = {
|
||||
# Manor Renovation
|
||||
"Manor - Ground Road": RLLocationData("Manor", 91_000),
|
||||
"Manor - Main Base": RLLocationData("Manor", 91_001),
|
||||
"Manor - Main Bottom Window": RLLocationData("Manor", 91_002),
|
||||
"Manor - Main Top Window": RLLocationData("Manor", 91_003),
|
||||
"Manor - Main Rooftop": RLLocationData("Manor", 91_004),
|
||||
"Manor - Left Wing Base": RLLocationData("Manor", 91_005),
|
||||
"Manor - Left Wing Window": RLLocationData("Manor", 91_006),
|
||||
"Manor - Left Wing Rooftop": RLLocationData("Manor", 91_007),
|
||||
"Manor - Left Big Base": RLLocationData("Manor", 91_008),
|
||||
"Manor - Left Big Upper 1": RLLocationData("Manor", 91_009),
|
||||
"Manor - Left Big Upper 2": RLLocationData("Manor", 91_010),
|
||||
"Manor - Left Big Windows": RLLocationData("Manor", 91_011),
|
||||
"Manor - Left Big Rooftop": RLLocationData("Manor", 91_012),
|
||||
"Manor - Left Far Base": RLLocationData("Manor", 91_013),
|
||||
"Manor - Left Far Roof": RLLocationData("Manor", 91_014),
|
||||
"Manor - Left Extension": RLLocationData("Manor", 91_015),
|
||||
"Manor - Left Tree 1": RLLocationData("Manor", 91_016),
|
||||
"Manor - Left Tree 2": RLLocationData("Manor", 91_017),
|
||||
"Manor - Right Wing Base": RLLocationData("Manor", 91_018),
|
||||
"Manor - Right Wing Window": RLLocationData("Manor", 91_019),
|
||||
"Manor - Right Wing Rooftop": RLLocationData("Manor", 91_020),
|
||||
"Manor - Right Big Base": RLLocationData("Manor", 91_021),
|
||||
"Manor - Right Big Upper": RLLocationData("Manor", 91_022),
|
||||
"Manor - Right Big Rooftop": RLLocationData("Manor", 91_023),
|
||||
"Manor - Right High Base": RLLocationData("Manor", 91_024),
|
||||
"Manor - Right High Upper": RLLocationData("Manor", 91_025),
|
||||
"Manor - Right High Tower": RLLocationData("Manor", 91_026),
|
||||
"Manor - Right Extension": RLLocationData("Manor", 91_027),
|
||||
"Manor - Right Tree": RLLocationData("Manor", 91_028),
|
||||
"Manor - Observatory Base": RLLocationData("Manor", 91_029),
|
||||
"Manor - Observatory Telescope": RLLocationData("Manor", 91_030),
|
||||
|
||||
# Boss Rewards
|
||||
LocationName.boss_castle: 91100,
|
||||
LocationName.boss_forest: 91102,
|
||||
LocationName.boss_tower: 91104,
|
||||
LocationName.boss_dungeon: 91106,
|
||||
|
||||
# Special Rooms
|
||||
LocationName.special_jukebox: 91200,
|
||||
LocationName.special_painting: 91201,
|
||||
LocationName.special_cheapskate: 91202,
|
||||
LocationName.special_carnival: 91203,
|
||||
"Castle Hamson Boss Reward": RLLocationData("Boss", 91_100),
|
||||
"Forest Abkhazia Boss Reward": RLLocationData("Boss", 91_102),
|
||||
"The Maya Boss Reward": RLLocationData("Boss", 91_104),
|
||||
"Land of Darkness Boss Reward": RLLocationData("Boss", 91_106),
|
||||
|
||||
# Special Locations
|
||||
LocationName.castle: None,
|
||||
LocationName.garden: None,
|
||||
LocationName.tower: None,
|
||||
LocationName.dungeon: None,
|
||||
LocationName.fountain: None,
|
||||
"Jukebox": RLLocationData("Special", 91_200),
|
||||
"Painting": RLLocationData("Special", 91_201),
|
||||
"Cheapskate Elf's Game": RLLocationData("Special", 91_202),
|
||||
"Carnival": RLLocationData("Special", 91_203),
|
||||
|
||||
# Diaries
|
||||
**{f"Diary {i+1}": RLLocationData("Diary", 91_300 + i) for i in range(0, 25)},
|
||||
|
||||
# Chests
|
||||
**{f"Castle Hamson - Chest {i+1}": RLLocationData("Chests", 91_600 + i) for i in range(0, 50)},
|
||||
**{f"Forest Abkhazia - Chest {i+1}": RLLocationData("Chests", 91_700 + i) for i in range(0, 50)},
|
||||
**{f"The Maya - Chest {i+1}": RLLocationData("Chests", 91_800 + i) for i in range(0, 50)},
|
||||
**{f"Land of Darkness - Chest {i+1}": RLLocationData("Chests", 91_900 + i) for i in range(0, 50)},
|
||||
**{f"Chest {i+1}": RLLocationData("Chests", 92_000 + i) for i in range(0, 200)},
|
||||
|
||||
# Fairy Chests
|
||||
**{f"Castle Hamson - Fairy Chest {i+1}": RLLocationData("Fairies", 91_400 + i) for i in range(0, 15)},
|
||||
**{f"Forest Abkhazia - Fairy Chest {i+1}": RLLocationData("Fairies", 91_450 + i) for i in range(0, 15)},
|
||||
**{f"The Maya - Fairy Chest {i+1}": RLLocationData("Fairies", 91_500 + i) for i in range(0, 15)},
|
||||
**{f"Land of Darkness - Fairy Chest {i+1}": RLLocationData("Fairies", 91_550 + i) for i in range(0, 15)},
|
||||
**{f"Fairy Chest {i+1}": RLLocationData("Fairies", 92_200 + i) for i in range(0, 60)},
|
||||
}
|
||||
|
||||
diary_location_table = {f"{LocationName.diary} {i + 1}": i + 91300 for i in range(0, 25)}
|
||||
|
||||
fairy_chest_location_table = {
|
||||
**{f"{LocationName.castle} - Fairy Chest {i + 1}": i + 91400 for i in range(0, 50)},
|
||||
**{f"{LocationName.garden} - Fairy Chest {i + 1}": i + 91450 for i in range(0, 50)},
|
||||
**{f"{LocationName.tower} - Fairy Chest {i + 1}": i + 91500 for i in range(0, 50)},
|
||||
**{f"{LocationName.dungeon} - Fairy Chest {i + 1}": i + 91550 for i in range(0, 50)},
|
||||
**{f"Fairy Chest {i + 1}": i + 92200 for i in range(0, 60)},
|
||||
event_location_table: Dict[str, RLLocationData] = {
|
||||
"Castle Hamson Boss Room": RLLocationData("Event"),
|
||||
"Forest Abkhazia Boss Room": RLLocationData("Event"),
|
||||
"The Maya Boss Room": RLLocationData("Event"),
|
||||
"Land of Darkness Boss Room": RLLocationData("Event"),
|
||||
"Fountain Room": RLLocationData("Event"),
|
||||
}
|
||||
|
||||
chest_location_table = {
|
||||
**{f"{LocationName.castle} - Chest {i + 1}": i + 91600 for i in range(0, 100)},
|
||||
**{f"{LocationName.garden} - Chest {i + 1}": i + 91700 for i in range(0, 100)},
|
||||
**{f"{LocationName.tower} - Chest {i + 1}": i + 91800 for i in range(0, 100)},
|
||||
**{f"{LocationName.dungeon} - Chest {i + 1}": i + 91900 for i in range(0, 100)},
|
||||
**{f"Chest {i + 1}": i + 92000 for i in range(0, 120)},
|
||||
}
|
||||
|
||||
location_table = {
|
||||
**base_location_table,
|
||||
**diary_location_table,
|
||||
**fairy_chest_location_table,
|
||||
**chest_location_table,
|
||||
}
|
||||
|
||||
lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in location_table.items()}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
# Vendor Definitions
|
||||
blacksmith = "Blacksmith"
|
||||
enchantress = "Enchantress"
|
||||
architect = "Architect"
|
||||
|
||||
# Progressive Class Definitions
|
||||
progressive_knight = "Progressive Knights"
|
||||
progressive_mage = "Progressive Mages"
|
||||
progressive_barbarian = "Progressive Barbarians"
|
||||
progressive_knave = "Progressive Knaves"
|
||||
progressive_shinobi = "Progressive Shinobis"
|
||||
progressive_miner = "Progressive Miners"
|
||||
progressive_lich = "Progressive Liches"
|
||||
progressive_spellthief = "Progressive Spellthieves"
|
||||
|
||||
# Static Class Definitions
|
||||
knight = "Knights"
|
||||
paladin = "Paladins"
|
||||
mage = "Mages"
|
||||
archmage = "Archmages"
|
||||
barbarian = "Barbarians"
|
||||
barbarian_king = "Barbarian Kings"
|
||||
knave = "Knaves"
|
||||
assassin = "Assassins"
|
||||
shinobi = "Shinobis"
|
||||
hokage = "Hokages"
|
||||
miner = "Miners"
|
||||
spelunker = "Spelunkers"
|
||||
lich = "Lichs"
|
||||
lich_king = "Lich Kings"
|
||||
spellthief = "Spellthieves"
|
||||
spellsword = "Spellswords"
|
||||
dragon = "Dragons"
|
||||
traitor = "Traitors"
|
||||
|
||||
# Skill Unlock Definitions
|
||||
health = "Health Up"
|
||||
mana = "Mana Up"
|
||||
attack = "Attack Up"
|
||||
magic_damage = "Magic Damage Up"
|
||||
armor = "Armor Up"
|
||||
equip = "Equip Up"
|
||||
crit_chance = "Crit Chance Up"
|
||||
crit_damage = "Crit Damage Up"
|
||||
down_strike = "Down Strike Up"
|
||||
gold_gain = "Gold Gain Up"
|
||||
potion_efficiency = "Potion Efficiency Up"
|
||||
invulnerability_time = "Invulnerability Time Up"
|
||||
mana_cost_down = "Mana Cost Down"
|
||||
death_defiance = "Death Defiance"
|
||||
haggling = "Haggling"
|
||||
random_children = "Randomize Children"
|
||||
|
||||
# Misc. Definitions
|
||||
trip_stat_increase = "Triple Stat Increase"
|
||||
gold_1000 = "1000 Gold"
|
||||
gold_3000 = "3000 Gold"
|
||||
gold_5000 = "5000 Gold"
|
||||
rage_trap = "Rage Trap"
|
||||
|
||||
# Blueprint Definitions
|
||||
progressive_blueprints = "Progressive Blueprints"
|
||||
squire_blueprints = "Squire Blueprints"
|
||||
silver_blueprints = "Silver Blueprints"
|
||||
guardian_blueprints = "Guardian Blueprints"
|
||||
imperial_blueprints = "Imperial Blueprints"
|
||||
royal_blueprints = "Royal Blueprints"
|
||||
knight_blueprints = "Knight Blueprints"
|
||||
ranger_blueprints = "Ranger Blueprints"
|
||||
sky_blueprints = "Sky Blueprints"
|
||||
dragon_blueprints = "Dragon Blueprints"
|
||||
slayer_blueprints = "Slayer Blueprints"
|
||||
blood_blueprints = "Blood Blueprints"
|
||||
sage_blueprints = "Sage Blueprints"
|
||||
retribution_blueprints = "Retribution Blueprints"
|
||||
holy_blueprints = "Holy Blueprints"
|
||||
dark_blueprints = "Dark Blueprints"
|
||||
|
||||
# Rune Definitions
|
||||
vault_runes = "Vault Runes"
|
||||
sprint_runes = "Sprint Runes"
|
||||
vampire_runes = "Vampire Runes"
|
||||
sky_runes = "Sky Runes"
|
||||
siphon_runes = "Siphon Runes"
|
||||
retaliation_runes = "Retaliation Runes"
|
||||
bounty_runes = "Bounty Runes"
|
||||
haste_runes = "Haste Runes"
|
||||
curse_runes = "Curse Runes"
|
||||
grace_runes = "Grace Runes"
|
||||
balance_runes = "Balance Runes"
|
||||
|
||||
# Event Definitions
|
||||
boss_castle = "Defeat Castle Hamson Boss"
|
||||
boss_forest = "Defeat Forest Abkhazia Boss"
|
||||
boss_tower = "Defeat The Maya Boss"
|
||||
boss_dungeon = "Defeat The Land of Darkness Boss"
|
||||
boss_fountain = "Defeat The Fountain"
|
||||
@@ -1,55 +0,0 @@
|
||||
# Manor Piece Definitions
|
||||
manor_ground_base = "Manor Renovation - Ground Road"
|
||||
manor_main_base = "Manor Renovation - Main Base"
|
||||
manor_main_bottom_window = "Manor Renovation - Main Bottom Window"
|
||||
manor_main_top_window = "Manor Renovation - Main Top Window"
|
||||
manor_main_roof = "Manor Renovation - Main Rooftop"
|
||||
manor_left_wing_base = "Manor Renovation - Left Wing Base"
|
||||
manor_left_wing_window = "Manor Renovation - Left Wing Window"
|
||||
manor_left_wing_roof = "Manor Renovation - Left Wing Rooftop"
|
||||
manor_left_big_base = "Manor Renovation - Left Big Base"
|
||||
manor_left_big_upper1 = "Manor Renovation - Left Big Upper 1"
|
||||
manor_left_big_upper2 = "Manor Renovation - Left Big Upper 2"
|
||||
manor_left_big_windows = "Manor Renovation - Left Big Windows"
|
||||
manor_left_big_roof = "Manor Renovation - Left Big Rooftop"
|
||||
manor_left_far_base = "Manor Renovation - Left Far Base"
|
||||
manor_left_far_roof = "Manor Renovation - Left Far Roof"
|
||||
manor_left_extension = "Manor Renovation - Left Extension"
|
||||
manor_left_tree1 = "Manor Renovation - Left Tree 1"
|
||||
manor_left_tree2 = "Manor Renovation - Left Tree 2"
|
||||
manor_right_wing_base = "Manor Renovation - Right Wing Base"
|
||||
manor_right_wing_window = "Manor Renovation - Right Wing Window"
|
||||
manor_right_wing_roof = "Manor Renovation - Right Wing Rooftop"
|
||||
manor_right_big_base = "Manor Renovation - Right Big Base"
|
||||
manor_right_big_upper = "Manor Renovation - Right Big Upper"
|
||||
manor_right_big_roof = "Manor Renovation - Right Big Rooftop"
|
||||
manor_right_high_base = "Manor Renovation - Right High Base"
|
||||
manor_right_high_upper = "Manor Renovation - Right High Upper"
|
||||
manor_right_high_tower = "Manor Renovation - Right High Tower"
|
||||
manor_right_extension = "Manor Renovation - Right Extension"
|
||||
manor_right_tree = "Manor Renovation - Right Tree"
|
||||
manor_observatory_base = "Manor Renovation - Observatory Base"
|
||||
manor_observatory_scope = "Manor Renovation - Observatory Telescope"
|
||||
|
||||
# Boss Chest Definitions
|
||||
boss_castle = "Castle Hamson Boss"
|
||||
boss_forest = "Forest Abkhazia Boss"
|
||||
boss_tower = "The Maya Boss"
|
||||
boss_dungeon = "The Land of Darkness Boss"
|
||||
|
||||
# Special Room Definitions
|
||||
special_jukebox = "Jukebox"
|
||||
special_painting = "Painting"
|
||||
special_cheapskate = "Cheapskate Elf's Game"
|
||||
special_carnival = "Carnival"
|
||||
|
||||
# Shorthand Definitions
|
||||
diary = "Diary"
|
||||
|
||||
# Region Definitions
|
||||
outside = "Outside Castle Hamson"
|
||||
castle = "Castle Hamson"
|
||||
garden = "Forest Abkhazia"
|
||||
tower = "The Maya"
|
||||
dungeon = "The Land of Darkness"
|
||||
fountain = "Fountain Room"
|
||||
@@ -1,6 +1,6 @@
|
||||
import typing
|
||||
from typing import Dict
|
||||
|
||||
from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, OptionSet
|
||||
from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionSet
|
||||
|
||||
|
||||
class StartingGender(Choice):
|
||||
@@ -63,9 +63,9 @@ class FairyChestsPerZone(Range):
|
||||
bonuses can be found in Fairy Chests.
|
||||
"""
|
||||
display_name = "Fairy Chests Per Zone"
|
||||
range_start = 5
|
||||
range_start = 0
|
||||
range_end = 15
|
||||
default = 5
|
||||
default = 1
|
||||
|
||||
|
||||
class ChestsPerZone(Range):
|
||||
@@ -74,9 +74,9 @@ class ChestsPerZone(Range):
|
||||
gold or stat bonuses can be found in Chests.
|
||||
"""
|
||||
display_name = "Chests Per Zone"
|
||||
range_start = 15
|
||||
range_end = 30
|
||||
default = 15
|
||||
range_start = 20
|
||||
range_end = 50
|
||||
default = 20
|
||||
|
||||
|
||||
class UniversalFairyChests(Toggle):
|
||||
@@ -111,8 +111,10 @@ class Architect(Choice):
|
||||
"""
|
||||
display_name = "Architect"
|
||||
option_start_unlocked = 0
|
||||
option_normal = 2
|
||||
option_early = 1
|
||||
option_anywhere = 2
|
||||
option_disabled = 3
|
||||
alias_normal = 2
|
||||
default = 2
|
||||
|
||||
|
||||
@@ -173,7 +175,7 @@ class NumberOfChildren(Range):
|
||||
default = 3
|
||||
|
||||
|
||||
class AdditionalNames(OptionList):
|
||||
class AdditionalNames(OptionSet):
|
||||
"""
|
||||
Set of additional names your potential offspring can have. If Allow Default Names is disabled, this is the only list
|
||||
of names your children can have. The first value will also be your initial character's name depending on Starting
|
||||
@@ -337,7 +339,8 @@ class AvailableClasses(OptionSet):
|
||||
default = {"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"}
|
||||
valid_keys = {"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"}
|
||||
|
||||
legacy_options: typing.Dict[str, type(Option)] = {
|
||||
|
||||
rl_options: Dict[str, type(Option)] = {
|
||||
"starting_gender": StartingGender,
|
||||
"starting_class": StartingClass,
|
||||
"available_classes": AvailableClasses,
|
||||
|
||||
@@ -1,72 +1,125 @@
|
||||
import typing
|
||||
from typing import Dict, List, NamedTuple, Optional
|
||||
|
||||
from BaseClasses import MultiWorld, Region, RegionType, Entrance, ItemClassification
|
||||
from .Items import LegacyItem
|
||||
from .Locations import LegacyLocation, diary_location_table, location_table, base_location_table
|
||||
from .Names import LocationName, ItemName
|
||||
|
||||
prog = ItemClassification.progression
|
||||
from BaseClasses import MultiWorld, Region, RegionType, Entrance
|
||||
from .Items import RLItem
|
||||
from .Locations import RLLocation, location_table, get_locations_by_category
|
||||
|
||||
|
||||
def create_regions(world, player: int):
|
||||
class RLRegionData(NamedTuple):
|
||||
locations: Optional[List[str]]
|
||||
exits: Optional[List[str]]
|
||||
|
||||
locations: typing.List[str] = []
|
||||
|
||||
# Add required locations.
|
||||
locations += [location for location in base_location_table]
|
||||
locations += [location for location in diary_location_table]
|
||||
def create_regions(world: MultiWorld, player: int):
|
||||
regions: Dict[str, RLRegionData] = {
|
||||
"Menu": RLRegionData(None, ["Castle Hamson"]),
|
||||
"The Manor": RLRegionData([], []),
|
||||
"Castle Hamson": RLRegionData([], ["Forest Abkhazia",
|
||||
"The Maya",
|
||||
"Land of Darkness",
|
||||
"The Fountain Room",
|
||||
"The Manor"]),
|
||||
"Forest Abkhazia": RLRegionData([], []),
|
||||
"The Maya": RLRegionData([], []),
|
||||
"Land of Darkness": RLRegionData([], []),
|
||||
"The Fountain Room": RLRegionData([], None),
|
||||
}
|
||||
|
||||
# Add chests per settings.
|
||||
if world.universal_fairy_chests[player]:
|
||||
fairies = int(world.fairy_chests_per_zone[player]) * 4
|
||||
for i in range(0, fairies):
|
||||
locations += [f"Fairy Chest {i + 1}"]
|
||||
else:
|
||||
fairies = int(world.fairy_chests_per_zone[player])
|
||||
for i in range(0, fairies):
|
||||
locations += [f"{LocationName.castle} - Fairy Chest {i + 1}"]
|
||||
locations += [f"{LocationName.garden} - Fairy Chest {i + 1}"]
|
||||
locations += [f"{LocationName.tower} - Fairy Chest {i + 1}"]
|
||||
locations += [f"{LocationName.dungeon} - Fairy Chest {i + 1}"]
|
||||
# Diaries
|
||||
for diary in range(0, 25):
|
||||
region: str
|
||||
if 0 <= diary < 6:
|
||||
region = "Castle Hamson"
|
||||
elif 6 <= diary < 12:
|
||||
region = "Forest Abkhazia"
|
||||
elif 12 <= diary < 18:
|
||||
region = "The Maya"
|
||||
elif 18 <= diary < 24:
|
||||
region = "Land of Darkness"
|
||||
else:
|
||||
region = "The Fountain Room"
|
||||
|
||||
if world.universal_chests[player]:
|
||||
chests = int(world.chests_per_zone[player]) * 4
|
||||
for i in range(0, chests):
|
||||
locations += [f"Chest {i + 1}"]
|
||||
else:
|
||||
chests = int(world.chests_per_zone[player])
|
||||
for i in range(0, chests):
|
||||
locations += [f"{LocationName.castle} - Chest {i + 1}"]
|
||||
locations += [f"{LocationName.garden} - Chest {i + 1}"]
|
||||
locations += [f"{LocationName.tower} - Chest {i + 1}"]
|
||||
locations += [f"{LocationName.dungeon} - Chest {i + 1}"]
|
||||
regions[region].locations.append(f"Diary {diary + 1}")
|
||||
|
||||
# Manor & Special
|
||||
for manor in get_locations_by_category("Manor").keys():
|
||||
regions["The Manor"].locations.append(manor)
|
||||
for special in get_locations_by_category("Special").keys():
|
||||
regions["Castle Hamson"].locations.append(special)
|
||||
|
||||
# Boss Rewards
|
||||
regions["Castle Hamson"].locations.append("Castle Hamson Boss Reward")
|
||||
regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Reward")
|
||||
regions["The Maya"].locations.append("The Maya Boss Reward")
|
||||
regions["Land of Darkness"].locations.append("Land of Darkness Boss Reward")
|
||||
|
||||
# Events
|
||||
regions["Castle Hamson"].locations.append("Castle Hamson Boss Room")
|
||||
regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Room")
|
||||
regions["The Maya"].locations.append("The Maya Boss Room")
|
||||
regions["Land of Darkness"].locations.append("Land of Darkness Boss Room")
|
||||
regions["The Fountain Room"].locations.append("Fountain Room")
|
||||
|
||||
# Chests
|
||||
chests = int(world.chests_per_zone[player])
|
||||
for i in range(0, chests):
|
||||
if world.universal_chests[player]:
|
||||
regions["Castle Hamson"].locations.append(f"Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Chest {i + 1 + chests}")
|
||||
regions["The Maya"].locations.append(f"Chest {i + 1 + (chests * 2)}")
|
||||
regions["Land of Darkness"].locations.append(f"Chest {i + 1 + (chests * 3)}")
|
||||
else:
|
||||
regions["Castle Hamson"].locations.append(f"Castle Hamson - Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Chest {i + 1}")
|
||||
regions["The Maya"].locations.append(f"The Maya - Chest {i + 1}")
|
||||
regions["Land of Darkness"].locations.append(f"Land of Darkness - Chest {i + 1}")
|
||||
|
||||
# Fairy Chests
|
||||
chests = int(world.fairy_chests_per_zone[player])
|
||||
for i in range(0, chests):
|
||||
if world.universal_fairy_chests[player]:
|
||||
regions["Castle Hamson"].locations.append(f"Fairy Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Fairy Chest {i + 1 + chests}")
|
||||
regions["The Maya"].locations.append(f"Fairy Chest {i + 1 + (chests * 2)}")
|
||||
regions["Land of Darkness"].locations.append(f"Fairy Chest {i + 1 + (chests * 3)}")
|
||||
else:
|
||||
regions["Castle Hamson"].locations.append(f"Castle Hamson - Fairy Chest {i + 1}")
|
||||
regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Fairy Chest {i + 1}")
|
||||
regions["The Maya"].locations.append(f"The Maya - Fairy Chest {i + 1}")
|
||||
regions["Land of Darkness"].locations.append(f"Land of Darkness - Fairy Chest {i + 1}")
|
||||
|
||||
# Set up the regions correctly.
|
||||
world.regions += [
|
||||
create_region(world, player, "Menu", None, [LocationName.outside]),
|
||||
create_region(world, player, LocationName.castle, locations),
|
||||
]
|
||||
for name, data in regions.items():
|
||||
world.regions.append(create_region(world, player, name, data.locations, data.exits))
|
||||
|
||||
# Connect entrances and set up events.
|
||||
world.get_entrance(LocationName.outside, player).connect(world.get_region(LocationName.castle, player))
|
||||
world.get_location(LocationName.castle, player).place_locked_item(LegacyItem(ItemName.boss_castle, prog, None, player))
|
||||
world.get_location(LocationName.garden, player).place_locked_item(LegacyItem(ItemName.boss_forest, prog, None, player))
|
||||
world.get_location(LocationName.tower, player).place_locked_item(LegacyItem(ItemName.boss_tower, prog, None, player))
|
||||
world.get_location(LocationName.dungeon, player).place_locked_item(LegacyItem(ItemName.boss_dungeon, prog, None, player))
|
||||
world.get_location(LocationName.fountain, player).place_locked_item(LegacyItem(ItemName.boss_fountain, prog, None, player))
|
||||
world.get_entrance("Castle Hamson", player).connect(world.get_region("Castle Hamson", player))
|
||||
world.get_entrance("The Manor", player).connect(world.get_region("The Manor", player))
|
||||
world.get_entrance("Forest Abkhazia", player).connect(world.get_region("Forest Abkhazia", player))
|
||||
world.get_entrance("The Maya", player).connect(world.get_region("The Maya", player))
|
||||
world.get_entrance("Land of Darkness", player).connect(world.get_region("Land of Darkness", player))
|
||||
world.get_entrance("The Fountain Room", player).connect(world.get_region("The Fountain Room", player))
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
# Shamelessly stolen from the ROR2 definition, lol
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = location_table.get(location, 0)
|
||||
location = LegacyLocation(player, location, loc_id, ret)
|
||||
for loc_name in locations:
|
||||
loc_data = location_table.get(loc_name)
|
||||
location = RLLocation(player, loc_name, loc_data.code if loc_data else None, ret)
|
||||
|
||||
# Special rule handling for fairy chests.
|
||||
if "Fairy" in loc_name:
|
||||
location.access_rule = lambda state: state.has("Dragons", player) or (
|
||||
state.has("Enchantress", player) and (
|
||||
state.has("Vault Runes", player) or
|
||||
state.has("Sprint Runes", player) or
|
||||
state.has("Sky Runes", player)))
|
||||
ret.locations.append(location)
|
||||
if exits:
|
||||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
|
||||
entrance = Entrance(player, exit, ret)
|
||||
ret.exits.append(entrance)
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
@@ -1,177 +1,86 @@
|
||||
from BaseClasses import MultiWorld
|
||||
from .Names import LocationName, ItemName
|
||||
from BaseClasses import MultiWorld, CollectionState
|
||||
|
||||
from ..AutoWorld import LogicMixin
|
||||
from ..generic.Rules import set_rule
|
||||
|
||||
|
||||
class LegacyLogic(LogicMixin):
|
||||
def _legacy_has_any_vendors(self, player: int) -> bool:
|
||||
return self.has_any({ItemName.blacksmith, ItemName.enchantress}, player)
|
||||
def has_any_vendors(self: CollectionState, player: int) -> bool:
|
||||
return self.has_any({"Blacksmith", "Enchantress"}, player)
|
||||
|
||||
def _legacy_has_all_vendors(self, player: int) -> bool:
|
||||
return self.has_all({ItemName.blacksmith, ItemName.enchantress}, player)
|
||||
def has_all_vendors(self: CollectionState, player: int) -> bool:
|
||||
return self.has_all({"Blacksmith", "Enchantress"}, player)
|
||||
|
||||
def _legacy_has_stat_upgrades(self, player: int, amount: int) -> bool:
|
||||
return self._legacy_stat_upgrade_count(player) >= amount
|
||||
def has_stat_upgrades(self, player: int, amount: int) -> bool:
|
||||
return self.stat_upgrade_count(player) >= amount
|
||||
|
||||
def _legacy_total_stat_upgrades_count(self, player: int) -> int:
|
||||
return int(self.world.health_pool[player]) + \
|
||||
int(self.world.mana_pool[player]) + \
|
||||
int(self.world.attack_pool[player]) + \
|
||||
int(self.world.magic_damage_pool[player]) + \
|
||||
int(self.world.armor_pool[player]) + \
|
||||
int(self.world.equip_pool[player])
|
||||
def total_stat_upgrades_count(self, player: int) -> int:
|
||||
return int(self.multiworld.health_pool[player]) + \
|
||||
int(self.multiworld.mana_pool[player]) + \
|
||||
int(self.multiworld.attack_pool[player]) + \
|
||||
int(self.multiworld.magic_damage_pool[player])
|
||||
|
||||
def _legacy_stat_upgrade_count(self, player: int) -> int:
|
||||
return self.item_count(ItemName.health, player) + self.item_count(ItemName.mana, player) + \
|
||||
self.item_count(ItemName.attack, player) + self.item_count(ItemName.magic_damage, player) + \
|
||||
self.item_count(ItemName.armor, player) + self.item_count(ItemName.equip, player)
|
||||
def stat_upgrade_count(self: CollectionState, player: int) -> int:
|
||||
return self.item_count("Health Up", player) + self.item_count("Mana Up", player) + \
|
||||
self.item_count("Attack Up", player) + self.item_count("Magic Damage Up", player)
|
||||
|
||||
|
||||
def set_rules(world: MultiWorld, player: int):
|
||||
# Check for duplicate names.
|
||||
if len(set(world.additional_lady_names[player].value)) != len(world.additional_lady_names[player].value):
|
||||
raise Exception(f"Duplicate values are not allowed in additional_lady_names.")
|
||||
if len(set(world.additional_sir_names[player].value)) != len(world.additional_sir_names[player].value):
|
||||
raise Exception(f"Duplicate values are not allowed in additional_sir_names.")
|
||||
|
||||
if not world.allow_default_names[player]:
|
||||
# Check for quantity.
|
||||
name_count = len(world.additional_lady_names[player].value)
|
||||
if name_count < int(world.number_of_children[player]):
|
||||
raise Exception(f"allow_default_names is off, but not enough names are defined in additional_lady_names. Expected {int(world.number_of_children[player])}, Got {name_count}")
|
||||
|
||||
name_count = len(world.additional_sir_names[player].value)
|
||||
if name_count < int(world.number_of_children[player]):
|
||||
raise Exception(f"allow_default_names is off, but not enough names are defined in additional_sir_names. Expected {int(world.number_of_children[player])}, Got {name_count}")
|
||||
|
||||
# Chests
|
||||
if world.universal_chests[player]:
|
||||
for i in range(0, world.chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 1)}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 2)}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 3)}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
else:
|
||||
for i in range(0, world.chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"{LocationName.garden} - Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"{LocationName.tower} - Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"{LocationName.dungeon} - Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
|
||||
# Fairy Chests
|
||||
if world.universal_fairy_chests[player]:
|
||||
for i in range(0, world.fairy_chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 1)}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 2)}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 3)}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
else:
|
||||
for i in range(0, world.fairy_chests_per_zone[player]):
|
||||
set_rule(world.get_location(f"{LocationName.garden} - Fairy Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"{LocationName.tower} - Fairy Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"{LocationName.dungeon} - Fairy Chest {i + 1}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
|
||||
# Vendors
|
||||
if world.vendors[player] == "early":
|
||||
set_rule(world.get_location(LocationName.boss_castle, player),
|
||||
lambda state: state._legacy_has_all_vendors(player))
|
||||
elif world.vendors[player] == "normal":
|
||||
set_rule(world.get_location(LocationName.garden, player),
|
||||
lambda state: state._legacy_has_any_vendors(player))
|
||||
|
||||
# Diaries
|
||||
for i in range(0, 5):
|
||||
set_rule(world.get_location(f"Diary {i + 6}", player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(f"Diary {i + 11}", player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(f"Diary {i + 16}", player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(f"Diary {i + 21}", player),
|
||||
lambda state: state.has(ItemName.boss_dungeon, player))
|
||||
if world.vendors[player] == "normal":
|
||||
set_rule(world.get_location("Forest Abkhazia Boss Reward", player),
|
||||
lambda state: state.has_all_vendors(player))
|
||||
|
||||
# Scale each manor location.
|
||||
set_rule(world.get_location(LocationName.manor_left_wing_window, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_wing_roof, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_wing_window, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_wing_roof, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_base, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_big_base, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_tree1, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_tree2, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_tree, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_upper1, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_upper2, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_windows, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_big_roof, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_far_base, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_far_roof, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_left_extension, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_big_upper, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_big_roof, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_extension, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_high_base, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_high_upper, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_right_high_tower, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_observatory_base, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.manor_observatory_scope, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
manor_rules = {
|
||||
"Defeat Khidr" if world.khidr[player] == "vanilla" else "Defeat Neo Khidr": [
|
||||
"Manor - Left Wing Window",
|
||||
"Manor - Left Wing Rooftop",
|
||||
"Manor - Right Wing Window",
|
||||
"Manor - Right Wing Rooftop",
|
||||
"Manor - Left Big Base",
|
||||
"Manor - Right Big Base",
|
||||
"Manor - Left Tree 1",
|
||||
"Manor - Left Tree 2",
|
||||
"Manor - Right Tree",
|
||||
],
|
||||
"Defeat Alexander" if world.alexander[player] == "vanilla" else "Defeat Alexander IV": [
|
||||
"Manor - Left Big Upper 1",
|
||||
"Manor - Left Big Upper 2",
|
||||
"Manor - Left Big Windows",
|
||||
"Manor - Left Big Rooftop",
|
||||
"Manor - Left Far Base",
|
||||
"Manor - Left Far Roof",
|
||||
"Manor - Left Extension",
|
||||
"Manor - Right Big Upper",
|
||||
"Manor - Right Big Rooftop",
|
||||
"Manor - Right Extension",
|
||||
],
|
||||
"Defeat Ponce de Leon" if world.leon[player] == "vanilla" else "Defeat Ponce de Freon": [
|
||||
"Manor - Right High Base",
|
||||
"Manor - Right High Upper",
|
||||
"Manor - Right High Tower",
|
||||
"Manor - Observatory Base",
|
||||
"Manor - Observatory Telescope",
|
||||
]
|
||||
}
|
||||
|
||||
for event, locations in manor_rules.items():
|
||||
for location in locations:
|
||||
set_rule(world.get_location(location, player), lambda state: state.has(event, player))
|
||||
|
||||
# Standard Zone Progression
|
||||
set_rule(world.get_location(LocationName.garden, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.125 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.tower, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.3125 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.dungeon, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.5 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_tower, player))
|
||||
world.get_entrance("Forest Abkhazia", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.125 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Khidr", player) or state.has("Defeat Neo Khidr", player)))
|
||||
world.get_entrance("The Maya", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.25 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Alexander", player) or state.has("Defeat Alexander IV", player)))
|
||||
world.get_entrance("Land of Darkness", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.375 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Ponce de Leon", player) or state.has("Defeat Ponce de Freon", player)))
|
||||
world.get_entrance("The Fountain Room", player).access_rule = \
|
||||
(lambda state: state.has_stat_upgrades(player, 0.5 * state.total_stat_upgrades_count(player)) and
|
||||
(state.has("Defeat Herodotus", player) or state.has("Defeat Astrodotus", player)))
|
||||
|
||||
# Bosses
|
||||
set_rule(world.get_location(LocationName.boss_castle, player),
|
||||
lambda state: state.has(ItemName.boss_castle, player))
|
||||
set_rule(world.get_location(LocationName.boss_forest, player),
|
||||
lambda state: state.has(ItemName.boss_forest, player))
|
||||
set_rule(world.get_location(LocationName.boss_tower, player),
|
||||
lambda state: state.has(ItemName.boss_tower, player))
|
||||
set_rule(world.get_location(LocationName.boss_dungeon, player),
|
||||
lambda state: state.has(ItemName.boss_dungeon, player))
|
||||
set_rule(world.get_location(LocationName.fountain, player),
|
||||
lambda state: state._legacy_has_stat_upgrades(player, 0.625 * state._legacy_total_stat_upgrades_count(player))
|
||||
and state.has(ItemName.boss_castle, player)
|
||||
and state.has(ItemName.boss_forest, player)
|
||||
and state.has(ItemName.boss_tower, player)
|
||||
and state.has(ItemName.boss_dungeon, player))
|
||||
|
||||
world.completion_condition[player] = lambda state: state.has(ItemName.boss_fountain, player)
|
||||
world.completion_condition[player] = lambda state: state.has("Defeat The Fountain", player)
|
||||
|
||||
@@ -36,4 +36,3 @@ traits = [
|
||||
"Glaucoma",
|
||||
"Adopted",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,176 +1,265 @@
|
||||
import typing
|
||||
from typing import List
|
||||
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from .Items import LegacyItem, ItemData, item_table, vendors_table, static_classes_table, progressive_classes_table, \
|
||||
skill_unlocks_table, blueprints_table, runes_table, misc_items_table
|
||||
from .Locations import LegacyLocation, location_table, base_location_table
|
||||
from .Options import legacy_options
|
||||
from BaseClasses import Tutorial
|
||||
from .Items import RLItem, RLItemData, event_item_table, item_table, get_items_by_category
|
||||
from .Locations import RLLocation, location_table
|
||||
from .Options import rl_options
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules
|
||||
from .Names import ItemName
|
||||
from ..AutoWorld import World, WebWorld
|
||||
|
||||
|
||||
class LegacyWeb(WebWorld):
|
||||
class RLWeb(WebWorld):
|
||||
theme = "stone"
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, multiworld, and related software.",
|
||||
"A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, "
|
||||
"multiworld, and related software.",
|
||||
"English",
|
||||
"rogue-legacy_en.md",
|
||||
"rogue-legacy/en",
|
||||
["Phar"]
|
||||
)]
|
||||
bug_report_page = "https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=" \
|
||||
"report-an-issue---.md&title=%5BIssue%5D"
|
||||
|
||||
|
||||
class LegacyWorld(World):
|
||||
class RLWorld(World):
|
||||
"""
|
||||
Rogue Legacy is a genealogical rogue-"LITE" where anyone can be a hero. Each time you die, your child will succeed
|
||||
you. Every child is unique. One child might be colorblind, another might have vertigo-- they could even be a dwarf.
|
||||
But that's OK, because no one is perfect, and you don't have to be to succeed.
|
||||
"""
|
||||
game: str = "Rogue Legacy"
|
||||
option_definitions = legacy_options
|
||||
topology_present = False
|
||||
data_version = 3
|
||||
required_client_version = (0, 2, 3)
|
||||
web = LegacyWeb()
|
||||
option_definitions = rl_options
|
||||
topology_present = True
|
||||
data_version = 4
|
||||
required_client_version = (0, 3, 5)
|
||||
web = RLWeb()
|
||||
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
location_name_to_id = location_table
|
||||
location_name_to_id = {name: data.code for name, data in location_table.items()}
|
||||
|
||||
def _get_slot_data(self):
|
||||
return {
|
||||
"starting_gender": self.world.starting_gender[self.player],
|
||||
"starting_class": self.world.starting_class[self.player],
|
||||
"new_game_plus": self.world.new_game_plus[self.player],
|
||||
"fairy_chests_per_zone": self.world.fairy_chests_per_zone[self.player],
|
||||
"chests_per_zone": self.world.chests_per_zone[self.player],
|
||||
"universal_fairy_chests": self.world.universal_fairy_chests[self.player],
|
||||
"universal_chests": self.world.universal_chests[self.player],
|
||||
"vendors": self.world.vendors[self.player],
|
||||
"architect_fee": self.world.architect_fee[self.player],
|
||||
"disable_charon": self.world.disable_charon[self.player],
|
||||
"require_purchasing": self.world.require_purchasing[self.player],
|
||||
"gold_gain_multiplier": self.world.gold_gain_multiplier[self.player],
|
||||
"number_of_children": self.world.number_of_children[self.player],
|
||||
"khidr": self.world.khidr[self.player],
|
||||
"alexander": self.world.alexander[self.player],
|
||||
"leon": self.world.leon[self.player],
|
||||
"herodotus": self.world.herodotus[self.player],
|
||||
"allow_default_names": self.world.allow_default_names[self.player],
|
||||
"additional_sir_names": self.world.additional_sir_names[self.player],
|
||||
"additional_lady_names": self.world.additional_lady_names[self.player],
|
||||
"death_link": self.world.death_link[self.player],
|
||||
}
|
||||
item_pool: List[RLItem] = []
|
||||
prefill_items: List[RLItem] = []
|
||||
|
||||
def _create_items(self, name: str):
|
||||
data = item_table[name]
|
||||
return [self.create_item(name) for _ in range(data.quantity)]
|
||||
def setting(self, name: str):
|
||||
return getattr(self.multiworld, name)[self.player]
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = self._get_slot_data()
|
||||
for option_name in legacy_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
return {option_name: self.setting(option_name).value for option_name in rl_options}
|
||||
|
||||
return slot_data
|
||||
def generate_early(self):
|
||||
self.prefill_items = []
|
||||
|
||||
# Check validation of names.
|
||||
additional_lady_names = len(self.setting("additional_lady_names").value)
|
||||
additional_sir_names = len(self.setting("additional_sir_names").value)
|
||||
if not self.setting("allow_default_names"):
|
||||
# Check for max_quantity.
|
||||
if additional_lady_names < int(self.setting("number_of_children")):
|
||||
raise Exception(
|
||||
f"allow_default_names is off, but not enough names are defined in additional_lady_names. "
|
||||
f"Expected {int(self.setting('number_of_children'))}, Got {additional_lady_names}")
|
||||
|
||||
if additional_sir_names < int(self.setting("number_of_children")):
|
||||
raise Exception(
|
||||
f"allow_default_names is off, but not enough names are defined in additional_sir_names. "
|
||||
f"Expected {int(self.setting('number_of_children'))}, Got {additional_sir_names}")
|
||||
|
||||
if self.setting("vendors") == "early":
|
||||
self.prefill_items += [self.create_item("Blacksmith"), self.create_item("Enchantress")]
|
||||
|
||||
if self.setting("architect") == "early":
|
||||
self.prefill_items += [self.create_item("Architect")]
|
||||
|
||||
def generate_basic(self):
|
||||
itempool: typing.List[LegacyItem] = []
|
||||
total_required_locations = 64 + (self.world.chests_per_zone[self.player] * 4) + (self.world.fairy_chests_per_zone[self.player] * 4)
|
||||
self.item_pool = []
|
||||
total_locations = 64 + (self.setting("chests_per_zone") * 4) + (self.setting("fairy_chests_per_zone") * 4)
|
||||
|
||||
# Fill item pool with all required items
|
||||
for item in {**skill_unlocks_table, **runes_table}:
|
||||
# if Haggling, do not add if Disable Charon.
|
||||
if item == ItemName.haggling and self.world.disable_charon[self.player] == 1:
|
||||
# Add items to item pool. Anything with a "continue" will not be added to the item pool.
|
||||
for name, data in item_table.items():
|
||||
quantity = data.max_quantity
|
||||
|
||||
# Architect
|
||||
if name == "Architect":
|
||||
if self.setting("architect") == "disabled" or self.setting("architect") == "early":
|
||||
continue
|
||||
if self.setting("architect") == "start_unlocked":
|
||||
self.multiworld.push_precollected(self.create_item(name))
|
||||
continue
|
||||
|
||||
# Blacksmith and Enchantress
|
||||
if name == "Blacksmith" or name == "Enchantress":
|
||||
if self.setting("vendors") == "start_unlocked":
|
||||
self.multiworld.push_precollected(self.create_item(name))
|
||||
continue
|
||||
if self.setting("vendors") == "early":
|
||||
continue
|
||||
|
||||
# Haggling
|
||||
if name == "Haggling" and self.setting("disable_charon"):
|
||||
continue
|
||||
itempool += self._create_items(item)
|
||||
|
||||
# Blueprints
|
||||
if self.world.progressive_blueprints[self.player]:
|
||||
itempool += [self.create_item(ItemName.progressive_blueprints) for _ in range(15)]
|
||||
else:
|
||||
for item in blueprints_table:
|
||||
itempool += self._create_items(item)
|
||||
# Blueprints
|
||||
if data.category == "Blueprints":
|
||||
# No progressive blueprints if progressive_blueprints are disabled.
|
||||
if name == "Progressive Blueprints" and not self.setting("progressive_blueprints"):
|
||||
continue
|
||||
# No distinct blueprints if progressive_blueprints are enabled.
|
||||
elif name != "Progressive Blueprints" and self.setting("progressive_blueprints"):
|
||||
continue
|
||||
|
||||
# Check Pool settings to add a certain amount of these items.
|
||||
itempool += [self.create_item(ItemName.health) for _ in range(self.world.health_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.mana) for _ in range(self.world.mana_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.attack) for _ in range(self.world.attack_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.magic_damage) for _ in range(self.world.magic_damage_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.armor) for _ in range(self.world.armor_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.equip) for _ in range(self.world.equip_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.crit_chance) for _ in range(self.world.crit_chance_pool[self.player])]
|
||||
itempool += [self.create_item(ItemName.crit_damage) for _ in range(self.world.crit_damage_pool[self.player])]
|
||||
# Classes
|
||||
if data.category == "Classes":
|
||||
if name == "Progressive Knights":
|
||||
if "Knight" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
classes = self.world.available_classes[self.player]
|
||||
if "Dragon" in classes:
|
||||
itempool.append(self.create_item(ItemName.dragon))
|
||||
if "Traitor" in classes:
|
||||
itempool.append(self.create_item(ItemName.traitor))
|
||||
if self.world.starting_class[self.player] == "knight":
|
||||
itempool.append(self.create_item(ItemName.progressive_knight))
|
||||
elif "Knight" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_knight))
|
||||
if self.world.starting_class[self.player] == "mage":
|
||||
itempool.append(self.create_item(ItemName.progressive_mage))
|
||||
elif "Mage" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_mage))
|
||||
if self.world.starting_class[self.player] == "barbarian":
|
||||
itempool.append(self.create_item(ItemName.progressive_barbarian))
|
||||
elif "Barbarian" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_barbarian))
|
||||
if self.world.starting_class[self.player] == "knave":
|
||||
itempool.append(self.create_item(ItemName.progressive_knave))
|
||||
elif "Knave" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_knave))
|
||||
if self.world.starting_class[self.player] == "shinobi":
|
||||
itempool.append(self.create_item(ItemName.progressive_shinobi))
|
||||
elif "Shinobi" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_shinobi))
|
||||
if self.world.starting_class[self.player] == "miner":
|
||||
itempool.append(self.create_item(ItemName.progressive_miner))
|
||||
elif "Miner" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_miner))
|
||||
if self.world.starting_class[self.player] == "lich":
|
||||
itempool.append(self.create_item(ItemName.progressive_lich))
|
||||
elif "Lich" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_lich))
|
||||
if self.world.starting_class[self.player] == "spellthief":
|
||||
itempool.append(self.create_item(ItemName.progressive_spellthief))
|
||||
elif "Spellthief" in classes:
|
||||
itempool.extend(self._create_items(ItemName.progressive_spellthief))
|
||||
if self.setting("starting_class") == "knight":
|
||||
quantity = 1
|
||||
if name == "Progressive Mages":
|
||||
if "Mage" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Check if we need to start with these vendors or put them in the pool.
|
||||
if self.world.vendors[self.player] == "start_unlocked":
|
||||
self.world.push_precollected(self.world.create_item(ItemName.blacksmith, self.player))
|
||||
self.world.push_precollected(self.world.create_item(ItemName.enchantress, self.player))
|
||||
else:
|
||||
itempool += [self.create_item(ItemName.blacksmith), self.create_item(ItemName.enchantress)]
|
||||
if self.setting("starting_class") == "mage":
|
||||
quantity = 1
|
||||
if name == "Progressive Barbarians":
|
||||
if "Barbarian" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Add Architect.
|
||||
if self.world.architect[self.player] == "start_unlocked":
|
||||
self.world.push_precollected(self.world.create_item(ItemName.architect, self.player))
|
||||
elif self.world.architect[self.player] != "disabled":
|
||||
itempool.append(self.create_item(ItemName.architect))
|
||||
if self.setting("starting_class") == "barbarian":
|
||||
quantity = 1
|
||||
if name == "Progressive Knaves":
|
||||
if "Knave" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Fill item pool with the remaining
|
||||
for _ in range(len(itempool), total_required_locations):
|
||||
item = self.world.random.choice(list(misc_items_table.keys()))
|
||||
itempool.append(self.create_item(item))
|
||||
if self.setting("starting_class") == "knave":
|
||||
quantity = 1
|
||||
if name == "Progressive Miners":
|
||||
if "Miner" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
self.world.itempool += itempool
|
||||
if self.setting("starting_class") == "miner":
|
||||
quantity = 1
|
||||
if name == "Progressive Shinobis":
|
||||
if "Shinobi" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
if self.setting("starting_class") == "shinobi":
|
||||
quantity = 1
|
||||
if name == "Progressive Liches":
|
||||
if "Lich" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
if self.setting("starting_class") == "lich":
|
||||
quantity = 1
|
||||
if name == "Progressive Spellthieves":
|
||||
if "Spellthief" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
if self.setting("starting_class") == "spellthief":
|
||||
quantity = 1
|
||||
if name == "Dragons":
|
||||
if "Dragon" not in self.setting("available_classes"):
|
||||
continue
|
||||
if name == "Traitors":
|
||||
if "Traitor" not in self.setting("available_classes"):
|
||||
continue
|
||||
|
||||
# Skills
|
||||
if name == "Health Up":
|
||||
quantity = self.setting("health_pool")
|
||||
elif name == "Mana Up":
|
||||
quantity = self.setting("mana_pool")
|
||||
elif name == "Attack Up":
|
||||
quantity = self.setting("attack_pool")
|
||||
elif name == "Magic Damage Up":
|
||||
quantity = self.setting("magic_damage_pool")
|
||||
elif name == "Armor Up":
|
||||
quantity = self.setting("armor_pool")
|
||||
elif name == "Equip Up":
|
||||
quantity = self.setting("equip_pool")
|
||||
elif name == "Crit Chance Up":
|
||||
quantity = self.setting("crit_chance_pool")
|
||||
elif name == "Crit Damage Up":
|
||||
quantity = self.setting("crit_damage_pool")
|
||||
|
||||
# Ignore filler, it will be added in a later stage.
|
||||
if data.category == "Filler":
|
||||
continue
|
||||
|
||||
self.item_pool += [self.create_item(name) for _ in range(0, quantity)]
|
||||
|
||||
# Fill any empty locations with filler items.
|
||||
while len(self.item_pool) + len(self.prefill_items) < total_locations:
|
||||
self.item_pool.append(self.create_item(self.get_filler_item_name()))
|
||||
|
||||
self.multiworld.itempool += self.item_pool
|
||||
|
||||
def pre_fill(self) -> None:
|
||||
reachable = [loc for loc in self.multiworld.get_reachable_locations(player=self.player) if not loc.item]
|
||||
self.multiworld.random.shuffle(reachable)
|
||||
items = self.prefill_items.copy()
|
||||
for item in items:
|
||||
reachable.pop().place_locked_item(item)
|
||||
|
||||
def get_pre_fill_items(self) -> List[RLItem]:
|
||||
return self.prefill_items
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(list(misc_items_table.keys()))
|
||||
fillers = get_items_by_category("Filler")
|
||||
weights = [data.weight for data in fillers.values()]
|
||||
return self.multiworld.random.choices([filler for filler in fillers.keys()], weights, k=1)[0]
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
def create_item(self, name: str) -> RLItem:
|
||||
data = item_table[name]
|
||||
return LegacyItem(name, ItemClassification.progression if data.progression else ItemClassification.filler, data.code, self.player)
|
||||
return RLItem(name, data.classification, data.code, self.player)
|
||||
|
||||
def create_event(self, name: str) -> RLItem:
|
||||
data = event_item_table[name]
|
||||
return RLItem(name, data.classification, data.code, self.player)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.multiworld, self.player)
|
||||
self._place_events()
|
||||
|
||||
def _place_events(self):
|
||||
# Fountain
|
||||
self.multiworld.get_location("Fountain Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat The Fountain"))
|
||||
|
||||
# Khidr / Neo Khidr
|
||||
if self.setting("khidr") == "vanilla":
|
||||
self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Khidr"))
|
||||
else:
|
||||
self.multiworld.get_location("Castle Hamson Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Neo Khidr"))
|
||||
|
||||
# Alexander / Alexander IV
|
||||
if self.setting("alexander") == "vanilla":
|
||||
self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Alexander"))
|
||||
else:
|
||||
self.multiworld.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Alexander IV"))
|
||||
|
||||
# Ponce de Leon / Ponce de Freon
|
||||
if self.setting("leon") == "vanilla":
|
||||
self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Ponce de Leon"))
|
||||
else:
|
||||
self.multiworld.get_location("The Maya Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Ponce de Freon"))
|
||||
|
||||
# Herodotus / Astrodotus
|
||||
if self.setting("herodotus") == "vanilla":
|
||||
self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Herodotus"))
|
||||
else:
|
||||
self.multiworld.get_location("Land of Darkness Boss Room", self.player).place_locked_item(
|
||||
self.create_event("Defeat Astrodotus"))
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
## Where is the settings page?
|
||||
|
||||
The [player settings page for this game](../player-settings) is located contains all the options you need to configure
|
||||
and export a config file.
|
||||
The [player settings page for this game](../player-settings) contains most of the options you need to
|
||||
configure and export a config file. Some settings can only be made via YAML, but an explaination can be found in the
|
||||
[template yaml here](../../../static/generated/configs/Rogue%20Legacy.yaml).
|
||||
|
||||
## What does randomization do to this game?
|
||||
|
||||
You are not able to buy skill upgrades in the manor upgrade screen, and instead, need to find them in order to level up
|
||||
your character to make fighting the 5 bosses easier.
|
||||
Rogue Legacy Randomizer takes all the classes, skills, runes, and blueprints and spreads them out into chests, the manor
|
||||
upgrade screen, bosses, and some special individual locations. The goal is to become powerful enough to defeat the four
|
||||
zone bosses and then defeat The Fountain.
|
||||
|
||||
## What items and locations get shuffled?
|
||||
|
||||
@@ -16,6 +18,8 @@ All the skill upgrades, class upgrades, runes packs, and equipment packs are shu
|
||||
checks, chests and fairy chests, and boss rewards. Skill upgrades are also grouped in packs of 5 to make the finding of
|
||||
stats less of a chore. Runes and Equipment are also grouped together.
|
||||
|
||||
Some additional locations that can contain items are the Jukebox, the Portraits, and the mini-game rewards.
|
||||
|
||||
## Which items can be in another player's world?
|
||||
|
||||
Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit
|
||||
@@ -25,3 +29,8 @@ certain items to your own world.
|
||||
|
||||
When the player receives an item, your character will hold the item above their head and display it to the world. It's
|
||||
good for business!
|
||||
|
||||
## What do I do if I encounter a bug with the game?
|
||||
|
||||
Please reach out to Phar#4444 on Discord or you can drop a bug report on the
|
||||
[GitHub page for Rogue Legacy Randomizer](https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=report-an-issue---.md&title=%5BIssue%5D).
|
||||
@@ -28,25 +28,8 @@ provides an alternative one to the default values.
|
||||
Once you have entered the required values, you go to Connect and then select Confirm on the "Ready to Start" screen. Now
|
||||
you're off to start your legacy!
|
||||
|
||||
## Manual Installation
|
||||
## Recommended Installation Instructions
|
||||
|
||||
In order to run Rogue Legacy Randomizer you will need to have Rogue Legacy installed on your local machine. Extract the
|
||||
Randomizer release into a desired folder **outside** of your Rogue Legacy install. Copy the following files from your
|
||||
Rogue Legacy install into the main directory of your Rogue Legacy Randomizer install:
|
||||
|
||||
- DS2DEngine.dll
|
||||
- InputSystem.dll
|
||||
- Nuclex.Input.dll
|
||||
- SpriteSystem.dll
|
||||
- Tweener.dll
|
||||
|
||||
And copy the directory from your Rogue Legacy install as well into the main directory of your Rogue Legacy Randomizer
|
||||
install:
|
||||
|
||||
- Content/
|
||||
|
||||
Then copy the contents of the CustomContent directory in your Rogue Legacy Randomizer into the newly copied Content
|
||||
directory and overwrite all files.
|
||||
|
||||
**BE SURE YOU ARE REPLACING THE COPIED FILES IN YOUR ROGUE LEGACY RANDOMIZER DIRECTORY AND NOT REPLACING YOUR ROGUE
|
||||
LEGACY FILES!**
|
||||
Please read the README file on the
|
||||
[Rogue Legacy Randomizer GitHub](https://github.com/ThePhar/RogueLegacyRandomizer/blob/master/README.md) page for up to
|
||||
date installation instructions.
|
||||
|
||||
@@ -42,40 +42,40 @@ class RiskOfRainWorld(World):
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# figure out how many revivals should exist in the pool
|
||||
self.total_revivals = int(self.world.total_revivals[self.player].value / 100 *
|
||||
self.world.total_locations[self.player].value)
|
||||
self.total_revivals = int(self.multiworld.total_revivals[self.player].value / 100 *
|
||||
self.multiworld.total_locations[self.player].value)
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
# shortcut for starting_inventory... The start_with_revive option lets you start with a Dio's Best Friend
|
||||
if self.world.start_with_revive[self.player].value:
|
||||
self.world.push_precollected(self.world.create_item("Dio's Best Friend", self.player))
|
||||
if self.multiworld.start_with_revive[self.player].value:
|
||||
self.multiworld.push_precollected(self.multiworld.create_item("Dio's Best Friend", self.player))
|
||||
|
||||
# if presets are enabled generate junk_pool from the selected preset
|
||||
pool_option = self.world.item_weights[self.player].value
|
||||
pool_option = self.multiworld.item_weights[self.player].value
|
||||
junk_pool: Dict[str, int] = {}
|
||||
if self.world.item_pool_presets[self.player]:
|
||||
if self.multiworld.item_pool_presets[self.player]:
|
||||
# generate chaos weights if the preset is chosen
|
||||
if pool_option == ItemWeights.option_chaos:
|
||||
for name, max_value in item_pool_weights[pool_option].items():
|
||||
junk_pool[name] = self.world.random.randint(0, max_value)
|
||||
junk_pool[name] = self.multiworld.random.randint(0, max_value)
|
||||
else:
|
||||
junk_pool = item_pool_weights[pool_option].copy()
|
||||
else: # generate junk pool from user created presets
|
||||
junk_pool = {
|
||||
"Item Scrap, Green": self.world.green_scrap[self.player].value,
|
||||
"Item Scrap, Red": self.world.red_scrap[self.player].value,
|
||||
"Item Scrap, Yellow": self.world.yellow_scrap[self.player].value,
|
||||
"Item Scrap, White": self.world.white_scrap[self.player].value,
|
||||
"Common Item": self.world.common_item[self.player].value,
|
||||
"Uncommon Item": self.world.uncommon_item[self.player].value,
|
||||
"Legendary Item": self.world.legendary_item[self.player].value,
|
||||
"Boss Item": self.world.boss_item[self.player].value,
|
||||
"Lunar Item": self.world.lunar_item[self.player].value,
|
||||
"Equipment": self.world.equipment[self.player].value
|
||||
"Item Scrap, Green": self.multiworld.green_scrap[self.player].value,
|
||||
"Item Scrap, Red": self.multiworld.red_scrap[self.player].value,
|
||||
"Item Scrap, Yellow": self.multiworld.yellow_scrap[self.player].value,
|
||||
"Item Scrap, White": self.multiworld.white_scrap[self.player].value,
|
||||
"Common Item": self.multiworld.common_item[self.player].value,
|
||||
"Uncommon Item": self.multiworld.uncommon_item[self.player].value,
|
||||
"Legendary Item": self.multiworld.legendary_item[self.player].value,
|
||||
"Boss Item": self.multiworld.boss_item[self.player].value,
|
||||
"Lunar Item": self.multiworld.lunar_item[self.player].value,
|
||||
"Equipment": self.multiworld.equipment[self.player].value
|
||||
}
|
||||
|
||||
# remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled
|
||||
if not (self.world.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
|
||||
if not (self.multiworld.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
|
||||
junk_pool.pop("Lunar Item")
|
||||
|
||||
# Generate item pool
|
||||
@@ -84,38 +84,38 @@ class RiskOfRainWorld(World):
|
||||
itempool += ["Dio's Best Friend"] * self.total_revivals
|
||||
|
||||
# Fill remaining items with randomly generated junk
|
||||
itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
||||
k=self.world.total_locations[self.player].value - self.total_revivals)
|
||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
||||
k=self.multiworld.total_locations[self.player].value - self.total_revivals)
|
||||
|
||||
# Convert itempool into real items
|
||||
itempool = list(map(lambda name: self.create_item(name), itempool))
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
def set_rules(self) -> None:
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self) -> None:
|
||||
menu = create_region(self.world, self.player, "Menu")
|
||||
petrichor = create_region(self.world, self.player, "Petrichor V",
|
||||
[f"ItemPickup{i + 1}" for i in range(self.world.total_locations[self.player].value)])
|
||||
menu = create_region(self.multiworld, self.player, "Menu")
|
||||
petrichor = create_region(self.multiworld, self.player, "Petrichor V",
|
||||
[f"ItemPickup{i + 1}" for i in range(self.multiworld.total_locations[self.player].value)])
|
||||
|
||||
connection = Entrance(self.player, "Lobby", menu)
|
||||
menu.exits.append(connection)
|
||||
connection.connect(petrichor)
|
||||
|
||||
self.world.regions += [menu, petrichor]
|
||||
self.multiworld.regions += [menu, petrichor]
|
||||
|
||||
create_events(self.world, self.player)
|
||||
create_events(self.multiworld, self.player)
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"itemPickupStep": self.world.item_pickup_step[self.player].value,
|
||||
"seed": "".join(self.world.slot_seeds[self.player].choice(string.digits) for _ in range(16)),
|
||||
"totalLocations": self.world.total_locations[self.player].value,
|
||||
"totalRevivals": self.world.total_revivals[self.player].value,
|
||||
"startWithDio": self.world.start_with_revive[self.player].value,
|
||||
"FinalStageDeath": self.world.final_stage_death[self.player].value
|
||||
"itemPickupStep": self.multiworld.item_pickup_step[self.player].value,
|
||||
"seed": "".join(self.multiworld.slot_seeds[self.player].choice(string.digits) for _ in range(16)),
|
||||
"totalLocations": self.multiworld.total_locations[self.player].value,
|
||||
"totalRevivals": self.multiworld.total_revivals[self.player].value,
|
||||
"startWithDio": self.multiworld.start_with_revive[self.player].value,
|
||||
"FinalStageDeath": self.multiworld.final_stage_death[self.player].value
|
||||
}
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
|
||||
@@ -690,7 +690,7 @@ def connect_regions(world, player, gates: typing.List[LevelGate], cannon_core_em
|
||||
def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None):
|
||||
# Shamelessly stolen from the ROR2 definition
|
||||
ret = Region(name, None, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = active_locations.get(location, 0)
|
||||
|
||||
@@ -69,15 +69,15 @@ class SA2BWorld(World):
|
||||
return {
|
||||
"ModVersion": 101,
|
||||
"MusicMap": self.music_map,
|
||||
"MusicShuffle": self.world.music_shuffle[self.player].value,
|
||||
"RequiredRank": self.world.required_rank[self.player].value,
|
||||
"ChaoRaceChecks": self.world.chao_race_checks[self.player].value,
|
||||
"ChaoGardenDifficulty": self.world.chao_garden_difficulty[self.player].value,
|
||||
"DeathLink": self.world.death_link[self.player].value,
|
||||
"IncludeMissions": self.world.include_missions[self.player].value,
|
||||
"EmblemPercentageForCannonsCore": self.world.emblem_percentage_for_cannons_core[self.player].value,
|
||||
"NumberOfLevelGates": self.world.number_of_level_gates[self.player].value,
|
||||
"LevelGateDistribution": self.world.level_gate_distribution[self.player].value,
|
||||
"MusicShuffle": self.multiworld.music_shuffle[self.player].value,
|
||||
"RequiredRank": self.multiworld.required_rank[self.player].value,
|
||||
"ChaoRaceChecks": self.multiworld.chao_race_checks[self.player].value,
|
||||
"ChaoGardenDifficulty": self.multiworld.chao_garden_difficulty[self.player].value,
|
||||
"DeathLink": self.multiworld.death_link[self.player].value,
|
||||
"IncludeMissions": self.multiworld.include_missions[self.player].value,
|
||||
"EmblemPercentageForCannonsCore": self.multiworld.emblem_percentage_for_cannons_core[self.player].value,
|
||||
"NumberOfLevelGates": self.multiworld.number_of_level_gates[self.player].value,
|
||||
"LevelGateDistribution": self.multiworld.level_gate_distribution[self.player].value,
|
||||
"EmblemsForCannonsCore": self.emblems_for_cannons_core,
|
||||
"RegionEmblemMap": self.region_emblem_map,
|
||||
"GateCosts": self.gate_costs,
|
||||
@@ -92,14 +92,14 @@ class SA2BWorld(World):
|
||||
slot_data = self._get_slot_data()
|
||||
slot_data["MusicMap"] = self.music_map
|
||||
for option_name in sa2b_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
|
||||
return slot_data
|
||||
|
||||
def get_levels_per_gate(self) -> list:
|
||||
levels_per_gate = list()
|
||||
max_gate_index = self.world.number_of_level_gates[self.player]
|
||||
max_gate_index = self.multiworld.number_of_level_gates[self.player]
|
||||
average_level_count = 30 / (max_gate_index + 1)
|
||||
levels_added = 0
|
||||
|
||||
@@ -112,8 +112,8 @@ class SA2BWorld(World):
|
||||
levels_added += 1
|
||||
additional_count_iterator += 1 if additional_count_iterator < max_gate_index else -max_gate_index
|
||||
|
||||
if self.world.level_gate_distribution[self.player] == 0 or self.world.level_gate_distribution[self.player] == 2:
|
||||
early_distribution = self.world.level_gate_distribution[self.player] == 0
|
||||
if self.multiworld.level_gate_distribution[self.player] == 0 or self.multiworld.level_gate_distribution[self.player] == 2:
|
||||
early_distribution = self.multiworld.level_gate_distribution[self.player] == 0
|
||||
levels_to_distribute = 5
|
||||
gate_index_offset = 0
|
||||
while levels_to_distribute > 0:
|
||||
@@ -134,10 +134,10 @@ class SA2BWorld(World):
|
||||
return levels_per_gate
|
||||
|
||||
def generate_early(self):
|
||||
self.gate_bosses = get_gate_bosses(self.world, self.player)
|
||||
self.gate_bosses = get_gate_bosses(self.multiworld, self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
self.world.get_location(LocationName.biolizard, self.player).place_locked_item(self.create_item(ItemName.maria))
|
||||
self.multiworld.get_location(LocationName.biolizard, self.player).place_locked_item(self.create_item(ItemName.maria))
|
||||
|
||||
itempool: typing.List[SA2BItem] = []
|
||||
|
||||
@@ -155,20 +155,20 @@ class SA2BWorld(World):
|
||||
extra_junk_count = raw_emblem_count - total_emblem_count
|
||||
|
||||
self.emblems_for_cannons_core = math.floor(
|
||||
total_emblem_count * (self.world.emblem_percentage_for_cannons_core[self.player].value / 100.0))
|
||||
total_emblem_count * (self.multiworld.emblem_percentage_for_cannons_core[self.player].value / 100.0))
|
||||
|
||||
gate_cost_mult = 1.0
|
||||
if self.world.level_gate_costs[self.player].value == 0:
|
||||
if self.multiworld.level_gate_costs[self.player].value == 0:
|
||||
gate_cost_mult = 0.6
|
||||
elif self.world.level_gate_costs[self.player].value == 1:
|
||||
elif self.multiworld.level_gate_costs[self.player].value == 1:
|
||||
gate_cost_mult = 0.8
|
||||
|
||||
shuffled_region_list = list(range(30))
|
||||
emblem_requirement_list = list()
|
||||
self.world.random.shuffle(shuffled_region_list)
|
||||
self.multiworld.random.shuffle(shuffled_region_list)
|
||||
levels_per_gate = self.get_levels_per_gate()
|
||||
|
||||
check_for_impossible_shuffle(shuffled_region_list, math.ceil(levels_per_gate[0]), self.world)
|
||||
check_for_impossible_shuffle(shuffled_region_list, math.ceil(levels_per_gate[0]), self.multiworld)
|
||||
levels_added_to_gate = 0
|
||||
total_levels_added = 0
|
||||
current_gate = 0
|
||||
@@ -184,8 +184,8 @@ class SA2BWorld(World):
|
||||
total_levels_added += 1
|
||||
if levels_added_to_gate >= levels_per_gate[current_gate]:
|
||||
current_gate += 1
|
||||
if current_gate > self.world.number_of_level_gates[self.player].value:
|
||||
current_gate = self.world.number_of_level_gates[self.player].value
|
||||
if current_gate > self.multiworld.number_of_level_gates[self.player].value:
|
||||
current_gate = self.multiworld.number_of_level_gates[self.player].value
|
||||
else:
|
||||
current_gate_emblems = max(
|
||||
math.floor(total_emblem_count * math.pow(total_levels_added / 30.0, 2.0) * gate_cost_mult), current_gate)
|
||||
@@ -195,60 +195,60 @@ class SA2BWorld(World):
|
||||
|
||||
self.region_emblem_map = dict(zip(shuffled_region_list, emblem_requirement_list))
|
||||
|
||||
connect_regions(self.world, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses)
|
||||
connect_regions(self.multiworld, self.player, gates, self.emblems_for_cannons_core, self.gate_bosses)
|
||||
|
||||
max_required_emblems = max(max(emblem_requirement_list), self.emblems_for_cannons_core)
|
||||
itempool += [self.create_item(ItemName.emblem) for _ in range(max_required_emblems)]
|
||||
|
||||
non_required_emblems = (total_emblem_count - max_required_emblems)
|
||||
junk_count = math.floor(non_required_emblems * (self.world.junk_fill_percentage[self.player].value / 100.0))
|
||||
junk_count = math.floor(non_required_emblems * (self.multiworld.junk_fill_percentage[self.player].value / 100.0))
|
||||
itempool += [self.create_item(ItemName.emblem, True) for _ in range(non_required_emblems - junk_count)]
|
||||
|
||||
# Carve Traps out of junk_count
|
||||
trap_weights = []
|
||||
trap_weights += ([ItemName.omochao_trap] * self.world.omochao_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.timestop_trap] * self.world.timestop_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.confuse_trap] * self.world.confusion_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.tiny_trap] * self.world.tiny_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.omochao_trap] * self.multiworld.omochao_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.timestop_trap] * self.multiworld.timestop_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.confuse_trap] * self.multiworld.confusion_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.tiny_trap] * self.multiworld.tiny_trap_weight[self.player].value)
|
||||
|
||||
junk_count += extra_junk_count
|
||||
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.world.trap_fill_percentage[self.player].value / 100.0))
|
||||
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.multiworld.trap_fill_percentage[self.player].value / 100.0))
|
||||
junk_count -= trap_count
|
||||
|
||||
junk_pool = []
|
||||
junk_keys = list(junk_table.keys())
|
||||
for i in range(junk_count):
|
||||
junk_item = self.world.random.choice(junk_keys)
|
||||
junk_item = self.multiworld.random.choice(junk_keys)
|
||||
junk_pool.append(self.create_item(junk_item))
|
||||
|
||||
itempool += junk_pool
|
||||
|
||||
trap_pool = []
|
||||
for i in range(trap_count):
|
||||
trap_item = self.world.random.choice(trap_weights)
|
||||
trap_item = self.multiworld.random.choice(trap_weights)
|
||||
trap_pool.append(self.create_item(trap_item))
|
||||
|
||||
itempool += trap_pool
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
# Music Shuffle
|
||||
if self.world.music_shuffle[self.player] == "levels":
|
||||
if self.multiworld.music_shuffle[self.player] == "levels":
|
||||
musiclist_o = list(range(0, 47))
|
||||
musiclist_s = musiclist_o.copy()
|
||||
self.world.random.shuffle(musiclist_s)
|
||||
self.multiworld.random.shuffle(musiclist_s)
|
||||
self.music_map = dict(zip(musiclist_o, musiclist_s))
|
||||
elif self.world.music_shuffle[self.player] == "full":
|
||||
elif self.multiworld.music_shuffle[self.player] == "full":
|
||||
musiclist_o = list(range(0, 78))
|
||||
musiclist_s = musiclist_o.copy()
|
||||
self.world.random.shuffle(musiclist_s)
|
||||
self.multiworld.random.shuffle(musiclist_s)
|
||||
self.music_map = dict(zip(musiclist_o, musiclist_s))
|
||||
else:
|
||||
self.music_map = dict()
|
||||
|
||||
def create_regions(self):
|
||||
self.location_table = setup_locations(self.world, self.player)
|
||||
create_regions(self.world, self.player, self.location_table)
|
||||
self.location_table = setup_locations(self.multiworld, self.player)
|
||||
create_regions(self.multiworld, self.player, self.location_table)
|
||||
|
||||
def create_item(self, name: str, force_non_progression=False) -> Item:
|
||||
data = item_table[name]
|
||||
@@ -269,12 +269,12 @@ class SA2BWorld(World):
|
||||
return created_item
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player, self.gate_bosses)
|
||||
set_rules(self.multiworld, self.player, self.gate_bosses)
|
||||
|
||||
def write_spoiler(self, spoiler_handle: typing.TextIO):
|
||||
spoiler_handle.write("\n")
|
||||
header_text = "Sonic Adventure 2 Bosses for {}:\n"
|
||||
header_text = header_text.format(self.world.player_name[self.player])
|
||||
header_text = header_text.format(self.multiworld.player_name[self.player])
|
||||
spoiler_handle.write(header_text)
|
||||
for x in range(len(self.gate_bosses.values())):
|
||||
text = "Gate {0} Boss: {1}\n"
|
||||
|
||||
@@ -236,7 +236,7 @@ def create_location(player: int, location_data: LocationData, region: Region,
|
||||
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]],
|
||||
location_cache: List[Location], name: str) -> Region:
|
||||
region = Region(name, RegionType.Generic, name, player)
|
||||
region.world = world
|
||||
region.multiworld = world
|
||||
|
||||
if name in locations_per_region:
|
||||
for location_data in locations_per_region[name]:
|
||||
|
||||
@@ -59,31 +59,31 @@ class SC2WoLWorld(World):
|
||||
|
||||
def create_regions(self):
|
||||
self.mission_req_table, self.final_mission_id, self.victory_item = create_regions(
|
||||
self.world, self.player, get_locations(self.world, self.player), self.location_cache
|
||||
self.multiworld, self.player, get_locations(self.multiworld, self.player), self.location_cache
|
||||
)
|
||||
|
||||
def generate_basic(self):
|
||||
excluded_items = get_excluded_items(self, self.world, self.player)
|
||||
excluded_items = get_excluded_items(self, self.multiworld, self.player)
|
||||
|
||||
starter_items = assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
|
||||
starter_items = assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations)
|
||||
|
||||
pool = get_item_pool(self.world, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache)
|
||||
pool = get_item_pool(self.multiworld, self.player, self.mission_req_table, starter_items, excluded_items, self.location_cache)
|
||||
|
||||
fill_item_pool_with_dummy_items(self, self.world, self.player, self.locked_locations, self.location_cache, pool)
|
||||
fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
def set_rules(self):
|
||||
setup_events(self.world, self.player, self.locked_locations, self.location_cache)
|
||||
self.world.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player)
|
||||
setup_events(self.multiworld, self.player, self.locked_locations, self.location_cache)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has(self.victory_item, self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(filler_items)
|
||||
return self.multiworld.random.choice(filler_items)
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
for option_name in sc2wol_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if type(option.value) in {str, int}:
|
||||
slot_data[option_name] = int(option.value)
|
||||
slot_req_table = {}
|
||||
|
||||
@@ -4,7 +4,7 @@ import time
|
||||
|
||||
from NetUtils import ClientStatus, color
|
||||
from worlds.AutoSNIClient import SNIClient
|
||||
from .Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT
|
||||
from .Rom import SM_ROM_MAX_PLAYERID
|
||||
|
||||
snes_logger = logging.getLogger("SNES")
|
||||
|
||||
@@ -143,7 +143,7 @@ class SMSNIClient(SNIClient):
|
||||
else:
|
||||
location_id = 0x00 #backward compat
|
||||
|
||||
player_id = item.player if item.player <= SM_ROM_PLAYER_LIMIT else 0
|
||||
player_id = item.player if item.player <= SM_ROM_MAX_PLAYERID else 0
|
||||
snes_buffered_write(ctx, SM_RECV_QUEUE_START + item_out_ptr * 4, bytes(
|
||||
[player_id & 0xFF, (player_id >> 8) & 0xFF, item_id & 0xFF, location_id & 0xFF]))
|
||||
item_out_ptr += 1
|
||||
|
||||
@@ -7,8 +7,8 @@ from Utils import read_snes_rom
|
||||
from worlds.Files import APDeltaPatch
|
||||
|
||||
SMJUHASH = '21f3e98df4780ee1c667b84e57d88675'
|
||||
ROM_PLAYER_LIMIT = 65535 # max archipelago player ID. note, SM ROM itself will only store 201 names+ids max
|
||||
|
||||
SM_ROM_MAX_PLAYERID = 65535
|
||||
SM_ROM_PLAYERDATA_COUNT = 202
|
||||
|
||||
class SMDeltaPatch(APDeltaPatch):
|
||||
hash = SMJUHASH
|
||||
|
||||
@@ -5,7 +5,7 @@ import copy
|
||||
import os
|
||||
import threading
|
||||
import base64
|
||||
from typing import Set, TextIO
|
||||
from typing import Any, Dict, Iterable, List, Set, TextIO, TypedDict
|
||||
|
||||
from worlds.sm.variaRandomizer.graph.graph_utils import GraphUtils
|
||||
|
||||
@@ -15,7 +15,7 @@ from .Regions import create_regions
|
||||
from .Rules import set_rules, add_entrance_rule
|
||||
from .Options import sm_options
|
||||
from .Client import SMSNIClient
|
||||
from .Rom import get_base_rom_path, ROM_PLAYER_LIMIT, SMDeltaPatch, get_sm_symbols
|
||||
from .Rom import get_base_rom_path, SM_ROM_MAX_PLAYERID, SM_ROM_PLAYERDATA_COUNT, SMDeltaPatch, get_sm_symbols
|
||||
import Utils
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, RegionType, CollectionState, Tutorial
|
||||
@@ -67,6 +67,13 @@ class SMWeb(WebWorld):
|
||||
["Farrak Kilhn"]
|
||||
)]
|
||||
|
||||
|
||||
class ByteEdit(TypedDict):
|
||||
sym: Dict[str, Any]
|
||||
offset: int
|
||||
values: Iterable[int]
|
||||
|
||||
|
||||
locations_start_id = 82000
|
||||
items_start_id = 83000
|
||||
|
||||
@@ -111,27 +118,27 @@ class SMWorld(World):
|
||||
def generate_early(self):
|
||||
Logic.factory('vanilla')
|
||||
|
||||
self.variaRando = VariaRandomizer(self.world, get_base_rom_path(), self.player)
|
||||
self.world.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty)
|
||||
self.variaRando = VariaRandomizer(self.multiworld, get_base_rom_path(), self.player)
|
||||
self.multiworld.state.smbm[self.player] = SMBoolManager(self.player, self.variaRando.maxDifficulty)
|
||||
|
||||
# keeps Nothing items local so no player will ever pickup Nothing
|
||||
# doing so reduces contribution of this world to the Multiworld the more Nothing there is though
|
||||
self.world.local_items[self.player].value.add('Nothing')
|
||||
self.world.local_items[self.player].value.add('No Energy')
|
||||
self.multiworld.local_items[self.player].value.add('Nothing')
|
||||
self.multiworld.local_items[self.player].value.add('No Energy')
|
||||
|
||||
if (self.variaRando.args.morphPlacement == "early"):
|
||||
self.world.local_items[self.player].value.add('Morph')
|
||||
self.multiworld.local_items[self.player].value.add('Morph')
|
||||
|
||||
self.remote_items = self.world.remote_items[self.player]
|
||||
self.remote_items = self.multiworld.remote_items[self.player]
|
||||
|
||||
if (len(self.variaRando.randoExec.setup.restrictedLocs) > 0):
|
||||
self.world.accessibility[self.player] = self.world.accessibility[self.player].from_text("minimal")
|
||||
logger.warning(f"accessibility forced to 'minimal' for player {self.world.get_player_name(self.player)} because of 'fun' settings")
|
||||
self.multiworld.accessibility[self.player] = self.multiworld.accessibility[self.player].from_text("minimal")
|
||||
logger.warning(f"accessibility forced to 'minimal' for player {self.multiworld.get_player_name(self.player)} because of 'fun' settings")
|
||||
|
||||
def generate_basic(self):
|
||||
itemPool = self.variaRando.container.itemPool
|
||||
self.startItems = [variaItem for item in self.world.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name]
|
||||
if self.world.start_inventory_removes_from_pool[self.player]:
|
||||
self.startItems = [variaItem for item in self.multiworld.precollected_items[self.player] for variaItem in ItemManager.Items.values() if variaItem.Name == item.name]
|
||||
if self.multiworld.start_inventory_removes_from_pool[self.player]:
|
||||
for item in self.startItems:
|
||||
if (item in itemPool):
|
||||
itemPool.remove(item)
|
||||
@@ -175,33 +182,34 @@ class SMWorld(World):
|
||||
else:
|
||||
pool.append(smitem)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
for (location, item) in self.locked_items.items():
|
||||
self.world.get_location(location, self.player).place_locked_item(item)
|
||||
self.world.get_location(location, self.player).address = None
|
||||
self.multiworld.get_location(location, self.player).place_locked_item(item)
|
||||
self.multiworld.get_location(location, self.player).address = None
|
||||
|
||||
startAP = self.world.get_entrance('StartAP', self.player)
|
||||
startAP.connect(self.world.get_region(self.variaRando.args.startLocation, self.player))
|
||||
startAP = self.multiworld.get_entrance('StartAP', self.player)
|
||||
startAP.connect(self.multiworld.get_region(self.variaRando.args.startLocation, self.player))
|
||||
|
||||
for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions:
|
||||
src_region = self.world.get_region(src.Name, self.player)
|
||||
dest_region = self.world.get_region(dest.Name, self.player)
|
||||
if ((src.Name + "->" + dest.Name, self.player) not in self.world._entrance_cache):
|
||||
src_region = self.multiworld.get_region(src.Name, self.player)
|
||||
dest_region = self.multiworld.get_region(dest.Name, self.player)
|
||||
if ((src.Name + "->" + dest.Name, self.player) not in self.multiworld._entrance_cache):
|
||||
src_region.exits.append(Entrance(self.player, src.Name + "->" + dest.Name, src_region))
|
||||
srcDestEntrance = self.world.get_entrance(src.Name + "->" + dest.Name, self.player)
|
||||
srcDestEntrance = self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player)
|
||||
srcDestEntrance.connect(dest_region)
|
||||
add_entrance_rule(self.world.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse)
|
||||
add_entrance_rule(self.multiworld.get_entrance(src.Name + "->" + dest.Name, self.player), self.player, getAccessPoint(src.Name).traverse)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
|
||||
def create_regions(self):
|
||||
create_locations(self, self.player)
|
||||
create_regions(self, self.world, self.player)
|
||||
create_regions(self, self.multiworld, self.player)
|
||||
|
||||
def getWordArray(self, w): # little-endian convert a 16-bit number to an array of numbers <= 255 each
|
||||
def getWordArray(self, w: int) -> List[int]:
|
||||
""" little-endian convert a 16-bit number to an array of numbers <= 255 each """
|
||||
return [w & 0x00FF, (w & 0xFF00) >> 8]
|
||||
|
||||
# used for remote location Credits Spoiler of local items
|
||||
@@ -281,48 +289,87 @@ class SMWorld(World):
|
||||
"data", "SMBasepatch_prebuilt", "variapatches.ips"))
|
||||
|
||||
def APPostPatchRom(self, romPatcher):
|
||||
symbols = get_sm_symbols(os.path.join(os.path.dirname(__file__),
|
||||
symbols = get_sm_symbols(os.path.join(os.path.dirname(__file__),
|
||||
"data", "SMBasepatch_prebuilt", "sm-basepatch-symbols.json"))
|
||||
multiWorldLocations = []
|
||||
multiWorldItems = []
|
||||
|
||||
# gather all player ids and names relevant to this rom, then write player name and player id data tables
|
||||
playerIdSet: Set[int] = {0} # 0 is for "Archipelago" server
|
||||
for itemLoc in self.multiworld.get_locations():
|
||||
assert itemLoc.item, f"World of player '{self.multiworld.player_name[itemLoc.player]}' has a loc.item " + \
|
||||
f"that is {itemLoc.item} during generate_output"
|
||||
# add each playerid who has a location containing an item to send to us *or* to an item_link we're part of
|
||||
if itemLoc.item.player == self.player or \
|
||||
(itemLoc.item.player in self.multiworld.groups and
|
||||
self.player in self.multiworld.groups[itemLoc.item.player]['players']):
|
||||
playerIdSet |= {itemLoc.player}
|
||||
# add each playerid, including item link ids, that we'll be sending items to
|
||||
if itemLoc.player == self.player:
|
||||
playerIdSet |= {itemLoc.item.player}
|
||||
if len(playerIdSet) > SM_ROM_PLAYERDATA_COUNT:
|
||||
# max 202 entries, but it's possible for item links to add enough replacement items for us, that are placed
|
||||
# in worlds that otherwise have no relation to us, that the 2*location count limit is exceeded
|
||||
logger.warning("SM is interacting with too many players to fit in ROM. "
|
||||
f"Removing the highest {len(playerIdSet) - SM_ROM_PLAYERDATA_COUNT} ids to fit")
|
||||
playerIdSet = set(sorted(playerIdSet)[:SM_ROM_PLAYERDATA_COUNT])
|
||||
otherPlayerIndex: Dict[int, int] = {} # ap player id -> rom-local player index
|
||||
playerNameData: List[ByteEdit] = []
|
||||
playerIdData: List[ByteEdit] = []
|
||||
# sort all player data by player id so that the game can look up a player's data reasonably quickly when
|
||||
# the client sends an ap playerid to the game
|
||||
for i, playerid in enumerate(sorted(playerIdSet)):
|
||||
playername = self.multiworld.player_name[playerid] if playerid != 0 else "Archipelago"
|
||||
playerIdForRom = playerid
|
||||
if playerid > SM_ROM_MAX_PLAYERID:
|
||||
# note, playerIdForRom = 0 is not unique so the game cannot look it up.
|
||||
# instead it will display the player received-from as "Archipelago"
|
||||
playerIdForRom = 0
|
||||
if playerid == self.player:
|
||||
raise Exception(f"SM rom cannot fit enough bits to represent self player id {playerid}")
|
||||
else:
|
||||
logger.warning(f"SM rom cannot fit enough bits to represent player id {playerid}, setting to 0 in rom")
|
||||
otherPlayerIndex[playerid] = i
|
||||
playerNameData.append({"sym": symbols["rando_player_name_table"],
|
||||
"offset": i * 16,
|
||||
"values": playername[:16].upper().center(16).encode()})
|
||||
playerIdData.append({"sym": symbols["rando_player_id_table"],
|
||||
"offset": i * 2,
|
||||
"values": self.getWordArray(playerIdForRom)})
|
||||
|
||||
multiWorldLocations: List[ByteEdit] = []
|
||||
multiWorldItems: List[ByteEdit] = []
|
||||
idx = 0
|
||||
self.playerIDMap = {}
|
||||
playerIDCount = 0 # 0 is for "Archipelago" server; highest possible = 200 (201 entries)
|
||||
vanillaItemTypesCount = 21
|
||||
for itemLoc in self.world.get_locations():
|
||||
for itemLoc in self.multiworld.get_locations():
|
||||
if itemLoc.player == self.player and locationsDict[itemLoc.name].Id != None:
|
||||
# this SM world can find this item: write full item data to tables and assign player data for writing
|
||||
romPlayerID = itemLoc.item.player if itemLoc.item.player <= ROM_PLAYER_LIMIT else 0
|
||||
# item to place in this SM world: write full item data to tables
|
||||
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items:
|
||||
itemId = ItemManager.Items[itemLoc.item.type].Id
|
||||
else:
|
||||
itemId = ItemManager.Items['ArchipelagoItem'].Id + idx
|
||||
itemId = ItemManager.Items["ArchipelagoItem"].Id + idx
|
||||
multiWorldItems.append({"sym": symbols["message_item_names"],
|
||||
"offset": (vanillaItemTypesCount + idx)*64,
|
||||
"values": self.convertToROMItemName(itemLoc.item.name)})
|
||||
idx += 1
|
||||
|
||||
if (romPlayerID > 0 and romPlayerID not in self.playerIDMap.keys()):
|
||||
playerIDCount += 1
|
||||
self.playerIDMap[romPlayerID] = playerIDCount
|
||||
if itemLoc.item.player == self.player:
|
||||
itemDestinationType = 0 # dest type 0 means 'regular old SM item' per itemtable.asm
|
||||
elif itemLoc.item.player in self.multiworld.groups and \
|
||||
self.player in self.multiworld.groups[itemLoc.item.player]['players']:
|
||||
# dest type 2 means 'SM item link item that sends to the current player and others'
|
||||
# per itemtable.asm (groups are synonymous with item_links, currently)
|
||||
itemDestinationType = 2
|
||||
else:
|
||||
itemDestinationType = 1 # dest type 1 means 'item for entirely someone else' per itemtable.asm
|
||||
|
||||
[w0, w1] = self.getWordArray(0 if itemLoc.item.player == self.player else 1)
|
||||
[w0, w1] = self.getWordArray(itemDestinationType)
|
||||
[w2, w3] = self.getWordArray(itemId)
|
||||
[w4, w5] = self.getWordArray(romPlayerID)
|
||||
[w4, w5] = self.getWordArray(otherPlayerIndex[itemLoc.item.player] if itemLoc.item.player in
|
||||
otherPlayerIndex else 0)
|
||||
[w6, w7] = self.getWordArray(0 if itemLoc.item.advancement else 1)
|
||||
multiWorldLocations.append({"sym": symbols["rando_item_table"],
|
||||
"offset": locationsDict[itemLoc.name].Id*8,
|
||||
"values": [w0, w1, w2, w3, w4, w5, w6, w7]})
|
||||
|
||||
elif itemLoc.item.player == self.player:
|
||||
# this SM world owns the item: so in case the sending player might not have anything placed in this
|
||||
# world to receive from it, assign them space in playerIDMap so that the ROM can display their name
|
||||
# (SM item name not needed, as SM item type id will be in the message they send to this world live)
|
||||
romPlayerID = itemLoc.player if itemLoc.player <= ROM_PLAYER_LIMIT else 0
|
||||
if (romPlayerID > 0 and romPlayerID not in self.playerIDMap.keys()):
|
||||
playerIDCount += 1
|
||||
self.playerIDMap[romPlayerID] = playerIDCount
|
||||
|
||||
itemSprites = [{"fileName": "off_world_prog_item.bin",
|
||||
"paletteSymbolName": "prog_item_eight_palette_indices",
|
||||
"dataSymbolName": "offworld_graphics_data_progression_item"},
|
||||
@@ -331,7 +378,7 @@ class SMWorld(World):
|
||||
"paletteSymbolName": "nonprog_item_eight_palette_indices",
|
||||
"dataSymbolName": "offworld_graphics_data_item"}]
|
||||
idx = 0
|
||||
offworldSprites = []
|
||||
offworldSprites: List[ByteEdit] = []
|
||||
for itemSprite in itemSprites:
|
||||
with open(os.path.join(os.path.dirname(__file__), "data", "custom_sprite", itemSprite["fileName"]), 'rb') as stream:
|
||||
buffer = bytearray(stream.read())
|
||||
@@ -343,31 +390,21 @@ class SMWorld(World):
|
||||
"values": buffer[8:264]})
|
||||
idx += 1
|
||||
|
||||
deathLink = [{"sym": symbols["config_deathlink"],
|
||||
"offset": 0,
|
||||
"values": [self.world.death_link[self.player].value]}]
|
||||
remoteItem = [{"sym": symbols["config_remote_items"],
|
||||
"offset": 0,
|
||||
"values": self.getWordArray(0b001 + (0b010 if self.remote_items else 0b000))}]
|
||||
ownPlayerId = [{"sym": symbols["config_player_id"],
|
||||
"offset": 0,
|
||||
"values": self.getWordArray(self.player)}]
|
||||
|
||||
playerNames = []
|
||||
playerNameIDMap = []
|
||||
playerNames.append({"sym": symbols["rando_player_table"],
|
||||
"offset": 0,
|
||||
"values": "Archipelago".upper().center(16).encode()})
|
||||
playerNameIDMap.append({"sym": symbols["rando_player_id_table"],
|
||||
"offset": 0,
|
||||
"values": self.getWordArray(0)})
|
||||
for key,value in self.playerIDMap.items():
|
||||
playerNames.append({"sym": symbols["rando_player_table"],
|
||||
"offset": value * 16,
|
||||
"values": self.world.player_name[key][:16].upper().center(16).encode()})
|
||||
playerNameIDMap.append({"sym": symbols["rando_player_id_table"],
|
||||
"offset": value * 2,
|
||||
"values": self.getWordArray(key)})
|
||||
deathLink: List[ByteEdit] = [{
|
||||
"sym": symbols["config_deathlink"],
|
||||
"offset": 0,
|
||||
"values": [self.multiworld.death_link[self.player].value]
|
||||
}]
|
||||
remoteItem: List[ByteEdit] = [{
|
||||
"sym": symbols["config_remote_items"],
|
||||
"offset": 0,
|
||||
"values": self.getWordArray(0b001 + (0b010 if self.remote_items else 0b000))
|
||||
}]
|
||||
ownPlayerId: List[ByteEdit] = [{
|
||||
"sym": symbols["config_player_id"],
|
||||
"offset": 0,
|
||||
"values": self.getWordArray(self.player)
|
||||
}]
|
||||
|
||||
patchDict = { 'MultiWorldLocations': multiWorldLocations,
|
||||
'MultiWorldItems': multiWorldItems,
|
||||
@@ -375,15 +412,15 @@ class SMWorld(World):
|
||||
'deathLink': deathLink,
|
||||
'remoteItem': remoteItem,
|
||||
'ownPlayerId': ownPlayerId,
|
||||
'PlayerName': playerNames,
|
||||
'PlayerNameIDMap': playerNameIDMap}
|
||||
'playerNameData': playerNameData,
|
||||
'playerIdData': playerIdData}
|
||||
|
||||
# convert an array of symbolic byte_edit dicts like {"sym": symobj, "offset": 0, "values": [1, 0]}
|
||||
# to a single rom patch dict like {0x438c: [1, 0], 0xa4a5: [0, 0, 0]} which varia will understand and apply
|
||||
def resolve_symbols_to_file_offset_based_dict(byte_edits_arr) -> dict:
|
||||
this_patch_as_dict = {}
|
||||
def resolve_symbols_to_file_offset_based_dict(byte_edits_arr: List[ByteEdit]) -> Dict[int, Iterable[int]]:
|
||||
this_patch_as_dict: Dict[int, Iterable[int]] = {}
|
||||
for byte_edit in byte_edits_arr:
|
||||
offset_within_rom_file = byte_edit["sym"]["offset_within_rom_file"] + byte_edit["offset"]
|
||||
offset_within_rom_file: int = byte_edit["sym"]["offset_within_rom_file"] + byte_edit["offset"]
|
||||
this_patch_as_dict[offset_within_rom_file] = byte_edit["values"]
|
||||
return this_patch_as_dict
|
||||
|
||||
@@ -399,7 +436,7 @@ class SMWorld(World):
|
||||
# set rom name
|
||||
# 21 bytes
|
||||
from Main import __version__
|
||||
self.romName = bytearray(f'SM{__version__.replace(".", "")[0:3]}_{self.player}_{self.world.seed:11}', 'utf8')[:21]
|
||||
self.romName = bytearray(f'SM{__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}', 'utf8')[:21]
|
||||
self.romName.extend([0] * (21 - len(self.romName)))
|
||||
# clients should read from 0x7FC0, the location of the rom title in the SNES header.
|
||||
# duplicative ROM name at 0x1C4F00 is still written here for now, since people with archipelago pre-0.3.0 client installed will still be depending on this location for connecting to SM
|
||||
@@ -493,20 +530,20 @@ class SMWorld(World):
|
||||
if isinstance(itemLoc.item, SMItem) and itemLoc.item.type in ItemManager.Items else
|
||||
'ArchipelagoItem'],
|
||||
locationsDict[itemLoc.name], True)
|
||||
for itemLoc in self.world.get_locations() if itemLoc.player == self.player
|
||||
for itemLoc in self.multiworld.get_locations() if itemLoc.player == self.player
|
||||
]
|
||||
romPatcher.writeItemsLocs(itemLocs)
|
||||
|
||||
itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.world.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.world.get_locations() if itemLoc.item.player == self.player]
|
||||
progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.world.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.world.get_locations() if itemLoc.item.player == self.player and itemLoc.item.advancement == True]
|
||||
# progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.world.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True]
|
||||
itemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player]
|
||||
progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type], locationsDict[itemLoc.name] if itemLoc.name in locationsDict and itemLoc.player == self.player else self.DummyLocation(self.multiworld.get_player_name(itemLoc.player) + " " + itemLoc.name), True) for itemLoc in self.multiworld.get_locations() if itemLoc.item.player == self.player and itemLoc.item.advancement == True]
|
||||
# progItemLocs = [ItemLocation(ItemManager.Items[itemLoc.item.type if itemLoc.item.type in ItemManager.Items else 'ArchipelagoItem'], locationsDict[itemLoc.name], True) for itemLoc in self.multiworld.get_locations() if itemLoc.player == self.player and itemLoc.item.player == self.player and itemLoc.item.advancement == True]
|
||||
|
||||
# romPatcher.writeSplitLocs(self.variaRando.args.majorsSplit, itemLocs, progItemLocs)
|
||||
romPatcher.writeSpoiler(itemLocs, progItemLocs)
|
||||
romPatcher.writeRandoSettings(self.variaRando.randoExec.randoSettings, itemLocs)
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
outfilebase = self.world.get_out_file_name_base(self.player)
|
||||
outfilebase = self.multiworld.get_out_file_name_base(self.player)
|
||||
outputFilename = os.path.join(output_directory, f"{outfilebase}.sfc")
|
||||
|
||||
try:
|
||||
@@ -516,8 +553,8 @@ class SMWorld(World):
|
||||
except:
|
||||
raise
|
||||
else:
|
||||
patch = SMDeltaPatch(os.path.splitext(outputFilename)[0]+SMDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=outputFilename)
|
||||
patch = SMDeltaPatch(os.path.splitext(outputFilename)[0] + SMDeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=outputFilename)
|
||||
patch.write()
|
||||
finally:
|
||||
if os.path.exists(outputFilename):
|
||||
@@ -560,13 +597,13 @@ class SMWorld(World):
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if rom_name:
|
||||
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
if not self.world.is_race:
|
||||
if not self.multiworld.is_race:
|
||||
for option_name in self.option_definitions:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
|
||||
slot_data["Preset"] = { "Knows": {},
|
||||
@@ -606,11 +643,11 @@ class SMWorld(World):
|
||||
player=self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
if self.world.random.randint(0, 100) < self.world.minor_qty[self.player].value:
|
||||
power_bombs = self.world.power_bomb_qty[self.player].value
|
||||
missiles = self.world.missile_qty[self.player].value
|
||||
super_missiles = self.world.super_qty[self.player].value
|
||||
roll = self.world.random.randint(1, power_bombs + missiles + super_missiles)
|
||||
if self.multiworld.random.randint(0, 100) < self.multiworld.minor_qty[self.player].value:
|
||||
power_bombs = self.multiworld.power_bomb_qty[self.player].value
|
||||
missiles = self.multiworld.missile_qty[self.player].value
|
||||
super_missiles = self.multiworld.super_qty[self.player].value
|
||||
roll = self.multiworld.random.randint(1, power_bombs + missiles + super_missiles)
|
||||
if roll <= power_bombs:
|
||||
return "Power Bomb"
|
||||
elif roll <= power_bombs + missiles:
|
||||
@@ -621,20 +658,20 @@ class SMWorld(World):
|
||||
return "Nothing"
|
||||
|
||||
def pre_fill(self):
|
||||
if (self.variaRando.args.morphPlacement == "early") and next((item for item in self.world.itempool if item.player == self.player and item.name == "Morph Ball"), False):
|
||||
if (self.variaRando.args.morphPlacement == "early") and next((item for item in self.multiworld.itempool if item.player == self.player and item.name == "Morph Ball"), False):
|
||||
viable = []
|
||||
for location in self.world.get_locations():
|
||||
for location in self.multiworld.get_locations():
|
||||
if location.player == self.player \
|
||||
and location.item is None \
|
||||
and location.can_reach(self.world.state):
|
||||
and location.can_reach(self.multiworld.state):
|
||||
viable.append(location)
|
||||
self.world.random.shuffle(viable)
|
||||
key = self.world.create_item("Morph Ball", self.player)
|
||||
self.multiworld.random.shuffle(viable)
|
||||
key = self.multiworld.create_item("Morph Ball", self.player)
|
||||
loc = viable.pop()
|
||||
loc.place_locked_item(key)
|
||||
self.world.itempool[:] = [item for item in self.world.itempool if
|
||||
item.player != self.player or
|
||||
item.name != "Morph Ball"]
|
||||
self.multiworld.itempool[:] = [item for item in self.multiworld.itempool if
|
||||
item.player != self.player or
|
||||
item.name != "Morph Ball"]
|
||||
|
||||
if len(self.NothingPool) > 0:
|
||||
nonChozoLoc = []
|
||||
@@ -647,8 +684,8 @@ class SMWorld(World):
|
||||
else:
|
||||
nonChozoLoc.append(loc)
|
||||
|
||||
self.world.random.shuffle(nonChozoLoc)
|
||||
self.world.random.shuffle(chozoLoc)
|
||||
self.multiworld.random.shuffle(nonChozoLoc)
|
||||
self.multiworld.random.shuffle(chozoLoc)
|
||||
missingCount = len(self.NothingPool) - len(nonChozoLoc)
|
||||
locations = nonChozoLoc
|
||||
if (missingCount > 0):
|
||||
@@ -677,17 +714,17 @@ class SMWorld(World):
|
||||
break
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO):
|
||||
if self.world.area_randomization[self.player].value != 0:
|
||||
if self.multiworld.area_randomization[self.player].value != 0:
|
||||
spoiler_handle.write('\n\nArea Transitions:\n\n')
|
||||
spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_name(self.player)}: '
|
||||
if self.world.players > 1 else '', src.Name,
|
||||
spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.multiworld.get_player_name(self.player)}: '
|
||||
if self.multiworld.players > 1 else '', src.Name,
|
||||
'<=>',
|
||||
dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if not src.Boss]))
|
||||
|
||||
if self.world.boss_randomization[self.player].value != 0:
|
||||
if self.multiworld.boss_randomization[self.player].value != 0:
|
||||
spoiler_handle.write('\n\nBoss Transitions:\n\n')
|
||||
spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_name(self.player)}: '
|
||||
if self.world.players > 1 else '', src.Name,
|
||||
spoiler_handle.write('\n'.join(['%s%s %s %s' % (f'{self.multiworld.get_player_name(self.player)}: '
|
||||
if self.multiworld.players > 1 else '', src.Name,
|
||||
'<=>',
|
||||
dest.Name) for src, dest in self.variaRando.randoExec.areaGraph.InterAreaTransitions if src.Boss]))
|
||||
|
||||
@@ -698,7 +735,7 @@ def create_locations(self, player: int):
|
||||
|
||||
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.LightWorld, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for loc in locations:
|
||||
location = self.locations[loc]
|
||||
@@ -720,7 +757,7 @@ class SMLocation(Location):
|
||||
return self.always_allow(state, item) or (self.item_rule(item) and (not check_access or (self.can_reach(state) and self.can_comeback(state, item))))
|
||||
|
||||
def can_comeback(self, state: CollectionState, item: Item):
|
||||
randoExec = state.world.worlds[self.player].variaRando.randoExec
|
||||
randoExec = state.multiworld.worlds[self.player].variaRando.randoExec
|
||||
for key in locationsDict[self.name].AccessFrom.keys():
|
||||
if (randoExec.areaGraph.canAccessList( state.smbm[self.player],
|
||||
key,
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"CLIPLEN_end": "85:990F",
|
||||
"CLIPLEN_no_multi": "85:990C",
|
||||
"CLIPSET": "85:FF1D",
|
||||
"COLLECTTANK": "B8:84E8",
|
||||
"COLLECTTANK": "B8:8503",
|
||||
"MISCFX": "85:FF45",
|
||||
"NORMAL": "84:8BF2",
|
||||
"SETFX": "85:FF4E",
|
||||
@@ -12,6 +12,11 @@
|
||||
"SOUNDFX_84": "84:F9E0",
|
||||
"SPECIALFX": "85:FF3C",
|
||||
"ammo_loop_table": "84:F896",
|
||||
"ap_playerid_to_rom_other_player_index": "B8:85BA",
|
||||
"ap_playerid_to_rom_other_player_index_checklastrow": "B8:85DD",
|
||||
"ap_playerid_to_rom_other_player_index_correctindex": "B8:85F8",
|
||||
"ap_playerid_to_rom_other_player_index_do_search_stage_1": "B8:85C0",
|
||||
"ap_playerid_to_rom_other_player_index_notfound": "B8:85F5",
|
||||
"archipelago_chozo_item_plm": "84:F874",
|
||||
"archipelago_hidden_item_plm": "84:F878",
|
||||
"archipelago_visible_item_plm": "84:F870",
|
||||
@@ -35,11 +40,13 @@
|
||||
"i_item_setup_shared_all_items": "B8:8878",
|
||||
"i_item_setup_shared_alwaysloaded": "B8:8883",
|
||||
"i_live_pickup": "84:FA79",
|
||||
"i_live_pickup_multiworld": "B8:8578",
|
||||
"i_live_pickup_multiworld_end": "B8:85BD",
|
||||
"i_live_pickup_multiworld_local_item_or_offworld": "B8:8594",
|
||||
"i_live_pickup_multiworld_own_item": "B8:85A9",
|
||||
"i_live_pickup_multiworld_own_item1": "B8:85B5",
|
||||
"i_live_pickup_multiworld": "B8:85FD",
|
||||
"i_live_pickup_multiworld_end": "B8:8679",
|
||||
"i_live_pickup_multiworld_item_link_item": "B8:8659",
|
||||
"i_live_pickup_multiworld_otherplayers_item": "B8:8649",
|
||||
"i_live_pickup_multiworld_own_item": "B8:8635",
|
||||
"i_live_pickup_multiworld_own_item1": "B8:8641",
|
||||
"i_live_pickup_multiworld_send_network": "B8:8620",
|
||||
"i_load_custom_graphics": "84:FA1E",
|
||||
"i_load_custom_graphics_all_items": "84:FA39",
|
||||
"i_load_custom_graphics_alwaysloaded": "84:FA49",
|
||||
@@ -52,36 +59,41 @@
|
||||
"i_start_draw_loop_visible_or_chozo": "84:F9E5",
|
||||
"i_visible_item": "84:F8A6",
|
||||
"i_visible_item_setup": "84:FA53",
|
||||
"message_PlaceholderBig": "85:BA8A",
|
||||
"message_char_table": "85:BA0A",
|
||||
"message_hook_tilemap_calc": "85:BABC",
|
||||
"message_hook_tilemap_calc_msgbox_mwrecv": "85:BADC",
|
||||
"message_hook_tilemap_calc_msgbox_mwsend": "85:BACE",
|
||||
"message_PlaceholderBig": "85:BB73",
|
||||
"message_char_table": "85:BAF3",
|
||||
"message_hook_tilemap_calc": "85:BBAA",
|
||||
"message_hook_tilemap_calc_msgbox_mw_item_link": "85:BBDD",
|
||||
"message_hook_tilemap_calc_msgbox_mwrecv": "85:BBCF",
|
||||
"message_hook_tilemap_calc_msgbox_mwsend": "85:BBC1",
|
||||
"message_hook_tilemap_calc_normal": "85:824C",
|
||||
"message_hook_tilemap_calc_vanilla": "85:BAC9",
|
||||
"message_hook_tilemap_calc_vanilla": "85:BBBC",
|
||||
"message_item_link_distributed": "85:B9A3",
|
||||
"message_item_link_distributed_end": "85:BAA3",
|
||||
"message_item_names": "85:9963",
|
||||
"message_item_received": "85:B8A3",
|
||||
"message_item_received_end": "85:B9A3",
|
||||
"message_item_sent": "85:B7A3",
|
||||
"message_item_sent_end": "85:B8A3",
|
||||
"message_multiworld_init_new_messagebox_if_needed": "85:BA95",
|
||||
"message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv": "85:BAB1",
|
||||
"message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend": "85:BAB1",
|
||||
"message_multiworld_init_new_messagebox_if_needed_vanilla": "85:BAA9",
|
||||
"message_write_placeholders": "85:B9A3",
|
||||
"message_write_placeholders_adjust": "85:B9A5",
|
||||
"message_write_placeholders_end": "85:BA04",
|
||||
"message_write_placeholders_loop": "85:B9CA",
|
||||
"message_write_placeholders_notfound": "85:B9DC",
|
||||
"message_write_placeholders_value_ok": "85:B9DF",
|
||||
"message_multiworld_init_new_messagebox_if_needed": "85:BB7E",
|
||||
"message_multiworld_init_new_messagebox_if_needed_msgbox_mw_item_link": "85:BB9F",
|
||||
"message_multiworld_init_new_messagebox_if_needed_msgbox_mwrecv": "85:BB9F",
|
||||
"message_multiworld_init_new_messagebox_if_needed_msgbox_mwsend": "85:BB9F",
|
||||
"message_multiworld_init_new_messagebox_if_needed_vanilla": "85:BB97",
|
||||
"message_write_placeholders": "85:BAA3",
|
||||
"message_write_placeholders_adjust": "85:BAA5",
|
||||
"message_write_placeholders_end": "85:BAED",
|
||||
"mw_cleanup_item_link_messagebox": "B8:84BB",
|
||||
"mw_display_item_sent": "B8:848B",
|
||||
"mw_handle_queue": "B8:84F8",
|
||||
"mw_handle_queue_end": "B8:8571",
|
||||
"mw_handle_queue_loop": "B8:84FA",
|
||||
"mw_handle_queue_new_remote_item": "B8:854A",
|
||||
"mw_handle_queue_next": "B8:8566",
|
||||
"mw_handle_queue_perform_receive": "B8:855C",
|
||||
"mw_hook_main_game": "B8:85C1",
|
||||
"mw_handle_queue": "B8:8513",
|
||||
"mw_handle_queue_collect_item_if_present": "B8:8562",
|
||||
"mw_handle_queue_end": "B8:85B3",
|
||||
"mw_handle_queue_found": "B8:859B",
|
||||
"mw_handle_queue_lookup_player": "B8:8522",
|
||||
"mw_handle_queue_loop": "B8:8515",
|
||||
"mw_handle_queue_new_remote_item": "B8:857C",
|
||||
"mw_handle_queue_next": "B8:85A7",
|
||||
"mw_handle_queue_perform_receive": "B8:858E",
|
||||
"mw_hook_main_game": "B8:867D",
|
||||
"mw_init": "B8:8311",
|
||||
"mw_init_continuereset": "B8:8366",
|
||||
"mw_init_end": "B8:83EA",
|
||||
@@ -91,8 +103,9 @@
|
||||
"mw_load_sram": "B8:8474",
|
||||
"mw_load_sram_done": "B8:8482",
|
||||
"mw_load_sram_setnewgame": "B8:8485",
|
||||
"mw_receive_item": "B8:84A9",
|
||||
"mw_receive_item_end": "B8:84E1",
|
||||
"mw_prep_item_link_messagebox": "B8:84A9",
|
||||
"mw_receive_item": "B8:84C4",
|
||||
"mw_receive_item_end": "B8:84FC",
|
||||
"mw_save_sram": "B8:8469",
|
||||
"mw_write_message": "B8:8442",
|
||||
"nonprog_item_eight_palette_indices": "84:F888",
|
||||
@@ -119,16 +132,16 @@
|
||||
"p_visible_item_end": "84:F96E",
|
||||
"p_visible_item_loop": "84:F95B",
|
||||
"p_visible_item_trigger": "84:F967",
|
||||
"patch_load_multiworld": "B8:85D8",
|
||||
"patch_load_multiworld": "B8:8694",
|
||||
"perform_item_pickup": "84:FA7E",
|
||||
"plm_graphics_entry_offworld_item": "84:F886",
|
||||
"plm_graphics_entry_offworld_progression_item": "84:F87C",
|
||||
"plm_sequence_generic_item_0_bitmask": "84:FA90",
|
||||
"prog_item_eight_palette_indices": "84:F87E",
|
||||
"rando_item_table": "B8:E000",
|
||||
"rando_player_id_table": "B8:DC90",
|
||||
"rando_player_id_table_end": "B8:DE22",
|
||||
"rando_player_table": "B8:D000",
|
||||
"rando_player_id_table": "B8:DCA0",
|
||||
"rando_player_id_table_end": "B8:DE34",
|
||||
"rando_player_name_table": "B8:D000",
|
||||
"rando_seed_data": "B8:CF00",
|
||||
"sm_item_graphics": "B8:8800",
|
||||
"sm_item_plm_pickup_sequence_pointers": "B8:882E",
|
||||
|
||||
@@ -43,18 +43,18 @@ class SM64World(World):
|
||||
option_definitions = sm64_options
|
||||
|
||||
def generate_early(self):
|
||||
self.topology_present = self.world.AreaRandomizer[self.player].value
|
||||
self.topology_present = self.multiworld.AreaRandomizer[self.player].value
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world,self.player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def set_rules(self):
|
||||
self.area_connections = {}
|
||||
set_rules(self.world, self.player, self.area_connections)
|
||||
set_rules(self.multiworld, self.player, self.area_connections)
|
||||
if self.topology_present:
|
||||
# Write area_connections to spoiler log
|
||||
for entrance, destination in self.area_connections.items():
|
||||
self.world.spoiler.set_entrance(
|
||||
self.multiworld.spoiler.set_entrance(
|
||||
sm64_internalloc_to_string[entrance] + " Entrance",
|
||||
sm64_internalloc_to_string[destination],
|
||||
'entrance', self.player)
|
||||
@@ -72,74 +72,74 @@ class SM64World(World):
|
||||
return item
|
||||
|
||||
def generate_basic(self):
|
||||
starcount = self.world.AmountOfStars[self.player].value
|
||||
if (not self.world.EnableCoinStars[self.player].value):
|
||||
starcount = max(35,self.world.AmountOfStars[self.player].value-15)
|
||||
starcount = max(starcount, self.world.FirstBowserStarDoorCost[self.player].value,
|
||||
self.world.BasementStarDoorCost[self.player].value, self.world.SecondFloorStarDoorCost[self.player].value,
|
||||
self.world.MIPS1Cost[self.player].value, self.world.MIPS2Cost[self.player].value,
|
||||
self.world.StarsToFinish[self.player].value)
|
||||
self.world.itempool += [self.create_item("Power Star") for i in range(0,starcount)]
|
||||
self.world.itempool += [self.create_item("1Up Mushroom") for i in range(starcount,120 - (15 if not self.world.EnableCoinStars[self.player].value else 0))]
|
||||
starcount = self.multiworld.AmountOfStars[self.player].value
|
||||
if (not self.multiworld.EnableCoinStars[self.player].value):
|
||||
starcount = max(35,self.multiworld.AmountOfStars[self.player].value-15)
|
||||
starcount = max(starcount, self.multiworld.FirstBowserStarDoorCost[self.player].value,
|
||||
self.multiworld.BasementStarDoorCost[self.player].value, self.multiworld.SecondFloorStarDoorCost[self.player].value,
|
||||
self.multiworld.MIPS1Cost[self.player].value, self.multiworld.MIPS2Cost[self.player].value,
|
||||
self.multiworld.StarsToFinish[self.player].value)
|
||||
self.multiworld.itempool += [self.create_item("Power Star") for i in range(0,starcount)]
|
||||
self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(starcount,120 - (15 if not self.multiworld.EnableCoinStars[self.player].value else 0))]
|
||||
|
||||
if (not self.world.ProgressiveKeys[self.player].value):
|
||||
if (not self.multiworld.ProgressiveKeys[self.player].value):
|
||||
key1 = self.create_item("Basement Key")
|
||||
key2 = self.create_item("Second Floor Key")
|
||||
self.world.itempool += [key1,key2]
|
||||
self.multiworld.itempool += [key1, key2]
|
||||
else:
|
||||
self.world.itempool += [self.create_item("Progressive Key") for i in range(0,2)]
|
||||
self.multiworld.itempool += [self.create_item("Progressive Key") for i in range(0,2)]
|
||||
|
||||
wingcap = self.create_item("Wing Cap")
|
||||
metalcap = self.create_item("Metal Cap")
|
||||
vanishcap = self.create_item("Vanish Cap")
|
||||
self.world.itempool += [wingcap,metalcap,vanishcap]
|
||||
self.multiworld.itempool += [wingcap, metalcap, vanishcap]
|
||||
|
||||
if (self.world.BuddyChecks[self.player].value):
|
||||
self.world.itempool += [self.create_item(name) for name, id in cannon_item_table.items()]
|
||||
if (self.multiworld.BuddyChecks[self.player].value):
|
||||
self.multiworld.itempool += [self.create_item(name) for name, id in cannon_item_table.items()]
|
||||
else:
|
||||
self.world.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB"))
|
||||
self.world.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF"))
|
||||
self.world.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB"))
|
||||
self.world.get_location("CCM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock CCM"))
|
||||
self.world.get_location("SSL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SSL"))
|
||||
self.world.get_location("SL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SL"))
|
||||
self.world.get_location("WDW: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WDW"))
|
||||
self.world.get_location("TTM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock TTM"))
|
||||
self.world.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI"))
|
||||
self.world.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR"))
|
||||
self.multiworld.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB"))
|
||||
self.multiworld.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF"))
|
||||
self.multiworld.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB"))
|
||||
self.multiworld.get_location("CCM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock CCM"))
|
||||
self.multiworld.get_location("SSL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SSL"))
|
||||
self.multiworld.get_location("SL: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock SL"))
|
||||
self.multiworld.get_location("WDW: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WDW"))
|
||||
self.multiworld.get_location("TTM: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock TTM"))
|
||||
self.multiworld.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI"))
|
||||
self.multiworld.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR"))
|
||||
|
||||
if (self.world.ExclamationBoxes[self.player].value > 0):
|
||||
self.world.itempool += [self.create_item("1Up Mushroom") for i in range(0,29)]
|
||||
if (self.multiworld.ExclamationBoxes[self.player].value > 0):
|
||||
self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(0,29)]
|
||||
else:
|
||||
self.world.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("BBH: 1Up Block Top of Mansion", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("HMC: 1Up Block above Pit", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("HMC: 1Up Block Past Rolling Rocks", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("SSL: 1Up Block Outside Pyramid", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("SSL: 1Up Block Pyramid Left Path", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("SSL: 1Up Block Pyramid Back", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("SL: 1Up Block Near Moneybags", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("SL: 1Up Block inside Igloo", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("WDW: 1Up Block in Downtown", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("TTM: 1Up Block on Red Mushroom", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("THI: 1Up Block THI Small near Start", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("THI: 1Up Block THI Large near Start", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("THI: 1Up Block Windy Area", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("TTC: 1Up Block Midway Up", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("TTC: 1Up Block at the Top", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("RR: 1Up Block Top of Red Coin Maze", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("RR: 1Up Block Under Fly Guy", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("RR: 1Up Block On House in the Sky", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Bowser in the Dark World 1Up Block on Tower", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Bowser in the Dark World 1Up Block near Goombas", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Cavern of the Metal Cap 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Vanish Cap Under the Moat 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Bowser in the Fire Sea 1Up Block Swaying Stairs", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Bowser in the Fire Sea 1Up Block Near Poles", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Wing Mario Over the Rainbow 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.world.get_location("Bowser in the Sky 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("BBH: 1Up Block Top of Mansion", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("HMC: 1Up Block above Pit", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("HMC: 1Up Block Past Rolling Rocks", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("SSL: 1Up Block Outside Pyramid", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("SSL: 1Up Block Pyramid Left Path", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("SSL: 1Up Block Pyramid Back", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("SL: 1Up Block Near Moneybags", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("SL: 1Up Block inside Igloo", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("WDW: 1Up Block in Downtown", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("TTM: 1Up Block on Red Mushroom", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("THI: 1Up Block THI Small near Start", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("THI: 1Up Block THI Large near Start", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("THI: 1Up Block Windy Area", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("TTC: 1Up Block Midway Up", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("TTC: 1Up Block at the Top", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("RR: 1Up Block Top of Red Coin Maze", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("RR: 1Up Block Under Fly Guy", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("RR: 1Up Block On House in the Sky", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Bowser in the Dark World 1Up Block on Tower", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Bowser in the Dark World 1Up Block near Goombas", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Cavern of the Metal Cap 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Vanish Cap Under the Moat 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Bowser in the Fire Sea 1Up Block Swaying Stairs", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Bowser in the Fire Sea 1Up Block Near Poles", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Wing Mario Over the Rainbow 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
self.multiworld.get_location("Bowser in the Sky 1Up Block", self.player).place_locked_item(self.create_item("1Up Mushroom"))
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "1Up Mushroom"
|
||||
@@ -147,21 +147,21 @@ class SM64World(World):
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"AreaRando": self.area_connections,
|
||||
"FirstBowserDoorCost": self.world.FirstBowserStarDoorCost[self.player].value,
|
||||
"BasementDoorCost": self.world.BasementStarDoorCost[self.player].value,
|
||||
"SecondFloorDoorCost": self.world.SecondFloorStarDoorCost[self.player].value,
|
||||
"MIPS1Cost": self.world.MIPS1Cost[self.player].value,
|
||||
"MIPS2Cost": self.world.MIPS2Cost[self.player].value,
|
||||
"StarsToFinish": self.world.StarsToFinish[self.player].value,
|
||||
"DeathLink": self.world.death_link[self.player].value,
|
||||
"FirstBowserDoorCost": self.multiworld.FirstBowserStarDoorCost[self.player].value,
|
||||
"BasementDoorCost": self.multiworld.BasementStarDoorCost[self.player].value,
|
||||
"SecondFloorDoorCost": self.multiworld.SecondFloorStarDoorCost[self.player].value,
|
||||
"MIPS1Cost": self.multiworld.MIPS1Cost[self.player].value,
|
||||
"MIPS2Cost": self.multiworld.MIPS2Cost[self.player].value,
|
||||
"StarsToFinish": self.multiworld.StarsToFinish[self.player].value,
|
||||
"DeathLink": self.multiworld.death_link[self.player].value,
|
||||
}
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
if self.world.players != 1:
|
||||
if self.multiworld.players != 1:
|
||||
return
|
||||
data = {
|
||||
"slot_data": self.fill_slot_data(),
|
||||
"location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.world.get_locations()},
|
||||
"location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.multiworld.get_locations()},
|
||||
"data_package": {
|
||||
"data": {
|
||||
"games": {
|
||||
@@ -173,7 +173,7 @@ class SM64World(World):
|
||||
}
|
||||
}
|
||||
}
|
||||
filename = f"{self.world.get_out_file_name_base(self.player)}.apsm64ex"
|
||||
filename = f"{self.multiworld.get_out_file_name_base(self.player)}.apsm64ex"
|
||||
with open(os.path.join(output_directory, filename), 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
@@ -182,7 +182,7 @@ class SM64World(World):
|
||||
er_hint_data = {}
|
||||
for entrance, destination in self.area_connections.items():
|
||||
regionid = sm64_internalloc_to_regionid[destination]
|
||||
region = self.world.get_region(sm64courses[regionid], self.player)
|
||||
region = self.multiworld.get_region(sm64courses[regionid], self.player)
|
||||
for location in region.locations:
|
||||
er_hint_data[location.address] = sm64_internalloc_to_string[entrance]
|
||||
multidata['er_hint_data'][self.player] = er_hint_data
|
||||
|
||||
@@ -62,14 +62,14 @@ class SMWWorld(World):
|
||||
|
||||
def _get_slot_data(self):
|
||||
return {
|
||||
#"death_link": self.world.death_link[self.player].value,
|
||||
#"death_link": self.multiworld.death_link[self.player].value,
|
||||
"active_levels": self.active_level_dict,
|
||||
}
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = self._get_slot_data()
|
||||
for option_name in smw_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = option.value
|
||||
|
||||
return slot_data
|
||||
@@ -77,20 +77,20 @@ class SMWWorld(World):
|
||||
def generate_basic(self):
|
||||
itempool: typing.List[SMWItem] = []
|
||||
|
||||
self.active_level_dict = dict(zip(generate_level_list(self.world, self.player), full_level_list))
|
||||
self.topology_present = self.world.level_shuffle[self.player]
|
||||
self.active_level_dict = dict(zip(generate_level_list(self.multiworld, self.player), full_level_list))
|
||||
self.topology_present = self.multiworld.level_shuffle[self.player]
|
||||
|
||||
connect_regions(self.world, self.player, self.active_level_dict)
|
||||
connect_regions(self.multiworld, self.player, self.active_level_dict)
|
||||
|
||||
# Add Boss Token amount requirements for Worlds
|
||||
add_rule(self.world.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1))
|
||||
add_rule(self.world.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2))
|
||||
add_rule(self.world.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4))
|
||||
add_rule(self.world.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5))
|
||||
add_rule(self.world.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6))
|
||||
add_rule(self.multiworld.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1))
|
||||
add_rule(self.multiworld.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2))
|
||||
add_rule(self.multiworld.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4))
|
||||
add_rule(self.multiworld.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5))
|
||||
add_rule(self.multiworld.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6))
|
||||
|
||||
total_required_locations = 96
|
||||
if self.world.dragon_coin_checks[self.player]:
|
||||
if self.multiworld.dragon_coin_checks[self.player]:
|
||||
total_required_locations += 49
|
||||
|
||||
itempool += [self.create_item(ItemName.mario_run)]
|
||||
@@ -108,24 +108,24 @@ class SMWWorld(World):
|
||||
itempool += [self.create_item(ItemName.red_switch_palace)]
|
||||
itempool += [self.create_item(ItemName.blue_switch_palace)]
|
||||
|
||||
if self.world.goal[self.player] == "yoshi_egg_hunt":
|
||||
if self.multiworld.goal[self.player] == "yoshi_egg_hunt":
|
||||
itempool += [self.create_item(ItemName.yoshi_egg)
|
||||
for _ in range(self.world.number_of_yoshi_eggs[self.player])]
|
||||
self.world.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
for _ in range(self.multiworld.number_of_yoshi_eggs[self.player])]
|
||||
self.multiworld.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
else:
|
||||
self.world.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
self.multiworld.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory))
|
||||
|
||||
junk_count = total_required_locations - len(itempool)
|
||||
trap_weights = []
|
||||
trap_weights += ([ItemName.ice_trap] * self.world.ice_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.stun_trap] * self.world.stun_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.literature_trap] * self.world.literature_trap_weight[self.player].value)
|
||||
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.world.trap_fill_percentage[self.player].value / 100.0))
|
||||
trap_weights += ([ItemName.ice_trap] * self.multiworld.ice_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.stun_trap] * self.multiworld.stun_trap_weight[self.player].value)
|
||||
trap_weights += ([ItemName.literature_trap] * self.multiworld.literature_trap_weight[self.player].value)
|
||||
trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.multiworld.trap_fill_percentage[self.player].value / 100.0))
|
||||
junk_count -= trap_count
|
||||
|
||||
trap_pool = []
|
||||
for i in range(trap_count):
|
||||
trap_item = self.world.random.choice(trap_weights)
|
||||
trap_item = self.multiworld.random.choice(trap_weights)
|
||||
trap_pool.append(self.create_item(trap_item))
|
||||
|
||||
itempool += trap_pool
|
||||
@@ -137,21 +137,21 @@ class SMWWorld(World):
|
||||
LocationName.valley_koopaling, LocationName.vanilla_reznor, LocationName.forest_reznor, LocationName.chocolate_reznor, LocationName.valley_reznor]
|
||||
|
||||
for location_name in boss_location_names:
|
||||
self.world.get_location(location_name, self.player).place_locked_item(self.create_item(ItemName.koopaling))
|
||||
self.multiworld.get_location(location_name, self.player).place_locked_item(self.create_item(ItemName.koopaling))
|
||||
|
||||
self.world.itempool += itempool
|
||||
self.multiworld.itempool += itempool
|
||||
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
rompath = "" # if variable is not declared finally clause may fail
|
||||
try:
|
||||
world = self.world
|
||||
world = self.multiworld
|
||||
player = self.player
|
||||
|
||||
rom = LocalRom(get_base_rom_path())
|
||||
patch_rom(self.world, rom, self.player, self.active_level_dict)
|
||||
patch_rom(self.multiworld, rom, self.player, self.active_level_dict)
|
||||
|
||||
rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc")
|
||||
rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc")
|
||||
rom.write_to_file(rompath)
|
||||
self.rom_name = rom.name
|
||||
|
||||
@@ -173,7 +173,7 @@ class SMWWorld(World):
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if rom_name:
|
||||
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
|
||||
def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
|
||||
if self.topology_present:
|
||||
@@ -212,18 +212,18 @@ class SMWWorld(World):
|
||||
if level_index >= world_cutoffs[i]:
|
||||
continue
|
||||
|
||||
if self.world.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name:
|
||||
if self.multiworld.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name:
|
||||
continue
|
||||
|
||||
location = self.world.get_location(loc_name, self.player)
|
||||
location = self.multiworld.get_location(loc_name, self.player)
|
||||
er_hint_data[location.address] = world_names[i]
|
||||
break
|
||||
|
||||
hint_data[self.player] = er_hint_data
|
||||
|
||||
def create_regions(self):
|
||||
location_table = setup_locations(self.world, self.player)
|
||||
create_regions(self.world, self.player, location_table)
|
||||
location_table = setup_locations(self.multiworld, self.player)
|
||||
create_regions(self.multiworld, self.player, location_table)
|
||||
|
||||
def create_item(self, name: str, force_non_progression=False) -> Item:
|
||||
data = item_table[name]
|
||||
@@ -244,4 +244,4 @@ class SMWWorld(World):
|
||||
return created_item
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
@@ -190,24 +190,24 @@ class SMZ3World(World):
|
||||
self.config = Config()
|
||||
self.config.GameMode = GameMode.Multiworld
|
||||
self.config.Z3Logic = Z3Logic.Normal
|
||||
self.config.SMLogic = SMLogic(self.world.sm_logic[self.player].value)
|
||||
self.config.SwordLocation = SwordLocation(self.world.sword_location[self.player].value)
|
||||
self.config.MorphLocation = MorphLocation(self.world.morph_location[self.player].value)
|
||||
self.config.Goal = Goal(self.world.goal[self.player].value)
|
||||
self.config.KeyShuffle = KeyShuffle(self.world.key_shuffle[self.player].value)
|
||||
self.config.OpenTower = OpenTower(self.world.open_tower[self.player].value)
|
||||
self.config.GanonVulnerable = GanonVulnerable(self.world.ganon_vulnerable[self.player].value)
|
||||
self.config.OpenTourian = OpenTourian(self.world.open_tourian[self.player].value)
|
||||
self.config.SMLogic = SMLogic(self.multiworld.sm_logic[self.player].value)
|
||||
self.config.SwordLocation = SwordLocation(self.multiworld.sword_location[self.player].value)
|
||||
self.config.MorphLocation = MorphLocation(self.multiworld.morph_location[self.player].value)
|
||||
self.config.Goal = Goal(self.multiworld.goal[self.player].value)
|
||||
self.config.KeyShuffle = KeyShuffle(self.multiworld.key_shuffle[self.player].value)
|
||||
self.config.OpenTower = OpenTower(self.multiworld.open_tower[self.player].value)
|
||||
self.config.GanonVulnerable = GanonVulnerable(self.multiworld.ganon_vulnerable[self.player].value)
|
||||
self.config.OpenTourian = OpenTourian(self.multiworld.open_tourian[self.player].value)
|
||||
|
||||
self.local_random = random.Random(self.world.random.randint(0, 1000))
|
||||
self.smz3World = TotalSMZ3World(self.config, self.world.get_player_name(self.player), self.player, self.world.seed_name)
|
||||
self.local_random = random.Random(self.multiworld.random.randint(0, 1000))
|
||||
self.smz3World = TotalSMZ3World(self.config, self.multiworld.get_player_name(self.player), self.player, self.multiworld.seed_name)
|
||||
self.smz3DungeonItems = []
|
||||
SMZ3World.location_names = frozenset(self.smz3World.locationLookup.keys())
|
||||
|
||||
self.world.state.smz3state[self.player] = TotalSMZ3Item.Progression([])
|
||||
self.multiworld.state.smz3state[self.player] = TotalSMZ3Item.Progression([])
|
||||
|
||||
def generate_basic(self):
|
||||
self.smz3World.Setup(WorldState.Generate(self.config, self.world.random))
|
||||
self.smz3World.Setup(WorldState.Generate(self.config, self.multiworld.random))
|
||||
self.dungeon = TotalSMZ3Item.Item.CreateDungeonPool(self.smz3World)
|
||||
self.dungeon.reverse()
|
||||
self.progression = TotalSMZ3Item.Item.CreateProgressionPool(self.smz3World)
|
||||
@@ -224,25 +224,25 @@ class SMZ3World(World):
|
||||
else:
|
||||
progressionItems = self.progression
|
||||
for item in self.keyCardsItems:
|
||||
self.world.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item))
|
||||
self.multiworld.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item))
|
||||
|
||||
itemPool = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in progressionItems] + \
|
||||
[SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in allJunkItems]
|
||||
self.smz3DungeonItems = [SMZ3Item(item.Type.name, ItemClassification.progression, item.Type, self.item_name_to_id[item.Type.name], self.player, item) for item in self.dungeon]
|
||||
self.world.itempool += itemPool
|
||||
self.multiworld.itempool += itemPool
|
||||
|
||||
def set_rules(self):
|
||||
# SM G4 is logically required to access Ganon's Tower in SMZ3
|
||||
self.world.completion_condition[self.player] = lambda state: \
|
||||
self.multiworld.completion_condition[self.player] = lambda state: \
|
||||
self.smz3World.GetRegion("Ganon's Tower").CanEnter(state.smz3state[self.player]) and \
|
||||
self.smz3World.GetRegion("Ganon's Tower").TowerAscend(state.smz3state[self.player])
|
||||
|
||||
for region in self.smz3World.Regions:
|
||||
entrance = self.world.get_entrance('Menu' + "->" + region.Name, self.player)
|
||||
entrance = self.multiworld.get_entrance('Menu' + "->" + region.Name, self.player)
|
||||
set_rule(entrance, lambda state, region=region: region.CanEnter(state.smz3state[self.player]))
|
||||
for loc in region.Locations:
|
||||
l = self.locations[loc.Name]
|
||||
if self.world.accessibility[self.player] != 'locations':
|
||||
if self.multiworld.accessibility[self.player] != 'locations':
|
||||
l.always_allow = lambda state, item, loc=loc: \
|
||||
item.game == "SMZ3" and \
|
||||
loc.alwaysAllow(TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World), state.smz3state[self.player])
|
||||
@@ -255,13 +255,13 @@ class SMZ3World(World):
|
||||
|
||||
def create_regions(self):
|
||||
self.create_locations(self.player)
|
||||
startRegion = self.create_region(self.world, self.player, 'Menu')
|
||||
self.world.regions.append(startRegion)
|
||||
startRegion = self.create_region(self.multiworld, self.player, 'Menu')
|
||||
self.multiworld.regions.append(startRegion)
|
||||
|
||||
for region in self.smz3World.Regions:
|
||||
currentRegion = self.create_region(self.world, self.player, region.Name, region.locationLookup.keys(), [region.Name + "->" + 'Menu'])
|
||||
self.world.regions.append(currentRegion)
|
||||
entrance = self.world.get_entrance(region.Name + "->" + 'Menu', self.player)
|
||||
currentRegion = self.create_region(self.multiworld, self.player, region.Name, region.locationLookup.keys(), [region.Name + "->" + 'Menu'])
|
||||
self.multiworld.regions.append(currentRegion)
|
||||
entrance = self.multiworld.get_entrance(region.Name + "->" + 'Menu', self.player)
|
||||
entrance.connect(startRegion)
|
||||
exit = Entrance(self.player, 'Menu' + "->" + region.Name, startRegion)
|
||||
startRegion.exits.append(exit)
|
||||
@@ -347,7 +347,7 @@ class SMZ3World(World):
|
||||
sm_remote_idx = 0
|
||||
lttp_remote_idx = 0
|
||||
for location in self.smz3World.Locations:
|
||||
if self.world.worlds[location.APLocation.item.player].game != self.game:
|
||||
if self.multiworld.worlds[location.APLocation.item.player].game != self.game:
|
||||
if location.Type == LocationType.Visible or location.Type == LocationType.Chozo or location.Type == LocationType.Hidden:
|
||||
patch[0x390000 + sm_remote_idx*64] = self.convert_to_sm_item_name(location.APLocation.item.name)
|
||||
sm_remote_idx += 1
|
||||
@@ -369,12 +369,12 @@ class SMZ3World(World):
|
||||
patch = {}
|
||||
|
||||
# smSpinjumps
|
||||
if (self.world.spin_jumps_animation[self.player].value == 1):
|
||||
if (self.multiworld.spin_jumps_animation[self.player].value == 1):
|
||||
patch[self.SnesCustomization(0x9B93FE)] = bytearray([0x01])
|
||||
|
||||
# z3HeartBeep
|
||||
values = [ 0x00, 0x80, 0x40, 0x20, 0x10]
|
||||
index = self.world.heart_beep_speed[self.player].value
|
||||
index = self.multiworld.heart_beep_speed[self.player].value
|
||||
patch[0x400033] = bytearray([values[index if index < len(values) else 2]])
|
||||
|
||||
# z3HeartColor
|
||||
@@ -384,17 +384,17 @@ class SMZ3World(World):
|
||||
[0x2C, [0xC9, 0x69]],
|
||||
[0x28, [0xBC, 0x02]]
|
||||
]
|
||||
index = self.world.heart_color[self.player].value
|
||||
index = self.multiworld.heart_color[self.player].value
|
||||
(hud, fileSelect) = values[index if index < len(values) else 0]
|
||||
for i in range(0, 20, 2):
|
||||
patch[self.SnesCustomization(0xDFA1E + i)] = bytearray([hud])
|
||||
patch[self.SnesCustomization(0x1BD6AA)] = bytearray(fileSelect)
|
||||
|
||||
# z3QuickSwap
|
||||
patch[0x40004B] = bytearray([0x01 if self.world.quick_swap[self.player].value else 0x00])
|
||||
patch[0x40004B] = bytearray([0x01 if self.multiworld.quick_swap[self.player].value else 0x00])
|
||||
|
||||
# smEnergyBeepOff
|
||||
if (self.world.energy_beep[self.player].value == 0):
|
||||
if (self.multiworld.energy_beep[self.player].value == 0):
|
||||
for ([addr, value]) in [
|
||||
[0x90EA9B, 0x80],
|
||||
[0x90F337, 0x80],
|
||||
@@ -411,12 +411,12 @@ class SMZ3World(World):
|
||||
base_combined_rom = basepatch.apply(base_combined_rom)
|
||||
|
||||
patcher = TotalSMZ3Patch(self.smz3World,
|
||||
[world.smz3World for key, world in self.world.worlds.items() if isinstance(world, SMZ3World) and hasattr(world, "smz3World")],
|
||||
self.world.seed_name,
|
||||
self.world.seed,
|
||||
self.local_random,
|
||||
{v: k for k, v in self.world.player_name.items()},
|
||||
next(iter(loc.player for loc in self.world.get_locations() if (loc.item.name == "SilverArrows" and loc.item.player == self.player))))
|
||||
[world.smz3World for key, world in self.multiworld.worlds.items() if isinstance(world, SMZ3World) and hasattr(world, "smz3World")],
|
||||
self.multiworld.seed_name,
|
||||
self.multiworld.seed,
|
||||
self.local_random,
|
||||
{v: k for k, v in self.multiworld.player_name.items()},
|
||||
next(iter(loc.player for loc in self.multiworld.get_locations() if (loc.item.name == "SilverArrows" and loc.item.player == self.player))))
|
||||
patches = patcher.Create(self.smz3World.Config)
|
||||
patches.update(self.apply_sm_custom_sprite())
|
||||
patches.update(self.apply_item_names())
|
||||
@@ -427,13 +427,13 @@ class SMZ3World(World):
|
||||
base_combined_rom[addr + offset] = byte
|
||||
offset += 1
|
||||
|
||||
outfilebase = self.world.get_out_file_name_base(self.player)
|
||||
outfilebase = self.multiworld.get_out_file_name_base(self.player)
|
||||
|
||||
filename = os.path.join(output_directory, f"{outfilebase}.sfc")
|
||||
with open(filename, "wb") as binary_file:
|
||||
binary_file.write(base_combined_rom)
|
||||
patch = SMZ3DeltaPatch(os.path.splitext(filename)[0]+SMZ3DeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.world.player_name[self.player], patched_path=filename)
|
||||
patch = SMZ3DeltaPatch(os.path.splitext(filename)[0] + SMZ3DeltaPatch.patch_file_ending, player=self.player,
|
||||
player_name=self.multiworld.player_name[self.player], patched_path=filename)
|
||||
patch.write()
|
||||
os.remove(filename)
|
||||
self.rom_name = bytearray(patcher.title, 'utf8')
|
||||
@@ -458,7 +458,7 @@ class SMZ3World(World):
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if rom_name:
|
||||
new_name = base64.b64encode(bytes(self.rom_name)).decode()
|
||||
payload = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
payload = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
multidata["connect_names"][new_name] = payload
|
||||
|
||||
def fill_slot_data(self):
|
||||
@@ -495,18 +495,18 @@ class SMZ3World(World):
|
||||
|
||||
if (not self.smz3World.Config.Keysanity):
|
||||
locations = [loc for loc in self.locations.values() if loc.item is None]
|
||||
self.world.random.shuffle(locations)
|
||||
self.multiworld.random.shuffle(locations)
|
||||
|
||||
all_state = self.world.get_all_state(False)
|
||||
all_state = self.multiworld.get_all_state(False)
|
||||
for item in self.smz3DungeonItems:
|
||||
all_state.remove(item)
|
||||
|
||||
all_dungeonItems = self.smz3DungeonItems[:]
|
||||
fill_restrictive(self.world, all_state, locations, all_dungeonItems, True, True)
|
||||
fill_restrictive(self.multiworld, all_state, locations, all_dungeonItems, True, True)
|
||||
# some small or big keys (those always_allow) can be unreachable in-game
|
||||
# while logic still collects some of them (probably to simulate the player collecting pot keys in the logic), some others don't
|
||||
# so we need to remove those exceptions as progression items
|
||||
if self.world.accessibility[self.player] != 'locations':
|
||||
if self.multiworld.accessibility[self.player] != 'locations':
|
||||
exception_item = [TotalSMZ3Item.ItemType.BigKeySW, TotalSMZ3Item.ItemType.BigKeySP, TotalSMZ3Item.ItemType.KeyTH]
|
||||
for item in self.smz3DungeonItems:
|
||||
if item.item.Type in exception_item and item.location.always_allow(all_state, item) and not all_state.can_reach(item.location):
|
||||
@@ -523,18 +523,18 @@ class SMZ3World(World):
|
||||
return []
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(self.junkItemsNames)
|
||||
return self.multiworld.random.choice(self.junkItemsNames)
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO):
|
||||
self.world.spoiler.unreachables.update(self.unreachable)
|
||||
self.multiworld.spoiler.unreachables.update(self.unreachable)
|
||||
|
||||
def JunkFillGT(self, factor):
|
||||
poolLength = len(self.world.itempool)
|
||||
playerGroups = self.world.get_player_groups(self.player)
|
||||
poolLength = len(self.multiworld.itempool)
|
||||
playerGroups = self.multiworld.get_player_groups(self.player)
|
||||
playerGroups.add(self.player)
|
||||
junkPoolIdx = [i for i in range(0, poolLength)
|
||||
if self.world.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) and
|
||||
self.world.itempool[i].player in playerGroups]
|
||||
junkPoolIdx = [i for i in range(0, poolLength)
|
||||
if self.multiworld.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) and
|
||||
self.multiworld.itempool[i].player in playerGroups]
|
||||
toRemove = []
|
||||
for loc in self.locations.values():
|
||||
# commenting this for now since doing a partial GT pre fill would allow for non SMZ3 progression in GT
|
||||
@@ -544,19 +544,19 @@ class SMZ3World(World):
|
||||
if loc.name in self.locationNamesGT and loc.item is None:
|
||||
poolLength = len(junkPoolIdx)
|
||||
# start looking at a random starting index and loop at start if no match found
|
||||
start = self.world.random.randint(0, poolLength)
|
||||
start = self.multiworld.random.randint(0, poolLength)
|
||||
for off in range(0, poolLength):
|
||||
i = (start + off) % poolLength
|
||||
candidate = self.world.itempool[junkPoolIdx[i]]
|
||||
if junkPoolIdx[i] not in toRemove and loc.can_fill(self.world.state, candidate, False):
|
||||
candidate = self.multiworld.itempool[junkPoolIdx[i]]
|
||||
if junkPoolIdx[i] not in toRemove and loc.can_fill(self.multiworld.state, candidate, False):
|
||||
itemFromPool = candidate
|
||||
toRemove.append(junkPoolIdx[i])
|
||||
break
|
||||
self.world.push_item(loc, itemFromPool, False)
|
||||
self.multiworld.push_item(loc, itemFromPool, False)
|
||||
loc.event = False
|
||||
toRemove.sort(reverse = True)
|
||||
for i in toRemove:
|
||||
self.world.itempool.pop(i)
|
||||
self.multiworld.itempool.pop(i)
|
||||
|
||||
def FillItemAtLocation(self, itemPool, itemType, location):
|
||||
itemToPlace = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World)
|
||||
@@ -564,28 +564,28 @@ class SMZ3World(World):
|
||||
raise Exception(f"Tried to place item {itemType} at {location.Name}, but there is no such item in the item pool")
|
||||
else:
|
||||
location.Item = itemToPlace
|
||||
itemFromPool = next((i for i in self.world.itempool if i.player == self.player and i.name == itemToPlace.Type.name), None)
|
||||
itemFromPool = next((i for i in self.multiworld.itempool if i.player == self.player and i.name == itemToPlace.Type.name), None)
|
||||
if itemFromPool is not None:
|
||||
self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||
self.world.itempool.remove(itemFromPool)
|
||||
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||
self.multiworld.itempool.remove(itemFromPool)
|
||||
else:
|
||||
itemFromPool = next((i for i in self.smz3DungeonItems if i.player == self.player and i.name == itemToPlace.Type.name), None)
|
||||
if itemFromPool is not None:
|
||||
self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||
self.smz3DungeonItems.remove(itemFromPool)
|
||||
itemPool.remove(itemToPlace)
|
||||
|
||||
def FrontFillItemInOwnWorld(self, itemPool, itemType):
|
||||
item = TotalSMZ3Item.Item.Get(itemPool, itemType, self.smz3World)
|
||||
location = next(iter(self.world.random.sample(TotalSMZ3Location.AvailableGlobal(TotalSMZ3Location.Empty(self.smz3World.Locations), self.smz3World.Items()), 1)), None)
|
||||
location = next(iter(self.multiworld.random.sample(TotalSMZ3Location.AvailableGlobal(TotalSMZ3Location.Empty(self.smz3World.Locations), self.smz3World.Items()), 1)), None)
|
||||
if (location == None):
|
||||
raise Exception(f"Tried to front fill {item.Name} in, but no location was available")
|
||||
|
||||
location.Item = item
|
||||
itemFromPool = next((i for i in self.world.itempool if i.player == self.player and i.name == item.Type.name and i.advancement == item.Progression), None)
|
||||
itemFromPool = next((i for i in self.multiworld.itempool if i.player == self.player and i.name == item.Type.name and i.advancement == item.Progression), None)
|
||||
if itemFromPool is not None:
|
||||
self.world.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||
self.world.itempool.remove(itemFromPool)
|
||||
self.multiworld.get_location(location.Name, self.player).place_locked_item(itemFromPool)
|
||||
self.multiworld.itempool.remove(itemFromPool)
|
||||
itemPool.remove(item)
|
||||
|
||||
def InitialFillInOwnWorld(self):
|
||||
@@ -621,7 +621,7 @@ class SMZ3World(World):
|
||||
|
||||
def create_region(self, world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.LightWorld, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for loc in locations:
|
||||
location = self.locations[loc]
|
||||
|
||||
@@ -178,11 +178,11 @@ class SoEWorld(World):
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# store option values that change logic
|
||||
self.energy_core = self.world.energy_core[self.player].value
|
||||
self.required_fragments = self.world.required_fragments[self.player].value
|
||||
if self.required_fragments > self.world.available_fragments[self.player].value:
|
||||
self.world.available_fragments[self.player].value = self.required_fragments
|
||||
self.available_fragments = self.world.available_fragments[self.player].value
|
||||
self.energy_core = self.multiworld.energy_core[self.player].value
|
||||
self.required_fragments = self.multiworld.required_fragments[self.player].value
|
||||
if self.required_fragments > self.multiworld.available_fragments[self.player].value:
|
||||
self.multiworld.available_fragments[self.player].value = self.required_fragments
|
||||
self.available_fragments = self.multiworld.available_fragments[self.player].value
|
||||
|
||||
def create_event(self, event: str) -> Item:
|
||||
return SoEItem(event, ItemClassification.progression, None, self.player)
|
||||
@@ -209,12 +209,12 @@ class SoEWorld(World):
|
||||
|
||||
def create_regions(self):
|
||||
# exclude 'hidden' on easy
|
||||
max_difficulty = 1 if self.world.difficulty[self.player] == Difficulty.option_easy else 256
|
||||
max_difficulty = 1 if self.multiworld.difficulty[self.player] == Difficulty.option_easy else 256
|
||||
|
||||
# TODO: generate *some* regions from locations' requirements?
|
||||
r = Region('Menu', RegionType.Generic, 'Menu', self.player, self.world)
|
||||
r = Region('Menu', RegionType.Generic, 'Menu', self.player, self.multiworld)
|
||||
r.exits = [Entrance(self.player, 'New Game', r)]
|
||||
self.world.regions += [r]
|
||||
self.multiworld.regions += [r]
|
||||
|
||||
# group locations into spheres (1, 2, 3+ at index 0, 1, 2)
|
||||
spheres: typing.Dict[int, typing.Dict[int, typing.List[SoELocation]]] = {}
|
||||
@@ -232,8 +232,8 @@ class SoEWorld(World):
|
||||
# mark some as excluded based on numbers above
|
||||
for trash_sphere, fills in trash_fills.items():
|
||||
for typ, counts in fills.items():
|
||||
count = counts[self.world.difficulty[self.player].value]
|
||||
for location in self.world.random.sample(spheres[trash_sphere][typ], count):
|
||||
count = counts[self.multiworld.difficulty[self.player].value]
|
||||
for location in self.multiworld.random.sample(spheres[trash_sphere][typ], count):
|
||||
location.progress_type = LocationProgressType.EXCLUDED
|
||||
# TODO: do we need to set an item rule?
|
||||
|
||||
@@ -243,7 +243,7 @@ class SoEWorld(World):
|
||||
if item.name in {"Gauge", "Wheel"}:
|
||||
return False
|
||||
# and some more for non-easy, non-mystery
|
||||
if self.world.difficulty[item.player] not in (Difficulty.option_easy, Difficulty.option_mystery):
|
||||
if self.multiworld.difficulty[item.player] not in (Difficulty.option_easy, Difficulty.option_mystery):
|
||||
if item.name in {"Laser Lance", "Atom Smasher", "Diamond Eye"}:
|
||||
return False
|
||||
return True
|
||||
@@ -253,16 +253,16 @@ class SoEWorld(World):
|
||||
add_item_rule(location, sphere1_blocked_items_rule)
|
||||
|
||||
# make some logically late(r) bosses priority locations to increase complexity
|
||||
if self.world.difficulty[self.player] == Difficulty.option_mystery:
|
||||
late_count = self.world.random.randint(0, 2)
|
||||
if self.multiworld.difficulty[self.player] == Difficulty.option_mystery:
|
||||
late_count = self.multiworld.random.randint(0, 2)
|
||||
else:
|
||||
late_count = self.world.difficulty[self.player].value
|
||||
late_count = self.multiworld.difficulty[self.player].value
|
||||
late_bosses = ("Tiny", "Aquagoth", "Megataur", "Rimsala",
|
||||
"Mungola", "Lightning Storm", "Magmar", "Volcano Viper")
|
||||
late_locations = self.world.random.sample(late_bosses, late_count)
|
||||
late_locations = self.multiworld.random.sample(late_bosses, late_count)
|
||||
|
||||
# add locations to the world
|
||||
r = Region('Ingame', RegionType.Generic, 'Ingame', self.player, self.world)
|
||||
r = Region('Ingame', RegionType.Generic, 'Ingame', self.player, self.multiworld)
|
||||
for sphere in spheres.values():
|
||||
for locations in sphere.values():
|
||||
for location in locations:
|
||||
@@ -271,9 +271,9 @@ class SoEWorld(World):
|
||||
location.progress_type = LocationProgressType.PRIORITY
|
||||
|
||||
r.locations.append(SoELocation(self.player, 'Done', None, r))
|
||||
self.world.regions += [r]
|
||||
self.multiworld.regions += [r]
|
||||
|
||||
self.world.get_entrance('New Game', self.player).connect(self.world.get_region('Ingame', self.player))
|
||||
self.multiworld.get_entrance('New Game', self.player).connect(self.multiworld.get_region('Ingame', self.player))
|
||||
|
||||
def create_items(self):
|
||||
# add regular items to the pool
|
||||
@@ -298,17 +298,17 @@ class SoEWorld(World):
|
||||
for _ in range(self.available_fragments - 1):
|
||||
if len(ingredients) < 1:
|
||||
break # out of ingredients to replace
|
||||
r = self.world.random.choice(ingredients)
|
||||
r = self.multiworld.random.choice(ingredients)
|
||||
ingredients.remove(r)
|
||||
items[r] = self.create_item("Energy Core Fragment")
|
||||
|
||||
# add traps to the pool
|
||||
trap_count = self.world.trap_count[self.player].value
|
||||
trap_count = self.multiworld.trap_count[self.player].value
|
||||
trap_chances = {}
|
||||
trap_names = {}
|
||||
if trap_count > 0:
|
||||
for trap_type in self.trap_types:
|
||||
trap_option = getattr(self.world, f'trap_chance_{trap_type}')[self.player]
|
||||
trap_option = getattr(self.multiworld, f'trap_chance_{trap_type}')[self.player]
|
||||
trap_chances[trap_type] = trap_option.value
|
||||
trap_names[trap_type] = trap_option.item_name
|
||||
trap_chances_total = sum(trap_chances.values())
|
||||
@@ -318,7 +318,7 @@ class SoEWorld(World):
|
||||
trap_chances_total = len(trap_chances)
|
||||
|
||||
def create_trap() -> Item:
|
||||
v = self.world.random.randrange(trap_chances_total)
|
||||
v = self.multiworld.random.randrange(trap_chances_total)
|
||||
for t, c in trap_chances.items():
|
||||
if v < c:
|
||||
return self.create_item(trap_names[t])
|
||||
@@ -328,26 +328,26 @@ class SoEWorld(World):
|
||||
for _ in range(trap_count):
|
||||
if len(ingredients) < 1:
|
||||
break # out of ingredients to replace
|
||||
r = self.world.random.choice(ingredients)
|
||||
r = self.multiworld.random.choice(ingredients)
|
||||
ingredients.remove(r)
|
||||
items[r] = create_trap()
|
||||
|
||||
self.world.itempool += items
|
||||
self.multiworld.itempool += items
|
||||
|
||||
def set_rules(self):
|
||||
self.world.completion_condition[self.player] = lambda state: state.has('Victory', self.player)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has('Victory', self.player)
|
||||
# set Done from goal option once we have multiple goals
|
||||
set_rule(self.world.get_location('Done', self.player),
|
||||
lambda state: state.soe_has(pyevermizer.P_FINAL_BOSS, self.world, self.player))
|
||||
set_rule(self.world.get_entrance('New Game', self.player), lambda state: True)
|
||||
set_rule(self.multiworld.get_location('Done', self.player),
|
||||
lambda state: state.soe_has(pyevermizer.P_FINAL_BOSS, self.multiworld, self.player))
|
||||
set_rule(self.multiworld.get_entrance('New Game', self.player), lambda state: True)
|
||||
for loc in _locations:
|
||||
location = self.world.get_location(loc.name, self.player)
|
||||
location = self.multiworld.get_location(loc.name, self.player)
|
||||
set_rule(location, self.make_rule(loc.requires))
|
||||
|
||||
def make_rule(self, requires: typing.List[typing.Tuple[int, int]]) -> typing.Callable[[typing.Any], bool]:
|
||||
def rule(state) -> bool:
|
||||
for count, progress in requires:
|
||||
if not state.soe_has(progress, self.world, self.player, count):
|
||||
if not state.soe_has(progress, self.multiworld, self.player, count):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -358,20 +358,20 @@ class SoEWorld(World):
|
||||
|
||||
def generate_basic(self):
|
||||
# place Victory event
|
||||
self.world.get_location('Done', self.player).place_locked_item(self.create_event('Victory'))
|
||||
self.multiworld.get_location('Done', self.player).place_locked_item(self.create_event('Victory'))
|
||||
# place wings in halls NE to avoid softlock
|
||||
wings_location = self.world.random.choice(self._halls_ne_chest_names)
|
||||
wings_location = self.multiworld.random.choice(self._halls_ne_chest_names)
|
||||
wings_item = self.create_item('Wings')
|
||||
self.world.get_location(wings_location, self.player).place_locked_item(wings_item)
|
||||
self.multiworld.get_location(wings_location, self.player).place_locked_item(wings_item)
|
||||
# place energy core at vanilla location for vanilla mode
|
||||
if self.energy_core == EnergyCore.option_vanilla:
|
||||
energy_core = self.create_item('Energy Core')
|
||||
self.world.get_location('Energy Core #285', self.player).place_locked_item(energy_core)
|
||||
self.multiworld.get_location('Energy Core #285', self.player).place_locked_item(energy_core)
|
||||
# generate stuff for later
|
||||
self.evermizer_seed = self.world.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando?
|
||||
self.evermizer_seed = self.multiworld.random.randint(0, 2 ** 16 - 1) # TODO: make this an option for "full" plando?
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
player_name = self.world.get_player_name(self.player)
|
||||
player_name = self.multiworld.get_player_name(self.player)
|
||||
self.connect_name = player_name[:32]
|
||||
while len(self.connect_name.encode('utf-8')) > 32:
|
||||
self.connect_name = self.connect_name[:-1]
|
||||
@@ -379,27 +379,27 @@ class SoEWorld(World):
|
||||
placement_file = ""
|
||||
out_file = ""
|
||||
try:
|
||||
money = self.world.money_modifier[self.player].value
|
||||
exp = self.world.exp_modifier[self.player].value
|
||||
money = self.multiworld.money_modifier[self.player].value
|
||||
exp = self.multiworld.exp_modifier[self.player].value
|
||||
switches: typing.List[str] = []
|
||||
if self.world.death_link[self.player].value:
|
||||
if self.multiworld.death_link[self.player].value:
|
||||
switches.append("--death-link")
|
||||
if self.energy_core == EnergyCore.option_fragments:
|
||||
switches.extend(('--available-fragments', str(self.available_fragments),
|
||||
'--required-fragments', str(self.required_fragments)))
|
||||
rom_file = get_base_rom_path()
|
||||
out_base = output_path(output_directory, self.world.get_out_file_name_base(self.player))
|
||||
out_base = output_path(output_directory, self.multiworld.get_out_file_name_base(self.player))
|
||||
out_file = out_base + '.sfc'
|
||||
placement_file = out_base + '.txt'
|
||||
patch_file = out_base + '.apsoe'
|
||||
flags = 'l' # spoiler log
|
||||
for option_name in self.option_definitions:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
if hasattr(option, 'to_flag'):
|
||||
flags += option.to_flag()
|
||||
|
||||
with open(placement_file, "wb") as f: # generate placement file
|
||||
for location in filter(lambda l: l.player == self.player, self.world.get_locations()):
|
||||
for location in filter(lambda l: l.player == self.player, self.multiworld.get_locations()):
|
||||
item = location.item
|
||||
assert item is not None, "Can't handle unfilled location"
|
||||
if item.code is None or location.address is None:
|
||||
@@ -414,7 +414,7 @@ class SoEWorld(World):
|
||||
|
||||
if not os.path.exists(rom_file):
|
||||
raise FileNotFoundError(rom_file)
|
||||
if (pyevermizer.main(rom_file, out_file, placement_file, self.world.seed_name, self.connect_name,
|
||||
if (pyevermizer.main(rom_file, out_file, placement_file, self.multiworld.seed_name, self.connect_name,
|
||||
self.evermizer_seed, flags, money, exp, switches)):
|
||||
raise RuntimeError()
|
||||
patch = SoEDeltaPatch(patch_file, player=self.player,
|
||||
@@ -434,12 +434,12 @@ class SoEWorld(World):
|
||||
# wait for self.connect_name to be available.
|
||||
self.connect_name_available_event.wait()
|
||||
# we skip in case of error, so that the original error in the output thread is the one that gets raised
|
||||
if self.connect_name and self.connect_name != self.world.player_name[self.player]:
|
||||
payload = multidata["connect_names"][self.world.player_name[self.player]]
|
||||
if self.connect_name and self.connect_name != self.multiworld.player_name[self.player]:
|
||||
payload = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||
multidata["connect_names"][self.connect_name] = payload
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(list(self.item_name_groups["Ingredients"]))
|
||||
return self.multiworld.random.choice(list(self.item_name_groups["Ingredients"]))
|
||||
|
||||
|
||||
class SoEItem(Item):
|
||||
|
||||
@@ -40,10 +40,10 @@ class SpireWorld(World):
|
||||
|
||||
def _get_slot_data(self):
|
||||
return {
|
||||
'seed': "".join(self.world.slot_seeds[self.player].choice(string.ascii_letters) for i in range(16)),
|
||||
'character': self.world.character[self.player],
|
||||
'ascension': self.world.ascension[self.player],
|
||||
'heart_run': self.world.heart_run[self.player]
|
||||
'seed': "".join(self.multiworld.slot_seeds[self.player].choice(string.ascii_letters) for i in range(16)),
|
||||
'character': self.multiworld.character[self.player],
|
||||
'ascension': self.multiworld.ascension[self.player],
|
||||
'heart_run': self.multiworld.heart_run[self.player]
|
||||
}
|
||||
|
||||
def generate_basic(self):
|
||||
@@ -55,40 +55,40 @@ class SpireWorld(World):
|
||||
item = SpireItem(name, self.player)
|
||||
pool.append(item)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
# Pair up our event locations with our event items
|
||||
for event, item in event_item_pairs.items():
|
||||
event_item = SpireItem(item, self.player)
|
||||
self.world.get_location(event, self.player).place_locked_item(event_item)
|
||||
self.multiworld.get_location(event, self.player).place_locked_item(event_item)
|
||||
|
||||
if self.world.logic[self.player] != 'no logic':
|
||||
self.world.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
if self.multiworld.logic[self.player] != 'no logic':
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
|
||||
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player)
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return SpireItem(name, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
slot_data = self._get_slot_data()
|
||||
for option_name in spire_options:
|
||||
option = getattr(self.world, option_name)[self.player]
|
||||
option = getattr(self.multiworld, option_name)[self.player]
|
||||
slot_data[option_name] = int(option.value)
|
||||
return slot_data
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"])
|
||||
return self.multiworld.random.choice(["Card Draw", "Card Draw", "Card Draw", "Relic", "Relic"])
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = location_table.get(location, 0)
|
||||
|
||||
@@ -277,7 +277,7 @@ aggression_rules: Dict[int, Callable[["CollectionState", int], bool]] = {
|
||||
|
||||
def set_rules(subnautica_world: "SubnauticaWorld"):
|
||||
player = subnautica_world.player
|
||||
world = subnautica_world.world
|
||||
world = subnautica_world.multiworld
|
||||
|
||||
for loc in location_table.values():
|
||||
set_location_rule(world, player, loc)
|
||||
|
||||
@@ -47,22 +47,22 @@ class SubnauticaWorld(World):
|
||||
creatures_to_scan: List[str]
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if "Seaglide Fragment" not in self.world.early_items[self.player]:
|
||||
self.world.early_items[self.player].value["Seaglide Fragment"] = 2
|
||||
if "Seaglide Fragment" not in self.multiworld.early_items[self.player]:
|
||||
self.multiworld.early_items[self.player].value["Seaglide Fragment"] = 2
|
||||
|
||||
scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player]
|
||||
scan_option: Options.AggressiveScanLogic = self.multiworld.creature_scan_logic[self.player]
|
||||
creature_pool = scan_option.get_pool()
|
||||
|
||||
self.world.creature_scans[self.player].value = min(
|
||||
self.multiworld.creature_scans[self.player].value = min(
|
||||
len(creature_pool),
|
||||
self.world.creature_scans[self.player].value
|
||||
self.multiworld.creature_scans[self.player].value
|
||||
)
|
||||
|
||||
self.creatures_to_scan = self.world.random.sample(creature_pool,
|
||||
self.world.creature_scans[self.player].value)
|
||||
self.creatures_to_scan = self.multiworld.random.sample(creature_pool,
|
||||
self.multiworld.creature_scans[self.player].value)
|
||||
|
||||
def create_regions(self):
|
||||
self.world.regions += [
|
||||
self.multiworld.regions += [
|
||||
self.create_region("Menu", None, ["Lifepod 5"]),
|
||||
self.create_region("Planet 4546B",
|
||||
Locations.events +
|
||||
@@ -75,13 +75,13 @@ class SubnauticaWorld(World):
|
||||
|
||||
def generate_basic(self):
|
||||
# Link regions
|
||||
self.world.get_entrance("Lifepod 5", self.player).connect(self.world.get_region("Planet 4546B", self.player))
|
||||
self.multiworld.get_entrance("Lifepod 5", self.player).connect(self.multiworld.get_region("Planet 4546B", self.player))
|
||||
|
||||
# Generate item pool
|
||||
pool = []
|
||||
neptune_launch_platform = None
|
||||
extras = self.world.creature_scans[self.player].value
|
||||
valuable = self.world.item_pool[self.player] == Options.ItemPool.option_valuable
|
||||
extras = self.multiworld.creature_scans[self.player].value
|
||||
valuable = self.multiworld.item_pool[self.player] == Options.ItemPool.option_valuable
|
||||
for item in item_table.values():
|
||||
for i in range(item["count"]):
|
||||
subnautica_item = self.create_item(item["name"])
|
||||
@@ -92,26 +92,26 @@ class SubnauticaWorld(World):
|
||||
else:
|
||||
pool.append(subnautica_item)
|
||||
|
||||
for item_name in self.world.random.choices(sorted(Items.advancement_item_names - {"Neptune Launch Platform"}),
|
||||
k=extras):
|
||||
for item_name in self.multiworld.random.choices(sorted(Items.advancement_item_names - {"Neptune Launch Platform"}),
|
||||
k=extras):
|
||||
item = self.create_item(item_name)
|
||||
item.classification = ItemClassification.filler # as it's an extra, just fast-fill it somewhere
|
||||
pool.append(item)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
# Victory item
|
||||
self.world.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item(
|
||||
self.multiworld.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item(
|
||||
neptune_launch_platform)
|
||||
for event in Locations.events:
|
||||
self.world.get_location(event, self.player).place_locked_item(
|
||||
self.multiworld.get_location(event, self.player).place_locked_item(
|
||||
SubnauticaItem(event, ItemClassification.progression, None, player=self.player))
|
||||
# make the goal event the victory "item"
|
||||
self.world.get_location(self.world.goal[self.player].get_event_name(), self.player).item.name = "Victory"
|
||||
self.multiworld.get_location(self.multiworld.goal[self.player].get_event_name(), self.player).item.name = "Victory"
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
goal: Options.Goal = self.world.goal[self.player]
|
||||
item_pool: Options.ItemPool = self.world.item_pool[self.player]
|
||||
goal: Options.Goal = self.multiworld.goal[self.player]
|
||||
item_pool: Options.ItemPool = self.multiworld.item_pool[self.player]
|
||||
vanilla_tech: List[str] = []
|
||||
if item_pool == Options.ItemPool.option_valuable:
|
||||
for item in Items.item_table.values():
|
||||
@@ -122,7 +122,7 @@ class SubnauticaWorld(World):
|
||||
"goal": goal.current_key,
|
||||
"vanilla_tech": vanilla_tech,
|
||||
"creatures_to_scan": self.creatures_to_scan,
|
||||
"death_link": self.world.death_link[self.player].value,
|
||||
"death_link": self.multiworld.death_link[self.player].value,
|
||||
}
|
||||
|
||||
return slot_data
|
||||
@@ -136,7 +136,7 @@ class SubnauticaWorld(World):
|
||||
|
||||
def create_region(self, name: str, locations=None, exits=None):
|
||||
ret = Region(name, RegionType.Generic, name, self.player)
|
||||
ret.world = self.world
|
||||
ret.multiworld = self.multiworld
|
||||
if locations:
|
||||
for location in locations:
|
||||
loc_id = self.location_name_to_id.get(location, None)
|
||||
|
||||
@@ -197,7 +197,7 @@ def create_location(player: int, location_data: LocationData, region: Region, lo
|
||||
|
||||
def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], location_cache: List[Location], name: str) -> Region:
|
||||
region = Region(name, RegionType.Generic, name, player)
|
||||
region.world = world
|
||||
region.multiworld = world
|
||||
|
||||
if name in locations_per_region:
|
||||
for location_data in locations_per_region[name]:
|
||||
|
||||
@@ -64,47 +64,47 @@ class TimespinnerWorld(World):
|
||||
|
||||
def generate_early(self):
|
||||
# in generate_early the start_inventory isnt copied over to precollected_items yet, so we can still modify the options directly
|
||||
if self.world.start_inventory[self.player].value.pop('Meyef', 0) > 0:
|
||||
self.world.StartWithMeyef[self.player].value = self.world.StartWithMeyef[self.player].option_true
|
||||
if self.world.start_inventory[self.player].value.pop('Talaria Attachment', 0) > 0:
|
||||
self.world.QuickSeed[self.player].value = self.world.QuickSeed[self.player].option_true
|
||||
if self.world.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0:
|
||||
self.world.StartWithJewelryBox[self.player].value = self.world.StartWithJewelryBox[self.player].option_true
|
||||
if self.multiworld.start_inventory[self.player].value.pop('Meyef', 0) > 0:
|
||||
self.multiworld.StartWithMeyef[self.player].value = self.multiworld.StartWithMeyef[self.player].option_true
|
||||
if self.multiworld.start_inventory[self.player].value.pop('Talaria Attachment', 0) > 0:
|
||||
self.multiworld.QuickSeed[self.player].value = self.multiworld.QuickSeed[self.player].option_true
|
||||
if self.multiworld.start_inventory[self.player].value.pop('Jewelry Box', 0) > 0:
|
||||
self.multiworld.StartWithJewelryBox[self.player].value = self.multiworld.StartWithJewelryBox[self.player].option_true
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world, self.player, get_locations(self.world, self.player),
|
||||
self.location_cache, self.pyramid_keys_unlock)
|
||||
create_regions(self.multiworld, self.player, get_locations(self.multiworld, self.player),
|
||||
self.location_cache, self.pyramid_keys_unlock)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return create_item_with_correct_settings(self.world, self.player, name)
|
||||
return create_item_with_correct_settings(self.multiworld, self.player, name)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(filler_items)
|
||||
return self.multiworld.random.choice(filler_items)
|
||||
|
||||
def set_rules(self):
|
||||
setup_events(self.player, self.locked_locations, self.location_cache)
|
||||
|
||||
self.world.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player)
|
||||
self.multiworld.completion_condition[self.player] = lambda state: state.has('Killed Nightmare', self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
excluded_items = get_excluded_items(self, self.world, self.player)
|
||||
excluded_items = get_excluded_items(self, self.multiworld, self.player)
|
||||
|
||||
assign_starter_items(self.world, self.player, excluded_items, self.locked_locations)
|
||||
assign_starter_items(self.multiworld, self.player, excluded_items, self.locked_locations)
|
||||
|
||||
if not is_option_enabled(self.world, self.player, "QuickSeed") and not is_option_enabled(self.world, self.player, "Inverted"):
|
||||
place_first_progression_item(self.world, self.player, excluded_items, self.locked_locations)
|
||||
if not is_option_enabled(self.multiworld, self.player, "QuickSeed") and not is_option_enabled(self.multiworld, self.player, "Inverted"):
|
||||
place_first_progression_item(self.multiworld, self.player, excluded_items, self.locked_locations)
|
||||
|
||||
pool = get_item_pool(self.world, self.player, excluded_items)
|
||||
pool = get_item_pool(self.multiworld, self.player, excluded_items)
|
||||
|
||||
fill_item_pool_with_dummy_items(self, self.world, self.player, self.locked_locations, self.location_cache, pool)
|
||||
fill_item_pool_with_dummy_items(self, self.multiworld, self.player, self.locked_locations, self.location_cache, pool)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, object]:
|
||||
slot_data: Dict[str, object] = {}
|
||||
|
||||
for option_name in timespinner_options:
|
||||
slot_data[option_name] = get_option_value(self.world, self.player, option_name)
|
||||
slot_data[option_name] = get_option_value(self.multiworld, self.player, option_name)
|
||||
|
||||
slot_data["StinkyMaw"] = True
|
||||
slot_data["ProgressiveVerticalMovement"] = False
|
||||
|
||||
@@ -44,42 +44,42 @@ class V6World(World):
|
||||
option_definitions = v6_options
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.world,self.player)
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def set_rules(self):
|
||||
self.area_connections = {}
|
||||
self.area_cost_map = {}
|
||||
set_rules(self.world, self.player, self.area_connections, self.area_cost_map)
|
||||
set_rules(self.multiworld, self.player, self.area_connections, self.area_cost_map)
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return V6Item(name, ItemClassification.progression, item_table[name], self.player)
|
||||
|
||||
def generate_basic(self):
|
||||
trinkets = [self.create_item("Trinket " + str(i+1).zfill(2)) for i in range(0,20)]
|
||||
self.world.itempool += trinkets
|
||||
self.multiworld.itempool += trinkets
|
||||
|
||||
musiclist_o = [1,2,3,4,9,12]
|
||||
musiclist_s = musiclist_o.copy()
|
||||
if self.world.MusicRandomizer[self.player].value:
|
||||
self.world.random.shuffle(musiclist_s)
|
||||
if self.multiworld.MusicRandomizer[self.player].value:
|
||||
self.multiworld.random.shuffle(musiclist_s)
|
||||
self.music_map = dict(zip(musiclist_o, musiclist_s))
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"MusicRando": self.music_map,
|
||||
"AreaRando": self.area_connections,
|
||||
"DoorCost": self.world.DoorCost[self.player].value,
|
||||
"DoorCost": self.multiworld.DoorCost[self.player].value,
|
||||
"AreaCostRando": self.area_cost_map,
|
||||
"DeathLink": self.world.death_link[self.player].value,
|
||||
"DeathLink_Amnesty": self.world.DeathLinkAmnesty[self.player].value
|
||||
"DeathLink": self.multiworld.death_link[self.player].value,
|
||||
"DeathLink_Amnesty": self.multiworld.DeathLinkAmnesty[self.player].value
|
||||
}
|
||||
|
||||
def generate_output(self, output_directory: str):
|
||||
if self.world.players != 1:
|
||||
if self.multiworld.players != 1:
|
||||
return
|
||||
data = {
|
||||
"slot_data": self.fill_slot_data(),
|
||||
"location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.world.get_locations()},
|
||||
"location_to_item": {self.location_name_to_id[i.name] : item_table[i.item.name] for i in self.multiworld.get_locations()},
|
||||
"data_package": {
|
||||
"data": {
|
||||
"games": {
|
||||
@@ -91,6 +91,6 @@ class V6World(World):
|
||||
}
|
||||
}
|
||||
}
|
||||
filename = f"{self.world.get_out_file_name_base(self.player)}.apv6"
|
||||
filename = f"{self.multiworld.get_out_file_name_base(self.player)}.apv6"
|
||||
with open(os.path.join(output_directory, filename), 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
@@ -54,7 +54,7 @@ class WitnessWorld(World):
|
||||
|
||||
def _get_slot_data(self):
|
||||
return {
|
||||
'seed': self.world.random.randint(0, 1000000),
|
||||
'seed': self.multiworld.random.randint(0, 1000000),
|
||||
'victory_location': int(self.player_logic.VICTORY_LOCATION, 16),
|
||||
'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID,
|
||||
'item_id_to_door_hexes': self.items.ITEM_ID_TO_DOOR_HEX,
|
||||
@@ -66,19 +66,19 @@ class WitnessWorld(World):
|
||||
}
|
||||
|
||||
def generate_early(self):
|
||||
if not (is_option_enabled(self.world, self.player, "shuffle_symbols")
|
||||
or get_option_value(self.world, self.player, "shuffle_doors")
|
||||
or is_option_enabled(self.world, self.player, "shuffle_lasers")):
|
||||
if self.world.players == 1:
|
||||
if not (is_option_enabled(self.multiworld, self.player, "shuffle_symbols")
|
||||
or get_option_value(self.multiworld, self.player, "shuffle_doors")
|
||||
or is_option_enabled(self.multiworld, self.player, "shuffle_lasers")):
|
||||
if self.multiworld.players == 1:
|
||||
warning("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle, Door"
|
||||
" Shuffle or Laser Shuffle if that doesn't seem right.")
|
||||
else:
|
||||
raise Exception("This Witness world doesn't have any progression items. Please turn on Symbol Shuffle,"
|
||||
" Door Shuffle or Laser Shuffle.")
|
||||
|
||||
self.player_logic = WitnessPlayerLogic(self.world, self.player)
|
||||
self.locat = WitnessPlayerLocations(self.world, self.player, self.player_logic)
|
||||
self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic)
|
||||
self.player_logic = WitnessPlayerLogic(self.multiworld, self.player)
|
||||
self.locat = WitnessPlayerLocations(self.multiworld, self.player, self.player_logic)
|
||||
self.items = WitnessPlayerItems(self.locat, self.multiworld, self.player, self.player_logic)
|
||||
self.regio = WitnessRegions(self.locat)
|
||||
|
||||
self.log_ids_to_hints = dict()
|
||||
@@ -99,12 +99,12 @@ class WitnessWorld(World):
|
||||
less_junk = 0
|
||||
|
||||
# Put good item on first check if symbol shuffle is on
|
||||
symbols = is_option_enabled(self.world, self.player, "shuffle_symbols")
|
||||
symbols = is_option_enabled(self.multiworld, self.player, "shuffle_symbols")
|
||||
|
||||
if symbols and get_option_value(self.world, self.player, "puzzle_randomization") != 1:
|
||||
random_good_item = self.world.random.choice(self.items.GOOD_ITEMS)
|
||||
if symbols and get_option_value(self.multiworld, self.player, "puzzle_randomization") != 1:
|
||||
random_good_item = self.multiworld.random.choice(self.items.GOOD_ITEMS)
|
||||
|
||||
first_check = self.world.get_location(
|
||||
first_check = self.multiworld.get_location(
|
||||
"Tutorial Gate Open", self.player
|
||||
)
|
||||
first_check.place_locked_item(items_by_name[random_good_item])
|
||||
@@ -113,7 +113,7 @@ class WitnessWorld(World):
|
||||
less_junk = 1
|
||||
|
||||
for item in self.player_logic.STARTING_INVENTORY:
|
||||
self.world.push_precollected(items_by_name[item])
|
||||
self.multiworld.push_precollected(items_by_name[item])
|
||||
pool.remove(items_by_name[item])
|
||||
|
||||
for item in self.items.EXTRA_AMOUNTS:
|
||||
@@ -133,28 +133,28 @@ class WitnessWorld(World):
|
||||
item_obj = self.create_item(
|
||||
self.player_logic.EVENT_ITEM_PAIRS[event_location]
|
||||
)
|
||||
location_obj = self.world.get_location(event_location, self.player)
|
||||
location_obj = self.multiworld.get_location(event_location, self.player)
|
||||
location_obj.place_locked_item(item_obj)
|
||||
|
||||
self.world.itempool += pool
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
def create_regions(self):
|
||||
self.regio.create_regions(self.world, self.player, self.player_logic)
|
||||
self.regio.create_regions(self.multiworld, self.player, self.player_logic)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.world, self.player, self.player_logic, self.locat)
|
||||
set_rules(self.multiworld, self.player, self.player_logic, self.locat)
|
||||
|
||||
def fill_slot_data(self) -> dict:
|
||||
hint_amount = get_option_value(self.world, self.player, "hint_amount")
|
||||
hint_amount = get_option_value(self.multiworld, self.player, "hint_amount")
|
||||
|
||||
credits_hint = ("This Randomizer", "is brought to you by", "NewSoupVi, Jarno, jbzdarkid, sigma144", -1)
|
||||
|
||||
audio_logs = get_audio_logs().copy()
|
||||
|
||||
if hint_amount != 0:
|
||||
generated_hints = make_hints(self.world, self.player, hint_amount)
|
||||
generated_hints = make_hints(self.multiworld, self.player, hint_amount)
|
||||
|
||||
self.world.random.shuffle(audio_logs)
|
||||
self.multiworld.random.shuffle(audio_logs)
|
||||
|
||||
duplicates = len(audio_logs) // hint_amount
|
||||
|
||||
@@ -169,7 +169,7 @@ class WitnessWorld(World):
|
||||
audio_log = audio_logs.pop()
|
||||
self.log_ids_to_hints[int(audio_log, 16)] = credits_hint
|
||||
|
||||
joke_hints = generate_joke_hints(self.world, len(audio_logs))
|
||||
joke_hints = generate_joke_hints(self.multiworld, len(audio_logs))
|
||||
|
||||
while audio_logs:
|
||||
audio_log = audio_logs.pop()
|
||||
@@ -181,7 +181,7 @@ class WitnessWorld(World):
|
||||
|
||||
for option_name in the_witness_options:
|
||||
slot_data[option_name] = get_option_value(
|
||||
self.world, self.player, option_name
|
||||
self.multiworld, self.player, option_name
|
||||
)
|
||||
|
||||
return slot_data
|
||||
@@ -234,7 +234,7 @@ def create_region(world: MultiWorld, player: int, name: str,
|
||||
"""
|
||||
|
||||
ret = Region(name, RegionType.Generic, name, player)
|
||||
ret.world = world
|
||||
ret.multiworld = world
|
||||
if region_locations:
|
||||
for location in region_locations:
|
||||
loc_id = locat.CHECK_LOCATION_TABLE[location]
|
||||
|
||||
@@ -121,10 +121,10 @@ class ZillionWorld(World):
|
||||
raise FileNotFoundError(rom_file)
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if not hasattr(self.world, "zillion_logic_cache"):
|
||||
setattr(self.world, "zillion_logic_cache", {})
|
||||
if not hasattr(self.multiworld, "zillion_logic_cache"):
|
||||
setattr(self.multiworld, "zillion_logic_cache", {})
|
||||
|
||||
zz_op, item_counts = validate(self.world, self.player)
|
||||
zz_op, item_counts = validate(self.multiworld, self.player)
|
||||
|
||||
self._item_counts = item_counts
|
||||
|
||||
@@ -148,7 +148,7 @@ class ZillionWorld(World):
|
||||
assert self.zz_system.randomizer, "generate_early hasn't been called"
|
||||
assert self.id_to_zz_item, "generate_early hasn't been called"
|
||||
p = self.player
|
||||
w = self.world
|
||||
w = self.multiworld
|
||||
self.my_locations = []
|
||||
|
||||
self.zz_system.randomizer.place_canister_gun_reqs()
|
||||
@@ -159,7 +159,7 @@ class ZillionWorld(World):
|
||||
for here_zz_name, zz_r in self.zz_system.randomizer.regions.items():
|
||||
here_name = "Menu" if here_zz_name == "start" else zz_reg_name_to_reg_name(here_zz_name)
|
||||
all[here_name] = ZillionRegion(zz_r, here_name, RegionType.Generic, here_name, p, w)
|
||||
self.world.regions.append(all[here_name])
|
||||
self.multiworld.regions.append(all[here_name])
|
||||
|
||||
limited_skill = Req(gun=3, jump=3, skill=self.zz_system.randomizer.options.skill, hp=940, red=1, floppy=126)
|
||||
queue = deque([start])
|
||||
@@ -191,7 +191,7 @@ class ZillionWorld(World):
|
||||
loc.access_rule = access_rule
|
||||
if not (limited_skill >= zz_loc.req):
|
||||
loc.progress_type = LocationProgressType.EXCLUDED
|
||||
self.world.exclude_locations[p].value.add(loc.name)
|
||||
self.multiworld.exclude_locations[p].value.add(loc.name)
|
||||
here.locations.append(loc)
|
||||
self.my_locations.append(loc)
|
||||
|
||||
@@ -222,11 +222,11 @@ class ZillionWorld(World):
|
||||
if item_name in item_counts:
|
||||
count = item_counts[item_name]
|
||||
self.logger.debug(f"Zillion Items: {item_name} {count}")
|
||||
self.world.itempool += [self.create_item(item_name) for _ in range(count)]
|
||||
self.multiworld.itempool += [self.create_item(item_name) for _ in range(count)]
|
||||
elif item_id < (3 + base_id) and zz_item.code == RESCUE:
|
||||
# One of the 3 rescues will not be in the pool and its zz_item will be 'empty'.
|
||||
self.logger.debug(f"Zillion Items: {item_name} 1")
|
||||
self.world.itempool.append(self.create_item(item_name))
|
||||
self.multiworld.itempool.append(self.create_item(item_name))
|
||||
|
||||
def set_rules(self) -> None:
|
||||
# logic for this game is in create_regions
|
||||
@@ -237,9 +237,9 @@ class ZillionWorld(World):
|
||||
# main location name is an alias
|
||||
main_loc_name = self.zz_system.randomizer.loc_name_2_pretty[self.zz_system.randomizer.locations['main'].name]
|
||||
|
||||
self.world.get_location(main_loc_name, self.player)\
|
||||
self.multiworld.get_location(main_loc_name, self.player)\
|
||||
.place_locked_item(self.create_item("Win"))
|
||||
self.world.completion_condition[self.player] = \
|
||||
self.multiworld.completion_condition[self.player] = \
|
||||
lambda state: state.has("Win", self.player)
|
||||
|
||||
@staticmethod
|
||||
@@ -295,7 +295,7 @@ class ZillionWorld(World):
|
||||
empty = zz_items[4]
|
||||
multi_item = empty # a different patcher method differentiates empty from ap multi item
|
||||
multi_items: Dict[str, Tuple[str, str]] = {} # zz_loc_name to (item_name, player_name)
|
||||
for loc in self.world.get_locations():
|
||||
for loc in self.multiworld.get_locations():
|
||||
if loc.player == self.player:
|
||||
z_loc = cast(ZillionLocation, loc)
|
||||
# debug_zz_loc_ids[z_loc.zz_loc.name] = id(z_loc.zz_loc)
|
||||
@@ -310,7 +310,7 @@ class ZillionWorld(World):
|
||||
z_loc.zz_loc.item = multi_item
|
||||
multi_items[z_loc.zz_loc.name] = (
|
||||
z_loc.item.name,
|
||||
self.world.get_player_name(z_loc.item.player)
|
||||
self.multiworld.get_player_name(z_loc.item.player)
|
||||
)
|
||||
# debug_zz_loc_ids.sort()
|
||||
# for name, id_ in debug_zz_loc_ids.items():
|
||||
@@ -340,7 +340,7 @@ class ZillionWorld(World):
|
||||
zz_patcher.all_fixes_and_options(zz_options)
|
||||
zz_patcher.set_external_item_interface(zz_options.start_char, zz_options.max_level)
|
||||
zz_patcher.set_multiworld_items(multi_items)
|
||||
game_id = self.world.player_name[self.player].encode() + b'\x00' + self.world.seed_name[-6:].encode()
|
||||
game_id = self.multiworld.player_name[self.player].encode() + b'\x00' + self.multiworld.seed_name[-6:].encode()
|
||||
zz_patcher.set_rom_to_ram_data(game_id)
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
@@ -352,7 +352,7 @@ class ZillionWorld(World):
|
||||
# original_rom_bytes = self.zz_patcher.rom
|
||||
patched_rom_bytes = self.zz_system.patcher.get_patched_bytes()
|
||||
|
||||
out_file_base = self.world.get_out_file_name_base(self.player)
|
||||
out_file_base = self.multiworld.get_out_file_name_base(self.player)
|
||||
|
||||
filename = os.path.join(
|
||||
output_directory,
|
||||
@@ -363,7 +363,7 @@ class ZillionWorld(World):
|
||||
patch = ZillionDeltaPatch(
|
||||
os.path.splitext(filename)[0] + ZillionDeltaPatch.patch_file_ending,
|
||||
player=self.player,
|
||||
player_name=self.world.player_name[self.player],
|
||||
player_name=self.multiworld.player_name[self.player],
|
||||
patched_path=filename
|
||||
)
|
||||
patch.write()
|
||||
@@ -401,7 +401,7 @@ class ZillionWorld(World):
|
||||
# def modify_multidata(self, multidata: Dict[str, Any]) -> None:
|
||||
# """For deeper modification of server multidata."""
|
||||
# # not modifying multidata, just want to call this at the end of the generation process
|
||||
# cache = getattr(self.world, "zillion_logic_cache")
|
||||
# cache = getattr(self.multiworld, "zillion_logic_cache")
|
||||
# import sys
|
||||
# print(sys.getsizeof(cache))
|
||||
|
||||
@@ -409,7 +409,7 @@ class ZillionWorld(World):
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
"""Create an item for this world type and player.
|
||||
Warning: this may be called with self.world = None, for example by MultiServer"""
|
||||
Warning: this may be called with self.multiworld = None, for example by MultiServer"""
|
||||
item_id = _item_name_to_id[name]
|
||||
|
||||
if not self.id_to_zz_item:
|
||||
|
||||
@@ -18,7 +18,7 @@ def set_randomizer_locs(cs: CollectionState, p: int, zz_r: Randomizer) -> int:
|
||||
|
||||
returns a hash of the player and of the set locations with their items
|
||||
"""
|
||||
z_world = cs.world.worlds[p]
|
||||
z_world = cs.multiworld.worlds[p]
|
||||
my_locations = cast(List[ZillionLocation], getattr(z_world, "my_locations"))
|
||||
|
||||
_hash = p
|
||||
@@ -50,7 +50,7 @@ def cs_to_zz_locs(cs: CollectionState, p: int, zz_r: Randomizer, id_to_zz_item:
|
||||
returns frozenset of accessible zilliandomizer locations
|
||||
"""
|
||||
# caching this function because it would be slow
|
||||
logic_cache: LogicCacheType = getattr(cs.world, "zillion_logic_cache", {})
|
||||
logic_cache: LogicCacheType = getattr(cs.multiworld, "zillion_logic_cache", {})
|
||||
_hash = set_randomizer_locs(cs, p, zz_r)
|
||||
counts = item_counts(cs, p)
|
||||
_hash += hash(counts)
|
||||
|
||||
Reference in New Issue
Block a user