feedback1 integrate: ticker lines by broadcast priority; AI 托管 keeps a human's items out of temp
- the strip queues by the official broadcast priority (BOSS_HIT 30 > CHAR_DAMAGE 20 > SHOP_LEVEL 11 > ...), match-flow notices at 25, never cutting the line on screen short - on a human seat under AI 托管 an item left in temp with a full hand gets a hand slot (a bench operator sold)
This commit is contained in:
1 parent
75e2f02590
commit
0a15c6e41d
7 files changed
+99
-18
No files matched your search
+9
-2
@@ -207,7 +207,9 @@ lineup when a bench single exists), 拟态物质 on a pair, 博士投影 (both q
|
|||||||
normal operator, 突变细胞 on the least valuable normal operator below 6阶 (deployed or benched — never an elite, never one
|
normal operator, 突变细胞 on the least valuable normal operator below 6阶 (deployed or benched — never an elite, never one
|
||||||
of a merge pair, never one already carrying a cell; it comes back after every transformation, and a cell left in temp
|
of a merge pair, never one already carrying a cell; it comes back after every transformation, and a cell left in temp
|
||||||
gets a hand slot made for it, `makeHandRoom` — on a bot's own seat by destroying the cheapest other hand item if no
|
gets a hand slot made for it, `makeHandRoom` — on a bot's own seat by destroying the cheapest other hand item if no
|
||||||
chess can be sold, never a human's item under AI 托管), 身份牌 / 通讯机 / 寻呼模块 on a focus member; 画卷 copies the most
|
chess can be sold, never a human's item under AI 托管 — on such a seat any item left in temp with a full hand gets a
|
||||||
|
hand slot the same way, a bench operator sold for it; only a hand of nothing but items still drops it, as the temp
|
||||||
|
deadline would), 身份牌 / 通讯机 / 寻呼模块 on a focus member; 画卷 copies the most
|
||||||
valuable deployed operator; 教鞭 / “神秘顾客” are used after a perfect battle and otherwise kept in the hand — a bot drops
|
valuable deployed operator; 教鞭 / “神秘顾客” are used after a perfect battle and otherwise kept in the hand — a bot drops
|
||||||
a kept one only when it needs the hand slot (“神秘顾客” then pays its fund), a human's seat under AI 托管 never. 机变: a
|
a kept one only when it needs the hand slot (“神秘顾客” then pays its fund), a human's seat under AI 托管 never. 机变: a
|
||||||
bounty card is scored by its expected payout minus its expected leaks × the value of an LP (2 + 20 / LP), the kill
|
bounty card is scored by its expected payout minus its expected leaks × the value of an LP (2 + 20 / LP), the kill
|
||||||
@@ -671,7 +673,12 @@ current leader per player, each threshold once per boss round: the player's dama
|
|||||||
the result's 领袖伤害, adds both up; the browser's strip, which plays its queue 5.2 s per line, drops a BOSS_HIT line
|
the result's 领袖伤害, adds both up; the browser's strip, which plays its queue 5.2 s per line, drops a BOSS_HIT line
|
||||||
once the round it came in is over, and a player's newer BOSS_HIT line of the round replaces their older one, queued or
|
once the round it came in is over, and a player's newer BOSS_HIT line of the round replaces their older one, queued or
|
||||||
on screen — public/js/ui/ticker.js `tickerLineLive`, `tickerSupersedes`), CHAR_GIFT (to the receiver only), plus
|
on screen — public/js/ui/ticker.js `tickerLineLive`, `tickerSupersedes`), CHAR_GIFT (to the receiver only), plus
|
||||||
CUSTOM texts (eliminations, 联防, hidden core).
|
CUSTOM texts (eliminations, 联防, hidden core — the match-flow notices at `FLOW_TICKER_PRIORITY` 25 [ASSUMED], content
|
||||||
|
`ticker()` lines at 0). The strip queues by `priority`, highest first and first in first out among equals (research 06
|
||||||
|
§9.2 "The highest priority wins": BOSS_HIT 30 > CHAR_DAMAGE 20 > SHOP_LEVEL 11 > GOLDEN_CHAR 2 > CHAR_GIFT 1;
|
||||||
|
`enqueueTickerLines`); the line on screen is never cut short, and past 4 queued lines the lowest priority's oldest goes.
|
||||||
|
Until 0.1.1 the queue was first in, first out: the boss prep's shop-level lines held a Final Assault milestone until its
|
||||||
|
round was over. The official 1 s `broadcastBeginDelay` is not modelled.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -221,7 +221,9 @@ function wireNet() {
|
|||||||
// same player's next one (ui/ticker.js tickerLineLive / tickerSupersedes)
|
// same player's next one (ui/ticker.js tickerLineLive / tickerSupersedes)
|
||||||
const type = typeof msg.type === 'string' ? msg.type : null;
|
const type = typeof msg.type === 'string' ? msg.type : null;
|
||||||
const playerId = typeof msg.playerId === 'string' ? msg.playerId : null;
|
const playerId = typeof msg.playerId === 'string' ? msg.playerId : null;
|
||||||
store.set((s) => ({ ticker: [...s.ticker.slice(-(TICKER_KEEP - 1)), { id: ++seq, text: msg.text, at: Date.now(), type, playerId, round: s.match?.public?.round ?? null }] }));
|
// its broadcast priority: the strip plays the highest first (ui/ticker.js enqueueTickerLines)
|
||||||
|
const priority = Number.isFinite(msg.priority) ? msg.priority : 0;
|
||||||
|
store.set((s) => ({ ticker: [...s.ticker.slice(-(TICKER_KEEP - 1)), { id: ++seq, text: msg.text, at: Date.now(), type, playerId, round: s.match?.public?.round ?? null, priority }] }));
|
||||||
});
|
});
|
||||||
net.on('m.emote', (msg) => {
|
net.on('m.emote', (msg) => {
|
||||||
store.set((s) => ({ emotes: [...s.emotes.slice(-(EMOTE_KEEP - 1)), { seq: ++seq, playerId: msg.playerId, id: msg.id, at: Date.now() }] }));
|
store.set((s) => ({ emotes: [...s.emotes.slice(-(EMOTE_KEEP - 1)), { seq: ++seq, playerId: msg.playerId, id: msg.id, at: Date.now() }] }));
|
||||||
|
|||||||
+20
-5
@@ -1,5 +1,6 @@
|
|||||||
// Broadcast ticker (m.ticker): a strip under the top bar; each line slides in from the right, stays
|
// Broadcast ticker (m.ticker): a strip under the top bar; each line slides in from the right, stays
|
||||||
// TICKER_MS and leaves; queued lines play in order (only the newest QUEUE_MAX are kept).
|
// TICKER_MS and leaves; queued lines play by their broadcast priority, first in first out among equals (QUEUE_MAX kept;
|
||||||
|
// enqueueTickerLines).
|
||||||
// A leader-damage line (BOSS_HIT "{0}博士对敌方领袖造成的伤害超过X%!") is news about the leader in play: it is dropped,
|
// A leader-damage line (BOSS_HIT "{0}博士对敌方领袖造成的伤害超过X%!") is news about the leader in play: it is dropped,
|
||||||
// queued or on screen, once the round it came in is over — the queue can lag a line by up to QUEUE_MAX × TICKER_MS, and
|
// queued or on screen, once the round it came in is over — the queue can lag a line by up to QUEUE_MAX × TICKER_MS, and
|
||||||
// a Final Assault line must never play over the Hidden Core's fresh leader (player report after 0.1.0: "隐藏boss还没打
|
// a Final Assault line must never play over the Hidden Core's fresh leader (player report after 0.1.0: "隐藏boss还没打
|
||||||
@@ -18,7 +19,7 @@ const TICKER_MS = 5200;
|
|||||||
const QUEUE_MAX = 4;
|
const QUEUE_MAX = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{ id: number, text: string, at?: number, type?: string|null, playerId?: string|null, round?: number|null }} TickerLine
|
* @typedef {{ id: number, text: string, at?: number, type?: string|null, playerId?: string|null, round?: number|null, priority?: number }} TickerLine
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,9 +44,16 @@ export function tickerSupersedes(line, old) {
|
|||||||
&& line.playerId != null && line.playerId === old.playerId && line.round === old.round;
|
&& line.playerId != null && line.playerId === old.playerId && line.round === old.round;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A line's broadcast priority (activity_table autoChessData.broadcastList `priority`, sent with m.ticker; none = 0). */
|
||||||
|
const prio = (t) => (t && typeof t.priority === 'number' && Number.isFinite(t.priority) ? t.priority : 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes `fresh` lines in: a line that supersedes the one on screen replaces it there, one that supersedes a queued line
|
* Takes `fresh` lines in: a line that supersedes the one on screen replaces it there, one that supersedes a queued line
|
||||||
* takes that line's place at the back of the queue; the rest queue behind (only the newest `max` are kept).
|
* replaces that line; every other line queues by priority — research 06 §9.2 "The highest priority wins": BOSS_HIT 30 >
|
||||||
|
* CHAR_DAMAGE 20 > SHOP_LEVEL 11 > GOLDEN_CHAR 2 > CHAR_GIFT 1, the remake's own notices between (server Match
|
||||||
|
* FLOW_TICKER_PRIORITY) or 0 — behind the queued lines of its priority or higher (first in, first out among equals). The
|
||||||
|
* line on screen is never cut short. Past `max` queued lines the lowest priority's oldest goes. Until 0.1.1 the queue
|
||||||
|
* was first in, first out, so shop-level and promotion lines held a leader-damage line until its round was over.
|
||||||
* @param {TickerLine[]} queue
|
* @param {TickerLine[]} queue
|
||||||
* @param {TickerLine|null} cur the line on screen
|
* @param {TickerLine|null} cur the line on screen
|
||||||
* @param {TickerLine[]} fresh
|
* @param {TickerLine[]} fresh
|
||||||
@@ -58,9 +66,16 @@ export function enqueueTickerLines(queue, cur, fresh, max = QUEUE_MAX) {
|
|||||||
if (!t) continue;
|
if (!t) continue;
|
||||||
if (c && tickerSupersedes(t, c)) { c = t; continue; }
|
if (c && tickerSupersedes(t, c)) { c = t; continue; }
|
||||||
q = q.filter((o) => !tickerSupersedes(t, o));
|
q = q.filter((o) => !tickerSupersedes(t, o));
|
||||||
q.push(t);
|
let at = q.length;
|
||||||
|
while (at > 0 && prio(q[at - 1]) < prio(t)) at--;
|
||||||
|
q.splice(at, 0, t);
|
||||||
}
|
}
|
||||||
return { queue: q.slice(-max), cur: c };
|
while (q.length > max) {
|
||||||
|
let low = 0;
|
||||||
|
for (let i = 1; i < q.length; i++) if (prio(q[i]) < prio(q[low])) low = i;
|
||||||
|
q.splice(low, 1);
|
||||||
|
}
|
||||||
|
return { queue: q, cur: c };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+17
-7
@@ -147,6 +147,12 @@ import { botPrepBeginSteps, botPrepEndSteps, botPickBand, botPickCard } from './
|
|||||||
const BOT_REHEARSAL_DEFAULT = 3;
|
const BOT_REHEARSAL_DEFAULT = 3;
|
||||||
/** Wall-clock ms of bot layout rehearsal per scheduler callback (real time; virtual time runs it in one go). */
|
/** Wall-clock ms of bot layout rehearsal per scheduler callback (real time; virtual time runs it in one go). */
|
||||||
const BOT_SLICE_MS = 8;
|
const BOT_SLICE_MS = 8;
|
||||||
|
/**
|
||||||
|
* Ticker priority of the remake's match-flow notices (隐秘核心已解锁, 联防阶段, a player out or gone): the official lines
|
||||||
|
* (research 06 §9.2) go BOSS_HIT 30 > CHAR_DAMAGE 20 > SHOP_LEVEL 11 > GOLDEN_CHAR 2 > CHAR_GIFT 1; ours sit under the
|
||||||
|
* leader-damage lines [ASSUMED].
|
||||||
|
*/
|
||||||
|
export const FLOW_TICKER_PRIORITY = 25;
|
||||||
const GAME_TYPES = new Set(Object.keys(C2S).filter((t) => Object.hasOwn(C2S, t) && (t.startsWith('g.') || t.startsWith('b.'))));
|
const GAME_TYPES = new Set(Object.keys(C2S).filter((t) => Object.hasOwn(C2S, t) && (t.startsWith('g.') || t.startsWith('b.'))));
|
||||||
const env = (k) => (typeof process !== 'undefined' && process.env ? process.env[k] : undefined);
|
const env = (k) => (typeof process !== 'undefined' && process.env ? process.env[k] : undefined);
|
||||||
/** Default combat mode: client-side unless SP_COMBAT=server. */
|
/** Default combat mode: client-side unless SP_COMBAT=server. */
|
||||||
@@ -506,7 +512,7 @@ export class Match {
|
|||||||
}
|
}
|
||||||
ps.lp = 0;
|
ps.lp = 0;
|
||||||
ps.eliminate(passedRound);
|
ps.eliminate(passedRound);
|
||||||
this.tickerText(`${ps.name}博士中途退出了模拟`);
|
this.tickerText(`${ps.name}博士中途退出了模拟`, FLOW_TICKER_PRIORITY);
|
||||||
if (this.bossWaves && (phase === PHASE.ROUND_START || phase === PHASE.SP_DRAFT || phase === PHASE.PREP)) {
|
if (this.bossWaves && (phase === PHASE.ROUND_START || phase === PHASE.SP_DRAFT || phase === PHASE.PREP)) {
|
||||||
// before the boss fight: pair the players left again (the prep preview shows the new partner / template); a
|
// before the boss fight: pair the players left again (the prep preview shows the new partner / template); a
|
||||||
// player moved to the other half re-checks its board there at once (recompute → deployMap, marks it private)
|
// player moved to the other half re-checks its board there at once (recompute → deployMap, marks it private)
|
||||||
@@ -690,9 +696,13 @@ export class Match {
|
|||||||
else this.broadcast(msg);
|
else this.broadcast(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
tickerText(text) {
|
/**
|
||||||
|
* A ticker line of the remake's own (type CUSTOM). `priority`: the match-flow notices (隐秘核心已解锁, 联防阶段, a player out
|
||||||
|
* or gone) take FLOW_TICKER_PRIORITY so the strip does not hold them behind shop-level lines; other lines 0.
|
||||||
|
*/
|
||||||
|
tickerText(text, priority = 0) {
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
this.broadcast({ t: 'm.ticker', text: String(text).slice(0, 200), id: null, type: 'CUSTOM', priority: 0, playerId: null });
|
this.broadcast({ t: 'm.ticker', text: String(text).slice(0, 200), id: null, type: 'CUSTOM', priority: Number(priority) || 0, playerId: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
markPublic() { this._pubDirty = true; }
|
markPublic() { this._pubDirty = true; }
|
||||||
@@ -1845,7 +1855,7 @@ export class Match {
|
|||||||
this.deadline = this.sched.instant ? 0 : this.sched.now() + Math.round((limit / this.gameSpeed) * 1000);
|
this.deadline = this.sched.instant ? 0 : this.sched.now() + Math.round((limit / this.gameSpeed) * 1000);
|
||||||
this._defaultWatch();
|
this._defaultWatch();
|
||||||
this.markPublic();
|
this.markPublic();
|
||||||
this.tickerText(`联防阶段:${plan.helpers.map((p) => p.name).join('、')} 迎战突破防线的敌人`);
|
this.tickerText(`联防阶段:${plan.helpers.map((p) => p.name).join('、')} 迎战突破防线的敌人`, FLOW_TICKER_PRIORITY);
|
||||||
this._uniteLeftKey = null;
|
this._uniteLeftKey = null;
|
||||||
this.runner = new FieldRunner(this, this.fields, {
|
this.runner = new FieldRunner(this, this.fields, {
|
||||||
onTick: (runner) => this._uniteTick(runner),
|
onTick: (runner) => this._uniteTick(runner),
|
||||||
@@ -2265,7 +2275,7 @@ export class Match {
|
|||||||
this._sendStart(ps.playerId, f, { watch: !f.players.includes(ps.playerId) });
|
this._sendStart(ps.playerId, f, { watch: !f.players.includes(ps.playerId) });
|
||||||
}
|
}
|
||||||
this.markPublic();
|
this.markPublic();
|
||||||
this.tickerText(`联防阶段:${plan.helpers.map((p) => p.name).join('、')} 迎战突破防线的敌人`);
|
this.tickerText(`联防阶段:${plan.helpers.map((p) => p.name).join('、')} 迎战突破防线的敌人`, FLOW_TICKER_PRIORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
_finishUniteClient() {
|
_finishUniteClient() {
|
||||||
@@ -2746,7 +2756,7 @@ export class Match {
|
|||||||
ps.lp = 0;
|
ps.lp = 0;
|
||||||
ps.eliminate(this.round);
|
ps.eliminate(this.round);
|
||||||
this.toast(ps, 'error', '你的目标生命值耗尽,已被淘汰');
|
this.toast(ps, 'error', '你的目标生命值耗尽,已被淘汰');
|
||||||
this.tickerText(`${ps.name}博士的目标生命值已耗尽`);
|
this.tickerText(`${ps.name}博士的目标生命值已耗尽`, FLOW_TICKER_PRIORITY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.fields = [];
|
this.fields = [];
|
||||||
@@ -3013,7 +3023,7 @@ export class Match {
|
|||||||
if (eligible) {
|
if (eligible) {
|
||||||
this.hiddenReached = true;
|
this.hiddenReached = true;
|
||||||
this.bossPool = null;
|
this.bossPool = null;
|
||||||
this.tickerText('隐秘核心已解锁');
|
this.tickerText('隐秘核心已解锁', FLOW_TICKER_PRIORITY);
|
||||||
this.startRound(this.gd.hiddenRound);
|
this.startRound(this.gd.hiddenRound);
|
||||||
} else {
|
} else {
|
||||||
this.finish({ victory, reason: victory ? 'victory' : 'defeat' });
|
this.finish({ victory, reason: victory ? 'victory' : 'defeat' });
|
||||||
|
|||||||
+3
-2
@@ -1742,8 +1742,9 @@ function resolveTemp(m, ps) {
|
|||||||
if (j >= 0) tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }));
|
if (j >= 0) tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }));
|
||||||
}
|
}
|
||||||
} else if (p.kind === 'item') {
|
} else if (p.kind === 'item') {
|
||||||
// 突变细胞 comes back after every transformation (昆图斯's strategy item): make room in the hand rather than lose it
|
// 突变细胞 comes back after every transformation (昆图斯's strategy item), and a human's item under AI 托管 is theirs:
|
||||||
if (isMutationCell(m.gd, p.id) && makeHandRoom(m, ps)) {
|
// make room in the hand (sell a bench operator) rather than lose it — only a hand of nothing but items still drops it
|
||||||
|
if ((isMutationCell(m.gd, p.id) || ps.autoplay) && makeHandRoom(m, ps)) {
|
||||||
const j = freeSlot(ps.hand);
|
const j = freeSlot(ps.hand);
|
||||||
if (j >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }))) continue;
|
if (j >= 0 && tryDo(() => ps.move(p.uid, { area: 'hand', idx: j }))) continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,6 +334,21 @@ test('#4 the transformation completes a merge on the board: the cell is returned
|
|||||||
m.dispose();
|
m.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#10 AI 托管 never destroys a human\'s item to clear temp: a bench operator is sold for its slot instead', () => {
|
||||||
|
const s = setup({ seed: 34 });
|
||||||
|
const { m, ps } = s;
|
||||||
|
ps.autoplay = true; // a human seat under AI 托管 (ps.isBot stays false)
|
||||||
|
const elites = [1, 2, 3, 4, 5, 6].flatMap((t) => plainOf(m, t)).map((id) => m.gd.goldenIdOf(id)).filter((g) => g && m.pool.left(m.gd.baseIdOf(g)) >= m.gd.goldenCopies);
|
||||||
|
while (ps.hand.some((x) => x == null)) give(m, ps, elites.shift(), 'hand');
|
||||||
|
const item = Object.values(DATA.items).find((it) => it && it.id !== CELL && !it.isGolden && it.price > 0 && /_e_a$/.test(it.id));
|
||||||
|
giveItem(m, ps, item.id, 'temp');
|
||||||
|
botPrepEnd(m, ps);
|
||||||
|
assert.equal(ownedItems(ps, item.id).length, 1, `${item.name}: kept (it used to be destroyed when the hand was full)`);
|
||||||
|
assert.ok(ps.tempEmpty, 'temp resolved');
|
||||||
|
checkInvariants(m);
|
||||||
|
m.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
test('#4 a bot never destroys the cell: left in temp with a full hand and nobody to inject, it gets a hand slot', () => {
|
test('#4 a bot never destroys the cell: left in temp with a full hand and nobody to inject, it gets a hand slot', () => {
|
||||||
const s = setup({ seed: 33 });
|
const s = setup({ seed: 33 });
|
||||||
const { m, ps } = s;
|
const { m, ps } = s;
|
||||||
|
|||||||
@@ -147,10 +147,41 @@ test('timeline: a Final Assault burst shows its top milestone before the round e
|
|||||||
assert.equal(shown.find((s) => s.text === '隐秘核心已解锁').at, 5.5, 'the Hidden Core\'s unlock line shows as its round starts');
|
assert.equal(shown.find((s) => s.text === '隐秘核心已解锁').at, 5.5, 'the Hidden Core\'s unlock line shows as its round starts');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('lines queue by broadcast priority (research 06 §9.2 "The highest priority wins"), first in first out among equals; the lowest priority\'s oldest goes past QUEUE_MAX', () => {
|
||||||
|
const P = { BOSS_HIT: 30, CHAR_DAMAGE: 20, SHOP_LEVEL: 11, GOLDEN_CHAR: 2, CHAR_GIFT: 1, CUSTOM: 0 };
|
||||||
|
const pl = (type, text, priority = P[type]) => ({ type, round: 14, text, priority });
|
||||||
|
const shop1 = pl('SHOP_LEVEL', 's1'), gold = pl('GOLDEN_CHAR', 'g'), shop2 = pl('SHOP_LEVEL', 's2');
|
||||||
|
const boss = { ...hit('A', 20, 14), priority: 30 }, dmg = pl('CHAR_DAMAGE', 'd'), flow = pl('CUSTOM', '隐秘核心已解锁', 25);
|
||||||
|
let r = enqueueTickerLines([], pl('CUSTOM', 'on screen'), [shop1, gold, shop2]);
|
||||||
|
assert.deepEqual(texts(r.queue), ['s1', 's2', 'g']);
|
||||||
|
r = enqueueTickerLines(r.queue, r.cur, [boss]);
|
||||||
|
assert.equal(r.cur.text, 'on screen', 'the line on screen is never cut short');
|
||||||
|
assert.deepEqual(texts(r.queue), [boss.text, 's1', 's2', 'g'], 'the leader-damage line jumps the queue');
|
||||||
|
r = enqueueTickerLines(r.queue, r.cur, [dmg, flow]);
|
||||||
|
assert.deepEqual(texts(r.queue), [boss.text, '隐秘核心已解锁', 'd', 's2'], 'flow notice under the leader line, then operator damage; past QUEUE_MAX the promotion line, then the older shop line go');
|
||||||
|
// the superseding rule still holds under priorities
|
||||||
|
r = enqueueTickerLines(r.queue, r.cur, [{ ...hit('A', 50, 14), priority: 30 }]);
|
||||||
|
assert.deepEqual(texts(r.queue).slice(0, 2), ['A博士对敌方领袖造成的伤害超过50%!', '隐秘核心已解锁']);
|
||||||
|
assert.equal(r.queue.filter((t) => t.type === 'BOSS_HIT').length, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('timeline with priorities: shop-level and promotion lines of the boss prep no longer hold the Final Assault\'s milestones until the round ends', () => {
|
||||||
|
const pr = (o, priority) => ({ ...o, priority });
|
||||||
|
const shown = playStrip([
|
||||||
|
{ at: 0, round: 14, lines: [pr(line('SHOP_LEVEL', null, 'B6'), 11), pr(line('SHOP_LEVEL', null, 'C6'), 11), pr(line('GOLDEN_CHAR', null, 'Bgold'), 2), pr(line('SHOP_LEVEL', null, 'D6'), 11)] },
|
||||||
|
{ at: 1, round: 14, lines: [pr(hit('A', 20), 30)] },
|
||||||
|
{ at: 4, round: 14, lines: [pr(hit('A', 50), 30)] },
|
||||||
|
{ at: 12, round: 15, lines: [pr(line('CUSTOM', null, '隐秘核心已解锁'), 25)] },
|
||||||
|
], 40);
|
||||||
|
const r14 = shown.filter((s) => s.round === 14).map((s) => s.text);
|
||||||
|
assert.deepEqual(r14, ['B6', 'A博士对敌方领袖造成的伤害超过50%!', 'C6'], `the milestone plays second (${r14.join(' | ')})`);
|
||||||
|
assert.equal(shown.find((s) => s.text === '隐秘核心已解锁').round, 15);
|
||||||
|
});
|
||||||
|
|
||||||
test('main.js stamps every ticker line with its type, player and round; the strip is built on the helpers', () => {
|
test('main.js stamps every ticker line with its type, player and round; the strip is built on the helpers', () => {
|
||||||
const main = readFileSync(new URL('../../public/js/main.js', import.meta.url), 'utf8');
|
const main = readFileSync(new URL('../../public/js/main.js', import.meta.url), 'utf8');
|
||||||
const handler = main.slice(main.indexOf("net.on('m.ticker'"), main.indexOf("net.on('m.emote'"));
|
const handler = main.slice(main.indexOf("net.on('m.ticker'"), main.indexOf("net.on('m.emote'"));
|
||||||
assert.match(handler, /type, playerId, round:\s*s\.match\?\.public\?\.round/, 'the m.ticker handler keeps type, player and round');
|
assert.match(handler, /type, playerId, round:\s*s\.match\?\.public\?\.round \?\? null, priority/, 'the m.ticker handler keeps type, player, round and priority');
|
||||||
const strip = readFileSync(new URL('../../public/js/ui/ticker.js', import.meta.url), 'utf8');
|
const strip = readFileSync(new URL('../../public/js/ui/ticker.js', import.meta.url), 'utf8');
|
||||||
const comp = strip.slice(strip.indexOf('export function Ticker('));
|
const comp = strip.slice(strip.indexOf('export function Ticker('));
|
||||||
assert.match(comp, /useStore\(\(s\) => s\.match\?\.public\?\.round/, 'the strip follows m.public.round');
|
assert.match(comp, /useStore\(\(s\) => s\.match\?\.public\?\.round/, 'the strip follows m.public.round');
|
||||||
|
|||||||
Reference in new issue
Block a user