Corrections après tests de combat
Some checks failed
Release Creation / build (release) Failing after 1m34s

This commit is contained in:
2026-04-14 18:59:09 +02:00
parent 63c0153860
commit b3cf0b0aa1
29 changed files with 121 additions and 124 deletions

View File

@@ -46,6 +46,7 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
skillLevel: CelestopolActorSheet.#onSkillLevel,
factionLevel: CelestopolActorSheet.#onFactionLevel,
toggleArmure: CelestopolActorSheet.#onToggleArmure,
toggleWeapon: CelestopolActorSheet.#onToggleWeapon,
},
}
@@ -237,6 +238,13 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
if (item?.type === "armure") await item.update({ "system.equipped": !item.system.equipped })
}
static async #onToggleWeapon(_event, target) {
const uuid = target.closest('[data-item-uuid]')?.dataset.itemUuid
if (!uuid) return
const item = await fromUuid(uuid)
if (item?.type === "weapon") await item.update({ "system.equipped": !item.system.equipped })
}
static #onFactionLevel(_event, target) {
if (!this.isEditable) return
const factionId = target.dataset.faction

View File

@@ -320,6 +320,8 @@ export class CelestopolRoll extends Roll {
: null
const resolvedWeaponName = (isRangedDefense && selectedCombatTarget?.weaponName) ? selectedCombatTarget.weaponName : weaponName
const resolvedWeaponDegats = (isRangedDefense && selectedCombatTarget?.weaponDegats) ? selectedCombatTarget.weaponDegats : weaponDegats
// Dégâts de l'arme adverse en cas d'échec (arme équipée du PNJ ciblé en mêlée, arme distance en esquive)
const incomingWeaponDegats = selectedCombatTarget?.weaponDegats ?? resolvedWeaponDegats
const targetActorId = selectedCombatTarget?.id || ""
const targetActorUuid = selectedCombatTarget?.uuid || ""
const targetActorName = selectedCombatTarget?.name || ""
@@ -373,6 +375,7 @@ export class CelestopolRoll extends Roll {
weaponType,
weaponName: resolvedWeaponName,
weaponDegats: resolvedWeaponDegats,
incomingWeaponDegats,
targetActorId,
targetActorUuid,
targetActorName,
@@ -407,9 +410,9 @@ export class CelestopolRoll extends Roll {
}
}
// 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 incomingWounds = this.getIncomingWounds(resolvedWeaponDegats)
// Mêlée échouée OU défense à distance échouée → le protagoniste (PJ uniquement) subit les dégâts de l'arme PNJ
if (isCombat && (weaponType === "melee" || isRangedDefense) && actor?.type === "character" && roll.options.resultType === "failure") {
const incomingWounds = this.getIncomingWounds(roll.options.incomingWeaponDegats ?? resolvedWeaponDegats)
const protection = this.getActorArmorProtection(actor)
const appliedWounds = incomingWounds === null
? 1
@@ -555,6 +558,13 @@ export class CelestopolRoll extends Roll {
? Math.max(0, incomingWounds - selectedTargetProtection)
: null
// Type de l'acteur qui lance le jet (character | npc)
const rollingActor = await this.constructor.resolveActor({
actorUuid: this.options.actorUuid ?? null,
actorId: this.options.actorId ?? null,
})
const actorType = rollingActor?.type ?? this.options.actorType ?? null
// Libellé de difficulté : en combat "Corps PNJ : N", en opposition "vs ?", sinon "Seuil : 11"
const difficultyLabel = this.options.isCombat
? `${game.i18n.localize("CELESTOPOL.Combat.corpsPnj")} : ${threshold}`
@@ -600,6 +610,8 @@ export class CelestopolRoll extends Roll {
woundLabel,
isResistance: this.options.isResistance ?? false,
isCombat: this.options.isCombat ?? false,
actorType,
isNpcAttack: actorType === "npc",
weaponName: this.options.weaponName ?? null,
weaponDegats,
weaponType: this.options.weaponType ?? null,

View File

@@ -13,8 +13,6 @@
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
@@ -275,40 +273,39 @@ 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.
* @param {object} options
* @param {boolean} [options.onlyRanged=false]
* @param {boolean} [options.fallbackToAll=false]
* @param {boolean} [options.onlyRanged=false] - Filtrer sur les PNJ ayant une arme à distance équipée
* @param {boolean} [options.fallbackToAll=false] - Revenir à tous les PNJ si aucune cible trouvée
* @param {boolean} [options.includeMeleeWeapon=false] - Inclure l'arme de mêlée équipée du PNJ (dégâts adverses)
* @returns {Array<{id:string, uuid:string, name:string, corps:number, weaponName?:string, weaponDegats?:string}>}
*/
_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)
_getCombatTargets({ onlyRanged = false, fallbackToAll = false, includeMeleeWeapon = false } = {}) {
const getEquippedWeapon = (actor, type) =>
actor.itemTypes?.weapon?.find(item => item.system.type === type && item.system.equipped) ?? null
const toEntry = actor => {
const entry = {
id: actor.id,
uuid: actor.uuid,
name: actor.name,
corps: actor.system.stats?.corps?.res ?? 0,
}
if (onlyRanged) {
const weapon = getEquippedWeapon(actor, "distance")
if (weapon) {
entry.weaponName = weapon.name
entry.weaponDegats = weapon.system.degats
}
} else if (includeMeleeWeapon) {
const weapon = getEquippedWeapon(actor, "melee")
entry.weaponDegats = weapon ? weapon.system.degats : "0"
}
return entry
}
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 ?? []) : []
const targets = [...new Map(sceneTokens
.filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id)
.filter(t => !onlyRanged || getBestRangedWeapon(t.actor))
.filter(t => !onlyRanged || getEquippedWeapon(t.actor, "distance"))
.map(t => {
const actor = t.actor
return [actor.uuid, toEntry(actor)]
@@ -353,13 +350,12 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
weaponType: item.system.type,
weaponName: item.name,
weaponDegats: item.system.degats,
availableTargets: this._getCombatTargets(),
availableTargets: this._getCombatTargets({ includeMeleeWeapon: item.system.type === "melee" }),
})
}
/**
* Lance une attaque de mêlée à mains nues.
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
* Lance une attaque à mains nues (Échauffourée sans arme).
*/
async rollUnarmedAttack() {
const { CelestopolRoll } = await import("../documents/roll.mjs")
@@ -387,7 +383,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
weaponType: "melee",
weaponName: game.i18n.localize("CELESTOPOL.Combat.unarmedAttack"),
weaponDegats: "0",
availableTargets: this._getCombatTargets(),
availableTargets: this._getCombatTargets({ includeMeleeWeapon: true }),
})
}
@@ -426,7 +422,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
weaponType: "distance",
weaponName: item.name,
weaponDegats: "0",
availableTargets: this._getCombatTargets(),
availableTargets: this._getCombatTargets({ onlyRanged: true, fallbackToAll: true }),
})
}

View File

@@ -85,6 +85,7 @@ export class CelestopolWeapon extends foundry.abstract.TypeDataModel {
choices: Object.keys(SYSTEM.WEAPON_DAMAGE_TYPES) }),
portee: new fields.StringField({ required: true, nullable: false, initial: "contact",
choices: Object.keys(SYSTEM.WEAPON_RANGE_TYPES) }),
equipped: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ required: true, textSearch: true }),
}
}