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 typing import Union
import secrets
import random
class World(object):
debug_types = False
player_names: list
@ -28,6 +32,8 @@ class World(object):
setattr(self, name[7:], method)
logging.debug(f"Set {self}.{name[7:]} to {method}")
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.teams = 1
self.shuffle = shuffle.copy()
@ -116,6 +122,9 @@ class World(object):
set_player_attr('triforce_pieces_available', 30)
set_player_attr('triforce_pieces_required', 20)
def secure(self):
self.random = secrets.SystemRandom()
@property
def player_ids(self):
yield from range(1, self.players + 1)

View File

@ -1,5 +1,4 @@
import logging
import random
from BaseClasses import Boss
from Fill import FillError
@ -193,11 +192,11 @@ def place_bosses(world, player):
if world.boss_shuffle[player] == "basic": # vanilla bosses shuffled
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
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)
random.shuffle(bosses)
world.random.shuffle(bosses)
for [loc, level] in boss_locations:
loc_text = loc + (' ('+level+')' if level else '')
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:
loc_text = loc + (' ('+level+')' if level else '')
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:
raise FillError('Could not place boss for location %s' % loc_text)

View File

@ -1,5 +1,3 @@
import random
from BaseClasses import Dungeon
from Bosses import BossFactory
from Fill import fill_restrictive
@ -58,7 +56,7 @@ def fill_dungeons(world):
dungeon_regions, big_key, small_keys, dungeon_items = dungeons.pop(0)
# 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]
random.shuffle(dungeon_locations)
world.random.shuffle(dungeon_locations)
all_state = all_state_base.copy()

View File

@ -3,7 +3,6 @@ import argparse
import copy
import os
import logging
import random
import textwrap
import shlex
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
from BaseClasses import CollectionState
@ -10,10 +9,10 @@ class FillError(RuntimeError):
def distribute_items_cutoff(world, cutoffrate=0.33):
# get list of locations to fill in
fill_locations = world.get_unfilled_locations()
random.shuffle(fill_locations)
world.random.shuffle(fill_locations)
# get items to distribute
random.shuffle(world.itempool)
world.random.shuffle(world.itempool)
itempool = world.itempool
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):
# get list of locations to fill in
fill_locations = world.get_unfilled_locations()
random.shuffle(fill_locations)
world.random.shuffle(fill_locations)
# get items to distribute
random.shuffle(world.itempool)
world.random.shuffle(world.itempool)
itempool = world.itempool
progress_done = False
@ -131,7 +130,7 @@ def distribute_items_staleness(world):
spot_to_fill = None
for location in fill_locations:
# 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
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 fill_locations:
fill_locations = world.get_unfilled_locations()
random.shuffle(fill_locations)
world.random.shuffle(fill_locations)
# get items to distribute
random.shuffle(world.itempool)
world.random.shuffle(world.itempool)
progitempool = []
localprioitempool = {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
gftower_trash_count = (
random.randint(15, 50) if 'triforcehunt' in world.goal[player]
else random.randint(0, 15))
world.random.randint(15, 50) if 'triforcehunt' in world.goal[player]
else world.random.randint(0, 15))
gtower_locations = [location for location in fill_locations if
'Ganons Tower' in location.name and location.player == player]
random.shuffle(gtower_locations)
world.random.shuffle(gtower_locations)
trashcnt = 0
localrest = localrestitempool[player]
if localrest:
gt_item_pool = restitempool + localrest
random.shuffle(gt_item_pool)
world.random.shuffle(gt_item_pool)
else:
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)
trashcnt += 1
random.shuffle(fill_locations)
world.random.shuffle(fill_locations)
fill_locations.reverse()
# 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
for player, items in localprioitempool.items(): # items already shuffled
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:
spot_to_fill = local_locations.pop()
world.push_item(spot_to_fill, item_to_place, False)
fill_locations.remove(spot_to_fill)
for player, items in localrestitempool.items(): # items already shuffled
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:
spot_to_fill = local_locations.pop()
world.push_item(spot_to_fill, item_to_place, False)
fill_locations.remove(spot_to_fill)
random.shuffle(fill_locations)
world.random.shuffle(fill_locations)
fast_fill(world, prioitempool, fill_locations)
@ -314,7 +313,7 @@ def fast_fill(world, item_pool, fill_locations):
def flood_items(world):
# get items to distribute
random.shuffle(world.itempool)
world.random.shuffle(world.itempool)
itempool = world.itempool
progress_done = False
@ -324,7 +323,7 @@ def flood_items(world):
# fill world from top of itempool while we can
while not progress_done:
location_list = world.get_unfilled_locations()
random.shuffle(location_list)
world.random.shuffle(location_list)
spot_to_fill = None
for location in location_list:
if location.can_fill(world.state, itempool[0]):
@ -360,7 +359,7 @@ def flood_items(world):
# find item to replace with progress item
location_list = world.get_reachable_locations()
random.shuffle(location_list)
world.random.shuffle(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:
# safe to replace
@ -380,7 +379,7 @@ def balance_multiworld_progression(world):
state = CollectionState(world)
checked_locations = []
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)}

View File

@ -1,6 +1,5 @@
from collections import namedtuple
import logging
import random
from BaseClasses import Region, RegionType, Shop, ShopType, Location
from Bosses import place_bosses
@ -181,10 +180,7 @@ def generate_itempool(world, player):
# set up item pool
if world.custom:
(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],
world.difficulty[player], world.timer[player], world.goal[player],
world.mode[player], world.swords[player],
world.retro[player], world.customitemarray)
lamps_needed_for_dark_rooms) = make_custom_item_pool(world, player)
world.rupoor_cost = min(world.customitemarray[69], 9999)
else:
(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 not in possible_weapons:
possible_weapons.append(item)
starting_weapon = random.choice(possible_weapons)
starting_weapon = world.random.choice(possible_weapons)
placed_items["Link's Uncle"] = 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']:
@ -255,24 +251,25 @@ def generate_itempool(world, player):
4: {'trap': 100}}
def beemizer(item):
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
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]
random.shuffle(nonprogressionitems)
world.random.shuffle(nonprogressionitems)
triforce_pieces = world.triforce_pieces_available[player]
if 'triforcehunt' in world.goal[player] and 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
# shuffle medallions
mm_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
mm_medallion = ['Ether', 'Quake', 'Bombos'][world.random.randint(0, 2)]
tr_medallion = ['Ether', 'Quake', 'Bombos'][world.random.randint(0, 2)]
world.required_medallions[player] = (mm_medallion, tr_medallion)
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:
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)
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]
if swords:
sword = random.choice(swords)
sword = world.random.choice(swords)
world.itempool.remove(sword)
world.itempool.append(ItemFactory('Rupees (20)', player))
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.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()
entrance = world.get_region(reg, player).entrances[0]
connect_entrance(world, entrance.name, take_any.name, player)
@ -368,8 +365,8 @@ def fill_prizes(world, attempts=15):
try:
prizepool = list(unplaced_prizes)
prize_locs = list(empty_crystal_locations)
random.shuffle(prizepool)
random.shuffle(prize_locs)
world.random.shuffle(prizepool)
world.random.shuffle(prize_locs)
fill_restrictive(world, all_state, prize_locs, prizepool, True)
except FillError as 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
if not rss.locked:
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.add_inventory(0, 'Single Arrow', 80)
shop.add_inventory(1, 'Small Key (Universal)', 100)
@ -422,7 +421,7 @@ def get_pool_core(world, player: int):
placed_items[loc] = item
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
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
# all bottles, since only one bottle is available
if diff.same_bottle:
thisbottle = random.choice(diff.bottles)
thisbottle = world.random.choice(diff.bottles)
for _ in range(diff.bottle_count):
if not diff.same_bottle:
thisbottle = random.choice(diff.bottles)
thisbottle = world.random.choice(diff.bottles)
pool.append(thisbottle)
if want_progressives():
@ -482,7 +481,7 @@ def get_pool_core(world, player: int):
pool.extend(diff.swordless)
elif swords == 'vanilla':
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('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.extend(diff.retro)
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)')
else:
pool.extend(['Small Key (Universal)'])
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 = []
placed_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
# all bottles, since only one bottle is available
if diff.same_bottle:
thisbottle = random.choice(diff.bottles)
thisbottle = world.random.choice(diff.bottles)
for _ in range(customitemarray[18]):
if not diff.same_bottle:
thisbottle = random.choice(diff.bottles)
thisbottle = world.random.choice(diff.bottles)
pool.append(thisbottle)
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 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)')
pool.extend(['Small Key (Universal)'] * max((customitemarray[70] - 1), 0))
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,
args.item_functionality, args.timer, args.progressive.copy(), args.goal, args.algorithm,
args.accessibility, args.shuffleganon, args.retro, args.custom, args.customitemarray, args.hints)
logger = logging.getLogger('')
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.mapshuffle = args.mapshuffle.copy()
world.compassshuffle = args.compassshuffle.copy()
world.keyshuffle = args.keyshuffle.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_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)}
world.crystals_needed_for_ganon = {
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.boss_shuffle = args.shufflebosses.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.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)
@ -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()) +
list(args.keyshuffle.values()) + list(args.bigkeyshuffle.values())):
shuffled_locations = world.get_unfilled_locations()
random.shuffle(shuffled_locations)
world.random.shuffle(shuffled_locations)
fill_dungeons_restrictive(world, shuffled_locations)
else:
fill_dungeons(world)

View File

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

View File

@ -56,6 +56,9 @@ def main(args=None):
seed = get_seed(args.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))
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,
'RandomizeTileTrapPattern': world.enemy_shuffle[player] == 'chaos',
'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',
# TODO: this is currently non-deterministic due to being called in a thread
'RandomizeSpriteOnHit': random_sprite_on_hit,
'DebugMode': False,
'DebugForceEnemy': False,
@ -289,7 +290,7 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli, random_sprite
if sprites:
while len(sprites) < 32:
sprites.extend(sprites)
random.shuffle(sprites)
world.random.shuffle(sprites)
for i, path in enumerate(sprites[:32]):
sprite = Sprite(path)
@ -1606,9 +1607,9 @@ def write_strings(rom, world, player, team):
if world.hints[player]:
tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!'
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]
random.shuffle(all_entrances)
world.random.shuffle(all_entrances)
#First we take care of the one inconvenient dungeon in the appropriately simple shuffles.
entrances_to_hint = {}
@ -1683,12 +1684,12 @@ def write_strings(rom, world, player, team):
locations_to_hint = InconvenientLocations.copy()
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
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
del locations_to_hint[hint_count:]
for location in locations_to_hint:
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)
second_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
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 + '.')
tt[hint_locations.pop(0)] = this_hint
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)
second_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
else:
@ -1736,12 +1737,12 @@ def write_strings(rom, world, player, team):
items_to_hint.extend(SmallKeys)
if world.bigkeyshuffle[player]:
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
while hint_count > 0:
this_item = items_to_hint.pop(0)
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.
if this_location:
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.
junk_hints = junk_texts.copy()
random.shuffle(junk_hints)
world.random.shuffle(junk_hints)
for location in hint_locations:
tt[location] = junk_hints.pop(0)
@ -1761,7 +1762,7 @@ def write_strings(rom, world, player, team):
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!'
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
@ -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
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
@ -1786,19 +1788,23 @@ def write_strings(rom, world, player, team):
greenpendant = world.find_items('Green Pendant', player)[0]
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_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]
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_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']:
tt['sign_ganon'] = 'You need to complete all the dungeons.'
tt['uncle_leaving_text'] = Uncle_texts[random.randint(0, len(Uncle_texts) - 1)]
tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[random.randint(0, len(Triforce_texts) - 1)]
tt['bomb_shop_big_bomb'] = BombShop2_texts[random.randint(0, len(BombShop2_texts) - 1)]
tt['uncle_leaving_text'] = Uncle_texts[world.random.randint(0, len(Uncle_texts) - 1)]
tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[world.random.randint(0, len(Triforce_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
tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[random.randint(0, len(Sahasrahla2_texts) - 1)]
tt['blind_by_the_light'] = Blind_texts[random.randint(0, len(Blind_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[world.random.randint(0, len(Blind_texts) - 1)]
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.'
@ -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!'
else:
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]
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_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!'
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_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
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']:
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
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()
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_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_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_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('sanctuary', 0, random.choice(Sanctuary_texts))
credits.update_credits_line('castle', 0, world.random.choice(KingsReturn_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('desert', 0, random.choice(DesertPalace_texts))
credits.update_credits_line('hera', 0, random.choice(MountainTower_texts))
credits.update_credits_line('house', 0, random.choice(LinksHouse_texts))
credits.update_credits_line('kakariko', 0,
world.random.choice(Kakariko_texts).format(world.random.choice(Sahasrahla_names)))
credits.update_credits_line('desert', 0, world.random.choice(DesertPalace_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('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('well', 0, random.choice(WishingWell_texts))
credits.update_credits_line('smithy', 0, random.choice(Blacksmiths_texts))
credits.update_credits_line('well', 0, world.random.choice(WishingWell_texts))
credits.update_credits_line('smithy', 0, world.random.choice(Blacksmiths_texts))
credits.update_credits_line('kakariko2', 0, sickkiditem_text)
credits.update_credits_line('bridge', 0, random.choice(DeathMountain_texts))
credits.update_credits_line('woods', 0, random.choice(LostWoods_texts))
credits.update_credits_line('bridge', 0, world.random.choice(DeathMountain_texts))
credits.update_credits_line('woods', 0, world.random.choice(LostWoods_texts))
credits.update_credits_line('pedestal', 0, pedestal_credit_text)
(pointers, data) = credits.get_bytes()