Player Settings fully operational, but missing sprite options

This commit is contained in:
Chris Wilson 2020-08-27 21:31:18 -04:00
parent 366c92de49
commit b5ee2edca5
4 changed files with 1336 additions and 693 deletions

View File

@ -6,9 +6,9 @@ window.addEventListener('load', () => {
// Update localStorage with three settings objects. Preserve original objects if present. // Update localStorage with three settings objects. Preserve original objects if present.
for (let i=1; i<=3; i++) { for (let i=1; i<=3; i++) {
const localSettings = JSON.parse(localStorage.getItem(`gameSettings${i}`)); const localSettings = JSON.parse(localStorage.getItem(`playerSettings${i}`));
const updatedObj = localSettings ? Object.assign(sourceData, localSettings) : sourceData; const updatedObj = localSettings ? Object.assign(sourceData, localSettings) : sourceData;
localStorage.setItem(`gameSettings${i}`, JSON.stringify(updatedObj)); localStorage.setItem(`playerSettings${i}`, JSON.stringify(updatedObj));
} }
// Build the entire UI // Build the entire UI
@ -19,6 +19,8 @@ window.addEventListener('load', () => {
document.getElementById('preset-number').addEventListener('change', populateSettings); document.getElementById('preset-number').addEventListener('change', populateSettings);
gameSettings.addEventListener('change', handleOptionChange); gameSettings.addEventListener('change', handleOptionChange);
gameSettings.addEventListener('keyup', handleOptionChange); gameSettings.addEventListener('keyup', handleOptionChange);
document.getElementById('export-button').addEventListener('click', exportSettings);
}).catch((error) => { }).catch((error) => {
gameSettings.innerHTML = ` gameSettings.innerHTML = `
<h2>Something went wrong while loading your game settings page.</h2> <h2>Something went wrong while loading your game settings page.</h2>
@ -59,20 +61,20 @@ const fetchPlayerSettingsJson = () => new Promise((resolve, reject) => {
const handleOptionChange = (event) => { const handleOptionChange = (event) => {
if(!event.target.matches('.setting')) { return; } if(!event.target.matches('.setting')) { return; }
const presetNumber = document.getElementById('preset-number').value; const presetNumber = document.getElementById('preset-number').value;
const settings = JSON.parse(localStorage.getItem(`gameSettings${presetNumber}`)) const settings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`))
const settingString = event.target.getAttribute('data-setting'); const settingString = event.target.getAttribute('data-setting');
document.getElementById(settingString).innerText = event.target.value; document.getElementById(settingString).innerText = event.target.value;
if(getSettingValue(settings, settingString) !== false){ if(getSettingValue(settings, settingString) !== false){
const keys = settingString.split('.'); const keys = settingString.split('.');
switch (keys.length) { switch (keys.length) {
case 1: case 1:
settings[keys[0]] = event.target.value; settings[keys[0]] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
break; break;
case 2: case 2:
settings[keys[0]][keys[1]] = event.target.value; settings[keys[0]][keys[1]] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
break; break;
case 3: case 3:
settings[keys[0]][keys[1]][keys[2]] = event.target.value; settings[keys[0]][keys[1]][keys[2]] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
break; break;
default: default:
console.warn(`Unknown setting string received: ${settingString}`) console.warn(`Unknown setting string received: ${settingString}`)
@ -80,7 +82,7 @@ const handleOptionChange = (event) => {
} }
// Save the updated settings object bask to localStorage // Save the updated settings object bask to localStorage
localStorage.setItem(`gameSettings${presetNumber}`, JSON.stringify(settings)); localStorage.setItem(`playerSettings${presetNumber}`, JSON.stringify(settings));
}else{ }else{
console.warn(`Unknown setting string received: ${settingString}`) console.warn(`Unknown setting string received: ${settingString}`)
} }
@ -88,7 +90,7 @@ const handleOptionChange = (event) => {
const populateSettings = () => { const populateSettings = () => {
const presetNumber = document.getElementById('preset-number').value; const presetNumber = document.getElementById('preset-number').value;
const settings = JSON.parse(localStorage.getItem(`gameSettings${presetNumber}`)) const settings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`))
const settingsInputs = Array.from(document.querySelectorAll('.setting')); const settingsInputs = Array.from(document.querySelectorAll('.setting'));
settingsInputs.forEach((input) => { settingsInputs.forEach((input) => {
const settingString = input.getAttribute('data-setting'); const settingString = input.getAttribute('data-setting');
@ -119,9 +121,37 @@ const getSettingValue = (settings, keyString) => {
return currentVal; return currentVal;
}; };
const exportSettings = () => {
const presetNumber = document.getElementById('preset-number').value;
const settings = JSON.parse(localStorage.getItem(`playerSettings${presetNumber}`));
const yamlText = jsyaml.safeDump(settings);
download(`${settings.description}.yaml`, yamlText);
};
/** Create an anchor and trigger a download of a text file. */
const download = (filename, text) => {
const downloadLink = document.createElement('a');
downloadLink.setAttribute('href','data:text/yaml;charset=utf-8,'+ encodeURIComponent(text))
downloadLink.setAttribute('download', filename);
downloadLink.style.display = 'none';
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
};
const buildUI = (settings) => { const buildUI = (settings) => {
const settingsWrapper = document.getElementById('settings-wrapper'); const settingsWrapper = document.getElementById('settings-wrapper');
Object.values(settings).forEach((setting) => { const settingTypes = {
gameOptions: 'Game Options',
romOptions: 'ROM Options'
}
Object.keys(settingTypes).forEach((settingTypeKey) => {
const sectionHeader = document.createElement('h1');
sectionHeader.innerText = settingTypes[settingTypeKey];
settingsWrapper.appendChild(sectionHeader);
Object.values(settings[settingTypeKey]).forEach((setting) => {
if (typeof(setting.inputType) === 'undefined' || !setting.inputType){ if (typeof(setting.inputType) === 'undefined' || !setting.inputType){
console.error(setting); console.error(setting);
throw new Error('Setting with no inputType specified.'); throw new Error('Setting with no inputType specified.');
@ -139,6 +169,7 @@ const buildUI = (settings) => {
throw new Error('Unhandled inputType specified.'); throw new Error('Unhandled inputType specified.');
} }
}); });
});
}; };
const buildRangeSettings = (parentElement, settings) => { const buildRangeSettings = (parentElement, settings) => {

View File

@ -1,4 +1,5 @@
{ {
"gameOptions": {
"description": { "description": {
"keyString": "description", "keyString": "description",
"friendlyName": "Description", "friendlyName": "Description",
@ -699,5 +700,608 @@
"defaultValue": 0 "defaultValue": 0
} }
} }
},
"progression_balancing": {
"keyString": "progression_balancing",
"friendlyName": "Progression Balancing",
"description": "A system to reduce time spent in BK mode. It moves your items into an earlier access sphere to make it more likely you have access to progression items.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "progression_balancing.on",
"friendlyName": "On",
"description": "Enable progression balancing.",
"defaultValue": 50
},
"off": {
"keyString": "progression_balancing.off",
"friendlyName": "Off",
"description": "Disable progression balancing.",
"defaultValue": 0
}
}
},
"boss_shuffle": {
"keyString": "boss_shuffle",
"friendlyName": "Boss Shuffle",
"description": "Determines which boss appears in which dungeon.",
"inputType": "range",
"subOptions": {
"none": {
"keyString": "boss_shuffle.none",
"friendlyName": "None",
"description": "Bosses appear in vanilla locations.",
"defaultValue": 50
},
"simple": {
"keyString": "boss_shuffle.simple",
"friendlyName": "Simple",
"description": "Existing bosses except Ganon and Agahnim are shuffled throughout dungeons.",
"defaultValue": 0
},
"full": {
"keyString": "boss_shuffle.full",
"friendlyName": "Full",
"description": "Bosses are shuffled, and three of them may occur twice.",
"defaultValue": 0
},
"random": {
"keyString": "boss_shuffle.random",
"friendlyName": "Random",
"description": "Any boss may appear any number of times.",
"defaultValue": 0
},
"singularity": {
"keyString": "boss_shuffle.singularity",
"friendlyName": "Singularity",
"description": "Picks a boss at random and puts it in every dungeon it can appear in. Remaining dungeons bosses are chosen at random.",
"defaultValue": 0
}
}
},
"enemy_shuffle": {
"keyString": "enemy_shuffle",
"friendlyName": "Enemy Shuffle",
"description": "Randomizes which enemies appear throughout the game.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "enemy_shuffle.on",
"friendlyName": "On",
"description": "Enable enemy shuffle.",
"defaultValue": 0
},
"off": {
"keyString": "enemy_shuffle.off",
"friendlyName": "Off",
"description": "Disable enemy shuffle.",
"defaultValue": 50
}
}
},
"killable_thieves": {
"keyString": "killable_thieves",
"friendlyName": "Killable Thieves",
"description": "Determines whether thieves may be killed or not.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "killable_thieves.on",
"friendlyName": "On",
"description": "Thieves are mortal.",
"defaultValue": 0
},
"off": {
"keyString": "killable_thieves.off",
"friendlyName": "Off",
"description": "Thieves are invulnerable.",
"defaultValue": 50
}
}
},
"tile_shuffle": {
"keyString": "tile_shuffle",
"friendlyName": "Tile Shuffle",
"description": "Randomizes tile layouts in rooms where floor tiles attack you.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "tile_shuffle.on",
"friendlyName": "On",
"description": "Enable tile shuffle.",
"defaultValue": 0
},
"off": {
"keyString": "tile_shuffle.off",
"friendlyName": "Off",
"description": "Disable tile shuffle.",
"defaultValue": 50
}
}
},
"bush_shuffle": {
"keyString": "bush_shuffle",
"friendlyName": "Bush Shuffle",
"description": "Randomize the chance that bushes around Hyrule have enemies hiding under them.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "bush_shuffle.on",
"friendlyName": "On",
"description": "Enable bush shuffle.",
"defaultValue": 0
},
"off": {
"keyString": "bush_shuffle.off",
"friendlyName": "Off",
"description": "Disable bush shuffle.",
"defaultValue": 50
}
}
},
"enemy_damage": {
"keyString": "enemy_damage",
"friendlyName": "Enemy Damage",
"description": "Randomizes how much damage enemies can deal to you.",
"inputType": "range",
"subOptions": {
"default": {
"keyString": "enemy_damage.default",
"friendlyName": "Vanilla Damage",
"description": "Enemies deal the same damage as in the vanilla game.",
"defaultValue": 50
},
"shuffled": {
"keyString": "enemy_damage.shuffled",
"friendlyName": "Shuffled",
"description": "Enemies deal zero to four hearts of damage, and armor reduces this damage.",
"defaultValue": 0
},
"random": {
"keyString": "enemy_damage.random",
"friendlyName": "Random",
"description": "Enemies may deal zero through eight hearts of damage, and armor re-shuffles how much damage you take from each enemy.",
"defaultValue": 0
}
}
},
"enemy_health": {
"keyString": "enemy_health",
"friendlyName": "Enemy Health",
"description": "Randomizes the amount of health enemies have. Does not affect bosses.",
"inputType": "range",
"subOptions": {
"default": {
"keyString": "enemy_health.default",
"friendlyName": "Vanilla",
"description": "Enemies have the same amount of health as in the vanilla game.",
"defaultValue": 50
},
"easy": {
"keyString": "enemy_health.easy",
"friendlyName": "Reduced",
"description": "Enemies have generally reduced health.",
"defaultValue": 0
},
"hard": {
"keyString": "enemy_health.hard",
"friendlyName": "Increased",
"description": "Enemies have generally increased health.",
"defaultValue": 0
},
"expert": {
"keyString": "enemy_health.expert",
"friendlyName": "Armor-Plated",
"description": "Enemies will be very heard to defeat.",
"defaultValue": 0
}
}
},
"pot_shuffle": {
"keyString": "pot_shuffle",
"friendlyName": "Pot Shuffle",
"description": "Keys, items, and buttons hidden under pots in dungeons may be shuffled with other pots in their super-tile.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "pot_shuffle.on",
"friendlyName": "On",
"description": "Enable pot shuffle.",
"defaultValue": 0
},
"off": {
"keyString": "pot_shuffle.off",
"friendlyName": "Off",
"description": "Disable pot shuffle.",
"defaultValue": 50
}
}
},
"beemizer": {
"keyString": "beemizer",
"friendlyName": "Beemizer",
"description": "Remove items from the global item pool and replace them with single bees and bee traps.",
"inputType": "range",
"subOptions": {
"0": {
"keyString": "beemizer.0",
"friendlyName": "Level 0",
"description": "No bee traps are placed.",
"defaultValue": 50
},
"1": {
"keyString": "beemizer.1",
"friendlyName": "Level 1",
"description": "25% of the non-essential item pool is replaced with bee traps.",
"defaultValue": 1
},
"2": {
"keyString": "beemizer.2",
"friendlyName": "Level 2",
"description": "60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees.",
"defaultValue": 2
},
"3": {
"keyString": "beemizer.3",
"friendlyName": "Level 3",
"description": "100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees.",
"defaultValue": 3
},
"4": {
"keyString": "beemizer.4",
"friendlyName": "Level 4",
"description": "100% of the non-essential item pool is replaced with bee traps.",
"defaultValue": 4
}
}
},
"shop_shuffle": {
"keyString": "shop_shuffle",
"friendlyName": "Shop Shuffle",
"description": "Alters the inventory and prices of shops.",
"inputType": "range",
"subOptions": {
"none": {
"keyString": "shop_shuffle.none",
"friendlyName": "Vanilla Shops",
"description": "Shop contents are left unchanged.",
"defaultValue": 50
},
"i": {
"keyString": "shop_shuffle.i",
"friendlyName": "Inventory Shuffle",
"description": "Randomizes the inventories of shops.",
"defaultValue": 0
},
"p": {
"keyString": "shop_shuffle.p",
"friendlyName": "Price Shuffle",
"description": "Randomizes the price of items sold in shops.",
"defaultValue": 0
},
"u": {
"keyString": "shop_shuffle.u",
"friendlyName": "Capacity Upgrades",
"description": "Shuffles capacity upgrades throughout the game world.",
"defaultValue": 0
},
"ip": {
"keyString": "shop_shuffle.ip",
"friendlyName": "Inventory & Prices",
"description": "Shuffles the inventory and randomizes the prices of items in shops.",
"defaultValue": 0
},
"uip": {
"keyString": "shop_shuffle.uip",
"friendlyName": "Full Shuffle",
"description": "Shuffles the inventory and randomizes the prices of items in shops. Also distributes capacity upgrades throughout the world.",
"defaultValue": 0
}
}
},
"timer": {
"keyString": "timer",
"friendlyName": "Timed Modes",
"description": "Add a timer to the game UI, and cause it to have various effects.",
"inputType": "range",
"subOptions": {
"none": {
"keyString": "timer.none",
"friendlyName": "Disabled",
"description": "No timed mode is applied to the game.",
"defaultValue": 50
},
"timed": {
"keyString": "timer.timed",
"friendlyName": "Timed Mode",
"description": "Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end.",
"defaultValue": 0
},
"timed_ohko": {
"keyString": "timer.times_ohko",
"friendlyName": "Timed OHKO",
"description": "Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit.",
"defaultValue": 0
},
"ohko": {
"keyString": "timer.ohko",
"friendlyName": "One-Hit KO",
"description": "Timer always at zero. Permanent OHKO.",
"defaultValue": 0
},
"timed_countdown": {
"keyString": "timer.timed_countdown",
"friendlyName": "Timed Countdown",
"description": "Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.",
"defaultValue": 0
},
"display": {
"keyString": "timer.display",
"friendlyName": "Timer Only",
"description": "Displays a timer, but otherwise does not affect gameplay or the item pool.",
"defaultValue": 0
}
}
},
"glitch_boots": {
"keyString": "glitch_boots",
"friendlyName": "Glitch Boots",
"description": "Start with Pegasus Boots in any glitched logic mode that makes use of them.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "glitch_boots.on",
"friendlyName": "On",
"description": "Enable glitch boots.",
"defaultValue": 50
},
"off": {
"keyString": "glitch_boots.off",
"friendlyName": "Off",
"description": "Disable glitch boots.",
"defaultValue": 0
}
}
},
"door_shuffle": {
"keyString": "door_shuffle",
"friendlyName": "Door Shuffle",
"description": "Shuffles the interior layout of dungeons. Only available if the host rolls the game using the doors version of the generator.",
"inputType": "range",
"subOptions": {
"vanilla": {
"keyString": "door_shuffle.vanilla",
"friendlyName": "Vanilla",
"description": "Doors within dungeons remain unchanged from the vanilla game.",
"defaultValue": 50
},
"basic": {
"keyString": "door_shuffle.basic",
"friendlyName": "Basic",
"description": "Dungeons are shuffled within themselves.",
"defaultValue": 0
},
"crossed": {
"keyString": "door_shuffle.crossed",
"friendlyName": "Crossed",
"description": "Dungeons are shuffled across each other. Eastern may contain POD, Mire, and Hera.",
"defaultValue": 0
}
}
}
},
"romOptions": {
"disablemusic": {
"keyString": "rom.disablemusic",
"friendlyName": "Game Music",
"description": "Enable or disable all in-game music. Sound-effects are unaffected.",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "rom.disablemusic.on",
"friendlyName": "Disabled",
"description": "Disables in-game music.",
"defaultValue": 0
},
"off": {
"keyString": "rom.disablemusic.off",
"friendlyName": "Enabled",
"description": "Enables in-game music.",
"defaultValue": 50
}
}
},
"quickswap": {
"keyString": "rom.quickswap",
"friendlyName": "Item Quick-Swap",
"description": "Quickly change items by pressing the L+R shoulder buttons. Pressing L+R at the same time toggles the in-slot item (arrows and silver arrows, for example).",
"inputType": "range",
"subOptions": {
"on": {
"keyString": "rom.quickswap.on",
"friendlyName": "Enabled",
"description": "Enable quick-swap.",
"defaultValue": 0
},
"off": {
"keyString": "rom.quickswap.off",
"friendlyName": "Disabled",
"description": "Disable quick-swap.",
"defaultValue": 50
}
}
},
"menuspeed": {
"keyString": "menuspeed",
"friendlyName": "Menu Speed",
"description": "Choose how fast the in-game menu opens and closes.",
"inputType": "range",
"subOptions": {
"normal": {
"keyString": "rom.menuspeed.normal",
"friendlyName": "Vanilla",
"description": "Menu speed is unchanged from the vanilla game.",
"defaultValue": 50
},
"instant": {
"keyString": "rom.menuspeed.instant",
"friendlyName": "Instant",
"description": "The in-game menu appears and disappears instantly.",
"defaultValue": 0
},
"double": {
"keyString": "rom.menuspeed.double",
"friendlyName": "Double Speed",
"description": "The in-game menu animation moves at double speed.",
"defaultValue": 0
},
"triple": {
"keyString": "rom.menuspeed.triple",
"friendlyName": "Triple Speed",
"description": "The in-game menu animation moves at triple speed.",
"defaultValue": 0
},
"quadruple": {
"keyString": "rom.menuspeed.quadruple",
"friendlyName": "Quadruple Speed",
"description": "The in-game menu animation moves at quadruple speed.",
"defaultValue": 0
},
"half": {
"keyString": "rom.menuspeed.half",
"friendlyName": "Half Speed",
"description": "The in-game menu animation moves at half speed.",
"defaultValue": 0
}
}
},
"heartcolor": {
"keyString": "rom.heartcolor",
"friendlyName": "Heart Color",
"description": "Changes the color of your in-game health hearts.",
"inputType": "range",
"subOptions": {
"red": {
"keyString": "rom.heartcolor.red",
"friendlyName": "Red",
"description": "Health hearts are red.",
"defaultValue": 50
},
"blue": {
"keyString": "rom.heartcolor.blue",
"friendlyName": "Blue",
"description": "Health hearts are blue.",
"defaultValue": 0
},
"green": {
"keyString": "rom.heartcolor.green",
"friendlyName": "Green",
"description": "Health hearts are green.",
"defaultValue": 0
},
"yellow": {
"keyString": "rom.heartcolor.yellow",
"friendlyName": "Yellow",
"description": "Health hearts are yellow.",
"defaultValue": 0
},
"random": {
"keyString": "rom.heartcolor.random",
"friendlyName": "Random",
"description": "Health heart color is chosen randomly from red, green, blue, and yellow.",
"defaultValue": 0
}
}
},
"heartbeep": {
"keyString": "rom.heartbeep",
"friendlyName": "Heart Beep Speed",
"description": "Controls the frequency of the low-health beeping.",
"inputType": "range",
"subOptions": {
"double": {
"keyString": "rom.heartbeep.double",
"friendlyName": "Double",
"description": "Doubles the frequency of the low-health beep.",
"defaultValue": 0
},
"normal": {
"keyString": "rom.heartbeep.normal",
"friendlyName": "Vanilla",
"description": "Heart beep frequency is unchanged from the vanilla game.",
"defaultValue": 50
},
"half": {
"keyString": "rom.heartbeep.half",
"friendlyName": "Half Speed",
"description": "Heart beep plays at half-speed.",
"defaultValue": 0
},
"quarter": {
"keyString": "rom.heartbeep.quarter",
"friendlyName": "Quarter Speed",
"description": "Heart beep plays at one quarter-speed.",
"defaultValue": 0
},
"off": {
"keyString": "rom.heartbeep.off",
"friendlyName": "Disabled",
"description": "Disables the low-health heart beep.",
"defaultValue": 0
}
}
},
"ow_palettes": {
"keyString": "rom.ow_palettes",
"friendlyName": "Overworld Palette",
"description": "Randomize the colors of the overworld, within reason.",
"inputType": "range",
"subOptions": {
"default": {
"keyString": "rom.ow_palettes.default",
"friendlyName": "Vanilla",
"description": "Overworld colors will remain unchanged.",
"defaultValue": 50
},
"random": {
"keyString": "rom.ow_palettes.random",
"friendlyName": "Random",
"description": "Shuffles the colors of the overworld palette.",
"defaultValue": 0
},
"blackout": {
"keyString": "rom.ow_palettes.blackout",
"friendlyName": "Blackout",
"description": "Never use this. Makes all overworld palette colors black.",
"defaultValue": 0
}
}
},
"uw_palettes": {
"keyString": "rom.uw_palettes",
"friendlyName": "Underworld Palettes",
"description": "Randomize the colors of the underworld (caves, dungeons, etc.), within reason.",
"inputType": "range",
"subOptions": {
"default": {
"keyString": "rom.uw_palettes.default",
"friendlyName": "Vanilla",
"description": "Underworld colors will remain unchanged.",
"defaultValue": 50
},
"random": {
"keyString": "rom.uw_palettes.random",
"friendlyName": "Random",
"description": "Shuffles the colors of the underworld palette.",
"defaultValue": 0
},
"blackout": {
"keyString": "rom.uw_palettes.blackout",
"friendlyName": "Blackout",
"description": "Never use this. Makes all underworld palette colors black.",
"defaultValue": 0
}
}
}
} }
} }

View File

@ -50,3 +50,8 @@
min-width: 300px; min-width: 300px;
vertical-align: middle; vertical-align: middle;
} }
#game-settings #game-settings-button-row{
width: 100%;
text-align: center;
}

View File

@ -1,7 +1,7 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block head %} {% block head %}
<title>Game Settings</title> <title>Player Settings</title>
<link rel="stylesheet" type="text/css" href="{{ static_autoversion("styles/player-settings.css") }}" /> <link rel="stylesheet" type="text/css" href="{{ static_autoversion("styles/player-settings.css") }}" />
<script type="application/ecmascript" src="{{ static_autoversion("assets/js-yaml.min.js") }}"></script> <script type="application/ecmascript" src="{{ static_autoversion("assets/js-yaml.min.js") }}"></script>
<script type="application/ecmascript" src="{{ static_autoversion("assets/player-settings.js") }}"></script> <script type="application/ecmascript" src="{{ static_autoversion("assets/player-settings.js") }}"></script>
@ -61,5 +61,8 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div id="game-settings-button-row">
<button id="export-button">Export Settings</button>
</div>
</div> </div>
{% endblock %} {% endblock %}