fix(player): порт — анимация атаки NPC (setAttacking, R15 attack), scene.setVisible по ref

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
min 2026-06-05 20:26:26 +03:00
parent 53f9f3be00
commit 88f4307308
4 changed files with 34 additions and 3 deletions

View File

@ -1612,6 +1612,11 @@ export class GameRuntime {
}); });
return; return;
} }
if (cmd === 'npc.setAttacking') {
this._npcCmd(payload?.ref, (nid) =>
this.scene3d?.npcManager?.setAttacking?.(nid, !!payload?.on));
return;
}
if (cmd === 'npc.stop') { if (cmd === 'npc.stop') {
this._npcCmd(payload?.ref, (nid) => this._npcCmd(payload?.ref, (nid) =>
this.scene3d?.npcManager?.stopNpc(nid)); this.scene3d?.npcManager?.stopNpc(nid));
@ -2933,8 +2938,12 @@ export class GameRuntime {
} }
if (cmd === 'scene.setVisible') { if (cmd === 'scene.setVisible') {
try { try {
const kind = payload?.kind; let kind = payload?.kind;
const id = payload?.id; let id = payload?.id;
if ((kind == null || id == null) && typeof payload?.ref === 'string') {
const colon = payload.ref.indexOf(':');
if (colon > 0) { kind = payload.ref.slice(0, colon); id = payload.ref.slice(colon + 1); }
}
const visible = !!payload?.visible; const visible = !!payload?.visible;
if (id == null) return; if (id == null) return;
if (kind === 'primitive') { if (kind === 'primitive') {

View File

@ -275,6 +275,12 @@ export class NpcManager {
npc.isMoving = false; npc.isMoving = false;
} }
/** Включить/выключить анимацию атаки. */
setAttacking(id, on) {
const npc = this.npcs.get(Number(id));
if (npc) npc.attacking = !!on;
}
/** Реплика над головой NPC на duration секунд. */ /** Реплика над головой NPC на duration секунд. */
say(id, text, duration = 3) { say(id, text, duration = 3) {
const npc = this.npcs.get(Number(id)); const npc = this.npcs.get(Number(id));
@ -406,7 +412,7 @@ export class NpcManager {
// R15-NPC (skin_*): процедурная анимация бега/покоя через R15Animator. // R15-NPC (skin_*): процедурная анимация бега/покоя через R15Animator.
if (npc.r15Animator) { if (npc.r15Animator) {
try { try {
npc.r15Animator.setState(moving ? 'run' : 'idle'); npc.r15Animator.setState(npc.attacking ? 'attack' : (moving ? 'run' : 'idle'));
npc.r15Animator.update(dt); npc.r15Animator.update(dt);
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
} }

View File

@ -131,6 +131,18 @@ const ANIMS_STD = {
{ bone: B.SPINE, axis: AXIS_RIGHT, angleDeg: 10, { bone: B.SPINE, axis: AXIS_RIGHT, angleDeg: 10,
times: [0.0, 0.4, 0.8], values: [0.0, 1.0, 0.0] }, times: [0.0, 0.4, 0.8], values: [0.0, 1.0, 0.0] },
]), ]),
attack: makeAnim(0.5, true, [
{ bone: B.RIGHT_ARM, axis: AXIS_RIGHT, angleDeg: -95,
times: [0.0, 0.15, 0.3, 0.5], values: [0.3, 1.0, 0.2, 0.3] },
{ bone: B.RIGHT_FOREARM, axis: AXIS_RIGHT, angleDeg: -50,
times: [0.0, 0.15, 0.3, 0.5], values: [1.0, 0.2, 1.0, 1.0] },
{ bone: B.LEFT_ARM, axis: AXIS_RIGHT, angleDeg: -45,
times: [0.0, 0.5], values: [1.0, 1.0] },
{ bone: B.LEFT_FOREARM, axis: AXIS_RIGHT, angleDeg: -70,
times: [0.0, 0.5], values: [1.0, 1.0] },
{ bone: B.SPINE, axis: AXIS_RIGHT, angleDeg: -12,
times: [0.0, 0.15, 0.3, 0.5], values: [0.0, 1.0, 0.3, 0.0] },
]),
// === ЭМОЦИИ (game.player.playAnimation) === // === ЭМОЦИИ (game.player.playAnimation) ===
// Разовые анимации поверх авто-состояния. loop=false — играют один раз, // Разовые анимации поверх авто-состояния. loop=false — играют один раз,

View File

@ -219,6 +219,10 @@ function _makeNpcProxy(ref) {
damage(amount) { damage(amount) {
_send('npc.damage', { ref, amount: Number(amount) || 0 }); _send('npc.damage', { ref, amount: Number(amount) || 0 });
}, },
/** Включить/выключить анимацию атаки. */
setAttacking(on) {
_send('npc.setAttacking', { ref, on: !!on });
},
/** Убрать NPC со сцены. */ /** Убрать NPC со сцены. */
remove() { remove() {
_send('npc.remove', { ref }); _send('npc.remove', { ref });