144 lines
7.6 KiB
JavaScript
144 lines
7.6 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const { loadMembers } = require('./new-player-ab-test-support.cjs');
|
|
const json = file => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
const popupFile = 'assets/action_bundle/script/WinStreak.ts';
|
|
const normalAtlas = json('assets/action_bundle/img/action_hammer.plist.meta');
|
|
const rainbowAtlas = json('assets/action_bundle/img/xinWinStreak/winStreak.plist.meta');
|
|
const Config = loadMembers('assets/Script/module/Config/GameConfig.ts', [
|
|
'isRainbowWinStreak', 'isOldRainbowWinStreak', 'usesRainbowWinStreakUI',
|
|
]);
|
|
|
|
class Sprite {
|
|
set spriteFrame(frame) {
|
|
this.frame = frame;
|
|
if (frame && frame.width) { this.node.width = frame.width; this.node.height = frame.height; }
|
|
}
|
|
get spriteFrame() { return this.frame; }
|
|
}
|
|
class Node {
|
|
constructor(raw = {}) {
|
|
this.name = raw._name || '';
|
|
this.active = raw._active !== false;
|
|
this.children = [];
|
|
this.components = new Map();
|
|
this.x = raw._trs ? raw._trs.array[0] : 0;
|
|
this.y = raw._trs ? raw._trs.array[1] : 0;
|
|
this.scaleX = raw._trs ? raw._trs.array[7] : 1;
|
|
this.scaleY = raw._trs ? raw._trs.array[8] : 1;
|
|
this.angle = raw._eulerAngles ? raw._eulerAngles.z : 0;
|
|
this.width = raw._contentSize ? raw._contentSize.width : 0;
|
|
this.height = raw._contentSize ? raw._contentSize.height : 0;
|
|
}
|
|
set parent(node) { this._parent = node; node.children.push(this); }
|
|
get parent() { return this._parent; }
|
|
getChildByName(name) { return this.children.find(node => node.name === name); }
|
|
getComponent(type) { return this.components.get(type); }
|
|
addComponent(type) { const value = new type(); value.node = this; this.components.set(type, value); return value; }
|
|
removeAllChildren() { this.children = []; }
|
|
convertToWorldSpaceAR(p) {
|
|
const r = this.angle * Math.PI / 180, x = p.x * this.scaleX, y = p.y * this.scaleY;
|
|
const q = { x: this.x + x * Math.cos(r) - y * Math.sin(r), y: this.y + x * Math.sin(r) + y * Math.cos(r) };
|
|
return this.parent ? this.parent.convertToWorldSpaceAR(q) : q;
|
|
}
|
|
convertToNodeSpaceAR(p) {
|
|
const q = this.parent ? this.parent.convertToNodeSpaceAR(p) : p;
|
|
const r = -this.angle * Math.PI / 180, x = q.x - this.x, y = q.y - this.y;
|
|
return { x: (x * Math.cos(r) - y * Math.sin(r)) / this.scaleX, y: (x * Math.sin(r) + y * Math.cos(r)) / this.scaleY };
|
|
}
|
|
}
|
|
function inflate(data, index = 1) {
|
|
const raw = data[index], node = new Node(raw);
|
|
for (const ref of raw._components) {
|
|
const component = data[ref.__id__];
|
|
if (component.__type__ === 'cc.Sprite') node.addComponent(Sprite).fillRange = component._fillRange;
|
|
}
|
|
for (const ref of raw._children) inflate(data, ref.__id__).parent = node;
|
|
return node;
|
|
}
|
|
function atlas(meta) {
|
|
return { _spriteFrames: Object.fromEntries(Object.entries(meta.subMetas)
|
|
.map(([name, frame]) => [name.replace(/\.png$/, ''), frame])) };
|
|
}
|
|
function fixture(group, remote) {
|
|
const config = { forceRainbowWinStreakB: false, forceOldRainbowWinStreakC: false,
|
|
GM_INFO: { abTests: { layer_two: group }, winStreak: 1,
|
|
useravatarIcon: remote ? 'https://example.test/avatar.png' : 'icon_3' },
|
|
isRainbowWinStreak: Config.isRainbowWinStreak,
|
|
isOldRainbowWinStreak: Config.isOldRainbowWinStreak,
|
|
usesRainbowWinStreakUI: Config.usesRainbowWinStreakUI };
|
|
const calls = { spine: [], remote: [] };
|
|
const cc = { Node, Sprite, v2: (x = 0, y = 0) => ({ x, y }), isValid: () => true,
|
|
SpriteFrame: class { constructor(texture) { this.texture = texture; } setTexture(texture) { this.texture = texture; } },
|
|
tween: () => ({ delay() { return this; }, to() { return this; }, start() {} }),
|
|
fx: { GameConfig: config, GameTool: {
|
|
getActionName: selected => selected,
|
|
loadSpineSimple: (node, action) => calls.spine.push({ node, action }),
|
|
} },
|
|
assetManager: { loadRemote: (url, options, callback) => calls.remote.push({ url, options, callback }) },
|
|
};
|
|
const data = json(`assets/action_bundle/prefab/${group === 1 ? 'winStreak' : 'winStreak2'}.prefab`);
|
|
const binding = data.find(record => record.fontUI);
|
|
assert.equal(binding.fontUI.__uuid__, normalAtlas.uuid);
|
|
if (group !== 1) assert.equal(binding.fontUI2.__uuid__, rainbowAtlas.uuid);
|
|
const Popup = loadMembers(popupFile, ['rainbowStars', 'starTime', 'avatarFrame', 'init',
|
|
'initRainbowMarkers', 'update', 'numberToImageNodes2', 'numberToImageNodes3'], { cc });
|
|
const popup = Object.assign(new Popup(), { node: inflate(data), fontUI: atlas(normalAtlas),
|
|
fontUI2: binding.fontUI2 ? atlas(rainbowAtlas) : null });
|
|
return { popup, config, calls };
|
|
}
|
|
function assertDigits(popup, count, meta) {
|
|
const actual = popup.node.getChildByName('number').children.map(node => node.getComponent(Sprite).spriteFrame.uuid);
|
|
assert.deepEqual(actual, String(count).split('').map(digit => meta.subMetas[`${digit}.png`].uuid));
|
|
}
|
|
|
|
for (const group of [2, 3]) for (const remote of [false, true]) {
|
|
test(`${group === 2 ? 'B' : 'C'} popup uses rainbow digit frames, current milestone and ${remote ? 'remote' : 'local'} avatar`, () => {
|
|
const { popup, config, calls } = fixture(group, remote);
|
|
const root = popup.node, avatar = root.getChildByName('avatar'), reward = root.getChildByName('reward');
|
|
const progress = root.getChildByName('progress'), hammer = root.getChildByName('hammer');
|
|
const mask = avatar.getChildByName('mask'), icon = mask.getChildByName('icon'), spine = mask.getChildByName('sp');
|
|
const originalY = [avatar.y, reward.y];
|
|
for (const value of [1, 10, 0, 11, 1]) {
|
|
config.GM_INFO.winStreak = value;
|
|
popup.init();
|
|
const count = Math.min(10, value);
|
|
assertDigits(popup, count, rainbowAtlas);
|
|
assert.equal(progress.getComponent(Sprite).fillRange, count / 10);
|
|
assert.equal(hammer.active, false);
|
|
const marker = count ? hammer.getChildByName(String(count)) : progress;
|
|
const expectedX = root.convertToNodeSpaceAR(marker.convertToWorldSpaceAR({ x: 0, y: 0 })).x;
|
|
assert.equal(avatar.x, expectedX);
|
|
assert.equal(reward.x, expectedX);
|
|
assert.deepEqual([avatar.y, reward.y], originalY);
|
|
assert.equal(icon.active, remote);
|
|
assert.equal(spine.active, !remote);
|
|
if (remote) {
|
|
const request = calls.remote[calls.remote.length - 1];
|
|
assert.equal(request.url, config.GM_INFO.useravatarIcon);
|
|
assert.equal(request.options.ext, '.png');
|
|
const texture = { count };
|
|
request.callback(null, texture);
|
|
assert.equal(icon.getComponent(Sprite).spriteFrame.texture, texture);
|
|
assert.equal(calls.spine.length, 0);
|
|
} else {
|
|
const request = calls.spine[calls.spine.length - 1];
|
|
assert.equal(request.node, spine);
|
|
assert.equal(request.action, 'icon_3');
|
|
assert.equal(calls.remote.length, 0);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
test('A popup keeps the original digit atlas and hammer initialization', () => {
|
|
const { popup, calls } = fixture(1, false);
|
|
popup.init();
|
|
assertDigits(popup, 1, normalAtlas);
|
|
assert.equal(popup.node.getChildByName('hammer').active, true);
|
|
assert.equal(popup.node.getChildByName('hammer').children[0].active, true);
|
|
assert.equal(calls.spine.length, 0);
|
|
assert.equal(calls.remote.length, 0);
|
|
});
|