- 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:
113
BaseClasses.py
113
BaseClasses.py
@@ -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
3
CLI.py
@@ -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": "",
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
58
Main.py
@@ -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:
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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
10
Rom.py
@@ -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
125
mystery_example.yml
Normal 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
|
||||||
@@ -359,5 +359,6 @@
|
|||||||
"never"
|
"never"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"outputname": {}
|
"outputname": {},
|
||||||
|
"code": {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user