359 lines
13 KiB
JavaScript
359 lines
13 KiB
JavaScript
import {ActionManager} from "../sheets/actions/action-manager.mjs";
|
|
|
|
const {
|
|
ApplicationV2,
|
|
HandlebarsApplicationMixin
|
|
} = foundry.applications.api
|
|
|
|
|
|
/**
|
|
* @typedef TokenDistance
|
|
* @property {Number} x
|
|
* @property {Number} y
|
|
* @property {Number} d
|
|
* @property {Token} token
|
|
*/
|
|
|
|
export class CombatActionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
|
|
static DEFAULT_OPTIONS = {
|
|
classes: ['dsa41', 'dialog', 'combat'],
|
|
tag: "form",
|
|
position: {
|
|
width: 480,
|
|
height: 640
|
|
},
|
|
window: {
|
|
resizable: false,
|
|
},
|
|
form: {
|
|
submitOnChange: false,
|
|
closeOnSubmit: true,
|
|
handler: CombatActionDialog.#onSubmitForm
|
|
},
|
|
actions: {
|
|
selectTarget: CombatActionDialog.#onSelectTarget,
|
|
selectWeaponAndSkill: CombatActionDialog.#onSelectWeaponAndSkill,
|
|
selectManeuver: CombatActionDialog.#onSelectManeuver,
|
|
}
|
|
}
|
|
|
|
static PARTS = {
|
|
form: {
|
|
template: 'systems/DSA_4-1/templates/dialog/combat-action.hbs',
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @type {Actor}
|
|
* @private
|
|
*/
|
|
_actor = null
|
|
|
|
constructor(actor) {
|
|
super();
|
|
this._actor = actor
|
|
this._targetId = null
|
|
this._skillId = null
|
|
this._weaponId = null
|
|
this._defenseManeuverId = null
|
|
this._actionManager = new ActionManager(this._actor)
|
|
CombatActionDialog._instance = this
|
|
}
|
|
|
|
static _instance = null
|
|
|
|
|
|
|
|
async #processOnSelectTarget(event, target) {
|
|
const {targetId} = target.dataset
|
|
this._targetId = this._targetId === targetId ? null : targetId
|
|
this.render({parts: ["form"]})
|
|
}
|
|
|
|
static async #onSelectTarget(event, target) {
|
|
event.preventDefault()
|
|
CombatActionDialog._instance.#processOnSelectTarget(event, target)
|
|
}
|
|
|
|
|
|
async #processOnSelectManeuver(event, target) {
|
|
const {maneuverId} = target.dataset
|
|
this._defenseManeuverId = this._defenseManeuverId === maneuverId ? null : maneuverId
|
|
this.render({parts: ["form"]})
|
|
}
|
|
|
|
|
|
static async #onSelectManeuver(event, target) {
|
|
event.preventDefault()
|
|
CombatActionDialog._instance.#processOnSelectManeuver(event, target)
|
|
}
|
|
|
|
async #processOnSelectWeaponAndSkill(event, target) {
|
|
const {weaponId, skillId} = target.dataset
|
|
this._weaponId = this._weaponId === weaponId ? null : weaponId
|
|
this._skillId = this._skillId === skillId ? null : skillId
|
|
this.render({parts: ["form"]})
|
|
}
|
|
|
|
|
|
static async #onSelectWeaponAndSkill(event, target) {
|
|
event.preventDefault()
|
|
CombatActionDialog._instance.#processOnSelectWeaponAndSkill(event, target)
|
|
}
|
|
|
|
async #processOnSubmitForm(event, form, formData) {
|
|
const maneuver = CombatActionDialog._instance.#evaluateManeuvers().find(p => p.id === this._defenseManeuverId)
|
|
const weapon = this._actor.itemTypes["Equipment"].find(p => p._id === this._weaponId)
|
|
const skill = this._actor.itemTypes["Skill"].find(p => p._id === this._skillId)
|
|
const target = game.actors.get(game.scenes.current.tokens.find(p => p._id === this._targetId).actorId)
|
|
if (maneuver.cost !== ActionManager.CONTINUING) {
|
|
this._actor.rollAttack({
|
|
weapon: this._weaponId,
|
|
skill: this._skillId,
|
|
target: this._targetId,
|
|
title: maneuver.name,
|
|
maneuver: maneuver.activate.toString(),
|
|
mod: this._mod,
|
|
circumstance: this._circumstance,
|
|
penalty: this._penalty,
|
|
targetNumber: this._targetNumber,
|
|
modDescription: maneuver?.modDescription?.replace("{}", "" + this._mod) ?? ""
|
|
})
|
|
return true
|
|
} else { // push into cooldown queue
|
|
const cooldowns = this._actor.system.cooldowns
|
|
/** @type Cooldown */
|
|
const newCooldown = {
|
|
start: maneuver.cooldown({weapon, skill, target, mod: this._mod}),
|
|
current: maneuver.cooldown({weapon, skill, target, mod: this._mod}),
|
|
data: {
|
|
cssClass: "Kampf",
|
|
weapon: this._weaponId,
|
|
skill: this._skillId,
|
|
target: this._targetId,
|
|
title: maneuver.name,
|
|
maneuver: maneuver.activate.toString(),
|
|
mod: this._mod,
|
|
circumstance: this._circumstance,
|
|
penalty: this._penalty,
|
|
targetNumber: this._targetNumber,
|
|
modDescription: maneuver?.modDescription?.replace("{}", "" + this._mod) ?? ""
|
|
}
|
|
}
|
|
cooldowns.push(newCooldown)
|
|
await this._actor.update({"system.cooldowns": cooldowns})
|
|
ui.notifications.info(`Neue Aktion für ${maneuver.name} mit Abklingzeit von ${maneuver.cooldown({
|
|
weapon,
|
|
skill,
|
|
target,
|
|
mod: this._mod
|
|
})} Aktionen hinzugefügt`)
|
|
}
|
|
}
|
|
|
|
static async #onSubmitForm(event, form, formData) {
|
|
event.preventDefault()
|
|
|
|
CombatActionDialog._instance.#processOnSubmitForm(event, form, formData)
|
|
}
|
|
|
|
_configureRenderOptions(options) {
|
|
super._configureRenderOptions(options)
|
|
if (options.window) {
|
|
options.window.title = `Mit ${this._actor.name} angreifen`
|
|
}
|
|
return options
|
|
}
|
|
|
|
#getDistanceBetween(originToken, targetToken) {
|
|
const distance = game.scenes.current.dimensions.distancePixels
|
|
|
|
// get distances between of tokens and thisTokenRepresentative
|
|
const actorOfToken = game.actors.get(targetToken.actorId)
|
|
return {
|
|
id: targetToken._id,
|
|
x: targetToken.x,
|
|
y: targetToken.y,
|
|
actor: actorOfToken,
|
|
token: targetToken,
|
|
d: ((Math.sqrt(Math.pow(targetToken.x - originToken.x, 2) + Math.pow(targetToken.y - originToken.y, 2))) / distance).toFixed(2),
|
|
}
|
|
}
|
|
|
|
#evaluateDistances() {
|
|
|
|
// get tokens in range on the current scene
|
|
const scene = game.scenes.current
|
|
const tokens = scene.tokens
|
|
const tokenId = this._actor.getActiveTokens()[0].id
|
|
const thisTokenRepresentative = tokens.get(tokenId)
|
|
|
|
/**
|
|
*
|
|
* @type {[TokenDistance]}
|
|
*/
|
|
return tokens.map(token => {
|
|
return {
|
|
isSelected: this._targetId === token.id,
|
|
...this.#getDistanceBetween(thisTokenRepresentative, token)
|
|
}
|
|
}).sort((a, b) => a.d - b.d).sort((a, b) => (a.isSelected ? 0 : 1) - (b.isSelected ? 0 : 1))
|
|
}
|
|
|
|
#evaluateWeapons() {
|
|
|
|
// get equipped weapons and adjust AT/PA values by basis values from actor TODO: and W/M of weapons
|
|
const equips = this._actor.system.heldenausruestung[this._actor.system.setEquipped]
|
|
const weapons = []
|
|
|
|
const equippedWeapons = ["links", "rechts", "fernkampf"]
|
|
|
|
const baseAt = {
|
|
links: this._actor.system.at.links.aktuell, // TODO hook Beidhändigerkampf/linkhand
|
|
rechts: this._actor.system.at.rechts.aktuell,
|
|
fernkampf: this._actor.system.fk.aktuell,
|
|
}
|
|
const basePa = {
|
|
links: this._actor.system.pa.links.aktuell, // TODO hook Beidhändigerkampf/linkhand
|
|
rechts: this._actor.system.pa.rechts.aktuell,
|
|
fernkampf: 0,
|
|
}
|
|
|
|
equippedWeapons.forEach(slot => {
|
|
const equip = equips[slot]
|
|
const weapon = this._actor.itemTypes["Equipment"].find(p => p._id === equip)
|
|
if (weapon) {
|
|
|
|
const variantWeaponSkills = [...weapon.system.rangedSkills, ...weapon.system.meleeSkills]
|
|
|
|
variantWeaponSkills.forEach(weaponSkill => {
|
|
|
|
const skill = this._actor.itemTypes["Skill"].find(p => p.name === weaponSkill)
|
|
if (skill) {
|
|
|
|
const skillAt = skill.system.at
|
|
const skillPa = skill.system.pa
|
|
|
|
weapons.push({
|
|
isSelected: this._skillId === skill._id && this._weaponId === weapon._id,
|
|
weaponId: weapon._id,
|
|
skillId: skill._id,
|
|
name: weapon.name,
|
|
skillName: skill.name,
|
|
img: weapon.img,
|
|
combatStatistics: {
|
|
at: baseAt[slot] + weapon.system.attackModifier + skillAt,
|
|
pa: basePa[slot] + weapon.system.parryModifier + skillPa
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
this._weapons = weapons.sort((a, b) => (a.isSelected ? 0 : 1) - (b.isSelected ? 0 : 1))
|
|
return this._weapons
|
|
|
|
}
|
|
|
|
#evaluateManeuvers() {
|
|
const manager = this._actionManager
|
|
const weapon = this._actor.itemTypes["Equipment"].find(p => p._id === this._weaponId)
|
|
const skill = this._actor.itemTypes["Skill"].find(p => p._id === this._skillId)
|
|
const target = game.actors.get(game.scenes.current.tokens.find(p => p._id === this._targetId).actorId)
|
|
this._maneuvers = manager.evaluate({
|
|
target,
|
|
weapon,
|
|
skill
|
|
}).filter(p => p.type === ActionManager.ATTACK).map(action => {
|
|
return {
|
|
isSelected: this._defenseManeuverId === action.name,
|
|
id: action.name,
|
|
name: action.name,
|
|
type: action.type,
|
|
source: action.source,
|
|
cost: action.cost,
|
|
penalty: action.eval?.mod ?? 0,
|
|
mod: action.mod,
|
|
modDescription: action.modDescription,
|
|
cooldown: action.cooldown,
|
|
activate: action.activate,
|
|
}
|
|
}).sort((a, b) => (a.isSelected ? 0 : 1) - (b.isSelected ? 0 : 1))
|
|
return this._maneuvers
|
|
}
|
|
|
|
async _prepareContext(options) {
|
|
const context = await super._prepareContext(options)
|
|
|
|
context.actor = this._actor
|
|
context.distanceUnit = game.scenes.current.grid.units
|
|
|
|
if (this._actor.getActiveTokens()[0]?.id) {
|
|
|
|
context.tokenDistances = this.#evaluateDistances()
|
|
context.weapons = this.#evaluateWeapons()
|
|
|
|
if (this._targetId && this._weaponId && this._skillId) {
|
|
context.maneuver = this.#evaluateManeuvers()
|
|
}
|
|
const maneuver = this._maneuvers?.find(p => p.id === this._defenseManeuverId)
|
|
if (maneuver) {
|
|
context.canMod = maneuver.mod != undefined
|
|
}
|
|
|
|
context.targetNumber = context.weapons.find(p => p.weaponId === this._weaponId && p.skillId === this._skillId)?.combatStatistics.at
|
|
|
|
// TODO get W/M of weapon NOW
|
|
|
|
context.ready = this._targetId && this._weaponId && this._skillId && this._defenseManeuverId
|
|
return context
|
|
} else {
|
|
ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
|
|
}
|
|
|
|
}
|
|
|
|
#update(context) {
|
|
const target = this.element.querySelector(".actions button .value")
|
|
const targetDescription = this.element.querySelector(".modResult")
|
|
const at = Number(context.targetNumber)
|
|
const maneuver = this._maneuvers?.find(p => p.id === this._defenseManeuverId)
|
|
const mod = Number(this.element.querySelector('[name="mod"]').value)
|
|
const penalty = 0 - (maneuver?.penalty ?? 0) + (maneuver?.mod?.(mod) ?? 0) ?? 0
|
|
const circumstance = Number(this.element.querySelector('[name="circumstance"]').value)
|
|
this.element.querySelector('[name="penalty"]').value = penalty + circumstance
|
|
const result = (at + circumstance + penalty)
|
|
|
|
this._circumstance = circumstance
|
|
this._penalty = penalty
|
|
this._targetNumber = result
|
|
this._mod = mod
|
|
this._modDescription = maneuver?.modDescription?.replace("{}", "" + mod) ?? ""
|
|
|
|
target.textContent = `(${result})`
|
|
targetDescription.textContent = this._modDescription
|
|
|
|
if (result <= 0) {
|
|
context.ready = false
|
|
this.element.querySelector(".actions button").classList.remove("ready")
|
|
this.element.querySelector(".actions button").setAttribute("disabled", true)
|
|
} else {
|
|
context.ready = true
|
|
this.element.querySelector(".actions button").classList.add("ready")
|
|
this.element.querySelector(".actions button").removeAttribute("disabled")
|
|
}
|
|
}
|
|
|
|
_onRender(context, options) {
|
|
this.#update(context)
|
|
|
|
this.element.querySelectorAll('[name="mod"], [name="malus"], [name="circumstance"]').forEach(e => e.addEventListener('change', (event) => {
|
|
this.#update(context)
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
} |