feat: separate skill value/max (transcendence pool vs level)
Release Creation / build (release) Successful in 58s

- skill.value = spendable transcendence pool (editable select)
- skill.max = max level (locked by default, unlock btn)
- computeRollFormula/computeResults/confront use skill.max
- clamp transcendence spend to skill.value
- resetTranscendence button in Bio&Notes
- v14 compat: duplicate→deepClone, mergeObject→spread, socket fixes
- Dialog v1 removed, DragDrop dedup, await fixes
This commit is contained in:
2026-07-24 09:00:26 +02:00
parent 8c79e6f0ba
commit 31e2561797
63 changed files with 385 additions and 340 deletions
+24 -43
View File
@@ -131,7 +131,7 @@ export class EcrymeUtility {
/*-------------------------------------------- */
static buildSkillConfig() {
// Build skill configuration from DataModel structure
game.system.ecryme.config.skills = {}
game.ecryme.config.skills = {}
const skillCategories = {
physical: {
@@ -169,9 +169,9 @@ export class EcrymeUtility {
for (let categKey in skillCategories) {
let category = skillCategories[categKey]
for (let skillKey in category.skilllist) {
let skill = foundry.utils.duplicate(category.skilllist[skillKey])
let skill = foundry.utils.deepClone(category.skilllist[skillKey])
skill.categKey = categKey
game.system.ecryme.config.skills[skillKey] = skill
game.ecryme.config.skills[skillKey] = skill
}
}
}
@@ -229,19 +229,19 @@ export class EcrymeUtility {
let maxMargin // Dummy max
if (confront.marginExecution > 0) { // Successful hit
// Limit with skill+spec
maxMargin = confront.rollData1.skill.value + ((confront.rollData1.spec) ? 2 : 0)
maxMargin = confront.rollData1.skill.max + ((confront.rollData1.spec) ? 2 : 0)
confront.marginExecution = Math.min(confront.marginExecution, maxMargin)
} else { // Failed hit
maxMargin = confront.rollData2.skill.value + ((confront.rollData2.spec) ? 2 : 0)
maxMargin = confront.rollData2.skill.max + ((confront.rollData2.spec) ? 2 : 0)
confront.marginExecution = -Math.min(Math.abs(confront.marginExecution), maxMargin)
}
if (confront.marginPreservation > 0) { // Successful defense
// Limit with skill+spec
maxMargin = confront.rollData1.skill.value + ((confront.rollData1.spec) ? 2 : 0)
maxMargin = confront.rollData1.skill.max + ((confront.rollData1.spec) ? 2 : 0)
confront.marginPreservation = Math.min(confront.marginPreservation, maxMargin)
} else { // Failed defense
maxMargin = confront.rollData2.skill.value + ((confront.rollData2.spec) ? 2 : 0)
maxMargin = confront.rollData2.skill.max + ((confront.rollData2.spec) ? 2 : 0)
confront.marginPreservation = - Math.min(Math.abs(confront.marginPreservation), maxMargin)
}
@@ -264,7 +264,7 @@ export class EcrymeUtility {
}
let msg = await this.createChatWithRollMode(this.confrontData1.alias, {
content: await renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-confrontation-result.hbs`, confront)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-confrontation-result.hbs`, confront)
})
await msg.setFlag("world", "ecryme-rolldata", confront)
console.log("Confront result", confront)
@@ -287,7 +287,7 @@ export class EcrymeUtility {
rollData.marginPreservation = -1
}
let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-cephaly-result.hbs`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-cephaly-result.hbs`, rollData)
})
msg.setFlag("world", "ecryme-rolldata", rollData)
console.log("Cephaly result", rollData)
@@ -457,7 +457,7 @@ export class EcrymeUtility {
let id = rollData.rollId
let oldRollData = this.rollDataStore[id] || {}
let newRollData = foundry.utils.mergeObject(oldRollData, rollData)
let newRollData = { ...oldRollData, ...rollData }
this.rollDataStore[id] = newRollData
}
@@ -468,7 +468,7 @@ export class EcrymeUtility {
let rollData = msg.data.rollData
if (game.user.isGM) {
let chatMsg = await this.createChatMessage(rollData.alias, "blindroll", {
content: await renderTemplate(msg.data.template, rollData),
content: await foundry.applications.handlebars.renderTemplate(msg.data.template, rollData),
whisper: game.user.id
})
chatMsg.setFlag("world", "ecryme-rolldata", rollData)
@@ -480,7 +480,7 @@ export class EcrymeUtility {
static async searchItem(dataItem) {
let item
if (dataItem.pack) {
let id = dataItem.id || dataItem._id
let id = dataItem.id
let items = await this.loadCompendium(dataItem.pack, item => item.id == id)
item = items[0] || undefined
} else {
@@ -499,7 +499,7 @@ export class EcrymeUtility {
if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id);
if (chatData.rollMode === "blindroll") chatData["blind"] = true;
else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user];
else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user.id];
if (forceWhisper) { // Final force !
chatData["speaker"] = ChatMessage.getSpeaker();
@@ -551,7 +551,7 @@ export class EcrymeUtility {
rollData.margin = rollData.total - rollData.difficulty
if (rollData.total > rollData.difficulty) {
rollData.isSuccess = true
let maxMargin = rollData.skill.value + ((rollData.spec) ? 2 : 0)
let maxMargin = rollData.skill.max + ((rollData.spec) ? 2 : 0)
rollData.margin = Math.min(rollData.margin, maxMargin)
}
}
@@ -567,11 +567,12 @@ export class EcrymeUtility {
diceFormula = (isConfrontation) ? "5d6kl2" : "3d6kl2"
}
if (rollData.skill) {
diceFormula += "+" + rollData.skill.value
diceFormula += "+" + rollData.skill.max
}
if (rollData.skillTranscendence) {
diceFormula += "+" + rollData.skillTranscendence
actor.spentSkillTranscendence(rollData.skill, rollData.skillTranscendence)
let toSpend = Math.min(rollData.skillTranscendence, rollData.skill.value)
diceFormula += "+" + toSpend
actor.spentSkillTranscendence(rollData.skill, toSpend)
}
if (rollData.selectedSpecs && rollData.selectedSpecs.length > 0) {
rollData.spec = actor.getSpecialization(rollData.selectedSpecs[0])
@@ -620,7 +621,7 @@ export class EcrymeUtility {
// Performs roll
let myRoll = await new Roll(diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
rollData.roll = foundry.utils.deepClone(myRoll)
rollData.total = myRoll.total
rollData.diceSum = myRoll.terms[0].total
@@ -644,7 +645,7 @@ export class EcrymeUtility {
actor.spentSkillTranscendence(rollData.skill, value)
let msg = await this.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-generic-result.hbs`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-ecryme/templates/chat/chat-generic-result.hbs`, rollData)
})
await msg.setFlag("world", "ecryme-rolldata", rollData)
}
@@ -748,7 +749,7 @@ export class EcrymeUtility {
useSpleen: false,
useIdeal: false,
impactMalus: 0,
config: foundry.utils.duplicate(game.system.ecryme.config)
config: foundry.utils.deepClone(game.ecryme.config)
}
EcrymeUtility.updateWithTarget(rollData)
return rollData
@@ -770,29 +771,9 @@ export class EcrymeUtility {
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
let msgTxt = "<p>Etes vous certain de souhaiter envoyer cet item dans les limbes ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Oui, retirez-le",
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: "Confirm removal",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
if (!confirm("Etes vous certain de souhaiter envoyer cet item dans les limbes ?")) return;
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false));
}
}