Attempt to add HUD core

This commit is contained in:
2025-11-12 23:41:15 +01:00
parent 68a0d03740
commit 6ad8226265
37 changed files with 1639 additions and 903 deletions

View File

@@ -16,6 +16,14 @@ export default class CthulhuEternalUtils {
config: true,
onChange: _ => window.location.reload()
});
game.settings.register("fvtt-cthulhu-eternal", "roll-opposed-store", {
name: "Roll Opposed Store",
hint: "Whether to store opposed roll results for later use",
default: { roll1: null, roll2: null },
scope: "world",
type: Object,
config: false
});
}
static async loadCompendiumData(compendium) {
@@ -180,6 +188,53 @@ export default class CthulhuEternalUtils {
});
}
/* -------------------------------------------- */
static removeChatMessageId(messageId) {
if (messageId) {
game.messages.get(messageId)?.delete();
}
}
static findChatMessageId(current) {
return HawkmoonUtility.getChatMessageId(HawkmoonUtility.findChatMessage(current));
}
static getChatMessageId(node) {
return node?.attributes.getNamedItem('data-message-id')?.value;
}
static findChatMessage(current) {
return HawkmoonUtility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id'))
}
static findNodeMatching(current, predicate) {
if (current) {
if (predicate(current)) {
return current;
}
return HawkmoonUtility.findNodeMatching(current.parentElement, predicate);
}
return undefined;
}
/* -------------------------------------------- */
static getWeaponSkill(actor, weapon, era) {
let skill
if (weapon.system.hasDirectSkill) {
let skillName = weapon.name
skill = { type: "skill", name: skillName, system: { base: 0, bonus: weapon.system.directSkillValue, skillTotal: weapon.system.directSkillValue } }
} else {
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][weapon.system.weaponType])
skill = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
if (!skill) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponSkill"))
return
}
}
return skill
}
/* -------------------------------------------- */
static async applySANType(rollMessage, event) {
let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
if (!rollData) {
@@ -248,6 +303,134 @@ export default class CthulhuEternalUtils {
})
}
static async opposedRollManagement(rollMessage, event) {
let rollData = rollMessage.getFlag("fvtt-cthulhu-eternal", "rollData")
if (!rollData) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noRollDataFound"))
return
}
// Get the store
let store = game.settings.get("fvtt-cthulhu-eternal", "roll-opposed-store")
if (!store.roll1) {
store.roll1 = {
rollData: rollData,
messageId: rollMessage.id
}
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollFirstStored"))
}
else if (!store.roll2) {
store.roll2 = {
rollData: rollData,
messageId: rollMessage.id
}
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
ui.notifications.info(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollSecondStored"))
// Now perform the opposed roll resolution
await this.resolveOpposedRolls(store.roll1, store.roll2)
// Clear the store
store.roll1 = null
store.roll2 = null
await game.settings.set("fvtt-cthulhu-eternal", "roll-opposed-store", store)
}
else {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.opposedRollStoreFull"))
}
}
static async resolveOpposedRolls(roll1, roll2) {
// Get actors
let actor1 = game.actors.get(roll1.rollData.actorId)
let actor2 = game.actors.get(roll2.rollData.actorId)
if (!actor1 || !actor2) {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noActorFound"))
return
}
// Determine winner
let winner = null
let loser = null
// If there critical success/failure, apply them first (remark : this d100 results)
roll1.rollData.rollCompare = roll1.rollData.rollResult
roll2.rollData.rollCompare = roll2.rollData.rollResult
if (roll1.rollData.resultType === "successCritical") {
roll1.rollData.rollCompare = -roll1.rollData.rollResult
}
if (roll2.rollData.resultType === "failureCritical") {
roll2.rollData.rollCompare = 100 + roll2.rollData.rollResult
}
if (roll2.rollData.resultType === "successCritical") {
roll2.rollData.rollCompare = -roll2.rollData.rollResult
}
if (roll1.rollData.resultType === "failureCritical") {
roll1.rollData.rollCompare = roll1.rollData.rollResult * 2
}
if (roll1.rollData.isSuccess && roll2.rollData.isFailure) {
winner = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
loser = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
}
else if (roll2.rollData.isSuccess && roll1.rollData.isFailure) {
winner = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
loser = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
}
else if (roll1.rollData.rollCompare < roll2.rollData.rollCompare) {
winner = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
loser = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
}
else {
winner = { actor: actor2, rollData: roll2.rollData, messageId: roll2.messageId }
loser = { actor: actor1, rollData: roll1.rollData, messageId: roll1.messageId }
}
console.log("Opposed roll result", winner, loser)
// Check if winner was attacking with a weapon that can apply damage
let canApplyDamage = winner && winner.rollData?.weapon && winner.rollData.weapon.system
// Prepare data for the template
let msgData = {
winner: {
actor: {
name: winner.actor.name,
img: winner.actor.img
},
rollData: {
rollResult: winner.rollData.rollResult || winner.rollData.total,
isCritical: winner.rollData.isCritical,
weapon: winner.rollData.weapon
}
},
loser: {
actor: {
name: loser.actor.name,
img: loser.actor.img
},
rollData: {
rollResult: loser.rollData.rollResult || loser.rollData.total,
isCritical: loser.rollData.isCritical
}
},
canApplyDamage: canApplyDamage
}
// Render the template
let content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-cthulhu-eternal/templates/chat-opposed-result.hbs",
msgData
)
// Display the result in chat
let chatMsg = await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: winner.actor.id }),
content: content
})
// Store the winner's roll data for damage roll if applicable
if (canApplyDamage) {
await chatMsg.setFlag("fvtt-cthulhu-eternal", "rollData", winner.rollData)
}
}
static translateRangeUnit(range) {
if (typeof range === 'string') {
return game.i18n.localize(`CTHULHUETERNAL.Label.${range}`)
@@ -285,6 +468,7 @@ export default class CthulhuEternalUtils {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Label.noActorFound"))
return
}
console.log("Damage roll data", rollData)
rollData.weapon.resultType = rollData.resultType // Keep the result type from the roll message
rollData.weapon.selectiveFireChoice = rollData.selectiveFireChoice // Keep the selected fire choice from the roll message
@@ -357,6 +541,10 @@ export default class CthulhuEternalUtils {
roll.toMessage()
actor.system.modifyWP(-dialogContext.wpCost)
// Delete the initial roll message
await rollMessage.delete()
}
static setupCSSRootVariables() {
@@ -395,6 +583,10 @@ export default class CthulhuEternalUtils {
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.noActorFound"))
return
}
console.log("Applying wounds", woundData)
// Remove the chat message
this.removeChatMessageId(message.id)
// Get the targetted actorId from the HTML select event
let targetCombatantId = event.target.value
let combatant = game.combat.combatants.get(targetCombatantId)