Archipelago/WebHostLib/static/assets/weighted-settings.js

1023 lines
40 KiB
JavaScript

window.addEventListener('load', () => {
fetchSettingData().then((results) => {
let settingHash = localStorage.getItem('weighted-settings-hash');
if (!settingHash) {
// If no hash data has been set before, set it now
localStorage.setItem('weighted-settings-hash', md5(JSON.stringify(results)));
localStorage.removeItem('weighted-settings');
settingHash = md5(JSON.stringify(results));
}
if (settingHash !== md5(JSON.stringify(results))) {
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.";
userMessage.classList.add('visible');
userMessage.addEventListener('click', resetSettings);
}
// Page setup
createDefaultSettings(results);
buildUI(results);
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());
// 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;
});
});
const resetSettings = () => {
localStorage.removeItem('weighted-settings');
localStorage.removeItem('weighted-settings-hash')
window.location.reload();
};
const fetchSettingData = () => new Promise((resolve, reject) => {
fetch(new Request(`${window.location.origin}/static/generated/weighted-settings.json`)).then((response) => {
try{ response.json().then((jsonObj) => resolve(jsonObj)); }
catch(error){ reject(error); }
});
});
const createDefaultSettings = (settingData) => {
if (!localStorage.getItem('weighted-settings')) {
const newSettings = {};
// Transfer base options directly
for (let baseOption of Object.keys(settingData.baseOptions)){
newSettings[baseOption] = settingData.baseOptions[baseOption];
}
// Set options per game
for (let game of Object.keys(settingData.games)) {
// Initialize game object
newSettings[game] = {};
// Transfer game settings
for (let gameSetting of Object.keys(settingData.games[game].gameSettings)){
newSettings[game][gameSetting] = {};
const setting = settingData.games[game].gameSettings[gameSetting];
switch(setting.type){
case 'select':
setting.options.forEach((option) => {
newSettings[game][gameSetting][option.value] =
(setting.hasOwnProperty('defaultValue') && setting.defaultValue === option.value) ? 25 : 0;
});
break;
case 'range':
for (let i = setting.min; i <= setting.max; ++i){
newSettings[game][gameSetting][i] =
(setting.hasOwnProperty('defaultValue') && setting.defaultValue === i) ? 25 : 0;
}
newSettings[game][gameSetting]['random'] = 0;
newSettings[game][gameSetting]['random-low'] = 0;
newSettings[game][gameSetting]['random-high'] = 0;
break;
case 'items-list':
case 'locations-list':
case 'custom-list':
newSettings[game][gameSetting] = [];
break;
default:
console.error(`Unknown setting type for ${game} setting ${gameSetting}: ${setting.type}`);
}
}
newSettings[game].start_inventory = {};
newSettings[game].exclude_locations = [];
newSettings[game].local_items = [];
newSettings[game].non_local_items = [];
newSettings[game].start_hints = [];
newSettings[game].start_location_hints = [];
}
localStorage.setItem('weighted-settings', JSON.stringify(newSettings));
}
};
const buildUI = (settingData) => {
// Build the game-choice div
buildGameChoice(settingData.games);
const gamesWrapper = document.getElementById('games-wrapper');
Object.keys(settingData.games).forEach((game) => {
// Create game div, invisible by default
const gameDiv = document.createElement('div');
gameDiv.setAttribute('id', `${game}-div`);
gameDiv.classList.add('game-div');
gameDiv.classList.add('invisible');
const gameHeader = document.createElement('h2');
gameHeader.innerText = game;
gameDiv.appendChild(gameHeader);
const collapseButton = document.createElement('a');
collapseButton.innerText = '(Collapse)';
gameDiv.appendChild(collapseButton);
const expandButton = document.createElement('a');
expandButton.innerText = '(Expand)';
expandButton.classList.add('invisible');
gameDiv.appendChild(expandButton);
const weightedSettingsDiv = buildWeightedSettingsDiv(game, settingData.games[game].gameSettings);
gameDiv.appendChild(weightedSettingsDiv);
const itemsDiv = buildItemsDiv(game, settingData.games[game].gameItems);
gameDiv.appendChild(itemsDiv);
const hintsDiv = buildHintsDiv(game, settingData.games[game].gameItems, settingData.games[game].gameLocations);
gameDiv.appendChild(hintsDiv);
gamesWrapper.appendChild(gameDiv);
collapseButton.addEventListener('click', () => {
collapseButton.classList.add('invisible');
weightedSettingsDiv.classList.add('invisible');
itemsDiv.classList.add('invisible');
hintsDiv.classList.add('invisible');
expandButton.classList.remove('invisible');
});
expandButton.addEventListener('click', () => {
collapseButton.classList.remove('invisible');
weightedSettingsDiv.classList.remove('invisible');
itemsDiv.classList.remove('invisible');
hintsDiv.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);
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(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 = 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);
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) => {
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', updateGameSetting);
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':
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) {
const tr = document.createElement('tr');
const tdLeft = document.createElement('td');
tdLeft.classList.add('td-left');
tdLeft.innerText = i;
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-setting', settingName);
range.setAttribute('data-option', i);
range.setAttribute('min', 0);
range.setAttribute('max', 50);
range.addEventListener('change', updateGameSetting);
range.value = currentSettings[game][settingName][i];
tdMiddle.appendChild(range);
tr.appendChild(tdMiddle);
const tdRight = document.createElement('td');
tdRight.setAttribute('id', `${game}-${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.<br />Minimum value: ${setting.min}<br />` +
`Maximum value: ${setting.max}`;
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')); }
});
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; }
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', `${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', updateGameSetting);
range.value = currentSettings[game][settingName][parseInt(option, 10)];
tdMiddle.appendChild(range);
tr.appendChild(tdMiddle);
const tdRight = document.createElement('td');
tdRight.setAttribute('id', `${game}-${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;
range.dispatchEvent(new Event('change'));
rangeTbody.removeChild(tr);
});
tdDelete.appendChild(deleteButton);
tr.appendChild(tdDelete);
rangeTbody.appendChild(tr);
});
Object.keys(currentSettings[game][settingName]).forEach((option) => {
if (currentSettings[game][settingName][option] > 0) {
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', `${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', updateGameSetting);
range.value = currentSettings[game][settingName][parseInt(option, 10)];
tdMiddle.appendChild(range);
tr.appendChild(tdMiddle);
const tdRight = document.createElement('td');
tdRight.setAttribute('id', `${game}-${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;
range.dispatchEvent(new Event('change'));
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');
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', `${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', updateGameSetting);
range.value = currentSettings[game][settingName][option];
tdMiddle.appendChild(range);
tr.appendChild(tdMiddle);
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);
});
rangeTable.appendChild(rangeTbody);
settingWrapper.appendChild(rangeTable);
break;
case 'items-list':
// TODO
break;
case 'locations-list':
// TODO
break;
case 'custom-list':
// TODO
break;
default:
console.error(`Unknown setting type for ${game} setting ${setting}: ${settings[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];
}
} 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. Excluded locations will not contain progression items.';
hintsDiv.appendChild(hintsDescription);
const itemHintsContainer = document.createElement('div');
itemHintsContainer.classList.add('hints-container');
const itemHintsWrapper = document.createElement('div');
itemHintsWrapper.classList.add('hints-wrapper');
itemHintsWrapper.innerText = 'Starting Item Hints';
const itemHintsDiv = document.createElement('div');
itemHintsDiv.classList.add('item-container');
items.forEach((item) => {
const itemDiv = document.createElement('div');
itemDiv.classList.add('hint-div');
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', hintChangeHandler);
itemLabel.appendChild(itemCheckbox);
const itemName = document.createElement('span');
itemName.innerText = item;
itemLabel.appendChild(itemName);
itemDiv.appendChild(itemLabel);
itemHintsDiv.appendChild(itemDiv);
});
itemHintsWrapper.appendChild(itemHintsDiv);
itemHintsContainer.appendChild(itemHintsWrapper);
const locationHintsWrapper = document.createElement('div');
locationHintsWrapper.classList.add('hints-wrapper');
locationHintsWrapper.innerText = 'Starting Location Hints';
const locationHintsDiv = document.createElement('div');
locationHintsDiv.classList.add('item-container');
locations.forEach((location) => {
const locationDiv = document.createElement('div');
locationDiv.classList.add('hint-div');
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', hintChangeHandler);
locationLabel.appendChild(locationCheckbox);
const locationName = document.createElement('span');
locationName.innerText = location;
locationLabel.appendChild(locationName);
locationDiv.appendChild(locationLabel);
locationHintsDiv.appendChild(locationDiv);
});
locationHintsWrapper.appendChild(locationHintsDiv);
itemHintsContainer.appendChild(locationHintsWrapper);
const excludeLocationsWrapper = document.createElement('div');
excludeLocationsWrapper.classList.add('hints-wrapper');
excludeLocationsWrapper.innerText = 'Exclude Locations';
const excludeLocationsDiv = document.createElement('div');
excludeLocationsDiv.classList.add('item-container');
locations.forEach((location) => {
const locationDiv = document.createElement('div');
locationDiv.classList.add('hint-div');
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', hintChangeHandler);
locationLabel.appendChild(locationCheckbox);
const locationName = document.createElement('span');
locationName.innerText = location;
locationLabel.appendChild(locationName);
locationDiv.appendChild(locationLabel);
excludeLocationsDiv.appendChild(locationDiv);
});
excludeLocationsWrapper.appendChild(excludeLocationsDiv);
itemHintsContainer.appendChild(excludeLocationsWrapper);
hintsDiv.appendChild(itemHintsContainer);
return hintsDiv;
};
const hintChangeHandler = (evt) => {
const currentSettings = 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 (!currentSettings[game][setting].includes(option)) {
currentSettings[game][setting].push(option);
}
} else {
if (currentSettings[game][setting].includes(option)) {
currentSettings[game][setting].splice(currentSettings[game][setting].indexOf(option), 1);
}
}
localStorage.setItem('weighted-settings', JSON.stringify(currentSettings));
};
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 updateGameSetting = (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;
options[game][setting][option] = isNaN(evt.target.value) ?
evt.target.value : parseInt(evt.target.value, 10);
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',
});
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;
}
// Remove any disabled options
Object.keys(settings[game]).forEach((setting) => {
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!`;
}
});
});
if (Object.keys(settings.game).length === 0) {
errorMessage = 'You have not chosen a game to play!';
}
// 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;
};
const exportSettings = () => {
const settings = 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);
};
/** 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 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,
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);
});
};