90de66d668
Release Creation / build (release) Failing after 1m28s
- hooks.mjs: replace static dice-so-nice import with dynamic import
using game.modules.get('dice-so-nice').id (path removed in v14)
- hooks.mjs: fix permission condition (|| -> &&), jQuery -> vanilla JS
- less/base.less: override --color-text-* and --button-text-color
for both .themed.theme-dark (AppV2) and body.theme-dark (legacy apps)
- target #settings-config buttons + labels + hints for dark grays
286 lines
9.4 KiB
JavaScript
286 lines
9.4 KiB
JavaScript
|
|
|
|
/**
|
|
* Handles combat-related dice rolls for Vermine2047.
|
|
* Uses d10-based system with success counting as per official rules.
|
|
*/
|
|
export class VermineFight {
|
|
|
|
/**
|
|
* Performs a d10-based test according to Vermine2047 official rules.
|
|
* Each die result is compared to a difficulty threshold.
|
|
* Each result >= difficulty counts as 1 Success.
|
|
*
|
|
* @param {number} enemyAchievement - Opponent's achievement score
|
|
* @param {number} enemyConservation - Opponent's conservation score
|
|
* @param {string} skillKey - The skill key being tested
|
|
* @param {number} skill - The skill value
|
|
* @param {Object} params - Additional test parameters
|
|
* @param {number} [params.difficulty=7] - Difficulty threshold (3-10)
|
|
* @param {boolean} [params.spleen] - Whether to use spleen rule
|
|
* @param {boolean} [params.purpose] - Whether to use purpose rule
|
|
* @param {number} [params.usure] - Wear/usage modifier
|
|
* @param {number} [params.trait] - Trait bonus
|
|
* @param {boolean} [params.specialization] - Whether specialization applies
|
|
* @param {Actor} actor - The actor performing the test
|
|
* @returns {Promise<Object>} Roll data including successes count
|
|
*/
|
|
async performTest(enemyAchievement, enemyConservation, skillKey, skill, params, actor) {
|
|
// Use d10 as per Vermine2047 official rules
|
|
const basePool = Math.max(skill, 1)
|
|
const dicePool = (params.spleen != undefined || params.purpose != undefined) ? String(basePool + 1) : String(basePool)
|
|
const difficulty = params.difficulty || 7; // Default difficulty
|
|
const r = new Roll(dicePool + `d10`);
|
|
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('VERMINE.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('VERMINE.PurposeTrait');
|
|
} else if (params.spleen != undefined) {
|
|
discardedRoll = r.terms[0].results.pop();
|
|
dicePoolHint = ' - ' + game.i18n.format('VERMINE.SpleenTrait');
|
|
}
|
|
const discardedRollText = (discardedRoll.result != undefined) ? '<div class="discarded-roll">' + discardedRoll.result + '</div>' : "";
|
|
|
|
// Count successes (dice >= difficulty) as per Vermine2047 rules
|
|
let successes = 0;
|
|
for (let i = 0; i < r.terms[0].results.length; i++) {
|
|
let result = r.terms[0].results[i].result;
|
|
if (result >= difficulty) {
|
|
successes++;
|
|
}
|
|
diceString += '<li class="roll die d10 die-' + i + (result >= difficulty ? ' success' : '') + '">' + result + '</li>';
|
|
}
|
|
|
|
let hintText = game.i18n.format('VERMINE.ConfrontationHint');
|
|
|
|
// Build a dynamic html using the variables from above.
|
|
const html = `
|
|
<div class="vermine2047 roll confrontation">
|
|
<div class="dice-roll">
|
|
<div class="dice-result">
|
|
<div class="dice-formula">
|
|
` + dicePool + `d10 ` + 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('VERMINE.Achievement') + `</a>
|
|
<a class="inline-block button add-to-conservation">` + game.i18n.format('VERMINE.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 class="success-count">
|
|
<strong>` + game.i18n.format('VERMINE.success_count') + `:</strong> <span class="success-value">` + successes + `</span>
|
|
</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);
|
|
};
|
|
|
|
// Return roll data for further processing with d10 success counting
|
|
return {
|
|
roll: r,
|
|
successes: successes,
|
|
difficulty: difficulty,
|
|
dicePool: parseInt(dicePool, 10),
|
|
total: r.total
|
|
};
|
|
|
|
}
|
|
|
|
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 (!VermineFight.instance)
|
|
VermineFight.instance = new VermineFight();
|
|
return VermineFight.instance;
|
|
}
|
|
|
|
|
|
// data injected to char data
|
|
static previousValues = {
|
|
dicePool: 4,
|
|
skills: [],
|
|
cskills: [],
|
|
cephalic: false,
|
|
achievementReroll: 0,
|
|
conservationReroll: 0
|
|
};
|
|
|
|
static rollerTemplate = 'systems/vermine2047/templates/fight.html';
|
|
static CombatResultTemplate = 'systems/vermine2047/templates/fight-result.html';
|
|
|
|
}
|
|
|
|
export class VermineCombat extends Combat {
|
|
_encounterCheck() {
|
|
// encounter check
|
|
}
|
|
|
|
async rollInitiative(ids, formula = undefined, messageOptions = {}) {
|
|
// roll initiative
|
|
return super.rollInitiative(ids, formula, messageOptions)
|
|
|
|
}
|
|
|
|
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()*/
|
|
}
|
|
}
|
|
|
|
export class VermineCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
|
|
|
get template() {
|
|
return "systems/vermine2047/templates/combat-tracker.hbs";
|
|
}
|
|
|
|
async getData(options) {
|
|
const context = await super.getData(options);
|
|
|
|
if (!context.hasCombat) {
|
|
return context;
|
|
}
|
|
|
|
|
|
return context;
|
|
}
|
|
|
|
activateListeners(html) {
|
|
super.activateListeners(html);
|
|
html.querySelectorAll("[data-attitude]").forEach(el => {
|
|
el.addEventListener("click", this._setStatut.bind(this));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @description Use to put an attitude to an actor
|
|
* @param {*} event
|
|
*/
|
|
|
|
async _setStatut(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
const btn = event.currentTarget;
|
|
const attitude = btn.dataset.attitude;
|
|
const li = btn.closest(".combatant");
|
|
const combat = this.viewed;
|
|
const combatant = combat.combatants.get(li.dataset.combatantId);
|
|
let flag = combatant.getFlag("world", "attitude");
|
|
if (flag == attitude) {
|
|
await combatant.setFlag("world", "attitude", null);
|
|
} else {
|
|
await combatant.setFlag("world", "attitude", attitude);
|
|
}
|
|
|
|
let actor = await game.actors.get(combatant.actorId)
|
|
// combatant attitude set
|
|
}
|
|
}
|
|
|
|
export class VermineCombatant extends Combatant {
|
|
constructor(data, context) {
|
|
super(data, context)
|
|
if (this.token) {
|
|
this.attitude = this.token.flags.world?.attitude || "active"
|
|
} else {
|
|
this.attitude = "active"
|
|
}
|
|
}
|
|
_getInitiativeFormula() {
|
|
return String(CONFIG.Combat.initiative.formula || game.system.initiative)
|
|
}
|
|
} |