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._portal_cache = {}
|
||||||
self.sanc_portal = {}
|
self.sanc_portal = {}
|
||||||
self.fish = BabelFish()
|
self.fish = BabelFish()
|
||||||
|
self.pot_contents = {}
|
||||||
|
|
||||||
for player in range(1, players + 1):
|
for player in range(1, players + 1):
|
||||||
# If World State is Retro, set to Open and set Retro flag
|
# If World State is Retro, set to Open and set Retro flag
|
||||||
@@ -381,6 +382,8 @@ class World(object):
|
|||||||
location.item = item
|
location.item = item
|
||||||
item.location = location
|
item.location = location
|
||||||
item.world = self
|
item.world = self
|
||||||
|
if location.player != item.player and location.type == LocationType.Pot:
|
||||||
|
self.pot_contents[location.player].multiworld_count += 1
|
||||||
if collect:
|
if collect:
|
||||||
self.state.collect(item, location.event, location)
|
self.state.collect(item, location.event, location)
|
||||||
|
|
||||||
@@ -2123,8 +2126,15 @@ class Location(object):
|
|||||||
self.pot = None
|
self.pot = None
|
||||||
|
|
||||||
def can_fill(self, state, item, check_access=True):
|
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)))
|
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):
|
def can_reach(self, state):
|
||||||
return self.parent_region.can_reach(state) and self.access_rule(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_shuffle': self.world.enemy_shuffle,
|
||||||
'enemy_health': self.world.enemy_health,
|
'enemy_health': self.world.enemy_health,
|
||||||
'enemy_damage': self.world.enemy_damage,
|
'enemy_damage': self.world.enemy_damage,
|
||||||
'potshuffle': self.world.potshuffle,
|
|
||||||
'players': self.world.players,
|
'players': self.world.players,
|
||||||
'teams': self.world.teams,
|
'teams': self.world.teams,
|
||||||
'experimental': self.world.experimental,
|
'experimental': self.world.experimental,
|
||||||
'keydropshuffle': self.world.keydropshuffle,
|
'dropshuffle': self.world.dropshuffle,
|
||||||
|
'pottery': self.world.pottery,
|
||||||
|
'potshuffle': self.world.potshuffle,
|
||||||
'shopsanity': self.world.shopsanity,
|
'shopsanity': self.world.shopsanity,
|
||||||
'pseudoboots': self.world.pseudoboots,
|
'pseudoboots': self.world.pseudoboots,
|
||||||
'triforcegoal': self.world.treasure_hunt_count,
|
'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(f"Link's House Shuffled: {yn(self.metadata['shufflelinks'])}\n")
|
||||||
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player])
|
outfile.write('Door Shuffle: %s\n' % self.metadata['door_shuffle'][player])
|
||||||
outfile.write('Intensity: %s\n' % self.metadata['intensity'][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 ''
|
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))
|
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 ''
|
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 shuffle: %s\n' % self.metadata['enemy_shuffle'][player])
|
||||||
outfile.write('Enemy health: %s\n' % self.metadata['enemy_health'][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('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"Hints: {yn(self.metadata['hints'][player])}\n")
|
||||||
outfile.write(f"Experimental: {yn(self.metadata['experimental'][player])}\n")
|
outfile.write(f"Experimental: {yn(self.metadata['experimental'][player])}\n")
|
||||||
outfile.write(f"Shopsanity: {yn(self.metadata['shopsanity'][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}
|
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||||
func_mode = {"normal": 0, "hard": 1, "expert": 2}
|
func_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||||
|
|
||||||
# byte 3: SKMM PIII (shop, keydrop, mixed, palettes, intensity)
|
# byte 3: S?MM PIII (shop, unused, mixed, palettes, intensity)
|
||||||
# todo keydrop is not longer a switch
|
# keydrop now has it's own byte
|
||||||
mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
|
mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
|
||||||
# intensity is 3 bits (reserves 4-7 levels)
|
# 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}
|
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}
|
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}
|
boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3}
|
||||||
enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 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_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
|
||||||
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
|
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}
|
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}
|
algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4}
|
||||||
|
|
||||||
# additions
|
# additions
|
||||||
@@ -2779,10 +2796,12 @@ class Settings(object):
|
|||||||
(goal_mode[w.goal[p]] << 5) | (diff_mode[w.difficulty[p]] << 3)
|
(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),
|
| (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)
|
(0x80 if w.shopsanity[p] else 0) | (mixed_travel_mode[w.mixed_travel[p]] << 4)
|
||||||
| (mixed_travel_mode[w.mixed_travel[p]] << 4) | (0x8 if w.standardize_palettes[p] == "original" else 0)
|
| (0x8 if w.standardize_palettes[p] == "original" else 0)
|
||||||
| (0 if w.intensity[p] == "random" else w.intensity[p]),
|
| (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)
|
((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),
|
| (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.hints[p] = True if settings[2] & 0x01 else False
|
||||||
args.retro[p] = True if settings[1] & 0x01 else False
|
args.retro[p] = True if settings[1] & 0x01 else False
|
||||||
args.shopsanity[p] = True if settings[3] & 0x80 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.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4]
|
||||||
args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
|
args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
|
||||||
intensity = settings[3] & 0x7
|
intensity = settings[3] & 0x7
|
||||||
args.intensity[p] = "random" if intensity == 0 else intensity
|
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.crystals_gt[p] = "random" if cgt == 8 else cgt
|
||||||
args.experimental[p] = True if settings[4] & 0x1 else False
|
args.experimental[p] = True if settings[5] & 0x1 else False
|
||||||
cgan = (settings[5] & 0xf8) >> 3
|
|
||||||
|
cgan = (settings[6] & 0xf8) >> 3
|
||||||
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
|
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
|
||||||
args.openpyramid[p] = True if settings[5] & 0x4 else False
|
args.openpyramid[p] = True if settings[6] & 0x4 else False
|
||||||
args.bigkeyshuffle[p] = True if settings[6] & 0x80 else False
|
|
||||||
args.keyshuffle[p] = True if settings[6] & 0x40 else False
|
args.bigkeyshuffle[p] = True if settings[7] & 0x80 else False
|
||||||
args.mapshuffle[p] = True if settings[6] & 0x20 else False
|
args.keyshuffle[p] = True if settings[7] & 0x40 else False
|
||||||
args.compassshuffle[p] = True if settings[6] & 0x10 else False
|
args.mapshuffle[p] = True if settings[7] & 0x20 else False
|
||||||
args.shufflebosses[p] = r(boss_mode)[(settings[6] & 0xc) >> 2]
|
args.compassshuffle[p] = True if settings[7] & 0x10 else False
|
||||||
args.shuffleenemies[p] = r(enemy_mode)[settings[6] & 0x3]
|
args.shufflebosses[p] = r(boss_mode)[(settings[7] & 0xc) >> 2]
|
||||||
args.enemy_health[p] = r(e_health)[(settings[7] & 0xE0) >> 5]
|
args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3]
|
||||||
args.enemy_damage[p] = r(e_dmg)[(settings[7] & 0x18) >> 3]
|
|
||||||
|
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.shufflepots[p] = True if settings[7] & 0x4 else False
|
||||||
args.bombbag[p] = True if settings[7] & 0x2 else False
|
args.bombbag[p] = True if settings[8] & 0x2 else False
|
||||||
args.shufflelinks[p] = True if settings[7] & 0x1 else False
|
args.shufflelinks[p] = True if settings[8] & 0x1 else False
|
||||||
if len(settings) > 8:
|
if len(settings) > 9:
|
||||||
args.restrict_boss_items[p] = True if r(rb_mode)[(settings[8] & 0x80) >> 6] else False
|
args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0x80) >> 6]
|
||||||
|
|
||||||
|
|
||||||
class KeyRuleType(FastEnum):
|
class KeyRuleType(FastEnum):
|
||||||
|
|||||||
16
CLI.py
16
CLI.py
@@ -88,6 +88,10 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
if ret.keysanity:
|
if ret.keysanity:
|
||||||
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
|
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:
|
if multiargs.multi:
|
||||||
defaults = copy.deepcopy(ret)
|
defaults = copy.deepcopy(ret)
|
||||||
for player in range(1, multiargs.multi + 1):
|
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',
|
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
|
||||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||||
'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code',
|
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery',
|
||||||
'reduce_flashing', 'shuffle_sfx']:
|
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx']:
|
||||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
setattr(ret, name, {1: value})
|
setattr(ret, name, {1: value})
|
||||||
@@ -150,7 +154,6 @@ def parse_settings():
|
|||||||
"overworld_map": "default",
|
"overworld_map": "default",
|
||||||
"pseudoboots": False,
|
"pseudoboots": False,
|
||||||
|
|
||||||
"shufflepots": False,
|
|
||||||
"shuffleenemies": "none",
|
"shuffleenemies": "none",
|
||||||
"shufflebosses": "none",
|
"shufflebosses": "none",
|
||||||
"enemy_damage": "default",
|
"enemy_damage": "default",
|
||||||
@@ -158,7 +161,10 @@ def parse_settings():
|
|||||||
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
|
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
|
||||||
|
|
||||||
"shopsanity": False,
|
"shopsanity": False,
|
||||||
"keydropshuffle": 'none',
|
'keydropshuffle': False,
|
||||||
|
'dropshuffle': False,
|
||||||
|
'pottery': 'none',
|
||||||
|
'shufflepots': False,
|
||||||
"mapshuffle": False,
|
"mapshuffle": False,
|
||||||
"compassshuffle": False,
|
"compassshuffle": False,
|
||||||
"keyshuffle": False,
|
"keyshuffle": False,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from enum import unique, Flag
|
|||||||
from typing import DefaultDict, Dict, List
|
from typing import DefaultDict, Dict, List
|
||||||
|
|
||||||
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
||||||
|
from BaseClasses import PotFlags, LocationType
|
||||||
from Doors import reset_portals
|
from Doors import reset_portals
|
||||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
||||||
from Dungeons import dungeon_bigs, dungeon_hints
|
from Dungeons import dungeon_bigs, dungeon_hints
|
||||||
@@ -378,7 +379,7 @@ def choose_portals(world, player):
|
|||||||
if world.doorShuffle[player] in ['basic', 'crossed']:
|
if world.doorShuffle[player] in ['basic', 'crossed']:
|
||||||
cross_flag = world.doorShuffle[player] == 'crossed'
|
cross_flag = world.doorShuffle[player] == 'crossed'
|
||||||
# key drops allow the big key in the right place in Desert Tiles 2
|
# 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'
|
std_flag = world.mode[player] == 'standard'
|
||||||
# roast incognito doors
|
# roast incognito doors
|
||||||
world.get_room(0x60, player).delete(5)
|
world.get_room(0x60, player).delete(5)
|
||||||
@@ -994,10 +995,15 @@ def cross_dungeon(world, player):
|
|||||||
|
|
||||||
assign_cross_keys(dungeon_builders, 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))
|
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 = 34
|
||||||
target_items = 35 if world.retro[player] else 96
|
if world.retro[player]:
|
||||||
|
target_items += 1 if world.dropshuffle[player] else 0 # the hc big key
|
||||||
else:
|
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
|
d_items = target_items - all_dungeon_items_cnt
|
||||||
world.pool_adjustment[player] = d_items
|
world.pool_adjustment[player] = d_items
|
||||||
smooth_door_pairs(world, player)
|
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"))
|
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
|
||||||
start = time.process_time()
|
start = time.process_time()
|
||||||
if world.retro[player]:
|
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:
|
else:
|
||||||
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
|
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
|
||||||
total_keys = remaining
|
total_keys = remaining
|
||||||
@@ -1074,7 +1084,6 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
total_candidates += builder.key_doors_num
|
total_candidates += builder.key_doors_num
|
||||||
start_regions_map[name] = start_regions
|
start_regions_map[name] = start_regions
|
||||||
|
|
||||||
|
|
||||||
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
||||||
for name, builder in dungeon_builders.items():
|
for name, builder in dungeon_builders.items():
|
||||||
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
||||||
@@ -1251,7 +1260,13 @@ def refine_hints(dungeon_builders):
|
|||||||
for region in builder.master_sector.regions:
|
for region in builder.master_sector.regions:
|
||||||
for location in region.locations:
|
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 not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
|
||||||
location.hint_text = dungeon_hints[name]
|
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]
|
||||||
|
|
||||||
|
|
||||||
def refine_boss_exits(world, player):
|
def refine_boss_exits(world, player):
|
||||||
@@ -3015,7 +3030,8 @@ palette_map = {
|
|||||||
'Tower of Hera': (0x6, None),
|
'Tower of Hera': (0x6, None),
|
||||||
'Thieves Town': (0x17, None), # the attic uses 0x23
|
'Thieves Town': (0x17, None), # the attic uses 0x23
|
||||||
'Turtle Rock': (0x18, 0x19, 'TR Boss SW'),
|
'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:
|
# implications:
|
||||||
|
|||||||
39
Fill.py
39
Fill.py
@@ -7,7 +7,7 @@ from BaseClasses import CollectionState, FillError, LocationType
|
|||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from Regions import shop_to_location_table, retro_shops
|
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_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):
|
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)
|
fill_locations.remove(l)
|
||||||
filtered_fill(world, placeholder_items, placeholder_locations)
|
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':
|
if world.algorithm == 'vanilla_fill':
|
||||||
fast_vanilla_fill(world, restitempool, fill_locations)
|
fast_vanilla_fill(world, restitempool, fill_locations)
|
||||||
else:
|
else:
|
||||||
@@ -445,11 +447,14 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
if unplaced or unfilled:
|
if unplaced or unfilled:
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
||||||
|
|
||||||
# convert Arrows 5 and Nothing when necessary
|
for loc in world.get_locations():
|
||||||
invalid_locations = [loc for loc in world.get_locations() if loc.item.name in {'Arrows (5)', 'Nothing'} and
|
# convert Arrows 5 and Nothing when necessary
|
||||||
(loc.type != LocationType.Pot or loc.item.player != loc.player)]
|
if (loc.item.name in {'Arrows (5)', 'Nothing'}
|
||||||
for i_loc in invalid_locations:
|
and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
|
||||||
i_loc.item = ItemFactory(invalid_location_replacement[i_loc.item.name], i_loc.item.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)'}
|
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)
|
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):
|
def filtered_fill(world, item_pool, fill_locations):
|
||||||
while item_pool and fill_locations:
|
while item_pool and fill_locations:
|
||||||
item_to_place = item_pool.pop()
|
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, '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, '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, '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 - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||||
'Kakariko Well - Bottom'],
|
'Kakariko Well - Bottom'],
|
||||||
['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']),
|
['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, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||||
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||||
create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
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, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||||
create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||||
create_lw_region(player, 'Hobo Bridge', ['Hobo']),
|
create_lw_region(player, 'Hobo Bridge', ['Hobo']),
|
||||||
|
|||||||
@@ -389,12 +389,15 @@ def generate_itempool(world, player):
|
|||||||
|
|
||||||
if world.retro[player]:
|
if world.retro[player]:
|
||||||
set_up_take_anys(world, player)
|
set_up_take_anys(world, player)
|
||||||
if world.keydropshuffle[player] != 'none':
|
if world.dropshuffle[player]:
|
||||||
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32
|
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)
|
create_dynamic_shop_locations(world, player)
|
||||||
|
|
||||||
if world.keydropshuffle[player] == 'potsanity':
|
if world.pottery[player] == 'lottery':
|
||||||
add_pot_contents(world, player)
|
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.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.experimental = args.experimental.copy()
|
||||||
world.dungeon_counters = args.dungeon_counters.copy()
|
world.dungeon_counters = args.dungeon_counters.copy()
|
||||||
world.potshuffle = args.shufflepots.copy()
|
|
||||||
world.fish = fish
|
world.fish = fish
|
||||||
world.shopsanity = args.shopsanity.copy()
|
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.mixed_travel = args.mixed_travel.copy()
|
||||||
world.standardize_palettes = args.standardize_palettes.copy()
|
world.standardize_palettes = args.standardize_palettes.copy()
|
||||||
world.treasure_hunt_count = args.triforce_goal.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"))
|
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.potshuffle[player]:
|
if world.potshuffle[player]:
|
||||||
if world.keydropshuffle[player] != 'potsanity':
|
if world.pottery[player] != 'lottery':
|
||||||
shuffle_pots(world, player)
|
shuffle_pots(world, player)
|
||||||
else:
|
else:
|
||||||
shuffle_pot_switches(world, player)
|
shuffle_pot_switches(world, player)
|
||||||
@@ -401,7 +402,9 @@ def copy_world(world):
|
|||||||
ret.intensity = world.intensity.copy()
|
ret.intensity = world.intensity.copy()
|
||||||
ret.experimental = world.experimental.copy()
|
ret.experimental = world.experimental.copy()
|
||||||
ret.shopsanity = world.shopsanity.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.mixed_travel = world.mixed_travel.copy()
|
||||||
ret.standardize_palettes = world.standardize_palettes.copy()
|
ret.standardize_palettes = world.standardize_palettes.copy()
|
||||||
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import shlex
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
|
from BaseClasses import PotItem, PotFlags
|
||||||
import Items
|
import Items
|
||||||
import Regions
|
import Regions
|
||||||
|
import PotShuffle
|
||||||
|
|
||||||
|
|
||||||
class ReceivedItem:
|
class ReceivedItem:
|
||||||
@@ -57,8 +59,13 @@ class Context:
|
|||||||
self.key_drop_mode = False
|
self.key_drop_mode = False
|
||||||
self.shop_mode = False
|
self.shop_mode = False
|
||||||
self.retro_mode = False
|
self.retro_mode = False
|
||||||
|
self.pottery_mode = False
|
||||||
|
self.mystery_mode = False
|
||||||
self.ignore_count = 0
|
self.ignore_count = 0
|
||||||
|
|
||||||
|
self.lookup_name_to_id = {}
|
||||||
|
self.lookup_id_to_name = {}
|
||||||
|
|
||||||
def color_code(*args):
|
def color_code(*args):
|
||||||
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
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,
|
'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_START = WRAM_START + 0xF000
|
||||||
SAVEDATA_SIZE = 0x500
|
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_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
||||||
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||||
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 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),
|
'Purple Chest': (0x3c9, 0x10),
|
||||||
"Link's Uncle": (0x3c6, 0x1),
|
"Link's Uncle": (0x3c6, 0x1),
|
||||||
'Hobo': (0x3c9, 0x1)}
|
'Hobo': (0x3c9, 0x1)}
|
||||||
|
location_table_pot_items = {}
|
||||||
|
location_table_sprite_items = {}
|
||||||
|
|
||||||
SNES_DISCONNECTED = 0
|
SNES_DISCONNECTED = 0
|
||||||
SNES_CONNECTING = 1
|
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]}
|
ctx.player_names = {p: n for p, n in args[1]}
|
||||||
msgs = []
|
msgs = []
|
||||||
if ctx.locations_checked:
|
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:
|
if ctx.locations_scouted:
|
||||||
msgs.append(['LocationScouts', list(ctx.locations_scouted)])
|
msgs.append(['LocationScouts', list(ctx.locations_scouted)])
|
||||||
if msgs:
|
if msgs:
|
||||||
@@ -689,7 +702,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||||||
elif start_index != len(ctx.items_received):
|
elif start_index != len(ctx.items_received):
|
||||||
sync_msg = [['Sync']]
|
sync_msg = [['Sync']]
|
||||||
if ctx.locations_checked:
|
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)
|
await send_msgs(ctx.socket, sync_msg)
|
||||||
if start_index == len(ctx.items_received):
|
if start_index == len(ctx.items_received):
|
||||||
for item in items:
|
for item in items:
|
||||||
@@ -701,7 +714,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||||||
if location not in ctx.locations_info:
|
if location not in ctx.locations_info:
|
||||||
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
|
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
|
||||||
item_name = replacements.get(item, get_item_name_from_id(item))
|
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.locations_info[location] = (item, player)
|
||||||
ctx.watcher_event.set()
|
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')
|
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_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')
|
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':
|
if cmd == 'Print':
|
||||||
logging.info(args)
|
logging.info(args)
|
||||||
@@ -779,10 +793,10 @@ async def console_loop(ctx : Context):
|
|||||||
for index, item in enumerate(ctx.items_received, 1):
|
for index, item in enumerate(ctx.items_received, 1):
|
||||||
logging.info('%s from %s (%s) (%d/%d in list)' % (
|
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'),
|
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':
|
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 type(v) is int and not filter_location(ctx, k)]:
|
||||||
if location not in ctx.locations_checked:
|
if location not in ctx.locations_checked:
|
||||||
logging.info('Missing: ' + location)
|
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})'
|
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:
|
if type(address) is str:
|
||||||
return address
|
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):
|
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
|
return True
|
||||||
if not ctx.shop_mode and location in Regions.flat_normal_shops:
|
if not ctx.shop_mode and location in Regions.flat_normal_shops:
|
||||||
return True
|
return True
|
||||||
@@ -821,6 +839,31 @@ def filter_location(ctx, location):
|
|||||||
return False
|
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):
|
async def track_locations(ctx : Context, roomid, roomdata):
|
||||||
new_locations = []
|
new_locations = []
|
||||||
|
|
||||||
@@ -832,9 +875,12 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
|||||||
|
|
||||||
if ctx.mode_flags is None:
|
if ctx.mode_flags is None:
|
||||||
flags = await snes_read(ctx, MODE_FLAGS, 1)
|
flags = await snes_read(ctx, MODE_FLAGS, 1)
|
||||||
|
ctx.mode_flags = flags
|
||||||
ctx.key_drop_mode = flags[0] & 0x1
|
ctx.key_drop_mode = flags[0] & 0x1
|
||||||
ctx.shop_mode = flags[0] & 0x2
|
ctx.shop_mode = flags[0] & 0x2
|
||||||
ctx.retro_mode = flags[0] & 0x4
|
ctx.retro_mode = flags[0] & 0x4
|
||||||
|
ctx.pottery_mode = flags[0] & 0x8
|
||||||
|
ctx.mystery_mode = flags[0] & 0x10
|
||||||
|
|
||||||
def new_check(location):
|
def new_check(location):
|
||||||
ctx.locations_checked.add(location)
|
ctx.locations_checked.add(location)
|
||||||
@@ -842,8 +888,9 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
|||||||
if ignored:
|
if ignored:
|
||||||
ctx.ignore_count += 1
|
ctx.ignore_count += 1
|
||||||
else:
|
else:
|
||||||
logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{ctx.total_locations})")
|
total = '???' if ctx.mystery_mode else ctx.total_locations
|
||||||
new_locations.append(Regions.lookup_name_to_id[location])
|
logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{total})")
|
||||||
|
new_locations.append(ctx.lookup_name_to_id[location])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if ctx.shop_mode or ctx.retro_mode:
|
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:
|
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
|
||||||
new_check(location)
|
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]])
|
await send_msgs(ctx.socket, [['LocationChecks', new_locations]])
|
||||||
|
|
||||||
async def game_watcher(ctx : Context):
|
async def game_watcher(ctx : Context):
|
||||||
@@ -955,7 +1018,7 @@ async def game_watcher(ctx : Context):
|
|||||||
item = ctx.items_received[recv_index]
|
item = ctx.items_received[recv_index]
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
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'),
|
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
|
recv_index += 1
|
||||||
snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
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]))
|
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:
|
if scout_location > 0 and scout_location not in ctx.locations_scouted:
|
||||||
ctx.locations_scouted.add(scout_location)
|
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 send_msgs(ctx.socket, [['LocationScouts', [scout_location]]])
|
||||||
await track_locations(ctx, roomid, roomdata)
|
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))
|
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||||
|
|
||||||
ctx = Context(args.snes, args.connect, args.password)
|
ctx = Context(args.snes, args.connect, args.password)
|
||||||
|
init_lookups(ctx)
|
||||||
|
|
||||||
input_task = asyncio.create_task(console_loop(ctx))
|
input_task = asyncio.create_task(console_loop(ctx))
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import urllib.request
|
|||||||
import websockets
|
import websockets
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
|
from BaseClasses import PotItem, PotFlags
|
||||||
import Items
|
import Items
|
||||||
import Regions
|
import Regions
|
||||||
|
import PotShuffle
|
||||||
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
@@ -40,6 +42,9 @@ class Context:
|
|||||||
self.clients = []
|
self.clients = []
|
||||||
self.received_items = {}
|
self.received_items = {}
|
||||||
|
|
||||||
|
self.lookup_name_to_id = {}
|
||||||
|
self.lookup_id_to_name = {}
|
||||||
|
|
||||||
async def send_msgs(websocket, msgs):
|
async def send_msgs(websocket, msgs):
|
||||||
if not websocket or not websocket.open or websocket.closed:
|
if not websocket or not websocket.open or websocket.closed:
|
||||||
return
|
return
|
||||||
@@ -176,7 +181,8 @@ def register_location_checks(ctx : Context, team, slot, locations):
|
|||||||
recvd_items.append(new_item)
|
recvd_items.append(new_item)
|
||||||
if slot != target_player:
|
if slot != target_player:
|
||||||
broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]])
|
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
|
found_items = True
|
||||||
send_new_items(ctx)
|
send_new_items(ctx)
|
||||||
|
|
||||||
@@ -249,11 +255,11 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
|
|||||||
return
|
return
|
||||||
locs = []
|
locs = []
|
||||||
for location in args:
|
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']])
|
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
|
||||||
return
|
return
|
||||||
loc_name = list(Regions.lookup_id_to_name.keys())[location - 1]
|
loc_name = list(ctx.lookup_id_to_name.keys())[location - 1]
|
||||||
target_item, target_player = ctx.locations[(Regions.lookup_name_to_id[loc_name], client.slot)]
|
target_item, target_player = ctx.locations[(ctx.lookup_name_to_id[loc_name], client.slot)]
|
||||||
|
|
||||||
replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D}
|
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]
|
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] != '/':
|
if command[0][0] != '/':
|
||||||
notify_all(ctx, '[Server]: ' + input)
|
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():
|
async def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--host', default=None)
|
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))
|
logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||||
|
|
||||||
ctx = Context(args.host, args.port, args.password)
|
ctx = Context(args.host, args.port, args.password)
|
||||||
|
init_lookups(ctx)
|
||||||
ctx.data_filename = args.multidata
|
ctx.data_filename = args.multidata
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -173,7 +173,9 @@ def roll_settings(weights):
|
|||||||
ret.shufflelinks = get_choice('shufflelinks') == 'on'
|
ret.shufflelinks = get_choice('shufflelinks') == 'on'
|
||||||
ret.pseudoboots = get_choice('pseudoboots') == 'on'
|
ret.pseudoboots = get_choice('pseudoboots') == 'on'
|
||||||
ret.shopsanity = get_choice('shopsanity') == '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.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'
|
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.enemy_health = get_choice('enemy_health')
|
||||||
|
|
||||||
ret.shufflepots = get_choice('pot_shuffle') == 'on'
|
|
||||||
|
|
||||||
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
|
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
|
||||||
|
|
||||||
inventoryweights = weights.get('startinventory', {})
|
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'),
|
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, 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(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')],
|
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'),
|
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')],
|
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 = {
|
key_drop_data = {
|
||||||
'Hyrule Castle - Map Guard Key Drop': ['Drop', (0x09E20C, 0x72), 'in Hyrule Castle', 'Small Key (Escape)'],
|
'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), '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), '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), 'in Hyrule Castle', 'Big 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 Eastern Palace', 'Small Key (Eastern Palace)'],
|
'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), '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 Desert Palace', 'Small Key (Desert 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 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 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), 'in Castle Tower', 'Small Key (Agahnims Tower)'],
|
'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), '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 Swamp Palace', 'Small Key (Swamp Palace)'],
|
'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 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 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 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 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 Skull Woods', 'Small Key (Skull Woods)'],
|
'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), 'near Mothula', '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 Thieves' Town", 'Small Key (Thieves Town)'],
|
"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 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), 'in Ice Palace', 'Small Key (Ice Palace)'],
|
'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), '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, '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, '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 Misery Mire', 'Small Key (Misery Mire)'],
|
'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 forgotten 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), 'in Misery 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), 'in Turtle Rock', 'Small Key (Turtle Rock)'],
|
'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), '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 Ganon's Tower", 'Small Key (Ganons Tower)'],
|
'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 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 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), "atop 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):
|
class PotSecretTable(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.room_map = defaultdict(list)
|
self.room_map = defaultdict(list)
|
||||||
|
self.multiworld_count = 0
|
||||||
|
|
||||||
def write_pot_data_to_rom(self, rom):
|
def write_pot_data_to_rom(self, rom):
|
||||||
pointer_address = 0x140000 # pots currently in bank 28
|
pointer_address = 0x140000 # pots currently in bank 28
|
||||||
|
|||||||
@@ -2,6 +2,38 @@
|
|||||||
|
|
||||||
## New Features
|
## 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
|
## Restricted Item Placement Algorithm
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
68
Regions.py
68
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, '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, '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, '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 - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||||
'Kakariko Well - Bottom'],
|
'Kakariko Well - Bottom'],
|
||||||
['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']),
|
['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, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||||
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||||
create_lw_region(player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
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, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||||
create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
create_cave_region(player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||||
create_lw_region(player, 'Hobo Bridge', ['Hobo']),
|
create_lw_region(player, 'Hobo Bridge', ['Hobo']),
|
||||||
@@ -993,16 +993,19 @@ def adjust_locations(world, player):
|
|||||||
world.pot_contents[player] = PotSecretTable()
|
world.pot_contents[player] = PotSecretTable()
|
||||||
for location, datum in key_drop_data.items():
|
for location, datum in key_drop_data.items():
|
||||||
loc = world.get_location(location, player)
|
loc = world.get_location(location, player)
|
||||||
if 'Drop' == datum[0]:
|
drop_location = 'Drop' == datum[0]
|
||||||
|
if drop_location:
|
||||||
loc.type = LocationType.Drop
|
loc.type = LocationType.Drop
|
||||||
snes_address, room = datum[1]
|
snes_address, room, sprite_idx = datum[1]
|
||||||
loc.address = snes_address
|
loc.address = snes_address
|
||||||
else:
|
else:
|
||||||
loc.type = LocationType.Pot
|
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
|
loc.pot = pot
|
||||||
world.pot_contents[player].room_map[datum[1]].append(pot)
|
if (not world.dropshuffle[player] and drop_location)\
|
||||||
if world.keydropshuffle[player] == 'none':
|
or (not drop_location and world.pottery[player] == 'none'):
|
||||||
loc.skip = True
|
loc.skip = True
|
||||||
else:
|
else:
|
||||||
key_item = loc.item
|
key_item = loc.item
|
||||||
@@ -1019,23 +1022,14 @@ def adjust_locations(world, player):
|
|||||||
dungeon.big_key = key_item
|
dungeon.big_key = key_item
|
||||||
for super_tile, pot_list in vanilla_pots.items():
|
for super_tile, pot_list in vanilla_pots.items():
|
||||||
for pot_index, pot_orig in enumerate(pot_list):
|
for pot_index, pot_orig in enumerate(pot_list):
|
||||||
pot = pot_orig.copy()
|
if pot_orig.item == PotItem.Key:
|
||||||
if pot.item != PotItem.Key:
|
loc = next(location for location, datum in key_drop_data.items() if datum[1] == super_tile)
|
||||||
world.pot_contents[player].room_map[super_tile].append(pot)
|
pot = world.get_location(loc, player).pot
|
||||||
if world.keydropshuffle[player] == 'potsanity':
|
else:
|
||||||
if (pot.item not in [PotItem.Key, PotItem.Hole]
|
pot = pot_orig.copy()
|
||||||
and (pot.item != PotItem.Switch or world.potshuffle[player])):
|
world.pot_contents[player].room_map[super_tile].append(pot)
|
||||||
parent = world.get_region(pot.room, player)
|
if world.pottery[player] == 'lottery':
|
||||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
create_pot_location(pot, pot_index, super_tile, world, player)
|
||||||
# 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.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
index = 0
|
index = 0
|
||||||
for shop, location_list in shop_to_location_table.items():
|
for shop, location_list in shop_to_location_table.items():
|
||||||
@@ -1057,6 +1051,28 @@ def adjust_locations(world, player):
|
|||||||
location.skip = True
|
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])
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
# 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 = {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_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 = {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)
|
lookup_name_to_id.update(shop_table_by_location)
|
||||||
|
|||||||
47
Rom.py
47
Rom.py
@@ -35,7 +35,7 @@ from source.item.FillUtil import valid_pot_items
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '8d13470d5a3127c6705846674cfb6209'
|
RANDOMIZERBASEHASH = 'dea3a3bc1435aa0895181c5d46dc6d43'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -554,7 +554,7 @@ class Sprite(object):
|
|||||||
def handle_native_dungeon(location, itemid):
|
def handle_native_dungeon(location, itemid):
|
||||||
# Keys in their native dungeon should use the original item code for keys
|
# Keys in their native dungeon should use the original item code for keys
|
||||||
if location.parent_region.dungeon:
|
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:
|
if location.item.bigkey:
|
||||||
return 0x32
|
return 0x32
|
||||||
if location.item.smallkey:
|
if location.item.smallkey:
|
||||||
@@ -604,12 +604,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
else:
|
else:
|
||||||
code = 0xF8
|
code = 0xF8
|
||||||
sprite_pointer = snes_to_pc(location.address)
|
sprite_pointer = snes_to_pc(location.address)
|
||||||
if code != 0xE4:
|
rom.write_byte(sprite_pointer, handle_native_dungeon(location, itemid))
|
||||||
rom.write_byte(sprite_pointer, handle_native_dungeon(location, itemid))
|
if code == 0xF9:
|
||||||
if code == 0xF9:
|
rom.write_byte(sprite_pointer+1, location.item.player)
|
||||||
rom.write_byte(sprite_pointer+1, location.item.player)
|
else:
|
||||||
else:
|
rom.write_byte(sprite_pointer+1, 0)
|
||||||
rom.write_byte(sprite_pointer+1, 0)
|
|
||||||
rom.write_byte(sprite_pointer+2, code)
|
rom.write_byte(sprite_pointer+2, code)
|
||||||
continue
|
continue
|
||||||
if location.address is None or (type(location.address) is int and location.address >= 0x400000):
|
if location.address is None or (type(location.address) is int and location.address >= 0x400000):
|
||||||
@@ -650,6 +649,13 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
if world.pot_contents[player].size() > 0x2800:
|
if world.pot_contents[player].size() > 0x2800:
|
||||||
raise Exception('Pot table is too big for current area')
|
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)
|
||||||
|
# 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
|
# patch entrance/exits/holes
|
||||||
for region in world.regions:
|
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)
|
valid_loc_by_dungeon = valid_dungeon_locations(valid_locations)
|
||||||
|
|
||||||
# fix hc big key problems (map and compass too)
|
# 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(0x151f1, 2)
|
||||||
rom.write_byte(0x15270, 2)
|
rom.write_byte(0x15270, 2)
|
||||||
sanctuary = world.get_region('Sanctuary', player)
|
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)
|
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(0x142A50, 1)
|
||||||
rom.write_byte(0x142A51, 1)
|
multiClientFlags = ((0x1 if world.dropshuffle[player] else 0)
|
||||||
if world.keydropshuffle[player] == 'potsanity':
|
| (0x2 if world.shopsanity[player] else 0)
|
||||||
rom.write_bytes(0x04DFD8, [0x18, 0x0B, 0x1C])
|
| (0x4 if world.retro[player] else 0)
|
||||||
rom.write_byte(0x04E002, 0xFF)
|
| (0x8 if world.pottery[player] in ['keys', 'lottery'] else 0)
|
||||||
# write_int16(0x142A52, credits_total - 216) # todo: this is now a delta
|
| (0x10 if is_mystery else 0))
|
||||||
|
rom.write_byte(0x142A51, multiClientFlags)
|
||||||
|
|
||||||
write_int16(rom, 0x187010, credits_total) # dynamic credits
|
write_int16(rom, 0x187010, credits_total) # dynamic credits
|
||||||
if credits_total != 216:
|
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
|
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
|
||||||
|
|
||||||
gametype = 0x04 # item
|
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
|
gametype |= 0x02 # entrance/door
|
||||||
if enemized:
|
if enemized:
|
||||||
gametype |= 0x01 # enemizer
|
gametype |= 0x01 # enemizer
|
||||||
@@ -1452,8 +1460,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x18003C, 0x00)
|
rom.write_byte(0x18003C, 0x00)
|
||||||
elif world.dungeon_counters[player] == 'on':
|
elif world.dungeon_counters[player] == 'on':
|
||||||
compass_mode = 0x02 # always on
|
compass_mode = 0x02 # always on
|
||||||
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla'
|
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||||
or world.dungeon_counters[player] == 'pickup' or world.keydropshuffle[player] != 'none'):
|
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] != 'none'):
|
||||||
compass_mode = 0x01 # show on pickup
|
compass_mode = 0x01 # show on pickup
|
||||||
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
|
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
|
||||||
compass_mode |= 0x80 # turn on locating dungeons
|
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(0xFED31, 0x0E) # preopen bombable exit
|
||||||
rom.write_byte(0xFEE41, 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:
|
for room in world.rooms:
|
||||||
if room.player == player and room.modified:
|
if room.player == player and room.modified:
|
||||||
rom.write_bytes(room.address(), room.rom_data())
|
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):
|
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]
|
blocks = [l for l in world.get_locations() if l.type == LocationType.Pot and l.pot.flags & PotFlags.Block]
|
||||||
for block_pot in blocks:
|
for block_pot in blocks:
|
||||||
add_rule(block_pot, lambda state: state.can_lift_rocks(player))
|
add_rule(block_pot, lambda state: state.can_lift_rocks(player))
|
||||||
|
|||||||
Binary file not shown.
@@ -65,12 +65,24 @@
|
|||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
"keydropshuffle" : {
|
"keydropshuffle" : {
|
||||||
"choices": [
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"dropshuffle" : {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"pottery" : {
|
||||||
|
"choices" : [
|
||||||
"none",
|
"none",
|
||||||
"keydrops",
|
"keys",
|
||||||
"potsanity"
|
"lottery"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"shufflepots": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
"mixed_travel" : {
|
"mixed_travel" : {
|
||||||
"choices": [
|
"choices": [
|
||||||
"prevent",
|
"prevent",
|
||||||
@@ -388,10 +400,6 @@
|
|||||||
"random"
|
"random"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"shufflepots": {
|
|
||||||
"action": "store_true",
|
|
||||||
"type": "bool"
|
|
||||||
},
|
|
||||||
"remote_items": {
|
"remote_items": {
|
||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
|
|||||||
@@ -254,11 +254,13 @@
|
|||||||
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
"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)" ],
|
"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)"],
|
"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:",
|
"dropshuffle": [ "Keys dropped by enemies are shuffled and other items can take their place. (default: %(default)s)"],
|
||||||
"None: No pots or enemies are included",
|
"pottery": [ "Controls how items under pots are shuffled and if other items can take their place:",
|
||||||
"Keydrops: Only key drops are included",
|
"None: No pots are changed",
|
||||||
"Potsanity: All key drops and all normal pots are included"
|
"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": [
|
"mixed_travel": [
|
||||||
"How to handle potential traversal between dungeon in Crossed door shuffle",
|
"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",
|
"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.compassshuffle": "Compasses",
|
||||||
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
|
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
|
||||||
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
||||||
"randomizer.dungeon.keydropshuffle": "Drops and Pots",
|
"randomizer.dungeon.keydropshuffle": "Drop and Pot Keys",
|
||||||
"randomizer.dungeon.keydropshuffle.none": "None",
|
"randomizer.dungeon.dropshuffle": "Shuffle Key Drops",
|
||||||
"randomizer.dungeon.keydropshuffle.keydrops": "Only Key Drops",
|
"randomizer.dungeon.potshuffle": "Pot Shuffle (Legacy)",
|
||||||
"randomizer.dungeon.keydropshuffle.potsanity": "All Pots and Key Drops",
|
"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": "Dungeon Door Shuffle",
|
||||||
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
||||||
@@ -69,7 +72,6 @@
|
|||||||
"randomizer.dungeon.dungeonintensity.3": "3: Dungeon Lobbies",
|
"randomizer.dungeon.dungeonintensity.3": "3: Dungeon Lobbies",
|
||||||
"randomizer.dungeon.dungeonintensity.random": "Random",
|
"randomizer.dungeon.dungeonintensity.random": "Random",
|
||||||
|
|
||||||
"randomizer.dungeon.potshuffle": "Pot Shuffle",
|
|
||||||
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
||||||
|
|
||||||
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
||||||
|
|||||||
@@ -22,15 +22,20 @@
|
|||||||
"width": 45
|
"width": 45
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keydropshuffle": {
|
"keydropshuffle": { "type": "checkbox" },
|
||||||
|
"pottery": {
|
||||||
"type": "selectbox",
|
"type": "selectbox",
|
||||||
"default": "none",
|
"default": "none",
|
||||||
"options": [
|
"options": [
|
||||||
"none",
|
"none",
|
||||||
"keydrops",
|
"keys",
|
||||||
"potsanity"
|
"lottery"
|
||||||
]
|
],
|
||||||
|
"config": {
|
||||||
|
"width": 35
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
"dropshuffle": { "type": "checkbox" },
|
||||||
"potshuffle": { "type": "checkbox" },
|
"potshuffle": { "type": "checkbox" },
|
||||||
"experimental": { "type": "checkbox" },
|
"experimental": { "type": "checkbox" },
|
||||||
"dungeon_counters": {
|
"dungeon_counters": {
|
||||||
|
|||||||
@@ -93,9 +93,11 @@ SETTINGSTOPROCESS = {
|
|||||||
"compassshuffle": "compassshuffle",
|
"compassshuffle": "compassshuffle",
|
||||||
"smallkeyshuffle": "keyshuffle",
|
"smallkeyshuffle": "keyshuffle",
|
||||||
"bigkeyshuffle": "bigkeyshuffle",
|
"bigkeyshuffle": "bigkeyshuffle",
|
||||||
"keydropshuffle": "keydropshuffle",
|
|
||||||
"dungeondoorshuffle": "door_shuffle",
|
"dungeondoorshuffle": "door_shuffle",
|
||||||
"dungeonintensity": "intensity",
|
"dungeonintensity": "intensity",
|
||||||
|
"keydropshuffle": "keydropshuffle",
|
||||||
|
"dropshuffle": "dropshuffle",
|
||||||
|
"pottery": "pottery",
|
||||||
"potshuffle": "shufflepots",
|
"potshuffle": "shufflepots",
|
||||||
"experimental": "experimental",
|
"experimental": "experimental",
|
||||||
"dungeon_counters": "dungeon_counters",
|
"dungeon_counters": "dungeon_counters",
|
||||||
|
|||||||
@@ -65,45 +65,40 @@ def create_item_pool_config(world):
|
|||||||
config.static_placement = {}
|
config.static_placement = {}
|
||||||
config.location_groups = {}
|
config.location_groups = {}
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
config.static_placement[player] = vanilla_mapping.copy()
|
config.static_placement[player] = defaultdict(list)
|
||||||
if world.keydropshuffle[player] != 'none':
|
config.static_placement[player].update(vanilla_mapping)
|
||||||
|
if world.dropshuffle[player]:
|
||||||
for item, locs in keydrop_vanilla_mapping.items():
|
for item, locs in keydrop_vanilla_mapping.items():
|
||||||
if item in config.static_placement[player]:
|
config.static_placement[player][item].extend(locs)
|
||||||
config.static_placement[player][item].extend(locs)
|
if world.pottery[player] != 'none':
|
||||||
else:
|
for item, locs in potkeys_vanilla_mapping.items():
|
||||||
config.static_placement[player][item] = list(locs)
|
config.static_placement[player][item].extend(locs)
|
||||||
if world.keydropshuffle[player] == 'potsanity':
|
if world.pottery[player] == 'lottery':
|
||||||
for super_tile, pot_list in vanilla_pots.items():
|
for super_tile, pot_list in vanilla_pots.items():
|
||||||
for pot_index, pot in enumerate(pot_list):
|
for pot_index, pot in enumerate(pot_list):
|
||||||
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
|
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
|
||||||
item = pot_items[pot.item]
|
item = pot_items[pot.item]
|
||||||
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||||
location = f'{pot.room} {descriptor}'
|
location = f'{pot.room} {descriptor}'
|
||||||
if item in config.static_placement[player]:
|
config.static_placement[player][item].append(location)
|
||||||
config.static_placement[player][item].append(location)
|
|
||||||
else:
|
|
||||||
config.static_placement[player][item] = list([location])
|
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
for item, locs in shop_vanilla_mapping.items():
|
for item, locs in shop_vanilla_mapping.items():
|
||||||
if item in config.static_placement[player]:
|
config.static_placement[player][item].extend(locs)
|
||||||
config.static_placement[player][item].extend(locs)
|
|
||||||
else:
|
|
||||||
config.static_placement[player][item] = list(locs)
|
|
||||||
if world.retro[player]:
|
if world.retro[player]:
|
||||||
for item, locs in retro_vanilla_mapping.items():
|
for item, locs in retro_vanilla_mapping.items():
|
||||||
if item in config.static_placement[player]:
|
config.static_placement[player][item].extend(locs)
|
||||||
config.static_placement[player][item].extend(locs)
|
|
||||||
else:
|
|
||||||
config.static_placement[player][item] = list(locs)
|
|
||||||
# universal keys
|
# universal keys
|
||||||
universal_key_locations = []
|
universal_key_locations = []
|
||||||
for item, locs in vanilla_mapping.items():
|
for item, locs in vanilla_mapping.items():
|
||||||
if 'Small Key' in item:
|
if 'Small Key' in item:
|
||||||
universal_key_locations.extend(locs)
|
universal_key_locations.extend(locs)
|
||||||
if world.keydropshuffle[player] != 'none':
|
if world.dropshuffle[player]:
|
||||||
for item, locs in keydrop_vanilla_mapping.items():
|
for item, locs in keydrop_vanilla_mapping.items():
|
||||||
if 'Small Key' in item:
|
if 'Small Key' in item:
|
||||||
universal_key_locations.extend(locs)
|
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]:
|
if world.shopsanity[player]:
|
||||||
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
|
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
|
||||||
single_arrow_placement.append('Red Shield Shop - Right')
|
single_arrow_placement.append('Red Shield Shop - Right')
|
||||||
@@ -677,25 +672,31 @@ vanilla_mapping = {
|
|||||||
|
|
||||||
|
|
||||||
keydrop_vanilla_mapping = {
|
keydrop_vanilla_mapping = {
|
||||||
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Eyegore Key Drop'],
|
||||||
'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 (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
'Small Key (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
'Hyrule Castle - Key Rat Key Drop'],
|
'Hyrule Castle - Key Rat Key Drop'],
|
||||||
'Big Key (Escape)': ['Hyrule Castle - Big 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 (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 (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',
|
'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 - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
|
||||||
'Swamp Palace - Waterway Pot Key'],
|
'Swamp Palace - Waterway Pot Key'],
|
||||||
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
'Small Key (Ice Palace)': ['Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
||||||
'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 (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 (Ganons Tower)': ['Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch 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 = {
|
shop_vanilla_mapping = {
|
||||||
|
|||||||
Reference in New Issue
Block a user