Presets and categories (#73)

* add code categories

* add exclusives and dependencies

* add code selection presets

* remove standard category from some codes

* remove presets from nozzle lock

* reset preset selection on select & unselect stage loader

* update some translations

* remove stage loader from selection when changing versions

* add stage loader help text & warn when changing version if a code is selected

* prevent selecting disabled codes

* drop yarn in favor of npm

* Update Codes.xml

* reset code preset select on cancel

* minor style changes
This commit is contained in:
Matteias Collet 2021-10-10 15:45:53 +02:00 committed by GitHub
parent 12c9379f7e
commit 527ed6fbc5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 12157 additions and 8441 deletions

View file

@ -5,7 +5,7 @@
"args": { "VARIANT": "14" } "args": { "VARIANT": "14" }
}, },
"settings": { "settings": {
"terminal.integrated.defaultProfile.linux": "/bin/bash" "terminal.integrated.defaultProfile.linux": "bash"
}, },
"extensions": [ "extensions": [
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
@ -14,6 +14,6 @@
"wayou.vscode-todo-highlight", "wayou.vscode-todo-highlight",
], ],
"forwardPorts": [8080], "forwardPorts": [8080],
"postCreateCommand": "yarn install", "postCreateCommand": "npm install",
"remoteUser": "node" "remoteUser": "node"
} }

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<codes> <codes>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">DPad Functions</title> <title lang="en-US">DPad Functions</title>
<title lang="de-CH">DPad Funktionen</title> <title lang="de-CH">DPad Funktionen</title>
<title lang="fr-FR">Fonctions de la croix directionnelle</title> <title lang="fr-FR">Fonctions de la croix directionnelle</title>
@ -234,6 +236,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>misc</category>
<title lang="en-US">Nozzle Lock</title> <title lang="en-US">Nozzle Lock</title>
<title lang="fr-FR">Verrouillage de buses</title> <title lang="fr-FR">Verrouillage de buses</title>
<author>Psychonauter, Dan Salvato, Link Master, James0x57</author> <author>Psychonauter, Dan Salvato, Link Master, James0x57</author>
@ -325,6 +328,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>misc</category>
<title lang="en-US">Coin Count Savestate</title> <title lang="en-US">Coin Count Savestate</title>
<title lang="de-CH">Münzenzahl Speicherstand</title> <title lang="de-CH">Münzenzahl Speicherstand</title>
<author>Psychonauter</author> <author>Psychonauter</author>
@ -384,6 +388,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Infinite Lives</title> <title lang="en-US">Infinite Lives</title>
<title lang="de-CH">Unendlich Leben</title> <title lang="de-CH">Unendlich Leben</title>
<title lang="fr-FR">Vies infinies</title> <title lang="fr-FR">Vies infinies</title>
@ -409,6 +415,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Disable Blue Coin Flag</title> <title lang="en-US">Disable Blue Coin Flag</title>
<title lang="de-CH">Deaktiviere Blaue-Münzen-Kennung</title> <title lang="de-CH">Deaktiviere Blaue-Münzen-Kennung</title>
<title lang="fr-FR">Désactiver la sauvegarde des pièces bleues</title> <title lang="fr-FR">Désactiver la sauvegarde des pièces bleues</title>
@ -450,6 +458,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il,hfsetup</presets>
<title lang="en-US">FMV Skips</title> <title lang="en-US">FMV Skips</title>
<title lang="de-CH">FMV Skips</title> <title lang="de-CH">FMV Skips</title>
<title lang="fr-FR">Passer les FMV</title> <title lang="fr-FR">Passer les FMV</title>
@ -479,6 +489,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>cosmetic</category>
<title lang="en-US">Mute Background Music</title> <title lang="en-US">Mute Background Music</title>
<title lang="de-CH">Hintergrundmusik stummschalten</title> <title lang="de-CH">Hintergrundmusik stummschalten</title>
<title lang="fr-FR">Supprimer la musique de fond</title> <title lang="fr-FR">Supprimer la musique de fond</title>
@ -504,6 +515,7 @@
</source> </source>
</code> </code>
<!--<code> <!--<code>
<category>misc</category>
<title lang="en-US">Remove Save Boxes</title> <title lang="en-US">Remove Save Boxes</title>
<title lang="de-CH">Speicher-Schaltfläche entfernen</title> <title lang="de-CH">Speicher-Schaltfläche entfernen</title>
<title lang="fr-FR">Supprimer les boîtes de sauvegarde</title> <title lang="fr-FR">Supprimer les boîtes de sauvegarde</title>
@ -533,6 +545,8 @@
</source> </source>
</code>--> </code>-->
<code> <code>
<category>qol</category>
<presets>recommended,il</presets>
<title lang="en-US">Unlock Yoshi</title> <title lang="en-US">Unlock Yoshi</title>
<title lang="de-CH">Yoshi Freischalten</title> <title lang="de-CH">Yoshi Freischalten</title>
<title lang="fr-FR">Débloquer Yoshi</title> <title lang="fr-FR">Débloquer Yoshi</title>
@ -562,6 +576,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>recommended,il</presets>
<title lang="en-US">Unlock Nozzles</title> <title lang="en-US">Unlock Nozzles</title>
<title lang="de-CH">Düsen freischalten</title> <title lang="de-CH">Düsen freischalten</title>
<title lang="fr-FR">Débloquer les buses</title> <title lang="fr-FR">Débloquer les buses</title>
@ -591,6 +607,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Free Pause</title> <title lang="en-US">Free Pause</title>
<title lang="de-CH">Freies Pausieren</title> <title lang="de-CH">Freies Pausieren</title>
<title lang="fr-FR">Pause libre</title> <title lang="fr-FR">Pause libre</title>
@ -648,6 +666,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il,hfsetup</presets>
<title lang="en-US">Enable Exit Area Everywhere</title> <title lang="en-US">Enable Exit Area Everywhere</title>
<title lang="de-CH">'Level Verlassen' überall aktivieren</title> <title lang="de-CH">'Level Verlassen' überall aktivieren</title>
<title lang="fr-FR">Activer « Sortir de la zone » partout</title> <title lang="fr-FR">Activer « Sortir de la zone » partout</title>
@ -673,6 +693,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>timer</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Shine Get Timer</title> <title lang="en-US">Shine Get Timer</title>
<title lang="de-CH">Shine Get Timer</title> <title lang="de-CH">Shine Get Timer</title>
<title lang="fr-FR">Chronomètre Shine Get</title> <title lang="fr-FR">Chronomètre Shine Get</title>
@ -1198,6 +1220,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>timer</category>
<title lang="en-US">Quarterframe Timer (Experimental)</title> <title lang="en-US">Quarterframe Timer (Experimental)</title>
<author>Noki Doki</author> <author>Noki Doki</author>
<version>0.1</version> <version>0.1</version>
@ -1419,6 +1442,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>loader</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Level Select</title> <title lang="en-US">Level Select</title>
<title lang="de-CH">Level Select</title> <title lang="de-CH">Level Select</title>
<title lang="fr-FR">Sélecteur de niveau</title> <title lang="fr-FR">Sélecteur de niveau</title>
@ -1985,6 +2010,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>loader</category>
<presets>fap</presets>
<title lang="en-US">Fast Any%</title> <title lang="en-US">Fast Any%</title>
<title lang="de-CH">Fast Any%</title> <title lang="de-CH">Fast Any%</title>
<title lang="fr-FR">Fast Any%</title> <title lang="fr-FR">Fast Any%</title>
@ -2532,6 +2559,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>recommended,il</presets>
<title lang="en-US">Any Fruit Opens Yoshi Eggs</title> <title lang="en-US">Any Fruit Opens Yoshi Eggs</title>
<title lang="de-CH">Jede Frucht öffnet Yoshi-Eier</title> <title lang="de-CH">Jede Frucht öffnet Yoshi-Eier</title>
<title lang="fr-FR">Tous les fruits ouvrent les œufs de Yoshi</title> <title lang="fr-FR">Tous les fruits ouvrent les œufs de Yoshi</title>
@ -2557,6 +2586,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>recommended,il</presets>
<title lang="en-US">Infinite Juice</title> <title lang="en-US">Infinite Juice</title>
<title lang="de-CH">Unendlich Saft</title> <title lang="de-CH">Unendlich Saft</title>
<title lang="fr-FR">Jus infini</title> <title lang="fr-FR">Jus infini</title>
@ -2582,6 +2613,7 @@
</source> </source>
</code> </code>
<!--<code> <!--<code>
<category>loader</category>
<title lang="en-US">Stage Randomizer (Experimental)</title> <title lang="en-US">Stage Randomizer (Experimental)</title>
<title lang="de-CH">Stage Randomizer (Experimentell)</title> <title lang="de-CH">Stage Randomizer (Experimentell)</title>
<title lang="fr-FR">Randomiseur de niveaux (expérimental)</title> <title lang="fr-FR">Randomiseur de niveaux (expérimental)</title>
@ -2657,6 +2689,7 @@
</source> </source>
</code>--> </code>-->
<code> <code>
<category>cosmetic</category>
<title lang="en-US">Replace Episode names with their ID</title> <title lang="en-US">Replace Episode names with their ID</title>
<title lang="de-CH">Ersetze Episodennamen mit ihrer ID</title> <title lang="de-CH">Ersetze Episodennamen mit ihrer ID</title>
<title lang="fr-FR">Remplacer les noms d'épisodes par leur numéro</title> <title lang="fr-FR">Remplacer les noms d'épisodes par leur numéro</title>
@ -2710,6 +2743,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>misc</category>
<title lang="en-US">Position/angle/speed display</title> <title lang="en-US">Position/angle/speed display</title>
<title lang="de-CH">Position/Winkel/Geschw. Display</title> <title lang="de-CH">Position/Winkel/Geschw. Display</title>
<title lang="fr-FR">Affichage de position/angle/vitesse</title> <title lang="fr-FR">Affichage de position/angle/vitesse</title>
@ -3015,6 +3049,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Intro Skip</title> <title lang="en-US">Intro Skip</title>
<title lang="de-CH">Überspringbare Intros</title> <title lang="de-CH">Überspringbare Intros</title>
<title lang="fr-FR">Passer l'intro</title> <title lang="fr-FR">Passer l'intro</title>
@ -3066,7 +3102,7 @@
B07F000E B01F0010 B07F000E B01F0010
4BFFFEB0 00000000 4BFFFEB0 00000000
</source> </source>
<source version="GMSP01"> <source version="GMSP01" exclude-from-presets="true">
0428D4C4 48000264 0428D4C4 48000264
0428D9B8 48000014 0428D9B8 48000014
0629E51C 00000014 0629E51C 00000014
@ -3076,6 +3112,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>qol</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Respawn One-Time Shines</title> <title lang="en-US">Respawn One-Time Shines</title>
<title lang="de-CH">Einmalige Shines Respawnen</title> <title lang="de-CH">Einmalige Shines Respawnen</title>
<title lang="fr-FR">Restaurer les Shines uniques</title> <title lang="fr-FR">Restaurer les Shines uniques</title>
@ -3109,6 +3147,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>misc</category>
<presets>standard,recommended,il</presets>
<title lang="en-US">Force Plaza Events</title> <title lang="en-US">Force Plaza Events</title>
<title lang="de-CH">Erzwungene Plaza Events</title> <title lang="de-CH">Erzwungene Plaza Events</title>
<title lang="fr-FR">Forcer les événements de la place Delfino</title> <title lang="fr-FR">Forcer les événements de la place Delfino</title>
@ -3162,6 +3202,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>memcardpatch</category>
<title lang="en-US">Force SJIS Memory Card Encoding</title> <title lang="en-US">Force SJIS Memory Card Encoding</title>
<title lang="fr-FR">Forcer l'encodage SJIS pour la carte mémoire</title> <title lang="fr-FR">Forcer l'encodage SJIS pour la carte mémoire</title>
<author>Noki Doki</author> <author>Noki Doki</author>
@ -3177,6 +3218,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>memcardpatch</category>
<title lang="en-US">Force ANSI Memory Card Encoding</title> <title lang="en-US">Force ANSI Memory Card Encoding</title>
<title lang="fr-FR">Forcer l'encodage ANSI pour la carte mémoire</title> <title lang="fr-FR">Forcer l'encodage ANSI pour la carte mémoire</title>
<author>Noki Doki</author> <author>Noki Doki</author>
@ -3195,6 +3237,8 @@
</source> </source>
</code> </code>
<code> <code>
<category>misc</category>
<presets>standard,recommended,il,fap</presets>
<title lang="en-US">Fix Manta Splitting</title> <title lang="en-US">Fix Manta Splitting</title>
<title lang="de-CH">Fix Manta Splitting</title> <title lang="de-CH">Fix Manta Splitting</title>
<title lang="fr-FR">Corriger la séparation de la raie manta</title> <title lang="fr-FR">Corriger la séparation de la raie manta</title>
@ -3243,6 +3287,7 @@
</source> </source>
</code> </code>
<code> <code>
<category>cosmetic</category>
<title lang="en-US">Shine Outfit</title> <title lang="en-US">Shine Outfit</title>
<title lang="de-CH">Shine Outfit</title> <title lang="de-CH">Shine Outfit</title>
<title lang="fr-FR">Tenue Shine</title> <title lang="fr-FR">Tenue Shine</title>

View file

@ -47,28 +47,25 @@ Note that in the code reference files everything following the `<!-- injectionpo
If you intend to change site code you need [NodeJS](https://nodejs.org/en/) version 14.X LTS installed on your local. If you intend to change site code you need [NodeJS](https://nodejs.org/en/) version 14.X LTS installed on your local.
```sh ```sh
# Install yarn
npm i -g yarn
# Install dependencies # Install dependencies
yarn npm i
# Run project in watch mode # Run project in watch mode
# This will serve the page in development mode on http://localhost:8080 # This will serve the page in development mode on http://localhost:8080
yarn dev npm run dev
# Build project # Build project
yarn build npm run build
``` ```
The XML codes will be written automatically to the json file and code reference during the following actions: The XML codes will be written automatically to the json file and code reference during the following actions:
- Starting the development server with `yarn dev` - Starting the development server with `npm run dev`
- Building the site with `yarn build` - Building the site with `npm run build`
If you want to inject the codes at any given point you can use `yarn codes:inject`. If you want to inject the codes at any given point you can use `npm run codes:inject`.
**!!! Note that if yarn was used, `yarn codes:clean` is ran automatically as a pre-commit hook, removing all injected codes and staging ALL changes.** If you want to commit changes without cleaning codes first you have to commit through `git commit --no-verify`. **!!! Note that if npm was used, `npm run codes:clean` is ran automatically as a pre-commit hook, removing all injected codes and staging ALL changes.** If you want to commit changes without cleaning codes first you have to commit through `git commit --no-verify`.
### Build and preview the site (Docker) ### Build and preview the site (Docker)

View file

@ -6,8 +6,8 @@ RUN pwsh -File ./scripts/build_archives.ps1
FROM node:lts-buster AS build FROM node:lts-buster AS build
WORKDIR /src WORKDIR /src
COPY --from=prebuild /src . COPY --from=prebuild /src .
RUN yarn RUN npm i
RUN yarn build RUN npm run build
FROM mcr.microsoft.com/powershell:latest AS final FROM mcr.microsoft.com/powershell:latest AS final
WORKDIR /src WORKDIR /src

View file

@ -6,8 +6,8 @@ RUN pwsh -File ./scripts/build_archives.ps1
FROM node:lts-buster AS build FROM node:lts-buster AS build
WORKDIR /src WORKDIR /src
COPY --from=prebuild /src . COPY --from=prebuild /src .
RUN yarn RUN npm i
RUN yarn build RUN npm run build
FROM httpd:latest AS final FROM httpd:latest AS final
WORKDIR /usr/local/apache2/htdocs/ WORKDIR /usr/local/apache2/htdocs/

11728
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,13 +9,13 @@
}, },
"repository": "https://github.com/BitPatty/gctGenerator/gctGenerator", "repository": "https://github.com/BitPatty/gctGenerator/gctGenerator",
"scripts": { "scripts": {
"dev": "yarn codes:inject && yarn translations:compare && vuepress dev site", "dev": "npm run codes:inject && npm run translations:compare && vuepress dev site",
"build": "node ./scripts/inject_codes.js && yarn translations:compare && vuepress build site", "build": "node ./scripts/inject_codes.js && npm run translations:compare && vuepress build site",
"format": "prettier --write ./site/**/*{.md,.js,.json,.vue}", "format": "prettier --write ./site/**/*{.md,.js,.json,.vue}",
"translations:compare": "node ./scripts/compare_translations.js", "translations:compare": "node ./scripts/compare_translations.js",
"codes:inject": "node ./scripts/inject_codes.js && yarn format", "codes:inject": "node ./scripts/inject_codes.js && npm run format",
"codes:clean": "node ./scripts/clean_codes.js && yarn format", "codes:clean": "node ./scripts/clean_codes.js && npm run format",
"precommit": "yarn codes:clean && git add ." "precommit": "npm run codes:clean && git add ."
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"devDependencies": { "devDependencies": {

View file

@ -10,6 +10,11 @@ const md = require('@vuepress/markdown')({
const themePlugins = require(path.join(__dirname, '../site/.vuepress/data/themePlugins.json')); const themePlugins = require(path.join(__dirname, '../site/.vuepress/data/themePlugins.json'));
const locales = require(path.join(__dirname, '../site/.vuepress/i18n/locales.json')); const locales = require(path.join(__dirname, '../site/.vuepress/i18n/locales.json'));
const codeCategories = require(path.join(__dirname, '../site/.vuepress/data/codeCategories.json'));
const presetCategories = require(path.join(
__dirname,
'../site/.vuepress/data/presetCategories.json',
));
const xml = fs.readFileSync(path.join(__dirname, `../Codes.xml`)); const xml = fs.readFileSync(path.join(__dirname, `../Codes.xml`));
// Constants // Constants
@ -37,6 +42,22 @@ const validateXML = (xmlString) => {
if (!codeTitle || !codeTitle.textContent) if (!codeTitle || !codeTitle.textContent)
throw new Error(`Missing Fallback Title (en-US) in code nr ${i}`); throw new Error(`Missing Fallback Title (en-US) in code nr ${i}`);
// Code has a valid category
const codeCategory = codes[i].querySelector('category');
if (!codeCategory || !codeCategory.textContent)
throw new Error(`Missing code category in ${codeTitle.textContent}`);
if (!codeCategories.map((c) => c.identifier).includes(codeCategory.textContent))
throw new Error(`Invalid code category for ${codeTitle.textContent}`);
const codePresets = codes[i].querySelector('presets');
if (codePresets && codePresets.textContent) {
for (const preset of codePresets.textContent.split(',')) {
if (!presetCategories.map((c) => c.identifier).includes(preset))
throw new Error(`Invalid preset ${preset} for ${codeTitle.textContent}`);
}
}
// All lang attributes on all titles are valid // All lang attributes on all titles are valid
const codeTitles = codes[i].querySelectorAll('title'); const codeTitles = codes[i].querySelectorAll('title');
for (let j = 0; j < codeTitles.length; j++) { for (let j = 0; j < codeTitles.length; j++) {
@ -177,6 +198,23 @@ const readTextNode = (node, identifier, lang = null, fallbackLang = null) => {
throw new Error(`No fallback ${identifier} found on ${node.textContent}`); throw new Error(`No fallback ${identifier} found on ${node.textContent}`);
}; };
/**
* Reads the presets from the specified code
* @param {*} node The parent node
* @param {*} gameVersion The target game version
* @returns The list of presets
*/
const readPresetList = (node, gameVersion) => {
if (!node) throw new Error('No node provided');
const presets = node.querySelector('presets');
if (!presets || !presets.textContent) return [];
const targetCode = node.querySelector(`source[version='${gameVersion}']`);
if (!targetCode) return [];
const exclusionAttribute = targetCode.getAttribute('exclude-from-presets');
if (exclusionAttribute === 'true') return [];
return presets.textContent.split(',');
};
/** /**
* Creates an object of localized child nodes * Creates an object of localized child nodes
* @param {*} node The parent node * @param {*} node The parent node
@ -247,6 +285,8 @@ const parseXml = (xmlString, gameVersion = null) => {
version: readTextNode(code, 'version'), version: readTextNode(code, 'version'),
date: readTextNode(code, 'date'), date: readTextNode(code, 'date'),
source: readCode(code, 'source', gameVersion), source: readCode(code, 'source', gameVersion),
presets: readPresetList(code, gameVersion),
category: readTextNode(code, 'category'),
})) }))
.filter((code) => code.source != null); .filter((code) => code.source != null);
}; };

View file

@ -1,25 +1,52 @@
<template> <template>
<div>
<div class="preset-select">
<SelectComponent
:options="getPresetOptions()"
:onChange="(v) => loadPreset(v)"
:placeholder="getPresetPlaceholder()"
:key="generation"
/>
</div>
<div v-for="category in codeCategories" v-bind:key="category.identifier" class="code-group">
<div class="category-title">{{ getCategoryTitle(category) }}</div>
<ul> <ul>
<li <li
v-for="(code, idx) in availableCodes" v-for="(code, idx) in availableCodes.filter((c) => c.category === category.identifier)"
v-bind:key="idx" v-bind:key="idx"
:class="code.selected ? 'checked' : ''" :class="code.selected ? 'checked' : code.disabled ? 'disabled' : ''"
@click="toggle(code)" @click="toggle(code)"
@mouseover="inspect(code)" @mouseover="inspect(code)"
> >
{{ getCodeTitle(code) }} {{ getCodeTitle(code) }}
</li> </li>
<li
v-if="category.identifier === 'loader'"
:class="stageLoaderSelected ? 'checked' : ''"
@click="toggleStageLoader()"
@mouseover="showStageLoaderHelp()"
>
{{ getStageLoaderLabel() }}
</li>
</ul> </ul>
</div>
</div>
</template> </template>
<script> <script>
import { translateCode } from '../i18n/localeHelper'; import SelectComponent from './SelectComponent';
import { translateCode, translate } from '../i18n/localeHelper';
import codeCategories from '../data/codeCategories.json';
import presetCategories from '../data/presetCategories.json';
export default { export default {
props: { props: {
codes: { type: Array }, codes: { type: Array },
onSelectionChanged: { type: Function }, onSelectionChanged: { type: Function },
onInspect: { type: Function }, onInspect: { type: Function },
onStageLoaderToggle: { type: Function },
onInspectStageLoader: { type: Function },
}, },
mounted() { mounted() {
this.populate(); this.populate();
@ -27,32 +54,150 @@ export default {
watch: { watch: {
codes: function () { codes: function () {
this.populate(); this.populate();
this.unselectStageLoader();
}, },
}, },
data() { data() {
return { return {
availableCodes: [], availableCodes: [],
codeCategories,
presetCategories,
stageLoaderSelected: false,
generation: 0,
}; };
}, },
methods: { methods: {
getPresetOptions() {
return presetCategories.map((c) => ({
label: c.i18nKey,
value: c.identifier,
}));
},
loadPreset(identifier) {
if (
(this.stageLoaderSelected || this.availableCodes.find((c) => c.selected)) &&
!confirm(translate('common.selectionreset', this.$lang))
) {
this.generation++;
return;
}
for (const code of this.availableCodes) {
code.selected = code.presets.includes(identifier);
}
this.unselectStageLoader();
this.onSelectionChanged(this.availableCodes.filter((c) => c.selected));
this.refreshDisabledCodes();
this.generation++;
},
getPresetPlaceholder() {
return translate('common.loadpresetplaceholder', this.$lang);
},
unselectStageLoader() {
if (this.stageLoaderSelected) {
this.stageLoaderSelected = false;
this.onStageLoaderToggle(false);
}
},
getCodeTitle(code) { getCodeTitle(code) {
return translateCode(code, this.$lang).title; return translateCode(code, this.$lang).title;
}, },
toggle(code) { getCategoryTitle(category) {
code.selected = !code.selected; return translate(category.i18nKey, this.$lang);
},
getStageLoaderLabel() {
return translate('headers.stageloader', this.$lang);
},
toggleStageLoader() {
for (const c of this.availableCodes.filter((c) => c.category === 'loader' && c.selected)) {
c.selected = false;
}
const newState = !this.stageLoaderSelected;
this.stageLoaderSelected = newState;
this.onStageLoaderToggle(newState);
this.onSelectionChanged(this.availableCodes.filter((c) => c.selected)); this.onSelectionChanged(this.availableCodes.filter((c) => c.selected));
this.refreshDisabledCodes();
},
refreshDisabledCodes() {
for (const dependentCategory of codeCategories.filter((c) => c.dependsOn.length > 0)) {
for (const dependency of dependentCategory.dependsOn) {
const enableCodes =
(dependency === 'loader' && this.stageLoaderSelected) ||
this.availableCodes.find((c) => c.selected && c.category === dependency);
for (const code of this.availableCodes.filter(
(c) => c.category === dependentCategory.identifier && c.disabled !== !enableCodes,
)) {
code.disabled = !enableCodes;
if (code.disabled && code.selected) {
this.toggle(code);
}
}
}
}
},
toggle(code) {
if (!code.selected && codeCategories.find((c) => c.identifier === code.category).exclusive) {
for (const availableCode of this.availableCodes.filter(
(c) => c.category === code.category && c.selected,
)) {
availableCode.selected = false;
}
}
if (!code.selected && code.category === 'loader' && this.stageLoaderSelected) {
this.stageLoaderSelected = false;
this.onStageLoaderToggle(false);
}
code.selected = code.disabled ? false : !code.selected;
this.onSelectionChanged(this.availableCodes.filter((c) => c.selected));
this.refreshDisabledCodes();
}, },
populate() { populate() {
this.availableCodes = this.codes.map((c) => ({ ...c, selected: false })); this.availableCodes = this.codes.map((c) => ({ ...c, selected: false }));
this.refreshDisabledCodes();
}, },
inspect(code) { inspect(code) {
this.onInspect(code); this.onInspect(code);
}, },
showStageLoaderHelp() {
this.onInspectStageLoader();
},
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.category-title {
color: white;
font-weight: 500;
text-align: center;
background: #383838b5;
padding-top: 2px;
padding-bottom: 2px;
margin-bottom: 0;
}
.category-title ~ ul {
margin-top: 0;
}
.preset-select {
margin-bottom: 20px;
}
.code-group {
border: 1px solid #d7d7d7;
margin-bottom: 20px;
}
.code-group ul {
margin-bottom: 0;
}
ul { ul {
list-style-type: none; list-style-type: none;
padding-left: 0; padding-left: 0;
@ -74,11 +219,15 @@ ul li {
text-align: left; text-align: left;
} }
ul li {
background: #f9f9f9;
}
ul li:nth-child(odd) { ul li:nth-child(odd) {
background: #e7e7e7; background: #e7e7e7;
} }
ul li:hover { ul li:not(.disabled):hover {
background: #3eaf7c; background: #3eaf7c;
color: #fff; color: #fff;
} }
@ -93,6 +242,15 @@ ul li.checked {
color: #fff; color: #fff;
} }
ul li.disabled {
background: #c7c7c7;
color: #767676;
}
ul li.disabled:hover {
cursor: not-allowed;
}
li { li {
position: relative; position: relative;
padding-left: 26px; padding-left: 26px;
@ -111,7 +269,7 @@ li::before {
width: 10px; width: 10px;
} }
li:hover::before { li:not(.disabled):not(.checked):hover::before {
border-color: #fff; border-color: #fff;
background-color: #1fa76e; background-color: #1fa76e;
} }

View file

@ -3,20 +3,16 @@
<section class="config"> <section class="config">
<div> <div>
<span>{{ getLabel('generatorconfig.gameversion.label') }}</span> <span>{{ getLabel('generatorconfig.gameversion.label') }}</span>
<VersionSelect :onChange="onVersionChanged" :selectedValue="selectedVersion" /> <VersionSelect
:onChange="onVersionChanged"
:selectedValue="selectedVersion"
:key="generation"
/>
</div> </div>
<div> <div>
<span>{{ getLabel('generatorconfig.downloadformat.label') }}</span> <span>{{ getLabel('generatorconfig.downloadformat.label') }}</span>
<FormatSelect :onChange="onFormatChanged" :selectedValue="selectedFormat" /> <FormatSelect :onChange="onFormatChanged" :selectedValue="selectedFormat" />
</div> </div>
<div>
<span>{{ getLabel('generatorconfig.usestageloader') }}</span>
<SelectComponent
:options="useStageLoaderOptions"
:onChange="onStageLoaderChanged"
:value="useStageLoader"
/>
</div>
<div> <div>
<span>{{ getLabel('common.download') }}</span> <span>{{ getLabel('common.download') }}</span>
<DownloadButton <DownloadButton
@ -33,9 +29,11 @@
<div v-if="codes && codes.length > 0"> <div v-if="codes && codes.length > 0">
<h3>{{ getLabel('headers.codelist') }}</h3> <h3>{{ getLabel('headers.codelist') }}</h3>
<CodeList <CodeList
:onStageLoaderToggle="onStageLoaderToggle"
:codes="codes" :codes="codes"
:onSelectionChanged="onCheatSelectionChanged" :onSelectionChanged="onCheatSelectionChanged"
:onInspect="inspect" :onInspect="inspect"
:onInspectStageLoader="displayStageLoaderHelp"
/> />
</div> </div>
<div class="prevent-shrink" v-if="codes && codes.length > 0 && useStageLoader"> <div class="prevent-shrink" v-if="codes && codes.length > 0 && useStageLoader">
@ -46,6 +44,12 @@
<div v-if="codes && codes.length > 0" class="help"> <div v-if="codes && codes.length > 0" class="help">
<h3>{{ getLabel('headers.help') }}</h3> <h3>{{ getLabel('headers.help') }}</h3>
<CodeInfo v-if="!!inspectingCode" :code="inspectingCode" /> <CodeInfo v-if="!!inspectingCode" :code="inspectingCode" />
<div v-else-if="showStageLoaderHelp">
<h3>{{ getLabel('headers.stageloader') }}</h3>
<div>
{{ getLabel('stageloader.help') }}
</div>
</div>
<div v-else>{{ getLabel('misc.defaulthelpmessage') }}</div> <div v-else>{{ getLabel('misc.defaulthelpmessage') }}</div>
</div> </div>
<div v-if="selectedVersion == null" class="help"> <div v-if="selectedVersion == null" class="help">
@ -116,10 +120,8 @@ export default {
selectedFormat: 'gct', selectedFormat: 'gct',
useStageLoader: false, useStageLoader: false,
stageLoaderCodes: [], stageLoaderCodes: [],
useStageLoaderOptions: [ showStageLoaderHelp: false,
{ value: false, label: 'common.no' }, generation: 0,
{ value: true, label: 'common.yes' },
],
}; };
}, },
methods: { methods: {
@ -127,11 +129,20 @@ export default {
return translate(key, this.$lang); return translate(key, this.$lang);
}, },
onVersionChanged(e) { onVersionChanged(e) {
if (
this.selectedCheats.length > 0 &&
!confirm(translate('common.selectionreset', this.$lang))
) {
this.generation++;
return;
}
this.selectedVersion = e; this.selectedVersion = e;
this.selectedCheats = []; this.selectedCheats = [];
this.codes = gameVersions.find((c) => c.identifier === e).codes; this.codes = gameVersions.find((c) => c.identifier === e).codes;
this.stageLoaderCodes = gameVersions.find((c) => c.identifier === e).fastCode; this.stageLoaderCodes = gameVersions.find((c) => c.identifier === e).fastCode;
this.inspectingCode = null; this.inspectingCode = null;
this.showStageLoaderHelp = false;
try { try {
window._paq.push([ window._paq.push([
'trackEvent', 'trackEvent',
@ -152,15 +163,15 @@ export default {
]); ]);
} catch {} } catch {}
}, },
onStageLoaderChanged(e) { onStageLoaderToggle(enabled) {
this.useStageLoader = e === true || e === 'true'; this.useStageLoader = enabled;
if (!this.useStageLoader) this.selectedStageLoader = null; if (!this.useStageLoader) this.selectedStageLoader = null;
try { try {
window._paq.push([ window._paq.push([
'trackEvent', 'trackEvent',
'GCT Generator', 'GCT Generator',
'Change StageLoader State', 'Change StageLoader State',
JSON.stringify({ enabled: e }), JSON.stringify({ enabled }),
]); ]);
} catch {} } catch {}
}, },
@ -173,7 +184,12 @@ export default {
onStageLoaderCodeChanged(e) { onStageLoaderCodeChanged(e) {
this.selectedStageLoader = e; this.selectedStageLoader = e;
}, },
displayStageLoaderHelp() {
this.inspectingCode = null;
this.showStageLoaderHelp = true;
},
inspect(code) { inspect(code) {
this.showStageLoaderHelp = false;
this.inspectingCode = code; this.inspectingCode = code;
}, },
}, },
@ -183,6 +199,7 @@ export default {
section { section {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
position: relative;
} }
.prevent-shrink { .prevent-shrink {
@ -209,7 +226,11 @@ section > div:not(:first-child) {
} }
.help { .help {
position: sticky;
top: 80px;
text-align: left; text-align: left;
align-self: flex-start;
width: 100%;
} }
.centered { .centered {

View file

@ -1,5 +1,14 @@
<template> <template>
<div> <div>
<div class="sub">
<GroupSelectComponent
:placeholder="getLabel('common.loadpresetplaceholder')"
:optGroups="stageLoaderPresetOptions"
:onChange="onStageLoaderPresetSelected"
selectedValue="placeholder"
:key="generation + 1"
/>
</div>
<div class="config"> <div class="config">
<span>{{ getLabel('stageloader.removedialogue.label') }}</span> <span>{{ getLabel('stageloader.removedialogue.label') }}</span>
<SelectComponent <SelectComponent
@ -69,15 +78,6 @@
<div class="sub"> <div class="sub">
<ButtonComponent :label="getLabel('stageloader.clear')" :onClick="onClearList" /> <ButtonComponent :label="getLabel('stageloader.clear')" :onClick="onClearList" />
</div> </div>
<div class="sub">
<GroupSelectComponent
:placeholder="getLabel('stageloader.loadpresetplaceholder')"
:optGroups="stageLoaderPresetOptions"
:onChange="onStageLoaderPresetSelected"
selectedValue="placeholder"
:key="generation + 1"
/>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -188,7 +188,7 @@ export default {
if ( if (
this.selectedRoute?.length > 0 && this.selectedRoute?.length > 0 &&
!confirm('Loading a preset will erase your current list. Continue?') !confirm(translate('common.selectionreset', this.$lang))
) { ) {
return; return;
} }

View file

@ -0,0 +1,38 @@
[
{
"identifier": "qol",
"i18nKey": "generatorconfig.categories.qol",
"exclusive": false,
"dependsOn": []
},
{
"identifier": "loader",
"i18nKey": "generatorconfig.categories.loader",
"exclusive": true,
"dependsOn": []
},
{
"identifier": "timer",
"i18nKey": "generatorconfig.categories.timer",
"exclusive": false,
"dependsOn": ["loader"]
},
{
"identifier": "misc",
"i18nKey": "generatorconfig.categories.misc",
"exclusive": false,
"dependsOn": []
},
{
"identifier": "memcardpatch",
"i18nKey": "generatorconfig.categories.memcardpatch",
"exclusive": true,
"dependsOn": []
},
{
"identifier": "cosmetic",
"i18nKey": "generatorconfig.categories.cosmetic",
"exclusive": false,
"dependsOn": []
}
]

View file

@ -0,0 +1,22 @@
[
{
"identifier": "standard",
"i18nKey": "generatorconfig.presets.standard"
},
{
"identifier": "recommended",
"i18nKey": "generatorconfig.presets.recommended"
},
{
"identifier": "il",
"i18nKey": "generatorconfig.presets.il"
},
{
"identifier": "fap",
"i18nKey": "generatorconfig.presets.fap"
},
{
"identifier": "hfsetup",
"i18nKey": "generatorconfig.presets.hfsetup"
}
]

View file

@ -6,12 +6,14 @@
"GMSE01": "GMSE01 (NTSC-U)", "GMSE01": "GMSE01 (NTSC-U)",
"GMSJ01": "GMSJ01 (NTSC-J 1.0)", "GMSJ01": "GMSJ01 (NTSC-J 1.0)",
"GMSJ0A": "GMSJ01 (NTSC-J 1.1)", "GMSJ0A": "GMSJ01 (NTSC-J 1.1)",
"GMSP01": "GMSP01 (PAL)" "GMSP01": "GMSP01 (PAL)",
"loadpresetplaceholder": "Lade eine Vorlage..",
"selectionreset": "Deine Auswahl wird zurückgesetzt. Fortfahren?"
}, },
"headers": { "headers": {
"help": "Hilfe", "help": "Hilfe",
"codelist": "Verfügbare Codes", "codelist": "Verfügbare Codes",
"stageloader": "Stage Loader" "stageloader": "Stage List Loader"
}, },
"codeinfo": { "codeinfo": {
"author": "Autor:", "author": "Autor:",
@ -23,7 +25,6 @@
"label": "Spiel Version:", "label": "Spiel Version:",
"placeholder": "Wähle Version.." "placeholder": "Wähle Version.."
}, },
"usestageloader": "Stage Loader verwenden:",
"downloadformat": { "downloadformat": {
"label": "Download Format:", "label": "Download Format:",
"options": { "options": {
@ -31,6 +32,21 @@
"dolphin": "Dolphin INI", "dolphin": "Dolphin INI",
"gcm": "CheatManager TXT" "gcm": "CheatManager TXT"
} }
},
"categories": {
"qol": "Allgemein",
"loader": "Loader",
"timer": "Timer",
"misc": "Misc",
"memcardpatch": "Memory Card Patches",
"cosmetic": "Kosmetisch"
},
"presets": {
"standard": "Standard",
"recommended": "Empfohlen",
"il": "IL Runs",
"fap": "Fast Any%",
"hfsetup": "Hacked File Setup"
} }
}, },
"landingpage": { "landingpage": {
@ -46,6 +62,7 @@
} }
}, },
"stageloader": { "stageloader": {
"help": "Ladet die Level in einer benutzerdefinierten Reihenfolge.",
"levelorder": { "levelorder": {
"label": "Level Reihenfolge:", "label": "Level Reihenfolge:",
"options": { "options": {
@ -260,7 +277,6 @@
} }
}, },
"levelselectplaceholder": "Wähle ein Level..", "levelselectplaceholder": "Wähle ein Level..",
"loadpresetplaceholder": "Lade eine Vorlage..",
"route": "Route", "route": "Route",
"clear": "Liste leeren" "clear": "Liste leeren"
}, },

View file

@ -6,7 +6,9 @@
"GMSE01": "GMSE01 (NTSC-U)", "GMSE01": "GMSE01 (NTSC-U)",
"GMSJ01": "GMSJ01 (NTSC-J 1.0)", "GMSJ01": "GMSJ01 (NTSC-J 1.0)",
"GMSJ0A": "GMSJ01 (NTSC-J 1.1)", "GMSJ0A": "GMSJ01 (NTSC-J 1.1)",
"GMSP01": "GMSP01 (PAL)" "GMSP01": "GMSP01 (PAL)",
"loadpresetplaceholder": "Load a preset..",
"selectionreset": "This will reset your selection, continue?"
}, },
"headers": { "headers": {
"codelist": "Available Codes", "codelist": "Available Codes",
@ -23,7 +25,6 @@
"label": "Game Version:", "label": "Game Version:",
"placeholder": "Choose Version.." "placeholder": "Choose Version.."
}, },
"usestageloader": "Use Stage List Loader",
"downloadformat": { "downloadformat": {
"label": "Download Format:", "label": "Download Format:",
"options": { "options": {
@ -31,6 +32,21 @@
"dolphin": "Dolphin INI", "dolphin": "Dolphin INI",
"gcm": "CheatManager TXT" "gcm": "CheatManager TXT"
} }
},
"categories": {
"qol": "Quality of Life",
"loader": "Loaders",
"timer": "Timers",
"misc": "Misc",
"memcardpatch": "Memory Card Patches",
"cosmetic": "Cosmetic"
},
"presets": {
"standard": "Standard",
"recommended": "Recommended",
"il": "IL Runs",
"fap": "Fast Any%",
"hfsetup": "Hacked File Setup"
} }
}, },
"landingpage": { "landingpage": {
@ -46,6 +62,7 @@
} }
}, },
"stageloader": { "stageloader": {
"help": "Loads levels in a customized order.",
"levelorder": { "levelorder": {
"label": "Level Order", "label": "Level Order",
"options": { "options": {
@ -260,7 +277,6 @@
} }
}, },
"levelselectplaceholder": "Choose a level..", "levelselectplaceholder": "Choose a level..",
"loadpresetplaceholder": "Load a preset..",
"route": "Route", "route": "Route",
"clear": "Clear List" "clear": "Clear List"
}, },

View file

@ -6,7 +6,8 @@
"GMSE01": "GMSE01 (NTSC-U)", "GMSE01": "GMSE01 (NTSC-U)",
"GMSJ01": "GMSJ01 (NTSC-J 1.0)", "GMSJ01": "GMSJ01 (NTSC-J 1.0)",
"GMSJ0A": "GMSJ01 (NTSC-J 1.1)", "GMSJ0A": "GMSJ01 (NTSC-J 1.1)",
"GMSP01": "GMSP01 (PAL)" "GMSP01": "GMSP01 (PAL)",
"loadpresetplaceholder": "Charger une liste prédéfinie…"
}, },
"headers": { "headers": {
"codelist": "Codes disponibles", "codelist": "Codes disponibles",
@ -23,7 +24,6 @@
"label": "Version du jeu :", "label": "Version du jeu :",
"placeholder": "Choisissez une version…" "placeholder": "Choisissez une version…"
}, },
"usestageloader": "Utiliser le chargeur de liste",
"downloadformat": { "downloadformat": {
"label": "Format de fichier :", "label": "Format de fichier :",
"options": { "options": {
@ -260,7 +260,6 @@
} }
}, },
"levelselectplaceholder": "Choisir un niveau…", "levelselectplaceholder": "Choisir un niveau…",
"loadpresetplaceholder": "Charger une liste prédéfinie…",
"route": "Route", "route": "Route",
"clear": "Effacer la liste" "clear": "Effacer la liste"
}, },

View file

@ -6,7 +6,8 @@
"GMSE01": "GMSE01 (NTSC-U)", "GMSE01": "GMSE01 (NTSC-U)",
"GMSJ01": "GMSJ01 (NTSC-J 1.0)", "GMSJ01": "GMSJ01 (NTSC-J 1.0)",
"GMSJ0A": "GMSJ01 (NTSC-J 1.1)", "GMSJ0A": "GMSJ01 (NTSC-J 1.1)",
"GMSP01": "GMSP01 (PAL)" "GMSP01": "GMSP01 (PAL)",
"loadpresetplaceholder": "プリセットをロードします.."
}, },
"headers": { "headers": {
"codelist": "利用可能なコード", "codelist": "利用可能なコード",
@ -23,7 +24,6 @@
"label": "ゲームバージョン", "label": "ゲームバージョン",
"placeholder": "バージョンを選択…" "placeholder": "バージョンを選択…"
}, },
"usestageloader": "ステージローダーの使用",
"downloadformat": { "downloadformat": {
"label": "ダウンロードフォーマット", "label": "ダウンロードフォーマット",
"options": { "options": {
@ -259,7 +259,6 @@
} }
}, },
"levelselectplaceholder": "レベルを選択します..", "levelselectplaceholder": "レベルを選択します..",
"loadpresetplaceholder": "プリセットをロードします..",
"route": "ルート", "route": "ルート",
"clear": "リストをクリア" "clear": "リストをクリア"
}, },

8363
yarn.lock

File diff suppressed because it is too large Load diff