Merge branch 'DoorDevVolatile' into Customizer

# Conflicts:
#	BaseClasses.py
#	ItemList.py
#	Main.py
This commit is contained in:
aerinon
2022-04-21 11:14:40 -06:00
23 changed files with 1087 additions and 546 deletions

View File

@@ -16,6 +16,7 @@ from EntranceShuffle import door_addresses, indirect_connections
from Utils import int16_as_bytes
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
from RoomData import Room
from source.dungeon.RoomObject import RoomObject
class World(object):
@@ -139,6 +140,8 @@ class World(object):
set_player_attr('pot_contents', None)
set_player_attr('pseudoboots', False)
set_player_attr('collection_rate', False)
set_player_attr('colorizepots', False)
set_player_attr('pot_pool', {})
set_player_attr('shopsanity', False)
set_player_attr('mixed_travel', 'prevent')
@@ -2710,7 +2713,7 @@ class PotFlags(FastEnum):
class Pot(object):
def __init__(self, x, y, item, room, flags = PotFlags.Normal):
def __init__(self, x, y, item, room, flags=PotFlags.Normal, obj=None):
self.x = x
self.y = y
self.item = item
@@ -2718,9 +2721,12 @@ class Pot(object):
self.flags = flags
self.indicator = None # 0x80 for standing item, 0xC0 multiworld item
self.standing_item_code = None # standing item code if nay
self.obj_ref = obj
self.location = None # location back ref
def copy(self):
return Pot(self.x, self.y, self.item, self.room, self.flags)
obj_ref = RoomObject(self.obj_ref.address, self.obj_ref.data) if self.obj_ref else None
return Pot(self.x, self.y, self.item, self.room, self.flags, obj_ref)
def pot_data(self):
high_byte = self.y
@@ -2731,6 +2737,12 @@ class Pot(object):
item = self.item if not self.indicator else self.standing_item_code
return [self.x, high_byte, item]
def __eq__(self, other):
return self.x == other.x and self.y == other.y and self.room == other.room
def __hash__(self):
return hash((self.x, self.y, self.room))
# byte 0: DDDE EEEE (DR, ER)
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0}
@@ -2754,7 +2766,8 @@ mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
# new byte 4: ?DDD PPPP (unused, drop, pottery)
# dropshuffle reserves 2 bits, pottery needs 2 but reserves 2 for future modes)
pottery_mode = {"none": 0, "shuffle": 1, "keys": 2, 'lottery': 3, 'dungeon': 4, 'cave': 5}
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
'clustered': 8, 'nonempty': 9}
# byte 5: CCCC CTTX (crystals gt, ctr2, experimental)
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}

4
CLI.py
View File

@@ -125,7 +125,7 @@ def parse_cli(argv, no_defaults=False):
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
'msu_resume', 'collection_rate']:
'msu_resume', 'collection_rate', 'colorizepots']:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})
@@ -162,6 +162,7 @@ def parse_settings():
"progressive": "on",
"accessibility": "items",
"algorithm": "balanced",
'mystery': False,
"restrict_boss_items": "none",
# Shuffle Ganon defaults to TRUE
@@ -182,6 +183,7 @@ def parse_settings():
'keydropshuffle': False,
'dropshuffle': False,
'pottery': 'none',
'colorizepots': False,
'shufflepots': False,
"mapshuffle": False,
"compassshuffle": False,

View File

@@ -491,6 +491,9 @@ def ensure_good_pots(world, write_skips=False):
loc.item.player = loc.player
else:
loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player)
# do the arrow retro check
if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}:
loc.item = ItemFactory('Rupees (5)', loc.item.player)
# don't write out all pots to spoiler
if write_skips:
if loc.type == LocationType.Pot and loc.item.name in valid_pot_items:

View File

@@ -574,7 +574,7 @@ def set_up_shops(world, player):
removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player]
for remove in removals:
world.itempool.remove(remove)
world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade
world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade
else:
cap_shop = world.get_region('Capacity Upgrade', player).shop
cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bombbag
@@ -781,8 +781,10 @@ def add_pot_contents(world, player):
for super_tile, pot_list in vanilla_pots.items():
for pot in pot_list:
if pot.item not in [PotItem.Hole, PotItem.Key, PotItem.Switch]:
if valid_pot_location(pot, world, player):
world.itempool.append(ItemFactory(pot_items[pot.item], player))
if valid_pot_location(pot, world.pot_pool[player], world, player):
item = ('Rupees (5)' if world.retro[player] and pot_items[pot.item] == 'Arrows (5)'
else pot_items[pot.item])
world.itempool.append(ItemFactory(item, player))
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic):

View File

@@ -33,7 +33,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
from source.tools.BPS import create_bps_from_data
from source.classes.CustomSettings import CustomSettings
__version__ = '1.0.1.12w'
__version__ = '1.0.1.13w'
from source.classes.BabelFish import BabelFish
@@ -122,6 +122,7 @@ def main(args, seed=None, fish=None):
world.overworld_map = args.overworld_map.copy()
world.restrict_boss_items = args.restrict_boss_items.copy()
world.collection_rate = args.collection_rate.copy()
world.colorizepots = args.colorizepots.copy()
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
@@ -183,7 +184,7 @@ def main(args, seed=None, fish=None):
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
for player in range(1, world.players + 1):
if world.potshuffle[player]:
if world.pottery[player] not in ['lottery', 'dungeon']:
if world.pottery[player] in ['none', 'cave', 'keys', 'cavekeys']:
shuffle_pots(world, player)
else:
shuffle_pot_switches(world, player)
@@ -321,7 +322,7 @@ def main(args, seed=None, fish=None):
logging.warning(enemizerMsg)
raise EnemizerError(enemizerMsg)
patch_rom(world, rom, player, team, enemized, bool(args.outputname))
patch_rom(world, rom, player, team, enemized, bool(args.mystery))
if args.race:
patch_race_rom(rom)

View File

@@ -1,9 +1,6 @@
import argparse
import logging
import RaceRandom as random
import urllib.request
import urllib.parse
import yaml
from DungeonRandomizer import parse_cli
from Main import main as DRMain
@@ -18,6 +15,7 @@ def add_bool(self, node):
SafeConstructor.add_constructor(u'tag:yaml.org,2002:bool', add_bool)
def main():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
@@ -72,6 +70,7 @@ def main():
erargs.outputname = seedname
erargs.outputpath = args.outputpath
erargs.loglevel = args.loglevel
erargs.mystery = True
if args.rom:
erargs.rom = args.rom
@@ -103,182 +102,6 @@ def main():
DRMain(erargs, seed, BabelFish())
def get_weights(path):
try:
if urllib.parse.urlparse(path).scheme:
return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader)
with open(path, 'r', encoding='utf-8') as f:
return yaml.load(f, Loader=yaml.SafeLoader)
except Exception as e:
raise Exception(f'Failed to read weights file: {e}')
def roll_settings(weights):
def get_choice(option, root=None):
root = weights if root is None else root
if option not in root:
return None
if type(root[option]) is not dict:
return root[option]
if not root[option]:
return None
return random.choices(list(root[option].keys()), weights=list(map(int,root[option].values())))[0]
def get_choice_default(option, root=weights, default=None):
choice = get_choice(option, root)
if choice is None and default is not None:
return default
return choice
while True:
subweights = weights.get('subweights', {})
if len(subweights) == 0:
break
chances = ({k: int(v['chance']) for (k, v) in subweights.items()})
subweight_name = random.choices(list(chances.keys()), weights=list(chances.values()))[0]
subweights = weights.get('subweights', {}).get(subweight_name, {}).get('weights', {})
subweights['subweights'] = subweights.get('subweights', {})
weights = {**weights, **subweights}
ret = argparse.Namespace()
ret.algorithm = get_choice('algorithm')
glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches',
'owg': 'owglitches', 'minorglitches': 'minorglitches'}
glitches_required = get_choice('glitches_required')
if glitches_required is not None:
if glitches_required not in glitch_map.keys():
print(f'Logic did not match one of: {", ".join(glitch_map.keys())}')
glitches_required = 'none'
ret.logic = glitch_map[glitches_required]
item_placement = get_choice('item_placement')
# not supported in ER
dungeon_items = get_choice('dungeon_items')
ret.mapshuffle = get_choice('map_shuffle') == 'on' if 'map_shuffle' in weights else dungeon_items in ['mc', 'mcs', 'full']
ret.compassshuffle = get_choice('compass_shuffle') == 'on' if 'compass_shuffle' in weights else dungeon_items in ['mc', 'mcs', 'full']
ret.keyshuffle = get_choice('smallkey_shuffle') == 'on' if 'smallkey_shuffle' in weights else dungeon_items in ['mcs', 'full']
ret.bigkeyshuffle = get_choice('bigkey_shuffle') == 'on' if 'bigkey_shuffle' in weights else dungeon_items in ['full']
ret.accessibility = get_choice('accessibility')
ret.restrict_boss_items = get_choice('restrict_boss_items')
entrance_shuffle = get_choice('entrance_shuffle')
ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
overworld_map = get_choice('overworld_map')
ret.overworld_map = overworld_map if overworld_map != 'default' else 'default'
door_shuffle = get_choice('door_shuffle')
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
ret.intensity = get_choice('intensity')
ret.experimental = get_choice('experimental') == 'on'
ret.collection_rate = get_choice('collection_rate') == 'on'
ret.dungeon_counters = get_choice('dungeon_counters') if 'dungeon_counters' in weights else 'default'
if ret.dungeon_counters == 'default':
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
ret.shufflelinks = get_choice('shufflelinks') == 'on'
ret.pseudoboots = get_choice('pseudoboots') == 'on'
ret.shopsanity = get_choice('shopsanity') == 'on'
ret.dropshuffle = get_choice('dropshuffle') == 'on'
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
ret.shufflepots = get_choice('pot_shuffle') == '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')
if goal is not None:
ret.goal = {'ganon': 'ganon',
'fast_ganon': 'crystals',
'dungeons': 'dungeons',
'pedestal': 'pedestal',
'triforce-hunt': 'triforcehunt'
}[goal]
ret.openpyramid = goal == 'fast_ganon' if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False
ret.crystals_gt = get_choice('tower_open')
ret.crystals_ganon = get_choice('ganon_open')
goal_min = get_choice_default('triforce_goal_min', default=20)
goal_max = get_choice_default('triforce_goal_max', default=20)
pool_min = get_choice_default('triforce_pool_min', default=30)
pool_max = get_choice_default('triforce_pool_max', default=30)
ret.triforce_goal = random.randint(int(goal_min), int(goal_max))
min_diff = get_choice_default('triforce_min_difference', default=10)
ret.triforce_pool = random.randint(max(int(pool_min), ret.triforce_goal + int(min_diff)), int(pool_max))
ret.mode = get_choice('world_state')
if ret.mode == 'retro':
ret.mode = 'open'
ret.retro = True
ret.retro = get_choice('retro') == 'on' # this overrides world_state if used
ret.bombbag = get_choice('bombbag') == 'on'
ret.hints = get_choice('hints') == 'on'
swords = get_choice('weapons')
if swords is not None:
ret.swords = {'randomized': 'random',
'assured': 'assured',
'vanilla': 'vanilla',
'swordless': 'swordless'
}[swords]
ret.difficulty = get_choice('item_pool')
ret.item_functionality = get_choice('item_functionality')
old_style_bosses = {'basic': 'simple',
'normal': 'full',
'chaos': 'random'}
boss_choice = get_choice('boss_shuffle')
if boss_choice in old_style_bosses.keys():
boss_choice = old_style_bosses[boss_choice]
ret.shufflebosses = boss_choice
enemy_choice = get_choice('enemy_shuffle')
if enemy_choice == 'chaos':
enemy_choice = 'random'
ret.shuffleenemies = enemy_choice
old_style_damage = {'none': 'default',
'chaos': 'random'}
damage_choice = get_choice('enemy_damage')
if damage_choice in old_style_damage:
damage_choice = old_style_damage[damage_choice]
ret.enemy_damage = damage_choice
ret.enemy_health = get_choice('enemy_health')
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
inventoryweights = weights.get('startinventory', {})
startitems = []
for item in inventoryweights.keys():
if get_choice(item, inventoryweights) == 'on':
startitems.append(item)
ret.startinventory = ','.join(startitems)
if len(startitems) > 0:
ret.usestartinventory = True
if 'rom' in weights:
romweights = weights['rom']
ret.sprite = get_choice('sprite', romweights)
ret.disablemusic = get_choice('disablemusic', romweights) == 'on'
ret.quickswap = get_choice('quickswap', romweights) == 'on'
ret.reduce_flashing = get_choice('reduce_flashing', romweights) == 'on'
ret.msu_resume = get_choice('msu_resume', romweights) == 'on'
ret.fastmenu = get_choice('menuspeed', romweights)
ret.heartcolor = get_choice('heartcolor', romweights)
ret.heartbeep = get_choice('heartbeep', romweights)
ret.ow_palettes = get_choice('ow_palettes', romweights)
ret.uw_palettes = get_choice('uw_palettes', romweights)
ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on'
return ret
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

View File

@@ -10,15 +10,25 @@ New pottery option that control which pots (and large blocks) are in the locatio
* Key Pots: The pots that have keys are in the pool. This is about half of the old keydropshuffle option
* Cave Pots: The pots that are not found in dungeons are in the pool. (Includes the large block in Spike Cave). Does
not include key pots.
* CaveKeys: Both non-dungeon pots and pots that used to have keys are in the pool.
* Reduced: Same as CaveKeys but also roughly a quarter of dungeon pots are added to the location pool picked at random. This is a dynamic mode so pots in the pool will be colored. Pots out of the pool will have vanilla contents.
* Clustered: LIke reduced but pot are grouped by logical sets and roughly 50% of pots are chosen from those group. This is a dynamic mode like the above.
* Nonempty: All pots that had some sort of objects under them are chosen to be in the location pool. This excludes most large blocks and some pots out of dungeons.
* Dungeon Pots: The pots that are in dungeons are in the pool. (Includes serveral large blocks)
* Lottery: All pots and large blocks are in the pool
By default, switches remain in their vanilla location (unless you turn on the legacy option below)
CLI `--pottery <option>` from `none, keys, lottery`
CLI `--pottery <option>` from `none, keys, cave, cavekeys, reduced, clustered, nonempty, dungeon, lottery`
Note for multiworld: due to the design of the pottery lottery, only 256 items for other players can be under pots in your world.
### Colorize Pots
If the pottery mode is dynamic, this option is forced to be on (clustered and reduced). It is allowed to be on in all other pottery modes. Exception "none" where no pots would be colored, and "lottery" where all pots would be. This option colors the pots differently that have been chosen to be part of the location pool. If not specified, you are expected to remember the pottery setting you chose.
CLI `--colorizepots`
### Shuffle key drops
Enemies that drop keys can have their drop shuffled into the pool. This is the other half of the keydropshuffle option.
@@ -160,6 +170,13 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
#### Volatile
* 1.0.1.13
* New pottery modes
* Trinity goal added
* Potential fix for pottery hera key
* Fix for arrows sneaking into item pool with rupee bow
* Fixed msu resume bug on patcher
* Bonk Recoil OHKO fix (again)
* 1.0.1.12
* Fix for Multiworld forfeits, shops and pot items now included
* Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill.

View File

@@ -1,7 +1,7 @@
import collections
from Items import ItemFactory
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType, LocationType, PotItem, PotFlags
from PotShuffle import key_drop_data, vanilla_pots, PotSecretTable
from PotShuffle import key_drop_data, vanilla_pots, choose_pots, PotSecretTable
def create_regions(world, player):
@@ -1007,6 +1007,7 @@ def adjust_locations(world, player):
pot = pot.copy()
loc.address = pot_address(pot_index, datum[1])
loc.pot = pot
pot.location = loc
if (not world.dropshuffle[player] and drop_location)\
or (not drop_location and world.pottery[player] in ['none', 'cave']):
loc.skip = True
@@ -1023,6 +1024,7 @@ def adjust_locations(world, player):
dungeon.small_keys.append(key_item)
elif key_item.bigkey:
dungeon.big_key = key_item
world.pot_pool[player] = choose_pots(world, player)
for super_tile, pot_list in vanilla_pots.items():
for pot_index, pot_orig in enumerate(pot_list):
if pot_orig.item == PotItem.Key:
@@ -1032,7 +1034,7 @@ def adjust_locations(world, player):
pot = pot_orig.copy()
world.pot_contents[player].room_map[super_tile].append(pot)
if valid_pot_location(pot, world, player):
if valid_pot_location(pot, world.pot_pool[player], world, player):
create_pot_location(pot, pot_index, super_tile, world, player)
if world.shopsanity[player]:
index = 0
@@ -1055,12 +1057,16 @@ def adjust_locations(world, player):
location.skip = True
def valid_pot_location(pot, world, player):
def valid_pot_location(pot, pot_set, world, player):
if world.pottery[player] == 'lottery':
return True
if world.pottery[player] == 'nonempty' and pot.item != PotItem.Nothing:
return True
if world.pottery[player] in ['reduced', 'clustered'] and pot in pot_set:
return True
if world.pottery[player] == 'dungeon' and world.get_region(pot.room, player).type == RegionType.Dungeon:
return True
if world.pottery[player] == 'cave' and world.get_region(pot.room, player).type == RegionType.Cave:
if world.pottery[player] in ['cave', 'cavekeys'] and world.get_region(pot.room, player).type == RegionType.Cave:
return True
return False
@@ -1068,7 +1074,7 @@ def valid_pot_location(pot, world, player):
def create_pot_location(pot, pot_index, super_tile, world, player):
if (pot.item not in [PotItem.Key, PotItem.Hole]
and (pot.item != PotItem.Switch or (world.potshuffle[player]
and world.pottery[player] in ['lottery', 'dungeon']))):
and world.pottery[player] not in ['none', 'cave', 'keys', 'cavekeys']))):
address = pot_address(pot_index, super_tile)
region = pot.room
if world.mode[player] == 'inverted':
@@ -1084,6 +1090,7 @@ def create_pot_location(pot, pot_index, super_tile, world, player):
parent=parent)
world.dynamic_locations.append(pot_location)
pot_location.pot = pot
pot.location = pot_location
pot_location.type = LocationType.Pot
parent.locations.append(pot_location)

22
Rom.py
View File

@@ -32,10 +32,11 @@ from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
from source.classes.SFX import randomize_sfx
from source.item.FillUtil import valid_pot_items
from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '8d196e8024faebbbbe1304032158ccea'
RANDOMIZERBASEHASH = 'd143684aa6a8e4560eb3ac912d600525'
class JsonRom(object):
@@ -650,10 +651,18 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.mapshuffle[player]:
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
if world.pottery[player] not in ['none']:
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
# make hammer pegs use different tiles
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
if world.pot_contents[player]:
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
and (world.colorizepots[player]
or world.pottery[player] in ['reduced', 'clustered']))
if world.pot_contents[player].size() > 0x2800:
raise Exception('Pot table is too big for current area')
world.pot_contents[player].write_pot_data_to_rom(rom)
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
# fix for swamp drains if necessary
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
if not swamp1location.pot.indicator:
@@ -893,14 +902,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
if world.pottery[player] not in ['none', 'keys']:
# Cuccos should not prevent kill rooms from opening
rom.write_byte(snes_to_pc(0x0DB457), 0x40)
if world.pottery[player] in ['none', 'keys']:
rom.write_byte(snes_to_pc(0x28AA56), 0)
elif world.pottery[player] == 'cave':
rom.write_byte(snes_to_pc(0x28AA56), 1)
elif world.pottery[player] == 'dungeon':
rom.write_byte(snes_to_pc(0x28AA56), 2)
elif world.pottery[player] == 'lottery':
rom.write_byte(snes_to_pc(0x28AA56), 3)
rom.write_byte(snes_to_pc(0x28AA56), 0 if world.pottery[player] == 'none' else 1)
write_int16(rom, 0x187010, credits_total) # dynamic credits
if credits_total != 216:

View File

@@ -721,7 +721,7 @@ def bomb_rules(world, player):
def pot_rules(world, player):
if world.pottery[player] in ['lottery', 'cave']:
if world.pottery[player] != 'none':
blocks = [l for l in world.get_locations() if l.type == LocationType.Pot and l.pot.flags & PotFlags.Block]
for block_pot in blocks:
add_rule(block_pot, lambda state: state.can_lift_rocks(player))
@@ -748,8 +748,6 @@ def pot_rules(world, player):
or state.has('Cape', player)
or (state.has('Cane of Byrna', player)
and state.world.difficulty_adjustments[player] == 'normal'))
if world.pottery[player] in ['lottery', 'dungeon']:
for l in world.get_region('Ice Hammer Block', player).locations:
if l.type == LocationType.Pot:
add_rule(l, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player))

View File

@@ -679,9 +679,22 @@ def extract_data_from_jp_rom(rom):
# print()
def check_pots():
from PotShuffle import vanilla_pots
for supertile, pot_list in vanilla_pots.items():
for i,pot in enumerate(pot_list):
if pot.obj_ref:
r = pot.obj_ref
secret_vram = pot.x | (pot.y << 8)
tile_vram = ((r.data[1] & 0xFC) << 5) | ((r.data[0] & 0xFC) >> 1)
if secret_vram != tile_vram:
print(f'{pot.room}#{i+1} secret: {hex(secret_vram)} tile: {hex(tile_vram)}')
if __name__ == '__main__':
# make_new_base2current()
# read_entrance_data(old_rom=sys.argv[1])
# room_palette_data(old_rom=sys.argv[1])
# extract_data_from_us_rom(sys.argv[1])
extract_data_from_jp_rom(sys.argv[1])
# extract_data_from_jp_rom(sys.argv[1])
check_pots()

Binary file not shown.

View File

@@ -13,6 +13,11 @@
"suppress_spoiler": {
"action": "store_true"
},
"mystery": {
"action": "store_true",
"type": "bool",
"help": "suppress"
},
"logic": {
"choices": [
"noglitches",
@@ -79,9 +84,17 @@
"keys",
"dungeon",
"cave",
"cavekeys",
"reduced",
"clustered",
"nonempty",
"lottery"
]
},
"colorizepots" : {
"action": "store_true",
"type": "bool"
},
"shufflepots": {
"action": "store_true",
"type": "bool"

View File

@@ -260,9 +260,15 @@
"None: No pots are changed",
"Keys: Key pots are included in the location pool and other items can take their place",
"Cave: Only pots in houses and caves are included in the location pool",
"CaveKeys: Both pots in houses and caves and keys pots are included in the location pool",
"Reduced: Same as KeyCaves + 25% of Pots in dungeons (dynamic mode)",
"Clustered: Same as KeyCaves + 50% of Pots in dungeons, chosen by logical group (dynamic mode)",
"NonEmpty: All pots that are not originally empty are included in the location pool",
"Dungeon: Only pots in dungeons are included in the location pool",
"Lottery: All pots are part of the location pool"
],
"colorizepots": ["All pots chosen to be in location pool by the pottery setting are different.",
"Forced on in dynamic modes. Forced off in lottery"],
"shufflepots": [ "Pots and switches are shuffled on the supertile (legacy potshuffle) (default: %(default)s)"],
"mixed_travel": [
"How to handle potential traversal between dungeon in Crossed door shuffle",

View File

@@ -61,8 +61,13 @@
"randomizer.dungeon.pottery.none": "None",
"randomizer.dungeon.pottery.keys": "Key Pots",
"randomizer.dungeon.pottery.cave": "Cave Pots",
"randomizer.dungeon.pottery.cavekeys": "Cave+Key Pots",
"randomizer.dungeon.pottery.reduced": "Reduced Dungeon Pots (Dynamic)",
"randomizer.dungeon.pottery.clustered": "Clustered Dungeon Pots (Dynamic)",
"randomizer.dungeon.pottery.nonempty": "Excludes Empty Pots",
"randomizer.dungeon.pottery.dungeon": "Dungeon Pots",
"randomizer.dungeon.pottery.lottery": "Lottery (All Pots and Large Blocks)",
"randomizer.dungeon.colorizepots": "Colorize Randomized Pots",
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",

View File

@@ -30,6 +30,10 @@
"none",
"keys",
"cave",
"cavekeys",
"reduced",
"clustered",
"nonempty",
"dungeon",
"lottery"
],
@@ -37,6 +41,7 @@
"width": 35
}
},
"colorizepots": { "type": "checkbox" },
"dropshuffle": { "type": "checkbox" },
"potshuffle": { "type": "checkbox" },
"experimental": { "type": "checkbox" },

View File

@@ -98,6 +98,7 @@ SETTINGSTOPROCESS = {
"keydropshuffle": "keydropshuffle",
"dropshuffle": "dropshuffle",
"pottery": "pottery",
"colorizepots": "colorizepots",
"potshuffle": "shufflepots",
"experimental": "experimental",
"dungeon_counters": "dungeon_counters",

View File

@@ -0,0 +1,58 @@
from RoomData import DoorKind, Position
from source.dungeon.RoomObject import RoomObject, DoorObject
class Room:
def __init__(self, layout, layer1, layer2, doors):
self.layout = layout
self.layer1 = layer1
self.layer2 = layer2
self.doors = doors
def write_to_rom(self, address, rom):
rom.write_bytes(address, self.layout)
address += 2
for obj in self.layer1:
rom.write_bytes(address, obj.data)
address += 3
rom.write_bytes(address, [0xFF, 0xFF])
address += 2
for obj in self.layer2:
rom.write_bytes(address, obj.data)
address += 3
rom.write_bytes(address, [0xFF, 0xFF, 0xF0, 0xFF])
address += 4
for door in self.doors:
rom.write_bytes(address, door.get_bytes())
address += 2
rom.write_bytes(address, [0xFF, 0xFF])
return address + 2 # where the data ended
Room0127 = Room([0xE1, 0x00],
[RoomObject(0x0AB600, [0xFE, 0x89, 0x00]),
RoomObject(0x0AB603, [0xA2, 0xA1, 0x61]),
RoomObject(0x0AB606, [0xFE, 0x8E, 0x81]),
RoomObject(0x0AB609, [0xFF, 0x49, 0x02]),
RoomObject(0x0AB60C, [0xD2, 0xA1, 0x62]),
RoomObject(0x0AB60F, [0xFF, 0x4E, 0x83]),
RoomObject(0x0AB612, [0x20, 0xB3, 0xDD]),
RoomObject(0x0AB615, [0x50, 0xB3, 0xDD]),
RoomObject(0x0AB618, [0x33, 0xCB, 0xFA]),
RoomObject(0x0AB61B, [0x3B, 0xCB, 0xFA]),
RoomObject(0x0AB61E, [0x43, 0xCB, 0xFA]),
RoomObject(0x0AB621, [0x4B, 0xCB, 0xFA]),
RoomObject(0x0AB624, [0xBF, 0x94, 0xF9]),
RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA]),
RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA]),
RoomObject(0x0AB62D, [0xAD, 0xC8, 0xDF]),
RoomObject(0x0AB630, [0xC4, 0xC8, 0xDF]),
RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA]),
RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]),
RoomObject(0x0AB639, [0x81, 0x93, 0xC0]),
RoomObject(0x0AB63C, [0x81, 0xD2, 0xC0]),
RoomObject(0x0AB63F, [0xE1, 0x93, 0xC0]),
RoomObject(0x0AB642, [0xE1, 0xD2, 0xC0])],
[], [DoorObject(Position.SouthW, DoorKind.CaveEntrance),
DoorObject(Position.SouthE, DoorKind.CaveEntrance)])

View File

@@ -0,0 +1,34 @@
from Utils import snes_to_pc
# Subtype 3 object (0x2xx by jpdasm id - see bank 01)
# B
Normal_Pot = (0xFA, 3, 3)
Shuffled_Pot = (0xFB, 0, 0) # formerly weird pot, or black diagonal thing
class RoomObject:
def __init__(self, address, data):
self.address = address
self.data = data
def change_type(self, new_type):
type_id, datum_a, datum_b = new_type
if 0xF8 <= type_id < 0xFC: # sub type 3
self.data = (self.data[0] & 0xFC) | datum_a, (self.data[1] & 0xFC) | datum_b, type_id
else:
pass # not yet implemented
def write_to_rom(self, rom):
rom.write_bytes(snes_to_pc(self.address), self.data)
class DoorObject:
def __init__(self, pos, kind):
self.pos = pos
self.kind = kind
def get_bytes(self):
return [self.pos.value, self.kind.value]

View File

@@ -157,6 +157,7 @@ def adjust_page(top, parent, settings):
"quickswap": "quickswap",
"nobgm": "disablemusic",
"reduce_flashing": "reduce_flashing",
'msu_resume': 'msu_resume',
"shuffle_sfx": "shuffle_sfx",
"msu_resume": "msu_resume",
}

View File

@@ -70,7 +70,7 @@ def create_item_pool_config(world):
if world.dropshuffle[player]:
for item, locs in keydrop_vanilla_mapping.items():
config.static_placement[player][item].extend(locs)
if world.pottery[player] != 'none':
if world.pottery[player] not in ['none', 'cave']:
for item, locs in potkeys_vanilla_mapping.items():
config.static_placement[player][item].extend(locs)
if world.pottery[player] in ['lottery', 'cave', 'dungeon']:

View File

@@ -92,6 +92,7 @@ def roll_settings(weights):
ret.dropshuffle = get_choice('dropshuffle') == 'on' or keydropshuffle
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
ret.pottery = 'keys' if ret.pottery == 'none' and keydropshuffle else ret.pottery
ret.colorizepots = get_choice('colorizepots') == 'on'
ret.shufflepots = get_choice('pot_shuffle') == '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