WebHost: consistent naming for player options (#2037)

* WebHost: unify references to options

* it was just an extra s the whole time...

* grammar

* redirect from old pages

* redirect stuff correctly

* use url_for

* use " for modified strings

* remove redirect cache

* player_settings

* update site map
This commit is contained in:
Aaron Wagener 2023-10-23 19:20:08 -05:00 committed by GitHub
parent 12c73acb20
commit 764128568e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 190 additions and 178 deletions

View File

@ -169,7 +169,7 @@ def main(args=None, callback=ERmain):
for player in range(1, args.multi + 1): for player in range(1, args.multi + 1):
player_path_cache[player] = player_files.get(player, args.weights_file_path) player_path_cache[player] = player_files.get(player, args.weights_file_path)
name_counter = Counter() name_counter = Counter()
erargs.player_settings = {} erargs.player_options = {}
player = 1 player = 1
while player <= args.multi: while player <= args.multi:

View File

@ -37,17 +37,29 @@ def start_playing():
return render_template(f"startPlaying.html") return render_template(f"startPlaying.html")
@app.route('/weighted-settings') # TODO for back compat. remove around 0.4.5
@cache.cached() @app.route("/weighted-settings")
def weighted_settings(): def weighted_settings():
return render_template(f"weighted-settings.html") return redirect("weighted-options", 301)
# Player settings pages @app.route("/weighted-options")
@app.route('/games/<string:game>/player-settings')
@cache.cached() @cache.cached()
def player_settings(game): def weighted_options():
return render_template(f"player-settings.html", game=game, theme=get_world_theme(game)) return render_template("weighted-options.html")
# TODO for back compat. remove around 0.4.5
@app.route("/games/<string:game>/player-settings")
def player_settings(game: str):
return redirect(url_for("player_options", game=game), 301)
# Player options pages
@app.route("/games/<string:game>/player-options")
@cache.cached()
def player_options(game: str):
return render_template("player-options.html", game=game, theme=get_world_theme(game))
# Game Info Pages # Game Info Pages
@ -181,6 +193,6 @@ def get_sitemap():
available_games: List[Dict[str, Union[str, bool]]] = [] available_games: List[Dict[str, Union[str, bool]]] = []
for game, world in AutoWorldRegister.world_types.items(): for game, world in AutoWorldRegister.world_types.items():
if not world.hidden: if not world.hidden:
has_settings: bool = isinstance(world.web.settings_page, bool) and world.web.settings_page has_settings: bool = isinstance(world.web.options_page, bool) and world.web.options_page
available_games.append({ 'title': game, 'has_settings': has_settings }) available_games.append({ 'title': game, 'has_settings': has_settings })
return render_template("siteMap.html", games=available_games) return render_template("siteMap.html", games=available_games)

View File

@ -25,7 +25,7 @@ def create():
return "Please document me!" return "Please document me!"
return "\n".join(line.strip() for line in option_type.__doc__.split("\n")).strip() return "\n".join(line.strip() for line in option_type.__doc__.split("\n")).strip()
weighted_settings = { weighted_options = {
"baseOptions": { "baseOptions": {
"description": "Generated by https://archipelago.gg/", "description": "Generated by https://archipelago.gg/",
"name": "Player", "name": "Player",
@ -38,8 +38,8 @@ def create():
all_options: typing.Dict[str, Options.AssembleOptions] = world.options_dataclass.type_hints all_options: typing.Dict[str, Options.AssembleOptions] = world.options_dataclass.type_hints
# Generate JSON files for player-settings pages # Generate JSON files for player-options pages
player_settings = { player_options = {
"baseOptions": { "baseOptions": {
"description": f"Generated by https://archipelago.gg/ for {game_name}", "description": f"Generated by https://archipelago.gg/ for {game_name}",
"game": game_name, "game": game_name,
@ -117,17 +117,17 @@ def create():
} }
else: else:
logging.debug(f"{option} not exported to Web Settings.") logging.debug(f"{option} not exported to Web options.")
player_settings["gameOptions"] = game_options player_options["gameOptions"] = game_options
os.makedirs(os.path.join(target_folder, 'player-settings'), exist_ok=True) os.makedirs(os.path.join(target_folder, 'player-options'), exist_ok=True)
with open(os.path.join(target_folder, 'player-settings', game_name + ".json"), "w") as f: with open(os.path.join(target_folder, 'player-options', game_name + ".json"), "w") as f:
json.dump(player_settings, f, indent=2, separators=(',', ': ')) json.dump(player_options, f, indent=2, separators=(',', ': '))
if not world.hidden and world.web.settings_page is True: if not world.hidden and world.web.options_page is True:
# Add the random option to Choice, TextChoice, and Toggle settings # Add the random option to Choice, TextChoice, and Toggle options
for option in game_options.values(): for option in game_options.values():
if option["type"] == "select": if option["type"] == "select":
option["options"].append({"name": "Random", "value": "random"}) option["options"].append({"name": "Random", "value": "random"})
@ -135,11 +135,11 @@ def create():
if not option["defaultValue"]: if not option["defaultValue"]:
option["defaultValue"] = "random" option["defaultValue"] = "random"
weighted_settings["baseOptions"]["game"][game_name] = 0 weighted_options["baseOptions"]["game"][game_name] = 0
weighted_settings["games"][game_name] = {} weighted_options["games"][game_name] = {}
weighted_settings["games"][game_name]["gameSettings"] = game_options weighted_options["games"][game_name]["gameSettings"] = game_options
weighted_settings["games"][game_name]["gameItems"] = tuple(world.item_names) weighted_options["games"][game_name]["gameItems"] = tuple(world.item_names)
weighted_settings["games"][game_name]["gameLocations"] = tuple(world.location_names) weighted_options["games"][game_name]["gameLocations"] = tuple(world.location_names)
with open(os.path.join(target_folder, 'weighted-settings.json'), "w") as f: with open(os.path.join(target_folder, 'weighted-options.json'), "w") as f:
json.dump(weighted_settings, f, indent=2, separators=(',', ': ')) json.dump(weighted_options, f, indent=2, separators=(',', ': '))

View File

@ -1,41 +1,41 @@
let gameName = null; let gameName = null;
window.addEventListener('load', () => { window.addEventListener('load', () => {
gameName = document.getElementById('player-settings').getAttribute('data-game'); gameName = document.getElementById('player-options').getAttribute('data-game');
// Update game name on page // Update game name on page
document.getElementById('game-name').innerText = gameName; document.getElementById('game-name').innerText = gameName;
fetchSettingData().then((results) => { fetchOptionData().then((results) => {
let settingHash = localStorage.getItem(`${gameName}-hash`); let optionHash = localStorage.getItem(`${gameName}-hash`);
if (!settingHash) { if (!optionHash) {
// If no hash data has been set before, set it now // If no hash data has been set before, set it now
settingHash = md5(JSON.stringify(results)); optionHash = md5(JSON.stringify(results));
localStorage.setItem(`${gameName}-hash`, settingHash); localStorage.setItem(`${gameName}-hash`, optionHash);
localStorage.removeItem(gameName); localStorage.removeItem(gameName);
} }
if (settingHash !== md5(JSON.stringify(results))) { if (optionHash !== md5(JSON.stringify(results))) {
showUserMessage("Your settings are out of date! Click here to update them! Be aware this will reset " + showUserMessage("Your options are out of date! Click here to update them! Be aware this will reset " +
"them all to default."); "them all to default.");
document.getElementById('user-message').addEventListener('click', resetSettings); document.getElementById('user-message').addEventListener('click', resetOptions);
} }
// Page setup // Page setup
createDefaultSettings(results); createDefaultOptions(results);
buildUI(results); buildUI(results);
adjustHeaderWidth(); adjustHeaderWidth();
// Event listeners // Event listeners
document.getElementById('export-settings').addEventListener('click', () => exportSettings()); document.getElementById('export-options').addEventListener('click', () => exportOptions());
document.getElementById('generate-race').addEventListener('click', () => generateGame(true)); document.getElementById('generate-race').addEventListener('click', () => generateGame(true));
document.getElementById('generate-game').addEventListener('click', () => generateGame()); document.getElementById('generate-game').addEventListener('click', () => generateGame());
// Name input field // Name input field
const playerSettings = JSON.parse(localStorage.getItem(gameName)); const playerOptions = JSON.parse(localStorage.getItem(gameName));
const nameInput = document.getElementById('player-name'); const nameInput = document.getElementById('player-name');
nameInput.addEventListener('keyup', (event) => updateBaseSetting(event)); nameInput.addEventListener('keyup', (event) => updateBaseOption(event));
nameInput.value = playerSettings.name; nameInput.value = playerOptions.name;
}).catch((e) => { }).catch((e) => {
console.error(e); console.error(e);
const url = new URL(window.location.href); const url = new URL(window.location.href);
@ -43,13 +43,13 @@ window.addEventListener('load', () => {
}) })
}); });
const resetSettings = () => { const resetOptions = () => {
localStorage.removeItem(gameName); localStorage.removeItem(gameName);
localStorage.removeItem(`${gameName}-hash`) localStorage.removeItem(`${gameName}-hash`)
window.location.reload(); window.location.reload();
}; };
const fetchSettingData = () => new Promise((resolve, reject) => { const fetchOptionData = () => new Promise((resolve, reject) => {
const ajax = new XMLHttpRequest(); const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => { ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; } if (ajax.readyState !== 4) { return; }
@ -60,54 +60,54 @@ const fetchSettingData = () => new Promise((resolve, reject) => {
try{ resolve(JSON.parse(ajax.responseText)); } try{ resolve(JSON.parse(ajax.responseText)); }
catch(error){ reject(error); } catch(error){ reject(error); }
}; };
ajax.open('GET', `${window.location.origin}/static/generated/player-settings/${gameName}.json`, true); ajax.open('GET', `${window.location.origin}/static/generated/player-options/${gameName}.json`, true);
ajax.send(); ajax.send();
}); });
const createDefaultSettings = (settingData) => { const createDefaultOptions = (optionData) => {
if (!localStorage.getItem(gameName)) { if (!localStorage.getItem(gameName)) {
const newSettings = { const newOptions = {
[gameName]: {}, [gameName]: {},
}; };
for (let baseOption of Object.keys(settingData.baseOptions)){ for (let baseOption of Object.keys(optionData.baseOptions)){
newSettings[baseOption] = settingData.baseOptions[baseOption]; newOptions[baseOption] = optionData.baseOptions[baseOption];
} }
for (let gameOption of Object.keys(settingData.gameOptions)){ for (let gameOption of Object.keys(optionData.gameOptions)){
newSettings[gameName][gameOption] = settingData.gameOptions[gameOption].defaultValue; newOptions[gameName][gameOption] = optionData.gameOptions[gameOption].defaultValue;
} }
localStorage.setItem(gameName, JSON.stringify(newSettings)); localStorage.setItem(gameName, JSON.stringify(newOptions));
} }
}; };
const buildUI = (settingData) => { const buildUI = (optionData) => {
// Game Options // Game Options
const leftGameOpts = {}; const leftGameOpts = {};
const rightGameOpts = {}; const rightGameOpts = {};
Object.keys(settingData.gameOptions).forEach((key, index) => { Object.keys(optionData.gameOptions).forEach((key, index) => {
if (index < Object.keys(settingData.gameOptions).length / 2) { leftGameOpts[key] = settingData.gameOptions[key]; } if (index < Object.keys(optionData.gameOptions).length / 2) { leftGameOpts[key] = optionData.gameOptions[key]; }
else { rightGameOpts[key] = settingData.gameOptions[key]; } else { rightGameOpts[key] = optionData.gameOptions[key]; }
}); });
document.getElementById('game-options-left').appendChild(buildOptionsTable(leftGameOpts)); document.getElementById('game-options-left').appendChild(buildOptionsTable(leftGameOpts));
document.getElementById('game-options-right').appendChild(buildOptionsTable(rightGameOpts)); document.getElementById('game-options-right').appendChild(buildOptionsTable(rightGameOpts));
}; };
const buildOptionsTable = (settings, romOpts = false) => { const buildOptionsTable = (options, romOpts = false) => {
const currentSettings = JSON.parse(localStorage.getItem(gameName)); const currentOptions = JSON.parse(localStorage.getItem(gameName));
const table = document.createElement('table'); const table = document.createElement('table');
const tbody = document.createElement('tbody'); const tbody = document.createElement('tbody');
Object.keys(settings).forEach((setting) => { Object.keys(options).forEach((option) => {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
// td Left // td Left
const tdl = document.createElement('td'); const tdl = document.createElement('td');
const label = document.createElement('label'); const label = document.createElement('label');
label.textContent = `${settings[setting].displayName}: `; label.textContent = `${options[option].displayName}: `;
label.setAttribute('for', setting); label.setAttribute('for', option);
const questionSpan = document.createElement('span'); const questionSpan = document.createElement('span');
questionSpan.classList.add('interactive'); questionSpan.classList.add('interactive');
questionSpan.setAttribute('data-tooltip', settings[setting].description); questionSpan.setAttribute('data-tooltip', options[option].description);
questionSpan.innerText = '(?)'; questionSpan.innerText = '(?)';
label.appendChild(questionSpan); label.appendChild(questionSpan);
@ -120,36 +120,36 @@ const buildOptionsTable = (settings, romOpts = false) => {
const randomButton = document.createElement('button'); const randomButton = document.createElement('button');
switch(settings[setting].type){ switch(options[option].type){
case 'select': case 'select':
element = document.createElement('div'); element = document.createElement('div');
element.classList.add('select-container'); element.classList.add('select-container');
let select = document.createElement('select'); let select = document.createElement('select');
select.setAttribute('id', setting); select.setAttribute('id', option);
select.setAttribute('data-key', setting); select.setAttribute('data-key', option);
if (romOpts) { select.setAttribute('data-romOpt', '1'); } if (romOpts) { select.setAttribute('data-romOpt', '1'); }
settings[setting].options.forEach((opt) => { options[option].options.forEach((opt) => {
const option = document.createElement('option'); const option = document.createElement('option');
option.setAttribute('value', opt.value); option.setAttribute('value', opt.value);
option.innerText = opt.name; option.innerText = opt.name;
if ((isNaN(currentSettings[gameName][setting]) && if ((isNaN(currentOptions[gameName][option]) &&
(parseInt(opt.value, 10) === parseInt(currentSettings[gameName][setting]))) || (parseInt(opt.value, 10) === parseInt(currentOptions[gameName][option]))) ||
(opt.value === currentSettings[gameName][setting])) (opt.value === currentOptions[gameName][option]))
{ {
option.selected = true; option.selected = true;
} }
select.appendChild(option); select.appendChild(option);
}); });
select.addEventListener('change', (event) => updateGameSetting(event.target)); select.addEventListener('change', (event) => updateGameOption(event.target));
element.appendChild(select); element.appendChild(select);
// Randomize button // Randomize button
randomButton.innerText = '🎲'; randomButton.innerText = '🎲';
randomButton.classList.add('randomize-button'); randomButton.classList.add('randomize-button');
randomButton.setAttribute('data-key', setting); randomButton.setAttribute('data-key', option);
randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!'); randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!');
randomButton.addEventListener('click', (event) => toggleRandomize(event, select)); randomButton.addEventListener('click', (event) => toggleRandomize(event, select));
if (currentSettings[gameName][setting] === 'random') { if (currentOptions[gameName][option] === 'random') {
randomButton.classList.add('active'); randomButton.classList.add('active');
select.disabled = true; select.disabled = true;
} }
@ -163,30 +163,30 @@ const buildOptionsTable = (settings, romOpts = false) => {
let range = document.createElement('input'); let range = document.createElement('input');
range.setAttribute('type', 'range'); range.setAttribute('type', 'range');
range.setAttribute('data-key', setting); range.setAttribute('data-key', option);
range.setAttribute('min', settings[setting].min); range.setAttribute('min', options[option].min);
range.setAttribute('max', settings[setting].max); range.setAttribute('max', options[option].max);
range.value = currentSettings[gameName][setting]; range.value = currentOptions[gameName][option];
range.addEventListener('change', (event) => { range.addEventListener('change', (event) => {
document.getElementById(`${setting}-value`).innerText = event.target.value; document.getElementById(`${option}-value`).innerText = event.target.value;
updateGameSetting(event.target); updateGameOption(event.target);
}); });
element.appendChild(range); element.appendChild(range);
let rangeVal = document.createElement('span'); let rangeVal = document.createElement('span');
rangeVal.classList.add('range-value'); rangeVal.classList.add('range-value');
rangeVal.setAttribute('id', `${setting}-value`); rangeVal.setAttribute('id', `${option}-value`);
rangeVal.innerText = currentSettings[gameName][setting] !== 'random' ? rangeVal.innerText = currentOptions[gameName][option] !== 'random' ?
currentSettings[gameName][setting] : settings[setting].defaultValue; currentOptions[gameName][option] : options[option].defaultValue;
element.appendChild(rangeVal); element.appendChild(rangeVal);
// Randomize button // Randomize button
randomButton.innerText = '🎲'; randomButton.innerText = '🎲';
randomButton.classList.add('randomize-button'); randomButton.classList.add('randomize-button');
randomButton.setAttribute('data-key', setting); randomButton.setAttribute('data-key', option);
randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!'); randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!');
randomButton.addEventListener('click', (event) => toggleRandomize(event, range)); randomButton.addEventListener('click', (event) => toggleRandomize(event, range));
if (currentSettings[gameName][setting] === 'random') { if (currentOptions[gameName][option] === 'random') {
randomButton.classList.add('active'); randomButton.classList.add('active');
range.disabled = true; range.disabled = true;
} }
@ -200,11 +200,11 @@ const buildOptionsTable = (settings, romOpts = false) => {
// Build the select element // Build the select element
let specialRangeSelect = document.createElement('select'); let specialRangeSelect = document.createElement('select');
specialRangeSelect.setAttribute('data-key', setting); specialRangeSelect.setAttribute('data-key', option);
Object.keys(settings[setting].value_names).forEach((presetName) => { Object.keys(options[option].value_names).forEach((presetName) => {
let presetOption = document.createElement('option'); let presetOption = document.createElement('option');
presetOption.innerText = presetName; presetOption.innerText = presetName;
presetOption.value = settings[setting].value_names[presetName]; presetOption.value = options[option].value_names[presetName];
const words = presetOption.innerText.split("_"); const words = presetOption.innerText.split("_");
for (let i = 0; i < words.length; i++) { for (let i = 0; i < words.length; i++) {
words[i] = words[i][0].toUpperCase() + words[i].substring(1); words[i] = words[i][0].toUpperCase() + words[i].substring(1);
@ -217,8 +217,8 @@ const buildOptionsTable = (settings, romOpts = false) => {
customOption.value = 'custom'; customOption.value = 'custom';
customOption.selected = true; customOption.selected = true;
specialRangeSelect.appendChild(customOption); specialRangeSelect.appendChild(customOption);
if (Object.values(settings[setting].value_names).includes(Number(currentSettings[gameName][setting]))) { if (Object.values(options[option].value_names).includes(Number(currentOptions[gameName][option]))) {
specialRangeSelect.value = Number(currentSettings[gameName][setting]); specialRangeSelect.value = Number(currentOptions[gameName][option]);
} }
// Build range element // Build range element
@ -226,17 +226,17 @@ const buildOptionsTable = (settings, romOpts = false) => {
specialRangeWrapper.classList.add('special-range-wrapper'); specialRangeWrapper.classList.add('special-range-wrapper');
let specialRange = document.createElement('input'); let specialRange = document.createElement('input');
specialRange.setAttribute('type', 'range'); specialRange.setAttribute('type', 'range');
specialRange.setAttribute('data-key', setting); specialRange.setAttribute('data-key', option);
specialRange.setAttribute('min', settings[setting].min); specialRange.setAttribute('min', options[option].min);
specialRange.setAttribute('max', settings[setting].max); specialRange.setAttribute('max', options[option].max);
specialRange.value = currentSettings[gameName][setting]; specialRange.value = currentOptions[gameName][option];
// Build rage value element // Build rage value element
let specialRangeVal = document.createElement('span'); let specialRangeVal = document.createElement('span');
specialRangeVal.classList.add('range-value'); specialRangeVal.classList.add('range-value');
specialRangeVal.setAttribute('id', `${setting}-value`); specialRangeVal.setAttribute('id', `${option}-value`);
specialRangeVal.innerText = currentSettings[gameName][setting] !== 'random' ? specialRangeVal.innerText = currentOptions[gameName][option] !== 'random' ?
currentSettings[gameName][setting] : settings[setting].defaultValue; currentOptions[gameName][option] : options[option].defaultValue;
// Configure select event listener // Configure select event listener
specialRangeSelect.addEventListener('change', (event) => { specialRangeSelect.addEventListener('change', (event) => {
@ -244,18 +244,18 @@ const buildOptionsTable = (settings, romOpts = false) => {
// Update range slider // Update range slider
specialRange.value = event.target.value; specialRange.value = event.target.value;
document.getElementById(`${setting}-value`).innerText = event.target.value; document.getElementById(`${option}-value`).innerText = event.target.value;
updateGameSetting(event.target); updateGameOption(event.target);
}); });
// Configure range event handler // Configure range event handler
specialRange.addEventListener('change', (event) => { specialRange.addEventListener('change', (event) => {
// Update select element // Update select element
specialRangeSelect.value = specialRangeSelect.value =
(Object.values(settings[setting].value_names).includes(parseInt(event.target.value))) ? (Object.values(options[option].value_names).includes(parseInt(event.target.value))) ?
parseInt(event.target.value) : 'custom'; parseInt(event.target.value) : 'custom';
document.getElementById(`${setting}-value`).innerText = event.target.value; document.getElementById(`${option}-value`).innerText = event.target.value;
updateGameSetting(event.target); updateGameOption(event.target);
}); });
element.appendChild(specialRangeSelect); element.appendChild(specialRangeSelect);
@ -266,12 +266,12 @@ const buildOptionsTable = (settings, romOpts = false) => {
// Randomize button // Randomize button
randomButton.innerText = '🎲'; randomButton.innerText = '🎲';
randomButton.classList.add('randomize-button'); randomButton.classList.add('randomize-button');
randomButton.setAttribute('data-key', setting); randomButton.setAttribute('data-key', option);
randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!'); randomButton.setAttribute('data-tooltip', 'Toggle randomization for this option!');
randomButton.addEventListener('click', (event) => toggleRandomize( randomButton.addEventListener('click', (event) => toggleRandomize(
event, specialRange, specialRangeSelect) event, specialRange, specialRangeSelect)
); );
if (currentSettings[gameName][setting] === 'random') { if (currentOptions[gameName][option] === 'random') {
randomButton.classList.add('active'); randomButton.classList.add('active');
specialRange.disabled = true; specialRange.disabled = true;
specialRangeSelect.disabled = true; specialRangeSelect.disabled = true;
@ -281,7 +281,7 @@ const buildOptionsTable = (settings, romOpts = false) => {
break; break;
default: default:
console.error(`Ignoring unknown setting type: ${settings[setting].type} with name ${setting}`); console.error(`Ignoring unknown option type: ${options[option].type} with name ${option}`);
return; return;
} }
@ -311,35 +311,35 @@ const toggleRandomize = (event, inputElement, optionalSelectElement = null) => {
optionalSelectElement.disabled = true; optionalSelectElement.disabled = true;
} }
} }
updateGameSetting(active ? inputElement : randomButton); updateGameOption(active ? inputElement : randomButton);
}; };
const updateBaseSetting = (event) => { const updateBaseOption = (event) => {
const options = JSON.parse(localStorage.getItem(gameName)); const options = JSON.parse(localStorage.getItem(gameName));
options[event.target.getAttribute('data-key')] = isNaN(event.target.value) ? options[event.target.getAttribute('data-key')] = isNaN(event.target.value) ?
event.target.value : parseInt(event.target.value); event.target.value : parseInt(event.target.value);
localStorage.setItem(gameName, JSON.stringify(options)); localStorage.setItem(gameName, JSON.stringify(options));
}; };
const updateGameSetting = (settingElement) => { const updateGameOption = (optionElement) => {
const options = JSON.parse(localStorage.getItem(gameName)); const options = JSON.parse(localStorage.getItem(gameName));
if (settingElement.classList.contains('randomize-button')) { if (optionElement.classList.contains('randomize-button')) {
// If the event passed in is the randomize button, then we know what we must do. // If the event passed in is the randomize button, then we know what we must do.
options[gameName][settingElement.getAttribute('data-key')] = 'random'; options[gameName][optionElement.getAttribute('data-key')] = 'random';
} else { } else {
options[gameName][settingElement.getAttribute('data-key')] = isNaN(settingElement.value) ? options[gameName][optionElement.getAttribute('data-key')] = isNaN(optionElement.value) ?
settingElement.value : parseInt(settingElement.value, 10); optionElement.value : parseInt(optionElement.value, 10);
} }
localStorage.setItem(gameName, JSON.stringify(options)); localStorage.setItem(gameName, JSON.stringify(options));
}; };
const exportSettings = () => { const exportOptions = () => {
const settings = JSON.parse(localStorage.getItem(gameName)); const options = JSON.parse(localStorage.getItem(gameName));
if (!settings.name || settings.name.toLowerCase() === 'player' || settings.name.trim().length === 0) { if (!options.name || options.name.toLowerCase() === 'player' || options.name.trim().length === 0) {
return showUserMessage('You must enter a player name!'); return showUserMessage('You must enter a player name!');
} }
const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`); const yamlText = jsyaml.safeDump(options, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`);
download(`${document.getElementById('player-name').value}.yaml`, yamlText); download(`${document.getElementById('player-name').value}.yaml`, yamlText);
}; };
@ -355,14 +355,14 @@ const download = (filename, text) => {
}; };
const generateGame = (raceMode = false) => { const generateGame = (raceMode = false) => {
const settings = JSON.parse(localStorage.getItem(gameName)); const options = JSON.parse(localStorage.getItem(gameName));
if (!settings.name || settings.name.toLowerCase() === 'player' || settings.name.trim().length === 0) { if (!options.name || options.name.toLowerCase() === 'player' || options.name.trim().length === 0) {
return showUserMessage('You must enter a player name!'); return showUserMessage('You must enter a player name!');
} }
axios.post('/api/generate', { axios.post('/api/generate', {
weights: { player: settings }, weights: { player: options },
presetData: { player: settings }, presetData: { player: options },
playerCount: 1, playerCount: 1,
spoiler: 3, spoiler: 3,
race: raceMode ? '1' : '0', race: raceMode ? '1' : '0',

View File

@ -23,7 +23,7 @@ window.addEventListener('load', () => {
adjustHeaderWidth(); adjustHeaderWidth();
// Event listeners // Event listeners
document.getElementById('export-settings').addEventListener('click', () => settings.export()); document.getElementById('export-options').addEventListener('click', () => settings.export());
document.getElementById('generate-race').addEventListener('click', () => settings.generateGame(true)); document.getElementById('generate-race').addEventListener('click', () => settings.generateGame(true));
document.getElementById('generate-game').addEventListener('click', () => settings.generateGame()); document.getElementById('generate-game').addEventListener('click', () => settings.generateGame());

View File

@ -4,7 +4,7 @@ html{
background-size: 650px 650px; background-size: 650px 650px;
} }
#player-settings{ #player-options{
box-sizing: border-box; box-sizing: border-box;
max-width: 1024px; max-width: 1024px;
margin-left: auto; margin-left: auto;
@ -15,14 +15,14 @@ html{
color: #eeffeb; color: #eeffeb;
} }
#player-settings #player-settings-button-row{ #player-options #player-options-button-row{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
margin-top: 15px; margin-top: 15px;
} }
#player-settings code{ #player-options code{
background-color: #d9cd8e; background-color: #d9cd8e;
border-radius: 4px; border-radius: 4px;
padding-left: 0.25rem; padding-left: 0.25rem;
@ -30,7 +30,7 @@ html{
color: #000000; color: #000000;
} }
#player-settings #user-message{ #player-options #user-message{
display: none; display: none;
width: calc(100% - 8px); width: calc(100% - 8px);
background-color: #ffe86b; background-color: #ffe86b;
@ -40,12 +40,12 @@ html{
text-align: center; text-align: center;
} }
#player-settings #user-message.visible{ #player-options #user-message.visible{
display: block; display: block;
cursor: pointer; cursor: pointer;
} }
#player-settings h1{ #player-options h1{
font-size: 2.5rem; font-size: 2.5rem;
font-weight: normal; font-weight: normal;
width: 100%; width: 100%;
@ -53,7 +53,7 @@ html{
text-shadow: 1px 1px 4px #000000; text-shadow: 1px 1px 4px #000000;
} }
#player-settings h2{ #player-options h2{
font-size: 40px; font-size: 40px;
font-weight: normal; font-weight: normal;
width: 100%; width: 100%;
@ -62,22 +62,22 @@ html{
text-shadow: 1px 1px 2px #000000; text-shadow: 1px 1px 2px #000000;
} }
#player-settings h3, #player-settings h4, #player-settings h5, #player-settings h6{ #player-options h3, #player-options h4, #player-options h5, #player-options h6{
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5); text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
} }
#player-settings input:not([type]){ #player-options input:not([type]){
border: 1px solid #000000; border: 1px solid #000000;
padding: 3px; padding: 3px;
border-radius: 3px; border-radius: 3px;
min-width: 150px; min-width: 150px;
} }
#player-settings input:not([type]):focus{ #player-options input:not([type]):focus{
border: 1px solid #ffffff; border: 1px solid #ffffff;
} }
#player-settings select{ #player-options select{
border: 1px solid #000000; border: 1px solid #000000;
padding: 3px; padding: 3px;
border-radius: 3px; border-radius: 3px;
@ -85,72 +85,72 @@ html{
background-color: #ffffff; background-color: #ffffff;
} }
#player-settings #game-options, #player-settings #rom-options{ #player-options #game-options, #player-options #rom-options{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
#player-settings .left, #player-settings .right{ #player-options .left, #player-options .right{
flex-grow: 1; flex-grow: 1;
} }
#player-settings .left{ #player-options .left{
margin-right: 10px; margin-right: 10px;
} }
#player-settings .right{ #player-options .right{
margin-left: 10px; margin-left: 10px;
} }
#player-settings table{ #player-options table{
margin-bottom: 30px; margin-bottom: 30px;
width: 100%; width: 100%;
} }
#player-settings table .select-container{ #player-options table .select-container{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
#player-settings table .select-container select{ #player-options table .select-container select{
min-width: 200px; min-width: 200px;
flex-grow: 1; flex-grow: 1;
} }
#player-settings table select:disabled{ #player-options table select:disabled{
background-color: lightgray; background-color: lightgray;
} }
#player-settings table .range-container{ #player-options table .range-container{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
#player-settings table .range-container input[type=range]{ #player-options table .range-container input[type=range]{
flex-grow: 1; flex-grow: 1;
} }
#player-settings table .range-value{ #player-options table .range-value{
min-width: 20px; min-width: 20px;
margin-left: 0.25rem; margin-left: 0.25rem;
} }
#player-settings table .special-range-container{ #player-options table .special-range-container{
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#player-settings table .special-range-wrapper{ #player-options table .special-range-wrapper{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-top: 0.25rem; margin-top: 0.25rem;
} }
#player-settings table .special-range-wrapper input[type=range]{ #player-options table .special-range-wrapper input[type=range]{
flex-grow: 1; flex-grow: 1;
} }
#player-settings table .randomize-button { #player-options table .randomize-button {
max-height: 24px; max-height: 24px;
line-height: 16px; line-height: 16px;
padding: 2px 8px; padding: 2px 8px;
@ -160,23 +160,23 @@ html{
border-radius: 3px; border-radius: 3px;
} }
#player-settings table .randomize-button.active { #player-options table .randomize-button.active {
background-color: #ffef00; /* Same as .interactive in globalStyles.css */ background-color: #ffef00; /* Same as .interactive in globalStyles.css */
} }
#player-settings table .randomize-button[data-tooltip]::after { #player-options table .randomize-button[data-tooltip]::after {
left: unset; left: unset;
right: 0; right: 0;
} }
#player-settings table label{ #player-options table label{
display: block; display: block;
min-width: 200px; min-width: 200px;
margin-right: 4px; margin-right: 4px;
cursor: default; cursor: default;
} }
#player-settings th, #player-settings td{ #player-options th, #player-options td{
border: none; border: none;
padding: 3px; padding: 3px;
font-size: 17px; font-size: 17px;
@ -184,17 +184,17 @@ html{
} }
@media all and (max-width: 1024px) { @media all and (max-width: 1024px) {
#player-settings { #player-options {
border-radius: 0; border-radius: 0;
} }
#player-settings #game-options{ #player-options #game-options{
justify-content: flex-start; justify-content: flex-start;
flex-wrap: wrap; flex-wrap: wrap;
} }
#player-settings .left, #player-options .left,
#player-settings .right { #player-options .right {
margin: 0; margin: 0;
} }

View File

@ -1,26 +1,26 @@
{% extends 'pageWrapper.html' %} {% extends 'pageWrapper.html' %}
{% block head %} {% block head %}
<title>{{ game }} Settings</title> <title>{{ game }} Options</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" /> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/player-settings.css") }}" /> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/player-options.css") }}" />
<script type="application/ecmascript" src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script type="application/ecmascript" src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/md5.min.js") }}"></script> <script type="application/ecmascript" src="{{ url_for('static', filename="assets/md5.min.js") }}"></script>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/js-yaml.min.js") }}"></script> <script type="application/ecmascript" src="{{ url_for('static', filename="assets/js-yaml.min.js") }}"></script>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/player-settings.js") }}"></script> <script type="application/ecmascript" src="{{ url_for('static', filename="assets/player-options.js") }}"></script>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% include 'header/'+theme+'Header.html' %} {% include 'header/'+theme+'Header.html' %}
<div id="player-settings" class="markdown" data-game="{{ game }}"> <div id="player-options" class="markdown" data-game="{{ game }}">
<div id="user-message"></div> <div id="user-message"></div>
<h1><span id="game-name">Player</span> Settings</h1> <h1><span id="game-name">Player</span> Options</h1>
<p>Choose the options you would like to play with! You may generate a single-player game from this page, <p>Choose the options you would like to play with! You may generate a single-player game from this page,
or download a settings file you can use to participate in a MultiWorld.</p> or download an options file you can use to participate in a MultiWorld.</p>
<p> <p>
A more advanced settings configuration for all games can be found on the A more advanced options configuration for all games can be found on the
<a href="/weighted-settings">Weighted Settings</a> page. <a href="/weighted-options">Weighted options</a> page.
<br /> <br />
A list of all games you have generated can be found on the <a href="/user-content">User Content Page</a>. A list of all games you have generated can be found on the <a href="/user-content">User Content Page</a>.
<br /> <br />
@ -39,8 +39,8 @@
<div id="game-options-right" class="right"></div> <div id="game-options-right" class="right"></div>
</div> </div>
<div id="player-settings-button-row"> <div id="player-options-button-row">
<button id="export-settings">Export Settings</button> <button id="export-options">Export Options</button>
<button id="generate-game">Generate Game</button> <button id="generate-game">Generate Game</button>
<button id="generate-race">Generate Race</button> <button id="generate-race">Generate Race</button>
</div> </div>

View File

@ -24,7 +24,7 @@
<li><a href="/games">Supported Games Page</a></li> <li><a href="/games">Supported Games Page</a></li>
<li><a href="/tutorial">Tutorials Page</a></li> <li><a href="/tutorial">Tutorials Page</a></li>
<li><a href="/user-content">User Content</a></li> <li><a href="/user-content">User Content</a></li>
<li><a href="/weighted-settings">Weighted Settings Page</a></li> <li><a href="/weighted-options">Weighted Options Page</a></li>
<li><a href="{{url_for('stats')}}">Game Statistics</a></li> <li><a href="{{url_for('stats')}}">Game Statistics</a></li>
<li><a href="/glossary/en">Glossary</a></li> <li><a href="/glossary/en">Glossary</a></li>
</ul> </ul>
@ -46,11 +46,11 @@
{% endfor %} {% endfor %}
</ul> </ul>
<h2>Game Settings Pages</h2> <h2>Game Options Pages</h2>
<ul> <ul>
{% for game in games | title_sorted %} {% for game in games | title_sorted %}
{% if game['has_settings'] %} {% if game['has_settings'] %}
<li><a href="{{ url_for('player_settings', game=game['title']) }}">{{ game['title'] }}</a></li> <li><a href="{{ url_for('player_options', game=game['title']) }}">{{ game['title'] }}</a></li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -51,12 +51,12 @@
<span class="link-spacer">|</span> <span class="link-spacer">|</span>
<a href="{{ url_for("tutorial_landing") }}#{{ game_name }}">Setup Guides</a> <a href="{{ url_for("tutorial_landing") }}#{{ game_name }}">Setup Guides</a>
{% endif %} {% endif %}
{% if world.web.settings_page is string %} {% if world.web.options_page is string %}
<span class="link-spacer">|</span> <span class="link-spacer">|</span>
<a href="{{ world.web.settings_page }}">Settings Page</a> <a href="{{ world.web.settings_page }}">Options Page</a>
{% elif world.web.settings_page %} {% elif world.web.options_page %}
<span class="link-spacer">|</span> <span class="link-spacer">|</span>
<a href="{{ url_for("player_settings", game=game_name) }}">Settings Page</a> <a href="{{ url_for("player_options", game=game_name) }}">Options Page</a>
{% endif %} {% endif %}
{% if world.web.bug_report_page %} {% if world.web.bug_report_page %}
<span class="link-spacer">|</span> <span class="link-spacer">|</span>

View File

@ -1,26 +1,26 @@
{% extends 'pageWrapper.html' %} {% extends 'pageWrapper.html' %}
{% block head %} {% block head %}
<title>{{ game }} Settings</title> <title>{{ game }} Options</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" /> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/weighted-settings.css") }}" /> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/weighted-options.css") }}" />
<script type="application/ecmascript" src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script type="application/ecmascript" src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/md5.min.js") }}"></script> <script type="application/ecmascript" src="{{ url_for('static', filename="assets/md5.min.js") }}"></script>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/js-yaml.min.js") }}"></script> <script type="application/ecmascript" src="{{ url_for('static', filename="assets/js-yaml.min.js") }}"></script>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/weighted-settings.js") }}"></script> <script type="application/ecmascript" src="{{ url_for('static', filename="assets/weighted-options.js") }}"></script>
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% include 'header/grassHeader.html' %} {% include 'header/grassHeader.html' %}
<div id="weighted-settings" class="markdown" data-game="{{ game }}"> <div id="weighted-settings" class="markdown" data-game="{{ game }}">
<div id="user-message"></div> <div id="user-message"></div>
<h1>Weighted Settings</h1> <h1>Weighted Options</h1>
<p>Weighted Settings allows you to choose how likely a particular option is to be used in game generation. <p>Weighted options allow you to choose how likely a particular option is to be used in game generation.
The higher an option is weighted, the more likely the option will be chosen. Think of them like The higher an option is weighted, the more likely the option will be chosen. Think of them like
entries in a raffle.</p> entries in a raffle.</p>
<p>Choose the games and options you would like to play with! You may generate a single-player game from <p>Choose the games and options you would like to play with! You may generate a single-player game from
this page, or download a settings file you can use to participate in a MultiWorld.</p> this page, or download an options file you can use to participate in a MultiWorld.</p>
<p>A list of all games you have generated can be found on the <a href="/user-content">User Content</a> <p>A list of all games you have generated can be found on the <a href="/user-content">User Content</a>
page.</p> page.</p>
@ -40,7 +40,7 @@
</div> </div>
<div id="weighted-settings-button-row"> <div id="weighted-settings-button-row">
<button id="export-settings">Export Settings</button> <button id="export-options">Export Options</button>
<button id="generate-game">Generate Game</button> <button id="generate-game">Generate Game</button>
<button id="generate-race">Generate Race</button> <button id="generate-race">Generate Race</button>
</div> </div>

View File

@ -149,7 +149,7 @@ def call_stage(multiworld: "MultiWorld", method_name: str, *args: Any) -> None:
class WebWorld: class WebWorld:
"""Webhost integration""" """Webhost integration"""
settings_page: Union[bool, str] = True options_page: Union[bool, str] = True
"""display a settings page. Can be a link to a specific page or external tool.""" """display a settings page. Can be a link to a specific page or external tool."""
game_info_languages: List[str] = ['en'] game_info_languages: List[str] = ['en']

View File

@ -5,7 +5,7 @@ from ..AutoWorld import WebWorld, World
class Bk_SudokuWebWorld(WebWorld): class Bk_SudokuWebWorld(WebWorld):
settings_page = "games/Sudoku/info/en" options_page = "games/Sudoku/info/en"
theme = 'partyTime' theme = 'partyTime'
tutorials = [ tutorials = [
Tutorial( Tutorial(

View File

@ -14,7 +14,7 @@ class FF1Settings(settings.Group):
class FF1Web(WebWorld): class FF1Web(WebWorld):
settings_page = "https://finalfantasyrandomizer.com/" options_page = "https://finalfantasyrandomizer.com/"
tutorials = [Tutorial( tutorials = [Tutorial(
"Multiworld Setup Guide", "Multiworld Setup Guide",
"A guide to playing Final Fantasy multiworld. This guide only covers playing multiworld.", "A guide to playing Final Fantasy multiworld. This guide only covers playing multiworld.",