Add Item functionality setting and progressive bow

Progressive bows still have issue for silvers hint
This commit is contained in:
Kevin Cathcart 2019-08-10 19:37:26 -04:00
parent 996bf8495c
commit 418568df60
6 changed files with 76 additions and 27 deletions

View File

@ -8,13 +8,14 @@ from Utils import int16_as_bytes
class World(object):
def __init__(self, players, shuffle, logic, mode, swords, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints):
def __init__(self, players, shuffle, logic, mode, swords, difficulty, difficulty_adjustments, timer, progressive, goal, algorithm, place_dungeon_items, accessibility, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray, boss_shuffle, hints):
self.players = players
self.shuffle = shuffle
self.logic = logic
self.mode = mode
self.swords = swords
self.difficulty = difficulty
self.difficulty_adjustments = difficulty_adjustments
self.timer = timer
self.progressive = progressive
self.goal = goal
@ -166,9 +167,9 @@ class World(object):
elif 'Bow' in item.name:
if ret.has('Silver Arrows', item.player):
pass
elif ret.has('Bow', item.player):
elif ret.has('Bow', item.player) and self.difficulty_requirements.progressive_bow_limit >= 2:
ret.prog_items.add(('Silver Arrows', item.player))
else:
elif self.difficulty_requirements.progressive_bow_limit >= 1:
ret.prog_items.add(('Bow', item.player))
elif item.name.startswith('Bottle'):
if ret.bottle_count(item.player) < self.difficulty_requirements.progressive_bottle_limit:
@ -403,10 +404,11 @@ class CollectionState(object):
def heart_count(self, player):
# Warning: This only considers items that are marked as advancement items
diff = self.world.difficulty_requirements
return (
self.item_count('Boss Heart Container', player)
min(self.item_count('Boss Heart Container', player), diff.boss_heart_container_limit)
+ self.item_count('Sanctuary Heart Container', player)
+ self.item_count('Piece of Heart', player) // 4
+ min(self.item_count('Piece of Heart', player), diff.heart_piece_limit) // 4
+ 3 # starting hearts
)
@ -420,9 +422,9 @@ class CollectionState(object):
elif self.has('Half Magic', player):
basemagic = 16
if self.can_buy_unlimited('Green Potion', player) or self.can_buy_unlimited('Blue Potion', player):
if self.world.difficulty == 'hard' and not fullrefill:
if self.world.difficulty_adjustments == 'hard' and not fullrefill:
basemagic = basemagic + int(basemagic * 0.5 * self.bottle_count(player))
elif self.world.difficulty == 'expert' and not fullrefill:
elif self.world.difficulty_adjustments == 'expert' and not fullrefill:
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count(player))
else:
basemagic = basemagic + basemagic * self.bottle_count(player)
@ -1019,6 +1021,7 @@ class Spoiler(object):
'shuffle': self.world.shuffle,
'algorithm': self.world.algorithm,
'difficulty': self.world.difficulty,
'difficulty_mode': self.world.difficulty_adjustments,
'timer': self.world.timer,
'progressive': self.world.progressive,
'accessibility': self.world.accessibility,
@ -1052,6 +1055,8 @@ class Spoiler(object):
outfile.write('Logic: %s\n' % self.metadata['logic'])
outfile.write('Mode: %s\n' % self.metadata['mode'])
outfile.write('Goal: %s\n' % self.metadata['goal'])
outfile.write('Difficulty: %s\n' % self.metadata['difficulty'])
outfile.write('Item Functionality: %s\n' % self.metadata['difficulty_mode'])
outfile.write('Entrance Shuffle: %s\n' % self.metadata['shuffle'])
outfile.write('Filling Algorithm: %s\n' % self.metadata['algorithm'])
outfile.write('Accessibility: %s\n' % self.metadata['accessibility'])

View File

@ -74,6 +74,13 @@ def start():
Hard: A harder setting with less equipment and reduced health.
Expert: A harder yet setting with minimum equipment and health.
''')
parser.add_argument('--item_functionality', default='normal', const='normal', nargs='?', choices=['normal', 'hard', 'expert'],
help='''\
Select limits on item functionality to increase difficulty. (default: %(default)s)
Normal: Normal functionality.
Hard: Reduced functionality.
Expert: Greatly reduced functionality.
''')
parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'],
help='''\
Select game timer setting. Affects available itempool. (default: %(default)s)

View File

@ -13,7 +13,7 @@ from Items import ItemFactory
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp',
alwaysitems = ['Bombos', 'Book of Mudora', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp',
'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang']
progressivegloves = ['Progressive Glove'] * 2
basicgloves = ['Power Glove', 'Titans Mitts']
@ -21,7 +21,7 @@ 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 (10)'] +
normalbaseitems = (['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)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
@ -32,10 +32,11 @@ normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
Difficulty = namedtuple('Difficulty',
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
'progressivesword', 'basicsword', 'timedohko', 'timedother',
'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother',
'triforcehunt', 'triforce_pieces_required', 'retro',
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
'progressive_armor_limit', 'progressive_bottle_limit'])
'progressive_armor_limit', 'progressive_bottle_limit',
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
total_items_to_place = 153
@ -52,6 +53,7 @@ difficulties = {
swordless = ['Rupees (20)'] * 4,
progressivesword = ['Progressive Sword'] * 3,
basicsword = ['Master Sword', 'Tempered Sword', 'Golden Sword'],
basicbow = ['Bow', 'Silver Arrows'],
timedohko = ['Green Clock'] * 25,
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30,
@ -61,7 +63,10 @@ difficulties = {
progressive_sword_limit = 4,
progressive_shield_limit = 3,
progressive_armor_limit = 2,
progressive_bow_limit = 2,
progressive_bottle_limit = 4,
boss_heart_container_limit = 255,
heart_piece_limit = 255,
),
'hard': Difficulty(
baseitems = normalbaseitems,
@ -71,10 +76,11 @@ difficulties = {
progressiveshield = ['Progressive Shield'] * 3,
basicshield = ['Blue Shield', 'Red Shield', 'Red Shield'],
progressivearmor = ['Progressive Armor'] * 2,
basicarmor = ['Progressive Armor'] * 2, #only the first one will upgrade, making this equivalent to two blue mail
basicarmor = ['Progressive Armor'] * 2, # neither will count
swordless = ['Rupees (20)'] * 4,
progressivesword = ['Progressive Sword'] * 3,
basicsword = ['Master Sword', 'Master Sword', 'Tempered Sword'],
basicbow = ['Bow'] * 2,
timedohko = ['Green Clock'] * 25,
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30,
@ -83,8 +89,11 @@ difficulties = {
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
progressive_sword_limit = 3,
progressive_shield_limit = 2,
progressive_armor_limit = 1,
progressive_armor_limit = 0,
progressive_bow_limit = 1,
progressive_bottle_limit = 4,
boss_heart_container_limit = 6,
heart_piece_limit = 16,
),
'expert': Difficulty(
baseitems = normalbaseitems,
@ -98,6 +107,7 @@ difficulties = {
swordless = ['Rupees (20)'] * 4,
progressivesword = ['Progressive Sword'] * 3,
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
basicbow = ['Bow'] * 2,
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30,
@ -107,7 +117,10 @@ difficulties = {
progressive_sword_limit = 2,
progressive_shield_limit = 1,
progressive_armor_limit = 0,
progressive_bow_limit = 1,
progressive_bottle_limit = 4,
boss_heart_container_limit = 2,
heart_piece_limit = 8,
),
}
@ -366,8 +379,18 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, swords, r
else:
pool.extend(diff.basicarmor)
if swords != 'swordless':
if want_progressives():
pool.extend(['Progressive Bow'] * 2)
else:
pool.extend(diff.basicbow)
if swords == 'swordless':
pool.extend(diff.swordless)
if want_progressives():
pool.extend(['Progressive Bow'] * 2)
else:
pool.extend(['Bow', 'Silver Arrows'])
elif swords == 'assured':
precollected_items.append('Fighter Sword')
if want_progressives():

View File

@ -24,7 +24,7 @@ def main(args, seed=None):
start = time.clock()
# initialize the world
world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints)
world = World(args.multi, args.shuffle, args.logic, args.mode, args.swords, args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.accessibility, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray, args.shufflebosses, args.hints)
logger = logging.getLogger('')
if seed is None:
random.seed(None)
@ -117,7 +117,7 @@ def main(args, seed=None):
else:
sprite = None
outfilebase = 'ER_%s_%s-%s-%s%s_%s-%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed)
outfilebase = 'ER_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed)
use_enemizer = args.enemizercli and (args.shufflebosses != 'none' or args.shuffleenemies or args.enemy_health != 'default' or args.enemy_health != 'default' or args.enemy_damage or args.shufflepalette or args.shufflepots)
@ -180,7 +180,7 @@ def gt_filler(world):
def copy_world(world):
# ToDo: Not good yet
ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints)
ret = World(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.accessibility, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray, world.boss_shuffle, world.hints)
ret.required_medallions = world.required_medallions.copy()
ret.swamp_patch_required = world.swamp_patch_required.copy()
ret.ganon_at_pyramid = world.ganon_at_pyramid.copy()

View File

@ -348,6 +348,12 @@ Select the game completion goal. (default: ganon)
Select the game difficulty. Affects available itempool. (default: normal)
```
--item_functionality [{normal,hard,expert}]
```
Select limits on item functionality to increase difficulty. (default: normal)
```
--timer [{none,display,timed,timed-ohko,ohko,timed-countdown}]
```

30
Rom.py
View File

@ -580,8 +580,9 @@ def patch_rom(world, player, rom):
GREEN_CLOCK = ItemFactory('Green Clock', player).code
rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on
# handle difficulty
if world.difficulty == 'hard':
# handle difficulty_adjustments
if world.difficulty_adjustments == 'hard':
# Powdered Fairies Prize
rom.write_byte(0x36DD0, 0xD8) # One Heart
# potion heal amount
@ -599,9 +600,7 @@ def patch_rom(world, player, rom):
rom.write_int16(0x180036, world.rupoor_cost)
# Set stun items
rom.write_byte(0x180180, 0x02) # Hookshot only
# Make silver arrows only usable against Ganon
rom.write_byte(0x180181, 0x01)
elif world.difficulty == 'expert':
elif world.difficulty_adjustments == 'expert':
# Powdered Fairies Prize
rom.write_byte(0x36DD0, 0xD8) # One Heart
# potion heal amount
@ -619,8 +618,6 @@ def patch_rom(world, player, rom):
rom.write_int16(0x180036, world.rupoor_cost)
# Set stun items
rom.write_byte(0x180180, 0x00) # Nothing
# Make silver arrows only usable against Ganon
rom.write_byte(0x180181, 0x01)
else:
# Powdered Fairies Prize
rom.write_byte(0x36DD0, 0xE3) # fairy
@ -638,20 +635,28 @@ def patch_rom(world, player, rom):
rom.write_int16(0x180036, world.rupoor_cost)
# Set stun items
rom.write_byte(0x180180, 0x03) # All standard items
# Make silver arrows freely usable
rom.write_byte(0x180181, 0x00)
#Set overflow items for progressive equipment
if world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
overflow_replacement = GREEN_CLOCK
else:
overflow_replacement = GREEN_TWENTY_RUPEES
rom.write_byte(0x180181, 0x00) # Make silver arrows freely usable
rom.write_byte(0x180182, 0x01) # auto equip silvers on pickup
#Byrna residual magic cost
rom.write_bytes(0x45C42, [0x04, 0x02, 0x01])
difficulty = world.difficulty_requirements
if difficulty.progressive_bow_limit < 2 and world.swords == 'swordless':
# TODO: write 2 to progressive bow limit byte
rom.write_byte(0x180181, 0x01) # Make silver arrows work on on ganon
else:
# TODO: write difficulty.progressive_bow_limit to progressive bow limit byte
pass
#Set overflow items for progressive equipment
rom.write_bytes(0x180090,
[difficulty.progressive_sword_limit, overflow_replacement,
@ -686,7 +691,7 @@ def patch_rom(world, player, rom):
random.shuffle(packs)
prizes[:56] = [drop for pack in packs for drop in pack]
if world.difficulty in ['hard', 'expert']:
if world.difficulty_adjustments in ['hard', 'expert']:
prize_replacements = {0xE0: 0xDF, # Fairy -> heart
0xE3: 0xD8} # Big magic -> small magic
prizes = [prize_replacements.get(prize, prize) for prize in prizes]
@ -735,6 +740,9 @@ def patch_rom(world, player, rom):
0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees
0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade
0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade
0x58, 0x01, 0x36 if world.retro else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode)
0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20
0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20
0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel
])
@ -804,7 +812,7 @@ def patch_rom(world, player, rom):
rom.write_int32(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
if world.difficulty == 'normal':
if world.difficulty_adjustments == 'normal':
rom.write_int32(0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
else:
rom.write_int32(0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32)