Aller au contenu

Compétences techniques

La migration 0028_skill_tree_system crée skill_branches, skill_nodes, skill_node_buffs, skill_levels, character_skill_progress et character_skill_unlocks.

Modèle de données

  • skill_branches : name, description, icon, position (ordre d'affichage), enabled.
  • skill_nodes : branch_id (FK CASCADE), cost (int, CHECK > 0), prerequisite_node_id (FK nullable vers skill_nodes, ON DELETE RESTRICT — un nœud ayant un dépendant ne peut pas être supprimé sans le retirer d'abord), canvas_x/canvas_y (position admin, réutilisée par la NUI joueur), permission_flags (JSON list[str], format ^[A-Z][A-Z0-9_]*$, unicité vérifiée en service tous nœuds confondus), enabled.
  • skill_node_buffs : table de liaison (node_id, buff_definition_id), UNIQUE, FK buff_definition_id en ON DELETE RESTRICT vers buff_definitions. Un même buff peut être référencé par plusieurs nœuds ; chaque déblocage crée sa propre instance de buff (le service buffs ne déduplique jamais, cf. services/buffs.py:apply), donc deux nœuds partageant un buff produisent deux instances indépendantes qui s'empilent normalement.
  • skill_levels : level (unique, ≥ 1), xp_required (cumulatif, niveau 1 toujours à 0), points_reward. La séquence doit rester contiguë depuis 1 (créer le niveau N exige que N-1 existe déjà ; supprimer un niveau est refusé s'il existe un niveau supérieur) — validé dans services/skill_tree.py:save_level/delete_level.
  • character_skill_progress : character_id (PK, FK CASCADE), xp (≥ 0). Niveau et points disponibles ne sont jamais stockés : toujours dérivés à la lecture (services/skill_progress.py:_derive) en parcourant la courbe une seule fois, triée par niveau.
  • character_skill_unlocks : PK composite (character_id, node_id), tous deux ON DELETE CASCADE, plus applied_buff_ids (JSON list[str]) — nécessaire car services/buffs.py:remove exige un id d'instance, pas un id de définition ; cette colonne mémorise quelles instances ont été créées par ce déblocage précis pour pouvoir les retirer plus tard sans en affecter d'autres.

Services (api/app/services/)

skill_tree.py porte le CRUD de l'arbre et de la courbe, avec toutes les validations ci-dessus. skill_progress.py porte la progression par personnage :

  • unlock verrouille character_skill_progress (SELECT ... FOR UPDATE) avant de vérifier prérequis/solde/état actif, pour empêcher toute double dépense en cas de rejeu réseau.
  • revoke_node_effects(refund) retire les instances de buff que ce nœud lui-même a créées (applied_buff_ids) — jamais celles d'un autre nœud partageant le même buff, chacune ayant sa propre instance. Avec refund=False (désactivation/suppression de nœud), la ligne de déblocage est conservée (coût toujours compté comme dépensé, applied_buff_ids vidée) : c'est ce qui permet à reactivate_all_for_node de réappliquer les buffs si l'admin réactive le nœud plus tard, sans double dépense de points. Avec refund=True (respec), la ligne est supprimée et le coût recrédité.
  • respec calcule d'abord, par parcours de la chaîne de prérequis, l'ensemble complet des nœuds encore débloqués qui dépendent (directement ou indirectement) du nœud demandé, puis les retire tous dans le même appel (les plus dépendants d'abord), chacun étant remboursé.
  • set_node_enabled (dans skill_tree.py) appelle revoke_all_for_node(refund=False) en désactivant, et reactivate_all_for_node en réactivant.

Routes (api/app/routers/skills.py)

  • internal (/internal/v1/skills, clé API uniquement) : GET /tree, GET /characters/{id}, POST /characters/{id}/unlock, POST /characters/{id}/xp (générique, sans vérification de rôle — mécanisme pour de futures activités de jeu), POST /admin/xp et POST /admin/respec (F10, discord_role_ids + permission ADMIN_ALLOW_GRANT_XP/ADMIN_ALLOW_RESPEC_SKILLS).
  • admin (/admin/skills, session + CSRF) : CRUD branches/nœuds/courbe (ADMIN_ALLOW_EDIT_SKILLS), lecture (ADMIN_ALLOW_VIEW_SKILLS), POST /xp et POST /respec.

Convention F10 confirmée sur le code réel (évite la confusion rencontrée lors du système bancaire) : medusa-admin/server.js construit ses payloads via actorPayload(), qui envoie discord_role_ids — tous les schémas Ingame* de app/schemas/skills.py attendent donc discord_role_ids, jamais actor_role_ids.

Ressource FiveM medusa-skills

Premier usage d'une state bag joueur répliquée dans ce dépôt : Player(source).state:set( 'medusaSkillFlags', flags, true) (3ᵉ argument true), lisible par n'importe quel client et côté serveur par toute autre ressource, sans appel API. Le serveur maintient sa propre table source -> characterId (aucun export de session active n'existe dans medusa-player-utils), peuplée par un aller-retour serveur au moment où le client signale medusa:character:selected.

L'arbre est mis en cache et resynchronisé façon medusa-clothing-shops : diff de fingerprint, setInterval toutes les 5s, setTimeout immédiat au démarrage, et un événement de forçage — chaque changement détecté déclenche une resynchronisation des indicateurs de tous les personnages connectés actuellement suivis.

Le joueur ouvre la NUI via le raccourci F4 (RegisterCommand('skills', ...) + RegisterKeyMapping('skills', ..., 'F4'), F3/F9/F10/F11 étant déjà pris par d'autres ressources). La NUI (web/) est un canevas SVG déclaratif, pannable et zoomable, sans dépendance ni étape de build, cohérent avec le reste des NUI Medusa.

Admin web et F10

La feature admin/public/js/features/skills.js décrit le formulaire d'un nœud, notamment le pré-requis optionnel sous forme de options, puis délègue entièrement la création des contrôles à admin/public/js/core/modal.js. Le core rend ce champ comme un <select> et ne lui applique pas de type d'input ; la confirmation conserve le payload et les routes Skills existants.

admin/public/ reçoit un nouvel onglet Compétences : canevas SVG glisser-déposer pour positionner les nœuds (persistance de canvas_x/canvas_y au relâchement du pointeur), listes de branches/nœuds pour le CRUD complet, et un éditeur de courbe de niveau en liste. La fiche personnage (web) et le panneau F10 (medusa-admin) exposent, en parité stricte, la consultation de la progression, l'octroi manuel d'XP (motif obligatoire) et le respec — mais pas la construction de l'arbre, réservée au web. La commande /grantxp <compte> <montant> <motif> (dans medusa-admin, ciblant un personnage actif via son compte) offre une troisième surface d'octroi, vérifiée avec la même permission ADMIN_ALLOW_GRANT_XP.