mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-24 16:33:20 -07:00
March Refactors (#77)
* Reorg imports, small fix to Rock Village movement. * Fix wait-on-title message never going to ready message. * Colorama init fix. * Swap trap list for a dictionary of trap weights. * The more laws, the less justice. * Quick readability update. * Have memory reader provide instructions for slow booting games. * Revert some things.
This commit is contained in:
committed by
GitHub
parent
e0e4926fbb
commit
b5d02be9ed
@@ -1,36 +1,39 @@
|
||||
# Python standard libraries
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from logging import Logger
|
||||
from datetime import datetime
|
||||
import sys
|
||||
|
||||
import colorama
|
||||
|
||||
import asyncio
|
||||
from asyncio import Task
|
||||
from datetime import datetime
|
||||
from logging import Logger
|
||||
from typing import Awaitable
|
||||
|
||||
from typing import Set, Awaitable
|
||||
|
||||
# Misc imports
|
||||
import colorama
|
||||
import pymem
|
||||
|
||||
from pymem.exception import ProcessNotFound
|
||||
|
||||
import Utils
|
||||
from NetUtils import ClientStatus
|
||||
from CommonClient import ClientCommandProcessor, CommonContext, server_loop, gui_enabled
|
||||
from .Options import EnableOrbsanity
|
||||
|
||||
from .GameID import jak1_name
|
||||
from .client.ReplClient import JakAndDaxterReplClient
|
||||
from .client.MemoryReader import JakAndDaxterMemoryReader
|
||||
|
||||
# Archipelago imports
|
||||
import ModuleUpdate
|
||||
import Utils
|
||||
|
||||
from CommonClient import ClientCommandProcessor, CommonContext, server_loop, gui_enabled
|
||||
from NetUtils import ClientStatus
|
||||
|
||||
# Jak imports
|
||||
from .GameID import jak1_name
|
||||
from .Options import EnableOrbsanity
|
||||
from .client.MemoryReader import JakAndDaxterMemoryReader
|
||||
from .client.ReplClient import JakAndDaxterReplClient
|
||||
|
||||
|
||||
ModuleUpdate.update()
|
||||
|
||||
|
||||
logger = logging.getLogger("JakClient")
|
||||
all_tasks: Set[Task] = set()
|
||||
all_tasks: set[Task] = set()
|
||||
|
||||
|
||||
def create_task_log_exception(awaitable: Awaitable) -> asyncio.Task:
|
||||
@@ -141,7 +144,7 @@ class JakAndDaxterContext(CommonContext):
|
||||
orbsanity_bundle = 1
|
||||
|
||||
# Connected packet is unaware of starting inventory or if player is returning to an existing game.
|
||||
# Set initial item count to 0 if it hasn't been set higher by a ReceivedItems packet yet.
|
||||
# Set initial_item_count to 0, see below comments for more info.
|
||||
if not self.repl.received_initial_items and self.repl.initial_item_count < 0:
|
||||
self.repl.initial_item_count = 0
|
||||
|
||||
@@ -178,11 +181,15 @@ class JakAndDaxterContext(CommonContext):
|
||||
create_task_log_exception(self.repl.subtract_traded_orbs(orbs_traded))
|
||||
|
||||
if cmd == "ReceivedItems":
|
||||
if not self.repl.received_initial_items:
|
||||
|
||||
# ReceivedItems packet should set the initial item count to > 0, even if already set to 0 by the
|
||||
# Connected packet. Then we should tell the game to update the title screen, telling the player
|
||||
# to wait while we process the initial items. This is skipped if no initial items are sent.
|
||||
# If you have a starting inventory or are returning to a game where you have items, a ReceivedItems will be
|
||||
# in the same network packet as Connected. This guarantees it is the first of any ReceivedItems we process.
|
||||
# In this case, we should set the initial_item_count to > 0, even if already set to 0 by Connected, as well
|
||||
# as the received_initial_items flag. Finally, use send_connection_status to tell the player to wait while
|
||||
# we process the initial items. However, we will skip all this if there was no initial ReceivedItems and
|
||||
# the REPL indicates it already handled any initial items (0 or otherwise).
|
||||
if not self.repl.received_initial_items and not self.repl.processed_initial_items:
|
||||
self.repl.received_initial_items = True
|
||||
self.repl.initial_item_count = len(args["items"])
|
||||
create_task_log_exception(self.repl.send_connection_status("wait"))
|
||||
|
||||
@@ -225,14 +232,14 @@ class JakAndDaxterContext(CommonContext):
|
||||
# Write to game display.
|
||||
self.repl.queue_game_text(my_item_name, my_item_finder, their_item_name, their_item_owner)
|
||||
|
||||
# Even though N items come in as 1 ReceivedItems packet, there are still N PrintJson packets to process,
|
||||
# and they all arrive before the ReceivedItems packet does. Defer processing of these packets as
|
||||
# async tasks to speed up large releases of items.
|
||||
def on_print_json(self, args: dict) -> None:
|
||||
|
||||
# Even though N items come in as 1 ReceivedItems packet, there are still N PrintJson packets to process,
|
||||
# and they all arrive before the ReceivedItems packet does. Defer processing of these packets as
|
||||
# async tasks to speed up large releases of items.
|
||||
create_task_log_exception(self.json_to_game_text(args))
|
||||
super(JakAndDaxterContext, self).on_print_json(args)
|
||||
|
||||
# We need to do a little more than just use CommonClient's on_deathlink.
|
||||
def on_deathlink(self, data: dict):
|
||||
if self.memr.deathlink_enabled:
|
||||
self.repl.received_deathlink = True
|
||||
@@ -245,6 +252,11 @@ class JakAndDaxterContext(CommonContext):
|
||||
def on_location_check(self, location_ids: list[int]):
|
||||
create_task_log_exception(self.ap_inform_location_check(location_ids))
|
||||
|
||||
# TODO - Use CommonClient's check_locations function as our async task - AP 0.6.0 ONLY.
|
||||
# def on_location_check(self, location_ids: list[int]):
|
||||
# create_task_log_exception(self.check_locations(location_ids))
|
||||
|
||||
# CommonClient has no finished_game function, so we will have to craft our own. TODO - Update if that changes.
|
||||
async def ap_inform_finished_game(self):
|
||||
if not self.finished_game and self.memr.finished_game:
|
||||
message = [{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]
|
||||
@@ -254,6 +266,7 @@ class JakAndDaxterContext(CommonContext):
|
||||
def on_finish_check(self):
|
||||
create_task_log_exception(self.ap_inform_finished_game())
|
||||
|
||||
# We need to do a little more than just use CommonClient's send_death.
|
||||
async def ap_inform_deathlink(self):
|
||||
if self.memr.deathlink_enabled:
|
||||
player = self.player_names[self.slot] if self.slot is not None else "Jak"
|
||||
@@ -268,12 +281,11 @@ class JakAndDaxterContext(CommonContext):
|
||||
def on_deathlink_check(self):
|
||||
create_task_log_exception(self.ap_inform_deathlink())
|
||||
|
||||
async def ap_inform_deathlink_toggle(self):
|
||||
await self.update_death_link(self.memr.deathlink_enabled)
|
||||
|
||||
# Use CommonClient's update_death_link function as our async task.
|
||||
def on_deathlink_toggle(self):
|
||||
create_task_log_exception(self.ap_inform_deathlink_toggle())
|
||||
create_task_log_exception(self.update_death_link(self.memr.deathlink_enabled))
|
||||
|
||||
# Orb trades are situations unique to Jak, so we have to craft our own function.
|
||||
async def ap_inform_orb_trade(self, orbs_changed: int):
|
||||
if self.memr.orbsanity_enabled:
|
||||
await self.send_msgs([{"cmd": "Set",
|
||||
@@ -286,32 +298,32 @@ class JakAndDaxterContext(CommonContext):
|
||||
def on_orb_trade(self, orbs_changed: int):
|
||||
create_task_log_exception(self.ap_inform_orb_trade(orbs_changed))
|
||||
|
||||
def _markup_panels(self, msg: str, c: str = None):
|
||||
color = self.jsontotextparser.color_codes[c] if c else None
|
||||
message = f"[color={color}]{msg}[/color]" if c else msg
|
||||
|
||||
self.ui.log_panels["Archipelago"].on_message_markup(message)
|
||||
self.ui.log_panels["All"].on_message_markup(message)
|
||||
|
||||
def on_log_error(self, lg: Logger, message: str):
|
||||
lg.error(message)
|
||||
if self.ui:
|
||||
color = self.jsontotextparser.color_codes["red"]
|
||||
self.ui.log_panels["Archipelago"].on_message_markup(f"[color={color}]{message}[/color]")
|
||||
self.ui.log_panels["All"].on_message_markup(f"[color={color}]{message}[/color]")
|
||||
self._markup_panels(message, "red")
|
||||
|
||||
def on_log_warn(self, lg: Logger, message: str):
|
||||
lg.warning(message)
|
||||
if self.ui:
|
||||
color = self.jsontotextparser.color_codes["orange"]
|
||||
self.ui.log_panels["Archipelago"].on_message_markup(f"[color={color}]{message}[/color]")
|
||||
self.ui.log_panels["All"].on_message_markup(f"[color={color}]{message}[/color]")
|
||||
self._markup_panels(message, "orange")
|
||||
|
||||
def on_log_success(self, lg: Logger, message: str):
|
||||
lg.info(message)
|
||||
if self.ui:
|
||||
color = self.jsontotextparser.color_codes["green"]
|
||||
self.ui.log_panels["Archipelago"].on_message_markup(f"[color={color}]{message}[/color]")
|
||||
self.ui.log_panels["All"].on_message_markup(f"[color={color}]{message}[/color]")
|
||||
self._markup_panels(message, "green")
|
||||
|
||||
def on_log_info(self, lg: Logger, message: str):
|
||||
lg.info(message)
|
||||
if self.ui:
|
||||
self.ui.log_panels["Archipelago"].on_message_markup(f"{message}")
|
||||
self.ui.log_panels["All"].on_message_markup(f"{message}")
|
||||
self._markup_panels(message)
|
||||
|
||||
async def run_repl_loop(self):
|
||||
while True:
|
||||
@@ -327,20 +339,21 @@ class JakAndDaxterContext(CommonContext):
|
||||
def find_root_directory(ctx: JakAndDaxterContext):
|
||||
|
||||
# The path to this file is platform-dependent.
|
||||
if sys.platform == "win32":
|
||||
if Utils.is_windows:
|
||||
appdata = os.getenv("APPDATA")
|
||||
settings_path = os.path.normpath(f"{appdata}/OpenGOAL-Launcher/settings.json")
|
||||
elif sys.platform == "linux":
|
||||
elif Utils.is_linux:
|
||||
home = os.path.expanduser("~")
|
||||
settings_path = os.path.normpath(f"{home}/.config/OpenGOAL-Launcher/settings.json")
|
||||
elif sys.platform == "darwin":
|
||||
home = os.path.expanduser("~") # MacOS
|
||||
elif Utils.is_macos:
|
||||
home = os.path.expanduser("~")
|
||||
settings_path = os.path.normpath(f"{home}/Library/Application Support/OpenGOAL-Launcher/settings.json")
|
||||
else:
|
||||
ctx.on_log_error(logger, f"Unknown operating system: {sys.platform}!")
|
||||
return
|
||||
|
||||
# Boilerplate message that all error messages in this function should add at the end.
|
||||
# Boilerplate messages that all error messages in this function should have.
|
||||
err_title = "Unable to locate the ArchipelaGOAL install directory"
|
||||
alt_instructions = (f"Please verify that OpenGOAL and ArchipelaGOAL are installed properly. "
|
||||
f"If the problem persists, follow these steps:\n"
|
||||
f" Run the OpenGOAL Launcher, click Jak and Daxter > Features > Mods > ArchipelaGOAL.\n"
|
||||
@@ -354,7 +367,7 @@ def find_root_directory(ctx: JakAndDaxterContext):
|
||||
f" Close all launchers, games, clients, and console windows, then restart Archipelago.")
|
||||
|
||||
if not os.path.exists(settings_path):
|
||||
msg = (f"Unable to locate the ArchipelaGOAL install directory: the OpenGOAL settings file does not exist.\n"
|
||||
msg = (f"{err_title}: the OpenGOAL settings file does not exist.\n"
|
||||
f"{alt_instructions}")
|
||||
ctx.on_log_error(logger, msg)
|
||||
return
|
||||
@@ -364,16 +377,14 @@ def find_root_directory(ctx: JakAndDaxterContext):
|
||||
|
||||
jak1_installed = load["games"]["Jak 1"]["isInstalled"]
|
||||
if not jak1_installed:
|
||||
msg = (f"Unable to locate the ArchipelaGOAL install directory: "
|
||||
f"The OpenGOAL Launcher is missing a normal install of Jak 1!\n"
|
||||
msg = (f"{err_title}: The OpenGOAL Launcher is missing a normal install of Jak 1!\n"
|
||||
f"{alt_instructions}")
|
||||
ctx.on_log_error(logger, msg)
|
||||
return
|
||||
|
||||
mod_sources = load["games"]["Jak 1"]["modsInstalledVersion"]
|
||||
if mod_sources is None:
|
||||
msg = (f"Unable to locate the ArchipelaGOAL install directory: "
|
||||
f"No mod sources have been configured in the OpenGOAL Launcher!\n"
|
||||
msg = (f"{err_title}: No mod sources have been configured in the OpenGOAL Launcher!\n"
|
||||
f"{alt_instructions}")
|
||||
ctx.on_log_error(logger, msg)
|
||||
return
|
||||
@@ -387,8 +398,7 @@ def find_root_directory(ctx: JakAndDaxterContext):
|
||||
archipelagoal_source = src
|
||||
# Using this file, we could verify the right version is installed, but we don't need to.
|
||||
if archipelagoal_source is None:
|
||||
msg = (f"Unable to locate the ArchipelaGOAL install directory: "
|
||||
f"The ArchipelaGOAL mod is not installed in the OpenGOAL Launcher!\n"
|
||||
msg = (f"{err_title}: The ArchipelaGOAL mod is not installed in the OpenGOAL Launcher!\n"
|
||||
f"{alt_instructions}")
|
||||
ctx.on_log_error(logger, msg)
|
||||
return
|
||||
@@ -423,11 +433,12 @@ async def run_game(ctx: JakAndDaxterContext):
|
||||
ctx.on_log_warn(logger, "Compiler not running, attempting to start.")
|
||||
|
||||
try:
|
||||
auto_detect_root_directory = Utils.get_settings()["jakanddaxter_options"]["auto_detect_root_directory"]
|
||||
settings = Utils.get_settings()
|
||||
auto_detect_root_directory = settings["jakanddaxter_options"]["auto_detect_root_directory"]
|
||||
if auto_detect_root_directory:
|
||||
root_path = find_root_directory(ctx)
|
||||
else:
|
||||
root_path = Utils.get_settings()["jakanddaxter_options"]["root_directory"]
|
||||
root_path = settings["jakanddaxter_options"]["root_directory"]
|
||||
|
||||
# Always trust your instincts... the user may not have entered their root_directory properly.
|
||||
# We don't have to do this check if the root directory was auto-detected.
|
||||
@@ -584,6 +595,7 @@ async def main():
|
||||
|
||||
|
||||
def launch():
|
||||
colorama.init()
|
||||
# use colorama to display colored text highlighting
|
||||
colorama.just_fix_windows_console()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
from Options import PerGameCommonOptions, StartInventoryPool, Toggle, Choice, Range, DefaultOnToggle, OptionSet
|
||||
from functools import cached_property
|
||||
from Options import PerGameCommonOptions, StartInventoryPool, Toggle, Choice, Range, DefaultOnToggle, OptionDict
|
||||
from .Items import trap_item_table
|
||||
|
||||
|
||||
@@ -194,13 +195,21 @@ class TrapEffectDuration(Range):
|
||||
default = 30
|
||||
|
||||
|
||||
class ChosenTraps(OptionSet):
|
||||
# TODO - Revisit once ArchipelagoMW/Archipelago#3756 is merged.
|
||||
class TrapWeights(OptionDict):
|
||||
"""
|
||||
The list of traps that will be randomly added to the item pool. If the list is empty, no traps are created.
|
||||
The list of traps and corresponding weights that will be randomly added to the item pool. A trap with weight 10 is
|
||||
twice as likely to appear as a trap with weight 5. Set a weight to 0 to prevent that trap from appearing altogether.
|
||||
If all weights are 0, no traps are created, overriding the values of "Filler * Replaced With Traps."
|
||||
"""
|
||||
display_name = "Chosen Traps"
|
||||
default = {trap for trap in trap_item_table.values()}
|
||||
valid_keys = {trap for trap in trap_item_table.values()}
|
||||
display_name = "Trap Weights"
|
||||
default = {trap: 1 for trap in trap_item_table.values()}
|
||||
valid_keys = sorted({trap for trap in trap_item_table.values()})
|
||||
|
||||
@cached_property
|
||||
def weights_pair(self) -> tuple[list[str], list[int]]:
|
||||
return (list(self.value.keys()),
|
||||
list(max(0, v) for v in self.value.values()))
|
||||
|
||||
|
||||
class CompletionCondition(Choice):
|
||||
@@ -232,6 +241,6 @@ class JakAndDaxterOptions(PerGameCommonOptions):
|
||||
filler_power_cells_replaced_with_traps: FillerPowerCellsReplacedWithTraps
|
||||
filler_orb_bundles_replaced_with_traps: FillerOrbBundlesReplacedWithTraps
|
||||
trap_effect_duration: TrapEffectDuration
|
||||
chosen_traps: ChosenTraps
|
||||
trap_weights: TrapWeights
|
||||
jak_completion_condition: CompletionCondition
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
|
||||
@@ -177,6 +177,7 @@ def enforce_multiplayer_limits(world: JakAndDaxterWorld):
|
||||
raise OptionError(f"{world.player_name}: The options you have chosen may disrupt the multiworld. \n"
|
||||
f"Please adjust the following Options for a multiplayer game. \n"
|
||||
f"{friendly_message}"
|
||||
f"Or use 'random-range-x-y' instead of 'random' in your player yaml.\n"
|
||||
f"Or set 'enforce_friendly_options' in the seed generator's host.yaml to false. "
|
||||
f"(Use at your own risk!)")
|
||||
|
||||
@@ -207,6 +208,7 @@ def enforce_singleplayer_limits(world: JakAndDaxterWorld):
|
||||
raise OptionError(f"The options you have chosen may result in seed generation failures. \n"
|
||||
f"Please adjust the following Options for a singleplayer game. \n"
|
||||
f"{friendly_message}"
|
||||
f"Or use 'random-range-x-y' instead of 'random' in your player yaml.\n"
|
||||
f"Or set 'enforce_friendly_options' in your host.yaml to false. "
|
||||
f"(Use at your own risk!)")
|
||||
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import typing
|
||||
from typing import Any, ClassVar, Callable
|
||||
# Python standard libraries
|
||||
from math import ceil
|
||||
import Utils
|
||||
import settings
|
||||
from Options import OptionGroup
|
||||
from typing import Any, ClassVar, Callable, Union, cast
|
||||
|
||||
# Archipelago imports
|
||||
import settings
|
||||
import Utils
|
||||
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import components, Component, launch_subprocess, Type, icon_paths
|
||||
from BaseClasses import (Item,
|
||||
ItemClassification as ItemClass,
|
||||
Tutorial,
|
||||
CollectionState)
|
||||
from Options import OptionGroup
|
||||
|
||||
# Jak imports
|
||||
from .Options import *
|
||||
from .GameID import jak1_id, jak1_name, jak1_max
|
||||
from . import Options
|
||||
from .Locations import (JakAndDaxterLocation,
|
||||
location_table,
|
||||
cell_location_table,
|
||||
scout_location_table,
|
||||
special_location_table,
|
||||
cache_location_table,
|
||||
orb_location_table)
|
||||
from .Items import (JakAndDaxterItem,
|
||||
item_table,
|
||||
cell_item_table,
|
||||
@@ -27,14 +26,19 @@ from .Items import (JakAndDaxterItem,
|
||||
orb_item_table,
|
||||
trap_item_table)
|
||||
from .Levels import level_table, level_table_with_global
|
||||
from .regs.RegionBase import JakAndDaxterRegion
|
||||
from .Locations import (JakAndDaxterLocation,
|
||||
location_table,
|
||||
cell_location_table,
|
||||
scout_location_table,
|
||||
special_location_table,
|
||||
cache_location_table,
|
||||
orb_location_table)
|
||||
from .locs import (CellLocations as Cells,
|
||||
ScoutLocations as Scouts,
|
||||
SpecialLocations as Specials,
|
||||
OrbCacheLocations as Caches,
|
||||
OrbLocations as Orbs)
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import components, Component, launch_subprocess, Type, icon_paths
|
||||
from .regs.RegionBase import JakAndDaxterRegion
|
||||
|
||||
|
||||
def launch_client():
|
||||
@@ -70,8 +74,8 @@ class JakAndDaxterSettings(settings.Group):
|
||||
root_directory: RootDirectory = RootDirectory(
|
||||
"%programfiles%/OpenGOAL-Launcher/features/jak1/mods/JakMods/archipelagoal")
|
||||
# Don't ever change these type hints again.
|
||||
auto_detect_root_directory: typing.Union[AutoDetectRootDirectory, bool] = True
|
||||
enforce_friendly_options: typing.Union[EnforceFriendlyOptions, bool] = True
|
||||
auto_detect_root_directory: Union[AutoDetectRootDirectory, bool] = True
|
||||
enforce_friendly_options: Union[EnforceFriendlyOptions, bool] = True
|
||||
|
||||
|
||||
class JakAndDaxterWebWorld(WebWorld):
|
||||
@@ -107,7 +111,7 @@ class JakAndDaxterWebWorld(WebWorld):
|
||||
Options.FillerPowerCellsReplacedWithTraps,
|
||||
Options.FillerOrbBundlesReplacedWithTraps,
|
||||
Options.TrapEffectDuration,
|
||||
Options.ChosenTraps,
|
||||
Options.TrapWeights,
|
||||
]),
|
||||
]
|
||||
|
||||
@@ -220,7 +224,7 @@ class JakAndDaxterWorld(World):
|
||||
total_trap_cells: int = 0
|
||||
total_filler_cells: int = 0
|
||||
power_cell_thresholds: list[int] = []
|
||||
chosen_traps: list[str] = []
|
||||
trap_weights: tuple[list[str], list[int]] = ({}, {})
|
||||
|
||||
# Handles various options validation, rules enforcement, and caching of important information.
|
||||
def generate_early(self) -> None:
|
||||
@@ -295,7 +299,7 @@ class JakAndDaxterWorld(World):
|
||||
else:
|
||||
self.options.filler_orb_bundles_replaced_with_traps.value = 0
|
||||
|
||||
self.chosen_traps = list(self.options.chosen_traps.value)
|
||||
self.trap_weights = self.options.trap_weights.weights_pair
|
||||
|
||||
# Options drive which trade rules to use, so they need to be setup before we create_regions.
|
||||
from .Rules import set_orb_trade_rule
|
||||
@@ -388,20 +392,20 @@ class JakAndDaxterWorld(World):
|
||||
items_made += count
|
||||
|
||||
# Handle Traps (for real).
|
||||
# Manually fill the item pool with a random assortment of trap items, equal to the sum of
|
||||
# total_trap_cells + total_trap_orb_bundles. Only do this if one or more traps have been selected.
|
||||
if len(self.chosen_traps) > 0:
|
||||
# Manually fill the item pool with a weighted assortment of trap items, equal to the sum of
|
||||
# total_trap_cells + total_trap_orb_bundles. Only do this if one or more traps have weights > 0.
|
||||
names, weights = self.trap_weights
|
||||
if sum(weights) > 0:
|
||||
total_traps = self.total_trap_cells + self.total_trap_orb_bundles
|
||||
for _ in range(total_traps):
|
||||
trap_name = self.random.choice(self.chosen_traps)
|
||||
self.multiworld.itempool.append(self.create_item(trap_name))
|
||||
trap_list = self.random.choices(names, weights=weights, k=total_traps)
|
||||
self.multiworld.itempool += [self.create_item(trap_name) for trap_name in trap_list]
|
||||
items_made += total_traps
|
||||
|
||||
# Handle Unfilled Locations.
|
||||
# Add an amount of filler items equal to the number of locations yet to be filled.
|
||||
# This is the final set of items we will add to the pool.
|
||||
all_regions = self.multiworld.get_regions(self.player)
|
||||
total_locations = sum(reg.location_count for reg in typing.cast(list[JakAndDaxterRegion], all_regions))
|
||||
total_locations = sum(reg.location_count for reg in cast(list[JakAndDaxterRegion], all_regions))
|
||||
total_filler = total_locations - items_made
|
||||
self.multiworld.itempool += [self.create_filler() for _ in range(total_filler)]
|
||||
|
||||
@@ -482,7 +486,7 @@ class JakAndDaxterWorld(World):
|
||||
"filler_power_cells_replaced_with_traps",
|
||||
"filler_orb_bundles_replaced_with_traps",
|
||||
"trap_effect_duration",
|
||||
"chosen_traps",
|
||||
"trap_weights",
|
||||
"jak_completion_condition",
|
||||
"require_punch_for_klaww",
|
||||
)
|
||||
|
||||
@@ -161,7 +161,7 @@ def autopsy(cause: int) -> str:
|
||||
|
||||
class JakAndDaxterMemoryReader:
|
||||
marker: ByteString
|
||||
goal_address = None
|
||||
goal_address: int | None = None
|
||||
connected: bool = False
|
||||
initiated_connect: bool = False
|
||||
|
||||
@@ -223,7 +223,6 @@ class JakAndDaxterMemoryReader:
|
||||
async def main_tick(self):
|
||||
if self.initiated_connect:
|
||||
await self.connect()
|
||||
await self.verify_memory_version()
|
||||
self.initiated_connect = False
|
||||
|
||||
if self.connected:
|
||||
@@ -243,7 +242,6 @@ class JakAndDaxterMemoryReader:
|
||||
else:
|
||||
return
|
||||
|
||||
# TODO - How drastic of a change is this, to wrap all of main_tick in a self.connected check?
|
||||
if self.connected:
|
||||
|
||||
# Save some state variables temporarily.
|
||||
@@ -293,32 +291,47 @@ class JakAndDaxterMemoryReader:
|
||||
byteorder="little",
|
||||
signed=False)
|
||||
logger.debug("Found the archipelago memory address: " + str(self.goal_address))
|
||||
self.connected = True
|
||||
await self.verify_memory_version()
|
||||
else:
|
||||
self.log_error(logger, "Could not find the archipelago memory address!")
|
||||
self.log_error(logger, "Could not find the Archipelago marker address!")
|
||||
self.connected = False
|
||||
|
||||
async def verify_memory_version(self):
|
||||
if not self.connected:
|
||||
self.log_error(logger, "The Memory Reader is not connected!")
|
||||
if self.goal_address is None:
|
||||
self.log_error(logger, "Could not find the Archipelago memory address!")
|
||||
self.connected = False
|
||||
return
|
||||
|
||||
memory_version: int | None = None
|
||||
try:
|
||||
memory_version = self.read_goal_address(memory_version_offset, sizeof_uint32)
|
||||
if memory_version == expected_memory_version:
|
||||
self.log_success(logger, "The Memory Reader is ready!")
|
||||
self.connected = True
|
||||
else:
|
||||
raise MemoryReadError(memory_version_offset, sizeof_uint32)
|
||||
except (ProcessError, MemoryReadError, WinAPIError):
|
||||
msg = (f"The OpenGOAL memory structure is incompatible with the current Archipelago client!\n"
|
||||
f" Expected Version: {str(expected_memory_version)}\n"
|
||||
f" Found Version: {str(memory_version)}\n"
|
||||
f"Please follow these steps:\n"
|
||||
f" Run the OpenGOAL Launcher, click Jak and Daxter > Features > Mods > ArchipelaGOAL.\n"
|
||||
f" Click Update (if one is available).\n"
|
||||
f" Click Advanced > Compile. When this is done, click Continue.\n"
|
||||
f" Click Versions and verify the latest version is marked 'Active'.\n"
|
||||
f" Close all launchers, games, clients, and console windows, then restart Archipelago.")
|
||||
if memory_version is None:
|
||||
msg = (f"Could not find a version number in the OpenGOAL memory structure!\n"
|
||||
f" Expected Version: {str(expected_memory_version)}\n"
|
||||
f" Found Version: {str(memory_version)}\n"
|
||||
f"Please follow these steps:\n"
|
||||
f" If the game is running, try entering '/memr connect' in the client.\n"
|
||||
f" You should see 'The Memory Reader is ready!'\n"
|
||||
f" If that did not work, or the game is not running, run the OpenGOAL Launcher.\n"
|
||||
f" Click Jak and Daxter > Features > Mods > ArchipelaGOAL.\n"
|
||||
f" Then click Advanced > Play in Debug Mode.\n"
|
||||
f" Try entering '/memr connect' in the client again.")
|
||||
else:
|
||||
msg = (f"The OpenGOAL memory structure is incompatible with the current Archipelago client!\n"
|
||||
f" Expected Version: {str(expected_memory_version)}\n"
|
||||
f" Found Version: {str(memory_version)}\n"
|
||||
f"Please follow these steps:\n"
|
||||
f" Run the OpenGOAL Launcher, click Jak and Daxter > Features > Mods > ArchipelaGOAL.\n"
|
||||
f" Click Update (if one is available).\n"
|
||||
f" Click Advanced > Compile. When this is done, click Continue.\n"
|
||||
f" Click Versions and verify the latest version is marked 'Active'.\n"
|
||||
f" Close all launchers, games, clients, and console windows, then restart Archipelago.")
|
||||
self.log_error(logger, msg)
|
||||
self.connected = False
|
||||
|
||||
|
||||
@@ -58,7 +58,11 @@ class JakAndDaxterReplClient:
|
||||
initiated_connect: bool = False # Signals when user tells us to try reconnecting.
|
||||
received_deathlink: bool = False
|
||||
balanced_orbs: bool = False
|
||||
|
||||
# Variables to handle the title screen and initial game connection.
|
||||
initial_item_count = -1 # Brand new games have 0 items, so initialize this to -1.
|
||||
received_initial_items = False
|
||||
processed_initial_items = False
|
||||
|
||||
# The REPL client needs the REPL/compiler process running, but that process
|
||||
# also needs the game running. Therefore, the REPL client needs both running.
|
||||
@@ -67,7 +71,6 @@ class JakAndDaxterReplClient:
|
||||
|
||||
item_inbox: dict[int, NetworkItem] = {}
|
||||
inbox_index = 0
|
||||
initial_item_count = -1 # New games have 0 items, so initialize this to -1.
|
||||
json_message_queue: Queue[JsonMessageData] = queue.Queue()
|
||||
|
||||
# Logging callbacks
|
||||
@@ -127,19 +130,21 @@ class JakAndDaxterReplClient:
|
||||
else:
|
||||
return
|
||||
|
||||
# When connecting the game to the AP server on the title screen, we may be processing items from starting
|
||||
# inventory or items received in an async game. Once we have caught up to the initial count, tell the player
|
||||
# that we are ready to start. New items may even come in during the title screen, so if we go over the count,
|
||||
# we should still send the ready signal.
|
||||
if not self.processed_initial_items:
|
||||
if self.inbox_index >= self.initial_item_count >= 0:
|
||||
self.processed_initial_items = True
|
||||
await self.send_connection_status("ready")
|
||||
|
||||
# Receive Items from AP. Handle 1 item per tick.
|
||||
if len(self.item_inbox) > self.inbox_index:
|
||||
await self.receive_item()
|
||||
await self.save_data()
|
||||
self.inbox_index += 1
|
||||
|
||||
# When connecting the game to the AP server on the title screen, we may be processing items from starting
|
||||
# inventory or items received in an async game. Once we are done, tell the player that we are ready to start.
|
||||
if not self.received_initial_items and self.initial_item_count >= 0:
|
||||
if self.inbox_index == self.initial_item_count:
|
||||
self.received_initial_items = True
|
||||
await self.send_connection_status("ready")
|
||||
|
||||
if self.received_deathlink:
|
||||
await self.receive_deathlink()
|
||||
self.received_deathlink = False
|
||||
|
||||
@@ -6,7 +6,7 @@ class NoTrapsTest(JakAndDaxterTestBase):
|
||||
options = {
|
||||
"filler_power_cells_replaced_with_traps": 0,
|
||||
"filler_orb_bundles_replaced_with_traps": 0,
|
||||
"chosen_traps": ["Trip Trap"]
|
||||
"trap_weights": {"Trip Trap": 1},
|
||||
}
|
||||
|
||||
def test_trap_count(self):
|
||||
@@ -32,7 +32,7 @@ class SomeTrapsTest(JakAndDaxterTestBase):
|
||||
options = {
|
||||
"filler_power_cells_replaced_with_traps": 10,
|
||||
"filler_orb_bundles_replaced_with_traps": 10,
|
||||
"chosen_traps": ["Trip Trap"]
|
||||
"trap_weights": {"Trip Trap": 1},
|
||||
}
|
||||
|
||||
def test_trap_count(self):
|
||||
@@ -58,7 +58,7 @@ class MaximumTrapsTest(JakAndDaxterTestBase):
|
||||
options = {
|
||||
"filler_power_cells_replaced_with_traps": 100,
|
||||
"filler_orb_bundles_replaced_with_traps": 100,
|
||||
"chosen_traps": ["Trip Trap"]
|
||||
"trap_weights": {"Trip Trap": 1},
|
||||
}
|
||||
|
||||
def test_trap_count(self):
|
||||
|
||||
Reference in New Issue
Block a user