implement secrets.SystemRandom() for --race

This commit is contained in:
Fabian Dill 2020-07-14 07:01:51 +02:00
parent 59a71dbb05
commit 93ecf5988b
11 changed files with 312 additions and 250 deletions

View File

@ -10,6 +10,10 @@ from EntranceShuffle import door_addresses, indirect_connections
from Utils import int16_as_bytes from Utils import int16_as_bytes
from typing import Union from typing import Union
import secrets
import random
class World(object): class World(object):
debug_types = False debug_types = False
player_names: list player_names: list
@ -28,6 +32,8 @@ class World(object):
setattr(self, name[7:], method) setattr(self, name[7:], method)
logging.debug(f"Set {self}.{name[7:]} to {method}") logging.debug(f"Set {self}.{name[7:]} to {method}")
self.get_location = self._debug_get_location self.get_location = self._debug_get_location
self.random = random.Random() # world-local random state is saved in case of future use a
# persistently running program with multiple worlds rolling concurrently
self.players = players self.players = players
self.teams = 1 self.teams = 1
self.shuffle = shuffle.copy() self.shuffle = shuffle.copy()
@ -116,6 +122,9 @@ class World(object):
set_player_attr('triforce_pieces_available', 30) set_player_attr('triforce_pieces_available', 30)
set_player_attr('triforce_pieces_required', 20) set_player_attr('triforce_pieces_required', 20)
def secure(self):
self.random = secrets.SystemRandom()
@property @property
def player_ids(self): def player_ids(self):
yield from range(1, self.players + 1) yield from range(1, self.players + 1)

View File

@ -1,5 +1,4 @@
import logging import logging
import random
from BaseClasses import Boss from BaseClasses import Boss
from Fill import FillError from Fill import FillError
@ -193,11 +192,11 @@ def place_bosses(world, player):
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm'] bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
else: # all bosses present, the three duplicates chosen at random else: # all bosses present, the three duplicates chosen at random
bosses = all_bosses + [random.choice(placeable_bosses) for _ in range(3)] bosses = all_bosses + [world.random.choice(placeable_bosses) for _ in range(3)]
logging.getLogger('').debug('Bosses chosen %s', bosses) logging.getLogger('').debug('Bosses chosen %s', bosses)
random.shuffle(bosses) world.random.shuffle(bosses)
for [loc, level] in boss_locations: for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '') loc_text = loc + (' ('+level+')' if level else '')
boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None) boss = next((b for b in bosses if can_place_boss(world, player, b, loc, level)), None)
@ -211,7 +210,8 @@ def place_bosses(world, player):
for [loc, level] in boss_locations: for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '') loc_text = loc + (' ('+level+')' if level else '')
try: try:
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)]) boss = world.random.choice(
[b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)])
except IndexError: except IndexError:
raise FillError('Could not place boss for location %s' % loc_text) raise FillError('Could not place boss for location %s' % loc_text)

View File

@ -1,5 +1,3 @@
import random
from BaseClasses import Dungeon from BaseClasses import Dungeon
from Bosses import BossFactory from Bosses import BossFactory
from Fill import fill_restrictive from Fill import fill_restrictive
@ -58,7 +56,7 @@ def fill_dungeons(world):
dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0) dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0)
# this is what we need to fill # this is what we need to fill
dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions] dungeon_locations = [location for location in world.get_unfilled_locations() if location.parent_region.name in dungeon_regions]
random.shuffle(dungeon_locations) world.random.shuffle(dungeon_locations)
all_state = all_state_base.copy() all_state = all_state_base.copy()

View File

@ -3,7 +3,6 @@ import argparse
import copy import copy
import os import os
import logging import logging
import random
import textwrap import textwrap
import shlex import shlex
import sys import sys

File diff suppressed because it is too large Load Diff

39
Fill.py
View File

@ -1,4 +1,3 @@
import random
import logging import logging
from BaseClasses import CollectionState from BaseClasses import CollectionState
@ -10,10 +9,10 @@ class FillError(RuntimeError):
def distribute_items_cutoff(world, cutoffrate=0.33): def distribute_items_cutoff(world, cutoffrate=0.33):
# get list of locations to fill in # get list of locations to fill in
fill_locations = world.get_unfilled_locations() fill_locations = world.get_unfilled_locations()
random.shuffle(fill_locations) world.random.shuffle(fill_locations)
# get items to distribute # get items to distribute
random.shuffle(world.itempool) world.random.shuffle(world.itempool)
itempool = world.itempool itempool = world.itempool
total_advancement_items = len([item for item in itempool if item.advancement]) total_advancement_items = len([item for item in itempool if item.advancement])
@ -83,10 +82,10 @@ def distribute_items_cutoff(world, cutoffrate=0.33):
def distribute_items_staleness(world): def distribute_items_staleness(world):
# get list of locations to fill in # get list of locations to fill in
fill_locations = world.get_unfilled_locations() fill_locations = world.get_unfilled_locations()
random.shuffle(fill_locations) world.random.shuffle(fill_locations)
# get items to distribute # get items to distribute
random.shuffle(world.itempool) world.random.shuffle(world.itempool)
itempool = world.itempool itempool = world.itempool
progress_done = False progress_done = False
@ -131,7 +130,7 @@ def distribute_items_staleness(world):
spot_to_fill = None spot_to_fill = None
for location in fill_locations: for location in fill_locations:
# increase likelyhood of skipping a location if it has been found stale # increase likelyhood of skipping a location if it has been found stale
if not progress_done and random.randint(0, location.staleness_count) > 2: if not progress_done and world.random.randint(0, location.staleness_count) > 2:
continue continue
if location.can_fill(world.state, item_to_place): if location.can_fill(world.state, item_to_place):
@ -215,10 +214,10 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
# If not passed in, then get a shuffled list of locations to fill in # If not passed in, then get a shuffled list of locations to fill in
if not fill_locations: if not fill_locations:
fill_locations = world.get_unfilled_locations() fill_locations = world.get_unfilled_locations()
random.shuffle(fill_locations) world.random.shuffle(fill_locations)
# get items to distribute # get items to distribute
random.shuffle(world.itempool) world.random.shuffle(world.itempool)
progitempool = [] progitempool = []
localprioitempool = {player: [] for player in range(1, world.players + 1)} localprioitempool = {player: [] for player in range(1, world.players + 1)}
localrestitempool = {player: [] for player in range(1, world.players + 1)} localrestitempool = {player: [] for player in range(1, world.players + 1)}
@ -244,17 +243,17 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
continue continue
gftower_trash_count = ( gftower_trash_count = (
random.randint(15, 50) if 'triforcehunt' in world.goal[player] world.random.randint(15, 50) if 'triforcehunt' in world.goal[player]
else random.randint(0, 15)) else world.random.randint(0, 15))
gtower_locations = [location for location in fill_locations if gtower_locations = [location for location in fill_locations if
'Ganons Tower' in location.name and location.player == player] 'Ganons Tower' in location.name and location.player == player]
random.shuffle(gtower_locations) world.random.shuffle(gtower_locations)
trashcnt = 0 trashcnt = 0
localrest = localrestitempool[player] localrest = localrestitempool[player]
if localrest: if localrest:
gt_item_pool = restitempool + localrest gt_item_pool = restitempool + localrest
random.shuffle(gt_item_pool) world.random.shuffle(gt_item_pool)
else: else:
gt_item_pool = restitempool.copy() gt_item_pool = restitempool.copy()
@ -269,7 +268,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
fill_locations.remove(spot_to_fill) fill_locations.remove(spot_to_fill)
trashcnt += 1 trashcnt += 1
random.shuffle(fill_locations) world.random.shuffle(fill_locations)
fill_locations.reverse() fill_locations.reverse()
# Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots
@ -283,20 +282,20 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
localprioitempool.values() or localrestitempool.values()): # we need to make sure some fills are limited to certain worlds localprioitempool.values() or localrestitempool.values()): # we need to make sure some fills are limited to certain worlds
for player, items in localprioitempool.items(): # items already shuffled for player, items in localprioitempool.items(): # items already shuffled
local_locations = [location for location in fill_locations if location.player == player] local_locations = [location for location in fill_locations if location.player == player]
random.shuffle(local_locations) world.random.shuffle(local_locations)
for item_to_place in items: for item_to_place in items:
spot_to_fill = local_locations.pop() spot_to_fill = local_locations.pop()
world.push_item(spot_to_fill, item_to_place, False) world.push_item(spot_to_fill, item_to_place, False)
fill_locations.remove(spot_to_fill) fill_locations.remove(spot_to_fill)
for player, items in localrestitempool.items(): # items already shuffled for player, items in localrestitempool.items(): # items already shuffled
local_locations = [location for location in fill_locations if location.player == player] local_locations = [location for location in fill_locations if location.player == player]
random.shuffle(local_locations) world.random.shuffle(local_locations)
for item_to_place in items: for item_to_place in items:
spot_to_fill = local_locations.pop() spot_to_fill = local_locations.pop()
world.push_item(spot_to_fill, item_to_place, False) world.push_item(spot_to_fill, item_to_place, False)
fill_locations.remove(spot_to_fill) fill_locations.remove(spot_to_fill)
random.shuffle(fill_locations) world.random.shuffle(fill_locations)
fast_fill(world, prioitempool, fill_locations) fast_fill(world, prioitempool, fill_locations)
@ -314,7 +313,7 @@ def fast_fill(world, item_pool, fill_locations):
def flood_items(world): def flood_items(world):
# get items to distribute # get items to distribute
random.shuffle(world.itempool) world.random.shuffle(world.itempool)
itempool = world.itempool itempool = world.itempool
progress_done = False progress_done = False
@ -324,7 +323,7 @@ def flood_items(world):
# fill world from top of itempool while we can # fill world from top of itempool while we can
while not progress_done: while not progress_done:
location_list = world.get_unfilled_locations() location_list = world.get_unfilled_locations()
random.shuffle(location_list) world.random.shuffle(location_list)
spot_to_fill = None spot_to_fill = None
for location in location_list: for location in location_list:
if location.can_fill(world.state, itempool[0]): if location.can_fill(world.state, itempool[0]):
@ -360,7 +359,7 @@ def flood_items(world):
# find item to replace with progress item # find item to replace with progress item
location_list = world.get_reachable_locations() location_list = world.get_reachable_locations()
random.shuffle(location_list) world.random.shuffle(location_list)
for location in location_list: for location in location_list:
if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey: if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey:
# safe to replace # safe to replace
@ -380,7 +379,7 @@ def balance_multiworld_progression(world):
state = CollectionState(world) state = CollectionState(world)
checked_locations = [] checked_locations = []
unchecked_locations = world.get_locations().copy() unchecked_locations = world.get_locations().copy()
random.shuffle(unchecked_locations) world.random.shuffle(unchecked_locations)
reachable_locations_count = {player: 0 for player in range(1, world.players + 1)} reachable_locations_count = {player: 0 for player in range(1, world.players + 1)}

View File

@ -1,6 +1,5 @@
from collections import namedtuple from collections import namedtuple
import logging import logging
import random
from BaseClasses import Region, RegionType, Shop, ShopType, Location from BaseClasses import Region, RegionType, Shop, ShopType, Location
from Bosses import place_bosses from Bosses import place_bosses
@ -181,10 +180,7 @@ def generate_itempool(world, player):
# set up item pool # set up item pool
if world.custom: if world.custom:
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon,
lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive[player], world.shuffle[player], lamps_needed_for_dark_rooms) = make_custom_item_pool(world, player)
world.difficulty[player], world.timer[player], world.goal[player],
world.mode[player], world.swords[player],
world.retro[player], world.customitemarray)
world.rupoor_cost = min(world.customitemarray[69], 9999) world.rupoor_cost = min(world.customitemarray[69], 9999)
else: else:
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon,
@ -209,7 +205,7 @@ def generate_itempool(world, player):
if item in ['Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: if item in ['Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
if item not in possible_weapons: if item not in possible_weapons:
possible_weapons.append(item) possible_weapons.append(item)
starting_weapon = random.choice(possible_weapons) starting_weapon = world.random.choice(possible_weapons)
placed_items["Link's Uncle"] = starting_weapon placed_items["Link's Uncle"] = starting_weapon
pool.remove(starting_weapon) pool.remove(starting_weapon)
if placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria', 'Cane of Byrna'] and world.enemy_health[player] not in ['default', 'easy']: if placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria', 'Cane of Byrna'] and world.enemy_health[player] not in ['default', 'easy']:
@ -255,24 +251,25 @@ def generate_itempool(world, player):
4: {'trap': 100}} 4: {'trap': 100}}
def beemizer(item): def beemizer(item):
if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type: if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type:
choice = random.choices(list(beeweights[world.beemizer[item.player]].keys()), weights=list(beeweights[world.beemizer[item.player]].values()))[0] choice = world.random.choices(list(beeweights[world.beemizer[item.player]].keys()),
weights=list(beeweights[world.beemizer[item.player]].values()))[0]
return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee", player) return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee", player)
return item return item
progressionitems = [item for item in items if item.advancement or item.priority or item.type] progressionitems = [item for item in items if item.advancement or item.priority or item.type]
nonprogressionitems = [beemizer(item) for item in items if not item.advancement and not item.priority and not item.type] nonprogressionitems = [beemizer(item) for item in items if not item.advancement and not item.priority and not item.type]
random.shuffle(nonprogressionitems) world.random.shuffle(nonprogressionitems)
triforce_pieces = world.triforce_pieces_available[player] triforce_pieces = world.triforce_pieces_available[player]
if 'triforcehunt' in world.goal[player] and triforce_pieces > 30: if 'triforcehunt' in world.goal[player] and triforce_pieces > 30:
progressionitems += [ItemFactory("Triforce Piece", player)] * (triforce_pieces - 30) progressionitems += [ItemFactory("Triforce Piece", player)] * (triforce_pieces - 30)
nonprogressionitems = nonprogressionitems[(triforce_pieces-30):] nonprogressionitems = nonprogressionitems[(triforce_pieces - 30):]
world.itempool += progressionitems + nonprogressionitems world.itempool += progressionitems + nonprogressionitems
# shuffle medallions # shuffle medallions
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] mm_medallion = ['Ether', 'Quake', 'Bombos'][world.random.randint(0, 2)]
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] tr_medallion = ['Ether', 'Quake', 'Bombos'][world.random.randint(0, 2)]
world.required_medallions[player] = (mm_medallion, tr_medallion) world.required_medallions[player] = (mm_medallion, tr_medallion)
place_bosses(world, player) place_bosses(world, player)
@ -297,7 +294,7 @@ def set_up_take_anys(world, player):
if world.mode[player] == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations: if world.mode[player] == 'inverted' and 'Dark Sanctuary Hint' in take_any_locations:
take_any_locations.remove('Dark Sanctuary Hint') take_any_locations.remove('Dark Sanctuary Hint')
regions = random.sample(take_any_locations, 5) regions = world.random.sample(take_any_locations, 5)
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player) old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
world.regions.append(old_man_take_any) world.regions.append(old_man_take_any)
@ -312,7 +309,7 @@ def set_up_take_anys(world, player):
swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player] swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player]
if swords: if swords:
sword = random.choice(swords) sword = world.random.choice(swords)
world.itempool.remove(sword) world.itempool.remove(sword)
world.itempool.append(ItemFactory('Rupees (20)', player)) world.itempool.append(ItemFactory('Rupees (20)', player))
old_man_take_any.shop.add_inventory(0, sword.name, 0, 0, create_location=True) old_man_take_any.shop.add_inventory(0, sword.name, 0, 0, create_location=True)
@ -324,7 +321,7 @@ def set_up_take_anys(world, player):
world.regions.append(take_any) world.regions.append(take_any)
world.dynamic_regions.append(take_any) world.dynamic_regions.append(take_any)
target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)]) target, room_id = world.random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)])
reg = regions.pop() reg = regions.pop()
entrance = world.get_region(reg, player).entrances[0] entrance = world.get_region(reg, player).entrances[0]
connect_entrance(world, entrance.name, take_any.name, player) connect_entrance(world, entrance.name, take_any.name, player)
@ -368,8 +365,8 @@ def fill_prizes(world, attempts=15):
try: try:
prizepool = list(unplaced_prizes) prizepool = list(unplaced_prizes)
prize_locs = list(empty_crystal_locations) prize_locs = list(empty_crystal_locations)
random.shuffle(prizepool) world.random.shuffle(prizepool)
random.shuffle(prize_locs) world.random.shuffle(prize_locs)
fill_restrictive(world, all_state, prize_locs, prizepool, True) fill_restrictive(world, all_state, prize_locs, prizepool, True)
except FillError as e: except FillError as e:
logging.getLogger('').exception("Failed to place dungeon prizes (%s). Will retry %s more times", e, logging.getLogger('').exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
@ -389,7 +386,9 @@ def set_up_shops(world, player):
rss = world.get_region('Red Shield Shop', player).shop rss = world.get_region('Red Shield Shop', player).shop
if not rss.locked: if not rss.locked:
rss.add_inventory(2, 'Single Arrow', 80) rss.add_inventory(2, 'Single Arrow', 80)
for shop in random.sample([s for s in world.shops if s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player], 5): for shop in world.random.sample([s for s in world.shops if
s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player],
5):
shop.locked = True shop.locked = True
shop.add_inventory(0, 'Single Arrow', 80) shop.add_inventory(0, 'Single Arrow', 80)
shop.add_inventory(1, 'Small Key (Universal)', 100) shop.add_inventory(1, 'Small Key (Universal)', 100)
@ -422,7 +421,7 @@ def get_pool_core(world, player: int):
placed_items[loc] = item placed_items[loc] = item
def want_progressives(): def want_progressives():
return random.choice([True, False]) if progressive == 'random' else progressive == 'on' return world.random.choice([True, False]) if progressive == 'random' else progressive == 'on'
# provide boots to major glitch dependent seeds # provide boots to major glitch dependent seeds
if logic in {'owglitches', 'nologic'} and world.glitch_boots[player]: if logic in {'owglitches', 'nologic'} and world.glitch_boots[player]:
@ -455,10 +454,10 @@ def get_pool_core(world, player: int):
# expert+ difficulties produce the same contents for # expert+ difficulties produce the same contents for
# all bottles, since only one bottle is available # all bottles, since only one bottle is available
if diff.same_bottle: if diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = world.random.choice(diff.bottles)
for _ in range(diff.bottle_count): for _ in range(diff.bottle_count):
if not diff.same_bottle: if not diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = world.random.choice(diff.bottles)
pool.append(thisbottle) pool.append(thisbottle)
if want_progressives(): if want_progressives():
@ -482,7 +481,7 @@ def get_pool_core(world, player: int):
pool.extend(diff.swordless) pool.extend(diff.swordless)
elif swords == 'vanilla': elif swords == 'vanilla':
swords_to_use = diff.progressivesword.copy() if want_progressives() else diff.basicsword.copy() swords_to_use = diff.progressivesword.copy() if want_progressives() else diff.basicsword.copy()
random.shuffle(swords_to_use) world.random.shuffle(swords_to_use)
place_item('Link\'s Uncle', swords_to_use.pop()) place_item('Link\'s Uncle', swords_to_use.pop())
place_item('Blacksmith', swords_to_use.pop()) place_item('Blacksmith', swords_to_use.pop())
@ -535,13 +534,24 @@ def get_pool_core(world, player: int):
pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool] pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool]
pool.extend(diff.retro) pool.extend(diff.retro)
if mode == 'standard': if mode == 'standard':
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) key_location = world.random.choice(
['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
place_item(key_location, 'Small Key (Universal)') place_item(key_location, 'Small Key (Universal)')
else: else:
pool.extend(['Small Key (Universal)']) pool.extend(['Small Key (Universal)'])
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
def make_custom_item_pool(world, player):
shuffle = world.shuffle[player]
difficulty = world.difficulty[player]
timer = world.timer[player]
goal = world.goal[player]
mode = world.mode[player]
retro = world.retro[player]
customitemarray = world.customitemarray[player]
pool = [] pool = []
placed_items = {} placed_items = {}
precollected_items = [] precollected_items = []
@ -636,10 +646,10 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
# expert+ difficulties produce the same contents for # expert+ difficulties produce the same contents for
# all bottles, since only one bottle is available # all bottles, since only one bottle is available
if diff.same_bottle: if diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = world.random.choice(diff.bottles)
for _ in range(customitemarray[18]): for _ in range(customitemarray[18]):
if not diff.same_bottle: if not diff.same_bottle:
thisbottle = random.choice(diff.bottles) thisbottle = world.random.choice(diff.bottles)
pool.append(thisbottle) pool.append(thisbottle)
if customitemarray[66] > 0 or customitemarray[67] > 0: if customitemarray[66] > 0 or customitemarray[67] > 0:
@ -664,7 +674,9 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
if mode == 'standard': if mode == 'standard':
if retro: if retro:
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) key_location = world.random.choice(
['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
place_item(key_location, 'Small Key (Universal)') place_item(key_location, 'Small Key (Universal)')
pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0)) pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0))
else: else:

18
Main.py
View File

@ -43,17 +43,25 @@ def main(args, seed=None):
world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty,
args.item_functionality, args.timer, args.progressive.copy(), args.goal, args.algorithm, args.item_functionality, args.timer, args.progressive.copy(), args.goal, args.algorithm,
args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints) args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
logger = logging.getLogger('') logger = logging.getLogger('')
world.seed = get_seed(seed) world.seed = get_seed(seed)
random.seed(world.seed) if args.race:
world.secure()
else:
world.random.seed(world.seed)
world.remote_items = args.remote_items.copy() world.remote_items = args.remote_items.copy()
world.mapshuffle = args.mapshuffle.copy() world.mapshuffle = args.mapshuffle.copy()
world.compassshuffle = args.compassshuffle.copy() world.compassshuffle = args.compassshuffle.copy()
world.keyshuffle = args.keyshuffle.copy() world.keyshuffle = args.keyshuffle.copy()
world.bigkeyshuffle = args.bigkeyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy()
world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)} world.crystals_needed_for_ganon = {
world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)} player: world.random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(
args.crystals_ganon[player]) for player in range(1, world.players + 1)}
world.crystals_needed_for_gt = {
player: world.random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player])
for player in range(1, world.players + 1)}
world.open_pyramid = args.openpyramid.copy() world.open_pyramid = args.openpyramid.copy()
world.boss_shuffle = args.shufflebosses.copy() world.boss_shuffle = args.shufflebosses.copy()
world.enemy_shuffle = args.shuffleenemies.copy() world.enemy_shuffle = args.shuffleenemies.copy()
@ -69,7 +77,7 @@ def main(args, seed=None):
world.triforce_pieces_required = args.triforce_pieces_required.copy() world.triforce_pieces_required = args.triforce_pieces_required.copy()
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()} world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.rom_seeds = {player: world.random.randint(0, 999999999) for player in range(1, world.players + 1)}
logger.info('ALttP Berserker\'s Multiworld Version %s - Seed: %s\n', __version__, world.seed) logger.info('ALttP Berserker\'s Multiworld Version %s - Seed: %s\n', __version__, world.seed)
@ -134,7 +142,7 @@ def main(args, seed=None):
if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) + if args.algorithm in ['balanced', 'vt26'] or any(list(args.mapshuffle.values()) + list(args.compassshuffle.values()) +
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())): list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
shuffled_locations = world.get_unfilled_locations() shuffled_locations = world.get_unfilled_locations()
random.shuffle(shuffled_locations) world.random.shuffle(shuffled_locations)
fill_dungeons_restrictive(world, shuffled_locations) fill_dungeons_restrictive(world, shuffled_locations)
else: else:
fill_dungeons(world) fill_dungeons(world)

View File

@ -12,6 +12,7 @@ import inspect
import weakref import weakref
import datetime import datetime
import threading import threading
import random
import ModuleUpdate import ModuleUpdate
@ -830,7 +831,6 @@ class ClientMessageProcessor(CommonCommandProcessor):
can_pay = points_available // self.ctx.hint_cost can_pay = points_available // self.ctx.hint_cost
else: else:
can_pay = 1000 can_pay = 1000
import random
random.shuffle(not_found_hints) random.shuffle(not_found_hints)

View File

@ -56,6 +56,9 @@ def main(args=None):
seed = get_seed(args.seed) seed = get_seed(args.seed)
random.seed(seed) random.seed(seed)
if args.race:
random.seed() # reset to time-based random source
seedname = "M" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits)) seedname = "M" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits))
print(f"Generating mystery for {args.multi} player{'s' if args.multi > 1 else ''}, {seedname} Seed {seed}") print(f"Generating mystery for {args.multi} player{'s' if args.multi > 1 else ''}, {seedname} Seed {seed}")

86
Rom.py
View File

@ -236,8 +236,9 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite
'BeesLevel': 0, 'BeesLevel': 0,
'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos', 'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos',
'RandomizeTileTrapFloorTile': False, 'RandomizeTileTrapFloorTile': False,
'AllowKillableThief': bool(random.randint(0, 1)) if 'thieves' in world.enemy_shuffle[player] else 'AllowKillableThief': bool(world.random.randint(0, 1)) if 'thieves' in world.enemy_shuffle[player] else
world.enemy_shuffle[player] != 'none', world.enemy_shuffle[player] != 'none',
# TODO: this is currently non-deterministic due to being called in a thread
'RandomizeSpriteOnHit': random_sprite_on_hit, 'RandomizeSpriteOnHit': random_sprite_on_hit,
'DebugMode': False, 'DebugMode': False,
'DebugForceEnemy': False, 'DebugForceEnemy': False,
@ -289,7 +290,7 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite
if sprites: if sprites:
while len(sprites) < 32: while len(sprites) < 32:
sprites.extend(sprites) sprites.extend(sprites)
random.shuffle(sprites) world.random.shuffle(sprites)
for i, path in enumerate(sprites[:32]): for i, path in enumerate(sprites[:32]):
sprite = Sprite(path) sprite = Sprite(path)
@ -1606,9 +1607,9 @@ def write_strings(rom, world, player, team):
if world.hints[player]: if world.hints[player]:
tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!' tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!'
hint_locations = HintLocations.copy() hint_locations = HintLocations.copy()
random.shuffle(hint_locations) world.random.shuffle(hint_locations)
all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player] all_entrances = [entrance for entrance in world.get_entrances() if entrance.player == player]
random.shuffle(all_entrances) world.random.shuffle(all_entrances)
#First we take care of the one inconvenient dungeon in the appropriately simple shuffles. #First we take care of the one inconvenient dungeon in the appropriately simple shuffles.
entrances_to_hint = {} entrances_to_hint = {}
@ -1683,12 +1684,12 @@ def write_strings(rom, world, player, team):
locations_to_hint = InconvenientLocations.copy() locations_to_hint = InconvenientLocations.copy()
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
locations_to_hint.extend(InconvenientVanillaLocations) locations_to_hint.extend(InconvenientVanillaLocations)
random.shuffle(locations_to_hint) world.random.shuffle(locations_to_hint)
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5 hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5
del locations_to_hint[hint_count:] del locations_to_hint[hint_count:]
for location in locations_to_hint: for location in locations_to_hint:
if location == 'Swamp Left': if location == 'Swamp Left':
if random.randint(0, 1) == 0: if world.random.randint(0, 1) == 0:
first_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item) first_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
second_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item) second_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
else: else:
@ -1697,7 +1698,7 @@ def write_strings(rom, world, player, team):
this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.') this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.')
tt[hint_locations.pop(0)] = this_hint tt[hint_locations.pop(0)] = this_hint
elif location == 'Mire Left': elif location == 'Mire Left':
if random.randint(0, 1) == 0: if world.random.randint(0, 1) == 0:
first_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item) first_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
second_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item) second_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
else: else:
@ -1736,12 +1737,12 @@ def write_strings(rom, world, player, team):
items_to_hint.extend(SmallKeys) items_to_hint.extend(SmallKeys)
if world.bigkeyshuffle[player]: if world.bigkeyshuffle[player]:
items_to_hint.extend(BigKeys) items_to_hint.extend(BigKeys)
random.shuffle(items_to_hint) world.random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8
while hint_count > 0: while hint_count > 0:
this_item = items_to_hint.pop(0) this_item = items_to_hint.pop(0)
this_location = world.find_items(this_item, player) this_location = world.find_items(this_item, player)
random.shuffle(this_location) world.random.shuffle(this_location)
#This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool. #This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool.
if this_location: if this_location:
if this_location[0].name == 'Skull Woods - Pinball Room': if this_location[0].name == 'Skull Woods - Pinball Room':
@ -1753,7 +1754,7 @@ def write_strings(rom, world, player, team):
# All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint isn't selected twice. # All remaining hint slots are filled with junk hints. It is done this way to ensure the same junk hint isn't selected twice.
junk_hints = junk_texts.copy() junk_hints = junk_texts.copy()
random.shuffle(junk_hints) world.random.shuffle(junk_hints)
for location in hint_locations: for location in hint_locations:
tt[location] = junk_hints.pop(0) tt[location] = junk_hints.pop(0)
@ -1761,7 +1762,7 @@ def write_strings(rom, world, player, team):
silverarrows = world.find_items('Silver Bow', player) silverarrows = world.find_items('Silver Bow', player)
random.shuffle(silverarrows) world.random.shuffle(silverarrows)
silverarrow_hint = (' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!' silverarrow_hint = (' %s?' % hint_text(silverarrows[0]).replace('Ganon\'s', 'my')) if silverarrows else '?\nI think not!'
tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint
tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint
@ -1775,7 +1776,8 @@ def write_strings(rom, world, player, team):
tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint
if any(prog_bow_locs): if any(prog_bow_locs):
silverarrow_hint = (' %s?' % hint_text(random.choice(prog_bow_locs)).replace('Ganon\'s', 'my')) if progressive_silvers else '?\nI think not!' silverarrow_hint = (' %s?' % hint_text(world.random.choice(prog_bow_locs)).replace('Ganon\'s',
'my')) if progressive_silvers else '?\nI think not!'
tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint
@ -1786,19 +1788,23 @@ def write_strings(rom, world, player, team):
greenpendant = world.find_items('Green Pendant', player)[0] greenpendant = world.find_items('Green Pendant', player)[0]
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text
tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[player] == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt[player] tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[
tt['sign_ganon'] = ('You need %d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else 'You need %d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player] player] == 1 else 'You need %d crystals to enter.') % \
world.crystals_needed_for_gt[player]
tt['sign_ganon'] = ('You need %d crystal to beat Ganon.' if world.crystals_needed_for_ganon[
player] == 1 else 'You need %d crystals to beat Ganon.') % \
world.crystals_needed_for_ganon[player]
if world.goal[player] in ['dungeons']: if world.goal[player] in ['dungeons']:
tt['sign_ganon'] = 'You need to complete all the dungeons.' tt['sign_ganon'] = 'You need to complete all the dungeons.'
tt['uncle_leaving_text'] = Uncle_texts[random.randint(0, len(Uncle_texts) - 1)] tt['uncle_leaving_text'] = Uncle_texts[world.random.randint(0, len(Uncle_texts) - 1)]
tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[random.randint(0, len(Triforce_texts) - 1)] tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[world.random.randint(0, len(Triforce_texts) - 1)]
tt['bomb_shop_big_bomb'] = BombShop2_texts[random.randint(0, len(BombShop2_texts) - 1)] tt['bomb_shop_big_bomb'] = BombShop2_texts[world.random.randint(0, len(BombShop2_texts) - 1)]
# this is what shows after getting the green pendant item in rando # this is what shows after getting the green pendant item in rando
tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[random.randint(0, len(Sahasrahla2_texts) - 1)] tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[world.random.randint(0, len(Sahasrahla2_texts) - 1)]
tt['blind_by_the_light'] = Blind_texts[random.randint(0, len(Blind_texts) - 1)] tt['blind_by_the_light'] = Blind_texts[world.random.randint(0, len(Blind_texts) - 1)]
if world.goal[player] in ['triforcehunt', 'localtriforcehunt']: if world.goal[player] in ['triforcehunt', 'localtriforcehunt']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.'
@ -1807,14 +1813,15 @@ def write_strings(rom, world, player, team):
tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!' tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!'
else: else:
tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!'
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % \ tt[
'murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % \
world.treasure_hunt_count[player] world.treasure_hunt_count[player]
elif world.goal[player] in ['pedestal']: elif world.goal[player] in ['pedestal']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.' tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.'
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!' tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!'
else: else:
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)] tt['ganon_fall_in'] = Ganon1_texts[world.random.randint(0, len(Ganon1_texts) - 1)]
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!' tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!' tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
if world.goal[player] == 'ganontriforcehunt' and world.players > 1: if world.goal[player] == 'ganontriforcehunt' and world.players > 1:
@ -1822,7 +1829,7 @@ def write_strings(rom, world, player, team):
elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']: elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
tt['sign_ganon'] = 'You need to find %d Triforce pieces to defeat Ganon.' % world.treasure_hunt_count[player] tt['sign_ganon'] = 'You need to find %d Triforce pieces to defeat Ganon.' % world.treasure_hunt_count[player]
tt['kakariko_tavern_fisherman'] = TavernMan_texts[random.randint(0, len(TavernMan_texts) - 1)] tt['kakariko_tavern_fisherman'] = TavernMan_texts[world.random.randint(0, len(TavernMan_texts) - 1)]
pedestalitem = world.get_location('Master Sword Pedestal', player).item pedestalitem = world.get_location('Master Sword Pedestal', player).item
pedestal_text = 'Some Hot Air' if pedestalitem is None else hint_text(pedestalitem, True) if pedestalitem.pedestal_hint_text is not None else 'Unknown Item' pedestal_text = 'Some Hot Air' if pedestalitem is None else hint_text(pedestalitem, True) if pedestalitem.pedestal_hint_text is not None else 'Unknown Item'
@ -1853,33 +1860,38 @@ def write_strings(rom, world, player, team):
credits = Credits() credits = Credits()
sickkiditem = world.get_location('Sick Kid', player).item sickkiditem = world.get_location('Sick Kid', player).item
sickkiditem_text = random.choice(SickKid_texts) if sickkiditem is None or sickkiditem.sickkid_credit_text is None else sickkiditem.sickkid_credit_text sickkiditem_text = world.random.choice(
SickKid_texts) if sickkiditem is None or sickkiditem.sickkid_credit_text is None else sickkiditem.sickkid_credit_text
zoraitem = world.get_location('King Zora', player).item zoraitem = world.get_location('King Zora', player).item
zoraitem_text = random.choice(Zora_texts) if zoraitem is None or zoraitem.zora_credit_text is None else zoraitem.zora_credit_text zoraitem_text = world.random.choice(
Zora_texts) if zoraitem is None or zoraitem.zora_credit_text is None else zoraitem.zora_credit_text
magicshopitem = world.get_location('Potion Shop', player).item magicshopitem = world.get_location('Potion Shop', player).item
magicshopitem_text = random.choice(MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text magicshopitem_text = world.random.choice(
MagicShop_texts) if magicshopitem is None or magicshopitem.magicshop_credit_text is None else magicshopitem.magicshop_credit_text
fluteboyitem = world.get_location('Flute Spot', player).item fluteboyitem = world.get_location('Flute Spot', player).item
fluteboyitem_text = random.choice(FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text fluteboyitem_text = world.random.choice(
FluteBoy_texts) if fluteboyitem is None or fluteboyitem.fluteboy_credit_text is None else fluteboyitem.fluteboy_credit_text
credits.update_credits_line('castle', 0, random.choice(KingsReturn_texts)) credits.update_credits_line('castle', 0, world.random.choice(KingsReturn_texts))
credits.update_credits_line('sanctuary', 0, random.choice(Sanctuary_texts)) credits.update_credits_line('sanctuary', 0, world.random.choice(Sanctuary_texts))
credits.update_credits_line('kakariko', 0, random.choice(Kakariko_texts).format(random.choice(Sahasrahla_names))) credits.update_credits_line('kakariko', 0,
credits.update_credits_line('desert', 0, random.choice(DesertPalace_texts)) world.random.choice(Kakariko_texts).format(world.random.choice(Sahasrahla_names)))
credits.update_credits_line('hera', 0, random.choice(MountainTower_texts)) credits.update_credits_line('desert', 0, world.random.choice(DesertPalace_texts))
credits.update_credits_line('house', 0, random.choice(LinksHouse_texts)) credits.update_credits_line('hera', 0, world.random.choice(MountainTower_texts))
credits.update_credits_line('house', 0, world.random.choice(LinksHouse_texts))
credits.update_credits_line('zora', 0, zoraitem_text) credits.update_credits_line('zora', 0, zoraitem_text)
credits.update_credits_line('witch', 0, magicshopitem_text) credits.update_credits_line('witch', 0, magicshopitem_text)
credits.update_credits_line('lumberjacks', 0, random.choice(Lumberjacks_texts)) credits.update_credits_line('lumberjacks', 0, world.random.choice(Lumberjacks_texts))
credits.update_credits_line('grove', 0, fluteboyitem_text) credits.update_credits_line('grove', 0, fluteboyitem_text)
credits.update_credits_line('well', 0, random.choice(WishingWell_texts)) credits.update_credits_line('well', 0, world.random.choice(WishingWell_texts))
credits.update_credits_line('smithy', 0, random.choice(Blacksmiths_texts)) credits.update_credits_line('smithy', 0, world.random.choice(Blacksmiths_texts))
credits.update_credits_line('kakariko2', 0, sickkiditem_text) credits.update_credits_line('kakariko2', 0, sickkiditem_text)
credits.update_credits_line('bridge', 0, random.choice(DeathMountain_texts)) credits.update_credits_line('bridge', 0, world.random.choice(DeathMountain_texts))
credits.update_credits_line('woods', 0, random.choice(LostWoods_texts)) credits.update_credits_line('woods', 0, world.random.choice(LostWoods_texts))
credits.update_credits_line('pedestal', 0, pedestal_credit_text) credits.update_credits_line('pedestal', 0, pedestal_credit_text)
(pointers, data) = credits.get_bytes() (pointers, data) = credits.get_bytes()