diff --git a/app.js b/app.js index e5d2805..fe395da 100644 --- a/app.js +++ b/app.js @@ -524,7 +524,8 @@ class RomPatcher { static FIRST_MOD_BANK = 0x4 + 0x11; // Hashes of known ROMs static KNOWN_ROMS = { - "utmgm9": "STB 2.5", + "utmgm9": { "name": "Super Tilt Bro. v2.5 hash:utmgm9", "isOnlineEnabled": true }, + "itn6my": { "name": "Super Tilt Bro. v2.5 hash:itn6my", "isOnlineEnabled": false } }; constructor(storage, onchange) { @@ -583,7 +584,14 @@ class RomPatcher { // Return name of the ROM, return a hash or a known name static getRomName(romData) { const hash = hashByteArray(romData); - return this.KNOWN_ROMS[hash] || `hash:${hash}`; + if(this.KNOWN_ROMS[hash]) return this.KNOWN_ROMS[hash].name; + return `hash:${hash}`; + } + + static isRomOnlineEnabled(romData) { + const hash = hashByteArray(romData); + if(this.KNOWN_ROMS[hash]) return this.KNOWN_ROMS[hash].isOnlineEnabled + return true; // assume online is enabled } // Write a 16-bit value, return end offset @@ -765,6 +773,8 @@ const app = Vue.createApp({ return { tree: Vue.computed(() => this.tree), conf: Vue.computed(() => this.conf), + characterFileIndex: Vue.computed(() => this.characterFileIndex), + characterUrls: Vue.computed(() => this.characterUrls), } }, @@ -1628,14 +1638,15 @@ app.component('stb-sprite-thumbnail', { }); app.component('stb-animation-thumbnail', { - props: ['animation', 'zoom', 'rect', 'swap'], + props: ['animation', 'zoom', 'rect', 'swap', 'data'], inject: ['tree', 'conf'], methods: { updateAnimation() { const rect = this.rect || Utils.animationRect(this.animation, {}); const swap = this.swap === undefined ? this.conf.colorSwap : this.swap; - const palettes = Utils.getPalettes(this.tree, swap); + const palettes = this.data === undefined ? Utils.getPalettes(this.tree, swap) : Utils.getPalettes(this.data, swap); + const tree = this.data === undefined ? this.tree : this.data; // Prepare the canvas const canvas = this.$refs.canvas; @@ -1648,7 +1659,7 @@ app.component('stb-animation-thumbnail', { const promises = frames.map(frame => { ctx.fillStyle = this.conf.bgColor; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - Utils.drawSingleFrame(ctx, this.tree, frame, { + Utils.drawSingleFrame(ctx, tree, frame, { zoom: this.zoom, palettes, dx: -rect.x, dy: -rect.y, }); return createImageBitmap(canvas); @@ -2606,26 +2617,72 @@ const AiTab = { } const RomTab = { - inject: ['tree'], + inject: ['tree','conf','characterFileIndex','characterUrls'], data() { return { romData: null, romError: null, + romIsOnlineEnabled: true, characterSlot: 0, + // mod data stored in stbeditor.slot.${id} + characters: [ + { "id": 0, "name": "sinbad" }, + { "id": 1, "name": "kiki" }, + { "id": 2, "name": "pepper" }, + { "id": 3, "name": "vgsage" }, + { "id": 4, "name": "sunny" }, + ], + characterSlots: null, + characterFiles: null, + modTemplate: null, } }, created() { this.patcher = new RomPatcher(localStorage); this.romData = this.patcher.loadRomData(); + this.romIsOnlineEnabled = this.romData ? RomPatcher.isRomOnlineEnabled(this.romData) : true; this.characterSlot = this.patcher.getCharacterSlot(); + this.modTemplate = this.getModTemplate(); }, computed: { romName() { return this.romData ? RomPatcher.getRomName(this.romData) : null; - } + }, + characterData() { + this.characterFiles = {}; + this.characterFiles[""] = { "id": "" }; + for(charfile in this.characterFileIndex) { + this.characterFiles[this.characterFileIndex[charfile].name] = { "id": this.characterFileIndex[charfile].name, "name": this.characterFileIndex[charfile].name, "data": JSON.parse(localStorage.getItem(`stbeditor.data.${this.characterFileIndex[charfile].name}`)), "isDefault": false }; + } + for(charfile in this.characterUrls) { + const entry = { "id": "->"+charfile, "name": charfile, "data": this.characterUrls[charfile], "isDefault": true }; + if(entry.data) { + this.characterFiles[entry.id] = entry; + fetch(entry.data) + .then(response => response.json()) + .then(data => { + this.characterFiles[entry.id].data = data; + }) + .catch(err => console.error(err)); + } + } + return this.characterFiles; + }, + characterSlotsFilled() { + if(!this.characterSlots) this.loadCharacterSlots(); + for(i=0; i response.json()) + .then(data => { this.modTemplate = data; }) + .catch(err => console.info(`cannot load static configuration: ${err}`)); + }, + generateAndDownloadModFile() { + console.debug(`Download mod.json`); + let modFile = cloneData(this.modTemplate); + for(i=0; i

Patch a ROM

+
+

Characters to Patch

+
+

+ + + + + + +    + {{ slot.name }} +

+
+
+
+

Apply Mod Patch

+       Build ROM +
or
+
+
+

Patch an Existing ROM

Write character data to the original ROM. Limitations listed below apply.
Basically graphics, hitboxes and hurtboxes can be modified. Everything else must match the ROM to patch.
@@ -2671,17 +2824,16 @@ const RomTab = {

{{ romError }} - A ROM is loaded ({{ romName }}) + A ROM is loaded ({{ romName }}) + [ Online Enabled?] + No ROM loaded

- -

-

- - (no character file loaded) +

+
`, } diff --git a/data/mod.json b/data/mod.json new file mode 100644 index 0000000..6a0eceb --- /dev/null +++ b/data/mod.json @@ -0,0 +1,15 @@ +{ + "type": "gamemod", + "characters": [ + {"type": "import", "src": "characters/sinbad/sinbad.json"}, + {"type": "import", "src": "characters/kiki/kiki.json"}, + {"type": "import", "src": "characters/pepper/pepper.json"}, + {"type": "import", "src": "characters/vgsage/vgsage.json"}, + {"type": "import", "src": "characters/sunny/sunny.json"} + ], + "tilesets": [ + {"type": "import", "src": "tilesets/ruins.json"}, + {"type": "import", "src": "tilesets/jungle.json"}, + {"type": "import", "src": "tilesets/magma.json"} + ] +} \ No newline at end of file