Initial import

This commit is contained in:
2022-01-09 09:33:47 +01:00
parent 1ed90e1945
commit 1a36fcf95b
102 changed files with 6050 additions and 95 deletions

View File

@ -0,0 +1,220 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { YggdrasillUtility } from "./yggdrasill-utility.js";
/* -------------------------------------------- */
export class YggdrasillActorSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["yggdrasill", "sheet", "actor"],
template: "systems/fvtt-yggdrasill/templates/actor-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
});
}
/* -------------------------------------------- */
getData() {
const objectData = YggdrasillUtility.data(this.object);
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(YggdrasillUtility.templateData(this.object)),
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
isEpuise: this.actor.isEpuise(),
isBlesse: this.actor.isBlesse(),
isMeurtri: this.actor.isMeurtri(),
competencesGenerales: this.actor.getCompetencesGenerales(),
competencesMartiales: this.actor.getCompetencesMartiales(),
competencesMagiques: this.actor.getCompetencesMagiques(),
dons: this.actor.getDons(),
faiblesses: this.actor.getFaiblesses(),
blessures: this.actor.getBlessures(),
armes: this.actor.getArmes(),
armures: this.actor.getArmures(),
prouessesMartiales: this.actor.getProuessesMartiales(),
equipements: this.actor.getToutEquipements(),
effetsmagiques: this.actor.getEffetsMagiques(),
effetsRunes: this.actor.getEffetsDeRunes(),
encTotal: this.actor.getEncTotal(),
protectionTotal: this.actor.getProtectionTotal(),
monnaies: this.actor.getMonnaies(),
sortsSejdr:this.actor.getSortsSejdr(),
sortsGaldr:this.actor.getSortsGaldr(),
runes: this.actor.getRunes(),
optionsDMDP: YggdrasillUtility.createDirectSortedOptionList(-10, +10),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsFuror: YggdrasillUtility.createDirectOptionList(0, 15),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM
}
// Dynamic update some fields
this.updateDM(formData.data);
this.updateDP(formData.data);
console.log("YGG : ", formData);
return formData;
}
/* -------------------------------------------- */
updateDM( data ) {
let dm = data.caracsecondaire.defensemen;
dm.total = dm.max + Number(dm.bonusmalus);
}
/* -------------------------------------------- */
updateDP( data ) {
let dp = data.caracsecondaire.defensephy;
dp.total = dp.max + Number(dp.bonusmalus);
dp.total += (dp.bouclierequipe) ? 3 : 0;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
item.sheet.render(true);
});
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
YggdrasillUtility.confirmDelete(this, li);
});
html.find('#isEpuise').click(event => {
this.actor.toggleEpuise( );
} );
html.find('.munition-moins').click(event => {
const li = $(event.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
this.actor.decrementeMunition( item );
} );
html.find('.munition-plus').click(event => {
const li = $(event.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
this.actor.incrementeMunition( item );
} );
html.find('.equipement-moins').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.decrementeQuantite( li.data("item-id") );
} );
html.find('.equipement-plus').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.incrementeQuantite( li.data("item-id") );
} );
html.find('.combat-label a').click((event) => {
let combatName = event.currentTarget.attributes.name.value;
this.actor.rollCombat(combatName);
});
html.find('.magie-label a').click((event) => {
let magieName = event.currentTarget.attributes.name.value;
this.actor.rollMagie(magieName);
});
html.find('.competence-label a').click((event) => {
const li = $(event.currentTarget).parents(".item");
const competenceId = li.data("item-id");
this.actor.rollCompetence(competenceId);
});
html.find('.technique-label a').click((event) => {
const li = $(event.currentTarget).parents(".item");
const techniqueId = li.data("item-id");
this.checkTechnique(techniqueId);
});
html.find('.sort-sejdr').click((event) => {
const li = $(event.currentTarget).parents(".item");
const sortId = li.data("item-id");
this.actor.rollSort(sortId, "sejdr");
});
html.find('.sort-galdr').click((event) => {
const li = $(event.currentTarget).parents(".item");
const sortId = li.data("item-id");
this.actor.rollSort(sortId, "galdr");
});
html.find('.sort-rune').click((event) => {
const li = $(event.currentTarget).parents(".item");
const sortId = li.data("item-id");
this.actor.rollSort(sortId, "rune");
});
html.find('.arme-label a').click((event) => {
const li = $(event.currentTarget).parents(".item");
const armeId = li.data("arme-id");
this.actor.rollArme(armeId);
});
html.find('.carac-roll').click((event) => {
const li = $(event.currentTarget).parents(".item");
let categ = li.data("carac-categ");
let carac = li.data("carac-key");
this.actor.rollCarac(categ, carac);
});
html.find('.weapon-damage').click((event) => {
const li = $(event.currentTarget).parents(".item");
const weapon = this.actor.getOwnedItem(li.data("item-id"));
this.actor.rollDamage(weapon, 'damage');
});
html.find('.competence-base').change((event) => {
const li = $(event.currentTarget).parents(".item");
const compId = li.data("item-id");
this.actor.updateCompetence(compId, parseInt(event.target.value));
});
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
html.find('.item-link a').click((event) => {
const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.getOwnedItem(itemId);
item.sheet.render(true);
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equiperObject( li.data("item-id") );
this.render(true);
});
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Update the Actor
return this.object.update(formData);
}
}

758
modules/yggdrasill-actor.js Normal file
View File

@ -0,0 +1,758 @@
/* -------------------------------------------- */
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillRoll } from "./yggdrasill-roll-dialog.js";
/* -------------------------------------------- */
const statusEffects = [
{ yggdrasill: true, id: 'epuise', label: 'Epuisé', icon: 'icons/svg/stoned.svg' },
{ yggdrasill: true, id: 'blesse', label: 'Blessé', icon: 'icons/svg/blood.svg' },
{ yggdrasill: true, id: 'meurtri', label: 'Meurtri', icon: 'icons/svg/falling.svg' }
]
const armeCategorieToCompetence = { "lutte": "Lutte", "improvisee": "Armes Improvisées", "courte":"Armes courtes", "longue": "Armes longues", "deuxmains": "Armes à deux mains",
"hast": "Armes d'Hast", "tir": "Armes de tir", "jet": "Lancer" }
const attackMode = {
"classique": {
"categName": "corps",
"caracName": "agilite",
"malus": 0,
"protection": 0,
"bonusdegats": 0,
"label": "Attaque Classique",
"description": "Attaque classique"
},
"force": {
"categName": "corps",
"caracName": "puissance",
"malus": 0,
"protection": 0,
"bonusdegats": "puissance;1",
"label": "Attaque en Force",
"description": "Attaque en Force : Malus: 0, +PUI en dégats"
},
"devastatrice": {
"categName": "corps",
"caracName": "puissance",
"malus": "puissance;1",
"bonusdegats": "puissance;3",
"protection": 0,
"label": "Attaque Dévastatrice",
"description": "Attaque Dévastratrice : Malus -PUI, +PUI*3 en dégats"
},
"precise": {
"categName": "esprit",
"caracName": "perception",
"malus": "0",
"bonusdegats": 0,
"protection": "perception;1",
"label": "Attaque Précise",
"description": "Attaque précise : Malus : 0, protection réduite de -PER"
},
"visee": {
"categName": "esprit",
"caracName": "perception",
"malus": "perception;1",
"bonusdegats": 0,
"protection": "perception;3",
"label": "Attaque Visée",
"description": "Attaque visée : Malus : -PER, protection réduite de -PER"
}
}
const tirMode = {
"pose": {
"categName": "corps",
"caracName": "agilite",
"malus": 0,
"protection": 0,
"bonusdegats": 0,
"label": "Tir posé",
"description": "Tir posé"
},
"arrettir": {
"categName": "ame",
"caracName": "instinct",
"malus": 0,
"protection": 0,
"bonusdegats": "instinct;1",
"label": "Tir d'Arrêt (Tir)",
"description": "Tir d'Arrêt (Tir) : Malus: 0, +INS en dégats"
},
"arretjet": {
"categName": "corps",
"caracName": "puissance",
"malus": 0,
"protection": 0,
"bonusdegats": "puissance;1",
"label": "Tir d'Arrêt (Jet)",
"description": "Tir d'Arrêt (Jet) : Malus: 0, +PUI en dégats"
},
"impacttir": {
"categName": "ame",
"caracName": "instinct",
"malus": "instinct;1",
"protection": 0,
"bonusdegats": "instinct;3",
"label": "Tir d'Impact (Tir)",
"description": "Tir d'Impact (Tir) : Malus: -INS, +INS*3 en dégats"
},
"impactjet": {
"categName": "corps",
"caracName": "puissance",
"malus": "puissance;1",
"protection": 0,
"bonusdegats": "puissance;3",
"label": "Attaque d'Impact (Jet)",
"description": "Attaque d'Impact (Jet) : Malus: -PUI, +PUI*3 en dégats"
},
"precision": {
"categName": "esprit",
"caracName": "perception",
"malus": "0",
"bonusdegats": 0,
"protection": "perception;1",
"label": "Tir de Précision",
"description": "Tir de Précision : Malus : 0, protection réduite de -PER"
},
"vise": {
"categName": "esprit",
"caracName": "perception",
"malus": "perception;1",
"bonusdegats": 0,
"protection": "perception;3",
"label": "Tir Visée",
"description": "Tir visée : Malus : -PER, protection réduite de -PER"
}
}
/* -------------------------------------------- */
/* -------------------------------------------- */
/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
/**
* Override the create() function to provide additional SoS functionality.
*
* This overrided create() function adds initial items
* Namely: Basic skills, money,
*
* @param {Object} data Barebones actor data which this function adds onto.
* @param {Object} options (Unused) Additional options which customize the creation workflow.
*
*/
static async create(data, options) {
// Case of compendium global import
if (data instanceof Array) {
return super.create(data, options);
}
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
if (data.items) {
let actor = super.create(data, options);
return actor;
}
const competencesGen = await YggdrasillUtility.loadCompendium("fvtt-yggdrasill.competences-generales");
const competencesMar = await YggdrasillUtility.loadCompendium("fvtt-yggdrasill.competences-martiales");
const competencesMag = await YggdrasillUtility.loadCompendium("fvtt-yggdrasill.competences-magiques");
const competences = competencesGen.concat(competencesMar).concat(competencesMag);
data.items = competences.map(i => i.toObject());
return super.create(data, options);
}
/* -------------------------------------------- */
prepareBaseData() {
if ( this.type == "personnage") {
this.computeCaracSecondaire();
}
}
/* -------------------------------------------- */
async prepareData() {
if ( this.type == "personnage") {
this.computeCaracSecondaire();
if (this.data.data.furor.value == 0)
await this.setEpuise();
else
await this.cleanEpuise();
if ( this.data.data.caracsecondaire.pv.value < (this.data.data.caracsecondaire.pv.max/4) )
await this.setMeurtri();
else
await this.cleanMeurtri();
if ( this.data.data.caracsecondaire.pv.value < (this.data.data.caracsecondaire.pv.max/2) )
await this.setBlesse();
else
await this.cleanBlesse();
}
super.prepareData();
}
/* -------------------------------------------- */
_preUpdate(changed, options, user) {
if ( changed.data?.caracsecondaire?.pv?.value ) {
if ( changed.data.caracsecondaire.pv.value < 0 )
changed.data.caracsecondaire.pv.value = 0;
if ( changed.data.caracsecondaire.pv.value > this.data.data.caracsecondaire.pv.max )
changed.data.caracsecondaire.pv.value = this.data.data.caracsecondaire.pv.max;
}
if ( changed.data?.furor?.value ) {
if ( changed.data.furor.value < 0 )
changed.data.furor.value = 0;
if ( changed.data.furor.value > this.data.data.furor.max )
changed.data.furor.value = this.data.data.furor.max;
}
super._preUpdate(changed, options, user);
}
/* -------------------------------------------- */
getCompetences() {
let comp = this.data.items.filter( item => item.type == 'competence');
return comp;
}
/* -------------------------------------------- */
compareName( a, b) {
if ( a.name < b.name ) {
return -1;
}
if ( a.name > b.name ) {
return 1;
}
return 0;
}
/* -------------------------------------------- */
getInitiativeScore() {
return this.data.data.caracsecondaire.reaction.max;
}
/* -------------------------------------------- */
getCompetencesGenerales() {
let comp = this.data.items.filter( item => item.type == 'competence' && item.data.data.categorie == 'generale');
return comp.sort( this.compareName );
}
/* -------------------------------------------- */
getCompetencesMartiales() {
let comp = this.data.items.filter( item => item.type == 'competence' && item.data.data.categorie == 'martiale');
return comp.sort( this.compareName );
}
/* -------------------------------------------- */
getCompetencesMagiques() {
let comp = this.data.items.filter( item => item.type == 'competence' && item.data.data.categorie == 'magique');
return comp.sort( this.compareName );
}
/* -------------------------------------------- */
getDons( ) {
let dons = this.data.items.filter( item => item.type == 'don');
return dons.sort( this.compareName );
}
/* -------------------------------------------- */
getEffetsMagiques( ) {
let effets = this.data.items.filter( item => item.type == 'effetmagique');
return effets.sort( this.compareName );
}
/* -------------------------------------------- */
getEffetsDeRunes( ) {
let effets = this.data.items.filter( item => item.type == 'effetderune');
return effets.sort( this.compareName );
}
/* -------------------------------------------- */
getMonnaies( ) {
let monnaies = this.data.items.filter( item => item.type == 'monnaie');
return monnaies.sort( this.compareName );
}
/* -------------------------------------------- */
getFaiblesses( ) {
let faib = this.data.items.filter( item => item.type == 'faiblesse');
return faib.sort( this.compareName );
}
/* -------------------------------------------- */
getBlessures( ) {
return this.data.items.filter( item => item.type == 'blessure');
}
/* -------------------------------------------- */
getToutEquipements() {
return this.data.items.filter( item => item.type == 'equipement' || item.type == 'armure' || item.type == 'armecc' || item.type == 'armedist');
}
/* -------------------------------------------- */
getArmes() {
return this.data.items.filter( item => (item.type == 'armecc' || item.type == 'armedist') && item.data.data.equipe );
}
/* -------------------------------------------- */
getArmures() {
return this.data.items.filter( item => item.type == 'armure' && item.data.data.equipe );
}
getProuessesMartiales() {
let prouesse = this.data.items.filter( item => item.type == 'prouesse' );
return prouesse.sort( this.compareName );
}
getSortsSejdr() {
let sort = this.data.items.filter( item => item.type == 'sortsejdr' );
return sort.sort( this.compareName );
}
getSortsGaldr() {
let sort = this.data.items.filter( item => item.type == 'sortgaldr' );
return sort.sort( this.compareName );
}
getRunes() {
let sort = this.data.items.filter( item => item.type == 'rune' );
return sort.sort( this.compareName );
}
/* -------------------------------------------- */
async setEpuise( ) {
await this.update({ 'data.status.epuise': true});
this.data.data.status.epuise = true;
/*let effect = this.getEffectByLabel('Epuisé');
if ( !effect ) {
let effect = statusEffects.find( ef => ef.id == 'epuise');
await this.createEmbeddedDocuments("ActiveEffect", [ effect ] );
}*/
}
/* -------------------------------------------- */
async cleanEpuise() {
await this.update({ 'data.status.epuise': false});
this.data.data.status.epuise = false;
/*let effect = this.getEffectByLabel('Epuisé');
if ( effect ) {
await this.deleteEmbeddedDocuments("ActiveEffect", [ effect.id ]);
}*/
}
/* -------------------------------------------- */
async toggleEpuise( ) {
if ( this.data.data.status.epuise ) {
await this.cleanEpuise();
} else {
await this.setEpuise();
}
}
/* -------------------------------------------- */
isEpuise() {
return this.data.data.status.epuise;
}
/* -------------------------------------------- */
async setBlesse( ) {
await this.update({ 'data.status.blesse': true} );
this.data.data.status.blesse = true;
console.log("BLESSSE !!!!");
/*let effect = this.getEffectByLabel('Blessé');
if ( !effect ) {
let effect = statusEffects.find( ef => ef.id == 'blesse');
await this.createEmbeddedDocuments("ActiveEffect", [ effect ] );
}*/
}
/* -------------------------------------------- */
async cleanBlesse() {
await this.update({ 'data.status.blesse': false} );
this.data.data.status.blesse = false;
/*let effect = this.getEffectByLabel('Blessé');
if ( effect ) {
await this.deleteEmbeddedDocuments("ActiveEffect", [ effect.id ]);
}*/
}
/* -------------------------------------------- */
isBlesse() {
return this.data.data.status.blesse;
//return this.getEffectByLabel('Blessé');
}
/* -------------------------------------------- */
async setMeurtri( ) {
await this.setBlesse();
await this.update({ 'data.status.meurtri': true});
this.data.data.status.meurtri = true;
}
/* -------------------------------------------- */
async cleanMeurtri() {
await this.update({ 'data.status.meurtri': false});
this.data.data.status.meurtri = false;
}
/* -------------------------------------------- */
isMeurtri() {
return this.data.data.status.meurtri;
}
/* -------------------------------------------- */
async decrementFuror( nbFuror) {
await this.update( { 'data.furor.value': this.data.data.furor.value - nbFuror } );
}
/* -------------------------------------------- */
getCurrentFuror() {
return this.data.data.furor.value;
}
/* -------------------------------------------- */
getActiveEffects(matching = it => true) {
let array = Array.from(this.getEmbeddedCollection("ActiveEffect").values());
return Array.from(this.getEmbeddedCollection("ActiveEffect").values()).filter(it => matching(it));
}
/* -------------------------------------------- */
getEffectByLabel(label) {
return this.getActiveEffects().find(it => it.data.label == label);
}
/* -------------------------------------------- */
getEffectById(id) {
return this.getActiveEffects().find(it => it.id == id);
}
/* -------------------------------------------- */
getCarac( caracName ) {
for( let key in this.data.data.carac) {
let categ = this.data.data.carac[key];
for( let carac in categ.carac) {
if (carac.toLowerCase() == caracName.toLowerCase() ) {
return deepClone(categ.carac[carac]);
}
}
}
}
/* -------------------------------------------- */
computeCaracSecondaire( ) {
if ( this.type == "personnage") {
let basecorps = this.data.data.carac.corps.carac;
let sumcorps = basecorps.puissance.value + basecorps.agilite.value + basecorps.vigueur.value
let baseesprit = this.data.data.carac.esprit.carac;
let sumesprit = baseesprit.intellect.value + baseesprit.perception.value + baseesprit.tenacite.value
let baseame = this.data.data.carac.ame.carac;
let sumame = baseame.charisme.value + baseame.communication.value + baseame.instinct.value
let newPV = (sumcorps*3) + (sumesprit *2) + sumame;
if ( newPV != this.data.data.caracsecondaire.pv.max) {
this.data.data.caracsecondaire.pv.max = newPV;
this.update( { 'data.caracsecondaire.pv.max': newPV });
}
this.data.data.caracsecondaire.reaction.value = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
this.data.data.caracsecondaire.reaction.max = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
this.data.data.caracsecondaire.defensephy.value = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
this.data.data.caracsecondaire.defensephy.max = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
this.data.data.caracsecondaire.defensemen.value = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
this.data.data.caracsecondaire.defensemen.max = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
this.data.data.caracsecondaire.deplacement.value = basecorps.agilite.value + basecorps.vigueur.value;
this.data.data.caracsecondaire.deplacement.max = basecorps.agilite.value + basecorps.vigueur.value;
this.data.data.caracsecondaire.capaenc.value = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
this.data.data.caracsecondaire.capaenc.max = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
}
}
/* -------------------------------------------- */
async equiperObject( equipementId ) {
let item = this.data.items.find( item => item.id == equipementId );
if (item && item.data.data) {
let update = { _id: item.id, "data.equipe": !item.data.data.equipe };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async updateCompetence( compId, niveau) {
let comp = this.data.items.find( item => item.type == 'competence' && item.id == compId);
console.log("Comp updated!!!!", compId, niveau);
if (comp) {
const update = { _id: comp.id, 'data.niveau': niveau };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
} else {
ui.notifications.warn("Compétence inconnue", compId)
}
}
/* -------------------------------------------- */
buildListeActionsCombat( ) {
let armes = [];
}
/* -------------------------------------------- */
async rollAttribute( attrkey, subAttrKey = 'defaut') {
let attr = duplicate(this.data.data.attributs[attrkey]);
let subAttr = duplicate(this.data.data.attributs[attrkey].values[subAttrKey] );
console.log("ATTR : ", attr, subAttr);
if ( attr ) {
subAttr.label = subAttr.label || "";
let title = `Attribut : ${attr.label} ${subAttr.label} : ${subAttr.value}`;
let rollData = {
mode: "attribut",
alias: this.name,
actorImg: this.img,
actorId: this.id,
attr: attr,
valuePhysique: this.data.data.attributs["physique"].values["defaut"].value,
subAttr: subAttr,
rollMode: game.settings.get("core", "rollMode"),
title: title,
isBlesse: this.data.data.etat.etat == "blesse",
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
bonusMalus: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 0
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
} else {
ui.notifications.warn("Attribut non trouvée");
}
}
/* -------------------------------------------- */
async rollCarac( categName, caracName) {
let carac = duplicate(this.data.data.carac[categName].carac[caracName]);
console.log("CARAC : ", carac, this.data.data.carac);
if ( carac) {
let rollData = {
mode: "carac",
alias: this.name,
actorImg: this.img,
actorId: this.id,
img: `systems/fvtt-yggdrasill/images/icons/icon_carac_${categName}.png`,
rollMode: game.settings.get("core", "rollMode"),
title: `Caractéristique ${carac.label} : ${carac.value}`,
selectedCarac: carac,
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 0
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
} else {
ui.notifications.warn("Caractéristique non trouvée");
}
}
/* -------------------------------------------- */
async rollCompetence( competenceId ) {
let competence = this.data.items.find( item => item.type == 'competence' && item.id == competenceId);
if ( competence) {
let rollData = {
mode: "competence",
alias: this.name,
actorImg: this.img,
actorId: this.id,
img: competence.img,
rollMode: game.settings.get("core", "rollMode"),
title: `Compétence ${competence.name} : ${competence.data.data.niveau}`,
competence: duplicate(competence),
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 0
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
} else {
ui.notifications.warn("Compétence non trouvée");
}
}
/* -------------------------------------------- */
getAttaqueData( mode ) {
let attackData = duplicate(attackMode[mode]);
if ( attackData){
attackData.mode = mode;
attackData.carac = duplicate(this.data.data.carac[attackData.categName].carac[attackData.caracName]);
if ( attackData.malus != 0) {
let malusTab = attackData.malus.split(';');
attackData.malus = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.protection != 0) {
let malusTab = attackData.protection.split(';');
attackData.protection = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.bonusdegats != 0) {
let malusTab = attackData.bonusdegats.split(';');
attackData.bonusdegats = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
}
return attackData;
}
/* -------------------------------------------- */
getTirData( mode) {
let attackData = duplicate( tirMode[mode] );
if ( attackData){
attackData.mode = mode;
attackData.carac = duplicate(this.data.data.carac[attackData.categName].carac[attackData.caracName]);
if ( attackData.malus != 0) {
let malusTab = attackData.malus.split(';');
attackData.malus = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.protection != 0) {
let malusTab = attackData.protection.split(';');
attackData.protection = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
if ( attackData.bonusdegats != 0) {
let malusTab = attackData.bonusdegats.split(';');
attackData.bonusdegats = this.data.data.carac[attackData.categName].carac[malusTab[0]].value * Number(malusTab[1])
}
}
return attackData;
}
/* -------------------------------------------- */
async rollSort( sortId, magie) {
let sort = this.data.items.find( item => item.id == sortId);
let competence = this.data.items.find( item => item.type == 'competence' && item.name.toLowerCase().includes(magie));
console.log("SORT :", sortId, sort, competence );
let carac;
if ( magie == "sejdr") {
carac = duplicate(this.data.data.carac.ame.carac.instinct);
} else if ( magie == "rune") {
carac = duplicate(this.data.data.carac.ame.carac.communication);
} else {
carac = duplicate(this.data.data.carac.ame.carac.charisme);
}
if ( sort && competence) {
let rollData = {
mode: magie,
alias: this.name,
actorImg: this.img,
actorId: this.id,
img: sort.img,
rollMode: game.settings.get("core", "rollMode"),
title: magie + " - " + sort.name,
selectedCarac: carac,
agiliteCarac: duplicate(this.data.data.carac.corps.carac.agilite),
instinctCarac: duplicate(this.data.data.carac.ame.carac.instinct),
sort: duplicate(sort),
competence: duplicate(competence),
dureeGaldr: "1d5a",
nbCibles: "1",
zoneGaldr: "INS10cm3",
bonusdefense: 0,
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
optionsBD: YggdrasillUtility.buildListOptions(0, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 14,
puissanceRune: 1,
optionsPuissanceRune: YggdrasillUtility.buildListOptions(1, 15),
supportRune: "peau",
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
} else {
ui.notifications.warn("Sortilège ou Compétence non trouvée !", sort, compName);
}
}
/* -------------------------------------------- */
async rollArme( armeId ) {
let arme = this.data.items.find( item => item.id == armeId);
let compName = armeCategorieToCompetence[arme.data.data.categorie];
let competence = this.data.items.find( item => item.type == 'competence' && item.name == compName);
console.log("ARME :", armeId, arme, competence );
if ( arme && competence) {
let attackDef
if (arme.type == 'armecc') {
attackDef = this.getAttaqueData("classique");
} else {
attackDef = this.getTirData("pose");
}
let rollData = {
mode: arme.type,
attackDef: attackDef,
alias: this.name,
actorImg: this.img,
actorId: this.id,
img: competence.img,
rollMode: game.settings.get("core", "rollMode"),
title: "Attaque !",
selectedCarac: duplicate(this.data.data.carac.corps.carac.agilite),
arme: duplicate(arme),
competence: duplicate(competence),
bonusdefense: 0,
isEpuise: this.isEpuise(),
isBlesse: this.isBlesse(),
isMeurtri: this.isMeurtri(),
optionsBonusMalus: YggdrasillUtility.buildListOptions(-6, +6),
optionsBD: YggdrasillUtility.buildListOptions(0, +6),
bonusMalus: 0,
optionsFuror: YggdrasillUtility.buildListOptions(0, this.getCurrentFuror() ),
furorUsage: 0,
optionsSR: YggdrasillUtility.buildSROptions( ),
sr: 14
}
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
} else {
ui.notifications.warn("Arme ou Compétence Martiale non trouvée !", arme, compName);
}
}
/* -------------------------------------------- */
getEncTotal( ) {
let encTotal = 0;
for( let item of this.data.items) {
if (item.type == "equipement" || item.type == "armecc"
|| item.type == "armedist" || item.type == "armure" || item.type == "monnaie") {
encTotal += item.data.data.enc;
}
}
return encTotal;
}
/* -------------------------------------------- */
getProtectionTotal( ) {
let protectionTotal = 0;
for( let item of this.data.items) {
if (item.type == "armure" && item.data.data.equipe) {
protectionTotal += Number(item.data.data.protection);
}
}
return protectionTotal;
}
/* -------------------------------------------- */
async incrementeQuantite( objetId ) {
let objetQ = this.data.items.find( item => item.id == objetId );
if (objetQ) {
let newQ = objetQ.data.data.quantite + 1;
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.quantite': newQ }]); // pdates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async decrementeQuantite( objetId ) {
let objetQ = this.data.items.find( item => item.id == objetId );
if (objetQ) {
let newQ = objetQ.data.data.quantite - 1;
newQ = (newQ <= 0) ? 0 : newQ;
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.quantite': newQ }]); // pdates one EmbeddedEntity
}
}
}

View File

@ -0,0 +1,46 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
/* -------------------------------------------- */
export class YggdrasillCombat extends Combat {
/* -------------------------------------------- */
async rollInitiative(ids, formula = undefined, messageOptions = {} ) {
ids = typeof ids === "string" ? [ids] : ids;
const currentId = this.combatant._id;
for (let cId = 0; cId < ids.length; cId++) {
const c = this.combatants.get(ids[cId]);
let initBonus = c.actor ? c.actor.getInitiativeScore() : 0;
//console.log("Init for ", initBonus);
const roll = c.getInitiativeRoll("1d10+"+initBonus);
if ( !roll.total) {
roll.evaluate( {async: false});
}
if (roll.total <= 0) roll.total = 0;
//console.log("Compute init for", roll.total);
let id = c._id || c.id;
await this.updateEmbeddedDocuments("Combatant", [{ _id: id, initiative: roll.total }]);
// Send a chat message
let rollMode = messageOptions.rollMode || game.settings.get("core", "rollMode");
let messageData = mergeObject(
{
speaker: {
scene: canvas.scene._id,
actor: c.actor ? c.actor._id : null,
token: c.token._id,
alias: c.token.name,
sound: CONFIG.sounds.dice,
},
flavor: `${c.token.name} a fait son jet d'Initiative (1d10+${initBonus})
<br>
`,
},
messageOptions
);
roll.toMessage(messageData, { rollMode, create: true });
}
return this;
}
}

View File

@ -0,0 +1,123 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { YggdrasillUtility } from "./yggdrasill-utility.js";
/* -------------------------------------------- */
export class YggdrasillFigurantSheet extends ActorSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["yggdrasill", "sheet", "actor"],
template: "systems/fvtt-yggdrasill/templates/figurant-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
});
}
/* -------------------------------------------- */
getData() {
const objectData = YggdrasillUtility.data(this.object);
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(YggdrasillUtility.templateData(this.object)),
limited: this.object.limited,
equipements: this.actor.getToutEquipements(),
effetsmagiques: this.actor.getEffetsMagiques(),
encTotal: this.actor.getEncTotal(),
monnaies: this.actor.getMonnaies(),
optionsAttr: new Array(21).fill('option'),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM
}
console.log("FIGURANT : ", formData);
return formData;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.actor.getOwnedItem(li.data("item-id"));
item.sheet.render(true);
});
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
YggdrasillUtility.confirmDelete(this, li);
});
html.find('.equipement-moins').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.decrementeQuantite( li.data("item-id") );
} );
html.find('.equipement-plus').click(event => {
const li = $(event.currentTarget).parents(".item");
this.actor.incrementeQuantite( li.data("item-id") );
} );
html.find('.attribut-roll').click((event) => {
const li = $(event.currentTarget).parents(".item");
let attrKey = li.data("attr-key");
let attrSubKey = $(event.currentTarget).data("attr-sub-key");
this.actor.rollAttribute(attrKey, attrSubKey);
});
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
html.find('.item-link a').click((event) => {
const itemId = $(event.currentTarget).data("item-id");
const item = this.actor.getOwnedItem(itemId);
item.sheet.render(true);
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equiperObject( li.data("item-id") );
this.render(true);
});
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
// Update the Actor
return this.object.update(formData);
}
}

View File

@ -0,0 +1,101 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class YggdrasillItemSheet extends ItemSheet {
/** @override */
static get defaultOptions() {
return mergeObject(super.defaultOptions, {
classes: ["fvtt-yggdrasill", "sheet", "item"],
template: "systems/fvtt-yggdrasill/templates/item-sheet.html",
width: 550,
height: 550
//tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => {}
})
return buttons
}
/* -------------------------------------------- */
/** @override */
setPosition(options={}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
async getData() {
const objectData = YggdrasillUtility.data(this.object);
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(YggdrasillUtility.templateData(this.object)),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsNiveaux4: YggdrasillUtility.buildListOptions(1, 5),
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
isGM: game.user.isGM
}
return formData;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.object.options.actor.getOwnedItem(li.data("item-id"));
item.sheet.render(true);
});
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.object.options.actor.deleteOwnedItem( li.data("item-id") ).then( this.render(true));
});
}
/* -------------------------------------------- */
get template()
{
let type = this.item.type;
return `systems/fvtt-yggdrasill/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
}
}

100
modules/yggdrasill-main.js Normal file
View File

@ -0,0 +1,100 @@
/**
* YggDrasill system
* Author: Uberwald
* Software License: Prop
*/
/* -------------------------------------------- */
/* -------------------------------------------- */
// Import Modules
import { YggdrasillActor } from "./yggdrasill-actor.js";
import { YggdrasillItemSheet } from "./yggdrasill-item-sheet.js";
import { YggdrasillActorSheet } from "./yggdrasill-actor-sheet.js";
import { YggdrasillFigurantSheet } from "./yggdrasill-figurant-sheet.js";
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillCombat } from "./yggdrasill-combat.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
/************************************************************************************/
Hooks.once("init", async function () {
console.log(`Initializing Yggdrasill`);
/* -------------------------------------------- */
// preload handlebars templates
YggdrasillUtility.preloadHandlebarsTemplates();
/* -------------------------------------------- */
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1d20",
decimals: 0
};
/* -------------------------------------------- */
game.socket.on("system.fvtt-yggdrasill", data => {
YggdrasillUtility.onSocketMesssage(data);
});
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Actor.documentClass = YggdrasillActor;
CONFIG.Combat.documentClass = YggdrasillCombat;
CONFIG.Yggdrasill = {
}
/* -------------------------------------------- */
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("fvtt-yggdrasill", YggdrasillActorSheet, { types: ["personnage"], makeDefault: true });
Actors.registerSheet("fvtt-yggdrasill", YggdrasillFigurantSheet, { types: ["figurant"], makeDefault: false });
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("fvtt-yggdrasill", YggdrasillItemSheet, { makeDefault: true });
// Init/registers
Hooks.on('renderChatLog', (log, html, data) => {
//YggdrasillUtility.registerChatCallbacks(html);
});
});
/* -------------------------------------------- */
function welcomeMessage() {
//ChatUtility.removeMyChatMessageContaining('<div id="welcome-message-sos">');
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-yggdrasill"><span class="rdd-roll-part">Bienvenue !</div>
` });
}
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.once("ready", function () {
// User warning
if (!game.user.isGM && game.user.character == undefined) {
ui.notifications.info("Attention ! Vous n'est connecté à aucun personnage");
ChatMessage.create({
content: "<b>WARNING</b> Le joueur " + game.user.name + " n'est pas connecté à un personnage !",
user: game.user._id
});
}
welcomeMessage();
});
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') {
let regExp = /(\S+)/g;
let commands = content.toLowerCase().match(regExp);
console.log(commands);
}
return true;
});

View File

@ -0,0 +1,207 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15};
const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15};
const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15};
export class YggdrasillRoll extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let html
let h = 440;
if ( rollData.mode == "competence") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-competence.html', rollData);
h = 340;
} else if (rollData.mode == "carac") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-carac.html', rollData);
h = 320;
} else if (rollData.mode == "attribut") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-attribut.html', rollData);
h = 320;
} else if (rollData.mode == "armecc") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armecc.html', rollData);
} else if (rollData.mode == "sejdr") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-sejdr.html', rollData);
} else if (rollData.mode == "rune") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-rune.html', rollData);
} else if (rollData.mode == "galdr") {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-galdr.html', rollData);
} else {
html = await renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armetir.html', rollData);
}
let options = { classes: ["yggdrasilldialog"], width: 600, height: h, 'z-index': 99999 };
return new YggdrasillRoll(actor, rollData, html, options );
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: (rollData.mode == "competence") ? "Compétence" : "Caractéristique",
content: html,
buttons: {
roll: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer le Test",
callback: () => { this.roll() }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
} },
default: "Roll",
close: close
}
super(conf, options);
this.actor = actor;
this.rollData = rollData;
}
/* -------------------------------------------- */
roll () {
if ( this.rollData.mode == "attribut") {
YggdrasillUtility.rollAttribute(this.rollData)
} else {
YggdrasillUtility.rollYggdrasill( this.rollData )
}
}
/* -------------------------------------------- */
updateGaldrSR( ) {
let sdDuree = Number(dureeGaldrSD[this.rollData.dureeGaldr]);
let sdVar = 0;
if ( this.rollData.sort.data.voie == "illusion") {
sdVar = Number(zonesciblesGaldrSD[this.rollData.zoneGaldr]);
} else {
sdVar = Number(ciblesGaldrSD[this.rollData.nbCibles]);
}
let SR = Number(this.rollData.sort.data.sd) + sdDuree + sdVar;
$("#srTotal").text(SR);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
updateRuneSR() {
let support = 0;
this.rollData.dureeRune = 6 - this.rollData.agiliteCarac.value;
if ( this.rollData.supportRune == "peau") {
support = 3;
this.rollData.echelleDuree = "Actions";
}
if ( this.rollData.supportRune == "tissu") {
support = 6;
this.rollData.echelleDuree = "Tours";
}
if ( this.rollData.supportRune == "cuir") {
support = 9;
this.rollData.echelleDuree = "Minutes";
}
if ( this.rollData.supportRune == "bois") {
support = 12;
this.rollData.echelleDuree = "Heures";
}
if ( this.rollData.supportRune == "pierremetal") {
support = 15;
this.rollData.echelleDuree = "Jours";
}
let SR = this.rollData.puissanceRune + (Number(this.rollData.sort.data.niveau)*3) + support;
$("#srTotal").text(SR);
$("#runeDuree").text( this.rollData.dureeRune + " " + this.rollData.echelleDuree);
$("#runeDureeVie").text( this.rollData.puissanceRune + " " + this.rollData.echelleDuree);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
if (dialog.rollData.mode == "competence") {
let carac = dialog.actor.getCarac( "Puissance" );
dialog.rollData.selectedCarac = carac;
} else if (dialog.rollData.mode == "armecc" || dialog.rollData.mode == "armedist" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
$("#attackDescr").text(dialog.rollData.attackDef.description);
} else if ( dialog.rollData.mode == "sejdr" || dialog.rollData.mode == "rune" || dialog.rollData.mode == "galdr" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
}
if (dialog.rollData.mode == "rune" ) {
dialog.updateRuneSR();
}
if (dialog.rollData.mode == "galdr" ) {
dialog.updateGaldrSR();
}
if (dialog.rollData.mode == "attribut") {
$("#attrValue").text("2d10+"+dialog.rollData.subAttr.value);
} else {
$("#caracValue").text(dialog.rollData.selectedCarac.value+"d10");
}
}
$(function () { onLoad(); });
html.find('#caracName').change((event) => {
let caracKey = event.currentTarget.value;
let carac = this.actor.getCarac( caracKey );
this.rollData.selectedCarac = carac;
$("#caracValue").text(carac.value+"d10");
});
html.find('#typeAttack').change((event) => {
let attackType = event.currentTarget.value;
let attackDef
if ( this.rollData.mode == 'armecc')
attackDef = this.actor.getAttaqueData( attackType);
else
attackDef = this.actor.getTirData( attackType);
this.rollData.attackDef = attackDef;
this.rollData.selectedCarac = attackDef.carac;
$("#caracValue").text(attackDef.carac.value+"d10");
$("#caracName").text(attackDef.carac.label);
$("#attackDescr").text(attackDef.description);
$("#malus").text(attackDef.malus);
});
html.find('#supportRune').change((event) => {
this.rollData.supportRune = event.currentTarget.value;
this.updateRuneSR();
});
html.find('#puissanceRune').change((event) => {
this.rollData.puissanceRune = Number(event.currentTarget.value);
this.updateRuneSR();
});
html.find('#dureeGaldr').change((event) => {
this.rollData.dureeGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#nbCibles').change((event) => {
this.rollData.nbCibles = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#zoneGaldr').change((event) => {
this.rollData.zoneGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#bonusMalus').change((event) => {
this.rollData.bonusMalus = Number(event.currentTarget.value);
});
html.find('#furorUsage').change((event) => {
this.rollData.furorUsage = Number(event.currentTarget.value);
});
html.find('#sr').change((event) => {
this.rollData.sr = Number(event.currentTarget.value);
});
html.find('#bonusdefense').change((event) => {
this.rollData.bonusdefense = Number(event.currentTarget.value);
});
}
}

View File

@ -0,0 +1,362 @@
/* -------------------------------------------- */
//import { YggdrasillCombat } from "./yggdrasill-combat.js";
/* -------------------------------------------- */
const dureeGaldrText = { "1d5a": "Actions", "1d10t": "Tours", "1d10m": "Minutes", "1d10h": "Heures", "1d5j": "Jours"};
const ciblesGaldrText = { "1": "1", "2_4": "2 à 4", "5_9": "5 à 9", "10_49": "10 à 49", "50plus": "50 et plus"};
/* -------------------------------------------- */
export class YggdrasillUtility {
/* -------------------------------------------- */
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-yggdrasill/templates/actor-sheet.html',
'systems/fvtt-yggdrasill/templates/editor-notes-gm.html',
'systems/fvtt-yggdrasill/templates/hud-actor-attaque.html',
'systems/fvtt-yggdrasill/templates/hud-actor-sort.html'
]
return loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static templateData(it) {
return YggdrasillUtility.data(it)?.data ?? {}
}
/* -------------------------------------------- */
static data(it) {
if (it instanceof Actor || it instanceof Item || it instanceof Combatant) {
return it.data;
}
return it;
}
/* -------------------------------------------- */
static createDirectSortedOptionList( min, max) {
let options = [];
for(let i=min; i<=max; i++) {
options.push( {value:i, text: `${i}` } );
}
return options;
}
/* -------------------------------------------- */
static createDirectOptionList( min, max) {
let options = {};
for(let i=min; i<=max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
/* -------------------------------------------- */
static buildListOptions(min, max) {
let options = ""
for (let i = min; i <= max; i++) {
options += `<option value="${i}">${i}</option>`
}
return options;
}
/* -------------------------------------------- */
static buildSROptions( ) {
let options = ""
options += `<option value="0">Aucun</option>`
options += `<option value="5">Très Simple (5)</option>`
options += `<option value="7">Simple (7)</option>`
options += `<option value="10">Aisé (10)</option>`
options += `<option value="14">Moyen (14)</option>`
options += `<option value="19">Difficile (19)</option>`
options += `<option value="25">Trés Difficile (25)</option>`
options += `<option value="32">Exceptionnel (32)</option>`
options += `<option value="40">Légendaire (40)</option>`
options += `<option value="49">Divin (49)</option>`
return options;
}
/* -------------------------------------------- */
static onSocketMesssage( msg ) {
if( !game.user.isGM ) return; // Only GM
if (msg.name == 'msg_attack' ) {
this.performAttack( msg.data );
}
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await YggdrasillUtility.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static async rollAttribute( rollData ) {
// Init stuff
let isCritical = false;
let isFailure = false;
let isSuccess = false;
let marge = 0;
let niveau = rollData.subAttr.value;
// Bonus/Malus total
rollData.finalBM = rollData.bonusMalus;
// Gestion cas blessé (malus de -3)
if ( rollData.isBlesse) { // Cas blesse : malus de -3
rollData.finalBM -= 3;
}
let myRoll = new Roll("2d10+"+niveau+"+"+rollData.finalBM).roll( { async: false} );
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode") );
// Compute total SR
rollData.srFinal = rollData.sr;
if ( rollData.srFinal > 0 ) {
isCritical = myRoll.total >= rollData.srFinal*2;
isSuccess = myRoll.total >= rollData.srFinal;
marge = myRoll.total - rollData.srFinal;
}
if (myRoll.dice[0].results[0].result == 1 && myRoll.dice[0].results[1].result == 1) {
isFailure = true;
}
// Dégats
if ( isSuccess && rollData.subAttr.degats ) {
rollData.degatsExplain = `Marge(${marge}) + Physique(${rollData.valuePhysique}) + 1d10`;
rollData.rollDegats = new Roll("1d10+"+marge+"+"+rollData.valuePhysique).roll( { async: false} );
await this.showDiceSoNice(rollData.rollDegats, game.settings.get("core", "rollMode") );
rollData.degats = rollData.rollDegats.total;
}
// Stockage resultats
rollData.isFailure = isFailure;
rollData.isSuccess = isSuccess;
rollData.isCritical = isCritical;
rollData.marge = marge;
rollData.roll = myRoll
console.log("ROLLLL ATTR!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
});
//myRoll.toMessage();
}
/* -------------------------------------------- */
static async rollYggdrasill( rollData ) {
let sumDice = ( rollData.isEpuise | rollData.isMeurtri | rollData.isBlesse) ? 1 : 2;
// Init stuff
let isCritical = false;
let isFailure = false;
let isSuccess = false;
let marge = 0;
let nbDice = rollData.selectedCarac.value;
let niveauCompetence = 0;
// Select niveau de competence/arme/carac
if ( rollData.mode != "carac" ) {
niveauCompetence = rollData.competence.data.niveau;
} else {
niveauCompetence = rollData.selectedCarac.value;
}
// Bonus/Malus total
rollData.finalBM = rollData.bonusMalus;
if ( rollData.attackDef) {
rollData.finalBM -= rollData.attackDef.malus;
}
if ( rollData.sort && rollData.sort.data.malus ) {
rollData.finalBM += rollData.sort.data.malus;
}
// Gestion cas blessé (malus de -3)
if ( rollData.isBlesse) { // Cas blesse : malus de -3
rollData.finalBM -= 3;
}
if (sumDice > nbDice) sumDice = nbDice;
let myRoll = new Roll(nbDice+"d10x10kh"+sumDice+"+"+rollData.furorUsage+"d10+"+niveauCompetence+"+"+rollData.finalBM).roll( { async: false} );
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode") );
// Compute total SR
rollData.srFinal = rollData.sr;
if ( rollData.bonusdefense ) {
rollData.srFinal += rollData.bonusdefense;
}
if ( rollData.srFinal > 0 ) {
isCritical = myRoll.total >= rollData.srFinal*2;
isSuccess = myRoll.total >= rollData.srFinal;
marge = myRoll.total - rollData.srFinal;
}
rollData.rawDices = duplicate(myRoll.dice[0].results);
if (nbDice == 1 && myRoll.dice[0].results[0].result == 1) {
isFailure = true;
}
if (nbDice == 2 && myRoll.dice[0].results[0].result == 1 && myRoll.dice[0].results[1].result == 1) {
isFailure = true;
}
if (nbDice >= 3 ) {
let nbOnes = myRoll.dice[0].results.filter( dice => dice.result == 1);
isFailure = nbOnes.length >= 3;
}
if ( rollData.furorUsage > 0 ) {
let actor = game.actors.get(rollData.actorId);
actor.decrementFuror( rollData.furorUsage);
}
// Dégats
if ( isSuccess && (rollData.mode == "armecc" || rollData.mode == "armedist") ) {
rollData.degatsExplain = `Marge(${marge}) + Degats Arme(${rollData.arme.data.degat}) + Bonus Attaque(${rollData.attackDef.bonusdegats})`;
rollData.degats = marge + rollData.arme.data.degat + rollData.attackDef.bonusdegats;
}
// Stockage resultats
rollData.sumDice = sumDice;
rollData.isFailure = isFailure;
rollData.isSuccess = isSuccess;
rollData.isCritical = isCritical;
rollData.marge = marge;
rollData.roll = myRoll
// Specific GALDR
if ( rollData.sort?.type == "sortgaldr" && rollData.isSuccess) {
let galdrRoll = new Roll( rollData.dureeGaldr.substring(0, rollData.dureeGaldr.length - 1) ).roll( { async: false} );
await this.showDiceSoNice(galdrRoll, game.settings.get("core", "rollMode") );
rollData.dureeGaldrText = galdrRoll.total + " " + dureeGaldrText[rollData.dureeGaldr];
if ( rollData.sort.data.voie == "illusion") {
let volume = rollData.zoneGaldr.substring(3, rollData.zoneGaldr.length);
rollData.zoneGaldrText = rollData.instinctCarac.value + " x " + volume;
} else {
rollData.ciblesGaldrText = ciblesGaldrText[rollData.nbCibles];
}
}
console.log("ROLLLL!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
});
//myRoll.toMessage();
}
/* -------------------------------------------- */
static getUsers(filter) {
return game.users.filter(filter).map(user => user.data._id);
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
switch (rollMode) {
case "blindroll": return this.getUsers(user => user.isGM);
case "gmroll": return this.getWhisperRecipientsAndGMs(name);
case "selfroll": return [game.user.id];
}
return undefined;
}
/* -------------------------------------------- */
static getWhisperRecipientsAndGMs(name) {
return ChatMessage.getWhisperRecipients(name)
.concat(ChatMessage.getWhisperRecipients('GM'));
}
/* -------------------------------------------- */
static blindMessageToGM(chatOptions) {
let chatGM = duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Message aveugle de " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
game.socket.emit("system.foundryvtt-yggdrasill", { msg: "msg_gm_chat_message", data: chatGM });
}
/* -------------------------------------------- */
static createChatMessage(name, rollMode, chatOptions) {
switch (rollMode) {
case "blindroll": // GM only
if (!game.user.isGM) {
this.blindMessageToGM(chatOptions);
chatOptions.whisper = [game.user.id];
chatOptions.content = "Message envoyé en aveugle au Gardien";
}
else {
chatOptions.whisper = this.getUsers(user => user.isGM);
}
break;
default:
chatOptions.whisper = this.getWhisperRecipients(rollMode, name);
break;
}
chatOptions.alias = chatOptions.alias || name;
ChatMessage.create(chatOptions);
}
/* -------------------------------------------- */
static createChatWithRollMode(name, chatOptions) {
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions);
}
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
let msgTxt = "<p>Etes vous certain de souhaiter supprimer cet item ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Oui, à supprimer",
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments( "Item", [itemId] );
li.slideUp(200, () => actorSheet.render(false));
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirmer la suppression",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
}
/* -------------------------------------------- */
static async showDiceSoNice(roll, rollMode) {
if (game.modules.get("dice-so-nice")?.active) {
if (game.dice3d) {
let whisper = null;
let blind = false;
rollMode = rollMode ?? game.settings.get("core", "rollMode");
switch (rollMode) {
case "blindroll": //GM only
blind = true;
case "gmroll": //GM + rolling player
whisper = this.getUsers(user => user.isGM);
break;
case "roll": //everybody
whisper = this.getUsers(user => user.active);
break;
case "selfroll":
whisper = [game.user.id];
break;
}
await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
}
}
}
}