diff --git a/appData/templates/blank/project.gbsproj b/appData/templates/blank/project.gbsproj index 15d5db6c7..7a00eb2a0 100644 --- a/appData/templates/blank/project.gbsproj +++ b/appData/templates/blank/project.gbsproj @@ -2,7 +2,7 @@ "name": "___PROJECT_NAME___", "author": "___AUTHOR___", "_version": "4.0.0", - "_release": "1", + "_release": "3", "scenes": [], "backgrounds": [ { diff --git a/appData/templates/gbhtml/project.gbsproj b/appData/templates/gbhtml/project.gbsproj index 704a51e84..add1c8a50 100644 --- a/appData/templates/gbhtml/project.gbsproj +++ b/appData/templates/gbhtml/project.gbsproj @@ -2,7 +2,7 @@ "name": "___PROJECT_NAME___", "author": "___AUTHOR___", "_version": "4.0.0", - "_release": "1", + "_release": "3", "scenes": [ { "id": "94c18861-b352-4f49-a64d-52f2e3415077", diff --git a/appData/templates/gbs2/project.gbsproj b/appData/templates/gbs2/project.gbsproj index e2e42037c..4270165e3 100644 --- a/appData/templates/gbs2/project.gbsproj +++ b/appData/templates/gbs2/project.gbsproj @@ -2,7 +2,7 @@ "name": "___PROJECT_NAME___", "author": "___AUTHOR___", "_version": "4.0.0", - "_release": "1", + "_release": "3", "scenes": [ { "id": "94c18861-b352-4f49-a64d-52f2e3415077", @@ -4728,8 +4728,14 @@ { "command": "EVENT_REPLACE_TILE_XY_SEQUENCE", "args": { - "x": 22, - "y": 6, + "x": { + "type": "number", + "value": 22 + }, + "y": { + "type": "number", + "value": 6 + }, "tileIndex": { "type": "number", "value": 0 @@ -4746,8 +4752,14 @@ { "command": "EVENT_REPLACE_TILE_XY_SEQUENCE", "args": { - "x": 22, - "y": 17, + "x": { + "type": "number", + "value": 22 + }, + "y": { + "type": "number", + "value": 17 + }, "tileIndex": { "type": "number", "value": 4 @@ -4764,8 +4776,14 @@ { "command": "EVENT_REPLACE_TILE_XY_SEQUENCE", "args": { - "x": 4, - "y": 11, + "x": { + "type": "number", + "value": 4 + }, + "y": { + "type": "number", + "value": 11 + }, "tileIndex": { "type": "number", "value": 0 diff --git a/src/lib/compiler/scriptBuilder.ts b/src/lib/compiler/scriptBuilder.ts index 922275aa6..3e43751f0 100644 --- a/src/lib/compiler/scriptBuilder.ts +++ b/src/lib/compiler/scriptBuilder.ts @@ -2142,7 +2142,11 @@ extern void __mute_mask_${symbol}; ); }; - _getTileXY = (addr: ScriptBuilderStackVariable, x: number, y: number) => { + _getTileXY = ( + addr: ScriptBuilderStackVariable, + x: ScriptBuilderStackVariable, + y: ScriptBuilderStackVariable + ) => { this._addCmd("VM_GET_TILE_XY", addr, x, y); }; @@ -5205,8 +5209,8 @@ extern void __mute_mask_${symbol}; }; replaceTileXYScriptValue = ( - x: number, - y: number, + x: ScriptValue, + y: ScriptValue, tilesetId: string, tileIndexValue: ScriptValue, tileSize: "8px" | "16px" @@ -5219,43 +5223,134 @@ extern void __mute_mask_${symbol}; const tileIndex = this._declareLocal("tile_index", 1, true); this._addComment(`Replace Tile XY`); - const [rpnOps, fetchOps] = precompileScriptValue( + + const [rpnOpsX, fetchOpsX] = precompileScriptValue(optimiseScriptValue(x)); + const [rpnOpsY, fetchOpsY] = precompileScriptValue(optimiseScriptValue(y)); + const [rpnOpsTile, fetchOpsTile] = precompileScriptValue( optimiseScriptValue(tileIndexValue) ); - const localsLookup = this._performFetchOperations(fetchOps); - const rpn = this._rpn(); - this._performValueRPN(rpn, rpnOps, localsLookup); - rpn.refSet(tileIndex); - rpn.stop(); - if (tileSize === "16px") { - // Top left tile - this._replaceTileXY(x, y, tileset.symbol, tileIndex); - // Top right tile - this._rpn() // - .ref(tileIndex) - .int8(1) - .operator(".ADD") - .refSet(tileIndex) - .stop(); - this._replaceTileXY(x + 1, y, tileset.symbol, tileIndex); - // Bottom right tile - this._rpn() // - .ref(tileIndex) - .int8(tileset.width) - .operator(".ADD") - .refSet(tileIndex) - .stop(); - this._replaceTileXY(x + 1, y + 1, tileset.symbol, tileIndex); - // Bottom left tile - this._rpn() // - .ref(tileIndex) - .int8(1) - .operator(".SUB") - .refSet(tileIndex) - .stop(); - this._replaceTileXY(x, y + 1, tileset.symbol, tileIndex); + + if ( + rpnOpsX.length === 1 && + rpnOpsX[0].type === "number" && + rpnOpsY.length === 1 && + rpnOpsY[0].type === "number" + ) { + // Can optimise using constant values for X and Y coordinates + const localsLookup = this._performFetchOperations(fetchOpsTile); + const constX = rpnOpsX[0].value; + const constY = rpnOpsY[0].value; + const rpn = this._rpn(); + this._performValueRPN(rpn, rpnOpsTile, localsLookup); + rpn.refSet(tileIndex); + rpn.stop(); + if (tileSize === "16px") { + // 16px tiles - Top left tile + this._replaceTileXY(constX, constY, tileset.symbol, tileIndex); + // 16px tiles - Top right tile + this._rpn() // + .ref(tileIndex) + .int8(1) + .operator(".ADD") + .refSet(tileIndex) + .stop(); + this._replaceTileXY(constX + 1, constY, tileset.symbol, tileIndex); + // 16px tiles - Bottom right tile + this._rpn() // + .ref(tileIndex) + .int8(tileset.width) + .operator(".ADD") + .refSet(tileIndex) + .stop(); + this._replaceTileXY(constX + 1, constY + 1, tileset.symbol, tileIndex); + // 16px tiles - Bottom left tile + this._rpn() // + .ref(tileIndex) + .int8(1) + .operator(".SUB") + .refSet(tileIndex) + .stop(); + this._replaceTileXY(constX, constY + 1, tileset.symbol, tileIndex); + } else { + // 8px tiles + this._replaceTileXY(constX, constY, tileset.symbol, tileIndex); + } } else { - this._replaceTileXY(x, y, tileset.symbol, tileIndex); + // Using RPN for X/Y values + const tileX = this._declareLocal("tile_x", 1, true); + const tileY = this._declareLocal("tile_y", 1, true); + const tileAddr = this._declareLocal("tile_addr", 1, true); + + const localsLookup = this._performFetchOperations([ + ...fetchOpsX, + ...fetchOpsY, + ...fetchOpsTile, + ]); + const rpn = this._rpn(); + this._performValueRPN(rpn, rpnOpsX, localsLookup); + rpn.refSet(tileX); + this._performValueRPN(rpn, rpnOpsY, localsLookup); + rpn.refSet(tileY); + this._performValueRPN(rpn, rpnOpsTile, localsLookup); + rpn.refSet(tileIndex); + rpn.stop(); + + if (tileSize === "16px") { + // 16px tiles - Top left tile + this._getTileXY(tileAddr, tileX, tileY); + this._replaceTile(tileAddr, tileset.symbol, tileIndex, 1); + // 16px tiles - Top right tile + this._rpn() // + // Inc Tile X + .ref(tileX) + .int8(1) + .operator(".ADD") + .refSetVariable(tileX) + // Inc Tile Index + .ref(tileIndex) + .int8(1) + .operator(".ADD") + .refSet(tileIndex) + .stop(); + this._getTileXY(tileAddr, tileX, tileY); + this._replaceTile(tileAddr, tileset.symbol, tileIndex, 1); + // 16px tiles - Bottom right tile + this._rpn() // + // Inc Tile Y + .ref(tileY) + .int8(1) + .operator(".ADD") + .refSetVariable(tileY) + // Inc Tile Index + .ref(tileIndex) + .int8(tileset.width) + .operator(".ADD") + .refSet(tileIndex) + .stop(); + this._getTileXY(tileAddr, tileX, tileY); + this._replaceTile(tileAddr, tileset.symbol, tileIndex, 1); + // 16px tiles - Bottom left tile + this._rpn() // + // Inc Tile X + .ref(tileX) + .int8(1) + .operator(".SUB") + .refSetVariable(tileX) + // Inc Tile Index + .ref(tileIndex) + .int8(1) + .operator(".SUB") + .refSet(tileIndex) + .stop(); + this._getTileXY(tileAddr, tileX, tileY); + this._replaceTile(tileAddr, tileset.symbol, tileIndex, 1); + } else { + // 8px tiles + this._getTileXY(tileAddr, tileX, tileY); + this._replaceTile(tileAddr, tileset.symbol, tileIndex, 1); + } + + this.markLocalsUsed(tileIndex, tileAddr, tileX, tileY); } }; diff --git a/src/lib/events/eventReplaceTileXY.js b/src/lib/events/eventReplaceTileXY.js index 0a5121687..ce8cce70f 100644 --- a/src/lib/events/eventReplaceTileXY.js +++ b/src/lib/events/eventReplaceTileXY.js @@ -21,21 +21,27 @@ const fields = [ key: "x", label: l10n("FIELD_X"), description: l10n("FIELD_X_DESC"), - type: "number", + type: "value", min: 0, max: 255, width: "50%", - defaultValue: 0, + defaultValue: { + type: "number", + value: 0, + }, }, { key: "y", label: l10n("FIELD_Y"), description: l10n("FIELD_Y_DESC"), - type: "number", + type: "value", min: 0, max: 255, width: "50%", - defaultValue: 0, + defaultValue: { + type: "number", + value: 0, + }, }, ], }, diff --git a/src/lib/events/eventReplaceTileXYSequence.js b/src/lib/events/eventReplaceTileXYSequence.js index c07466ac2..2fb605989 100644 --- a/src/lib/events/eventReplaceTileXYSequence.js +++ b/src/lib/events/eventReplaceTileXYSequence.js @@ -21,21 +21,27 @@ const fields = [ key: "x", label: l10n("FIELD_X"), description: l10n("FIELD_X_DESC"), - type: "number", + type: "value", min: 0, max: 255, width: "50%", - defaultValue: 0, + defaultValue: { + type: "number", + value: 0, + }, }, { key: "y", label: l10n("FIELD_Y"), description: l10n("FIELD_Y_DESC"), - type: "number", + type: "value", min: 0, max: 255, width: "50%", - defaultValue: 0, + defaultValue: { + type: "number", + value: 0, + }, }, ], }, @@ -89,7 +95,7 @@ const fields = [ const compile = (input, helpers) => { const { - replaceTileXYVariable, + replaceTileXYScriptValue, ifVariableCompare, ifVariableCompareScriptValue, variableInc, @@ -145,11 +151,14 @@ const compile = (input, helpers) => { variableSetToScriptValue(input.variable, input.tileIndex); }); - replaceTileXYVariable( + replaceTileXYScriptValue( input.x, input.y, input.tilesetId, - input.variable, + { + type: "variable", + value: input.variable, + }, input.tileSize ); diff --git a/src/lib/project/migrateProject.ts b/src/lib/project/migrateProject.ts index 25ddc077a..99aefb447 100644 --- a/src/lib/project/migrateProject.ts +++ b/src/lib/project/migrateProject.ts @@ -50,11 +50,12 @@ import { ValueOperatorType, isScriptValue, } from "shared/lib/scriptValue/types"; +import { ensureNumber } from "shared/types"; const indexById = (arr: T[]) => keyBy(arr, "id"); export const LATEST_PROJECT_VERSION = "4.0.0"; -export const LATEST_PROJECT_MINOR_VERSION = "2"; +export const LATEST_PROJECT_MINOR_VERSION = "3"; const ensureProjectAssetSync = ( relativePath: string, @@ -92,6 +93,22 @@ const generateMigrateMeta = }; }; +const applyEventsMigration = ( + data: ProjectData, + fn: (event: ScriptEvent) => ScriptEvent +): ProjectData => { + return { + ...data, + scenes: mapScenesScript(data.scenes, fn), + customEvents: (data.customEvents || []).map((customEvent) => { + return { + ...customEvent, + script: mapScript(customEvent.script, fn), + }; + }), + }; +}; + type ProjectDataV1 = Omit & { spriteSheets: (SpriteSheetData & { numFrames: number })[]; scenes: (Omit & { @@ -2473,17 +2490,35 @@ export const migrateFrom400r1To400r2Event = ( return event; }; -const migrateFrom400r1To400r2Events = (data: ProjectData): ProjectData => { - return { - ...data, - scenes: mapScenesScript(data.scenes, migrateFrom400r1To400r2Event), - customEvents: (data.customEvents || []).map((customEvent) => { - return { - ...customEvent, - script: mapScript(customEvent.script, migrateFrom400r1To400r2Event), - }; - }), - }; +/* Version 4.0.0 r3 updates EVENT_REPLACE_TILE_XY and EVENT_REPLACE_TILE_XY_SEQUENCE + * to use script values for X/Y coordinates + */ +export const migrateFrom400r2To400r3Event = ( + event: ScriptEvent +): ScriptEvent => { + const migrateMeta = generateMigrateMeta(event); + + if ( + event.args && + (event.command === "EVENT_REPLACE_TILE_XY" || + event.command === "EVENT_REPLACE_TILE_XY_SEQUENCE") + ) { + return migrateMeta({ + ...event, + args: { + ...event.args, + x: { + type: "number", + value: ensureNumber(event.args.x, 0), + }, + y: { + type: "number", + value: ensureNumber(event.args.y, 0), + }, + }, + }); + } + return event; }; const migrateProject = ( @@ -2680,9 +2715,13 @@ const migrateProject = ( if (version === "4.0.0") { if (release === "1") { - data = migrateFrom400r1To400r2Events(data); + data = applyEventsMigration(data, migrateFrom400r1To400r2Event); release = "2"; } + if (release === "2") { + data = applyEventsMigration(data, migrateFrom400r2To400r3Event); + release = "3"; + } } if (process.env.NODE_ENV !== "production") { diff --git a/test/migrate/migrate400releases.test.js b/test/migrate/migrate400releases.test.js index 7bab8642a..506e5b3fd 100644 --- a/test/migrate/migrate400releases.test.js +++ b/test/migrate/migrate400releases.test.js @@ -1,4 +1,7 @@ -import { migrateFrom400r1To400r2Event } from "../../src/lib/project/migrateProject"; +import { + migrateFrom400r1To400r2Event, + migrateFrom400r2To400r3Event, +} from "../../src/lib/project/migrateProject"; import initElectronL10N from "../../src/lib/lang/initElectronL10N"; beforeAll(async () => { @@ -149,3 +152,112 @@ test("should keep Engine Field Update script values", () => { }, }); }); + +test("should migrate x/y coordinate from replace tile event", () => { + const oldEvent = { + command: "EVENT_REPLACE_TILE_XY", + args: { + x: 14, + y: 7, + tilesetId: "d7042527-d20e-486d-93e6-66b9397df510", + tileIndex: { + type: "number", + value: 1, + }, + }, + }; + expect(migrateFrom400r2To400r3Event(oldEvent)).toMatchObject({ + command: "EVENT_REPLACE_TILE_XY", + args: { + x: { + type: "number", + value: 14, + }, + y: { + type: "number", + value: 7, + }, + tilesetId: "d7042527-d20e-486d-93e6-66b9397df510", + tileIndex: { + type: "number", + value: 1, + }, + }, + }); +}); + +test("should migrate x/y coordinate from replace tile sequence event", () => { + const oldEvent = { + command: "EVENT_REPLACE_TILE_XY_SEQUENCE", + args: { + x: 4, + y: 11, + tileIndex: { + type: "number", + value: 0, + }, + variable: "L2", + tilesetId: "d7042527-d20e-486d-93e6-66b9397df510", + frames: { + type: "number", + value: 4, + }, + }, + }; + expect(migrateFrom400r2To400r3Event(oldEvent)).toMatchObject({ + command: "EVENT_REPLACE_TILE_XY_SEQUENCE", + args: { + x: { + type: "number", + value: 4, + }, + y: { + type: "number", + value: 11, + }, + tileIndex: { + type: "number", + value: 0, + }, + variable: "L2", + tilesetId: "d7042527-d20e-486d-93e6-66b9397df510", + frames: { + type: "number", + value: 4, + }, + }, + }); +}); + +test("should migrate x/y coordinate values as 0 in replace tile event if invalid", () => { + const oldEvent = { + command: "EVENT_REPLACE_TILE_XY", + args: { + x: "", + y: undefined, + tilesetId: "d7042527-d20e-486d-93e6-66b9397df510", + tileIndex: { + type: "number", + value: 1, + }, + }, + }; + expect(migrateFrom400r2To400r3Event(oldEvent)).toMatchObject({ + command: "EVENT_REPLACE_TILE_XY", + args: { + x: { + type: "number", + value: 0, + }, + y: { + type: "number", + value: 0, + }, + tilesetId: "d7042527-d20e-486d-93e6-66b9397df510", + tileIndex: { + type: "number", + value: 1, + }, + }, + }); +});