diff --git a/WebHostLib/static/assets/weighted-settings.js b/WebHostLib/static/assets/weighted-settings.js
index fb7d3a34..2cd61d2e 100644
--- a/WebHostLib/static/assets/weighted-settings.js
+++ b/WebHostLib/static/assets/weighted-settings.js
@@ -1,14 +1,14 @@
window.addEventListener('load', () => {
- fetchSettingData().then((results) => {
+ fetchSettingData().then((data) => {
let settingHash = localStorage.getItem('weighted-settings-hash');
if (!settingHash) {
// If no hash data has been set before, set it now
- settingHash = md5(JSON.stringify(results));
+ settingHash = md5(JSON.stringify(data));
localStorage.setItem('weighted-settings-hash', settingHash);
localStorage.removeItem('weighted-settings');
}
- if (settingHash !== md5(JSON.stringify(results))) {
+ if (settingHash !== md5(JSON.stringify(data))) {
const userMessage = document.getElementById('user-message');
userMessage.innerText = "Your settings are out of date! Click here to update them! Be aware this will reset " +
"them all to default.";
@@ -17,23 +17,22 @@ window.addEventListener('load', () => {
}
// Page setup
- createDefaultSettings(results);
- buildUI(results);
- updateVisibleGames();
+ const settings = new WeightedSettings(data);
+ settings.buildUI();
+ settings.updateVisibleGames();
adjustHeaderWidth();
// Event listeners
- document.getElementById('export-settings').addEventListener('click', () => exportSettings());
- document.getElementById('generate-race').addEventListener('click', () => generateGame(true));
- document.getElementById('generate-game').addEventListener('click', () => generateGame());
+ document.getElementById('export-settings').addEventListener('click', () => settings.export());
+ document.getElementById('generate-race').addEventListener('click', () => settings.generateGame(true));
+ document.getElementById('generate-game').addEventListener('click', () => settings.generateGame());
// Name input field
- const weightedSettings = JSON.parse(localStorage.getItem('weighted-settings'));
const nameInput = document.getElementById('player-name');
nameInput.setAttribute('data-type', 'data');
nameInput.setAttribute('data-setting', 'name');
- nameInput.addEventListener('keyup', updateBaseSetting);
- nameInput.value = weightedSettings.name;
+ nameInput.addEventListener('keyup', (evt) => settings.updateBaseSetting(evt));
+ nameInput.value = settings.current.name;
});
});
@@ -50,48 +49,65 @@ const fetchSettingData = () => new Promise((resolve, reject) => {
});
});
-const createDefaultSettings = (settingData) => {
- if (!localStorage.getItem('weighted-settings')) {
- const newSettings = {};
+/// The weighted settings across all games.
+class WeightedSettings {
+ // The data from the server describing the types of settings available for
+ // each game, as a JSON-safe blob.
+ data;
+
+ // The settings chosen by the user as they'd appear in the YAML file, stored
+ // to and retrieved from local storage.
+ current;
+
+ // A record mapping game names to the associated GameSettings.
+ games;
+
+ constructor(data) {
+ this.data = data;
+ this.current = JSON.parse(localStorage.getItem('weighted-settings'));
+ this.games = Object.keys(this.data.games).map((game) => new GameSettings(this, game));
+ if (this.current) { return; }
+
+ this.current = {};
// Transfer base options directly
- for (let baseOption of Object.keys(settingData.baseOptions)){
- newSettings[baseOption] = settingData.baseOptions[baseOption];
+ for (let baseOption of Object.keys(this.data.baseOptions)){
+ this.current[baseOption] = this.data.baseOptions[baseOption];
}
// Set options per game
- for (let game of Object.keys(settingData.games)) {
+ for (let game of Object.keys(this.data.games)) {
// Initialize game object
- newSettings[game] = {};
+ this.current[game] = {};
// Transfer game settings
- for (let gameSetting of Object.keys(settingData.games[game].gameSettings)){
- newSettings[game][gameSetting] = {};
+ for (let gameSetting of Object.keys(this.data.games[game].gameSettings)){
+ this.current[game][gameSetting] = {};
- const setting = settingData.games[game].gameSettings[gameSetting];
+ const setting = this.data.games[game].gameSettings[gameSetting];
switch(setting.type){
case 'select':
setting.options.forEach((option) => {
- newSettings[game][gameSetting][option.value] =
+ this.current[game][gameSetting][option.value] =
(setting.hasOwnProperty('defaultValue') && setting.defaultValue === option.value) ? 25 : 0;
});
break;
case 'range':
case 'special_range':
- newSettings[game][gameSetting]['random'] = 0;
- newSettings[game][gameSetting]['random-low'] = 0;
- newSettings[game][gameSetting]['random-high'] = 0;
+ this.current[game][gameSetting]['random'] = 0;
+ this.current[game][gameSetting]['random-low'] = 0;
+ this.current[game][gameSetting]['random-high'] = 0;
if (setting.hasOwnProperty('defaultValue')) {
- newSettings[game][gameSetting][setting.defaultValue] = 25;
+ this.current[game][gameSetting][setting.defaultValue] = 25;
} else {
- newSettings[game][gameSetting][setting.min] = 25;
+ this.current[game][gameSetting][setting.min] = 25;
}
break;
case 'items-list':
case 'locations-list':
case 'custom-list':
- newSettings[game][gameSetting] = setting.defaultValue;
+ this.current[game][gameSetting] = setting.defaultValue;
break;
default:
@@ -99,33 +115,301 @@ const createDefaultSettings = (settingData) => {
}
}
- newSettings[game].start_inventory = {};
- newSettings[game].exclude_locations = [];
- newSettings[game].priority_locations = [];
- newSettings[game].local_items = [];
- newSettings[game].non_local_items = [];
- newSettings[game].start_hints = [];
- newSettings[game].start_location_hints = [];
+ this.current[game].start_inventory = {};
+ this.current[game].exclude_locations = [];
+ this.current[game].priority_locations = [];
+ this.current[game].local_items = [];
+ this.current[game].non_local_items = [];
+ this.current[game].start_hints = [];
+ this.current[game].start_location_hints = [];
}
- localStorage.setItem('weighted-settings', JSON.stringify(newSettings));
+ this.save();
}
-};
-const buildUI = (settingData) => {
- // Build the game-choice div
- buildGameChoice(settingData.games);
+ // Saves the current settings to local storage.
+ save() {
+ localStorage.setItem('weighted-settings', JSON.stringify(this.current));
+ }
- const gamesWrapper = document.getElementById('games-wrapper');
- Object.keys(settingData.games).forEach((game) => {
+ buildUI() {
+ // Build the game-choice div
+ this.#buildGameChoice();
+
+ const gamesWrapper = document.getElementById('games-wrapper');
+ this.games.forEach((game) => {
+ gamesWrapper.appendChild(game.buildUI());
+ });
+ }
+
+ #buildGameChoice() {
+ const gameChoiceDiv = document.getElementById('game-choice');
+ const h2 = document.createElement('h2');
+ h2.innerText = 'Game Select';
+ gameChoiceDiv.appendChild(h2);
+
+ const gameSelectDescription = document.createElement('p');
+ gameSelectDescription.classList.add('setting-description');
+ gameSelectDescription.innerText = 'Choose which games you might be required to play.';
+ gameChoiceDiv.appendChild(gameSelectDescription);
+
+ const hintText = document.createElement('p');
+ hintText.classList.add('hint-text');
+ hintText.innerText = 'If a game\'s value is greater than zero, you can click it\'s name to jump ' +
+ 'to that section.'
+ gameChoiceDiv.appendChild(hintText);
+
+ // Build the game choice table
+ const table = document.createElement('table');
+ const tbody = document.createElement('tbody');
+
+ Object.keys(this.data.games).forEach((game) => {
+ const tr = document.createElement('tr');
+ const tdLeft = document.createElement('td');
+ tdLeft.classList.add('td-left');
+ const span = document.createElement('span');
+ span.innerText = game;
+ span.setAttribute('id', `${game}-game-option`)
+ tdLeft.appendChild(span);
+ tr.appendChild(tdLeft);
+
+ const tdMiddle = document.createElement('td');
+ tdMiddle.classList.add('td-middle');
+ const range = document.createElement('input');
+ range.setAttribute('type', 'range');
+ range.setAttribute('min', 0);
+ range.setAttribute('max', 50);
+ range.setAttribute('data-type', 'weight');
+ range.setAttribute('data-setting', 'game');
+ range.setAttribute('data-option', game);
+ range.value = this.current.game[game];
+ range.addEventListener('change', (evt) => {
+ this.updateBaseSetting(evt);
+ this.updateVisibleGames(); // Show or hide games based on the new settings
+ });
+ tdMiddle.appendChild(range);
+ tr.appendChild(tdMiddle);
+
+ const tdRight = document.createElement('td');
+ tdRight.setAttribute('id', `game-${game}`)
+ tdRight.classList.add('td-right');
+ tdRight.innerText = range.value;
+ tr.appendChild(tdRight);
+ tbody.appendChild(tr);
+ });
+
+ table.appendChild(tbody);
+ gameChoiceDiv.appendChild(table);
+ }
+
+ // Verifies that `this.settings` meets all the requirements for world
+ // generation, normalizes it for serialization, and returns the result.
+ #validateSettings() {
+ const settings = structuredClone(this.current);
+ const userMessage = document.getElementById('user-message');
+ let errorMessage = null;
+
+ // User must choose a name for their file
+ if (!settings.name || settings.name.trim().length === 0 || settings.name.toLowerCase().trim() === 'player') {
+ userMessage.innerText = 'You forgot to set your player name at the top of the page!';
+ userMessage.classList.add('visible');
+ userMessage.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
+ return;
+ }
+
+ // Clean up the settings output
+ Object.keys(settings.game).forEach((game) => {
+ // Remove any disabled games
+ if (settings.game[game] === 0) {
+ delete settings.game[game];
+ delete settings[game];
+ return;
+ }
+
+ Object.keys(settings[game]).forEach((setting) => {
+ // Remove any disabled options
+ Object.keys(settings[game][setting]).forEach((option) => {
+ if (settings[game][setting][option] === 0) {
+ delete settings[game][setting][option];
+ }
+ });
+
+ if (
+ Object.keys(settings[game][setting]).length === 0 &&
+ !Array.isArray(settings[game][setting]) &&
+ setting !== 'start_inventory'
+ ) {
+ errorMessage = `${game} // ${setting} has no values above zero!`;
+ }
+
+ // Remove weights from options with only one possibility
+ if (
+ Object.keys(settings[game][setting]).length === 1 &&
+ !Array.isArray(settings[game][setting]) &&
+ setting !== 'start_inventory'
+ ) {
+ settings[game][setting] = Object.keys(settings[game][setting])[0];
+ }
+
+ // Remove empty arrays
+ else if (
+ ['exclude_locations', 'priority_locations', 'local_items',
+ 'non_local_items', 'start_hints', 'start_location_hints'].includes(setting) &&
+ settings[game][setting].length === 0
+ ) {
+ delete settings[game][setting];
+ }
+
+ // Remove empty start inventory
+ else if (
+ setting === 'start_inventory' &&
+ Object.keys(settings[game]['start_inventory']).length === 0
+ ) {
+ delete settings[game]['start_inventory'];
+ }
+ });
+ });
+
+ if (Object.keys(settings.game).length === 0) {
+ errorMessage = 'You have not chosen a game to play!';
+ }
+
+ // Remove weights if there is only one game
+ else if (Object.keys(settings.game).length === 1) {
+ settings.game = Object.keys(settings.game)[0];
+ }
+
+ // If an error occurred, alert the user and do not export the file
+ if (errorMessage) {
+ userMessage.innerText = errorMessage;
+ userMessage.classList.add('visible');
+ userMessage.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
+ return;
+ }
+
+ // If no error occurred, hide the user message if it is visible
+ userMessage.classList.remove('visible');
+ return settings;
+ }
+
+ updateVisibleGames() {
+ Object.entries(this.current.game).forEach(([game, weight]) => {
+ const gameDiv = document.getElementById(`${game}-div`);
+ const gameOption = document.getElementById(`${game}-game-option`);
+ if (parseInt(weight, 10) > 0) {
+ gameDiv.classList.remove('invisible');
+ gameOption.classList.add('jump-link');
+ gameOption.addEventListener('click', () => {
+ const gameDiv = document.getElementById(`${game}-div`);
+ if (gameDiv.classList.contains('invisible')) { return; }
+ gameDiv.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
+ });
+ } else {
+ gameDiv.classList.add('invisible');
+ gameOption.classList.remove('jump-link');
+ }
+ });
+ }
+
+ updateBaseSetting(event) {
+ const setting = event.target.getAttribute('data-setting');
+ const option = event.target.getAttribute('data-option');
+ const type = event.target.getAttribute('data-type');
+
+ switch(type){
+ case 'weight':
+ this.current[setting][option] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
+ document.getElementById(`${setting}-${option}`).innerText = event.target.value;
+ break;
+ case 'data':
+ this.current[setting] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
+ break;
+ }
+
+ this.save();
+ }
+
+ export() {
+ const settings = this.#validateSettings();
+ if (!settings) { return; }
+
+ const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`);
+ download(`${document.getElementById('player-name').value}.yaml`, yamlText);
+ }
+
+ generateGame(raceMode = false) {
+ const settings = this.#validateSettings();
+ if (!settings) { return; }
+
+ axios.post('/api/generate', {
+ weights: { player: JSON.stringify(settings) },
+ presetData: { player: JSON.stringify(settings) },
+ playerCount: 1,
+ spoiler: 3,
+ race: raceMode ? '1' : '0',
+ }).then((response) => {
+ window.location.href = response.data.url;
+ }).catch((error) => {
+ const userMessage = document.getElementById('user-message');
+ userMessage.innerText = 'Something went wrong and your game could not be generated.';
+ if (error.response.data.text) {
+ userMessage.innerText += ' ' + error.response.data.text;
+ }
+ userMessage.classList.add('visible');
+ userMessage.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
+ console.error(error);
+ });
+ }
+}
+
+// Settings for an individual game.
+class GameSettings {
+ // The WeightedSettings that contains this game's settings. Used to save
+ // settings after editing.
+ #allSettings;
+
+ // The name of this game.
+ name;
+
+ // The data from the server describing the types of settings available for
+ // this game, as a JSON-safe blob.
+ get data() {
+ return this.#allSettings.data.games[this.name];
+ }
+
+ // The settings chosen by the user as they'd appear in the YAML file, stored
+ // to and retrieved from local storage.
+ get current() {
+ return this.#allSettings.current[this.name];
+ }
+
+ constructor(allSettings, name) {
+ this.#allSettings = allSettings;
+ this.name = name;
+ }
+
+ // Builds and returns the settings UI for this game.
+ buildUI() {
// Create game div, invisible by default
const gameDiv = document.createElement('div');
- gameDiv.setAttribute('id', `${game}-div`);
+ gameDiv.setAttribute('id', `${this.name}-div`);
gameDiv.classList.add('game-div');
gameDiv.classList.add('invisible');
const gameHeader = document.createElement('h2');
- gameHeader.innerText = game;
+ gameHeader.innerText = this.name;
gameDiv.appendChild(gameHeader);
const collapseButton = document.createElement('a');
@@ -137,24 +421,22 @@ const buildUI = (settingData) => {
expandButton.classList.add('invisible');
gameDiv.appendChild(expandButton);
- settingData.games[game].gameItems.sort((a, b) => (a > b ? 1 : (a < b ? -1 : 0)));
- settingData.games[game].gameLocations.sort((a, b) => (a > b ? 1 : (a < b ? -1 : 0)));
+ // Sort items and locations alphabetically.
+ this.data.gameItems.sort();
+ this.data.gameLocations.sort();
- const weightedSettingsDiv = buildWeightedSettingsDiv(game, settingData.games[game].gameSettings,
- settingData.games[game].gameItems, settingData.games[game].gameLocations);
+ const weightedSettingsDiv = this.#buildWeightedSettingsDiv();
gameDiv.appendChild(weightedSettingsDiv);
- const itemPoolDiv = buildItemsDiv(game, settingData.games[game].gameItems);
+ const itemPoolDiv = this.#buildItemsDiv();
gameDiv.appendChild(itemPoolDiv);
- const hintsDiv = buildHintsDiv(game, settingData.games[game].gameItems, settingData.games[game].gameLocations);
+ const hintsDiv = this.#buildHintsDiv();
gameDiv.appendChild(hintsDiv);
- const locationsDiv = buildLocationsDiv(game, settingData.games[game].gameLocations);
+ const locationsDiv = this.#buildLocationsDiv();
gameDiv.appendChild(locationsDiv);
- gamesWrapper.appendChild(gameDiv);
-
collapseButton.addEventListener('click', () => {
collapseButton.classList.add('invisible');
weightedSettingsDiv.classList.add('invisible');
@@ -172,257 +454,145 @@ const buildUI = (settingData) => {
locationsDiv.classList.remove('invisible');
expandButton.classList.add('invisible');
});
- });
-};
-const buildGameChoice = (games) => {
- const settings = JSON.parse(localStorage.getItem('weighted-settings'));
- const gameChoiceDiv = document.getElementById('game-choice');
- const h2 = document.createElement('h2');
- h2.innerText = 'Game Select';
- gameChoiceDiv.appendChild(h2);
+ return gameDiv;
+ }
- const gameSelectDescription = document.createElement('p');
- gameSelectDescription.classList.add('setting-description');
- gameSelectDescription.innerText = 'Choose which games you might be required to play.';
- gameChoiceDiv.appendChild(gameSelectDescription);
+ #buildWeightedSettingsDiv() {
+ const settingsWrapper = document.createElement('div');
+ settingsWrapper.classList.add('settings-wrapper');
- const hintText = document.createElement('p');
- hintText.classList.add('hint-text');
- hintText.innerText = 'If a game\'s value is greater than zero, you can click it\'s name to jump ' +
- 'to that section.'
- gameChoiceDiv.appendChild(hintText);
+ Object.keys(this.data.gameSettings).forEach((settingName) => {
+ const setting = this.data.gameSettings[settingName];
+ const settingWrapper = document.createElement('div');
+ settingWrapper.classList.add('setting-wrapper');
- // Build the game choice table
- const table = document.createElement('table');
- const tbody = document.createElement('tbody');
+ const settingNameHeader = document.createElement('h4');
+ settingNameHeader.innerText = setting.displayName;
+ settingWrapper.appendChild(settingNameHeader);
- Object.keys(games).forEach((game) => {
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- const span = document.createElement('span');
- span.innerText = game;
- span.setAttribute('id', `${game}-game-option`)
- tdLeft.appendChild(span);
- tr.appendChild(tdLeft);
+ const settingDescription = document.createElement('p');
+ settingDescription.classList.add('setting-description');
+ settingDescription.innerText = setting.description.replace(/(\n)/g, ' ');
+ settingWrapper.appendChild(settingDescription);
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.setAttribute('data-type', 'weight');
- range.setAttribute('data-setting', 'game');
- range.setAttribute('data-option', game);
- range.value = settings.game[game];
- range.addEventListener('change', (evt) => {
- updateBaseSetting(evt);
- updateVisibleGames(); // Show or hide games based on the new settings
- });
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
+ switch(setting.type){
+ case 'select':
+ const optionTable = document.createElement('table');
+ const tbody = document.createElement('tbody');
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `game-${game}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
- tbody.appendChild(tr);
- });
-
- table.appendChild(tbody);
- gameChoiceDiv.appendChild(table);
-};
-
-const buildWeightedSettingsDiv = (game, settings, gameItems, gameLocations) => {
- const currentSettings = JSON.parse(localStorage.getItem('weighted-settings'));
- const settingsWrapper = document.createElement('div');
- settingsWrapper.classList.add('settings-wrapper');
-
- Object.keys(settings).forEach((settingName) => {
- const setting = settings[settingName];
- const settingWrapper = document.createElement('div');
- settingWrapper.classList.add('setting-wrapper');
-
- const settingNameHeader = document.createElement('h4');
- settingNameHeader.innerText = setting.displayName;
- settingWrapper.appendChild(settingNameHeader);
-
- const settingDescription = document.createElement('p');
- settingDescription.classList.add('setting-description');
- settingDescription.innerText = setting.description.replace(/(\n)/g, ' ');
- settingWrapper.appendChild(settingDescription);
-
- switch(setting.type){
- case 'select':
- const optionTable = document.createElement('table');
- const tbody = document.createElement('tbody');
-
- // Add a weight range for each option
- setting.options.forEach((option) => {
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- tdLeft.innerText = option.name;
- tr.appendChild(tdLeft);
-
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('data-game', game);
- range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', option.value);
- range.setAttribute('data-type', setting.type);
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.addEventListener('change', updateRangeSetting);
- range.value = currentSettings[game][settingName][option.value];
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
-
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${game}-${settingName}-${option.value}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
-
- tbody.appendChild(tr);
- });
-
- optionTable.appendChild(tbody);
- settingWrapper.appendChild(optionTable);
- break;
-
- case 'range':
- case 'special_range':
- const rangeTable = document.createElement('table');
- const rangeTbody = document.createElement('tbody');
-
- if (((setting.max - setting.min) + 1) < 11) {
- for (let i=setting.min; i <= setting.max; ++i) {
+ // Add a weight range for each option
+ setting.options.forEach((option) => {
const tr = document.createElement('tr');
const tdLeft = document.createElement('td');
tdLeft.classList.add('td-left');
- tdLeft.innerText = i;
+ tdLeft.innerText = option.name;
tr.appendChild(tdLeft);
const tdMiddle = document.createElement('td');
tdMiddle.classList.add('td-middle');
const range = document.createElement('input');
range.setAttribute('type', 'range');
- range.setAttribute('id', `${game}-${settingName}-${i}-range`);
- range.setAttribute('data-game', game);
+ range.setAttribute('data-game', this.name);
range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', i);
+ range.setAttribute('data-option', option.value);
+ range.setAttribute('data-type', setting.type);
range.setAttribute('min', 0);
range.setAttribute('max', 50);
- range.addEventListener('change', updateRangeSetting);
- range.value = currentSettings[game][settingName][i] || 0;
+ range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
+ range.value = this.current[settingName][option.value];
tdMiddle.appendChild(range);
tr.appendChild(tdMiddle);
const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${game}-${settingName}-${i}`)
+ tdRight.setAttribute('id', `${this.name}-${settingName}-${option.value}`);
tdRight.classList.add('td-right');
tdRight.innerText = range.value;
tr.appendChild(tdRight);
- rangeTbody.appendChild(tr);
- }
- } else {
- const hintText = document.createElement('p');
- hintText.classList.add('hint-text');
- hintText.innerHTML = 'This is a range option. You may enter a valid numerical value in the text box ' +
- `below, then press the "Add" button to add a weight for it.
Minimum value: ${setting.min}
` +
- `Maximum value: ${setting.max}`;
-
- if (setting.hasOwnProperty('value_names')) {
- hintText.innerHTML += '
Certain values have special meaning:';
- Object.keys(setting.value_names).forEach((specialName) => {
- hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`;
- });
- }
-
- settingWrapper.appendChild(hintText);
-
- const addOptionDiv = document.createElement('div');
- addOptionDiv.classList.add('add-option-div');
- const optionInput = document.createElement('input');
- optionInput.setAttribute('id', `${game}-${settingName}-option`);
- optionInput.setAttribute('placeholder', `${setting.min} - ${setting.max}`);
- addOptionDiv.appendChild(optionInput);
- const addOptionButton = document.createElement('button');
- addOptionButton.innerText = 'Add';
- addOptionDiv.appendChild(addOptionButton);
- settingWrapper.appendChild(addOptionDiv);
- optionInput.addEventListener('keydown', (evt) => {
- if (evt.key === 'Enter') { addOptionButton.dispatchEvent(new Event('click')); }
+ tbody.appendChild(tr);
});
- addOptionButton.addEventListener('click', () => {
- const optionInput = document.getElementById(`${game}-${settingName}-option`);
- let option = optionInput.value;
- if (!option || !option.trim()) { return; }
- option = parseInt(option, 10);
- if ((option < setting.min) || (option > setting.max)) { return; }
- optionInput.value = '';
- if (document.getElementById(`${game}-${settingName}-${option}-range`)) { return; }
+ optionTable.appendChild(tbody);
+ settingWrapper.appendChild(optionTable);
+ break;
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- tdLeft.innerText = option;
- tr.appendChild(tdLeft);
+ case 'range':
+ case 'special_range':
+ const rangeTable = document.createElement('table');
+ const rangeTbody = document.createElement('tbody');
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('id', `${game}-${settingName}-${option}-range`);
- range.setAttribute('data-game', game);
- range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', option);
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.addEventListener('change', updateRangeSetting);
- range.value = currentSettings[game][settingName][parseInt(option, 10)];
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
+ if (((setting.max - setting.min) + 1) < 11) {
+ for (let i=setting.min; i <= setting.max; ++i) {
+ const tr = document.createElement('tr');
+ const tdLeft = document.createElement('td');
+ tdLeft.classList.add('td-left');
+ tdLeft.innerText = i;
+ tr.appendChild(tdLeft);
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${game}-${settingName}-${option}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
+ const tdMiddle = document.createElement('td');
+ tdMiddle.classList.add('td-middle');
+ const range = document.createElement('input');
+ range.setAttribute('type', 'range');
+ range.setAttribute('id', `${this.name}-${settingName}-${i}-range`);
+ range.setAttribute('data-game', this.name);
+ range.setAttribute('data-setting', settingName);
+ range.setAttribute('data-option', i);
+ range.setAttribute('min', 0);
+ range.setAttribute('max', 50);
+ range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
+ range.value = this.current[settingName][i] || 0;
+ tdMiddle.appendChild(range);
+ tr.appendChild(tdMiddle);
- const tdDelete = document.createElement('td');
- tdDelete.classList.add('td-delete');
- const deleteButton = document.createElement('span');
- deleteButton.classList.add('range-option-delete');
- deleteButton.innerText = '❌';
- deleteButton.addEventListener('click', () => {
- range.value = 0;
- range.dispatchEvent(new Event('change'));
- rangeTbody.removeChild(tr);
+ const tdRight = document.createElement('td');
+ tdRight.setAttribute('id', `${this.name}-${settingName}-${i}`)
+ tdRight.classList.add('td-right');
+ tdRight.innerText = range.value;
+ tr.appendChild(tdRight);
+
+ rangeTbody.appendChild(tr);
+ }
+ } else {
+ const hintText = document.createElement('p');
+ hintText.classList.add('hint-text');
+ hintText.innerHTML = 'This is a range option. You may enter a valid numerical value in the text box ' +
+ `below, then press the "Add" button to add a weight for it.
Minimum value: ${setting.min}
` +
+ `Maximum value: ${setting.max}`;
+
+ if (setting.hasOwnProperty('value_names')) {
+ hintText.innerHTML += '
Certain values have special meaning:';
+ Object.keys(setting.value_names).forEach((specialName) => {
+ hintText.innerHTML += `
${specialName}: ${setting.value_names[specialName]}`;
+ });
+ }
+
+ settingWrapper.appendChild(hintText);
+
+ const addOptionDiv = document.createElement('div');
+ addOptionDiv.classList.add('add-option-div');
+ const optionInput = document.createElement('input');
+ optionInput.setAttribute('id', `${this.name}-${settingName}-option`);
+ optionInput.setAttribute('placeholder', `${setting.min} - ${setting.max}`);
+ addOptionDiv.appendChild(optionInput);
+ const addOptionButton = document.createElement('button');
+ addOptionButton.innerText = 'Add';
+ addOptionDiv.appendChild(addOptionButton);
+ settingWrapper.appendChild(addOptionDiv);
+ optionInput.addEventListener('keydown', (evt) => {
+ if (evt.key === 'Enter') { addOptionButton.dispatchEvent(new Event('click')); }
});
- tdDelete.appendChild(deleteButton);
- tr.appendChild(tdDelete);
- rangeTbody.appendChild(tr);
+ addOptionButton.addEventListener('click', () => {
+ const optionInput = document.getElementById(`${this.name}-${settingName}-option`);
+ let option = optionInput.value;
+ if (!option || !option.trim()) { return; }
+ option = parseInt(option, 10);
+ if ((option < setting.min) || (option > setting.max)) { return; }
+ optionInput.value = '';
+ if (document.getElementById(`${this.name}-${settingName}-${option}-range`)) { return; }
- // Save new option to settings
- range.dispatchEvent(new Event('change'));
- });
-
- Object.keys(currentSettings[game][settingName]).forEach((option) => {
- // These options are statically generated below, and should always appear even if they are deleted
- // from localStorage
- if (['random-low', 'random', 'random-high'].includes(option)) { return; }
-
- const tr = document.createElement('tr');
+ const tr = document.createElement('tr');
const tdLeft = document.createElement('td');
tdLeft.classList.add('td-left');
tdLeft.innerText = option;
@@ -432,19 +602,19 @@ const buildWeightedSettingsDiv = (game, settings, gameItems, gameLocations) => {
tdMiddle.classList.add('td-middle');
const range = document.createElement('input');
range.setAttribute('type', 'range');
- range.setAttribute('id', `${game}-${settingName}-${option}-range`);
- range.setAttribute('data-game', game);
+ range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
+ range.setAttribute('data-game', this.name);
range.setAttribute('data-setting', settingName);
range.setAttribute('data-option', option);
range.setAttribute('min', 0);
range.setAttribute('max', 50);
- range.addEventListener('change', updateRangeSetting);
- range.value = currentSettings[game][settingName][parseInt(option, 10)];
+ range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
+ range.value = this.current[settingName][parseInt(option, 10)];
tdMiddle.appendChild(range);
tr.appendChild(tdMiddle);
const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${game}-${settingName}-${option}`)
+ tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
tdRight.classList.add('td-right');
tdRight.innerText = range.value;
tr.appendChild(tdRight);
@@ -456,762 +626,651 @@ const buildWeightedSettingsDiv = (game, settings, gameItems, gameLocations) => {
deleteButton.innerText = '❌';
deleteButton.addEventListener('click', () => {
range.value = 0;
- const changeEvent = new Event('change');
- changeEvent.action = 'rangeDelete';
- range.dispatchEvent(changeEvent);
+ range.dispatchEvent(new Event('change'));
rangeTbody.removeChild(tr);
});
tdDelete.appendChild(deleteButton);
tr.appendChild(tdDelete);
rangeTbody.appendChild(tr);
+
+ // Save new option to settings
+ range.dispatchEvent(new Event('change'));
+ });
+
+ Object.keys(this.current[settingName]).forEach((option) => {
+ // These options are statically generated below, and should always appear even if they are deleted
+ // from localStorage
+ if (['random-low', 'random', 'random-high'].includes(option)) { return; }
+
+ const tr = document.createElement('tr');
+ const tdLeft = document.createElement('td');
+ tdLeft.classList.add('td-left');
+ tdLeft.innerText = option;
+ tr.appendChild(tdLeft);
+
+ const tdMiddle = document.createElement('td');
+ tdMiddle.classList.add('td-middle');
+ const range = document.createElement('input');
+ range.setAttribute('type', 'range');
+ range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
+ range.setAttribute('data-game', this.name);
+ range.setAttribute('data-setting', settingName);
+ range.setAttribute('data-option', option);
+ range.setAttribute('min', 0);
+ range.setAttribute('max', 50);
+ range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
+ range.value = this.current[settingName][parseInt(option, 10)];
+ tdMiddle.appendChild(range);
+ tr.appendChild(tdMiddle);
+
+ const tdRight = document.createElement('td');
+ tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
+ tdRight.classList.add('td-right');
+ tdRight.innerText = range.value;
+ tr.appendChild(tdRight);
+
+ const tdDelete = document.createElement('td');
+ tdDelete.classList.add('td-delete');
+ const deleteButton = document.createElement('span');
+ deleteButton.classList.add('range-option-delete');
+ deleteButton.innerText = '❌';
+ deleteButton.addEventListener('click', () => {
+ range.value = 0;
+ const changeEvent = new Event('change');
+ changeEvent.action = 'rangeDelete';
+ range.dispatchEvent(changeEvent);
+ rangeTbody.removeChild(tr);
+ });
+ tdDelete.appendChild(deleteButton);
+ tr.appendChild(tdDelete);
+
+ rangeTbody.appendChild(tr);
+ });
+ }
+
+ ['random', 'random-low', 'random-high'].forEach((option) => {
+ const tr = document.createElement('tr');
+ const tdLeft = document.createElement('td');
+ tdLeft.classList.add('td-left');
+ switch(option){
+ case 'random':
+ tdLeft.innerText = 'Random';
+ break;
+ case 'random-low':
+ tdLeft.innerText = "Random (Low)";
+ break;
+ case 'random-high':
+ tdLeft.innerText = "Random (High)";
+ break;
+ }
+ tr.appendChild(tdLeft);
+
+ const tdMiddle = document.createElement('td');
+ tdMiddle.classList.add('td-middle');
+ const range = document.createElement('input');
+ range.setAttribute('type', 'range');
+ range.setAttribute('id', `${this.name}-${settingName}-${option}-range`);
+ range.setAttribute('data-game', this.name);
+ range.setAttribute('data-setting', settingName);
+ range.setAttribute('data-option', option);
+ range.setAttribute('min', 0);
+ range.setAttribute('max', 50);
+ range.addEventListener('change', (evt) => this.#updateRangeSetting(evt));
+ range.value = this.current[settingName][option];
+ tdMiddle.appendChild(range);
+ tr.appendChild(tdMiddle);
+
+ const tdRight = document.createElement('td');
+ tdRight.setAttribute('id', `${this.name}-${settingName}-${option}`)
+ tdRight.classList.add('td-right');
+ tdRight.innerText = range.value;
+ tr.appendChild(tdRight);
+ rangeTbody.appendChild(tr);
});
- }
- ['random', 'random-low', 'random-high'].forEach((option) => {
- const tr = document.createElement('tr');
- const tdLeft = document.createElement('td');
- tdLeft.classList.add('td-left');
- switch(option){
- case 'random':
- tdLeft.innerText = 'Random';
- break;
- case 'random-low':
- tdLeft.innerText = "Random (Low)";
- break;
- case 'random-high':
- tdLeft.innerText = "Random (High)";
- break;
+ rangeTable.appendChild(rangeTbody);
+ settingWrapper.appendChild(rangeTable);
+ break;
+
+ case 'items-list':
+ const itemsList = document.createElement('div');
+ itemsList.classList.add('simple-list');
+
+ Object.values(this.data.gameItems).forEach((item) => {
+ const itemRow = document.createElement('div');
+ itemRow.classList.add('list-row');
+
+ const itemLabel = document.createElement('label');
+ itemLabel.setAttribute('for', `${this.name}-${settingName}-${item}`)
+
+ const itemCheckbox = document.createElement('input');
+ itemCheckbox.setAttribute('id', `${this.name}-${settingName}-${item}`);
+ itemCheckbox.setAttribute('type', 'checkbox');
+ itemCheckbox.setAttribute('data-game', this.name);
+ itemCheckbox.setAttribute('data-setting', settingName);
+ itemCheckbox.setAttribute('data-option', item.toString());
+ itemCheckbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
+ if (this.current[settingName].includes(item)) {
+ itemCheckbox.setAttribute('checked', '1');
}
- tr.appendChild(tdLeft);
- const tdMiddle = document.createElement('td');
- tdMiddle.classList.add('td-middle');
- const range = document.createElement('input');
- range.setAttribute('type', 'range');
- range.setAttribute('id', `${game}-${settingName}-${option}-range`);
- range.setAttribute('data-game', game);
- range.setAttribute('data-setting', settingName);
- range.setAttribute('data-option', option);
- range.setAttribute('min', 0);
- range.setAttribute('max', 50);
- range.addEventListener('change', updateRangeSetting);
- range.value = currentSettings[game][settingName][option];
- tdMiddle.appendChild(range);
- tr.appendChild(tdMiddle);
+ const itemName = document.createElement('span');
+ itemName.innerText = item.toString();
- const tdRight = document.createElement('td');
- tdRight.setAttribute('id', `${game}-${settingName}-${option}`)
- tdRight.classList.add('td-right');
- tdRight.innerText = range.value;
- tr.appendChild(tdRight);
- rangeTbody.appendChild(tr);
- });
+ itemLabel.appendChild(itemCheckbox);
+ itemLabel.appendChild(itemName);
- rangeTable.appendChild(rangeTbody);
- settingWrapper.appendChild(rangeTable);
- break;
+ itemRow.appendChild(itemLabel);
+ itemsList.appendChild((itemRow));
+ });
- case 'items-list':
- const itemsList = document.createElement('div');
- itemsList.classList.add('simple-list');
+ settingWrapper.appendChild(itemsList);
+ break;
- Object.values(gameItems).forEach((item) => {
- const itemRow = document.createElement('div');
- itemRow.classList.add('list-row');
+ case 'locations-list':
+ const locationsList = document.createElement('div');
+ locationsList.classList.add('simple-list');
- const itemLabel = document.createElement('label');
- itemLabel.setAttribute('for', `${game}-${settingName}-${item}`)
+ Object.values(this.data.gameLocations).forEach((location) => {
+ const locationRow = document.createElement('div');
+ locationRow.classList.add('list-row');
- const itemCheckbox = document.createElement('input');
- itemCheckbox.setAttribute('id', `${game}-${settingName}-${item}`);
- itemCheckbox.setAttribute('type', 'checkbox');
- itemCheckbox.setAttribute('data-game', game);
- itemCheckbox.setAttribute('data-setting', settingName);
- itemCheckbox.setAttribute('data-option', item.toString());
- itemCheckbox.addEventListener('change', updateListSetting);
- if (currentSettings[game][settingName].includes(item)) {
- itemCheckbox.setAttribute('checked', '1');
- }
+ const locationLabel = document.createElement('label');
+ locationLabel.setAttribute('for', `${this.name}-${settingName}-${location}`)
- const itemName = document.createElement('span');
- itemName.innerText = item.toString();
+ const locationCheckbox = document.createElement('input');
+ locationCheckbox.setAttribute('id', `${this.name}-${settingName}-${location}`);
+ locationCheckbox.setAttribute('type', 'checkbox');
+ locationCheckbox.setAttribute('data-game', this.name);
+ locationCheckbox.setAttribute('data-setting', settingName);
+ locationCheckbox.setAttribute('data-option', location.toString());
+ locationCheckbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
+ if (this.current[settingName].includes(location)) {
+ locationCheckbox.setAttribute('checked', '1');
+ }
- itemLabel.appendChild(itemCheckbox);
- itemLabel.appendChild(itemName);
+ const locationName = document.createElement('span');
+ locationName.innerText = location.toString();
- itemRow.appendChild(itemLabel);
- itemsList.appendChild((itemRow));
- });
+ locationLabel.appendChild(locationCheckbox);
+ locationLabel.appendChild(locationName);
- settingWrapper.appendChild(itemsList);
- break;
+ locationRow.appendChild(locationLabel);
+ locationsList.appendChild((locationRow));
+ });
- case 'locations-list':
- const locationsList = document.createElement('div');
- locationsList.classList.add('simple-list');
+ settingWrapper.appendChild(locationsList);
+ break;
- Object.values(gameLocations).forEach((location) => {
- const locationRow = document.createElement('div');
- locationRow.classList.add('list-row');
+ case 'custom-list':
+ const customList = document.createElement('div');
+ customList.classList.add('simple-list');
- const locationLabel = document.createElement('label');
- locationLabel.setAttribute('for', `${game}-${settingName}-${location}`)
+ Object.values(this.data.gameSettings[settingName].options).forEach((listItem) => {
+ const customListRow = document.createElement('div');
+ customListRow.classList.add('list-row');
- const locationCheckbox = document.createElement('input');
- locationCheckbox.setAttribute('id', `${game}-${settingName}-${location}`);
- locationCheckbox.setAttribute('type', 'checkbox');
- locationCheckbox.setAttribute('data-game', game);
- locationCheckbox.setAttribute('data-setting', settingName);
- locationCheckbox.setAttribute('data-option', location.toString());
- locationCheckbox.addEventListener('change', updateListSetting);
- if (currentSettings[game][settingName].includes(location)) {
- locationCheckbox.setAttribute('checked', '1');
- }
+ const customItemLabel = document.createElement('label');
+ customItemLabel.setAttribute('for', `${this.name}-${settingName}-${listItem}`)
- const locationName = document.createElement('span');
- locationName.innerText = location.toString();
+ const customItemCheckbox = document.createElement('input');
+ customItemCheckbox.setAttribute('id', `${this.name}-${settingName}-${listItem}`);
+ customItemCheckbox.setAttribute('type', 'checkbox');
+ customItemCheckbox.setAttribute('data-game', this.name);
+ customItemCheckbox.setAttribute('data-setting', settingName);
+ customItemCheckbox.setAttribute('data-option', listItem.toString());
+ customItemCheckbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
+ if (this.current[settingName].includes(listItem)) {
+ customItemCheckbox.setAttribute('checked', '1');
+ }
- locationLabel.appendChild(locationCheckbox);
- locationLabel.appendChild(locationName);
+ const customItemName = document.createElement('span');
+ customItemName.innerText = listItem.toString();
- locationRow.appendChild(locationLabel);
- locationsList.appendChild((locationRow));
- });
+ customItemLabel.appendChild(customItemCheckbox);
+ customItemLabel.appendChild(customItemName);
- settingWrapper.appendChild(locationsList);
- break;
+ customListRow.appendChild(customItemLabel);
+ customList.appendChild((customListRow));
+ });
- case 'custom-list':
- const customList = document.createElement('div');
- customList.classList.add('simple-list');
+ settingWrapper.appendChild(customList);
+ break;
- Object.values(settings[settingName].options).forEach((listItem) => {
- const customListRow = document.createElement('div');
- customListRow.classList.add('list-row');
-
- const customItemLabel = document.createElement('label');
- customItemLabel.setAttribute('for', `${game}-${settingName}-${listItem}`)
-
- const customItemCheckbox = document.createElement('input');
- customItemCheckbox.setAttribute('id', `${game}-${settingName}-${listItem}`);
- customItemCheckbox.setAttribute('type', 'checkbox');
- customItemCheckbox.setAttribute('data-game', game);
- customItemCheckbox.setAttribute('data-setting', settingName);
- customItemCheckbox.setAttribute('data-option', listItem.toString());
- customItemCheckbox.addEventListener('change', updateListSetting);
- if (currentSettings[game][settingName].includes(listItem)) {
- customItemCheckbox.setAttribute('checked', '1');
- }
-
- const customItemName = document.createElement('span');
- customItemName.innerText = listItem.toString();
-
- customItemLabel.appendChild(customItemCheckbox);
- customItemLabel.appendChild(customItemName);
-
- customListRow.appendChild(customItemLabel);
- customList.appendChild((customListRow));
- });
-
- settingWrapper.appendChild(customList);
- break;
-
- default:
- console.error(`Unknown setting type for ${game} setting ${settingName}: ${setting.type}`);
- return;
- }
-
- settingsWrapper.appendChild(settingWrapper);
- });
-
- return settingsWrapper;
-};
-
-const buildItemsDiv = (game, items) => {
- // Sort alphabetical, in pace
- items.sort();
-
- const currentSettings = JSON.parse(localStorage.getItem('weighted-settings'));
- const itemsDiv = document.createElement('div');
- itemsDiv.classList.add('items-div');
-
- const itemsDivHeader = document.createElement('h3');
- itemsDivHeader.innerText = 'Item Pool';
- itemsDiv.appendChild(itemsDivHeader);
-
- const itemsDescription = document.createElement('p');
- itemsDescription.classList.add('setting-description');
- itemsDescription.innerText = 'Choose if you would like to start with items, or control if they are placed in ' +
- 'your seed or someone else\'s.';
- itemsDiv.appendChild(itemsDescription);
-
- const itemsHint = document.createElement('p');
- itemsHint.classList.add('hint-text');
- itemsHint.innerText = 'Drag and drop items from one box to another.';
- itemsDiv.appendChild(itemsHint);
-
- const itemsWrapper = document.createElement('div');
- itemsWrapper.classList.add('items-wrapper');
-
- // Create container divs for each category
- const availableItemsWrapper = document.createElement('div');
- availableItemsWrapper.classList.add('item-set-wrapper');
- availableItemsWrapper.innerText = 'Available Items';
- const availableItems = document.createElement('div');
- availableItems.classList.add('item-container');
- availableItems.setAttribute('id', `${game}-available_items`);
- availableItems.addEventListener('dragover', itemDragoverHandler);
- availableItems.addEventListener('drop', itemDropHandler);
-
- const startInventoryWrapper = document.createElement('div');
- startInventoryWrapper.classList.add('item-set-wrapper');
- startInventoryWrapper.innerText = 'Start Inventory';
- const startInventory = document.createElement('div');
- startInventory.classList.add('item-container');
- startInventory.setAttribute('id', `${game}-start_inventory`);
- startInventory.setAttribute('data-setting', 'start_inventory');
- startInventory.addEventListener('dragover', itemDragoverHandler);
- startInventory.addEventListener('drop', itemDropHandler);
-
- const localItemsWrapper = document.createElement('div');
- localItemsWrapper.classList.add('item-set-wrapper');
- localItemsWrapper.innerText = 'Local Items';
- const localItems = document.createElement('div');
- localItems.classList.add('item-container');
- localItems.setAttribute('id', `${game}-local_items`);
- localItems.setAttribute('data-setting', 'local_items')
- localItems.addEventListener('dragover', itemDragoverHandler);
- localItems.addEventListener('drop', itemDropHandler);
-
- const nonLocalItemsWrapper = document.createElement('div');
- nonLocalItemsWrapper.classList.add('item-set-wrapper');
- nonLocalItemsWrapper.innerText = 'Non-Local Items';
- const nonLocalItems = document.createElement('div');
- nonLocalItems.classList.add('item-container');
- nonLocalItems.setAttribute('id', `${game}-non_local_items`);
- nonLocalItems.setAttribute('data-setting', 'non_local_items');
- nonLocalItems.addEventListener('dragover', itemDragoverHandler);
- nonLocalItems.addEventListener('drop', itemDropHandler);
-
- // Populate the divs
- items.forEach((item) => {
- if (Object.keys(currentSettings[game].start_inventory).includes(item)){
- const itemDiv = buildItemQtyDiv(game, item);
- itemDiv.setAttribute('data-setting', 'start_inventory');
- startInventory.appendChild(itemDiv);
- } else if (currentSettings[game].local_items.includes(item)) {
- const itemDiv = buildItemDiv(game, item);
- itemDiv.setAttribute('data-setting', 'local_items');
- localItems.appendChild(itemDiv);
- } else if (currentSettings[game].non_local_items.includes(item)) {
- const itemDiv = buildItemDiv(game, item);
- itemDiv.setAttribute('data-setting', 'non_local_items');
- nonLocalItems.appendChild(itemDiv);
- } else {
- const itemDiv = buildItemDiv(game, item);
- availableItems.appendChild(itemDiv);
- }
- });
-
- availableItemsWrapper.appendChild(availableItems);
- startInventoryWrapper.appendChild(startInventory);
- localItemsWrapper.appendChild(localItems);
- nonLocalItemsWrapper.appendChild(nonLocalItems);
- itemsWrapper.appendChild(availableItemsWrapper);
- itemsWrapper.appendChild(startInventoryWrapper);
- itemsWrapper.appendChild(localItemsWrapper);
- itemsWrapper.appendChild(nonLocalItemsWrapper);
- itemsDiv.appendChild(itemsWrapper);
- return itemsDiv;
-};
-
-const buildItemDiv = (game, item) => {
- const itemDiv = document.createElement('div');
- itemDiv.classList.add('item-div');
- itemDiv.setAttribute('id', `${game}-${item}`);
- itemDiv.setAttribute('data-game', game);
- itemDiv.setAttribute('data-item', item);
- itemDiv.setAttribute('draggable', 'true');
- itemDiv.innerText = item;
- itemDiv.addEventListener('dragstart', (evt) => {
- evt.dataTransfer.setData('text/plain', itemDiv.getAttribute('id'));
- });
- return itemDiv;
-};
-
-const buildItemQtyDiv = (game, item) => {
- const currentSettings = JSON.parse(localStorage.getItem('weighted-settings'));
- const itemQtyDiv = document.createElement('div');
- itemQtyDiv.classList.add('item-qty-div');
- itemQtyDiv.setAttribute('id', `${game}-${item}`);
- itemQtyDiv.setAttribute('data-game', game);
- itemQtyDiv.setAttribute('data-item', item);
- itemQtyDiv.setAttribute('draggable', 'true');
- itemQtyDiv.innerText = item;
-
- const inputWrapper = document.createElement('div');
- inputWrapper.classList.add('item-qty-input-wrapper')
-
- const itemQty = document.createElement('input');
- itemQty.setAttribute('value', currentSettings[game].start_inventory.hasOwnProperty(item) ?
- currentSettings[game].start_inventory[item] : '1');
- itemQty.setAttribute('data-game', game);
- itemQty.setAttribute('data-setting', 'start_inventory');
- itemQty.setAttribute('data-option', item);
- itemQty.setAttribute('maxlength', '3');
- itemQty.addEventListener('keyup', (evt) => {
- evt.target.value = isNaN(parseInt(evt.target.value)) ? 0 : parseInt(evt.target.value);
- updateItemSetting(evt);
- });
- inputWrapper.appendChild(itemQty);
- itemQtyDiv.appendChild(inputWrapper);
-
- itemQtyDiv.addEventListener('dragstart', (evt) => {
- evt.dataTransfer.setData('text/plain', itemQtyDiv.getAttribute('id'));
- });
- return itemQtyDiv;
-};
-
-const itemDragoverHandler = (evt) => {
- evt.preventDefault();
-};
-
-const itemDropHandler = (evt) => {
- evt.preventDefault();
- const sourceId = evt.dataTransfer.getData('text/plain');
- const sourceDiv = document.getElementById(sourceId);
-
- const currentSettings = JSON.parse(localStorage.getItem('weighted-settings'));
- const game = sourceDiv.getAttribute('data-game');
- const item = sourceDiv.getAttribute('data-item');
-
- const oldSetting = sourceDiv.hasAttribute('data-setting') ? sourceDiv.getAttribute('data-setting') : null;
- const newSetting = evt.target.hasAttribute('data-setting') ? evt.target.getAttribute('data-setting') : null;
-
- const itemDiv = newSetting === 'start_inventory' ? buildItemQtyDiv(game, item) : buildItemDiv(game, item);
-
- if (oldSetting) {
- if (oldSetting === 'start_inventory') {
- if (currentSettings[game][oldSetting].hasOwnProperty(item)) {
- delete currentSettings[game][oldSetting][item];
+ default:
+ console.error(`Unknown setting type for ${this.name} setting ${settingName}: ${setting.type}`);
+ return;
}
- } else {
- if (currentSettings[game][oldSetting].includes(item)) {
- currentSettings[game][oldSetting].splice(currentSettings[game][oldSetting].indexOf(item), 1);
- }
- }
- }
- if (newSetting) {
- itemDiv.setAttribute('data-setting', newSetting);
- document.getElementById(`${game}-${newSetting}`).appendChild(itemDiv);
- if (newSetting === 'start_inventory') {
- currentSettings[game][newSetting][item] = 1;
- } else {
- if (!currentSettings[game][newSetting].includes(item)){
- currentSettings[game][newSetting].push(item);
- }
- }
- } else {
- // No setting was assigned, this item has been removed from the settings
- document.getElementById(`${game}-available_items`).appendChild(itemDiv);
- }
-
- // Remove the source drag object
- sourceDiv.parentElement.removeChild(sourceDiv);
-
- // Save the updated settings
- localStorage.setItem('weighted-settings', JSON.stringify(currentSettings));
-};
-
-const buildHintsDiv = (game, items, locations) => {
- const currentSettings = JSON.parse(localStorage.getItem('weighted-settings'));
-
- // Sort alphabetical, in place
- items.sort();
- locations.sort();
-
- const hintsDiv = document.createElement('div');
- hintsDiv.classList.add('hints-div');
- const hintsHeader = document.createElement('h3');
- hintsHeader.innerText = 'Item & Location Hints';
- hintsDiv.appendChild(hintsHeader);
- const hintsDescription = document.createElement('p');
- hintsDescription.classList.add('setting-description');
- hintsDescription.innerText = 'Choose any items or locations to begin the game with the knowledge of where those ' +
- ' items are, or what those locations contain.';
- hintsDiv.appendChild(hintsDescription);
-
- const itemHintsContainer = document.createElement('div');
- itemHintsContainer.classList.add('hints-container');
-
- // Item Hints
- const itemHintsWrapper = document.createElement('div');
- itemHintsWrapper.classList.add('hints-wrapper');
- itemHintsWrapper.innerText = 'Starting Item Hints';
-
- const itemHintsDiv = document.createElement('div');
- itemHintsDiv.classList.add('simple-list');
- items.forEach((item) => {
- const itemRow = document.createElement('div');
- itemRow.classList.add('list-row');
-
- const itemLabel = document.createElement('label');
- itemLabel.setAttribute('for', `${game}-start_hints-${item}`);
-
- const itemCheckbox = document.createElement('input');
- itemCheckbox.setAttribute('type', 'checkbox');
- itemCheckbox.setAttribute('id', `${game}-start_hints-${item}`);
- itemCheckbox.setAttribute('data-game', game);
- itemCheckbox.setAttribute('data-setting', 'start_hints');
- itemCheckbox.setAttribute('data-option', item);
- if (currentSettings[game].start_hints.includes(item)) {
- itemCheckbox.setAttribute('checked', 'true');
- }
- itemCheckbox.addEventListener('change', updateListSetting);
- itemLabel.appendChild(itemCheckbox);
-
- const itemName = document.createElement('span');
- itemName.innerText = item;
- itemLabel.appendChild(itemName);
-
- itemRow.appendChild(itemLabel);
- itemHintsDiv.appendChild(itemRow);
- });
-
- itemHintsWrapper.appendChild(itemHintsDiv);
- itemHintsContainer.appendChild(itemHintsWrapper);
-
- // Starting Location Hints
- const locationHintsWrapper = document.createElement('div');
- locationHintsWrapper.classList.add('hints-wrapper');
- locationHintsWrapper.innerText = 'Starting Location Hints';
-
- const locationHintsDiv = document.createElement('div');
- locationHintsDiv.classList.add('simple-list');
- locations.forEach((location) => {
- const locationRow = document.createElement('div');
- locationRow.classList.add('list-row');
-
- const locationLabel = document.createElement('label');
- locationLabel.setAttribute('for', `${game}-start_location_hints-${location}`);
-
- const locationCheckbox = document.createElement('input');
- locationCheckbox.setAttribute('type', 'checkbox');
- locationCheckbox.setAttribute('id', `${game}-start_location_hints-${location}`);
- locationCheckbox.setAttribute('data-game', game);
- locationCheckbox.setAttribute('data-setting', 'start_location_hints');
- locationCheckbox.setAttribute('data-option', location);
- if (currentSettings[game].start_location_hints.includes(location)) {
- locationCheckbox.setAttribute('checked', '1');
- }
- locationCheckbox.addEventListener('change', updateListSetting);
- locationLabel.appendChild(locationCheckbox);
-
- const locationName = document.createElement('span');
- locationName.innerText = location;
- locationLabel.appendChild(locationName);
-
- locationRow.appendChild(locationLabel);
- locationHintsDiv.appendChild(locationRow);
- });
-
- locationHintsWrapper.appendChild(locationHintsDiv);
- itemHintsContainer.appendChild(locationHintsWrapper);
-
- hintsDiv.appendChild(itemHintsContainer);
- return hintsDiv;
-};
-
-const buildLocationsDiv = (game, locations) => {
- const currentSettings = JSON.parse(localStorage.getItem('weighted-settings'));
- locations.sort(); // Sort alphabetical, in-place
-
- const locationsDiv = document.createElement('div');
- locationsDiv.classList.add('locations-div');
- const locationsHeader = document.createElement('h3');
- locationsHeader.innerText = 'Priority & Exclusion Locations';
- locationsDiv.appendChild(locationsHeader);
- const locationsDescription = document.createElement('p');
- locationsDescription.classList.add('setting-description');
- locationsDescription.innerText = 'Priority locations guarantee a progression item will be placed there while ' +
- 'excluded locations will not contain progression or useful items.';
- locationsDiv.appendChild(locationsDescription);
-
- const locationsContainer = document.createElement('div');
- locationsContainer.classList.add('locations-container');
-
- // Priority Locations
- const priorityLocationsWrapper = document.createElement('div');
- priorityLocationsWrapper.classList.add('locations-wrapper');
- priorityLocationsWrapper.innerText = 'Priority Locations';
-
- const priorityLocationsDiv = document.createElement('div');
- priorityLocationsDiv.classList.add('simple-list');
- locations.forEach((location) => {
- const locationRow = document.createElement('div');
- locationRow.classList.add('list-row');
-
- const locationLabel = document.createElement('label');
- locationLabel.setAttribute('for', `${game}-priority_locations-${location}`);
-
- const locationCheckbox = document.createElement('input');
- locationCheckbox.setAttribute('type', 'checkbox');
- locationCheckbox.setAttribute('id', `${game}-priority_locations-${location}`);
- locationCheckbox.setAttribute('data-game', game);
- locationCheckbox.setAttribute('data-setting', 'priority_locations');
- locationCheckbox.setAttribute('data-option', location);
- if (currentSettings[game].priority_locations.includes(location)) {
- locationCheckbox.setAttribute('checked', '1');
- }
- locationCheckbox.addEventListener('change', updateListSetting);
- locationLabel.appendChild(locationCheckbox);
-
- const locationName = document.createElement('span');
- locationName.innerText = location;
- locationLabel.appendChild(locationName);
-
- locationRow.appendChild(locationLabel);
- priorityLocationsDiv.appendChild(locationRow);
- });
-
- priorityLocationsWrapper.appendChild(priorityLocationsDiv);
- locationsContainer.appendChild(priorityLocationsWrapper);
-
- // Exclude Locations
- const excludeLocationsWrapper = document.createElement('div');
- excludeLocationsWrapper.classList.add('locations-wrapper');
- excludeLocationsWrapper.innerText = 'Exclude Locations';
-
- const excludeLocationsDiv = document.createElement('div');
- excludeLocationsDiv.classList.add('simple-list');
- locations.forEach((location) => {
- const locationRow = document.createElement('div');
- locationRow.classList.add('list-row');
-
- const locationLabel = document.createElement('label');
- locationLabel.setAttribute('for', `${game}-exclude_locations-${location}`);
-
- const locationCheckbox = document.createElement('input');
- locationCheckbox.setAttribute('type', 'checkbox');
- locationCheckbox.setAttribute('id', `${game}-exclude_locations-${location}`);
- locationCheckbox.setAttribute('data-game', game);
- locationCheckbox.setAttribute('data-setting', 'exclude_locations');
- locationCheckbox.setAttribute('data-option', location);
- if (currentSettings[game].exclude_locations.includes(location)) {
- locationCheckbox.setAttribute('checked', '1');
- }
- locationCheckbox.addEventListener('change', updateListSetting);
- locationLabel.appendChild(locationCheckbox);
-
- const locationName = document.createElement('span');
- locationName.innerText = location;
- locationLabel.appendChild(locationName);
-
- locationRow.appendChild(locationLabel);
- excludeLocationsDiv.appendChild(locationRow);
- });
-
- excludeLocationsWrapper.appendChild(excludeLocationsDiv);
- locationsContainer.appendChild(excludeLocationsWrapper);
-
- locationsDiv.appendChild(locationsContainer);
- return locationsDiv;
-};
-
-const updateVisibleGames = () => {
- const settings = JSON.parse(localStorage.getItem('weighted-settings'));
- Object.keys(settings.game).forEach((game) => {
- const gameDiv = document.getElementById(`${game}-div`);
- const gameOption = document.getElementById(`${game}-game-option`);
- if (parseInt(settings.game[game], 10) > 0) {
- gameDiv.classList.remove('invisible');
- gameOption.classList.add('jump-link');
- gameOption.addEventListener('click', () => {
- const gameDiv = document.getElementById(`${game}-div`);
- if (gameDiv.classList.contains('invisible')) { return; }
- gameDiv.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- });
- } else {
- gameDiv.classList.add('invisible');
- gameOption.classList.remove('jump-link');
-
- }
- });
-};
-
-const updateBaseSetting = (event) => {
- const settings = JSON.parse(localStorage.getItem('weighted-settings'));
- const setting = event.target.getAttribute('data-setting');
- const option = event.target.getAttribute('data-option');
- const type = event.target.getAttribute('data-type');
-
- switch(type){
- case 'weight':
- settings[setting][option] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
- document.getElementById(`${setting}-${option}`).innerText = event.target.value;
- break;
- case 'data':
- settings[setting] = isNaN(event.target.value) ? event.target.value : parseInt(event.target.value, 10);
- break;
- }
-
- localStorage.setItem('weighted-settings', JSON.stringify(settings));
-};
-
-const updateRangeSetting = (evt) => {
- const options = JSON.parse(localStorage.getItem('weighted-settings'));
- const game = evt.target.getAttribute('data-game');
- const setting = evt.target.getAttribute('data-setting');
- const option = evt.target.getAttribute('data-option');
- document.getElementById(`${game}-${setting}-${option}`).innerText = evt.target.value;
- if (evt.action && evt.action === 'rangeDelete') {
- delete options[game][setting][option];
- } else {
- options[game][setting][option] = parseInt(evt.target.value, 10);
- }
- localStorage.setItem('weighted-settings', JSON.stringify(options));
-};
-
-const updateListSetting = (evt) => {
- const options = JSON.parse(localStorage.getItem('weighted-settings'));
- const game = evt.target.getAttribute('data-game');
- const setting = evt.target.getAttribute('data-setting');
- const option = evt.target.getAttribute('data-option');
-
- if (evt.target.checked) {
- // If the option is to be enabled and it is already enabled, do nothing
- if (options[game][setting].includes(option)) { return; }
-
- options[game][setting].push(option);
- } else {
- // If the option is to be disabled and it is already disabled, do nothing
- if (!options[game][setting].includes(option)) { return; }
-
- options[game][setting].splice(options[game][setting].indexOf(option), 1);
- }
- localStorage.setItem('weighted-settings', JSON.stringify(options));
-};
-
-const updateItemSetting = (evt) => {
- const options = JSON.parse(localStorage.getItem('weighted-settings'));
- const game = evt.target.getAttribute('data-game');
- const setting = evt.target.getAttribute('data-setting');
- const option = evt.target.getAttribute('data-option');
- if (setting === 'start_inventory') {
- options[game][setting][option] = evt.target.value.trim() ? parseInt(evt.target.value) : 0;
- } else {
- options[game][setting][option] = isNaN(evt.target.value) ?
- evt.target.value : parseInt(evt.target.value, 10);
- }
- localStorage.setItem('weighted-settings', JSON.stringify(options));
-};
-
-const validateSettings = () => {
- const settings = JSON.parse(localStorage.getItem('weighted-settings'));
- const userMessage = document.getElementById('user-message');
- let errorMessage = null;
-
- // User must choose a name for their file
- if (!settings.name || settings.name.trim().length === 0 || settings.name.toLowerCase().trim() === 'player') {
- userMessage.innerText = 'You forgot to set your player name at the top of the page!';
- userMessage.classList.add('visible');
- userMessage.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
+ settingsWrapper.appendChild(settingWrapper);
});
- return;
+
+ return settingsWrapper;
}
- // Clean up the settings output
- Object.keys(settings.game).forEach((game) => {
- // Remove any disabled games
- if (settings.game[game] === 0) {
- delete settings.game[game];
- delete settings[game];
- return;
- }
+ #buildItemsDiv() {
+ const itemsDiv = document.createElement('div');
+ itemsDiv.classList.add('items-div');
- Object.keys(settings[game]).forEach((setting) => {
- // Remove any disabled options
- Object.keys(settings[game][setting]).forEach((option) => {
- if (settings[game][setting][option] === 0) {
- delete settings[game][setting][option];
+ const itemsDivHeader = document.createElement('h3');
+ itemsDivHeader.innerText = 'Item Pool';
+ itemsDiv.appendChild(itemsDivHeader);
+
+ const itemsDescription = document.createElement('p');
+ itemsDescription.classList.add('setting-description');
+ itemsDescription.innerText = 'Choose if you would like to start with items, or control if they are placed in ' +
+ 'your seed or someone else\'s.';
+ itemsDiv.appendChild(itemsDescription);
+
+ const itemsHint = document.createElement('p');
+ itemsHint.classList.add('hint-text');
+ itemsHint.innerText = 'Drag and drop items from one box to another.';
+ itemsDiv.appendChild(itemsHint);
+
+ const itemsWrapper = document.createElement('div');
+ itemsWrapper.classList.add('items-wrapper');
+
+ const itemDragoverHandler = (evt) => evt.preventDefault();
+ const itemDropHandler = (evt) => this.#itemDropHandler(evt);
+
+ // Create container divs for each category
+ const availableItemsWrapper = document.createElement('div');
+ availableItemsWrapper.classList.add('item-set-wrapper');
+ availableItemsWrapper.innerText = 'Available Items';
+ const availableItems = document.createElement('div');
+ availableItems.classList.add('item-container');
+ availableItems.setAttribute('id', `${this.name}-available_items`);
+ availableItems.addEventListener('dragover', itemDragoverHandler);
+ availableItems.addEventListener('drop', itemDropHandler);
+
+ const startInventoryWrapper = document.createElement('div');
+ startInventoryWrapper.classList.add('item-set-wrapper');
+ startInventoryWrapper.innerText = 'Start Inventory';
+ const startInventory = document.createElement('div');
+ startInventory.classList.add('item-container');
+ startInventory.setAttribute('id', `${this.name}-start_inventory`);
+ startInventory.setAttribute('data-setting', 'start_inventory');
+ startInventory.addEventListener('dragover', itemDragoverHandler);
+ startInventory.addEventListener('drop', itemDropHandler);
+
+ const localItemsWrapper = document.createElement('div');
+ localItemsWrapper.classList.add('item-set-wrapper');
+ localItemsWrapper.innerText = 'Local Items';
+ const localItems = document.createElement('div');
+ localItems.classList.add('item-container');
+ localItems.setAttribute('id', `${this.name}-local_items`);
+ localItems.setAttribute('data-setting', 'local_items')
+ localItems.addEventListener('dragover', itemDragoverHandler);
+ localItems.addEventListener('drop', itemDropHandler);
+
+ const nonLocalItemsWrapper = document.createElement('div');
+ nonLocalItemsWrapper.classList.add('item-set-wrapper');
+ nonLocalItemsWrapper.innerText = 'Non-Local Items';
+ const nonLocalItems = document.createElement('div');
+ nonLocalItems.classList.add('item-container');
+ nonLocalItems.setAttribute('id', `${this.name}-non_local_items`);
+ nonLocalItems.setAttribute('data-setting', 'non_local_items');
+ nonLocalItems.addEventListener('dragover', itemDragoverHandler);
+ nonLocalItems.addEventListener('drop', itemDropHandler);
+
+ // Populate the divs
+ this.data.gameItems.forEach((item) => {
+ if (Object.keys(this.current.start_inventory).includes(item)){
+ const itemDiv = this.#buildItemQtyDiv(item);
+ itemDiv.setAttribute('data-setting', 'start_inventory');
+ startInventory.appendChild(itemDiv);
+ } else if (this.current.local_items.includes(item)) {
+ const itemDiv = this.#buildItemDiv(item);
+ itemDiv.setAttribute('data-setting', 'local_items');
+ localItems.appendChild(itemDiv);
+ } else if (this.current.non_local_items.includes(item)) {
+ const itemDiv = this.#buildItemDiv(item);
+ itemDiv.setAttribute('data-setting', 'non_local_items');
+ nonLocalItems.appendChild(itemDiv);
+ } else {
+ const itemDiv = this.#buildItemDiv(item);
+ availableItems.appendChild(itemDiv);
+ }
+ });
+
+ availableItemsWrapper.appendChild(availableItems);
+ startInventoryWrapper.appendChild(startInventory);
+ localItemsWrapper.appendChild(localItems);
+ nonLocalItemsWrapper.appendChild(nonLocalItems);
+ itemsWrapper.appendChild(availableItemsWrapper);
+ itemsWrapper.appendChild(startInventoryWrapper);
+ itemsWrapper.appendChild(localItemsWrapper);
+ itemsWrapper.appendChild(nonLocalItemsWrapper);
+ itemsDiv.appendChild(itemsWrapper);
+ return itemsDiv;
+ }
+
+ #buildItemDiv(item) {
+ const itemDiv = document.createElement('div');
+ itemDiv.classList.add('item-div');
+ itemDiv.setAttribute('id', `${this.name}-${item}`);
+ itemDiv.setAttribute('data-game', this.name);
+ itemDiv.setAttribute('data-item', item);
+ itemDiv.setAttribute('draggable', 'true');
+ itemDiv.innerText = item;
+ itemDiv.addEventListener('dragstart', (evt) => {
+ evt.dataTransfer.setData('text/plain', itemDiv.getAttribute('id'));
+ });
+ return itemDiv;
+ }
+
+ #buildItemQtyDiv(item) {
+ const itemQtyDiv = document.createElement('div');
+ itemQtyDiv.classList.add('item-qty-div');
+ itemQtyDiv.setAttribute('id', `${this.name}-${item}`);
+ itemQtyDiv.setAttribute('data-game', this.name);
+ itemQtyDiv.setAttribute('data-item', item);
+ itemQtyDiv.setAttribute('draggable', 'true');
+ itemQtyDiv.innerText = item;
+
+ const inputWrapper = document.createElement('div');
+ inputWrapper.classList.add('item-qty-input-wrapper')
+
+ const itemQty = document.createElement('input');
+ itemQty.setAttribute('value', this.current.start_inventory.hasOwnProperty(item) ?
+ this.current.start_inventory[item] : '1');
+ itemQty.setAttribute('data-game', this.name);
+ itemQty.setAttribute('data-setting', 'start_inventory');
+ itemQty.setAttribute('data-option', item);
+ itemQty.setAttribute('maxlength', '3');
+ itemQty.addEventListener('keyup', (evt) => {
+ evt.target.value = isNaN(parseInt(evt.target.value)) ? 0 : parseInt(evt.target.value);
+ this.#updateItemSetting(evt);
+ });
+ inputWrapper.appendChild(itemQty);
+ itemQtyDiv.appendChild(inputWrapper);
+
+ itemQtyDiv.addEventListener('dragstart', (evt) => {
+ evt.dataTransfer.setData('text/plain', itemQtyDiv.getAttribute('id'));
+ });
+ return itemQtyDiv;
+ }
+
+ #itemDropHandler(evt) {
+ evt.preventDefault();
+ const sourceId = evt.dataTransfer.getData('text/plain');
+ const sourceDiv = document.getElementById(sourceId);
+
+ const item = sourceDiv.getAttribute('data-item');
+
+ const oldSetting = sourceDiv.hasAttribute('data-setting') ? sourceDiv.getAttribute('data-setting') : null;
+ const newSetting = evt.target.hasAttribute('data-setting') ? evt.target.getAttribute('data-setting') : null;
+
+ const itemDiv = newSetting === 'start_inventory' ? this.#buildItemQtyDiv(item) : this.#buildItemDiv(item);
+
+ if (oldSetting) {
+ if (oldSetting === 'start_inventory') {
+ if (this.current[oldSetting].hasOwnProperty(item)) {
+ delete this.current[oldSetting][item];
+ }
+ } else {
+ if (this.current[oldSetting].includes(item)) {
+ this.current[oldSetting].splice(this.current[oldSetting].indexOf(item), 1);
}
- });
-
- if (
- Object.keys(settings[game][setting]).length === 0 &&
- !Array.isArray(settings[game][setting]) &&
- setting !== 'start_inventory'
- ) {
- errorMessage = `${game} // ${setting} has no values above zero!`;
}
+ }
- // Remove weights from options with only one possibility
- if (
- Object.keys(settings[game][setting]).length === 1 &&
- !Array.isArray(settings[game][setting]) &&
- setting !== 'start_inventory'
- ) {
- settings[game][setting] = Object.keys(settings[game][setting])[0];
+ if (newSetting) {
+ itemDiv.setAttribute('data-setting', newSetting);
+ document.getElementById(`${this.name}-${newSetting}`).appendChild(itemDiv);
+ if (newSetting === 'start_inventory') {
+ this.current[newSetting][item] = 1;
+ } else {
+ if (!this.current[newSetting].includes(item)){
+ this.current[newSetting].push(item);
+ }
}
+ } else {
+ // No setting was assigned, this item has been removed from the settings
+ document.getElementById(`${this.name}-available_items`).appendChild(itemDiv);
+ }
- // Remove empty arrays
- else if (
- ['exclude_locations', 'priority_locations', 'local_items',
- 'non_local_items', 'start_hints', 'start_location_hints'].includes(setting) &&
- settings[game][setting].length === 0
- ) {
- delete settings[game][setting];
- }
+ // Remove the source drag object
+ sourceDiv.parentElement.removeChild(sourceDiv);
- // Remove empty start inventory
- else if (
- setting === 'start_inventory' &&
- Object.keys(settings[game]['start_inventory']).length === 0
- ) {
- delete settings[game]['start_inventory'];
+ // Save the updated settings
+ this.save();
+ }
+
+ #buildHintsDiv() {
+ const hintsDiv = document.createElement('div');
+ hintsDiv.classList.add('hints-div');
+ const hintsHeader = document.createElement('h3');
+ hintsHeader.innerText = 'Item & Location Hints';
+ hintsDiv.appendChild(hintsHeader);
+ const hintsDescription = document.createElement('p');
+ hintsDescription.classList.add('setting-description');
+ hintsDescription.innerText = 'Choose any items or locations to begin the game with the knowledge of where those ' +
+ ' items are, or what those locations contain.';
+ hintsDiv.appendChild(hintsDescription);
+
+ const itemHintsContainer = document.createElement('div');
+ itemHintsContainer.classList.add('hints-container');
+
+ // Item Hints
+ const itemHintsWrapper = document.createElement('div');
+ itemHintsWrapper.classList.add('hints-wrapper');
+ itemHintsWrapper.innerText = 'Starting Item Hints';
+
+ const itemHintsDiv = document.createElement('div');
+ itemHintsDiv.classList.add('simple-list');
+ this.data.gameItems.forEach((item) => {
+ const itemRow = document.createElement('div');
+ itemRow.classList.add('list-row');
+
+ const itemLabel = document.createElement('label');
+ itemLabel.setAttribute('for', `${this.name}-start_hints-${item}`);
+
+ const itemCheckbox = document.createElement('input');
+ itemCheckbox.setAttribute('type', 'checkbox');
+ itemCheckbox.setAttribute('id', `${this.name}-start_hints-${item}`);
+ itemCheckbox.setAttribute('data-game', this.name);
+ itemCheckbox.setAttribute('data-setting', 'start_hints');
+ itemCheckbox.setAttribute('data-option', item);
+ if (this.current.start_hints.includes(item)) {
+ itemCheckbox.setAttribute('checked', 'true');
}
+ itemCheckbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
+ itemLabel.appendChild(itemCheckbox);
+
+ const itemName = document.createElement('span');
+ itemName.innerText = item;
+ itemLabel.appendChild(itemName);
+
+ itemRow.appendChild(itemLabel);
+ itemHintsDiv.appendChild(itemRow);
});
- });
- if (Object.keys(settings.game).length === 0) {
- errorMessage = 'You have not chosen a game to play!';
- }
+ itemHintsWrapper.appendChild(itemHintsDiv);
+ itemHintsContainer.appendChild(itemHintsWrapper);
- // Remove weights if there is only one game
- else if (Object.keys(settings.game).length === 1) {
- settings.game = Object.keys(settings.game)[0];
- }
+ // Starting Location Hints
+ const locationHintsWrapper = document.createElement('div');
+ locationHintsWrapper.classList.add('hints-wrapper');
+ locationHintsWrapper.innerText = 'Starting Location Hints';
- // If an error occurred, alert the user and do not export the file
- if (errorMessage) {
- userMessage.innerText = errorMessage;
- userMessage.classList.add('visible');
- userMessage.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
+ const locationHintsDiv = document.createElement('div');
+ locationHintsDiv.classList.add('simple-list');
+ this.data.gameLocations.forEach((location) => {
+ const locationRow = document.createElement('div');
+ locationRow.classList.add('list-row');
+
+ const locationLabel = document.createElement('label');
+ locationLabel.setAttribute('for', `${this.name}-start_location_hints-${location}`);
+
+ const locationCheckbox = document.createElement('input');
+ locationCheckbox.setAttribute('type', 'checkbox');
+ locationCheckbox.setAttribute('id', `${this.name}-start_location_hints-${location}`);
+ locationCheckbox.setAttribute('data-game', this.name);
+ locationCheckbox.setAttribute('data-setting', 'start_location_hints');
+ locationCheckbox.setAttribute('data-option', location);
+ if (this.current.start_location_hints.includes(location)) {
+ locationCheckbox.setAttribute('checked', '1');
+ }
+ locationCheckbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
+ locationLabel.appendChild(locationCheckbox);
+
+ const locationName = document.createElement('span');
+ locationName.innerText = location;
+ locationLabel.appendChild(locationName);
+
+ locationRow.appendChild(locationLabel);
+ locationHintsDiv.appendChild(locationRow);
});
- return;
+
+ locationHintsWrapper.appendChild(locationHintsDiv);
+ itemHintsContainer.appendChild(locationHintsWrapper);
+
+ hintsDiv.appendChild(itemHintsContainer);
+ return hintsDiv;
}
- // If no error occurred, hide the user message if it is visible
- userMessage.classList.remove('visible');
- return settings;
-};
+ #buildLocationsDiv() {
+ const locationsDiv = document.createElement('div');
+ locationsDiv.classList.add('locations-div');
+ const locationsHeader = document.createElement('h3');
+ locationsHeader.innerText = 'Priority & Exclusion Locations';
+ locationsDiv.appendChild(locationsHeader);
+ const locationsDescription = document.createElement('p');
+ locationsDescription.classList.add('setting-description');
+ locationsDescription.innerText = 'Priority locations guarantee a progression item will be placed there while ' +
+ 'excluded locations will not contain progression or useful items.';
+ locationsDiv.appendChild(locationsDescription);
-const exportSettings = () => {
- const settings = validateSettings();
- if (!settings) { return; }
+ const locationsContainer = document.createElement('div');
+ locationsContainer.classList.add('locations-container');
- const yamlText = jsyaml.safeDump(settings, { noCompatMode: true }).replaceAll(/'(\d+)':/g, (x, y) => `${y}:`);
- download(`${document.getElementById('player-name').value}.yaml`, yamlText);
-};
+ // Priority Locations
+ const priorityLocationsWrapper = document.createElement('div');
+ priorityLocationsWrapper.classList.add('locations-wrapper');
+ priorityLocationsWrapper.innerText = 'Priority Locations';
+
+ const priorityLocationsDiv = document.createElement('div');
+ priorityLocationsDiv.classList.add('simple-list');
+ this.data.gameLocations.forEach((location) => {
+ const locationRow = document.createElement('div');
+ locationRow.classList.add('list-row');
+
+ const locationLabel = document.createElement('label');
+ locationLabel.setAttribute('for', `${this.name}-priority_locations-${location}`);
+
+ const locationCheckbox = document.createElement('input');
+ locationCheckbox.setAttribute('type', 'checkbox');
+ locationCheckbox.setAttribute('id', `${this.name}-priority_locations-${location}`);
+ locationCheckbox.setAttribute('data-game', this.name);
+ locationCheckbox.setAttribute('data-setting', 'priority_locations');
+ locationCheckbox.setAttribute('data-option', location);
+ if (this.current.priority_locations.includes(location)) {
+ locationCheckbox.setAttribute('checked', '1');
+ }
+ locationCheckbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
+ locationLabel.appendChild(locationCheckbox);
+
+ const locationName = document.createElement('span');
+ locationName.innerText = location;
+ locationLabel.appendChild(locationName);
+
+ locationRow.appendChild(locationLabel);
+ priorityLocationsDiv.appendChild(locationRow);
+ });
+
+ priorityLocationsWrapper.appendChild(priorityLocationsDiv);
+ locationsContainer.appendChild(priorityLocationsWrapper);
+
+ // Exclude Locations
+ const excludeLocationsWrapper = document.createElement('div');
+ excludeLocationsWrapper.classList.add('locations-wrapper');
+ excludeLocationsWrapper.innerText = 'Exclude Locations';
+
+ const excludeLocationsDiv = document.createElement('div');
+ excludeLocationsDiv.classList.add('simple-list');
+ this.data.gameLocations.forEach((location) => {
+ const locationRow = document.createElement('div');
+ locationRow.classList.add('list-row');
+
+ const locationLabel = document.createElement('label');
+ locationLabel.setAttribute('for', `${this.name}-exclude_locations-${location}`);
+
+ const locationCheckbox = document.createElement('input');
+ locationCheckbox.setAttribute('type', 'checkbox');
+ locationCheckbox.setAttribute('id', `${this.name}-exclude_locations-${location}`);
+ locationCheckbox.setAttribute('data-game', this.name);
+ locationCheckbox.setAttribute('data-setting', 'exclude_locations');
+ locationCheckbox.setAttribute('data-option', location);
+ if (this.current.exclude_locations.includes(location)) {
+ locationCheckbox.setAttribute('checked', '1');
+ }
+ locationCheckbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
+ locationLabel.appendChild(locationCheckbox);
+
+ const locationName = document.createElement('span');
+ locationName.innerText = location;
+ locationLabel.appendChild(locationName);
+
+ locationRow.appendChild(locationLabel);
+ excludeLocationsDiv.appendChild(locationRow);
+ });
+
+ excludeLocationsWrapper.appendChild(excludeLocationsDiv);
+ locationsContainer.appendChild(excludeLocationsWrapper);
+
+ locationsDiv.appendChild(locationsContainer);
+ return locationsDiv;
+ }
+
+ #updateRangeSetting(evt) {
+ const setting = evt.target.getAttribute('data-setting');
+ const option = evt.target.getAttribute('data-option');
+ document.getElementById(`${this.name}-${setting}-${option}`).innerText = evt.target.value;
+ if (evt.action && evt.action === 'rangeDelete') {
+ delete this.current[setting][option];
+ } else {
+ this.current[setting][option] = parseInt(evt.target.value, 10);
+ }
+ this.save();
+ }
+
+ #updateListSetting(evt) {
+ const setting = evt.target.getAttribute('data-setting');
+ const option = evt.target.getAttribute('data-option');
+
+ if (evt.target.checked) {
+ // If the option is to be enabled and it is already enabled, do nothing
+ if (this.current[setting].includes(option)) { return; }
+
+ this.current[setting].push(option);
+ } else {
+ // If the option is to be disabled and it is already disabled, do nothing
+ if (!this.current[setting].includes(option)) { return; }
+
+ this.current[setting].splice(this.current[setting].indexOf(option), 1);
+ }
+ this.save();
+ }
+
+ #updateItemSetting(evt) {
+ const setting = evt.target.getAttribute('data-setting');
+ const option = evt.target.getAttribute('data-option');
+ if (setting === 'start_inventory') {
+ this.current[setting][option] = evt.target.value.trim() ? parseInt(evt.target.value) : 0;
+ } else {
+ this.current[setting][option] = isNaN(evt.target.value) ?
+ evt.target.value : parseInt(evt.target.value, 10);
+ }
+ this.save();
+ }
+
+ // Saves the current settings to local storage.
+ save() {
+ this.#allSettings.save();
+ }
+}
/** Create an anchor and trigger a download of a text file. */
const download = (filename, text) => {
@@ -1223,30 +1282,3 @@ const download = (filename, text) => {
downloadLink.click();
document.body.removeChild(downloadLink);
};
-
-const generateGame = (raceMode = false) => {
- const settings = validateSettings();
- if (!settings) { return; }
-
- axios.post('/api/generate', {
- weights: { player: JSON.stringify(settings) },
- presetData: { player: JSON.stringify(settings) },
- playerCount: 1,
- spoiler: 3,
- race: raceMode ? '1' : '0',
- }).then((response) => {
- window.location.href = response.data.url;
- }).catch((error) => {
- const userMessage = document.getElementById('user-message');
- userMessage.innerText = 'Something went wrong and your game could not be generated.';
- if (error.response.data.text) {
- userMessage.innerText += ' ' + error.response.data.text;
- }
- userMessage.classList.add('visible');
- userMessage.scrollIntoView({
- behavior: 'smooth',
- block: 'start',
- });
- console.error(error);
- });
-};