- 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 json
import logging
@@ -134,7 +135,7 @@ class World(object):
set_player_attr('keydropshuffle', False)
set_player_attr('mixed_travel', 'prevent')
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):
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,
'experimental': self.world.experimental,
'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):
@@ -1997,6 +2000,7 @@ class Spoiler(object):
if len(self.hashes) > 0:
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(f'Settings Code: {self.metadata["code"][player]}\n')
outfile.write('Logic: %s\n' % self.metadata['logic'][player])
outfile.write('Mode: %s\n' % self.metadata['mode'][player])
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('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(f"Shopsanity: {'Yes' if self.metadata['shopsanity'][player] else 'No'}\n")
if self.doors:
outfile.write('\n\nDoors:\n\n')
outfile.write('\n'.join(
@@ -2154,3 +2159,109 @@ class Pot(object):
self.item = item
self.room = room
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',
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
'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)
if player == 1:
setattr(ret, name, {1: value})
@@ -147,6 +147,7 @@ def parse_settings():
"mixed_travel": "prevent",
"standardize_palettes": "standardize",
"code": "",
"multi": 1,
"names": "",

View File

@@ -89,7 +89,7 @@ def link_doors_main(world, player):
world.get_portal('Desert East', player).destination = True
if world.mode[player] == 'inverted':
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('Turtle Rock Lazy Eyes', 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:
shop = world.get_region(shop_name, player).shop
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',

58
Main.py
View File

@@ -8,7 +8,7 @@ import random
import time
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 KeyDoorShuffle import validate_key_placement
from PotShuffle import shuffle_pots
@@ -28,6 +28,7 @@ from Utils import output_path, parse_player_names
__version__ = '0.3.1.1-u'
class EnemizerError(RuntimeError):
pass
@@ -40,6 +41,10 @@ def main(args, seed=None, fish=None):
start = time.perf_counter()
# 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,
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)
@@ -218,7 +223,7 @@ def main(args, seed=None, fish=None):
customize_shops(world, player)
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 = []
jsonout = {}
@@ -264,59 +269,12 @@ def main(args, seed=None, fish=None):
if args.jsonout:
jsonout[f'patch_t{team}_p{player}'] = rom.patches
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 ''
if world.players > 1:
outfilepname += f'_P{player}'
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 ''
outfilestuffs = {
"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
outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else ''
rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc'))
if world.players > 1:

View File

@@ -156,6 +156,11 @@ def roll_settings(weights):
if ret.dungeon_counters == 'default':
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')
ret.goal = {'ganon': 'ganon',
'fast_ganon': 'crystals',
@@ -173,6 +178,7 @@ def roll_settings(weights):
if ret.mode == 'retro':
ret.mode = 'open'
ret.retro = True
ret.retro = get_choice('retro') == 'on' # this overrides world_state if used
ret.hints = get_choice('hints') == 'on'

View File

@@ -3,6 +3,7 @@
## Shopsanity
--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.
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 Arrow Capacity
1. Initially, 1 of each type of potion refill is shuffled to the shops. (the Capacity Fairy is excluded from this).
This ensures that potions can be bought somewhere.
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.
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
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.
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.
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.
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 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.)
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.
#### Pricing Guide
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.
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.
| Category | Items | Base Price | Typical Range |
| ----------------- | ------- |:----------:|:-------------:|
| Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-250
| | 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
| 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
@@ -88,40 +78,27 @@ In addition, 4-7 items are steeply discounted at random.
#### Rupee Balancing Algorithm
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.
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.
(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)
(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)
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.
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.
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.
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.
#### Retro and Shopsanity
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 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 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.
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
(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.
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.
##### 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
@@ -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.
## 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
* 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 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.
* Minor fix to Standard generation
* 0.3.0.1-u
* Problem with lobbies on re-rolls corrected
* 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):
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:
_populate_sprite_table()
sprites = list(_sprite_table.values())
@@ -1537,8 +1544,7 @@ def write_custom_shops(rom, world, player):
if item is None:
break
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 + slot, 1)
rom.write_byte(0x186560 + shop.sram_address + index, 1)
item_id = ItemFactory(item['item'], player).code
price = int16_as_bytes(item['price'])
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"
]
},
"outputname": {}
"outputname": {},
"code": {}
}