ENhance actor sheet with roll messages
@@ -4,6 +4,8 @@ globalThis.SYSTEM = SYSTEM
|
|||||||
import * as models from "./module/models/_module.mjs"
|
import * as models from "./module/models/_module.mjs"
|
||||||
import * as documents from "./module/documents/_module.mjs"
|
import * as documents from "./module/documents/_module.mjs"
|
||||||
import * as applications from "./module/applications/_module.mjs"
|
import * as applications from "./module/applications/_module.mjs"
|
||||||
|
import { SystemManager } from "./module/applications/hud/system-manager.js"
|
||||||
|
import { MODULE, REQUIRED_CORE_MODULE_VERSION } from "./module/applications/hud/constants.js"
|
||||||
|
|
||||||
Hooks.once("init", function () {
|
Hooks.once("init", function () {
|
||||||
console.info("Adventures with Emmy | Initializing System")
|
console.info("Adventures with Emmy | Initializing System")
|
||||||
@@ -72,6 +74,13 @@ Hooks.once("init", function () {
|
|||||||
CONFIG.Dice.rolls.push(documents.AwERoll)
|
CONFIG.Dice.rolls.push(documents.AwERoll)
|
||||||
CONFIG.Combatant.documentClass = documents.AwECombatant
|
CONFIG.Combatant.documentClass = documents.AwECombatant
|
||||||
|
|
||||||
|
// Register conditions as status effects (token HUD overlays)
|
||||||
|
CONFIG.statusEffects = Object.values(SYSTEM.CONDITIONS).map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
name: c.label,
|
||||||
|
img: `systems/fvtt-adventures-with-emmy/assets/conditions/${c.id}.svg`
|
||||||
|
}))
|
||||||
|
|
||||||
// Handlebars helpers
|
// Handlebars helpers
|
||||||
Handlebars.registerHelper("abs", (value) => Math.abs(value ?? 0))
|
Handlebars.registerHelper("abs", (value) => Math.abs(value ?? 0))
|
||||||
})
|
})
|
||||||
@@ -79,3 +88,33 @@ Hooks.once("init", function () {
|
|||||||
Hooks.once("ready", function () {
|
Hooks.once("ready", function () {
|
||||||
console.info("Adventures with Emmy | System Ready")
|
console.info("Adventures with Emmy | System Ready")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Token Action HUD Core integration (only fires if the module is active)
|
||||||
|
Hooks.on('tokenActionHudCoreApiReady', async () => {
|
||||||
|
const module = {
|
||||||
|
api: {
|
||||||
|
requiredCoreModuleVersion: REQUIRED_CORE_MODULE_VERSION,
|
||||||
|
SystemManager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Hooks.call('tokenActionHudSystemReady', module)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Chat message: "Roll Damage" button on weapon attack messages
|
||||||
|
Hooks.on('renderChatMessageHTML', (message, html) => {
|
||||||
|
const btn = html.querySelector('.roll-damage-btn')
|
||||||
|
if (!btn) return
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const actorId = btn.dataset.actorId
|
||||||
|
const actor = game.actors.get(actorId)
|
||||||
|
if (!actor) return ui.notifications.warn('Actor not found')
|
||||||
|
await actor.rollDamage({
|
||||||
|
name: btn.dataset.itemName,
|
||||||
|
img: btn.dataset.itemImg,
|
||||||
|
system: {
|
||||||
|
damageFormula: btn.dataset.damageFormula,
|
||||||
|
damageType: btn.dataset.damageType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#2255aa"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">E</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#886644"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">H</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#ff8800"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">I</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#7744aa"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">J</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#aa0000"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">!</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#665533"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">P</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#22aa22"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">Q</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#5566bb"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">S</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<circle cx="32" cy="32" r="30" fill="#cc2222"/>
|
||||||
|
<text x="32" y="42" text-anchor="middle" font-size="30" fill="white" font-family="sans-serif" font-weight="bold">V</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 242 B |
@@ -28,6 +28,11 @@
|
|||||||
"AWEMMY.Condition.Quickened": "Quickened",
|
"AWEMMY.Condition.Quickened": "Quickened",
|
||||||
"AWEMMY.Condition.Slowed": "Slowed",
|
"AWEMMY.Condition.Slowed": "Slowed",
|
||||||
"AWEMMY.Condition.Vulnerable": "Vulnerable",
|
"AWEMMY.Condition.Vulnerable": "Vulnerable",
|
||||||
|
"AWEMMY.Condition.Panel": "Conditions",
|
||||||
|
"AWEMMY.Roll.ConditionBonus": "Condition",
|
||||||
|
"AWEMMY.Kit.Use": "Use Kit",
|
||||||
|
"AWEMMY.Kit.Used": "{name} used (charges: {value}/{max})",
|
||||||
|
"AWEMMY.Kit.Depleted": "{name} has no charges remaining!",
|
||||||
"AWEMMY.Ability.Cost.Free": "Free",
|
"AWEMMY.Ability.Cost.Free": "Free",
|
||||||
"AWEMMY.Ability.TypeLabel": "Type",
|
"AWEMMY.Ability.TypeLabel": "Type",
|
||||||
"AWEMMY.Ability.Type.Field": "Field",
|
"AWEMMY.Ability.Type.Field": "Field",
|
||||||
@@ -95,17 +100,27 @@
|
|||||||
"AWEMMY.Creature.Hints": "Hints",
|
"AWEMMY.Creature.Hints": "Hints",
|
||||||
"AWEMMY.Creature.Threshold1": "Threshold 1",
|
"AWEMMY.Creature.Threshold1": "Threshold 1",
|
||||||
"AWEMMY.Creature.Threshold2": "Threshold 2",
|
"AWEMMY.Creature.Threshold2": "Threshold 2",
|
||||||
|
"AWEMMY.Creature.Threshold3": "Threshold 3",
|
||||||
"AWEMMY.Ability.CostLabel": "Cost",
|
"AWEMMY.Ability.CostLabel": "Cost",
|
||||||
"AWEMMY.Ability.Frequency": "Frequency",
|
"AWEMMY.Ability.Frequency": "Frequency",
|
||||||
"AWEMMY.Ability.Requirements": "Requirements",
|
"AWEMMY.Ability.Requirements": "Requirements",
|
||||||
"AWEMMY.Ability.Trigger": "Trigger",
|
"AWEMMY.Ability.Trigger": "Trigger",
|
||||||
"AWEMMY.Ability.Traits": "Traits",
|
"AWEMMY.Ability.Traits": "Traits",
|
||||||
"AWEMMY.Ability.AddTrait": "Add trait...",
|
"AWEMMY.Ability.AddTrait": "Add trait...",
|
||||||
|
"AWEMMY.Ability.FlowPointCost": "Flow Point Cost",
|
||||||
|
"AWEMMY.Ability.Use": "Use Ability",
|
||||||
|
"AWEMMY.Ability.AlreadyUsed": "{name} has already been used today!",
|
||||||
|
"AWEMMY.Ability.NotEnoughFP": "{name} requires {cost} Flow Point(s), but you only have {current}!",
|
||||||
|
"AWEMMY.Ability.DailyReset": "Daily Preparations",
|
||||||
|
"AWEMMY.Ability.DailyResetHint": "Reset all once-per-day abilities (daily preparations)",
|
||||||
|
"AWEMMY.Ability.DailyResetDone": "Daily preparations complete — abilities reset.",
|
||||||
"AWEMMY.Weapon.Range": "Range",
|
"AWEMMY.Weapon.Range": "Range",
|
||||||
"AWEMMY.Weapon.Damage": "Damage",
|
"AWEMMY.Weapon.Damage": "Damage",
|
||||||
"AWEMMY.Weapon.DamageType": "Damage Type",
|
"AWEMMY.Weapon.DamageType": "Damage Type",
|
||||||
"AWEMMY.Weapon.AttackAttribute": "Attack Attribute",
|
"AWEMMY.Weapon.AttackAttribute": "Attack Attribute",
|
||||||
"AWEMMY.Weapon.AttackRoll": "Attack Roll",
|
"AWEMMY.Weapon.AttackRoll": "Attack Roll",
|
||||||
|
"AWEMMY.Weapon.DamageRoll": "Damage Roll",
|
||||||
|
"AWEMMY.Weapon.NoDamageFormula": "This weapon has no damage formula.",
|
||||||
"AWEMMY.Weapon.Hit": "Hit",
|
"AWEMMY.Weapon.Hit": "Hit",
|
||||||
"AWEMMY.Weapon.CriticalHit": "Critical Hit!",
|
"AWEMMY.Weapon.CriticalHit": "Critical Hit!",
|
||||||
"AWEMMY.Field.KeyAttribute": "Key Attribute",
|
"AWEMMY.Field.KeyAttribute": "Key Attribute",
|
||||||
@@ -117,5 +132,29 @@
|
|||||||
"AWEMMY.Kit.Field": "Field",
|
"AWEMMY.Kit.Field": "Field",
|
||||||
"AWEMMY.Kit.Charges": "Charges",
|
"AWEMMY.Kit.Charges": "Charges",
|
||||||
"AWEMMY.Equipment.Quantity": "Quantity",
|
"AWEMMY.Equipment.Quantity": "Quantity",
|
||||||
"AWEMMY.Equipment.Weight": "Weight"
|
"AWEMMY.Equipment.Weight": "Weight",
|
||||||
|
"AWEMMY.Sheet.EditItem": "Edit",
|
||||||
|
"AWEMMY.Sheet.DeleteItem": "Delete",
|
||||||
|
"AWEMMY.Character.ItemReplaced": "Replaced existing item: {name}",
|
||||||
|
"AWEMMY.Rest.LongRest": "Rest & Daily Preparations",
|
||||||
|
"AWEMMY.Rest.LongRestConfirm": "{name} takes an 8-hour rest and performs daily preparations.",
|
||||||
|
"AWEMMY.Rest.Rest": "Rest",
|
||||||
|
"AWEMMY.Rest.Cancel": "Cancel",
|
||||||
|
"AWEMMY.Rest.LongRestTitle": "{name} rests for 8 hours",
|
||||||
|
"AWEMMY.Rest.HPRestored": "Recovered {amount} HP (now {max}/{max})",
|
||||||
|
"AWEMMY.Rest.AbilitiesReset": "{count} daily ability(ies) reset",
|
||||||
|
"AWEMMY.Rest.KitsReplenished": "{count} kit(s) replenished",
|
||||||
|
"AWEMMY.Rest.AlreadyRested": "Already at full health — no recovery needed.",
|
||||||
|
|
||||||
|
"AWEMMY.TAH.Stats": "Stats",
|
||||||
|
"AWEMMY.TAH.Combat": "Combat",
|
||||||
|
"AWEMMY.TAH.Items": "Items",
|
||||||
|
"AWEMMY.TAH.Utility": "Utility",
|
||||||
|
"AWEMMY.TAH.Weapons": "Weapons",
|
||||||
|
"AWEMMY.TAH.Abilities": "Abilities",
|
||||||
|
"AWEMMY.TAH.Kits": "Kits",
|
||||||
|
"AWEMMY.TAH.HP": "HP",
|
||||||
|
"AWEMMY.TAH.Flow": "Flow Points",
|
||||||
|
"AWEMMY.TAH.LongRest": "Long Rest",
|
||||||
|
"AWEMMY.TAH.EndTurn": "End Turn"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { SYSTEM } from '../../config/system.mjs'
|
||||||
|
|
||||||
|
export let ActionHandler = null
|
||||||
|
|
||||||
|
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||||
|
ActionHandler = class ActionHandler extends coreModule.api.ActionHandler {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async buildSystemActions(groupIds) {
|
||||||
|
this.actors = (!this.actor) ? this._getActors() : [this.actor]
|
||||||
|
this.actorType = this.actor?.type
|
||||||
|
|
||||||
|
if (this.actorType === 'character') {
|
||||||
|
await this.#buildCharacterActions()
|
||||||
|
} else if (this.actorType === 'creature') {
|
||||||
|
await this.#buildCreatureActions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildCharacterActions() {
|
||||||
|
await this.#buildAttributes()
|
||||||
|
await this.#buildHP()
|
||||||
|
await this.#buildFlow()
|
||||||
|
await this.#buildWeapons()
|
||||||
|
await this.#buildConditions()
|
||||||
|
await this.#buildAbilities()
|
||||||
|
await this.#buildKits()
|
||||||
|
await this.#buildUtility()
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildCreatureActions() {
|
||||||
|
await this.#buildAttributes()
|
||||||
|
await this.#buildHP()
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildAttributes() {
|
||||||
|
const actions = []
|
||||||
|
const attrKeys = ['agility', 'fitness', 'awareness', 'influence']
|
||||||
|
const labelKeys = {
|
||||||
|
agility: 'AWEMMY.Attribute.Agility',
|
||||||
|
fitness: 'AWEMMY.Attribute.Fitness',
|
||||||
|
awareness: 'AWEMMY.Attribute.Awareness',
|
||||||
|
influence: 'AWEMMY.Attribute.Influence'
|
||||||
|
}
|
||||||
|
for (const key of attrKeys) {
|
||||||
|
const attr = this.actor.system.attributes?.[key]
|
||||||
|
if (!attr) continue
|
||||||
|
const mod = attr.mod ?? 0
|
||||||
|
const modText = mod >= 0 ? `+${mod}` : `${mod}`
|
||||||
|
actions.push({
|
||||||
|
name: coreModule.api.Utils.i18n(labelKeys[key]),
|
||||||
|
id: key,
|
||||||
|
info1: { text: modText },
|
||||||
|
encodedValue: ['attribute', key].join(this.delimiter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await this.addActions(actions, { id: 'attributes', type: 'system' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildHP() {
|
||||||
|
const hp = this.actor.system.hp
|
||||||
|
if (!hp) return
|
||||||
|
const tooltip = { content: `${hp.value} / ${hp.max}`, direction: 'LEFT' }
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
name: `${hp.value} / ${hp.max}`,
|
||||||
|
id: 'hp_display',
|
||||||
|
tooltip,
|
||||||
|
encodedValue: ['hp', 'display'].join(this.delimiter)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '+',
|
||||||
|
id: 'hp_add',
|
||||||
|
tooltip,
|
||||||
|
encodedValue: ['hp', 'add'].join(this.delimiter)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '−',
|
||||||
|
id: 'hp_sub',
|
||||||
|
tooltip,
|
||||||
|
encodedValue: ['hp', 'sub'].join(this.delimiter)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
await this.addActions(actions, { id: 'hp', type: 'system' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildFlow() {
|
||||||
|
const fp = this.actor.system.flowPoints
|
||||||
|
if (fp === undefined) return
|
||||||
|
const tooltip = { content: `FP: ${fp.value}`, direction: 'LEFT' }
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
name: `${fp.value} FP`,
|
||||||
|
id: 'flow_display',
|
||||||
|
tooltip,
|
||||||
|
encodedValue: ['flow', 'display'].join(this.delimiter)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '+',
|
||||||
|
id: 'flow_add',
|
||||||
|
tooltip,
|
||||||
|
encodedValue: ['flow', 'add'].join(this.delimiter)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '−',
|
||||||
|
id: 'flow_sub',
|
||||||
|
tooltip,
|
||||||
|
encodedValue: ['flow', 'sub'].join(this.delimiter)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
await this.addActions(actions, { id: 'flow', type: 'system' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildWeapons() {
|
||||||
|
const weapons = this.actor.itemTypes?.weapon ?? []
|
||||||
|
for (const weapon of weapons) {
|
||||||
|
const attrId = weapon.system.attackAttribute
|
||||||
|
const attr = this.actor.system.attributes?.[attrId]
|
||||||
|
const mod = attr?.mod ?? 0
|
||||||
|
const modText = mod >= 0 ? `+${mod}` : `${mod}`
|
||||||
|
|
||||||
|
const groupData = { id: `weapon_${weapon.id}`, name: weapon.name, type: 'system' }
|
||||||
|
this.addGroup(groupData, { id: 'weapons', type: 'system' }, true)
|
||||||
|
|
||||||
|
const actions = [{
|
||||||
|
name: weapon.name,
|
||||||
|
id: `weapon_${weapon.id}`,
|
||||||
|
info1: { text: modText },
|
||||||
|
encodedValue: ['weapon', weapon.id].join(this.delimiter)
|
||||||
|
}]
|
||||||
|
await this.addActions(actions, { id: `weapon_${weapon.id}`, type: 'system' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildConditions() {
|
||||||
|
const actions = []
|
||||||
|
for (const [key, cond] of Object.entries(SYSTEM.CONDITIONS)) {
|
||||||
|
const isActive = this.actor.statuses?.has(key) ?? false
|
||||||
|
actions.push({
|
||||||
|
name: coreModule.api.Utils.i18n(cond.label),
|
||||||
|
id: key,
|
||||||
|
info1: { text: isActive ? '✓' : '' },
|
||||||
|
encodedValue: ['condition', key].join(this.delimiter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await this.addActions(actions, { id: 'conditions', type: 'system' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildAbilities() {
|
||||||
|
const abilities = this.actor.itemTypes?.ability ?? []
|
||||||
|
const actions = []
|
||||||
|
for (const ability of abilities) {
|
||||||
|
const sys = ability.system
|
||||||
|
const costLabel = game.i18n.localize(SYSTEM.ABILITY_COST[sys.cost]?.label ?? sys.cost)
|
||||||
|
const isUsed = sys.usedToday
|
||||||
|
actions.push({
|
||||||
|
name: ability.name,
|
||||||
|
id: ability.id,
|
||||||
|
info1: { text: isUsed ? `${costLabel} ✓` : costLabel },
|
||||||
|
encodedValue: ['ability', ability.id].join(this.delimiter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await this.addActions(actions, { id: 'abilities', type: 'system' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildKits() {
|
||||||
|
const kits = this.actor.itemTypes?.kit ?? []
|
||||||
|
const actions = []
|
||||||
|
for (const kit of kits) {
|
||||||
|
const charges = kit.system.charges
|
||||||
|
const chargesText = `${charges.value}/${charges.max}`
|
||||||
|
actions.push({
|
||||||
|
name: kit.name,
|
||||||
|
id: kit.id,
|
||||||
|
info1: { text: chargesText },
|
||||||
|
encodedValue: ['kit', kit.id].join(this.delimiter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await this.addActions(actions, { id: 'kits', type: 'system' })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #buildUtility() {
|
||||||
|
const actions = []
|
||||||
|
actions.push({
|
||||||
|
name: coreModule.api.Utils.i18n('AWEMMY.TAH.LongRest'),
|
||||||
|
id: 'longRest',
|
||||||
|
encodedValue: ['utility', 'longRest'].join(this.delimiter)
|
||||||
|
})
|
||||||
|
if (game.combat?.current?.tokenId === this.token?.id) {
|
||||||
|
actions.push({
|
||||||
|
name: coreModule.api.Utils.i18n('AWEMMY.TAH.EndTurn'),
|
||||||
|
id: 'endTurn',
|
||||||
|
encodedValue: ['utility', 'endTurn'].join(this.delimiter)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await this.addActions(actions, { id: 'utility', type: 'system' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const MODULE = { ID: 'token-action-hud-adventures-with-emmy' }
|
||||||
|
export const CORE_MODULE = { ID: 'token-action-hud-core' }
|
||||||
|
export const REQUIRED_CORE_MODULE_VERSION = '2.0'
|
||||||
|
|
||||||
|
export const GROUP = {
|
||||||
|
attributes: { id: 'attributes', name: 'AWEMMY.Character.Attributes', type: 'system' },
|
||||||
|
hp: { id: 'hp', name: 'AWEMMY.TAH.HP', type: 'system' },
|
||||||
|
flow: { id: 'flow', name: 'AWEMMY.TAH.Flow', type: 'system' },
|
||||||
|
weapons: { id: 'weapons', name: 'AWEMMY.TAH.Weapons', type: 'system' },
|
||||||
|
conditions: { id: 'conditions', name: 'AWEMMY.Condition.Panel', type: 'system' },
|
||||||
|
abilities: { id: 'abilities', name: 'AWEMMY.TAH.Abilities', type: 'system' },
|
||||||
|
kits: { id: 'kits', name: 'AWEMMY.TAH.Kits', type: 'system' },
|
||||||
|
utility: { id: 'utility', name: 'AWEMMY.TAH.Utility', type: 'system' }
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { GROUP } from './constants.js'
|
||||||
|
|
||||||
|
export let DEFAULTS = null
|
||||||
|
|
||||||
|
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||||
|
const groups = foundry.utils.deepClone(GROUP)
|
||||||
|
Object.values(groups).forEach(group => {
|
||||||
|
group.name = coreModule.api.Utils.i18n(group.name)
|
||||||
|
group.listName = `Group: ${coreModule.api.Utils.i18n(group.listName ?? group.name)}`
|
||||||
|
})
|
||||||
|
const groupsArray = Object.values(groups)
|
||||||
|
|
||||||
|
DEFAULTS = {
|
||||||
|
layout: [
|
||||||
|
{
|
||||||
|
nestId: 'stats',
|
||||||
|
id: 'stats',
|
||||||
|
name: game.i18n.localize('AWEMMY.TAH.Stats'),
|
||||||
|
groups: [
|
||||||
|
{ ...groups.attributes, nestId: 'stats_attributes' },
|
||||||
|
{ ...groups.hp, nestId: 'stats_hp' },
|
||||||
|
{ ...groups.flow, nestId: 'stats_flow' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nestId: 'combat',
|
||||||
|
id: 'combat',
|
||||||
|
name: game.i18n.localize('AWEMMY.TAH.Combat'),
|
||||||
|
groups: [
|
||||||
|
{ ...groups.weapons, nestId: 'combat_weapons' },
|
||||||
|
{ ...groups.conditions, nestId: 'combat_conditions' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nestId: 'items',
|
||||||
|
id: 'items',
|
||||||
|
name: game.i18n.localize('AWEMMY.TAH.Items'),
|
||||||
|
groups: [
|
||||||
|
{ ...groups.abilities, nestId: 'items_abilities' },
|
||||||
|
{ ...groups.kits, nestId: 'items_kits' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nestId: 'utility',
|
||||||
|
id: 'utility',
|
||||||
|
name: game.i18n.localize('AWEMMY.TAH.Utility'),
|
||||||
|
groups: [
|
||||||
|
{ ...groups.utility, nestId: 'utility_utility' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
groups: groupsArray
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
export let RollHandler = null
|
||||||
|
|
||||||
|
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||||
|
RollHandler = class RollHandler extends coreModule.api.RollHandler {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async handleActionClick(event, encodedValue) {
|
||||||
|
const [actionTypeId, actionId] = encodedValue.split(this.delimiter ?? '|')
|
||||||
|
|
||||||
|
if (this.actor) {
|
||||||
|
await this.#handleAction(event, this.actor, this.token, actionTypeId, actionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const knownTypes = ['character', 'creature']
|
||||||
|
for (const token of canvas.tokens.controlled.filter(t => knownTypes.includes(t.actor?.type))) {
|
||||||
|
await this.#handleAction(event, token.actor, token, actionTypeId, actionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async handleActionHover(event, encodedValue) {}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async handleGroupClick(event, group) {}
|
||||||
|
|
||||||
|
async #handleAction(event, actor, token, actionTypeId, actionId) {
|
||||||
|
switch (actionTypeId) {
|
||||||
|
case 'attribute':
|
||||||
|
await actor.rollAttribute(actionId)
|
||||||
|
break
|
||||||
|
case 'hp':
|
||||||
|
await this.#handleHP(actor, actionId)
|
||||||
|
break
|
||||||
|
case 'flow':
|
||||||
|
await this.#handleFlow(actor, actionId)
|
||||||
|
break
|
||||||
|
case 'weapon':
|
||||||
|
await this.#handleWeapon(actor, actionId)
|
||||||
|
break
|
||||||
|
case 'condition':
|
||||||
|
await actor.toggleStatusEffect(actionId)
|
||||||
|
break
|
||||||
|
case 'ability':
|
||||||
|
await actor.useAbility(actionId)
|
||||||
|
break
|
||||||
|
case 'kit':
|
||||||
|
await actor.useKit(actionId)
|
||||||
|
break
|
||||||
|
case 'utility':
|
||||||
|
await this.#handleUtility(actor, token, actionId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleHP(actor, actionId) {
|
||||||
|
if (actionId === 'display') return
|
||||||
|
const hp = actor.system.hp
|
||||||
|
if (!hp) return
|
||||||
|
const newValue = actionId === 'add' ? hp.value + 1 : hp.value - 1
|
||||||
|
if (newValue < 0 || newValue > hp.max) return
|
||||||
|
await actor.update({ 'system.hp.value': newValue })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleFlow(actor, actionId) {
|
||||||
|
if (actionId === 'display') return
|
||||||
|
const fp = actor.system.flowPoints
|
||||||
|
if (fp === undefined) return
|
||||||
|
const newValue = actionId === 'add' ? fp.value + 1 : Math.max(0, fp.value - 1)
|
||||||
|
await actor.update({ 'system.flowPoints.value': newValue })
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleWeapon(actor, actionId) {
|
||||||
|
const weapon = actor.items.get(actionId)
|
||||||
|
if (!weapon) return
|
||||||
|
await actor.rollWeapon(weapon)
|
||||||
|
}
|
||||||
|
|
||||||
|
async #handleUtility(actor, token, actionId) {
|
||||||
|
switch (actionId) {
|
||||||
|
case 'longRest':
|
||||||
|
await actor.longRest()
|
||||||
|
break
|
||||||
|
case 'endTurn':
|
||||||
|
if (game.combat?.current?.tokenId === token?.id) {
|
||||||
|
await game.combat.nextTurn()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Register module settings.
|
||||||
|
* Called by Token Action HUD Core to register Token Action HUD system module settings.
|
||||||
|
* @param {function} coreUpdate Token Action HUD Core update function
|
||||||
|
*/
|
||||||
|
export function register(coreUpdate) {
|
||||||
|
// No system-specific settings for now
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { ActionHandler } from './action-handler.js'
|
||||||
|
import { RollHandler as Core } from './roll-handler.js'
|
||||||
|
import { MODULE } from './constants.js'
|
||||||
|
import { DEFAULTS } from './defaults.js'
|
||||||
|
import * as systemSettings from './settings.js'
|
||||||
|
|
||||||
|
export let SystemManager = null
|
||||||
|
|
||||||
|
Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
|
||||||
|
SystemManager = class SystemManager extends coreModule.api.SystemManager {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getActionHandler() {
|
||||||
|
return new ActionHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getAvailableRollHandlers() {
|
||||||
|
return { core: 'Adventures with Emmy' }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
getRollHandler(rollHandlerId) {
|
||||||
|
switch (rollHandlerId) {
|
||||||
|
case 'core':
|
||||||
|
default:
|
||||||
|
return new Core()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async registerDefaults() {
|
||||||
|
return DEFAULTS
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
registerSettings(coreUpdate) {
|
||||||
|
systemSettings.register(coreUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
registerStyles() {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -86,6 +86,8 @@ export default class AwEActorSheet extends HandlebarsApplicationMixin(foundry.ap
|
|||||||
*/
|
*/
|
||||||
async _onRoll(event) {
|
async _onRoll(event) {
|
||||||
if (this.isEditMode) return
|
if (this.isEditMode) return
|
||||||
|
// Skip if the element has a registered data-action (handled by the action system)
|
||||||
|
if (event.currentTarget.dataset.action) return
|
||||||
const attributeId = event.currentTarget.dataset.attributeId
|
const attributeId = event.currentTarget.dataset.attributeId
|
||||||
if (!attributeId) return
|
if (!attributeId) return
|
||||||
await this.document.rollAttribute(attributeId)
|
await this.document.rollAttribute(attributeId)
|
||||||
@@ -233,6 +235,7 @@ export default class AwEActorSheet extends HandlebarsApplicationMixin(foundry.ap
|
|||||||
static async #onItemDelete(event, target) {
|
static async #onItemDelete(event, target) {
|
||||||
const itemUuid = target.getAttribute("data-item-uuid")
|
const itemUuid = target.getAttribute("data-item-uuid")
|
||||||
const item = await fromUuid(itemUuid)
|
const item = await fromUuid(itemUuid)
|
||||||
|
if (!item) return
|
||||||
await item.deleteDialog()
|
await item.deleteDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ export default class AwECharacterSheet extends AwEActorSheet {
|
|||||||
flowPointsPlus: AwECharacterSheet.#onFlowPointsPlus,
|
flowPointsPlus: AwECharacterSheet.#onFlowPointsPlus,
|
||||||
flowPointsMinus: AwECharacterSheet.#onFlowPointsMinus,
|
flowPointsMinus: AwECharacterSheet.#onFlowPointsMinus,
|
||||||
rollField: AwECharacterSheet.#onRollField,
|
rollField: AwECharacterSheet.#onRollField,
|
||||||
rollWeapon: AwECharacterSheet.#onRollWeapon
|
rollWeapon: AwECharacterSheet.#onRollWeapon,
|
||||||
|
rollDamage: AwECharacterSheet.#onRollDamage,
|
||||||
|
toggleCondition: AwECharacterSheet.#onToggleCondition,
|
||||||
|
useKit: AwECharacterSheet.#onUseKit,
|
||||||
|
useAbility: AwECharacterSheet.#onUseAbility,
|
||||||
|
dailyReset: AwECharacterSheet.#onDailyReset,
|
||||||
|
longRest: AwECharacterSheet.#onLongRest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +90,15 @@ export default class AwECharacterSheet extends AwEActorSheet {
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
img: item.img,
|
img: item.img,
|
||||||
system: item.system,
|
system: item.system,
|
||||||
costLabel: game.i18n.localize(SYSTEM.ABILITY_COST[item.system.cost]?.label ?? item.system.cost)
|
costLabel: game.i18n.localize(SYSTEM.ABILITY_COST[item.system.cost]?.label ?? item.system.cost),
|
||||||
|
usedToday: item.system.usedToday
|
||||||
|
}))
|
||||||
|
context.hasUsedAbilities = context.abilities.some(a => a.usedToday)
|
||||||
|
context.conditions = Object.values(SYSTEM.CONDITIONS).map(c => ({
|
||||||
|
...c,
|
||||||
|
label: game.i18n.localize(c.label),
|
||||||
|
img: `systems/fvtt-adventures-with-emmy/assets/conditions/${c.id}.svg`,
|
||||||
|
active: doc.statuses.has(c.id)
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
case "biography":
|
case "biography":
|
||||||
@@ -147,7 +161,10 @@ export default class AwECharacterSheet extends AwEActorSheet {
|
|||||||
// field/background/specialization: max 1 (replace existing); archetype: multiple allowed
|
// field/background/specialization: max 1 (replace existing); archetype: multiple allowed
|
||||||
if (item.type === "field" || item.type === "background" || item.type === "specialization") {
|
if (item.type === "field" || item.type === "background" || item.type === "specialization") {
|
||||||
const existing = this.document.itemTypes[item.type]
|
const existing = this.document.itemTypes[item.type]
|
||||||
if (existing.length > 0) await existing[0].delete()
|
if (existing.length > 0) {
|
||||||
|
ui.notifications.info(game.i18n.format("AWEMMY.Character.ItemReplaced", { name: existing[0].name }))
|
||||||
|
await existing[0].delete()
|
||||||
|
}
|
||||||
return this.document.createEmbeddedDocuments("Item", [item.toObject()])
|
return this.document.createEmbeddedDocuments("Item", [item.toObject()])
|
||||||
}
|
}
|
||||||
if (item.type === "archetype") {
|
if (item.type === "archetype") {
|
||||||
@@ -248,8 +265,51 @@ export default class AwECharacterSheet extends AwEActorSheet {
|
|||||||
await this.document.rollWeapon(item)
|
await this.document.rollWeapon(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onRollDamage(event, target) {
|
||||||
|
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||||
|
const item = this.document.items.get(itemId)
|
||||||
|
if (!item) return
|
||||||
|
await this.document.rollDamage(item)
|
||||||
|
}
|
||||||
|
|
||||||
/** Slugify a string for loose name matching (lowercase, trim, spaces→dash, strip non-alphanum). */
|
/** Slugify a string for loose name matching (lowercase, trim, spaces→dash, strip non-alphanum). */
|
||||||
static #slugify(str) {
|
static #slugify(str) {
|
||||||
return (str ?? "").toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")
|
return (str ?? "").toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onToggleCondition(event, target) {
|
||||||
|
const conditionId = target.dataset.conditionId
|
||||||
|
await this.document.toggleStatusEffect(conditionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onUseKit(event, target) {
|
||||||
|
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||||
|
await this.document.useKit(itemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onUseAbility(event, target) {
|
||||||
|
const itemId = target.closest("[data-item-id]")?.dataset.itemId
|
||||||
|
await this.document.useAbility(itemId)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onDailyReset(event, target) {
|
||||||
|
const actor = this.document
|
||||||
|
const dailyAbilities = actor.itemTypes.ability.filter(i => i.system.usedToday)
|
||||||
|
if (!dailyAbilities.length) return
|
||||||
|
const updates = dailyAbilities.map(i => ({ _id: i.id, "system.usedToday": false }))
|
||||||
|
await actor.updateEmbeddedDocuments("Item", updates)
|
||||||
|
ui.notifications.info(game.i18n.localize("AWEMMY.Ability.DailyResetDone"))
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onLongRest(event, target) {
|
||||||
|
const actor = this.document
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: { title: game.i18n.localize("AWEMMY.Rest.LongRest") },
|
||||||
|
content: `<p>${game.i18n.format("AWEMMY.Rest.LongRestConfirm", { name: actor.name })}</p>`,
|
||||||
|
yes: { label: game.i18n.localize("AWEMMY.Rest.Rest"), icon: "fa-solid fa-moon" },
|
||||||
|
no: { label: game.i18n.localize("AWEMMY.Rest.Cancel") }
|
||||||
|
})
|
||||||
|
if (!confirmed) return
|
||||||
|
await actor.longRest()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default class AwECreatureSheet extends AwEActorSheet {
|
|||||||
const context = await super._prepareContext()
|
const context = await super._prepareContext()
|
||||||
context.tabs = this.#getTabs()
|
context.tabs = this.#getTabs()
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
|
||||||
this.document.system.description, { async: true }
|
this.document.system.description ?? "", { async: true }
|
||||||
)
|
)
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import AwERoll from "./roll.mjs"
|
import AwERoll from "./roll.mjs"
|
||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
export default class AwEActor extends Actor {
|
export default class AwEActor extends Actor {
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -48,11 +49,15 @@ export default class AwEActor extends Actor {
|
|||||||
bonus: f.system.knowledgeBonus ?? ""
|
bonus: f.system.knowledgeBonus ?? ""
|
||||||
})).filter(f => f.bonus !== "") ?? []
|
})).filter(f => f.bonus !== "") ?? []
|
||||||
|
|
||||||
return AwERoll.prompt({
|
const { conditionBonus, conditionLabels } = this.#buildConditionOptions()
|
||||||
|
|
||||||
|
const roll = await AwERoll.prompt({
|
||||||
attributeKey: attrId,
|
attributeKey: attrId,
|
||||||
modifier: attribute.mod ?? 0,
|
modifier: attribute.mod ?? 0,
|
||||||
attributeBonus: attribute.bonus ?? 0,
|
attributeBonus: attribute.bonus ?? 0,
|
||||||
knowledgeBonuses,
|
knowledgeBonuses,
|
||||||
|
conditionBonus,
|
||||||
|
conditionLabels,
|
||||||
actorId: this.id,
|
actorId: this.id,
|
||||||
actorName: this.name,
|
actorName: this.name,
|
||||||
actorImage: this.img,
|
actorImage: this.img,
|
||||||
@@ -62,6 +67,28 @@ export default class AwEActor extends Actor {
|
|||||||
damageType: weaponItem.system.damageType,
|
damageType: weaponItem.system.damageType,
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Remove consumed conditions
|
||||||
|
if (roll && this.statuses.has("edge")) await this.toggleStatusEffect("edge")
|
||||||
|
return roll
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roll weapon damage directly (no attack roll).
|
||||||
|
* @param {Item} weaponItem - The weapon item.
|
||||||
|
* @returns {Promise<Roll>}
|
||||||
|
*/
|
||||||
|
async rollDamage(weaponItem) {
|
||||||
|
const formula = weaponItem.system.damageFormula
|
||||||
|
if (!formula) return ui.notifications.warn(game.i18n.localize("AWEMMY.Weapon.NoDamageFormula"))
|
||||||
|
const roll = new Roll(formula)
|
||||||
|
await roll.evaluate()
|
||||||
|
const typeStr = weaponItem.system.damageType ? ` (${weaponItem.system.damageType})` : ""
|
||||||
|
await roll.toMessage({
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor: this }),
|
||||||
|
flavor: `<strong>${weaponItem.name}</strong> — ${game.i18n.localize("AWEMMY.Weapon.DamageRoll")}${typeStr}`
|
||||||
|
})
|
||||||
|
return roll
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,15 +107,144 @@ export default class AwEActor extends Actor {
|
|||||||
bonus: f.system.knowledgeBonus ?? ""
|
bonus: f.system.knowledgeBonus ?? ""
|
||||||
})).filter(f => f.bonus !== "") ?? []
|
})).filter(f => f.bonus !== "") ?? []
|
||||||
|
|
||||||
return AwERoll.prompt({
|
const { conditionBonus, conditionLabels } = this.#buildConditionOptions()
|
||||||
|
|
||||||
|
const roll = await AwERoll.prompt({
|
||||||
attributeKey: attributeId,
|
attributeKey: attributeId,
|
||||||
modifier: attribute.mod ?? 0,
|
modifier: attribute.mod ?? 0,
|
||||||
attributeBonus: attribute.bonus ?? 0,
|
attributeBonus: attribute.bonus ?? 0,
|
||||||
knowledgeBonuses,
|
knowledgeBonuses,
|
||||||
|
conditionBonus,
|
||||||
|
conditionLabels,
|
||||||
actorId: this.id,
|
actorId: this.id,
|
||||||
actorName: this.name,
|
actorName: this.name,
|
||||||
actorImage: this.img,
|
actorImage: this.img,
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Remove consumed conditions
|
||||||
|
if (roll && this.statuses.has("edge")) await this.toggleStatusEffect("edge")
|
||||||
|
return roll
|
||||||
|
}
|
||||||
|
|
||||||
|
#buildConditionOptions() {
|
||||||
|
let conditionBonus = 0
|
||||||
|
const conditionLabels = []
|
||||||
|
if (this.statuses.has("edge")) {
|
||||||
|
conditionBonus += 2
|
||||||
|
conditionLabels.push({ label: game.i18n.localize("AWEMMY.Condition.Edge"), bonus: 2 })
|
||||||
|
}
|
||||||
|
if (this.statuses.has("prone")) {
|
||||||
|
conditionBonus -= 2
|
||||||
|
conditionLabels.push({ label: game.i18n.localize("AWEMMY.Condition.Prone"), bonus: -2 })
|
||||||
|
}
|
||||||
|
if (this.statuses.has("jumbled")) {
|
||||||
|
conditionBonus -= 2
|
||||||
|
conditionLabels.push({ label: game.i18n.localize("AWEMMY.Condition.Jumbled"), bonus: -2 })
|
||||||
|
}
|
||||||
|
return { conditionBonus, conditionLabels }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a kit item: decrement charges and post a chat message.
|
||||||
|
* @param {string} kitId - The kit item ID.
|
||||||
|
*/
|
||||||
|
async useKit(kitId) {
|
||||||
|
const item = this.items.get(kitId)
|
||||||
|
if (!item) return
|
||||||
|
const charges = item.system.charges
|
||||||
|
if (charges.value <= 0) {
|
||||||
|
ui.notifications.warn(game.i18n.format("AWEMMY.Kit.Depleted", { name: item.name }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await item.update({ "system.charges.value": charges.value - 1 })
|
||||||
|
await ChatMessage.create({
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor: this }),
|
||||||
|
content: `<p>${game.i18n.format("AWEMMY.Kit.Used", { name: item.name, value: charges.value - 1, max: charges.max })}</p>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use an ability item: check daily/FP constraints, deduct FP, mark used, post chat card.
|
||||||
|
* @param {string} abilityId - The ability item ID.
|
||||||
|
*/
|
||||||
|
async useAbility(abilityId) {
|
||||||
|
const item = this.items.get(abilityId)
|
||||||
|
if (!item) return
|
||||||
|
const sys = item.system
|
||||||
|
|
||||||
|
if (sys.usedToday) {
|
||||||
|
ui.notifications.warn(game.i18n.format("AWEMMY.Ability.AlreadyUsed", { name: item.name }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sys.flowPointCost > 0) {
|
||||||
|
const fp = this.system.flowPoints.value
|
||||||
|
if (fp < sys.flowPointCost) {
|
||||||
|
ui.notifications.warn(game.i18n.format("AWEMMY.Ability.NotEnoughFP", { name: item.name, cost: sys.flowPointCost, current: fp }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.update({ "system.flowPoints.value": fp - sys.flowPointCost })
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDaily = sys.frequency?.toLowerCase().includes("day")
|
||||||
|
if (isDaily) await item.update({ "system.usedToday": true })
|
||||||
|
|
||||||
|
const abilityTypeLabel = game.i18n.localize(SYSTEM.ABILITY_TYPE[sys.abilityType]?.label ?? sys.abilityType)
|
||||||
|
const costLabel = game.i18n.localize(SYSTEM.ABILITY_COST[sys.cost]?.label ?? sys.cost)
|
||||||
|
const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(sys.description ?? "", { async: true, relativeTo: item })
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-adventures-with-emmy/templates/ability-use.hbs",
|
||||||
|
{
|
||||||
|
name: item.name,
|
||||||
|
img: item.img,
|
||||||
|
costLabel,
|
||||||
|
abilityTypeLabel,
|
||||||
|
traits: sys.traits ?? [],
|
||||||
|
frequency: sys.frequency,
|
||||||
|
trigger: sys.trigger,
|
||||||
|
requirements: sys.requirements,
|
||||||
|
flowPointCost: sys.flowPointCost || 0,
|
||||||
|
description: enrichedDescription
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await ChatMessage.create({ speaker: ChatMessage.getSpeaker({ actor: this }), content })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a long rest: restore HP, reset daily abilities, refill kits, post chat card.
|
||||||
|
* No confirmation dialog — caller is responsible for confirming if needed.
|
||||||
|
*/
|
||||||
|
async longRest() {
|
||||||
|
const sys = this.system
|
||||||
|
const updates = {}
|
||||||
|
const summary = []
|
||||||
|
|
||||||
|
const hpMissing = sys.hp.max - sys.hp.value
|
||||||
|
if (hpMissing > 0) {
|
||||||
|
updates["system.hp.value"] = sys.hp.max
|
||||||
|
summary.push(game.i18n.format("AWEMMY.Rest.HPRestored", { amount: hpMissing, max: sys.hp.max }))
|
||||||
|
}
|
||||||
|
if (Object.keys(updates).length > 0) await this.update(updates)
|
||||||
|
|
||||||
|
const dailyAbilities = this.itemTypes.ability.filter(i => i.system.usedToday)
|
||||||
|
if (dailyAbilities.length) {
|
||||||
|
await this.updateEmbeddedDocuments("Item", dailyAbilities.map(i => ({ _id: i.id, "system.usedToday": false })))
|
||||||
|
summary.push(game.i18n.format("AWEMMY.Rest.AbilitiesReset", { count: dailyAbilities.length }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const depleted = this.itemTypes.kit.filter(i => i.system.charges.value < i.system.charges.max)
|
||||||
|
if (depleted.length) {
|
||||||
|
await this.updateEmbeddedDocuments("Item", depleted.map(i => ({ _id: i.id, "system.charges.value": i.system.charges.max })))
|
||||||
|
summary.push(game.i18n.format("AWEMMY.Rest.KitsReplenished", { count: depleted.length }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const bulletList = summary.map(s => `<li>${s}</li>`).join("")
|
||||||
|
const content = `
|
||||||
|
<div class="awemmy-rest-message">
|
||||||
|
<h3><i class="fa-solid fa-moon"></i> ${game.i18n.format("AWEMMY.Rest.LongRestTitle", { name: this.name })}</h3>
|
||||||
|
${summary.length ? `<ul>${bulletList}</ul>` : `<p>${game.i18n.localize("AWEMMY.Rest.AlreadyRested")}</p>`}
|
||||||
|
</div>`
|
||||||
|
await ChatMessage.create({ speaker: ChatMessage.getSpeaker({ actor: this }), content })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export default class AwERoll extends Roll {
|
|||||||
get damageType() { return this.options.damageType }
|
get damageType() { return this.options.damageType }
|
||||||
get damageResult() { return this.options.damageResult }
|
get damageResult() { return this.options.damageResult }
|
||||||
get damageCritical() { return this.options.damageCritical ?? false }
|
get damageCritical() { return this.options.damageCritical ?? false }
|
||||||
|
get conditionBonus() { return this.options.conditionBonus ?? 0 }
|
||||||
|
get conditionLabels() { return this.options.conditionLabels ?? [] }
|
||||||
|
|
||||||
// --- Outcome calculation ---
|
// --- Outcome calculation ---
|
||||||
|
|
||||||
@@ -110,7 +112,7 @@ export default class AwERoll extends Roll {
|
|||||||
classes: ["awemmy"],
|
classes: ["awemmy"],
|
||||||
content,
|
content,
|
||||||
render: (event, dialog) => {
|
render: (event, dialog) => {
|
||||||
const baseMod = mod + attrBonus
|
const baseMod = mod + attrBonus + (options.conditionBonus ?? 0)
|
||||||
const el = dialog.element
|
const el = dialog.element
|
||||||
const bonusSelect = el.querySelector('#awe-bonus')
|
const bonusSelect = el.querySelector('#awe-bonus')
|
||||||
const knowledgeSel = el.querySelector('#awe-knowledge')
|
const knowledgeSel = el.querySelector('#awe-knowledge')
|
||||||
@@ -145,8 +147,8 @@ export default class AwERoll extends Roll {
|
|||||||
const dc = result.dc !== "" ? parseInt(result.dc) : undefined
|
const dc = result.dc !== "" ? parseInt(result.dc) : undefined
|
||||||
const rollMode = result.visibility ?? game.settings.get("core", "rollMode")
|
const rollMode = result.visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
|
||||||
// Formula: 1d20 + (mod + attrBonus) [± bonus] [± knowledgeBonus]
|
// Formula: 1d20 + (mod + attrBonus) [± bonus] [± knowledgeBonus] [± conditionBonus]
|
||||||
const totalMod = mod + attrBonus + bonus + knowledgeBonus
|
const totalMod = mod + attrBonus + bonus + knowledgeBonus + (options.conditionBonus ?? 0)
|
||||||
let formula = `1d20`
|
let formula = `1d20`
|
||||||
if (totalMod > 0) formula += ` + ${totalMod}`
|
if (totalMod > 0) formula += ` + ${totalMod}`
|
||||||
else if (totalMod < 0) formula += ` - ${Math.abs(totalMod)}`
|
else if (totalMod < 0) formula += ` - ${Math.abs(totalMod)}`
|
||||||
@@ -158,12 +160,16 @@ export default class AwERoll extends Roll {
|
|||||||
attributeBonus: attrBonus,
|
attributeBonus: attrBonus,
|
||||||
bonus,
|
bonus,
|
||||||
knowledgeBonus,
|
knowledgeBonus,
|
||||||
|
conditionBonus: options.conditionBonus ?? 0,
|
||||||
|
conditionLabels: options.conditionLabels ?? [],
|
||||||
dc,
|
dc,
|
||||||
actorId: options.actorId,
|
actorId: options.actorId,
|
||||||
actorName: options.actorName,
|
actorName: options.actorName,
|
||||||
actorImage: options.actorImage,
|
actorImage: options.actorImage,
|
||||||
sourceItemName: options.sourceItemName,
|
sourceItemName: options.sourceItemName,
|
||||||
sourceItemImg: options.sourceItemImg
|
sourceItemImg: options.sourceItemImg,
|
||||||
|
damageFormula: options.damageFormula,
|
||||||
|
damageType: options.damageType
|
||||||
})
|
})
|
||||||
|
|
||||||
await roll.evaluate()
|
await roll.evaluate()
|
||||||
@@ -211,13 +217,17 @@ export default class AwERoll extends Roll {
|
|||||||
modifier: this.modifier,
|
modifier: this.modifier,
|
||||||
bonus: this.bonus,
|
bonus: this.bonus,
|
||||||
knowledgeBonus: this.knowledgeBonus,
|
knowledgeBonus: this.knowledgeBonus,
|
||||||
|
conditionBonus: isPrivate ? null : this.conditionBonus,
|
||||||
|
conditionLabels: this.conditionLabels,
|
||||||
dice: this.dice,
|
dice: this.dice,
|
||||||
outcome: isPrivate ? null : this.outcome,
|
outcome: isPrivate ? null : this.outcome,
|
||||||
dc: this.dc,
|
dc: this.dc,
|
||||||
|
actorId: this.actorId,
|
||||||
actorName: this.actorName,
|
actorName: this.actorName,
|
||||||
actorImage: this.actorImage,
|
actorImage: this.actorImage,
|
||||||
sourceItemName: this.sourceItemName,
|
sourceItemName: this.sourceItemName,
|
||||||
sourceItemImg: this.sourceItemImg,
|
sourceItemImg: this.sourceItemImg,
|
||||||
|
damageFormula: this.damageFormula,
|
||||||
damageResult: isPrivate ? null : this.damageResult,
|
damageResult: isPrivate ? null : this.damageResult,
|
||||||
damageCritical: this.damageCritical,
|
damageCritical: this.damageCritical,
|
||||||
damageType: this.damageType,
|
damageType: this.damageType,
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export default class AwEAbility extends foundry.abstract.TypeDataModel {
|
|||||||
schema.requirements = new fields.StringField({ initial: "", required: false, nullable: true })
|
schema.requirements = new fields.StringField({ initial: "", required: false, nullable: true })
|
||||||
schema.trigger = new fields.StringField({ initial: "", required: false, nullable: true })
|
schema.trigger = new fields.StringField({ initial: "", required: false, nullable: true })
|
||||||
schema.traits = new fields.ArrayField(new fields.StringField())
|
schema.traits = new fields.ArrayField(new fields.StringField())
|
||||||
|
schema.flowPointCost = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0, integer: true })
|
||||||
|
schema.usedToday = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export default class AwECreature extends foundry.abstract.TypeDataModel {
|
|||||||
schema.eurekaEvidence = new fields.StringField({ initial: "", required: false, nullable: true })
|
schema.eurekaEvidence = new fields.StringField({ initial: "", required: false, nullable: true })
|
||||||
schema.eurekaThreshold1 = new fields.StringField({ initial: "", required: false, nullable: true })
|
schema.eurekaThreshold1 = new fields.StringField({ initial: "", required: false, nullable: true })
|
||||||
schema.eurekaThreshold2 = new fields.StringField({ initial: "", required: false, nullable: true })
|
schema.eurekaThreshold2 = new fields.StringField({ initial: "", required: false, nullable: true })
|
||||||
|
schema.eurekaThreshold3 = new fields.StringField({ initial: "", required: false, nullable: true })
|
||||||
schema.eurekaHints = new fields.StringField({ initial: "", required: false, nullable: true })
|
schema.eurekaHints = new fields.StringField({ initial: "", required: false, nullable: true })
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<div class="ability-use-message">
|
||||||
|
<div class="ability-use-header">
|
||||||
|
<img class="ability-use-img" src="{{img}}" alt="{{name}}" />
|
||||||
|
<div class="ability-use-title">
|
||||||
|
<span class="ability-use-name">{{name}}</span>
|
||||||
|
<span class="ability-use-cost">{{costLabel}}</span>
|
||||||
|
</div>
|
||||||
|
<span class="ability-use-type">{{abilityTypeLabel}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if traits.length}}
|
||||||
|
<div class="ability-use-traits">
|
||||||
|
{{#each traits}}<span class="ability-trait-tag">{{this}}</span>{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="ability-use-meta">
|
||||||
|
{{#if frequency}}
|
||||||
|
<div class="ability-meta-row">
|
||||||
|
<span class="ability-meta-label">{{localize "AWEMMY.Ability.Frequency"}}</span>
|
||||||
|
<span class="ability-meta-value">{{frequency}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if trigger}}
|
||||||
|
<div class="ability-meta-row">
|
||||||
|
<span class="ability-meta-label">{{localize "AWEMMY.Ability.Trigger"}}</span>
|
||||||
|
<span class="ability-meta-value">{{trigger}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if requirements}}
|
||||||
|
<div class="ability-meta-row">
|
||||||
|
<span class="ability-meta-label">{{localize "AWEMMY.Ability.Requirements"}}</span>
|
||||||
|
<span class="ability-meta-value">{{requirements}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if flowPointCost}}
|
||||||
|
<div class="ability-meta-row">
|
||||||
|
<span class="ability-meta-label">{{localize "AWEMMY.Ability.FlowPointCost"}}</span>
|
||||||
|
<span class="ability-meta-value ability-fp-cost">−{{flowPointCost}} FP</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if description}}
|
||||||
|
<div class="ability-use-description">{{{description}}}</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
@@ -14,6 +14,10 @@
|
|||||||
<label>{{localize "AWEMMY.Ability.CostLabel"}}</label>
|
<label>{{localize "AWEMMY.Ability.CostLabel"}}</label>
|
||||||
{{formField systemFields.cost value=system.cost localize=true}}
|
{{formField systemFields.cost value=system.cost localize=true}}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "AWEMMY.Ability.FlowPointCost"}}</label>
|
||||||
|
{{formInput systemFields.flowPointCost value=system.flowPointCost}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</span>
|
</span>
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Edit"><i class="fas fa-edit"></i></a>
|
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.EditItem'}}"><i class="fas fa-edit"></i></a>
|
||||||
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Delete"><i class="fas fa-trash"></i></a>
|
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.DeleteItem'}}"><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@@ -57,8 +57,8 @@
|
|||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Edit"><i class="fas fa-edit"></i></a>
|
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.EditItem'}}"><i class="fas fa-edit"></i></a>
|
||||||
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Delete"><i class="fas fa-trash"></i></a>
|
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.DeleteItem'}}"><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@@ -73,8 +73,8 @@
|
|||||||
<img src="{{img}}" class="item-img" alt="{{name}}" />
|
<img src="{{img}}" class="item-img" alt="{{name}}" />
|
||||||
<span class="item-name">{{name}}</span>
|
<span class="item-name">{{name}}</span>
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Edit"><i class="fas fa-edit"></i></a>
|
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.EditItem'}}"><i class="fas fa-edit"></i></a>
|
||||||
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Delete"><i class="fas fa-trash"></i></a>
|
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.DeleteItem'}}"><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@@ -89,8 +89,8 @@
|
|||||||
<img src="{{img}}" class="item-img" alt="{{name}}" />
|
<img src="{{img}}" class="item-img" alt="{{name}}" />
|
||||||
<span class="item-name">{{name}}</span>
|
<span class="item-name">{{name}}</span>
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Edit"><i class="fas fa-edit"></i></a>
|
<a class="item-control" data-action="edit" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.EditItem'}}"><i class="fas fa-edit"></i></a>
|
||||||
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="Delete"><i class="fas fa-trash"></i></a>
|
<a class="item-control" data-action="delete" data-item-id="{{id}}" data-item-uuid="{{uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.DeleteItem'}}"><i class="fas fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<div class="item-name">{{item.name}}</div>
|
<div class="item-name">{{item.name}}</div>
|
||||||
<div class="item-charges">{{item.system.charges.value}}/{{item.system.charges.max}}</div>
|
<div class="item-charges">{{item.system.charges.value}}/{{item.system.charges.max}}</div>
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
|
<a data-action="useKit" data-item-id="{{item.id}}" data-tooltip="{{localize "AWEMMY.Kit.Use"}}"><i class="fas fa-hand-sparkles"></i></a>
|
||||||
<a data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
<a data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||||
{{#if ../isEditMode}}
|
{{#if ../isEditMode}}
|
||||||
<a data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
<a data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
<div class="item-range">{{localize "AWEMMY.Weapon.Range"}}: {{item.system.range}}</div>
|
<div class="item-range">{{localize "AWEMMY.Weapon.Range"}}: {{item.system.range}}</div>
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a data-action="rollWeapon" data-item-id="{{item.id}}" data-tooltip="{{localize "AWEMMY.Weapon.AttackRoll"}}"><i class="fas fa-dice-d20"></i></a>
|
<a data-action="rollWeapon" data-item-id="{{item.id}}" data-tooltip="{{localize "AWEMMY.Weapon.AttackRoll"}}"><i class="fas fa-dice-d20"></i></a>
|
||||||
|
<a data-action="rollDamage" data-item-id="{{item.id}}" data-tooltip="{{localize "AWEMMY.Weapon.DamageRoll"}}"><i class="fas fa-burst"></i></a>
|
||||||
<a data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
<a data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-edit"></i></a>
|
||||||
{{#if ../isEditMode}}
|
{{#if ../isEditMode}}
|
||||||
<a data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
<a data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fas fa-trash"></i></a>
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sheet-controls">
|
<div class="sheet-controls">
|
||||||
|
<button type="button" data-action="longRest" data-tooltip="{{localize 'AWEMMY.Rest.LongRest'}}">
|
||||||
|
<i class="fa-solid fa-moon"></i>
|
||||||
|
</button>
|
||||||
<button type="button" data-action="toggleSheet" data-tooltip="{{#if isPlayMode}}{{localize 'AWEMMY.Sheet.EditMode'}}{{else}}{{localize 'AWEMMY.Sheet.PlayMode'}}{{/if}}">
|
<button type="button" data-action="toggleSheet" data-tooltip="{{#if isPlayMode}}{{localize 'AWEMMY.Sheet.EditMode'}}{{else}}{{localize 'AWEMMY.Sheet.PlayMode'}}{{/if}}">
|
||||||
{{#if isPlayMode}}<i class="fa-solid fa-lock"></i>{{else}}<i class="fa-solid fa-unlock"></i>{{/if}}
|
{{#if isPlayMode}}<i class="fa-solid fa-lock"></i>{{else}}<i class="fa-solid fa-unlock"></i>{{/if}}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -51,14 +51,20 @@
|
|||||||
<legend>{{localize "AWEMMY.Item.Ability"}}</legend>
|
<legend>{{localize "AWEMMY.Item.Ability"}}</legend>
|
||||||
<div class="item-list">
|
<div class="item-list">
|
||||||
{{#each abilities as |item|}}
|
{{#each abilities as |item|}}
|
||||||
<div class="item-row" data-drag="true" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
|
<div class="item-row {{#if item.usedToday}}ability-used{{/if}}" data-drag="true" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
|
||||||
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
|
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
|
||||||
<div class="item-name">{{item.name}}</div>
|
<div class="item-name">{{item.name}}</div>
|
||||||
<div class="item-cost">{{item.costLabel}}</div>
|
<div class="item-cost">{{item.costLabel}}</div>
|
||||||
|
{{#if item.system.frequency}}<div class="item-frequency">{{item.system.frequency}}</div>{{/if}}
|
||||||
<div class="item-controls">
|
<div class="item-controls">
|
||||||
<a data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-tooltip="Edit"><i class="fas fa-edit"></i></a>
|
<a data-action="useAbility" data-item-id="{{item.id}}"
|
||||||
|
class="{{#if item.usedToday}}ability-used-btn{{/if}}"
|
||||||
|
data-tooltip="{{#if item.usedToday}}{{localize "AWEMMY.Ability.AlreadyUsed"}}{{else}}{{localize "AWEMMY.Ability.Use"}}{{/if}}">
|
||||||
|
<i class="fas fa-bolt"></i>
|
||||||
|
</a>
|
||||||
|
<a data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.EditItem'}}"><i class="fas fa-edit"></i></a>
|
||||||
{{#if ../isEditMode}}
|
{{#if ../isEditMode}}
|
||||||
<a data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-tooltip="Delete"><i class="fas fa-trash"></i></a>
|
<a data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-tooltip="{{localize 'AWEMMY.Sheet.DeleteItem'}}"><i class="fas fa-trash"></i></a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,6 +74,26 @@
|
|||||||
<button type="button" data-action="createAbility"><i class="fas fa-plus"></i> {{localize "AWEMMY.Item.Ability"}}</button>
|
<button type="button" data-action="createAbility"><i class="fas fa-plus"></i> {{localize "AWEMMY.Item.Ability"}}</button>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if hasUsedAbilities}}
|
||||||
|
<div class="daily-reset">
|
||||||
|
<button type="button" data-action="dailyReset" data-tooltip="{{localize 'AWEMMY.Ability.DailyResetHint'}}">
|
||||||
|
<i class="fas fa-sun"></i> {{localize "AWEMMY.Ability.DailyReset"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
{{!-- Conditions --}}
|
||||||
|
<fieldset class="conditions-fieldset">
|
||||||
|
<legend>{{localize "AWEMMY.Condition.Panel"}}</legend>
|
||||||
|
<div class="conditions-panel">
|
||||||
|
{{#each conditions as |cond|}}
|
||||||
|
<a class="condition-badge {{#if cond.active}}active{{/if}}" data-action="toggleCondition" data-condition-id="{{cond.id}}" data-tooltip="{{cond.label}}">
|
||||||
|
<img src="{{cond.img}}" alt="{{cond.label}}" />
|
||||||
|
<span class="condition-label">{{cond.label}}</span>
|
||||||
|
</a>
|
||||||
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,11 @@
|
|||||||
{{#if (gt knowledgeBonus 0)}}+ {{knowledgeBonus}}{{else if (lt knowledgeBonus 0)}}− {{abs knowledgeBonus}}{{/if}}
|
{{#if (gt knowledgeBonus 0)}}+ {{knowledgeBonus}}{{else if (lt knowledgeBonus 0)}}− {{abs knowledgeBonus}}{{/if}}
|
||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if conditionBonus}}
|
||||||
|
<span class="roll-mod condition">
|
||||||
|
{{#if (gt conditionBonus 0)}}+ {{conditionBonus}}{{else if (lt conditionBonus 0)}}− {{abs conditionBonus}}{{/if}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
<span class="roll-equals">=</span>
|
<span class="roll-equals">=</span>
|
||||||
<span class="roll-total">{{total}}</span>
|
<span class="roll-total">{{total}}</span>
|
||||||
{{#if dc}}<span class="roll-dc">/ DC {{dc}}</span>{{/if}}
|
{{#if dc}}<span class="roll-dc">/ DC {{dc}}</span>{{/if}}
|
||||||
@@ -80,6 +85,22 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{!-- Damage roll button (shown on weapon attacks where damage wasn't auto-rolled) --}}
|
||||||
|
{{#if damageFormula}}
|
||||||
|
{{#unless damageResult}}
|
||||||
|
<div class="chat-damage-action">
|
||||||
|
<button type="button" class="roll-damage-btn"
|
||||||
|
data-actor-id="{{actorId}}"
|
||||||
|
data-damage-formula="{{damageFormula}}"
|
||||||
|
data-damage-type="{{damageType}}"
|
||||||
|
data-item-name="{{sourceItemName}}"
|
||||||
|
data-item-img="{{sourceItemImg}}">
|
||||||
|
<i class="fas fa-burst"></i> {{localize "AWEMMY.Weapon.DamageRoll"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="private-result">
|
<div class="private-result">
|
||||||
<i class="fa-solid fa-eye-slash"></i> {{localize "AWEMMY.Roll.Private"}}
|
<i class="fa-solid fa-eye-slash"></i> {{localize "AWEMMY.Roll.Private"}}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
<label>{{localize "AWEMMY.Creature.Threshold2"}}</label>
|
<label>{{localize "AWEMMY.Creature.Threshold2"}}</label>
|
||||||
<textarea name="system.eurekaThreshold2" rows="3" placeholder="{{localize 'AWEMMY.Creature.Threshold2'}}…">{{system.eurekaThreshold2}}</textarea>
|
<textarea name="system.eurekaThreshold2" rows="3" placeholder="{{localize 'AWEMMY.Creature.Threshold2'}}…">{{system.eurekaThreshold2}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="eureka-field">
|
||||||
|
<label>{{localize "AWEMMY.Creature.Threshold3"}}</label>
|
||||||
|
<textarea name="system.eurekaThreshold3" rows="3" placeholder="{{localize 'AWEMMY.Creature.Threshold3'}}…">{{system.eurekaThreshold3}}</textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="eureka-field">
|
<div class="eureka-field">
|
||||||
|
|||||||