Manage selective fire
This commit is contained in:
@ -199,7 +199,7 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
|
||||
* corresponding value from the document's system and performs the roll.
|
||||
*/
|
||||
async _onRoll(event, target) {
|
||||
const rollType = $(event.currentTarget).data("roll-type")
|
||||
let rollType = $(event.currentTarget).data("roll-type")
|
||||
let item
|
||||
let li
|
||||
// Debug : console.log(">>>>", event, target, rollType)
|
||||
@ -231,6 +231,12 @@ export default class CthulhuEternalProtagonistSheet extends CthulhuEternalActorS
|
||||
item.name = game.i18n.localize("CTHULHUETERNAL.Label.SAN")
|
||||
item.targetScore = item.value
|
||||
break;
|
||||
case "luck":
|
||||
item = foundry.utils.duplicate(this.actor.system.characteristics.int)
|
||||
item.name = game.i18n.localize("CTHULHUETERNAL.Label.Luck")
|
||||
item.value = 10
|
||||
item.targetScore = 50
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown roll type ${rollType}`)
|
||||
}
|
||||
|
@ -18,4 +18,13 @@ export default class CthulhuEternalWeaponSheet extends CthulhuEternalItemSheet {
|
||||
template: "systems/fvtt-cthulhu-eternal/templates/weapon.hbs",
|
||||
},
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
let context = await super._prepareContext()
|
||||
context.isFireArm = this.item.system.isFireArm()
|
||||
context.isRanged = this.item.system.isRanged()
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,6 @@ export default class CthulhuEternalActor extends Actor {
|
||||
}
|
||||
|
||||
_onUpdate(changed, options, userId) {
|
||||
// DEBUG : console.log("CthulhuEternalActor.update", changed, options, userId)
|
||||
if (changed?.system?.wp?.exhausted) {
|
||||
ChatMessage.create({
|
||||
user: userId,
|
||||
|
@ -105,6 +105,105 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
$(".resource-score").text(`${rating} (${options.percentScore}%)`)
|
||||
}
|
||||
|
||||
static buildSelectiveFireChoices(actor, weapon) {
|
||||
if (!weapon || !weapon?.system?.hasSelectiveFire) {
|
||||
return {}
|
||||
}
|
||||
// Loop thru the selective fire choices and build the choices object when enough ammo in the weapon
|
||||
let choices = {}
|
||||
for (let choiceKey in SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES) {
|
||||
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[choiceKey]
|
||||
if (choice.ammoUsed > 0 && choice.ammoUsed <= weapon.system.ammo.value) {
|
||||
choices[choiceKey] = choice
|
||||
}
|
||||
}
|
||||
// If no choices available, warn the user
|
||||
if (Object.keys(choices).length === 0) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoSelectiveFireChoices"))
|
||||
return {}
|
||||
}
|
||||
return choices
|
||||
}
|
||||
|
||||
static async processWeaponDamage(actor, options) {
|
||||
let isLethal = false
|
||||
let weapon = options.rollItem
|
||||
let ammoUsed = weapon.system.weaponType.includes("ranged") ? 1 : 0 // Default ammo used for melee weapons is 0
|
||||
options.isNudge = false
|
||||
|
||||
// Selective fire management
|
||||
if (weapon.system.hasSelectiveFire && weapon.selectiveFireChoice) {
|
||||
let choice = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES[weapon.selectiveFireChoice]
|
||||
if (choice.ammoUsed > weapon.system.ammo.value) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
|
||||
return
|
||||
}
|
||||
weapon.system.selectiveFireChoiceLabel = choice.label // Store the choice in the weapon
|
||||
weapon.system.lethality = choice.lethality // Override lethality
|
||||
weapon.system.killRadius = choice.killRadius // Override kill radius
|
||||
ammoUsed = choice.ammoUsed // Override ammo used
|
||||
}
|
||||
|
||||
if (weapon.system.lethality > 0) {
|
||||
let lethalityRoll = new Roll("1d100")
|
||||
await lethalityRoll.evaluate()
|
||||
let lethalScore = (options?.previousResultType === "successCritical") ? weapon.system.lethality * 2 : weapon.system.lethality
|
||||
isLethal = (lethalityRoll.total <= lethalScore)
|
||||
if (ammoUsed > 0) {
|
||||
await actor.updateEmbeddedDocuments("Item", [{
|
||||
_id: weapon._id,
|
||||
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
|
||||
}])
|
||||
}
|
||||
let wounds = Math.floor(lethalityRoll.total / 10) + (lethalityRoll.total % 10)
|
||||
let msgData = {
|
||||
weapon,
|
||||
wounds,
|
||||
lethalScore,
|
||||
isLethal,
|
||||
ammoUsed,
|
||||
rollResult: lethalityRoll.total,
|
||||
}
|
||||
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-lethal-damage.hbs", msgData)
|
||||
ChatMessage.create({
|
||||
user: game.user.id,
|
||||
content: flavor,
|
||||
speaker: ChatMessage.getSpeaker({ actor: actor }),
|
||||
}, { rollMode: options.rollMode, create: true })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If the weapon is not lethal, we can proceed with the regular damage roll
|
||||
let formula = weapon.system.damage
|
||||
if (weapon.system.weaponType === "melee" || weapon.system.weaponType === "unarmed") {
|
||||
formula += ` + ${weapon.damageBonus}`
|
||||
}
|
||||
if (options?.previousResultType === "successCritical") {
|
||||
formula = `( ${formula} ) * 2`
|
||||
}
|
||||
if (ammoUsed > 0) {
|
||||
await actor.updateEmbeddedDocuments("Item", [{
|
||||
_id: weapon._id,
|
||||
"system.ammo.value": Math.max(0, weapon.system.ammo.value - ammoUsed)
|
||||
}])
|
||||
}
|
||||
let damageRoll = new Roll(formula)
|
||||
await damageRoll.evaluate()
|
||||
let msgData = {
|
||||
weapon,
|
||||
formula,
|
||||
ammoUsed,
|
||||
rollResult: damageRoll.total,
|
||||
}
|
||||
let flavor = await foundry.applications.handlebars.renderTemplate("systems/fvtt-cthulhu-eternal/templates/chat-regular-damage.hbs", msgData)
|
||||
ChatMessage.create({
|
||||
user: game.user.id,
|
||||
content: flavor,
|
||||
speaker: ChatMessage.getSpeaker({ actor: actor }),
|
||||
}, { rollMode: options.rollMode, create: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user with a dialog to configure and execute a roll.
|
||||
*
|
||||
@ -124,12 +223,18 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
let hasModifier = true
|
||||
let hasMultiplier = false
|
||||
options.isNudge = true
|
||||
let actor = game.actors.get(options.actorId)
|
||||
|
||||
switch (options.rollType) {
|
||||
case "skill":
|
||||
console.log(options.rollItem)
|
||||
options.initialScore = options.rollItem.system.computeScore()
|
||||
break
|
||||
case "luck":
|
||||
hasModifier = false
|
||||
options.initialScore = 50
|
||||
options.isNudge = false
|
||||
break
|
||||
case "san":
|
||||
case "char":
|
||||
options.initialScore = options.rollItem.targetScore
|
||||
@ -146,40 +251,8 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
options.rollItem.enableStorage = true
|
||||
options.isNudge = false
|
||||
break
|
||||
case "damage": {
|
||||
let isLethal = false
|
||||
options.isNudge = false
|
||||
if (options.rollItem.system.lethality > 0) {
|
||||
let lethalityRoll = new Roll("1d100")
|
||||
await lethalityRoll.evaluate()
|
||||
let lethalScore = (options?.previousResultType === "successCritical") ? options.rollItem.system.lethality * 2 : options.rollItem.system.lethality
|
||||
isLethal = (lethalityRoll.total <= lethalScore)
|
||||
let flavor = `${options.rollItem.name} - <strong> ${game.i18n.localize("CTHULHUETERNAL.Label.lethalityRoll")} </strong> : ${lethalityRoll.total} <= ${lethalScore} => ${isLethal}`
|
||||
if (isLethal) {
|
||||
flavor += `<br> ${game.i18n.localize("CTHULHUETERNAL.Label.lethalityWounded")} => HP = 0`
|
||||
} else {
|
||||
let wounds = Math.floor(lethalityRoll.total / 10) + (lethalityRoll.total % 10)
|
||||
flavor += `<br> ${game.i18n.localize("CTHULHUETERNAL.Label.lethalityNotWounded")} => HP loss = ${wounds}`
|
||||
}
|
||||
await lethalityRoll.toMessage({
|
||||
flavor: flavor
|
||||
});
|
||||
return
|
||||
}
|
||||
let formula = options.rollItem.system.damage
|
||||
if (options.rollItem.system.weaponType === "melee" || options.rollItem.system.weaponType === "unarmed") {
|
||||
formula += ` + ${options.rollItem.damageBonus}`
|
||||
}
|
||||
if (options?.previousResultType === "successCritical") {
|
||||
formula = `( ${formula} ) * 2`
|
||||
}
|
||||
let damageRoll = new Roll(formula)
|
||||
await damageRoll.evaluate()
|
||||
await damageRoll.toMessage({
|
||||
flavor: `${options.rollItem.name} - ${game.i18n.localize("CTHULHUETERNAL.Label.damageRoll")}`
|
||||
});
|
||||
}
|
||||
return
|
||||
case "damage":
|
||||
return this.processWeaponDamage(actor, options)
|
||||
case "weapon": {
|
||||
let era = game.settings.get("fvtt-cthulhu-eternal", "settings-era")
|
||||
if (era !== options.rollItem.system.settings) {
|
||||
@ -192,6 +265,11 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
console.log("WP Not found", era, options.rollItem.system.weaponType)
|
||||
return
|
||||
}
|
||||
// Check if the weapon has enouth ammo in case of a firearm
|
||||
if (options.rollItem.system.isFireArm() && options.rollItem.system.ammo.value <= 0) {
|
||||
ui.notifications.warn(game.i18n.localize("CTHULHUETERNAL.Notifications.NoAmmo"))
|
||||
return
|
||||
}
|
||||
options.weapon = options.rollItem
|
||||
if (options.rollItem.system.hasDirectSkill) {
|
||||
let skillName = options.rollItem.name
|
||||
@ -199,7 +277,6 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
options.initialScore = options.weapon.system.directSkillValue
|
||||
} else {
|
||||
let skillName = game.i18n.localize(SYSTEM.WEAPON_SKILL_MAPPING[era][options.rollItem.system.weaponType])
|
||||
let actor = game.actors.get(options.actorId)
|
||||
options.rollItem = actor.items.find(i => i.type === "skill" && i.name.toLowerCase() === skillName.toLowerCase())
|
||||
if (!options.rollItem) {
|
||||
ui.notifications.error(game.i18n.localize("CTHULHUETERNAL.Notifications.NoWeaponSkill"))
|
||||
@ -215,7 +292,6 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
break
|
||||
}
|
||||
|
||||
console.log("Roll options", CONFIG.Dice.rollModes);
|
||||
const rollModes = foundry.utils.duplicate(CONFIG.Dice.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,
|
||||
@ -225,7 +301,7 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
|
||||
const choiceModifier = SYSTEM.MODIFIER_CHOICES
|
||||
const choiceMultiplier = SYSTEM.MULTIPLIER_CHOICES
|
||||
const choiceSelectiveFire = SYSTEM.WEAPON_SELECTIVE_FIRE_CHOICES
|
||||
const choiceSelectiveFire = this.buildSelectiveFireChoices(actor, options?.weapon)
|
||||
|
||||
let modifier = "+0"
|
||||
let multiplier = "5"
|
||||
@ -318,6 +394,10 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
}
|
||||
rollData.targetScore = Math.min(Math.max(rollData.targetScore, 0), 100)
|
||||
}
|
||||
if (!rollData.targetScore) {
|
||||
rollData.targetScore = options.initialScore
|
||||
rollData.modifier = "0"
|
||||
}
|
||||
|
||||
if (Hooks.call("fvtt-cthulhu-eternal.preRoll", options, rollData) === false) return
|
||||
|
||||
@ -390,6 +470,8 @@ export default class CthulhuEternalRoll extends Roll {
|
||||
*/
|
||||
static createTitle(type, target) {
|
||||
switch (type) {
|
||||
case "luck":
|
||||
return `${game.i18n.localize("CTHULHUETERNAL.Label.titleLuck")}`
|
||||
case "skill":
|
||||
return `${game.i18n.localize("CTHULHUETERNAL.Label.titleSkill")}`
|
||||
case "weapon":
|
||||
|
@ -35,7 +35,9 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
schema.hp = new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||
stunned: new fields.BooleanField({ required: true, initial: false })
|
||||
stunned: new fields.BooleanField({ required: true, initial: false }),
|
||||
unconscious: new fields.BooleanField({ required: true, initial: false }),
|
||||
dead: new fields.BooleanField({ required: true, initial: false })
|
||||
})
|
||||
|
||||
schema.san = new fields.SchemaField({
|
||||
@ -130,6 +132,22 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
updates[`system.damageBonus`] = dmgBonus
|
||||
}
|
||||
|
||||
// Unconsciousness management
|
||||
if (!this.hp.unconscious && this.hp.value <= 2) {
|
||||
updates[`system.hp.unconscious`] = true
|
||||
}
|
||||
if (this.hp.unconscious && this.hp.value > 2) {
|
||||
updates[`system.hp.unconscious`] = false
|
||||
}
|
||||
|
||||
// Dead management
|
||||
if (!this.hp.dead && this.hp.value <= 0) {
|
||||
updates[`system.hp.dead`] = true
|
||||
}
|
||||
if (this.hp.dead && this.hp.value > 0) {
|
||||
updates[`system.hp.dead`] = false
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if (this.san.value > this.san.max) {
|
||||
updates[`system.san.value`] = this.san.max
|
||||
@ -165,6 +183,10 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
}
|
||||
}
|
||||
|
||||
isStunned() {
|
||||
return this.hp.stunned
|
||||
}
|
||||
|
||||
isLowWP() {
|
||||
return this.wp.value <= 2
|
||||
}
|
||||
@ -207,6 +229,32 @@ export default class CthulhuEternalProtagonist extends foundry.abstract.TypeData
|
||||
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
||||
*/
|
||||
async roll(rollType, rollItem) {
|
||||
|
||||
if (this.hp.dead ) {
|
||||
// Warn with chat message
|
||||
ChatMessage.create({
|
||||
content: `<p>${game.i18n.format("CTHULHUETERNAL.Label.deadWarning", {con: this.characteristics.con.value} )}</p>`,
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.parent })
|
||||
})
|
||||
return null
|
||||
}
|
||||
if (this.hp.unconscious ) {
|
||||
// Warn with chat message
|
||||
ChatMessage.create({
|
||||
content: `<p>${game.i18n.localize("CTHULHUETERNAL.Label.unconsciousWarning")}</p>`,
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.parent })
|
||||
})
|
||||
return null
|
||||
}
|
||||
if (this.hp.stunned && rollType === "skill") {
|
||||
// Warn with chat message
|
||||
ChatMessage.create({
|
||||
content: `<p>${game.i18n.localize("CTHULHUETERNAL.Label.stunnedWarning")}</p>`,
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.parent })
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
let opponentTarget
|
||||
const hasTarget = opponentTarget !== undefined
|
||||
|
||||
|
@ -15,6 +15,8 @@ export default class CthulhuEternalSkill extends foundry.abstract.TypeDataModel
|
||||
schema.diceEvolved = new fields.BooleanField({ required: true, initial: true })
|
||||
schema.rollFailed = new fields.BooleanField({ required: true, initial: false })
|
||||
schema.isAdversary = new fields.BooleanField({ required: true, initial: false })
|
||||
schema.isHealing = new fields.BooleanField({ required: true, initial: false })
|
||||
schema.healingFormula = new fields.StringField({ required: true, initial: "1d4" })
|
||||
|
||||
return schema
|
||||
}
|
||||
@ -36,11 +38,11 @@ export default class CthulhuEternalSkill extends foundry.abstract.TypeDataModel
|
||||
return `${this.base} + ${ String(this.bonus)}`;
|
||||
}
|
||||
|
||||
// Split the base value per stat :
|
||||
// Split the base value per stat :
|
||||
let base = this.base.toLowerCase();
|
||||
let char = actor.system.characteristics[base];
|
||||
if (!char) {
|
||||
ui.notifications.error(`The characteristic ${base} is wrong for actor ${actor.name}`);
|
||||
ui.notifications.error(`The characteristic ${base} is wrong for actor ${actor.name}`);
|
||||
return `${this.base } + ${ String(this.bonus)}`;
|
||||
}
|
||||
let charValue = char.value;
|
||||
|
@ -25,6 +25,10 @@ export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel
|
||||
schema.armorPiercing = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||
schema.weaponSubtype = new fields.StringField({ required: true, initial: "basicfirearm", choices: SYSTEM.WEAPON_SUBTYPE })
|
||||
schema.state = new fields.StringField({ required: true, initial: "pristine", choices: SYSTEM.EQUIPMENT_STATES })
|
||||
schema.ammo = new fields.SchemaField({
|
||||
value: new fields.NumberField({ ...requiredInteger, initial: 6, min: 0 }),
|
||||
max: new fields.NumberField({ ...requiredInteger, initial: 6, min: 0 })
|
||||
})
|
||||
|
||||
schema.resourceLevel = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||
|
||||
@ -37,4 +41,12 @@ export default class CthulhuEternalWeapon extends foundry.abstract.TypeDataModel
|
||||
get weaponCategory() {
|
||||
return game.i18n.localize(CATEGORY[this.category].label)
|
||||
}
|
||||
|
||||
isRanged() {
|
||||
return this.weaponType.includes("ranged")
|
||||
}
|
||||
|
||||
isFireArm() {
|
||||
return this.weaponType === "rangedfirearm"
|
||||
}
|
||||
}
|
||||
|
@ -180,6 +180,30 @@ export default class CthulhuEternalUtils {
|
||||
});
|
||||
}
|
||||
|
||||
static async healingRoll(rollMessage) {
|
||||
let rollData = rollMessage.rolls[0]?.options?.rollData
|
||||
let healingFormula = rollData.rollItem.system.healingFormula
|
||||
let healingMsg = "CTHULHUETERNAL.Label.healingRoll"
|
||||
if (rollData.resultType === "successCritical") {
|
||||
healingFormula += " * 2"
|
||||
}
|
||||
if (rollData.resultType === "failureCritical") {
|
||||
healingMsg = "CTHULHUETERNAL.Label.healingRollFailure"
|
||||
}
|
||||
// Now display the result in chat message
|
||||
let roll = new Roll(healingFormula)
|
||||
await roll.evaluate()
|
||||
roll.toMessage({
|
||||
speaker: ChatMessage.getSpeaker({ actor: rollData.actorId }),
|
||||
flavor: `${game.i18n.localize(healingMsg)} : ${roll.total}`,
|
||||
rolls: [roll],
|
||||
options: {
|
||||
rollData: rollData,
|
||||
resultType: rollData.resultType
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static async damageRoll(rollMessage) {
|
||||
let rollData = rollMessage.rolls[0]?.options?.rollData
|
||||
let actor = game.actors.get(rollData.actorId)
|
||||
@ -187,7 +211,9 @@ 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
|
||||
actor.system.roll("damage", rollData.weapon)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user