Add setting for self-looping doors

This commit is contained in:
Catobat
2023-08-02 02:23:19 +02:00
parent 1817cf3824
commit 7197a23b45
14 changed files with 54 additions and 10 deletions

View File

@@ -145,6 +145,7 @@ class World(object):
set_player_attr('colorizepots', True) set_player_attr('colorizepots', True)
set_player_attr('pot_pool', {}) set_player_attr('pot_pool', {})
set_player_attr('decoupledoors', False) set_player_attr('decoupledoors', False)
set_player_attr('door_self_loops', False)
set_player_attr('door_type_mode', 'original') set_player_attr('door_type_mode', 'original')
set_player_attr('trap_door_mode', 'optional') set_player_attr('trap_door_mode', 'optional')
set_player_attr('key_logic_algorithm', 'default') set_player_attr('key_logic_algorithm', 'default')
@@ -2472,6 +2473,7 @@ class Spoiler(object):
'trap_door_mode': self.world.trap_door_mode, 'trap_door_mode': self.world.trap_door_mode,
'key_logic': self.world.key_logic_algorithm, 'key_logic': self.world.key_logic_algorithm,
'decoupledoors': self.world.decoupledoors, 'decoupledoors': self.world.decoupledoors,
'door_self_loops': self.world.door_self_loops,
'dungeon_counters': self.world.dungeon_counters, 'dungeon_counters': self.world.dungeon_counters,
'item_pool': self.world.difficulty, 'item_pool': self.world.difficulty,
'item_functionality': self.world.difficulty_adjustments, 'item_functionality': self.world.difficulty_adjustments,
@@ -2682,6 +2684,7 @@ class Spoiler(object):
outfile.write(f"Trap Door Mode: {self.metadata['trap_door_mode'][player]}\n") outfile.write(f"Trap Door Mode: {self.metadata['trap_door_mode'][player]}\n")
outfile.write(f"Key Logic Algorithm: {self.metadata['key_logic'][player]}\n") outfile.write(f"Key Logic Algorithm: {self.metadata['key_logic'][player]}\n")
outfile.write(f"Decouple Doors: {yn(self.metadata['decoupledoors'][player])}\n") outfile.write(f"Decouple Doors: {yn(self.metadata['decoupledoors'][player])}\n")
outfile.write(f"Spiral Stairs can self-loop: {yn(self.metadata['door_self_loops'][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"Dungeon Counters: {self.metadata['dungeon_counters'][player]}\n") outfile.write(f"Dungeon Counters: {self.metadata['dungeon_counters'][player]}\n")
outfile.write(f"Drop Shuffle: {yn(self.metadata['dropshuffle'][player])}\n") outfile.write(f"Drop Shuffle: {yn(self.metadata['dropshuffle'][player])}\n")
@@ -2947,10 +2950,10 @@ mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7, pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
'clustered': 8, 'nonempty': 9} 'clustered': 8, 'nonempty': 9}
# byte 5: CCCC CTTX (crystals gt, ctr2, experimental) # byte 5: SCCC CTTX (self-loop doors, 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 6: CCCC CPAA (crystals ganon, pyramid, access # byte 6: ?CCC CPAA (crystals ganon, pyramid, access
access_mode = {"items": 0, "locations": 1, "none": 2} access_mode = {"items": 0, "locations": 1, "none": 2}
# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies) # byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies)
@@ -3008,7 +3011,8 @@ class Settings(object):
(0x80 if w.shuffletavern[p] else 0) | (0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]), (0x80 if w.shuffletavern[p] else 0) | (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) (0x80 if w.door_self_loops[p] else 0)
| ((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),
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3) ((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
@@ -3067,12 +3071,13 @@ class Settings(object):
args.dropshuffle[p] = True if settings[4] & 0x10 else False args.dropshuffle[p] = True if settings[4] & 0x10 else False
args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F] args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F]
args.door_self_loops[p] = True if settings[5] & 0x80 else False
args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1] args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1]
cgt = (settings[5] & 0xf8) >> 3 cgt = (settings[5] & 0x78) >> 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[5] & 0x1 else False args.experimental[p] = True if settings[5] & 0x1 else False
cgan = (settings[6] & 0xf8) >> 3 cgan = (settings[6] & 0x78) >> 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[6] & 0x4 else False args.openpyramid[p] = True if settings[6] & 0x4 else False

3
CLI.py
View File

@@ -141,7 +141,7 @@ def parse_cli(argv, no_defaults=False):
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle', 'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx', 'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode', 'msu_resume', 'collection_rate', 'colorizepots', 'decoupledoors', 'door_type_mode',
'trap_door_mode', 'key_logic_algorithm']: 'trap_door_mode', 'key_logic_algorithm', 'door_self_loops']:
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})
@@ -218,6 +218,7 @@ def parse_settings():
'trap_door_mode': 'optional', 'trap_door_mode': 'optional',
'key_logic_algorithm': 'default', 'key_logic_algorithm': 'default',
'decoupledoors': False, 'decoupledoors': False,
'door_self_loops': False,
'experimental': False, 'experimental': False,
'dungeon_counters': 'default', 'dungeon_counters': 'default',
'mixed_travel': 'prevent', 'mixed_travel': 'prevent',

View File

@@ -115,6 +115,7 @@ def main(args, seed=None, fish=None):
world.trap_door_mode = args.trap_door_mode.copy() world.trap_door_mode = args.trap_door_mode.copy()
world.key_logic_algorithm = args.key_logic_algorithm.copy() world.key_logic_algorithm = args.key_logic_algorithm.copy()
world.decoupledoors = args.decoupledoors.copy() world.decoupledoors = args.decoupledoors.copy()
world.door_self_loops = args.door_self_loops.copy()
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.fish = fish world.fish = fish
@@ -487,6 +488,7 @@ def copy_world(world):
ret.beemizer = world.beemizer.copy() ret.beemizer = world.beemizer.copy()
ret.intensity = world.intensity.copy() ret.intensity = world.intensity.copy()
ret.decoupledoors = world.decoupledoors.copy() ret.decoupledoors = world.decoupledoors.copy()
ret.door_self_loops = world.door_self_loops.copy()
ret.experimental = world.experimental.copy() ret.experimental = world.experimental.copy()
ret.shopsanity = world.shopsanity.copy() ret.shopsanity = world.shopsanity.copy()
ret.dropshuffle = world.dropshuffle.copy() ret.dropshuffle = world.dropshuffle.copy()

View File

@@ -174,7 +174,13 @@ CLI: `--key_logic [default|partial|strict]`
This is similar to insanity mode in ER where door entrances and exits are not paired anymore. Tends to remove more logic from dungeons as many rooms will not be required to traverse to explore. Hope you like transitions. This is similar to insanity mode in ER where door entrances and exits are not paired anymore. Tends to remove more logic from dungeons as many rooms will not be required to traverse to explore. Hope you like transitions.
CLI `--decoupledoors` CLI: `--decoupledoors`
### Self-Looping Spiral Stairs
If enabled, spiral stairs are allowed to lead to themselves.
CLI: `--door_self_loops`
### Pottery ### Pottery

View File

@@ -31,6 +31,9 @@
partial: 0 partial: 0
strict: 0 strict: 0
decoupledoors: off decoupledoors: off
door_self_loops:
on: 1
off: 1
dropshuffle: dropshuffle:
on: 1 on: 1
off: 1 off: 1

View File

@@ -31,6 +31,9 @@ key_logic_algorithm:
decoupledoors: decoupledoors:
off: 9 # more strict off: 9 # more strict
on: 1 on: 1
door_self_loops:
on: 1
off: 1
dropshuffle: dropshuffle:
on: 1 on: 1
off: 1 off: 1

View File

@@ -212,6 +212,10 @@
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
}, },
"door_self_loops": {
"action": "store_true",
"type": "bool"
},
"experimental": { "experimental": {
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"

View File

@@ -253,6 +253,7 @@
"strict: Ensure small keys are available" "strict: Ensure small keys are available"
], ],
"decoupledoors" : [ "Door entrances and exits are decoupled" ], "decoupledoors" : [ "Door entrances and exits are decoupled" ],
"door_self_loops" : [ "Spiral stairs are allowed to self-loop" ],
"experimental": [ "Enable experimental features. (default: %(default)s)" ], "experimental": [ "Enable experimental features. (default: %(default)s)" ],
"dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ], "dungeon_counters": [ "Enable dungeon chest counters. (default: %(default)s)" ],
"crystals_ganon": [ "crystals_ganon": [

View File

@@ -58,6 +58,7 @@
"randomizer.dungeon.smallkeyshuffle.universal": "Universal", "randomizer.dungeon.smallkeyshuffle.universal": "Universal",
"randomizer.dungeon.bigkeyshuffle": "Big Keys", "randomizer.dungeon.bigkeyshuffle": "Big Keys",
"randomizer.dungeon.decoupledoors": "Decouple Doors", "randomizer.dungeon.decoupledoors": "Decouple Doors",
"randomizer.dungeon.door_self_loops": "Allow Self-Looping Spiral Stairs",
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle", "randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla", "randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",

View File

@@ -66,6 +66,12 @@
} }
}, },
"decoupledoors": { "decoupledoors": {
"type": "checkbox",
"config": {
"padx": [20,0]
}
},
"door_self_loops": {
"type": "checkbox", "type": "checkbox",
"config": { "config": {
"padx": [20,0], "padx": [20,0],

View File

@@ -113,6 +113,7 @@ class CustomSettings(object):
args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p]) args.trap_door_mode[p] = get_setting(settings['trap_door_mode'], args.trap_door_mode[p])
args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p]) args.key_logic_algorithm[p] = get_setting(settings['key_logic_algorithm'], args.key_logic_algorithm[p])
args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p]) args.decoupledoors[p] = get_setting(settings['decoupledoors'], args.decoupledoors[p])
args.door_self_loops[p] = get_setting(settings['door_self_loops'], args.door_self_loops[p])
args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p]) args.dungeon_counters[p] = get_setting(settings['dungeon_counters'], args.dungeon_counters[p])
args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p]) args.crystals_gt[p] = get_setting(settings['crystals_gt'], args.crystals_gt[p])
args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p]) args.crystals_ganon[p] = get_setting(settings['crystals_ganon'], args.crystals_ganon[p])
@@ -232,6 +233,7 @@ class CustomSettings(object):
settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p] settings_dict[p]['trap_door_mode'] = world.trap_door_mode[p]
settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p] settings_dict[p]['key_logic_algorithm'] = world.key_logic_algorithm[p]
settings_dict[p]['decoupledoors'] = world.decoupledoors[p] settings_dict[p]['decoupledoors'] = world.decoupledoors[p]
settings_dict[p]['door_self_loops'] = world.door_self_loops[p]
settings_dict[p]['logic'] = world.logic[p] settings_dict[p]['logic'] = world.logic[p]
settings_dict[p]['mode'] = world.mode[p] settings_dict[p]['mode'] = world.mode[p]
settings_dict[p]['swords'] = world.swords[p] settings_dict[p]['swords'] = world.swords[p]

View File

@@ -106,6 +106,7 @@ SETTINGSTOPROCESS = {
"door_type_mode": "door_type_mode", "door_type_mode": "door_type_mode",
"trap_door_mode": "trap_door_mode", "trap_door_mode": "trap_door_mode",
"decoupledoors": "decoupledoors", "decoupledoors": "decoupledoors",
"door_self_loops": "door_self_loops",
"experimental": "experimental", "experimental": "experimental",
"dungeon_counters": "dungeon_counters", "dungeon_counters": "dungeon_counters",
"mixed_travel": "mixed_travel", "mixed_travel": "mixed_travel",

View File

@@ -22,7 +22,7 @@ def generate_dungeon(builder, entrance_region_names, split_dungeon, world, playe
queue = collections.deque(proposed_map.items()) queue = collections.deque(proposed_map.items())
while len(queue) > 0: while len(queue) > 0:
a, b = queue.popleft() a, b = queue.popleft()
if world.decoupledoors[player]: if a == b or world.decoupledoors[player]:
connect_doors_one_way(a, b) connect_doors_one_way(a, b)
else: else:
connect_doors(a, b) connect_doors(a, b)
@@ -128,14 +128,14 @@ def create_random_proposal(doors_to_connect, world, player):
next_hook = random.choice(hooks_left) next_hook = random.choice(hooks_left)
primary_door = random.choice(primary_bucket[next_hook]) primary_door = random.choice(primary_bucket[next_hook])
opp_hook, secondary_door = type_map[next_hook], None opp_hook, secondary_door = type_map[next_hook], None
while (secondary_door is None or secondary_door == primary_door while (secondary_door is None or (secondary_door == primary_door and not world.door_self_loops[player])
or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook], or decouple_check(primary_bucket[next_hook], secondary_bucket[opp_hook],
primary_door, secondary_door, world, player)): primary_door, secondary_door, world, player)):
secondary_door = random.choice(secondary_bucket[opp_hook]) secondary_door = random.choice(secondary_bucket[opp_hook])
proposal[primary_door] = secondary_door proposal[primary_door] = secondary_door
primary_bucket[next_hook].remove(primary_door) primary_bucket[next_hook].remove(primary_door)
secondary_bucket[opp_hook].remove(secondary_door) secondary_bucket[opp_hook].remove(secondary_door)
if not world.decoupledoors[player]: if primary_door != secondary_door and not world.decoupledoors[player]:
proposal[secondary_door] = primary_door proposal[secondary_door] = primary_door
primary_bucket[opp_hook].remove(secondary_door) primary_bucket[opp_hook].remove(secondary_door)
secondary_bucket[next_hook].remove(primary_door) secondary_bucket[next_hook].remove(primary_door)
@@ -205,6 +205,14 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
old_attempt = proposed_map[new_door] old_attempt = proposed_map[new_door]
else: else:
old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door) old_attempt = next(x for x in proposed_map if proposed_map[x] == new_door)
# ensure nothing gets messed up when something loops with itself
if attempt == old_target and old_attempt == new_door:
old_attempt = new_door
old_target = attempt
elif attempt == old_target:
old_target = old_attempt
elif old_attempt == new_door:
old_attempt = old_target
proposed_map[old_attempt] = old_target proposed_map[old_attempt] = old_target
if not world.decoupledoors[player]: if not world.decoupledoors[player]:
proposed_map[old_target] = old_attempt proposed_map[old_target] = old_attempt

View File

@@ -87,6 +87,7 @@ def roll_settings(weights):
ret.trap_door_mode = get_choice('trap_door_mode') ret.trap_door_mode = get_choice('trap_door_mode')
ret.key_logic_algorithm = get_choice('key_logic_algorithm') ret.key_logic_algorithm = get_choice('key_logic_algorithm')
ret.decoupledoors = get_choice('decoupledoors') == 'on' ret.decoupledoors = get_choice('decoupledoors') == 'on'
ret.door_self_loops = get_choice('door_self_loops') == 'on'
ret.experimental = get_choice('experimental') == 'on' ret.experimental = get_choice('experimental') == 'on'
ret.collection_rate = get_choice('collection_rate') == 'on' ret.collection_rate = get_choice('collection_rate') == 'on'