diff --git a/JakAndDaxterClient.py b/JakAndDaxterClient.py deleted file mode 100644 index 040f8ff389..0000000000 --- a/JakAndDaxterClient.py +++ /dev/null @@ -1,9 +0,0 @@ -import Utils -from worlds.jakanddaxter.Client import launch - -import ModuleUpdate -ModuleUpdate.update() - -if __name__ == '__main__': - Utils.init_logging("JakAndDaxterClient", exception_logger="Client") - launch() diff --git a/worlds/jakanddaxter/Client.py b/worlds/jakanddaxter/Client.py index c5c36b610e..2b2a83795f 100644 --- a/worlds/jakanddaxter/Client.py +++ b/worlds/jakanddaxter/Client.py @@ -85,6 +85,8 @@ class JakAndDaxterContext(CommonContext): def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None: self.repl = JakAndDaxterReplClient() self.memr = JakAndDaxterMemoryReader() + # self.memr.load_data() + # self.repl.load_data() super().__init__(server_address, password) def run_gui(self): @@ -110,6 +112,8 @@ class JakAndDaxterContext(CommonContext): for index, item in enumerate(args["items"], start=args["index"]): logger.info(args) self.repl.item_inbox[index] = item + self.memr.save_data() + self.repl.save_data() async def ap_inform_location_check(self, location_ids: typing.List[int]): message = [{"cmd": "LocationChecks", "locations": location_ids}] @@ -140,8 +144,7 @@ class JakAndDaxterContext(CommonContext): async def run_game(ctx: JakAndDaxterContext): - # If you're running the game through the mod launcher, these may already be running. - # If they are not running, try to start them. + # These may already be running. If they are not running, try to start them. gk_running = False try: pymem.Pymem("gk.exe") # The GOAL Kernel @@ -157,7 +160,7 @@ async def run_game(ctx: JakAndDaxterContext): logger.info("Compiler not running, attempting to start.") # Don't mind all the arguments, they are exactly what you get when you run "task boot-game" or "task repl". - # TODO - Support other OS's. cmd for some reason does not work with goalc. + # TODO - Support other OS's. cmd for some reason does not work with goalc. Pymem is Windows-only. if not gk_running: try: gk_path = Utils.get_settings()["jakanddaxter_options"]["root_directory"] diff --git a/worlds/jakanddaxter/__init__.py b/worlds/jakanddaxter/__init__.py index 82e588214e..5cbf3ca4e4 100644 --- a/worlds/jakanddaxter/__init__.py +++ b/worlds/jakanddaxter/__init__.py @@ -1,6 +1,7 @@ import typing import settings +from Utils import local_path from BaseClasses import Item, ItemClassification, Tutorial from .GameID import jak1_id, jak1_name from .JakAndDaxterOptions import JakAndDaxterOptions @@ -11,7 +12,21 @@ from .locs import CellLocations as Cells, ScoutLocations as Scouts, OrbLocations from .Regions import create_regions from .Rules import set_rules from worlds.AutoWorld import World, WebWorld -from worlds.LauncherComponents import components, Component, launch_subprocess, Type +from worlds.LauncherComponents import components, Component, launch_subprocess, Type, icon_paths + + +def launch_client(): + from .Client import launch + launch_subprocess(launch, name="JakAndDaxterClient") + + +components.append(Component("Jak and Daxter Client", + "JakAndDaxterClient", + func=launch_client, + component_type=Type.CLIENT, + icon="egg")) + +icon_paths["egg"] = local_path("worlds", "jakanddaxter", "icons", "egg.png") class JakAndDaxterSettings(settings.Group): @@ -126,9 +141,3 @@ class JakAndDaxterWorld(World): def launch_client(): from .Client import launch launch_subprocess(launch, name="JakAndDaxterClient") - - -components.append(Component("Jak and Daxter Client", - "JakAndDaxterClient", - func=launch_client, - component_type=Type.CLIENT)) diff --git a/worlds/jakanddaxter/client/MemoryReader.py b/worlds/jakanddaxter/client/MemoryReader.py index 64a37352bc..30ed927bb1 100644 --- a/worlds/jakanddaxter/client/MemoryReader.py +++ b/worlds/jakanddaxter/client/MemoryReader.py @@ -2,6 +2,7 @@ import typing import pymem from pymem import pattern from pymem.exception import ProcessNotFound, ProcessError, MemoryReadError, WinAPIError +import json from CommonClient import logger from worlds.jakanddaxter.locs import CellLocations as Cells, ScoutLocations as Flies, SpecialLocations as Specials @@ -58,7 +59,7 @@ class JakAndDaxterMemoryReader: # Read the memory address to check the state of the game. self.read_memory() - location_callback(self.location_outbox) # TODO - I forgot why call this here when it's already down below... + # location_callback(self.location_outbox) # TODO - I forgot why call this here when it's already down below... # Checked Locations in game. Handle the entire outbox every tick until we're up to speed. if len(self.location_outbox) > self.outbox_index: @@ -169,3 +170,20 @@ class JakAndDaxterMemoryReader: self.connected = False return self.location_outbox + + def save_data(self): + with open("jakanddaxter_location_outbox.json", "w+") as f: + dump = { + "outbox_index": self.outbox_index, + "location_outbox": self.location_outbox + } + json.dump(dump, f, indent=4) + + def load_data(self): + try: + with open("jakanddaxter_location_outbox.json", "r") as f: + load = json.load(f) + self.outbox_index = load["outbox_index"] + self.location_outbox = load["location_outbox"] + except FileNotFoundError: + pass diff --git a/worlds/jakanddaxter/client/ReplClient.py b/worlds/jakanddaxter/client/ReplClient.py index e5c8030c3e..8b6c935d75 100644 --- a/worlds/jakanddaxter/client/ReplClient.py +++ b/worlds/jakanddaxter/client/ReplClient.py @@ -1,11 +1,14 @@ +import json import time import struct +import typing from socket import socket, AF_INET, SOCK_STREAM import pymem from pymem.exception import ProcessNotFound, ProcessError from CommonClient import logger +from NetUtils import NetworkItem from worlds.jakanddaxter.GameID import jak1_id from worlds.jakanddaxter.Items import item_table from worlds.jakanddaxter.locs import ( @@ -27,7 +30,7 @@ class JakAndDaxterReplClient: gk_process: pymem.process = None goalc_process: pymem.process = None - item_inbox = {} + item_inbox: typing.Dict[int, NetworkItem] = {} inbox_index = 0 def __init__(self, ip: str = "127.0.0.1", port: int = 8181): @@ -139,11 +142,15 @@ class JakAndDaxterReplClient: # Disable cheat-mode and debug (close the visual cue). # self.send_form("(set! *debug-segment* #f)") - if self.send_form("(set! *cheat-mode* #f)"): + if self.send_form("(set! *cheat-mode* #f)", print_ok=False): + ok_count += 1 + + # Run the retail game start sequence (while still in debug). + if self.send_form("(start \'play (get-continue-by-name *game-info* \"title-start\"))"): ok_count += 1 # Now wait until we see the success message... 6 times. - if ok_count == 6: + if ok_count == 7: self.connected = True else: self.connected = False @@ -219,3 +226,32 @@ class JakAndDaxterReplClient: else: logger.error(f"Unable to receive special unlock {item_table[ap_id]}!") return ok + + def save_data(self): + with open("jakanddaxter_item_inbox.json", "w+") as f: + dump = { + "inbox_index": self.inbox_index, + "item_inbox": [{ + "item": self.item_inbox[k].item, + "location": self.item_inbox[k].location, + "player": self.item_inbox[k].player, + "flags": self.item_inbox[k].flags + } for k in self.item_inbox + ] + } + json.dump(dump, f, indent=4) + + def load_data(self): + try: + with open("jakanddaxter_item_inbox.json", "r") as f: + load = json.load(f) + self.inbox_index = load["inbox_index"] + self.item_inbox = {k: NetworkItem( + item=load["item_inbox"][k]["item"], + location=load["item_inbox"][k]["location"], + player=load["item_inbox"][k]["player"], + flags=load["item_inbox"][k]["flags"] + ) for k in range(0, len(load["item_inbox"])) + } + except FileNotFoundError: + pass diff --git a/worlds/jakanddaxter/docs/setup_en.md b/worlds/jakanddaxter/docs/setup_en.md index ce2b193677..db1385a4f8 100644 --- a/worlds/jakanddaxter/docs/setup_en.md +++ b/worlds/jakanddaxter/docs/setup_en.md @@ -3,83 +3,105 @@ ## Required Software - A legally purchased copy of *Jak And Daxter: The Precursor Legacy.* -- Python version 3.10 or higher. Make sure this is added to your PATH environment variable. -- [Task](https://taskfile.dev/installation/) (This makes it easier to run commands.) +- [The OpenGOAL Mod Launcher](https://jakmods.dev/) +- [The Jak and Daxter .APWORLD package](https://github.com/ArchipelaGOAL/Archipelago/releases) + +At this time, this method of setup works on Windows only, but Linux support is a strong likelihood in the near future. +(OpenGOAL itself supports Linux, and the mod launcher is runnable with Python.) + +## Preparations + +- Dump your copy of the game as an ISO file to your PC. +- Install the Mod Launcher. +- If you are prompted by the Mod Launcher at any time during setup, provide the path to your ISO file. ## Installation -### Installation via OpenGOAL Mod Launcher +***OpenGOAL Mod Launcher*** -At this time, the only supported method of setup is through Manual Compilation. Aside from the legal copy of the game, all tools required to do this are free. +- Run the Mod Launcher and click `ArchipelaGOAL` in the mod list. +- Click `Install` and wait for it to complete. + - If you have yet to be prompted for the ISO, click `Re-Extract` and provide the path to your ISO file. +- Click `Recompile`. This may take between 30-60 seconds. It should run to 100% completion. If it does not, see the Troubleshooting section. +- Click `View Folder`. + - In the new file explorer window, take note of the current path. It should contain `gk.exe` and `goalc.exe`. +- Verify that the mod launcher copied the extracted ISO files to the mod directory: + - `C:\Users\\AppData\Roaming\OpenGOAL-Mods\archipelagoal\iso_data` should have *all* the same files as + - `C:\Users\\AppData\Roaming\OpenGOAL-Mods\_iso_data`, if it doesn't, copy those files over manually. + - And then `Recompile` if you needed to copy the files over. +- **DO NOT LAUNCH THE GAME FROM THE MOD LAUNCHER.** It will run in retail mode, which is incompatible with Archipelago. We need it to run in debug mode (see below). -***Windows Preparations*** +***Archipelago Launcher*** -***Linux Preparations*** - -***Using the Launcher*** - -### Manual Compilation (Linux/Windows) - -***Windows Preparations*** - -- Dump your copy of the game as an ISO file to your PC. -- Download a zipped up copy of the Archipelago Server and Client [here.](https://github.com/ArchipelaGOAL/Archipelago) -- Download a zipped up copy of the modded OpenGOAL game [here.](https://github.com/ArchipelaGOAL/ArchipelaGOAL) -- Unzip the two projects into easily accessible directories. - - -***Linux Preparations*** - -***Compiling*** +- Copy the `jakanddaxter.apworld` file into your `Archipelago/lib/worlds` directory. + - Reminder: the default installation location for Archipelago is `C:\ProgramData\Archipelago`. +- Run the Archipelago Launcher. +- From the left-most list, click `Generate Template Options`. +- Select `Jak and Daxter The Precursor Legacy.yaml`. In the text file that opens, enter the name you want and remember it for later. +- Save this file in `Archipelago/players`. You can now close the file. +- Back in the Launcher, from the left-most list, click `Generate`. A window will appear to generate your seed and close itself. +- If you plan to host the game yourself, from the left-most list, click `Host`. + - When asked to select your multiworld seed, navigate to `Archipelago/output` and select the zip file containing the seed you just generated. + - You can sort by Date Modified to make it easy to find. ## Starting a Game -- Open 3 Powershell windows. If you have VSCode, you can run 3 terminals to consolidate this process. - - In the first window, navigate to the Archipelago folder using `cd` and run `python ./Launcher.py --update_settings`. Then run it again without the `--update_settings` flag. - - In the second window, navigate to the ArchipelaGOAL folder and run `task extract`. This will prompt you to tell the mod where to find your ISO file to dump its contents. When that is done, run `task repl`. - - In the third window, navigate to the ArchipelaGOAL folder and run `task boot-game`. At this point, Jak should be standing outside Samos's hut. - - Once you confirm all those tasks succeeded, you can now close all these windows. -- Edit your host.yaml file and ensure these lines exist. And don't forget to specify your ACTUAL install path. If you're on Windows, no backslashes! +***New Game*** + +- Run the Archipelago Launcher. +- From the right-most list, find and click `Jak and Daxter Client`. +- 4 new windows should appear: + - A powershell window will open to run the OpenGOAL compiler. It should take about 30 seconds to compile the game. + - As before, it should run to 100% completion, and you should hear a musical cue to indicate it is done. If it does not run to 100%, or you do not hear the musical cue, see the Troubleshooting section. + - Another powershell window will open to run the game. + - The game window itself will launch, and Jak will be standing outside Samos's Hut. + - Finally, the Archipelago text client will open. + - You should see several messages appear after the compiler has run to 100% completion. If you see `The REPL is ready!` and `The Memory Reader is ready!` then that should indicate a successful startup. + - The game should then load in the title screen. +- You can *minimize* the 2 powershell windows, **BUT DO NOT CLOSE THEM.** They are required for Archipelago and the game to communicate with each other. +- Start a new game in the title screen, and play through the cutscenes. +- Once you reach Geyser Rock, you can connect to the Archipelago server. + - Provide your slot/player name and hit Enter, and then start the game! + - You can leave Geyser Rock immediately if you so choose - just step on the warp gate button. + +***Returning / Async Game*** + +- One important note is to connect to the Archipelago server **AFTER** you load your save file. This is to allow AP to give you all the items you had previously. +- Otherwise, the same steps as New Game apply. + +## Troubleshooting + +***Installation Failure*** + +- If you encounter errors during extraction or compilation of the game when using the Mod Launcher, you may see errors like this: ``` -jakanddaxter_options: - # Path to folder containing the ArchipelaGOAL mod. - root_directory: "D:/Files/Repositories/ArchipelaGOAL" -``` -- In the Launcher, click Generate to create a new random seed. Save the resulting zip file. -- In the Launcher, click Host to host the Archipelago server. It will prompt you for the location of that zip file. -- Once the server is running, in the Launcher, find the Jak and Daxter Client and click it. You should see the command window begin to compile the game. -- When it completes, you should hear the menu closing sound effect, and you should see the text client indicate that the two agents are ready to communicate with the game. -- Connect the client to the Archipelago server and enter your slot name. Once this is done, the game should be ready to play. Talk to Samos to trigger the cutscene where he sends you to Geyser Rock, and off you go! +-- Compilation Error! -- +Input file iso_data/jak1/MUS/TWEAKVAL.MUS does not exist. +``` + - If this occurs, you may need to copy the extracted data to the mod folder manually. + - From a location like this: `C:\Users\\AppData\Roaming\OpenGOAL-Mods\_iso_data` + - To a location like this: `C:\Users\\AppData\Roaming\OpenGOAL-Mods\archipelagoal\iso_data` + - Then try clicking `Recompile` in the Mod Launcher (ensure you have selected the right mod first!) -Once you complete the setup steps, you should only need to run the Launcher again to generate a game, host a server, or run the client and connect to a server. -- You never need to download the zip copies of the projects again (unless there are updates). -- You never need to dump your ISO again. -- You never need to extract the ISO assets again. +***Game Failure*** -### Joining a MultiWorld Game - -MultiWorld games are untested at this time. - -### Playing Offline - -Offline play is untested at this time. - -## Installation and Setup Troubleshooting - -### Compilation Failures - -### Runtime Failures - -- If the client window appears but no sound plays, you will need to enter the following commands into the client to connect it to the game. +- If at any point the text client says `The process has died`, you will need to restart the appropriate + application: + - Open a new powershell window. + - Navigating to the directory containing `gk.exe` and `goalc.exe` via `cd`. + - Run the command corresponding to the dead process: + - `.\gk.exe --game jak1 -- -v -boot -fakeiso -debug` + - `.\goalc.exe --game jak1` + - Then enter the following commands into the text client to reconnect everything to the game. - `/repl connect` - `/memr connect` -- Once these are done, you can enter `/repl status` and `/memr status` to check that everything is connected and ready. - -## Gameplay Troubleshooting + - Once these are done, you can enter `/repl status` and `/memr status` to verify. +- If the game freezes by replaying the same two frames over and over, but the music still runs in the background, you may have accidentally interacted with the powershell windows in the background - they halt the game if you:scroll up in them, highlight text in them, etc. + - To unfreeze the game, scroll to the very bottom of the log output and right click. That will release powershell from your control and allow the game to continue. + - It is recommended to keep these windows minimized and out of your way. ### Known Issues -- I've streamlined the process of connecting the client's agents to the game, but it comes at the cost of more granular commands useful for troubleshooting. - The game needs to run in debug mode in order to allow the repl to connect to it. At some point I want to make sure it can run in retail mode, or at least hide the debug text on screen and play the game's introductory cutscenes properly. - The client is currently not very robust and doesn't handle failures gracefully. This may result in items not being delivered to the game, or location checks not being delivered to the server. -- The game relates tasks and power cells closely but separately. Some issues may result from having to tell the game to check for the power cells you own, rather than the tasks you completed. \ No newline at end of file +- The game relates tasks and power cells closely but separately. Some issues may result from custom code to add distinct items to the game (like the Fisherman's Boat, the Pontoons, or the Gondola). diff --git a/worlds/jakanddaxter/icons/egg.ico b/worlds/jakanddaxter/icons/egg.ico new file mode 100644 index 0000000000..5579bad24f Binary files /dev/null and b/worlds/jakanddaxter/icons/egg.ico differ diff --git a/worlds/jakanddaxter/icons/egg.png b/worlds/jakanddaxter/icons/egg.png new file mode 100644 index 0000000000..6e9961b4fd Binary files /dev/null and b/worlds/jakanddaxter/icons/egg.png differ