Init/progression dice
This commit is contained in:
@ -2,6 +2,17 @@
|
||||
/* -------------------------------------------- */
|
||||
export class LethalFantasyCombatTracker extends CombatTracker {
|
||||
|
||||
async getData(options) {
|
||||
let data = await super.getData(options);
|
||||
for (let u of data.turns) {
|
||||
let c = game.combat.combatants.get(u.id);
|
||||
u.progressionCount = c.system.progressionCount
|
||||
}
|
||||
console.log("Combat Data", data);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------- */
|
||||
static get defaultOptions() {
|
||||
let path = "systems/fvtt-lethal-fantasy/templates/combat-tracker.hbs";
|
||||
@ -36,104 +47,25 @@ export class LethalFantasyCombat extends Combat {
|
||||
|
||||
async rollInitiative(ids, options) {
|
||||
console.log("%%%%%%%%% Roll Initiative", ids, options);
|
||||
await this.setFlag("acks", "lock-turns", true);
|
||||
|
||||
ids = typeof ids === "string" ? [ids] : ids;
|
||||
let messages = [];
|
||||
let rollMode = game.settings.get("core", "rollMode");
|
||||
|
||||
// Get current groups
|
||||
let groups = this.getFlag('acks', 'groups') || [];
|
||||
let maxInit = { value: -1, cId: "" }
|
||||
let updates = [];
|
||||
for (let cId of ids) {
|
||||
const c = this.combatants.get(cId);
|
||||
//console.log("Init for combattant", cId, c, ids)
|
||||
let id = c._id || c.id
|
||||
// get the associated token
|
||||
let tokenId = c.token.id;
|
||||
// Check if the current token ID is in a group
|
||||
let groupData = groups.find((groupData) => groupData.tokens.includes(tokenId));
|
||||
let initValue = -1;
|
||||
let showMessage = true
|
||||
let roll
|
||||
if (groupData && groupData.initiative > 0) {
|
||||
initValue = groupData.initiative;
|
||||
showMessage = false
|
||||
let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id);
|
||||
if (user?.hasPlayerOwner) {
|
||||
console.log("Rolling initiative for", c.actor.name);
|
||||
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollInitiative", actorId: c.actor.id, combatId: this.id, combatantId: c.id });
|
||||
} else {
|
||||
roll = c.getInitiativeRoll();
|
||||
await roll.evaluate();
|
||||
initValue = roll.total;
|
||||
}
|
||||
if (groupData) {
|
||||
groupData.initiative = initValue
|
||||
}
|
||||
updates.push({ _id: id, initiative: initValue });
|
||||
if (initValue > maxInit.value) {
|
||||
maxInit.value = initValue;
|
||||
maxInit.cId = id;
|
||||
}
|
||||
|
||||
if (showMessage) {
|
||||
// Determine the roll mode
|
||||
if ((c.token.hidden || c.hidden)
|
||||
&& (rollMode === "roll")) {
|
||||
rollMode = "gmroll";
|
||||
}
|
||||
|
||||
// Construct chat message data
|
||||
const messageData = foundry.utils.mergeObject({
|
||||
speaker: {
|
||||
scene: canvas.scene._id,
|
||||
actor: c.actor?.id || null,
|
||||
token: c.token.id,
|
||||
alias: c.token.name
|
||||
},
|
||||
flavor: game.i18n.format('ACKS.roll.individualInit', {
|
||||
name: c.token.name,
|
||||
}),
|
||||
}, {});
|
||||
|
||||
const chatData = await roll.toMessage(messageData, {
|
||||
rollMode,
|
||||
create: false,
|
||||
});
|
||||
if (messages.length > 0) {
|
||||
chatData.sound = null;
|
||||
}
|
||||
messages.push(chatData);
|
||||
user = game.users.find(u => u.active && u.isGM);
|
||||
c.actor.system.rollInitiative(this.id, c.id);
|
||||
}
|
||||
}
|
||||
|
||||
await CONFIG.ChatMessage.documentClass.create(messages);
|
||||
this.pools = AcksCombat.getCombatantsPool();
|
||||
await this.processOutNumbering();
|
||||
|
||||
await this.setFlag("acks", "lock-turns", false);
|
||||
await this.updateEmbeddedDocuments("Combatant", updates);
|
||||
|
||||
setTimeout(function () {
|
||||
const updateData = { turn: 0 };
|
||||
game.combat.update(updateData);
|
||||
}, 200);
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
async startCombat() {
|
||||
console.log("Start Combat 1 !")
|
||||
// Send chat message to all players to roll for initiative
|
||||
ChatMessage.create({
|
||||
user: game.user.id,
|
||||
content: await renderTemplate(`systems/fvtt-lethal-fantasy/templates/chat-ask-initiative.hbs`, {
|
||||
title: "Initiative roll requested",
|
||||
text: text,
|
||||
rollType: type,
|
||||
}),
|
||||
flags: { "fvtt-lethal-fantasy": { msg: "request-initiative-roll", content: {} } },
|
||||
})
|
||||
ChatMessage.create({ content: message, type: CONST.CHAT_MESSAGE_TYPES.GAME });
|
||||
}
|
||||
|
||||
async nextTurn() {
|
||||
@ -170,12 +102,25 @@ export class LethalFantasyCombat extends Combat {
|
||||
this.turnsDone = false
|
||||
|
||||
let turn = this.turn === null ? null : 0; // Preserve the fact that it's no-one's turn currently.
|
||||
console.log("ROUND", this.round, this.turns);
|
||||
console.log("ROUND", this);
|
||||
|
||||
let advanceTime = Math.max(this.turns.length - this.turn, 0) * CONFIG.time.turnTime;
|
||||
advanceTime += CONFIG.time.roundTime;
|
||||
let nextRound = this.round + 1;
|
||||
|
||||
for (let c of this.combatants) {
|
||||
if ( nextRound >= c.initiative) {
|
||||
c.update({ 'system.progressionCount': c.system.progressionCount + 1 });
|
||||
let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id);
|
||||
if (user?.hasPlayerOwner) {
|
||||
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollProgressionDice", progressionCount: c.system.progressionCount+1, actorId: c.actor.id, combatId: this.id, combatantId: c.id });
|
||||
} else {
|
||||
user = game.users.find(u => u.active && u.isGM);
|
||||
c.actor.system.rollProgressionDice(this.id, c.id, c.system.progressionCount+1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the document, passing data through a hook first
|
||||
const updateData = { round: nextRound, turn };
|
||||
const updateOptions = { advanceTime, direction: 1 };
|
||||
|
@ -6,7 +6,7 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["character"],
|
||||
position: {
|
||||
width: 1080,
|
||||
width: 972,
|
||||
height: 780,
|
||||
},
|
||||
window: {
|
||||
@ -18,6 +18,10 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
|
||||
rollInitiative: LethalFantasyCharacterSheet.#onRollInitiative,
|
||||
armorHitPointsPlus: LethalFantasyCharacterSheet.#onArmorHitPointsPlus,
|
||||
armorHitPointsMinus: LethalFantasyCharacterSheet.#onArmorHitPointsMinus,
|
||||
divinityPointsPlus: LethalFantasyCharacterSheet.#onDivinityPointsPlus,
|
||||
divinityPointsMinus: LethalFantasyCharacterSheet.#onDivinityPointsMinus,
|
||||
aetherPointsPlus: LethalFantasyCharacterSheet.#onAetherPointsPlus,
|
||||
aetherPointsMinus: LethalFantasyCharacterSheet.#onAetherPointsMinus,
|
||||
},
|
||||
}
|
||||
|
||||
@ -162,22 +166,7 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
|
||||
}
|
||||
|
||||
static async #onRollInitiative(event, target) {
|
||||
const hasTarget = false
|
||||
let actorClass = this.actor.system.biodata.class;
|
||||
|
||||
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find((c) => c.value === this.actor.system.characteristics.wis.value)
|
||||
let maxInit = Number(wisDef.init_cap) || 1000
|
||||
|
||||
let roll = await LethalFantasyRoll.promptInitiative({
|
||||
actorId: this.actor.id,
|
||||
actorName: this.actor.name,
|
||||
actorImage: this.actor.img,
|
||||
actorClass,
|
||||
maxInit,
|
||||
})
|
||||
if (!roll) return null
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
await this.document.system.rollInitiative()
|
||||
}
|
||||
|
||||
static #onArmorHitPointsPlus(event, target) {
|
||||
@ -192,6 +181,34 @@ export default class LethalFantasyCharacterSheet extends LethalFantasyActorSheet
|
||||
this.actor.update({ "system.combat.armorHitPoints": Math.max(armorHP, 0) })
|
||||
}
|
||||
|
||||
static #onDivinityPointsPlus(event, target) {
|
||||
let points = this.actor.system.divinityPoints.value
|
||||
points += 1
|
||||
points = Math.min(points, this.actor.system.divinityPoints.max)
|
||||
this.actor.update({ "system.divinityPoints.value": points })
|
||||
}
|
||||
|
||||
static #onDivinityPointsMinus(event, target) {
|
||||
let points = this.actor.system.divinityPoints.value
|
||||
points -= 1
|
||||
points = Math.max(points, 0)
|
||||
this.actor.update({ "system.divinityPoints.value": points })
|
||||
}
|
||||
|
||||
static #onAetherPointsPlus(event, target) {
|
||||
let points = this.actor.system.aetherPoints.value
|
||||
points += 1
|
||||
points = Math.min(points, this.actor.system.aetherPoints.max)
|
||||
this.actor.update({ "system.aetherPoints.value": points })
|
||||
}
|
||||
|
||||
static #onAetherPointsMinus(event, target) {
|
||||
let points = this.actor.system.aetherPoints.value
|
||||
points -= 1
|
||||
points = Math.max(points, 0)
|
||||
this.actor.update({ "system.aetherPoints.value": points })
|
||||
}
|
||||
|
||||
static #onCreateEquipment(event, target) {
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ export default class LethalFantasyMonsterSheet extends LethalFantasyActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["monster"],
|
||||
position: {
|
||||
width: 980,
|
||||
width: 1060,
|
||||
height: 780,
|
||||
},
|
||||
window: {
|
||||
@ -118,37 +118,7 @@ export default class LethalFantasyMonsterSheet extends LethalFantasyActorSheet {
|
||||
}
|
||||
|
||||
static async #onRollInitiative(event, target) {
|
||||
const hasTarget = false
|
||||
let actorClass = this.actor.system.biodata.class;
|
||||
|
||||
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find((c) => c.value === this.actor.system.characteristics.wis.value)
|
||||
let maxInit = Number(wisDef.init_cap) || 1000
|
||||
|
||||
let roll = await LethalFantasyRoll.promptInitiative({
|
||||
actorId: this.actor.id,
|
||||
actorName: this.actor.name,
|
||||
actorImage: this.actor.img,
|
||||
actorClass,
|
||||
maxInit,
|
||||
})
|
||||
if (!roll) return null
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
}
|
||||
|
||||
static #onArmorHitPointsPlus(event, target) {
|
||||
let armorHP = this.actor.system.combat.armorHitPoints
|
||||
armorHP += 1
|
||||
this.actor.update({ "system.combat.armorHitPoints": armorHP })
|
||||
}
|
||||
|
||||
static #onArmorHitPointsMinus(event, target) {
|
||||
let armorHP = this.actor.system.combat.armorHitPoints
|
||||
armorHP -= 1
|
||||
this.actor.update({ "system.combat.armorHitPoints": Math.max(armorHP, 0) })
|
||||
}
|
||||
|
||||
static #onCreateEquipment(event, target) {
|
||||
await this.document.system.rollInitiative(event, target)
|
||||
}
|
||||
|
||||
getBestWeaponClassSkill(skills, rollType, multiplier = 1.0) {
|
||||
|
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Menu spécifique au système
|
||||
*/
|
||||
export function initControlButtons() {
|
||||
CONFIG.Canvas.layers.tenebris = { layerClass: ControlsLayer, group: "primary" }
|
||||
|
||||
Hooks.on("getSceneControlButtons", (btns) => {
|
||||
let menu = []
|
||||
|
||||
menu.push({
|
||||
name: "fortune",
|
||||
title: game.i18n.localize("TENEBRIS.Fortune.title"),
|
||||
icon: "fa-solid fa-clover",
|
||||
button: true,
|
||||
onClick: () => {
|
||||
if (!foundry.applications.instances.has("tenebris-application-fortune")) {
|
||||
game.system.applicationFortune.render(true)
|
||||
} else game.system.applicationFortune.close()
|
||||
},
|
||||
})
|
||||
|
||||
if (game.user.isGM) {
|
||||
menu.push({
|
||||
name: "gm-manager",
|
||||
title: game.i18n.localize("TENEBRIS.Manager.title"),
|
||||
icon: "fa-solid fa-users",
|
||||
button: true,
|
||||
onClick: () => {
|
||||
if (!foundry.applications.instances.has("tenebris-application-manager")) {
|
||||
game.system.applicationManager.render(true)
|
||||
} else game.system.applicationManager.close()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
btns.push({
|
||||
name: "tenebris",
|
||||
title: "Cthulhu LethalFantasy",
|
||||
icon: "tenebris",
|
||||
layer: "tenebris",
|
||||
tools: menu,
|
||||
})
|
||||
})
|
||||
}
|
@ -229,7 +229,7 @@ export default class LethalFantasyRoll extends Roll {
|
||||
options.rollName = options.rollTarget.name
|
||||
hasModifier = true
|
||||
hasChangeDice = false
|
||||
options.rollTarget.value = 0
|
||||
options.rollTarget.value = options.rollTarget.damageModifier
|
||||
options.rollTarget.charModifier = 0
|
||||
dice = options.rollTarget.damageDice
|
||||
dice = dice.replace("E", "")
|
||||
@ -517,11 +517,95 @@ export default class LethalFantasyRoll extends Roll {
|
||||
|
||||
let initRoll = new Roll(`min(${rollContext.initiativeDice}, ${options.maxInit})`, options.data, rollContext)
|
||||
await initRoll.evaluate()
|
||||
initRoll.toMessage( {flavor: `Initiative for ${options.actorName}`}, {rollMode: rollContext.visibility} )
|
||||
let msg = await initRoll.toMessage( {flavor: `Initiative for ${options.actorName}`}, {rollMode: rollContext.visibility} )
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
|
||||
|
||||
if (options.combatId && options.combatantId) {
|
||||
let combat = game.combats.get(options.combatId)
|
||||
combat.updateEmbeddedDocuments("Combatant", [ { _id: options.combatantId, initiative: initRoll.total, 'system.progressionCount': 0 } ]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static async promptProgressionDice(options = {}) {
|
||||
|
||||
const rollModes = Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)]))
|
||||
const fieldRollMode = new foundry.data.fields.StringField({
|
||||
choices: rollModes,
|
||||
blank: false,
|
||||
default: "public",
|
||||
})
|
||||
let dialogContext = {
|
||||
progressionDiceId: "",
|
||||
fieldRollMode,
|
||||
rollModes,
|
||||
...options
|
||||
}
|
||||
|
||||
console.log("CTX PROGRESSION", dialogContext)
|
||||
|
||||
const content = await renderTemplate("systems/fvtt-lethal-fantasy/templates/roll-progression-dice-dialog.hbs", dialogContext)
|
||||
|
||||
const label = game.i18n.localize("LETHALFANTASY.Label.rollProgressionDice")
|
||||
const rollContext = await foundry.applications.api.DialogV2.wait({
|
||||
window: { title: "Progression Roll" },
|
||||
classes: ["lethalfantasy"],
|
||||
content,
|
||||
buttons: [
|
||||
{
|
||||
action: "roll",
|
||||
label: label,
|
||||
callback: (event, button, dialog) => {
|
||||
const output = Array.from(button.form.elements).reduce((obj, input) => {
|
||||
if (input.name) obj[input.name] = input.value
|
||||
return obj
|
||||
}, {})
|
||||
return output
|
||||
},
|
||||
},
|
||||
{
|
||||
action: "cancel",
|
||||
label: "Other action, no weapon progression dice",
|
||||
callback: (event, button, dialog) => { return null; }
|
||||
}
|
||||
],
|
||||
rejectClose: false // Click on Close button will not launch an error
|
||||
})
|
||||
|
||||
console.log("RollContext", rollContext)
|
||||
if (rollContext === null || !rollContext?.progressionDiceId) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the weapons from the actor items
|
||||
let actor = game.actors.get(options.actorId)
|
||||
let weapon = actor.items.find(i => i.type === "weapon" && i.id === rollContext.progressionDiceId)
|
||||
// Get the dice and roll it
|
||||
let formula = weapon.system.combatProgressionDice
|
||||
let roll = new Roll(formula)
|
||||
await roll.evaluate()
|
||||
|
||||
let max = roll.dice[0].faces - 1
|
||||
max = Math.min(options.rollProgressionCount, max)
|
||||
let msg = await roll.toMessage( {flavor: `Progression Roll for ${weapon.name}, progression count : ${options.rollProgressionCount}/${max}`}, {rollMode: rollContext.visibility} )
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
|
||||
|
||||
if (roll.total <= max ) {
|
||||
// Notify that the player can act now with a chat message
|
||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOK", { name: actor.name, weapon: weapon.name, roll: roll.total })
|
||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: actor }) })
|
||||
// Update the combatant progression count
|
||||
let combat = game.combats.get(options.combatId)
|
||||
let combatant = combat.combatants.get(options.combatantId)
|
||||
combatant.update({ 'system.progressionCount': 0 })
|
||||
} else {
|
||||
// Notify that the player cannot act now with a chat message
|
||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKO", { name: actor.name, weapon: weapon.name, roll: roll.total })
|
||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: actor }) })
|
||||
}
|
||||
}
|
||||
|
||||
static async promptRangedDefense(rollTarget) {
|
||||
|
||||
|
||||
const rollModes = Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)]))
|
||||
const fieldRollMode = new foundry.data.fields.StringField({
|
||||
@ -681,30 +765,8 @@ export default class LethalFantasyRoll extends Roll {
|
||||
return await renderTemplate(this.constructor.CHAT_TEMPLATE, chatData)
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Generates the data required for rendering a roll chat card.
|
||||
*
|
||||
* @param {boolean} isPrivate Indicates if the chat card is private.
|
||||
* @returns {Promise<Object>} A promise that resolves to an object containing the chat card data.
|
||||
* @property {Array<string>} css - CSS classes for the chat card.
|
||||
* @property {Object} data - The data associated with the roll.
|
||||
* @property {number} diceTotal - The total value of the dice rolled.
|
||||
* @property {boolean} isGM - Indicates if the user is a Game Master.
|
||||
* @property {string} formula - The formula used for the roll.
|
||||
* @property {number} total - The total result of the roll.
|
||||
* @property {boolean} isSave - Indicates if the roll is a saving throw.
|
||||
* @property {boolean} isDamage - Indicates if the roll is for damage.
|
||||
* @property {boolean} isFailure - Indicates if the roll is a failure.
|
||||
* @property {string} actorId - The ID of the actor performing the roll.
|
||||
* @property {string} actingCharName - The name of the character performing the roll.
|
||||
* @property {string} actingCharImg - The image of the character performing the roll.
|
||||
* @property {string} resultType - The type of result (e.g., success, failure).
|
||||
* @property {boolean} hasTarget - Indicates if the roll has a target.
|
||||
* @property {string} targetName - The name of the target.
|
||||
* @property {number} targetArmor - The armor value of the target.
|
||||
* @property {boolean} isPrivate - Indicates if the chat card is private.
|
||||
* @property {string} cssClass - The combined CSS classes as a single string.
|
||||
* @property {string} tooltip - The tooltip text for the chat card.
|
||||
*/
|
||||
async _getChatCardData(isPrivate) {
|
||||
const cardData = {
|
||||
|
@ -59,7 +59,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
|
||||
)
|
||||
const woundFieldSchema = {
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
duration: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
description: new fields.StringField({ initial: "", required: false, nullable: true }),
|
||||
}
|
||||
@ -68,9 +68,11 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||
painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
wounds: new fields.ArrayField(new fields.SchemaField(woundFieldSchema ) , { initial: [{description:"", value:0, duration:0},{description:"", value:0, duration:0},
|
||||
{description:"", value:0, duration:0},{description:"", value:0, duration:0},{description:"", value:0, duration:0},{description:"", value:0, duration:0},
|
||||
{description:"", value:0, duration:0},{description:"", value:0, duration:0}], min:8} ),
|
||||
wounds: new fields.ArrayField(new fields.SchemaField(woundFieldSchema), {
|
||||
initial: [{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
|
||||
{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
|
||||
{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }], min: 8
|
||||
}),
|
||||
})
|
||||
|
||||
schema.perception = new fields.SchemaField({
|
||||
@ -99,7 +101,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
vertical: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
})
|
||||
schema.biodata = new fields.SchemaField({
|
||||
class: new fields.StringField({required: true, initial: "untrained", choices: SYSTEM.CHAR_CLASSES}),
|
||||
class: new fields.StringField({ required: true, initial: "untrained", choices: SYSTEM.CHAR_CLASSES }),
|
||||
level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
|
||||
mortal: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
@ -110,13 +112,13 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
magicUser: new fields.BooleanField({ initial: false }),
|
||||
clericUser: new fields.BooleanField({ initial: false }),
|
||||
})
|
||||
|
||||
|
||||
schema.modifiers = new fields.SchemaField({
|
||||
levelSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
saveModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
levelMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
intSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
chaMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
chaMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
})
|
||||
|
||||
schema.developmentPoints = new fields.SchemaField({
|
||||
@ -127,6 +129,14 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
used: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||
})
|
||||
schema.aetherPoints = new fields.SchemaField({
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||
})
|
||||
schema.divinityPoints = new fields.SchemaField({
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||
})
|
||||
schema.combat = new fields.SchemaField({
|
||||
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
@ -187,7 +197,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
let conDef = SYSTEM.CHARACTERISTICS_TABLES.con.find(s => s.value === this.characteristics.con.value)
|
||||
this.saves.pain.value = conDef.pain_save + this.modifiers.saveModifier
|
||||
this.saves.toughness.value = conDef.toughness_save + this.modifiers.saveModifier
|
||||
this.challenges.dying.value = conDef.stabilization_dice
|
||||
this.challenges.dying.value = conDef.stabilization_dice
|
||||
|
||||
this.saves.contagion.value = this.characteristics.con.value + this.modifiers.saveModifier
|
||||
this.saves.poison.value = this.characteristics.con.value + this.modifiers.saveModifier
|
||||
@ -209,7 +219,7 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
|
||||
this.combat.damageModifier += chaDef.damage
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -234,4 +244,46 @@ export default class LethalFantasyCharacter extends foundry.abstract.TypeDataMod
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
}
|
||||
|
||||
async rollInitiative(combatId = undefined, combatantId = undefined) {
|
||||
const hasTarget = false
|
||||
let actorClass = this.biodata.class;
|
||||
|
||||
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find((c) => c.value === this.characteristics.wis.value)
|
||||
let maxInit = Number(wisDef.init_cap) || 1000
|
||||
console.log("Rolling initiative for", this)
|
||||
|
||||
let roll = await LethalFantasyRoll.promptInitiative({
|
||||
actorId: this.parent.id,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
combatId,
|
||||
combatantId ,
|
||||
actorClass,
|
||||
maxInit,
|
||||
})
|
||||
if (!roll) return null
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
}
|
||||
|
||||
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
|
||||
|
||||
// Get all weapons from the actor
|
||||
let weapons = this.parent.items.filter(i => i.type === "weapon")
|
||||
let weaponsChoices = weapons.map(w => { return { id: w.id, name: `${w.name} (${w.system.combatProgressionDice})`, combatProgressionDice: w.system.combatProgressionDice } })
|
||||
|
||||
let roll = await LethalFantasyRoll.promptProgressionDice({
|
||||
actorId: this.parent.id,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
weaponsChoices,
|
||||
combatId,
|
||||
combatantId,
|
||||
rollProgressionCount,
|
||||
type: "progression",
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
||||
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
damageDice: new fields.StringField({ required: true, nullable: false, initial: "1D6" }),
|
||||
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
}
|
||||
return new fields.SchemaField(schema, { label })
|
||||
}
|
||||
@ -104,7 +105,7 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
||||
height: new fields.NumberField({ ...requiredInteger, initial: 170, min: 50 }),
|
||||
length: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||
weight: new fields.NumberField({ ...requiredInteger, initial: 70, min: 0 })
|
||||
})
|
||||
})
|
||||
schema.combat = new fields.SchemaField({
|
||||
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||
@ -141,4 +142,59 @@ export default class LethalFantasyMonster extends foundry.abstract.TypeDataModel
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
}
|
||||
|
||||
async rollInitiative(combatId = undefined, combatantId = undefined) {
|
||||
const hasTarget = false
|
||||
|
||||
let maxInit = 100
|
||||
|
||||
let roll = await LethalFantasyRoll.promptInitiative({
|
||||
actorId: this.parent.id,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
combatId,
|
||||
combatantId,
|
||||
maxInit,
|
||||
})
|
||||
if (!roll) return null
|
||||
|
||||
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||
}
|
||||
|
||||
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
|
||||
|
||||
const rollModes = Object.fromEntries(Object.entries(CONFIG.Dice.rollModes).map(([key, value]) => [key, game.i18n.localize(value)]))
|
||||
const fieldRollMode = new foundry.data.fields.StringField({
|
||||
choices: rollModes,
|
||||
blank: false,
|
||||
default: "public",
|
||||
})
|
||||
|
||||
let roll = new Roll("1D8")
|
||||
await roll.evaluate()
|
||||
let max = rollProgressionCount
|
||||
let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}, progression count : ${rollProgressionCount}/${max}` } )
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
|
||||
|
||||
let hasAttack = false
|
||||
for (let key in this.attacks) {
|
||||
let attack = this.attacks[key]
|
||||
if (attack.attackScore > 0 && attack.attackScore === roll.total) {
|
||||
hasAttack = true
|
||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionOK", { name: this.parent.name, weapon: attack.name, roll: roll.total })
|
||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
||||
// Update the combatant progression count
|
||||
let combat = game.combats.get(combatId)
|
||||
let combatant = combat.combatants.get(combatantId)
|
||||
combatant.update({ 'system.progressionCount': 0 })
|
||||
}
|
||||
}
|
||||
if (!hasAttack) {
|
||||
let message = game.i18n.format("LETHALFANTASY.Notifications.messageProgressionKO", { name: this.parent.name, roll: roll.total })
|
||||
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,32 +0,0 @@
|
||||
|
||||
/**
|
||||
* Handles socket events based on the provided action.
|
||||
*
|
||||
* @param {Object} [params={}] The parameters for the socket event.
|
||||
* @param {string|null} [params.action=null] The action to be performed.
|
||||
* @param {Object} [params.data={}] The data associated with the action.
|
||||
* @returns {*} The result of the action handler, if applicable.
|
||||
*/
|
||||
export function handleSocketEvent({ action = null, data = {} } = {}) {
|
||||
console.debug("handleSocketEvent", action, data)
|
||||
switch (action) {
|
||||
case "fortune":
|
||||
return LethalFantasyFortune.handleSocketEvent(data)
|
||||
case "askRoll":
|
||||
return _askRoll(data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the socket event to ask for a roll.
|
||||
*
|
||||
* @param {Object} [options={}] The options object.
|
||||
* @param {string} [options.userId] The ID of the user who initiated the roll.
|
||||
*/
|
||||
export function _askRoll({ userId } = {}) {
|
||||
console.debug(`handleSocketEvent _askRoll from ${userId} !`)
|
||||
const currentUser = game.user._id
|
||||
if (userId === currentUser) {
|
||||
foundry.audio.AudioHelper.play({ src: "/systems/fvtt-lethal-fantasy/sounds/drums.wav", volume: 0.8, autoplay: true, loop: false }, false)
|
||||
}
|
||||
}
|
@ -13,6 +13,21 @@ export default class LethalFantasyUtils {
|
||||
return compendiumData.filter(filter)
|
||||
}
|
||||
|
||||
static handleSocketEvent(msg = {}) {
|
||||
console.log(`handleSocketEvent !`, msg)
|
||||
let actor
|
||||
switch (msg.type) {
|
||||
case "rollInitiative":
|
||||
actor = game.actors.get(msg.actorId)
|
||||
actor.system.rollInitiative(msg.combatId, msg.combatantId)
|
||||
break
|
||||
case "rollProgressionDice":
|
||||
actor = game.actors.get(msg.actorId)
|
||||
actor.system.rollProgressionDice(msg.combatId, msg.combatantId, msg.rollProgressionCount)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
static registerHandlebarsHelpers() {
|
||||
|
||||
Handlebars.registerHelper('isNull', function (val) {
|
||||
|
Reference in New Issue
Block a user