Merge branch 'fouton_vanilla' into beta
This commit is contained in:
+8
-1
@@ -1519,7 +1519,10 @@ class CollectionState(object):
|
|||||||
if not item:
|
if not item:
|
||||||
return
|
return
|
||||||
changed = False
|
changed = False
|
||||||
if item.name.startswith('Progressive '):
|
if item.name == "Sword and Shield":
|
||||||
|
self.prog_items["Fighter Sword", item.player] += 1
|
||||||
|
self.prog_items["Blue Shield", item.player] += 1
|
||||||
|
elif item.name.startswith('Progressive '):
|
||||||
if 'Sword' in item.name:
|
if 'Sword' in item.name:
|
||||||
if self.has('Golden Sword', item.player):
|
if self.has('Golden Sword', item.player):
|
||||||
pass
|
pass
|
||||||
@@ -2895,6 +2898,10 @@ class Item(object):
|
|||||||
def compass(self):
|
def compass(self):
|
||||||
return self.type == 'Compass'
|
return self.type == 'Compass'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def event(self):
|
||||||
|
return self.type == 'Event'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dungeon(self):
|
def dungeon(self):
|
||||||
if not self.smallkey and not self.bigkey and not self.map and not self.compass:
|
if not self.smallkey and not self.bigkey and not self.map and not self.compass:
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli", "help", "count")), type=int)
|
parser.add_argument('--count', default=defval(int(settings["count"]) if settings["count"] != "" and settings["count"] is not None else 1), help="\n".join(fish.translate("cli", "help", "count")), type=int)
|
||||||
parser.add_argument('--tries', default=defval(int(settings["tries"]) if settings["tries"] != "" and settings["tries"] is not None else 1), help="\n".join(fish.translate("cli", "help", "tries")), type=int)
|
parser.add_argument('--tries', default=defval(int(settings["tries"]) if settings["tries"] != "" and settings["tries"] is not None else 1), help="\n".join(fish.translate("cli", "help", "tries")), type=int)
|
||||||
parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS)
|
parser.add_argument('--customitemarray', default={}, help=argparse.SUPPRESS)
|
||||||
|
parser.add_argument('--skip_money_balance', action="store_true", help=argparse.SUPPRESS)
|
||||||
|
|
||||||
# included for backwards compatibility
|
# included for backwards compatibility
|
||||||
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
|
parser.add_argument('--multi', default=defval(settings["multi"]), type=lambda value: min(max(int(value), 1), 255))
|
||||||
@@ -106,7 +107,14 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
ret = parser.parse_args(argv)
|
ret = parser.parse_args(argv)
|
||||||
|
|
||||||
if ret.keysanity:
|
if ret.keysanity:
|
||||||
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = ['wild'] * 4
|
if ret.mapshuffle == "none":
|
||||||
|
ret.mapshuffle = "wild"
|
||||||
|
if ret.compassshuffle == "none":
|
||||||
|
ret.compassshuffle = "wild"
|
||||||
|
if ret.keyshuffle == "none":
|
||||||
|
ret.keyshuffle = "wild"
|
||||||
|
if ret.bigkeyshuffle == "none":
|
||||||
|
ret.bigkeyshuffle = "wild"
|
||||||
|
|
||||||
if ret.keydropshuffle:
|
if ret.keydropshuffle:
|
||||||
ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' else ret.dropshuffle
|
ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' else ret.dropshuffle
|
||||||
@@ -178,7 +186,7 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
'decoupledoors', 'door_type_mode', 'bonk_drops',
|
'decoupledoors', 'door_type_mode', 'bonk_drops',
|
||||||
'trap_door_mode', 'key_logic_algorithm',
|
'trap_door_mode', 'key_logic_algorithm',
|
||||||
'door_self_loops', 'any_enemy_logic', 'aga_randomness',
|
'door_self_loops', 'any_enemy_logic', 'aga_randomness',
|
||||||
'money_balance']:
|
'money_balance', 'patches']:
|
||||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
setattr(ret, name, {1: value})
|
setattr(ret, name, {1: value})
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
|
|||||||
or (world.algorithm == 'vanilla_fill' and item_to_place.is_near_dungeon_item(world)))) \
|
or (world.algorithm == 'vanilla_fill' and item_to_place.is_near_dungeon_item(world)))) \
|
||||||
or valid_dungeon_placement(item_to_place, location, world):
|
or valid_dungeon_placement(item_to_place, location, world):
|
||||||
return location
|
return location
|
||||||
|
|
||||||
if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize:
|
if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize:
|
||||||
location.item = None
|
location.item = None
|
||||||
location.event = False
|
location.event = False
|
||||||
@@ -311,7 +312,9 @@ def valid_dungeon_placement(item, location, world):
|
|||||||
dungeon = check_dungeon
|
dungeon = check_dungeon
|
||||||
if dungeon:
|
if dungeon:
|
||||||
layout = world.dungeon_layouts[location.player][dungeon.name]
|
layout = world.dungeon_layouts[location.player][dungeon.name]
|
||||||
if not is_dungeon_item(item, world) or item.player != location.player:
|
if item.event:
|
||||||
|
return True
|
||||||
|
elif not is_dungeon_item(item, world) or item.player != location.player:
|
||||||
if item.prize and item.is_near_dungeon_item(world):
|
if item.prize and item.is_near_dungeon_item(world):
|
||||||
return item.dungeon_object == dungeon and layout.free_items > 0
|
return item.dungeon_object == dungeon and layout.free_items > 0
|
||||||
return layout.free_items > 0
|
return layout.free_items > 0
|
||||||
|
|||||||
+11
-1
@@ -372,6 +372,13 @@ def generate_itempool(world, player):
|
|||||||
world.push_precollected(ItemFactory(item, player))
|
world.push_precollected(ItemFactory(item, player))
|
||||||
|
|
||||||
if world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player):
|
if world.mode[player] == 'standard' and not world.state.has_blunt_weapon(player):
|
||||||
|
if world.customizer:
|
||||||
|
placements = world.customizer.get_placements()
|
||||||
|
if placements:
|
||||||
|
custom_uncle = placements.get(player, {}).get("Link's Uncle")
|
||||||
|
if custom_uncle:
|
||||||
|
placed_items["Link's Uncle"] = custom_uncle
|
||||||
|
|
||||||
if "Link's Uncle" not in placed_items:
|
if "Link's Uncle" not in placed_items:
|
||||||
found_sword = False
|
found_sword = False
|
||||||
found_bow = False
|
found_bow = False
|
||||||
@@ -1669,12 +1676,15 @@ def fill_specific_items(world):
|
|||||||
item_to_place, event_flag = get_item_and_event_flag(item, world, player,
|
item_to_place, event_flag = get_item_and_event_flag(item, world, player,
|
||||||
dungeon_pool, prize_set, prize_pool)
|
dungeon_pool, prize_set, prize_pool)
|
||||||
if item_to_place:
|
if item_to_place:
|
||||||
|
if not loc.item:
|
||||||
world.push_item(loc, item_to_place, False)
|
world.push_item(loc, item_to_place, False)
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
track_outside_keys(item_to_place, loc, world)
|
track_outside_keys(item_to_place, loc, world)
|
||||||
track_dungeon_items(item_to_place, loc, world)
|
track_dungeon_items(item_to_place, loc, world)
|
||||||
loc.event = (event_flag or item_to_place.advancement
|
loc.event = (event_flag or item_to_place.advancement
|
||||||
or item_to_place.bigkey or item_to_place.smallkey)
|
or item_to_place.bigkey or item_to_place.smallkey)
|
||||||
|
elif loc.item != item_to_place:
|
||||||
|
logging.getLogger('').warning("Failed to place item %s at location %s because it already contained %s", item_to_place, loc.name, loc.item)
|
||||||
else:
|
else:
|
||||||
raise Exception(f'Did not find "{item}" in item pool to place at "{location}"')
|
raise Exception(f'Did not find "{item}" in item pool to place at "{location}"')
|
||||||
advanced_placements = world.customizer.get_advanced_placements()
|
advanced_placements = world.customizer.get_advanced_placements()
|
||||||
@@ -1802,7 +1812,7 @@ def shuffle_event_items(world, player):
|
|||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise FillError(f'Unable to place followers: {", ".join(list(map(lambda d: d.hint_text, follower_locations)))}')
|
raise FillError(f'Unable to place followers: {", ".join(list(map(lambda f: f.name, pickup_items)))}')
|
||||||
|
|
||||||
|
|
||||||
def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool):
|
def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_pool):
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'Bow!\nJoin the archer class
|
|||||||
'Master Sword': (True, False, 'Sword', 0x50, 100, 'Master Sword!\nEvil\'s bane!', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
|
'Master Sword': (True, False, 'Sword', 0x50, 100, 'Master Sword!\nEvil\'s bane!', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
|
||||||
'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'Tempered Sword!\nMore slashy!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
|
'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'Tempered Sword!\nMore slashy!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
|
||||||
'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'Fighter Sword!\nStarter level slashy!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'),
|
'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'Fighter Sword!\nStarter level slashy!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'),
|
||||||
'Sword and Shield': (True, False, 'Sword', 0x00, 'Sword and Shield!\nUncle sword ahoy!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
|
'Sword and Shield': (True, False, 'Sword', 0x00, 50, 'Sword and Shield!\nUncle sword ahoy!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
|
||||||
'Golden Sword': (True, False, 'Sword', 0x03, 200, 'Golden Sword!\nBest slashy!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
|
'Golden Sword': (True, False, 'Sword', 0x03, 200, 'Golden Sword!\nBest slashy!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
|
||||||
'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'Sword!\nA better sword for your time!', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
|
'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'Sword!\nA better sword for your time!', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
|
||||||
'Progressive Glove': (True, False, None, 0x61, 150, 'Glove!\nLift more than you can now!', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
|
'Progressive Glove': (True, False, None, 0x61, 150, 'Glove!\nLift more than you can now!', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ from Rom import (
|
|||||||
JsonRom,
|
JsonRom,
|
||||||
LocalRom,
|
LocalRom,
|
||||||
apply_rom_settings,
|
apply_rom_settings,
|
||||||
|
apply_rom_patches,
|
||||||
get_hash_string,
|
get_hash_string,
|
||||||
patch_race_rom,
|
patch_race_rom,
|
||||||
patch_rom,
|
patch_rom,
|
||||||
@@ -335,7 +336,7 @@ def main(args, seed=None, fish=None):
|
|||||||
for player in range(1, world.players+1):
|
for player in range(1, world.players+1):
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
customize_shops(world, player)
|
customize_shops(world, player)
|
||||||
if args.algorithm in ['balanced', 'equitable']:
|
if not args.skip_money_balance and args.algorithm in ['balanced', 'equitable']:
|
||||||
balance_money_progression(world)
|
balance_money_progression(world)
|
||||||
ensure_good_items(world, True)
|
ensure_good_items(world, True)
|
||||||
|
|
||||||
@@ -364,6 +365,8 @@ def main(args, seed=None, fish=None):
|
|||||||
rom_names.append((player, team, list(rom.name)))
|
rom_names.append((player, team, list(rom.name)))
|
||||||
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
|
world.spoiler.hashes[(player, team)] = get_hash_string(rom.hash)
|
||||||
|
|
||||||
|
if args.patches[player]:
|
||||||
|
apply_rom_patches(rom, map(lambda arg: arg.strip(), args.patches[player].split(",")))
|
||||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
||||||
args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.triforce_gfx[player],
|
args.fastmenu[player], args.disablemusic[player], args.sprite[player], args.triforce_gfx[player],
|
||||||
args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player],
|
args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player],
|
||||||
@@ -572,7 +575,7 @@ def init_world(args, fish):
|
|||||||
world.collection_rate = args.collection_rate.copy()
|
world.collection_rate = args.collection_rate.copy()
|
||||||
world.colorizepots = args.colorizepots.copy()
|
world.colorizepots = args.colorizepots.copy()
|
||||||
world.aga_randomness = args.aga_randomness.copy()
|
world.aga_randomness = args.aga_randomness.copy()
|
||||||
world.money_balance = args.money_balance.copy()
|
world.money_balance = {player: int(args.money_balance[player]) for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
# custom settings - these haven't been promoted to full settings yet
|
# custom settings - these haven't been promoted to full settings yet
|
||||||
in_progress_settings = ['force_enemy']
|
in_progress_settings = ['force_enemy']
|
||||||
|
|||||||
@@ -994,7 +994,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
|||||||
set_inverted_mode(world, player, rom, inverted_buffer)
|
set_inverted_mode(world, player, rom, inverted_buffer)
|
||||||
|
|
||||||
uncle_location = world.get_location('Link\'s Uncle', player)
|
uncle_location = world.get_location('Link\'s Uncle', player)
|
||||||
if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', 'Fighter Sword', 'Golden Sword', 'Progressive Sword']:
|
if uncle_location.item is None or uncle_location.item.name not in ['Master Sword', 'Tempered Sword', 'Fighter Sword', 'Golden Sword', 'Progressive Sword', 'Sword and Shield']:
|
||||||
# disable sword sprite from uncle
|
# disable sword sprite from uncle
|
||||||
rom.write_bytes(0x6D263, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
rom.write_bytes(0x6D263, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||||
rom.write_bytes(0x6D26B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
rom.write_bytes(0x6D26B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||||
@@ -1779,6 +1779,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
|
|||||||
rom.write_byte(0x180358, 0x01 if glitches_enabled else 0x00)
|
rom.write_byte(0x180358, 0x01 if glitches_enabled else 0x00)
|
||||||
rom.write_byte(0x18008B, 0x01 if glitches_enabled else 0x00)
|
rom.write_byte(0x18008B, 0x01 if glitches_enabled else 0x00)
|
||||||
|
|
||||||
|
if uncle_location.item is None or uncle_location.item.name not in ['Sword and Shield']:
|
||||||
# remove shield from uncle
|
# remove shield from uncle
|
||||||
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||||
rom.write_bytes(0x6D25B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
rom.write_bytes(0x6D25B, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||||
@@ -2074,6 +2075,49 @@ def hud_format_text(text):
|
|||||||
output += b'\x7f\x00'
|
output += b'\x7f\x00'
|
||||||
return output[:32]
|
return output[:32]
|
||||||
|
|
||||||
|
def read_bytes(f, count):
|
||||||
|
values = f.read(count)
|
||||||
|
if len(values) < count:
|
||||||
|
raise EOFError
|
||||||
|
return values
|
||||||
|
|
||||||
|
def apply_rom_patches(rom, patches):
|
||||||
|
for patch in patches:
|
||||||
|
if not os.path.exists(f"patches/{patch}.ips"):
|
||||||
|
logging.getLogger('').warning("Patch %s not found -- skipping", patch)
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(f"patches/{patch}.ips", "rb") as f:
|
||||||
|
byte_changes = []
|
||||||
|
try:
|
||||||
|
header = read_bytes(f, 5)
|
||||||
|
if header != b"PATCH":
|
||||||
|
logging.getLogger('').warning("Patch %s invalid -- skipping", patch)
|
||||||
|
continue
|
||||||
|
|
||||||
|
while True:
|
||||||
|
address = read_bytes(f, 3)
|
||||||
|
if address == b"EOF":
|
||||||
|
break
|
||||||
|
|
||||||
|
address = int.from_bytes(address, byteorder="big")
|
||||||
|
length = int.from_bytes(read_bytes(f, 2), byteorder="big")
|
||||||
|
|
||||||
|
if length > 0:
|
||||||
|
byte_changes.append((address, list(f.read(length))))
|
||||||
|
else:
|
||||||
|
length = int.from_bytes(read_bytes(f, 2), byteorder="big")
|
||||||
|
value = read_bytes(f, 1)
|
||||||
|
byte_changes.append((address, length * list(value)))
|
||||||
|
|
||||||
|
for address, values in byte_changes:
|
||||||
|
rom.write_bytes(address, values)
|
||||||
|
|
||||||
|
logging.getLogger("").info("Patch %s applied successfully", patch)
|
||||||
|
|
||||||
|
except EOFError:
|
||||||
|
logging.getLogger('').warning("Patch %s invalid -- skipping", patch)
|
||||||
|
continue
|
||||||
|
|
||||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, triforce_gfx,
|
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, triforce_gfx,
|
||||||
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx,
|
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -754,6 +754,10 @@
|
|||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
"money_balance": {},
|
"money_balance": {},
|
||||||
|
"patches": {
|
||||||
|
"type": "str",
|
||||||
|
"help": "suppress"
|
||||||
|
},
|
||||||
"settingsonload": {
|
"settingsonload": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"default",
|
"default",
|
||||||
|
|||||||
Reference in New Issue
Block a user