Implement customizable code; add Instant Restart

This commit is contained in:
sup39 2022-04-08 12:48:37 +09:00
parent ea10541da0
commit 06f7ad9864
11 changed files with 233 additions and 3 deletions

View file

@ -4040,4 +4040,34 @@
00000000 59800004
</source>
</code>
<code>
<id>InstantRestart</id>
<category>qol</category>
<presets>recommended</presets>
<title lang="en-US">Instant Restart</title>
<title lang="ja-JP">ポーズせずにやり直し</title>
<author>sup39(サポミク)</author>
<version>0.1.3</version>
<date>Jan 07, 2022</date>
<description lang="en-US">
When you pressed the buttons configured in [#Button Config](#config) simultaneously,
you can restart the current area without selecting "Exit Area" in pause menu.
Note that the restart function behaves differently than pressing Y or Z with "Level Select".
This code only supports restarting 1 area only.
For example, you can restart outside a secret stage or inside a secret stage individually,
but you can NOT restart a combination of outside+inside a secret stage.
::: warning
You can NOT restart after destroying the last platform in Bowser fight at the moment.
:::
</description>
<description lang="ja-JP">
[#ボタン設定](#config)で設定したボタンを同時に押すと、ポーズメニューから「コースから出る」を選択せずに所在のエリアをやり直すことができます。ただし、Level SelectのYとZのやり直し機能と異なり、エリアごとのやり直ししかできないので注意してください。例えば、ヒミツ外部のみ、ヒミツ内部のみといった一つのエリアのやり直しはできますが、ヒミツ外部+ヒミツ内部といった組み合わせのやり直しはできません。
::: warning
現時点ではクッパ戦で最後の足場を破壊するとやり直しできません。
:::
</description>
<source version="GMSJ01"></source>
</code>
</codes>

View file

@ -1,4 +1,27 @@
# Changelog
## Apr 08, 2022
### Implemented customizable code
- Add code info in `Codes.xml` as other code,
but you should specify `<id>` and available versions with `<source version="XXX"></source>`
(the code in `<source>` will be ignored).
- Create directory `site/.vuepress/components/codes/YOUR_CODE_NAME`,
and create the following two files (file names are arbitrary):
+ `codegen.js`: **export default** a `codegen(version: string): string` function,
where `version` is the game version (e.g. `GMSJ01`),
and the return value is the gecko code string.
The config of the code can be read from localStorage
(with key = `config/YOUR_CODE_ID` as convention) directly.
+ `config.vue`: The vue component of the config UI,
which is shown below the `<description>` given in `Codes.xml`.
When the config is changed, store the new config into localStorage
(with key = `config/YOUR_CODE_ID` as convention).
- Register `codegen.js` in `site/.vuepress/components/codes/codegen.js`
and `config.vue` in `site/.vuepress/components/codes/ui.js`.
Note that the name used in export must match the `<id>` of the code.
### Added 'Instant Restart' (GMSJ01 only)
Restarts the area without pausing.
## Mar 25, 2022
### Implemented dependencies system
Add `<dependencies>` tag in `<code>` (e.g. `<dependencies>dep1,dep2,dep3</dependencies>`)

View file

@ -107,8 +107,7 @@ const validateXML = (xmlString) => {
// Each source has a valid length
for (let j = 0; j < codeSources.length; j++) {
if (
codeSources[j].textContent.replace(/[\s\n\r\t]+/gm, '').length % 16 != 0 ||
codeSources[j].textContent.replace(/[\s\n\r\t]+/gm, '').length < 16
codeSources[j].textContent.replace(/[\s\n\r\t]+/gm, '').length % 16 != 0
)
throw new Error(
`Invalid source length for code '${codeTitle.textContent}' and version ${

View file

@ -11,21 +11,27 @@
<span v-else>{{ getLabel('codeinfo.author') }} {{ translatedCode.author }}</span>
</div>
<p class="description" v-html="translatedCode.description"></p>
<component v-if="configUI" :is="configUI" :version="version"></component>
</div>
</template>
<script>
import { translate, translateCode } from '../i18n/localeHelper';
import configUIs from './codes/ui.js';
export default {
props: {
anchor: { type: Boolean },
code: { type: Object },
version: { type: String },
},
computed: {
translatedCode: function () {
return translateCode(this.code, this.$lang);
},
configUI: function () {
return configUIs[this.code.id];
},
},
data() {
return {};

View file

@ -15,6 +15,9 @@ import gameVersions from '../data/gameVersions.json';
// Util
import { translateCode } from '../i18n/localeHelper';
// customizable code
import codegens from './codes/codegen.js';
export default {
props: {
codes: { type: Array },
@ -59,6 +62,15 @@ export default {
const fileName = gameVersions.find((v) => v.identifier === this.versionIdentifier).version;
// apply customizable codes
for (const code of c) {
const codegen = codegens[code.id];
if (codegen) {
code.source = codegen(this.versionIdentifier);
}
}
// generate file
switch (this.format) {
case 'gct':
this.generateGCT(c, fileName);

View file

@ -42,7 +42,7 @@
<div v-if="codes && codes.length > 0" class="help">
<h3>{{ getLabel('headers.help') }}</h3>
<CodeInfo v-if="!!inspectingCode" :code="inspectingCode" />
<CodeInfo v-if="!!inspectingCode" :code="inspectingCode" :version="selectedVersion" />
<div v-else-if="showStageLoaderHelp">
<h3>{{ getLabel('headers.stageloader') }}</h3>
<div>

View file

@ -0,0 +1,52 @@
import { parseJSON } from '../codegen.js';
export const lskey = 'config/InstantRestart';
export const buttonValues = {
START: 0x1000,
Y: 0x0800,
X: 0x0400,
B: 0x0200,
A: 0x0100,
L: 0x0040,
R: 0x0020,
Z: 0x0010,
DU: 0x0008,
DD: 0x0004,
DR: 0x0002,
DL: 0x0001,
};
export const defaultConfig = {
button: buttonValues.Y | buttonValues.DL,
};
export function getConfig() {
return {
...defaultConfig,
...(parseJSON(localStorage.getItem(lskey)) ?? {}),
};
}
export default function codegen(version) {
const { button } = getConfig();
let code;
switch (version) {
case 'GMSJ01':
code = `
c20eafa0 00000009
3c608040 a0a30d50
2805${button.toString(16).padStart(4, '0')} 40a20030
3c60817f 38a00001
98a300b3 98a30100
3c60803e 80a3600e
90a36012 3c60800e
6063b3f8 7c6803a6
4e800020 2c000002
60000000 00000000
`;
if (button & buttonValues.Z) {
code += '\n040eb024 60000000';
}
break;
default:
return '';
}
return code.replace(/\s/g, '');
}

View file

@ -0,0 +1,68 @@
<template>
<div id="config">
<h3>{{l('h3Config')}}</h3>
<div>
<span>{{l('lblButton')}}</span>
<div v-for="info in buttonInfos" class="inline" :key="info.value">
<input type="checkbox" :checked="button & info.value"
@change="toggleButton($event, info.value)"><span>{{info.text}}</span>
</div>
</div>
<div>
<span>{{l('lblDPad')}}</span>
<div v-for="info in dpadButtonInfos" class="inline" :key="info.value">
<input type="checkbox" :checked="button & info.value"
@change="toggleButton($event, info.value)"><span>{{info.text}}</span>
</div>
</div>
<div v-if="button & buttonValues.Z" class="custom-block danger">
<p>{{l('pCautionZ')}}</p>
</div>
</div>
</template>
<script>
import {getConfig, lskey, buttonValues} from './codegen.js';
import labels from './labels.json';
import {getLabels} from '../codegen.js';
export default {
methods: {
updateConfig() {
localStorage.setItem(lskey, JSON.stringify({button: this.button}));
},
toggleButton(event, value) {
this.button = event.target.checked ?
this.button | value : // ON
this.button & ~value; // OFF
this.updateConfig();
},
l(id) {
return labels[id][this.$lang] ?? labels[id]['en-US'];
},
},
data() {
const {button} = getConfig();
return {
button,
// const
buttonInfos: [
'A', 'B', 'X', 'Y', 'L', 'R', 'Z',
].map(text => ({text, value: buttonValues[text]})),
dpadButtonInfos: [
{text: '←', value: buttonValues.DL},
{text: '↓', value: buttonValues.DD},
{text: '↑', value: buttonValues.DU},
{text: '→', value: buttonValues.DR},
],
buttonValues,
};
},
}
</script>
<style>
.inline {
display: inline-block;
}
</style>

View file

@ -0,0 +1,18 @@
{
"h3Config": {
"ja-JP": "ボタン設定",
"en-US": "Button Config"
},
"lblDPad": {
"ja-JP": "【十字キー】",
"en-US": "D-Pad: "
},
"lblButton": {
"ja-JP": "【ボタン】",
"en-US": "Button: "
},
"pCautionZ": {
"ja-JP": "Zメニューが無効化されるので注意してください",
"en-US": "Note that Z menu will be disabled."
}
}

View file

@ -0,0 +1,17 @@
import InstantRestart from './InstantRestart/codegen.js';
export default {
InstantRestart,
};
/**
* @param {string|null} s -- raw input string
*/
export function parseJSON(s) {
if (s == null) return null;
try {
return JSON.parse(s);
} catch {
return null;
}
}

View file

@ -0,0 +1,5 @@
import InstantRestart from './InstantRestart/config.vue';
export default {
InstantRestart,
};