From 97b1ae5ee9aabd6cc5b0efc3d75504186198e34d Mon Sep 17 00:00:00 2001 From: Yussur Mustafa Oraji Date: Sat, 12 Mar 2022 22:05:54 +0100 Subject: [PATCH] v6,sm64ex: Add support for offline singeplayer seeds (#301) --- WebHostLib/downloads.py | 4 ++++ .../static/assets/tutorial/sm64ex/setup_en.md | 8 ++++++- .../static/assets/tutorial/v6/setup_en.md | 8 ++++++- WebHostLib/templates/macros.html | 6 +++++ WebHostLib/upload.py | 9 ++++++++ worlds/sm64ex/__init__.py | 23 +++++++++++++++++++ worlds/v6/__init__.py | 23 ++++++++++++++++++- 7 files changed, 78 insertions(+), 3 deletions(-) diff --git a/WebHostLib/downloads.py b/WebHostLib/downloads.py index ce623c1e..c81c32e3 100644 --- a/WebHostLib/downloads.py +++ b/WebHostLib/downloads.py @@ -53,6 +53,10 @@ def download_slot_file(room_id, player_id: int): fname = name.rsplit("/", 1)[0]+".zip" elif slot_data.game == "Ocarina of Time": fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_P{slot_data.player_id}_{slot_data.player_name}.apz5" + elif slot_data.game == "VVVVVV": + fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_SP.apv6" + elif slot_data.game == "Super Mario 64": + fname = f"AP_{app.jinja_env.filters['suuid'](room_id)}_SP.apsm64ex" else: return "Game download not supported." return send_file(io.BytesIO(slot_data.data), as_attachment=True, attachment_filename=fname) diff --git a/WebHostLib/static/assets/tutorial/sm64ex/setup_en.md b/WebHostLib/static/assets/tutorial/sm64ex/setup_en.md index c4949142..c000e943 100644 --- a/WebHostLib/static/assets/tutorial/sm64ex/setup_en.md +++ b/WebHostLib/static/assets/tutorial/sm64ex/setup_en.md @@ -54,13 +54,19 @@ In case you are using the Archipelago Website, the IP should be `archipelago.gg` If everything worked out, you will see a textbox informing you the connection has been established after the story intro. +# Playing offline + +To play offline, first generate a seed on the game's settings page. +Create a room and download the `.apsm64ex` file, and start the game with the `--sm64ap_file FileName` launch argument. + ## Installation Troubleshooting Start the game from the command line to view helpful messages regarding SM64EX. ### Game doesn't start after compiling -Most likely you forgot to set the launch options. `--sm64ap_name YourName` and `--sm64ap_ip ServerIP:Port` are required for startup. +Most likely you forgot to set the launch options. `--sm64ap_name YourName` and `--sm64ap_ip ServerIP:Port` are required for startup for Multiworlds, and +`--sm64ap_file FileName` is required for (offline) singleplayer. If your Name or Password have spaces in them, surround them in quotes. ## Game Troubleshooting diff --git a/WebHostLib/static/assets/tutorial/v6/setup_en.md b/WebHostLib/static/assets/tutorial/v6/setup_en.md index d39e75ea..b6df5753 100644 --- a/WebHostLib/static/assets/tutorial/v6/setup_en.md +++ b/WebHostLib/static/assets/tutorial/v6/setup_en.md @@ -20,13 +20,19 @@ In case you are using the Archipelago Website, the IP should be `archipelago.gg` If everything worked out, you will see a textbox informing you the connection has been established after the story intro. +# Playing offline + +To play offline, first generate a seed on the game's settings page. +Create a room and download the `.apv6` file, and start the game with the `-v6ap_file FileName` launch argument. + ## Installation Troubleshooting Start the game from the command line to view helpful messages regarding V6AP. These will look something like "V6AP: Message" ### Game no longer starts after copying the .exe -Most likely you forgot to set the launch options. `-v6ap_name YourName` and `-v6ap_ip ServerIP:Port` are required for startup. +Most likely you forgot to set the launch options. `-v6ap_name YourName` and `-v6ap_ip ServerIP:Port` are required for startup for Multiworlds, and +`-v6ap_file FileName` is required for (offline) singleplayer. If your Name or Password have spaces in them, surround them in quotes. ## Game Troubleshooting diff --git a/WebHostLib/templates/macros.html b/WebHostLib/templates/macros.html index 549d3ace..9af0b89b 100644 --- a/WebHostLib/templates/macros.html +++ b/WebHostLib/templates/macros.html @@ -34,6 +34,12 @@ {% elif patch.game == "Ocarina of Time" %} Download APZ5 File... + {% elif patch.game == "VVVVVV" and room.seed.slots|length == 1 %} + + Download APV6 File... + {% elif patch.game == "Super Mario 64" and room.seed.slots|length == 1 %} + + Download APSM64EX File... {% elif patch.game in ["A Link to the Past", "Secret of Evermore", "Super Metroid"] %} Download Patch File... diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 1245fb31..4e3095e1 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -47,6 +47,15 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s player_id=metadata["player_id"], game="Minecraft")) + elif file.filename.endswith(".apv6"): + _, seed_name, slot_id, slot_name = file.filename.split('.')[0].split('_', 3) + slots.add(Slot(data=zfile.open(file, "r").read(), player_name=slot_name, + player_id=int(slot_id[1:]), game="VVVVVV")) + elif file.filename.endswith(".apsm64ex"): + _, seed_name, slot_id, slot_name = file.filename.split('.')[0].split('_', 3) + slots.add(Slot(data=zfile.open(file, "r").read(), player_name=slot_name, + player_id=int(slot_id[1:]), game="Super Mario 64")) + elif file.filename.endswith(".zip"): # Factorio mods need a specific name or they do not function _, seed_name, slot_id, slot_name = file.filename.rsplit("_", 1)[0].split("-", 3) diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index e8601267..f88c3b4a 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -1,4 +1,6 @@ import typing +import os +import json from .Items import item_table, cannon_item_table, SM64Item from .Locations import location_table, SM64Location from .Options import sm64_options @@ -87,3 +89,24 @@ class SM64World(World): "StarsToFinish": self.world.StarsToFinish[self.player].value, "DeathLink": self.world.DeathLink[self.player].value, } + + def generate_output(self, output_directory: str): + if self.world.players != 1: + return + data = { + "slot_data": self.fill_slot_data(), + "location_to_item": {self.location_name_to_id[i] : item_table[self.world.get_location(i, self.player).item.name] for i in self.location_name_to_id}, + "data_package": { + "data": { + "games": { + self.game: { + "item_name_to_id": self.item_name_to_id, + "location_name_to_id": self.location_name_to_id + } + } + } + } + } + filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_player_name(self.player)}.apsm64ex" + with open(os.path.join(output_directory, filename), 'w') as f: + json.dump(data, f) diff --git a/worlds/v6/__init__.py b/worlds/v6/__init__.py index 66dd9a9f..2460beeb 100644 --- a/worlds/v6/__init__.py +++ b/worlds/v6/__init__.py @@ -1,5 +1,5 @@ import typing - +import os, json from .Items import item_table, V6Item from .Locations import location_table, V6Location from .Options import v6_options @@ -61,3 +61,24 @@ class V6World(World): "DeathLink": self.world.DeathLink[self.player].value, "DeathLink_Amnesty": self.world.DeathLinkAmnesty[self.player].value } + + def generate_output(self, output_directory: str): + if self.world.players != 1: + return + data = { + "slot_data": self.fill_slot_data(), + "location_to_item": {self.location_name_to_id[i] : item_table[self.world.get_location(i, self.player).item.name] for i in self.location_name_to_id}, + "data_package": { + "data": { + "games": { + self.game: { + "item_name_to_id": self.item_name_to_id, + "location_name_to_id": self.location_name_to_id + } + } + } + } + } + filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_player_name(self.player)}.apv6" + with open(os.path.join(output_directory, filename), 'w') as f: + json.dump(data, f)