This commit is contained in:
CookieCat
2023-11-04 17:55:48 -04:00
parent 60df274157
commit bd8698e1fd
102 changed files with 1190 additions and 1463 deletions

View File

@@ -139,13 +139,7 @@ def create():
weighted_options["games"][game_name] = {}
weighted_options["games"][game_name]["gameSettings"] = game_options
weighted_options["games"][game_name]["gameItems"] = tuple(world.item_names)
weighted_options["games"][game_name]["gameItemGroups"] = [
group for group in world.item_name_groups.keys() if group != "Everything"
]
weighted_options["games"][game_name]["gameLocations"] = tuple(world.location_names)
weighted_options["games"][game_name]["gameLocationGroups"] = [
group for group in world.location_name_groups.keys() if group != "Everywhere"
]
with open(os.path.join(target_folder, 'weighted-options.json'), "w") as f:
json.dump(weighted_options, f, indent=2, separators=(',', ': '))

View File

@@ -2,62 +2,13 @@
## What is a randomizer?
A randomizer is a modification of a game which reorganizes the items required to progress through that game. A
normal play-through might require you to use item A to unlock item B, then C, and so forth. In a randomized
A randomizer is a modification of a video game which reorganizes the items required to progress through the game. A
normal play-through of a game might require you to use item A to unlock item B, then C, and so forth. In a randomized
game, you might first find item C, then A, then B.
This transforms the game from a linear experience into a puzzle, presenting players with a new challenge each time they
play. Putting items in non-standard locations can require the player to think about the game world and the items they
encounter in new and interesting ways.
## What is a multiworld?
While a randomizer shuffles a game, a multiworld randomizer shuffles that game for multiple players. For example, in a
two player multiworld, players A and B each get their own randomized version of a game, called a world. In each
player's game, they may find items which belong to the other player. If player A finds an item which belongs to
player B, the item will be sent to player B's world over the internet. This creates a cooperative experience, requiring
players to rely upon each other to complete their game.
## What does multi-game mean?
While a multiworld game traditionally requires all players to be playing the same game, a multi-game multiworld allows
players to randomize any of the supported games, and send items between them. This allows players of different
games to interact with one another in a single multiplayer environment. Archipelago supports multi-game multiworld.
Here is a list of our [Supported Games](https://archipelago.gg/games).
## Can I generate a single-player game with Archipelago?
Yes. All of our supported games can be generated as single-player experiences both on the website and by installing
the Archipelago generator software. The fastest way to do this is on the website. Find the Supported Game you wish to
play, open the Settings Page, pick your settings, and click Generate Game.
## How do I get started?
We have a [Getting Started](https://archipelago.gg/tutorial/Archipelago/setup/en) guide that will help you get the
software set up. You can use that guide to learn how to generate multiworlds. There are also basic instructions for
including multiple games, and hosting multiworlds on the website for ease and convenience.
If you are ready to start randomizing games, or want to start playing your favorite randomizer with others, please join
our discord server at the [Archipelago Discord](https://discord.gg/8Z65BR2). There are always people ready to answer
any questions you might have.
## What are some common terms I should know?
As randomizers and multiworld randomizers have been around for a while now, there are quite a few common terms used
by the communities surrounding them. A list of Archipelago jargon and terms commonly used by the community can be
found in the [Glossary](/glossary/en).
## Does everyone need to be connected at the same time?
There are two different play-styles that are common for Archipelago multiworld sessions. These sessions can either
be considered synchronous (or "sync"), where everyone connects and plays at the same time, or asynchronous (or "async"),
where players connect and play at their own pace. The setup for both is identical. The difference in play-style is how
you and your friends choose to organize and play your multiworld. Most groups decide on the format before creating
their multiworld.
If a player must leave early, they can use Archipelago's release system. When a player releases their game, all items
in that game belonging to other players are sent out automatically. This allows other players to continue to play
uninterrupted. Here is a list of all of our [Server Commands](https://archipelago.gg/tutorial/Archipelago/commands/en).
This transforms games from a linear experience into a puzzle, presenting players with a new challenge each time they
play a randomized game. Putting items in non-standard locations can require the player to think about the game world and
the items they encounter in new and interesting ways.
## What happens if an item is placed somewhere it is impossible to get?
@@ -66,15 +17,53 @@ is to ensure items necessary to complete the game will be accessible to the play
rules allowing certain items to be placed in normally unreachable locations, provided the player has indicated they are
comfortable exploiting certain glitches in the game.
## What is a multi-world?
While a randomizer shuffles a game, a multi-world randomizer shuffles that game for multiple players. For example, in a
two player multi-world, players A and B each get their own randomized version of a game, called a world. In each player's
game, they may find items which belong to the other player. If player A finds an item which belongs to player B, the
item will be sent to player B's world over the internet.
This creates a cooperative experience during multi-world games, requiring players to rely upon each other to complete
their game.
## What happens if a person has to leave early?
If a player must leave early, they can use Archipelago's release system. When a player releases their game, all the
items in that game which belong to other players are sent out automatically, so other players can continue to play.
## What does multi-game mean?
While a multi-world game traditionally requires all players to be playing the same game, a multi-game multi-world allows
players to randomize any of a number of supported games, and send items between them. This allows players of different
games to interact with one another in a single multiplayer environment.
## Can I generate a single-player game with Archipelago?
Yes. All our supported games can be generated as single-player experiences, and so long as you download the software,
the website is not required to generate them.
## How do I get started?
If you are ready to start randomizing games, or want to start playing your favorite randomizer with others, please join
our discord server at the [Archipelago Discord](https://discord.gg/8Z65BR2). There are always people ready to answer
any questions you might have.
## What are some common terms I should know?
As randomizers and multiworld randomizers have been around for a while now there are quite a lot of common terms
and jargon that is used in conjunction by the communities surrounding them. For a lot of the terms that are more common
to Archipelago and its specific systems please see the [Glossary](/glossary/en).
## I want to add a game to the Archipelago randomizer. How do I do that?
The best way to get started is to take a look at our code on GitHub:
[Archipelago GitHub Page](https://github.com/ArchipelagoMW/Archipelago).
The best way to get started is to take a look at our code on GitHub
at [Archipelago GitHub Page](https://github.com/ArchipelagoMW/Archipelago).
There, you will find examples of games in the `worlds` folder:
[/worlds Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/worlds).
There you will find examples of games in the worlds folder
at [/worlds Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/worlds).
You may also find developer documentation in the `docs` folder:
[/docs Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/docs).
You may also find developer documentation in the docs folder
at [/docs Folder in Archipelago Code](https://github.com/ArchipelagoMW/Archipelago/tree/main/docs).
If you have more questions, feel free to ask in the **#archipelago-dev** channel on our Discord.

View File

@@ -43,7 +43,7 @@ const resetSettings = () => {
};
const fetchSettingData = () => new Promise((resolve, reject) => {
fetch(new Request(`${window.location.origin}/static/generated/weighted-options.json`)).then((response) => {
fetch(new Request(`${window.location.origin}/static/generated/weighted-settings.json`)).then((response) => {
try{ response.json().then((jsonObj) => resolve(jsonObj)); }
catch(error){ reject(error); }
});
@@ -428,13 +428,13 @@ class GameSettings {
const weightedSettingsDiv = this.#buildWeightedSettingsDiv();
gameDiv.appendChild(weightedSettingsDiv);
const itemPoolDiv = this.#buildItemPoolDiv();
const itemPoolDiv = this.#buildItemsDiv();
gameDiv.appendChild(itemPoolDiv);
const hintsDiv = this.#buildHintsDiv();
gameDiv.appendChild(hintsDiv);
const locationsDiv = this.#buildPriorityExclusionDiv();
const locationsDiv = this.#buildLocationsDiv();
gameDiv.appendChild(locationsDiv);
collapseButton.addEventListener('click', () => {
@@ -734,17 +734,107 @@ class GameSettings {
break;
case 'items-list':
const itemsList = this.#buildItemsDiv(settingName);
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');
}
const itemName = document.createElement('span');
itemName.innerText = item.toString();
itemLabel.appendChild(itemCheckbox);
itemLabel.appendChild(itemName);
itemRow.appendChild(itemLabel);
itemsList.appendChild((itemRow));
});
settingWrapper.appendChild(itemsList);
break;
case 'locations-list':
const locationsList = this.#buildLocationsDiv(settingName);
const locationsList = document.createElement('div');
locationsList.classList.add('simple-list');
Object.values(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}-${settingName}-${location}`)
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');
}
const locationName = document.createElement('span');
locationName.innerText = location.toString();
locationLabel.appendChild(locationCheckbox);
locationLabel.appendChild(locationName);
locationRow.appendChild(locationLabel);
locationsList.appendChild((locationRow));
});
settingWrapper.appendChild(locationsList);
break;
case 'custom-list':
const customList = this.#buildListDiv(settingName, this.data.gameSettings[settingName].options);
const customList = document.createElement('div');
customList.classList.add('simple-list');
Object.values(this.data.gameSettings[settingName].options).forEach((listItem) => {
const customListRow = document.createElement('div');
customListRow.classList.add('list-row');
const customItemLabel = document.createElement('label');
customItemLabel.setAttribute('for', `${this.name}-${settingName}-${listItem}`)
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');
}
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;
@@ -759,7 +849,7 @@ class GameSettings {
return settingsWrapper;
}
#buildItemPoolDiv() {
#buildItemsDiv() {
const itemsDiv = document.createElement('div');
itemsDiv.classList.add('items-div');
@@ -968,7 +1058,35 @@ class GameSettings {
itemHintsWrapper.classList.add('hints-wrapper');
itemHintsWrapper.innerText = 'Starting Item Hints';
const itemHintsDiv = this.#buildItemsDiv('start_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);
});
itemHintsWrapper.appendChild(itemHintsDiv);
itemHintsContainer.appendChild(itemHintsWrapper);
@@ -977,7 +1095,35 @@ class GameSettings {
locationHintsWrapper.classList.add('hints-wrapper');
locationHintsWrapper.innerText = 'Starting Location Hints';
const locationHintsDiv = this.#buildLocationsDiv('start_location_hints');
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);
});
locationHintsWrapper.appendChild(locationHintsDiv);
itemHintsContainer.appendChild(locationHintsWrapper);
@@ -985,7 +1131,7 @@ class GameSettings {
return hintsDiv;
}
#buildPriorityExclusionDiv() {
#buildLocationsDiv() {
const locationsDiv = document.createElement('div');
locationsDiv.classList.add('locations-div');
const locationsHeader = document.createElement('h3');
@@ -1005,7 +1151,35 @@ class GameSettings {
priorityLocationsWrapper.classList.add('locations-wrapper');
priorityLocationsWrapper.innerText = 'Priority Locations';
const priorityLocationsDiv = this.#buildLocationsDiv('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);
@@ -1014,7 +1188,35 @@ class GameSettings {
excludeLocationsWrapper.classList.add('locations-wrapper');
excludeLocationsWrapper.innerText = 'Exclude Locations';
const excludeLocationsDiv = this.#buildLocationsDiv('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);
@@ -1022,71 +1224,6 @@ class GameSettings {
return locationsDiv;
}
// Builds a div for a setting whose value is a list of locations.
#buildLocationsDiv(setting) {
return this.#buildListDiv(setting, this.data.gameLocations, this.data.gameLocationGroups);
}
// Builds a div for a setting whose value is a list of items.
#buildItemsDiv(setting) {
return this.#buildListDiv(setting, this.data.gameItems, this.data.gameItemGroups);
}
// Builds a div for a setting named `setting` with a list value that can
// contain `items`.
//
// The `groups` option can be a list of additional options for this list
// (usually `item_name_groups` or `location_name_groups`) that are displayed
// in a special section at the top of the list.
#buildListDiv(setting, items, groups = []) {
const div = document.createElement('div');
div.classList.add('simple-list');
groups.forEach((group) => {
const row = this.#addListRow(setting, group);
div.appendChild(row);
});
if (groups.length > 0) {
div.appendChild(document.createElement('hr'));
}
items.forEach((item) => {
const row = this.#addListRow(setting, item);
div.appendChild(row);
});
return div;
}
// Builds and returns a row for a list of checkboxes.
#addListRow(setting, item) {
const row = document.createElement('div');
row.classList.add('list-row');
const label = document.createElement('label');
label.setAttribute('for', `${this.name}-${setting}-${item}`);
const checkbox = document.createElement('input');
checkbox.setAttribute('type', 'checkbox');
checkbox.setAttribute('id', `${this.name}-${setting}-${item}`);
checkbox.setAttribute('data-game', this.name);
checkbox.setAttribute('data-setting', setting);
checkbox.setAttribute('data-option', item);
if (this.current[setting].includes(item)) {
checkbox.setAttribute('checked', '1');
}
checkbox.addEventListener('change', (evt) => this.#updateListSetting(evt));
label.appendChild(checkbox);
const name = document.createElement('span');
name.innerText = item;
label.appendChild(name);
row.appendChild(label);
return row;
}
#updateRangeSetting(evt) {
const setting = evt.target.getAttribute('data-setting');
const option = evt.target.getAttribute('data-option');

View File

@@ -292,12 +292,6 @@ html{
margin-right: 0.5rem;
}
#weighted-settings .simple-list hr{
width: calc(100% - 2px);
margin: 2px auto;
border-bottom: 1px solid rgb(255 255 255 / 0.6);
}
#weighted-settings .invisible{
display: none;
}

View File

@@ -153,7 +153,7 @@
{%- endif -%}
{% endif %}
{%- endfor -%}
<td class="center-column">{{ "{0:.2f}".format(percent_total_checks_done[team][player]) }}</td>
<td class="center-column">{{ percent_total_checks_done[team][player] }}</td>
{%- if activity_timers[(team, player)] -%}
<td class="center-column">{{ activity_timers[(team, player)].total_seconds() }}</td>
{%- else -%}

View File

@@ -55,7 +55,7 @@
<td class="center-column" data-sort="{{ checks["Total"] }}">
{{ checks["Total"] }}/{{ locations[player] | length }}
</td>
<td class="center-column">{{ "{0:.2f}".format(percent_total_checks_done[team][player]) }}</td>
<td class="center-column">{{ percent_total_checks_done[team][player] }}</td>
{%- if activity_timers[team, player] -%}
<td class="center-column">{{ activity_timers[team, player].total_seconds() }}</td>
{%- else -%}
@@ -72,13 +72,7 @@
<td>All Games</td>
<td>{{ completed_worlds }}/{{ players|length }} Complete</td>
<td class="center-column">{{ players.values()|sum(attribute='Total') }}/{{ total_locations[team] }}</td>
<td class="center-column">
{% if total_locations[team] == 0 %}
100
{% else %}
{{ "{0:.2f}".format(players.values()|sum(attribute='Total') / total_locations[team] * 100) }}
{% endif %}
</td>
<td class="center-column">{{ (players.values()|sum(attribute='Total') / total_locations[team] * 100) | int }}</td>
<td class="center-column last-activity"></td>
</tr>
</tfoot>

View File

@@ -1532,11 +1532,9 @@ def _get_multiworld_tracker_data(tracker: UUID) -> typing.Optional[typing.Dict[s
continue
player_locations = locations[player]
checks_done[team][player]["Total"] = len(locations_checked)
percent_total_checks_done[team][player] = (
checks_done[team][player]["Total"] / len(player_locations) * 100
if player_locations
else 100
)
percent_total_checks_done[team][player] = int(checks_done[team][player]["Total"] /
len(player_locations) * 100) \
if player_locations else 100
activity_timers = {}
now = datetime.datetime.utcnow()
@@ -1692,13 +1690,10 @@ def get_LttP_multiworld_tracker(tracker: UUID):
for recipient in recipients:
attribute_item(team, recipient, item)
checks_done[team][player][player_location_to_area[player][location]] += 1
checks_done[team][player]["Total"] = len(locations_checked)
percent_total_checks_done[team][player] = (
checks_done[team][player]["Total"] / len(player_locations) * 100
if player_locations
else 100
)
checks_done[team][player]["Total"] += 1
percent_total_checks_done[team][player] = int(
checks_done[team][player]["Total"] / len(player_locations) * 100) if \
player_locations else 100
for (team, player), game_state in multisave.get("client_game_state", {}).items():
if player in groups: