Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions editor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Editor changelog

- A party member who leaves the battle line mid-fight, for example captured or banished by a state that hides them, disappears from the battlefield instead of standing there with their last pose (runtime 20261113.1). Clearing a sprite's bitmap assigned a new texture frame, which Pixi v8 never applies, so the sprite kept drawing the old image; it now returns to an empty texture of its own. Status window, target lists and turn order were already right.
- For plugin authors: two methods on `Game_Enemy` to replace instead of working around. `rrActionPatterns()` returns the action patterns an enemy chooses from, and the turn-based, Action and Tactical battle modes now all read them through it, so a plugin that gives an enemy other skills changes one method and every mode follows. `rrEquipSource()` returns the rows an enemy's equipment is first read from, so a plugin can equip an enemy from somewhere other than its database entry. Both return the database lists by default, so nothing changes for a project without such a plugin. (runtime 20261112.1)

The full technical list, one file per release or labeled development checkpoint, newest first. The short version of each release is in the [root changelog](../CHANGELOG.md).
Expand Down
73 changes: 73 additions & 0 deletions editor/tests/sprite-null-bitmap.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict';
// Sprite.bitmap = null must stop a sprite drawing its previous bitmap under Pixi v8.
// An actor sprite whose battler leaves battleMembers() mid-battle is cleared this way;
// before the fix it kept rendering that actor's last frame.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
const native = require('pixi.js');

const runtime = path.resolve(__dirname, '../../runtime');
const read = file => fs.readFileSync(path.join(runtime, file), 'utf8');

function loadSprite() {
const context = {
PIXI: { ...native }, TypeError, Error, Reflect, Float32Array, Uint8Array, ArrayBuffer,
console: { log() {}, warn() {}, error() {} }, location: { search: '' }
};
context.window = context;
vm.createContext(context);
vm.runInContext(read('libs/pixi_compat.js'), context);
const core = read('reactor_core.js');
const rectangleStart = core.indexOf('function Rectangle()');
vm.runInContext(core.slice(rectangleStart, core.indexOf('//----', rectangleStart)), context);
const display = read('reactor_core_display.js');
vm.runInContext(display.slice(0, display.indexOf('Sprite.prototype._createColorFilter')) + '\nthis.Sprite = Sprite;', context);
return context;
}

function fakeBitmap(P) {
const baseTexture = P.BaseTexture.fromBuffer(new Uint8Array(64 * 64 * 4), 64, 64);
return { baseTexture, width: 64, height: 64, addLoadListener(callback) { callback(this); } };
}

test('clearing a sprite bitmap leaves it on an empty texture of its own', () => {
const context = loadSprite();
const bitmap = fakeBitmap(context.PIXI);
const sprite = new context.Sprite(bitmap);
assert.equal(sprite.texture.source, bitmap.baseTexture.source);
assert.equal(sprite.texture.uvs.x1, 1);
const drawn = sprite.texture;

sprite.bitmap = null;

assert.notEqual(sprite.texture.source, bitmap.baseTexture.source, 'no longer samples the old bitmap');
assert.equal(sprite.texture.uvs.x1, 0, 'UVs cover nothing');
assert.deepEqual([sprite.texture.frame.width, sprite.texture.frame.height], [0, 0]);
assert.equal(sprite.texture.__rrSpriteOwned, true);
assert.equal(drawn.destroyed, true, 'the replaced sprite-owned texture is released');
assert.equal(bitmap.baseTexture.source.destroyed, false, 'the bitmap source itself is untouched');
});

test('a cleared sprite shows its bitmap again when one is assigned', () => {
const context = loadSprite();
const bitmap = fakeBitmap(context.PIXI);
const sprite = new context.Sprite(bitmap);
sprite.bitmap = null;
sprite.bitmap = bitmap;
assert.equal(sprite.texture.source, bitmap.baseTexture.source);
assert.deepEqual([sprite.texture.frame.width, sprite.texture.frame.height], [64, 64]);
});

test('a new sprite without a bitmap keeps its constructor texture', () => {
const context = loadSprite();
const realCreate = context.PIXICreateTexture;
let created = 0;
context.PIXICreateTexture = (...args) => { created++; return realCreate(...args); };
const sprite = new context.Sprite();
assert.equal(created, 1, 'initialize() does not build a second empty texture');
assert.equal(sprite.texture.__rrSpriteOwned, true);
assert.deepEqual([sprite.texture.frame.width, sprite.texture.frame.height], [0, 0]);
});
21 changes: 20 additions & 1 deletion runtime/reactor_core_display.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,26 @@ Sprite.prototype._onBitmapChange = function() {
this._bitmap.addLoadListener(this._onBitmapLoad.bind(this));
} else {
this._refreshFrame = false;
this.texture.frame = new Rectangle();
if (PIXI.TextureSource) {
// v8: assigning texture.frame refreshes neither the UVs nor the
// source, so a sprite whose bitmap is cleared kept drawing the old
// bitmap's last frame (an actor sprite left without a battler went
// on showing that actor). Go back to an empty texture of the
// sprite's own, as initialize() starts with. A texture whose frame
// is already 0x0 draws nothing (initialize() calls this with one),
// so it is kept rather than allocated twice.
const old = this.texture;
if (!old || old.frame.width > 0 || old.frame.height > 0) {
const texture = PIXICreateTexture(Sprite._emptyBaseTexture, new Rectangle());
texture.__rrSpriteOwned = true;
this.texture = texture;
if (old && old.__rrSpriteOwned && !old.destroyed) {
old.destroy();
}
}
} else {
this.texture.frame = new Rectangle();
}
}
};

Expand Down
4 changes: 2 additions & 2 deletions runtime/reactor_main.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//=============================================================================
// reactor_main.js — RPG Reactor runtime entry point
// RPG Reactor runtime version: 0.99.5
// RPG Reactor runtime revision: 20261112.1
// RPG Reactor runtime revision: 20261113.1
// The same stamp, reachable from the F12 console: which engine is this
// window actually running? Type RPG_REACTOR_RUNTIME_REVISION to see.
globalThis.RPG_REACTOR_RUNTIME_REVISION = "20261112.1";
globalThis.RPG_REACTOR_RUNTIME_REVISION = "20261113.1";
//=============================================================================

const scriptUrls = [
Expand Down
21 changes: 20 additions & 1 deletion template/Demo/js/reactor_core_display.js
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,26 @@ Sprite.prototype._onBitmapChange = function() {
this._bitmap.addLoadListener(this._onBitmapLoad.bind(this));
} else {
this._refreshFrame = false;
this.texture.frame = new Rectangle();
if (PIXI.TextureSource) {
// v8: assigning texture.frame refreshes neither the UVs nor the
// source, so a sprite whose bitmap is cleared kept drawing the old
// bitmap's last frame (an actor sprite left without a battler went
// on showing that actor). Go back to an empty texture of the
// sprite's own, as initialize() starts with. A texture whose frame
// is already 0x0 draws nothing (initialize() calls this with one),
// so it is kept rather than allocated twice.
const old = this.texture;
if (!old || old.frame.width > 0 || old.frame.height > 0) {
const texture = PIXICreateTexture(Sprite._emptyBaseTexture, new Rectangle());
texture.__rrSpriteOwned = true;
this.texture = texture;
if (old && old.__rrSpriteOwned && !old.destroyed) {
old.destroy();
}
}
} else {
this.texture.frame = new Rectangle();
}
}
};

Expand Down
4 changes: 2 additions & 2 deletions template/Demo/js/reactor_main.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//=============================================================================
// reactor_main.js — RPG Reactor runtime entry point
// RPG Reactor runtime version: 0.99.5
// RPG Reactor runtime revision: 20261112.1
// RPG Reactor runtime revision: 20261113.1
// The same stamp, reachable from the F12 console: which engine is this
// window actually running? Type RPG_REACTOR_RUNTIME_REVISION to see.
globalThis.RPG_REACTOR_RUNTIME_REVISION = "20261112.1";
globalThis.RPG_REACTOR_RUNTIME_REVISION = "20261113.1";
//=============================================================================

const scriptUrls = [
Expand Down
Loading