mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-23 13:13:22 -07:00
Merge branch 'main' into main
This commit is contained in:
20
Options.py
20
Options.py
@@ -689,9 +689,9 @@ class Range(NumericOption):
|
||||
@classmethod
|
||||
def weighted_range(cls, text) -> Range:
|
||||
if text == "random-low":
|
||||
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_start))
|
||||
return cls(cls.triangular(cls.range_start, cls.range_end, 0.0))
|
||||
elif text == "random-high":
|
||||
return cls(cls.triangular(cls.range_start, cls.range_end, cls.range_end))
|
||||
return cls(cls.triangular(cls.range_start, cls.range_end, 1.0))
|
||||
elif text == "random-middle":
|
||||
return cls(cls.triangular(cls.range_start, cls.range_end))
|
||||
elif text.startswith("random-range-"):
|
||||
@@ -717,11 +717,11 @@ class Range(NumericOption):
|
||||
f"{random_range[0]}-{random_range[1]} is outside allowed range "
|
||||
f"{cls.range_start}-{cls.range_end} for option {cls.__name__}")
|
||||
if text.startswith("random-range-low"):
|
||||
return cls(cls.triangular(random_range[0], random_range[1], random_range[0]))
|
||||
return cls(cls.triangular(random_range[0], random_range[1], 0.0))
|
||||
elif text.startswith("random-range-middle"):
|
||||
return cls(cls.triangular(random_range[0], random_range[1]))
|
||||
elif text.startswith("random-range-high"):
|
||||
return cls(cls.triangular(random_range[0], random_range[1], random_range[1]))
|
||||
return cls(cls.triangular(random_range[0], random_range[1], 1.0))
|
||||
else:
|
||||
return cls(random.randint(random_range[0], random_range[1]))
|
||||
|
||||
@@ -739,8 +739,16 @@ class Range(NumericOption):
|
||||
return str(self.value)
|
||||
|
||||
@staticmethod
|
||||
def triangular(lower: int, end: int, tri: typing.Optional[int] = None) -> int:
|
||||
return int(round(random.triangular(lower, end, tri), 0))
|
||||
def triangular(lower: int, end: int, tri: float = 0.5) -> int:
|
||||
"""
|
||||
Integer triangular distribution for `lower` inclusive to `end` inclusive.
|
||||
|
||||
Expects `lower <= end` and `0.0 <= tri <= 1.0`. The result of other inputs is undefined.
|
||||
"""
|
||||
# Use the continuous range [lower, end + 1) to produce an integer result in [lower, end].
|
||||
# random.triangular is actually [a, b] and not [a, b), so there is a very small chance of getting exactly b even
|
||||
# when a != b, so ensure the result is never more than `end`.
|
||||
return min(end, math.floor(random.triangular(0.0, 1.0, tri) * (end - lower + 1) + lower))
|
||||
|
||||
|
||||
class NamedRange(Range):
|
||||
|
||||
4
Utils.py
4
Utils.py
@@ -521,8 +521,8 @@ def init_logging(name: str, loglevel: typing.Union[str, int] = logging.INFO,
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
return self.condition(record)
|
||||
|
||||
file_handler.addFilter(Filter("NoStream", lambda record: not getattr(record, "NoFile", False)))
|
||||
file_handler.addFilter(Filter("NoCarriageReturn", lambda record: '\r' not in record.msg))
|
||||
file_handler.addFilter(Filter("NoStream", lambda record: not getattr(record, "NoFile", False)))
|
||||
file_handler.addFilter(Filter("NoCarriageReturn", lambda record: '\r' not in record.getMessage()))
|
||||
root_logger.addHandler(file_handler)
|
||||
if sys.stdout:
|
||||
formatter = logging.Formatter(fmt='[%(asctime)s] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||
|
||||
@@ -2,3 +2,6 @@
|
||||
python_files = test_*.py Test*.py # TODO: remove Test* once all worlds have been ported
|
||||
python_classes = Test
|
||||
python_functions = test
|
||||
testpaths =
|
||||
test
|
||||
worlds
|
||||
|
||||
@@ -141,9 +141,12 @@ def set_dw_rules(world: "HatInTimeWorld"):
|
||||
add_dw_rules(world, all_clear)
|
||||
add_rule(main_stamp, main_objective.access_rule)
|
||||
add_rule(all_clear, main_objective.access_rule)
|
||||
# Only set bonus stamp rules if we don't auto complete bonuses
|
||||
# Only set bonus stamp rules to require All Clear if we don't auto complete bonuses
|
||||
if not world.options.DWAutoCompleteBonuses and not world.is_bonus_excluded(all_clear.name):
|
||||
add_rule(bonus_stamps, all_clear.access_rule)
|
||||
else:
|
||||
# As soon as the Main Objective is completed, the bonuses auto-complete.
|
||||
add_rule(bonus_stamps, main_objective.access_rule)
|
||||
|
||||
if world.options.DWShuffle:
|
||||
for i in range(len(world.dw_shuffle)-1):
|
||||
@@ -343,6 +346,7 @@ def create_enemy_events(world: "HatInTimeWorld"):
|
||||
|
||||
def set_enemy_rules(world: "HatInTimeWorld"):
|
||||
no_tourist = "Camera Tourist" in world.excluded_dws or "Camera Tourist" in world.excluded_bonuses
|
||||
difficulty = get_difficulty(world)
|
||||
|
||||
for enemy, regions in hit_list.items():
|
||||
if no_tourist and enemy in bosses:
|
||||
@@ -372,6 +376,14 @@ def set_enemy_rules(world: "HatInTimeWorld"):
|
||||
or state.has("Zipline Unlock - The Lava Cake Path", world.player)
|
||||
or state.has("Zipline Unlock - The Windmill Path", world.player))
|
||||
|
||||
elif enemy == "Toilet":
|
||||
if area == "Toilet of Doom":
|
||||
# The boss firewall is in the way and can only be skipped on Expert logic using a cherry hover.
|
||||
add_rule(event, lambda state: has_paintings(state, world, 1, allow_skip=difficulty == Difficulty.EXPERT))
|
||||
if difficulty < Difficulty.HARD:
|
||||
# Hard logic and above can cross the boss arena gap with a cherry bridge.
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world))
|
||||
|
||||
elif enemy == "Director":
|
||||
if area == "Dead Bird Studio Basement":
|
||||
add_rule(event, lambda state: can_use_hookshot(state, world))
|
||||
@@ -430,7 +442,7 @@ hit_list = {
|
||||
# Bosses
|
||||
"Mafia Boss": ["Down with the Mafia!", "Encore! Encore!", "Boss Rush"],
|
||||
|
||||
"Conductor": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
|
||||
"Director": ["Dead Bird Studio Basement", "Killing Two Birds", "Boss Rush"],
|
||||
"Toilet": ["Toilet of Doom", "Boss Rush"],
|
||||
|
||||
"Snatcher": ["Your Contract has Expired", "Breaching the Contract", "Boss Rush",
|
||||
@@ -454,7 +466,7 @@ triple_enemy_locations = [
|
||||
|
||||
bosses = [
|
||||
"Mafia Boss",
|
||||
"Conductor",
|
||||
"Director",
|
||||
"Toilet",
|
||||
"Snatcher",
|
||||
"Toxic Flower",
|
||||
|
||||
@@ -264,7 +264,6 @@ ahit_locations = {
|
||||
required_hats=[HatType.DWELLER], paintings=3),
|
||||
|
||||
"Subcon Forest - Tall Tree Hookshot Swing": LocData(2000324766, "Subcon Forest Area",
|
||||
required_hats=[HatType.DWELLER],
|
||||
hookshot=True,
|
||||
paintings=3),
|
||||
|
||||
@@ -323,7 +322,7 @@ ahit_locations = {
|
||||
"Alpine Skyline - The Twilight Path": LocData(2000334434, "Alpine Skyline Area", required_hats=[HatType.DWELLER]),
|
||||
"Alpine Skyline - The Twilight Bell: Wide Purple Platform": LocData(2000336478, "The Twilight Bell"),
|
||||
"Alpine Skyline - The Twilight Bell: Ice Platform": LocData(2000335826, "The Twilight Bell"),
|
||||
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area"),
|
||||
"Alpine Skyline - Goat Outpost Horn": LocData(2000334760, "Alpine Skyline Area (TIHS)", hookshot=True),
|
||||
"Alpine Skyline - Windy Passage": LocData(2000334776, "Alpine Skyline Area (TIHS)", hookshot=True),
|
||||
"Alpine Skyline - The Windmill: Inside Pon Cluster": LocData(2000336395, "The Windmill"),
|
||||
"Alpine Skyline - The Windmill: Entrance": LocData(2000335783, "The Windmill"),
|
||||
@@ -407,7 +406,7 @@ act_completions = {
|
||||
hit_type=HitType.umbrella_or_brewing, hookshot=True, paintings=1),
|
||||
|
||||
"Act Completion (Queen Vanessa's Manor)": LocData(2000312017, "Queen Vanessa's Manor",
|
||||
hit_type=HitType.umbrella, paintings=1),
|
||||
hit_type=HitType.dweller_bell, paintings=1),
|
||||
|
||||
"Act Completion (Mail Delivery Service)": LocData(2000312032, "Mail Delivery Service",
|
||||
required_hats=[HatType.SPRINT]),
|
||||
@@ -878,7 +877,7 @@ snatcher_coins = {
|
||||
dlc_flags=HatDLC.death_wish),
|
||||
|
||||
"Snatcher Coin - Top of HQ (DW: BTH)": LocData(0, "Beat the Heat", snatcher_coin="Snatcher Coin - Top of HQ",
|
||||
dlc_flags=HatDLC.death_wish),
|
||||
hit_type=HitType.umbrella, dlc_flags=HatDLC.death_wish),
|
||||
|
||||
"Snatcher Coin - Top of Tower": LocData(0, "Mafia Town Area (HUMT)", snatcher_coin="Snatcher Coin - Top of Tower",
|
||||
dlc_flags=HatDLC.death_wish),
|
||||
|
||||
@@ -414,7 +414,7 @@ def set_moderate_rules(world: "HatInTimeWorld"):
|
||||
|
||||
# Moderate: Mystifying Time Mesa time trial without hats
|
||||
set_rule(world.multiworld.get_location("Alpine Skyline - Mystifying Time Mesa: Zipline", world.player),
|
||||
lambda state: can_use_hookshot(state, world))
|
||||
lambda state: True)
|
||||
|
||||
# Moderate: Goat Refinery from TIHS with Sprint only
|
||||
add_rule(world.multiworld.get_location("Alpine Skyline - Goat Refinery", world.player),
|
||||
@@ -493,9 +493,6 @@ def set_hard_rules(world: "HatInTimeWorld"):
|
||||
lambda state: has_paintings(state, world, 3, True))
|
||||
|
||||
# SDJ
|
||||
add_rule(world.multiworld.get_location("Subcon Forest - Long Tree Climb Chest", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.SPRINT) and has_paintings(state, world, 2), "or")
|
||||
|
||||
add_rule(world.multiworld.get_location("Act Completion (Time Rift - Curly Tail Trail)", world.player),
|
||||
lambda state: can_use_hat(state, world, HatType.SPRINT), "or")
|
||||
|
||||
@@ -533,7 +530,10 @@ def set_expert_rules(world: "HatInTimeWorld"):
|
||||
# Expert: Mafia Town - Above Boats, Top of Lighthouse, and Hot Air Balloon with nothing
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Above Boats", world.player), lambda state: True)
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Top of Lighthouse", world.player), lambda state: True)
|
||||
set_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player), lambda state: True)
|
||||
# There are not enough buckets/beach balls to bucket/ball hover in Heating Up Mafia Town, so any other Mafia Town
|
||||
# act is required.
|
||||
add_rule(world.multiworld.get_location("Mafia Town - Hot Air Balloon", world.player),
|
||||
lambda state: state.can_reach_region("Mafia Town Area", world.player), "or")
|
||||
|
||||
# Expert: Clear Dead Bird Studio with nothing
|
||||
for loc in world.multiworld.get_region("Dead Bird Studio - Post Elevator Area", world.player).locations:
|
||||
@@ -590,7 +590,7 @@ def set_expert_rules(world: "HatInTimeWorld"):
|
||||
|
||||
if world.is_dlc2():
|
||||
# Expert: clear Rush Hour with nothing
|
||||
if not world.options.NoTicketSkips:
|
||||
if world.options.NoTicketSkips != NoTicketSkips.option_true:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player), lambda state: True)
|
||||
else:
|
||||
set_rule(world.multiworld.get_location("Act Completion (Rush Hour)", world.player),
|
||||
@@ -739,7 +739,7 @@ def set_dlc1_rules(world: "HatInTimeWorld"):
|
||||
|
||||
# This particular item isn't present in Act 3 for some reason, yes in vanilla too
|
||||
add_rule(world.multiworld.get_location("The Arctic Cruise - Toilet", world.player),
|
||||
lambda state: state.can_reach("Bon Voyage!", "Region", world.player)
|
||||
lambda state: (state.can_reach("Bon Voyage!", "Region", world.player) and can_use_hookshot(state, world))
|
||||
or state.can_reach("Ship Shape", "Region", world.player))
|
||||
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ class KH2Context(CommonContext):
|
||||
18: TWTNW_Checks,
|
||||
# 255: {}, # starting screen
|
||||
}
|
||||
self.last_world_int = -1
|
||||
# 0x2A09C00+0x40 is the sve anchor. +1 is the last saved room
|
||||
# self.sveroom = 0x2A09C00 + 0x41
|
||||
# 0 not in battle 1 in yellow battle 2 red battle #short
|
||||
@@ -345,33 +346,12 @@ class KH2Context(CommonContext):
|
||||
self.lookup_id_to_item = {v: k for k, v in self.kh2_item_name_to_id.items()}
|
||||
self.ability_code_list = [self.kh2_item_name_to_id[item] for item in exclusion_item_table["Ability"]]
|
||||
|
||||
if "keyblade_abilities" in self.kh2slotdata.keys():
|
||||
sora_ability_dict = self.kh2slotdata["KeybladeAbilities"]
|
||||
if "KeybladeAbilities" in self.kh2slotdata.keys():
|
||||
# sora ability to slot
|
||||
self.AbilityQuantityDict.update(self.kh2slotdata["KeybladeAbilities"])
|
||||
# itemid:[slots that are available for that item]
|
||||
for k, v in sora_ability_dict.items():
|
||||
if v >= 1:
|
||||
if k not in self.sora_ability_to_slot.keys():
|
||||
self.sora_ability_to_slot[k] = []
|
||||
for _ in range(sora_ability_dict[k]):
|
||||
self.sora_ability_to_slot[k].append(self.kh2_seed_save_cache["SoraInvo"][0])
|
||||
self.kh2_seed_save_cache["SoraInvo"][0] -= 2
|
||||
donald_ability_dict = self.kh2slotdata["StaffAbilities"]
|
||||
for k, v in donald_ability_dict.items():
|
||||
if v >= 1:
|
||||
if k not in self.donald_ability_to_slot.keys():
|
||||
self.donald_ability_to_slot[k] = []
|
||||
for _ in range(donald_ability_dict[k]):
|
||||
self.donald_ability_to_slot[k].append(self.kh2_seed_save_cache["DonaldInvo"][0])
|
||||
self.kh2_seed_save_cache["DonaldInvo"][0] -= 2
|
||||
goofy_ability_dict = self.kh2slotdata["ShieldAbilities"]
|
||||
for k, v in goofy_ability_dict.items():
|
||||
if v >= 1:
|
||||
if k not in self.goofy_ability_to_slot.keys():
|
||||
self.goofy_ability_to_slot[k] = []
|
||||
for _ in range(goofy_ability_dict[k]):
|
||||
self.goofy_ability_to_slot[k].append(self.kh2_seed_save_cache["GoofyInvo"][0])
|
||||
self.kh2_seed_save_cache["GoofyInvo"][0] -= 2
|
||||
self.AbilityQuantityDict.update(self.kh2slotdata["StaffAbilities"])
|
||||
self.AbilityQuantityDict.update(self.kh2slotdata["ShieldAbilities"])
|
||||
|
||||
all_weapon_location_id = []
|
||||
for weapon_location in all_weapon_slot:
|
||||
@@ -408,13 +388,15 @@ class KH2Context(CommonContext):
|
||||
async def checkWorldLocations(self):
|
||||
try:
|
||||
currentworldint = self.kh2_read_byte(self.Now)
|
||||
await self.send_msgs([{
|
||||
"cmd": "Set", "key": "Slot: " + str(self.slot) + " :CurrentWorld",
|
||||
"default": 0, "want_reply": True, "operations": [{
|
||||
"operation": "replace",
|
||||
"value": currentworldint
|
||||
}]
|
||||
}])
|
||||
if self.last_world_int != currentworldint:
|
||||
self.last_world_int = currentworldint
|
||||
await self.send_msgs([{
|
||||
"cmd": "Set", "key": "Slot: " + str(self.slot) + " :CurrentWorld",
|
||||
"default": 0, "want_reply": False, "operations": [{
|
||||
"operation": "replace",
|
||||
"value": currentworldint
|
||||
}]
|
||||
}])
|
||||
if currentworldint in self.worldid_to_locations:
|
||||
curworldid = self.worldid_to_locations[currentworldint]
|
||||
for location, data in curworldid.items():
|
||||
@@ -525,27 +507,7 @@ class KH2Context(CommonContext):
|
||||
if itemname not in self.kh2_seed_save_cache["AmountInvo"]["Ability"]:
|
||||
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname] = []
|
||||
# appending the slot that the ability should be in
|
||||
# for non beta. remove after 4.3
|
||||
if "PoptrackerVersion" in self.kh2slotdata:
|
||||
if self.kh2slotdata["PoptrackerVersionCheck"] < 4.3:
|
||||
if (itemname in self.sora_ability_set
|
||||
and len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < self.item_name_to_data[itemname].quantity) \
|
||||
and self.kh2_seed_save_cache["SoraInvo"][1] > 0x254C:
|
||||
ability_slot = self.kh2_seed_save_cache["SoraInvo"][1]
|
||||
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
|
||||
self.kh2_seed_save_cache["SoraInvo"][1] -= 2
|
||||
elif itemname in self.donald_ability_set:
|
||||
ability_slot = self.kh2_seed_save_cache["DonaldInvo"][1]
|
||||
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
|
||||
self.kh2_seed_save_cache["DonaldInvo"][1] -= 2
|
||||
else:
|
||||
ability_slot = self.kh2_seed_save_cache["GoofyInvo"][1]
|
||||
self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname].append(ability_slot)
|
||||
self.kh2_seed_save_cache["GoofyInvo"][1] -= 2
|
||||
if ability_slot in self.front_ability_slots:
|
||||
self.front_ability_slots.remove(ability_slot)
|
||||
|
||||
elif len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < \
|
||||
if len(self.kh2_seed_save_cache["AmountInvo"]["Ability"][itemname]) < \
|
||||
self.AbilityQuantityDict[itemname]:
|
||||
if itemname in self.sora_ability_set:
|
||||
ability_slot = self.kh2_seed_save_cache["SoraInvo"][0]
|
||||
@@ -845,7 +807,7 @@ class KH2Context(CommonContext):
|
||||
logger.info("line 840")
|
||||
|
||||
|
||||
def finishedGame(ctx: KH2Context, message):
|
||||
def finishedGame(ctx: KH2Context):
|
||||
if ctx.kh2slotdata['FinalXemnas'] == 1:
|
||||
if not ctx.final_xemnas and ctx.kh2_read_byte(ctx.Save + all_world_locations[LocationName.FinalXemnas].addrObtained) \
|
||||
& 0x1 << all_world_locations[LocationName.FinalXemnas].bitIndex > 0:
|
||||
@@ -877,8 +839,9 @@ def finishedGame(ctx: KH2Context, message):
|
||||
elif ctx.kh2slotdata['Goal'] == 2:
|
||||
# for backwards compat
|
||||
if "hitlist" in ctx.kh2slotdata:
|
||||
locations = ctx.sending
|
||||
for boss in ctx.kh2slotdata["hitlist"]:
|
||||
if boss in message[0]["locations"]:
|
||||
if boss in locations:
|
||||
ctx.hitlist_bounties += 1
|
||||
if ctx.hitlist_bounties >= ctx.kh2slotdata["BountyRequired"] or ctx.kh2_seed_save_cache["AmountInvo"]["Amount"]["Bounty"] >= ctx.kh2slotdata["BountyRequired"]:
|
||||
if ctx.kh2_read_byte(ctx.Save + 0x36B3) < 1:
|
||||
@@ -919,11 +882,12 @@ async def kh2_watcher(ctx: KH2Context):
|
||||
await asyncio.create_task(ctx.verifyChests())
|
||||
await asyncio.create_task(ctx.verifyItems())
|
||||
await asyncio.create_task(ctx.verifyLevel())
|
||||
message = [{"cmd": 'LocationChecks', "locations": ctx.sending}]
|
||||
if finishedGame(ctx, message) and not ctx.kh2_finished_game:
|
||||
if finishedGame(ctx) and not ctx.kh2_finished_game:
|
||||
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
|
||||
ctx.kh2_finished_game = True
|
||||
await ctx.send_msgs(message)
|
||||
if ctx.sending:
|
||||
message = [{"cmd": 'LocationChecks', "locations": ctx.sending}]
|
||||
await ctx.send_msgs(message)
|
||||
elif not ctx.kh2connected and ctx.serverconneced:
|
||||
logger.info("Game Connection lost. waiting 15 seconds until trying to reconnect.")
|
||||
ctx.kh2 = None
|
||||
|
||||
@@ -103,6 +103,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
assembler.const("wGoldenLeaves", 0xDB42) # New memory location where to store the golden leaf counter
|
||||
assembler.const("wCollectedTunics", 0xDB6D) # Memory location where to store which tunic options are available (and boots)
|
||||
assembler.const("wCustomMessage", 0xC0A0)
|
||||
assembler.const("wOverworldRoomStatus", 0xD800)
|
||||
|
||||
# We store the link info in unused color dungeon flags, so it gets preserved in the savegame.
|
||||
assembler.const("wLinkSyncSequenceNumber", 0xDDF6)
|
||||
|
||||
@@ -2,6 +2,10 @@ import typing
|
||||
from ..checkMetadata import checkMetadataTable
|
||||
from .constants import *
|
||||
|
||||
custom_name_replacements = {
|
||||
'"':"'",
|
||||
'_':' ',
|
||||
}
|
||||
|
||||
class ItemInfo:
|
||||
MULTIWORLD = True
|
||||
@@ -23,6 +27,11 @@ class ItemInfo:
|
||||
def setLocation(self, location):
|
||||
self._location = location
|
||||
|
||||
def setCustomItemName(self, name):
|
||||
for key, val in custom_name_replacements.items():
|
||||
name = name.replace(key, val)
|
||||
self.custom_item_name = name
|
||||
|
||||
def getOptions(self):
|
||||
return self.OPTIONS
|
||||
|
||||
|
||||
@@ -716,9 +716,7 @@ def addWarpImprovements(rom, extra_warps):
|
||||
|
||||
# Allow cursor to move over black squares
|
||||
# This allows warping to undiscovered areas - a fine cheat, but needs a check for wOverworldRoomStatus in the warp code
|
||||
CHEAT_WARP_ANYWHERE = False
|
||||
if CHEAT_WARP_ANYWHERE:
|
||||
rom.patch(0x01, 0x1AE8, None, ASM("jp $5AF5"))
|
||||
rom.patch(0x01, 0x1AE8, None, ASM("jp $5AF5"))
|
||||
|
||||
# This disables the arrows around the selection bubble
|
||||
#rom.patch(0x01, 0x1B6F, None, ASM("ret"), fill_nop=True)
|
||||
@@ -797,8 +795,14 @@ def addWarpImprovements(rom, extra_warps):
|
||||
TeleportHandler:
|
||||
|
||||
ld a, [$DBB4] ; Load the current selected tile
|
||||
; TODO: check if actually revealed so we can have free movement
|
||||
; Check cursor against different tiles to see if we are selecting a warp
|
||||
ld hl, wOverworldRoomStatus
|
||||
ld e, a ; $5D38: $5F
|
||||
ld d, $00 ; $5D39: $16 $00
|
||||
add hl, de ; $5D3B: $19
|
||||
ld a, [hl]
|
||||
and $80
|
||||
jr z, exit
|
||||
ld a, [$DBB4] ; Load the current selected tile
|
||||
{warp_jump}
|
||||
jr exit
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ class LinksAwakeningWorld(World):
|
||||
# Otherwise, use a cute letter as the icon
|
||||
elif self.options.foreign_item_icons == 'guess_by_name':
|
||||
loc.ladxr_item.item = self.guess_icon_for_other_world(loc.item)
|
||||
loc.ladxr_item.custom_item_name = loc.item.name
|
||||
loc.ladxr_item.setCustomItemName(loc.item.name)
|
||||
|
||||
else:
|
||||
if loc.item.advancement:
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
pyevermizer==0.48.0 \
|
||||
--hash=sha256:069ce348e480e04fd6208cfd0f789c600b18d7c34b5272375b95823be191ed57 \
|
||||
--hash=sha256:58164dddaba2f340b0a8b4f39605e9dac46d8b0ffb16120e2e57bef2bfc1d683 \
|
||||
--hash=sha256:115dd09d38a10f11d4629b340dfd75e2ba4089a1ff9e9748a11619829e02c876 \
|
||||
--hash=sha256:b5e79cfe721e75cd7dec306b5eecd6385ce059e31ef7523ba7f677e22161ec6f \
|
||||
--hash=sha256:382882fa9d641b9969a6c3ed89449a814bdabcb6b17b558872d95008a6cc908b \
|
||||
--hash=sha256:92f67700e9132064a90858d391dd0b8fb111aff6dfd472befed57772d89ae567 \
|
||||
--hash=sha256:fe4c453b7dbd5aa834b81f9a7aedb949a605455650b938b8b304d8e5a7edcbf7 \
|
||||
--hash=sha256:c6bdbc45daf73818f763ed59ad079f16494593395d806f772dd62605c722b3e9 \
|
||||
--hash=sha256:bb09f45448fdfd28566ae6fcc38c35a6632f4c31a9de2483848f6ce17b2359b5 \
|
||||
--hash=sha256:00a8b9014744bd1528d0d39c33ede7c0d1713ad797a331cebb33d377a5bc1064 \
|
||||
--hash=sha256:64ee69edc0a7d3b3caded78f2e46975f9beaff1ff8feaf29b87da44c45f38d7d \
|
||||
--hash=sha256:9211bdb1313e9f4869ed5bdc61f3831d39679bd08bb4087f1c1e5475d9e3018b \
|
||||
--hash=sha256:4a57821e422a1d75fe3307931a78db7a65e76955f8e401c4b347db6570390d09 \
|
||||
--hash=sha256:04670cee0a0b913f24d2b9a1e771781560e2485bda31e6cd372a08421cf85cfa \
|
||||
--hash=sha256:971fe77d0a20a1db984020ad253b613d0983f5e23ff22cba60ee5ac00d8128de \
|
||||
--hash=sha256:127265fdb49f718f54706bf15604af1cec23590afd00d423089dea4331dcfc61 \
|
||||
--hash=sha256:d47576360337c1a23f424cd49944a8d68fc4f3338e00719c9f89972c84604bef \
|
||||
--hash=sha256:879659603e51130a0de8d9885d815a2fa1df8bd6cebe6d520d1c6002302adfdb \
|
||||
--hash=sha256:6a91bfc53dd130db6424adf8ac97a1133e97b4157ed00f889d8cbd26a2a4b340 \
|
||||
--hash=sha256:f3bf35fc5eef4cda49d2de77339fc201dd3206660a3dc15db005625b15bb806c \
|
||||
--hash=sha256:e7c8d5bf59a3c16db20411bc5d8e9c9087a30b6b4edf1b5ed9f4c013291427e4 \
|
||||
--hash=sha256:054a4d84ffe75448d41e88e1e0642ef719eb6111be5fe608e71e27a558c59069 \
|
||||
--hash=sha256:e6f141ca367469c69ba7fbf65836c479ec6672c598cfcb6b39e8098c60d346bc \
|
||||
--hash=sha256:6e65eb88f0c1ff4acde1c13b24ce649b0fe3d1d3916d02d96836c781a5022571 \
|
||||
--hash=sha256:e61e8f476b6da809cf38912755ed8bb009665f589e913eb8df877e9fa763024b \
|
||||
--hash=sha256:7e7c5484c0a2e3da6064de3f73d8d988d6703db58ab0be4730cbbf1a82319237 \
|
||||
--hash=sha256:9033b954e5f4878fd94af6d2056c78e3316115521fb1c24a4416d5cbf2ad66ad \
|
||||
--hash=sha256:824c623fff8ae4da176306c458ad63ad16a06a495a16db700665eca3c115924f \
|
||||
--hash=sha256:8e31031409a8386c6a63b79d480393481badb3ba29f32ff7a0db2b4abed20ac8 \
|
||||
--hash=sha256:7dbb7bb13e1e94f69f7ccdbcf4d35776424555fce5af1ca29d0256f91fdf087a \
|
||||
--hash=sha256:3a24e331b259407b6912d6e0738aa8a675831db3b7493fcf54dc17cb0cb80d37 \
|
||||
--hash=sha256:fdda06662a994271e96633cba100dd92b2fcd524acef8b2f664d1aaa14503cbd \
|
||||
--hash=sha256:0f0fc81bef3dbb78ba6a7622dd4296f23c59825968a0bb0448beb16eb3397cc2 \
|
||||
--hash=sha256:e07cbef776a7468669211546887357cc88e9afcf1578b23a4a4f2480517b15d9 \
|
||||
--hash=sha256:e442212695bdf60e455673b7b9dd83a5d4b830d714376477093d2c9054d92832
|
||||
pyevermizer==0.48.1 \
|
||||
--hash=sha256:db85cb4760abfde9d4b566d4613f2eddb8c2ff6f1c202ca0c2c5800bd62c9507 \
|
||||
--hash=sha256:1c67d0dff0a42b9a037cdb138c0c7b2c776d8d7425830e7fd32f7ebf8f35ac00 \
|
||||
--hash=sha256:d417f5b0407b063496aca43a65389e3308b6d0933c1d7907f7ecc8a00057903b \
|
||||
--hash=sha256:abf6560204128783239c8f0fb15059a7c2ff453812f85fb8567766706b7839cc \
|
||||
--hash=sha256:39e0cba1de1bc108c5b770ebe0fcbf3f6cb05575daf6bebe78c831c74848d101 \
|
||||
--hash=sha256:a16054ce0d904749ef27ede375c0ca8f420831e28c4e84c67361e8181207f00d \
|
||||
--hash=sha256:e6de509e4943bcde3e207a3640cad8efe3d8183740b63dc3cdbf5013db0f618b \
|
||||
--hash=sha256:e9269cf1290ab2967eaac0bc24e658336fb0e1f6612efce8d7ef0e76c1c26200 \
|
||||
--hash=sha256:f69e244229a110183d36b6a43ca557e716016d17e11265dca4070b8857afdb8d \
|
||||
--hash=sha256:118d059b8ccd246dafb0a51d0aa8e4543c172f9665378983b9f43c680487732e \
|
||||
--hash=sha256:185210c68b16351b3add4896ecfc26fe3867dadee9022f6a256e13093cca4a3b \
|
||||
--hash=sha256:10e281612c38bbec11d35f5c09f5a5174fb884cc60e6f16b6790d854e4346678 \
|
||||
--hash=sha256:9fc7d7e986243a96e96c1c05a386eb5d2ae4faef1ba810ab7e9e63dd83e86c2b \
|
||||
--hash=sha256:c26eafc2230dca9e91aaf925a346532586d0f448456437ea4ce5054e15653fd8 \
|
||||
--hash=sha256:8f96ffc5cfbe17b5c08818052be6f96906a1c9d3911e7bc4fbefee9b9ffa8f15 \
|
||||
--hash=sha256:e40948cbcaab27aa4febb58054752f83357e81f4a6f088da22a71c4ec9aa7ef2 \
|
||||
--hash=sha256:d59369cafa5df0fd2ce5cd5656c926e2fc0226a5a67a003d95497d56a0728dd3 \
|
||||
--hash=sha256:345a25675d92aada5d94bc3f3d3e2946efd940a7228628bf8c05d2853ddda86d \
|
||||
--hash=sha256:c0aa5054178c5e9900bfcf393c2bffdc69921d165521a3e9e5271528b01ef442 \
|
||||
--hash=sha256:719d417fc21778d5036c9d25b7ce55582ab6f49da63ab93ec17d75ea6042364c \
|
||||
--hash=sha256:28e220939850cfd8da16743365b28fa36d5bfc1dc58564789ae415e014ebc354 \
|
||||
--hash=sha256:770e582000abf64dc7f0c62672e4a1f64729bb20695664c59e29d238398cb865 \
|
||||
--hash=sha256:61d451b6f7d76fd435a5e9d2df111533e6e43da397a457f310151917318bd175 \
|
||||
--hash=sha256:1c8b596e246bb8437c7fc6c9bb8d9c2c70bd9942f09b06ada02d2fabe596fa0b \
|
||||
--hash=sha256:617f3eb0938e71a07b16477529f97fdf64487875462eb2edba6c9820b9686c0a \
|
||||
--hash=sha256:98d655a256040a3ae6305145a9692a5483ddcfb9b9bbdb78d43f5e93e002a3ae \
|
||||
--hash=sha256:d565bde7b1eb873badeedc2c9f327b4e226702b571aab2019778d46aa4509572 \
|
||||
--hash=sha256:e04b89d6edf6ffdbf5c725b0cbf7375c87003378da80e6666818a2b6d59d3fc9 \
|
||||
--hash=sha256:cc35e72f2a9e438786451f54532ce663ca63aedc3b4a43532f4ee97b45a71ed1 \
|
||||
--hash=sha256:2e4640a975bf324e75f15edd6450e63db8228e2046b893bbdc47d896d5aec890 \
|
||||
--hash=sha256:752716024255f13f96e40877b932694a517100a382a13f76c0bed3116b77f6d6 \
|
||||
--hash=sha256:d36518349132cf2f3f4e5a6b0294db0b40f395daa620b0938227c2c8f5b1213e \
|
||||
--hash=sha256:b5bca6e7fe5dcccd1e8757db4fb20d4bd998ed2b0f4b9ed26f7407c0a9b48d9f \
|
||||
--hash=sha256:4663b727d2637ce7713e3db7b68828ca7dc6f03482f4763a055156f3fd16e026 \
|
||||
--hash=sha256:7732bec7ffb29337418e62f15dc924e229faf09c55b079ad3f46f47eedc10c0d \
|
||||
--hash=sha256:b83a7a4df24800f82844f6acc6d43cd4673de0c24c9041ab56e57f518defa5a1 \
|
||||
|
||||
@@ -12,13 +12,13 @@ class OoBTest(SoETestBase):
|
||||
# some locations that just need a weapon + OoB
|
||||
oob_reachable = [
|
||||
"Aquagoth", "Sons of Sth.", "Mad Monk", "Magmar", # OoB can use volcano shop to skip rock skip
|
||||
"Levitate", "Fireball", "Drain", "Speed",
|
||||
"Levitate", "Fireball", "Speed",
|
||||
"E. Crustacia #107", "Energy Core #285", "Vanilla Gauge #57",
|
||||
]
|
||||
# some locations that should still be unreachable
|
||||
oob_unreachable = [
|
||||
"Tiny", "Rimsala",
|
||||
"Barrier", "Call Up", "Reflect", "Force Field", "Stop", # Stop guy doesn't spawn for the other entrances
|
||||
"Barrier", "Drain", "Call Up", "Reflect", "Force Field", "Stop", # Stop guy only spawns from one entrance
|
||||
"Pyramid bottom #118", "Tiny's hideout #160", "Tiny's hideout #161", "Greenhouse #275",
|
||||
]
|
||||
# OoB + Diamond Eyes
|
||||
@@ -31,11 +31,42 @@ class OoBTest(SoETestBase):
|
||||
"Tiny's hideout #161",
|
||||
]
|
||||
|
||||
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=False)
|
||||
self.collect_by_name("Gladiator Sword")
|
||||
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=in_logic)
|
||||
self.collect_by_name("Diamond Eye")
|
||||
self.assertLocationReachability(reachable=de_reachable, unreachable=de_unreachable, satisfied=in_logic)
|
||||
with self.subTest("No items", oob_logic=in_logic):
|
||||
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=False)
|
||||
with self.subTest("Cutting Weapon", oob_logic=in_logic):
|
||||
self.collect_by_name("Gladiator Sword")
|
||||
self.assertLocationReachability(reachable=oob_reachable, unreachable=oob_unreachable, satisfied=in_logic)
|
||||
with self.subTest("Cutting Weapon + DEs", oob_logic=in_logic):
|
||||
self.collect_by_name("Diamond Eye")
|
||||
self.assertLocationReachability(reachable=de_reachable, unreachable=de_unreachable, satisfied=in_logic)
|
||||
|
||||
def test_real_axe(self) -> None:
|
||||
in_logic = self.options["out_of_bounds"] == "logic"
|
||||
|
||||
# needs real Bronze Axe+, regardless of OoB
|
||||
real_axe_required = [
|
||||
"Drain",
|
||||
"Drain Cave #180",
|
||||
"Drain Cave #181",
|
||||
]
|
||||
also_des_required = [
|
||||
"Double Drain",
|
||||
]
|
||||
|
||||
with self.subTest("No Axe", oob_logic=in_logic):
|
||||
self.collect_by_name("Gladiator Sword")
|
||||
self.assertLocationReachability(reachable=real_axe_required, satisfied=False)
|
||||
with self.subTest("Bronze Axe", oob_logic=in_logic):
|
||||
self.collect_by_name("Bronze Axe")
|
||||
self.assertLocationReachability(reachable=real_axe_required, satisfied=True)
|
||||
with self.subTest("Knight Basher", oob_logic=in_logic):
|
||||
self.remove_by_name("Bronze Axe")
|
||||
self.collect_by_name("Knight Basher")
|
||||
self.assertLocationReachability(reachable=real_axe_required, satisfied=True)
|
||||
self.assertLocationReachability(reachable=also_des_required, satisfied=False)
|
||||
with self.subTest("Knight Basher + DEs", oob_logic=in_logic):
|
||||
self.collect_by_name("Diamond Eye")
|
||||
self.assertLocationReachability(reachable=also_des_required, satisfied=True)
|
||||
|
||||
def test_oob_goal(self) -> None:
|
||||
# still need Energy Core with OoB if sequence breaks are not in logic
|
||||
|
||||
@@ -95,7 +95,7 @@ class TunicWorld(World):
|
||||
|
||||
# for the local_fill option
|
||||
fill_items: List[TunicItem]
|
||||
fill_locations: List[TunicLocation]
|
||||
fill_locations: List[Location]
|
||||
amount_to_local_fill: int
|
||||
|
||||
# so we only loop the multiworld locations once
|
||||
@@ -394,8 +394,6 @@ class TunicWorld(World):
|
||||
self.multiworld.itempool += tunic_items
|
||||
|
||||
def pre_fill(self) -> None:
|
||||
self.fill_locations = []
|
||||
|
||||
if self.options.local_fill > 0 and self.multiworld.players > 1:
|
||||
# we need to reserve a couple locations so that we don't fill up every sphere 1 location
|
||||
reserved_locations: Set[str] = set(self.random.sample(sphere_one, 2))
|
||||
@@ -406,8 +404,8 @@ class TunicWorld(World):
|
||||
if len(viable_locations) < self.amount_to_local_fill:
|
||||
raise OptionError(f"TUNIC: Not enough locations for local_fill option for {self.player_name}. "
|
||||
f"This is likely due to excess plando or priority locations.")
|
||||
|
||||
self.fill_locations += viable_locations
|
||||
self.random.shuffle(viable_locations)
|
||||
self.fill_locations = viable_locations[:self.amount_to_local_fill]
|
||||
|
||||
@classmethod
|
||||
def stage_pre_fill(cls, multiworld: MultiWorld) -> None:
|
||||
|
||||
Reference in New Issue
Block a user