Corrections diverses autout du combat
This commit is contained in:
@@ -40,6 +40,8 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
||||
delete: CelestopolActorSheet.#onItemDelete,
|
||||
attack: CelestopolActorSheet.#onAttack,
|
||||
rangedDefense: CelestopolActorSheet.#onRangedDefense,
|
||||
unarmedAttack: CelestopolActorSheet.#onUnarmedAttack,
|
||||
baseRangedDefense: CelestopolActorSheet.#onBaseRangedDefense,
|
||||
trackBox: CelestopolActorSheet.#onTrackBox,
|
||||
skillLevel: CelestopolActorSheet.#onSkillLevel,
|
||||
factionLevel: CelestopolActorSheet.#onFactionLevel,
|
||||
@@ -197,6 +199,16 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
|
||||
await this.document.system.rollRangedDefense(itemId)
|
||||
}
|
||||
|
||||
static async #onUnarmedAttack() {
|
||||
if (typeof this.document.system.rollUnarmedAttack !== "function") return
|
||||
await this.document.system.rollUnarmedAttack()
|
||||
}
|
||||
|
||||
static async #onBaseRangedDefense() {
|
||||
if (typeof this.document.system.rollRangedDefenseBase !== "function") return
|
||||
await this.document.system.rollRangedDefenseBase()
|
||||
}
|
||||
|
||||
/** Met à jour une jauge de piste (blessures/destin/spleen) par clic sur une case. */
|
||||
static #onTrackBox(_event, target) {
|
||||
if (!this.isEditable) return
|
||||
|
||||
@@ -42,6 +42,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
competences:{ template: "systems/fvtt-celestopol/templates/character-competences.hbs" },
|
||||
blessures: { template: "systems/fvtt-celestopol/templates/character-blessures.hbs" },
|
||||
factions: { template: "systems/fvtt-celestopol/templates/character-factions.hbs" },
|
||||
combat: { template: "systems/fvtt-celestopol/templates/character-combat.hbs" },
|
||||
equipement: { template: "systems/fvtt-celestopol/templates/character-equipement.hbs" },
|
||||
biography: { template: "systems/fvtt-celestopol/templates/character-biography.hbs" },
|
||||
}
|
||||
@@ -53,6 +54,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
competences:{ id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" },
|
||||
blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" },
|
||||
factions: { id: "factions", group: "sheet", icon: "fa-solid fa-flag", label: "CELESTOPOL.Tab.factions" },
|
||||
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-khanda", label: "CELESTOPOL.Tab.combat" },
|
||||
equipement: { id: "equipement", group: "sheet", icon: "fa-solid fa-shield-halved",label: "CELESTOPOL.Tab.equipement" },
|
||||
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "CELESTOPOL.Tab.biography" },
|
||||
}
|
||||
@@ -156,6 +158,12 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
})
|
||||
break
|
||||
|
||||
case "combat":
|
||||
context.tab = context.tabs.combat
|
||||
context.weapons = doc.itemTypes.weapon.sort((a, b) => a.name.localeCompare(b.name))
|
||||
context.armures = doc.itemTypes.armure.sort((a, b) => a.name.localeCompare(b.name))
|
||||
break
|
||||
|
||||
case "biography":
|
||||
context.tab = context.tabs.biography
|
||||
context.xpLogEmpty = (doc.system.xp?.log?.length ?? 0) === 0
|
||||
@@ -173,8 +181,6 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
|
||||
|
||||
case "equipement":
|
||||
context.tab = context.tabs.equipement
|
||||
context.weapons = doc.itemTypes.weapon.sort((a, b) => a.name.localeCompare(b.name))
|
||||
context.armures = doc.itemTypes.armure.sort((a, b) => a.name.localeCompare(b.name))
|
||||
context.equipments= doc.itemTypes.equipment.sort((a, b) => a.name.localeCompare(b.name))
|
||||
break
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
||||
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||
competences: { template: "systems/fvtt-celestopol/templates/npc-competences.hbs" },
|
||||
blessures: { template: "systems/fvtt-celestopol/templates/npc-blessures.hbs" },
|
||||
combat: { template: "systems/fvtt-celestopol/templates/npc-combat.hbs" },
|
||||
equipement: { template: "systems/fvtt-celestopol/templates/npc-equipement.hbs" },
|
||||
biographie: { template: "systems/fvtt-celestopol/templates/npc-biographie.hbs" },
|
||||
}
|
||||
@@ -45,6 +46,7 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
||||
const tabs = {
|
||||
competences: { id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" },
|
||||
blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" },
|
||||
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-khanda", label: "CELESTOPOL.Tab.combat" },
|
||||
equipement: { id: "equipement", group: "sheet", icon: "fa-solid fa-shield-halved",label: "CELESTOPOL.Tab.equipement" },
|
||||
biographie: { id: "biographie", group: "sheet", icon: "fa-solid fa-book-open", label: "CELESTOPOL.Tab.biographie" },
|
||||
}
|
||||
@@ -106,6 +108,9 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
|
||||
context.system.notes, { relativeTo: this.document }
|
||||
)
|
||||
break
|
||||
case "combat":
|
||||
context.tab = context.tabs.combat
|
||||
break
|
||||
case "equipement":
|
||||
context.tab = context.tabs.equipement
|
||||
break
|
||||
|
||||
@@ -78,6 +78,22 @@ export class CelestopolRoll extends Roll {
|
||||
.reduce((sum, a) => sum + Math.abs(a.system.protection ?? a.system.malus ?? 0), 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Résout un acteur à partir de son UUID si disponible, sinon via son identifiant monde.
|
||||
* @param {object} ref
|
||||
* @param {string|null} ref.actorUuid
|
||||
* @param {string|null} ref.actorId
|
||||
* @returns {Promise<Actor|null>}
|
||||
*/
|
||||
static async resolveActor({ actorUuid = null, actorId = null } = {}) {
|
||||
if (actorUuid) {
|
||||
const actorFromUuid = await fromUuid(actorUuid)
|
||||
if (actorFromUuid) return actorFromUuid
|
||||
}
|
||||
if (actorId) return game.actors.get(actorId) ?? null
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Ouvre le dialogue de configuration du jet via DialogV2 et exécute le jet.
|
||||
* @param {object} options
|
||||
@@ -298,11 +314,14 @@ export class CelestopolRoll extends Roll {
|
||||
const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true")
|
||||
const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
|
||||
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true"
|
||||
const selectedCombatTargetId = typeof rollContext.targetSelect === "string" ? rollContext.targetSelect : ""
|
||||
const selectedCombatTarget = selectedCombatTargetId
|
||||
? availableTargets.find(t => t.id === selectedCombatTargetId) ?? null
|
||||
const selectedCombatTargetRef = typeof rollContext.targetSelect === "string" ? rollContext.targetSelect : ""
|
||||
const selectedCombatTarget = selectedCombatTargetRef
|
||||
? availableTargets.find(t => t.uuid === selectedCombatTargetRef || t.id === selectedCombatTargetRef) ?? null
|
||||
: null
|
||||
const resolvedWeaponName = (isRangedDefense && selectedCombatTarget?.weaponName) ? selectedCombatTarget.weaponName : weaponName
|
||||
const resolvedWeaponDegats = (isRangedDefense && selectedCombatTarget?.weaponDegats) ? selectedCombatTarget.weaponDegats : weaponDegats
|
||||
const targetActorId = selectedCombatTarget?.id || ""
|
||||
const targetActorUuid = selectedCombatTarget?.uuid || ""
|
||||
const targetActorName = selectedCombatTarget?.name || ""
|
||||
|
||||
// En résistance : forcer puiser=false, lune=false, fortune=false, destin=false
|
||||
@@ -352,9 +371,10 @@ export class CelestopolRoll extends Roll {
|
||||
isCombat,
|
||||
isRangedDefense,
|
||||
weaponType,
|
||||
weaponName,
|
||||
weaponDegats,
|
||||
weaponName: resolvedWeaponName,
|
||||
weaponDegats: resolvedWeaponDegats,
|
||||
targetActorId,
|
||||
targetActorUuid,
|
||||
targetActorName,
|
||||
availableTargets,
|
||||
rangedMod: effectiveRangedMod,
|
||||
@@ -375,7 +395,10 @@ export class CelestopolRoll extends Roll {
|
||||
roll.computeResult()
|
||||
|
||||
// Test de résistance échoué → cocher automatiquement la prochaine case de blessure
|
||||
const actor = game.actors.get(options.actorId)
|
||||
const actor = await this.resolveActor({
|
||||
actorUuid: options.actorUuid ?? null,
|
||||
actorId: options.actorId ?? null,
|
||||
})
|
||||
if (isResistance && actor && roll.options.resultType === "failure") {
|
||||
const nextLvl = (actor.system.blessures.lvl ?? 0) + 1
|
||||
if (nextLvl <= 8) {
|
||||
@@ -384,16 +407,25 @@ export class CelestopolRoll extends Roll {
|
||||
}
|
||||
}
|
||||
|
||||
// Mêlée échouée OU défense à distance échouée → joueur prend une blessure
|
||||
// Mêlée échouée OU défense à distance échouée → le protagoniste subit les dégâts de l'arme PNJ
|
||||
if (isCombat && (weaponType === "melee" || isRangedDefense) && actor && roll.options.resultType === "failure") {
|
||||
const nextLvl = (actor.system.blessures.lvl ?? 0) + 1
|
||||
if (nextLvl <= 8) {
|
||||
const incomingWounds = this.getIncomingWounds(resolvedWeaponDegats)
|
||||
const protection = this.getActorArmorProtection(actor)
|
||||
const appliedWounds = incomingWounds === null
|
||||
? 1
|
||||
: Math.max(0, incomingWounds - protection)
|
||||
if (appliedWounds > 0) {
|
||||
const nextLvl = Math.min(8, (actor.system.blessures.lvl ?? 0) + appliedWounds)
|
||||
await actor.update({ "system.blessures.lvl": nextLvl })
|
||||
roll.options.woundTaken = nextLvl
|
||||
roll.options.woundTakenCount = appliedWounds
|
||||
roll.options.incomingWounds = incomingWounds
|
||||
roll.options.selectedTargetProtection = protection
|
||||
roll.options.selectedTargetAppliedWounds = appliedWounds
|
||||
}
|
||||
}
|
||||
|
||||
await roll.toMessage({}, { rollMode: rollData.rollMode })
|
||||
await roll.toMessage({}, { messageMode: rollData.rollMode })
|
||||
|
||||
// Batching de toutes les mises à jour de l'acteur en un seul appel réseau
|
||||
if (actor) {
|
||||
@@ -506,12 +538,16 @@ export class CelestopolRoll extends Roll {
|
||||
const incomingWounds = isWeaponHit ? this.constructor.getIncomingWounds(weaponDegats) : null
|
||||
const hasVariableDamage = isWeaponHit && incomingWounds === null
|
||||
const targetActorId = this.options.targetActorId ?? ""
|
||||
const targetActorUuid = this.options.targetActorUuid ?? ""
|
||||
const targetActorName = this.options.targetActorName ?? ""
|
||||
const availableTargets = (this.options.availableTargets ?? []).map(target => ({
|
||||
...target,
|
||||
selected: target.id === targetActorId,
|
||||
selected: target.uuid === targetActorUuid || target.id === targetActorId,
|
||||
}))
|
||||
const selectedTargetActor = targetActorId ? game.actors.get(targetActorId) : null
|
||||
const selectedTargetActor = await this.constructor.resolveActor({
|
||||
actorUuid: targetActorUuid,
|
||||
actorId: targetActorId,
|
||||
})
|
||||
const selectedTargetProtection = selectedTargetActor
|
||||
? this.constructor.getActorArmorProtection(selectedTargetActor)
|
||||
: null
|
||||
@@ -569,6 +605,7 @@ export class CelestopolRoll extends Roll {
|
||||
weaponType: this.options.weaponType ?? null,
|
||||
isRangedDefense: this.options.isRangedDefense ?? false,
|
||||
woundTaken: this.options.woundTaken ?? null,
|
||||
woundTakenCount: this.options.woundTakenCount ?? null,
|
||||
situationMod: this.options.situationMod ?? 0,
|
||||
rangedMod: this.options.rangedMod ?? 0,
|
||||
hasDamageSummary: isWeaponHit,
|
||||
@@ -577,6 +614,7 @@ export class CelestopolRoll extends Roll {
|
||||
hasVariableDamage,
|
||||
canApplyWeaponDamage: incomingWounds !== null,
|
||||
targetActorId,
|
||||
targetActorUuid,
|
||||
targetActorName,
|
||||
selectedTargetProtection,
|
||||
selectedTargetAppliedWounds,
|
||||
@@ -595,14 +633,40 @@ export class CelestopolRoll extends Roll {
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async toMessage(messageData = {}, { rollMode, create = true } = {}) {
|
||||
async toMessage(messageData = {}, { messageMode, rollMode, create = true } = {}) {
|
||||
if (rollMode) {
|
||||
messageMode = Roll._mapLegacyRollMode(rollMode)
|
||||
}
|
||||
messageMode ||= game.settings.get("core", "messageMode")
|
||||
if (!this._evaluated) await this.evaluate({ allowInteractive: messageMode !== "blind" })
|
||||
|
||||
const skillLocalized = this.skillLabel ? game.i18n.localize(this.skillLabel) : ""
|
||||
const statLocalized = this.options.statLabel
|
||||
? game.i18n.localize(this.options.statLabel) : ""
|
||||
const flavor = statLocalized
|
||||
? `<strong>${statLocalized} › ${skillLocalized}</strong>`
|
||||
: `<strong>${skillLocalized}</strong>`
|
||||
return super.toMessage({ flavor, ...messageData }, { rollMode })
|
||||
const speakerActor = await this.constructor.resolveActor({
|
||||
actorUuid: this.options.actorUuid ?? null,
|
||||
actorId: this.options.actorId ?? null,
|
||||
})
|
||||
const content = await this.render({ isPrivate: messageMode !== "public" })
|
||||
const chatData = foundry.utils.mergeObject({
|
||||
author: game.user.id,
|
||||
content,
|
||||
flavor,
|
||||
sound: CONFIG.sounds.dice,
|
||||
rolls: [this],
|
||||
speaker: speakerActor ? ChatMessage.getSpeaker({ actor: speakerActor }) : undefined,
|
||||
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
|
||||
}, messageData)
|
||||
|
||||
const cls = foundry.utils.getDocumentClass("ChatMessage")
|
||||
const msg = new cls(chatData)
|
||||
msg.applyMode(messageMode)
|
||||
|
||||
if (create) return cls.create(msg)
|
||||
return msg.toObject()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -641,7 +705,7 @@ export class CelestopolRoll extends Roll {
|
||||
content,
|
||||
speaker,
|
||||
rolls: [roll],
|
||||
style: CONST.CHAT_MESSAGE_STYLES?.ROLL ?? 5,
|
||||
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
import { SYSTEM } from "../config/system.mjs"
|
||||
|
||||
const WEAPON_DAMAGE_PRIORITY = { "0": 0, "1": 1, "2": 2, X: 3 }
|
||||
|
||||
export default class CelestopolCharacter extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields
|
||||
@@ -218,6 +220,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId,
|
||||
@@ -249,6 +252,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId,
|
||||
@@ -270,21 +274,47 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
||||
/**
|
||||
* Collecte les cibles de combat sur la scène active.
|
||||
* Pour un PJ attaquant, seules les cibles PNJ présentes sur la scène sont proposées.
|
||||
* @returns {Array<{id:string, name:string, corps:number}>}
|
||||
* @param {object} options
|
||||
* @param {boolean} [options.onlyRanged=false]
|
||||
* @param {boolean} [options.fallbackToAll=false]
|
||||
* @returns {Array<{id:string, uuid:string, name:string, corps:number, weaponName?:string, weaponDegats?:string}>}
|
||||
*/
|
||||
_getCombatTargets() {
|
||||
_getCombatTargets({ onlyRanged = false, fallbackToAll = false } = {}) {
|
||||
const getBestRangedWeapon = actor => {
|
||||
const rangedWeapons = actor.itemTypes?.weapon?.filter(item => item.system.type === "distance") ?? []
|
||||
if (!rangedWeapons.length) return null
|
||||
return rangedWeapons.reduce((best, item) => {
|
||||
if (!best) return item
|
||||
const bestPriority = WEAPON_DAMAGE_PRIORITY[best.system.degats] ?? -1
|
||||
const itemPriority = WEAPON_DAMAGE_PRIORITY[item.system.degats] ?? -1
|
||||
if (itemPriority !== bestPriority) return itemPriority > bestPriority ? item : best
|
||||
return item.name.localeCompare(best.name) < 0 ? item : best
|
||||
}, null)
|
||||
}
|
||||
|
||||
const toEntry = actor => ({
|
||||
id: actor.id,
|
||||
uuid: actor.uuid,
|
||||
name: actor.name,
|
||||
corps: actor.system.stats?.corps?.res ?? 0,
|
||||
...(onlyRanged ? (() => {
|
||||
const weapon = getBestRangedWeapon(actor)
|
||||
return weapon ? {
|
||||
weaponName: weapon.name,
|
||||
weaponDegats: weapon.system.degats,
|
||||
} : {}
|
||||
})() : {}),
|
||||
})
|
||||
const sceneTokens = canvas?.scene?.isView ? (canvas.tokens?.placeables ?? []) : []
|
||||
return [...new Map(sceneTokens
|
||||
const targets = [...new Map(sceneTokens
|
||||
.filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id)
|
||||
.filter(t => !onlyRanged || getBestRangedWeapon(t.actor))
|
||||
.map(t => {
|
||||
const actor = t.actor
|
||||
return [actor.id, toEntry(actor)]
|
||||
return [actor.uuid, toEntry(actor)]
|
||||
})).values()]
|
||||
if (!targets.length && onlyRanged && fallbackToAll) return this._getCombatTargets()
|
||||
return targets
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,6 +334,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId: "corps",
|
||||
@@ -326,6 +357,40 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance une attaque de mêlée à mains nues.
|
||||
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
|
||||
*/
|
||||
async rollUnarmedAttack() {
|
||||
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
||||
const echauffouree = this.stats.corps.echauffouree
|
||||
if (!echauffouree) return null
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId: "corps",
|
||||
skillId: "echauffouree",
|
||||
statLabel: SYSTEM.STATS.corps.label,
|
||||
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
|
||||
skillValue: echauffouree.value,
|
||||
woundMalus: this.getWoundMalus(),
|
||||
armorMalus: this.getArmorMalusForRoll("corps", "echauffouree"),
|
||||
woundLevel: this.blessures.lvl,
|
||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||
destGaugeFull: this.destin.lvl > 0,
|
||||
fortuneValue: this.attributs.fortune.value,
|
||||
isCombat: true,
|
||||
isRangedDefense: false,
|
||||
weaponType: "melee",
|
||||
weaponName: game.i18n.localize("CELESTOPOL.Combat.unarmedAttack"),
|
||||
weaponDegats: "0",
|
||||
availableTargets: this._getCombatTargets(),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance un jet de défense contre une attaque à distance (test Mobilité vs Corps PNJ).
|
||||
* Succès → esquive réussie.
|
||||
@@ -342,6 +407,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId: "corps",
|
||||
@@ -363,4 +429,38 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
|
||||
availableTargets: this._getCombatTargets(),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance une esquive d'attaque à distance sans dépendre d'une arme possédée par le PJ.
|
||||
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
|
||||
*/
|
||||
async rollRangedDefenseBase() {
|
||||
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
||||
const mobilite = this.stats.corps.mobilite
|
||||
if (!mobilite) return null
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId: "corps",
|
||||
skillId: "mobilite",
|
||||
statLabel: SYSTEM.STATS.corps.label,
|
||||
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
|
||||
skillValue: mobilite.value,
|
||||
woundMalus: this.getWoundMalus(),
|
||||
armorMalus: this.getArmorMalusForRoll("corps", "mobilite"),
|
||||
woundLevel: this.blessures.lvl,
|
||||
rollMoonDie: this.prefs.rollMoonDie ?? false,
|
||||
destGaugeFull: this.destin.lvl > 0,
|
||||
fortuneValue: this.attributs.fortune.value,
|
||||
isCombat: true,
|
||||
isRangedDefense: true,
|
||||
weaponType: "distance",
|
||||
weaponName: game.i18n.localize("CELESTOPOL.Combat.rangedAttack"),
|
||||
weaponDegats: "0",
|
||||
availableTargets: this._getCombatTargets({ onlyRanged: true, fallbackToAll: true }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId,
|
||||
@@ -127,4 +128,95 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
|
||||
async rollResistance(statId) {
|
||||
return this.roll(statId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Collecte les cibles protagonistes de la scène active pour les jets de combat PNJ.
|
||||
* @returns {Array<{id:string, name:string, corps:number}>}
|
||||
*/
|
||||
_getCombatTargets() {
|
||||
const toEntry = actor => ({
|
||||
id: actor.id,
|
||||
uuid: actor.uuid,
|
||||
name: actor.name,
|
||||
corps: actor.system.stats?.corps?.res ?? 0,
|
||||
})
|
||||
const sceneTokens = canvas?.scene?.isView ? (canvas.tokens?.placeables ?? []) : []
|
||||
return [...new Map(sceneTokens
|
||||
.filter(t => t.actor?.type === "character" && t.actor.id !== this.parent.id)
|
||||
.map(t => {
|
||||
const actor = t.actor
|
||||
return [actor.id, toEntry(actor)]
|
||||
})).values()]
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance une attaque PNJ avec une arme.
|
||||
* Le test utilise le domaine Corps et transmet explicitement les dégâts de l'arme.
|
||||
* @param {string} itemId
|
||||
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
|
||||
*/
|
||||
async rollAttack(itemId) {
|
||||
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
||||
const item = this.parent.items.get(itemId)
|
||||
if (!item || item.type !== "weapon") return null
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId: "corps",
|
||||
skillId: null,
|
||||
statLabel: SYSTEM.STATS.corps.label,
|
||||
skillLabel: "CELESTOPOL.Combat.attack",
|
||||
skillValue: this.stats.corps.res,
|
||||
woundMalus: this.getWoundMalus(),
|
||||
armorMalus: this.getArmorMalusForRoll("corps"),
|
||||
woundLevel: this.blessures.lvl,
|
||||
rollMoonDie: false,
|
||||
destGaugeFull: false,
|
||||
fortuneValue: 0,
|
||||
isCombat: true,
|
||||
isRangedDefense: false,
|
||||
weaponType: item.system.type,
|
||||
weaponName: item.name,
|
||||
weaponDegats: item.system.degats,
|
||||
availableTargets: this._getCombatTargets(),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Lance un jet de tir/esquive PNJ avec une arme à distance.
|
||||
* @param {string} itemId
|
||||
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
|
||||
*/
|
||||
async rollRangedDefense(itemId) {
|
||||
const { CelestopolRoll } = await import("../documents/roll.mjs")
|
||||
const item = this.parent.items.get(itemId)
|
||||
if (!item || item.type !== "weapon" || item.system.type !== "distance") return null
|
||||
|
||||
return CelestopolRoll.prompt({
|
||||
actorId: this.parent.id,
|
||||
actorUuid: this.parent.uuid,
|
||||
actorName: this.parent.name,
|
||||
actorImage: this.parent.img,
|
||||
statId: "corps",
|
||||
skillId: null,
|
||||
statLabel: SYSTEM.STATS.corps.label,
|
||||
skillLabel: "CELESTOPOL.Combat.rangedDefenseTitle",
|
||||
skillValue: this.stats.corps.res,
|
||||
woundMalus: this.getWoundMalus(),
|
||||
armorMalus: this.getArmorMalusForRoll("corps"),
|
||||
woundLevel: this.blessures.lvl,
|
||||
rollMoonDie: false,
|
||||
destGaugeFull: false,
|
||||
fortuneValue: 0,
|
||||
isCombat: true,
|
||||
isRangedDefense: true,
|
||||
weaponType: "distance",
|
||||
weaponName: item.name,
|
||||
weaponDegats: item.system.degats,
|
||||
availableTargets: this._getCombatTargets(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user