feedback5 WE2: 望's 棋子 may be placed on any tile, as PRTS records (#22)
tools/build-data.mjs TOKEN_POSITION_CORRECTIONS (PRTS 棋子 部署位置 "全部位", 备注 "游戏内召唤物信息与实际不符(显示为仅部署在近战位)") sets the 棋子 record's position to ALL; validateAll checks every entry. Regenerated offline: one path changed, backups.tokens.token_10064_wang_stone1.position MELEE -> ALL. Her stock / 阻回 unchanged (an owner question). Golden: no scenario moved (none places 棋子).
This commit is contained in:
1 parent
99d8807328
commit
ad23320411
4 files changed
+94
-4
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
{"units":{"char_600_cpione":{"charId":"char_600_cpione","name":"预备干员-先锋","appellation":"Reserve Operator - Vanguard","rarity":4,"profession":"PIONEER","subProfessionId":"pioneer","subProfessionName":"尖兵","position":"MELEE","nationId":null,"isNotObtainable":true,"assets":{"avatar":"char_600_cpione","avatarGolden":"char_600_cpione","portrait":"char_600_cpione_1","portraitGolden":"char_600_cpione_1","spine":"char_600_cpione","subProfIcon":"sub_pioneer_icon"},"moduleNames":{},"standsIn":["chess_char_5_01_a"],"forms":{"2/1/4/0":{"status":{"phase":2,"level":1,"skillLevel":4,"equipLevel":0},"stats":{"maxHp":1225,"atk":320,"def":272,"res":0,"cost":11,"blockCnt":2,"bat":1.05,"aspd":100,"respawnTime":70,"spRecovery":1,"hpRecoveryPerSec":0,"moveSpeed":1,"tauntLevel":0,"massLevel":0,"deployLimit":1,"deckStack":0},"immunities":{"stun":false,"silence":false,"sleep":false,"frozen":false,"levitate":false},"rangeId":"1-1","rangeGrid":[[0,0],[0,1]],"dmgType":"phys","attackKind":"melee","projectile":"none","canHitFly":false,"targetPriority":null,"trait":{"desc":"能够阻挡两个敌人","descRaw":"能够阻挡两个敌人","bb":{},"bbStr":{},"rangeGrid":null},"skills":[{"skillId":"skcom_charge_cost[1]","iconId":"skcom_charge_cost[1]","name":"冲锋号令·α型","level":4,"desc":"立即获得6点部署费用","descRaw":"立即获得<@ba.vup>6</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":27,"initSp":3,"maxChargeTime":1,"increment":1,"bb":{"cost":6},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":0,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[2]","iconId":"skcom_charge_cost[2]","name":"冲锋号令·β型","level":4,"desc":"立即获得9点部署费用","descRaw":"立即获得<@ba.vup>9</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":36,"initSp":9,"maxChargeTime":1,"increment":1,"bb":{"cost":9},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[3]","iconId":"skcom_charge_cost[3]","name":"冲锋号令·γ型","level":4,"desc":"立即获得12点部署费用","descRaw":"立即获得<@ba.vup>12</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":41,"initSp":12,"maxChargeTime":1,"increment":1,"bb":{"cost":12},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":2,"overrideTokenKey":null}],"talents":[{"index":0,"name":"攻击提升","desc":"攻击力+8%","descRaw":"攻击力+8%","bb":{"atk":0.08},"bbStr":{},"rangeGrid":null,"tokenKey":null,"hidden":false,"fromModule":false}],"displayTokens":[],"tokens":[]},"2/60/7/1":{"status":{"phase":2,"level":60,"skillLevel":7,"equipLevel":1},"stats":{"maxHp":1674,"atk":380,"def":322,"res":0,"cost":11,"blockCnt":2,"bat":1.05,"aspd":100,"respawnTime":70,"spRecovery":1,"hpRecoveryPerSec":0,"moveSpeed":1,"tauntLevel":0,"massLevel":0,"deployLimit":1,"deckStack":0},"immunities":{"stun":false,"silence":false,"sleep":false,"frozen":false,"levitate":false},"rangeId":"1-1","rangeGrid":[[0,0],[0,1]],"dmgType":"phys","attackKind":"melee","projectile":"none","canHitFly":false,"targetPriority":null,"trait":{"desc":"能够阻挡两个敌人","descRaw":"能够阻挡两个敌人","bb":{},"bbStr":{},"rangeGrid":null},"skills":[{"skillId":"skcom_charge_cost[1]","iconId":"skcom_charge_cost[1]","name":"冲锋号令·α型","level":7,"desc":"立即获得6点部署费用","descRaw":"立即获得<@ba.vup>6</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":25,"initSp":6,"maxChargeTime":1,"increment":1,"bb":{"cost":6},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":0,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[2]","iconId":"skcom_charge_cost[2]","name":"冲锋号令·β型","level":7,"desc":"立即获得9点部署费用","descRaw":"立即获得<@ba.vup>9</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":33,"initSp":10,"maxChargeTime":1,"increment":1,"bb":{"cost":9},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[3]","iconId":"skcom_charge_cost[3]","name":"冲锋号令·γ型","level":7,"desc":"立即获得12点部署费用","descRaw":"立即获得<@ba.vup>12</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WILine truncated
|
||||
{"units":{"char_600_cpione":{"charId":"char_600_cpione","name":"预备干员-先锋","appellation":"Reserve Operator - Vanguard","rarity":4,"profession":"PIONEER","subProfessionId":"pioneer","subProfessionName":"尖兵","position":"MELEE","nationId":null,"isNotObtainable":true,"assets":{"avatar":"char_600_cpione","avatarGolden":"char_600_cpione","portrait":"char_600_cpione_1","portraitGolden":"char_600_cpione_1","spine":"char_600_cpione","subProfIcon":"sub_pioneer_icon"},"moduleNames":{},"standsIn":["chess_char_5_01_a"],"forms":{"2/1/4/0":{"status":{"phase":2,"level":1,"skillLevel":4,"equipLevel":0},"stats":{"maxHp":1225,"atk":320,"def":272,"res":0,"cost":11,"blockCnt":2,"bat":1.05,"aspd":100,"respawnTime":70,"spRecovery":1,"hpRecoveryPerSec":0,"moveSpeed":1,"tauntLevel":0,"massLevel":0,"deployLimit":1,"deckStack":0},"immunities":{"stun":false,"silence":false,"sleep":false,"frozen":false,"levitate":false},"rangeId":"1-1","rangeGrid":[[0,0],[0,1]],"dmgType":"phys","attackKind":"melee","projectile":"none","canHitFly":false,"targetPriority":null,"trait":{"desc":"能够阻挡两个敌人","descRaw":"能够阻挡两个敌人","bb":{},"bbStr":{},"rangeGrid":null},"skills":[{"skillId":"skcom_charge_cost[1]","iconId":"skcom_charge_cost[1]","name":"冲锋号令·α型","level":4,"desc":"立即获得6点部署费用","descRaw":"立即获得<@ba.vup>6</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":27,"initSp":3,"maxChargeTime":1,"increment":1,"bb":{"cost":6},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":0,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[2]","iconId":"skcom_charge_cost[2]","name":"冲锋号令·β型","level":4,"desc":"立即获得9点部署费用","descRaw":"立即获得<@ba.vup>9</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":36,"initSp":9,"maxChargeTime":1,"increment":1,"bb":{"cost":9},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[3]","iconId":"skcom_charge_cost[3]","name":"冲锋号令·γ型","level":4,"desc":"立即获得12点部署费用","descRaw":"立即获得<@ba.vup>12</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":41,"initSp":12,"maxChargeTime":1,"increment":1,"bb":{"cost":12},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":2,"overrideTokenKey":null}],"talents":[{"index":0,"name":"攻击提升","desc":"攻击力+8%","descRaw":"攻击力+8%","bb":{"atk":0.08},"bbStr":{},"rangeGrid":null,"tokenKey":null,"hidden":false,"fromModule":false}],"displayTokens":[],"tokens":[]},"2/60/7/1":{"status":{"phase":2,"level":60,"skillLevel":7,"equipLevel":1},"stats":{"maxHp":1674,"atk":380,"def":322,"res":0,"cost":11,"blockCnt":2,"bat":1.05,"aspd":100,"respawnTime":70,"spRecovery":1,"hpRecoveryPerSec":0,"moveSpeed":1,"tauntLevel":0,"massLevel":0,"deployLimit":1,"deckStack":0},"immunities":{"stun":false,"silence":false,"sleep":false,"frozen":false,"levitate":false},"rangeId":"1-1","rangeGrid":[[0,0],[0,1]],"dmgType":"phys","attackKind":"melee","projectile":"none","canHitFly":false,"targetPriority":null,"trait":{"desc":"能够阻挡两个敌人","descRaw":"能够阻挡两个敌人","bb":{},"bbStr":{},"rangeGrid":null},"skills":[{"skillId":"skcom_charge_cost[1]","iconId":"skcom_charge_cost[1]","name":"冲锋号令·α型","level":7,"desc":"立即获得6点部署费用","descRaw":"立即获得<@ba.vup>6</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":25,"initSp":6,"maxChargeTime":1,"increment":1,"bb":{"cost":6},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":0,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[2]","iconId":"skcom_charge_cost[2]","name":"冲锋号令·β型","level":7,"desc":"立即获得9点部署费用","descRaw":"立即获得<@ba.vup>9</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WITH_TIME","spCost":33,"initSp":10,"maxChargeTime":1,"increment":1,"bb":{"cost":9},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skcom_charge_cost","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null},{"skillId":"skcom_charge_cost[3]","iconId":"skcom_charge_cost[3]","name":"冲锋号令·γ型","level":7,"desc":"立即获得12点部署费用","descRaw":"立即获得<@ba.vup>12</>点部署费用","skillType":"AUTO","durationType":"NONE","duration":0,"spType":"INCREASE_WILine truncated
|
||||
+1
-1
@@ -480,7 +480,7 @@ Glyph legend (`rows`):
|
||||
| Field | Example (`token_10028_vigil_wolf`) | Meaning |
|
||||
|---|---|---|
|
||||
| `tokenId`, `kind`, `name`, `appellation`, `desc`, `descRaw` | …, `"summon"`, `"狼群"` | |
|
||||
| `profession`, `subProfessionId`, `position` | `"TOKEN"`, `"notchar1"`, `"MELEE"` | |
|
||||
| `profession`, `subProfessionId`, `position` | `"TOKEN"`, `"notchar1"`, `"MELEE"` | `position` = the token row's, except where PRTS records the client's row as wrong (`tools/build-data.mjs TOKEN_POSITION_CORRECTIONS`): 望's 棋子 `ALL` (PRTS 棋子 部署位置 "全部位", 备注 "游戏内召唤物信息与实际不符(显示为仅部署在近战位)") — the prep's placement class (board.js `positionClass`) |
|
||||
| `displayType`, `placeable` | `"DEFAULT"`, `true` | `shopStateTokenDict` DEFAULT / HIDDEN (battle-only) / `null` (not listed). `placeable` (a prep hand piece) = a manually deployable summon (PRTS 卫戍协议/帮助 "可手动部署的附属召唤物…加入手牌区"; user playtest #6): not HIDDEN and made by an owner's talent or skill — 医疗探机 (赫默 S2), 诅咒娃娃 (巫恋 S2), 海嗣, 狼群, 流形 and 爬行号·防护单元 (凯瑟琳's talent device — the only pool summon missing from `shopStateTokenDict`, read as shown: placed by hand in the base game, by the friend's report, and confirmed by the user after playtest #6, DESIGN §20); 投递坐标 (HIDDEN) is not. In battle a skill's summon deploys once at the battle start, then on its tile each time the skill gives one (SIM.md, token pieces) |
|
||||
| `ownerRange` | `true` | the token text reads "只能部署在召唤者攻击范围内" (`desc`; the tacticians' 援军 — 狼群, 流形; PRTS 狼群 特性): its hand piece may only stand on a tile of its owner's attack range (server/match/board.js `ownerRangeKeys`, `PlayerState._legal`, the client's `gameLogic.summonRange`; player report #9 after 0.1.0). `false` for every other token |
|
||||
| `owners[]` | `["chess_char_3_19_a","chess_char_3_19_b"]` | |
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// test/match/feedback5-we2-placement.test.js — 0.2.0 WE2: where the 自选 summon pieces may be placed in prep.
|
||||
// #22 望's 棋子 go on any tile class (PRTS 棋子 部署位置 "全部位", 备注 "游戏内召唤物信息与实际不符(显示为仅部署在近战位)").
|
||||
// Run: node --test test/match/feedback5-we2-placement.test.js
|
||||
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { ERR } from '../../shared/constants.js';
|
||||
import { FIELD, canPlace, placeClass, tileKey } from '../../server/match/board.js';
|
||||
import { makeMatch, checkInvariants } from './harness.js';
|
||||
|
||||
const T5A = 'chess_char_5_diy1_a';
|
||||
const WANG = 'char_2027_wang';
|
||||
const STONE = 'token_10064_wang_stone1';
|
||||
|
||||
/** A solo match whose human slots `picks`, in its first prep on 战场#01 (3 高台 tiles) with an empty board and hand. */
|
||||
function prep(picks, seed = 2101) {
|
||||
const seats = [{ seat: 0, playerId: 'p_0', name: 'P0', isBot: false, connected: true, diy: picks }];
|
||||
const h = makeMatch({ mode: 'solo', difficulty: 'NORMAL', seats, seed }).start();
|
||||
h.toPrep(1);
|
||||
h.setStage('act2autochess_m01');
|
||||
const ps = h.ps('p_0');
|
||||
for (const p of [...ps.board.values(), ...ps.hand.filter(Boolean)]) if (p.kind === 'chess') ps.returnCopies(p);
|
||||
ps.board.clear();
|
||||
ps.hand.fill(null);
|
||||
ps.recompute();
|
||||
ps.funds = 100;
|
||||
return { h, m: h.m, ps };
|
||||
}
|
||||
|
||||
/** Free tiles of the player's deploy map of class `cls` ('melee' | 'ranged'), reading order. */
|
||||
function tilesOf(ps, cls) {
|
||||
const map = ps.deployMap();
|
||||
const out = [];
|
||||
for (let r = FIELD.r1; r >= FIELD.r0; r--) for (let c = FIELD.c0; c <= FIELD.c1; c++) {
|
||||
if (!ps.board.has(tileKey(r, c)) && map.get(tileKey(r, c)) === cls) out.push([r, c]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Gain slot `slotId` (its 自选 operator) and place it on the first free tile of its class; returns the piece. */
|
||||
function placeDiy(m, ps, slotId, at = null) {
|
||||
const piece = ps.acquireChess(slotId, { source: 'buy' });
|
||||
assert.ok(piece, `${slotId} gained`);
|
||||
const map = ps.deployMap();
|
||||
const pos = placeClass(ps, ps.gd.chess(slotId));
|
||||
let tile = at;
|
||||
for (let r = FIELD.r1; r >= FIELD.r0 && !tile; r--) for (let c = FIELD.c0; c <= FIELD.c1 && !tile; c++) {
|
||||
if (!ps.board.has(tileKey(r, c)) && canPlace(map, pos, r, c)) tile = [r, c];
|
||||
}
|
||||
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: piece.uid, to: { area: 'board', row: tile[0], col: tile[1] } }), { ok: true }, `${slotId} placed`);
|
||||
return piece;
|
||||
}
|
||||
|
||||
test('#22 望\'s 棋子: a hand piece goes on a 高台 (ranged tile) as well as on the ground — PRTS 棋子 全部位', () => {
|
||||
const { m, ps } = prep({ [T5A]: { charId: WANG, skillIndex: 0 } });
|
||||
assert.equal(ps.gd.token(STONE).position, 'ALL');
|
||||
const wang = placeDiy(m, ps, T5A);
|
||||
const stack = ps.hand.find((p) => p && p.kind === 'token' && p.id === STONE && p.ownerUid === wang.uid);
|
||||
assert.ok(stack && stack.count === 6, 'her 6 棋子 joined the hand');
|
||||
const high = tilesOf(ps, 'ranged');
|
||||
const ground = tilesOf(ps, 'melee');
|
||||
assert.ok(high.length && ground.length, 'the stage has both tile classes');
|
||||
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: stack.uid, to: { area: 'board', row: high[0][0], col: high[0][1] } }), { ok: true }, 'onto a 高台');
|
||||
const left = ps.hand.find((p) => p && p.kind === 'token' && p.id === STONE);
|
||||
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: left.uid, to: { area: 'board', row: ground[0][0], col: ground[0][1] } }), { ok: true }, 'onto the ground');
|
||||
// still refused off the deploy map (a forbidden tile of the field)
|
||||
const map = ps.deployMap();
|
||||
let off = null;
|
||||
for (let r = FIELD.r1; r >= FIELD.r0 && !off; r--) for (let c = FIELD.c0; c <= FIELD.c1 && !off; c++) if (!map.get(tileKey(r, c)) && !ps.board.has(tileKey(r, c))) off = [r, c];
|
||||
if (off) {
|
||||
const again = ps.hand.find((p) => p && p.kind === 'token' && p.id === STONE);
|
||||
assert.deepEqual(m.handle('p_0', { t: 'g.move', uid: again.uid, to: { area: 'board', row: off[0], col: off[1] } }), { error: ERR.BAD_TILE }, 'not a deployable tile');
|
||||
}
|
||||
checkInvariants(m);
|
||||
m.dispose();
|
||||
});
|
||||
+16
-2
@@ -1483,6 +1483,15 @@ const TOKEN_ABNORMAL = Object.freeze({
|
||||
enemy_9012_acloon: ['isolated'], // “炎佑”
|
||||
});
|
||||
|
||||
/**
|
||||
* Summon deployment positions the client's token row gets wrong, from the PRTS summon pages: 望's 棋子 — PRTS 棋子
|
||||
* 部署位置 "全部位", 备注 "游戏内召唤物信息与实际不符(显示为仅部署在近战位)" (character_table: MELEE). The record's `position`
|
||||
* (the prep's placement class, board.js positionClass) takes this value.
|
||||
*/
|
||||
const TOKEN_POSITION_CORRECTIONS = Object.freeze({
|
||||
token_10064_wang_stone1: 'ALL', // 棋子
|
||||
});
|
||||
|
||||
/**
|
||||
* The tokens.json record of a summon (buildTokens; the 自选 picks' summons, buildDiyTokens) from its per-owner
|
||||
* `variants` (key → variant; `owners` = the keys, in order; the defaults are the first owner's variant).
|
||||
@@ -1502,7 +1511,7 @@ const TOKEN_ABNORMAL = Object.freeze({
|
||||
* `ownerRange`: the token text "只能部署在召唤者攻击范围内" (the tacticians' 援军 — 伺夜's 狼群, 缪尔赛思's 流形; PRTS 狼群
|
||||
* 特性): its hand piece may only be placed on a tile of its owner's attack range (server/match/board.js
|
||||
* ownerRangeKeys, PlayerState._legal; player report #9 after 0.1.0: 伺夜's tactical point could go anywhere).
|
||||
* `abnormal` = TOKEN_ABNORMAL (PRTS).
|
||||
* `abnormal` = TOKEN_ABNORMAL (PRTS); `position` = TOKEN_POSITION_CORRECTIONS (PRTS) or the token row's.
|
||||
*/
|
||||
function summonRecord(ctx, tokenId, char, variants, produced) {
|
||||
const displayType = ctx.ac.shopStateTokenDict?.[tokenId]?.tokenDisplayType || null;
|
||||
@@ -1512,7 +1521,7 @@ function summonRecord(ctx, tokenId, char, variants, produced) {
|
||||
return {
|
||||
tokenId, kind: 'summon', name: char.name, appellation: char.appellation || null,
|
||||
desc: stripRich(first.trait.desc), descRaw: first.trait.descRaw,
|
||||
profession: char.profession, subProfessionId: char.subProfessionId, position: char.position,
|
||||
profession: char.profession, subProfessionId: char.subProfessionId, position: TOKEN_POSITION_CORRECTIONS[tokenId] ?? char.position,
|
||||
displayType, placeable: displayType !== 'HIDDEN' && produced, ownerRange,
|
||||
owners,
|
||||
// Defaults = first owner's variant; per-owner data in variants[owner].
|
||||
@@ -3777,6 +3786,11 @@ function validateAll(f) {
|
||||
if (!tokens[id]) err(`TOKEN_ABNORMAL: ${id} is not a token`);
|
||||
for (const f of fl) if (f !== 'healFree' && f !== 'isolated') err(`TOKEN_ABNORMAL: ${id}: unknown effect ${f}`);
|
||||
}
|
||||
for (const [id, pos] of Object.entries(TOKEN_POSITION_CORRECTIONS)) {
|
||||
const t = tokens[id] ?? backups?.tokens?.[id];
|
||||
if (!t || t.kind !== 'summon') err(`TOKEN_POSITION_CORRECTIONS: ${id} is not a summon`);
|
||||
else if (t.position !== pos || !['MELEE', 'RANGED', 'ALL'].includes(pos)) err(`TOKEN_POSITION_CORRECTIONS: ${id}: position ${t.position}`);
|
||||
}
|
||||
for (const t of Object.values(tokens)) {
|
||||
if (!t.stats) err(`token ${t.tokenId}: no stats`);
|
||||
for (const [o, v] of Object.entries(t.variants || {})) if (!Array.isArray(v.sources) || !v.sources.length) err(`token ${t.tokenId}@${o}: no sources`);
|
||||
|
||||
Reference in new issue
Block a user