Various fixes/update based on first tests feedback

This commit is contained in:
2026-04-10 14:47:21 +02:00
parent 63da2ef664
commit 999b78c6fc
21 changed files with 151 additions and 75 deletions
+27
View File
@@ -1,3 +1,30 @@
# Documentation source (game rules, design docs)
_docs/
# Dependencies
node_modules/
# Build output
css/
packs/
dist/
# OS
.DS_Store
Thumbs.db
# Editor
.vscode/
.idea/
*.swp
*.swo
# Logs
*.log
npm-debug.log*
# Environment
.env
.env.local
.github/
+11 -5
View File
@@ -30,15 +30,19 @@
"AWEMMY.Condition.Vulnerable": "Vulnerable",
"AWEMMY.Condition.Panel": "Conditions",
"AWEMMY.Roll.ConditionBonus": "Condition",
"AWEMMY.Item.Description": "Description",
"AWEMMY.Error.TraitPasteFailed": "Failed to update traits — please try again.",
"AWEMMY.Kit.Use": "Use Kit",
"AWEMMY.Kit.Used": "{name} used (charges: {value}/{max})",
"AWEMMY.Kit.Depleted": "{name} has no charges remaining!",
"AWEMMY.Kit.Used": "{name} used",
"AWEMMY.Ability.Cost.Free": "Free",
"AWEMMY.Ability.Cost.Variable": "Variable (Δ)",
"AWEMMY.Ability.TypeLabel": "Type",
"AWEMMY.Ability.Type.Field": "Field",
"AWEMMY.Ability.Type.Archetype": "Archetype",
"AWEMMY.Ability.Type.General": "General",
"AWEMMY.Ability.Type.Beginner": "Beginner",
"AWEMMY.Ability.Type.Advanced": "Advanced",
"AWEMMY.Ability.Type.Pinnacle": "Pinnacle",
"AWEMMY.Archetype.PrerequisiteLevel": "Prerequisite Level",
"AWEMMY.Item.Ability": "Ability",
"AWEMMY.Item.Field": "Field",
@@ -105,8 +109,12 @@
"AWEMMY.Ability.Frequency": "Frequency",
"AWEMMY.Ability.Requirements": "Requirements",
"AWEMMY.Ability.Trigger": "Trigger",
"AWEMMY.Ability.Range": "Range",
"AWEMMY.Ability.Targets": "Targets",
"AWEMMY.Ability.Duration": "Duration",
"AWEMMY.Ability.Traits": "Traits",
"AWEMMY.Ability.AddTrait": "Add trait...",
"AWEMMY.Ability.IsDaily": "Daily (1/day)",
"AWEMMY.Ability.FlowPointCost": "Flow Point Cost",
"AWEMMY.Ability.Use": "Use Ability",
"AWEMMY.Ability.AlreadyUsed": "{name} has already been used today!",
@@ -128,9 +136,8 @@
"AWEMMY.Field.Specializations": "Specializations",
"AWEMMY.Field.AddSpecialization": "Add specialization...",
"AWEMMY.Field.KnowledgeBonus": "Knowledge Bonus",
"AWEMMY.Background.Bonus": "Background Bonus",
"AWEMMY.Background.AttributeBoosts": "Attribute Boosts",
"AWEMMY.Kit.Field": "Field",
"AWEMMY.Kit.Charges": "Charges",
"AWEMMY.Equipment.Quantity": "Quantity",
"AWEMMY.Equipment.Weight": "Weight",
"AWEMMY.Sheet.EditItem": "Edit",
@@ -143,7 +150,6 @@
"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",
@@ -167,12 +167,9 @@ Hooks.once('tokenActionHudCoreApiReady', async (coreModule) => {
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)
})
}
+1 -1
View File
@@ -4,7 +4,7 @@ export default class AwEAbilitySheet extends AwEItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ability"],
position: { width: 620 },
position: { width: 620, height: 560 },
window: { contentClasses: ["ability-content"] }
}
+17 -1
View File
@@ -21,7 +21,7 @@ export default class AwEItemSheet extends HandlebarsApplicationMixin(foundry.app
classes: ["awemmy", "item"],
position: {
width: 600,
height: "auto"
height: 480
},
form: {
submitOnChange: true
@@ -91,6 +91,22 @@ export default class AwEItemSheet extends HandlebarsApplicationMixin(foundry.app
const handler = this.options.actions?.[actionName]
if (handler) handler.call(this, event, input)
})
// Auto-split comma-separated values on paste
input.addEventListener("paste", async event => {
const pasted = (event.clipboardData ?? window.clipboardData).getData("text")
const parts = pasted.split(",").map(s => s.trim().toLowerCase()).filter(Boolean)
if (parts.length < 2) return // single value: let default paste handle it
event.preventDefault()
const fieldName = input.dataset.field ?? "system.traits"
const current = foundry.utils.getProperty(this.document, fieldName) ?? []
const merged = [...new Set([...current, ...parts])]
try {
await this.document.update({ [fieldName]: merged })
} catch (err) {
ui.notifications.error(game.i18n.localize("AWEMMY.Error.TraitPasteFailed"))
console.error("AwE | trait paste update failed:", err)
}
})
})
}
@@ -293,12 +293,8 @@ export default class AwECharacterSheet extends AwEActorSheet {
}
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"))
const count = await this.document.resetDailyAbilities()
if (count > 0) ui.notifications.info(game.i18n.localize("AWEMMY.Ability.DailyResetDone"))
}
static async #onLongRest(event, target) {
+5 -2
View File
@@ -3,8 +3,8 @@ export const DEV_MODE = false
export const ATTRIBUTES = {
agility: { id: "agility", abbrev: "AGI", label: "AWEMMY.Attribute.Agility" },
fitness: { id: "fitness", abbrev: "FIT", label: "AWEMMY.Attribute.Fitness" },
awareness: { id: "awareness", abbrev: "AWA", label: "AWEMMY.Attribute.Awareness" },
fitness: { id: "fitness", abbrev: "FIT", label: "AWEMMY.Attribute.Fitness" },
influence: { id: "influence", abbrev: "INF", label: "AWEMMY.Attribute.Influence" }
}
@@ -26,6 +26,7 @@ export const ABILITY_COST = {
"three": { id: "three", label: "ΔΔΔ" },
"reaction": { id: "reaction", label: "↩" },
"free": { id: "free", label: "AWEMMY.Ability.Cost.Free" },
"variable": { id: "variable", label: "AWEMMY.Ability.Cost.Variable" },
"none": { id: "none", label: "—" }
}
@@ -33,7 +34,9 @@ export const ABILITY_TYPE = {
"field": { id: "field", label: "AWEMMY.Ability.Type.Field" },
"archetype": { id: "archetype", label: "AWEMMY.Ability.Type.Archetype" },
"general": { id: "general", label: "AWEMMY.Ability.Type.General" },
"beginner": { id: "beginner", label: "AWEMMY.Ability.Type.Beginner" }
"beginner": { id: "beginner", label: "AWEMMY.Ability.Type.Beginner" },
"advanced": { id: "advanced", label: "AWEMMY.Ability.Type.Advanced" },
"pinnacle": { id: "pinnacle", label: "AWEMMY.Ability.Type.Pinnacle" }
}
export const OUTCOME_LABELS = {
+19 -21
View File
@@ -146,21 +146,15 @@ export default class AwEActor extends Actor {
}
/**
* Use a kit item: decrement charges and post a chat message.
* Use a kit item: 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>`
content: `<p>${game.i18n.format("AWEMMY.Kit.Used", { name: item.name })}</p>`
})
}
@@ -173,7 +167,8 @@ export default class AwEActor extends Actor {
if (!item) return
const sys = item.system
if (sys.usedToday) {
const isDaily = sys.isDaily
if (isDaily && sys.usedToday) {
ui.notifications.warn(game.i18n.format("AWEMMY.Ability.AlreadyUsed", { name: item.name }))
return
}
@@ -187,7 +182,6 @@ export default class AwEActor extends Actor {
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)
@@ -212,7 +206,18 @@ export default class AwEActor extends Actor {
}
/**
* Perform a long rest: restore HP, reset daily abilities, refill kits, post chat card.
* Reset all once-per-day abilities (usedToday → false).
* @returns {Promise<void>}
*/
async resetDailyAbilities() {
const dailyAbilities = this.itemTypes.ability.filter(i => i.system.usedToday)
if (!dailyAbilities.length) return 0
await this.updateEmbeddedDocuments("Item", dailyAbilities.map(i => ({ _id: i.id, "system.usedToday": false })))
return dailyAbilities.length
}
/**
* Perform a long rest: restore HP, reset daily abilities, post chat card.
* No confirmation dialog — caller is responsible for confirming if needed.
*/
async longRest() {
@@ -227,16 +232,9 @@ export default class AwEActor extends Actor {
}
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 resetCount = await this.resetDailyAbilities()
if (resetCount > 0) {
summary.push(game.i18n.format("AWEMMY.Rest.AbilitiesReset", { count: resetCount }))
}
const bulletList = summary.map(s => `<li>${s}</li>`).join("")
+4
View File
@@ -21,7 +21,11 @@ export default class AwEAbility extends foundry.abstract.TypeDataModel {
schema.frequency = 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.range = new fields.StringField({ initial: "", required: false, nullable: true })
schema.targets = new fields.StringField({ initial: "", required: false, nullable: true })
schema.duration = new fields.StringField({ initial: "", required: false, nullable: true })
schema.traits = new fields.ArrayField(new fields.StringField())
schema.isDaily = new fields.BooleanField({ required: true, initial: false })
schema.flowPointCost = new fields.NumberField({ required: true, nullable: false, initial: 0, min: 0, integer: true })
schema.usedToday = new fields.BooleanField({ required: true, initial: false })
+4 -1
View File
@@ -4,7 +4,10 @@ export default class AwEBackground extends foundry.abstract.TypeDataModel {
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.bonus = new fields.StringField({ initial: "", required: false, nullable: true })
schema.boostAgility = new fields.BooleanField({ initial: false })
schema.boostAwareness = new fields.BooleanField({ initial: false })
schema.boostFitness = new fields.BooleanField({ initial: false })
schema.boostInfluence = new fields.BooleanField({ initial: false })
return schema
}
-5
View File
@@ -1,15 +1,10 @@
export default class AwEKit extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.fieldName = new fields.StringField({ initial: "", required: false, nullable: true })
schema.charges = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
return schema
}
+27 -5
View File
@@ -14,10 +14,6 @@
<label>{{localize "AWEMMY.Ability.CostLabel"}}</label>
{{formField systemFields.cost value=system.cost localize=true}}
</div>
<div class="form-group">
<label>{{localize "AWEMMY.Ability.FlowPointCost"}}</label>
{{formInput systemFields.flowPointCost value=system.flowPointCost}}
</div>
</div>
<div class="form-group">
@@ -35,6 +31,32 @@
{{formInput systemFields.trigger value=system.trigger}}
</div>
<div class="form-row">
<div class="form-group">
<label>{{localize "AWEMMY.Ability.Range"}}</label>
{{formInput systemFields.range value=system.range}}
</div>
<div class="form-group">
<label>{{localize "AWEMMY.Ability.Targets"}}</label>
{{formInput systemFields.targets value=system.targets}}
</div>
<div class="form-group">
<label>{{localize "AWEMMY.Ability.Duration"}}</label>
{{formInput systemFields.duration value=system.duration}}
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>{{localize "AWEMMY.Ability.IsDaily"}}</label>
{{formInput systemFields.isDaily value=system.isDaily}}
</div>
<div class="form-group">
<label>{{localize "AWEMMY.Ability.FlowPointCost"}}</label>
{{formInput systemFields.flowPointCost value=system.flowPointCost}}
</div>
</div>
<div class="form-group-tags">
<label>{{localize "AWEMMY.Ability.Traits"}}</label>
<div class="tags-list">
@@ -51,7 +73,7 @@
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
+1 -1
View File
@@ -11,7 +11,7 @@
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
+21 -4
View File
@@ -5,13 +5,30 @@
</div>
<div class="item-body">
<div class="form-group">
<label>{{localize "AWEMMY.Background.Bonus"}}</label>
{{formInput systemFields.bonus value=system.bonus}}
<div class="form-group-boosts">
<label>{{localize "AWEMMY.Background.AttributeBoosts"}}</label>
<div class="boost-checks">
<label class="boost-check">
{{formInput systemFields.boostAgility value=system.boostAgility}}
<span>{{localize "AWEMMY.Attribute.AGI"}}</span>
</label>
<label class="boost-check">
{{formInput systemFields.boostAwareness value=system.boostAwareness}}
<span>{{localize "AWEMMY.Attribute.AWA"}}</span>
</label>
<label class="boost-check">
{{formInput systemFields.boostFitness value=system.boostFitness}}
<span>{{localize "AWEMMY.Attribute.FIT"}}</span>
</label>
<label class="boost-check">
{{formInput systemFields.boostInfluence value=system.boostInfluence}}
<span>{{localize "AWEMMY.Attribute.INF"}}</span>
</label>
</div>
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
-1
View File
@@ -8,7 +8,6 @@
<div class="item-row" data-drag="true" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
<img class="item-img" src="{{item.img}}" data-tooltip="{{item.name}}" />
<div class="item-name">{{item.name}}</div>
<div class="item-charges">{{item.system.charges.value}}/{{item.system.charges.max}}</div>
<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>
+7 -7
View File
@@ -21,13 +21,6 @@
<td class="attr-dc">{{system.attributes.agility.dc}}</td>
<td>{{formInput systemFields.attributes.fields.agility.fields.bonus value=system.attributes.agility.bonus disabled=isPlayMode}}</td>
</tr>
<tr>
<td class="attr-label rollable" data-attribute-id="fitness">{{localize "AWEMMY.Attribute.Fitness"}} <i class="fa-solid fa-dice-d20"></i></td>
<td>{{formInput systemFields.attributes.fields.fitness.fields.boostLevel value=system.attributes.fitness.boostLevel disabled=isPlayMode}}</td>
<td class="attr-mod" data-tooltip="{{system.attributes.fitness.modBreakdown}}">{{system.attributes.fitness.mod}}</td>
<td class="attr-dc">{{system.attributes.fitness.dc}}</td>
<td>{{formInput systemFields.attributes.fields.fitness.fields.bonus value=system.attributes.fitness.bonus disabled=isPlayMode}}</td>
</tr>
<tr>
<td class="attr-label rollable" data-attribute-id="awareness">{{localize "AWEMMY.Attribute.Awareness"}} <i class="fa-solid fa-dice-d20"></i></td>
<td>{{formInput systemFields.attributes.fields.awareness.fields.boostLevel value=system.attributes.awareness.boostLevel disabled=isPlayMode}}</td>
@@ -35,6 +28,13 @@
<td class="attr-dc">{{system.attributes.awareness.dc}}</td>
<td>{{formInput systemFields.attributes.fields.awareness.fields.bonus value=system.attributes.awareness.bonus disabled=isPlayMode}}</td>
</tr>
<tr>
<td class="attr-label rollable" data-attribute-id="fitness">{{localize "AWEMMY.Attribute.Fitness"}} <i class="fa-solid fa-dice-d20"></i></td>
<td>{{formInput systemFields.attributes.fields.fitness.fields.boostLevel value=system.attributes.fitness.boostLevel disabled=isPlayMode}}</td>
<td class="attr-mod" data-tooltip="{{system.attributes.fitness.modBreakdown}}">{{system.attributes.fitness.mod}}</td>
<td class="attr-dc">{{system.attributes.fitness.dc}}</td>
<td>{{formInput systemFields.attributes.fields.fitness.fields.bonus value=system.attributes.fitness.bonus disabled=isPlayMode}}</td>
</tr>
<tr>
<td class="attr-label rollable" data-attribute-id="influence">{{localize "AWEMMY.Attribute.Influence"}} <i class="fa-solid fa-dice-d20"></i></td>
<td>{{formInput systemFields.attributes.fields.influence.fields.boostLevel value=system.attributes.influence.boostLevel disabled=isPlayMode}}</td>
+1 -1
View File
@@ -17,7 +17,7 @@
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
+1 -1
View File
@@ -22,7 +22,7 @@
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
+1 -8
View File
@@ -10,15 +10,8 @@
{{formInput systemFields.fieldName value=system.fieldName}}
</div>
<div class="charges-group">
<label>{{localize "AWEMMY.Kit.Charges"}}</label>
{{formInput systemFields.charges.fields.value value=system.charges.value}}
<span class="separator">/</span>
{{formInput systemFields.charges.fields.max value=system.charges.max}}
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
+1 -1
View File
@@ -32,7 +32,7 @@
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
+1 -1
View File
@@ -42,7 +42,7 @@
</div>
<fieldset>
<legend>Description</legend>
<legend>{{localize "AWEMMY.Item.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>