Corrections diverses autout du combat

This commit is contained in:
2026-04-13 14:19:24 +02:00
parent 44cc07db73
commit d69144f506
46 changed files with 1340 additions and 241 deletions

View File

@@ -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 }),
})
}
}