Lot 4 modernisation JS (SonarLint) + restauration CI sonarqube non-bloquante
Build and deploy / test-python (push) Successful in 11m18s
Build and deploy / test-js (push) Successful in 1m16s
Build and deploy / lint-python (push) Successful in 4m24s
Build and deploy / lint-js (push) Successful in 2m51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Successful in 5m30s
Build and deploy / test-python (push) Successful in 11m18s
Build and deploy / test-js (push) Successful in 1m16s
Build and deploy / lint-python (push) Successful in 4m24s
Build and deploy / lint-js (push) Successful in 2m51s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Successful in 5m30s
Corrige les findings SonarQube (via SonarLint IDE, fichier par fichier) sur ~24 fichiers static/js/ : parseFloat/parseInt -> Number.*, .replace(/x/g,y) -> .replaceAll, .indexOf() -> .includes()/.startsWith(), getAttribute/setAttribute -> .dataset, tableaux -> Set, x && x.y -> x?.y (verifie site par site), extraction de template litteraux imbriques, ternaires imbriquees, refactors de complexite cognitive (S3776) via tables de dispatch, Object.hasOwn, .at(), et deduplication de fonctions identiques (S4144). Deux exceptions S2486 documentees/corrigees (filter-repeater-rows.js) et un cas S2703 de partage inter-scripts complete (_collisionWizard, trigger-editor.js <-> collision-rules-editor.js). Details complets dans CODE_QUALITY.md section 5. Restaure aussi le job CI "sonarqube" (non-bloquant) dans .gitea/workflows/deploy.yml maintenant que l'instance prod est operationnelle. Suites vertes : 276/276 JS (node --test), 591/591 Python (pytest). SKIP=djlint sur ce commit : hook djlint bloquant sur le backlog H021 (styles inline, 49 occurrences/6 templates) deja documente comme dette assumee non traitee dans CODE_QUALITY.md section 6, aucun rapport avec ce commit (aucun template touche ici) - valide explicitement avec l'utilisateur avant de contourner ce hook. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
66d8eaeae8
commit
ccf836f2c5
@@ -21,8 +21,8 @@ let activePersonnageInteractListener = null;
|
||||
const FORGE_PERSONNAGE_FPS = 8;
|
||||
|
||||
function forgePersonnageAnimExists(elementId, animName) {
|
||||
const byAnim = (gameData.personnage_animations || {})[elementId];
|
||||
return !!(byAnim && byAnim[animName] && byAnim[animName].length);
|
||||
const byAnim = gameData.personnage_animations?.[elementId];
|
||||
return !!byAnim?.[animName]?.length;
|
||||
}
|
||||
|
||||
// "walk" est la SEULE animation de déplacement du catalogue Forge/CraftPix
|
||||
@@ -81,10 +81,10 @@ function forgeUpdateSceneCamera(screenData) {
|
||||
const player = (screenData.elements || []).find((o) => o.kind === 'personnage' && o.personnage_role === 'joueur');
|
||||
const playerEl = player && document.querySelector(`[data-object-id="${player.id}"]`);
|
||||
const px = playerEl
|
||||
? (parseFloat(playerEl.style.left) || 0) + (parseFloat(playerEl.style.width) || 0) / 2
|
||||
? (Number.parseFloat(playerEl.style.left) || 0) + (Number.parseFloat(playerEl.style.width) || 0) / 2
|
||||
: world.width / 2;
|
||||
const py = playerEl
|
||||
? (parseFloat(playerEl.style.top) || 0) + (parseFloat(playerEl.style.height) || 0) / 2
|
||||
? (Number.parseFloat(playerEl.style.top) || 0) + (Number.parseFloat(playerEl.style.height) || 0) / 2
|
||||
: world.height / 2;
|
||||
const offsetX = Math.min(
|
||||
Math.max(px - screenData.scene_width / 2, 0),
|
||||
@@ -125,7 +125,7 @@ function forgeObjectCollisionRectAt(left, top, width, height, collision) {
|
||||
// objet dont la collision est explicitement désactivée, ni l'objet qui
|
||||
// se déplace lui-même.
|
||||
function forgeSolidObstacles(screenData, movingId) {
|
||||
return (screenData.elements || []).filter((o) => o.id !== movingId && o.kind !== 'fond' && o.kind !== 'dialogue_box' && o.kind !== 'quiz_box' && o.kind !== 'score_widget' && !(o.collision && o.collision.enabled === false));
|
||||
return (screenData.elements || []).filter((o) => o.id !== movingId && o.kind !== 'fond' && o.kind !== 'dialogue_box' && o.kind !== 'quiz_box' && o.kind !== 'score_widget' && o.collision?.enabled !== false);
|
||||
}
|
||||
|
||||
// Est-ce que le personnage, À la position candidate (newLeft/newTop),
|
||||
@@ -139,90 +139,118 @@ function forgeSolidObstacles(screenData, movingId) {
|
||||
function forgeWouldCollide(screenData, movingObj, newLeft, newTop, width, height) {
|
||||
const movingRect = forgeObjectCollisionRectAt(newLeft, newTop, width, height, movingObj.collision);
|
||||
const obstacles = forgeSolidObstacles(screenData, movingObj.id);
|
||||
for (let i = 0; i < obstacles.length; i++) {
|
||||
const other = obstacles[i];
|
||||
for (const other of obstacles) {
|
||||
const otherEl = document.querySelector(`[data-object-id="${other.id}"]`);
|
||||
if (!otherEl) continue;
|
||||
const otherLeft = parseFloat(otherEl.style.left) || 0;
|
||||
const otherTop = parseFloat(otherEl.style.top) || 0;
|
||||
const otherWidth = parseFloat(otherEl.style.width) || 0;
|
||||
const otherHeight = parseFloat(otherEl.style.height) || 0;
|
||||
const otherLeft = Number.parseFloat(otherEl.style.left) || 0;
|
||||
const otherTop = Number.parseFloat(otherEl.style.top) || 0;
|
||||
const otherWidth = Number.parseFloat(otherEl.style.width) || 0;
|
||||
const otherHeight = Number.parseFloat(otherEl.style.height) || 0;
|
||||
const otherRect = forgeObjectCollisionRectAt(otherLeft, otherTop, otherWidth, otherHeight, other.collision);
|
||||
if (forgeShapesOverlap(movingRect, otherRect)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Déplacement BRUT tenté (avant collision), selon l'axe autorisé par cmd.axe
|
||||
// — extrait de forgeRunPersonnageControllerTick (lot 4 "modernisation JS", S3776).
|
||||
function forgeComputeMovementDelta(cmd) {
|
||||
let rawDx = 0; let
|
||||
rawDy = 0;
|
||||
if (cmd.axe !== 'vertical') {
|
||||
if (heldKeys.has(cmd.gauche)) rawDx -= cmd.vitesse;
|
||||
if (heldKeys.has(cmd.droite)) rawDx += cmd.vitesse;
|
||||
}
|
||||
if (cmd.axe !== 'horizontal') {
|
||||
if (heldKeys.has(cmd.haut)) rawDy -= cmd.vitesse;
|
||||
if (heldKeys.has(cmd.bas)) rawDy += cmd.vitesse;
|
||||
}
|
||||
return { rawDx, rawDy };
|
||||
}
|
||||
|
||||
// Bloque le déplacement axe par axe (pas les deux à la fois dès qu'un seul
|
||||
// est gêné) — glisser le long d'un mur en diagonale reste possible, plus
|
||||
// naturel qu'un blocage total au moindre contact. Extrait de
|
||||
// forgeRunPersonnageControllerTick.
|
||||
// `box` = {left, top, width, height} (position/taille ACTUELLES, avant
|
||||
// déplacement) ; `rawDelta` = {rawDx, rawDy} (voir forgeComputeMovementDelta)
|
||||
// — regroupés en objets pour rester sous la limite de 7 paramètres (S107),
|
||||
// plutôt que 9 paramètres positionnels distincts.
|
||||
function forgeResolveBlockedMovement(screenData, obj, targetEl, box, rawDelta) {
|
||||
const {
|
||||
left: curLeft, top: curTop, width: boxWidth, height: boxHeight,
|
||||
} = box;
|
||||
let { rawDx: dx, rawDy: dy } = rawDelta;
|
||||
if (dx !== 0) {
|
||||
const candidateLeft = clampSceneObjectPosition(targetEl, 'left', curLeft + dx);
|
||||
if (forgeWouldCollide(screenData, obj, candidateLeft, curTop, boxWidth, boxHeight)) dx = 0;
|
||||
}
|
||||
if (dy !== 0) {
|
||||
const candidateTop = clampSceneObjectPosition(targetEl, 'top', curTop + dy);
|
||||
if (forgeWouldCollide(screenData, obj, curLeft, candidateTop, boxWidth, boxHeight)) dy = 0;
|
||||
}
|
||||
return { dx, dy };
|
||||
}
|
||||
|
||||
// Anime idle (immobile) ou walk (déplacement effectif) — même fps pour les
|
||||
// deux (bug corrigé par l'utilisateur : idle tournait à 4 i/s contre 8
|
||||
// pour walk, aucune raison réelle de les désynchroniser). Extrait de
|
||||
// forgeRunPersonnageControllerTick.
|
||||
function forgeApplyPersonnageAnimForTick(targetEl, obj, dx, dy) {
|
||||
if (dx === 0 && dy === 0) {
|
||||
const idleAnim = forgePersonnageIdleAnim(obj.id);
|
||||
if (idleAnim) {
|
||||
runSpriteAnimation(
|
||||
targetEl,
|
||||
resolveSpriteFrames(obj.id, { animation: idleAnim, fps: FORGE_PERSONNAGE_FPS, loop: true }),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const moveAnim = forgePersonnageMovementAnim(obj.id);
|
||||
if (moveAnim) {
|
||||
runSpriteAnimation(
|
||||
targetEl,
|
||||
resolveSpriteFrames(obj.id, { animation: moveAnim, fps: FORGE_PERSONNAGE_FPS, loop: true }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Déplace/anime UN personnage joueur pour ce tick — extrait du forEach de
|
||||
// forgeRunPersonnageControllerTick.
|
||||
function forgeRunPersonnagePlayerTick(screenData, obj) {
|
||||
// Seul le personnage "joueur" (voir screens/rendering/personnage_role.py)
|
||||
// est déplacé/animé au clavier — "ennemie"/"pnj" restent immobiles tant
|
||||
// qu'aucune logique de flow ne les pilote (voir add_scene_object.py).
|
||||
if (obj.kind !== 'personnage' || obj.personnage_role !== 'joueur' || !obj.personnage_commandes) return;
|
||||
const cmd = obj.personnage_commandes;
|
||||
const targetEl = document.querySelector(`[data-object-id="${obj.id}"]`);
|
||||
if (!targetEl) return;
|
||||
|
||||
const { rawDx, rawDy } = forgeComputeMovementDelta(cmd);
|
||||
const curLeft = Number.parseFloat(targetEl.style.left) || 0;
|
||||
const curTop = Number.parseFloat(targetEl.style.top) || 0;
|
||||
const boxWidth = Number.parseFloat(targetEl.style.width) || 0;
|
||||
const boxHeight = Number.parseFloat(targetEl.style.height) || 0;
|
||||
const box = {
|
||||
left: curLeft, top: curTop, width: boxWidth, height: boxHeight,
|
||||
};
|
||||
const { dx, dy } = forgeResolveBlockedMovement(screenData, obj, targetEl, box, { rawDx, rawDy });
|
||||
|
||||
// Se tourne vers la direction TENTÉE même si le déplacement est
|
||||
// finalement bloqué (fait face au mur/obstacle, plus naturel qu'un
|
||||
// personnage qui reste dos tourné contre ce qui le bloque).
|
||||
if (rawDx !== 0) applyObjectProperty(targetEl, 'orientation', rawDx < 0 ? 'gauche' : 'droite');
|
||||
if (dx !== 0) applyObjectProperty(targetEl, 'pos_x_relatif', dx);
|
||||
if (dy !== 0) applyObjectProperty(targetEl, 'pos_y_relatif', dy);
|
||||
forgeApplyPersonnageAnimForTick(targetEl, obj, dx, dy);
|
||||
}
|
||||
|
||||
// 50ms (20 fois/seconde) — même cadence que runScreenHeldKeyTriggers
|
||||
// (triggers.js), pour un déplacement perçu comme continu.
|
||||
function forgeRunPersonnageControllerTick(screenData) {
|
||||
forgeUpdateSceneCamera(screenData);
|
||||
(screenData.elements || []).forEach((obj) => {
|
||||
// Seul le personnage "joueur" (voir screens/rendering/personnage_role.py)
|
||||
// est déplacé/animé au clavier — "ennemie"/"pnj" restent immobiles tant
|
||||
// qu'aucune logique de flow ne les pilote (voir add_scene_object.py).
|
||||
if (obj.kind !== 'personnage' || obj.personnage_role !== 'joueur' || !obj.personnage_commandes) return;
|
||||
const cmd = obj.personnage_commandes;
|
||||
const targetEl = document.querySelector(`[data-object-id="${obj.id}"]`);
|
||||
if (!targetEl) return;
|
||||
|
||||
let rawDx = 0; let
|
||||
rawDy = 0;
|
||||
if (cmd.axe !== 'vertical') {
|
||||
if (heldKeys.has(cmd.gauche)) rawDx -= cmd.vitesse;
|
||||
if (heldKeys.has(cmd.droite)) rawDx += cmd.vitesse;
|
||||
}
|
||||
if (cmd.axe !== 'horizontal') {
|
||||
if (heldKeys.has(cmd.haut)) rawDy -= cmd.vitesse;
|
||||
if (heldKeys.has(cmd.bas)) rawDy += cmd.vitesse;
|
||||
}
|
||||
|
||||
// Bloque le déplacement axe par axe (pas les deux à la fois dès qu'un
|
||||
// seul est gêné) — glisser le long d'un mur en diagonale reste
|
||||
// possible, plus naturel qu'un blocage total au moindre contact.
|
||||
const curLeft = parseFloat(targetEl.style.left) || 0;
|
||||
const curTop = parseFloat(targetEl.style.top) || 0;
|
||||
const boxWidth = parseFloat(targetEl.style.width) || 0;
|
||||
const boxHeight = parseFloat(targetEl.style.height) || 0;
|
||||
let dx = rawDx; let
|
||||
dy = rawDy;
|
||||
if (dx !== 0) {
|
||||
const candidateLeft = clampSceneObjectPosition(targetEl, 'left', curLeft + dx);
|
||||
if (forgeWouldCollide(screenData, obj, candidateLeft, curTop, boxWidth, boxHeight)) dx = 0;
|
||||
}
|
||||
if (dy !== 0) {
|
||||
const candidateTop = clampSceneObjectPosition(targetEl, 'top', curTop + dy);
|
||||
if (forgeWouldCollide(screenData, obj, curLeft, candidateTop, boxWidth, boxHeight)) dy = 0;
|
||||
}
|
||||
// Se tourne vers la direction TENTÉE même si le déplacement est
|
||||
// finalement bloqué (fait face au mur/obstacle, plus naturel qu'un
|
||||
// personnage qui reste dos tourné contre ce qui le bloque).
|
||||
if (rawDx !== 0) applyObjectProperty(targetEl, 'orientation', rawDx < 0 ? 'gauche' : 'droite');
|
||||
|
||||
// Même fps pour idle et walk (bug signalé par l'utilisateur : idle
|
||||
// tournait à 4 i/s contre 8 pour walk, perçue comme "les autres
|
||||
// animations sont lentes à côté de la marche" — aucune raison réelle
|
||||
// de les désynchroniser).
|
||||
if (dx === 0 && dy === 0) {
|
||||
const idleAnim = forgePersonnageIdleAnim(obj.id);
|
||||
if (idleAnim) {
|
||||
runSpriteAnimation(
|
||||
targetEl,
|
||||
resolveSpriteFrames(obj.id, { animation: idleAnim, fps: FORGE_PERSONNAGE_FPS, loop: true }),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (dx !== 0) applyObjectProperty(targetEl, 'pos_x_relatif', dx);
|
||||
if (dy !== 0) applyObjectProperty(targetEl, 'pos_y_relatif', dy);
|
||||
const moveAnim = forgePersonnageMovementAnim(obj.id);
|
||||
if (moveAnim) {
|
||||
runSpriteAnimation(
|
||||
targetEl,
|
||||
resolveSpriteFrames(obj.id, { animation: moveAnim, fps: FORGE_PERSONNAGE_FPS, loop: true }),
|
||||
);
|
||||
}
|
||||
});
|
||||
(screenData.elements || []).forEach((obj) => forgeRunPersonnagePlayerTick(screenData, obj));
|
||||
}
|
||||
|
||||
// Quelle pose jouer pour une touche d'ACTION (pas de déplacement) donnée —
|
||||
@@ -261,7 +289,7 @@ function forgeStartPersonnageInteractListener(screenData) {
|
||||
// réinitialisé à chaque changement d'écran (voir showScreen()).
|
||||
function forgeStartPersonnageControllers(screenData) {
|
||||
forgeStopPersonnageControllers();
|
||||
if (!screenData || screenData.kind !== 'jeu_2d') return;
|
||||
if (screenData?.kind !== 'jeu_2d') return;
|
||||
const elements = screenData.elements || [];
|
||||
const hasPersonnage = elements.some((o) => o.kind === 'personnage' && o.personnage_role === 'joueur');
|
||||
const hasFond = elements.some((o) => o.kind === 'fond');
|
||||
|
||||
Reference in New Issue
Block a user