要塞分支不再攻击空中单位;莫斯提马的减速显示状态图标 (#48)
要塞分支(灰毫 chess_char_2_18、号角 chess_char_5_08,普通与精锐)按官方不能攻击空中单位:build-data 不再把 fortress 归为可对空,重新生成 data/chess.json(只改这 4 条记录的 canHitFly),DESIGN §21.29 的技能释放调整保持不变。莫斯提马天赋的减速补上可见的减速状态图标(减速本身 0.1.1 已生效)。fix #35
This commit is contained in:
1 parent
8b10625d3c
commit
052e906753
7 files changed
+41
-7
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
{"chess_char_1_01_a":{"chessId":"chess_char_1_01_a","baseId":"chess_char_1_01_a","goldenId":"chess_char_1_01_b","isGolden":false,"tier":1,"identifier":0,"isHidden":false,"isDiy":false,"visible":true,"chessType":"PRESET","shopSortId":1,"charId":"char_498_inside","name":"隐现","appellation":"Insider","rarity":5,"profession":"SNIPER","subProfessionId":"fastshot","subProfessionName":"速射手","position":"RANGED","nationId":"laterano","bonds":["lateranoShip","swiftShip"],"garrisonIds":["garrison_16_a"],"price":2,"sellPrice":1,"upgradeNum":3,"upgradeChessId":"chess_char_1_01_b","status":{"phase":1,"level":55,"skillLevel":4,"equipLevel":0},"stats":{"maxHp":1123,"atk":399,"def":140,"res":0,"cost":15,"blockCnt":1,"bat":1,"aspd":100,"respawnTime":80,"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":"3-3","rangeGrid":[[1,0],[1,1],[1,2],[1,3],[0,0],[0,1],[0,2],[0,3],[-1,0],[-1,1],[-1,2],[-1,3]],"dmgType":"phys","attackKind":"ranged","projectile":"arrow","canHitFly":true,"targetPriority":"fly","trait":{"desc":"优先攻击空中单位","descRaw":"优先攻击空中单位","bb":{},"bbStr":{},"rangeGrid":null},"skill":{"skillId":"skchr_inside_2","iconId":"skchr_inside_2","name":"“解决麻烦”","level":4,"desc":"攻击力+80%,攻击间隔略微缩短,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n攻击装有14发弹药,打完后结束(可随时停止技能)","descRaw":"攻击力<@ba.vup>+80%</>,攻击间隔<@ba.vup>略微缩短</>,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n<@ba.rem>攻击装有14发弹药,打完后结束(可随时停止技能)</>","skillType":"MANUAL","durationType":"AMMO","duration":0,"spType":"INCREASE_WITH_TIME","spCost":24,"initSp":8,"maxChargeTime":1,"increment":1,"bb":{"atk":0.8,"base_attack_time":-0.3,"attack@trigger_time":14},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skchr_inside_2","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null},"talents":[{"index":0,"name":"火力支援","desc":"在场停留20秒后,自身弹药类技能弹药上限+2","descRaw":"在场停留20秒后,自身弹药类技能弹药上限+2","bb":{"self_ammo":2,"duration":20},"bbStr":{},"rangeGrid":null,"tokenKey":null,"hidden":false,"fromModule":false}],"tokens":[],"module":{"id":"uniequip_002_inside","name":"“最初的惊喜”","type":"MAR-X","level":0,"active":false},"assets":{"avatar":"char_498_inside","portrait":"char_498_inside_1","spine":"char_498_inside","skillIcon":"skchr_inside_2","subProfIcon":"sub_fastshot_icon"},"skills":[{"skillId":"skchr_inside_1","iconId":"skchr_inside_1","name":"“不惹麻烦”","level":4,"desc":"每次攻击造成相当于攻击力185%的物理伤害\n攻击装有4发弹药,打完后结束(可随时停止技能)","descRaw":"每次攻击造成相当于攻击力<@ba.vup>185%</>的物理伤害\n<@ba.rem>攻击装有4发弹药,打完后结束(可随时停止技能)</>","skillType":"AUTO","durationType":"AMMO","duration":0,"spType":"INCREASE_WHEN_ATTACK","spCost":14,"initSp":0,"maxChargeTime":1,"increment":1,"bb":{"attack@atk_scale":1.85,"attack@trigger_time":4},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skchr_inside_1","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":0,"overrideTokenKey":null,"isDefault":false},{"skillId":"skchr_inside_2","iconId":"skchr_inside_2","name":"“解决麻烦”","level":4,"desc":"攻击力+80%,攻击间隔略微缩短,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n攻击装有14发弹药,打完后结束(可随时停止技能)","descRaw":"攻击力<@ba.vup>+80%</>,攻击间隔<@ba.vup>略微缩短</>,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n<@ba.rem>攻击装有14发弹药,打完后结束(可随时停止技能)</>","skillType":"MANUAL","durationType":"AMMO","duration":0,"spType":"INCREASE_WITH_TIME","spCost":24,"initSp":8,"maxChargeTime":1,"increment":1,"bb":{"atk":0.8,"base_attack_time":-0.3,"attack@trigger_time":14},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skchr_inside_2","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null,"isDefault":true}]},"chess_char_1_01_b":{"chessId":"chess_char_1_01_b","baseId":"chess_char_1_01_a","goldenId":"chess_char_1_01_b","isGolden":true,"tier":1,"identifier":133,"isHidden":false,"isDiy":false,"visible":true,"chessType":"PRESET","shopSortId":1,"charId":"char_498_inside","name":"隐现","appellation":"Insider","rarity":5,"profession":"SNIPER","subProfessionId":"fastshot","subProfessionName":"速射手","position":"RANGED","nationId":"laterano","bonds":["lateranoShip","swiftShip"],"garriLine truncated
|
||||
{"chess_char_1_01_a":{"chessId":"chess_char_1_01_a","baseId":"chess_char_1_01_a","goldenId":"chess_char_1_01_b","isGolden":false,"tier":1,"identifier":0,"isHidden":false,"isDiy":false,"visible":true,"chessType":"PRESET","shopSortId":1,"charId":"char_498_inside","name":"隐现","appellation":"Insider","rarity":5,"profession":"SNIPER","subProfessionId":"fastshot","subProfessionName":"速射手","position":"RANGED","nationId":"laterano","bonds":["lateranoShip","swiftShip"],"garrisonIds":["garrison_16_a"],"price":2,"sellPrice":1,"upgradeNum":3,"upgradeChessId":"chess_char_1_01_b","status":{"phase":1,"level":55,"skillLevel":4,"equipLevel":0},"stats":{"maxHp":1123,"atk":399,"def":140,"res":0,"cost":15,"blockCnt":1,"bat":1,"aspd":100,"respawnTime":80,"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":"3-3","rangeGrid":[[1,0],[1,1],[1,2],[1,3],[0,0],[0,1],[0,2],[0,3],[-1,0],[-1,1],[-1,2],[-1,3]],"dmgType":"phys","attackKind":"ranged","projectile":"arrow","canHitFly":true,"targetPriority":"fly","trait":{"desc":"优先攻击空中单位","descRaw":"优先攻击空中单位","bb":{},"bbStr":{},"rangeGrid":null},"skill":{"skillId":"skchr_inside_2","iconId":"skchr_inside_2","name":"“解决麻烦”","level":4,"desc":"攻击力+80%,攻击间隔略微缩短,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n攻击装有14发弹药,打完后结束(可随时停止技能)","descRaw":"攻击力<@ba.vup>+80%</>,攻击间隔<@ba.vup>略微缩短</>,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n<@ba.rem>攻击装有14发弹药,打完后结束(可随时停止技能)</>","skillType":"MANUAL","durationType":"AMMO","duration":0,"spType":"INCREASE_WITH_TIME","spCost":24,"initSp":8,"maxChargeTime":1,"increment":1,"bb":{"atk":0.8,"base_attack_time":-0.3,"attack@trigger_time":14},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skchr_inside_2","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null},"talents":[{"index":0,"name":"火力支援","desc":"在场停留20秒后,自身弹药类技能弹药上限+2","descRaw":"在场停留20秒后,自身弹药类技能弹药上限+2","bb":{"self_ammo":2,"duration":20},"bbStr":{},"rangeGrid":null,"tokenKey":null,"hidden":false,"fromModule":false}],"tokens":[],"module":{"id":"uniequip_002_inside","name":"“最初的惊喜”","type":"MAR-X","level":0,"active":false},"assets":{"avatar":"char_498_inside","portrait":"char_498_inside_1","spine":"char_498_inside","skillIcon":"skchr_inside_2","subProfIcon":"sub_fastshot_icon"},"skills":[{"skillId":"skchr_inside_1","iconId":"skchr_inside_1","name":"“不惹麻烦”","level":4,"desc":"每次攻击造成相当于攻击力185%的物理伤害\n攻击装有4发弹药,打完后结束(可随时停止技能)","descRaw":"每次攻击造成相当于攻击力<@ba.vup>185%</>的物理伤害\n<@ba.rem>攻击装有4发弹药,打完后结束(可随时停止技能)</>","skillType":"AUTO","durationType":"AMMO","duration":0,"spType":"INCREASE_WHEN_ATTACK","spCost":14,"initSp":0,"maxChargeTime":1,"increment":1,"bb":{"attack@atk_scale":1.85,"attack@trigger_time":4},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skchr_inside_1","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":0,"overrideTokenKey":null,"isDefault":false},{"skillId":"skchr_inside_2","iconId":"skchr_inside_2","name":"“解决麻烦”","level":4,"desc":"攻击力+80%,攻击间隔略微缩短,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n攻击装有14发弹药,打完后结束(可随时停止技能)","descRaw":"攻击力<@ba.vup>+80%</>,攻击间隔<@ba.vup>略微缩短</>,优先攻击使用远程武器的敌人并且不容易成为敌人的攻击目标\n<@ba.rem>攻击装有14发弹药,打完后结束(可随时停止技能)</>","skillType":"MANUAL","durationType":"AMMO","duration":0,"spType":"INCREASE_WITH_TIME","spCost":24,"initSp":8,"maxChargeTime":1,"increment":1,"bb":{"atk":0.8,"base_attack_time":-0.3,"attack@trigger_time":14},"bbStr":{},"rangeId":null,"rangeGrid":null,"prefabId":"skchr_inside_2","trigger":{"rule":"DEFAULT","rawRule":"DEFAULT","customRangeGrid":null},"index":1,"overrideTokenKey":null,"isDefault":true}]},"chess_char_1_01_b":{"chessId":"chess_char_1_01_b","baseId":"chess_char_1_01_a","goldenId":"chess_char_1_01_b","isGolden":true,"tier":1,"identifier":133,"isHidden":false,"isDiy":false,"visible":true,"chessType":"PRESET","shopSortId":1,"charId":"char_498_inside","name":"隐现","appellation":"Insider","rarity":5,"profession":"SNIPER","subProfessionId":"fastshot","subProfessionName":"速射手","position":"RANGED","nationId":"laterano","bonds":["lateranoShip","swiftShip"],"garriLine truncated
|
||||
+1
-1
@@ -1074,7 +1074,7 @@ table: `hitSleep` (targets and damages sleeping enemies — "可以攻击沉睡
|
||||
| bard | no attack; every second heals allies in range 10 % ATK (bb atk_to_hp_recovery_ratio) |
|
||||
| craftsman | melee phys (support devices via kit) |
|
||||
| shotprotector | ranged phys, can hit FLY, blocks 3 |
|
||||
| fortress | melee single target while blocking, ranged 1.0 splash otherwise |
|
||||
| fortress | melee single target while blocking, ranged 1.0 splash otherwise, ground only (never hits FLY) |
|
||||
| unyield / musha / reaper | cannot be healed by others; musha heals itself 50 (bb value) per hit; reaper hits every enemy in range and heals 50 × min(hits, block) |
|
||||
| centurion / crusher / pusher | hit every blocked enemy at once |
|
||||
| hammer | 50 % splash (bb atk_scale_2) to others within 1 tile |
|
||||
|
||||
@@ -386,7 +386,7 @@ const kits = {
|
||||
{ install(battle, unit) { // 主观缓时: enemies in range −15 % move speed (×talent_scale during S3)
|
||||
whileDeployed(battle, unit, AURA, () => {
|
||||
const v = num(t1.move_speed, -0.15) * (S3 && skillActive(unit) ? num(bb.talent_scale, 3) : 1);
|
||||
for (const e of enemiesOnRange(battle, unit)) pulse(battle, e, `mostma:slow:${unit.id}`, { moveMul: Math.max(0, 1 + v) });
|
||||
for (const e of enemiesOnRange(battle, unit)) pulse(battle, e, `mostma:slow:${unit.id}`, { moveMul: Math.max(0, 1 + v) }, { status: 'slow', visible: true });
|
||||
});
|
||||
} },
|
||||
],
|
||||
|
||||
@@ -350,7 +350,7 @@ export const SUB = Object.freeze({
|
||||
primprotector: P({}),
|
||||
unyield: P({ noHeal: true }),
|
||||
duelist: P({}),
|
||||
fortress: P({ fortress: true, splashRadius: 1.0, projectile: 'bomb' }),
|
||||
fortress: P({ fortress: true, splashRadius: 1.0, projectile: 'bomb', canHitFly: false, groundOnly: true }),
|
||||
// --- WARRIOR
|
||||
centurion: P({ hitAllBlocked: true }),
|
||||
crusher: P({ hitAllBlocked: true }),
|
||||
@@ -497,6 +497,8 @@ export function resolveProfile(def, kitTrait = null) {
|
||||
if (p.dmgType === 'heal' && !p.heal && !p.noAttack) p.heal = { mode: 'single' };
|
||||
if (p.dmgType !== 'heal' && p.heal) p.heal = null;
|
||||
if (p.dmgType === 'none') p.noAttack = true;
|
||||
// a 要塞 (fortress) branch throws ground-only splash: the data's generic ranged default would let it hit FLY enemies
|
||||
if (p.fortress || p.groundOnly) { p.canHitFly = false; p.groundOnly = true; }
|
||||
if (kitTrait) Object.assign(p, kitTrait);
|
||||
// a 锁定攻击范围 AoE (阵法术师, 轰击术师) strikes every enemy on its range and has no projectile: they are struck at the
|
||||
// same moment ('beam' = instant hits, drawn as a line to each victim — PRTS 作战机制 "在攻击前摇结束时选取范围内的全体
|
||||
|
||||
@@ -109,7 +109,9 @@ test('莫斯提马 S3: ripple hits every enemy in range, ATK +90 %, knock-back;
|
||||
assert.ok(h.runUntil(() => u.skill.active, 10));
|
||||
approx(u.s.atk, u.base.atk * (1 + bb.atk), 1e-6);
|
||||
h.run(0.3);
|
||||
approx(es[0].findBuff(`mostma:slow:${u.id}`).mods.moveMul, 1 + t1.move_speed * bb.talent_scale, 1e-9, 'slow ×3');
|
||||
const slowBuff = es[0].findBuff(`mostma:slow:${u.id}`);
|
||||
approx(slowBuff.mods.moveMul, 1 + t1.move_speed * bb.talent_scale, 1e-9, 'slow ×3');
|
||||
assert.equal(slowBuff.status, 'slow', 'carries the slow status so the client draws its icon');
|
||||
const x0 = es.map((e) => e.x);
|
||||
const n0 = dmgBy(h, u).length;
|
||||
h.run(u.s.interval + 0.1);
|
||||
|
||||
@@ -351,3 +351,33 @@ test('resolveProfile: data fields win over table defaults; kit trait overrides w
|
||||
const bard = resolveProfile(ds.getChess('chess_char_4_25_a'));
|
||||
assert.equal(bard.noAttack, true);
|
||||
});
|
||||
|
||||
test('fortress (号角 / 灰毫) is ground-only and never fires at FLY enemies', () => {
|
||||
const ds = getDefaultSource();
|
||||
for (const id of ['chess_char_2_18_a', 'chess_char_2_18_b', 'chess_char_5_08_a', 'chess_char_5_08_b']) {
|
||||
const p = resolveProfile(ds.getChess(id));
|
||||
assert.equal(p.canHitFly, false, `${id}: cannot hit fly`);
|
||||
assert.equal(p.groundOnly, true, `${id}: ground only`);
|
||||
}
|
||||
// the data's generic ranged default would set canHitFly (resolveProfile derives it from attackKind),
|
||||
// so this checks the branch guard rather than the generated field
|
||||
const fort = () => mk('fortress', 'TANK', {
|
||||
attackKind: 'ranged', projectile: 'bomb',
|
||||
rangeGrid: [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [-1, 0], [-1, 1], [-1, 2]],
|
||||
stats: { blockCnt: 3, maxHp: 1e6 },
|
||||
});
|
||||
const fly = makeBattle({
|
||||
defs: { chess: { t_fortress: fort() }, enemies: { enemy_fly: enemyRec({ key: 'enemy_fly', hp: 1e6, speed: 0, motion: 'FLY' }) } },
|
||||
units: [{ chessId: 't_fortress', row: 10, col: 5 }], enemies: [{ key: 'enemy_fly', pos: [10, 7] }], content: 'none', autoFinish: false,
|
||||
});
|
||||
fly.run(8);
|
||||
assert.equal(fly.unit('t_fortress').stats.attacks, 0, 'no normal attack against air only');
|
||||
assert.equal(fly.enemy('enemy_fly').hp, 1e6, 'the FLY enemy is untouched');
|
||||
const ground = makeBattle({
|
||||
defs: { chess: { t_fortress: fort() }, enemies: { enemy_g: enemyRec({ key: 'enemy_g', hp: 1e6, speed: 0 }) } },
|
||||
units: [{ chessId: 't_fortress', row: 10, col: 5 }], enemies: [{ key: 'enemy_g', pos: [10, 7] }], content: 'none', autoFinish: false,
|
||||
});
|
||||
ground.run(8);
|
||||
assert.ok(ground.unit('t_fortress').stats.attacks > 0, 'ground enemies are still attacked');
|
||||
assert.ok(ground.enemy('enemy_g').hp < 1e6, 'and take splash damage');
|
||||
});
|
||||
@@ -709,8 +709,8 @@ function classifyAttack(char, traitText) {
|
||||
if (attackKind === 'heal') projectile = 'orb';
|
||||
else if (attackKind === 'ranged') projectile = dmgType === 'arts' ? 'bolt' : 'arrow';
|
||||
|
||||
// Ranged attackers hit FLY unless the trait restricts them to ground targets (投掷手 "地面敌人").
|
||||
const canHitFly = (attackKind === 'ranged' && !/地面敌人/.test(trait)) || sub === 'skywalker';
|
||||
// Ranged attackers hit FLY unless the trait restricts them to ground targets (投掷手 "地面敌人", 要塞).
|
||||
const canHitFly = (attackKind === 'ranged' && !/地面敌人/.test(trait) && sub !== 'fortress') || sub === 'skywalker';
|
||||
let targetPriority = null;
|
||||
if (/优先攻击空中单位/.test(trait)) targetPriority = 'fly';
|
||||
else if (/防御力最低/.test(trait)) targetPriority = 'lowestDef';
|
||||
|
||||
Reference in new issue
Block a user