736 lines
25 KiB
JavaScript
736 lines
25 KiB
JavaScript
// renderPlayers.js — Rendu des joueurs (sprites Kael ou cercles colorés)
|
||
|
||
import { Container, Graphics, Text, Assets, Sprite } from 'pixi.js';
|
||
import { CLASS_COLORS, PLAYER_HITBOX_RADIUS, KAEL_MELEE_RADIUS, KAEL_SLAM_RADIUS, KAEL_STORM_RADIUS,
|
||
DISPLACEMENT_COOLDOWNS } from './constants.js';
|
||
import { iso, getTh, getTw } from './renderer.js';
|
||
|
||
let _debugAttackRange = false;
|
||
export function setDebugAttackRange(v) { _debugAttackRange = v; }
|
||
|
||
let _debugHitboxes = false;
|
||
export function setDebugHitboxes(v) { _debugHitboxes = v; }
|
||
|
||
const BUFF_TINTS = {
|
||
invulnerable: 0xffcc44,
|
||
casting: 0x44ffff,
|
||
flying: 0x88ccff,
|
||
intangible: 0xcc88ff,
|
||
};
|
||
|
||
// Assets Kael
|
||
const _kt = {};
|
||
let _kaelReady = false;
|
||
|
||
// Assets Seris
|
||
const _st = {};
|
||
let _serisReady = false;
|
||
|
||
// Assets Aldric
|
||
const _at = {};
|
||
let _aldricReady = false;
|
||
|
||
export async function loadKaelAssets() {
|
||
if (_kaelReady) return;
|
||
const b = '../assets/sprites/kael/';
|
||
|
||
// Génère les entrées [clé, chemin] pour une animation
|
||
// prefix = 'run', 'atk', 'dash', 'sk2', 'sk3'
|
||
// dirs = tableau de directions (ex: ['south','north','east','west'])
|
||
// n = nombre de frames
|
||
// folder = dossier dans animations/
|
||
function anim(prefix, dirs, n, folder) {
|
||
const out = [];
|
||
for (const d of dirs) {
|
||
for (let i = 0; i < n; i++) {
|
||
const pad = String(i).padStart(3, '0');
|
||
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
const D4 = ['south', 'north', 'east', 'west'];
|
||
const D1 = ['south'];
|
||
|
||
const entries = [
|
||
// Rotations statiques (idle)
|
||
['south', b + 'rotations/south.png'],
|
||
['north', b + 'rotations/north.png'],
|
||
['east', b + 'rotations/east.png'],
|
||
['west', b + 'rotations/west.png'],
|
||
// Animations
|
||
...anim('run', D4, 4, 'running'), // running 4 dirs × 4 frames
|
||
...anim('atk', D4, 5, 'attack'), // attaque 4 dirs × 5 frames
|
||
...anim('dash', D4, 5, 'dash'), // dash 4 dirs × 5 frames
|
||
...anim('sk2', D1, 9, 'skill2'), // frappe lourde south × 9 frames
|
||
...anim('sk3', D1,17, 'skill3'), // tempête south × 17 frames
|
||
];
|
||
|
||
for (const [key, path] of entries) {
|
||
const tex = await Assets.load(path);
|
||
tex.source.scaleMode = 'nearest';
|
||
_kt[key] = tex;
|
||
}
|
||
_kaelReady = true;
|
||
}
|
||
|
||
export async function loadSerisAssets() {
|
||
if (_serisReady) return;
|
||
const b = '../assets/sprites/seris/';
|
||
|
||
function anim(prefix, dirs, n, folder) {
|
||
const out = [];
|
||
for (const d of dirs) {
|
||
for (let i = 0; i < n; i++) {
|
||
const pad = String(i).padStart(3, '0');
|
||
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
const D4 = ['south', 'north', 'east', 'west'];
|
||
const D1 = ['south'];
|
||
|
||
const entries = [
|
||
['s_south', b + 'rotations/south.png'],
|
||
['s_north', b + 'rotations/north.png'],
|
||
['s_east', b + 'rotations/east.png'],
|
||
['s_west', b + 'rotations/west.png'],
|
||
...anim('srun', D4, 6, 'running'), // course 4 dirs × 6 frames
|
||
...anim('satk', D4, 7, 'attack'), // attaque 4 dirs × 7 frames
|
||
...anim('stele', D1, 5, 'teleport'), // téléport south × 5 frames
|
||
...anim('ssk1', D1, 9, 'skill1'), // éventail south × 9 frames
|
||
...anim('ssk2', D1,13, 'skill2'), // vide south × 13 frames
|
||
];
|
||
|
||
for (const [key, path] of entries) {
|
||
const tex = await Assets.load(path);
|
||
tex.source.scaleMode = 'nearest';
|
||
_st[key] = tex;
|
||
}
|
||
_serisReady = true;
|
||
}
|
||
|
||
|
||
export async function loadAldricAssets() {
|
||
if (_aldricReady) return;
|
||
const b = '../assets/sprites/aldric/';
|
||
|
||
function anim(prefix, dirs, n, folder) {
|
||
const out = [];
|
||
for (const d of dirs) {
|
||
for (let i = 0; i < n; i++) {
|
||
const pad = String(i).padStart(3, '0');
|
||
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
const D4 = ['south', 'north', 'east', 'west'];
|
||
const D1 = ['south'];
|
||
|
||
const entries = [
|
||
['a_south', b + 'rotations/south.png'],
|
||
['a_north', b + 'rotations/north.png'],
|
||
['a_east', b + 'rotations/east.png'],
|
||
['a_west', b + 'rotations/west.png'],
|
||
...anim('awk', D4, 6, 'walking'), // marche 4 dirs × 6 frames
|
||
...anim('aatk', D4, 7, 'attack'), // attaque 4 dirs × 7 frames
|
||
...anim('afly', D1, 9, 'fly'), // envol south × 9 frames
|
||
...anim('ask1', D1, 9, 'skill1'), // halo south × 9 frames
|
||
...anim('ask2', D1,13, 'skill2'), // vague south × 13 frames
|
||
];
|
||
|
||
for (const [key, path] of entries) {
|
||
const tex = await Assets.load(path);
|
||
tex.source.scaleMode = 'nearest';
|
||
_at[key] = tex;
|
||
}
|
||
_aldricReady = true;
|
||
}
|
||
|
||
|
||
// Constantes d'animation
|
||
const TICK_DT = 0.05; // durée d'un tick serveur (20 Hz)
|
||
|
||
const RUN_FPS = 8; const RUN_TICK = 1 / RUN_FPS;
|
||
const ATK_FPS = 14; const ATK_TICK = 1 / ATK_FPS; const ATK_FRAMES = 5;
|
||
const DASH_FPS = 14; const DASH_TICK = 1 / DASH_FPS; const DASH_FRAMES = 5;
|
||
const DASH_DUR = DASH_FRAMES / DASH_FPS; // ~0.36s — durée totale du dash visuel
|
||
|
||
const SK2_FPS = 12; const SK2_TICK = 1 / SK2_FPS; const SK2_FRAMES = 9;
|
||
const SK3_FPS = 10; const SK3_TICK = 1 / SK3_FPS; const SK3_FRAMES = 17;
|
||
|
||
// Cooldowns max des skills (pour détecter le déclenchement côté client)
|
||
// Si le CD passe de ~0 à une valeur > THRESHOLD, le skill vient d'être utilisé.
|
||
const DISP_CD_MAX = 9.0;
|
||
const SK2_CD_MAX = 7.0; // kael slam (ability_1)
|
||
const SK3_CD_MAX = 12.0; // kael tempête (ability_2)
|
||
|
||
// Seris animation frame counts + FPS
|
||
const S_RUN_FRAMES = 6;
|
||
const S_ATK_FRAMES = 7;
|
||
const S_TELE_FRAMES = 5;
|
||
const S_SK1_FRAMES = 9;
|
||
const S_SK2_FRAMES = 13;
|
||
const S_RUN_FPS = 10; const S_RUN_TICK = 1 / S_RUN_FPS;
|
||
const S_ATK_FPS = 14; const S_ATK_TICK = 1 / S_ATK_FPS;
|
||
const S_TELE_FPS = 14; const S_TELE_TICK = 1 / S_TELE_FPS;
|
||
const S_SK1_FPS = 12; const S_SK1_TICK = 1 / S_SK1_FPS;
|
||
const S_SK2_FPS = 10; const S_SK2_TICK = 1 / S_SK2_FPS;
|
||
|
||
// Cooldowns Seris pour détection côté client
|
||
const S_DISP_CD_MAX = 4.0; // téléport
|
||
const S_SK1_CD_MAX = 6.0; // éventail
|
||
const S_SK2_CD_MAX = 10.0; // vide
|
||
|
||
// Aldric animation frame counts + FPS
|
||
const A_WK_FRAMES = 6;
|
||
const A_ATK_FRAMES = 7;
|
||
const A_FLY_FRAMES = 9;
|
||
const A_SK1_FRAMES = 9;
|
||
const A_SK2_FRAMES = 13;
|
||
const A_WK_FPS = 8; const A_WK_TICK = 1 / A_WK_FPS;
|
||
const A_ATK_FPS = 12; const A_ATK_TICK = 1 / A_ATK_FPS;
|
||
const A_FLY_FPS = 12; const A_FLY_TICK = 1 / A_FLY_FPS;
|
||
const A_SK1_FPS = 10; const A_SK1_TICK = 1 / A_SK1_FPS;
|
||
const A_SK2_FPS = 10; const A_SK2_TICK = 1 / A_SK2_FPS;
|
||
|
||
// Cooldowns Aldric pour détection côté client
|
||
const A_DISP_CD_MAX = 14.0; // envol
|
||
const A_SK1_CD_MAX = 8.0; // halo
|
||
const A_SK2_CD_MAX = 15.0; // vague
|
||
|
||
// État d'animation par joueur
|
||
const _prevPos = {}; // position précédente (pour détecter direction et dash)
|
||
const _animState = {}; // état d'animation complet
|
||
const _prevAtkCd = {}; // cooldown attaque au tick précédent
|
||
const _prevDispCd = {}; // cooldown displacement au tick précédent
|
||
const _prevAb2Cd = {}; // cooldown ability_2 au tick précédent
|
||
const _prevAb3Cd = {}; // cooldown ability_3 au tick précédent
|
||
const _dashAnim = {}; // { fromX, fromY, toX, toY, t } — interpolation de position dash
|
||
|
||
|
||
// Mise à jour
|
||
|
||
export function updatePlayers(layer, pool, players) {
|
||
const ids = new Set(players.map(p => p.id));
|
||
|
||
// Supprimer les joueurs déconnectés
|
||
for (const [id, container] of Object.entries(pool)) {
|
||
if (!ids.has(id)) {
|
||
layer.removeChild(container);
|
||
delete pool[id];
|
||
delete _prevPos[id]; delete _animState[id];
|
||
delete _prevAtkCd[id]; delete _prevDispCd[id];
|
||
delete _prevAb2Cd[id]; delete _prevAb3Cd[id];
|
||
delete _dashAnim[id];
|
||
}
|
||
}
|
||
|
||
for (const p of players) {
|
||
// Création du sprite si nouveau joueur
|
||
if (!pool[p.id]) {
|
||
pool[p.id] = _createPlayerGraphic(p);
|
||
layer.addChild(pool[p.id]);
|
||
_prevPos[p.id] = { x: p.x, y: p.y };
|
||
_animState[p.id] = {
|
||
facing: 'south',
|
||
frame: 0, timer: 0,
|
||
attacking: false, atkFrame: 0, atkTimer: 0,
|
||
dashing: false, dashDir: 'south', dashFrame: 0, dashTimer: 0,
|
||
sk2ing: false, sk2Frame: 0, sk2Timer: 0,
|
||
sk3ing: false, sk3Frame: 0, sk3Timer: 0,
|
||
// Seris
|
||
steling: false, steleFrame: 0, steleTimer: 0,
|
||
ssk1ing: false, ssk1Frame: 0, ssk1Timer: 0,
|
||
ssk2ing: false, ssk2Frame: 0, ssk2Timer: 0,
|
||
// Aldric
|
||
aflying: false, aflyFrame: 0, aflyTimer: 0,
|
||
ask1ing: false, ask1Frame: 0, ask1Timer: 0,
|
||
ask2ing: false, ask2Frame: 0, ask2Timer: 0,
|
||
};
|
||
_prevAtkCd[p.id] = 0;
|
||
_prevDispCd[p.id] = 0;
|
||
_prevAb2Cd[p.id] = 0;
|
||
_prevAb3Cd[p.id] = 0;
|
||
}
|
||
|
||
const prev = _prevPos[p.id];
|
||
const dx = p.x - prev.x;
|
||
const dy = p.y - prev.y;
|
||
const moving = Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01;
|
||
const anim = _animState[p.id];
|
||
|
||
// Détecter les déclenchements d'actions
|
||
// Attaque de base : attack CD 0 → > 0
|
||
const curAtkCd = p.cooldowns?.attack ?? 0;
|
||
if (curAtkCd > 0 && _prevAtkCd[p.id] === 0) {
|
||
anim.attacking = true; anim.atkFrame = 0; anim.atkTimer = 0;
|
||
}
|
||
_prevAtkCd[p.id] = curAtkCd;
|
||
|
||
// Displacement (E) — seuil par classe
|
||
const dispCdMax = DISPLACEMENT_COOLDOWNS[p.class] ?? DISP_CD_MAX;
|
||
const curDispCd = p.cooldowns?.displacement ?? 0;
|
||
if (curDispCd > _prevDispCd[p.id] + dispCdMax * 0.5) {
|
||
if (p.class === 'kael') {
|
||
anim.dashing = true;
|
||
anim.dashDir = anim.facing;
|
||
anim.dashFrame = 0;
|
||
anim.dashTimer = 0;
|
||
_dashAnim[p.id] = {
|
||
fromX: prev.x, fromY: prev.y,
|
||
toX: p.x, toY: p.y,
|
||
t: 0,
|
||
};
|
||
} else if (p.class === 'seris') {
|
||
anim.steling = true;
|
||
anim.steleFrame = 0;
|
||
anim.steleTimer = 0;
|
||
} else if (p.class === 'aldric') {
|
||
anim.aflying = true;
|
||
anim.aflyFrame = 0;
|
||
anim.aflyTimer = 0;
|
||
}
|
||
}
|
||
_prevDispCd[p.id] = curDispCd;
|
||
|
||
// Skill 1
|
||
const curAb2Cd = p.cooldowns?.ability_1 ?? 0;
|
||
if (p.class === 'kael') {
|
||
if (curAb2Cd > _prevAb2Cd[p.id] + SK2_CD_MAX * 0.5) {
|
||
anim.sk2ing = true; anim.sk2Frame = 0; anim.sk2Timer = 0;
|
||
}
|
||
} else if (p.class === 'seris') {
|
||
if (curAb2Cd > _prevAb2Cd[p.id] + S_SK1_CD_MAX * 0.5) {
|
||
anim.ssk1ing = true; anim.ssk1Frame = 0; anim.ssk1Timer = 0;
|
||
}
|
||
} else if (p.class === 'aldric') {
|
||
if (curAb2Cd > _prevAb2Cd[p.id] + A_SK1_CD_MAX * 0.5) {
|
||
anim.ask1ing = true; anim.ask1Frame = 0; anim.ask1Timer = 0;
|
||
}
|
||
}
|
||
_prevAb2Cd[p.id] = curAb2Cd;
|
||
|
||
// Skill 2
|
||
const curAb3Cd = p.cooldowns?.ability_2 ?? 0;
|
||
if (p.class === 'kael') {
|
||
if (curAb3Cd > _prevAb3Cd[p.id] + SK3_CD_MAX * 0.5) {
|
||
anim.sk3ing = true; anim.sk3Frame = 0; anim.sk3Timer = 0;
|
||
}
|
||
} else if (p.class === 'seris') {
|
||
if (curAb3Cd > _prevAb3Cd[p.id] + S_SK2_CD_MAX * 0.5) {
|
||
anim.ssk2ing = true; anim.ssk2Frame = 0; anim.ssk2Timer = 0;
|
||
}
|
||
} else if (p.class === 'aldric') {
|
||
if (curAb3Cd > _prevAb3Cd[p.id] + A_SK2_CD_MAX * 0.5) {
|
||
anim.ask2ing = true; anim.ask2Frame = 0; anim.ask2Timer = 0;
|
||
}
|
||
}
|
||
_prevAb3Cd[p.id] = curAb3Cd;
|
||
|
||
// Mise à jour direction depuis le mouvement
|
||
if (moving && !anim.dashing) {
|
||
anim.facing = _detectDir(dx, dy) || anim.facing;
|
||
}
|
||
|
||
// Calcul de la position à afficher
|
||
// Pendant un dash : interpoler linéairement de fromPos à toPos
|
||
let renderX = p.x, renderY = p.y;
|
||
const da = _dashAnim[p.id];
|
||
if (da) {
|
||
da.t += TICK_DT;
|
||
if (da.t >= DASH_DUR) {
|
||
delete _dashAnim[p.id]; // dash terminé
|
||
} else {
|
||
const alpha = da.t / DASH_DUR;
|
||
renderX = da.fromX + (da.toX - da.fromX) * alpha;
|
||
renderY = da.fromY + (da.toY - da.fromY) * alpha;
|
||
}
|
||
}
|
||
const pos = iso(renderX, renderY);
|
||
|
||
// Choisir la texture
|
||
if (p.class === 'kael' && _kaelReady && pool[p.id]._sprite) {
|
||
_updateKaelSprite(pool[p.id]._sprite, anim, moving);
|
||
} else if (p.class === 'seris' && _serisReady && pool[p.id]._sprite) {
|
||
_updateSerisSprite(pool[p.id]._sprite, anim, moving);
|
||
} else if (p.class === 'aldric' && _aldricReady && pool[p.id]._sprite) {
|
||
_updateAldricSprite(pool[p.id]._sprite, anim, moving);
|
||
}
|
||
|
||
// Debug : zones d'attaque
|
||
if (p.class === 'kael' && pool[p.id]._rangeGraphic) {
|
||
const rg = pool[p.id]._rangeGraphic;
|
||
rg.clear();
|
||
if (_debugAttackRange) {
|
||
const tw = getTw();
|
||
const th2 = getTh();
|
||
if (anim.attacking) {
|
||
// Attaque de base — demi-cercle (on affiche un cercle complet pour simplifier)
|
||
const rx = KAEL_MELEE_RADIUS * tw / 2;
|
||
const ry = KAEL_MELEE_RADIUS * th2 / 2;
|
||
rg.ellipse(0, 0, rx, ry).fill({ color: 0xff4444, alpha: 0.18 });
|
||
rg.ellipse(0, 0, rx, ry).stroke({ color: 0xff6666, width: 1.5, alpha: 0.7 });
|
||
}
|
||
if (anim.sk2ing) {
|
||
// Skill 2 — slam cercle complet (orange)
|
||
const rx = KAEL_SLAM_RADIUS * tw / 2;
|
||
const ry = KAEL_SLAM_RADIUS * th2 / 2;
|
||
rg.ellipse(0, 0, rx, ry).fill({ color: 0xff8800, alpha: 0.18 });
|
||
rg.ellipse(0, 0, rx, ry).stroke({ color: 0xffaa44, width: 1.5, alpha: 0.8 });
|
||
}
|
||
if (anim.sk3ing) {
|
||
// Skill 3 — tempête cercle large (violet)
|
||
const rx = KAEL_STORM_RADIUS * tw / 2;
|
||
const ry = KAEL_STORM_RADIUS * th2 / 2;
|
||
rg.ellipse(0, 0, rx, ry).fill({ color: 0xaa44ff, alpha: 0.15 });
|
||
rg.ellipse(0, 0, rx, ry).stroke({ color: 0xcc88ff, width: 2, alpha: 0.8 });
|
||
}
|
||
}
|
||
}
|
||
|
||
// Debug : hitbox de collision du joueur
|
||
if (pool[p.id]._hboxG) {
|
||
const hg = pool[p.id]._hboxG;
|
||
hg.clear();
|
||
if (_debugHitboxes) {
|
||
const rx = PLAYER_HITBOX_RADIUS * getTw() / 2;
|
||
const ry = PLAYER_HITBOX_RADIUS * getTh() / 2;
|
||
hg.ellipse(0, 0, rx, ry).stroke({ color: 0x00ffff, width: 1.5, alpha: 0.9 });
|
||
}
|
||
}
|
||
|
||
_prevPos[p.id] = { x: p.x, y: p.y };
|
||
|
||
// Teinte selon buff
|
||
let tint = 0xffffff;
|
||
let alpha = 1.0;
|
||
for (const b of (p.buffs ?? [])) {
|
||
if (BUFF_TINTS[b.type]) { tint = BUFF_TINTS[b.type]; break; }
|
||
}
|
||
if (p.buffs?.some(b => b.type === 'intangible')) alpha = 0.5;
|
||
|
||
pool[p.id].x = pos.x;
|
||
pool[p.id].y = pos.y;
|
||
pool[p.id].tint = tint;
|
||
pool[p.id].alpha = p.alive ? alpha : 0.2;
|
||
}
|
||
}
|
||
|
||
|
||
// Logique de sprite Kael
|
||
|
||
function _updateKaelSprite(sprite, anim, moving) {
|
||
// Priorité : dash > tempête (sk3) > frappe lourde (sk2) > attaque > run > idle
|
||
|
||
if (anim.dashing) {
|
||
anim.dashTimer += TICK_DT;
|
||
if (anim.dashTimer >= DASH_TICK) {
|
||
anim.dashTimer -= DASH_TICK;
|
||
anim.dashFrame++;
|
||
}
|
||
if (anim.dashFrame >= DASH_FRAMES) {
|
||
anim.dashing = false;
|
||
} else {
|
||
sprite.texture = _kt[`dash_${anim.dashDir}_${anim.dashFrame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.sk3ing) {
|
||
anim.sk3Timer += TICK_DT;
|
||
if (anim.sk3Timer >= SK3_TICK) {
|
||
anim.sk3Timer -= SK3_TICK;
|
||
anim.sk3Frame++;
|
||
}
|
||
if (anim.sk3Frame >= SK3_FRAMES) {
|
||
anim.sk3ing = false;
|
||
} else {
|
||
sprite.texture = _kt[`sk3_south_${anim.sk3Frame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.sk2ing) {
|
||
anim.sk2Timer += TICK_DT;
|
||
if (anim.sk2Timer >= SK2_TICK) {
|
||
anim.sk2Timer -= SK2_TICK;
|
||
anim.sk2Frame++;
|
||
}
|
||
if (anim.sk2Frame >= SK2_FRAMES) {
|
||
anim.sk2ing = false;
|
||
} else {
|
||
sprite.texture = _kt[`sk2_south_${anim.sk2Frame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.attacking) {
|
||
anim.atkTimer += TICK_DT;
|
||
if (anim.atkTimer >= ATK_TICK) {
|
||
anim.atkTimer -= ATK_TICK;
|
||
anim.atkFrame++;
|
||
}
|
||
if (anim.atkFrame >= ATK_FRAMES) {
|
||
anim.attacking = false;
|
||
} else {
|
||
sprite.texture = _kt[`atk_${anim.facing}_${anim.atkFrame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (moving) {
|
||
anim.timer += TICK_DT;
|
||
if (anim.timer >= RUN_TICK) {
|
||
anim.timer -= RUN_TICK;
|
||
anim.frame = (anim.frame + 1) % 4;
|
||
}
|
||
sprite.texture = _kt[`run_${anim.facing}_${anim.frame}`];
|
||
} else {
|
||
anim.frame = 0;
|
||
anim.timer = 0;
|
||
sprite.texture = _kt[anim.facing];
|
||
}
|
||
}
|
||
|
||
|
||
// Logique de sprite Seris
|
||
|
||
function _updateSerisSprite(sprite, anim, moving) {
|
||
// Priorité : téléport > vide (sk2) > éventail (sk1) > attaque > run > idle
|
||
|
||
if (anim.steling) {
|
||
anim.steleTimer += TICK_DT;
|
||
if (anim.steleTimer >= S_TELE_TICK) {
|
||
anim.steleTimer -= S_TELE_TICK;
|
||
anim.steleFrame++;
|
||
}
|
||
if (anim.steleFrame >= S_TELE_FRAMES) {
|
||
anim.steling = false;
|
||
} else {
|
||
sprite.texture = _st[`stele_south_${anim.steleFrame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.ssk2ing) {
|
||
anim.ssk2Timer += TICK_DT;
|
||
if (anim.ssk2Timer >= S_SK2_TICK) {
|
||
anim.ssk2Timer -= S_SK2_TICK;
|
||
anim.ssk2Frame++;
|
||
}
|
||
if (anim.ssk2Frame >= S_SK2_FRAMES) {
|
||
anim.ssk2ing = false;
|
||
} else {
|
||
sprite.texture = _st[`ssk2_south_${anim.ssk2Frame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.ssk1ing) {
|
||
anim.ssk1Timer += TICK_DT;
|
||
if (anim.ssk1Timer >= S_SK1_TICK) {
|
||
anim.ssk1Timer -= S_SK1_TICK;
|
||
anim.ssk1Frame++;
|
||
}
|
||
if (anim.ssk1Frame >= S_SK1_FRAMES) {
|
||
anim.ssk1ing = false;
|
||
} else {
|
||
sprite.texture = _st[`ssk1_south_${anim.ssk1Frame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.attacking) {
|
||
anim.atkTimer += TICK_DT;
|
||
if (anim.atkTimer >= S_ATK_TICK) {
|
||
anim.atkTimer -= S_ATK_TICK;
|
||
anim.atkFrame++;
|
||
}
|
||
if (anim.atkFrame >= S_ATK_FRAMES) {
|
||
anim.attacking = false;
|
||
} else {
|
||
sprite.texture = _st[`satk_${anim.facing}_${anim.atkFrame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (moving) {
|
||
anim.timer += TICK_DT;
|
||
if (anim.timer >= S_RUN_TICK) {
|
||
anim.timer -= S_RUN_TICK;
|
||
anim.frame = (anim.frame + 1) % S_RUN_FRAMES;
|
||
}
|
||
sprite.texture = _st[`srun_${anim.facing}_${anim.frame}`];
|
||
} else {
|
||
anim.frame = 0;
|
||
anim.timer = 0;
|
||
sprite.texture = _st[`s_${anim.facing}`];
|
||
}
|
||
}
|
||
|
||
|
||
// Logique de sprite Aldric
|
||
|
||
function _updateAldricSprite(sprite, anim, moving) {
|
||
// Priorité : envol > vague (sk2) > halo (sk1) > attaque > marche > idle
|
||
|
||
if (anim.aflying) {
|
||
anim.aflyTimer += TICK_DT;
|
||
if (anim.aflyTimer >= A_FLY_TICK) {
|
||
anim.aflyTimer -= A_FLY_TICK;
|
||
anim.aflyFrame++;
|
||
}
|
||
if (anim.aflyFrame >= A_FLY_FRAMES) {
|
||
anim.aflying = false;
|
||
} else {
|
||
sprite.texture = _at[`afly_south_${anim.aflyFrame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.ask2ing) {
|
||
anim.ask2Timer += TICK_DT;
|
||
if (anim.ask2Timer >= A_SK2_TICK) {
|
||
anim.ask2Timer -= A_SK2_TICK;
|
||
anim.ask2Frame++;
|
||
}
|
||
if (anim.ask2Frame >= A_SK2_FRAMES) {
|
||
anim.ask2ing = false;
|
||
} else {
|
||
sprite.texture = _at[`ask2_south_${anim.ask2Frame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.ask1ing) {
|
||
anim.ask1Timer += TICK_DT;
|
||
if (anim.ask1Timer >= A_SK1_TICK) {
|
||
anim.ask1Timer -= A_SK1_TICK;
|
||
anim.ask1Frame++;
|
||
}
|
||
if (anim.ask1Frame >= A_SK1_FRAMES) {
|
||
anim.ask1ing = false;
|
||
} else {
|
||
sprite.texture = _at[`ask1_south_${anim.ask1Frame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (anim.attacking) {
|
||
anim.atkTimer += TICK_DT;
|
||
if (anim.atkTimer >= A_ATK_TICK) {
|
||
anim.atkTimer -= A_ATK_TICK;
|
||
anim.atkFrame++;
|
||
}
|
||
if (anim.atkFrame >= A_ATK_FRAMES) {
|
||
anim.attacking = false;
|
||
} else {
|
||
sprite.texture = _at[`aatk_${anim.facing}_${anim.atkFrame}`];
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (moving) {
|
||
anim.timer += TICK_DT;
|
||
if (anim.timer >= A_WK_TICK) {
|
||
anim.timer -= A_WK_TICK;
|
||
anim.frame = (anim.frame + 1) % A_WK_FRAMES;
|
||
}
|
||
sprite.texture = _at[`awk_${anim.facing}_${anim.frame}`];
|
||
} else {
|
||
anim.frame = 0;
|
||
anim.timer = 0;
|
||
sprite.texture = _at[`a_${anim.facing}`];
|
||
}
|
||
}
|
||
|
||
|
||
// Helpers
|
||
|
||
// Renvoie la direction dominante ('south'|'north'|'east'|'west') ou null si stationnaire
|
||
function _detectDir(dx, dy) {
|
||
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01) return null;
|
||
if (Math.abs(dx) >= Math.abs(dy)) return dx > 0 ? 'east' : 'west';
|
||
return dy > 0 ? 'south' : 'north';
|
||
}
|
||
|
||
|
||
// Création du sprite
|
||
|
||
function _createPlayerGraphic(player) {
|
||
const th = getTh();
|
||
const container = new Container();
|
||
const r = Math.max(10, Math.min(40, th * 0.4));
|
||
|
||
const hboxG = new Graphics();
|
||
container._hboxG = hboxG;
|
||
|
||
if (player.class === 'kael' && _kaelReady) {
|
||
const shadow = new Graphics();
|
||
shadow.ellipse(0, r * 0.3, r * 0.5, r * 0.13).fill({ color: 0x000000, alpha: 0.3 });
|
||
|
||
const scale = (th * 1.2) / 48;
|
||
const sprite = new Sprite(_kt.south);
|
||
sprite.anchor.set(0.5, 0.75);
|
||
sprite.scale.set(scale);
|
||
container._sprite = sprite;
|
||
|
||
const rangeG = new Graphics();
|
||
container._rangeGraphic = rangeG;
|
||
|
||
container.addChild(rangeG, shadow, sprite, hboxG);
|
||
|
||
} else if (player.class === 'seris' && _serisReady) {
|
||
const shadow = new Graphics();
|
||
shadow.ellipse(0, r * 0.3, r * 0.5, r * 0.13).fill({ color: 0x000000, alpha: 0.3 });
|
||
|
||
const scale = (th * 1.2) / 48;
|
||
const sprite = new Sprite(_st.s_south);
|
||
sprite.anchor.set(0.5, 0.75);
|
||
sprite.scale.set(scale);
|
||
container._sprite = sprite;
|
||
|
||
container.addChild(shadow, sprite, hboxG);
|
||
|
||
} else if (player.class === 'aldric' && _aldricReady) {
|
||
const shadow = new Graphics();
|
||
shadow.ellipse(0, r * 0.3, r * 0.5, r * 0.13).fill({ color: 0x000000, alpha: 0.3 });
|
||
|
||
const scale = (th * 1.2) / 48;
|
||
const sprite = new Sprite(_at.a_south);
|
||
sprite.anchor.set(0.5, 0.75);
|
||
sprite.scale.set(scale);
|
||
container._sprite = sprite;
|
||
|
||
container.addChild(shadow, sprite, hboxG);
|
||
|
||
} else {
|
||
const color = CLASS_COLORS[player.class] ?? 0xffffff;
|
||
const body = new Graphics();
|
||
body.ellipse(0, r * 0.5, r * 0.6, r * 0.15).fill({ color: 0x000000, alpha: 0.25 });
|
||
body.circle(0, 0, r).fill({ color });
|
||
body.circle(0, 0, r).stroke({ color: 0xffffff, width: 1.5, alpha: 0.3 });
|
||
|
||
const label = new Text({
|
||
text: player.username,
|
||
style: {
|
||
fontSize: Math.max(9, Math.min(14, th * 0.15)),
|
||
fill: 0xdddddd,
|
||
fontFamily: 'Courier New',
|
||
},
|
||
});
|
||
label.anchor.set(0.5, 1);
|
||
label.y = -(r * 1.05 + 8);
|
||
|
||
container.addChild(body, label, hboxG);
|
||
}
|
||
|
||
return container;
|
||
}
|