🐐
This commit is contained in:
@@ -139,7 +139,7 @@ async def set_thiefnet(ctx: "Sly3Context"):
|
||||
|
||||
ctx.thiefnet_items.append(string)
|
||||
|
||||
ctx.thiefnet_purchases = PowerUps(*[
|
||||
ctx.thiefnet_purchases = PowerUps(*[False]*4+[
|
||||
Locations.location_dict[f"ThiefNet {i+1:02}"].code in ctx.checked_locations
|
||||
for i in range(thiefnet_n)
|
||||
])
|
||||
@@ -235,11 +235,19 @@ async def kick_from_episode(ctx: "Sly3Context", availability: Dict):
|
||||
|
||||
ep_not_unlocked = not ctx.available_episodes[Sly3Episode(ctx.current_episode)]
|
||||
|
||||
job_not_available = not availability.get(ctx.current_job,True)
|
||||
try:
|
||||
job_not_available = not availability[ctx.current_job]
|
||||
except:
|
||||
if ctx.current_job != 0xffffffff:
|
||||
print(f"Job ID not accounted for: {ctx.current_job}")
|
||||
job_not_available = False
|
||||
|
||||
if not_connected or ep_not_unlocked or job_not_available:
|
||||
print("Kicking")
|
||||
print(not_connected, ep_not_unlocked, job_not_available)
|
||||
ctx.game_interface.logger.debug(
|
||||
f"\nNot connected: {not_connected}"+
|
||||
f"\nEpisode not unlocked: {ep_not_unlocked}"+
|
||||
f"\nJob not available: {job_not_available}"
|
||||
)
|
||||
ctx.game_interface.to_episode_menu()
|
||||
|
||||
async def check_locations(ctx: "Sly3Context"):
|
||||
@@ -325,7 +333,7 @@ async def receive_items(ctx: "Sly3Context"):
|
||||
|
||||
available_episodes[episode] = True
|
||||
elif item.category == "Power-Up":
|
||||
item_name = item.name.lower().replace(" ","_")
|
||||
item_name = item.name.lower().replace(" ","_").replace("-","_")
|
||||
if item_name == "Progressive Shadow Power":
|
||||
if new_powerups[30]:
|
||||
idx = 32
|
||||
@@ -394,7 +402,7 @@ async def handle_job_markers(ctx: "Sly3Context", availability: Dict):
|
||||
inactive_jobs.append(job_id)
|
||||
|
||||
ctx.game_interface.activate_jobs(active_jobs)
|
||||
ctx.game_interface.activate_jobs(inactive_jobs)
|
||||
ctx.game_interface.deactivate_jobs(inactive_jobs)
|
||||
|
||||
async def handle_notifications(ctx: "Sly3Context"):
|
||||
if (
|
||||
@@ -458,6 +466,8 @@ async def update(ctx: "Sly3Context") -> None:
|
||||
return
|
||||
|
||||
if not ctx.game_interface.is_game_started():
|
||||
if ctx.game_interface.intro_done():
|
||||
ctx.game_interface.to_episode_menu()
|
||||
return
|
||||
|
||||
availability = accessibility(ctx)
|
||||
@@ -471,7 +481,8 @@ async def update(ctx: "Sly3Context") -> None:
|
||||
await send_checks(ctx)
|
||||
await receive_items(ctx)
|
||||
await check_goal(ctx)
|
||||
if ctx.game_interface.in_hub():
|
||||
|
||||
if ctx.current_episode != 0 and ctx.game_interface.in_hub():
|
||||
await handle_job_markers(ctx, availability)
|
||||
|
||||
if ctx.current_map != 0 and not ctx.in_safehouse:
|
||||
|
||||
151
Sly3Client.py
151
Sly3Client.py
@@ -3,11 +3,12 @@ import asyncio
|
||||
import multiprocessing
|
||||
import traceback
|
||||
|
||||
from .data import Items, Locations
|
||||
from .data.Constants import EPISODES, CHALLENGES
|
||||
from CommonClient import logger, server_loop, gui_enabled, get_base_parser
|
||||
from BaseClasses import ItemClassification
|
||||
import Utils
|
||||
|
||||
from .data import Items, Locations
|
||||
from .data.Constants import EPISODES, CHALLENGES, REQUIREMENTS
|
||||
from .Sly3Interface import Sly3Interface, Sly3Episode, PowerUps
|
||||
from .Sly3Callbacks import init, update
|
||||
|
||||
@@ -44,11 +45,21 @@ class Sly3CommandProcessor(ClientCommandProcessor): # type: ignore[misc]
|
||||
if isinstance(self.ctx, Sly3Context):
|
||||
self.ctx.game_interface.to_episode_menu()
|
||||
|
||||
def _cmd_reload(self):
|
||||
"""Reload (in case you're stuck)"""
|
||||
if isinstance(self.ctx, Sly3Context):
|
||||
self.ctx.game_interface._reload()
|
||||
|
||||
def _cmd_coins(self, amount: str):
|
||||
"""Add coins to game."""
|
||||
if isinstance(self.ctx, Sly3Context):
|
||||
self.ctx.game_interface.add_coins(int(amount))
|
||||
|
||||
def _cmd_notification(self, text: str):
|
||||
"""Add coins to game."""
|
||||
if isinstance(self.ctx, Sly3Context):
|
||||
self.ctx.notification(text)
|
||||
|
||||
class Sly3Context(CommonContext): # type: ignore[misc]
|
||||
command_processor = Sly3CommandProcessor
|
||||
game_interface: Sly3Interface
|
||||
@@ -115,7 +126,135 @@ class Sly3Context(CommonContext): # type: ignore[misc]
|
||||
|
||||
# AP version is added behind this automatically
|
||||
ui.base_title += " | Archipelago"
|
||||
return ui
|
||||
|
||||
# Making the out of logic tab
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
from kivy.metrics import dp
|
||||
|
||||
def make_left_label(**kwargs):
|
||||
lbl = Label(**kwargs)
|
||||
lbl.bind(width=lambda instance, value: setattr(instance, 'text_size', (value, None))) # type: ignore
|
||||
lbl.bind(texture_size=lambda instance, value: setattr(instance, 'height', value[1])) # type: ignore
|
||||
return lbl
|
||||
|
||||
container = BoxLayout(
|
||||
orientation='vertical',
|
||||
padding=dp(10),
|
||||
spacing=dp(8),
|
||||
size_hint_y=None,
|
||||
)
|
||||
container.bind(minimum_height=container.setter('height')) # type: ignore
|
||||
|
||||
container.add_widget(Label(
|
||||
text="Out of logic locations and their required progression items",
|
||||
font_size=dp(16),
|
||||
bold=True,
|
||||
size_hint_y=None,
|
||||
height=dp(40),
|
||||
halign="center",
|
||||
valign="middle",
|
||||
))
|
||||
|
||||
container.add_widget(make_left_label(
|
||||
text="Jobs",
|
||||
font_size=dp(14),
|
||||
bold=True,
|
||||
size_hint_y=None,
|
||||
height=dp(30),
|
||||
halign="left",
|
||||
valign="bottom",
|
||||
))
|
||||
self.jobs_label = make_left_label(
|
||||
size_hint_y=None,
|
||||
halign="left",
|
||||
valign="top",
|
||||
)
|
||||
container.add_widget(self.jobs_label)
|
||||
|
||||
container.add_widget(make_left_label(
|
||||
text="Master Thief Challenges",
|
||||
font_size=dp(14),
|
||||
bold=True,
|
||||
size_hint_y=None,
|
||||
height=dp(30),
|
||||
halign="left",
|
||||
valign="bottom",
|
||||
))
|
||||
self.challenges_label = make_left_label(
|
||||
size_hint_y=None,
|
||||
halign="left",
|
||||
valign="top",
|
||||
)
|
||||
container.add_widget(self.challenges_label)
|
||||
|
||||
scroll = ScrollView(size_hint=(1, 1))
|
||||
scroll.add_widget(container)
|
||||
self.out_of_logic_tab = scroll
|
||||
|
||||
class Manager(ui):
|
||||
def build(self):
|
||||
super().build()
|
||||
self.add_client_tab("Out-of-Logic", self.ctx.out_of_logic_tab)
|
||||
return self.container
|
||||
|
||||
return Manager
|
||||
|
||||
def update_gui(self):
|
||||
received_items = [Items.from_id(i.item) for i in self.items_received]
|
||||
progression_items = [i.name for i in received_items if i.classification == ItemClassification.progression]
|
||||
|
||||
section_requirements = {
|
||||
episode_name: [
|
||||
list(set(sum([
|
||||
sum(
|
||||
ep_reqs,
|
||||
[]
|
||||
)
|
||||
for ep_reqs
|
||||
in episode[:i-1]
|
||||
], [])))
|
||||
for i in range(1,5)
|
||||
]
|
||||
for episode_name, episode in REQUIREMENTS["Jobs"].items()
|
||||
}
|
||||
|
||||
# Jobs
|
||||
jobs = [
|
||||
f"{ep_name} - {job}"
|
||||
for ep_name, ep in EPISODES.items() for chapter in ep for job in chapter
|
||||
]
|
||||
job_requirements = [
|
||||
[r for r in reqs+section_requirements[ep_name][chapter_idx] if r not in progression_items]
|
||||
for ep_name, ep in REQUIREMENTS["Jobs"].items()
|
||||
for chapter_idx, chapter in enumerate(ep) for reqs in chapter
|
||||
]
|
||||
job_pairs = zip(jobs,job_requirements)
|
||||
|
||||
self.jobs_label.text = "\n".join([
|
||||
f"{job}: {', '.join(reqs)}"
|
||||
for job, reqs in job_pairs
|
||||
if reqs != []
|
||||
])
|
||||
|
||||
# Challenges
|
||||
challenges = [
|
||||
f"{ep_name} - {challenge}"
|
||||
for ep_name, ep in CHALLENGES.items() for chapter in ep for challenge in chapter
|
||||
]
|
||||
challenge_requirements = [
|
||||
sorted([r for r in list(set(reqs+section_requirements[ep_name][chapter_idx])) if r not in progression_items])
|
||||
for ep_name, ep in REQUIREMENTS["Challenges"].items()
|
||||
for chapter_idx, chapter in enumerate(ep) for reqs in chapter
|
||||
]
|
||||
challenge_pairs = zip(challenges,challenge_requirements)
|
||||
|
||||
self.challenges_label.text = "\n".join([
|
||||
f"{challenge}: {', '.join(reqs)}"
|
||||
for challenge, reqs in challenge_pairs
|
||||
if reqs != []
|
||||
])
|
||||
|
||||
# async def server_auth(self, password_requested: bool = False) -> None:
|
||||
# if password_requested and not self.password:
|
||||
@@ -150,10 +289,12 @@ class Sly3Context(CommonContext): # type: ignore[misc]
|
||||
for i in range(args["slot_data"]["thiefnet_locations"])
|
||||
]
|
||||
}]))
|
||||
self.update_gui()
|
||||
if cmd in ["RoomUpdate", "ReceivedItems"]:
|
||||
self.update_gui()
|
||||
|
||||
def notification(self, text: str):
|
||||
# TODO: Notifications
|
||||
pass
|
||||
self.notification_queue.append(text)
|
||||
|
||||
def update_connection_status(ctx: Sly3Context, status: bool):
|
||||
if ctx.is_connected_to_game == status:
|
||||
|
||||
133
Sly3Interface.py
133
Sly3Interface.py
@@ -3,6 +3,7 @@ import struct
|
||||
from logging import Logger
|
||||
from enum import IntEnum
|
||||
import traceback
|
||||
from time import sleep
|
||||
|
||||
from .pcsx2_interface.pine import Pine
|
||||
from .data.Constants import ADDRESSES, MENU_RETURN_DATA, POWER_UP_TEXT
|
||||
@@ -125,6 +126,11 @@ class GameInterface():
|
||||
def _write_float(self, address: int, value: float):
|
||||
self.pcsx2_interface.write_float(address, value)
|
||||
|
||||
def _batch_write32(self, operations: list[tuple[int,int]]):
|
||||
self.pcsx2_interface.batch_write_int32(operations)
|
||||
# for address, data in operations:
|
||||
# self._write32(address, data)
|
||||
|
||||
def connect_to_game(self):
|
||||
"""
|
||||
Initializes the connection to PCSX2 and verifies it is connected to the
|
||||
@@ -171,11 +177,12 @@ class Sly3Interface(GameInterface):
|
||||
############################
|
||||
## Private Helper Methods ##
|
||||
############################
|
||||
def _reload(self, reload_data: bytes):
|
||||
self._write_bytes(
|
||||
self.addresses["reload values"],
|
||||
reload_data
|
||||
)
|
||||
def _reload(self, reload_data: bytes = b""):
|
||||
if reload_data != b"":
|
||||
self._write_bytes(
|
||||
self.addresses["reload values"],
|
||||
reload_data
|
||||
)
|
||||
self._write32(self.addresses["reload"], 1)
|
||||
|
||||
def _get_job_address(self, task: int) -> int:
|
||||
@@ -197,6 +204,10 @@ class Sly3Interface(GameInterface):
|
||||
return self._read32(string_table_address+i*8+4)
|
||||
i += 1
|
||||
|
||||
def _job_parents_finished(self, job: int) -> bool:
|
||||
# TODO: Job Markers
|
||||
return True
|
||||
|
||||
###################
|
||||
## Current State ##
|
||||
###################
|
||||
@@ -211,7 +222,7 @@ class Sly3Interface(GameInterface):
|
||||
return (
|
||||
not self.is_loading() and
|
||||
self.in_hub() and
|
||||
self._read32(self.addresses["infobox string"]) in [
|
||||
self.current_infobox() in [
|
||||
5345,5346,5347,5348,5349,5350,5351
|
||||
]
|
||||
)
|
||||
@@ -232,17 +243,17 @@ class Sly3Interface(GameInterface):
|
||||
)
|
||||
|
||||
def showing_infobox(self) -> bool:
|
||||
# TODO: Notifications
|
||||
return False
|
||||
infobox_pointer = self._read32(self.addresses["infobox"])
|
||||
return self._read32(infobox_pointer+0x64) == 2
|
||||
|
||||
def alive(self) -> bool:
|
||||
active_character = self._read32(self.addresses["active character pointer"])
|
||||
if active_character == 0:
|
||||
return True
|
||||
|
||||
health_gui = self._read32(active_character+0x16c)
|
||||
health_gui_pointer = self._read32(active_character+0x168)
|
||||
health = self._read32(active_character+0x16c)
|
||||
return health_gui != 2 or health != 0
|
||||
return health_gui_pointer == 0 or health != 0
|
||||
|
||||
#######################
|
||||
## Getters & Setters ##
|
||||
@@ -254,9 +265,11 @@ class Sly3Interface(GameInterface):
|
||||
if i not in [28,36,37,39,40,42,43]
|
||||
][:len(data)]
|
||||
|
||||
operations = []
|
||||
|
||||
for i, address in enumerate(addresses):
|
||||
self._write32(address,data[i][0])
|
||||
self._write32(address+0xC,0)
|
||||
operations.append((address,data[i][0]))
|
||||
operations.append((address+0xC,0))
|
||||
name_id = self._read32(address+0x14)
|
||||
name_address = self._find_string_address(name_id)
|
||||
self.set_text(name_address, f"Check #{i+1}")
|
||||
@@ -267,7 +280,9 @@ class Sly3Interface(GameInterface):
|
||||
|
||||
for i in [28,36,37,39,40,42,43]+list(range(len(data),44)):
|
||||
address = self.addresses["thiefnet start"]+i*0x3c
|
||||
self._write32(address+0xC,10)
|
||||
operations.append((address+0xC,10))
|
||||
|
||||
self._batch_write32(operations)
|
||||
|
||||
def reset_thiefnet(self) -> None:
|
||||
for i in range(44):
|
||||
@@ -344,12 +359,40 @@ class Sly3Interface(GameInterface):
|
||||
return PowerUps(*relevant_bits)
|
||||
|
||||
def activate_jobs(self, job_ids: int|list[int]):
|
||||
# TODO: Job Markers
|
||||
pass
|
||||
if isinstance(job_ids, int):
|
||||
job_ids = [job_ids]
|
||||
|
||||
markers = self.addresses["job markers"]
|
||||
to_read = []
|
||||
for job in job_ids:
|
||||
if job not in markers:
|
||||
self.logger.debug(f"Job {job} not able to be activated")
|
||||
continue
|
||||
|
||||
to_read.append(job)
|
||||
|
||||
statuses = self._batch_read32([markers[j]+0x44 for j in to_read])
|
||||
to_write = [j for i,j in enumerate(to_read) if statuses[i] == 0 and self._job_parents_finished(j)]
|
||||
operations = [(markers[j]+0x44,1) for j in to_write]
|
||||
self._batch_write32(operations)
|
||||
|
||||
def deactivate_jobs(self, job_ids: int|list[int]):
|
||||
# TODO: Job Markers
|
||||
pass
|
||||
if isinstance(job_ids, int):
|
||||
job_ids = [job_ids]
|
||||
|
||||
markers = self.addresses["job markers"]
|
||||
to_read = []
|
||||
for job in job_ids:
|
||||
if job not in markers:
|
||||
self.logger.debug(f"Job {job} not able to be deactivated")
|
||||
continue
|
||||
|
||||
to_read.append(job)
|
||||
|
||||
statuses = self._batch_read32([markers[j]+0x44 for j in to_read])
|
||||
to_write = [j for i,j in enumerate(to_read) if statuses[i] == 1]
|
||||
operations = [(markers[j]+0x44,0) for j in to_write]
|
||||
self._batch_write32(operations)
|
||||
|
||||
def jobs_completed(self) -> list[bool]:
|
||||
addresses = [a for ep in self.addresses["job completed"].values() for c in ep for a in c]
|
||||
@@ -358,8 +401,7 @@ class Sly3Interface(GameInterface):
|
||||
return [s != 0 for s in states]
|
||||
|
||||
def current_infobox(self) -> int:
|
||||
# TODO: Notifications
|
||||
return 0
|
||||
return self._read32(self.addresses["infobox string"])
|
||||
|
||||
def get_damage_type(self) -> int:
|
||||
# TODO: Death Messages
|
||||
@@ -368,14 +410,19 @@ class Sly3Interface(GameInterface):
|
||||
#################
|
||||
## Other Utils ##
|
||||
#################
|
||||
def intro_done(self) -> bool:
|
||||
return self._read32(self.addresses["intro complete"]) != 0
|
||||
|
||||
def to_episode_menu(self) -> None:
|
||||
self.logger.info("Skipping to episode menu")
|
||||
if (
|
||||
self.get_current_map() == 35 and
|
||||
self.get_current_job() == 1797
|
||||
self.get_current_job() == 1797 and
|
||||
not self.intro_done()
|
||||
):
|
||||
self.set_current_job(0xffffffff)
|
||||
self.set_items_received(0)
|
||||
self._write32(self.addresses["intro complete"],1)
|
||||
|
||||
self._reload(bytes.fromhex(MENU_RETURN_DATA))
|
||||
|
||||
@@ -394,12 +441,22 @@ class Sly3Interface(GameInterface):
|
||||
self._write32(self.addresses["coins"],new_amount)
|
||||
|
||||
def disable_infobox(self):
|
||||
# TODO: Notifications
|
||||
pass
|
||||
infobox_pointer = self._read32(self.addresses["infobox"])
|
||||
if self._read32(infobox_pointer+0x54) != 1:
|
||||
self._write32(infobox_pointer+0x54,2)
|
||||
self._write32(infobox_pointer+0x54,1)
|
||||
|
||||
def set_infobox(self, text: str):
|
||||
# TODO: Notifications
|
||||
pass
|
||||
ep = self.get_current_episode()
|
||||
if ep == 0 or self.in_safehouse():
|
||||
return
|
||||
|
||||
infobox_pointer = self._read32(self.addresses["infobox"])
|
||||
self._write32(self.addresses["infobox scrolling"],1)
|
||||
self.set_text("infobox"," "*10+text)
|
||||
self._write32(self.addresses["infobox string"],1)
|
||||
self._write32(infobox_pointer+0x54,2)
|
||||
self._write32(self.addresses["infobox duration"],0xffffffff)
|
||||
|
||||
def kill_player(self):
|
||||
if self.in_safehouse() or self.get_current_episode() == Sly3Episode.Title_Screen:
|
||||
@@ -553,6 +610,19 @@ def current_job_info(interf: Sly3Interface):
|
||||
print("Job index:", i)
|
||||
print("Job state (should be 2):", interf._read32(address+0x44))
|
||||
|
||||
def active_jobs_info(interf: Sly3Interface):
|
||||
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)
|
||||
job_state = interf._read32(address+0x44)
|
||||
if job_state == 1:
|
||||
print(f"{job_id}: {hex(address)}")
|
||||
|
||||
address = interf._read32(address+0x20)
|
||||
i += 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
interf = Sly3Interface(Logger("test"))
|
||||
interf.connect_to_game()
|
||||
@@ -587,4 +657,17 @@ if __name__ == "__main__":
|
||||
# print(find_string_address(interf, address))
|
||||
# print("======")
|
||||
|
||||
print_thiefnet_text(interf)
|
||||
# print(hex(find_string_id(interf, 1)))
|
||||
# print([hex(i) for i in find_text(interf, "merges")])
|
||||
# active_jobs_info(interf)
|
||||
# interf.set_infobox("test")
|
||||
# interf.disable_infobox()
|
||||
# pointer = interf._read32(0x46F798)
|
||||
# print(interf._read32(pointer+0x54))
|
||||
# print(interf._read32(pointer+0x64))
|
||||
|
||||
# interf.to_episode_menu()
|
||||
# print(interf.get_items_received())
|
||||
# interf.set_items_received(28)
|
||||
# interf._write32(interf.addresses["intro complete"], 1)
|
||||
print(interf._read32(interf.addresses["reload"]))
|
||||
@@ -14,8 +14,10 @@ class StartingEpisode(Choice):
|
||||
"""
|
||||
Select Which episode to start with.
|
||||
|
||||
Flight of Fancy, A Cold Alliance and Dead Men Tell No Tales require starting
|
||||
items, so starting with them will break a solo game.
|
||||
Flight of Fancy and Dead Men Tell No Tales require items to do the first
|
||||
jobs, meaning you'll only have ThiefNet in logic.
|
||||
A Cold Alliance also requires items to even have ThiefNet in logic, so if you
|
||||
start with that episode, you will start with no locations in logic.
|
||||
"""
|
||||
|
||||
display_name = "Starting Episode"
|
||||
|
||||
37
Sly3Rules.py
37
Sly3Rules.py
@@ -10,6 +10,27 @@ from .data.Locations import location_dict
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import Sly3World
|
||||
|
||||
def make_thiefnet_rule(player: int, n: int):
|
||||
def new_rule(state: CollectionState):
|
||||
if (
|
||||
state.count_group("Episode", player) == 1 and
|
||||
state.has("A Cold Alliance", player) and
|
||||
not all(
|
||||
state.has(item, player)
|
||||
for item in ["Bentley", "Murray", "Guru", "Penelope", "Binocucom"]
|
||||
)
|
||||
):
|
||||
return False
|
||||
|
||||
progression_items = (
|
||||
state.count_group("Episode", player) +
|
||||
state.count_group("Crew", player)
|
||||
)
|
||||
|
||||
return progression_items >= n
|
||||
|
||||
return new_rule
|
||||
|
||||
def set_rules_sly3(world: "Sly3World"):
|
||||
player = world.player
|
||||
thiefnet_items = world.options.thiefnet_locations.value
|
||||
@@ -17,18 +38,14 @@ def set_rules_sly3(world: "Sly3World"):
|
||||
# Putting ThiefNet stuff out of logic, to make early game less slow.
|
||||
# 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+1):
|
||||
if not hasattr(world.multiworld, "generation_is_fake"): # (unless tracking)
|
||||
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.count_group("Episode", player) +
|
||||
state.count_group("Crew", player)
|
||||
) >= n
|
||||
for i in range(1,thiefnet_items+1):
|
||||
episode_items_n = ceil(i/divisor)
|
||||
add_rule(
|
||||
world.get_location(f"ThiefNet {i:02}"),
|
||||
make_thiefnet_rule(player, episode_items_n)
|
||||
)
|
||||
)
|
||||
|
||||
### Job requirements
|
||||
for episode, sections in EPISODES.items():
|
||||
|
||||
@@ -211,7 +211,7 @@ REQUIREMENTS = {
|
||||
[[]],
|
||||
[
|
||||
["Binocucom"],
|
||||
[],
|
||||
["Bombs"],
|
||||
["Bentley"],
|
||||
],
|
||||
[
|
||||
@@ -227,14 +227,14 @@ REQUIREMENTS = {
|
||||
"Rumble Down Under" :[
|
||||
[[]],
|
||||
[
|
||||
["Murray"],
|
||||
["Murray", "Ball Form"],
|
||||
[],
|
||||
[],
|
||||
["Guru"],
|
||||
["Murray", "Ball Form"],
|
||||
["Bentley", "Murray", "Ball Form", "Guru"],
|
||||
],
|
||||
[
|
||||
[],
|
||||
["Bentley"],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
[[]]
|
||||
@@ -244,10 +244,10 @@ REQUIREMENTS = {
|
||||
["Bentley"]
|
||||
],
|
||||
[
|
||||
["Murray", "Bentley", "Guru", "Fishing Pole"],
|
||||
["Murray", "Guru", "Fishing Pole"],
|
||||
["Murray"],
|
||||
["Penelope"],
|
||||
["Murray", "Bentley", "Guru", "Fishing Pole", "Penelope"]
|
||||
["Murray","Penelope"],
|
||||
["Murray", "Guru", "Fishing Pole", "Penelope"]
|
||||
],
|
||||
[
|
||||
["Binocucom"],
|
||||
@@ -293,6 +293,18 @@ REQUIREMENTS = {
|
||||
["Guru"]
|
||||
]
|
||||
],
|
||||
"Honor Among Thieves": [
|
||||
[
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
]
|
||||
],
|
||||
},
|
||||
"Challenges": {
|
||||
"An Opera of Fear": [
|
||||
@@ -317,8 +329,8 @@ REQUIREMENTS = {
|
||||
[
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
["Guru"]
|
||||
["Murray", "Ball Form"],
|
||||
["Murray", "Ball Form", "Guru"]
|
||||
],
|
||||
[
|
||||
[],
|
||||
@@ -331,12 +343,14 @@ REQUIREMENTS = {
|
||||
]
|
||||
],
|
||||
"Flight of Fancy": [
|
||||
[[]],
|
||||
[
|
||||
["Penelope"],
|
||||
["Penelope"],
|
||||
["Penelope"],
|
||||
["Murray", "Bentley", "Guru", "Fishing Pole", "Penelope"],
|
||||
["Bentley"]
|
||||
],
|
||||
[
|
||||
["Murray", "Penelope"],
|
||||
["Murray", "Penelope"],
|
||||
["Murray", "Penelope"],
|
||||
["Murray", "Guru", "Fishing Pole", "Penelope"],
|
||||
],
|
||||
[
|
||||
[],
|
||||
@@ -374,6 +388,15 @@ REQUIREMENTS = {
|
||||
[],
|
||||
[[]]
|
||||
],
|
||||
"Honor Among Thieves": [
|
||||
[
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
["Bentley", "Murray", "Guru", "Penelope", "Panda King", "Dimitri", "Carmelita"],
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,6 +452,7 @@ ADDRESSES = {
|
||||
"gadgets": 0x468DCC,
|
||||
"coins": 0x468DDC,
|
||||
"DAG root": 0x478C8C,
|
||||
"intro complete": 0x468EEC,
|
||||
"job completed": {
|
||||
"An Opera of Fear": [
|
||||
[
|
||||
@@ -537,8 +561,42 @@ ADDRESSES = {
|
||||
]
|
||||
],
|
||||
},
|
||||
"job markers": {
|
||||
2085: 0x1335d10,
|
||||
2230: 0x1350560,
|
||||
2283: 0x1357f80,
|
||||
2329: 0x135aba0,
|
||||
2139: 0x1330c40,
|
||||
2168: 0x1335dc0,
|
||||
2187: 0x133e9b0,
|
||||
2352: 0x1351520,
|
||||
2419: 0x135e550,
|
||||
2577: 0x6b4250,
|
||||
2596: 0x6b80f0,
|
||||
2805: 0x6d0770,
|
||||
2695: 0x5d26f0,
|
||||
2663: 0x6bdaf0,
|
||||
2623: 0x5caa20,
|
||||
2730: 0x5fe390,
|
||||
2780: 0x6ca940,
|
||||
2843: 0x6d4330,
|
||||
2983: 0x794360,
|
||||
3025: 0x627a60,
|
||||
3061: 0x62c7b0,
|
||||
3101: 0x630220,
|
||||
3140: 0xecb450,
|
||||
3202: 0x642a90,
|
||||
3164: 0x63b4d0,
|
||||
3225: 0x7adf50,
|
||||
3259: 0x651eb0
|
||||
},
|
||||
"active character pointer": 0x36F84C,
|
||||
"infobox scrolling": 0x46F780,
|
||||
"infobox string": 0x46F788,
|
||||
"infobox duration": 0x46F78C,
|
||||
"infobox": 0x46F798,
|
||||
# "infobox": 0x47671C,
|
||||
# "infobox": 0x479758,
|
||||
"thiefnet start": 0x343208,
|
||||
"string table": 0x47A2D8,
|
||||
"text": {
|
||||
@@ -551,6 +609,14 @@ ADDRESSES = {
|
||||
"A Cold Alliance": 0x53b710,
|
||||
"Dead Men Tell No Tales": 0x53b7b0,
|
||||
"Honor Among Thieves": 0x53b900,
|
||||
"infobox": {
|
||||
3: 0x5765d0,
|
||||
8: 0x564170,
|
||||
15: 0x579ec0,
|
||||
23: 0x579ec0,
|
||||
31: 0x0,
|
||||
35: 0x0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user