diff --git a/src/module/data/specialAbility.mjs b/src/module/data/specialAbility.mjs
index f26024cb..8341214b 100644
--- a/src/module/data/specialAbility.mjs
+++ b/src/module/data/specialAbility.mjs
@@ -48,7 +48,11 @@ export class SpecialAbilityDataModel extends BaseItem {
})
),
waffenLimit: new ArrayField(
- new StringField(),
+ new SchemaField({
+ waffe: new StringField(),
+ gruppe: new StringField(),
+ mod: new NumberField(),
+ })
),
mod: new ArrayField(new SchemaField({
name: new StringField(),
@@ -67,11 +71,13 @@ export class SpecialAbilityDataModel extends BaseItem {
}
}
- isActive() { // TODO also handle Waffenlimit
+ isActive(options) { // TODO also handle Waffenlimit
const requirements = this.#getRequirements()
let passes = false
+ let mod = 0
+
const flatActor = foundry.utils.flattenObject(this.parent.actor.system)
for (let requirement of requirements) {
@@ -90,11 +96,55 @@ export class SpecialAbilityDataModel extends BaseItem {
passes = targetField <= requirement.maxValue
}
+ if (requirement["compare"]) {
+ const {ownAttribute, operation, targetAttribute} = requirement["compare"]
+ if (options.target) {
+ const flatTarget = foundry.utils.flattenObject(options.target.system)
+ const foreignTargetField = flatTarget[targetAttribute]
+ const ourTargetField = flatActor[ownAttribute]
+ switch (operation) {
+ case "lt":
+ passes = ourTargetField < foreignTargetField;
+ break;
+ case "lte":
+ passes = ourTargetField <= foreignTargetField;
+ break;
+ case "eq":
+ passes = ourTargetField == foreignTargetField;
+ break;
+ case "neq":
+ passes = ourTargetField != foreignTargetField;
+ break;
+ case "gte":
+ passes = ourTargetField >= foreignTargetField;
+ break;
+ case "gt":
+ passes = ourTargetField > foreignTargetField;
+ break;
+ }
+ }
+ passes = false
+ }
+
if (!passes) {
break
}
}
- return passes
+ if (passes) { // TODO: how are we going to communicate the malus?
+ this.system.waffenLimit.forEach(waff => {
+ if (waff.waffe) {
+ passes = options.weapon.name === waff.waffe
+ if (waff.mod) mod = waff.mod
+ } else if (waff.gruppe) {
+ passes = options.skill.name === waff.gruppe
+ if (waff.mod) mod = waff.mod
+ }
+
+
+ })
+ }
+
+ return {passes, mod}
}
}
diff --git a/src/module/dialog/combatAction.mjs b/src/module/dialog/combatAction.mjs
new file mode 100644
index 00000000..81730f5c
--- /dev/null
+++ b/src/module/dialog/combatAction.mjs
@@ -0,0 +1,255 @@
+import {XmlImport} from "../xml-import/xml-import.mjs";
+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: 320,
+ height: 540
+ },
+ 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._combatManeuverId = null
+ this._actionManager = new ActionManager(this._actor)
+ }
+
+
+ static async #onSelectTarget(event, target) {
+ const {targetId} = target.dataset
+ this._targetId = this._targetId === targetId ? null : targetId
+ this.render({parts: ["form"]})
+ }
+
+ static async #onSelectManeuver(event, target) {
+ const {maneuverId} = target.dataset
+ this._combatManeuverId = this._combatManeuverId === maneuverId ? null : maneuverId
+ this.render({parts: ["form"]})
+ }
+
+
+ static async #onSelectWeaponAndSkill(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 #onSubmitForm(event, form, formData) {
+ event.preventDefault()
+ }
+
+ _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)
+ }
+ })
+ }
+
+ #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
+ 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(this._targetId)
+ this._maneuvers = manager.evaluate({
+ target,
+ weapon,
+ skill
+ }).filter(p => p.type === ActionManager.ATTACK).map(action => {
+ return {
+ isSelected: this._combatManeuverId === action.name,
+ id: action.name,
+ name: action.name,
+ type: action.type,
+ source: action.source,
+ cost: action.cost,
+ mod: action.mod,
+ }
+ })
+ 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._combatManeuverId)
+ 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._combatManeuverId
+ return context
+ } else {
+ ui.notifications.error(`Feature funktioniert nur wenn der Akteur ein Token auf der aktuellen Szene hat`);
+ }
+
+ }
+
+ _onRender(context, options) {
+ const target = this.element.querySelector(".actions button .value")
+ this.element.querySelectorAll('[name="mod"], [name="malus"]').forEach(e => e.addEventListener('change', (event) => {
+
+ const at = Number(context.targetNumber)
+ const malus = Number(this.element.querySelector('[name="malus"]').value)
+ const mod = Number(this.element.querySelector('[name="mod"]').value)
+ const maneuver = this._maneuvers?.find(p => p.id === this._combatManeuverId)
+ const result = at + (maneuver.mod?.(mod) ?? 0) + malus
+ target.textContent = `(${result})`
+ }))
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/module/documents/specialAbility.mjs b/src/module/documents/specialAbility.mjs
index e60847e5..454b3329 100644
--- a/src/module/documents/specialAbility.mjs
+++ b/src/module/documents/specialAbility.mjs
@@ -16,8 +16,4 @@ export class SpecialAbility extends Item {
}
}
- isActive() {
- return true
- }
-
}
diff --git a/src/module/sheets/actions/action-manager.mjs b/src/module/sheets/actions/action-manager.mjs
index b4ffc765..f2e01d6a 100644
--- a/src/module/sheets/actions/action-manager.mjs
+++ b/src/module/sheets/actions/action-manager.mjs
@@ -23,97 +23,121 @@ export class ActionManager {
cost: ActionManager.FREE,
type: ActionManager.DEFENSE,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Rufen",
cost: ActionManager.FREE,
type: ActionManager.INTERACTION,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Sich zu Boden fallen lassen",
cost: ActionManager.FREE,
type: ActionManager.MOVEMENT,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Waffe oder Gegenstand fallen lassen",
cost: ActionManager.FREE,
type: ActionManager.INTERACTION,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "getragenes Artefakt aktivieren",
cost: ActionManager.FREE,
type: ActionManager.INTERACTION,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Schnellziehen",
cost: ActionManager.FREE,
type: ActionManager.INTERACTION,
source: ActionManager.SF,
- eval: () => this.#hatSonderfertigkeit("Schnellziehen") && this.#evalSonderfertigkeitRequirements("Schnellziehen")
+ eval: (options) => this.#hatSonderfertigkeit("Schnellziehen", options) && this.#evalSonderfertigkeitRequirements("Schnellziehen", options)
}
]
#regularActions = [
{
- name: "Angriffsaktion",
+ name: "Nahkampfangriff",
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
- eval: () => this.#hatWaffeinHand()
+ eval: (options) => this.#hatWaffeinHand() && !this.#hatFernkampfWaffeinHand()
+ },
+ {
+ name: "Fernkampfangriff",
+ type: ActionManager.ATTACK,
+ cost: ActionManager.REGULAR,
+ source: ActionManager.DEFAULT,
+ eval: (options) => this.#hatFernkampfWaffeinHand(),
+ },
+ {
+ name: "Angesagter Fernkampfangriff",
+ type: ActionManager.ATTACK,
+ cost: ActionManager.CONTINUING,
+ source: ActionManager.DEFAULT,
+ eval: (options) => this.#hatFernkampfWaffeinHand(),
+ mod: (value) => -value * 2
+ },
+ {
+ name: "Scharfer Schuss",
+ type: ActionManager.ATTACK,
+ cost: ActionManager.CONTINUING,
+ source: ActionManager.SF,
+ eval: (options) => this.#hatFernkampfWaffeinHand() && this.#hatSonderfertigkeit("Scharfschütze", options) && this.#evalSonderfertigkeitRequirements("Scharfschütze", options),
+ mod: (value) => -value
},
{
name: "Schnellschuss",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
- eval: () => this.#hatFernkampfWaffeinHand()
+ eval: (options) => this.#hatFernkampfWaffeinHand()
},
{
name: "Schnellschuss (Scharfschütze)",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
- eval: () => this.#hatFernkampfWaffeinHand() && this.#hatSonderfertigkeit("Scharfschütze") && this.#evalSonderfertigkeitRequirements("Scharfschütze")
+ eval: (options) => this.#hatFernkampfWaffeinHand() && this.#hatSonderfertigkeit("Scharfschütze", options) && this.#evalSonderfertigkeitRequirements("Scharfschütze", options)
},
{
name: "Abwehraktion",
type: ActionManager.DEFENSE,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Bewegen",
type: ActionManager.MOVEMENT,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Position",
type: ActionManager.MOVEMENT,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Finte",
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.SF,
- eval: () =>
+ mod: (value) => value,
+ eval: (options) =>
this.#hatWaffeinHand() &&
- this.#hatSonderfertigkeit("Finte") &&
- this.#evalSonderfertigkeitRequirements("Finte")
+ this.#hatSonderfertigkeit("Finte", options) &&
+ this.#evalSonderfertigkeitRequirements("Finte", options)
},
{
@@ -121,23 +145,25 @@ export class ActionManager {
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.DEFAULT,
- eval: () => true
+ mod: (value) => -(value),
+ eval: (options) => !this.#hatFernkampfWaffeinHand()
},
{
name: "Wuchtschlag",
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.SF,
- eval: () => this.#hatSonderfertigkeit("Wuchtschlag")
- && this.#evalSonderfertigkeitRequirements("Wuchtschlag")
+ mod: (value) => -(value),
+ eval: (options) => !this.#hatFernkampfWaffeinHand() && this.#hatSonderfertigkeit("Wuchtschlag", options)
+ && this.#evalSonderfertigkeitRequirements("Wuchtschlag", options)
},
{
name: "Betäubungsschlag",
type: ActionManager.ATTACK,
cost: ActionManager.REGULAR,
source: ActionManager.SF,
- eval: () => this.#hatSonderfertigkeit("Betäubungsschlag")
- && this.#evalSonderfertigkeitRequirements("Betäubungsschlag")
+ eval: (options) => !this.#hatFernkampfWaffeinHand() && this.#hatSonderfertigkeit("Betäubungsschlag", options)
+ && this.#evalSonderfertigkeitRequirements("Betäubungsschlag", options)
}
]
@@ -147,76 +173,76 @@ export class ActionManager {
type: ActionManager.TALENT,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Waffe ziehen",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Sprinten",
type: ActionManager.MOVEMENT,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Gegenstand benutzen",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Schnellladen (Bogen)",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
- eval: () => this.#hatMunition()
- && this.#hatFernkampfWaffeinHand("Bogen")
- && this.#hatSonderfertigkeit("Schnellladen (Bogen)")
- && this.#evalSonderfertigkeitRequirements("Schnellladen (Bogen)")
+ eval: (options) => this.#hatMunition()
+ && this.#hatFernkampfWaffeinHand("Bogen", options)
+ && this.#hatSonderfertigkeit("Schnellladen (Bogen)", options)
+ && this.#evalSonderfertigkeitRequirements("Schnellladen (Bogen)", options)
},
{
name: "Schnellladen (Armbrust)",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
- eval: () => this.#hatMunition()
- && this.#hatFernkampfWaffeinHand("Armbrust")
- && this.#hatSonderfertigkeit("Schnellladen (Armbrust)")
- && this.#evalSonderfertigkeitRequirements("Schnellladen (Armbrust)")
+ eval: (options) => this.#hatMunition()
+ && this.#hatFernkampfWaffeinHand("Armbrust", options)
+ && this.#hatSonderfertigkeit("Schnellladen (Armbrust)", options)
+ && this.#evalSonderfertigkeitRequirements("Schnellladen (Armbrust)", options)
},
{
name: "Nachladen",
type: ActionManager.INTERACTION,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
- eval: () => this.#hatMunition()
+ eval: (options) => this.#hatMunition()
},
{
name: "Talenteinsatz",
type: ActionManager.TALENT,
cost: ActionManager.CONTINUING,
source: ActionManager.DEFAULT,
- eval: () => true
+ eval: (options) => true
},
{
name: "Zaubern",
type: ActionManager.SPELL,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
- eval: () => this.#hatSonderfertigkeitBeginnendMit("Repräsentation:")
+ eval: (options) => this.#hatSonderfertigkeitBeginnendMit("Repräsentation:", options)
},
{
name: "Liturgie wirken",
type: ActionManager.SPELL,
cost: ActionManager.CONTINUING,
source: ActionManager.SF,
- eval: () => this.#hatSonderfertigkeitBeginnendMit("Liturgiekenntnis")
+ eval: (options) => this.#hatSonderfertigkeitBeginnendMit("Liturgiekenntnis", options)
}
]
@@ -236,25 +262,30 @@ export class ActionManager {
return item != null
}
- #hatSonderfertigkeitBeginnendMit(name) {
+ #hatSonderfertigkeitBeginnendMit(name, options) {
return this.actor.itemTypes["SpecialAbility"]?.find(p => p.name.startsWith(name)) != null
}
- #hatSonderfertigkeit(name) {
+ #hatSonderfertigkeit(name, options) {
return this.actor.itemTypes["SpecialAbility"]?.find(p => p.name === name) != null
}
- #evalSonderfertigkeitRequirements(nameOfSF) {
+ #evalSonderfertigkeitRequirements(nameOfSF, options) {
const sf = this.actor.itemTypes["SpecialAbility"].find(p => p.name === nameOfSF)
- return sf.system.isActive()
+ return sf.system.isActive(options).passes
}
- evaluate() {
+ /**
+ *
+ * @param {{target: String?}} options
+ */
+
+ evaluate(options) {
let actionArray = [...this.#freeActions, ...this.#regularActions, ...this.#continuingActions]
console.log(this.actor, actionArray.map((action) => {
return {
...action,
- eval: action.eval()
+ eval: action.eval(options)
}
}))
const validActions = actionArray.filter(action => action.eval())
diff --git a/src/module/sheets/characterSheet.mjs b/src/module/sheets/characterSheet.mjs
index 8f7483ba..8e5621ec 100644
--- a/src/module/sheets/characterSheet.mjs
+++ b/src/module/sheets/characterSheet.mjs
@@ -7,6 +7,7 @@ import Meta from "./character/meta.mjs"
import Skills from "./character/skills.mjs"
import Social from "./character/social.mjs";
import Spells from "./character/spells.mjs"
+import {CombatActionDialog} from "../dialog/combatAction.mjs";
const {HandlebarsApplicationMixin} = foundry.applications.api
const {ActorSheetV2} = foundry.applications.sheets
@@ -36,6 +37,7 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
openEmbeddedDocument: CharacterSheet.#openEmbeddedDocument,
openCultureDocument: CharacterSheet.#openCultureDocument,
openSpeciesDocument: CharacterSheet.#openSpeciesDocument,
+ openCombatAction: CharacterSheet.#openCombatAction,
}
}
@@ -143,6 +145,10 @@ class CharacterSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
this.document.itemTypes["Species"]?.[0]?.sheet.render(true)
}
+ static #openCombatAction() {
+ new CombatActionDialog(this.document).render(true)
+ }
+
/**
* Handle form submission
* @this {AdvantageSheet}
diff --git a/src/style/organisms/_combat-action-dialog.scss b/src/style/organisms/_combat-action-dialog.scss
new file mode 100644
index 00000000..0fd892df
--- /dev/null
+++ b/src/style/organisms/_combat-action-dialog.scss
@@ -0,0 +1,91 @@
+@keyframes pulse {
+ 0% {
+ text-shadow: 0 0 0 red;
+ }
+ 75% {
+ text-shadow: 0 0 4px red;
+ }
+ 100% {
+ text-shadow: 0 0 0 red;
+ }
+}
+
+.dsa41.dialog.combat {
+
+ .window-content > section {
+
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+
+ fieldset {
+
+ flex: 1;
+
+ border-bottom: 0;
+ border-left: 0;
+ border-right: 0;
+ padding: 0;
+ margin: 0;
+
+ legend {
+ text-align: center;
+ padding: 0 16px;
+ }
+
+ }
+
+ ul {
+
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ text-indent: 0;
+
+ li {
+
+ height: 32px;
+ display: grid;
+ line-height: 32px;
+ vertical-align: middle;
+
+ grid-template-columns: 32px 1fr 83px;
+ grid-template-rows: 1fr;
+ border: 1px transparent;
+ margin: 8px;
+ gap: 0 8px;
+
+ &.selected {
+ background-color: rgba(255, 140, 0, 0.2);
+ border: 1px solid orange;
+ border-radius: 16px;
+ }
+
+ &.name-only {
+ display: block;
+ }
+ }
+ }
+
+ .malus-and-mod {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: 1fr;
+ gap: 0 8px;
+ }
+
+ .actions {
+ flex: 0;
+ display: flex;
+ justify-content: center;
+
+ button.ready {
+ animation: pulse 2s infinite;
+ }
+
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/style/styles.scss b/src/style/styles.scss
index f88ab0fc..80d733a7 100644
--- a/src/style/styles.scss
+++ b/src/style/styles.scss
@@ -24,4 +24,5 @@
@use "organisms/species-sheet";
@use "organisms/profession-sheet";
@use "organisms/xml-import-dialog";
+@use "organisms/combat-action-dialog";
diff --git a/src/templates/dialog/combat-action.hbs b/src/templates/dialog/combat-action.hbs
new file mode 100644
index 00000000..20cda01b
--- /dev/null
+++ b/src/templates/dialog/combat-action.hbs
@@ -0,0 +1,58 @@
+