/** * 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;