716c1b49ae
Implémentations majeures: - Classe GroupLink pour synchronisation bidirectionnelle acteurs↔groupes - Configuration complète des totems, PNJ et créatures - Redesign du RollDialog avec interface compacte et sélecteurs - Bonus/malus par domaine de totem - Réussites automatiques et seuils auto basés sur niveau de maîtrise - Choix du totem à garder avec recalcul des réussites - Conversion tous templates chat cards en .hbs - Fiches PNJ et Créature avec sélecteurs pour tous les niveaux - Documentation technique (ARCHITECTURE.md) et utilisateur (GUIDE_UTILISATEUR.md) - Mise à jour system.json pour compatibilité v14 - Tous les TODOs du README.md complétés Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
415 lines
13 KiB
JavaScript
415 lines
13 KiB
JavaScript
/**
|
|
* GroupLink - Gestion des liens entre acteurs et groupes
|
|
*
|
|
* Cette classe permet de gérer les relations bidirectionnelles entre :
|
|
* - Les personnages (characters) et leurs groupes/rencontres
|
|
* - Les groupes (groups) et leurs membres/rencontres
|
|
*
|
|
* @author Vermine2047 System
|
|
*/
|
|
|
|
export class GroupLink {
|
|
|
|
/**
|
|
* Met à jour les groupes dans tous les personnages membres
|
|
* quand un groupe est modifié
|
|
* @param {Actor} group - Le groupe modifié
|
|
* @param {Object} changes - Les changements effectués
|
|
*/
|
|
static async updateActorsOnGroupChange(group, changes) {
|
|
if (group.type !== 'group') return;
|
|
|
|
const groupData = group.system;
|
|
const members = groupData.members || [];
|
|
const encounters = groupData.encounters || [];
|
|
|
|
// Mettre à jour les membres du groupe
|
|
if (changes.members !== undefined || changes.encounters !== undefined) {
|
|
await this._updateMembersInGroup(group, members);
|
|
await this._updateEncountersInGroup(group, encounters);
|
|
}
|
|
|
|
// Synchroniser les données dans les acteurs membres
|
|
await this._syncGroupToMembers(group, members);
|
|
await this._syncGroupToEncounters(group, encounters);
|
|
}
|
|
|
|
/**
|
|
* Met à jour le groupe quand un personnage est modifié
|
|
* @param {Actor} actor - L'acteur modifié
|
|
* @param {Object} changes - Les changements effectués
|
|
*/
|
|
static async updateGroupsOnActorChange(actor, changes) {
|
|
if (actor.type === 'group') return;
|
|
|
|
const actorData = actor.system;
|
|
const encounters = actorData.encounters || [];
|
|
|
|
// Si les rencontres de l'acteur ont changé
|
|
if (changes.encounters !== undefined) {
|
|
// Pour chaque groupe dans les rencontres, mettre à jour les membres
|
|
for (const groupId of encounters) {
|
|
const group = game.actors.get(groupId);
|
|
if (group && group.type === 'group') {
|
|
await this._updateActorInGroupMembers(group, actor.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Synchronise les données du groupe vers les acteurs membres
|
|
* @param {Actor} group - Le groupe
|
|
* @param {Array} memberIds - Liste des IDs des membres
|
|
*/
|
|
static async _syncGroupToMembers(group, memberIds) {
|
|
for (const memberId of memberIds) {
|
|
const member = game.actors.get(memberId);
|
|
if (member) {
|
|
// Vérifier que le groupe est dans les rencontres du membre
|
|
const memberEncounters = member.system.encounters || [];
|
|
if (!memberEncounters.includes(group.id)) {
|
|
// Ajouter le groupe aux rencontres du membre
|
|
memberEncounters.push(group.id);
|
|
await member.update({
|
|
'system.encounters': memberEncounters
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Synchronise les données du groupe vers les acteurs rencontres
|
|
* @param {Actor} group - Le groupe
|
|
* @param {Array} encounterIds - Liste des IDs des rencontres
|
|
*/
|
|
static async _syncGroupToEncounters(group, encounterIds) {
|
|
for (const encounterId of encounterIds) {
|
|
const encounter = game.actors.get(encounterId);
|
|
if (encounter) {
|
|
// Vérifier que le groupe est dans les rencontres de l'acteur
|
|
const encounterGroups = encounter.system.encounters || [];
|
|
if (!encounterGroups.includes(group.id)) {
|
|
encounterGroups.push(group.id);
|
|
await encounter.update({
|
|
'system.encounters': encounterGroups
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour les membres dans un groupe
|
|
* @param {Actor} group - Le groupe
|
|
* @param {Array} memberIds - Liste des IDs des membres
|
|
*/
|
|
static async _updateMembersInGroup(group, memberIds) {
|
|
const currentMembers = group.system.members || [];
|
|
|
|
// Retirer les membres qui ne sont plus dans la liste
|
|
const membersToRemove = currentMembers.filter(id => !memberIds.includes(id));
|
|
const membersToAdd = memberIds.filter(id => !currentMembers.includes(id));
|
|
|
|
// Mettre à jour les acteurs qui ont été retirés
|
|
for (const memberId of membersToRemove) {
|
|
const member = game.actors.get(memberId);
|
|
if (member) {
|
|
const memberEncounters = (member.system.encounters || []).filter(id => id !== group.id);
|
|
await member.update({
|
|
'system.encounters': memberEncounters
|
|
});
|
|
}
|
|
}
|
|
|
|
// Mettre à jour les nouveaux membres
|
|
for (const memberId of membersToAdd) {
|
|
const member = game.actors.get(memberId);
|
|
if (member) {
|
|
const memberEncounters = member.system.encounters || [];
|
|
if (!memberEncounters.includes(group.id)) {
|
|
memberEncounters.push(group.id);
|
|
await member.update({
|
|
'system.encounters': memberEncounters
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour les rencontres dans un groupe
|
|
* @param {Actor} group - Le groupe
|
|
* @param {Array} encounterIds - Liste des IDs des rencontres
|
|
*/
|
|
static async _updateEncountersInGroup(group, encounterIds) {
|
|
const currentEncounters = group.system.encounters || [];
|
|
|
|
// Retirer les rencontres qui ne sont plus dans la liste
|
|
const encountersToRemove = currentEncounters.filter(id => !encounterIds.includes(id));
|
|
const encountersToAdd = encounterIds.filter(id => !currentEncounters.includes(id));
|
|
|
|
// Mettre à jour les acteurs qui ont été retirés des rencontres
|
|
for (const encounterId of encountersToRemove) {
|
|
const encounter = game.actors.get(encounterId);
|
|
if (encounter) {
|
|
const encounterGroups = (encounter.system.encounters || []).filter(id => id !== group.id);
|
|
await encounter.update({
|
|
'system.encounters': encounterGroups
|
|
});
|
|
}
|
|
}
|
|
|
|
// Mettre à jour les nouvelles rencontres
|
|
for (const encounterId of encountersToAdd) {
|
|
const encounter = game.actors.get(encounterId);
|
|
if (encounter) {
|
|
const encounterGroups = encounter.system.encounters || [];
|
|
if (!encounterGroups.includes(group.id)) {
|
|
encounterGroups.push(group.id);
|
|
await encounter.update({
|
|
'system.encounters': encounterGroups
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour un acteur dans les membres d'un groupe
|
|
* @param {Actor} group - Le groupe
|
|
* @param {string} actorId - L'ID de l'acteur
|
|
*/
|
|
static async _updateActorInGroupMembers(group, actorId) {
|
|
const groupMembers = group.system.members || [];
|
|
if (!groupMembers.includes(actorId)) {
|
|
groupMembers.push(actorId);
|
|
await group.update({
|
|
'system.members': groupMembers
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour un acteur dans les rencontres d'un groupe
|
|
* @param {Actor} group - Le groupe
|
|
* @param {string} actorId - L'ID de l'acteur
|
|
*/
|
|
static async _updateActorInGroupEncounters(group, actorId) {
|
|
const groupEncounters = group.system.encounters || [];
|
|
if (!groupEncounters.includes(actorId)) {
|
|
groupEncounters.push(actorId);
|
|
await group.update({
|
|
'system.encounters': groupEncounters
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retourne les objets Actor pour une liste d'IDs
|
|
* @param {Array} actorIds - Liste d'IDs d'acteurs
|
|
* @returns {Array} - Liste d'objets Actor
|
|
*/
|
|
static getActorObjects(actorIds) {
|
|
return actorIds
|
|
.map(id => game.actors.get(id))
|
|
.filter(actor => actor !== undefined);
|
|
}
|
|
|
|
/**
|
|
* Retourne les objets Actor pour les membres d'un groupe
|
|
* @param {Actor} group - Le groupe
|
|
* @returns {Array} - Liste d'objets Actor
|
|
*/
|
|
static getGroupMembers(group) {
|
|
const memberIds = group.system.members || [];
|
|
return this.getActorObjects(memberIds);
|
|
}
|
|
|
|
/**
|
|
* Retourne les objets Actor pour les rencontres d'un groupe
|
|
* @param {Actor} group - Le groupe
|
|
* @returns {Array} - Liste d'objets Actor
|
|
*/
|
|
static getGroupEncounters(group) {
|
|
const encounterIds = group.system.encounters || [];
|
|
return this.getActorObjects(encounterIds);
|
|
}
|
|
|
|
/**
|
|
* Retourne les groupes auxquels un acteur appartient
|
|
* @param {Actor} actor - L'acteur
|
|
* @returns {Array} - Liste d'objets Actor (groupes)
|
|
*/
|
|
static getActorGroups(actor) {
|
|
const groupIds = actor.system.encounters || [];
|
|
return this.getActorObjects(groupIds).filter(a => a.type === 'group');
|
|
}
|
|
|
|
/**
|
|
* Retourne les rencontres (PNJ/Créatures) d'un acteur
|
|
* @param {Actor} actor - L'acteur
|
|
* @returns {Array} - Liste d'objets Actor (PNJ/Créatures)
|
|
*/
|
|
static getActorEncounters(actor) {
|
|
const encounterIds = actor.system.encounters || [];
|
|
return this.getActorObjects(encounterIds).filter(a => a.type !== 'group');
|
|
}
|
|
|
|
/**
|
|
* Supprime un acteur de tous ses groupes
|
|
* @param {string} actorId - L'ID de l'acteur à supprimer
|
|
*/
|
|
static async removeActorFromAllGroups(actorId) {
|
|
const allGroups = game.actors.filter(a => a.type === 'group');
|
|
|
|
for (const group of allGroups) {
|
|
const members = group.system.members || [];
|
|
const encounters = group.system.encounters || [];
|
|
|
|
let needsUpdate = false;
|
|
const newMembers = members.filter(id => id !== actorId);
|
|
const newEncounters = encounters.filter(id => id !== actorId);
|
|
|
|
if (newMembers.length !== members.length || newEncounters.length !== encounters.length) {
|
|
needsUpdate = true;
|
|
}
|
|
|
|
if (needsUpdate) {
|
|
await group.update({
|
|
'system.members': newMembers,
|
|
'system.encounters': newEncounters
|
|
});
|
|
}
|
|
}
|
|
|
|
// Supprimer les groupes des rencontres de l'acteur
|
|
const actor = game.actors.get(actorId);
|
|
if (actor) {
|
|
await actor.update({
|
|
'system.encounters': []
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ajoute un acteur à un groupe
|
|
* @param {string} actorId - L'ID de l'acteur
|
|
* @param {string} groupId - L'ID du groupe
|
|
*/
|
|
static async addActorToGroup(actorId, groupId) {
|
|
const actor = game.actors.get(actorId);
|
|
const group = game.actors.get(groupId);
|
|
|
|
if (!actor || !group || group.type !== 'group') return;
|
|
|
|
// Ajouter l'acteur aux membres du groupe
|
|
const groupMembers = group.system.members || [];
|
|
if (!groupMembers.includes(actorId)) {
|
|
groupMembers.push(actorId);
|
|
await group.update({
|
|
'system.members': groupMembers
|
|
});
|
|
}
|
|
|
|
// Ajouter le groupe aux rencontres de l'acteur
|
|
const actorEncounters = actor.system.encounters || [];
|
|
if (!actorEncounters.includes(groupId)) {
|
|
actorEncounters.push(groupId);
|
|
await actor.update({
|
|
'system.encounters': actorEncounters
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retire un acteur d'un groupe
|
|
* @param {string} actorId - L'ID de l'acteur
|
|
* @param {string} groupId - L'ID du groupe
|
|
*/
|
|
static async removeActorFromGroup(actorId, groupId) {
|
|
const actor = game.actors.get(actorId);
|
|
const group = game.actors.get(groupId);
|
|
|
|
if (!actor || !group || group.type !== 'group') return;
|
|
|
|
// Retirer l'acteur des membres du groupe
|
|
const groupMembers = (group.system.members || []).filter(id => id !== actorId);
|
|
await group.update({
|
|
'system.members': groupMembers
|
|
});
|
|
|
|
// Retirer le groupe des rencontres de l'acteur
|
|
const actorEncounters = (actor.system.encounters || []).filter(id => id !== groupId);
|
|
await actor.update({
|
|
'system.encounters': actorEncounters
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialise les hooks pour la synchronisation automatique
|
|
*/
|
|
static registerHooks() {
|
|
// Hook sur la mise à jour d'un acteur
|
|
Hooks.on('updateActor', async (actor, changes, options, userId) => {
|
|
if (!game.user.isGM && userId !== game.userId) return;
|
|
|
|
// Si c'est un groupe qui est mis à jour
|
|
if (actor.type === 'group') {
|
|
await this.updateActorsOnGroupChange(actor, changes);
|
|
}
|
|
// Si c'est un autre acteur qui est mis à jour
|
|
else {
|
|
await this.updateGroupsOnActorChange(actor, changes);
|
|
}
|
|
});
|
|
|
|
// Hook sur la création d'un acteur
|
|
Hooks.on('createActor', async (actor, options, userId) => {
|
|
if (!game.user.isGM && userId !== game.userId) return;
|
|
|
|
// Si un personnage est créé, vérifier qu'il n'a pas de groupes invalides
|
|
if (actor.type !== 'group') {
|
|
const encounters = actor.system.encounters || [];
|
|
for (const groupId of encounters) {
|
|
const group = game.actors.get(groupId);
|
|
if (!group) {
|
|
// Nettoyer les références invalides
|
|
await actor.update({
|
|
'system.encounters': encounters.filter(id => game.actors.get(id))
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Hook sur la suppression d'un acteur
|
|
Hooks.on('deleteActor', async (actor, options, userId) => {
|
|
if (!game.user.isGM && userId !== game.userId) return;
|
|
|
|
if (actor.type === 'group') {
|
|
// Si un groupe est supprimé, nettoyer les références dans les acteurs
|
|
const memberIds = actor.system.members || [];
|
|
const encounterIds = actor.system.encounters || [];
|
|
|
|
for (const id of [...memberIds, ...encounterIds]) {
|
|
const a = game.actors.get(id);
|
|
if (a) {
|
|
const encounters = (a.system.encounters || []).filter(eid => eid !== actor.id);
|
|
await a.update({
|
|
'system.encounters': encounters
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
// Si un acteur est supprimé, le retirer de tous les groupes
|
|
await this.removeActorFromAllGroups(actor.id);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Exporter pour utilisation globale
|
|
export default GroupLink;
|