Re-worked settings for pottery lottery
Fixed multiworld support for lottery
This commit is contained in:
@@ -86,6 +86,7 @@ class World(object):
|
||||
self._portal_cache = {}
|
||||
self.sanc_portal = {}
|
||||
self.fish = BabelFish()
|
||||
self.pot_contents = {}
|
||||
|
||||
for player in range(1, players + 1):
|
||||
# If World State is Retro, set to Open and set Retro flag
|
||||
@@ -381,6 +382,8 @@ class World(object):
|
||||
location.item = item
|
||||
item.location = location
|
||||
item.world = self
|
||||
if location.player != item.player and location.type == LocationType.Pot:
|
||||
self.pot_contents[location.player].multiworld_count += 1
|
||||
if collect:
|
||||
self.state.collect(item, location.event, location)
|
||||
|
||||
@@ -2123,8 +2126,15 @@ class Location(object):
|
||||
self.pot = None
|
||||
|
||||
def can_fill(self, state, item, check_access=True):
|
||||
if not self.valid_multiworld(state, item):
|
||||
return False
|
||||
return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state)))
|
||||
|
||||
def valid_multiworld(self, state, item):
|
||||
if self.type == LocationType.Pot and self.player != item.player:
|
||||
return state.world.pot_contents[self.player].multiworld_count < 256
|
||||
return True
|
||||
|
||||
def can_reach(self, state):
|
||||
return self.parent_region.can_reach(state) and self.access_rule(state)
|
||||
|
||||
@@ -2465,11 +2475,12 @@ class Spoiler(object):
|
||||
'enemy_shuffle': self.world.enemy_shuffle,
|
||||
'enemy_health': self.world.enemy_health,
|
||||
'enemy_damage': self.world.enemy_damage,
|
||||
'potshuffle': self.world.potshuffle,
|
||||
'players': self.world.players,
|
||||
'teams': self.world.teams,
|
||||
'experimental': self.world.experimental,
|
||||
'keydropshuffle': self.world.keydropshuffle,
|
||||
'dropshuffle': self.world.dropshuffle,
|
||||
'pottery': self.world.pottery,
|
||||
'potshuffle': self.world.potshuffle,
|
||||
'shopsanity': self.world.shopsanity,
|
||||
'pseudoboots': self.world.pseudoboots,
|
||||
'triforcegoal': self.world.treasure_hunt_count,
|
||||
@@ -2530,7 +2541,9 @@ class Spoiler(object):
|
||||
outfile.write(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'])}\n")
|
||||
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player])
|
||||
outfile.write('Intensity: %s\n' % self.metadata['intensity'][player])
|
||||
outfile.write(f"Drop Shuffle Mode: {self.metadata['keydropshuffle'][player]}\n")
|
||||
outfile.write(f"Drop Shuffle: {yn(self.metadata['dropshuffle'][player])}\n")
|
||||
outfile.write(f"Pottery Mode: {self.metadata['pottery'][player]}\n")
|
||||
outfile.write(f"Pot Shuffle (Legacy): {yn(self.metadata['potshuffle'][player])}\n")
|
||||
addition = ' (Random)' if self.world.crystals_gt_orig[player] == 'random' else ''
|
||||
outfile.write('Crystals required for GT: %s\n' % (str(self.metadata['gt_crystals'][player]) + addition))
|
||||
addition = ' (Random)' if self.world.crystals_ganon_orig[player] == 'random' else ''
|
||||
@@ -2546,7 +2559,6 @@ class Spoiler(object):
|
||||
outfile.write('Enemy shuffle: %s\n' % self.metadata['enemy_shuffle'][player])
|
||||
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][player])
|
||||
outfile.write('Enemy damage: %s\n' % self.metadata['enemy_damage'][player])
|
||||
outfile.write(f"Pot shuffle: {yn(self.metadata['potshuffle'][player])}\n")
|
||||
outfile.write(f"Hints: {yn(self.metadata['hints'][player])}\n")
|
||||
outfile.write(f"Experimental: {yn(self.metadata['experimental'][player])}\n")
|
||||
outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][player])}\n")
|
||||
@@ -2737,28 +2749,33 @@ goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "cryst
|
||||
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)
|
||||
# todo keydrop is not longer a switch
|
||||
# byte 3: S?MM PIII (shop, unused, mixed, palettes, intensity)
|
||||
# keydrop now has it's own byte
|
||||
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)
|
||||
# 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}
|
||||
|
||||
# byte 5: CCCC CTTX (crystals gt, ctr2, experimental)
|
||||
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
|
||||
|
||||
# byte 5: CCCC CPAA (crystals ganon, pyramid, access
|
||||
# byte 6: CCCC CPAA (crystals ganon, pyramid, access
|
||||
access_mode = {"items": 0, "locations": 1, "none": 2}
|
||||
|
||||
# byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies)
|
||||
# byte 7: BSMC BBEE (big, small, maps, compass, bosses, enemies)
|
||||
boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3}
|
||||
enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3}
|
||||
|
||||
# byte 7: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
|
||||
# byte 8: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
|
||||
# potshuffle decprecated, now unused
|
||||
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
|
||||
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
|
||||
|
||||
# byte 8: RRAA A??? (restrict boss mode, algorithm, ? = unused)
|
||||
# byte 9: RRAA A??? (restrict boss mode, algorithm, ? = unused)
|
||||
rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
|
||||
# algorithm: todo with "biased shuffles"
|
||||
# algorithm:
|
||||
algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4}
|
||||
|
||||
# additions
|
||||
@@ -2779,10 +2796,12 @@ class Settings(object):
|
||||
(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)
|
||||
(0x80 if w.shopsanity[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]),
|
||||
|
||||
(0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[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),
|
||||
|
||||
@@ -2819,31 +2838,39 @@ class Settings(object):
|
||||
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.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.shuffleswitches[p] = True if settings[4] & 0x80 else False
|
||||
args.dropshuffle[p] = True if settings[4] & 0x10 else False
|
||||
args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F]
|
||||
|
||||
args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1]
|
||||
cgt = (settings[5] & 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.experimental[p] = True if settings[5] & 0x1 else False
|
||||
|
||||
cgan = (settings[6] & 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.openpyramid[p] = True if settings[6] & 0x4 else False
|
||||
|
||||
args.bigkeyshuffle[p] = True if settings[7] & 0x80 else False
|
||||
args.keyshuffle[p] = True if settings[7] & 0x40 else False
|
||||
args.mapshuffle[p] = True if settings[7] & 0x20 else False
|
||||
args.compassshuffle[p] = True if settings[7] & 0x10 else False
|
||||
args.shufflebosses[p] = r(boss_mode)[(settings[7] & 0xc) >> 2]
|
||||
args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3]
|
||||
|
||||
args.enemy_health[p] = r(e_health)[(settings[8] & 0xE0) >> 5]
|
||||
args.enemy_damage[p] = r(e_dmg)[(settings[8] & 0x18) >> 3]
|
||||
args.shufflepots[p] = True if settings[7] & 0x4 else False
|
||||
args.bombbag[p] = True if settings[7] & 0x2 else False
|
||||
args.shufflelinks[p] = True if settings[7] & 0x1 else False
|
||||
if len(settings) > 8:
|
||||
args.restrict_boss_items[p] = True if r(rb_mode)[(settings[8] & 0x80) >> 6] else False
|
||||
args.bombbag[p] = True if settings[8] & 0x2 else False
|
||||
args.shufflelinks[p] = True if settings[8] & 0x1 else False
|
||||
if len(settings) > 9:
|
||||
args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0x80) >> 6]
|
||||
|
||||
|
||||
class KeyRuleType(FastEnum):
|
||||
|
||||
16
CLI.py
16
CLI.py
@@ -88,6 +88,10 @@ def parse_cli(argv, no_defaults=False):
|
||||
if ret.keysanity:
|
||||
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
|
||||
|
||||
if ret.keydropshuffle:
|
||||
ret.dropshuffle = True
|
||||
ret.pottery = 'keys' if ret.pottery == 'none' else ret.pottery
|
||||
|
||||
if multiargs.multi:
|
||||
defaults = copy.deepcopy(ret)
|
||||
for player in range(1, multiargs.multi + 1):
|
||||
@@ -101,9 +105,9 @@ def parse_cli(argv, no_defaults=False):
|
||||
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
|
||||
'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', 'code',
|
||||
'reduce_flashing', 'shuffle_sfx']:
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery',
|
||||
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
@@ -150,7 +154,6 @@ def parse_settings():
|
||||
"overworld_map": "default",
|
||||
"pseudoboots": False,
|
||||
|
||||
"shufflepots": False,
|
||||
"shuffleenemies": "none",
|
||||
"shufflebosses": "none",
|
||||
"enemy_damage": "default",
|
||||
@@ -158,7 +161,10 @@ def parse_settings():
|
||||
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
|
||||
|
||||
"shopsanity": False,
|
||||
"keydropshuffle": 'none',
|
||||
'keydropshuffle': False,
|
||||
'dropshuffle': False,
|
||||
'pottery': 'none',
|
||||
'shufflepots': False,
|
||||
"mapshuffle": False,
|
||||
"compassshuffle": False,
|
||||
"keyshuffle": False,
|
||||
|
||||
@@ -6,6 +6,7 @@ from enum import unique, Flag
|
||||
from typing import DefaultDict, Dict, List
|
||||
|
||||
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
||||
from BaseClasses import PotFlags, LocationType
|
||||
from Doors import reset_portals
|
||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
||||
from Dungeons import dungeon_bigs, dungeon_hints
|
||||
@@ -378,7 +379,7 @@ def choose_portals(world, player):
|
||||
if world.doorShuffle[player] in ['basic', 'crossed']:
|
||||
cross_flag = world.doorShuffle[player] == 'crossed'
|
||||
# key drops allow the big key in the right place in Desert Tiles 2
|
||||
bk_shuffle = world.bigkeyshuffle[player] or world.keydropshuffle[player] != 'none'
|
||||
bk_shuffle = world.bigkeyshuffle[player] or world.dropshuffle[player]
|
||||
std_flag = world.mode[player] == 'standard'
|
||||
# roast incognito doors
|
||||
world.get_room(0x60, player).delete(5)
|
||||
@@ -994,10 +995,15 @@ def cross_dungeon(world, player):
|
||||
|
||||
assign_cross_keys(dungeon_builders, world, player)
|
||||
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
|
||||
if world.keydropshuffle[player] != 'none':
|
||||
target_items = 35 if world.retro[player] else 96
|
||||
target_items = 34
|
||||
if world.retro[player]:
|
||||
target_items += 1 if world.dropshuffle[player] else 0 # the hc big key
|
||||
else:
|
||||
target_items = 34 if world.retro[player] else 63
|
||||
target_items += 29 # small keys in chests
|
||||
if world.dropshuffle[player]:
|
||||
target_items += 14 # 13 dropped smalls + 1 big
|
||||
if world.pottery[player] != 'none':
|
||||
target_items += 19 # 19 pot keys
|
||||
d_items = target_items - all_dungeon_items_cnt
|
||||
world.pool_adjustment[player] = d_items
|
||||
smooth_door_pairs(world, player)
|
||||
@@ -1055,7 +1061,11 @@ def assign_cross_keys(dungeon_builders, world, player):
|
||||
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
|
||||
start = time.process_time()
|
||||
if world.retro[player]:
|
||||
remaining = 61 if world.keydropshuffle[player] != 'none' else 29
|
||||
remaining = 29
|
||||
if world.dropshuffle[player]:
|
||||
remaining += 13
|
||||
if world.pottery[player] in ['keys', 'lottery']:
|
||||
remaining += 19
|
||||
else:
|
||||
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
|
||||
total_keys = remaining
|
||||
@@ -1074,7 +1084,6 @@ def assign_cross_keys(dungeon_builders, world, player):
|
||||
total_candidates += builder.key_doors_num
|
||||
start_regions_map[name] = start_regions
|
||||
|
||||
|
||||
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
||||
for name, builder in dungeon_builders.items():
|
||||
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
||||
@@ -1251,6 +1260,12 @@ def refine_hints(dungeon_builders):
|
||||
for region in builder.master_sector.regions:
|
||||
for location in region.locations:
|
||||
if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
|
||||
if location.type == LocationType.Pot and location.pot:
|
||||
hint_text = ('under a block' if location.pot.flags & PotFlags.Block else 'in a pot')
|
||||
location.hint_text = f'{hint_text} {dungeon_hints[name]}'
|
||||
elif location.type == LocationType.Drop:
|
||||
location.hint_text = f'dropped {dungeon_hints[name]}'
|
||||
else:
|
||||
location.hint_text = dungeon_hints[name]
|
||||
|
||||
|
||||
@@ -3015,7 +3030,8 @@ palette_map = {
|
||||
'Tower of Hera': (0x6, None),
|
||||
'Thieves Town': (0x17, None), # the attic uses 0x23
|
||||
'Turtle Rock': (0x18, 0x19, 'TR Boss SW'),
|
||||
'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'), # other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?)
|
||||
'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'),
|
||||
# other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrobe moldorm pit f5?)
|
||||
}
|
||||
|
||||
# implications:
|
||||
|
||||
37
Fill.py
37
Fill.py
@@ -7,7 +7,7 @@ from BaseClasses import CollectionState, FillError, LocationType
|
||||
from Items import ItemFactory
|
||||
from Regions import shop_to_location_table, retro_shops
|
||||
from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
|
||||
from source.item.FillUtil import filter_pot_locations
|
||||
from source.item.FillUtil import filter_pot_locations, valid_pot_items
|
||||
|
||||
|
||||
def get_dungeon_item_pool(world):
|
||||
@@ -435,6 +435,8 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||
fill_locations.remove(l)
|
||||
filtered_fill(world, placeholder_items, placeholder_locations)
|
||||
|
||||
if world.players > 1:
|
||||
fast_fill_pot_for_multiworld(world, restitempool, fill_locations)
|
||||
if world.algorithm == 'vanilla_fill':
|
||||
fast_vanilla_fill(world, restitempool, fill_locations)
|
||||
else:
|
||||
@@ -445,11 +447,14 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||
if unplaced or unfilled:
|
||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
||||
|
||||
for loc in world.get_locations():
|
||||
# convert Arrows 5 and Nothing when necessary
|
||||
invalid_locations = [loc for loc in world.get_locations() if loc.item.name in {'Arrows (5)', 'Nothing'} and
|
||||
(loc.type != LocationType.Pot or loc.item.player != loc.player)]
|
||||
for i_loc in invalid_locations:
|
||||
i_loc.item = ItemFactory(invalid_location_replacement[i_loc.item.name], i_loc.item.player)
|
||||
if (loc.item.name in {'Arrows (5)', 'Nothing'}
|
||||
and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
|
||||
loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player)
|
||||
# don't write out all pots to spoiler
|
||||
if loc.type == LocationType.Pot and loc.item.name in valid_pot_items:
|
||||
loc.skip = True
|
||||
|
||||
|
||||
invalid_location_replacement = {'Arrows (5)': 'Arrows (10)', 'Nothing': 'Rupees (5)'}
|
||||
@@ -470,6 +475,28 @@ def fast_fill(world, item_pool, fill_locations):
|
||||
world.push_item(spot_to_fill, item_to_place, False)
|
||||
|
||||
|
||||
def fast_fill_pot_for_multiworld(world, item_pool, fill_locations):
|
||||
pot_item_pool = collections.defaultdict(list)
|
||||
pot_fill_locations = collections.defaultdict(list)
|
||||
for item in item_pool:
|
||||
if item.name in valid_pot_items:
|
||||
pot_item_pool[item.player].append(item)
|
||||
for loc in fill_locations:
|
||||
if loc.type == LocationType.Pot:
|
||||
pot_fill_locations[loc.player].append(loc)
|
||||
for player in range(1, world.players+1):
|
||||
flex = 256 - world.pot_contents[player].multiworld_count
|
||||
fill_count = len(pot_fill_locations[player]) - flex
|
||||
if fill_count > 0:
|
||||
fill_spots = random.sample(pot_fill_locations[player], fill_count)
|
||||
fill_items = random.sample(pot_item_pool[player], fill_count)
|
||||
for x in fill_items:
|
||||
item_pool.remove(x)
|
||||
for x in fill_spots:
|
||||
fill_locations.remove(x)
|
||||
fast_fill(world, fill_items, fill_spots)
|
||||
|
||||
|
||||
def filtered_fill(world, item_pool, fill_locations):
|
||||
while item_pool and fill_locations:
|
||||
item_to_place = item_pool.pop()
|
||||
|
||||
@@ -66,15 +66,15 @@ def create_inverted_regions(world, player):
|
||||
create_cave_region(player, 'Chicken House', 'a house with a chest', ['Chicken House']),
|
||||
create_cave_region(player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
|
||||
create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
|
||||
create_cave_region(player, 'Kakariko Well (top)', 'a drop\'s exit',
|
||||
create_cave_region(player, 'Kakariko Well (top)', 'a drop',
|
||||
['Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||
'Kakariko Well - Bottom'],
|
||||
['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']),
|
||||
create_cave_region(player, 'Kakariko Well (back)', 'a drop\'s exit', ['Kakariko Well - Top']),
|
||||
create_cave_region(player, 'Kakariko Well (back)', 'a drop', ['Kakariko Well - Top']),
|
||||
create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
create_cave_region(player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(player, 'Bat Cave (right)', 'a drop', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||
create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||
create_lw_region(player, 'Hobo Bridge', ['Hobo']),
|
||||
|
||||
@@ -389,12 +389,15 @@ def generate_itempool(world, player):
|
||||
|
||||
if world.retro[player]:
|
||||
set_up_take_anys(world, player)
|
||||
if world.keydropshuffle[player] != 'none':
|
||||
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32
|
||||
if world.dropshuffle[player]:
|
||||
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 13
|
||||
if world.pottery[player] != 'none':
|
||||
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 19
|
||||
|
||||
|
||||
create_dynamic_shop_locations(world, player)
|
||||
|
||||
if world.keydropshuffle[player] == 'potsanity':
|
||||
if world.pottery[player] == 'lottery':
|
||||
add_pot_contents(world, player)
|
||||
|
||||
|
||||
|
||||
11
Main.py
11
Main.py
@@ -97,10 +97,11 @@ def main(args, seed=None, fish=None):
|
||||
world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)}
|
||||
world.experimental = args.experimental.copy()
|
||||
world.dungeon_counters = args.dungeon_counters.copy()
|
||||
world.potshuffle = args.shufflepots.copy()
|
||||
world.fish = fish
|
||||
world.shopsanity = args.shopsanity.copy()
|
||||
world.keydropshuffle = args.keydropshuffle.copy()
|
||||
world.dropshuffle = args.dropshuffle.copy()
|
||||
world.pottery = args.pottery.copy()
|
||||
world.potshuffle = args.shufflepots.copy()
|
||||
world.mixed_travel = args.mixed_travel.copy()
|
||||
world.standardize_palettes = args.standardize_palettes.copy()
|
||||
world.treasure_hunt_count = args.triforce_goal.copy()
|
||||
@@ -158,7 +159,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.keydropshuffle[player] != 'potsanity':
|
||||
if world.pottery[player] != 'lottery':
|
||||
shuffle_pots(world, player)
|
||||
else:
|
||||
shuffle_pot_switches(world, player)
|
||||
@@ -401,7 +402,9 @@ def copy_world(world):
|
||||
ret.intensity = world.intensity.copy()
|
||||
ret.experimental = world.experimental.copy()
|
||||
ret.shopsanity = world.shopsanity.copy()
|
||||
ret.keydropshuffle = world.keydropshuffle.copy()
|
||||
ret.dropshuffle = world.dropshuffle.copy()
|
||||
ret.pottery = world.pottery.copy()
|
||||
ret.potshuffle = world.potshuffle.copy()
|
||||
ret.mixed_travel = world.mixed_travel.copy()
|
||||
ret.standardize_palettes = world.standardize_palettes.copy()
|
||||
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
||||
|
||||
@@ -8,8 +8,10 @@ import shlex
|
||||
import urllib.parse
|
||||
import websockets
|
||||
|
||||
from BaseClasses import PotItem, PotFlags
|
||||
import Items
|
||||
import Regions
|
||||
import PotShuffle
|
||||
|
||||
|
||||
class ReceivedItem:
|
||||
@@ -57,8 +59,13 @@ class Context:
|
||||
self.key_drop_mode = False
|
||||
self.shop_mode = False
|
||||
self.retro_mode = False
|
||||
self.pottery_mode = False
|
||||
self.mystery_mode = False
|
||||
self.ignore_count = 0
|
||||
|
||||
self.lookup_name_to_id = {}
|
||||
self.lookup_id_to_name = {}
|
||||
|
||||
def color_code(*args):
|
||||
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
||||
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
||||
@@ -83,6 +90,10 @@ INGAME_MODES = {0x07, 0x09, 0x0b}
|
||||
SAVEDATA_START = WRAM_START + 0xF000
|
||||
SAVEDATA_SIZE = 0x500
|
||||
|
||||
POT_ITEMS_SRAM_START = WRAM_START + 0x016600
|
||||
SPRITE_ITEMS_SRAM_START = WRAM_START + 0x016850
|
||||
ITEM_SRAM_SIZE = 0x250
|
||||
|
||||
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
||||
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||
@@ -349,6 +360,8 @@ location_table_misc = {'Bottle Merchant': (0x3c9, 0x2),
|
||||
'Purple Chest': (0x3c9, 0x10),
|
||||
"Link's Uncle": (0x3c6, 0x1),
|
||||
'Hobo': (0x3c9, 0x1)}
|
||||
location_table_pot_items = {}
|
||||
location_table_sprite_items = {}
|
||||
|
||||
SNES_DISCONNECTED = 0
|
||||
SNES_CONNECTING = 1
|
||||
@@ -676,7 +689,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
||||
ctx.player_names = {p: n for p, n in args[1]}
|
||||
msgs = []
|
||||
if ctx.locations_checked:
|
||||
msgs.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
||||
msgs.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
||||
if ctx.locations_scouted:
|
||||
msgs.append(['LocationScouts', list(ctx.locations_scouted)])
|
||||
if msgs:
|
||||
@@ -689,7 +702,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
||||
elif start_index != len(ctx.items_received):
|
||||
sync_msg = [['Sync']]
|
||||
if ctx.locations_checked:
|
||||
sync_msg.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
||||
sync_msg.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
||||
await send_msgs(ctx.socket, sync_msg)
|
||||
if start_index == len(ctx.items_received):
|
||||
for item in items:
|
||||
@@ -701,7 +714,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
||||
if location not in ctx.locations_info:
|
||||
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
|
||||
item_name = replacements.get(item, get_item_name_from_id(item))
|
||||
logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.lookup_id_to_name.keys())[location - 1]}")
|
||||
logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(ctx.lookup_id_to_name.keys())[location - 1]}")
|
||||
ctx.locations_info[location] = (item, player)
|
||||
ctx.watcher_event.set()
|
||||
|
||||
@@ -710,7 +723,8 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
||||
item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.slot else 'green')
|
||||
player_sent = color(ctx.player_names[player_sent], 'yellow' if player_sent != ctx.slot else 'magenta')
|
||||
player_recvd = color(ctx.player_names[player_recvd], 'yellow' if player_recvd != ctx.slot else 'magenta')
|
||||
logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, get_location_name_from_address(location)))
|
||||
location_name = get_location_name_from_address(ctx, location)
|
||||
logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, location_name))
|
||||
|
||||
if cmd == 'Print':
|
||||
logging.info(args)
|
||||
@@ -779,10 +793,10 @@ async def console_loop(ctx : Context):
|
||||
for index, item in enumerate(ctx.items_received, 1):
|
||||
logging.info('%s from %s (%s) (%d/%d in list)' % (
|
||||
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
get_location_name_from_address(item.location), index, len(ctx.items_received)))
|
||||
get_location_name_from_address(ctx, item.location), index, len(ctx.items_received)))
|
||||
|
||||
if command[0] == '/missing':
|
||||
for location in [k for k, v in Regions.lookup_name_to_id.items()
|
||||
for location in [k for k, v in ctx.lookup_name_to_id.items()
|
||||
if type(v) is int and not filter_location(ctx, k)]:
|
||||
if location not in ctx.locations_checked:
|
||||
logging.info('Missing: ' + location)
|
||||
@@ -804,15 +818,19 @@ def get_item_name_from_id(code):
|
||||
return items[0] if items else f'Unknown item (ID:{code})'
|
||||
|
||||
|
||||
def get_location_name_from_address(address):
|
||||
def get_location_name_from_address(ctx, address):
|
||||
if type(address) is str:
|
||||
return address
|
||||
|
||||
return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
|
||||
return ctx.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
|
||||
|
||||
|
||||
def filter_location(ctx, location):
|
||||
if not ctx.key_drop_mode and ('Key Drop' in location or 'Pot Key' in location):
|
||||
if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data
|
||||
and PotShuffle.key_drop_data[location][0] == 'Drop'):
|
||||
return True
|
||||
if (not ctx.pottery_mode and location in PotShuffle.key_drop_data
|
||||
and PotShuffle.key_drop_data[location][0] == 'Pot'):
|
||||
return True
|
||||
if not ctx.shop_mode and location in Regions.flat_normal_shops:
|
||||
return True
|
||||
@@ -821,6 +839,31 @@ def filter_location(ctx, location):
|
||||
return False
|
||||
|
||||
|
||||
def init_lookups(ctx):
|
||||
ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()}
|
||||
ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()}
|
||||
for location, datum in PotShuffle.key_drop_data.items():
|
||||
type = datum[0]
|
||||
if type == 'Drop':
|
||||
location_id, super_tile, sprite_index = datum[1]
|
||||
location_table_sprite_items[location] = (2 * super_tile, 0x8000 >> sprite_index)
|
||||
ctx.lookup_name_to_id[location] = location_id
|
||||
ctx.lookup_id_to_name[location_id] = location
|
||||
for super_tile, pot_list in PotShuffle.vanilla_pots.items():
|
||||
for pot_index, pot in enumerate(pot_list):
|
||||
if pot.item != PotItem.Hole:
|
||||
if pot.item == PotItem.Key:
|
||||
loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items()
|
||||
if datum[1] == super_tile)
|
||||
else:
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
loc_name = f'{pot.room} {descriptor}'
|
||||
location_table_pot_items[loc_name] = (2 * super_tile, 0x8000 >> pot_index)
|
||||
location_id = Regions.pot_address(pot_index, super_tile)
|
||||
ctx.lookup_name_to_id[loc_name] = location_id
|
||||
ctx.lookup_id_to_name[location_id] = loc_name
|
||||
|
||||
|
||||
async def track_locations(ctx : Context, roomid, roomdata):
|
||||
new_locations = []
|
||||
|
||||
@@ -832,9 +875,12 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
||||
|
||||
if ctx.mode_flags is None:
|
||||
flags = await snes_read(ctx, MODE_FLAGS, 1)
|
||||
ctx.mode_flags = flags
|
||||
ctx.key_drop_mode = flags[0] & 0x1
|
||||
ctx.shop_mode = flags[0] & 0x2
|
||||
ctx.retro_mode = flags[0] & 0x4
|
||||
ctx.pottery_mode = flags[0] & 0x8
|
||||
ctx.mystery_mode = flags[0] & 0x10
|
||||
|
||||
def new_check(location):
|
||||
ctx.locations_checked.add(location)
|
||||
@@ -842,8 +888,9 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
||||
if ignored:
|
||||
ctx.ignore_count += 1
|
||||
else:
|
||||
logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{ctx.total_locations})")
|
||||
new_locations.append(Regions.lookup_name_to_id[location])
|
||||
total = '???' if ctx.mystery_mode else ctx.total_locations
|
||||
logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{total})")
|
||||
new_locations.append(ctx.lookup_name_to_id[location])
|
||||
|
||||
try:
|
||||
if ctx.shop_mode or ctx.retro_mode:
|
||||
@@ -908,6 +955,22 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
||||
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
|
||||
new_check(location)
|
||||
|
||||
if not all([location in ctx.locations_checked for location in location_table_pot_items.keys()]):
|
||||
pot_items_data = await snes_read(ctx, POT_ITEMS_SRAM_START, ITEM_SRAM_SIZE)
|
||||
if pot_items_data is not None:
|
||||
for location, (offset, mask) in location_table_pot_items.items():
|
||||
pot_value = pot_items_data[offset] | (pot_items_data[offset + 1] << 8)
|
||||
if pot_value & mask != 0 and location not in ctx.locations_checked:
|
||||
new_check(location)
|
||||
|
||||
if not all([location in ctx.locations_checked for location in location_table_sprite_items.keys()]):
|
||||
sprite_items_data = await snes_read(ctx, SPRITE_ITEMS_SRAM_START, ITEM_SRAM_SIZE)
|
||||
if sprite_items_data is not None:
|
||||
for location, (offset, mask) in location_table_sprite_items.items():
|
||||
sprite_value = sprite_items_data[offset] | (sprite_items_data[offset + 1] << 8)
|
||||
if sprite_value & mask != 0 and location not in ctx.locations_checked:
|
||||
new_check(location)
|
||||
|
||||
await send_msgs(ctx.socket, [['LocationChecks', new_locations]])
|
||||
|
||||
async def game_watcher(ctx : Context):
|
||||
@@ -955,7 +1018,7 @@ async def game_watcher(ctx : Context):
|
||||
item = ctx.items_received[recv_index]
|
||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||
get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received)))
|
||||
get_location_name_from_address(ctx, item.location), recv_index + 1, len(ctx.items_received)))
|
||||
recv_index += 1
|
||||
snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item.item]))
|
||||
@@ -969,7 +1032,7 @@ async def game_watcher(ctx : Context):
|
||||
|
||||
if scout_location > 0 and scout_location not in ctx.locations_scouted:
|
||||
ctx.locations_scouted.add(scout_location)
|
||||
logging.info(f'Scouting item at {list(Regions.lookup_id_to_name.keys())[scout_location - 1]}')
|
||||
logging.info(f'Scouting item at {list(ctx.lookup_id_to_name.keys())[scout_location - 1]}')
|
||||
await send_msgs(ctx.socket, [['LocationScouts', [scout_location]]])
|
||||
await track_locations(ctx, roomid, roomdata)
|
||||
|
||||
@@ -984,6 +1047,7 @@ async def main():
|
||||
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||
|
||||
ctx = Context(args.snes, args.connect, args.password)
|
||||
init_lookups(ctx)
|
||||
|
||||
input_task = asyncio.create_task(console_loop(ctx))
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@ import urllib.request
|
||||
import websockets
|
||||
import zlib
|
||||
|
||||
from BaseClasses import PotItem, PotFlags
|
||||
import Items
|
||||
import Regions
|
||||
import PotShuffle
|
||||
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
||||
|
||||
class Client:
|
||||
@@ -40,6 +42,9 @@ class Context:
|
||||
self.clients = []
|
||||
self.received_items = {}
|
||||
|
||||
self.lookup_name_to_id = {}
|
||||
self.lookup_id_to_name = {}
|
||||
|
||||
async def send_msgs(websocket, msgs):
|
||||
if not websocket or not websocket.open or websocket.closed:
|
||||
return
|
||||
@@ -176,7 +181,8 @@ def register_location_checks(ctx : Context, team, slot, locations):
|
||||
recvd_items.append(new_item)
|
||||
if slot != target_player:
|
||||
broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]])
|
||||
logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], get_location_name_from_address(location)))
|
||||
loc_name = get_location_name_from_address(ctx, location)
|
||||
logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], loc_name))
|
||||
found_items = True
|
||||
send_new_items(ctx)
|
||||
|
||||
@@ -249,11 +255,11 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
|
||||
return
|
||||
locs = []
|
||||
for location in args:
|
||||
if type(location) is not int or 0 >= location > len(Regions.lookup_id_to_name.keys()):
|
||||
if type(location) is not int or 0 >= location > len(ctx.lookup_id_to_name.keys()):
|
||||
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
|
||||
return
|
||||
loc_name = list(Regions.lookup_id_to_name.keys())[location - 1]
|
||||
target_item, target_player = ctx.locations[(Regions.lookup_name_to_id[loc_name], client.slot)]
|
||||
loc_name = list(ctx.lookup_id_to_name.keys())[location - 1]
|
||||
target_item, target_player = ctx.locations[(ctx.lookup_name_to_id[loc_name], client.slot)]
|
||||
|
||||
replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D}
|
||||
item_type = [i[2] for i in Items.item_table.values() if type(i[3]) is int and i[3] == target_item]
|
||||
@@ -339,6 +345,30 @@ async def console(ctx : Context):
|
||||
if command[0][0] != '/':
|
||||
notify_all(ctx, '[Server]: ' + input)
|
||||
|
||||
|
||||
def init_lookups(ctx):
|
||||
ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()}
|
||||
ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()}
|
||||
for location, datum in PotShuffle.key_drop_data.items():
|
||||
type = datum[0]
|
||||
if type == 'Drop':
|
||||
location_id = datum[1][0]
|
||||
ctx.lookup_name_to_id[location] = location_id
|
||||
ctx.lookup_id_to_name[location_id] = location
|
||||
for super_tile, pot_list in PotShuffle.vanilla_pots.items():
|
||||
for pot_index, pot in enumerate(pot_list):
|
||||
if pot.item != PotItem.Hole:
|
||||
if pot.item == PotItem.Key:
|
||||
loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items()
|
||||
if datum[1] == super_tile)
|
||||
else:
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
loc_name = f'{pot.room} {descriptor}'
|
||||
location_id = Regions.pot_address(pot_index, super_tile)
|
||||
ctx.lookup_name_to_id[loc_name] = location_id
|
||||
ctx.lookup_id_to_name[location_id] = loc_name
|
||||
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--host', default=None)
|
||||
@@ -353,7 +383,7 @@ async def main():
|
||||
logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||
|
||||
ctx = Context(args.host, args.port, args.password)
|
||||
|
||||
init_lookups(ctx)
|
||||
ctx.data_filename = args.multidata
|
||||
|
||||
try:
|
||||
|
||||
@@ -173,7 +173,9 @@ def roll_settings(weights):
|
||||
ret.shufflelinks = get_choice('shufflelinks') == 'on'
|
||||
ret.pseudoboots = get_choice('pseudoboots') == 'on'
|
||||
ret.shopsanity = get_choice('shopsanity') == 'on'
|
||||
ret.keydropshuffle = get_choice('keydropshuffle')
|
||||
ret.dropshuffle = get_choice('dropshuffle') == 'on'
|
||||
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
|
||||
ret.shuffleswitches = get_choice('shuffleswitches') == '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'
|
||||
|
||||
@@ -243,8 +245,6 @@ def roll_settings(weights):
|
||||
|
||||
ret.enemy_health = get_choice('enemy_health')
|
||||
|
||||
ret.shufflepots = get_choice('pot_shuffle') == 'on'
|
||||
|
||||
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
|
||||
|
||||
inventoryweights = weights.get('startinventory', {})
|
||||
|
||||
@@ -91,7 +91,7 @@ vanilla_pots = {
|
||||
0x3F: [Pot(12, 25, PotItem.OneRupee, 'Ice Hammer Block'), Pot(20, 25, PotItem.OneRupee, 'Ice Hammer Block'),
|
||||
Pot(12, 26, PotItem.Bomb, 'Ice Hammer Block'), Pot(20, 26, PotItem.Bomb, 'Ice Hammer Block'),
|
||||
Pot(12, 27, PotItem.Switch, 'Ice Hammer Block'), Pot(20, 27, PotItem.Heart, 'Ice Hammer Block'),
|
||||
Pot(28, 23, PotItem.Key, 'Ice Hammer Block')],
|
||||
Pot(28, 23, PotItem.Key, 'Ice Hammer Block', PotFlags.Block)],
|
||||
0x41: [Pot(100, 10, PotItem.Heart, 'Sewers Behind Tapestry'), Pot(52, 15, PotItem.OneRupee, 'Sewers Behind Tapestry'), Pot(52, 16, PotItem.SmallMagic, 'Sewers Behind Tapestry'), Pot(148, 22, PotItem.SmallMagic, 'Sewers Behind Tapestry')],
|
||||
0x43: [Pot(66, 4, PotItem.FiveArrows, 'Desert Wall Slide'), Pot(78, 4, PotItem.SmallMagic, 'Desert Wall Slide'), Pot(66, 9, PotItem.Heart, 'Desert Wall Slide'), Pot(78, 9, PotItem.Heart, 'Desert Wall Slide'),
|
||||
Pot(112, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 28, PotItem.Nothing, 'Desert Tiles 2'), Pot(76, 20, PotItem.Nothing, 'Desert Tiles 2'), Pot(112, 20, PotItem.Key, 'Desert Tiles 2')],
|
||||
@@ -467,45 +467,46 @@ def shuffle_pot_switches(world, player):
|
||||
|
||||
|
||||
key_drop_data = {
|
||||
'Hyrule Castle - Map Guard Key Drop': ['Drop', (0x09E20C, 0x72), 'in Hyrule Castle', 'Small Key (Escape)'],
|
||||
'Hyrule Castle - Boomerang Guard Key Drop': ['Drop', (0x09E204, 0x71), 'in Hyrule Castle', 'Small Key (Escape)'],
|
||||
'Hyrule Castle - Key Rat Key Drop': ['Drop', (0x09DB80, 0x21), 'in Hyrule Castle', 'Small Key (Escape)'],
|
||||
'Hyrule Castle - Big Key Drop': ['Drop', (0x09E327, 0x80), 'in Hyrule Castle', 'Big Key (Escape)'],
|
||||
'Eastern Palace - Dark Square Pot Key': ['Pot', 0xBA, 'in Eastern Palace', 'Small Key (Eastern Palace)'],
|
||||
'Eastern Palace - Dark Eyegore Key Drop': ['Drop', (0x09E4F8, 0x99), 'in Eastern Palace', 'Small Key (Eastern Palace)'],
|
||||
'Desert Palace - Desert Tiles 1 Pot Key': ['Pot', 0x63, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Desert Palace - Beamos Hall Pot Key': ['Pot', 0x53, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Desert Palace - Desert Tiles 2 Pot Key': ['Pot', 0x43, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Castle Tower - Dark Archer Key Drop': ['Drop', (0x09E7C9, 0xC0), 'in Castle Tower', 'Small Key (Agahnims Tower)'],
|
||||
'Castle Tower - Circle of Pots Key Drop': ['Drop', (0x09E688, 0xB0), 'in Castle Tower', 'Small Key (Agahnims Tower)'],
|
||||
'Swamp Palace - Pot Row Pot Key': ['Pot', 0x38, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Trench 1 Pot Key': ['Pot', 0x37, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Hookshot Pot Key': ['Pot', 0x36, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Trench 2 Pot Key': ['Pot', 0x35, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Waterway Pot Key': ['Pot', 0x16, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Skull Woods - West Lobby Pot Key': ['Pot', 0x56, 'in Skull Woods', 'Small Key (Skull Woods)'],
|
||||
'Skull Woods - Spike Corner Key Drop': ['Drop', (0x09DD74, 0x39), 'near Mothula', 'Small Key (Skull Woods)'],
|
||||
"Thieves' Town - Hallway Pot Key": ['Pot', 0xBC, "in Thieves' Town", 'Small Key (Thieves Town)'],
|
||||
"Thieves' Town - Spike Switch Pot Key": ['Pot', 0xAB, "in Thieves' Town", 'Small Key (Thieves Town)'],
|
||||
'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE), 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E), 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in Misery Mire', 'Small Key (Misery Mire)'],
|
||||
'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in forgotten Mire', 'Small Key (Misery Mire)'],
|
||||
'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1), 'in Misery Mire', 'Small Key (Misery Mire)'],
|
||||
'Turtle Rock - Pokey 1 Key Drop': ['Drop', (0x09E70D, 0xB6), 'in Turtle Rock', 'Small Key (Turtle Rock)'],
|
||||
'Turtle Rock - Pokey 2 Key Drop': ['Drop', (0x09DA5D, 0x13), 'in Turtle Rock', 'Small Key (Turtle Rock)'],
|
||||
'Ganons Tower - Conveyor Cross Pot Key': ['Pot', 0x8B, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Double Switch Pot Key': ['Pot', 0x9B, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key': ['Pot', 0x7B, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Mini Helmasaur Key Drop': ['Drop', (0x09DDC4, 0x3D), "atop Ganon's Tower", 'Small Key (Ganons Tower)']
|
||||
'Hyrule Castle - Map Guard Key Drop': ['Drop', (0x09E20C, 0x72, 0), 'dropped in Hyrule Castle', 'Small Key (Escape)'],
|
||||
'Hyrule Castle - Boomerang Guard Key Drop': ['Drop', (0x09E204, 0x71, 1), 'dropped in Hyrule Castle', 'Small Key (Escape)'],
|
||||
'Hyrule Castle - Key Rat Key Drop': ['Drop', (0x09DB80, 0x21, 0), 'dropped in Hyrule Castle', 'Small Key (Escape)'],
|
||||
'Hyrule Castle - Big Key Drop': ['Drop', (0x09E327, 0x80, 2), 'dropped in Hyrule Castle', 'Big Key (Escape)'],
|
||||
'Eastern Palace - Dark Square Pot Key': ['Pot', 0xBA, 'in a pot in Eastern Palace', 'Small Key (Eastern Palace)'],
|
||||
'Eastern Palace - Dark Eyegore Key Drop': ['Drop', (0x09E4F8, 0x99, 3), 'dropped in Eastern Palace', 'Small Key (Eastern Palace)'],
|
||||
'Desert Palace - Desert Tiles 1 Pot Key': ['Pot', 0x63, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Desert Palace - Beamos Hall Pot Key': ['Pot', 0x53, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Desert Palace - Desert Tiles 2 Pot Key': ['Pot', 0x43, 'in a pot in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Castle Tower - Dark Archer Key Drop': ['Drop', (0x09E7C9, 0xC0, 3), 'dropped in Castle Tower', 'Small Key (Agahnims Tower)'],
|
||||
'Castle Tower - Circle of Pots Key Drop': ['Drop', (0x09E688, 0xB0, 10), 'dropped in Castle Tower', 'Small Key (Agahnims Tower)'],
|
||||
'Swamp Palace - Pot Row Pot Key': ['Pot', 0x38, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Trench 1 Pot Key': ['Pot', 0x37, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Hookshot Pot Key': ['Pot', 0x36, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Trench 2 Pot Key': ['Pot', 0x35, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Waterway Pot Key': ['Pot', 0x16, 'in a pot in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Skull Woods - West Lobby Pot Key': ['Pot', 0x56, 'in a pot in Skull Woods', 'Small Key (Skull Woods)'],
|
||||
'Skull Woods - Spike Corner Key Drop': ['Drop', (0x09DD74, 0x39, 1), 'dropped near Mothula', 'Small Key (Skull Woods)'],
|
||||
"Thieves' Town - Hallway Pot Key": ['Pot', 0xBC, "in a pot in Thieves' Town", 'Small Key (Thieves Town)'],
|
||||
"Thieves' Town - Spike Switch Pot Key": ['Pot', 0xAB, "in a pot in Thieves' Town", 'Small Key (Thieves Town)'],
|
||||
'Ice Palace - Jelly Key Drop': ['Drop', (0x09DA21, 0xE, 3), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Conveyor Key Drop': ['Drop', (0x09DE08, 0x3E, 8), 'dropped in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Hammer Block Key Drop': ['Pot', 0x3F, 'under a block in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Many Pots Pot Key': ['Pot', 0x9F, 'int a pot in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Misery Mire - Spikes Pot Key': ['Pot', 0xB3, 'in a pot in Misery Mire', 'Small Key (Misery Mire)'],
|
||||
'Misery Mire - Fishbone Pot Key': ['Pot', 0xA1, 'in a pot in forgotten Mire', 'Small Key (Misery Mire)'],
|
||||
'Misery Mire - Conveyor Crystal Key Drop': ['Drop', (0x09E7FB, 0xC1, 9), 'dropped in Misery Mire', 'Small Key (Misery Mire)'],
|
||||
'Turtle Rock - Pokey 1 Key Drop': ['Drop', (0x09E70D, 0xB6, 5), 'dropped in Turtle Rock', 'Small Key (Turtle Rock)'],
|
||||
'Turtle Rock - Pokey 2 Key Drop': ['Drop', (0x09DA5D, 0x13, 6), 'dropped in Turtle Rock', 'Small Key (Turtle Rock)'],
|
||||
'Ganons Tower - Conveyor Cross Pot Key': ['Pot', 0x8B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Double Switch Pot Key': ['Pot', 0x9B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key': ['Pot', 0x7B, "in a pot in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Mini Helmasaur Key Drop': ['Drop', (0x09DDC4, 0x3D, 2), "dropped atop Ganon's Tower", 'Small Key (Ganons Tower)']
|
||||
}
|
||||
|
||||
|
||||
class PotSecretTable(object):
|
||||
def __init__(self):
|
||||
self.room_map = defaultdict(list)
|
||||
self.multiworld_count = 0
|
||||
|
||||
def write_pot_data_to_rom(self, rom):
|
||||
pointer_address = 0x140000 # pots currently in bank 28
|
||||
|
||||
@@ -2,6 +2,38 @@
|
||||
|
||||
## New Features
|
||||
|
||||
## Pottery Lottery and Key Drop Shuffle Changes
|
||||
|
||||
### Pottery
|
||||
|
||||
New pottery option that control which pots are in the locations pool:
|
||||
|
||||
* None: No pots are in the pool, like normal randomizer
|
||||
* Key Pots: The pots that have keys are in the pool. This is about half of the old keydropshuffle option
|
||||
* 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`
|
||||
|
||||
Note for multiworld: due to the design of the pottery lottery, only 256 items for other players can be under pots in your world.
|
||||
|
||||
### Shuffle key drops
|
||||
|
||||
Enemies that drop keys can have their drop shuffled into the pool. This is the other half of the keydropshuffle option.
|
||||
|
||||
CLI `--dropshuffle`
|
||||
|
||||
#### Legacy options
|
||||
|
||||
"Drop and Pot Keys" or `--keydropshuffle` is still availabe for use. This simply sets the pottery to keys and turns dropshuffle on as well to have the same behavior as the
|
||||
|
||||
The old "Pot Shuffle" option is available under "Shuffle Pot Switches" or `--shuffleswitches` and works the same by shuffling all pots on a supertile. It works with the lottery option as well to move the switches while having every pot in the pool. I have no justification for the name change on the CLI/Mystery, it just kind of happened and I was too lazy to change it back.
|
||||
|
||||
#### Tracking Notes
|
||||
|
||||
The sram locations for pots and sprite drops have been moved, please reach out for assistance or investigate the rom changes.
|
||||
|
||||
## Restricted Item Placement Algorithm
|
||||
|
||||
|
||||
|
||||
64
Regions.py
64
Regions.py
@@ -58,15 +58,15 @@ def create_regions(world, player):
|
||||
create_cave_region(player, 'Chicken House', 'a house with a chest', ['Chicken House']),
|
||||
create_cave_region(player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
|
||||
create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
|
||||
create_cave_region(player, 'Kakariko Well (top)', 'a drop\'s exit',
|
||||
create_cave_region(player, 'Kakariko Well (top)', 'a drop',
|
||||
['Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||
'Kakariko Well - Bottom'],
|
||||
['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']),
|
||||
create_cave_region(player, 'Kakariko Well (back)', 'a drop\'s exit', ['Kakariko Well - Top']),
|
||||
create_cave_region(player, 'Kakariko Well (back)', 'a drop', ['Kakariko Well - Top']),
|
||||
create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
create_cave_region(player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(player, 'Bat Cave (right)', 'a drop', ['Magic Bat'], ['Bat Cave Door']),
|
||||
create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||
create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||
create_lw_region(player, 'Hobo Bridge', ['Hobo']),
|
||||
@@ -993,16 +993,19 @@ def adjust_locations(world, player):
|
||||
world.pot_contents[player] = PotSecretTable()
|
||||
for location, datum in key_drop_data.items():
|
||||
loc = world.get_location(location, player)
|
||||
if 'Drop' == datum[0]:
|
||||
drop_location = 'Drop' == datum[0]
|
||||
if drop_location:
|
||||
loc.type = LocationType.Drop
|
||||
snes_address, room = datum[1]
|
||||
snes_address, room, sprite_idx = datum[1]
|
||||
loc.address = snes_address
|
||||
else:
|
||||
loc.type = LocationType.Pot
|
||||
pot = next(p for p in vanilla_pots[datum[1]] if p.item == PotItem.Key).copy()
|
||||
pot, pot_index = next((p, i) for i, p in enumerate(vanilla_pots[datum[1]]) if p.item == PotItem.Key)
|
||||
pot = pot.copy()
|
||||
loc.address = pot_address(pot_index, datum[1])
|
||||
loc.pot = pot
|
||||
world.pot_contents[player].room_map[datum[1]].append(pot)
|
||||
if world.keydropshuffle[player] == 'none':
|
||||
if (not world.dropshuffle[player] and drop_location)\
|
||||
or (not drop_location and world.pottery[player] == 'none'):
|
||||
loc.skip = True
|
||||
else:
|
||||
key_item = loc.item
|
||||
@@ -1019,23 +1022,14 @@ def adjust_locations(world, player):
|
||||
dungeon.big_key = key_item
|
||||
for super_tile, pot_list in vanilla_pots.items():
|
||||
for pot_index, pot_orig in enumerate(pot_list):
|
||||
if pot_orig.item == PotItem.Key:
|
||||
loc = next(location for location, datum in key_drop_data.items() if datum[1] == super_tile)
|
||||
pot = world.get_location(loc, player).pot
|
||||
else:
|
||||
pot = pot_orig.copy()
|
||||
if pot.item != PotItem.Key:
|
||||
world.pot_contents[player].room_map[super_tile].append(pot)
|
||||
if world.keydropshuffle[player] == 'potsanity':
|
||||
if (pot.item not in [PotItem.Key, PotItem.Hole]
|
||||
and (pot.item != PotItem.Switch or world.potshuffle[player])):
|
||||
parent = world.get_region(pot.room, player)
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
# todo: better hints
|
||||
hint_text = 'under a block' if pot.flags & PotFlags.Block else 'in a pot'
|
||||
pot_location = Location(player, f'{pot.room} {descriptor}', player, hint_text=hint_text,
|
||||
parent=parent)
|
||||
world.dynamic_locations.append(pot_location)
|
||||
pot_location.pot = pot
|
||||
|
||||
pot_location.type = LocationType.Pot
|
||||
parent.locations.append(pot_location)
|
||||
if world.pottery[player] == 'lottery':
|
||||
create_pot_location(pot, pot_index, super_tile, world, player)
|
||||
if world.shopsanity[player]:
|
||||
index = 0
|
||||
for shop, location_list in shop_to_location_table.items():
|
||||
@@ -1057,6 +1051,28 @@ def adjust_locations(world, player):
|
||||
location.skip = True
|
||||
|
||||
|
||||
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])):
|
||||
address = pot_address(pot_index, super_tile)
|
||||
parent = world.get_region(pot.room, player)
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
hint_text = ('under a block' if pot.flags & PotFlags.Block else 'in a pot')
|
||||
modifier = parent.hint_text not in {'a storyteller', 'fairies deep in a cave', 'a spiky hint',
|
||||
'a bounty of five items', 'the sick kid', 'Sahasrahla'}
|
||||
hint_text = f'{hint_text} {"in" if modifier else "near"} {parent.hint_text}'
|
||||
pot_location = Location(player, f'{pot.room} {descriptor}', address, hint_text=hint_text,
|
||||
parent=parent)
|
||||
world.dynamic_locations.append(pot_location)
|
||||
pot_location.pot = pot
|
||||
|
||||
pot_location.type = LocationType.Pot
|
||||
parent.locations.append(pot_location)
|
||||
|
||||
|
||||
def pot_address(pot_index, super_tile):
|
||||
return 0x7f6600 + super_tile * 2 + (pot_index << 24)
|
||||
|
||||
|
||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||
@@ -1401,8 +1417,6 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
||||
}
|
||||
|
||||
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
||||
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}}
|
||||
lookup_id_to_name.update(shop_table_by_location_id)
|
||||
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
||||
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}}
|
||||
lookup_name_to_id.update(shop_table_by_location)
|
||||
|
||||
37
Rom.py
37
Rom.py
@@ -35,7 +35,7 @@ from source.item.FillUtil import valid_pot_items
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '8d13470d5a3127c6705846674cfb6209'
|
||||
RANDOMIZERBASEHASH = 'dea3a3bc1435aa0895181c5d46dc6d43'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -554,7 +554,7 @@ class Sprite(object):
|
||||
def handle_native_dungeon(location, itemid):
|
||||
# Keys in their native dungeon should use the original item code for keys
|
||||
if location.parent_region.dungeon:
|
||||
if location.parent_region.dungeon.is_dungeon_item(location.item):
|
||||
if location.parent_region.dungeon.name == location.item.dungeon:
|
||||
if location.item.bigkey:
|
||||
return 0x32
|
||||
if location.item.smallkey:
|
||||
@@ -604,7 +604,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
else:
|
||||
code = 0xF8
|
||||
sprite_pointer = snes_to_pc(location.address)
|
||||
if code != 0xE4:
|
||||
rom.write_byte(sprite_pointer, handle_native_dungeon(location, itemid))
|
||||
if code == 0xF9:
|
||||
rom.write_byte(sprite_pointer+1, location.item.player)
|
||||
@@ -650,6 +649,13 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
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)
|
||||
# fix for swamp drains if necessary
|
||||
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
|
||||
if not swamp1location.pot.indicator:
|
||||
rom.write_byte(0x142A53, 0)
|
||||
swamp2location = world.get_location('Swamp Palace - Trench 2 Pot Key', player)
|
||||
if not swamp2location.pot.indicator:
|
||||
rom.write_byte(0x142A54, 0)
|
||||
|
||||
# patch entrance/exits/holes
|
||||
for region in world.regions:
|
||||
@@ -740,7 +746,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
valid_loc_by_dungeon = valid_dungeon_locations(valid_locations)
|
||||
|
||||
# fix hc big key problems (map and compass too)
|
||||
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player] != 'none':
|
||||
if world.doorShuffle[player] == 'crossed' or world.dropshuffle[player]:
|
||||
rom.write_byte(0x151f1, 2)
|
||||
rom.write_byte(0x15270, 2)
|
||||
sanctuary = world.get_region('Sanctuary', player)
|
||||
@@ -868,13 +874,14 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
|
||||
credits_total = len(valid_locations)
|
||||
|
||||
if world.keydropshuffle[player] != 'none':
|
||||
if world.dropshuffle[player] or world.pottery[player] != 'none':
|
||||
rom.write_byte(0x142A50, 1)
|
||||
rom.write_byte(0x142A51, 1)
|
||||
if world.keydropshuffle[player] == 'potsanity':
|
||||
rom.write_bytes(0x04DFD8, [0x18, 0x0B, 0x1C])
|
||||
rom.write_byte(0x04E002, 0xFF)
|
||||
# write_int16(0x142A52, credits_total - 216) # todo: this is now a delta
|
||||
multiClientFlags = ((0x1 if world.dropshuffle[player] else 0)
|
||||
| (0x2 if world.shopsanity[player] else 0)
|
||||
| (0x4 if world.retro[player] else 0)
|
||||
| (0x8 if world.pottery[player] in ['keys', 'lottery'] else 0)
|
||||
| (0x10 if is_mystery else 0))
|
||||
rom.write_byte(0x142A51, multiClientFlags)
|
||||
|
||||
write_int16(rom, 0x187010, credits_total) # dynamic credits
|
||||
if credits_total != 216:
|
||||
@@ -1202,7 +1209,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
|
||||
|
||||
gametype = 0x04 # item
|
||||
if world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player] != 'none':
|
||||
if (world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||
or world.pottery[player] != 'none'):
|
||||
gametype |= 0x02 # entrance/door
|
||||
if enemized:
|
||||
gametype |= 0x01 # enemizer
|
||||
@@ -1452,8 +1460,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
rom.write_byte(0x18003C, 0x00)
|
||||
elif world.dungeon_counters[player] == 'on':
|
||||
compass_mode = 0x02 # always on
|
||||
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla'
|
||||
or world.dungeon_counters[player] == 'pickup' or world.keydropshuffle[player] != 'none'):
|
||||
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] != 'none'):
|
||||
compass_mode = 0x01 # show on pickup
|
||||
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
|
||||
compass_mode |= 0x80 # turn on locating dungeons
|
||||
@@ -1628,7 +1636,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
rom.write_byte(0xFED31, 0x0E) # preopen bombable exit
|
||||
rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit
|
||||
|
||||
if world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player] != 'none':
|
||||
if (world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||
or world.pottery[player] != 'none'):
|
||||
for room in world.rooms:
|
||||
if room.player == player and room.modified:
|
||||
rom.write_bytes(room.address(), room.rom_data())
|
||||
|
||||
2
Rules.py
2
Rules.py
@@ -711,7 +711,7 @@ def bomb_rules(world, player):
|
||||
|
||||
|
||||
def pot_rules(world, player):
|
||||
if world.keydropshuffle[player] == 'potsanity':
|
||||
if world.pottery[player] == 'lottery':
|
||||
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))
|
||||
|
||||
Binary file not shown.
@@ -65,12 +65,24 @@
|
||||
"type": "bool"
|
||||
},
|
||||
"keydropshuffle" : {
|
||||
"choices": [
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"dropshuffle" : {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"pottery" : {
|
||||
"choices" : [
|
||||
"none",
|
||||
"keydrops",
|
||||
"potsanity"
|
||||
"keys",
|
||||
"lottery"
|
||||
]
|
||||
},
|
||||
"shufflepots": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"mixed_travel" : {
|
||||
"choices": [
|
||||
"prevent",
|
||||
@@ -388,10 +400,6 @@
|
||||
"random"
|
||||
]
|
||||
},
|
||||
"shufflepots": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"remote_items": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
|
||||
@@ -254,11 +254,13 @@
|
||||
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||
"bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||
"shopsanity": ["Shop contents are shuffle in the main item pool and other items can take their place. (default: %(default)s)"],
|
||||
"keydropshuffle": [ "Key Drops (Pots and Enemies) are shuffled and other items can take their place:",
|
||||
"None: No pots or enemies are included",
|
||||
"Keydrops: Only key drops are included",
|
||||
"Potsanity: All key drops and all normal pots are included"
|
||||
"dropshuffle": [ "Keys dropped by enemies are shuffled and other items can take their place. (default: %(default)s)"],
|
||||
"pottery": [ "Controls how items under pots are shuffled and if other items can take their place:",
|
||||
"None: No pots are changed",
|
||||
"Keys: Key pots are included in the location pool and other items can take their place",
|
||||
"Lottery: All pots are part of the location pool"
|
||||
],
|
||||
"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",
|
||||
"Prevent: Rails are placed to prevent bombs jump and hovering from changing dungeon except with glitched logic settings",
|
||||
|
||||
@@ -53,10 +53,13 @@
|
||||
"randomizer.dungeon.compassshuffle": "Compasses",
|
||||
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
|
||||
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
||||
"randomizer.dungeon.keydropshuffle": "Drops and Pots",
|
||||
"randomizer.dungeon.keydropshuffle.none": "None",
|
||||
"randomizer.dungeon.keydropshuffle.keydrops": "Only Key Drops",
|
||||
"randomizer.dungeon.keydropshuffle.potsanity": "All Pots and Key Drops",
|
||||
"randomizer.dungeon.keydropshuffle": "Drop and Pot Keys",
|
||||
"randomizer.dungeon.dropshuffle": "Shuffle Key Drops",
|
||||
"randomizer.dungeon.potshuffle": "Pot Shuffle (Legacy)",
|
||||
"randomizer.dungeon.pottery": "Pottery",
|
||||
"randomizer.dungeon.pottery.none": "None",
|
||||
"randomizer.dungeon.pottery.keys": "Key Pots",
|
||||
"randomizer.dungeon.pottery.lottery": "Lottery (All Pots and Large Blocks)",
|
||||
|
||||
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
|
||||
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
||||
@@ -69,7 +72,6 @@
|
||||
"randomizer.dungeon.dungeonintensity.3": "3: Dungeon Lobbies",
|
||||
"randomizer.dungeon.dungeonintensity.random": "Random",
|
||||
|
||||
"randomizer.dungeon.potshuffle": "Pot Shuffle",
|
||||
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
||||
|
||||
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
||||
|
||||
@@ -22,15 +22,20 @@
|
||||
"width": 45
|
||||
}
|
||||
},
|
||||
"keydropshuffle": {
|
||||
"keydropshuffle": { "type": "checkbox" },
|
||||
"pottery": {
|
||||
"type": "selectbox",
|
||||
"default": "none",
|
||||
"options": [
|
||||
"none",
|
||||
"keydrops",
|
||||
"potsanity"
|
||||
]
|
||||
"keys",
|
||||
"lottery"
|
||||
],
|
||||
"config": {
|
||||
"width": 35
|
||||
}
|
||||
},
|
||||
"dropshuffle": { "type": "checkbox" },
|
||||
"potshuffle": { "type": "checkbox" },
|
||||
"experimental": { "type": "checkbox" },
|
||||
"dungeon_counters": {
|
||||
|
||||
@@ -93,9 +93,11 @@ SETTINGSTOPROCESS = {
|
||||
"compassshuffle": "compassshuffle",
|
||||
"smallkeyshuffle": "keyshuffle",
|
||||
"bigkeyshuffle": "bigkeyshuffle",
|
||||
"keydropshuffle": "keydropshuffle",
|
||||
"dungeondoorshuffle": "door_shuffle",
|
||||
"dungeonintensity": "intensity",
|
||||
"keydropshuffle": "keydropshuffle",
|
||||
"dropshuffle": "dropshuffle",
|
||||
"pottery": "pottery",
|
||||
"potshuffle": "shufflepots",
|
||||
"experimental": "experimental",
|
||||
"dungeon_counters": "dungeon_counters",
|
||||
|
||||
@@ -65,45 +65,40 @@ def create_item_pool_config(world):
|
||||
config.static_placement = {}
|
||||
config.location_groups = {}
|
||||
for player in range(1, world.players + 1):
|
||||
config.static_placement[player] = vanilla_mapping.copy()
|
||||
if world.keydropshuffle[player] != 'none':
|
||||
config.static_placement[player] = defaultdict(list)
|
||||
config.static_placement[player].update(vanilla_mapping)
|
||||
if world.dropshuffle[player]:
|
||||
for item, locs in keydrop_vanilla_mapping.items():
|
||||
if item in config.static_placement[player]:
|
||||
config.static_placement[player][item].extend(locs)
|
||||
else:
|
||||
config.static_placement[player][item] = list(locs)
|
||||
if world.keydropshuffle[player] == 'potsanity':
|
||||
if world.pottery[player] != 'none':
|
||||
for item, locs in potkeys_vanilla_mapping.items():
|
||||
config.static_placement[player][item].extend(locs)
|
||||
if world.pottery[player] == 'lottery':
|
||||
for super_tile, pot_list in vanilla_pots.items():
|
||||
for pot_index, pot in enumerate(pot_list):
|
||||
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
|
||||
item = pot_items[pot.item]
|
||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||
location = f'{pot.room} {descriptor}'
|
||||
if item in config.static_placement[player]:
|
||||
config.static_placement[player][item].append(location)
|
||||
else:
|
||||
config.static_placement[player][item] = list([location])
|
||||
if world.shopsanity[player]:
|
||||
for item, locs in shop_vanilla_mapping.items():
|
||||
if item in config.static_placement[player]:
|
||||
config.static_placement[player][item].extend(locs)
|
||||
else:
|
||||
config.static_placement[player][item] = list(locs)
|
||||
if world.retro[player]:
|
||||
for item, locs in retro_vanilla_mapping.items():
|
||||
if item in config.static_placement[player]:
|
||||
config.static_placement[player][item].extend(locs)
|
||||
else:
|
||||
config.static_placement[player][item] = list(locs)
|
||||
# universal keys
|
||||
universal_key_locations = []
|
||||
for item, locs in vanilla_mapping.items():
|
||||
if 'Small Key' in item:
|
||||
universal_key_locations.extend(locs)
|
||||
if world.keydropshuffle[player] != 'none':
|
||||
if world.dropshuffle[player]:
|
||||
for item, locs in keydrop_vanilla_mapping.items():
|
||||
if 'Small Key' in item:
|
||||
universal_key_locations.extend(locs)
|
||||
if world.pottery[player] != 'none':
|
||||
for item, locs in potkeys_vanilla_mapping.items():
|
||||
universal_key_locations.extend(locs)
|
||||
if world.shopsanity[player]:
|
||||
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
|
||||
single_arrow_placement.append('Red Shield Shop - Right')
|
||||
@@ -677,25 +672,31 @@ vanilla_mapping = {
|
||||
|
||||
|
||||
keydrop_vanilla_mapping = {
|
||||
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
||||
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key'],
|
||||
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop'],
|
||||
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Eyegore Key Drop'],
|
||||
'Small Key (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||
'Hyrule Castle - Key Rat Key Drop'],
|
||||
'Big Key (Escape)': ['Hyrule Castle - Big Key Drop'],
|
||||
'Small Key (Agahnims Tower)': ['Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'],
|
||||
'Small Key (Skull Woods)': ['Skull Woods - Spike Corner Key Drop'],
|
||||
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop'],
|
||||
'Small Key (Misery Mire)': ['Misery Mire - Conveyor Crystal Key Drop'],
|
||||
'Small Key (Turtle Rock)': ['Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||
'Small Key (Ganons Tower)': ['Ganons Tower - Mini Helmasaur Key Drop'],
|
||||
}
|
||||
|
||||
potkeys_vanilla_mapping = {
|
||||
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
||||
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key'],
|
||||
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Square Pot Key'],
|
||||
'Small Key (Thieves Town)': ["Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key"],
|
||||
'Small Key (Skull Woods)': ['Skull Woods - West Lobby Pot Key', 'Skull Woods - Spike Corner Key Drop'],
|
||||
'Small Key (Skull Woods)': ['Skull Woods - West Lobby Pot Key'],
|
||||
'Small Key (Swamp Palace)': ['Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key',
|
||||
'Swamp Palace - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
|
||||
'Swamp Palace - Waterway Pot Key'],
|
||||
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
||||
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
||||
'Small Key (Misery Mire)': ['Misery Mire - Spikes Pot Key',
|
||||
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop'],
|
||||
'Small Key (Turtle Rock)': ['Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||
'Small Key (Ice Palace)': ['Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
||||
'Small Key (Misery Mire)': ['Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key'],
|
||||
'Small Key (Ganons Tower)': ['Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch Pot Key',
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key', 'Ganons Tower - Mini Helmasaur Key Drop'],
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key'],
|
||||
}
|
||||
|
||||
shop_vanilla_mapping = {
|
||||
|
||||
Reference in New Issue
Block a user