This commit is contained in:
2025-12-13 21:53:24 +01:00
parent fd1405ada0
commit 6911df1f92
8 changed files with 467 additions and 400 deletions

View File

@@ -16,60 +16,60 @@ class Sly3Episode(IntEnum):
Honor_Among_Thieves = 6 Honor_Among_Thieves = 6
class PowerUps(NamedTuple): class PowerUps(NamedTuple):
attack = False attack: bool = False
binocucom = False binocucom: bool = False
bombs = False bombs: bool = False
unknown = False unknown: bool = False
trigger_Bomb = False trigger_Bomb: bool = False
fishing_pole = False fishing_pole: bool = False
alarm_clock = False alarm_clock: bool = False
adrenaline_burst = False adrenaline_burst: bool = False
health_extractor = False health_extractor: bool = False
hover_pack = False hover_pack: bool = False
insanity_strike = False insanity_strike: bool = False
grapple_cam = False grapple_cam: bool = False
size_destabilizer = False size_destabilizer: bool = False
rage_bomb = False rage_bomb: bool = False
reduction_bomb = False reduction_bomb: bool = False
ball_form = False ball_form: bool = False
berserker_charge = False berserker_charge: bool = False
juggernaut_throw = False juggernaut_throw: bool = False
guttural_roar = False guttural_roar: bool = False
fists_of_flame = False fists_of_flame: bool = False
temporal_lock = False temporal_lock: bool = False
raging_inferno_flop = False raging_inferno_flop: bool = False
diablo_fire_slam = False diablo_fire_slam: bool = False
smoke_bomb = False smoke_bomb: bool = False
combat_dodge = False combat_dodge: bool = False
paraglider = False paraglider: bool = False
silent_obliteration = False silent_obliteration: bool = False
feral_pounce = False feral_pounce: bool = False
mega_jump = False mega_jump: bool = False
knockout_dive = False knockout_dive: bool = False
shadow_power_1 = False shadow_power_1: bool = False
thief_reflexes = False thief_reflexes: bool = False
shadow_power_2 = False shadow_power_2: bool = False
rocket_boots = False rocket_boots: bool = False
treasure_map = False treasure_map: bool = False
shield = False shield: bool = False
venice_disguise = False venice_disguise: bool = False
photographer_disguise = False photographer_disguise: bool = False
pirate_disguise = False pirate_disguise: bool = False
spin_1 = False spin_1: bool = False
spin_2 = False spin_2: bool = False
spin_3 = False spin_3: bool = False
jump_1 = False jump_1: bool = False
jump_2 = False jump_2: bool = False
jump_3 = False jump_3: bool = False
push_1 = False push_1: bool = False
push_2 = False push_2: bool = False
push_3 = False push_3: bool = False
class GameInterface(): class GameInterface():
""" """
@@ -204,175 +204,136 @@ class Sly3Interface(GameInterface):
if self.in_cutscene() and pressing_x: if self.in_cutscene() and pressing_x:
self._write32(self.addresses["skip cutscene"],0) self._write32(self.addresses["skip cutscene"],0)
def load_powerups(self, powerups: PowerUps):
booleans = list(powerups)
byte_list = [
[False]*2+booleans[0:6],
booleans[6:14],
booleans[14:22],
booleans[22:30],
booleans[30:38],
booleans[38:46],
booleans[46:48]+[False]*2,
[False]*8
]
data = b''.join(
int(''.join(str(int(i)) for i in byte[::-1]),2).to_bytes(1,"big")
for byte in byte_list
)
self._write_bytes(self.addresses["gadgets"], data)
def read_powerups(self):
data = self._read_bytes(self.addresses["gadgets"], 8)
bits = [
bool(int(b))
for byte in data
for b in f"{byte:08b}"[::-1]
]
relevant_bits = bits[2:48]
return PowerUps(*relevant_bits)
def add_coins(self, to_add: int):
current_amount = self._read32(self.addresses["coins"])
new_amount = max(current_amount + to_add,0)
self._write32(self.addresses["coins"],new_amount)
#### TESTING ZONE ####
def read_text(interf: Sly3Interface, address: int):
"""Reads text at a specific address"""
text = ""
while True:
character = interf._read_bytes(address,2)
if character == b"\x00\x00":
break
text += character.decode("utf-16-le")
address += 2
return text
def find_string_id(interf: Sly3Interface, _id: int):
"""Searches for a specific string by ID"""
# String table starts at 0x47A2D8
# Each entry in the string table has 4 bytes of its ID and then 4 bytes of an
# address to the string
string_table_address = interf._read32(0x47A2D8)
i = 0
while True:
string_id = interf._read32(string_table_address+i*8)
if string_id == _id:
return interf._read32(string_table_address+i*8+4)
i += 1
def print_thiefnet_addresses(interf: Sly3Interface):
print(" {")
for i in range(44):
address = 0x343208+i*0x3c
interf._write32(address,i+1)
interf._write32(address+0xC,0)
name_id = interf._read32(address+0x14)
name_address = find_string_id(interf, name_id)
name_text = read_text(interf, name_address)
description_id = interf._read32(address+0x18)
description_address = find_string_id(interf, description_id)
print(
" " +
f"\"{name_text}\": "+
f"({hex(name_address)},{hex(description_address)}),"
)
print(" }")
def current_job_info(interf: Sly3Interface):
current_job = interf._read32(0x36DB98)
address = interf._read32(interf.addresses["DAG root"])
i = 0
while address != 0:
job_pointer = interf._read32(address+0x6c)
job_id = interf._read32(job_pointer+0x18)
if job_id == current_job:
break
address = interf._read32(address+0x20)
i += 1
print("Job ID:", current_job)
print("Job address:", hex(address))
print("Job index:", i)
print("Job state (should be 2):", interf._read32(address+0x44))
if __name__ == "__main__": if __name__ == "__main__":
interf = Sly3Interface(Logger("test")) interf = Sly3Interface(Logger("test"))
interf.connect_to_game() interf.connect_to_game()
# interf.to_episode_menu()
# interf.unlock_episodes()
# interf.skip_cutscene()
byte_list = [ # Loading all power-ups (except the one I don't know)
[ # power_ups = PowerUps(True, True, True, False, *[True]*44)
False, # ??? # interf.load_powerups(power_ups)
False, # ???
True, # Sly/Bentley square attack
True, # Binocucom
True, # Bentley's bombs
False, # ???
True, # Trigger Bomb
True # Fishing Pole
],
[
True, # Alarm Clock
True, # Adrenaline Burst
True, # Health Extractor
True, # Hover Pack (Doesn't activate until you reload)
True, # Insanity Strike
True, # Grapple Came
True, # Size Destabilizer
True # Rage Bomb
],
[
True, # Reduction Bomb
True, # Ball Form
True, # Berskerker Charge
True, # Juggernaut Throw
True, # Gutteral Roar
True, # Fists of Flame
True, # Temporal Lock
True # Raging Inferno Flop
],
[
True, # Diablo Fire Slam
True, # Smoke Bomb
True, # Combat Dodge
True, # Paraglider
True, # Silent Obliteration
True, # Feral Pounce
True, # Mega Jump
True # Knockout Dive
],
[
True, # Shadow Power Lvl 1
True, # Thief Reflexes
True, # Shadow Power Lvl 2
True, # Rocket Boots
True, # Treasure Map
False, # ???
True, # Venice Disguise
True # Photographer Disguise
],
[
True, # Pirate Disguise
True, # Spin Attack lvl 1
True, # Spin Attack lvl 2
True, # Spin Attack lvl 3
True, # Jump Attack lvl 1
True, # Jump Attack lvl 2
True, # Jump Attack lvl 3
True # Push Attack lvl 1
],
[
True, # Push Attack lvl 1
True, # Push Attack lvl 1
False,
False,
False,
False,
False,
False
],
[
False,
False,
False,
False,
False,
False,
False,
False
],
]
# byte_list = [[False for _ in range(8)] for _ in range(8)] # Adding 10000 coins
# interf.add_coins(10000)
data = b''.join( # === Testing Zone ===
int(''.join(str(int(i)) for i in byte[::-1]),2).to_bytes(1,"big")
for byte in byte_list
)
interf._write_bytes(0x468DCC,data) # power_ups = PowerUps()
# interf.load_powerups(power_ups)
# print_thiefnet_addresses(interf)
# data = interf._read_bytes(0x468DCC, 8) # interf._write32(0x1335d10+0x44, 0)
# bits = [
# bool(int(b))
# for byte in data
# for b in f"{byte:08b}"[::-1]
# ]
# print(bits) # current_job_info(interf)
interf._write32(0x468DDC, 10000)
# thiefnet_values = list(range(9)) + list(range(10,))
# def read_text(address: int):
# text = ""
# while True:
# character = interf._read_bytes(address,2)
# if character == b"\x00\x00":
# break
# text += character.decode("utf-16-le")
# address += 2
# return text
# def find_string_id(_id: int):
# string_table_address = interf._read32(0x47A2D8)
# i = 0
# while True:
# string_id = interf._read32(string_table_address+i*8)
# if string_id == _id:
# return interf._read32(string_table_address+i*8+4)
# i += 1
# print(" {")
# for i in range(44):
# address = 0x343208+i*0x3c
# interf._write32(address,i+1)
# interf._write32(address+0xC,0)
# name_id = interf._read32(address+0x14)
# name_address = find_string_id(name_id)
# name_text = read_text(name_address)
# description_id = interf._read32(address+0x18)
# description_address = find_string_id(description_id)
# print(
# " " +
# f"\"{name_text}\": "+
# f"({hex(name_address)},{hex(description_address)}),"
# )
# print(" }")
# string_table_address = interf._read32(0x47A2D8)
# for i in range(10):
# print("----")
# string_id = interf._read32(string_table_address+i*8)
# print(string_id)
# string_address = interf._read32(string_table_address+i*8+4)
# print(hex(string_address))
# print(read_text(string_address))
# print(interf._read32(0x6b4110+0x44))
print(interf._read32(0x1365be0+0x44))
print()
print(interf._read32(0x1357f80+0x44))
print(interf._read32(0x1350560+0x44))
print(interf._read32(0x135aba0+0x44))
print(interf._read32(0x36DB98))

View File

@@ -69,6 +69,15 @@ class CoinsMaximum(Range):
range_end = 1000 range_end = 1000
default = 200 default = 200
class ThiefNetLocations(Range):
"""
The number ThiefNet locations.
"""
display_name = "ThiefNet Locations"
range_start = 0
range_end = 37
default = 25
class ThiefNetCostMinimum(Range): class ThiefNetCostMinimum(Range):
""" """
@@ -100,6 +109,7 @@ class Sly3Options(PerGameCommonOptions):
include_mega_jump: IncludeMegaJump include_mega_jump: IncludeMegaJump
coins_minimum: CoinsMinimum coins_minimum: CoinsMinimum
coins_maximum: CoinsMaximum coins_maximum: CoinsMaximum
thiefnet_locations: ThiefNetLocations
thiefnet_minimum: ThiefNetCostMinimum thiefnet_minimum: ThiefNetCostMinimum
thiefnet_maximum: ThiefNetCostMaximum thiefnet_maximum: ThiefNetCostMaximum
@@ -113,6 +123,7 @@ sly3_option_groups = [
CoinsMaximum CoinsMaximum
]), ]),
OptionGroup("Locations",[ OptionGroup("Locations",[
ThiefNetLocations,
ThiefNetCostMinimum, ThiefNetCostMinimum,
ThiefNetCostMaximum ThiefNetCostMaximum
]) ])

View File

@@ -35,15 +35,14 @@ def gen_crew(world: "Sly3World") -> list[Item]:
return crew return crew
def gen_episodes(world: "Sly3World") -> list[Item]: def gen_episodes(world: "Sly3World") -> list[Item]:
"""Generate the progressive episodes items for the item pool""" """Generate the episodes items for the item pool"""
all_episodes = [ all_episodes = [
item_name for item_name in item_groups["Episode"] item_name for item_name in item_groups["Episode"]
for _ in range(4)
] ]
# Make sure the starting episode is precollected # Make sure the starting episode is precollected
starting_episode_n = world.options.starting_episode.value starting_episode_n = world.options.starting_episode.value
starting_episode = f"Progressive {list(EPISODES.keys())[starting_episode_n]}" starting_episode = list(EPISODES.keys())[starting_episode_n]
all_episodes.remove(starting_episode) all_episodes.remove(starting_episode)
world.multiworld.push_precollected(world.create_item(starting_episode)) world.multiworld.push_precollected(world.create_item(starting_episode))

View File

@@ -1,9 +1,9 @@
import typing import typing
from BaseClasses import Region, CollectionState, Location from BaseClasses import Region, CollectionState
from .data.Locations import location_dict from .data.Locations import location_dict
from .data.Constants import EPISODES, CHALLENGES from .data.Constants import EPISODES, CHALLENGES, REQUIREMENTS
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from . import Sly3World from . import Sly3World
@@ -13,40 +13,21 @@ def create_access_rule(episode: str, n: int, options: "Sly3Options", player: int
"""Returns a function that checks if the player has access to a specific region""" """Returns a function that checks if the player has access to a specific region"""
def rule(state: CollectionState): def rule(state: CollectionState):
access = True access = True
item_name = f"Progressive {episode}"
if episode == "Honor Among Thieves": if episode == "Honor Among Thieves":
access = access and state.count_group("Crew", player) == 7 access = access and state.count_group("Crew", player) == 7
else: else:
access = access and state.count(item_name, player) >= n access = access and state.count(episode, player) == 1
if n > 1: if n > 1:
requirements = sum({ section_requirements = [
"An Opera of Fear": [ sum(
[], ep_reqs,
["Binocucom", "Bentley"], []
["Carmelita", "Murray", "Ball Form", "Disguise (Venice)"] )
], for ep_reqs
"Rumble Down Under": [ in REQUIREMENTS["Jobs"][episode][:n-1]
[], ]
["Murray", "Guru"], requirements = list(set(sum(section_requirements, [])))
["Bentley"]
],
"Flight of Fancy": [
[],
["Murray", "Bentley", "Guru", "Fishing Pole", "Penelope"],
["Hover Pack", "Carmelita", "Binocucom"]
],
"A Cold Alliance": [
["Bentley", "Murray", "Guru", "Penelope", "Binocucom"],
["Disguise (Photographer)", "Grapple-Cam", "Panda King"],
["Carmelita"]
],
"Dead Men Tell No Tales": [
["Disguise (Pirate)"],
["Bentley", "Penelope", "Grapple-Cam", "Murray", "Silent Obliteration", "Treasure Map"],
["Panda King", "Dimitri"]
]
}[episode][:n-1], [])
access = access and all(state.has(i, player) for i in requirements) access = access and all(state.has(i, player) for i in requirements)
return access return access
@@ -59,12 +40,13 @@ def create_regions_sly3(world: "Sly3World"):
menu = Region("Menu", world.player, world.multiworld) menu = Region("Menu", world.player, world.multiworld)
menu.add_locations({ menu.add_locations({
f"ThiefNet {i+1:02}": location_dict[f"ThiefNet {i+1:02}"].code f"ThiefNet {i+1:02}": location_dict[f"ThiefNet {i+1:02}"].code
for i in range(37) for i in range(world.options.thiefnet_locations)
}) })
world.multiworld.regions.append(menu) world.multiworld.regions.append(menu)
for i, episode in enumerate(EPISODES.keys()): for i, episode in enumerate(EPISODES.keys()):
print(f"==={episode}===")
for n in range(1,5): for n in range(1,5):
if n == 2 and episode == "Honor Among Thieves": if n == 2 and episode == "Honor Among Thieves":
break break
@@ -80,6 +62,7 @@ def create_regions_sly3(world: "Sly3World"):
}) })
world.multiworld.regions.append(region) world.multiworld.regions.append(region)
menu.connect( menu.connect(
region, region,
None, None,

View File

@@ -4,177 +4,68 @@ from math import ceil
from BaseClasses import CollectionState from BaseClasses import CollectionState
from worlds.generic.Rules import add_rule from worlds.generic.Rules import add_rule
from .data.Constants import EPISODES from .data.Constants import EPISODES, CHALLENGES, REQUIREMENTS
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from . import Sly3World from . import Sly3World
def set_rules_sly3(world: "Sly3World"): def set_rules_sly3(world: "Sly3World"):
player = world.player player = world.player
thiefnet_items = world.options.thiefnet_locations.value
# Putting ThiefNet stuff out of logic, to make early game less slow. # Putting ThiefNet stuff out of logic, to make early game less slow.
# Divides the items into 8 groups of 3. First groups requires 2 episodes # Divides the items into groups that require a number of episode and crew
# items to be in logic, second group requires 4, etc. # items to be in logic
for i in range(1,38): for i in range(1,thiefnet_items):
episode_items_n = ceil(i/4)*2 divisor = ceil(thiefnet_items/12)
episode_items_n = ceil(i/divisor)
add_rule( add_rule(
world.get_location(f"ThiefNet {i:02}"), world.get_location(f"ThiefNet {i:02}"),
lambda state, n=episode_items_n: ( lambda state, n=episode_items_n: (
state.has_group("Episode", player, n) (
state.count_group("Episode", player) +
state.count_group("Crew", player)
) >= n
) )
) )
def require(location: str, item: str|list[str]): def require(location: str, item: str|list[str]):
if isinstance(item,str): add_rule(
add_rule( world.get_location(location),
world.get_location(location), lambda state, i=item: (
lambda state, i=item: ( all(state.has(j, player) for j in i)
state.has(i, player)
)
)
else:
add_rule(
world.get_location(location),
lambda state, i=item: (
all(state.has(j, player) for j in i)
)
) )
)
### Job requirements ### Job requirements
## An Opera of fear for episode, sections in EPISODES.items():
# An Opera of Fear - Police HQ if episode == "Honor Among Thieves":
continue
require("An Opera of Fear - Octavio Snap", "Binocucom") for i, s in enumerate(sections):
# An Opera of Fear - Into the Depths for j, job in enumerate(s):
require("An Opera of Fear - Canal Chase", "Bentley") reqs = REQUIREMENTS["Jobs"][episode][i][j]
add_rule(
require("An Opera of Fear - Turf War!", "Carmelita") world.get_location(f"{episode} - {job}"),
require("An Opera of Fear - Tar Ball", ["Murray", "Ball Form"]) lambda state, items=reqs: (
# An Opera of Fear - Run 'n Bomb all(state.has(item, player) for item in items)
require("An Opera of Fear - Guard Duty", "Disguise (Venice)") )
)
require("An Opera of Fear - Operation: Tar-Be Gone!", "Bombs")
## Rumble Down Under
# Rumble Down Under - Search for the Guru
require("Rumble Down Under - Spelunking", "Murray")
# Rumble Down Under - Dark Caves
# Rumble Down Under - Big Truck
require("Rumble Down Under - Unleash the Guru", "Guru")
# Rumble Down Under - The Claw
require("Rumble Down Under - Lemon Rage", "Bentley")
# Rumble Down Under - Hungry Croc
# Rumble Down Under - Operation: Moon Crash
## Flight of Fancy
# Flight of Fancy - Hidden Flight Roster
require("Flight of Fancy - Frame Team Belgium", ["Murray", "Bentley", "Guru", "Fishing Pole"])
require("Flight of Fancy - Frame Team Iceland", "Murray")
require("Flight of Fancy - Cooper Hangar Defense", "Penelope")
require("Flight of Fancy - ACES Semifinals", ["Murray", "Bentley", "Guru", "Fishing Pole", "Penelope"])
require("Flight of Fancy - Giant Wolf Massacre", "Binocucom")
require("Flight of Fancy - Windmill Firewall", "Hover Pack")
require("Flight of Fancy - Beauty and the Beast", "Carmelita")
require("Flight of Fancy - Operation: Turbo Dominant Eagle", "Paraglider")
## A Cold Alliance
require("A Cold Alliance - King of Fire", ["Bentley", "Murray", "Guru", "Penelope", "Binocucom"])
require("A Cold Alliance - Get a Job", "Disguise (Photographer)")
require("A Cold Alliance - Tearful Reunion", "Panda King")
require("A Cold Alliance - Grapple-Cam Break-In", "Grapple-Cam")
require("A Cold Alliance - Laptop Retrieval", ["Disguise (Photographer)", "Panda King", "Grapple-Cam"])
# A Cold Alliance - Vampiric Defense
# A Cold Alliance - Down the Line
require("A Cold Alliance - A Battery of Peril", "Carmelita")
# A Cold Alliance - Operation: Wedding Crasher
## Dead Men Tell No Tales
require("Dead Men Tell No Tales - The Talk of Pirates", "Disguise (Pirate)")
require("Dead Men Tell No Tales - Dynamic Duo", ["Bentley", "Penelope", "Grapple-Cam"])
require("Dead Men Tell No Tales - Jollyboat of Destruction", "Murray")
require("Dead Men Tell No Tales - X Marks the Spot", ["Bentley", "Penelope", "Grapple-Cam", "Murray", "Silent Obliteration", "Treasure Map"])
require("Dead Men Tell No Tales - Crusher from the Depths", "Panda King")
require("Dead Men Tell No Tales - Deep Sea Danger", "Dimitri")
# Dead Men Tell No Tales - Battle on the High Seas
require("Dead Men Tell No Tales - Operation: Reverse Double-Cross", "Guru")
## Honor Among Thieves
# Honor Among Thieves - Carmelita to the Rescue
# Honor Among Thieves - A Deadly Bite
# Honor Among Thieves - The Dark Current
# Honor Among Thieves - Bump-Charge-Jump
# Honor Among Thieves - Danger in the Skie
# Honor Among Thieves - The Ancestors' Gauntlet
# Honor Among Thieves - Stand your Ground
# Honor Among Thieves - Final Legacy
### Challenge requirements ### Challenge requirements
## An Opera of Fear for episode, sections in CHALLENGES.items():
require("An Opera of Fear - Canal Chase - Expert Course", "Bentley") if episode == "Honor Among Thieves":
require("An Opera of Fear - Air Time", ["Murray", "Ball Form"]) continue
# An Opera of Fear - Tower Scramble
# An Opera of Fear - Coin Chase
require("An Opera of Fear - Speed Bombing", "Bombs")
# An Opera of Fear - Octavio Canal Challenge
# An Opera of Fear - Octavio's Last Stand
require("An Opera of Fear - Venice Treasure Hunt", "Treasure Map")
## Rumble Down Under for i, s in enumerate(sections):
# Rumble Down Under - Rock Run for j, challenge in enumerate(s):
# Rumble Down Under - Cave Sprint reqs = REQUIREMENTS["Challenges"][episode][i][j]
# Rumble Down Under - Cave Mayhem add_rule(
# Rumble Down Under - Scaling the Drill world.get_location(f"{episode} - {challenge}"),
require("Rumble Down Under - Guard Swappin'", "Guru") lambda state, items=reqs: (
# Rumble Down Under - Quick Claw all(state.has(item, player) for item in items)
require("Rumble Down Under - Pressure Brawl", "Bentley") )
# Rumble Down Under - Croc and Coins )
# Rumble Down Under - Carmelita Climb
require("Rumble Down Under - Outback Treasure Hunt", "Treasure Map")
## Flight of Fancy
# Flight of Fancy - Castle Quick Climb
require("Flight of Fancy - Muggshot Goon Attack", "Penelope")
require("Flight of Fancy - Security Breach", "Penelope")
require("Flight of Fancy - Defend the Hangar", "Penelope")
require("Flight of Fancy - Precision Air Duel", ["Murray", "Bentley", "Guru", "Fishing Pole", "Penelope"])
require("Flight of Fancy - Wolf Rampage", "Guru")
require("Flight of Fancy - One Woman Army", "Carmelita")
require("Flight of Fancy - Going Out On A Wing", "Paraglider")
require("Flight of Fancy - Holland Treasure Hunt", "Treasure Map")
## A Cold Alliance
require("A Cold Alliance - Big Air in China", ["Bentley", "Murray", "Guru", "Penelope", "Binocucom"])
require("A Cold Alliance - Sharpshooter", "Panda King")
# A Cold Alliance - Treetop Tangle
# A Cold Alliance - Tsao Showdown
require("A Cold Alliance - China Treasure Hunt", "Treasure Map")
## Dead Men Tell No Tales
require("Dead Men Tell No Tales - Patch Grab", "Disguise (Pirate)")
require("Dead Men Tell No Tales - Stealth Challenge", "Disguise (Pirate)")
require("Dead Men Tell No Tales - Boat Bash", "Murray")
require("Dead Men Tell No Tales - Last Ship Sailing", ["Bentley", "Penelope", "Grapple-Cam", "Murray", "Silent Obliteration", "Treasure Map"])
# Dead Men Tell No Tales - Pirate Treasure Hunt
## Honor Among Thieves
# Beauty versus the Beast
# Road Rage
# Dr. M Dogfight
# Ultimate Gauntlet
# Battle Against Time
if world.options.goal.value < 6: if world.options.goal.value < 6:
victory_condition = [ victory_condition = [

View File

@@ -106,6 +106,7 @@ class Sly3World(World):
self.options.include_mega_jump.value = slot_data["include_mega_jump"] self.options.include_mega_jump.value = slot_data["include_mega_jump"]
self.options.coins_minimum.value = slot_data["coins_minimum"] self.options.coins_minimum.value = slot_data["coins_minimum"]
self.options.coins_maximum.value = slot_data["coins_maximum"] self.options.coins_maximum.value = slot_data["coins_maximum"]
self.options.thiefnet_locations.value = slot_data["thiefnet_locations"]
self.options.thiefnet_minimum.value = slot_data["thiefnet_minimum"] self.options.thiefnet_minimum.value = slot_data["thiefnet_minimum"]
self.options.thiefnet_maximum.value = slot_data["thiefnet_maximum"] self.options.thiefnet_maximum.value = slot_data["thiefnet_maximum"]
return return
@@ -155,6 +156,7 @@ class Sly3World(World):
"include_mega_jump", "include_mega_jump",
"coins_minimum", "coins_minimum",
"coins_maximum", "coins_maximum",
"thiefnet_locations",
"thiefnet_minimum", "thiefnet_minimum",
"thiefnet_maximum", "thiefnet_maximum",
) )

View File

@@ -203,6 +203,178 @@ CHALLENGES = {
] ]
} }
# Jobs/Challenges -> episode -> section -> job
# dict[ list[ list[ list[]]]]
REQUIREMENTS = {
"Jobs": {
"An Opera of Fear": [
[[]],
[
["Binocucom"],
[],
["Bentley"],
],
[
["Carmelita"],
["Murray", "Ball Form"],
[],
["Disguise (Venice)"],
],
[
["Bombs"]
]
],
"Rumble Down Under" :[
[[]],
[
["Murray"],
[],
[],
["Guru"],
],
[
[],
["Bentley"],
[]
],
[[]]
],
"Flight of Fancy": [
[[]],
[
["Murray", "Bentley", "Guru", "Fishing Pole"],
["Murray"],
["Penelope"],
["Murray", "Bentley", "Guru", "Fishing Pole", "Penelope"]
],
[
["Binocucom"],
["Hover Pack"],
["Carmelita"]
],
[
["Paraglider"]
]
],
"A Cold Alliance": [
[
["Bentley", "Murray", "Guru", "Penelope", "Binocucom"]
],
[
["Disguise (Photographer)"],
["Panda King"],
["Grapple-Cam"],
["Disguise (Photographer)", "Panda King", "Grapple-Cam"]
],
[
[],
[],
["Carmelita"]
],
[[]]
],
"Dead Men Tell No Tales": [
[
["Disguise (Pirate)"]
],
[
["Bentley", "Penelope", "Grapple-Cam"],
["Murray"],
["Bentley", "Penelope", "Grapple-Cam", "Murray", "Silent Obliteration", "Treasure Map"]
],
[
["Panda King"],
["Dimitri"],
[]
],
[
["Guru"]
]
],
},
"Challenges": {
"An Opera of Fear": [
[],
[
["Bentley"]
],
[
["Murray", "Ball Form"],
[],
[]
],
[
["Bombs"],
[],
[],
["Treasure Map"]
]
],
"Rumble Down Under" :[
[[]],
[
[],
[],
[],
["Guru"]
],
[
[],
["Bentley"],
[]
],
[
[],
["Treasure Map"]
]
],
"Flight of Fancy": [
[[]],
[
["Penelope"],
["Penelope"],
["Penelope"],
["Murray", "Bentley", "Guru", "Fishing Pole", "Penelope"],
],
[
[],
["Carmelita"]
],
[
["Paraglider"],
["Treasure Map"]
]
],
"A Cold Alliance": [
[
["Bentley", "Murray", "Guru", "Penelope", "Binocucom"]
],
[
["Panda King"],
[],
[],
[]
],
[],
[
["Treasure Map"]
]
],
"Dead Men Tell No Tales": [
[
["Disguise (Pirate)"],
["Disguise (Pirate)"]
],
[
["Murray"],
["Bentley", "Penelope", "Grapple-Cam", "Murray", "Silent Obliteration", "Treasure Map"]
],
[],
[[]]
],
}
}
ADDRESSES = { ADDRESSES = {
"SCUS-97464" : { "SCUS-97464" : {
"map id": 0x47989C, "map id": 0x47989C,
@@ -213,6 +385,9 @@ ADDRESSES = {
"frame counter": 0x389BE0, "frame counter": 0x389BE0,
"x pressed": 0x36E78E, "x pressed": 0x36E78E,
"skip cutscene": 0x389C20, "skip cutscene": 0x389C20,
"gadgets": 0x468DCC,
"coins": 0x468DDC,
"DAG root": 0x478C8C,
"jobs": [ "jobs": [
[ [
[0x1335d10] [0x1335d10]
@@ -227,7 +402,52 @@ ADDRESSES = {
], ],
"text": { "text": {
"powerups": [ "powerups": [
{}, {
"Trigger Bomb": (0x58db60,0x58dcf0),
"Fishing Pole": (0x595da0,0x595fc0),
"Alarm Clock": (0x591db0,0x591f40),
"Adrenaline Burst": (0x58e800,0x58e9c0),
"Health Extractor": (0x58ebe0,0x58ee00),
"Hover Pack": (0x58ef90,0x58f1b0),
"Insanity Strike": (0x593a40,0x593b70),
"Grapple-Cam": (0x5957d0,0x595ae0),
"Size Destabilizer": (0x58df70,0x58e170),
"Rage Bomb": (0x594160,0x5942d0),
"Reduction Bomb": (0x58f260,0x58f390),
"Be The Ball": (0x5955c0,0x595730),
"Berserker Charge": (0x5912d0,0x591380),
"Juggernaut Throw": (0x590730,0x590850),
"Guttural Roar": (0x5914e0,0x591610),
"Fists of Flame": (0x58f960,0x5900b0),
"Temporal Lock": (0x58f440,0x58f5a0),
"Raging Inferno Flop": (0x5916c0,0x5917f0),
"Diablo Fire Slam": (0x590fa0,0x591090),
"Smoke Bomb": (0x5918f0,0x591a00),
"Combat Dodge": (0x591b40,0x591c90),
"Paraglide": (0x5921f0,0x5924c0),
"Silent Obliteration": (0x592690,0x592870),
"Feral Pounce": (0x592c50,0x592de0),
"Mega Jump": (0x592fc0,0x593180),
"Knockout Dive": (0x5936d0,0x5938e0),
"Shadow Power Level 1": (0x594770,0x594880),
"Thief Reflexes": (0x592a10,0x592b50),
"Shadow Power Level 2": (0x5949e0,0x594d00),
"Rocket Boots": (0x577060,0x577300),
"Treasure Map": (0x576af0,0x576dc0),
"ENGLISHpowerup_shield_name": (0x596280,0x576450),
"Venice Disguise": (0x577510,0x577670),
"Photographer Disguise": (0x5778f0,0x577ac0),
"Pirate Disguise": (0x577ca0,0x577e20),
"Spin Attack Level 1": (0x577fe0,0x5781b0),
"Spin Attack Level 2": (0x578350,0x578500),
"Spin Attack Level 3": (0x578770,0x578af0),
"Jump Attack Level 1": (0x578d80,0x579070),
"Jump Attack Level 2": (0x579390,0x579620),
"Jump Attack Level 3": (0x5797b0,0x579950),
"Push Attack Level 1": (0x579ae0,0x579d70),
"Push Attack Level 2": (0x579f70,0x57a1f0),
"Push Attack Level 3": (0x57a670,0x57a940),
},
{}, {},
{ {
"Trigger Bomb": (0x592c40,0x592e00), "Trigger Bomb": (0x592c40,0x592e00),

View File

@@ -70,16 +70,16 @@ crew_list = [
("Carmelita", ItemClassification.progression, "Crew") ("Carmelita", ItemClassification.progression, "Crew")
] ]
progressive_episode_list = [ episode_list = [
(f"Progressive {e}", ItemClassification.progression, "Episode") (episode, ItemClassification.progression, "Episode")
for e in list(EPISODES.keys())[:-1] for episode in list(EPISODES.keys())[:-1]
] ]
item_list = ( item_list = (
filler_list + filler_list +
powerup_list + powerup_list +
crew_list + crew_list +
progressive_episode_list episode_list
) )
base_code = 5318008 base_code = 5318008