Merge branch 'DoorDevVolatile' into Customizer
# Conflicts: # BaseClasses.py # ItemList.py # Main.py
This commit is contained in:
@@ -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
4
CLI.py
@@ -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,
|
||||
|
||||
3
Fill.py
3
Fill.py
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
7
Main.py
7
Main.py
@@ -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)
|
||||
|
||||
181
Mystery.py
181
Mystery.py
@@ -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()
|
||||
|
||||
1208
PotShuffle.py
1208
PotShuffle.py
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
17
Regions.py
17
Regions.py
@@ -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
22
Rom.py
@@ -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:
|
||||
|
||||
4
Rules.py
4
Rules.py
@@ -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))
|
||||
|
||||
15
Utils.py
15
Utils.py
@@ -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.
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -98,6 +98,7 @@ SETTINGSTOPROCESS = {
|
||||
"keydropshuffle": "keydropshuffle",
|
||||
"dropshuffle": "dropshuffle",
|
||||
"pottery": "pottery",
|
||||
"colorizepots": "colorizepots",
|
||||
"potshuffle": "shufflepots",
|
||||
"experimental": "experimental",
|
||||
"dungeon_counters": "dungeon_counters",
|
||||
|
||||
58
source/dungeon/RoomList.py
Normal file
58
source/dungeon/RoomList.py
Normal 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)])
|
||||
34
source/dungeon/RoomObject.py
Normal file
34
source/dungeon/RoomObject.py
Normal 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]
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user