Starting inventory updates
Logic fix for skull woods star tile logic Standard logic improvement
This commit is contained in:
@@ -2287,6 +2287,9 @@ class Item(object):
|
||||
def __unicode__(self):
|
||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name and self.player == other.player
|
||||
|
||||
|
||||
# have 6 address that need to be filled
|
||||
class Crystal(Item):
|
||||
|
||||
@@ -2107,7 +2107,7 @@ def find_trappable_candidates(builder, world, player):
|
||||
if ext.door and ext.door.type in [DoorType.Interior, DoorType.Normal]]
|
||||
for d in filtered_doors:
|
||||
# I only support the first 3 due to the trapFlag right now
|
||||
if 0 <= d.doorListPos < 3 and not d.entranceFlag:
|
||||
if 0 <= d.doorListPos < 3 and not d.entranceFlag and d.name != 'Skull Small Hall WS':
|
||||
room = world.get_room(d.roomIndex, player)
|
||||
kind = room.kind(d)
|
||||
if d.type == DoorType.Interior:
|
||||
|
||||
113
ItemList.py
113
ItemList.py
@@ -286,6 +286,7 @@ def generate_itempool(world, player):
|
||||
for _ in range(0, amt):
|
||||
pool.append('Rupees (20)')
|
||||
|
||||
start_inventory = list(world.precollected_items)
|
||||
for item in precollected_items:
|
||||
world.push_precollected(ItemFactory(item, player))
|
||||
|
||||
@@ -435,6 +436,22 @@ def generate_itempool(world, player):
|
||||
if world.pottery[player] not in ['none', 'keys'] and not skip_pool_adjustments:
|
||||
add_pot_contents(world, player)
|
||||
|
||||
# modfiy based on start inventory, if any
|
||||
modify_pool_for_start_inventory(start_inventory, world, player)
|
||||
|
||||
# increase pool if not enough items
|
||||
ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if '- Prize' not in x.name)
|
||||
pool_size = count_player_dungeon_item_pool(world, player)
|
||||
pool_size += sum(1 for x in world.itempool if x.player == player)
|
||||
|
||||
if pool_size < ttl_locations:
|
||||
retro_bow = world.bow_mode[player].startswith('retro')
|
||||
amount_to_add = ttl_locations - pool_size
|
||||
filler_additions = random.choices(list(filler_items.keys()), filler_items.values(), k=amount_to_add)
|
||||
for item in filler_additions:
|
||||
item_name = 'Rupees (5)' if retro_bow and item == 'Arrows (10)' else item
|
||||
world.itempool.append(ItemFactory(item_name, player))
|
||||
|
||||
|
||||
take_any_locations = [
|
||||
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
|
||||
@@ -962,14 +979,60 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt
|
||||
pool.extend(['Small Key (Universal)'])
|
||||
else:
|
||||
pool.extend(['Small Key (Universal)'])
|
||||
modify_pool_for_start_inventory(pool, world, player)
|
||||
return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms)
|
||||
|
||||
|
||||
def modify_pool_for_start_inventory(pool, world, player):
|
||||
for item in world.precollected_items:
|
||||
item_alternates = {
|
||||
# Bows
|
||||
'Progressive Bow (Alt)': ('Progressive Bow', 1),
|
||||
'Bow': ('Progressive Bow', 1),
|
||||
'Silver Arrows': ('Progressive Bow', 2),
|
||||
# Gloves
|
||||
'Power Glove': ('Progressive Glove', 1),
|
||||
'Titans Mitts': ('Progressive Glove', 2),
|
||||
# Swords
|
||||
'Sword and Shield': ('Progressive Sword', 1), # could find a way to also remove a shield, but mostly not impactful
|
||||
'Fighter Sword': ('Progressive Sword', 1),
|
||||
'Master Sword': ('Progressive Sword', 2),
|
||||
'Tempered Sword': ('Progressive Sword', 3),
|
||||
'Golden Sword': ('Progressive Sword', 4),
|
||||
# Shields
|
||||
'Blue Shield': ('Progressive Shield', 1),
|
||||
'Red Shield': ('Progressive Shield', 2),
|
||||
'Mirror Shield': ('Progressive Shield', 3),
|
||||
# Armors
|
||||
'Blue Mail': ('Progressive Armor', 1),
|
||||
'Red Mail': ('Progressive Armor', 2),
|
||||
|
||||
'Magic Upgrade (1/4)': ('Magic Upgrade (1/2)', 2),
|
||||
'Ocarina': ('Ocarina (Activated)', 1),
|
||||
'Ocarina (Activated)': ('Ocarina', 1),
|
||||
'Boss Heart Container': ('Sanctuary Heart Container', 1),
|
||||
'Sanctuary Heart Container': ('Boss Heart Container', 1),
|
||||
'Power Star': ('Triforce Piece', 1)
|
||||
}
|
||||
|
||||
|
||||
def modify_pool_for_start_inventory(start_inventory, world, player):
|
||||
# skips custom item pools - these shouldn't be adjusted
|
||||
if (world.customizer and world.customizer.get_item_pool()) or world.custom:
|
||||
return
|
||||
for item in start_inventory:
|
||||
if item.player == player:
|
||||
pool.remove(item.name)
|
||||
if item in world.itempool:
|
||||
world.itempool.remove(item)
|
||||
elif item.name in item_alternates:
|
||||
alt = item_alternates[item.name]
|
||||
i = alt[1]
|
||||
while i > 0:
|
||||
alt_item = ItemFactory([alt[0]], player)[0]
|
||||
if alt_item in world.itempool:
|
||||
world.itempool.remove(alt_item)
|
||||
i = i-1
|
||||
elif 'Bottle' in item.name:
|
||||
bottle_item = next((x for x in world.itempool if 'Bottle' in item.name and x.player == player), None)
|
||||
if bottle_item is not None:
|
||||
world.itempool.remove(bottle_item)
|
||||
if item.dungeon:
|
||||
d = world.get_dungeon(item.dungeon, item.player)
|
||||
match = next((i for i in d.all_items if i.name == item.name), None)
|
||||
@@ -1085,6 +1148,22 @@ def make_custom_item_pool(world, player, progressive, shuffle, difficulty, timer
|
||||
# print("Placing " + str(nothings) + " Nothings")
|
||||
pool.extend(['Nothing'] * nothings)
|
||||
|
||||
start_inventory = [x for x in world.precollected_items if x.player == player]
|
||||
if not start_inventory:
|
||||
if world.logic[player] in ['owglitches', 'nologic']:
|
||||
precollected_items.append('Pegasus Boots')
|
||||
if 'Pegasus Boots' in pool:
|
||||
pool.remove('Pegasus Boots')
|
||||
pool.append('Rupees (20)')
|
||||
if world.swords[player] == 'assured':
|
||||
precollected_items.append('Progressive Sword')
|
||||
if 'Progressive Sword' in pool:
|
||||
pool.remove('Progressive Sword')
|
||||
pool.append('Rupees (50)')
|
||||
elif 'Fighter Sword' in pool:
|
||||
pool.remove('Fighter Sword')
|
||||
pool.append('Rupees (50)')
|
||||
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
|
||||
|
||||
@@ -1206,12 +1285,20 @@ def make_customizer_pool(world, player):
|
||||
if pieces < t:
|
||||
pool.extend(['Triforce Piece'] * (t - pieces))
|
||||
|
||||
ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if '- Prize' not in x.name)
|
||||
pool_size = len(get_player_dungeon_item_pool(world, player)) + len(pool)
|
||||
|
||||
if pool_size < ttl_locations:
|
||||
amount_to_add = ttl_locations - pool_size
|
||||
pool.extend(random.choices(list(filler_items.keys()), filler_items.values(), k=amount_to_add))
|
||||
if not world.customizer.get_start_inventory():
|
||||
if world.logic[player] in ['owglitches', 'nologic']:
|
||||
precollected_items.append('Pegasus Boots')
|
||||
if 'Pegasus Boots' in pool:
|
||||
pool.remove('Pegasus Boots')
|
||||
pool.append('Rupees (20)')
|
||||
if world.swords[player] == 'assured':
|
||||
precollected_items.append('Progressive Sword')
|
||||
if 'Progressive Sword' in pool:
|
||||
pool.remove('Progressive Sword')
|
||||
pool.append('Rupees (50)')
|
||||
elif 'Fighter Sword' in pool:
|
||||
pool.remove('Fighter Sword')
|
||||
pool.append('Rupees (50)')
|
||||
|
||||
return pool, placed_items, precollected_items, clock_mode, 1
|
||||
|
||||
@@ -1227,9 +1314,9 @@ filler_items = {
|
||||
}
|
||||
|
||||
|
||||
def get_player_dungeon_item_pool(world, player):
|
||||
return [item for dungeon in world.dungeons for item in dungeon.all_items
|
||||
if dungeon.player == player and item.location is None]
|
||||
def count_player_dungeon_item_pool(world, player):
|
||||
return sum(1 for dungeon in world.dungeons for item in dungeon.all_items
|
||||
if dungeon.player == player and item.location is None and is_dungeon_item(item.name, world, player))
|
||||
|
||||
|
||||
# location pool doesn't support larger values at this time
|
||||
|
||||
2
Main.py
2
Main.py
@@ -34,7 +34,7 @@ from source.overworld.EntranceShuffle2 import link_entrances_new
|
||||
from source.tools.BPS import create_bps_from_data
|
||||
from source.classes.CustomSettings import CustomSettings
|
||||
|
||||
__version__ = '1.2.0.3-u'
|
||||
__version__ = '1.2.0.4-u'
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
|
||||
|
||||
@@ -108,7 +108,11 @@ These are now independent of retro mode and have three options: None, Random, an
|
||||
* Bonk Fairy (Dark)
|
||||
|
||||
# Bug Fixes and Notes
|
||||
|
||||
* 1.2.0.4-u
|
||||
* Starting inventory fixes if item not present in the item pool.
|
||||
* Support for Assured sword setting and OWG Boots when using a custom item pool. (Customizer or GUI)
|
||||
* Logic fix for the skull woods star tile that lets you into the X pot room. Now accounts for small key or big key door there blocking the way from the star tile. A trap door is not allowed there.
|
||||
* Standard logic improvement that requires a path from Zelda to the start so that you cannot get softlocked by rescuing Zelda. Standard mirror scroll change may need to be reverted if impossible seed are still generated.
|
||||
* 1.2.0.3-u
|
||||
* Starting inventory taken into account with default item pool. (Custom pools must do this themselves)
|
||||
* Fast ROM update
|
||||
|
||||
16
Rom.py
16
Rom.py
@@ -2221,7 +2221,7 @@ def write_strings(rom, world, player, team):
|
||||
hint_candidates = []
|
||||
for name, district in world.districts[player].items():
|
||||
hint_type = 'foolish'
|
||||
choice_set = set()
|
||||
choices = []
|
||||
item_count, item_type = 0, 'useful'
|
||||
for loc_name in district.locations:
|
||||
location_item = world.get_location(loc_name, player).item
|
||||
@@ -2231,34 +2231,32 @@ def write_strings(rom, world, player, team):
|
||||
itm_type = 'useful' if useful_item_for_hint(location_item, world) else 'vital'
|
||||
hint_type = 'path'
|
||||
if item_type == itm_type:
|
||||
choice_set.add(location_item)
|
||||
choices.append(location_item)
|
||||
item_count += 1
|
||||
elif itm_type == 'vital':
|
||||
item_type = 'vital'
|
||||
item_count = 1
|
||||
choice_set.clear()
|
||||
choice_set.add(location_item)
|
||||
choices.clear()
|
||||
choices.append(location_item)
|
||||
if hint_type == 'foolish':
|
||||
if district.dungeons and world.shuffle[player] != 'vanilla':
|
||||
choice_set.update(district.dungeons)
|
||||
choices.extend(district.dungeons)
|
||||
hint_type = 'dungeon_path'
|
||||
elif district.access_points and world.shuffle[player] not in ['vanilla', 'dungeonssimple',
|
||||
'dungeonsfull']:
|
||||
choice_set.update([x.hint_text for x in district.access_points])
|
||||
choices.extend([x.hint_text for x in district.access_points])
|
||||
hint_type = 'connector'
|
||||
if hint_type == 'foolish':
|
||||
hint_candidates.append((hint_type, f'{name} is a foolish choice'))
|
||||
elif hint_type == 'dungeon_path':
|
||||
choices = sorted(list(choice_set))
|
||||
dungeon_choice = random.choice(choices) # prefer required dungeons...
|
||||
hint_candidates.append((hint_type, f'{name} is on the path to {dungeon_choice}'))
|
||||
elif hint_type == 'connector':
|
||||
choices = sorted(list(choice_set))
|
||||
access_point = random.choice(choices) # prefer required access...
|
||||
hint_candidates.append((hint_type, f'{name} can reach {access_point}'))
|
||||
elif hint_type == 'path':
|
||||
if item_count == 1:
|
||||
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
||||
the_item = text_for_item(next(iter(choices)), world, player, team)
|
||||
hint_candidates.append((hint_type, f'{name} conceals only {the_item}'))
|
||||
else:
|
||||
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
||||
|
||||
22
Rules.py
22
Rules.py
@@ -124,6 +124,10 @@ def or_rule(rule1, rule2):
|
||||
return lambda state: rule1(state) or rule2(state)
|
||||
|
||||
|
||||
def and_rule(rule1, rule2):
|
||||
return lambda state: rule1(state) and rule2(state)
|
||||
|
||||
|
||||
def add_lamp_requirement(spot, player):
|
||||
add_rule(spot, lambda state: state.has('Lamp', player, state.world.lamps_needed_for_dark_rooms))
|
||||
|
||||
@@ -277,8 +281,22 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player))
|
||||
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
|
||||
set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player) or state.has('Hidden Pits', player))
|
||||
set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), lambda state: state.has('Hidden Pits', player))
|
||||
|
||||
hidden_pits_door = world.get_door('Skull Small Hall WS', player)
|
||||
|
||||
def hidden_pits_rule(state):
|
||||
return state.has('Hidden Pits', player)
|
||||
|
||||
if hidden_pits_door.bigKey:
|
||||
key_logic = world.key_logic[player][hidden_pits_door.entrance.parent_region.dungeon.name]
|
||||
hidden_pits_rule = and_rule(hidden_pits_rule, create_rule(key_logic.bk_name, player))
|
||||
elif hidden_pits_door.smallKey:
|
||||
d_name = hidden_pits_door.entrance.parent_region.dungeon.name
|
||||
hidden_pits_rule = and_rule(hidden_pits_rule, eval_small_key_door('Skull Small Hall WS', d_name, player))
|
||||
|
||||
set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player)
|
||||
or hidden_pits_rule(state))
|
||||
set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), hidden_pits_rule)
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player))
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ meta:
|
||||
players: 1
|
||||
race: true
|
||||
settings:
|
||||
1:
|
||||
shopsanity: true
|
||||
pseudoboots: true
|
||||
goal: crystals
|
||||
|
||||
@@ -2,6 +2,7 @@ meta:
|
||||
players: 1
|
||||
race: true
|
||||
settings:
|
||||
1:
|
||||
shopsanity: true
|
||||
pseudoboots: true
|
||||
goal: crystals
|
||||
|
||||
@@ -2,6 +2,7 @@ meta:
|
||||
players: 1
|
||||
race: true
|
||||
settings:
|
||||
1:
|
||||
shopsanity: true
|
||||
pseudoboots: true
|
||||
goal: crystals
|
||||
|
||||
@@ -319,6 +319,8 @@ def determine_paths_for_dungeon(world, player, all_regions, name):
|
||||
if world.mode[player] == 'standard' and name == 'Hyrule Castle Dungeon':
|
||||
paths.append('Hyrule Dungeon Cellblock')
|
||||
paths.append(('Hyrule Dungeon Cellblock', 'Hyrule Castle Throne Room'))
|
||||
entrance = next(x for x in world.dungeon_portals[player] if x.name == 'Hyrule Castle South')
|
||||
paths.append(('Hyrule Dungeon Cellblock', entrance.door.entrance.parent_region.name))
|
||||
if world.doorShuffle[player] in ['basic'] and name == 'Thieves Town':
|
||||
paths.append('Thieves Attic Window')
|
||||
elif 'Thieves Attic Window' in all_r_names:
|
||||
|
||||
28
test/customizer/test_stuff.yaml
Normal file
28
test/customizer/test_stuff.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
meta:
|
||||
players: 1
|
||||
race: true
|
||||
settings:
|
||||
1:
|
||||
shopsanity: true
|
||||
pseudoboots: true
|
||||
goal: crystals
|
||||
crystals_gt: random
|
||||
keysanity: true
|
||||
door_shuffle: crossed
|
||||
intensity: 3
|
||||
door_type_mode: big
|
||||
pottery: keys
|
||||
dropshuffle: true
|
||||
experimental: true
|
||||
dungeon_counters: 'on'
|
||||
hints: true
|
||||
msu_resume: true
|
||||
collection_rate: true
|
||||
quickswap: true
|
||||
start_inventory:
|
||||
1:
|
||||
- Pegasus Boots
|
||||
- Ocarina (Activated)
|
||||
- Magic Mirror
|
||||
- Boss Heart Container
|
||||
- Blue Mail
|
||||
Reference in New Issue
Block a user