diff --git a/Sly3Interface.py b/Sly3Interface.py index 47a6e12..7e8b2de 100644 --- a/Sly3Interface.py +++ b/Sly3Interface.py @@ -16,60 +16,60 @@ class Sly3Episode(IntEnum): Honor_Among_Thieves = 6 class PowerUps(NamedTuple): - attack = False - binocucom = False - bombs = False - unknown = False - trigger_Bomb = False - fishing_pole = False + attack: bool = False + binocucom: bool = False + bombs: bool = False + unknown: bool = False + trigger_Bomb: bool = False + fishing_pole: bool = False - alarm_clock = False - adrenaline_burst = False - health_extractor = False - hover_pack = False - insanity_strike = False - grapple_cam = False - size_destabilizer = False - rage_bomb = False + alarm_clock: bool = False + adrenaline_burst: bool = False + health_extractor: bool = False + hover_pack: bool = False + insanity_strike: bool = False + grapple_cam: bool = False + size_destabilizer: bool = False + rage_bomb: bool = False - reduction_bomb = False - ball_form = False - berserker_charge = False - juggernaut_throw = False - guttural_roar = False - fists_of_flame = False - temporal_lock = False - raging_inferno_flop = False + reduction_bomb: bool = False + ball_form: bool = False + berserker_charge: bool = False + juggernaut_throw: bool = False + guttural_roar: bool = False + fists_of_flame: bool = False + temporal_lock: bool = False + raging_inferno_flop: bool = False - diablo_fire_slam = False - smoke_bomb = False - combat_dodge = False - paraglider = False - silent_obliteration = False - feral_pounce = False - mega_jump = False - knockout_dive = False + diablo_fire_slam: bool = False + smoke_bomb: bool = False + combat_dodge: bool = False + paraglider: bool = False + silent_obliteration: bool = False + feral_pounce: bool = False + mega_jump: bool = False + knockout_dive: bool = False - shadow_power_1 = False - thief_reflexes = False - shadow_power_2 = False - rocket_boots = False - treasure_map = False - shield = False - venice_disguise = False - photographer_disguise = False + shadow_power_1: bool = False + thief_reflexes: bool = False + shadow_power_2: bool = False + rocket_boots: bool = False + treasure_map: bool = False + shield: bool = False + venice_disguise: bool = False + photographer_disguise: bool = False - pirate_disguise = False - spin_1 = False - spin_2 = False - spin_3 = False - jump_1 = False - jump_2 = False - jump_3 = False - push_1 = False + pirate_disguise: bool = False + spin_1: bool = False + spin_2: bool = False + spin_3: bool = False + jump_1: bool = False + jump_2: bool = False + jump_3: bool = False + push_1: bool = False - push_2 = False - push_3 = False + push_2: bool = False + push_3: bool = False class GameInterface(): """ @@ -204,175 +204,136 @@ class Sly3Interface(GameInterface): if self.in_cutscene() and pressing_x: 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__": interf = Sly3Interface(Logger("test")) interf.connect_to_game() + # interf.to_episode_menu() + # interf.unlock_episodes() + # interf.skip_cutscene() - byte_list = [ - [ - False, # ??? - 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 - ], - ] + # Loading all power-ups (except the one I don't know) + # power_ups = PowerUps(True, True, True, False, *[True]*44) + # interf.load_powerups(power_ups) - # byte_list = [[False for _ in range(8)] for _ in range(8)] + # Adding 10000 coins + # interf.add_coins(10000) - data = b''.join( - int(''.join(str(int(i)) for i in byte[::-1]),2).to_bytes(1,"big") - for byte in byte_list - ) + # === Testing Zone === - interf._write_bytes(0x468DCC,data) + # power_ups = PowerUps() + # interf.load_powerups(power_ups) + # print_thiefnet_addresses(interf) - # data = interf._read_bytes(0x468DCC, 8) - # bits = [ - # bool(int(b)) - # for byte in data - # for b in f"{byte:08b}"[::-1] - # ] + # interf._write32(0x1335d10+0x44, 0) - # print(bits) - - 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)) \ No newline at end of file + # current_job_info(interf) \ No newline at end of file diff --git a/Sly3Options.py b/Sly3Options.py index c592879..1af969b 100644 --- a/Sly3Options.py +++ b/Sly3Options.py @@ -69,6 +69,15 @@ class CoinsMaximum(Range): range_end = 1000 default = 200 +class ThiefNetLocations(Range): + """ + The number ThiefNet locations. + """ + + display_name = "ThiefNet Locations" + range_start = 0 + range_end = 37 + default = 25 class ThiefNetCostMinimum(Range): """ @@ -100,6 +109,7 @@ class Sly3Options(PerGameCommonOptions): include_mega_jump: IncludeMegaJump coins_minimum: CoinsMinimum coins_maximum: CoinsMaximum + thiefnet_locations: ThiefNetLocations thiefnet_minimum: ThiefNetCostMinimum thiefnet_maximum: ThiefNetCostMaximum @@ -113,6 +123,7 @@ sly3_option_groups = [ CoinsMaximum ]), OptionGroup("Locations",[ + ThiefNetLocations, ThiefNetCostMinimum, ThiefNetCostMaximum ]) diff --git a/Sly3Pool.py b/Sly3Pool.py index 0a861e8..bd52f03 100644 --- a/Sly3Pool.py +++ b/Sly3Pool.py @@ -35,15 +35,14 @@ def gen_crew(world: "Sly3World") -> list[Item]: return crew 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 = [ item_name for item_name in item_groups["Episode"] - for _ in range(4) ] # Make sure the starting episode is precollected 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) world.multiworld.push_precollected(world.create_item(starting_episode)) diff --git a/Sly3Regions.py b/Sly3Regions.py index e920897..eb1a922 100644 --- a/Sly3Regions.py +++ b/Sly3Regions.py @@ -1,9 +1,9 @@ import typing -from BaseClasses import Region, CollectionState, Location +from BaseClasses import Region, CollectionState from .data.Locations import location_dict -from .data.Constants import EPISODES, CHALLENGES +from .data.Constants import EPISODES, CHALLENGES, REQUIREMENTS if typing.TYPE_CHECKING: 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""" def rule(state: CollectionState): access = True - item_name = f"Progressive {episode}" if episode == "Honor Among Thieves": access = access and state.count_group("Crew", player) == 7 else: - access = access and state.count(item_name, player) >= n + access = access and state.count(episode, player) == 1 if n > 1: - requirements = sum({ - "An Opera of Fear": [ - [], - ["Binocucom", "Bentley"], - ["Carmelita", "Murray", "Ball Form", "Disguise (Venice)"] - ], - "Rumble Down Under": [ - [], - ["Murray", "Guru"], - ["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], []) + section_requirements = [ + sum( + ep_reqs, + [] + ) + for ep_reqs + in REQUIREMENTS["Jobs"][episode][:n-1] + ] + requirements = list(set(sum(section_requirements, []))) access = access and all(state.has(i, player) for i in requirements) return access @@ -59,12 +40,13 @@ def create_regions_sly3(world: "Sly3World"): menu = Region("Menu", world.player, world.multiworld) menu.add_locations({ 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) for i, episode in enumerate(EPISODES.keys()): + print(f"==={episode}===") for n in range(1,5): if n == 2 and episode == "Honor Among Thieves": break @@ -80,6 +62,7 @@ def create_regions_sly3(world: "Sly3World"): }) world.multiworld.regions.append(region) + menu.connect( region, None, diff --git a/Sly3Rules.py b/Sly3Rules.py index 7e249ea..9b23641 100644 --- a/Sly3Rules.py +++ b/Sly3Rules.py @@ -4,177 +4,68 @@ from math import ceil from BaseClasses import CollectionState from worlds.generic.Rules import add_rule -from .data.Constants import EPISODES +from .data.Constants import EPISODES, CHALLENGES, REQUIREMENTS if typing.TYPE_CHECKING: from . import Sly3World def set_rules_sly3(world: "Sly3World"): player = world.player + thiefnet_items = world.options.thiefnet_locations.value # 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 - # items to be in logic, second group requires 4, etc. - for i in range(1,38): - episode_items_n = ceil(i/4)*2 + # Divides the items into groups that require a number of episode and crew + # items to be in logic + for i in range(1,thiefnet_items): + divisor = ceil(thiefnet_items/12) + episode_items_n = ceil(i/divisor) add_rule( world.get_location(f"ThiefNet {i:02}"), 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]): - if isinstance(item,str): - add_rule( - world.get_location(location), - lambda state, i=item: ( - state.has(i, player) - ) - ) - else: - add_rule( - world.get_location(location), - lambda state, i=item: ( - all(state.has(j, player) for j in i) - ) + add_rule( + world.get_location(location), + lambda state, i=item: ( + all(state.has(j, player) for j in i) ) + ) ### Job requirements - ## An Opera of fear - # An Opera of Fear - Police HQ + for episode, sections in EPISODES.items(): + if episode == "Honor Among Thieves": + continue - require("An Opera of Fear - Octavio Snap", "Binocucom") - # An Opera of Fear - Into the Depths - require("An Opera of Fear - Canal Chase", "Bentley") - - require("An Opera of Fear - Turf War!", "Carmelita") - require("An Opera of Fear - Tar Ball", ["Murray", "Ball Form"]) - # An Opera of Fear - Run 'n Bomb - 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 + for i, s in enumerate(sections): + for j, job in enumerate(s): + reqs = REQUIREMENTS["Jobs"][episode][i][j] + add_rule( + world.get_location(f"{episode} - {job}"), + lambda state, items=reqs: ( + all(state.has(item, player) for item in items) + ) + ) ### Challenge requirements - ## An Opera of Fear - require("An Opera of Fear - Canal Chase - Expert Course", "Bentley") - require("An Opera of Fear - Air Time", ["Murray", "Ball Form"]) - # 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") + for episode, sections in CHALLENGES.items(): + if episode == "Honor Among Thieves": + continue - ## Rumble Down Under - # Rumble Down Under - Rock Run - # Rumble Down Under - Cave Sprint - # Rumble Down Under - Cave Mayhem - # Rumble Down Under - Scaling the Drill - require("Rumble Down Under - Guard Swappin'", "Guru") - # Rumble Down Under - Quick Claw - 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 + for i, s in enumerate(sections): + for j, challenge in enumerate(s): + reqs = REQUIREMENTS["Challenges"][episode][i][j] + add_rule( + world.get_location(f"{episode} - {challenge}"), + lambda state, items=reqs: ( + all(state.has(item, player) for item in items) + ) + ) if world.options.goal.value < 6: victory_condition = [ diff --git a/__init__.py b/__init__.py index 7e5bdec..bd2a396 100644 --- a/__init__.py +++ b/__init__.py @@ -106,6 +106,7 @@ class Sly3World(World): self.options.include_mega_jump.value = slot_data["include_mega_jump"] self.options.coins_minimum.value = slot_data["coins_minimum"] 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_maximum.value = slot_data["thiefnet_maximum"] return @@ -155,6 +156,7 @@ class Sly3World(World): "include_mega_jump", "coins_minimum", "coins_maximum", + "thiefnet_locations", "thiefnet_minimum", "thiefnet_maximum", ) diff --git a/data/Constants.py b/data/Constants.py index e894cd7..7e4e877 100644 --- a/data/Constants.py +++ b/data/Constants.py @@ -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 = { "SCUS-97464" : { "map id": 0x47989C, @@ -213,6 +385,9 @@ ADDRESSES = { "frame counter": 0x389BE0, "x pressed": 0x36E78E, "skip cutscene": 0x389C20, + "gadgets": 0x468DCC, + "coins": 0x468DDC, + "DAG root": 0x478C8C, "jobs": [ [ [0x1335d10] @@ -227,7 +402,52 @@ ADDRESSES = { ], "text": { "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), diff --git a/data/Items.py b/data/Items.py index 819ec34..8cbb1f1 100644 --- a/data/Items.py +++ b/data/Items.py @@ -70,16 +70,16 @@ crew_list = [ ("Carmelita", ItemClassification.progression, "Crew") ] -progressive_episode_list = [ - (f"Progressive {e}", ItemClassification.progression, "Episode") - for e in list(EPISODES.keys())[:-1] +episode_list = [ + (episode, ItemClassification.progression, "Episode") + for episode in list(EPISODES.keys())[:-1] ] item_list = ( filler_list + powerup_list + crew_list + - progressive_episode_list + episode_list ) base_code = 5318008