diff --git a/BaseClasses.py b/BaseClasses.py
index bbf110d2..13e74d13 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -782,10 +782,9 @@ class RegionType(int, Enum):
class Region(object):
-
- def __init__(self, name: str, type: str, hint, player: int, world: Optional[MultiWorld] = None):
+ def __init__(self, name: str, type_: RegionType, hint, player: int, world: Optional[MultiWorld] = None):
self.name = name
- self.type = type
+ self.type = type_
self.entrances = []
self.exits = []
self.locations = []
diff --git a/Generate.py b/Generate.py
index 4704a59d..295eb140 100644
--- a/Generate.py
+++ b/Generate.py
@@ -23,6 +23,7 @@ import Options
from worlds.alttp import Bosses
from worlds.alttp.Text import TextTable
from worlds.AutoWorld import AutoWorldRegister
+import copy
categories = set(AutoWorldRegister.world_types)
@@ -148,7 +149,7 @@ def main(args=None, callback=ERmain):
if category_name is None:
weights_cache[path][key] = option
elif category_name not in weights_cache[path]:
- raise Exception(f"Meta: Category {category_name} is not present in {path}.")
+ logging.warning(f"Meta: Category {category_name} is not present in {path}.")
else:
weights_cache[path][category_name][key] = option
@@ -330,7 +331,7 @@ def update_weights(weights: dict, new_weights: dict, type: str, name: str) -> di
def roll_linked_options(weights: dict) -> dict:
- weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
+ weights = copy.deepcopy(weights) # make sure we don't write back to other weights sets in same_settings
for option_set in weights["linked_options"]:
if "name" not in option_set:
raise ValueError("One of your linked options does not have a name.")
@@ -352,7 +353,7 @@ def roll_linked_options(weights: dict) -> dict:
def roll_triggers(weights: dict, triggers: list) -> dict:
- weights = weights.copy() # make sure we don't write back to other weights sets in same_settings
+ weights = copy.deepcopy(weights) # make sure we don't write back to other weights sets in same_settings
weights["_Generator_Version"] = Utils.__version__
for i, option_set in enumerate(triggers):
try:
diff --git a/Options.py b/Options.py
index ef786e47..a5ef5f9c 100644
--- a/Options.py
+++ b/Options.py
@@ -125,6 +125,8 @@ class Toggle(Option):
def get_option_name(cls, value):
return ["No", "Yes"][int(value)]
+ __hash__ = Option.__hash__ # see https://docs.python.org/3/reference/datamodel.html#object.__hash__
+
class DefaultOnToggle(Toggle):
default = 1
@@ -184,6 +186,8 @@ class Choice(Option):
else:
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
+ __hash__ = Option.__hash__ # see https://docs.python.org/3/reference/datamodel.html#object.__hash__
+
class Range(Option, int):
range_start = 0
diff --git a/Utils.py b/Utils.py
index df5d3c96..c2eab405 100644
--- a/Utils.py
+++ b/Utils.py
@@ -23,7 +23,7 @@ class Version(typing.NamedTuple):
build: int
-__version__ = "0.2.2"
+__version__ = "0.2.3"
version_tuple = tuplize_version(__version__)
from yaml import load, dump, safe_load
diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py
index 085cfe56..847e99c8 100644
--- a/WebHostLib/__init__.py
+++ b/WebHostLib/__init__.py
@@ -193,6 +193,15 @@ def discord():
return redirect("https://discord.gg/archipelago")
+@app.route('/datapackage')
+@cache.cached()
+def get_datapackge():
+ """A pretty print version of /api/datapackage"""
+ from worlds import network_data_package
+ import json
+ return Response(json.dumps(network_data_package, indent=4), mimetype="text/plain")
+
+
from WebHostLib.customserver import run_server_process
from . import tracker, upload, landing, check, generate, downloads, api # to trigger app routing picking up on it
diff --git a/WebHostLib/api/__init__.py b/WebHostLib/api/__init__.py
index 63483abc..e7029722 100644
--- a/WebHostLib/api/__init__.py
+++ b/WebHostLib/api/__init__.py
@@ -31,6 +31,7 @@ def get_datapackge():
from worlds import network_data_package
return network_data_package
+
@api_endpoints.route('/datapackage_version')
@cache.cached()
def get_datapackge_versions():
diff --git a/WebHostLib/static/assets/gameInfo/en_Slay the Spire.md b/WebHostLib/static/assets/gameInfo/en_Slay the Spire.md
new file mode 100644
index 00000000..9ec04834
--- /dev/null
+++ b/WebHostLib/static/assets/gameInfo/en_Slay the Spire.md
@@ -0,0 +1,29 @@
+# Slay the Spire (PC)
+
+## Where is the settings page?
+The player settings page for this game is located here. It contains all the options
+you need to configure and export a config file.
+
+## What does randomization do to this game?
+Every non-boss relic drop, every boss relic and rare card drop, and every other card draw is replaced with an
+archipelago item. In heart runs, the blue key is also disconnected from the Archipelago item, so you can gather both.
+
+## What items and locations get shuffled?
+15 card packs, 10 relics, and 3 boss relics and rare card drops are shuffled into the item pool and can be found at any
+location that would normally give you these items, except for card packs, which are found at every other normal enemy
+encounter.
+
+## Which items can be in another player's world?
+Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to
+limit certain items to your own world.
+
+## When the player receives an item, what happens?
+When the player receives an item, you will see the counter in the top right corner with the Archipelago symbol increment
+by one. By clicking on this icon, it'll open a menu that lists all the items you received, but have not yet accepted.
+You can take any relics and card packs sent to you and add them to your current run. It is advised that you do not open
+this menu until you are outside an encounter or event to prevent the game from soft-locking.
+
+## What happens if a player dies in a run?
+When a player dies, they will be taken back to the main menu and will need to reconnect to start climbing the spire
+from the beginning, but they will have access to all the items ever sent to them in the Archipelago menu in the top
+right. Any items found in an earlier run will not be sent again if you encounter them in the same location.
diff --git a/WebHostLib/static/assets/tutorial/slay-the-spire/slay-the-spire_en.md b/WebHostLib/static/assets/tutorial/slay-the-spire/slay-the-spire_en.md
new file mode 100644
index 00000000..9ff2bc32
--- /dev/null
+++ b/WebHostLib/static/assets/tutorial/slay-the-spire/slay-the-spire_en.md
@@ -0,0 +1,32 @@
+# Slay the Spire Setup Guide
+
+## Required Software
+
+For steam-based installation, subscribe to the following mods:
+
+- ModTheSpire from the [Slay the Spire Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=1605060445)
+- BaseMod from the [Slay the Spire Workshop](https://steamcommunity.com/workshop/filedetails/?id=1605833019)
+- Archipelago Multiworld Randomizer Mod from the [Slay the Spire Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=2596397288)
+
+## Configuring your YAML file
+
+### What is a YAML file and why do I need one?
+Your YAML file contains a set of configuration options which provide the generator with information about how
+it should generate your game. Each player of a multiworld will provide their own YAML file. This setup allows
+each player to enjoy an experience customized for their taste, and different players in the same multiworld
+can all have different options.
+
+### Where do I get a YAML file?
+you can customize your settings by visiting the [Slay the Spire Settings Page](/games/Slay%20the%20Spire/player-settings).
+
+### Connect to the MultiServer
+For Steam-based installations, if you are subscribed to ModTheSpire, when you launch the game, you should have the
+option to launch the game with mods. On the mod loader screen, ensure you only have the following mods enabled and then
+start the game:
+
+- BaseMod
+- Archipelago Multiworld Randomizer
+
+Once you are in-game, you will be able to click the **Archipelago** menu option and enter the ip and port (separated by
+a colon) in the hostname field and enter your player slot name in the Slot Name field. Then click connect, and now you
+are ready to climb the spire!
diff --git a/WebHostLib/static/assets/tutorial/tutorials.json b/WebHostLib/static/assets/tutorial/tutorials.json
index 882d931f..66a46336 100644
--- a/WebHostLib/static/assets/tutorial/tutorials.json
+++ b/WebHostLib/static/assets/tutorial/tutorials.json
@@ -390,5 +390,24 @@
]
}
]
+ },
+ {
+ "gameTitle": "Slay the Spire",
+ "tutorials": [
+ {
+ "name": "Multiworld Setup Guide",
+ "description": "A guide to setting up Slay the Spire for Archipelago. This guide covers single-player, multiworld, and related software.",
+ "files": [
+ {
+ "language": "English",
+ "filename": "slay-the-spire/slay-the-spire_en.md",
+ "link": "slay-the-spire/slay-the-spire/en",
+ "authors": [
+ "Phar"
+ ]
+ }
+ ]
+ }
+ ]
}
]
diff --git a/WebHostLib/static/assets/weighted-settings.js b/WebHostLib/static/assets/weighted-settings.js
index 91789c2f..7d0a9698 100644
--- a/WebHostLib/static/assets/weighted-settings.js
+++ b/WebHostLib/static/assets/weighted-settings.js
@@ -128,19 +128,24 @@ const buildUI = (settingData) => {
expandButton.classList.add('invisible');
gameDiv.appendChild(expandButton);
- const optionsDiv = buildOptionsDiv(game, settingData.games[game].gameSettings);
- gameDiv.appendChild(optionsDiv);
+ const weightedSettingsDiv = buildWeightedSettingsDiv(game, settingData.games[game].gameSettings);
+ gameDiv.appendChild(weightedSettingsDiv);
+
+ const itemsDiv = buildItemsDiv(game, settingData.games[game].gameItems);
+ gameDiv.appendChild(itemsDiv);
gamesWrapper.appendChild(gameDiv);
collapseButton.addEventListener('click', () => {
collapseButton.classList.add('invisible');
- optionsDiv.classList.add('invisible');
+ weightedSettingsDiv.classList.add('invisible');
+ itemsDiv.classList.add('invisible');
expandButton.classList.remove('invisible');
});
expandButton.addEventListener('click', () => {
collapseButton.classList.remove('invisible');
- optionsDiv.classList.remove('invisible');
+ weightedSettingsDiv.classList.remove('invisible');
+ itemsDiv.classList.remove('invisible');
expandButton.classList.add('invisible');
});
});
@@ -207,10 +212,10 @@ const buildGameChoice = (games) => {
gameChoiceDiv.appendChild(table);
};
-const buildOptionsDiv = (game, settings) => {
+const buildWeightedSettingsDiv = (game, settings) => {
const currentSettings = JSON.parse(localStorage.getItem('weighted-settings'));
- const optionsWrapper = document.createElement('div');
- optionsWrapper.classList.add('settings-wrapper');
+ const settingsWrapper = document.createElement('div');
+ settingsWrapper.classList.add('settings-wrapper');
Object.keys(settings).forEach((settingName) => {
const setting = settings[settingName];
@@ -268,27 +273,6 @@ const buildOptionsDiv = (game, settings) => {
break;
case 'range':
- const hintText = document.createElement('p');
- hintText.classList.add('hint-text');
- hintText.innerHTML = 'This is a range option. You may enter valid numerical values in the text box below, ' +
- `then press the "Add" button to add a weight for it.
Minimum value: ${setting.min}
` +
- `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')); }
- });
-
const rangeTable = document.createElement('table');
const rangeTbody = document.createElement('tbody');
@@ -324,6 +308,79 @@ const buildOptionsDiv = (game, settings) => {
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}`;
+ 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');
@@ -403,58 +460,6 @@ const buildOptionsDiv = (game, settings) => {
rangeTable.appendChild(rangeTbody);
settingWrapper.appendChild(rangeTable);
-
- 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);
- });
break;
default:
@@ -462,10 +467,158 @@ const buildOptionsDiv = (game, settings) => {
return;
}
- optionsWrapper.appendChild(settingWrapper);
+ settingsWrapper.appendChild(settingWrapper);
});
- return optionsWrapper;
+ return settingsWrapper;
+};
+
+const buildItemsDiv = (game, items) => {
+ 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.sort().forEach((item) => {
+ const itemDiv = buildItemDiv(game, item);
+
+ if (currentSettings[game].start_inventory.includes(item)){
+ itemDiv.setAttribute('data-setting', 'start_inventory');
+ startInventory.appendChild(itemDiv);
+ } else if (currentSettings[game].local_items.includes(item)) {
+ itemDiv.setAttribute('data-setting', 'local_items');
+ localItems.appendChild(itemDiv);
+ } else if (currentSettings[game].non_local_items.includes(item)) {
+ itemDiv.setAttribute('data-setting', 'non_local_items');
+ nonLocalItems.appendChild(itemDiv);
+ } else {
+ 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 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 itemDiv = buildItemDiv(game, 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;
+
+ if (oldSetting) {
+ 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 (!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 updateVisibleGames = () => {
diff --git a/WebHostLib/static/styles/weighted-settings.css b/WebHostLib/static/styles/weighted-settings.css
index ae488aff..28ab4a10 100644
--- a/WebHostLib/static/styles/weighted-settings.css
+++ b/WebHostLib/static/styles/weighted-settings.css
@@ -90,6 +90,38 @@ html{
cursor: pointer;
}
+#weighted-settings .items-wrapper{
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+
+#weighted-settings .items-div h3{
+ margin-bottom: 0.5rem;
+}
+
+#weighted-settings .items-wrapper .item-set-wrapper{
+ width: 24%;
+}
+
+#weighted-settings .items-wrapper .item-container{
+ border: 1px solid #ffffff;
+ border-radius: 2px;
+ width: 100%;
+ height: 300px;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#weighted-settings .items-wrapper .item-container .item-div{
+ padding: 0.15rem;
+ cursor: pointer;
+}
+
+#weighted-settings .items-wrapper .item-container .item-div:hover{
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
#weighted-settings #weighted-settings-button-row{
display: flex;
flex-direction: row;
diff --git a/WebHostLib/templates/supportedGames.html b/WebHostLib/templates/supportedGames.html
index f6409916..7ed14ce0 100644
--- a/WebHostLib/templates/supportedGames.html
+++ b/WebHostLib/templates/supportedGames.html
@@ -9,7 +9,7 @@
{% include 'header/grassHeader.html' %}
Settings Page diff --git a/WebHostLib/templates/userContent.html b/WebHostLib/templates/userContent.html index 6d32f100..3e34c0e3 100644 --- a/WebHostLib/templates/userContent.html +++ b/WebHostLib/templates/userContent.html @@ -31,7 +31,7 @@