first data modeling

This commit is contained in:
François-Xavier Guillois
2023-04-20 15:12:05 +02:00
parent fa8e32f4dd
commit 5ddd9db9b3
12 changed files with 1567 additions and 25 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

+66
View File
@@ -0,0 +1,66 @@
export const TOTEM = {};
/**
* The set of Ability Scores used within the sytem.
* @type {Object}
*/
/*
TOTEM.nations = {
"istanie1":{
"label": "Istanie (îles du couchant)",
"cities": ["tanger", "argan", "ar'kobah", "ishandra"]
},
"istanie2":{
"label": "Istanie (monts dinariques)",
"cities": ["montenegro", "ishandra"]
},
"istanie3":{
"label": "Istanie (anatolie)",
"cities": ["ismyr", "istanbul", "ankara"]
},
"pentapolie":{
"label": "Pentapolie",
"cities": ["serone", "éole", "relais de l'affrevie", "relais de bragee", "géode", "théorie", "démos", "négoce", "lucé"]
},
"venice":{
"label": "Venice",
"cities": ["venice"]
},
"rhodesiennes":{
"label": "Provinces rhodesiennes",
"cities": ["alsyde", "spicule", "urbs", "les syénites"]
},
"methalune":{
"label": "Méthalune",
"cities": ["méthalune", "ferraille"]
},
"gloriana":{
"label": "Gloriana",
"cities": ["enclosure", "londres", "camelot", "hivernee"]
},
"antipolie":{
"label": "Antipolie",
"cities": ["paris", "ithar","candbury","abaya", "relais d'elphiel", "entrelace", "prague", "vienne"]
},
"olmune":{
"label": "Principautés d'Olmune",
"cities": ["entrepont", "olmune","arssens","braysine"]
},
"lansk":{
"label": "Lansk",
"cities": ["saint-petersbourg", "hypogée","sancre","moscou", "kiev","kryo"]
},
"nordanie":{
"label": "Nordanie",
"cities": ["souspente", "gottenborg","solth", "nacre", "dorvik", "mystille"]
},
"terraincognita":{
"label": "Terra Incognita",
"cities": ["chantier de transécryme"]
}
}
*/
+71
View File
@@ -0,0 +1,71 @@
export class WarningDialog extends Dialog {
constructor(dialogData) {
let options = { classes: ["warning"] };
let conf = {
title: "Avertissement",
content: dialogData.content
};
super(conf, options);
this.dialogData = dialogData;
}
/* -------------------------------------------- */
activateListeners(html) {
/*super.activateListeners(html);
this.html = html;
this.setEphemere(this.dialogData.signe.system.ephemere);
html.find(".signe-aleatoire").click(event => this.setSigneAleatoire());
html.find("[name='signe.system.ephemere']").change((event) => this.setEphemere(event.currentTarget.checked));
html.find(".signe-xp-sort").change((event) => this.onValeurXpSort(event));
html.find("input.select-actor").change((event) => this.onSelectActor(event));
html.find("input.select-tmr").change((event) => this.onSelectTmr(event));*/
}
async onSelectActor(event) {
/*const actorId = this.html.find(event.currentTarget)?.data("actor-id");
const actor = this.dialogData.actors.find(it => it.id == actorId);
if (actor) {
actor.selected = event.currentTarget.checked;
}*/
}
}
export class CombatResultDialog extends Dialog {
constructor(dialogData, options) {
let options = { classes: ["combat", "result"], ...options };
let conf = {
title: "Résultat de la confrontation",
content: dialogData.content
};
super(conf, options);
this.dialogData = dialogData;
}
/* -------------------------------------------- */
activateListeners(html) {
/*super.activateListeners(html);
this.html = html;
this.setEphemere(this.dialogData.signe.system.ephemere);
html.find(".signe-aleatoire").click(event => this.setSigneAleatoire());
html.find("[name='signe.system.ephemere']").change((event) => this.setEphemere(event.currentTarget.checked));
html.find(".signe-xp-sort").change((event) => this.onValeurXpSort(event));
html.find("input.select-actor").change((event) => this.onSelectActor(event));
html.find("input.select-tmr").change((event) => this.onSelectTmr(event));*/
}
async onSelectActor(event) {
/*const actorId = this.html.find(event.currentTarget)?.data("actor-id");
const actor = this.dialogData.actors.find(it => it.id == actorId);
if (actor) {
actor.selected = event.currentTarget.checked;
}*/
}
}
+63
View File
@@ -0,0 +1,63 @@
/**
* Manage Active Effect instances through the Actor Sheet via effect control buttons.
* @param {MouseEvent} event The left-click event on the effect control
* @param {Actor|Item} owner The owning document which manages this effect
*/
export function onManageActiveEffect(event, owner) {
event.preventDefault();
const a = event.currentTarget;
const li = a.closest("li");
const effect = li.dataset.effectId ? owner.effects.get(li.dataset.effectId) : null;
switch ( a.dataset.action ) {
case "create":
return owner.createEmbeddedDocuments("ActiveEffect", [{
label: "New Effect",
icon: "icons/svg/aura.svg",
origin: owner.uuid,
"duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined,
disabled: li.dataset.effectType === "inactive"
}]);
case "edit":
return effect.sheet.render(true);
case "delete":
return effect.delete();
case "toggle":
return effect.update({disabled: !effect.disabled});
}
}
/**
* Prepare the data structure for Active Effects which are currently applied to an Actor or Item.
* @param {ActiveEffect[]} effects The array of Active Effect instances to prepare sheet data for
* @return {object} Data for rendering
*/
export function prepareActiveEffectCategories(effects) {
// Define effect header categories
const categories = {
temporary: {
type: "temporary",
label: "Temporary Effects",
effects: []
},
passive: {
type: "passive",
label: "Passive Effects",
effects: []
},
inactive: {
type: "inactive",
label: "Inactive Effects",
effects: []
}
};
// Iterate over active effects, classifying them into categories
for ( let e of effects ) {
e._getSourceName(); // Trigger a lookup for the source name
if ( e.disabled ) categories.inactive.effects.push(e);
else if ( e.isTemporary ) categories.temporary.effects.push(e);
else categories.passive.effects.push(e);
}
return categories;
}
+424
View File
@@ -0,0 +1,424 @@
import { TOTEM } from "./config.mjs";
import { getActorSkillScore,updateActorSkillScore } from "./functions.mjs";
import { CombatResultDialog } from "./dialogs.mjs";
export class TotemFight {
async performTest(enemyAchievement, enemyConservation, skillKey, skill, params, actor) {
const dicePool = (params.spleen != undefined || params.purpose != undefined) ? '5' : '4';
const r = new Roll(dicePool +`d6`);
let diceString = '';
let dicePoolHint = '';
let discardedRoll = false;
let bonus = 0;
let bonusText = '/+';
let currentSkillScore = skill;
r.roll(); // dice are rolled
if (params.usure != undefined){
currentSkillScore += params.usure;
bonus += params.usure;
}
if (params.specialization != undefined){
currentSkillScore += 2;
}
if (params.trait != undefined){
currentSkillScore += params.trait;
bonus += params.trait;
}
bonusText += bonus;
let targetText = game.i18n.format('TOTEM.Selected') + ' : ' + game.i18n.format(skillKey) + " " + skill + bonusText;
if (params.specialization != undefined){
targetText += " (S)";
}
// tri par ordre croissant
r.terms[0].results.sort((a,b) => a.result - b.result );
if (params.purpose != undefined){
discardedRoll = r.terms[0].results.shift();
dicePoolHint = ' - ' + game.i18n.format('TOTEM.PurposeTrait');
} else if (params.spleen != undefined){
discardedRoll = r.terms[0].results.pop();
dicePoolHint = ' - ' + game.i18n.format('TOTEM.SpleenTrait');
}
const discardedRollText = (discardedRoll.result != undefined) ? '<div class="discarded-roll">' + discardedRoll.result + '</div>' : "";
for (let i = 0; i < r.terms[0].results.length; i++) {
let result = r.terms[0].results[i].result;
diceString += '<li class="roll die d6 die-'+ i +'">' + result + '</li>';
}
let hintText = game.i18n.format('TOTEM.ConfrontationHint');
// Build a dynamic html using the variables from above.
const html = `
<div class="totem roll confrontation">
<div class="dice-roll">
<div class="dice-result">
<div class="dice-formula">
` + dicePool + `d6 ` + dicePoolHint + `
</div>
<div class="dice-tooltip expanded">
<section class="tooltip-part">
<div class="parameters">
` + targetText + `
</div>
<div class="dice flexrow flex-between items-center">
<ol class="dice-rolls">` + diceString + `</ol>
<div class="discards text-right">` + discardedRollText + `</div>
</div>
</section>
</div>` +
`<p class="step1-text" id="step1">` + hintText + `</p>
<div class="row">
<a class="inline-block button add-to-achievement">` + game.i18n.format('TOTEM.Achievement') + `</a>
<a class="inline-block button add-to-conservation">` + game.i18n.format('TOTEM.Conservation') + `</a>
<a class="inline-block button reset"><i class="fa-solid fa-rotate-right"></i></a>
<a class="inline-block button resolve"><i class="fa-solid fa-check"></i></a>
</div>
</div>
</div>
</div>
`;
// Check if the dice3d module exists (Dice So Nice). If it does, post a roll in that and then
// send to chat after the roll has finished. If not just send to chat.
if (game.dice3d) {
game.dice3d.showForRoll(r).then((displayed) => {
this.sendToChat(html, r, actor);
});
} else {
this.sendToChat(html, r, actor);
};
// on fait les comptes
}
async sendToChat(content, roll, actor) {
let conf = {
user: game.user._id,
content: content,
roll: roll,
// sound: 'sounds/dice.wav'
};
if (actor)
conf.speaker = ChatMessage.getSpeaker({ actor: actor });
// Send's Chat Message to foundry, if items are missing they will appear as false or undefined and this not be rendered.
ChatMessage.create(conf).then((msg) => {
return msg;
});
}
static instance = null;
static get() {
if (!TotemFight.instance)
TotemFight.instance = new TotemFight();
return TotemFight.instance;
}
// data injected to char data
static previousValues = {
dicePool: 4,
skills: TOTEM.skillsList,
cskills: TOTEM.cskills,
cephalic: false,
achievementReroll: TOTEM.achievementReroll,
conservationReroll: TOTEM.conservationReroll
};
static rollerTemplate = 'systems/totem/templates/fight.html';
static CombatResultTemplate = 'systems/totem/templates/fight-result.html';
static async chatMessageHandler(message, html, data) {
// console.log("accès au fin du fin", message._id);
// sélection du dé actif
html.on("click", '.confrontation .die.d6', event => {
const diceResult = parseInt($(event.target).html(),10);
html.find('.confrontation .die.d6').removeClass('active');
$(event.target).addClass('active');
});
// sélection des dés d'accomplissement
html.on("click", '.confrontation .add-to-achievement', event => {
const diceResult = parseInt(html.find('.confrontation .die.d6.active').html(),10);
html.find('.confrontation .die.d6.active').removeClass('min').addClass('max');
});
// sélection des dés de conservation
html.on("click", '.confrontation .add-to-conservation', event => {
const diceResult = parseInt(html.find('.confrontation .die.d6.active').html(),10);
html.find('.confrontation .die.d6.active').removeClass('max').addClass('min');
});
// reset de la sélection des pools
html.on("click", '.confrontation .reset', event => {
html.find('.confrontation .die.d6')
.removeClass('max')
.removeClass('min');
});
// résolution de la confrontation
html.on("click", '.confrontation .resolve', async event => {
let achievementDice = 0;
let conservationDice = 0;
let achievementBasis = 0;
let conservationBasis = 0;
html.find('.confrontation .die.d6.max').each(function(index){
achievementDice += parseInt($(this).html(), 10);
});
html.find('.confrontation .die.d6.min').each(function(index){
conservationDice += parseInt($(this).html(), 10);
});
// saisie des résultats
achievementBasis = html.find('td.achievement-result').data('achievement-basis');
html.find('td.achievement-result').data('achievement-value', achievementDice);
html.find('td.achievement-result').html(achievementBasis + achievementDice);
conservationBasis = html.find('td.conservation-result').data('conservation-basis');
html.find('td.conservation-result').data('conservation-value', conservationDice);
html.find('td.conservation-result').html(conservationBasis + conservationDice);
// calcul des marges
const achievementMargin = achievementBasis + achievementDice - parseInt(html.find('td.adv-achievement-result').html(),10);
const conservationMargin = conservationBasis + conservationDice - parseInt(html.find('td.adv-conservation-result').html(),10);
html.find('td.achievement-margin').html(achievementMargin);
html.find('td.conservation-margin').html(conservationMargin);
});
// fin de la résolution de la confrontation
}
static async chatListeners(html) {
// supprime le masquage des résultats du dé
html.off("click", ".dice-roll");
}
/**
* main class function
* @returns
*/
static async ui(externalData = {}) {
let actor = {};
// get the actor
try {
actor = game.user.character;
} catch(e){
throw("Aucun personnage défini !");
}
if (actor == null && externalData.speakerId != undefined && externalData.speakerId != null){
// on récupère le speakerId, et de là l'objet actor
actor = game.actors.get(externalData.speakerId);
TotemFight.previousValues['speakerName'] = actor.name;
TotemFight.previousValues['speakerImg'] = actor.img;
} else {
TotemFight.previousValues['speakerName'] = "Anonyme";
}
// get the data
let charData = (externalData) => {
let o = Object.assign({ _template: TotemFight.rollerTemplate }, {...TotemFight.previousValues, ...externalData});
return o;
};
let data = charData(externalData);
console.log(data);
// render template
let html = await renderTemplate(data._template, data);
let ui = new Dialog({
title: game.i18n.localize("TOTEM.FightTool"),
content: html,
buttons: {
roll: {
label: game.i18n.localize('TOTEM.Roll4Fight'),
callback: (html) => {
let form = html.find('#dice-pool-form');
if (!form[0].checkValidity()) {
throw "Invalid Data";
}
let enemyAchievement, enemyConservation, skillKey, skill = 5, enemySkill, params = {};
form.serializeArray().forEach(e => {
switch (e.name) {
case "skill":
case "cephalic":
if (e.value !== ''){
skillKey = e.value;
}
break;
case "skill-score":
skill = +e.value;
break;
case "specialization":
params.specialization = true;
break;
case "usure":
params.usure = +e.value;
break;
case "trait":
params.trait = +e.value;
break;
case "purpose":
params.purpose = true;
break;
case "spleen":
params.spleen = true;
break;
case "adv-skill":
enemySkill = +e.value;
break;
case "achievement":
enemyAchievement = +e.value;
break;
case "conservation":
enemyConservation = +e.value;
break;
}
});
// prise en compte de l'usure sur la feuille de perso
if (params.usure != undefined){
const newSpentScore = getActorSkillScore(actor, skillKey, 'spent') + params.usure;
console.log(newSpentScore);
updateActorSkillScore(actor, skillKey, 'spent', newSpentScore);
}
return TotemFight.get().performTest(enemyAchievement + enemySkill, enemyConservation + enemySkill, skillKey, skill, params, actor);
}
},
cancel: {
label: game.i18n.localize('Close'),
callback: () => { }
}
},
render: function (h) {
h.on("change", 'select[name="skill"]', event => {
const skillLabel = $(event.target).val();
const currentSkillScore = getActorSkillScore(actor, skillLabel) - getActorSkillScore(actor, skillLabel, 'spent');
if (parseInt(currentSkillScore,10) >= 0){
h.find('input#skillScore').val(currentSkillScore);
}
});
}
}, { width: 601, height: 'fit-content' });
ui.render(true);
return ui;
}
}
export class TotemCombat extends Combat {
_encounterCheck(){
console.log('encounter combat object', this);
}
async rollInitiative(ids, formula = undefined, messageOptions = {}) {
// console.log(`${game.system.title} | Combat.rollInitiative()`, ids, formula, messageOptions);
// Structure input data
ids = typeof ids === "string" ? [ids] : ids;
// étape 1 : on vérifie que le combattant est un pj
/*if (ids.length == 1){
console.log("il n'y a qu'un actor en lice");
} else {
console.log("il faut prendre le premier pj pour lancer la confrontation");
}*/
const combatant = this.combatants.get(ids[0]);
let token = canvas.scene.tokens.get(combatant.tokenId);
combatant.type = game.actors.get( combatant.actorId)?.type;
combatant.disposition = token.disposition;
let enemies = [];
let adversaries = this.combatants.filter((cbt) => {
let token = canvas.scene.tokens.get(cbt.tokenId);
let enemy = token.actor;
const isEnemy = (token.disposition == -1) ? true : false;
if (isEnemy){
enemies.push({
id: enemy.id,
name: enemy.name,
img: enemy.img,
achievement: parseInt(enemy.system.reroll.achievement.value) + 7,
conservation: 7 - parseInt(enemy.system.reroll.conservation.value)
})
}
return isEnemy;
});
let allies = this.combatants.filter((cbt) => {
let token = canvas.scene.tokens.get(cbt.tokenId);
return (token.disposition == 1 && cbt.id != combatant.id) ? true : false;
});
if (combatant.type != 'character'){
let warningDialogHTML = await renderTemplate('systems/totem/templates/dialogs/warning.html', {
warningText: "Seuls les PJs peuvent initier des confrontations. Relancer l'opération au tour du PJ actif."
});
Dialog.prompt({
title: "Avertissement",
content: warningDialogHTML,
label: 'Okay !',
callback: () => {
// console.log('Il a compris');
},
});
} else {
// étape 2 : on envoie les infos
let fightingActor = game.actors.get(combatant.actorId);
TotemFight.ui({
speakerId: combatant.actorId,
speakerWeapons: fightingActor.items.filter(item => item.type == 'weapon'),
speakerExperience:fightingActor.system.attributes.experience.value,
speakerEffects: token.actor.effects,
adversaries: enemies,
allies: allies
});
}
}
nextRound() {
/*let combatants = this.combatants.contents
for (let c of combatants) {
let actor = game.actors.get( c.actorId )
actor.clearRoundModifiers()
}*/
super.nextRound();
}
/************************************************************************************/
startCombat() {
/*let combatants = this.combatants.contents
for (let c of combatants) {
let actor = game.actors.get( c.actorId )
actor.storeVitaliteCombat()
}*/
return super.startCombat();
}
/************************************************************************************/
_onDelete() {
/*let combatants = this.combatants.contents
for (let c of combatants) {
let actor = game.actors.get(c.actorId)
actor.clearInitiative()
actor.displayRecuperation()
}
super._onDelete()*/
}
}
+109
View File
@@ -0,0 +1,109 @@
import { TOTEM } from './config.mjs'
/**
* renvoie le score d'une compétence d'un actor existant
* @param {TotemActor}
* @return {number||null} Data for rendering or null
*/
export function getActorSkillScore(actor, skillLabel, property = "value") {
let returnedValue = null;
for(let i in actor.system.skills){
for(let j in actor.system.skills[i].data){
if (actor.system.skills[i].data[j].label == skillLabel){
returnedValue = actor.system.skills[i].data[j][property];
}
}
}
if (returnedValue == null){
for(let i in actor.system.cskills.data){
if (actor.system.cskills.data[i].label == skillLabel){
returnedValue = actor.system.cskills.data[i][property];
}
}
}
return returnedValue;
}
/**
* renvoie le type d'une compétence
* @param {TotemActor}
* @return {string||null} Data for rendering or null
*/
export function getSkillTypeFromLabel(skillLabel) {
let returnedValue = null;
for(let i in TOTEM.skills){
for(let j in TOTEM.skills[i].data){
if (TOTEM.skills[i].data[j].label == skillLabel){
returnedValue = j;
}
}
}
return returnedValue;
}
/**
* met à jour le score d'une compétence d'un actor existant
* @param {TotemActor}
* @return {boolean} bool
*/
export function updateActorSkillScore(selectedActor, skillLabel, property = "value", updatedValue) {
try {
let updated = false;
// on recherche le label parmi les compétences
for (let st in selectedActor.system.skills){
for (let s in selectedActor.system.skills[st].data){
if (selectedActor.system.skills[st].data[s].label == skillLabel){
selectedActor.system.skills[st].data[s][property] = updatedValue; // printing the new value
const systemSkillKey = `system.skills.${st}.data.${s}.${property}`;
selectedActor.update({[systemSkillKey]:updatedValue }); // updating actor's data
updated = true;
}
}
}
if (updated == false){
for (let s in selectedActor.system.cskills.data){
if (selectedActor.system.cskills.data[s].label == skillLabel){
selectedActor.system.cskills.data[s][property] = updatedValue; // printing the new value
const systemSkillKey = `system.cskills.data.${s}.${property}`;
selectedActor.update({[systemSkillKey]:updatedValue }); // updating actor's data
updated = true;
}
}
}
return updated;
} catch(e){
return false;
}
}
/**
* réinitialise toutes les dépenses d'usure
* @param {TotemActor}
* @return {boolean} bool
*/
export function resetActorSkillUsure(selectedActor) {
try {
// on recherche les usures des compétences
for (let st in selectedActor.system.skills){
for (let s in selectedActor.system.skills[st].data){
const systemSkillKey = `system.skills.${st}.data.${s}.spent`;
selectedActor.update({[systemSkillKey]:0 }); // updating actor's data
}
}
// on recherche les usures des compétences céphaliques
for (let s in selectedActor.system.cskills.data){
const systemSkillKey = `system.cskills.data.${s}.spent`;
selectedActor.update({[systemSkillKey]:0 }); // updating actor's data
}
return true;
} catch(e){
return false;
}
}
+25
View File
@@ -0,0 +1,25 @@
export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('concat', (...args) => args.slice(0, -1).join(''));
Handlebars.registerHelper('lower', e => e.toLocaleLowerCase());
Handlebars.registerHelper('toLowerCase', function(str) {
return str.toLowerCase();
});
// Ifis not equal
Handlebars.registerHelper('ifne', function (v1, v2, options) {
if (v1 !== v2) return options.fn(this);
else return options.inverse(this);
});
// if equal
Handlebars.registerHelper('ife', function (v1, v2, options) {
if (v1 === v2) return options.fn(this);
else return options.inverse(this);
});
// if equal
Handlebars.registerHelper('ifgt', function (v1, v2, options) {
if (v1 > v2) return options.fn(this);
else return options.inverse(this);
});
}
+105
View File
@@ -0,0 +1,105 @@
import { TotemFight } from './fight.mjs';
export const registerHooks = function () {
/**
* Ready hook loads tables, and override's foundry's entity link functions to provide extension to pseudo entities
*/
Hooks.once("ready", async () => {
console.info("Totem | System Initialized.");
});
// changement de la pause
Hooks.on("renderPause", async function () {
if ($("#pause").attr("class") !== "paused") return;
$(".paused img").attr("src", 'systems/totem/images/pause.webp');
$(".paused img").css({ "opacity": 1});
$("#pause.paused").css({ "display": "flex", "justify-content": "center" });
$("#pause.paused figcaption").css({ "width": `256px`, "height": `256px` });
$("#pause.paused figcaption").text(game.i18n.localize("TOTEM.PausedText"));
});
/*Hooks.on("renderPause", ((_app, html) => {
html.find("img").attr("src", "systems/bol/ui/pause2.webp")
}))
Hooks.on('renderChatLog', (log, html, data) => BoLUtility.chatListeners(html))
Hooks.on('renderChatMessage', (message, html, data) => BoLUtility.chatMessageHandler(message, html, data))
*/
console.log("rendering hooks");
Hooks.on('renderChatLog', (log, html, data) => TotemFight.chatListeners(html));
Hooks.on('renderChatMessage', (message, html, data) => TotemFight.chatMessageHandler(message, html, data));
/**
* Create a macro when dropping an entity on the hotbar
* Item - open roll dialog for item
* Actor - open actor sheet
* Journal - open journal sheet
*/
Hooks.on("hotbarDrop", async (bar, data, slot) => {
// console.log(data.type);
// Create item macro if rollable item - weapon, spell, prayer, trait, or skill
return false;
});
Hooks.on('getSceneControlButtons', (controls) => {
controls.find((c) => c.name === 'token').tools.push({
name: 'Dice Roller',
title: game.i18n.localize("TOTEM.RollTool"),
icon: 'fas fa-dice-d6',
button: true,
onClick() {
TotemRoll.ui();
}
});
});
/* -------------------------------------------- */
/* PreCreate Hooks */
/* -------------------------------------------- */
Hooks.on("preCreateActor", function (actor) {
console.log('pre create actor', actor);
if (actor.img == "icons/svg/mystery-man.svg") {
// actor.updateSource({"img": `systems/totem/icons/actors/${actor.type}.webp`});
// item.updateSource({"img": `systems/totem/icons/competence.webp`});
}
});
Hooks.on("preCreateItem", function (item) {
if (item.img == "icons/svg/item-bag.svg") {
item.updateSource({"img": `systems/totem/icons/items/${item.type}.webp`});
// item.updateSource({"img": `systems/totem/icons/competence.webp`});
}
});
/* -------------------------------------------- */
/* Combat Hooks */
/* -------------------------------------------- */
/*
Hooks.on("createCombatant", function (combatant) {
if (game.user.isGM) {
let actor = combatant.actor;
console.log('create combatant', actor);
}
});*/
Hooks.on("updateCombat", function () {
if (game.user.isGM) {
let combatant = (game.combat.combatant) ? game.combat.combatant.actor : "";
console.log('update combat', game.combat);
/*if (combatant.type == "marker" && combatant.system.settings.general.isCounter == true) {
let step = (!combatant.system.settings.general.counting) ? -1 : combatant.system.settings.general.counting;
let newQuantity = combatant.system.pools.quantity.value + step;
combatant.update({"system.pools.quantity.value": newQuantity});
}*/
}
});
}
+284
View File
@@ -0,0 +1,284 @@
import { getActorSkillScore, updateActorSkillScore } from "./functions.mjs";
export class TotemRoll {
async performTest(dicePool, target, trait, usingSpecialization, difficulty, skill, params, actor) {
const r = new Roll(dicePool + 'd6');
r.roll();
let _trait = trait || 0;
let _usingSpecialization = usingSpecialization || 0;
let _skillLabel = (params.skill != undefined) ? game.i18n.format(params.skill) : "";
let _used = (params.usure != undefined) ? params.usure : 0;
let diceString = '';
let total = 0;
// affichage des valeurs
let targetText = _skillLabel + ' : ' + skill + ' (+'+ _used +')';
if (trait)
targetText += ', '+ game.i18n.format('TOTEM.Traits') + ' : ' + _trait;
if (_usingSpecialization != 0)
targetText += ', '+ game.i18n.format('TOTEM.UsingSpecialization');
if (difficulty)
targetText += '<br />'+ game.i18n.format('TOTEM.Against') +': ' + Math.abs(difficulty);
// affichage des jets
for (let i = 0; i < dicePool; i++) {
let result = r.terms[0].results[i].result;
if (result == 6) {
diceString += '<li class="roll die d6 max">' + result + '</li>';
}
else if (result <= 5) {
diceString += '<li class="roll die d6">' + result + '</li>';
}
else if (result >= 1) {
diceString += '<li class="roll die d6 min">' + result + '</li>';
}
total += result;
}
// Here we want to check if the success was exactly one (as "1 Successes" doesn't make grammatical sense).
// We create a string for the Successes.
let successText = '';
let successMargin = 0;
successMargin = total + skill + _trait + _usingSpecialization + _used + difficulty;
if (params.usure != undefined){
successMargin += parseInt(params.usure,10);
}
// console.log(total, skill, _trait, _usingSpecialization, difficulty, successMargin);
// règle de la MR qui ne peut pas dépasser la compétence
if (successMargin > skill){
successMargin = skill;
}
if (successMargin > target + 1) {
successText = game.i18n.localize('TOTEM.RollSuccess') + ' (' + game.i18n.localize('TOTEM.SM') + ' ' + successMargin.toString() +')';
} else if (successMargin == target + 1) {
successText = game.i18n.localize('TOTEM.RollSuccess');
} else if (successMargin == target) {
successText = game.i18n.localize('TOTEM.PartialSuccess');
} else {
successText = game.i18n.localize('TOTEM.Failure');
}
// Build a dynamic html using the variables from above.
const html = `
<div class="totem roll attribute">
<div class="dice-roll">
<div class="dice-result">
<div class="dice-formula">
` + dicePool + `d6
</div>
<div class="dice-tooltip expanded">
<section class="tooltip-part flexrow">
<div class="" style="flex:60%;">
<div class="parameters">
` + targetText + `
</div>
<div class="dice">
<ol class="dice-rolls">` + diceString + `</ol>
</div>
</div>
<div class="align-center">
Résultat
<p style="font-weight:bold; font-size:2em;">` + (total + skill + _trait + _usingSpecialization + _used).toString() + `</p>
</div>
</section>
</div>` +
`<h4 class="dice-total">` + successText + `</h4>
</div>
</div>
</div>
`;
// Check if the dice3d module exists (Dice So Nice). If it does, post a roll in that and then
// send to chat after the roll has finished. If not just send to chat.
if (game.dice3d) {
game.dice3d.showForRoll(r).then((displayed) => {
this.sendToChat(html, r, actor);
});
} else {
this.sendToChat(html, r, actor);
};
}
async sendToChat(content, roll, actor) {
let conf = {
user: game.user._id,
content: content,
roll: roll,
sound: 'sounds/dice.wav'
};
if (actor)
conf.speaker = ChatMessage.getSpeaker({ actor: actor });
// Send's Chat Message to foundry, if items are missing they will appear as false or undefined and this not be rendered.
ChatMessage.create(conf).then((msg) => {
return msg;
});
}
static instance = null;
static get() {
if (!TotemRoll.instance)
TotemRoll.instance = new TotemRoll();
return TotemRoll.instance;
}
// Parse XdYtZfAc || XdYsZfAc
// {size of dice pool}d{target number}(t|s)[{skill level - for trait}f][{complication range}c][D]
async parse(cmd, usingSpecialization) {
let actor = game.user.character;
if (canvas.tokens.controlled.length > 0)
actor = canvas.tokens.controlled[0].actor;
let r = cmd.match(/([2-5])d([01]?[0-9])[ts](([4-8])f)?((20|[1][5-9])c)?(D)?/);
if (r) {
//console.log(r);
let dicePool = +r[1];
let target = +r[2];
let trait = +r[4];
if (!!r[7]) usingSpecialization = true;
let difficulty = +r[6];
this.performTest(dicePool, target, trait, usingSpecialization, difficulty, actor);
} else
ui.notifications.error("Unparsable command: " + cmd);
}
// data injected to char data
static previousValues = {
dicePool: 2
};
static rollerTemplate = 'systems/totem/templates/roll.html';
/**
* main class function
* @returns
*/
static async ui(externalData = {}) {
let charData = (externalData) => {
return Object.assign({ _template: TotemRoll.rollerTemplate }, {...TotemRoll.previousValues, ...externalData});
};
// get the actor
let actor = null;
try {
let actor = game.user.character;
if (canvas.tokens.controlled.length > 0)
actor = canvas.tokens.controlled[0].actor;
} catch (e) {
console.log(e);
}
if (actor == null && externalData.speakerId != undefined && externalData.speakerId != null){
// on récupère le speakerId, et de là l'objet actor
actor = game.actors.get(externalData.speakerId);
}
// get the data
let data = charData(externalData);
console.log('npc2', data);
if (actor.type != undefined){
data.actor_type = actor.type;
if (actor.type == 'character'){
data.skillMaxScore = getActorSkillScore(actor, data.skill);
data.skillScore = data.skillMaxScore - getActorSkillScore(actor, data.skill, 'spent');
data.skillSpent = getActorSkillScore(actor, data.skill, 'spent');
} else if(actor.type == 'npc'){
if (data.specialization == 1){
//data.skillMaxScore = getActorSkillScore(actor, data.skill);
// data.skillScore = data.skillMaxScore;
} else {
// compétence, il faut récupérer le score du skill type
data.skillScore = data.value;
}
}
}
// render template
let html = await renderTemplate(data._template, data);
let ui = new Dialog({
title: game.i18n.localize("TOTEM.RollTool"),
content: html,
buttons: {
roll: {
label: game.i18n.localize('TOTEM.RollDice'),
callback: (html) => {
let form = html.find('#dice-pool-form');
if (!form[0].checkValidity()) {
throw "Invalid Data";
}
let target = 0, trait, usingSpecialization, difficulty, skill = 0, params = {};
form.serializeArray().forEach(e => {
switch (e.name) {
case "difficulty":
if (e.value != "")
difficulty = -e.value;
break;
case "skillLabel":
params.skill = e.value;
break;
case "usure":
params.usure = +e.value;
break;
case "skill":
skill = +e.value;
break;
case "trait":
trait = +e.value;
break;
case "usingSpecialization":
if (e.value && +e.value > 1)
usingSpecialization = +e.value;
break;
}
// prise en compte de l'usure sur la feuille de perso
if (params.usure != undefined){
updateActorSkillScore(actor, data.skill, 'spent', data.skillSpent + parseInt(params.usure,10));
}
});
return TotemRoll.get().performTest(data.dicePool, target, trait, usingSpecialization, difficulty, skill, params, actor);
}
},
close: {
label: game.i18n.localize('Close'),
callback: () => { }
}
},
render: function (h) {
h.find("#skills-radio input").change(function () {
let s = $(this).attr("data-skill");
h.find(".trait-list .hidden").removeClass("show");
let f = h.find(".trait-list ." + s);
f.addClass("show");
if (f.length == 0) {
h.find(".use-trait input").attr("disabled", "disabled").prop("checked", false);
} else
h.find(".use-trait input").attr("disabled", null);
});
}
});
ui.render(true);
return ui;
}
}
Hooks.on("chatCommandsReady", function (chatCommands) {
chatCommands.registerCommand(chatCommands.createCommandFromData({
commandKey: "/dr",
invokeOnCommand: (chatlog, messageText, chatdata) => {
TotemRoll.get().parse(messageText);
},
shouldDisplayToChat: false,
iconClass: "fa-dice-d6",
description: "Roll Totem check"
}));
});
+29
View File
@@ -0,0 +1,29 @@
export const registerSettings = function () {
game.settings.register("totem", "game-level", {
name: game.i18n.localize("TOTEM.WorldSettings.GameLevel.Name"),
hint: game.i18n.localize("TOTEM.WorldSettings.GameLevel.Hint"),
scope: "system",
config: true,
type: String,
choices: {
"e": "Totem",
"c": "Céphale",
"b": "Bohème",
"a": "Amertume"
},
default: 'e',
onChange: value => {
console.log(value);
}
});
game.settings.register("totem", "granting_cephalie", {
name: game.i18n.localize("TOTEM.WorldSettings.GrantingCephales.Label"),
hint: game.i18n.localize("TOTEM.WorldSettings.GrantingCephales.Description"),
scope: "system",
config: true,
type: Boolean,
default: !1
})
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Define a set of template paths to pre-load
* Pre-loaded templates are compiled and cached for fast access when rendering
* @return {Promise}
*/
export const preloadHandlebarsTemplates = async function() {
return loadTemplates([
// Actor partials.
"systems/totem/templates/actor/parts/actor-traits.html",
"systems/totem/templates/actor/parts/actor-background.html",
"systems/totem/templates/actor/parts/actor-skills.html",
"systems/totem/templates/actor/parts/actor-items.html",
"systems/totem/templates/actor/parts/actor-cephalie.html",
"systems/totem/templates/actor/parts/actor-effects.html",
]);
};
+370 -21
View File
@@ -1,19 +1,20 @@
{ {
"Actor": { "Actor": {
"types": ["character", "npc"], "types": ["character", "npc", "creature"],
"templates": { "templates": {
"base": { "base": {
"health": { "minorWound": {
"value": 10, "value": 0,
"min": 0, "min": 0,
"max": 10 "max": 4
}, },
"power": { "majorWound": {
"value": 5, "value": 0,
"min": 0, "min": 0,
"max": 5 "max": 2
}, },
"biography": "" "totem":0,
"activity": ""
} }
}, },
"character": { "character": {
@@ -23,30 +24,378 @@
"value": 1 "value": 1
} }
}, },
"AbilityCategories": {
"physical": {
"label":"TOTEM.abilityCategory.physical"
},
"manual": {
"label":"TOTEM.abilityCategory.manual"
},
"mental": {
"label":"TOTEM.abilityCategory.mental"
},
"social": {
"label":"TOTEM.abilityCategory.social"
}
},
"abilities": { "abilities": {
"str": { "vig": {
"value": 10 "label":"TOTEM.abilities.vigor",
"value": 0,
"min": 0,
"max": 5,
"category": "physical"
}, },
"dex": { "vie": {
"value": 10 "label":"TOTEM.abilities.health",
"value": 0,
"min": 0,
"max": 5,
"category": "physical"
}, },
"con": { "pre": {
"value": 10 "label":"TOTEM.abilities.precision",
"value": 0,
"min": 0,
"max": 5,
"category": "manual"
}, },
"int": { "ref": {
"value": 10 "label":"TOTEM.abilities.reflexes",
"value": 0,
"min": 0,
"max": 5,
"category": "manual"
}, },
"wis": { "sav": {
"value": 10 "label":"TOTEM.abilities.knowledge",
"value": 0,
"min": 0,
"max": 5,
"category": "mental"
}, },
"cha": { "per": {
"value": 10 "label":"TOTEM.abilities.perception",
"value": 0,
"min": 0,
"max": 5,
"category": "mental"
},
"vol": {
"label":"TOTEM.abilities.will",
"value": 0,
"min": 0,
"max": 5,
"category": "social"
},
"emp": {
"label":"TOTEM.abilities.empathy",
"value": 0,
"min": 0,
"max": 5,
"category": "social"
}
},
"skillCategories": {
"man": {
"label":"TOTEM.skillCategory.man"
},
"animal": {
"label":"TOTEM.skillCategory.animal"
},
"machine": {
"label":"TOTEM.skillCategory.machine"
},
"weapon": {
"label":"TOTEM.skillCategory.weapon"
},
"survival": {
"label":"TOTEM.skillCategory.survival"
},
"earth": {
"label":"TOTEM.skillCategory.earth"
}
},
"skills": {
"arts": {
"label":"TOTEM.skills.arts",
"value": 0,
"min": 0,
"max": 5,
"category": "man",
"rarity":1
},
"civilization": {
"label":"TOTEM.skills.civilization",
"value": 0,
"min": 0,
"max": 5,
"category": "man",
"rarity":2
},
"psychology": {
"label":"TOTEM.skills.psychology",
"value": 0,
"min": 0,
"max": 5,
"category": "man",
"rarity":1
},
"rumors": {
"label":"TOTEM.skills.rumors",
"value": 0,
"min": 0,
"max": 5,
"category": "man",
"rarity":0
},
"healing": {
"label":"TOTEM.skills.healing",
"value": 0,
"min": 0,
"max": 5,
"category": "man",
"rarity":1
},
"animalism": {
"label":"TOTEM.skills.animalism",
"value": 0,
"min": 0,
"max": 5,
"category": "animal",
"rarity":1
},
"dissection": {
"label":"TOTEM.skills.dissection",
"value": 0,
"min": 0,
"max": 5,
"category": "animal",
"rarity":2
},
"wildlife": {
"label":"TOTEM.skills.wildlife",
"value": 0,
"min": 0,
"max": 5,
"category": "animal",
"rarity":1
},
"repulsion": {
"label":"TOTEM.skills.repulsion",
"value": 0,
"min": 0,
"max": 5,
"category": "animal",
"rarity":0
},
"tracks": {
"label":"TOTEM.skills.tracks",
"value": 0,
"min": 0,
"max": 5,
"category": "animal",
"rarity":0
},
"crafting": {
"label":"TOTEM.skills.crafting",
"value": 0,
"min": 0,
"max": 5,
"category": "machine",
"rarity":2
},
"diy": {
"label":"TOTEM.skills.diy",
"value": 0,
"min": 0,
"max": 5,
"category": "machine",
"rarity":0
},
"mecanical": {
"label":"TOTEM.skills.mecanical",
"value": 0,
"min": 0,
"max": 5,
"category": "machine",
"rarity":2
},
"driving": {
"label":"TOTEM.skills.driving",
"value": 0,
"min": 0,
"max": 5,
"category": "machine",
"rarity":1
},
"technology": {
"label":"TOTEM.skills.technology",
"value": 0,
"min": 0,
"max": 5,
"category": "machine",
"rarity":2
},
"firearms": {
"label":"TOTEM.skills.firearms",
"value": 0,
"min": 0,
"max": 5,
"category": "weapon",
"rarity":2
},
"archery": {
"label":"TOTEM.skills.archery",
"value": 0,
"min": 0,
"max": 5,
"category": "weapon",
"rarity":0
},
"armory": {
"label":"TOTEM.skills.armory",
"value": 0,
"min": 0,
"max": 5,
"category": "weapon",
"rarity":2
},
"throwing": {
"label":"TOTEM.skills.throwing",
"value": 0,
"min": 0,
"max": 5,
"category": "weapon",
"rarity":0
},
"melee": {
"label":"TOTEM.skills.melee",
"value": 0,
"min": 0,
"max": 5,
"category": "weapon",
"rarity":0
},
"feed": {
"label":"TOTEM.skills.feed",
"value": 0,
"min": 0,
"max": 5,
"category": "survival",
"rarity":0
},
"atletics": {
"label":"TOTEM.skills.atletics",
"value": 0,
"min": 0,
"max": 5,
"category": "survival",
"rarity":0
},
"brawling": {
"label":"TOTEM.skills.brawling",
"value": 0,
"min": 0,
"max": 5,
"category": "survival",
"rarity":0
},
"stealth": {
"label":"TOTEM.skills.stealth",
"value": 0,
"min": 0,
"max": 5,
"category": "survival",
"rarity":0
},
"alertness": {
"label":"TOTEM.skills.alertness",
"value": 0,
"min": 0,
"max": 5,
"category": "survival",
"rarity":0
},
"environment": {
"label":"TOTEM.skills.environment",
"value": 0,
"min": 0,
"max": 5,
"category": "earth",
"rarity":0
},
"flora": {
"label":"TOTEM.skills.flora",
"value": 0,
"min": 0,
"max": 5,
"category": "earth",
"rarity":0
},
"road": {
"label":"TOTEM.skills.road",
"value": 0,
"min": 0,
"max": 5,
"category": "earth",
"rarity":0
},
"toxics": {
"label":"TOTEM.skills.toxics",
"value": 0,
"min": 0,
"max": 5,
"category": "earth",
"rarity":0
},
"remains": {
"label":"TOTEM.skills.remains",
"value": 0,
"min": 0,
"max": 5,
"category": "earth",
"rarity":0
} }
} }
}, },
"npc": { "npc": {
"templates": ["base"], "templates": ["base"],
"cr": 0 "age": 15,
"threat": {
"value": 1,
"min": 1,
"max": 4
},
"experience": {
"value": 1,
"min": 1,
"max": 4
},
"role": {
"value": 1,
"min": 1,
"max": 4
}
},
"creature": {
"templates": ["base"],
"age": 15,
"template": {
"value": 1,
"min": 1,
"max": 4
},
"size": {
"value": 1,
"min": 1,
"max": 4
},
"role": {
"value": 1,
"min": 1,
"max": 4
}
} }
}, },
"Item": { "Item": {