Item ehnance, fixes on acto sheet

This commit is contained in:
2026-03-08 10:42:36 +01:00
parent 8f7f0169e4
commit e9abefd90d
22 changed files with 508 additions and 68 deletions

View File

@@ -238,12 +238,71 @@
width: 4rem;
text-align: center;
}
.oathhammer .identity-lineage-class {
gap: 8px;
margin-bottom: 8px;
}
.oathhammer .identity-lineage-class .item-slot {
flex: 1;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
border: 2px solid #c8a84b;
border-radius: 4px;
background: rgba(212, 160, 23, 0.08);
min-height: 40px;
}
.oathhammer .identity-lineage-class .item-slot.empty {
border-style: dashed;
opacity: 0.7;
cursor: default;
}
.oathhammer .identity-lineage-class .item-slot.empty .slot-icon {
font-size: 1.2rem;
color: #c8a84b;
}
.oathhammer .identity-lineage-class .item-slot.empty .slot-placeholder {
font-family: "Calibri", "Segoe UI", sans-serif;
font-style: italic;
color: #c8a84b;
font-size: 0.9rem;
}
.oathhammer .identity-lineage-class .item-slot .item-img {
width: 32px;
height: 32px;
-o-object-fit: contain;
object-fit: contain;
border: none;
flex-shrink: 0;
}
.oathhammer .identity-lineage-class .item-slot .item-name {
flex: 1;
font-family: "Sherwood", "Palatino Linotype", serif;
font-size: 1rem;
color: #2a1a0a;
}
.oathhammer .identity-lineage-class .item-slot a[data-action] {
color: #c8a84b;
}
.oathhammer .identity-lineage-class .item-slot a[data-action]:hover {
color: #084a74;
}
.oathhammer .biodata-col {
flex: 1;
display: flex;
flex-direction: column;
gap: 2px;
}
.oathhammer .character-arcane-stress .flexrow {
align-items: center;
gap: 4px;
}
.oathhammer .character-arcane-stress input {
min-width: 3rem;
max-width: 3rem;
text-align: center;
}
.oathhammer .defense-display {
min-width: 3rem;
max-width: 3rem;

View File

@@ -12,7 +12,9 @@
"MagicItem": "Oath Hammer Magic Item Sheet",
"Ability": "Oath Hammer Ability Sheet",
"Oath": "Oath Hammer Oath Sheet",
"Condition": "Oath Hammer Condition Sheet"
"Condition": "Oath Hammer Condition Sheet",
"Lineage": "Oath Hammer Lineage Sheet",
"Class": "Oath Hammer Class Sheet"
},
"Tab": {
"Identity": "Identity",
@@ -64,7 +66,21 @@
"Halfling": "Halfling",
"HighElf": "High Elf",
"Human": "Human",
"WoodElf": "Wood Elf"
"WoodElf": "Wood Elf",
"FIELDS": {
"description": {
"label": "Description"
},
"traits": {
"label": "Traits"
},
"movement": {
"label": "Movement (ft)"
},
"gritModifier": {
"label": "Grit Modifier"
}
}
},
"Class": {
"Berserker": "Berserker",
@@ -76,7 +92,21 @@
"Scout": "Scout",
"Soldier": "Soldier",
"Spellblade": "Spellblade",
"Troubadour": "Troubadour"
"Troubadour": "Troubadour",
"FIELDS": {
"description": {
"label": "Description"
},
"features": {
"label": "Features"
},
"armorProficiency": {
"label": "Armor Proficiency"
},
"weaponProficiency": {
"label": "Weapon Proficiency"
}
}
},
"Tradition": {
"Elemental": "Elemental",
@@ -212,7 +242,11 @@
"Bane": "Bane",
"Skill": "Skill",
"SkillRank": "Rank",
"TotalDice": "Total Dice"
"TotalDice": "Total Dice",
"DropLineage": "Drop Lineage Here",
"DropClass": "Drop Class Here",
"Traits": "Traits",
"Features": "Features"
},
"NewItem": {
"Weapon": "New Weapon",
@@ -232,8 +266,12 @@
"luck": {
"label": "Luck",
"fields": {
"value": { "label": "Luck" },
"max": { "label": "Luck Max" }
"value": {
"label": "Luck"
},
"max": {
"label": "Luck Max"
}
}
},
"arcaneStress": {
@@ -250,32 +288,34 @@
},
"biodata": {
"label": "Background",
"lineage": {
"label": "Lineage"
},
"class": {
"label": "Class"
},
"age": {
"label": "Age"
},
"gender": {
"label": "Gender"
},
"height": {
"label": "Height"
},
"weight": {
"label": "Weight"
},
"eyes": {
"label": "Eye Color"
},
"hair": {
"label": "Hair Color"
},
"alignment": {
"label": "Alignment"
"fields": {
"lineage": {
"label": "Lineage"
},
"class": {
"label": "Class"
},
"age": {
"label": "Age"
},
"gender": {
"label": "Gender"
},
"height": {
"label": "Height"
},
"weight": {
"label": "Weight"
},
"eyes": {
"label": "Eye Color"
},
"hair": {
"label": "Hair Color"
},
"alignment": {
"label": "Alignment"
}
}
},
"currency": {
@@ -717,7 +757,9 @@
"magic_item": "Magic Item",
"magic-item": "Magic Item",
"ability": "Ability",
"oath": "Oath"
"oath": "Oath",
"lineage": "Lineage",
"class": "Class"
},
"Actor": {
"character": "Character",

View File

@@ -123,6 +123,61 @@
}
}
// Lineage / Class item slots in Identity tab
.identity-lineage-class {
gap: 8px;
margin-bottom: 8px;
.item-slot {
flex: 1;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
border: 2px solid @color-gold;
border-radius: 4px;
background: rgba(212, 160, 23, 0.08);
min-height: 40px;
&.empty {
border-style: dashed;
opacity: 0.7;
cursor: default;
.slot-icon {
font-size: 1.2rem;
color: @color-gold;
}
.slot-placeholder {
font-family: @font-body;
font-style: italic;
color: @color-gold;
font-size: 0.9rem;
}
}
.item-img {
width: 32px;
height: 32px;
object-fit: contain;
border: none;
flex-shrink: 0;
}
.item-name {
flex: 1;
font-family: @font-primary;
font-size: 1rem;
color: @color-dark;
}
a[data-action] {
color: @color-gold;
&:hover { color: @color-blue; }
}
}
}
// Biodata
.biodata-col {
flex: 1;
@@ -131,6 +186,19 @@
gap: 2px;
}
// Arcane Stress narrow inputs
.character-arcane-stress {
.flexrow {
align-items: center;
gap: 4px;
}
input {
min-width: 3rem;
max-width: 3rem;
text-align: center;
}
}
// Defense display
.defense-display {
min-width: 3rem;

View File

@@ -9,3 +9,5 @@ export { default as OathHammerMiracleSheet } from "./sheets/miracle-sheet.mjs"
export { default as OathHammerMagicItemSheet } from "./sheets/magic-item-sheet.mjs"
export { default as OathHammerAbilitySheet } from "./sheets/ability-sheet.mjs"
export { default as OathHammerOathSheet } from "./sheets/oath-sheet.mjs"
export { default as OathHammerLineageSheet } from "./sheets/lineage-sheet.mjs"
export { default as OathHammerClassSheet } from "./sheets/class-sheet.mjs"

View File

@@ -95,6 +95,13 @@ export default class OathHammerActorSheet extends HandlebarsApplicationMixin(fou
async _onDropItem(item) {
const itemData = item.toObject()
// Lineage and class are unique: replace any existing item of the same type
if (item.type === "lineage" || item.type === "class") {
const existing = this.document.itemTypes[item.type]
if (existing.length > 0) {
await this.document.deleteEmbeddedDocuments("Item", existing.map(i => i.id))
}
}
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
}

View File

@@ -68,6 +68,8 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
break
case "identity":
context.tab = context.tabs.identity
context.lineage = doc.itemTypes.lineage?.[0] ?? null
context.characterClass = doc.itemTypes["class"]?.[0] ?? null
context.abilities = doc.itemTypes.ability
context.oaths = doc.itemTypes.oath
break
@@ -124,7 +126,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
}
async _onDrop(event) {
if (!this.isEditable || !this.isEditMode) return
if (!this.isEditable) return
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
if (data.type === "Item") {
const item = await fromUuid(data.uuid)

View File

@@ -0,0 +1,30 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerClassSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["class"],
position: {
width: 640,
},
window: {
contentClasses: ["class-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/class-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedFeatures = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.features ?? "", { async: true }
)
return context
}
}

View File

@@ -0,0 +1,30 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerLineageSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["lineage"],
position: {
width: 640,
},
window: {
contentClasses: ["lineage-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/lineage-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedTraits = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.traits ?? "", { async: true }
)
return context
}
}

View File

@@ -9,3 +9,5 @@ export { default as OathHammerMiracle } from "./miracle.mjs"
export { default as OathHammerMagicItem } from "./magic-item.mjs"
export { default as OathHammerAbility } from "./ability.mjs"
export { default as OathHammerOath } from "./oath.mjs"
export { default as OathHammerLineage } from "./lineage.mjs"
export { default as OathHammerClass } from "./class.mjs"

View File

@@ -1,4 +1,3 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerCharacter extends foundry.abstract.TypeDataModel {
static defineSchema() {
@@ -88,8 +87,6 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel
})
schema.biodata = new fields.SchemaField({
lineage: new fields.StringField({ required: true, initial: "dwarf", choices: SYSTEM.LINEAGE_CHOICES }),
class: new fields.StringField({ required: true, initial: "soldier", choices: SYSTEM.CLASS_CHOICES }),
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
gender: new fields.StringField({ required: true, nullable: false, initial: "" }),
height: new fields.StringField({ required: true, nullable: false, initial: "" }),

21
module/models/class.mjs Normal file
View File

@@ -0,0 +1,21 @@
export default class OathHammerClass extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
// Class features, starting abilities, advancement options (rich text)
schema.features = new fields.HTMLField({ required: true, textSearch: true })
// Armor proficiencies (e.g. "Light, Medium, Heavy")
schema.armorProficiency = new fields.StringField({ required: true, nullable: false, initial: "" })
// Weapon proficiencies (e.g. "Common, Dueling, Heavy, Throwing")
schema.weaponProficiency = new fields.StringField({ required: true, nullable: false, initial: "" })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Class"]
}

21
module/models/lineage.mjs Normal file
View File

@@ -0,0 +1,21 @@
export default class OathHammerLineage extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
// Racial traits and special abilities (rich text)
schema.traits = new fields.HTMLField({ required: true, textSearch: true })
// Base movement speed in feet
schema.movement = new fields.NumberField({ required: true, nullable: false, integer: true, initial: 30, min: 0 })
// Modifier to max Grit Points (e.g. -1 for High Elf, Wood Elf)
schema.gritModifier = new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Lineage"]
}

View File

@@ -31,7 +31,9 @@ Hooks.once("init", function () {
miracle: models.OathHammerMiracle,
"magic-item": models.OathHammerMagicItem,
ability: models.OathHammerAbility,
oath: models.OathHammerOath
oath: models.OathHammerOath,
lineage: models.OathHammerLineage,
"class": models.OathHammerClass
}
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
@@ -56,6 +58,8 @@ Hooks.once("init", function () {
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerMagicItemSheet, { types: ["magic-item"], makeDefault: true, label: "OATHHAMMER.Sheet.MagicItem" })
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerAbilitySheet, { types: ["ability"], makeDefault: true, label: "OATHHAMMER.Sheet.Ability" })
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerOathSheet, { types: ["oath"], makeDefault: true, label: "OATHHAMMER.Sheet.Oath" })
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerLineageSheet, { types: ["lineage"], makeDefault: true, label: "OATHHAMMER.Sheet.Lineage" })
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerClassSheet, { types: ["class"], makeDefault: true, label: "OATHHAMMER.Sheet.Class" })
CONFIG.statusEffects = STATUS_EFFECTS

View File

@@ -3,35 +3,122 @@
"title": "Oath Hammer RPG",
"description": "Oath Hammer RPG System for FoundryVTT",
"version": "13.0.0",
"compatibility": { "minimum": "13", "verified": "13" },
"esmodules": ["oath-hammer.mjs"],
"styles": ["css/fvtt-oath-hammer.css"],
"languages": [{ "lang": "en", "name": "English", "path": "lang/en.json" }],
"compatibility": {
"minimum": "13",
"verified": "13"
},
"esmodules": [
"oath-hammer.mjs"
],
"styles": [
"css/fvtt-oath-hammer.css"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"documentTypes": {
"Actor": {
"character": { "htmlFields": ["description", "notes"] },
"npc": { "htmlFields": ["description", "notes"] }
"character": {
"htmlFields": [
"description",
"notes"
]
},
"npc": {
"htmlFields": [
"description",
"notes"
]
}
},
"Item": {
"weapon": { "htmlFields": ["description", "magicEffect"] },
"armor": { "htmlFields": ["description", "magicEffect"] },
"ammunition": { "htmlFields": ["description"] },
"equipment": { "htmlFields": ["description"] },
"spell": { "htmlFields": ["effect"] },
"miracle": { "htmlFields": ["effect"] },
"magic-item": { "htmlFields": ["effect"] },
"ability": { "htmlFields": ["description"] },
"oath": { "htmlFields": ["tenet", "boon", "bane"] }
"weapon": {
"htmlFields": [
"description",
"magicEffect"
]
},
"armor": {
"htmlFields": [
"description",
"magicEffect"
]
},
"ammunition": {
"htmlFields": [
"description"
]
},
"equipment": {
"htmlFields": [
"description"
]
},
"spell": {
"htmlFields": [
"effect"
]
},
"miracle": {
"htmlFields": [
"effect"
]
},
"magic-item": {
"htmlFields": [
"effect"
]
},
"ability": {
"htmlFields": [
"description"
]
},
"oath": {
"htmlFields": [
"tenet",
"boon",
"bane"
]
},
"lineage": {
"htmlFields": [
"description",
"traits"
]
},
"class": {
"htmlFields": [
"description",
"features"
]
}
}
},
"grid": { "distance": 5, "units": "ft" },
"grid": {
"distance": 5,
"units": "ft"
},
"primaryTokenAttribute": "grit",
"socket": true,
"background": "systems/fvtt-oath-hammer/assets/ui/oath_hammer_paper.webp",
"flags": {
"hotReload": {
"extensions": ["css", "hbs", "json"],
"paths": ["css/", "lang/", "assets/", "templates/"]
"extensions": [
"css",
"hbs",
"json"
],
"paths": [
"css/",
"lang/",
"assets/",
"templates/"
]
}
}
}
}

View File

@@ -1,4 +1,4 @@
<section data-tab="combat" class="tab {{tab.cssClass}}">
<section data-tab="combat" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Defense"}}</legend>
<div class="flexrow">

View File

@@ -1,4 +1,4 @@
<section data-tab="equipment" class="tab {{tab.cssClass}}">
<section data-tab="equipment" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Equipment"}}
{{#unless isPlayMode}}<a data-action="createEquipment" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}

View File

@@ -1,10 +1,36 @@
<section data-tab="identity" class="tab {{tab.cssClass}}">
<section data-tab="identity" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<div class="identity-lineage-class flexrow">
<div class="item-slot lineage-slot {{#unless lineage}}empty{{/unless}}">
{{#if lineage}}
<img src="{{lineage.img}}" class="item-img" data-item-id="{{lineage.id}}" data-item-uuid="{{lineage.uuid}}" />
<span class="item-name">{{lineage.name}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{lineage.id}}" data-item-uuid="{{lineage.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{lineage.id}}" data-item-uuid="{{lineage.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
{{else}}
<i class="fa-solid fa-person slot-icon"></i>
<span class="slot-placeholder">{{localize "OATHHAMMER.Label.DropLineage"}}</span>
{{/if}}
</div>
<div class="item-slot class-slot {{#unless characterClass}}empty{{/unless}}">
{{#if characterClass}}
<img src="{{characterClass.img}}" class="item-img" data-item-id="{{characterClass.id}}" data-item-uuid="{{characterClass.uuid}}" />
<span class="item-name">{{characterClass.name}}</span>
{{#unless ../isPlayMode}}
<a data-action="edit" data-item-id="{{characterClass.id}}" data-item-uuid="{{characterClass.uuid}}"><i class="fa-solid fa-edit"></i></a>
<a data-action="delete" data-item-id="{{characterClass.id}}" data-item-uuid="{{characterClass.uuid}}"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
{{else}}
<i class="fa-solid fa-shield-halved slot-icon"></i>
<span class="slot-placeholder">{{localize "OATHHAMMER.Label.DropClass"}}</span>
{{/if}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Biodata"}}</legend>
<div class="flexrow">
<div class="biodata-col">
{{formField systemFields.biodata.fields.lineage value=system.biodata.lineage name="system.biodata.lineage" localize=true disabled=isPlayMode}}
{{formField systemFields.biodata.fields.class value=system.biodata.class name="system.biodata.class" localize=true disabled=isPlayMode}}
{{formField systemFields.biodata.fields.alignment value=system.biodata.alignment name="system.biodata.alignment" disabled=isPlayMode}}
{{formField systemFields.biodata.fields.age value=system.biodata.age name="system.biodata.age" disabled=isPlayMode}}
{{formField systemFields.biodata.fields.gender value=system.biodata.gender name="system.biodata.gender" disabled=isPlayMode}}

View File

@@ -1,11 +1,9 @@
<section data-tab="magic" class="tab {{tab.cssClass}}">
<section data-tab="magic" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.ArcaneStress"}}</legend>
<div class="flexrow">
<label>{{localize "OATHHAMMER.Label.StressValue"}}</label>
{{formInput systemFields.arcaneStress.fields.value value=system.arcaneStress.value name="system.arcaneStress.value" disabled=isPlayMode}}
<span>/</span>
{{formInput systemFields.arcaneStress.fields.threshold value=system.arcaneStress.threshold name="system.arcaneStress.threshold" disabled=isPlayMode}}
<span class="arcane-stress-display">{{system.arcaneStress.value}} / {{system.arcaneStress.threshold}}</span>
</div>
</fieldset>
<fieldset>

View File

@@ -1,4 +1,4 @@
<section data-tab="notes" class="tab {{tab.cssClass}}">
<section data-tab="notes" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}

View File

@@ -1,4 +1,4 @@
<section data-tab="skills" class="tab {{tab.cssClass}}">
<section data-tab="skills" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<div class="skills-container">
{{#each skillGroups as |group|}}
<fieldset class="skills-group">

View File

@@ -0,0 +1,22 @@
<section class="item-sheet-common">
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow stats-row">
<div class="form-group">
{{formField systemFields.armorProficiency value=system.armorProficiency name="system.armorProficiency"}}
</div>
<div class="form-group">
{{formField systemFields.weaponProficiency value=system.weaponProficiency name="system.weaponProficiency"}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Features"}}</legend>
{{formInput systemFields.features enriched=enrichedFeatures value=system.features name="system.features" toggled=true}}
</fieldset>
</section>

View File

@@ -0,0 +1,22 @@
<section class="item-sheet-common">
<div class="header">
<img class="item-img" src="{{item.img}}" data-edit="img" data-action="editImage" data-tooltip="{{item.name}}" />
{{formInput fields.name value=source.name}}
</div>
<div class="flexrow stats-row">
<div class="form-group">
{{formField systemFields.movement value=system.movement name="system.movement"}}
</div>
<div class="form-group">
{{formField systemFields.gritModifier value=system.gritModifier name="system.gritModifier"}}
</div>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Traits"}}</legend>
{{formInput systemFields.traits enriched=enrichedTraits value=system.traits name="system.traits" toggled=true}}
</fieldset>
</section>