feat: add Settlement actor type with Overview/Buildings/Inventory tabs

- New TypeDataModel: archetype, territory, renown, currency (gp/sp/cp),
  garrison, underSiege, isCapital, founded, taxNotes, description, notes
- 3-tab ApplicationV2 sheet with drag & drop for building/weapon/armor/equipment
- Currency steppers (+/−), building constructed toggle, qty controls
- LESS-based CSS (settlement-sheet.less) + base.less updated for shared styles
- Full i18n keys in lang/en.json (8 settlement archetypes)
- system.json: registered settlement actor type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-03-20 17:01:38 +01:00
parent b67d85c6be
commit b3fd7e1aa1
28 changed files with 966 additions and 270 deletions

View File

@@ -1,9 +1,17 @@
<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>
<span class="arcane-stress-display">{{system.arcaneStress.value}} / {{system.arcaneStress.threshold}}</span>
<div class="stress-controls">
<a class="stress-btn" data-action="adjustStress" data-delta="-1" title="1"></a>
<span class="arcane-stress-display {{#if stressBlocked}}stress-at-limit{{/if}}">
{{system.arcaneStress.value}} / {{system.arcaneStress.threshold}}
</span>
<a class="stress-btn" data-action="adjustStress" data-delta="1" title="+1">+</a>
<a class="stress-btn stress-btn--clear" data-action="clearStress" title="{{localize 'OATHHAMMER.Action.ClearStress'}}">
<i class="fa-solid fa-rotate-left"></i>
</a>
<span class="stress-bonus-label">{{localize "OATHHAMMER.Label.ThresholdBonus"}}</span>
<input type="number" name="system.arcaneStress.thresholdBonus" value="{{system.arcaneStress.thresholdBonus}}" min="0" class="stress-bonus-input">
</div>
</fieldset>
<fieldset>
@@ -17,7 +25,9 @@
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
<span>DV</span>
<span>{{localize "OATHHAMMER.Label.Tradition"}}</span>
<span>AS</span>
<span>{{localize "OATHHAMMER.Label.Range"}}</span>
<span>{{localize "OATHHAMMER.Label.Duration"}}</span>
<span>{{localize "OATHHAMMER.Label.SpellSave"}}</span>
<span></span>
</li>
{{#each spells as |spell|}}
@@ -26,7 +36,9 @@
<span class="item-name" {{#if spell._descTooltip}}data-tooltip="{{spell._descTooltip}}"{{/if}}>{{spell.name}}</span>
<span class="item-detail">{{spell.system.difficultyValue}}</span>
<span class="item-type">{{localize spell.system.tradition}}</span>
<span class="item-detail">—</span>
<span class="item-detail item-detail--small">{{#if spell.system.range}}{{spell.system.range}}{{else}}{{/if}}</span>
<span class="item-detail item-detail--small">{{#if spell.system.duration}}{{spell.system.duration}}{{else}}{{/if}}</span>
<span class="item-detail item-detail--small">{{#if spell.system.spellSave}}{{spell.system.spellSave}}{{else}}{{/if}}</span>
<div class="item-actions">
<a data-action="castSpell" data-item-id="{{spell.id}}" title="{{localize 'OATHHAMMER.Action.CastSpell'}}"><i class="fa-solid fa-wand-sparkles spell-cast-icon"></i></a>
<a data-action="edit" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-edit"></i></a>

View File

@@ -0,0 +1,41 @@
<section data-tab="buildings" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<p class="settlement-hint">{{localize "OATHHAMMER.Settlement.BuildingHint"}}</p>
{{#if buildings.length}}
<ul class="item-list item-list--buildings">
<li class="item-list-header">
<span></span>
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
<span>{{localize "OATHHAMMER.Label.Built"}}</span>
<span>{{localize "OATHHAMMER.Label.TaxRevenue"}}</span>
<span>{{localize "OATHHAMMER.Label.Cost"}}</span>
<span></span>
</li>
{{#each buildings as |building|}}
<li class="item-entry {{#if building.system.constructed}}building-constructed{{/if}}" data-item-id="{{building.id}}" data-item-uuid="{{building.uuid}}">
<img src="{{building.img}}" class="item-img" />
<span class="item-name" data-tooltip="{{building.name}}">{{building.name}}</span>
<span class="item-constructed">
{{#unless ../isPlayMode}}
<a data-action="toggleConstructed" data-item-id="{{building.id}}" class="construct-toggle">
<i class="fa-{{#if building.system.constructed}}solid{{else}}regular{{/if}} fa-square-check"></i>
</a>
{{else}}
<i class="fa-{{#if building.system.constructed}}solid{{else}}regular{{/if}} fa-square-check"></i>
{{/unless}}
</span>
<span class="item-tax {{#unless building.system.constructed}}item-tax--inactive{{/unless}}">
{{#if building.system.taxRevenue}}{{building.system.taxRevenue}}{{else}}{{/if}}
</span>
<span class="item-cost">{{building.system.cost}} gp</span>
<div class="item-actions">
<a data-action="edit" data-item-id="{{building.id}}" data-item-uuid="{{building.uuid}}"><i class="fa-solid fa-edit"></i></a>
{{#unless ../isPlayMode}}<a data-action="delete" data-item-id="{{building.id}}" data-item-uuid="{{building.uuid}}"><i class="fa-solid fa-trash"></i></a>{{/unless}}
</div>
</li>
{{/each}}
</ul>
{{else}}
<p class="no-items">{{localize "OATHHAMMER.Settlement.NoBuildings"}}</p>
{{/if}}
</section>

View File

@@ -0,0 +1,96 @@
<section data-tab="inventory" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<p class="settlement-hint">{{localize "OATHHAMMER.Settlement.InventoryHint"}}</p>
{{#if weapons.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Weapons"}}</legend>
<ul class="item-list item-list--weapons">
<li class="item-list-header">
<span></span>
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
<span>{{localize "OATHHAMMER.Label.Type"}}</span>
<span></span>
</li>
{{#each weapons as |weapon|}}
<li class="item-entry" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}">
<img src="{{weapon.img}}" class="item-img" />
<span class="item-name">{{weapon.name}}</span>
<span class="item-type">{{weapon.system.weaponType}}</span>
<div class="item-actions">
<a data-action="edit" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-edit"></i></a>
{{#unless ../isPlayMode}}<a data-action="delete" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-trash"></i></a>{{/unless}}
</div>
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
{{#if armors.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Armors"}}</legend>
<ul class="item-list item-list--armors">
<li class="item-list-header">
<span></span>
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
<span>{{localize "OATHHAMMER.Label.Type"}}</span>
<span></span>
</li>
{{#each armors as |armor|}}
<li class="item-entry" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}">
<img src="{{armor.img}}" class="item-img" />
<span class="item-name">{{armor.name}}</span>
<span class="item-type">{{localize armor.system.armorType}}</span>
<div class="item-actions">
<a data-action="edit" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-edit"></i></a>
{{#unless ../isPlayMode}}<a data-action="delete" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-trash"></i></a>{{/unless}}
</div>
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
{{#if equipments.length}}
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Equipment"}}</legend>
<ul class="item-list item-list--equipment">
<li class="item-list-header">
<span></span>
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
<span>{{localize "OATHHAMMER.Label.Type"}}</span>
<span>{{localize "OATHHAMMER.Label.Quantity"}}</span>
<span></span>
</li>
{{#each equipments as |equip|}}
<li class="item-entry" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}">
<img src="{{equip.img}}" class="item-img" />
<span class="item-name">{{equip.name}}</span>
<span class="item-type">{{localize equip.system.itemType}}</span>
<span class="item-qty-stepper">
{{#unless ../isPlayMode}}
<a data-action="adjustQty" data-item-id="{{equip.id}}" data-delta="-1" class="qty-btn"></a>
{{/unless}}
<span class="qty-value">{{equip.system.quantity}}</span>
{{#unless ../isPlayMode}}
<a data-action="adjustQty" data-item-id="{{equip.id}}" data-delta="1" class="qty-btn">+</a>
{{/unless}}
</span>
<div class="item-actions">
<a data-action="edit" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-edit"></i></a>
{{#unless ../isPlayMode}}<a data-action="delete" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-trash"></i></a>{{/unless}}
</div>
</li>
{{/each}}
</ul>
</fieldset>
{{/if}}
{{#unless weapons.length}}
{{#unless armors.length}}
{{#unless equipments.length}}
<p class="no-items">{{localize "OATHHAMMER.Settlement.NoInventory"}}</p>
{{/unless}}
{{/unless}}
{{/unless}}
</section>

View File

@@ -0,0 +1,61 @@
<section data-tab="overview" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
<fieldset class="currency-bar">
<legend>{{localize "OATHHAMMER.Label.Treasury"}}</legend>
<div class="flexrow">
<div class="currency-item">
<label>{{localize "OATHHAMMER.Currency.GP"}}</label>
<div class="currency-stepper">
<a data-action="adjustCurrency" data-field="system.currency.gold" data-delta="-1" class="qty-btn"></a>
{{formInput systemFields.currency.fields.gold value=system.currency.gold name="system.currency.gold"}}
<a data-action="adjustCurrency" data-field="system.currency.gold" data-delta="1" class="qty-btn">+</a>
</div>
</div>
<div class="currency-item">
<label>{{localize "OATHHAMMER.Currency.SP"}}</label>
<div class="currency-stepper">
<a data-action="adjustCurrency" data-field="system.currency.silver" data-delta="-1" class="qty-btn"></a>
{{formInput systemFields.currency.fields.silver value=system.currency.silver name="system.currency.silver"}}
<a data-action="adjustCurrency" data-field="system.currency.silver" data-delta="1" class="qty-btn">+</a>
</div>
</div>
<div class="currency-item">
<label>{{localize "OATHHAMMER.Currency.CP"}}</label>
<div class="currency-stepper">
<a data-action="adjustCurrency" data-field="system.currency.copper" data-delta="-1" class="qty-btn"></a>
{{formInput systemFields.currency.fields.copper value=system.currency.copper name="system.currency.copper"}}
<a data-action="adjustCurrency" data-field="system.currency.copper" data-delta="1" class="qty-btn">+</a>
</div>
</div>
</div>
</fieldset>
<div class="settlement-overview-grid">
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Garrison"}}</legend>
{{formInput systemFields.garrison value=system.garrison name="system.garrison" disabled=isPlayMode}}
</fieldset>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Founded"}}</legend>
{{formInput systemFields.founded value=system.founded name="system.founded" disabled=isPlayMode}}
</fieldset>
<fieldset class="tax-notes">
<legend>{{localize "OATHHAMMER.Label.TaxNotes"}}</legend>
{{formInput systemFields.taxNotes value=system.taxNotes name="system.taxNotes" disabled=isPlayMode}}
</fieldset>
</div>
<fieldset>
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
{{#if isEditMode}}
<prose-mirror name="system.description" toggled="false" collaborate="false">
{{{system.description}}}
</prose-mirror>
{{else}}
<div class="editor-content">{{{enrichedDescription}}}</div>
{{/if}}
</fieldset>
</section>

View File

@@ -0,0 +1,47 @@
<div class="settlement-main settlement-main-{{ifThen isPlayMode 'play' 'edit'}}">
<header class="settlement-header flexrow">
<img class="actor-img" src="{{actor.img}}" data-edit="img" data-action="editImage" data-tooltip="{{actor.name}}" />
<div class="settlement-header-info">
<div class="settlement-name-row flexrow">
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
{{#unless isPlayMode}}
{{formInput systemFields.archetype value=system.archetype name="system.archetype" localize=true}}
{{else}}
<span class="settlement-archetype-badge">{{localize (lookup archetypeChoices system.archetype)}}</span>
{{/unless}}
<a class="control" data-action="toggleSheet" data-tooltip="OATHHAMMER.ToggleSheet" data-tooltip-direction="UP">
<i class="fa-solid fa-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
</a>
</div>
<div class="settlement-stats flexrow">
<div class="stat-item">
<label>{{localize "OATHHAMMER.Label.Renown"}}</label>
{{formInput systemFields.renown value=system.renown name="system.renown" disabled=isPlayMode}}
</div>
<div class="stat-item">
<label>{{localize "OATHHAMMER.Label.Territory"}}</label>
{{formInput systemFields.territory value=system.territory name="system.territory" disabled=isPlayMode}}
</div>
<div class="stat-item">
<label>{{localize "OATHHAMMER.Label.Population"}}</label>
{{formInput systemFields.population value=system.population name="system.population" disabled=isPlayMode}}
</div>
<div class="stat-item stat-item--flags">
{{#unless isPlayMode}}
<label>
{{formInput systemFields.isCapital value=system.isCapital name="system.isCapital"}}
{{localize "OATHHAMMER.Label.IsCapital"}}
</label>
<label>
{{formInput systemFields.underSiege value=system.underSiege name="system.underSiege"}}
{{localize "OATHHAMMER.Label.UnderSiege"}}
</label>
{{else}}
{{#if system.isCapital}}<span class="settlement-badge settlement-badge--capital">★ {{localize "OATHHAMMER.Label.Capital"}}</span>{{/if}}
{{#if system.underSiege}}<span class="settlement-badge settlement-badge--siege">⚔ {{localize "OATHHAMMER.Label.UnderSiege"}}</span>{{/if}}
{{/unless}}
</div>
</div>
</div>
</header>
</div>

View File

@@ -53,6 +53,18 @@
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.AttackModifierHint"}}</span>
</div>
{{#if availableLuck}}
<div class="roll-option-row roll-option-luck">
<label>{{localize "OATHHAMMER.Dialog.LuckSpend"}} <i class="fa-solid fa-clover luck-icon"></i></label>
<select name="luckSpend">
{{#each luckOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<label class="luck-human-label" for="miracleLuckIsHuman">{{localize "OATHHAMMER.Dialog.LuckHuman"}}</label>
<input type="checkbox" id="miracleLuckIsHuman" name="luckIsHuman" value="true" {{#if isHuman}}checked{{/if}} />
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.LuckHint"}} ({{availableLuck}} {{localize "OATHHAMMER.Dialog.Available"}})</span>
</div>
{{/if}}
<div class="roll-option-row roll-option-check">
<label for="miracleExplodeOn5">{{localize "OATHHAMMER.Dialog.ExplodeOn5"}}</label>
<input type="checkbox" id="miracleExplodeOn5" name="explodeOn5" />

View File

@@ -34,6 +34,16 @@
= <strong>{{basePool}}d6</strong>
</div>
{{#if poolSizeOptions}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.PoolSize"}}</label>
<select name="poolSize">
{{#each poolSizeOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.PoolSizeHint"}}</span>
</div>
{{/if}}
<div class="roll-option-row">
<label>{{localize "OATHHAMMER.Dialog.Enhancement"}}</label>
<select name="enhancement" class="enhancement-select">
@@ -67,6 +77,18 @@
</select>
</div>
{{#if availableLuck}}
<div class="roll-option-row roll-option-luck">
<label>{{localize "OATHHAMMER.Dialog.LuckSpend"}} <i class="fa-solid fa-clover luck-icon"></i></label>
<select name="luckSpend">
{{#each luckOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
</select>
<label class="luck-human-label" for="spellLuckIsHuman">{{localize "OATHHAMMER.Dialog.LuckHuman"}}</label>
<input type="checkbox" id="spellLuckIsHuman" name="luckIsHuman" value="true" {{#if isHuman}}checked{{/if}} />
<span class="roll-option-hint">{{localize "OATHHAMMER.Dialog.LuckHint"}} ({{availableLuck}} {{localize "OATHHAMMER.Dialog.Available"}})</span>
</div>
{{/if}}
<div class="roll-option-row roll-option-check">
<label for="spellExplodeOn5">{{localize "OATHHAMMER.Dialog.ExplodeOn5"}}</label>
<input type="checkbox" id="spellExplodeOn5" name="explodeOn5" />