diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000000..b745f0d02f --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,58 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy web version to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + # Partially copy-pasted from ./build.yml + steps: + - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + with: + repository: emscripten-core/emsdk + path: emsdk + - name: Install Dependencies + run: | + cd emsdk + ./emsdk install 3.1.58 + ./emsdk activate 3.1.58 + - name: Compile + env: + ARCHIVE: 1 + ARCHIVE_NOZIP: 1 + run: | + source emsdk/emsdk_env.sh + emmake make release -j$(nproc) + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Upload pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: './build/release-emscripten-wasm32.zip' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/Makefile b/Makefile index 1dc0abd2fa..e0f9aeb081 100644 --- a/Makefile +++ b/Makefile @@ -1227,7 +1227,8 @@ ifeq ($(PLATFORM),emscripten) endif ifneq ($(BUILD_CLIENT),0) - TARGETS += $(B)/$(CLIENTBIN).html + TARGETS += $(B)/index.html + TARGETS += $(B)/upload-game-files-to-cache.html ifneq ($(EMSCRIPTEN_PRELOAD_FILE),1) TARGETS += $(B)/$(CLIENTBIN)-config.json endif @@ -1630,7 +1631,14 @@ endif ifneq ($(PLATFORM),darwin) ifdef ARCHIVE @rm -f $@ + ifndef ARCHIVE_NOZIP @(cd $(B) && zip -r9 ../../$@ $(NAKED_TARGETS) $(NAKED_GENERATEDTARGETS)) + else + # Instead of making a .zip file, make it a directory + # and simply copy the files there. + # This is a little ugly, but works for GitHub pages deployment. + @(cd $(B) && mkdir --parents ../../$@ && cp -t ../../$@ --parents $(NAKED_TARGETS) $(NAKED_GENERATEDTARGETS)) + endif endif endif @: @@ -3091,10 +3099,14 @@ $(B)/$(MISSIONPACK)/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC) # EMSCRIPTEN ############################################################################# -$(B)/$(CLIENTBIN).html: $(WEBDIR)/client.html +$(B)/index.html: $(WEBDIR)/index.html $(echo_cmd) "SED $@" $(Q)sed 's/__CLIENTBIN__/$(CLIENTBIN)/g;s/__BASEGAME__/$(BASEGAME)/g;s/__EMSCRIPTEN_PRELOAD_FILE__/$(EMSCRIPTEN_PRELOAD_FILE)/g' < $< > $@ +$(B)/upload-game-files-to-cache.html: $(WEBDIR)/upload-game-files-to-cache.html + $(echo_cmd) "CP $@" + $(Q)cp $< $@ + $(B)/$(CLIENTBIN)-config.json: $(WEBDIR)/client-config.json $(echo_cmd) "CP $@" $(Q)cp $< $@ diff --git a/README.md b/README.md index 8ba398ff78..d637deceec 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ For Web, building with Emscripten `client-config.json` will be loaded. 4. Start a web server serving this directory. `python3 -m http.server` is an easy default that you may already have installed. - 5. Open `http://localhost:8000/build/debug-emscripten-wasm32/ioquake3.html` + 5. Open `http://localhost:8000/build/debug-emscripten-wasm32/index.html` in a web browser. Open the developer console to see errors and warnings. 6. Debugging the C code is possible using a Chrome extension. For details see https://developer.chrome.com/blog/wasm-debugging-2020 diff --git a/code/web/client.html b/code/web/index.html similarity index 76% rename from code/web/client.html rename to code/web/index.html index 3a23ab92f3..0fe4dbdc42 100644 --- a/code/web/client.html +++ b/code/web/index.html @@ -30,7 +30,7 @@ configFilename='./client-config.json'; } -if (window.location.protocol === 'file:') throw new Error(`Unfortunately browser security restrictions prevent loading wasm from a file: URL. This file must be loaded from a web server. The easiest way to do this is probably to use Python\'s built-in web server by running \`python3 -m http.server\` in the top level source directory and then navigate to http://localhost:8000/build/debug-emscripten-wasm32/${CLIENTBIN}.html`); +if (window.location.protocol === 'file:') throw new Error(`Unfortunately browser security restrictions prevent loading wasm from a file: URL. This file must be loaded from a web server. The easiest way to do this is probably to use Python\'s built-in web server by running \`python3 -m http.server\` in the top level source directory and then navigate to http://localhost:8000/build/debug-emscripten-wasm32/index.html`); // First set up the command line arguments and the Emscripten filesystem. const urlParams = new URLSearchParams(window.location.search); @@ -73,6 +73,13 @@ const dataURL = new URL(dataPath, location.origin + location.pathname); +/** + * @typedef {{ src: string, dst: string }} FileConfig + */ +/** + * @typedef {Record }>} Config + */ +/** @type {Promise} */ const configPromise = ( EMSCRIPTEN_PRELOAD_FILE === 1 ) ? Promise.resolve({[BASEGAME]: {files: []}}) : fetch(configFilename).then(r => r.ok ? r.json() : { /* empty config */ }); @@ -96,17 +103,42 @@ console.warn(`Game directory '${gamedir}' cannot be used. It must have files listed in ${configFilename}.`); continue; } + const cache = await caches.open('quake-game-files'); const files = config[gamedir].files; - const fetches = files.map(file => fetch(new URL(file.src, dataURL))); - for (let i = 0; i < files.length; i++) { - const response = await fetches[i]; - if (!response.ok) continue; + /** @type {Array>} */ + const fetches = files.map(async file => [ + file, + (await cache.match(file.src)) ?? + (await fetch(new URL(file.src, dataURL))), + ]); + + Promise.all(fetches).then(fetches => { + const missingFiles = fetches + .filter(([file, response]) => !response.ok) + .map(([file]) => file.src) + if (missingFiles.length !== 0) { + // Preserve query parameters, + // so that that page knows where to return to. + const nextPathname = new URL( + './upload-game-files-to-cache.html', + location.href + ).pathname; + const nextUrl = new URL(location.href); + nextUrl.pathname = nextPathname; + location.assign(nextUrl); + } + }); + + const writeFilePromises = fetches.map(async (p) => { + const [file, response] = await p; + if (!response.ok) return; const data = await response.arrayBuffer(); - let name = files[i].src.match(/[^/]+$/)[0]; - let dir = files[i].dst; + let name = file.src.match(/[^/]+$/)[0]; + let dir = file.dst; module.FS.mkdirTree(dir); module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data)); - } + }); + await Promise.all(writeFilePromises); } } finally { module.removeRunDependency('setup-ioq3-filesystem'); diff --git a/code/web/upload-game-files-to-cache.html b/code/web/upload-game-files-to-cache.html new file mode 100644 index 0000000000..af217273a2 --- /dev/null +++ b/code/web/upload-game-files-to-cache.html @@ -0,0 +1,178 @@ + + + + + + + + ioquake3: upload game files + + + + + +

+ In order to play the game, you need to select the .pk3 files + from the original Quake III installation folder. + +

+ +

+ The full version of Quake III Arena can be purchased + on Steam + or + on GOG. +

+

+ After downloading and installing the full game, + locate the game directory, + and upload the pak0.pk3 - pak8.pk3 files + from the "baseq3" directory, + using the file picker below. +

+

+ To find the game directory on Steam, + find the game in your Steam library, + right click it, then, under the "Manage" submenu, + click "Browse local files". +

+

+ For more info, see + https://ioquake3.org/help/players-guide. +

+ +
+ + + + + +
+ + + + + \ No newline at end of file