Add support for starhunt and treasurehunt goals. Add timed difficulties.

This commit is contained in:
LLCoolDave 2017-06-04 14:44:23 +02:00
parent 4ed2a5b510
commit a20eaae13f
4 changed files with 93 additions and 57 deletions

View File

@ -124,6 +124,7 @@ class World(object):
prog_locations = [location for location in self.get_locations() if location.item is not None and location.item.advancement]
state = CollectionState(self)
treasure_pieces_collected = 0
while prog_locations:
sphere = []
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
@ -131,6 +132,10 @@ class World(object):
if state.can_reach(location):
if location.item.name == 'Triforce':
return True
elif location.item.name in ['Triforce Piece', 'Power Star']:
treasure_pieces_collected += 1
if self.goal in ['starhunt', 'triforcehunt'] and treasure_pieces_collected >= self.treasure_hunt_count:
return True
sphere.append(location)
if not sphere:

76
Main.py
View File

@ -305,21 +305,44 @@ def flood_items(world):
def generate_itempool(world):
if world.difficulty != 'normal' or world.goal not in ['ganon', 'pedestal', 'dungeons'] or world.mode not in ['open', 'standard']:
if world.difficulty not in ['normal', 'timed', 'timed-ohko', 'timed-countdown'] or world.goal not in ['ganon', 'pedestal', 'dungeons', 'starhunt', 'triforcehunt'] or world.mode not in ['open', 'standard']:
raise NotImplementedError('Not supported yet')
world.push_item('Ganon', ItemFactory('Triforce'), False)
# set up item pool
world.itempool = ItemFactory(['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 + ['Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] +
['Progressive Armor'] * 2 + ['Progressive Shield'] * 3 + ['Progressive Sword'] * 3 + ['Progressive Glove'] * 2 +
['Bottle'] * 4 +
['Bombos', 'Book of Mudora', 'Blue Boomerang', 'Bow', 'Bug Catching Net', 'Cane of Byrna', 'Cane of Somaria',
'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder',
'Red Boomerang', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Silver Arrows'] +
['Single Arrow', 'Sanctuary Heart Container', 'Rupees (100)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24 +
['Rupees (50)'] * 7 + ['Rupees (5)'] * 4 + ['Rupee (1)'] * 2 + ['Rupees (300)'] * 4 + ['Rupees (20)'] * 28 +
['Arrows (10)'] * 4 + ['Bombs (3)'] * 10)
if world.difficulty in ['timed', 'timed-countdown']:
world.itempool = ItemFactory(['Arrow Upgrade (+5)'] * 2 + ['Bomb Upgrade (+5)'] * 2 + ['Arrow Upgrade (+10)'] * 3 + ['Bomb Upgrade (+10)'] * 3 +
['Progressive Armor'] * 2 + ['Progressive Shield'] * 3 + ['Progressive Sword'] * 3 + ['Progressive Glove'] * 2 +
['Bottle'] * 4 +
['Bombos', 'Book of Mudora', 'Blue Boomerang', 'Bow', 'Bug Catching Net', 'Cane of Byrna', 'Cane of Somaria',
'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder',
'Red Boomerang', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Silver Arrows'] +
['Sanctuary Heart Container'] + ['Rupees (100)'] * 2 + ['Boss Heart Container'] * 12 + ['Piece of Heart'] * 16 +
['Rupees (50)'] * 8 + ['Rupees (300)'] * 5 + ['Rupees (20)'] * 4 +
['Arrows (10)'] * 2 + ['Bombs (3)'] * 10 + ['Red Clock'] * 10 + ['Blue Clock'] * 10 + ['Green Clock'] * 20)
world.clock_mode = 'stopwatch' if world.difficulty == 'timed' else 'countdown'
elif world.difficulty == 'timed-ohko':
world.itempool = ItemFactory(['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 + ['Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] +
['Progressive Armor'] * 2 + ['Progressive Shield'] * 3 + ['Progressive Sword'] * 3 + ['Progressive Glove'] * 2 +
['Bottle'] * 4 +
['Bombos', 'Book of Mudora', 'Blue Boomerang', 'Bow', 'Bug Catching Net', 'Cane of Byrna', 'Cane of Somaria',
'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder',
'Red Boomerang', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Silver Arrows'] +
['Single Arrow', 'Sanctuary Heart Container'] + ['Rupees (100)'] * 3 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24 +
['Rupees (50)'] * 7 + ['Rupees (300)'] * 6 + ['Rupees (20)'] * 5 +
['Arrows (10)'] * 4 + ['Bombs (3)'] * 10 + ['Green Clock'] * 25)
world.clock_mode = 'ohko'
else:
world.itempool = ItemFactory(['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 + ['Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] +
['Progressive Armor'] * 2 + ['Progressive Shield'] * 3 + ['Progressive Sword'] * 3 + ['Progressive Glove'] * 2 +
['Bottle'] * 4 +
['Bombos', 'Book of Mudora', 'Blue Boomerang', 'Bow', 'Bug Catching Net', 'Cane of Byrna', 'Cane of Somaria',
'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', 'Cape', 'Magic Powder',
'Red Boomerang', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Silver Arrows'] +
['Single Arrow', 'Sanctuary Heart Container', 'Rupees (100)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24 +
['Rupees (50)'] * 7 + ['Rupees (5)'] * 4 + ['Rupee (1)'] * 2 + ['Rupees (300)'] * 4 + ['Rupees (20)'] * 28 +
['Arrows (10)'] * 4 + ['Bombs (3)'] * 10)
if world.mode == 'standard':
world.push_item('Uncle', ItemFactory('Progressive Sword'))
@ -335,14 +358,14 @@ def generate_itempool(world):
if world.goal == 'pedestal':
world.push_item('Altar', ItemFactory('Triforce'))
items = list(world.itempool)
random.shuffle(items)
for item in items:
if not item.advancement:
# save to remove
world.itempool.remove(item)
break
# ToDo what to do if EVERYTHING is a progress item?
elif world.goal == 'starhunt':
world.treasure_hunt_count = 10
world.treasure_hunt_icon = 'Power Star'
world.itempool.extend(ItemFactory(['Power Star'] * 15))
elif world.goal == 'triforcehunt':
world.treasure_hunt_count = 3
world.treasure_hunt_icon = 'Triforce Piece'
world.itempool.extend(ItemFactory(['Triforce Piece'] * 3))
if random.randint(0, 3) == 0:
world.itempool.append(ItemFactory('Magic Upgrade (1/4)'))
@ -410,8 +433,8 @@ def create_playthrough(world):
# create a copy as we will modify it
world = copy_world(world)
# if we do pedestal%, ganon should not be a viable option as far as the playthrough is concerned
if world.goal == 'pedestal':
# in treasure hunt and pedestal goals, ganon is invincible
if world.goal in ['pedestal', 'starhunt', 'triforcehunt']:
world.get_location('Ganon').item = None
# get locations containing progress items
@ -478,9 +501,16 @@ if __name__ == '__main__':
help='Select Enforcement of Item Requirements. Minor Glitches may require Fake Flippers, Bunny Revival and Dark Room Navigation.')
parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open'],
help='Select game mode. Standard fixes Hyrule Castle Secret Entrance and Front Door, but may lead to weird rain state issues if you exit through the Hyrule Castle side exits before rescuing Zelda in a full shuffle.')
parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons'],
help='Select completion goal. Pedestal places a second Triforce at the Master Sword Pedestal, the playthrough may still deem Ganon to be the easier goal. All dungeons is not enforced ingame but considered in the rules.')
parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['normal'], help='Select game difficulty. Affects available itempool.')
parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'starhunt', 'triforcehunt'],
help='Select completion goal. Pedestal places the Triforce at the Master Sword Pedestal. All dungeons is not enforced ingame but considered in the playthrough. \n'
'Star Hunt places 15 Power Stars pieces in the world, collect 10 of them to beat the game.\n'
'Triforce Hunt places 3 Triforce Pieces in the world, collect them all to beat the game.')
parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['normal', 'timed', 'timed-ohko', 'timed-countdown'],
help='Select game difficulty. Affects available itempool.\n'
'Timed modes replace low value items with clocks, the overall rupee count in the pool stays roughly the same.\n'
'Timed starts with clock at zero. Green Clocks subtract 4 minutes (Total: 20), Blue Clocks subtract 2 minutes (Total: 10), Red Clocks add 2 minutes (Total: 10). Winner is player with lowest time at the end.\n'
'Timed OHKO starts clock at 10 minutes. Green Clocks add 5 minutes (Total: 25). As long as clock is at 0, Link will die in one hit.\n'
'Timed Countdown starts with clock at 40 minutes. Same clocks as Timed mode. If time runs out, you lose (but can still keep playing).')
parser.add_argument('--algorithm', default='freshness', const='freshness', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22'],
help='Select item filling algorithm. vt21 is unbiased in its selection, but has tendency to put Ice Rod in Turtle Rock.\n'
'vt22 drops off stale locations after 1/3 of progress items were placed to try to circumvent vt21\'s shortcomings.\n'

View File

@ -183,8 +183,8 @@ def create_playthrough(world):
# create a copy as we will modify it
world = copy_world(world)
# if we do pedestal%, ganon should not be a viable option as far as the playthrough is concerned
if world.goal == 'pedestal':
# in treasure hunt and pedestal goals, ganon is invincible
if world.goal in ['pedestal', 'starhunt', 'triforcehunt']:
world.get_location('Ganon').item = None
# get locations containing progress items

65
Rom.py
View File

@ -94,16 +94,7 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
write_byte(rom, 0x18003A, 0x00) # dark world light cone disable
# handle difficulty
if world.difficulty == 'normal':
# Spike Cave Damage
write_byte(rom, 0x180168, 0x08)
# Powdered Fairies Prize
write_byte(rom, 0x36DD0, 0xE3) # fairy
# potion heal amount
write_byte(rom, 0x180084, 0xA0) # full
# potion magic restore amount
write_byte(rom, 0x180085, 0x80) # full
elif world.difficulty == 'hard':
if world.difficulty == 'hard':
# Spike Cave Damage
write_byte(rom, 0x180168, 0x02)
# Powdered Fairies Prize
@ -112,6 +103,15 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
write_byte(rom, 0x180084, 0x08) # One Heart
# potion magic restore amount
write_byte(rom, 0x180085, 0x20) # Quarter Magic
else:
# Spike Cave Damage
write_byte(rom, 0x180168, 0x08)
# Powdered Fairies Prize
write_byte(rom, 0x36DD0, 0xE3) # fairy
# potion heal amount
write_byte(rom, 0x180084, 0xA0) # full
# potion magic restore amount
write_byte(rom, 0x180085, 0x80) # full
# set up game internal RNG seed
for i in range(1024):
@ -145,10 +145,11 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
write_bytes(rom, 0x37A78, prizes)
# prize pack drop chances
if world.difficulty == 'normal':
droprates = [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01] # 50%
else:
if world.difficulty == 'hard':
droprates = [0x01, 0x02, 0x03, 0x03, 0x03, 0x04, 0x04] # 50%, 25%, 3* 12.5%, 2* 6.25%
else:
droprates = [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01] # 50%
random.shuffle(droprates)
write_bytes(rom, 0x37A62, droprates)
@ -178,31 +179,31 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
# set up clocks for timed modes
if world.clock_mode == 'off':
write_bytes(rom, 0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
write_bytes(rom, 0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180208, [0x00, 0x00, 0x00, 0x00]) # green clock adjustment time (in frames, uint32)
write_bytes(rom, 0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, uint32)
write_bytes(rom, 0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x00, 0x00, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
elif world.clock_mode == 'ohko':
write_bytes(rom, 0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
write_bytes(rom, 0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180208, [0x50, 0x46, 0x00, 0x00]) # green clock adjustment time (in frames, uint32)
write_bytes(rom, 0x18020C, [0xA0, 0x8C, 0x00, 0x00]) # starting time (in frames, uint32)
write_bytes(rom, 0x180200, [0x00, 0x00, 0x00, 0x00]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x00, 0x00, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x50, 0x46, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0xA0, 0x8C, 0x00, 0x00]) # starting time (in frames, sint32)
if world.clock_mode == 'stopwatch':
write_bytes(rom, 0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
write_bytes(rom, 0x180200, [0x1C, 0x20, 0x00, 0x00]) # red clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180204, [0x1C, 0x20, 0x00, 0x00]) # blue clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180208, [0x38, 0x40, 0x00, 0x00]) # green clock adjustment time (in frames, uint32)
write_bytes(rom, 0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, uint32)
write_bytes(rom, 0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0x00, 0x00, 0x00, 0x00]) # starting time (in frames, sint32)
if world.clock_mode == 'countdown':
write_bytes(rom, 0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
write_bytes(rom, 0x180200, [0x1C, 0x20, 0x00, 0x00]) # red clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180204, [0x1C, 0x20, 0x00, 0x00]) # blue clock adjustment time (in frames, uint32)
write_bytes(rom, 0x180208, [0x38, 0x40, 0x00, 0x00]) # green clock adjustment time (in frames, uint32)
write_bytes(rom, 0x18020C, [0x80, 0x32, 0x02, 0x00]) # starting time (in frames, uint32)
write_bytes(rom, 0x180200, [0xE0, 0xE3, 0xFF, 0xFF]) # red clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180204, [0x20, 0x1C, 0x00, 0x00]) # blue clock adjustment time (in frames, sint32)
write_bytes(rom, 0x180208, [0x40, 0x38, 0x00, 0x00]) # green clock adjustment time (in frames, sint32)
write_bytes(rom, 0x18020C, [0x80, 0x32, 0x02, 0x00]) # starting time (in frames, sint32)
# set up goals for treasure hunt
write_bytes(rom, 0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce' else [0x0D, 0x28])
write_bytes(rom, 0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28])
write_byte(rom, 0x180167, world.treasure_hunt_count % 256)
# assorted fixes
@ -210,7 +211,7 @@ def patch_rom(world, rom, hashtable, quickswap=False, beep='normal', sprite=None
write_byte(rom, 0x180036, 0x0A) # Rupoor negative value
write_byte(rom, 0x180169, 0x01 if world.lock_aga_door_in_escape else 0x00) # Lock or unlock aga tower door during escape sequence.
write_byte(rom, 0x180086, 0x00 if world.aga_randomness == 'vanilla' else 0x02 if world.aga_randomness == 'table' else 0x01) # set blue ball and ganon warp randomness
if world.goal == 'pedestal':
if world.goal in ['pedestal', 'starhunt', 'triforcehunt']:
write_byte(rom, 0x18003E, 0x01) # make ganon invincible
# remove shield from uncle
@ -316,7 +317,7 @@ def write_strings(rom, world):
write_string_to_rom(rom, 'PyramidFairy', PyramidFairy_texts[random.randint(0, len(PyramidFairy_texts) - 1)])
write_string_to_rom(rom, 'Sahasrahla2', Sahasrahla2_texts[random.randint(0, len(Sahasrahla2_texts) - 1)])
write_string_to_rom(rom, 'Blind', Blind_texts[random.randint(0, len(Blind_texts) - 1)])
if world.goal == 'pedestal':
if world.goal in ['pedestal', 'starhunt', 'triforcehunt']:
write_string_to_rom(rom, 'Ganon1', 'Why are you even here?\n You can\'t even hurt me!')
else:
write_string_to_rom(rom, 'Ganon1', Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)])