- Settings code added (prototype)

- Mystery.py updates
- Blind maiden sequence restored if Blind is shuffled to TT
- Spoiler updated
- Fix for standard in vanilla ER
- Minor shopsanity discount
This commit is contained in:
aerinon
2021-02-09 16:31:12 -07:00
parent 7a22db5dd4
commit 2765f9bec0
10 changed files with 297 additions and 96 deletions

View File

@@ -1,3 +1,4 @@
import base64
import copy import copy
import json import json
import logging import logging
@@ -134,7 +135,7 @@ class World(object):
set_player_attr('keydropshuffle', False) set_player_attr('keydropshuffle', False)
set_player_attr('mixed_travel', 'prevent') set_player_attr('mixed_travel', 'prevent')
set_player_attr('standardize_palettes', 'standardize') set_player_attr('standardize_palettes', 'standardize')
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False}); set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
def get_name_string_for_object(self, obj): def get_name_string_for_object(self, obj):
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})' return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
@@ -1961,6 +1962,8 @@ class Spoiler(object):
'teams': self.world.teams, 'teams': self.world.teams,
'experimental': self.world.experimental, 'experimental': self.world.experimental,
'keydropshuffle': self.world.keydropshuffle, 'keydropshuffle': self.world.keydropshuffle,
'shopsanity': self.world.shopsanity,
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
} }
def to_json(self): def to_json(self):
@@ -1997,6 +2000,7 @@ class Spoiler(object):
if len(self.hashes) > 0: if len(self.hashes) > 0:
for team in range(self.world.teams): for team in range(self.world.teams):
outfile.write('%s%s\n' % (f"Hash - {self.world.player_names[player][team]} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ', self.hashes[player, team])) outfile.write('%s%s\n' % (f"Hash - {self.world.player_names[player][team]} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ', self.hashes[player, team]))
outfile.write(f'Settings Code: {self.metadata["code"][player]}\n')
outfile.write('Logic: %s\n' % self.metadata['logic'][player]) outfile.write('Logic: %s\n' % self.metadata['logic'][player])
outfile.write('Mode: %s\n' % self.metadata['mode'][player]) outfile.write('Mode: %s\n' % self.metadata['mode'][player])
outfile.write('Retro: %s\n' % ('Yes' if self.metadata['retro'][player] else 'No')) outfile.write('Retro: %s\n' % ('Yes' if self.metadata['retro'][player] else 'No'))
@@ -2024,6 +2028,7 @@ class Spoiler(object):
outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No')) outfile.write('Hints: %s\n' % ('Yes' if self.metadata['hints'][player] else 'No'))
outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No')) outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No')) outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
outfile.write(f"Shopsanity: {'Yes' if self.metadata['shopsanity'][player] else 'No'}\n")
if self.doors: if self.doors:
outfile.write('\n\nDoors:\n\n') outfile.write('\n\nDoors:\n\n')
outfile.write('\n'.join( outfile.write('\n'.join(
@@ -2154,3 +2159,109 @@ class Pot(object):
self.item = item self.item = item
self.room = room self.room = room
self.flags = flags self.flags = flags
# byte 0: DDDE EEEE (DR, ER)
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0}
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, "restricted_legacy": 8,
"full_legacy": 9, "madness_legacy": 10, "insanity_legacy": 11, "dungeonsfull": 7, "dungeonssimple": 6}
# byte 1: LLLW WSSR (logic, mode, sword, retro)
logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owg": 3, "majorglitches": 4}
world_mode = {"open": 0, "standard": 1, "inverted": 2}
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4}
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
func_mode = {"normal": 0, "hard": 1, "expert": 2}
# byte 3: SKMM PIII (shop, keydrop, mixed, palettes, intensity)
mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
# intensity is 3 bits (reserves 4-7 levels)
# byte 4: CCCC CTTX (crystals gt, ctr2, experimental)
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
# byte 5: CCCC CPAA (crystals ganon, pyramid, access
access_mode = {"items": 0, "locations": 1, "none": 2}
# byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies)
boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3}
enemy_mode = {"none": 0, "shuffled": 1, "random": 2}
# byte 7: HHHD DP?? (enemy_health, enemy_dmg, potshuffle, ?)
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
class Settings(object):
@staticmethod
def make_code(w, p):
code = bytes([
(dr_mode[w.doorShuffle[p]] << 5) | er_mode[w.shuffle[p]],
(logic_mode[w.logic[p]] << 5) | (world_mode[w.mode[p]] << 3)
| (sword_mode[w.swords[p]] << 1) | (1 if w.retro[p] else 0),
(goal_mode[w.goal[p]] << 5) | (diff_mode[w.difficulty[p]] << 3)
| (func_mode[w.difficulty_adjustments[p]] << 1) | (1 if w.hints[p] else 0),
(0x80 if w.shopsanity[p] else 0) | (0x40 if w.keydropshuffle[p] else 0)
| (mixed_travel_mode[w.mixed_travel[p]] << 4) | (0x8 if w.standardize_palettes[p] == "original" else 0)
| (0 if w.intensity[p] == "random" else w.intensity[p]),
((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3)
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
| (0x4 if w.open_pyramid[p] else 0) | access_mode[w.accessibility[p]],
(0x80 if w.bigkeyshuffle[p] else 0) | (0x40 if w.keyshuffle[p] else 0)
| (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0)
| (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)])
return base64.b64encode(code, "+-".encode()).decode()
@staticmethod
def adjust_args_from_code(code, player, args):
settings, p = base64.b64decode(code.encode(), "+-".encode()), player
def r(d):
return {y: x for x, y in d.items()}
args.shuffle[p] = r(er_mode)[settings[0] & 0x1F]
args.door_shuffle[p] = r(dr_mode)[(settings[0] & 0xE0) >> 5]
args.logic[p] = r(logic_mode)[(settings[1] & 0xE0) >> 5]
args.mode[p] = r(world_mode)[(settings[1] & 0x18) >> 3]
args.swords[p] = r(sword_mode)[(settings[1] & 0x6) >> 1]
args.difficulty[p] = r(diff_mode)[(settings[2] & 0x18) >> 3]
args.item_functionality[p] = r(func_mode)[(settings[2] & 0x6) >> 1]
args.goal[p] = r(goal_mode)[(settings[2] & 0xE0) >> 5]
args.accessibility[p] = r(access_mode)[settings[5] & 0x3]
args.retro[p] = True if settings[1] & 0x01 else False
args.hints[p] = True if settings[2] & 0x01 else False
args.retro[p] = True if settings[1] & 0x01 else False
args.shopsanity[p] = True if settings[3] & 0x80 else False
args.keydropshuffle[p] = True if settings[3] & 0x40 else False
args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4]
args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
intensity = settings[3] & 0x7
args.intensity[p] = "random" if intensity == 0 else intensity
args.dungeon_counters[p] = r(counter_mode)[(settings[4] & 0x6) >> 1]
cgt = (settings[4] & 0xf8) >> 3
args.crystals_gt[p] = "random" if cgt == 8 else cgt
args.experimental[p] = True if settings[4] & 0x1 else False
cgan = (settings[5] & 0xf8) >> 3
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
args.openpyramid[p] = True if settings[5] & 0x4 else False
args.bigkeyshuffle[p] = True if settings[6] & 0x80 else False
args.keyshuffle[p] = True if settings[6] & 0x40 else False
args.mapshuffle[p] = True if settings[6] & 0x20 else False
args.compassshuffle[p] = True if settings[6] & 0x10 else False
args.shufflebosses[p] = r(boss_mode)[(settings[6] & 0xc) >> 2]
args.shuffleenemies[p] = r(enemy_mode)[settings[6] & 0x3]
args.enemy_health[p] = r(e_health)[(settings[7] & 0xE0) >> 5]
args.enemy_damage[p] = r(e_dmg)[(settings[7] & 0x18) >> 3]
args.shufflepots[p] = True if settings[7] & 0x4 else False

3
CLI.py
View File

@@ -93,7 +93,7 @@ def parse_cli(argv, no_defaults=False):
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters', 'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots', 'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep', 'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes']: 'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name) value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1: if player == 1:
setattr(ret, name, {1: value}) setattr(ret, name, {1: value})
@@ -147,6 +147,7 @@ def parse_settings():
"mixed_travel": "prevent", "mixed_travel": "prevent",
"standardize_palettes": "standardize", "standardize_palettes": "standardize",
"code": "",
"multi": 1, "multi": 1,
"names": "", "names": "",

View File

@@ -89,7 +89,7 @@ def link_doors_main(world, player):
world.get_portal('Desert East', player).destination = True world.get_portal('Desert East', player).destination = True
if world.mode[player] == 'inverted': if world.mode[player] == 'inverted':
world.get_portal('Desert West', player).destination = True world.get_portal('Desert West', player).destination = True
if world.mode[player] == 'open': else:
world.get_portal('Skull 2 West', player).destination = True world.get_portal('Skull 2 West', player).destination = True
world.get_portal('Turtle Rock Lazy Eyes', player).destination = True world.get_portal('Turtle Rock Lazy Eyes', player).destination = True
world.get_portal('Turtle Rock Eye Bridge', player).destination = True world.get_portal('Turtle Rock Eye Bridge', player).destination = True

View File

@@ -619,7 +619,7 @@ def todays_discounts(world, player):
for location, shop_name, slot in chosen_locations: for location, shop_name, slot in chosen_locations:
shop = world.get_region(shop_name, player).shop shop = world.get_region(shop_name, player).shop
orig = location.item.price orig = location.item.price
shop.inventory[slot]['price'] = randomize_price(orig // 10) shop.inventory[slot]['price'] = randomize_price(orig // 5)
repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart', repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart',

58
Main.py
View File

@@ -8,7 +8,7 @@ import random
import time import time
import zlib import zlib
from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance, Settings
from Items import ItemFactory from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement from KeyDoorShuffle import validate_key_placement
from PotShuffle import shuffle_pots from PotShuffle import shuffle_pots
@@ -28,6 +28,7 @@ from Utils import output_path, parse_player_names
__version__ = '0.3.1.1-u' __version__ = '0.3.1.1-u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass
@@ -40,6 +41,10 @@ def main(args, seed=None, fish=None):
start = time.perf_counter() start = time.perf_counter()
# initialize the world # initialize the world
if args.code:
for player, code in args.code.items():
if code:
Settings.adjust_args_from_code(code, player, args)
world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords, world = World(args.multi, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, args.difficulty, args.item_functionality, args.timer, args.progressive, 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)
@@ -218,7 +223,7 @@ def main(args, seed=None, fish=None):
customize_shops(world, player) customize_shops(world, player)
balance_money_progression(world) balance_money_progression(world)
outfilebase = 'DR_%s' % (args.outputname if args.outputname else world.seed) outfilebase = f'DR_{args.outputname if args.outputname else world.seed}'
rom_names = [] rom_names = []
jsonout = {} jsonout = {}
@@ -264,59 +269,12 @@ def main(args, seed=None, fish=None):
if args.jsonout: if args.jsonout:
jsonout[f'patch_t{team}_p{player}'] = rom.patches jsonout[f'patch_t{team}_p{player}'] = rom.patches
else: else:
mcsb_name = ''
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]):
mcsb_name = '-keysanity'
elif [world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]].count(True) == 1:
mcsb_name = '-mapshuffle' if world.mapshuffle[player] else '-compassshuffle' if world.compassshuffle[player] else '-keyshuffle' if world.keyshuffle[player] else '-bigkeyshuffle'
elif any([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player], world.bigkeyshuffle[player]]):
mcsb_name = '-%s%s%s%sshuffle' % (
'M' if world.mapshuffle[player] else '', 'C' if world.compassshuffle[player] else '',
'S' if world.keyshuffle[player] else '', 'B' if world.bigkeyshuffle[player] else '')
outfilepname = f'_T{team+1}' if world.teams > 1 else '' outfilepname = f'_T{team+1}' if world.teams > 1 else ''
if world.players > 1: if world.players > 1:
outfilepname += f'_P{player}' outfilepname += f'_P{player}'
if world.players > 1 or world.teams > 1: if world.players > 1 or world.teams > 1:
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else '' outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
outfilestuffs = { outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else ''
"logic": world.logic[player], # 0
"difficulty": world.difficulty[player], # 1
"difficulty_adjustments": world.difficulty_adjustments[player], # 2
"mode": world.mode[player], # 3
"goal": world.goal[player], # 4
"timer": str(world.timer), # 5
"shuffle": world.shuffle[player], # 6
"doorShuffle": world.doorShuffle[player], # 7
"algorithm": world.algorithm, # 8
"mscb": mcsb_name, # 9
"retro": world.retro[player], # A
"progressive": world.progressive, # B
"hints": 'True' if world.hints[player] else 'False' # C
}
# 0 1 2 3 4 5 6 7 8 9 A B C
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s_%s-%s%s%s%s%s' % (
# 0 1 2 3 4 5 6 7 8 9 A B C
# _noglitches_normal-normal-open-ganon-ohko_simple_basic-balanced-keysanity-retro-prog_swords-nohints
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity-retro
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity -prog_swords
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity -nohints
outfilestuffs["logic"], # 0
outfilestuffs["difficulty"], # 1
outfilestuffs["difficulty_adjustments"], # 2
outfilestuffs["mode"], # 3
outfilestuffs["goal"], # 4
"" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5
outfilestuffs["shuffle"], # 6
outfilestuffs["doorShuffle"], # 7
outfilestuffs["algorithm"], # 8
outfilestuffs["mscb"], # 9
"-retro" if outfilestuffs["retro"] == "True" else "", # A
"-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # B
"-nohints" if not outfilestuffs["hints"] == "True" else "")) if not args.outputname else '' # C
rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')) rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc'))
if world.players > 1: if world.players > 1:

View File

@@ -156,6 +156,11 @@ def roll_settings(weights):
if ret.dungeon_counters == 'default': if ret.dungeon_counters == 'default':
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off' ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
ret.shopsanity = get_choice('shopsanity') == 'on'
ret.keydropshuffle = get_choice('keydropshuffle') == 'on'
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize'
goal = get_choice('goals') goal = get_choice('goals')
ret.goal = {'ganon': 'ganon', ret.goal = {'ganon': 'ganon',
'fast_ganon': 'crystals', 'fast_ganon': 'crystals',
@@ -173,6 +178,7 @@ def roll_settings(weights):
if ret.mode == 'retro': if ret.mode == 'retro':
ret.mode = 'open' ret.mode = 'open'
ret.retro = True ret.retro = True
ret.retro = get_choice('retro') == 'on' # this overrides world_state if used
ret.hints = get_choice('hints') == 'on' ret.hints = get_choice('hints') == 'on'

View File

@@ -3,6 +3,7 @@
## Shopsanity ## Shopsanity
--shopsanity added. This adds 32 shop locations (9 more in retro) to the general and location pool. --shopsanity added. This adds 32 shop locations (9 more in retro) to the general and location pool.
Multi-world supported. Thanks go to Pepper and CaitSith2 for figuring out several items related to this major feature. Multi-world supported. Thanks go to Pepper and CaitSith2 for figuring out several items related to this major feature.
Shop locations: Shop locations:
@@ -32,35 +33,24 @@ Item Pool changes: To accommodate the new locations, new items are added to the
* 1 - +5 Bomb Capacity * 1 - +5 Bomb Capacity
* 1 - +5 Arrow Capacity * 1 - +5 Arrow Capacity
1. Initially, 1 of each type of potion refill is shuffled to the shops. (the Capacity Fairy is excluded from this). 1. Initially, 1 of each type of potion refill is shuffled to the shops. (the Capacity Fairy is excluded from this, see step 4). This ensures that potions can be bought somewhere.
This ensures that potions can be bought somewhere.
2. The rest of the shop pool is shuffled with the rest of the item pool. 2. The rest of the shop pool is shuffled with the rest of the item pool.
3. At this time, only Ten Bombs, Ten Arrows, Capacity upgrades, and Small Hearts can appear outside of shops. Any other 3. At this time, only Ten Bombs, Ten Arrows, Capacity upgrades, and Small Hearts can appear outside of shops. Any other shop items are replaced with rupees of various amounts. This is because of two reasons: First, potion refills and the Bee are indistinguishable from Bottles with that item in them. Receiving those items without a bottle or empty bottle is essentially a nothing item but looks like a bottle. Second, the non-progressive Shields interact fine with Progressive Shields but are usually also a nothing item most of the time.
shop items are replaced with rupees of various amounts. This is because of two reasons: First, potion refills and the 4. The Capacity Fairy cannot sell Potion Refills because the graphics are incompatible. 300 Rupees will replace any potion refill that ends up there.
Bee are indistinguishable from Bottles with that item in them. Receiving those items without a bottle or empty bottle is 5. For capacity upgrades, if any shop sells capacity upgrades, then it will sell all seven of that type. Otherwise, if plain bombs or arrows are sold somewhere, then the other six capacity upgrades will be purchasable first at those locations and then replaced by the underlying ammo. If no suitable spot is found, then no more capacity upgrades will not be available for that seed. (There is always one somewhere in the pool.)
essentially a nothing item but looks like a bottle. Second, the non-progressive Shields interact fine with Progressive 6. Any shop item that is originally sold by shops can be bought indefinitely but only the first purchase counts toward total checks on the credits screen & item counter. All other items can be bought only once.
Shields but are usually also a nothing item most of the time.
4. The Capacity Fairy cannot sell Potion Refills because the graphics are incompatible. 300 Rupees will replace any
potion refill that ends up there.
5. For capacity upgrades, if any shop sells capacity upgrades, then it will sell all seven. Otherwise, if plain bombs or
arrows are sold somewhere, then the other six capacity upgrades will be purchasable first at those locations and then
replaced by the underlying ammo. If no suitable spot is found, then no more capacity upgrades will not be available for
that seed. (There is always one somewhere in the pool.)
6. Any shop item that is originally sold by shops can be bought indefinitely but only the first purchase counts toward
total checks on the credits screen & item counter. All other items can be bought only once.
All items in the item pool may appear in shops. All items in the item pool may appear in shops.
#### Pricing Guide #### Pricing Guide
All prices range approx. from half the base price to the base price in increments of 5, the exact price is chosen All prices range approx. from half the base price to the base price in increments of 5, the exact price is chosen randomly within the range.
randomly within the range.
| Category | Items | Base Price | Typical Range | | Category | Items | Base Price | Typical Range |
| ----------------- | ------- |:----------:|:-------------:| | ----------------- | ------- |:----------:|:-------------:|
| Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-250 | Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-250
| | Moon Pearl | 200 | 100-200 | | Moon Pearl | 200 | 100-200
| | Lamp, Progressive Bow, Glove, Sword | 150 | 75-150 | | Lamp, Progressive Bows, Gloves, & Swords | 150 | 75-150
| Medallions | Bombos, Ether, Quake | 100 | 50-100 | Medallions | Bombos, Ether, Quake | 100 | 50-100
| Safety/Fetch | Cape, Mushroom, Shovel, Powder, Bug Net, Byrna, Progressive Armor & Shields, Half Magic | 50 | 25-50 | Safety/Fetch | Cape, Mushroom, Shovel, Powder, Bug Net, Byrna, Progressive Armor & Shields, Half Magic | 50 | 25-50
| Bottles | Empty Bottle or Bee Bottle | 50 | 25-50 | Bottles | Empty Bottle or Bee Bottle | 50 | 25-50
@@ -88,40 +78,27 @@ In addition, 4-7 items are steeply discounted at random.
#### Rupee Balancing Algorithm #### Rupee Balancing Algorithm
To prevent needed to grind for rupees to buy things in Sphere 1 and later, a money balancing algorithm has been To prevent needed to grind for rupees to buy things in Sphere 1 and later, a money balancing algorithm has been developed to counteract the need for rupees. Basic logic: it assumes you buy nothing until you are blocked by a shop, a check that requires money, or blocked by Kiki. Then you must have enough to make all purchases. If not, any free rupees encountered may be swapped with higher denominations that have not been encountered. Ammo may also be swapped, if necessary.
developed to counteract the need for rupees. Basic logic: it assumes you buy nothing until you are blocked by a shop,
a check that requires money or blocked by Kiki. Then you must have enough to make all purchases. If not, any free rupees
encountered may be swapped with higher denominations that have not been encountered. Ammo may also be swapped,
if necessary.
(Checks that require money: Bottle Merchant, King Zora, Digging Game, Chest Game, Blacksmith, anything blocked by Kiki (Checks that require money: Bottle Merchant, King Zora, Digging Game, Chest Game, Blacksmith, anything blocked by Kiki e.g. all of Palace of Darkness when ER is vanilla)
e.g. all of Palace of Darkness when ER is vanilla)
The Houlihan room is not in logic but the five dungeon rooms that provide rupees are. Pots with rupees, the arrow game, The Houlihan room is not in logic but the five dungeon rooms that provide rupees are. Pots with rupees, the arrow game, and all other gambling games are not counted for determining income.
and all other gambling games are not counted for determining income.
Currently this is applied to seeds without shopsanity on so early money is slightly more likely if progression is on Currently this is applied to seeds without shopsanity on so early money is slightly more likely if progression is on a check that requires money even if Shopsanity is not turned on.
a check that requires money.
#### Retro and Shopsanity #### Retro and Shopsanity
9 new locations are added. 9 new locations are added.
The four "Take Any" caves are converted into "Take Both" caves. Those and the old man cave are included in the shuffle. The four "Take Any" caves are converted into "Take Both" caves. Those and the old man cave are included in the shuffle. The sword is returned to the pool, and the 4 heart containers and 4 blue potion refills are also added to the general item pool. All items found in the retro caves are free to take once. Potion refills will disappear after use.
The sword is returned to the pool, and the 4 heart containers and 4 blue potion refills are also added to the general
item pool. All items found in the retro caves are free to take once. Potion refills will disappear after use.
Arrow Capacity upgrades are now replaced by Rupees wherever it might end up. Arrow Capacity upgrades are now replaced by Rupees wherever it might end up.
The Ten Arrows and 5 randomly selected Small Hearts or Blue Shields are replaced by the quiver item The Ten Arrows and 5 randomly selected Small Hearts or Blue Shields are replaced by the quiver item (represented by the Single Arrow in game.) 5 Red Potion refills are replaced by the Universal small key. It is assured that at least one shop sells Universal Small Keys. The quiver may thus not be found in shops. The quiver and small keys retain their original base price, but may be discounted.
(represented by the Single Arrow in game.) 5 Red Potion refills are replaced by the Universal small key. It is assured
that at least one shop sells Universal Small Keys. The quiver may thus not be found in shops. The quiver and small keys
retain their original base price, but may be discounted.
##### Misc Notes ##### Misc Notes
The location counter now The location counter both experimental and the credits now reflects the total and current checks made. Original retro for example is 221 while shopsanity by itself is 248. Keydropshuffle+sanity+retro can reach up to 290.
## In-Room Staircases/Ladders ## In-Room Staircases/Ladders
@@ -130,6 +107,21 @@ any N/S connections. (those that appear to go up one floor are North connection
Big thanks to Catobat for doing all the hard work. Big thanks to Catobat for doing all the hard work.
## Enemizer change
The attic/maiden sequence is now active and required when Blind is the boss of Theives' Town even when bosses are shuffled.
## Settings code
File names have changed with a settings code instead of listing major settings chosen. Mystery games omit this for obvious reasons. Also found in the spoiler.
Added to CLI only now.
## Mystery fixes
The Mystery.py file has been updated for those who like to use that for generating games. Supports keydropshuffle,
shopsanity, and other settings that have been included.
# Bug Fixes # Bug Fixes
* 0.3.1.1-u * 0.3.1.1-u
@@ -138,6 +130,7 @@ Big thanks to Catobat for doing all the hard work.
* Fix for Ice Jelly room when going backward and enemizer is on * Fix for Ice Jelly room when going backward and enemizer is on
* Fix for inverted - don't start as a bunny in Dark Sanctuary * Fix for inverted - don't start as a bunny in Dark Sanctuary
* Fix for non-ER Inverted with Lobby shuffle. Aga Tower's exit works properly now. * Fix for non-ER Inverted with Lobby shuffle. Aga Tower's exit works properly now.
* Minor fix to Standard generation
* 0.3.0.1-u * 0.3.0.1-u
* Problem with lobbies on re-rolls corrected * Problem with lobbies on re-rolls corrected
* Potential playthrough problem addressed * Potential playthrough problem addressed

10
Rom.py
View File

@@ -310,6 +310,13 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_
for patch in json.load(f): for patch in json.load(f):
rom.write_bytes(patch["address"], patch["patchData"]) rom.write_bytes(patch["address"], patch["patchData"])
if world.get_dungeon("Thieves Town", player).boss.enemizer_name == "Blind":
rom.write_byte(0x04DE81, 0x6) # maiden spawn
# restore blind spawn code
rom.write_bytes(0xEA081, [0xaf, 0xcc, 0xf3, 0x7e, 0xc9, 0x6, 0xf0, 0x24,
0xad, 0x3, 0x4, 0x29, 0x20, 0xf0, 0x1d])
rom.write_byte(0x200101, 0) # Do not close boss room door on entry.
if random_sprite_on_hit: if random_sprite_on_hit:
_populate_sprite_table() _populate_sprite_table()
sprites = list(_sprite_table.values()) sprites = list(_sprite_table.values())
@@ -1537,8 +1544,7 @@ def write_custom_shops(rom, world, player):
if item is None: if item is None:
break break
if world.shopsanity[player] or shop.type == ShopType.TakeAny: if world.shopsanity[player] or shop.type == ShopType.TakeAny:
slot = 0 if shop.type == ShopType.TakeAny else index rom.write_byte(0x186560 + shop.sram_address + index, 1)
rom.write_byte(0x186560 + shop.sram_address + slot, 1)
item_id = ItemFactory(item['item'], player).code item_id = ItemFactory(item['item'], player).code
price = int16_as_bytes(item['price']) price = int16_as_bytes(item['price'])
replace = ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF replace = ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF

125
mystery_example.yml Normal file
View File

@@ -0,0 +1,125 @@
description: Example door rando weights
door_shuffle:
vanilla: 2
basic: 1
crossed: 1
intensity:
1: 1
2: 1
3: 4
keydropshuffle:
on: 1
off: 1
shopsanity:
on: 1
off: 1
pot_shuffle:
on: 1
off: 3
entrance_shuffle:
none: 15
dungeonssimple: 3
dungeonsfull: 2
simple: 2
restricted: 2
full: 2
crossed: 3
insanity: 1
world_state:
standard: 1
open: 1
inverted: 1
retro: 0
retro:
on: 1
off: 4
goals:
ganon: 2
fast_ganon: 2
dungeons: 1
pedestal: 2
triforce-hunt: 0
dungeon_items:
standard: 10
mc: 3
mcs: 2
full: 5
experimental:
on: 1
off: 0
glitches_required:
none: 1
no_logic: 0
accessibility:
items: 1
locations: 0
none: 0
tower_open:
"0": 1
"1": 2
"2": 3
"3": 4
"4": 4
"5": 3
"6": 2
"7": 1
random: 1
ganon_open:
"0": 1
"1": 2
"2": 3
"3": 4
"4": 4
"5": 3
"6": 2
"7": 1
random: 1
boss_shuffle:
none: 3
simple: 1
full: 1
random: 1
enemy_shuffle:
none: 3
shuffled: 1
random: 0
hints:
on: 1
off: 0
weapons:
randomized: 2
assured: 1
vanilla: 1
swordless: 0
item_pool:
normal: 1
hard: 0
expert: 0
item_functionality:
normal: 1
hard: 0
expert: 0
enemy_damage:
default: 3
shuffled: 1
random: 0
enemy_health:
default: 6
easy: 1
hard: 1
expert: 0
rom:
quickswap:
on: 1
off: 0
heartcolor:
red: 1
blue: 1
green: 1
yellow: 1
heartbeep:
double: 0
normal: 0
half: 0
quarter: 1
off: 0

View File

@@ -359,5 +359,6 @@
"never" "never"
] ]
}, },
"outputname": {} "outputname": {},
"code": {}
} }