Implement new weapons modes
This also includes some partial additional cleanup of the item pool.
This commit is contained in:
parent
b8ea2eb4b1
commit
996bf8495c
|
@ -3,7 +3,7 @@ from enum import Enum, unique
|
|||
import logging
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from _vendor.collections_extended import bag, setlist
|
||||
from _vendor.collections_extended import bag
|
||||
from Utils import int16_as_bytes
|
||||
|
||||
class World(object):
|
||||
|
@ -24,6 +24,7 @@ class World(object):
|
|||
self.shops = []
|
||||
self.itempool = []
|
||||
self.seed = None
|
||||
self.precollected_items = []
|
||||
self.state = CollectionState(self)
|
||||
self.required_medallions = dict([(player, ['Ether', 'Quake']) for player in range(1, players + 1)])
|
||||
self._cached_entrances = None
|
||||
|
@ -195,6 +196,10 @@ class World(object):
|
|||
def find_items(self, item, player):
|
||||
return [location for location in self.get_locations() if location.item is not None and location.item.name == item and location.item.player == player]
|
||||
|
||||
def push_precollected(self, item):
|
||||
self.precollected_items.append(item)
|
||||
self.state.collect(item, True)
|
||||
|
||||
def push_item(self, location, item, collect=True):
|
||||
if not isinstance(location, Location):
|
||||
raise RuntimeError('Cannot assign item %s to location %s (player %d).' % (item, location, item.player))
|
||||
|
@ -302,6 +307,8 @@ class CollectionState(object):
|
|||
self.path = {}
|
||||
self.locations_checked = set()
|
||||
self.stale = {player: True for player in range(1, parent.players + 1)}
|
||||
for item in parent.precollected_items:
|
||||
self.collect(item, True)
|
||||
|
||||
def update_reachable_regions(self, player):
|
||||
player_regions = [region for region in self.world.regions if region.player == player]
|
||||
|
|
97
ItemList.py
97
ItemList.py
|
@ -21,43 +21,24 @@ basicgloves = ['Power Glove', 'Titans Mitts']
|
|||
normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||
|
||||
normalbaseitems = (['Silver Arrows', 'Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrows (10)', 'Bombs (3)'] +
|
||||
normalbaseitems = (['Silver Arrows', 'Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrows (10)', 'Bombs (10)'] +
|
||||
['Rupees (300)'] * 4 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
|
||||
normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrows (10)'] * 6 + ['Bombs (3)'] * 6
|
||||
normalsecond15extra = ['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)'] + ['Bombs (10)']
|
||||
normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
|
||||
normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)']
|
||||
normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)']
|
||||
normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
||||
|
||||
hardbaseitems = ['Silver Arrows', 'Single Arrow', 'Bombs (10)'] + ['Rupees (300)'] * 4 + ['Boss Heart Container'] * 6 + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 7 + ['Bombs (3)'] * 4
|
||||
hardfirst20extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Bombs (3)'] * 5 + ['Rupees (5)'] * 10 + ['Arrows (10)', 'Rupee (1)']
|
||||
hardsecond10extra = ['Rupees (5)'] * 5 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
|
||||
hardthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3
|
||||
hardfourth10extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 7 + ['Rupees (5)']
|
||||
hardfinal20extra = ['Rupees (20)'] * 18 + ['Rupees (5)'] * 2
|
||||
|
||||
expertbaseitems = (['Rupees (300)'] * 4 + ['Single Arrow', 'Silver Arrows', 'Boss Heart Container', 'Rupee (1)', 'Bombs (10)'] + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 2 +
|
||||
['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2)
|
||||
expertfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Rupees (5)'] * 12
|
||||
expertsecond15extra = ['Rupees (5)'] * 10 + ['Rupees (20)'] * 5
|
||||
expertthird10extra = ['Rupees (50)'] * 4 + ['Rupees (5)'] * 2 + ['Arrows (10)'] * 3 + ['Rupee (1)']
|
||||
expertfourth5extra = ['Rupees (5)'] * 5
|
||||
expertfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
||||
|
||||
Difficulty = namedtuple('Difficulty',
|
||||
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
|
||||
'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
|
||||
'progressivesword', 'basicsword', 'timedohko', 'timedother',
|
||||
'triforcehunt', 'triforce_pieces_required', 'retro', 'conditional_extras',
|
||||
'triforcehunt', 'triforce_pieces_required', 'retro',
|
||||
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
|
||||
'progressive_armor_limit', 'progressive_bottle_limit'])
|
||||
|
||||
total_items_to_place = 153
|
||||
|
||||
def no_conditional_extras(*_args):
|
||||
return []
|
||||
|
||||
|
||||
difficulties = {
|
||||
'normal': Difficulty(
|
||||
baseitems = normalbaseitems,
|
||||
|
@ -76,7 +57,6 @@ difficulties = {
|
|||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
triforce_pieces_required = 20,
|
||||
retro = ['Small Key (Universal)'] * 17 + ['Rupees (20)'] * 10,
|
||||
conditional_extras = no_conditional_extras,
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 4,
|
||||
progressive_shield_limit = 3,
|
||||
|
@ -84,7 +64,7 @@ difficulties = {
|
|||
progressive_bottle_limit = 4,
|
||||
),
|
||||
'hard': Difficulty(
|
||||
baseitems = hardbaseitems,
|
||||
baseitems = normalbaseitems,
|
||||
bottles = hardbottles,
|
||||
bottle_count = 4,
|
||||
same_bottle = False,
|
||||
|
@ -95,27 +75,26 @@ difficulties = {
|
|||
swordless = ['Rupees (20)'] * 4,
|
||||
progressivesword = ['Progressive Sword'] * 3,
|
||||
basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'],
|
||||
timedohko = ['Green Clock'] * 20,
|
||||
timedohko = ['Green Clock'] * 25,
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
triforce_pieces_required = 20,
|
||||
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
||||
conditional_extras = no_conditional_extras,
|
||||
extras = [hardfirst20extra, hardsecond10extra, hardthird10extra, hardfourth10extra, hardfinal20extra],
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 3,
|
||||
progressive_shield_limit = 2,
|
||||
progressive_armor_limit = 1,
|
||||
progressive_bottle_limit = 4,
|
||||
),
|
||||
'expert': Difficulty(
|
||||
baseitems = expertbaseitems,
|
||||
baseitems = normalbaseitems,
|
||||
bottles = hardbottles,
|
||||
bottle_count = 4,
|
||||
same_bottle = False,
|
||||
progressiveshield = ['Progressive Shield'] * 3,
|
||||
basicshield = ['Progressive Shield'] * 3, #only the first one will upgrade, making this equivalent to two blue shields
|
||||
progressivearmor = [],
|
||||
basicarmor = [],
|
||||
progressivearmor = ['Progressive Armor'] * 2, # neither will count
|
||||
basicarmor = ['Progressive Armor'] * 2, # neither will count
|
||||
swordless = ['Rupees (20)'] * 4,
|
||||
progressivesword = ['Progressive Sword'] * 3,
|
||||
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
|
||||
|
@ -124,8 +103,7 @@ difficulties = {
|
|||
triforcehunt = ['Triforce Piece'] * 30,
|
||||
triforce_pieces_required = 20,
|
||||
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
||||
conditional_extras = no_conditional_extras,
|
||||
extras = [expertfirst15extra, expertsecond15extra, expertthird10extra, expertfourth5extra, expertfinal25extra],
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 2,
|
||||
progressive_shield_limit = 1,
|
||||
progressive_armor_limit = 0,
|
||||
|
@ -169,11 +147,13 @@ def generate_itempool(world, player):
|
|||
|
||||
# set up item pool
|
||||
if world.custom:
|
||||
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro, world.customitemarray)
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro, world.customitemarray)
|
||||
world.rupoor_cost = min(world.customitemarray[67], 9999)
|
||||
else:
|
||||
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro)
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.swords, world.retro)
|
||||
world.itempool += ItemFactory(pool, player)
|
||||
for item in precollected_items:
|
||||
world.push_precollected(ItemFactory(item, player))
|
||||
for (location, item) in placed_items:
|
||||
world.push_item(world.get_location(location, player), ItemFactory(item, player), False)
|
||||
world.get_location(location, player).event = True
|
||||
|
@ -301,7 +281,7 @@ def fill_prizes(world, attempts=15):
|
|||
random.shuffle(prize_locs)
|
||||
fill_restrictive(world, all_state, prize_locs, prizepool)
|
||||
except FillError as e:
|
||||
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times" % (e, attempts))
|
||||
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts)
|
||||
for location in empty_crystal_locations:
|
||||
location.item = None
|
||||
continue
|
||||
|
@ -335,6 +315,7 @@ def set_up_shops(world, player):
|
|||
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, retro):
|
||||
pool = []
|
||||
placed_items = []
|
||||
precollected_items = []
|
||||
clock_mode = None
|
||||
treasure_hunt_count = None
|
||||
treasure_hunt_icon = None
|
||||
|
@ -387,6 +368,31 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||
|
||||
if swords == 'swordless':
|
||||
pool.extend(diff.swordless)
|
||||
elif swords == 'assured':
|
||||
precollected_items.append('Fighter Sword')
|
||||
if want_progressives():
|
||||
pool.extend(diff.progressivesword)
|
||||
pool.extend(['Rupees (100)'])
|
||||
else:
|
||||
pool.extend(diff.basicsword)
|
||||
pool.extend(['Rupees (100)'])
|
||||
elif swords == 'vanilla':
|
||||
swords_to_use = []
|
||||
if want_progressives():
|
||||
swords_to_use.extend(diff.progressivesword)
|
||||
swords_to_use.extend(['Progressive Sword'])
|
||||
else:
|
||||
swords_to_use.extend(diff.basicsword)
|
||||
swords_to_use.extend(['Fighter Sword'])
|
||||
random.shuffle(swords_to_use)
|
||||
|
||||
placed_items.append(('Link\'s Uncle', swords_to_use.pop()))
|
||||
placed_items.append(('Blacksmith', swords_to_use.pop()))
|
||||
placed_items.append(('Pyramid Fairy - Left', swords_to_use.pop()))
|
||||
if goal != 'pedestal':
|
||||
placed_items.append(('Master Sword Pedestal', swords_to_use.pop()))
|
||||
else:
|
||||
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
||||
else:
|
||||
if want_progressives():
|
||||
pool.extend(diff.progressivesword)
|
||||
|
@ -411,16 +417,12 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||
treasure_hunt_count = diff.triforce_pieces_required
|
||||
treasure_hunt_icon = 'Triforce Piece'
|
||||
|
||||
cond_extras = diff.conditional_extras(timer, goal, mode, pool, placed_items)
|
||||
pool.extend(cond_extras)
|
||||
extraitems -= len(cond_extras)
|
||||
|
||||
for extra in diff.extras:
|
||||
if extraitems > 0:
|
||||
pool.extend(extra)
|
||||
extraitems -= len(extra)
|
||||
|
||||
if goal == 'pedestal':
|
||||
if goal == 'pedestal' and swords != 'vanilla':
|
||||
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
||||
if retro:
|
||||
pool = [item.replace('Single Arrow','Rupees (5)') for item in pool]
|
||||
|
@ -433,11 +435,12 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
|
|||
placed_items.append((key_location, 'Small Key (Universal)'))
|
||||
else:
|
||||
pool.extend(['Small Key (Universal)'])
|
||||
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
|
||||
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
|
||||
pool = []
|
||||
placed_items = []
|
||||
precollected_items = []
|
||||
clock_mode = None
|
||||
treasure_hunt_count = None
|
||||
treasure_hunt_icon = None
|
||||
|
@ -561,7 +564,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
|||
|
||||
pool.extend(['Fighter Sword'] * customitemarray[32])
|
||||
pool.extend(['Progressive Sword'] * customitemarray[36])
|
||||
|
||||
|
||||
if shuffle == 'insanity_legacy':
|
||||
placed_items.append(('Link\'s House', 'Magic Mirror'))
|
||||
placed_items.append(('Sanctuary', 'Moon Pearl'))
|
||||
|
@ -576,7 +579,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
|||
if itemtotal < total_items_to_place:
|
||||
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
|
||||
|
||||
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||
|
||||
# A quick test to ensure all combinations generate the correct amount of items.
|
||||
def test():
|
||||
|
@ -592,13 +595,15 @@ def test():
|
|||
count = len(out[0]) + len(out[1])
|
||||
|
||||
correct_count = total_items_to_place
|
||||
if goal in ['pedestal']:
|
||||
if goal == 'pedestal' and swords != 'vanilla':
|
||||
# pedestal goals generate one extra item
|
||||
correct_count += 1
|
||||
if retro:
|
||||
correct_count += 28
|
||||
|
||||
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, retro))
|
||||
try:
|
||||
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro))
|
||||
except AssertionError as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
|
1
Main.py
1
Main.py
|
@ -247,6 +247,7 @@ def copy_world(world):
|
|||
|
||||
# copy progress items in state
|
||||
ret.state.prog_items = world.state.prog_items.copy()
|
||||
ret.precollected_items = world.precollected_items.copy()
|
||||
ret.state.stale = {player: True for player in range(1, world.players + 1)}
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
|
|
12
Rom.py
12
Rom.py
|
@ -858,6 +858,18 @@ def patch_rom(world, player, rom):
|
|||
rom.write_byte(0x18302C, 0x18) # starting max health
|
||||
rom.write_byte(0x18302D, 0x18) # starting current health
|
||||
rom.write_byte(0x183039, 0x68) # starting abilities, bit array
|
||||
|
||||
for item in world.precollected_items:
|
||||
if item.player != player:
|
||||
continue
|
||||
|
||||
if item.name == 'Fighter Sword':
|
||||
rom.write_byte(0x183000+0x19, 0x01)
|
||||
rom.write_byte(0x0271A6+0x19, 0x01)
|
||||
rom.write_byte(0x180043, 0x01) # special starting sword byte
|
||||
else:
|
||||
raise RuntimeError("Unsupported pre-collected item: {}".format(item))
|
||||
|
||||
rom.write_byte(0x18004A, 0x00 if world.mode != 'inverted' else 0x01) # Inverted mode
|
||||
rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier
|
||||
rom.write_byte(0x2AF79, 0xD0 if world.mode != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both)
|
||||
|
|
1
Rules.py
1
Rules.py
|
@ -957,6 +957,7 @@ def standard_rules(world, player):
|
|||
def uncle_item_rule(item):
|
||||
copy_state = CollectionState(world)
|
||||
copy_state.collect(item)
|
||||
copy_state.sweep_for_events()
|
||||
return copy_state.can_reach('Sanctuary', 'Region', player)
|
||||
|
||||
add_item_rule(world.get_location('Link\'s Uncle', player), uncle_item_rule)
|
||||
|
|
Loading…
Reference in New Issue