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)