Merge branch 'main' into feature/ds3_use_slotdata

# Conflicts:
#	worlds/dark_souls_3/__init__.py
This commit is contained in:
Marechal-l
2022-11-01 12:00:32 +01:00
117 changed files with 3139 additions and 2940 deletions

View File

@@ -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]:

View File

@@ -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

View File

@@ -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'],

View File

@@ -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()

View File

@@ -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):

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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]]:

View File

@@ -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):

View File

@@ -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

View File

@@ -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":

View File

@@ -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

View File

@@ -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)

View File

@@ -89,7 +89,7 @@ class Hylics2Logic(LogicMixin):
def set_rules(hylics2world):
world = hylics2world.world
world = hylics2world.multiworld
player = hylics2world.player
# Afterlife

View File

@@ -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"]]))

View File

@@ -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:

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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}')

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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')):

View File

@@ -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):

View File

@@ -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,

View File

@@ -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:

View File

@@ -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:

View File

@@ -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):

View File

@@ -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.

View File

@@ -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"]),

View File

@@ -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"),

View File

@@ -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 ;)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,
}

View File

@@ -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),

View File

@@ -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)

View File

@@ -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)

View File

@@ -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}

View File

@@ -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()}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -36,4 +36,3 @@ traits = [
"Glaucoma",
"Adopted",
]

View File

@@ -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"))

View File

@@ -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).

View File

@@ -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.

View File

@@ -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:

View File

@@ -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)

View File

@@ -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"

View File

@@ -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]:

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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

View File

@@ -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,

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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

View File

@@ -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)

View File

@@ -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]

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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]:

View File

@@ -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

View File

@@ -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)

View File

@@ -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]

View File

@@ -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:

View File

@@ -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)