ChatMessage.create est async, il faut donc de préférence l'appeler avec un await. Des effets secondaires avaient lieu (ordre de messages, updates ultérieurs parfois pas pris en compte)
935 lines
31 KiB
JavaScript
935 lines
31 KiB
JavaScript
import { ChatVente } from "../achat-vente/chat-vente.js";
|
|
import { ChatUtility } from "../chat-utility.js";
|
|
import { renderTemplate, SYSTEM_SOCKET_ID } from "../constants.js";
|
|
import { Grammar } from "../grammar.js";
|
|
import { Monnaie } from "../item-monnaie.js";
|
|
import { ITEM_TYPES } from "../constants.js";
|
|
import { Misc } from "../misc.js";
|
|
import { RdDAudio } from "../rdd-audio.js";
|
|
import { RdDConfirm } from "../rdd-confirm.js";
|
|
import { RdDUtility } from "../rdd-utility.js";
|
|
import { SystemCompendiums } from "../settings/system-compendiums.js";
|
|
import { RdDItem } from "../item.js";
|
|
import { StatusEffects, STATUSES } from "../settings/status-effects.js";
|
|
import { Apprecier } from "../moral/apprecier.mjs";
|
|
import { CARACS } from "../rdd-carac.js";
|
|
|
|
export class RdDBaseActor extends Actor {
|
|
|
|
static _findCaracNode(carac, name) {
|
|
return Object.entries(carac)
|
|
.filter(it => Grammar.equalsInsensitive(it[1].label, name))
|
|
.map(it => it[0])
|
|
.find(it => it)
|
|
}
|
|
|
|
static $findCaracByName(carac, name) {
|
|
const caracList = Object.entries(carac)
|
|
let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique', onMessage: m => { } });
|
|
if (!entry || entry.length == 0) {
|
|
entry = Misc.findFirstLike(name, caracList, { mapper: it => it[1].label, description: 'caractéristique' });
|
|
}
|
|
return entry && entry.length > 0 ? carac[entry[0]] : undefined;
|
|
}
|
|
|
|
static getDefaultValue(actorType, path) {
|
|
if (path.includes('.')) {
|
|
path = path.split('.')
|
|
}
|
|
let obj = game.model.Actor[actorType]
|
|
for (let p of path) {
|
|
obj = obj ? obj[p] : undefined
|
|
}
|
|
return obj
|
|
}
|
|
|
|
static getDefaultImg(itemType) {
|
|
return game.system.rdd.actorClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType];
|
|
}
|
|
|
|
static init() {
|
|
Handlebars.registerHelper('actor-isFeminin', actor => actor.isFeminin())
|
|
Hooks.on("createItem", (item, options, id) => Misc.documentIfResponsible(item.parent)?.onCreateItem(item, options, id))
|
|
Hooks.on("updateItem", (item, updates, options, id) => Misc.documentIfResponsible(item.parent)?.onUpdateItem(item, updates, options, id))
|
|
Hooks.on("deleteItem", (item, options, id) => Misc.documentIfResponsible(item.parent)?.onDeleteItem(item, options, id))
|
|
}
|
|
|
|
static onSocketMessage(sockmsg) {
|
|
switch (sockmsg.msg) {
|
|
case "msg_remote_actor_call":
|
|
return RdDBaseActor.onRemoteActorCall(sockmsg.data, sockmsg.userId);
|
|
}
|
|
}
|
|
|
|
static remoteActorCall(callData, userId = undefined) {
|
|
if (game.user.isGM) {
|
|
RdDBaseActor.onRemoteActorCall(callData, game.user.id)
|
|
return false
|
|
}
|
|
else {
|
|
const gmUserId = Misc.firstConnectedGMId()
|
|
if (gmUserId) {
|
|
game.socket.emit(SYSTEM_SOCKET_ID, {
|
|
msg: "msg_remote_actor_call",
|
|
data: callData,
|
|
userId: gmUserId
|
|
})
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
static onRemoteActorCall(callData, userId) {
|
|
if (userId == game.user.id) {
|
|
// Seul le joueur choisi effectue l'appel: le joueur courant si propriétaire de l'actor, ou le MJ sinon
|
|
const actor = RdDBaseActor.getRealActor(callData?.actorId, callData?.tokenId);
|
|
const args = callData.args;
|
|
console.info(`RdDBaseActor.onRemoteActorCall: pour l'Actor ${callData.actorId}, appel de RdDBaseActor.${callData.method}(`, ...args, ')');
|
|
actor[callData.method](...args);
|
|
}
|
|
}
|
|
|
|
static getRealActor(actorId, tokenId) {
|
|
const actor = tokenId ? canvas.tokens.get(tokenId)?.actor : undefined
|
|
return actor ?? game.actors.get(actorId)
|
|
}
|
|
|
|
static extractActorMin(actor) { return { id: actor?.id, type: actor?.type, name: actor?.name, img: actor?.img } }
|
|
|
|
/**
|
|
* Cette methode surcharge Actor.create() pour ajouter si besoin des Items par défaut:
|
|
* compétences et monnaies.
|
|
*
|
|
* @param {Object} actorData template d'acteur auquel ajouter des informations.
|
|
* @param {Object} options optionspour customiser la création
|
|
*/
|
|
static async create(actorData, options) {
|
|
// import depuis un compendium
|
|
if (actorData instanceof Array) {
|
|
return super.create(actorData, options);
|
|
}
|
|
// Création d'un acteur avec des items (uniquement en cas de duplication): pas besoin d'ajouter d'items
|
|
if (actorData.items) {
|
|
return await super.create(actorData, options);
|
|
}
|
|
actorData.items = [];
|
|
if (actorData.type == "personnage") {
|
|
const competences = await SystemCompendiums.getCompetences(actorData.type);
|
|
actorData.items = actorData.items.concat(competences.map(i => i.toObject()))
|
|
.concat(Monnaie.monnaiesStandard());
|
|
}
|
|
else if (actorData.type == "commerce") {
|
|
actorData.items = actorData.items.concat(Monnaie.monnaiesStandard());
|
|
}
|
|
return super.create(actorData, options);
|
|
}
|
|
|
|
constructor(docData, context = {}) {
|
|
if (!context.rdd?.ready) {
|
|
foundry.utils.mergeObject(context, { rdd: { ready: true } });
|
|
const ActorConstructor = game.system.rdd.actorClasses[docData.type];
|
|
if (ActorConstructor) {
|
|
if (!docData.img) {
|
|
docData.img = ActorConstructor.defaultIcon;
|
|
}
|
|
return new ActorConstructor(docData, context);
|
|
}
|
|
}
|
|
context.rdd = undefined
|
|
super(docData, context);
|
|
}
|
|
|
|
getAlias() {
|
|
if (this.token?.name != null && this.token != this.prototypeToken) {
|
|
return this.token.name
|
|
}
|
|
return this.name
|
|
}
|
|
|
|
isPersonnageJoueur() { return false }
|
|
|
|
getCarac() {
|
|
return foundry.utils.duplicate(this.system.carac)
|
|
}
|
|
|
|
findCaracByName(name) {
|
|
name = Grammar.toLowerCaseNoAccent(name)
|
|
switch (name) {
|
|
case 'reve actuel':
|
|
name = CARACS.REVE_ACTUEL
|
|
break
|
|
case 'chance actuelle':
|
|
name = CARACS.CHANCE_ACTUELLE
|
|
break
|
|
}
|
|
const carac = this.getCarac()
|
|
return RdDBaseActor.$findCaracByName(carac, name);
|
|
}
|
|
|
|
getCaracCompetenceCreature() {
|
|
return this.isCreatureOuEntite()
|
|
? Object.fromEntries(this.itemTypes[ITEM_TYPES.competencecreature].map(it => [Grammar.toLowerCaseNoAccent(it.name), { label: it.name, value: it.system.carac_value }]))
|
|
: {}
|
|
}
|
|
|
|
mapCarac(caracCode) { return caracCode }
|
|
|
|
getCaracByName(name) {
|
|
name = this.mapCarac(Grammar.toLowerCaseNoAccent(name)) ?? name
|
|
switch (name) {
|
|
case 'reve actuel':
|
|
name = CARACS.REVE_ACTUEL
|
|
break
|
|
case 'chanceactuelle':
|
|
name = CARACS.CHANCE_ACTUELLE
|
|
break
|
|
}
|
|
switch (name) {
|
|
case CARACS.REVE_ACTUEL:
|
|
return this.getCaracReveActuel()[CARACS.REVE_ACTUEL]
|
|
case CARACS.CHANCE_ACTUELLE:
|
|
return this.getCaracChanceActuelle()[CARACS.CHANCE_ACTUELLE]
|
|
}
|
|
return this.findCaracByName(name);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async _preCreate(data, options, user) {
|
|
await super._preCreate(data, options, user);
|
|
|
|
// Configure prototype token settings
|
|
if (this.type === "personnage") {
|
|
this.updateSource({
|
|
sight: { enabled: true },
|
|
actorLink: options.fromCompendium ? data.prototypeToken.actorLink : true,
|
|
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
|
})
|
|
} else {
|
|
const prototypeToken = {
|
|
sight: { enabled: true },
|
|
disposition: CONST.TOKEN_DISPOSITIONS.NEUTRAL
|
|
}
|
|
this.updateSource({ prototypeToken });
|
|
}
|
|
}
|
|
|
|
async _preUpdate(changed, options, user) {
|
|
const updatedCarac = changed?.system?.carac
|
|
if (updatedCarac && (updatedCarac.force || updatedCarac.reve || updatedCarac.taille)) {
|
|
await this.setEffect(STATUSES.StatusSurEnc, this.isSurenc())
|
|
}
|
|
await this.delayedRenderSheet('_preUpdate', changed, options)
|
|
return super._preUpdate(changed, options, user)
|
|
}
|
|
|
|
_onUpdate(changed, options, userId) {
|
|
super._onUpdate(changed, options, userId)
|
|
if (userId == game.user.id){
|
|
this.delayedRenderSheet('_onUpdate', changed, options)
|
|
}
|
|
}
|
|
|
|
async delayedRenderSheet(caller, data, options) {
|
|
this.refreshDelayCounter = (this.refreshDelayCounter ?? 0)+1
|
|
if (this.refreshDelayCounter == 1) {
|
|
this.renderAfterDelay(this.refreshDelayCounter)
|
|
}
|
|
}
|
|
|
|
renderAfterDelay(currentCounter) {
|
|
setTimeout(async () => {
|
|
if (currentCounter == this.refreshDelayCounter) {
|
|
this.sheet?.render()
|
|
this.refreshDelayCounter = 0
|
|
}
|
|
else {
|
|
this.renderAfterDelay(this.refreshDelayCounter)
|
|
}
|
|
}, 30)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
prepareData() {
|
|
super.prepareData()
|
|
this.prepareActorData()
|
|
this.cleanupConteneurs()
|
|
this.computeEtatGeneral()
|
|
this.computeEncTotal()
|
|
}
|
|
|
|
prepareActorData() { }
|
|
|
|
async computeEtatGeneral() { }
|
|
|
|
/* -------------------------------------------- */
|
|
findPlayer() {
|
|
return game.users.players.find(player => player.active && player.character?.id == this.id);
|
|
}
|
|
|
|
isCreatureOuEntite() { return this.isCreature() || this.isEntite() }
|
|
isCreature() { return false }
|
|
isEntite() { return false }
|
|
isEntiteIncarnee() { return false }
|
|
isEntiteNonIncarnee() { return false }
|
|
isEntiteBlurette() { return false }
|
|
isHautRevant() { return false }
|
|
isVehicule() { return false }
|
|
isPersonnage() { return false }
|
|
isFeminin() { return false }
|
|
getItem(id, type = undefined) {
|
|
const item = this.items.get(id);
|
|
if (type == undefined || (item?.type == type)) {
|
|
return item;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
listeSuivants(filter = suivant => true) { return [] }
|
|
listeSuivants(filter = suivant => true) { return [] }
|
|
listItems(type = undefined) { return (type ? this.itemTypes[type] : this.items); }
|
|
filterItems(filter, type = undefined) { return (type ? this.itemTypes[type] : this.items)?.filter(filter) ?? []; }
|
|
findItemLike(idOrName, type) {
|
|
return this.getItem(idOrName, type)
|
|
?? Misc.findFirstLike(idOrName, this.listItems(type), { description: Misc.typeName('Item', type) });
|
|
}
|
|
|
|
getMonnaie(id) { return this.findItemLike(id, 'monnaie'); }
|
|
getEncombrementMax() { return 0 }
|
|
isSurenc() { return false }
|
|
|
|
/* -------------------------------------------- */
|
|
isEffectAllowed(effectId) { return false }
|
|
|
|
getEffects(filter = e => true, forceRequise = undefined) {
|
|
const effects = this.getEmbeddedCollection("ActiveEffect")
|
|
const selected = effects.filter(filter)
|
|
if (forceRequise && this.isForceInsuffisante(forceRequise)) {
|
|
selected.push(StatusEffects.prepareActiveEffect(STATUSES.StatusForceWeak))
|
|
}
|
|
return selected
|
|
}
|
|
|
|
getEffectsByStatus(effectId) {
|
|
return this.getEffects().filter(it => it.statuses.has(effectId))
|
|
}
|
|
|
|
async setEffect(effectId, status, options = { render: true }) {
|
|
if (this.isEffectAllowed(effectId)) {
|
|
const effects = this.getEffectsByStatus(effectId)
|
|
if (!status && effects.length > 0) {
|
|
await this.deleteEmbeddedDocuments('ActiveEffect', effects.map(it => it.id), options)
|
|
}
|
|
if (status && effects.length == 0) {
|
|
await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)], options)
|
|
}
|
|
}
|
|
}
|
|
|
|
async removeEffect(id) {
|
|
this.removeEffects(it => it.id == id)
|
|
}
|
|
|
|
async removeEffects(filter = e => true, options = { render: true }) {
|
|
if (game.user.isGM) {
|
|
const ids = this.getEffects(filter)
|
|
.filter(it => this.canRemoveEffects(it))
|
|
.map(it => it.id)
|
|
if (ids.length > 0) {
|
|
await this.deleteEmbeddedDocuments('ActiveEffect', ids, options)
|
|
}
|
|
|
|
}
|
|
}
|
|
canRemoveEffects(effect) {
|
|
if (effect.statuses.has(STATUSES.StatusSurEnc)) {
|
|
return !this.isSurenc()
|
|
}
|
|
if (effect.statuses.has(STATUSES.StatusDemiReve)) {
|
|
return !this.tmrApp
|
|
}
|
|
return true
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async updateCarac(caracName, to) {
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
|
|
async onCreateItem(item, options, id) {
|
|
}
|
|
|
|
async onUpdateItem(item, updates, options, id) {
|
|
const conteneur = item.findConteneur()
|
|
conteneur?.render(options.render)
|
|
}
|
|
|
|
async onDeleteItem(item, options, id) {
|
|
if (item.isInventaire()) {
|
|
await this._removeItemFromConteneur(item)
|
|
}
|
|
}
|
|
|
|
async _removeItemFromConteneur(item) {
|
|
const conteneur = this.findConteneur(item);
|
|
if (conteneur) {
|
|
const nouveauContenu = conteneur.system.contenu.filter(id => id != item.id)
|
|
const updates = { _id: conteneur.id, 'system.contenu': nouveauContenu }
|
|
if (updates.length > 0) {
|
|
await this.updateEmbeddedDocuments('Item', updates)
|
|
}
|
|
}
|
|
}
|
|
|
|
async onTimeChanging(oldTimestamp, newTimestamp) {
|
|
this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp))
|
|
.forEach(async it => await it.onFinPeriodeTemporel(oldTimestamp, newTimestamp))
|
|
}
|
|
|
|
async creerObjetParMJ(object) {
|
|
if (this.isOwner) {
|
|
await this.createEmbeddedDocuments('Item', [object])
|
|
return
|
|
}
|
|
RdDBaseActor.remoteActorCall({
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'creerObjetParMJ', args: [object]
|
|
})
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async cleanupConteneurs() {
|
|
if (Misc.isOwnerPlayer(this)) {
|
|
let updates = this.itemTypes[ITEM_TYPES.conteneur]
|
|
.filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0)
|
|
.map(c => { return { _id: c._id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } });
|
|
if (updates.length > 0) {
|
|
await this.updateEmbeddedDocuments("Item", updates)
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
getFortune() {
|
|
return Monnaie.getFortune(this.itemTypes[ITEM_TYPES.monnaie]);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async itemQuantiteIncDec(id, value) {
|
|
let item = this.getItem(id);
|
|
if (item && item.isInventaire()) {
|
|
const quantite = Math.max(0, item.system.quantite + value);
|
|
await item.update({ 'system.quantite': quantite });
|
|
}
|
|
}
|
|
|
|
computePrixTotalEquipement() {
|
|
return this.items.filter(it => it.isInventaire())
|
|
.filter(it => !it.isMonnaie())
|
|
.map(it => it.valeurTotale())
|
|
.reduce(Misc.sum(), 0);
|
|
}
|
|
|
|
async payerSols(depense) {
|
|
depense = Number(depense);
|
|
if (depense == 0) {
|
|
return;
|
|
}
|
|
let fortune = this.getFortune();
|
|
console.log("payer", game.user.character, depense, fortune);
|
|
// TODO: passer en handlebars
|
|
let msg = "";
|
|
if (fortune >= depense) {
|
|
await Monnaie.optimiserFortune(this, fortune - depense);
|
|
msg = `Vous avez payé <strong>${depense} Sols</strong>, qui ont été soustraits de votre argent.`;
|
|
RdDAudio.PlayContextAudio("argent"); // Petit son
|
|
} else {
|
|
msg = "Vous n'avez pas assez d'argent pour payer cette somme !";
|
|
}
|
|
|
|
await ChatMessage.create({
|
|
whisper: ChatUtility.getOwners(this),
|
|
content: msg
|
|
})
|
|
}
|
|
|
|
async depenserSols(sols) {
|
|
if (!this.isOwner) {
|
|
return RdDBaseActor.remoteActorCall({
|
|
userId: Misc.connectedGMOrUser(),
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'depenserSols', args: [sols]
|
|
})
|
|
}
|
|
let reste = this.getFortune() - Number(sols);
|
|
if (reste >= 0) {
|
|
await Monnaie.optimiserFortune(this, reste);
|
|
}
|
|
return reste;
|
|
}
|
|
|
|
async ajouterSols(sols, fromActorId = undefined) {
|
|
sols = Number(sols)
|
|
if (sols == 0) {
|
|
return
|
|
}
|
|
if (sols < 0) {
|
|
ui.notifications.error(`Impossible d'ajouter un gain de ${sols} <0`)
|
|
return
|
|
}
|
|
if (fromActorId && !this.isOwner) {
|
|
return RdDBaseActor.remoteActorCall({
|
|
userId: Misc.connectedGMOrUser(),
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'ajouterSols', args: [sols, fromActorId]
|
|
})
|
|
}
|
|
|
|
const fromActor = game.actors.get(fromActorId)
|
|
await Monnaie.optimiserFortune(this, sols + this.getFortune());
|
|
|
|
RdDAudio.PlayContextAudio("argent"); // Petit son
|
|
await ChatMessage.create({
|
|
whisper: ChatUtility.getOwners(this),
|
|
content: `Vous avez reçu <strong>${sols} Sols</strong> ${fromActor ? " de " + fromActor.name : ''}, qui ont été ajoutés à votre argent.`
|
|
})
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
getQuantiteDisponible(item) {
|
|
return item?.isService() ? undefined : item?.getQuantite();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async achatVente(achat) {
|
|
if (achat.vendeurId == achat.acheteurId) {
|
|
ui.notifications.info("Inutile de se vendre à soi-même")
|
|
return
|
|
}
|
|
const cout = Number(achat.prixTotal ?? 0)
|
|
const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined
|
|
|
|
if (acheteur && !acheteur.isOwner) {
|
|
ui.notifications.warn(`${game.user.id} n'est pas propriétaire de ${this.name} et effectue un achat pour lui!`)
|
|
RdDBaseActor.remoteActorCall({
|
|
actorId: achat.acheteurId,
|
|
method: 'achatVente', args: [achat]
|
|
});
|
|
return
|
|
}
|
|
|
|
|
|
const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined
|
|
const quantite = (achat.choix.nombreLots ?? 1) * (achat.vente.tailleLot);
|
|
const itemVendu = vendeur?.getItem(achat.vente.item._id) ?? game.items.get(achat.vente.item._id);
|
|
if (!itemVendu) {
|
|
ChatUtility.notifyUser(achat.userId, 'warn', vendeur ? `Le vendeur n'a pas plus de ${achat.vente.item.name} !` : `Impossible de retrouver: ${achat.vente.item.name} !`);
|
|
return
|
|
}
|
|
if (vendeur && !vendeur.verifierQuantite(itemVendu, quantite)) {
|
|
ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a pas assez de ${itemVendu.name} !`);
|
|
return
|
|
}
|
|
if (acheteur && !acheteur.verifierFortune(cout)) {
|
|
ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`);
|
|
return
|
|
}
|
|
await vendeur?.vendre(itemVendu, quantite, cout, acheteur);
|
|
await acheteur?.acheter(itemVendu, quantite, cout, achat, vendeur)
|
|
|
|
if (cout > 0) {
|
|
RdDAudio.PlayContextAudio("argent");
|
|
}
|
|
const chatAchatItem = foundry.utils.duplicate(achat.vente);
|
|
chatAchatItem.quantiteTotal = quantite;
|
|
await ChatMessage.create({
|
|
user: achat.userId,
|
|
speaker: { alias: (acheteur ?? vendeur).getAlias() },
|
|
whisper: ChatUtility.getOwners(this),
|
|
content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.hbs', chatAchatItem)
|
|
});
|
|
|
|
if (!achat.vente.quantiteIllimite) {
|
|
await this.updateMessageVente(achat.chatMessageIdVente, achat.choix.nombreLots);
|
|
}
|
|
}
|
|
|
|
async updateMessageVente(chatMessageIdVente, nombreLots) {
|
|
const chatMessage = game.messages.get(chatMessageIdVente)
|
|
if (!chatMessage.isOwner) {
|
|
return RdDBaseActor.remoteActorCall({
|
|
userId: Misc.firstConnectedGMId(),
|
|
actorId: this.id,
|
|
method: 'updateMessageVente', args: [chatMessageIdVente, nombreLots]
|
|
})
|
|
}
|
|
await ChatVente.diminuerQuantiteAchatVente(chatMessageIdVente, nombreLots);
|
|
}
|
|
|
|
async vendre(item, quantite, cout, acheteur) {
|
|
await this.ajouterSols(cout, acheteur?.id)
|
|
await this.decrementerQuantiteItem(item.id, quantite)
|
|
}
|
|
|
|
async acheter(item, quantite, cout, achat) {
|
|
await this.depenserSols(cout)
|
|
if (!this.isOwner) {
|
|
ui.notifications.warn(`${game.user.id} n'est pas propriétaire de ${this.name} et effectue un achat!`)
|
|
}
|
|
const createdItemId = await this.creerQuantiteItem(item, quantite)
|
|
if (item.type == ITEM_TYPES.nourritureboisson && achat.choix.consommer && createdItemId != undefined) {
|
|
achat.choix.doses = achat.choix.nombreLots;
|
|
await this.consommerNourritureboisson(createdItemId, achat.choix, achat.vente.actingUserId);
|
|
}
|
|
if (item.type == ITEM_TYPES.service) {
|
|
new Apprecier(this, item.system.appreciation, item.system.qualite).apprecier()
|
|
}
|
|
}
|
|
|
|
verifierFortune(cout) {
|
|
return this.getFortune() >= cout;
|
|
}
|
|
|
|
verifierQuantite(item, quantiteDemande) {
|
|
const disponible = this.getQuantiteDisponible(item);
|
|
return disponible == undefined || disponible >= quantiteDemande;
|
|
}
|
|
|
|
async consommerNourritureboisson(itemId, choix, userId) { }
|
|
|
|
async decrementerQuantiteItem(itemId, quantite, options = { supprimerSiZero: true }) {
|
|
const item = this.items.get(itemId)
|
|
if (item.isService()) {
|
|
return
|
|
}
|
|
|
|
if (!this.isOwner) {
|
|
return RdDBaseActor.remoteActorCall({
|
|
userId: Misc.connectedGMOrUser(),
|
|
tokenId: this.token?.id,
|
|
actorId: this.id,
|
|
method: 'decrementerQuantiteItem', args: [itemId, quantite, options]
|
|
})
|
|
}
|
|
|
|
const resteQuantite = Math.max((item.system.quantite ?? 1) - quantite, 0)
|
|
if (resteQuantite <= 0 && options.supprimerSiZero) {
|
|
await this.deleteEmbeddedDocuments("Item", [itemId]);
|
|
}
|
|
else {
|
|
await this.updateEmbeddedDocuments("Item", [{ _id: itemId, 'system.quantite': resteQuantite }])
|
|
}
|
|
}
|
|
|
|
async creerQuantiteItem(item, quantite) {
|
|
if (!this.canReceive(item)) {
|
|
return
|
|
}
|
|
|
|
const isItemEmpilable = "quantite" in item.system;
|
|
const baseItem = {
|
|
type: item.type,
|
|
img: item.img,
|
|
name: item.name,
|
|
system: foundry.utils.mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined }, { inplace: false })
|
|
}
|
|
|
|
const newItems = isItemEmpilable ? [baseItem] : Array.from({ length: quantite }, (_, i) => baseItem);
|
|
const items = await this.createEmbeddedDocuments("Item", newItems);
|
|
return newItems.length > 0 ? items[0].id : undefined;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async computeEncTotal() {
|
|
if (this.pack) {
|
|
this.encTotal = 0
|
|
}
|
|
else {
|
|
const wasSurenc = this.isSurenc()
|
|
this.encTotal = this.items.filter(it => RdDItem.getItemTypesInventaire().includes(it.type))
|
|
.map(it => it.getEncTotal()).reduce(Misc.sum(), 0)
|
|
const isSurenc = this.isSurenc()
|
|
if (isSurenc != wasSurenc) {
|
|
await this.setEffect(STATUSES.StatusSurEnc, isSurenc)
|
|
}
|
|
}
|
|
}
|
|
|
|
getEncTotal() {
|
|
return Number(this.encTotal?.toFixed(2) ?? 0.00)
|
|
}
|
|
|
|
async createItem(type, name = undefined) {
|
|
if (!name) {
|
|
name = 'Nouveau ' + Misc.typeName('Item', type);
|
|
}
|
|
await this.createEmbeddedDocuments('Item', [{ name: name, type: type }], { renderSheet: true });
|
|
}
|
|
|
|
canReceive(item) {
|
|
return false;
|
|
}
|
|
|
|
async processDropItem(params) {
|
|
const targetActorId = this.id
|
|
const sourceActorId = params.sourceActorId
|
|
const sourceTokenId = params.sourceTokenId
|
|
const itemId = params.itemId
|
|
const destId = params.destId
|
|
const srcId = params.srcId
|
|
if (sourceActorId && sourceActorId != targetActorId) {
|
|
console.log("Moving objects", sourceActorId, sourceTokenId, targetActorId, itemId);
|
|
this.moveItemsBetweenActors(itemId, sourceActorId, sourceTokenId);
|
|
return false;
|
|
}
|
|
let result = true;
|
|
const item = this.getItem(itemId);
|
|
if (item?.isInventaire('all') && sourceActorId == targetActorId) {
|
|
// rangement
|
|
if (srcId != destId && itemId != destId) { // déplacement de l'objet
|
|
const src = this.getItem(srcId);
|
|
const dest = this.getItem(destId);
|
|
const cible = this.findConteneurOrParent(dest);
|
|
const [empilable, message] = item.isInventaireEmpilable(dest);
|
|
if (empilable) {
|
|
await dest.empiler(item)
|
|
result = false;
|
|
}
|
|
// changer de conteneur
|
|
else if (!cible || this.conteneurPeutContenir(cible, item)) {
|
|
await this.enleverDeConteneur(item, src, params.onEnleverConteneur);
|
|
await this.ajouterDansConteneur(item, cible, params.onAjouterDansConteneur);
|
|
if (message && !dest.isConteneur()) {
|
|
ui.notifications.info(cible
|
|
? `${message}<br>${item.name} a été déplacé dans: ${cible.name}`
|
|
: `${message}<br>${item.name} a été sorti du conteneur`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
await this.computeEncTotal()
|
|
return result;
|
|
}
|
|
|
|
findConteneurOrParent(dest) {
|
|
if (!dest || dest.isConteneur()) {
|
|
return dest;
|
|
}
|
|
return this.findConteneur(dest)
|
|
}
|
|
|
|
findConteneur(item) {
|
|
return this.itemTypes[ITEM_TYPES.conteneur].find(it => it.system.contenu.includes(item.id));
|
|
}
|
|
|
|
|
|
/* -------------------------------------------- */
|
|
conteneurPeutContenir(dest, moved) {
|
|
if (!dest) {
|
|
return true;
|
|
}
|
|
if (!dest.isConteneur()) {
|
|
return false;
|
|
}
|
|
if (moved.isConteneurContenu(dest)) {
|
|
ui.notifications.warn(`Impossible de déplacer un conteneur parent (${moved.name}) dans un de ses contenus ${dest.name} !`);
|
|
return false;
|
|
}
|
|
|
|
// Calculer le total actuel des contenus
|
|
const encContenu = dest.getEncContenu();
|
|
const newEnc = moved.getEncTotal(); // Calculer le total actuel du nouvel objet
|
|
const placeDisponible = Misc.keepDecimals(dest.system.capacite - encContenu - newEnc, 4)
|
|
|
|
// Teste si le conteneur de destination a suffisament de capacité pour recevoir le nouvel objet
|
|
if (placeDisponible < 0) {
|
|
ui.notifications.warn(
|
|
`Le conteneur ${dest.name} a une capacité de ${dest.system.capacite}, et contient déjà ${encContenu}.
|
|
Impossible d'y ranger: ${moved.name} d'encombrement ${newEnc}!`);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** Ajoute un item dans un conteneur, sur la base de leurs ID */
|
|
async ajouterDansConteneur(item, conteneur, onAjouterDansConteneur) {
|
|
if (conteneur?.isConteneur()) {
|
|
item.estContenu = true;
|
|
const nouveauContenu = [...conteneur.system.contenu, item.id];
|
|
await conteneur.update({ 'system.contenu': nouveauContenu });
|
|
onAjouterDansConteneur(item.id, conteneur.id)
|
|
}
|
|
else {
|
|
item.estContenu = false;
|
|
await conteneur?.update({ 'system.-=contenu': undefined })
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/** Fonction de remise à plat de l'équipement (ie vide les champs 'contenu') */
|
|
async nettoyerConteneurs() {
|
|
RdDConfirm.confirmer({
|
|
settingConfirmer: "confirmation-vider",
|
|
content: `<p>Etes vous certain de vouloir vider tous les conteneurs ?</p>`,
|
|
title: 'Vider les conteneurs',
|
|
buttonLabel: 'Vider',
|
|
onAction: async () => {
|
|
const corrections = [];
|
|
for (let item of this.items) {
|
|
if (item.estContenu) {
|
|
item.estContenu = undefined;
|
|
}
|
|
if (item.system.contenu != undefined) {
|
|
if (item.type == 'conteneur') {
|
|
corrections.push({ _id: item.id, 'system.contenu': [] });
|
|
}
|
|
else {
|
|
corrections.push({ _id: item.id, 'system.-=contenu': undefined });
|
|
}
|
|
}
|
|
}
|
|
if (corrections.length > 0) {
|
|
await this.updateEmbeddedDocuments('Item', corrections);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
buildSubConteneurObjetList(conteneurId, deleteList) {
|
|
let conteneur = this.getItem(conteneurId);
|
|
if (conteneur?.type == 'conteneur') { // Si c'est un conteneur
|
|
for (let subId of conteneur.system.contenu) {
|
|
let subObj = this.getItem(subId);
|
|
if (subObj) {
|
|
if (subObj.type == 'conteneur') {
|
|
this.buildSubConteneurObjetList(subId, deleteList);
|
|
}
|
|
deleteList.push({ id: subId, conteneurId: conteneurId });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async deleteAllConteneur(itemId, options) {
|
|
let list = [];
|
|
list.push({ id: itemId, conteneurId: undefined }); // Init list
|
|
this.buildSubConteneurObjetList(itemId, list);
|
|
await this.deleteEmbeddedDocuments('Item', list.map(it => it.id), options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Supprime un item d'un conteneur, sur la base de leurs ID
|
|
*/
|
|
async enleverDeConteneur(item, conteneur, onEnleverDeConteneur) {
|
|
if (conteneur) {
|
|
if (conteneur.isConteneur()) {
|
|
const contenu = conteneur.system.contenu.filter(id => id != item.id);
|
|
await conteneur.update({ 'system.contenu': contenu });
|
|
onEnleverDeConteneur();
|
|
}
|
|
else {
|
|
await conteneur.update({ 'system.-=contenu': undefined })
|
|
}
|
|
}
|
|
item.estContenu = false;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async moveItemsBetweenActors(itemId, sourceActorId, sourceTokenId) {
|
|
let sourceActor = RdDBaseActor.getRealActor(sourceActorId, sourceTokenId)
|
|
let itemsList = [{ id: itemId, conteneurId: undefined }]
|
|
sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list
|
|
|
|
const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id))
|
|
.map(it => foundry.utils.duplicate(it))
|
|
.map(it => { it.system.contenu = []; return it; });
|
|
let newItems = await this.createEmbeddedDocuments('Item', itemsDataToCreate);
|
|
|
|
let itemMap = this._buildMapOldNewId(itemsList, newItems);
|
|
|
|
for (let item of itemsList) { // Second boucle pour traiter la remise en conteneurs
|
|
// gestion conteneur/contenu
|
|
if (item.conteneurId) { // l'Objet était dans un conteneur
|
|
const newConteneurId = itemMap[item.conteneurId];
|
|
const newConteneur = this.getItem(newConteneurId);
|
|
const newItemId = itemMap[item.id]; // Get newItem
|
|
|
|
console.log('New conteneur filling!', newConteneur, newItemId, item);
|
|
const nouveauContenu = [...newConteneur.system.contenu, newItemId]
|
|
await newConteneur.update({ 'system.contenu': nouveauContenu })
|
|
}
|
|
}
|
|
const deletedItemIds = itemsList.map(it => it.id)
|
|
await sourceActor.deleteEmbeddedDocuments('Item', deletedItemIds);
|
|
}
|
|
|
|
_buildMapOldNewId(itemsList, newItems) {
|
|
let itemMap = {};
|
|
for (let i = 0; i < itemsList.length; i++) {
|
|
itemMap[itemsList[i].id] = newItems[i].id; // Pour garder le lien ancien / nouveau
|
|
}
|
|
return itemMap;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
async postActorToChat(modeOverride) {
|
|
let chatData = {
|
|
doctype: 'Actor',
|
|
id: this.id,
|
|
type: this.type,
|
|
img: this.img,
|
|
pack: this.pack,
|
|
name: this.getAlias(),
|
|
system: { description: this.system.description }
|
|
}
|
|
renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-actor.hbs', chatData)
|
|
.then(async html => await ChatMessage.create(RdDUtility.chatDataSetup(html, modeOverride)));
|
|
}
|
|
|
|
actionImpossible(action) {
|
|
ui.notifications.info(`${this.getAlias()} ne peut pas faire cette action: ${action}`)
|
|
}
|
|
|
|
ajoutExperience(rollData) { }
|
|
isAlcoolise() { return false }
|
|
async jetEthylisme() { this.actionImpossible("jet d'éthylisme") }
|
|
async rollAppelChance() { this.actionImpossible("appel à la chance") }
|
|
async jetDeMoral() { this.actionImpossible("jet de moral") }
|
|
|
|
async resetItemUse() { }
|
|
async incDecItemUse(itemId, shouldIncrease = true) { }
|
|
getItemUse(itemId) { return 0; }
|
|
|
|
async finDeRound(options = { terminer: false }) { }
|
|
isActorCombat() { return false }
|
|
getCaracInit(competence) { return 0 }
|
|
|
|
listAttaques() {
|
|
return this.listActions({ isAttaque: true, isEquipe: false })
|
|
}
|
|
|
|
listActions({ isAttaque = false, isEquipe = false }) { return [] }
|
|
|
|
listActionsPossessions() {
|
|
return this.itemTypes[ITEM_TYPES.possession]
|
|
.filter(it => !it.system.possede)
|
|
.map(p => {
|
|
return {
|
|
label: p.name,
|
|
action: 'possession',
|
|
possessionid: p.system.possessionid,
|
|
}
|
|
})
|
|
}
|
|
|
|
} |