Merged in DR v1.2.0.1
This commit is contained in:
+20
-7
@@ -128,7 +128,8 @@ class World(object):
|
|||||||
set_player_attr('can_access_trock_front', None)
|
set_player_attr('can_access_trock_front', None)
|
||||||
set_player_attr('can_access_trock_big_chest', None)
|
set_player_attr('can_access_trock_big_chest', None)
|
||||||
set_player_attr('can_access_trock_middle', None)
|
set_player_attr('can_access_trock_middle', None)
|
||||||
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['lean', 'crossed', 'insanity'])
|
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic']
|
||||||
|
or shuffle[player] in ['lean', 'crossed', 'insanity'])
|
||||||
set_player_attr('mapshuffle', False)
|
set_player_attr('mapshuffle', False)
|
||||||
set_player_attr('compassshuffle', False)
|
set_player_attr('compassshuffle', False)
|
||||||
set_player_attr('keyshuffle', 'none')
|
set_player_attr('keyshuffle', 'none')
|
||||||
@@ -174,7 +175,9 @@ class World(object):
|
|||||||
def finish_init(self):
|
def finish_init(self):
|
||||||
for player in range(1, self.players + 1):
|
for player in range(1, self.players + 1):
|
||||||
if self.mode[player] == 'retro':
|
if self.mode[player] == 'retro':
|
||||||
self.mode[player] == 'open'
|
self.mode[player] = 'open'
|
||||||
|
if self.goal[player] == 'completionist':
|
||||||
|
self.accessibility[player] = 'locations'
|
||||||
|
|
||||||
def get_name_string_for_object(self, obj):
|
def get_name_string_for_object(self, obj):
|
||||||
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
||||||
@@ -547,7 +550,10 @@ class World(object):
|
|||||||
if self.has_beaten_game(state):
|
if self.has_beaten_game(state):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
|
prog_locations = [location for location in self.get_locations() if location.item is not None
|
||||||
|
and (location.item.advancement or location.event
|
||||||
|
or self.goal[location.player] == 'completionist')
|
||||||
|
and location not in state.locations_checked]
|
||||||
|
|
||||||
while prog_locations:
|
while prog_locations:
|
||||||
sphere = []
|
sphere = []
|
||||||
@@ -1129,6 +1135,12 @@ class CollectionState(object):
|
|||||||
def item_count(self, item, player):
|
def item_count(self, item, player):
|
||||||
return self.prog_items[item, player]
|
return self.prog_items[item, player]
|
||||||
|
|
||||||
|
def everything(self, player):
|
||||||
|
all_locations = self.world.get_filled_locations(player)
|
||||||
|
all_locations.remove(self.world.get_location('Ganon', player))
|
||||||
|
return (len([x for x in self.locations_checked if x.player == player])
|
||||||
|
>= len(all_locations))
|
||||||
|
|
||||||
def has_crystals(self, count, player):
|
def has_crystals(self, count, player):
|
||||||
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
|
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
|
||||||
return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count
|
return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count
|
||||||
@@ -1260,7 +1272,7 @@ class CollectionState(object):
|
|||||||
|
|
||||||
def can_flute(self, player):
|
def can_flute(self, player):
|
||||||
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
|
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
|
||||||
return False
|
return False # can't flute in rain state
|
||||||
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
|
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
|
||||||
return True
|
return True
|
||||||
lw = self.world.get_region('Kakariko Area', player)
|
lw = self.world.get_region('Kakariko Area', player)
|
||||||
@@ -3014,7 +3026,7 @@ class Spoiler(object):
|
|||||||
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
|
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
|
||||||
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
|
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
|
||||||
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
|
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
|
||||||
if self.metadata['goal'][player] in ['triforcehunt', 'trinity']:
|
if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player])
|
outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player])
|
||||||
outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player])
|
outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player])
|
||||||
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
|
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
|
||||||
@@ -3343,7 +3355,8 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2}
|
|||||||
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
|
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
|
||||||
|
|
||||||
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
|
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
|
||||||
goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4, "trinity": 5}
|
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5,
|
||||||
|
'ganonhunt': 6, 'completionist': 7}
|
||||||
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}
|
||||||
|
|
||||||
@@ -3391,7 +3404,7 @@ flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
|
|||||||
flute_mode = {'normal': 0, 'active': 1}
|
flute_mode = {'normal': 0, 'active': 1}
|
||||||
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes?
|
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes?
|
||||||
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
|
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
|
||||||
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silver': 3}
|
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
|
||||||
|
|
||||||
# additions
|
# additions
|
||||||
# psuedoboots does not effect code
|
# psuedoboots does not effect code
|
||||||
|
|||||||
+18
-11
@@ -6,7 +6,7 @@ from enum import unique, Flag
|
|||||||
from typing import DefaultDict, Dict, List
|
from typing import DefaultDict, Dict, List
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
||||||
from BaseClasses import PotFlags, LocationType, Direction
|
from BaseClasses import PotFlags, LocationType, Direction
|
||||||
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
|
||||||
@@ -15,13 +15,12 @@ from Items import ItemFactory
|
|||||||
from RoomData import DoorKind, PairedDoor, reset_rooms
|
from RoomData import DoorKind, PairedDoor, reset_rooms
|
||||||
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
|
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
|
||||||
from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2
|
from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2
|
||||||
# from DungeonGenerator import generate_dungeon
|
from DungeonGenerator import ExplorationState, convert_regions, determine_required_paths, drop_entrances
|
||||||
from DungeonGenerator import ExplorationState, convert_regions, pre_validate, determine_required_paths, drop_entrances
|
|
||||||
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
|
||||||
from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations
|
from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations
|
||||||
from DungeonGenerator import valid_region_to_explore
|
from DungeonGenerator import valid_region_to_explore
|
||||||
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
|
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
|
||||||
from KeyDoorShuffle import validate_bk_layout, check_bk_special
|
from KeyDoorShuffle import validate_bk_layout
|
||||||
from Utils import ncr, kth_combination
|
from Utils import ncr, kth_combination
|
||||||
|
|
||||||
|
|
||||||
@@ -1895,6 +1894,7 @@ def shuffle_big_key_doors(door_type_pools, used_doors, start_regions_map, world,
|
|||||||
|
|
||||||
|
|
||||||
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
|
def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, world, player):
|
||||||
|
max_computation = 11 # this is around 6 billion worse case factorial don't want to exceed this much
|
||||||
for pool, door_type_pool in door_type_pools:
|
for pool, door_type_pool in door_type_pools:
|
||||||
ttl = 0
|
ttl = 0
|
||||||
suggestion_map, small_map, flex_map = {}, {}, {}
|
suggestion_map, small_map, flex_map = {}, {}, {}
|
||||||
@@ -1921,27 +1921,28 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
|||||||
calculated = int(round(builder.key_doors_num*total_keys/ttl))
|
calculated = int(round(builder.key_doors_num*total_keys/ttl))
|
||||||
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
|
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder, world, player))
|
||||||
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
cand_len = max(0, len(builder.candidates.small) - builder.key_drop_cnt)
|
||||||
limit = min(max_keys, cand_len)
|
limit = min(max_keys, cand_len, max_computation)
|
||||||
suggested = min(calculated, limit)
|
suggested = min(calculated, limit)
|
||||||
combo_size = ncr(len(builder.candidates.small), suggested + builder.key_drop_cnt)
|
key_door_num = min(suggested + builder.key_drop_cnt, max_computation)
|
||||||
|
combo_size = ncr(len(builder.candidates.small), key_door_num)
|
||||||
while combo_size > 500000 and suggested > 0:
|
while combo_size > 500000 and suggested > 0:
|
||||||
suggested -= 1
|
suggested -= 1
|
||||||
combo_size = ncr(len(builder.candidates.small), suggested + builder.key_drop_cnt)
|
combo_size = ncr(len(builder.candidates.small), key_door_num)
|
||||||
suggestion_map[dungeon] = builder.key_doors_num = suggested + builder.key_drop_cnt
|
suggestion_map[dungeon] = builder.key_doors_num = key_door_num
|
||||||
remaining -= suggested + builder.key_drop_cnt
|
remaining -= suggested + builder.key_drop_cnt
|
||||||
builder.combo_size = combo_size
|
builder.combo_size = combo_size
|
||||||
flex_map[dungeon] = (limit - suggested) if suggested < limit else 0
|
flex_map[dungeon] = (limit - suggested) if suggested < limit else 0
|
||||||
for dungeon in pool:
|
for dungeon in pool:
|
||||||
builder = world.dungeon_layouts[player][dungeon]
|
builder = world.dungeon_layouts[player][dungeon]
|
||||||
if total_adjustable:
|
if total_adjustable:
|
||||||
builder.total_keys = suggestion_map[dungeon]
|
builder.total_keys = max(suggestion_map[dungeon], builder.key_drop_cnt)
|
||||||
valid_doors, small_number = find_valid_combination(builder, suggestion_map[dungeon],
|
valid_doors, small_number = find_valid_combination(builder, suggestion_map[dungeon],
|
||||||
start_regions_map[dungeon], world, player)
|
start_regions_map[dungeon], world, player)
|
||||||
small_map[dungeon] = valid_doors
|
small_map[dungeon] = valid_doors
|
||||||
actual_chest_keys = small_number - builder.key_drop_cnt
|
actual_chest_keys = small_number - builder.key_drop_cnt
|
||||||
if actual_chest_keys < suggestion_map[dungeon]:
|
if actual_chest_keys < suggestion_map[dungeon]:
|
||||||
if total_adjustable:
|
if total_adjustable:
|
||||||
builder.total_keys = actual_chest_keys
|
builder.total_keys = actual_chest_keys + builder.key_drop_cnt
|
||||||
flex_map[dungeon] = 0
|
flex_map[dungeon] = 0
|
||||||
remaining += suggestion_map[dungeon] - actual_chest_keys
|
remaining += suggestion_map[dungeon] - actual_chest_keys
|
||||||
suggestion_map[dungeon] = small_number
|
suggestion_map[dungeon] = small_number
|
||||||
@@ -1952,6 +1953,8 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
|||||||
builder = queue.popleft()
|
builder = queue.popleft()
|
||||||
dungeon = builder.name
|
dungeon = builder.name
|
||||||
increased = suggestion_map[dungeon] + 1
|
increased = suggestion_map[dungeon] + 1
|
||||||
|
if increased > max_computation:
|
||||||
|
continue
|
||||||
builder.key_doors_num = increased
|
builder.key_doors_num = increased
|
||||||
valid_doors, small_number = find_valid_combination(builder, increased, start_regions_map[dungeon],
|
valid_doors, small_number = find_valid_combination(builder, increased, start_regions_map[dungeon],
|
||||||
world, player)
|
world, player)
|
||||||
@@ -1961,7 +1964,7 @@ def shuffle_small_key_doors(door_type_pools, used_doors, start_regions_map, worl
|
|||||||
suggestion_map[dungeon] = increased
|
suggestion_map[dungeon] = increased
|
||||||
flex_map[dungeon] -= 1
|
flex_map[dungeon] -= 1
|
||||||
if total_adjustable:
|
if total_adjustable:
|
||||||
builder.total_keys = actual_chest_keys
|
builder.total_keys = max(increased, builder.key_drop_cnt)
|
||||||
if flex_map[dungeon] > 0:
|
if flex_map[dungeon] > 0:
|
||||||
builder.combo_size = ncr(len(builder.candidates.small), builder.key_doors_num)
|
builder.combo_size = ncr(len(builder.candidates.small), builder.key_doors_num)
|
||||||
queue.append(builder)
|
queue.append(builder)
|
||||||
@@ -2691,8 +2694,12 @@ def find_valid_bd_combination(builder, suggested, world, player):
|
|||||||
test = random.choice([True, False])
|
test = random.choice([True, False])
|
||||||
if test:
|
if test:
|
||||||
bomb_doors_needed -= 1
|
bomb_doors_needed -= 1
|
||||||
|
if bomb_doors_needed < 0:
|
||||||
|
bomb_doors_needed = 0
|
||||||
else:
|
else:
|
||||||
dash_doors_needed -= 1
|
dash_doors_needed -= 1
|
||||||
|
if dash_doors_needed < 0:
|
||||||
|
dash_doors_needed = 0
|
||||||
bomb_proposal = random.sample(bd_door_pool, k=bomb_doors_needed)
|
bomb_proposal = random.sample(bd_door_pool, k=bomb_doors_needed)
|
||||||
bomb_proposal.extend(custom_bomb_doors)
|
bomb_proposal.extend(custom_bomb_doors)
|
||||||
dash_pool = [x for x in bd_door_pool if x not in bomb_proposal]
|
dash_pool = [x for x in bd_door_pool if x not in bomb_proposal]
|
||||||
|
|||||||
+4
-3
@@ -12,7 +12,7 @@ from typing import List
|
|||||||
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector
|
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector
|
||||||
from BaseClasses import Hook, hook_from_door, Door
|
from BaseClasses import Hook, hook_from_door, Door
|
||||||
from Regions import dungeon_events, flooded_keys_reverse
|
from Regions import dungeon_events, flooded_keys_reverse
|
||||||
from Dungeons import dungeon_regions, split_region_starts
|
from Dungeons import split_region_starts
|
||||||
from RoomData import DoorKind
|
from RoomData import DoorKind
|
||||||
|
|
||||||
from source.dungeon.DungeonStitcher import generate_dungeon_find_proposal
|
from source.dungeon.DungeonStitcher import generate_dungeon_find_proposal
|
||||||
@@ -860,7 +860,7 @@ class ExplorationState(object):
|
|||||||
self.crystal = exp_door.crystal
|
self.crystal = exp_door.crystal
|
||||||
return exp_door
|
return exp_door
|
||||||
|
|
||||||
def visit_region(self, region, key_region=None, key_checks=False, bk_Flag=False):
|
def visit_region(self, region, key_region=None, key_checks=False, bk_flag=False):
|
||||||
if region.type != RegionType.Dungeon:
|
if region.type != RegionType.Dungeon:
|
||||||
self.crystal = CrystalBarrier.Orange
|
self.crystal = CrystalBarrier.Orange
|
||||||
if self.crystal == CrystalBarrier.Either:
|
if self.crystal == CrystalBarrier.Either:
|
||||||
@@ -881,7 +881,7 @@ class ExplorationState(object):
|
|||||||
self.ttl_locations += 1
|
self.ttl_locations += 1
|
||||||
if location not in self.found_locations:
|
if location not in self.found_locations:
|
||||||
self.found_locations.append(location)
|
self.found_locations.append(location)
|
||||||
if not bk_Flag and (not location.forced_item or 'Big Key' in location.item.name):
|
if not bk_flag and (not location.forced_item or 'Big Key' in location.item.name):
|
||||||
self.bk_found.add(location)
|
self.bk_found.add(location)
|
||||||
if location.name in dungeon_events and location.name not in self.events:
|
if location.name in dungeon_events and location.name not in self.events:
|
||||||
if self.flooded_key_check(location):
|
if self.flooded_key_check(location):
|
||||||
@@ -1335,6 +1335,7 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, dunge
|
|||||||
sector = find_sector(r_name, all_sectors)
|
sector = find_sector(r_name, all_sectors)
|
||||||
reverse_d_map[sector] = key
|
reverse_d_map[sector] = key
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
|
if 'Hyrule Castle' in dungeon_map:
|
||||||
current_dungeon = dungeon_map['Hyrule Castle']
|
current_dungeon = dungeon_map['Hyrule Castle']
|
||||||
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
|
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
|
||||||
|
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
else:
|
else:
|
||||||
max_trash = gt_count
|
max_trash = gt_count
|
||||||
scaled_trash = math.floor(max_trash * scale_factor)
|
scaled_trash = math.floor(max_trash * scale_factor)
|
||||||
if world.goal[player] in ['triforcehunt', 'trinity']:
|
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
gftower_trash_count = random.randint(scaled_trash, max_trash)
|
gftower_trash_count = random.randint(scaled_trash, max_trash)
|
||||||
else:
|
else:
|
||||||
gftower_trash_count = random.randint(0, scaled_trash)
|
gftower_trash_count = random.randint(0, scaled_trash)
|
||||||
|
|||||||
+17
-12
@@ -182,8 +182,12 @@ def get_custom_array_key(item):
|
|||||||
|
|
||||||
|
|
||||||
def generate_itempool(world, player):
|
def generate_itempool(world, player):
|
||||||
if (world.difficulty[player] not in ['normal', 'hard', 'expert'] or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals']
|
if (world.difficulty[player] not in ['normal', 'hard', 'expert']
|
||||||
or world.mode[player] not in ['open', 'standard', 'inverted'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.progressive not in ['on', 'off', 'random']):
|
or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals',
|
||||||
|
'ganonhunt', 'completionist']
|
||||||
|
or world.mode[player] not in ['open', 'standard', 'inverted']
|
||||||
|
or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']
|
||||||
|
or world.progressive not in ['on', 'off', 'random']):
|
||||||
raise NotImplementedError('Not supported yet')
|
raise NotImplementedError('Not supported yet')
|
||||||
|
|
||||||
if world.timer in ['ohko', 'timed-ohko']:
|
if world.timer in ['ohko', 'timed-ohko']:
|
||||||
@@ -364,7 +368,7 @@ def generate_itempool(world, player):
|
|||||||
world.clock_mode = clock_mode
|
world.clock_mode = clock_mode
|
||||||
|
|
||||||
goal = world.goal[player]
|
goal = world.goal[player]
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player])
|
g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player])
|
||||||
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t
|
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t
|
||||||
world.treasure_hunt_icon[player] = 'Triforce Piece'
|
world.treasure_hunt_icon[player] = 'Triforce Piece'
|
||||||
@@ -978,14 +982,15 @@ def add_bonkdrop_contents(world, player):
|
|||||||
count -= 1
|
count -= 1
|
||||||
|
|
||||||
|
|
||||||
def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, bombbag,
|
def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords,
|
||||||
door_shuffle, logic, flute_activated):
|
bombbag, door_shuffle, logic, flute_activated):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = {}
|
placed_items = {}
|
||||||
precollected_items = []
|
precollected_items = []
|
||||||
clock_mode = None
|
clock_mode = None
|
||||||
if treasure_hunt_total == 0 and goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
treasure_hunt_total = 30 if goal == 'triforcehunt' else 10
|
if treasure_hunt_total == 0:
|
||||||
|
treasure_hunt_total = 30 if goal in ['triforcehunt', 'ganonhunt'] else 10
|
||||||
# triforce pieces max out
|
# triforce pieces max out
|
||||||
triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal)
|
triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal)
|
||||||
|
|
||||||
@@ -1088,7 +1093,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt
|
|||||||
elif timer == 'timed-ohko':
|
elif timer == 'timed-ohko':
|
||||||
pool.extend(diff.timedohko)
|
pool.extend(diff.timedohko)
|
||||||
clock_mode = 'countdown-ohko'
|
clock_mode = 'countdown-ohko'
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
pool.extend(triforcepool)
|
pool.extend(triforcepool)
|
||||||
|
|
||||||
for extra in diff.extras:
|
for extra in diff.extras:
|
||||||
@@ -1146,7 +1151,7 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
|
|||||||
customitemarray["triforce"] = total_items_to_place
|
customitemarray["triforce"] = total_items_to_place
|
||||||
|
|
||||||
# Triforce Pieces
|
# Triforce Pieces
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
|
g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
|
||||||
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
|
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
|
||||||
|
|
||||||
@@ -1184,8 +1189,8 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
|
|||||||
treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1)
|
treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1)
|
||||||
treasure_hunt_icon = 'Triforce Piece'
|
treasure_hunt_icon = 'Triforce Piece'
|
||||||
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
||||||
if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity'])
|
if ((customitemarray["triforcepieces"] < treasure_hunt_count)
|
||||||
and (customitemarray["triforce"] == 0)):
|
and (goal in ['triforcehunt', 'trinity', 'ganonhunt']) and (customitemarray["triforce"] == 0)):
|
||||||
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
|
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
|
||||||
pool.extend(['Triforce Piece'] * extrapieces)
|
pool.extend(['Triforce Piece'] * extrapieces)
|
||||||
itemtotal = itemtotal + extrapieces
|
itemtotal = itemtotal + extrapieces
|
||||||
@@ -1385,7 +1390,7 @@ def get_player_dungeon_item_pool(world, player):
|
|||||||
# location pool doesn't support larger values at this time
|
# location pool doesn't support larger values at this time
|
||||||
def set_default_triforce(goal, custom_goal, custom_total):
|
def set_default_triforce(goal, custom_goal, custom_total):
|
||||||
triforce_goal, triforce_total = 0, 0
|
triforce_goal, triforce_total = 0, 0
|
||||||
if goal == 'triforcehunt':
|
if goal in ['triforcehunt', 'ganonhunt']:
|
||||||
triforce_goal, triforce_total = 20, 30
|
triforce_goal, triforce_total = 20, 30
|
||||||
elif goal == 'trinity':
|
elif goal == 'trinity':
|
||||||
triforce_goal, triforce_total = 8, 10
|
triforce_goal, triforce_total = 8, 10
|
||||||
|
|||||||
+1
-1
@@ -1445,7 +1445,7 @@ def validate_bk_layout(proposal, builder, start_regions, world, player):
|
|||||||
if loc.forced_big_key():
|
if loc.forced_big_key():
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return len(state.bk_found) > 0
|
return state.count_locations_exclude_specials(world, player) > 0
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
|
|||||||
from source.tools.BPS import create_bps_from_data
|
from source.tools.BPS import create_bps_from_data
|
||||||
from source.classes.CustomSettings import CustomSettings
|
from source.classes.CustomSettings import CustomSettings
|
||||||
|
|
||||||
__version__ = '1.2.0.0-u'
|
__version__ = '1.2.0.1-u'
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
@@ -786,7 +786,8 @@ def create_playthrough(world):
|
|||||||
world = copy_world(world)
|
world = copy_world(world)
|
||||||
|
|
||||||
# get locations containing progress items
|
# get locations containing progress items
|
||||||
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement
|
||||||
|
or world.goal[location.player] == 'completionist']
|
||||||
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile']
|
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile']
|
||||||
optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls
|
optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls
|
||||||
optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs
|
optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs
|
||||||
@@ -825,6 +826,8 @@ def create_playthrough(world):
|
|||||||
for num, sphere in reversed(list(enumerate(collection_spheres))):
|
for num, sphere in reversed(list(enumerate(collection_spheres))):
|
||||||
to_delete = set()
|
to_delete = set()
|
||||||
for location in sphere:
|
for location in sphere:
|
||||||
|
if world.goal[location.player] == 'completionist':
|
||||||
|
continue # every location for that player is required
|
||||||
# we remove the item at location and check if game is still beatable
|
# we remove the item at location and check if game is still beatable
|
||||||
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
logging.getLogger('').debug('Checking if %s (Player %d) is required to beat the game.', location.item.name, location.item.player)
|
||||||
old_item = location.item
|
old_item = location.item
|
||||||
|
|||||||
+17
-3
@@ -55,6 +55,15 @@ This is similar to insanity mode in ER where door entrances and exits are not pa
|
|||||||
|
|
||||||
Please see [Customizer documentation](docs/Customizer.md) on how to create custom seeds.
|
Please see [Customizer documentation](docs/Customizer.md) on how to create custom seeds.
|
||||||
|
|
||||||
|
## New Goals
|
||||||
|
|
||||||
|
### Triforce Hunt + Ganon
|
||||||
|
Collect the requisite triforce pieces, then defeat Ganon. (Aga2 not required). Use `ganonhunt` on CLI
|
||||||
|
|
||||||
|
### Completionist
|
||||||
|
All dungeons not enough for you? You have to obtain every item in the game too. This option turns on the collection rate counter and forces accessibility to be 100% locations. Finish by defeating Ganon.
|
||||||
|
|
||||||
|
|
||||||
## Standard Generation Change
|
## Standard Generation Change
|
||||||
|
|
||||||
Hyrule Castle in standard mode is generated a little differently now. The throne room is guaranteed to be in Hyrule Castle and the Sanctuary is guaranteed to be beyond that. Additionally, the Mirror Scroll will bring you back to Zelda's Cell or the Throne Room depending on what save point you last obtained, this is to make it consistent with where you end up if you die. If you are lucky enough to find the Mirror, it behaves differently and brings you the last entrance used - giving you more options for exploration in Hyrule Castle.
|
Hyrule Castle in standard mode is generated a little differently now. The throne room is guaranteed to be in Hyrule Castle and the Sanctuary is guaranteed to be beyond that. Additionally, the Mirror Scroll will bring you back to Zelda's Cell or the Throne Room depending on what save point you last obtained, this is to make it consistent with where you end up if you die. If you are lucky enough to find the Mirror, it behaves differently and brings you the last entrance used - giving you more options for exploration in Hyrule Castle.
|
||||||
@@ -100,10 +109,15 @@ These are now independent of retro mode and have three options: None, Random, an
|
|||||||
|
|
||||||
# Bug Fixes and Notes
|
# Bug Fixes and Notes
|
||||||
|
|
||||||
None yet
|
* 1.2.0.1-u
|
||||||
|
* Added new ganonhunt and completionist goals
|
||||||
|
* Fixed the issue when defeating Agahnim and standing in the doorway can cause door state to linger.
|
||||||
|
* Fix for Inverted Lean/Lite ER
|
||||||
|
* Fix for vanilla Doors + Standard + ER
|
||||||
|
* Added a limit per dungeon on small key doors to ensure reasonable generation
|
||||||
|
* Fixed many small bugs
|
||||||
|
|
||||||
# Known Issues
|
# Known Issues
|
||||||
|
|
||||||
* Standing in the doorway when defeating Aga 1 and being teleported to the Dark World will not clear door state. It may cause issues requiring a Save & Quit to fix.
|
|
||||||
* Decoupled doors can lead to situations where you aren't logically supposed to go back through a door without a big key or small key, but you can if you press the correct direction back through the door first. There are some transitions where you may get stuck without a bomb. These problems are planned to be fixed.
|
* Decoupled doors can lead to situations where you aren't logically supposed to go back through a door without a big key or small key, but you can if you press the correct direction back through the door first. There are some transitions where you may get stuck without a bomb. These problems are planned to be fixed.
|
||||||
* Logic getting to Skull X room may be wrong if a trap door, big key door, or bombable wall. A bomb jump to get to those pot may be required if you don't have boots to bonk across.
|
* Logic getting to Skull X room may be wrong if a trap door, big key door, or bombable wall is shuffled there. A bomb jump to get to those pot may be required if you don't have boots to bonk across.
|
||||||
@@ -15,7 +15,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception('Could not load BPS module')
|
raise Exception('Could not load BPS module')
|
||||||
|
|
||||||
from BaseClasses import ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, LocationType, Item
|
from BaseClasses import ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, LocationType
|
||||||
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
|
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
|
||||||
from Dungeons import dungeon_music_addresses, dungeon_table
|
from Dungeons import dungeon_music_addresses, dungeon_table
|
||||||
from Regions import location_table, shop_to_location_table, retro_shops
|
from Regions import location_table, shop_to_location_table, retro_shops
|
||||||
@@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127
|
|||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '7a6619830c3521f2a09c4c64cd8d7d75'
|
RANDOMIZERBASEHASH = '1cdb34399467efa346b713db54d6cb02'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -869,7 +869,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
|
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
|
||||||
if world.doorShuffle[player] not in ['vanilla', 'basic']:
|
if world.doorShuffle[player] not in ['vanilla', 'basic']:
|
||||||
dr_flags |= DROptions.Map_Info
|
dr_flags |= DROptions.Map_Info
|
||||||
if world.collection_rate[player] and world.goal[player] not in ['triforcehunt', 'trinity']:
|
if ((world.collection_rate[player] or world.goal[player] == 'completionist')
|
||||||
|
and world.goal[player] not in ['triforcehunt', 'trinity', 'ganonhunt']):
|
||||||
dr_flags |= DROptions.Debug
|
dr_flags |= DROptions.Debug
|
||||||
if world.doorShuffle[player] not in ['vanilla', 'basic'] and world.logic[player] != 'nologic'\
|
if world.doorShuffle[player] not in ['vanilla', 'basic'] and world.logic[player] != 'nologic'\
|
||||||
and world.mixed_travel[player] == 'prevent':
|
and world.mixed_travel[player] == 'prevent':
|
||||||
@@ -1339,7 +1340,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
|
|
||||||
# set up goals for treasure hunt
|
# set up goals for treasure hunt
|
||||||
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
|
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
|
||||||
if world.goal[player] in ['triforcehunt', 'trinity']:
|
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
|
||||||
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
|
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
|
||||||
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
|
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
|
||||||
|
|
||||||
@@ -1411,6 +1412,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat
|
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all dungeons are beat
|
||||||
elif world.goal[player] in ['crystals', 'trinity']:
|
elif world.goal[player] in ['crystals', 'trinity']:
|
||||||
rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals
|
rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals
|
||||||
|
elif world.goal[player] in ['ganonhunt']:
|
||||||
|
rom.write_byte(0x18003E, 0x05) # make ganon invincible until all triforce pieces collected
|
||||||
|
elif world.goal[player] in ['completionist']:
|
||||||
|
rom.write_byte(0x18003E, 0x0a) # make ganon invincible until everything is collected
|
||||||
else:
|
else:
|
||||||
rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected
|
rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected
|
||||||
|
|
||||||
@@ -2479,6 +2484,10 @@ def write_strings(rom, world, player, team):
|
|||||||
trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player]
|
trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player]
|
||||||
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
|
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
|
||||||
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
|
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
|
||||||
|
elif world.goal[player] == 'ganonhunt':
|
||||||
|
tt['sign_ganon'] = 'Go find the Triforce pieces to beat Ganon'
|
||||||
|
elif world.goal[player] == 'completionist':
|
||||||
|
tt['sign_ganon'] = 'Ganon only respects those who have done everything'
|
||||||
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)]
|
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)]
|
||||||
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
|
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
|
||||||
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
|
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ def set_rules(world, player):
|
|||||||
for location in world.get_region('Hyrule Castle Courtyard', player).locations:
|
for location in world.get_region('Hyrule Castle Courtyard', player).locations:
|
||||||
if location.name == 'Murahdahla':
|
if location.name == 'Murahdahla':
|
||||||
add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
||||||
|
elif world.goal[player] == 'ganonhunt':
|
||||||
|
add_rule(world.get_location('Ganon', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
||||||
|
elif world.goal[player] == 'completionist':
|
||||||
|
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))
|
||||||
|
|
||||||
# if swamp and dam have not been moved we require mirror for swamp palace
|
# if swamp and dam have not been moved we require mirror for swamp palace
|
||||||
if not world.swamp_patch_required[player]:
|
if not world.swamp_patch_required[player]:
|
||||||
@@ -2090,7 +2094,8 @@ def add_key_logic_rules(world, player):
|
|||||||
for chest in d_logic.bk_chests:
|
for chest in d_logic.bk_chests:
|
||||||
big_chest = world.get_location(chest.name, player)
|
big_chest = world.get_location(chest.name, player)
|
||||||
add_rule(big_chest, create_rule(d_logic.bk_name, player))
|
add_rule(big_chest, create_rule(d_logic.bk_name, player))
|
||||||
if len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1:
|
if (len(d_logic.bk_doors) == 0 and len(d_logic.bk_chests) <= 1
|
||||||
|
and world.accessibility[player] != 'locations'):
|
||||||
set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player))
|
set_always_allow(big_chest, allow_big_key_in_big_chest(d_logic.bk_name, player))
|
||||||
if world.keyshuffle[player] == 'universal':
|
if world.keyshuffle[player] == 'universal':
|
||||||
for d_name, layout in world.key_layout[player].items():
|
for d_name, layout in world.key_layout[player].items():
|
||||||
|
|||||||
Binary file not shown.
+3
-1
@@ -113,6 +113,8 @@
|
|||||||
pedestal: 2
|
pedestal: 2
|
||||||
triforce-hunt: 2
|
triforce-hunt: 2
|
||||||
trinity: 2
|
trinity: 2
|
||||||
|
ganonhunt: 2
|
||||||
|
completionist: 1
|
||||||
triforce_goal_min: 10
|
triforce_goal_min: 10
|
||||||
triforce_goal_max: 30
|
triforce_goal_max: 30
|
||||||
triforce_pool_min: 20
|
triforce_pool_min: 20
|
||||||
@@ -136,7 +138,7 @@
|
|||||||
mcu: 1 # map, compass, universal smalls
|
mcu: 1 # map, compass, universal smalls
|
||||||
# for use when you aren't using the dungeon_items above
|
# for use when you aren't using the dungeon_items above
|
||||||
# smallkey_shuffle:
|
# smallkey_shuffle:
|
||||||
# standard: 5
|
# none: 5
|
||||||
# wild: 1
|
# wild: 1
|
||||||
# universal: 1
|
# universal: 1
|
||||||
dungeon_counters:
|
dungeon_counters:
|
||||||
|
|||||||
@@ -67,7 +67,9 @@
|
|||||||
"dungeons",
|
"dungeons",
|
||||||
"triforcehunt",
|
"triforcehunt",
|
||||||
"trinity",
|
"trinity",
|
||||||
"crystals"
|
"crystals",
|
||||||
|
"ganonhunt",
|
||||||
|
"completionist"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"difficulty": {
|
"difficulty": {
|
||||||
|
|||||||
@@ -108,7 +108,10 @@
|
|||||||
"Triforce Hunt: Places 30 Triforce Pieces in the world, collect",
|
"Triforce Hunt: Places 30 Triforce Pieces in the world, collect",
|
||||||
" 20 of them to beat the game.",
|
" 20 of them to beat the game.",
|
||||||
"Trinity: Can beat the game by defeating Ganon, pulling",
|
"Trinity: Can beat the game by defeating Ganon, pulling",
|
||||||
" Pedestal, or delivering Triforce Pieces."
|
" Pedestal, or delivering Triforce Pieces.",
|
||||||
|
"Ganon Hunt: Places 30 Triforce Pieces in the world, collect",
|
||||||
|
" 20 of them then defeat Ganon.",
|
||||||
|
"Completionist: Find everything then defeat Ganon."
|
||||||
],
|
],
|
||||||
"difficulty": [
|
"difficulty": [
|
||||||
"Select game difficulty. Affects available itempool. (default: %(default)s)",
|
"Select game difficulty. Affects available itempool. (default: %(default)s)",
|
||||||
|
|||||||
@@ -279,6 +279,8 @@
|
|||||||
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
"randomizer.item.goal.triforcehunt": "Triforce Hunt",
|
||||||
"randomizer.item.goal.trinity": "Trinity",
|
"randomizer.item.goal.trinity": "Trinity",
|
||||||
"randomizer.item.goal.crystals": "Crystals",
|
"randomizer.item.goal.crystals": "Crystals",
|
||||||
|
"randomizer.item.goal.ganonhunt": "Triforce Hunt + Ganon",
|
||||||
|
"randomizer.item.goal.completionist": "Completionist",
|
||||||
|
|
||||||
"randomizer.item.crystals_gt": "Crystals to open GT",
|
"randomizer.item.crystals_gt": "Crystals to open GT",
|
||||||
"randomizer.item.crystals_gt.0": "0",
|
"randomizer.item.crystals_gt.0": "0",
|
||||||
|
|||||||
@@ -36,7 +36,9 @@
|
|||||||
"dungeons",
|
"dungeons",
|
||||||
"triforcehunt",
|
"triforcehunt",
|
||||||
"trinity",
|
"trinity",
|
||||||
"crystals"
|
"crystals",
|
||||||
|
"ganonhunt",
|
||||||
|
"completionist"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"crystals_gt": {
|
"crystals_gt": {
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ def modify_proposal(proposed_map, explored_state, doors_to_connect, hash_code_se
|
|||||||
else:
|
else:
|
||||||
logger.debug(f' Re-Linking {attempt.name} -> {new_door.name}')
|
logger.debug(f' Re-Linking {attempt.name} -> {new_door.name}')
|
||||||
logger.debug(f' Re-Linking {old_attempt.name} -> {old_target.name}')
|
logger.debug(f' Re-Linking {old_attempt.name} -> {old_target.name}')
|
||||||
hash_code_set.add(hash_code)
|
|
||||||
return proposed_map, hash_code
|
return proposed_map, hash_code
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+15
-9
@@ -189,26 +189,27 @@ def district_item_pool_config(world):
|
|||||||
if district.dungeon:
|
if district.dungeon:
|
||||||
adjustment = len([i for i in world.get_dungeon(name, p).all_items
|
adjustment = len([i for i in world.get_dungeon(name, p).all_items
|
||||||
if i.is_inside_dungeon_item(world)])
|
if i.is_inside_dungeon_item(world)])
|
||||||
dist_len = len(district.locations) - adjustment
|
dist_adj = adjustment
|
||||||
if name not in district_choices:
|
if name not in district_choices:
|
||||||
district_choices[name] = (district.sphere_one, dist_len)
|
district_choices[name] = (district.sphere_one, dist_adj)
|
||||||
else:
|
else:
|
||||||
so, amt = district_choices[name]
|
so, amt = district_choices[name]
|
||||||
district_choices[name] = (so or district.sphere_one, amt + dist_len)
|
district_choices[name] = (so or district.sphere_one, amt + dist_adj)
|
||||||
|
|
||||||
chosen_locations = defaultdict(set)
|
chosen_locations = defaultdict(set)
|
||||||
location_cnt = 0
|
adjustment_cnt = 0
|
||||||
|
|
||||||
# choose a sphere one district
|
# choose a sphere one district
|
||||||
sphere_one_choices = [d for d, info in district_choices.items() if info[0]]
|
sphere_one_choices = [d for d, info in district_choices.items() if info[0]]
|
||||||
sphere_one = random.choice(sphere_one_choices)
|
sphere_one = random.choice(sphere_one_choices)
|
||||||
so, amt = district_choices[sphere_one]
|
so, adj = district_choices[sphere_one]
|
||||||
location_cnt += amt
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
for location in world.districts[player][sphere_one].locations:
|
for location in world.districts[player][sphere_one].locations:
|
||||||
chosen_locations[location].add(player)
|
chosen_locations[location].add(player)
|
||||||
del district_choices[sphere_one]
|
del district_choices[sphere_one]
|
||||||
config.recorded_choices.append(sphere_one)
|
config.recorded_choices.append(sphere_one)
|
||||||
|
adjustment_cnt += adj
|
||||||
|
location_cnt = len(chosen_locations) - adjustment_cnt
|
||||||
|
|
||||||
scale_factors = defaultdict(int)
|
scale_factors = defaultdict(int)
|
||||||
scale_total = 0
|
scale_total = 0
|
||||||
@@ -217,6 +218,7 @@ def district_item_pool_config(world):
|
|||||||
dungeon = world.get_entrance(ent, p).connected_region.dungeon
|
dungeon = world.get_entrance(ent, p).connected_region.dungeon
|
||||||
if dungeon:
|
if dungeon:
|
||||||
scale = world.crystals_needed_for_gt[p]
|
scale = world.crystals_needed_for_gt[p]
|
||||||
|
if scale > 0:
|
||||||
scale_total += scale
|
scale_total += scale
|
||||||
scale_factors[dungeon.name] += scale
|
scale_factors[dungeon.name] += scale
|
||||||
scale_total = max(1, scale_total)
|
scale_total = max(1, scale_total)
|
||||||
@@ -226,13 +228,15 @@ def district_item_pool_config(world):
|
|||||||
while location_cnt < item_cnt:
|
while location_cnt < item_cnt:
|
||||||
weights = [scale_total / scale_divisors[d] for d in district_choices.keys()]
|
weights = [scale_total / scale_divisors[d] for d in district_choices.keys()]
|
||||||
choice = random.choices(list(district_choices.keys()), weights=weights, k=1)[0]
|
choice = random.choices(list(district_choices.keys()), weights=weights, k=1)[0]
|
||||||
so, amt = district_choices[choice]
|
so, adj = district_choices[choice]
|
||||||
location_cnt += amt
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
for location in world.districts[player][choice].locations:
|
for location in world.districts[player][choice].locations:
|
||||||
chosen_locations[location].add(player)
|
chosen_locations[location].add(player)
|
||||||
del district_choices[choice]
|
del district_choices[choice]
|
||||||
config.recorded_choices.append(choice)
|
config.recorded_choices.append(choice)
|
||||||
|
adjustment_cnt += adj
|
||||||
|
location_cnt = len(chosen_locations) - adjustment_cnt
|
||||||
config.placeholders = location_cnt - item_cnt
|
config.placeholders = location_cnt - item_cnt
|
||||||
config.location_groups[0].locations = chosen_locations
|
config.location_groups[0].locations = chosen_locations
|
||||||
|
|
||||||
@@ -383,7 +387,10 @@ def vanilla_fallback(item_to_place, locations, world):
|
|||||||
|
|
||||||
def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion=False):
|
def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion=False):
|
||||||
config = world.item_pool_config
|
config = world.item_pool_config
|
||||||
|
if not isinstance(item_to_place, str):
|
||||||
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||||
|
else:
|
||||||
|
item_name = item_to_place
|
||||||
if world.algorithm == 'vanilla_fill':
|
if world.algorithm == 'vanilla_fill':
|
||||||
filtered = []
|
filtered = []
|
||||||
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
item_name = 'Bottle' if item_to_place.name.startswith('Bottle') else item_to_place.name
|
||||||
@@ -443,7 +450,6 @@ def filter_pot_locations(locations, world):
|
|||||||
return locations
|
return locations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
vanilla_mapping = {
|
vanilla_mapping = {
|
||||||
'Green Pendant': ['Eastern Palace - Prize'],
|
'Green Pendant': ['Eastern Palace - Prize'],
|
||||||
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||||
|
|||||||
@@ -774,9 +774,16 @@ def do_vanilla_connect(pool_def, avail):
|
|||||||
if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']:
|
if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']:
|
||||||
return
|
return
|
||||||
defaults = inverted_default_connections if avail.inverted else default_connections
|
defaults = inverted_default_connections if avail.inverted else default_connections
|
||||||
|
if avail.inverted:
|
||||||
|
if 'Dark Death Mountain Fairy' in pool_def['entrances']:
|
||||||
|
pool_def['entrances'].remove('Dark Death Mountain Fairy')
|
||||||
|
pool_def['entrances'].append('Bumper Cave (top)')
|
||||||
for entrance in pool_def['entrances']:
|
for entrance in pool_def['entrances']:
|
||||||
if entrance in avail.entrances:
|
if entrance in avail.entrances:
|
||||||
target = defaults[entrance]
|
target = defaults[entrance]
|
||||||
|
if entrance in avail.default_map:
|
||||||
|
connect_vanilla_two_way(entrance, avail.default_map[entrance], avail)
|
||||||
|
else:
|
||||||
connect_simple(avail.world, entrance, target, avail.player)
|
connect_simple(avail.world, entrance, target, avail.player)
|
||||||
avail.entrances.remove(entrance)
|
avail.entrances.remove(entrance)
|
||||||
avail.exits.remove(target)
|
avail.exits.remove(target)
|
||||||
@@ -1228,7 +1235,7 @@ modes = {
|
|||||||
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
||||||
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
||||||
'Dark Desert Hint',
|
'Dark Desert Hint',
|
||||||
'Links House', 'Inverted Links House', 'Tavern North']
|
'Links House', 'Tavern North']
|
||||||
},
|
},
|
||||||
'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything
|
'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything
|
||||||
'special': 'old_man_cave_east',
|
'special': 'old_man_cave_east',
|
||||||
@@ -1304,7 +1311,7 @@ modes = {
|
|||||||
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
|
||||||
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
|
||||||
'Dark Desert Hint',
|
'Dark Desert Hint',
|
||||||
'Links House', 'Inverted Links House', 'Tavern North']
|
'Links House', 'Tavern North'] # inverted links house gets substituted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1572,6 +1579,7 @@ entrance_map = {
|
|||||||
'Paradox Cave (Bottom)': 'Paradox Cave Exit (Bottom)',
|
'Paradox Cave (Bottom)': 'Paradox Cave Exit (Bottom)',
|
||||||
'Paradox Cave (Middle)': 'Paradox Cave Exit (Middle)',
|
'Paradox Cave (Middle)': 'Paradox Cave Exit (Middle)',
|
||||||
'Paradox Cave (Top)': 'Paradox Cave Exit (Top)',
|
'Paradox Cave (Top)': 'Paradox Cave Exit (Top)',
|
||||||
|
'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary Exit',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1607,7 +1615,7 @@ single_entrance_map = {
|
|||||||
'Lake Hylia Fortune Teller': 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy': 'Lake Hylia Healer Fairy',
|
'Lake Hylia Fortune Teller': 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy': 'Lake Hylia Healer Fairy',
|
||||||
'Bonk Fairy (Light)': 'Bonk Fairy (Light)', 'Lumberjack House': 'Lumberjack House', 'Dam': 'Dam',
|
'Bonk Fairy (Light)': 'Bonk Fairy (Light)', 'Lumberjack House': 'Lumberjack House', 'Dam': 'Dam',
|
||||||
'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing',
|
'Blinds Hideout': 'Blinds Hideout', 'Waterfall of Wishing': 'Waterfall of Wishing',
|
||||||
'Inverted Bomb Shop': 'Inverted Bomb Shop', 'Inverted Dark Sanctuary': 'Inverted Dark Sanctuary',
|
'Inverted Bomb Shop': 'Inverted Bomb Shop'
|
||||||
}
|
}
|
||||||
|
|
||||||
default_dw = {
|
default_dw = {
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ def roll_settings(weights):
|
|||||||
'dungeons': 'dungeons',
|
'dungeons': 'dungeons',
|
||||||
'pedestal': 'pedestal',
|
'pedestal': 'pedestal',
|
||||||
'triforce-hunt': 'triforcehunt',
|
'triforce-hunt': 'triforcehunt',
|
||||||
'trinity': 'trinity'
|
'trinity': 'trinity',
|
||||||
|
'ganonhunt': 'ganonhunt',
|
||||||
|
'completionist': 'completionist'
|
||||||
}[goal]
|
}[goal]
|
||||||
|
|
||||||
ret.openpyramid = get_choice('open_pyramid') if 'open_pyramid' in weights else 'auto'
|
ret.openpyramid = get_choice('open_pyramid') if 'open_pyramid' in weights else 'auto'
|
||||||
@@ -215,5 +217,6 @@ def roll_settings(weights):
|
|||||||
ret.ow_palettes = get_choice('ow_palettes', romweights)
|
ret.ow_palettes = get_choice('ow_palettes', romweights)
|
||||||
ret.uw_palettes = get_choice('uw_palettes', romweights)
|
ret.uw_palettes = get_choice('uw_palettes', romweights)
|
||||||
ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on'
|
ret.shuffle_sfx = get_choice('shuffle_sfx', romweights) == 'on'
|
||||||
|
ret.msu_resume = get_choice('msu_resume', romweights) == 'on'
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
import logging
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
@@ -99,7 +101,7 @@ def test_loop(tests, entrance_set, exit_set, ctr, shuffle_mode, main_mode, links
|
|||||||
# seed = 635441530
|
# seed = 635441530
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
world = World(1, {1: shuffle_mode}, {1: 'vanilla'}, {1: 'noglitches'}, {1: main_mode}, {}, {}, {},
|
world = World(1, {1: shuffle_mode}, {1: 'vanilla'}, {1: 'noglitches'}, {1: main_mode}, {}, {}, {},
|
||||||
{}, {}, {}, {}, {}, True, {}, {}, [], {})
|
{}, {}, {}, {}, {}, True, {}, [], {})
|
||||||
world.customizer = False
|
world.customizer = False
|
||||||
world.shufflelinks = {1: links}
|
world.shufflelinks = {1: links}
|
||||||
create_regions(world, 1)
|
create_regions(world, 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user