From 093d66ba733ee00fb379622957156a8348e6bbcd Mon Sep 17 00:00:00 2001 From: Phar Date: Sun, 3 Sep 2023 00:15:17 -0500 Subject: [PATCH 01/14] WebHost: Developer-defined game option presets. --- WebHostLib/options.py | 11 +- WebHostLib/static/assets/player-settings.js | 112 ++++++++++++++++++- WebHostLib/static/styles/player-settings.css | 17 +++ WebHostLib/templates/player-settings.html | 23 +++- worlds/AutoWorld.py | 3 + worlds/rogue_legacy/Presets.py | 58 ++++++++++ worlds/rogue_legacy/__init__.py | 2 + 7 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 worlds/rogue_legacy/Presets.py diff --git a/WebHostLib/options.py b/WebHostLib/options.py index fca01407e06b..d1e0be31ccc1 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -3,11 +3,8 @@ import os import typing -import yaml -from jinja2 import Template - import Options -from Utils import __version__, local_path +from Utils import local_path from worlds.AutoWorld import AutoWorldRegister handled_in_js = {"start_inventory", "local_items", "non_local_items", "start_hints", "start_location_hints", @@ -28,7 +25,7 @@ def get_html_doc(option_type: type(Options.Option)) -> str: weighted_settings = { "baseOptions": { "description": "Generated by https://archipelago.gg/", - "name": "Player", + "name": "", "game": {}, }, "games": {}, @@ -46,7 +43,7 @@ def get_html_doc(option_type: type(Options.Option)) -> str: "baseOptions": { "description": f"Generated by https://archipelago.gg/ for {game_name}", "game": game_name, - "name": "Player", + "name": "", }, } @@ -124,6 +121,8 @@ def get_html_doc(option_type: type(Options.Option)) -> str: player_settings["gameOptions"] = game_options + player_settings["presetOptions"] = world.web.settings_presets + os.makedirs(os.path.join(target_folder, 'player-settings'), exist_ok=True) with open(os.path.join(target_folder, 'player-settings', game_name + ".json"), "w") as f: diff --git a/WebHostLib/static/assets/player-settings.js b/WebHostLib/static/assets/player-settings.js index f75ba9060303..9427fc37d8ca 100644 --- a/WebHostLib/static/assets/player-settings.js +++ b/WebHostLib/static/assets/player-settings.js @@ -36,6 +36,17 @@ window.addEventListener('load', () => { const nameInput = document.getElementById('player-name'); nameInput.addEventListener('keyup', (event) => updateBaseSetting(event)); nameInput.value = playerSettings.name; + + // Presets + const presetSelect = document.getElementById("game-options-preset"); + presetSelect.addEventListener("change", (event) => setPresets(results, event.target.value)); + for (const preset in results["presetOptions"]) { + const presetOption = document.createElement("option"); + presetOption.innerText = preset; + presetSelect.appendChild(presetOption); + } + presetSelect.value = localStorage.getItem(`${gameName}-preset`); + results["presetOptions"]["__default"] = {}; }).catch((e) => { console.error(e); const url = new URL(window.location.href); @@ -45,7 +56,8 @@ window.addEventListener('load', () => { const resetSettings = () => { localStorage.removeItem(gameName); - localStorage.removeItem(`${gameName}-hash`) + localStorage.removeItem(`${gameName}-hash`); + localStorage.removeItem(`${gameName}-preset`); window.location.reload(); }; @@ -77,6 +89,10 @@ const createDefaultSettings = (settingData) => { } localStorage.setItem(gameName, JSON.stringify(newSettings)); } + + if (!localStorage.getItem(`${gameName}-preset`)) { + localStorage.setItem(`${gameName}-preset`, "__default"); + } }; const buildUI = (settingData) => { @@ -162,6 +178,7 @@ const buildOptionsTable = (settings, romOpts = false) => { element.classList.add('range-container'); let range = document.createElement('input'); + range.setAttribute('id', setting); range.setAttribute('type', 'range'); range.setAttribute('data-key', setting); range.setAttribute('min', settings[setting].min); @@ -294,6 +311,75 @@ const buildOptionsTable = (settings, romOpts = false) => { return table; }; +const setPresets = (settingsData, presetName) => { + const defaults = settingsData["gameOptions"]; + const preset = settingsData["presetOptions"][presetName]; + + localStorage.setItem(`${gameName}-preset`, presetName); + + if (!preset) { + console.error(`No presets defined for preset name: "${presetName}"`); + return; + } + + for (const setting in defaults) { + let presetValue = preset[setting]; + if (presetValue === undefined) { + // Using the default value if not set in presets. + presetValue = defaults[setting]["defaultValue"]; + } + + switch (defaults[setting].type) { + case "range": + case "select": { + const optionElement = document.querySelector(`#${setting}[data-key="${setting}"]`); + const randomElement = document.querySelector(`.randomize-button[data-key="${setting}"]`); + + if (presetValue === "random") { + randomElement.classList.add("active"); + optionElement.disabled = true; + updateGameSetting(randomElement, false); + } else { + optionElement.value = presetValue; + randomElement.classList.remove("active"); + optionElement.disabled = undefined; + updateGameSetting(optionElement, false); + } + + break; + } + + case "special_range": { + const selectElement = document.querySelector(`select[data-key="${setting}"]`); + const rangeElement = document.querySelector(`input[data-key="${setting}"]`); + const randomElement = document.querySelector(`.randomize-button[data-key="${setting}"]`); + + if (presetValue === "random") { + randomElement.classList.add("active"); + selectElement.disabled = true; + rangeElement.disabled = true; + updateGameSetting(randomElement, false); + } else { + rangeElement.value = presetValue; + selectElement.value = Object.values(defaults[setting]["value_names"]).includes(parseInt(presetValue)) ? + parseInt(presetValue) : "custom"; + document.getElementById(`${setting}-value`).innerText = presetValue; + + randomElement.classList.remove("active"); + selectElement.disabled = undefined; + rangeElement.disabled = undefined; + updateGameSetting(rangeElement, false); + } + break; + } + + default: + console.warn(`Ignoring preset value for unknown setting type: ${defaults[setting].type} with name ${setting}`); + break; + } + } +}; + const toggleRandomize = (event, inputElement, optionalSelectElement = null) => { const active = event.target.classList.contains('active'); const randomButton = event.target; @@ -322,9 +408,15 @@ const updateBaseSetting = (event) => { localStorage.setItem(gameName, JSON.stringify(options)); }; -const updateGameSetting = (settingElement) => { +const updateGameSetting = (settingElement, toggleCustomPreset = true) => { const options = JSON.parse(localStorage.getItem(gameName)); + if (toggleCustomPreset) { + localStorage.setItem(`${gameName}-preset`, "__custom"); + const presetElement = document.getElementById("game-options-preset"); + presetElement.value = "__custom"; + } + if (settingElement.classList.contains('randomize-button')) { // If the event passed in is the randomize button, then we know what we must do. options[gameName][settingElement.getAttribute('data-key')] = 'random'; @@ -338,7 +430,21 @@ const updateGameSetting = (settingElement) => { const exportSettings = () => { const settings = JSON.parse(localStorage.getItem(gameName)); - if (!settings.name || settings.name.toLowerCase() === 'player' || settings.name.trim().length === 0) { + const preset = localStorage.getItem(`${gameName}-preset`); + switch (preset) { + case "__default": + settings["description"] = `Generated by https://archipelago.gg with the default preset.`; + break; + + case "__custom": + settings["description"] = `Generated by https://archipelago.gg.`; + break; + + default: + settings["description"] = `Generated by https://archipelago.gg with the ${preset} preset.`; + } + + if (!settings.name || settings.name.trim().length === 0) { return showUserMessage('You must enter a player name!'); } const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); diff --git a/WebHostLib/static/styles/player-settings.css b/WebHostLib/static/styles/player-settings.css index e6e0c292922a..bc8252560bb5 100644 --- a/WebHostLib/static/styles/player-settings.css +++ b/WebHostLib/static/styles/player-settings.css @@ -90,6 +90,23 @@ html{ flex-direction: row; } +#player-settings #meta-options { + display: flex; + flex-direction: column; + gap: 6px; +} + +#player-settings #meta-options label { + display: inline-block; + min-width: 180px; +} + +#player-settings #meta-options input, +#player-settings #meta-options select { + box-sizing: border-box; + min-width: 200px; +} + #player-settings .left, #player-settings .right{ flex-grow: 1; } diff --git a/WebHostLib/templates/player-settings.html b/WebHostLib/templates/player-settings.html index 50b9e3cbb1a2..f2ad8cf61b47 100644 --- a/WebHostLib/templates/player-settings.html +++ b/WebHostLib/templates/player-settings.html @@ -28,10 +28,25 @@ Player Settings template file for this game.
Please enter your player name. This will appear in-game as you send and receive - items if you are playing in a MultiWorld. - -