New goals and rom update

This commit is contained in:
aerinon
2022-12-01 14:10:10 -07:00
parent 1c91eef29a
commit becba348b9
14 changed files with 74 additions and 28 deletions

View File

@@ -155,7 +155,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)})'
@@ -1035,6 +1037,10 @@ 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):
return (len([x for x in self.locations_checked if x.player == player])
>= len(self.world.get_filled_locations(player)))
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
@@ -2587,7 +2593,7 @@ class Spoiler(object):
outfile.write('Mode: %s\n' % self.metadata['mode'][player]) outfile.write('Mode: %s\n' % self.metadata['mode'][player])
outfile.write('Swords: %s\n' % self.metadata['weapons'][player]) outfile.write('Swords: %s\n' % self.metadata['weapons'][player])
outfile.write('Goal: %s\n' % self.metadata['goal'][player]) outfile.write('Goal: %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: %s\n' % self.metadata['triforcegoal'][player]) outfile.write('Triforce Pieces Required: %s\n' % self.metadata['triforcegoal'][player])
outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player]) outfile.write('Triforce Pieces Total: %s\n' % self.metadata['triforcepool'][player])
outfile.write('Crystals required for GT: %s\n' % (str(self.world.crystals_gt_orig[player]))) outfile.write('Crystals required for GT: %s\n' % (str(self.world.crystals_gt_orig[player])))
@@ -2867,7 +2873,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}

View File

@@ -400,7 +400,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)

View File

@@ -181,8 +181,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']:
@@ -344,7 +348,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'
@@ -817,15 +821,15 @@ def add_pot_contents(world, player):
world.itempool.append(ItemFactory(item, player)) world.itempool.append(ItemFactory(item, player))
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 goal in ['triforcehunt', 'trinity']: if goal in ['triforcehunt', 'trinity', 'ganonhunt']:
if treasure_hunt_total == 0: if treasure_hunt_total == 0:
treasure_hunt_total = 30 if goal == 'triforcehunt' else 10 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)
@@ -928,7 +932,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:
@@ -987,7 +991,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
@@ -1025,8 +1029,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
@@ -1214,7 +1218,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

View File

@@ -34,7 +34,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

View File

@@ -53,7 +53,16 @@ This is similar to insanity mode in ER where door entrances and exits are not pa
## Customizer ## Customizer
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
@@ -100,10 +109,10 @@ 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
* Fixed the issue when defeating Agahnim and standing in the doorway can cause door state to linger.
# 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
Rom.py
View File

@@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'b6fcbc0d61faffa178135545f18fadbd' RANDOMIZERBASEHASH = 'fb7f9a0d501ba9ecd0a31066f9a0a973'
class JsonRom(object): class JsonRom(object):
@@ -730,7 +730,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':
@@ -1275,7 +1276,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)
@@ -1340,6 +1341,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, 0x09) # 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
@@ -2362,6 +2367,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!'

View File

@@ -60,6 +60,10 @@ def set_rules(world, player):
add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
elif world.goal[player] in ['triforcehunt', 'trinity']: elif world.goal[player] in ['triforcehunt', 'trinity']:
add_rule(world.get_location('Murahdahla', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) add_rule(world.get_location('Murahdahla', 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] == '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 world.mode[player] != 'inverted': if world.mode[player] != 'inverted':
set_big_bomb_rules(world, player) set_big_bomb_rules(world, player)

Binary file not shown.

View File

@@ -82,6 +82,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

View File

@@ -67,7 +67,9 @@
"dungeons", "dungeons",
"triforcehunt", "triforcehunt",
"trinity", "trinity",
"crystals" "crystals",
"ganonhunt",
"completionist"
] ]
}, },
"difficulty": { "difficulty": {

View File

@@ -107,7 +107,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)",

View File

@@ -248,6 +248,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",

View File

@@ -36,7 +36,9 @@
"dungeons", "dungeons",
"triforcehunt", "triforcehunt",
"trinity", "trinity",
"crystals" "crystals",
"ganonhunt",
"completionist"
] ]
}, },
"crystals_gt": { "crystals_gt": {

View File

@@ -113,7 +113,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 = goal in ['fast_ganon', 'trinity'] if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False ret.openpyramid = goal in ['fast_ganon', 'trinity'] if ret.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple'] else False