Merge branch 'main' into docs_consolidation
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
29
WebHostLib/static/assets/gameInfo/en_Slay the Spire.md
Normal file
29
WebHostLib/static/assets/gameInfo/en_Slay the Spire.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Slay the Spire (PC)
|
||||
|
||||
## Where is the settings page?
|
||||
The player settings page for this game is located <a href="../player-settings">here</a>. 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.
|
||||
@@ -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!
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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.<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')); }
|
||||
});
|
||||
|
||||
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.<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');
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% include 'header/grassHeader.html' %}
|
||||
<div id="games">
|
||||
<h1>Currently Supported Games</h1>
|
||||
{% for game, description in worlds.items() %}
|
||||
{% for game, description in worlds.items() | sort %}
|
||||
<h3><a href="{{ url_for("game_info", game=game, lang="en") }}">{{ game }}</a></h3>
|
||||
<p>
|
||||
<a href="{{ url_for("player_settings", game=game) }}">Settings Page</a>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<tr>
|
||||
<td><a href="{{ url_for("view_seed", seed=room.seed.id) }}">{{ room.seed.id|suuid }}</a></td>
|
||||
<td><a href="{{ url_for("host_room", room=room.id) }}">{{ room.id|suuid }}</a></td>
|
||||
<td>>={{ room.seed.slots|length }}</td>
|
||||
<td>{{ room.seed.slots|length }}</td>
|
||||
<td>{{ room.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
|
||||
<td>{{ room.last_activity.strftime("%Y-%m-%d %H:%M") }}</td>
|
||||
</tr>
|
||||
@@ -56,7 +56,7 @@
|
||||
{% for seed in seeds %}
|
||||
<tr>
|
||||
<td><a href="{{ url_for("view_seed", seed=seed.id) }}">{{ seed.id|suuid }}</a></td>
|
||||
<td>{% if seed.multidata %}>={{ seed.slots|length }}{% else %}1{% endif %}
|
||||
<td>{% if seed.multidata %}{{ seed.slots|length }}{% else %}1{% endif %}
|
||||
</td>
|
||||
<td>{{ seed.creation_time.strftime("%Y-%m-%d %H:%M") }}</td>
|
||||
</tr>
|
||||
|
||||
@@ -100,7 +100,7 @@ def uploads():
|
||||
if file.filename == '':
|
||||
flash('No selected file')
|
||||
elif file and allowed_file(file.filename):
|
||||
if zipfile.is_zipfile(file.filename):
|
||||
if zipfile.is_zipfile(file):
|
||||
with zipfile.ZipFile(file, 'r') as zfile:
|
||||
res = upload_zip_to_db(zfile)
|
||||
if type(res) == str:
|
||||
@@ -108,12 +108,13 @@ def uploads():
|
||||
elif res:
|
||||
return redirect(url_for("view_seed", seed=res.id))
|
||||
else:
|
||||
file.seek(0) # offset from is_zipfile check
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
multidata = file.read()
|
||||
MultiServer.Context.decompress(multidata)
|
||||
except:
|
||||
flash("Could not load multidata. File may be corrupted or incompatible.")
|
||||
except Exception as e:
|
||||
flash(f"Could not load multidata. File may be corrupted or incompatible. ({e})")
|
||||
else:
|
||||
seed = Seed(multidata=multidata, owner=session["_id"])
|
||||
flush() # place into DB and generate ids
|
||||
|
||||
Reference in New Issue
Block a user