Fixes and enhancements, from issue list
This commit is contained in:
@@ -550,6 +550,26 @@
|
|||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
font-size: calc(0.86rem * 0.9);
|
font-size: calc(0.86rem * 0.9);
|
||||||
}
|
}
|
||||||
|
.oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
.oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.4rem;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
font-weight: bold;
|
||||||
|
color: #535128;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-btn:hover {
|
||||||
|
color: #2a1a0a;
|
||||||
|
}
|
||||||
.oathhammer .character-main .character-stats-band .character-resources .resource-label {
|
.oathhammer .character-main .character-stats-band .character-resources .resource-label {
|
||||||
min-width: 4.2rem;
|
min-width: 4.2rem;
|
||||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
@@ -919,7 +939,7 @@
|
|||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
}
|
}
|
||||||
.oathhammer .npc-main .regiment-vitals-grid.regiment-row2 {
|
.oathhammer .npc-main .regiment-vitals-grid.regiment-row2 {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
@@ -1160,7 +1180,7 @@
|
|||||||
}
|
}
|
||||||
.oathhammer .item-list--oath .item-list-header,
|
.oathhammer .item-list--oath .item-list-header,
|
||||||
.oathhammer .item-list--oath .item-entry {
|
.oathhammer .item-list--oath .item-entry {
|
||||||
grid-template-columns: 24px 1fr 7rem 3.5rem 3.5rem;
|
grid-template-columns: 24px 12rem 1fr 3.5rem 3.5rem;
|
||||||
}
|
}
|
||||||
.oathhammer .item-list--npc-skill .item-list-header,
|
.oathhammer .item-list--npc-skill .item-list-header,
|
||||||
.oathhammer .item-list--npc-skill .item-entry {
|
.oathhammer .item-list--npc-skill .item-entry {
|
||||||
@@ -2574,9 +2594,39 @@
|
|||||||
.oathhammer .party-main .party-treasury .party-currency-cp .currency-label {
|
.oathhammer .party-main .party-treasury .party-currency-cp .currency-label {
|
||||||
color: #aa6633;
|
color: #aa6633;
|
||||||
}
|
}
|
||||||
|
.oathhammer .party-main .party-slots {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
}
|
||||||
|
.oathhammer .party-main .party-slots .party-slots-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #535128;
|
||||||
|
margin-right: 0.3rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
.oathhammer .party-main .party-slots .party-slots-current {
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 1.8rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.oathhammer .party-main .party-slots .party-slots-sep {
|
||||||
|
color: #535128;
|
||||||
|
}
|
||||||
|
.oathhammer .party-main .party-slots .party-slots-max {
|
||||||
|
width: 3.5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
}
|
||||||
.oathhammer .item-list--party-member .item-list-header,
|
.oathhammer .item-list--party-member .item-list-header,
|
||||||
.oathhammer .item-list--party-member .item-entry {
|
.oathhammer .item-list--party-member .item-entry {
|
||||||
grid-template-columns: 1.8rem 24px 1fr 7rem 3rem 5rem 5.5rem;
|
grid-template-columns: 1.8rem 24px 1fr 7rem 5rem 3rem 5.5rem;
|
||||||
}
|
}
|
||||||
.oathhammer .item-list--party-member .party-member-order {
|
.oathhammer .item-list--party-member .party-member-order {
|
||||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
@@ -2586,6 +2636,15 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
.oathhammer .item-list--party-member .item-detail--center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.oathhammer .item-list--party-member .item-detail--center a {
|
||||||
|
color: #2a1a0a;
|
||||||
|
}
|
||||||
|
.oathhammer .item-list--party-member .item-detail--center .fa-faded {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
.oathhammer .item-list--party-loot .item-list-header,
|
.oathhammer .item-list--party-loot .item-list-header,
|
||||||
.oathhammer .item-list--party-loot .item-entry {
|
.oathhammer .item-list--party-loot .item-entry {
|
||||||
grid-template-columns: 24px 1fr 6rem 5.5rem 5rem;
|
grid-template-columns: 24px 1fr 6rem 5.5rem 5rem;
|
||||||
@@ -2752,3 +2811,79 @@
|
|||||||
color: #987d2e;
|
color: #987d2e;
|
||||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
}
|
}
|
||||||
|
.oh-free-roll-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-top: 1px solid rgba(83, 81, 40, 0.4);
|
||||||
|
background: rgba(83, 81, 40, 0.08);
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-label {
|
||||||
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2a1a0a;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls select {
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
padding: 1px 2px;
|
||||||
|
height: 1.6rem;
|
||||||
|
border: 1px solid #535128;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls .oh-frb-pool {
|
||||||
|
width: 3.8rem;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls .oh-frb-color {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 6rem;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls .oh-frb-explode-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #2a1a0a;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls .oh-frb-explode-label input[type="checkbox"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls .oh-frb-roll-btn {
|
||||||
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 2px 8px;
|
||||||
|
height: 1.6rem;
|
||||||
|
border: 1px solid #535128;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(83, 81, 40, 0.2);
|
||||||
|
color: #2a1a0a;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.oh-free-roll-bar .oh-frb-controls .oh-frb-roll-btn:hover {
|
||||||
|
background: #c8a84b;
|
||||||
|
border-color: #ac8d34;
|
||||||
|
}
|
||||||
|
|||||||
11
lang/en.json
11
lang/en.json
@@ -190,7 +190,8 @@
|
|||||||
"ClassTrait": "Class Trait",
|
"ClassTrait": "Class Trait",
|
||||||
"LineageTrait": "Lineage Trait",
|
"LineageTrait": "Lineage Trait",
|
||||||
"NpcTrait": "NPC Trait",
|
"NpcTrait": "NPC Trait",
|
||||||
"CreatureTrait": "Creature Trait"
|
"CreatureTrait": "Creature Trait",
|
||||||
|
"RegimentTrait": "Regiment Trait"
|
||||||
},
|
},
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"Blinded": "Blinded",
|
"Blinded": "Blinded",
|
||||||
@@ -355,7 +356,7 @@
|
|||||||
"DropLeaderHint": "Drop a linked actor here",
|
"DropLeaderHint": "Drop a linked actor here",
|
||||||
"MarchingOrder": "Marching Order",
|
"MarchingOrder": "Marching Order",
|
||||||
"NoMembers": "No members yet — drag characters here.",
|
"NoMembers": "No members yet — drag characters here.",
|
||||||
"DropMemberHint": "Drag a character actor here to add them to the party.",
|
"DropMemberHint": "Drag a character or NPC actor here to add them to the party.",
|
||||||
"Loot": "Loot",
|
"Loot": "Loot",
|
||||||
"NoLoot": "No loot yet — drag items here.",
|
"NoLoot": "No loot yet — drag items here.",
|
||||||
"DropLootHint": "Drag weapons, armor or equipment here to add party loot.",
|
"DropLootHint": "Drag weapons, armor or equipment here to add party loot.",
|
||||||
@@ -368,7 +369,11 @@
|
|||||||
"Location": "Location",
|
"Location": "Location",
|
||||||
"Regiments": "Regiments",
|
"Regiments": "Regiments",
|
||||||
"DropRegimentHint": "Drag a regiment actor (must be token-linked) to add it to this army.",
|
"DropRegimentHint": "Drag a regiment actor (must be token-linked) to add it to this army.",
|
||||||
"TotalSupply": "Total Supply"
|
"TotalSupply": "Total Supply",
|
||||||
|
"Mercenary": "Mercenary",
|
||||||
|
"CurrentXP": "XP",
|
||||||
|
"CarriesLight": "Carries Light",
|
||||||
|
"Slots": "Slots"
|
||||||
},
|
},
|
||||||
"ColorDice": {
|
"ColorDice": {
|
||||||
"White": "White (4+)",
|
"White": "White (4+)",
|
||||||
|
|||||||
@@ -168,6 +168,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.res-sep { opacity: 0.7; font-size: @font-size-xs; }
|
.res-sep { opacity: 0.7; font-size: @font-size-xs; }
|
||||||
|
|
||||||
|
&.character-resource--luck {
|
||||||
|
.luck-stepper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
.luck-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.4rem;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-olive;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
&:hover { color: @color-dark; }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-label {
|
.resource-label {
|
||||||
|
|||||||
77
less/free-roll.less
Normal file
77
less/free-roll.less
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// ============================================================
|
||||||
|
// FREE ROLL BAR — injected below the chat log
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
.oh-free-roll-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-top: 1px solid fade(@color-olive, 40%);
|
||||||
|
background: fade(@color-olive, 8%);
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.oh-frb-label {
|
||||||
|
font-family: @font-secondary;
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-dark;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-frb-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
padding: 1px 2px;
|
||||||
|
height: 1.6rem;
|
||||||
|
border: 1px solid @color-olive;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-frb-pool { width: 3.8rem; }
|
||||||
|
.oh-frb-color { flex: 1; min-width: 6rem; }
|
||||||
|
|
||||||
|
.oh-frb-explode-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: @color-dark;
|
||||||
|
|
||||||
|
input[type="checkbox"] { cursor: pointer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-frb-roll-btn {
|
||||||
|
font-family: @font-secondary;
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 2px 8px;
|
||||||
|
height: 1.6rem;
|
||||||
|
border: 1px solid @color-olive;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: @color-olive-faint;
|
||||||
|
color: @color-dark;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
&:hover { background: @color-gold; border-color: darken(@color-gold, 10%); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,3 +14,4 @@
|
|||||||
@import "settlement-sheet";
|
@import "settlement-sheet";
|
||||||
@import "party-sheet";
|
@import "party-sheet";
|
||||||
@import "army-sheet";
|
@import "army-sheet";
|
||||||
|
@import "free-roll";
|
||||||
|
|||||||
@@ -191,7 +191,7 @@
|
|||||||
|
|
||||||
.item-list--oath {
|
.item-list--oath {
|
||||||
.item-list-header, .item-entry {
|
.item-list-header, .item-entry {
|
||||||
grid-template-columns: @item-img-size 1fr 7rem 3.5rem 3.5rem;
|
grid-template-columns: @item-img-size 12rem 1fr 3.5rem 3.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,7 @@
|
|||||||
.oathhammer .npc-main .regiment-vitals-grid {
|
.oathhammer .npc-main .regiment-vitals-grid {
|
||||||
&.regiment-row1 { grid-template-columns: 1fr 1fr 1fr; }
|
&.regiment-row1 { grid-template-columns: 1fr 1fr 1fr; }
|
||||||
&.regiment-row2 {
|
&.regiment-row2 {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
|
|||||||
@@ -132,13 +132,43 @@
|
|||||||
.party-currency-sp .currency-label { color: #888; }
|
.party-currency-sp .currency-label { color: #888; }
|
||||||
.party-currency-cp .currency-label { color: #aa6633; }
|
.party-currency-cp .currency-label { color: #aa6633; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.party-slots {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
font-family: @font-secondary;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
|
||||||
|
.party-slots-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-olive;
|
||||||
|
margin-right: 0.3rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
.party-slots-current {
|
||||||
|
font-weight: bold;
|
||||||
|
min-width: 1.8rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.party-slots-sep { color: @color-olive; }
|
||||||
|
.party-slots-max {
|
||||||
|
width: 3.5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-family: @font-secondary;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Member list ────────────────────────────────────────────────
|
// ── Member list ────────────────────────────────────────────────
|
||||||
.oathhammer .item-list--party-member {
|
.oathhammer .item-list--party-member {
|
||||||
.item-list-header, .item-entry {
|
.item-list-header, .item-entry {
|
||||||
// order# | img | name | class | level | grit | actions
|
// order# | img | name | lineage | grit | light | actions
|
||||||
grid-template-columns: 1.8rem @item-img-size 1fr 7rem 3rem 5rem 5.5rem;
|
grid-template-columns: 1.8rem @item-img-size 1fr 7rem 5rem 3rem 5.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.party-member-order {
|
.party-member-order {
|
||||||
@@ -149,6 +179,12 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-detail--center {
|
||||||
|
text-align: center;
|
||||||
|
a { color: @color-dark; }
|
||||||
|
.fa-faded { opacity: 0.2; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Loot list ──────────────────────────────────────────────────
|
// ── Loot list ──────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -56,10 +56,11 @@ export function injectFreeRollBar(_chatLog, html) {
|
|||||||
rollFree(pool, color, explode5)
|
rollFree(pool, color, explode5)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Insert between .chat-scroll and .chat-form
|
// Insert before the chat form — use chatForm.parentElement for AppV2 compatibility
|
||||||
|
// (in v13 parts are nested inside the app element, not direct children)
|
||||||
const chatForm = html.querySelector(".chat-form")
|
const chatForm = html.querySelector(".chat-form")
|
||||||
if (chatForm) {
|
if (chatForm) {
|
||||||
html.insertBefore(bar, chatForm)
|
chatForm.parentElement.insertBefore(bar, chatForm)
|
||||||
} else {
|
} else {
|
||||||
html.appendChild(bar)
|
html.appendChild(bar)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
|
|||||||
rollInitiative: OathHammerCharacterSheet.#onRollInitiative,
|
rollInitiative: OathHammerCharacterSheet.#onRollInitiative,
|
||||||
adjustQty: OathHammerCharacterSheet.#onAdjustQty,
|
adjustQty: OathHammerCharacterSheet.#onAdjustQty,
|
||||||
adjustCurrency: OathHammerCharacterSheet.#onAdjustCurrency,
|
adjustCurrency: OathHammerCharacterSheet.#onAdjustCurrency,
|
||||||
adjustStress: OathHammerCharacterSheet.#onAdjustStress,
|
adjustLuck: OathHammerCharacterSheet.#onAdjustLuck,
|
||||||
clearStress: OathHammerCharacterSheet.#onClearStress,
|
clearStress: OathHammerCharacterSheet.#onClearStress,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -410,6 +410,13 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
|
|||||||
await this.document.update({ [field]: Math.max(0, current + delta) })
|
await this.document.update({ [field]: Math.max(0, current + delta) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onAdjustLuck(event, target) {
|
||||||
|
const delta = parseInt(target.dataset.delta, 10)
|
||||||
|
const current = this.document.system.luck.value ?? 0
|
||||||
|
// No upper cap — luck can exceed max (e.g. from blessings/bonuses)
|
||||||
|
await this.document.update({ "system.luck.value": Math.max(0, current + delta) })
|
||||||
|
}
|
||||||
|
|
||||||
static async #onAdjustStress(event, target) {
|
static async #onAdjustStress(event, target) {
|
||||||
const delta = parseInt(target.dataset.delta, 10)
|
const delta = parseInt(target.dataset.delta, 10)
|
||||||
const current = this.document.system.arcaneStress.value ?? 0
|
const current = this.document.system.arcaneStress.value ?? 0
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
colorType: attack.system.colorDiceType,
|
colorType: attack.system.colorDiceType,
|
||||||
threshold: attack.system.threshold,
|
threshold: attack.system.threshold,
|
||||||
bonusOptions,
|
bonusOptions,
|
||||||
|
showExplodeOn5: true,
|
||||||
colorChoices: Object.fromEntries(
|
colorChoices: Object.fromEntries(
|
||||||
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
),
|
),
|
||||||
@@ -194,6 +195,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
|
|
||||||
await rollNPCAttackDamage(this.document, attack, {
|
await rollNPCAttackDamage(this.document, attack, {
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -228,6 +230,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
itemName: spell.name, itemImg: spell.img,
|
itemName: spell.name, itemImg: spell.img,
|
||||||
dv: spell.system.difficultyValue,
|
dv: spell.system.difficultyValue,
|
||||||
poolOptions, bonusOptions, colorChoices, showColor: true,
|
poolOptions, bonusOptions, colorChoices, showColor: true,
|
||||||
|
showExplodeOn5: true,
|
||||||
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
visibility: game.settings.get("core", "rollMode"),
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
}
|
}
|
||||||
@@ -249,6 +252,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
dicePool: parseInt(getValue("dicePool")) || 3,
|
dicePool: parseInt(getValue("dicePool")) || 3,
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
colorOverride: getValue("colorOverride") || null,
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -272,6 +276,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
itemName: miracle.name, itemImg: miracle.img,
|
itemName: miracle.name, itemImg: miracle.img,
|
||||||
dv: null, showColor: false,
|
dv: null, showColor: false,
|
||||||
poolOptions, bonusOptions,
|
poolOptions, bonusOptions,
|
||||||
|
showExplodeOn5: true,
|
||||||
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
visibility: game.settings.get("core", "rollMode"),
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
}
|
}
|
||||||
@@ -292,6 +297,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
await rollNPCMiracle(this.document, miracle, {
|
await rollNPCMiracle(this.document, miracle, {
|
||||||
dicePool: parseInt(getValue("dicePool")) || 3,
|
dicePool: parseInt(getValue("dicePool")) || 3,
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -323,6 +329,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
threshold,
|
threshold,
|
||||||
bonusOptions,
|
bonusOptions,
|
||||||
colorChoices,
|
colorChoices,
|
||||||
|
showExplodeOn5: true,
|
||||||
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
visibility: game.settings.get("core", "rollMode"),
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
}
|
}
|
||||||
@@ -343,6 +350,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
await rollNPCArmor(actor, {
|
await rollNPCArmor(actor, {
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
colorOverride: getValue("colorOverride") || null,
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -381,6 +389,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
threshold: item.system.threshold,
|
threshold: item.system.threshold,
|
||||||
bonusOptions,
|
bonusOptions,
|
||||||
colorChoices,
|
colorChoices,
|
||||||
|
showExplodeOn5: true,
|
||||||
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
visibility: game.settings.get("core", "rollMode"),
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
}
|
}
|
||||||
@@ -401,6 +410,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
await rollNPCSkill(this.document, item, {
|
await rollNPCSkill(this.document, item, {
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
colorOverride: getValue("colorOverride") || null,
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default class OathHammerPartySheet extends OathHammerActorSheet {
|
|||||||
removeMember: OathHammerPartySheet.#onRemoveMember,
|
removeMember: OathHammerPartySheet.#onRemoveMember,
|
||||||
moveMemberUp: OathHammerPartySheet.#onMoveMemberUp,
|
moveMemberUp: OathHammerPartySheet.#onMoveMemberUp,
|
||||||
moveMemberDown: OathHammerPartySheet.#onMoveMemberDown,
|
moveMemberDown: OathHammerPartySheet.#onMoveMemberDown,
|
||||||
|
toggleCarriesLight: OathHammerPartySheet.#onToggleCarriesLight,
|
||||||
adjustCurrency: OathHammerPartySheet.#onAdjustCurrency,
|
adjustCurrency: OathHammerPartySheet.#onAdjustCurrency,
|
||||||
adjustQty: OathHammerPartySheet.#onAdjustQty,
|
adjustQty: OathHammerPartySheet.#onAdjustQty,
|
||||||
},
|
},
|
||||||
@@ -53,27 +54,40 @@ export default class OathHammerPartySheet extends OathHammerActorSheet {
|
|||||||
async _preparePartContext(partId, context) {
|
async _preparePartContext(partId, context) {
|
||||||
const doc = this.document
|
const doc = this.document
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case "main":
|
case "main": {
|
||||||
|
const lootItems = doc.items.contents.filter(i => ALLOWED_LOOT_TYPES.has(i.type))
|
||||||
|
context.currentSlots = lootItems.reduce((sum, i) => {
|
||||||
|
const slots = i.system.slots ?? 0
|
||||||
|
const qty = i.system.quantity ?? 1
|
||||||
|
return sum + slots * qty
|
||||||
|
}, 0)
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case "members": {
|
case "members": {
|
||||||
context.tab = context.tabs.members
|
context.tab = context.tabs.members
|
||||||
const refs = doc.system.memberRefs ?? []
|
const refs = doc.system.memberRefs ?? []
|
||||||
context.members = refs.map((id, idx) => {
|
context.members = refs.map((ref, idx) => {
|
||||||
const actor = game.actors?.get(id)
|
const actor = game.actors?.get(ref.id)
|
||||||
if (!actor) return null
|
if (!actor) return null
|
||||||
const sys = actor.system
|
const sys = actor.system
|
||||||
const classItem = actor.items?.find(i => i.type === "class")
|
const isNpc = actor.type === "npc"
|
||||||
|
const classItem = !isNpc ? actor.items?.find(i => i.type === "class") : null
|
||||||
return {
|
return {
|
||||||
id: actor.id,
|
id: actor.id,
|
||||||
name: actor.name,
|
name: actor.name,
|
||||||
img: actor.img,
|
img: actor.img,
|
||||||
|
type: actor.type,
|
||||||
idx,
|
idx,
|
||||||
position: idx + 1,
|
position: idx + 1,
|
||||||
isFirst: idx === 0,
|
isFirst: idx === 0,
|
||||||
isLast: idx === refs.length - 1,
|
isLast: idx === refs.length - 1,
|
||||||
classLabel: classItem?.name ?? "—",
|
carriesLight: ref.carriesLight ?? false,
|
||||||
level: sys.level ?? "—",
|
classLabel: isNpc
|
||||||
|
? game.i18n.localize(`OATHHAMMER.NpcSubtype.${sys.subtype === "creature" ? "Creature" : "Npc"}`)
|
||||||
|
: (classItem?.name ?? "—"),
|
||||||
|
lineage: !isNpc ? (sys.lineage?.name || "—") : "—",
|
||||||
|
current: !isNpc ? (sys.experience?.current ?? "—") : "—",
|
||||||
grit: sys.grit ? `${sys.grit.value}/${sys.grit.max}` : "—",
|
grit: sys.grit ? `${sys.grit.value}/${sys.grit.max}` : "—",
|
||||||
}
|
}
|
||||||
}).filter(Boolean)
|
}).filter(Boolean)
|
||||||
@@ -109,10 +123,10 @@ export default class OathHammerPartySheet extends OathHammerActorSheet {
|
|||||||
|
|
||||||
if (data.type === "Actor") {
|
if (data.type === "Actor") {
|
||||||
const actor = await fromUuid(data.uuid)
|
const actor = await fromUuid(data.uuid)
|
||||||
if (!actor || actor.type !== "character") return
|
if (!actor || !["character", "npc"].includes(actor.type)) return
|
||||||
const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? [])
|
const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? [])
|
||||||
if (refs.includes(actor.id)) return
|
if (refs.some(r => r.id === actor.id)) return
|
||||||
refs.push(actor.id)
|
refs.push({ id: actor.id, carriesLight: false })
|
||||||
return this.document.update({ "system.memberRefs": refs })
|
return this.document.update({ "system.memberRefs": refs })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +146,7 @@ export default class OathHammerPartySheet extends OathHammerActorSheet {
|
|||||||
|
|
||||||
static async #onRemoveMember(event, target) {
|
static async #onRemoveMember(event, target) {
|
||||||
const id = target.dataset.actorId
|
const id = target.dataset.actorId
|
||||||
const refs = (this.document.system.memberRefs ?? []).filter(r => r !== id)
|
const refs = (this.document.system.memberRefs ?? []).filter(r => r.id !== id)
|
||||||
await this.document.update({ "system.memberRefs": refs })
|
await this.document.update({ "system.memberRefs": refs })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +166,14 @@ export default class OathHammerPartySheet extends OathHammerActorSheet {
|
|||||||
await this.document.update({ "system.memberRefs": refs })
|
await this.document.update({ "system.memberRefs": refs })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onToggleCarriesLight(event, target) {
|
||||||
|
const idx = parseInt(target.dataset.idx, 10)
|
||||||
|
const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? [])
|
||||||
|
if (!refs[idx]) return
|
||||||
|
refs[idx].carriesLight = !refs[idx].carriesLight
|
||||||
|
await this.document.update({ "system.memberRefs": refs })
|
||||||
|
}
|
||||||
|
|
||||||
static async #onAdjustCurrency(event, target) {
|
static async #onAdjustCurrency(event, target) {
|
||||||
const field = target.dataset.field
|
const field = target.dataset.field
|
||||||
const delta = parseInt(target.dataset.delta, 10)
|
const delta = parseInt(target.dataset.delta, 10)
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
context.colorChoices = Object.fromEntries(
|
context.colorChoices = Object.fromEntries(
|
||||||
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
)
|
)
|
||||||
|
context.traitTypeLabels = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.TRAIT_TYPE_CHOICES).map(([k, v]) => [k, v])
|
||||||
|
)
|
||||||
// Resolve leader actor
|
// Resolve leader actor
|
||||||
const leaderUuid = this.document.system.leaderUuid
|
const leaderUuid = this.document.system.leaderUuid
|
||||||
if (leaderUuid) {
|
if (leaderUuid) {
|
||||||
@@ -75,7 +78,7 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
break
|
break
|
||||||
case "skills":
|
case "skills":
|
||||||
context.tab = context.tabs.skills
|
context.tab = context.tabs.skills
|
||||||
context.skills = doc.itemTypes.skillnpc ?? []
|
context.skills = (doc.itemTypes.skillnpc ?? []).slice().sort((a, b) => a.name.localeCompare(b.name))
|
||||||
break
|
break
|
||||||
case "combat":
|
case "combat":
|
||||||
context.tab = context.tabs.combat
|
context.tab = context.tabs.combat
|
||||||
@@ -141,6 +144,9 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
const doc = this.document
|
const doc = this.document
|
||||||
const armorDice = doc.system.armorDice
|
const armorDice = doc.system.armorDice
|
||||||
if (!armorDice?.value) return ui.notifications.info("No armor dice to roll.")
|
if (!armorDice?.value) return ui.notifications.info("No armor dice to roll.")
|
||||||
|
const colorType = armorDice.colorDiceType || "white"
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
const v = i - 6
|
const v = i - 6
|
||||||
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
@@ -149,9 +155,10 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
||||||
{
|
{
|
||||||
skillName: game.i18n.localize("OATHHAMMER.Label.ArmorDice"),
|
skillName: game.i18n.localize("OATHHAMMER.Label.ArmorDice"),
|
||||||
skillImg: doc.img, basePool: armorDice.value, bonusOptions,
|
skillImg: doc.img, dicePool: armorDice.value,
|
||||||
|
colorType, colorEmoji, threshold, bonusOptions,
|
||||||
colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" },
|
colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" },
|
||||||
selectedColor: armorDice.colorDiceType,
|
showExplodeOn5: true,
|
||||||
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
visibility: game.settings.get("core", "rollMode")
|
visibility: game.settings.get("core", "rollMode")
|
||||||
}
|
}
|
||||||
@@ -167,6 +174,7 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
await rollNPCArmor(doc, {
|
await rollNPCArmor(doc, {
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
colorOverride: getValue("colorOverride") || null,
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -174,6 +182,9 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
static async #onRollSkillNPC(event, target) {
|
static async #onRollSkillNPC(event, target) {
|
||||||
const skill = this.document.items.get(target.dataset.itemId)
|
const skill = this.document.items.get(target.dataset.itemId)
|
||||||
if (!skill) return
|
if (!skill) return
|
||||||
|
const colorType = skill.system.colorDiceType || "white"
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
const v = i - 6
|
const v = i - 6
|
||||||
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
@@ -181,9 +192,10 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
||||||
{
|
{
|
||||||
skillName: skill.name, skillImg: skill.img, basePool: skill.system.dicePool, bonusOptions,
|
skillName: skill.name, skillImg: skill.img, dicePool: skill.system.dicePool,
|
||||||
|
colorType, colorEmoji, threshold, bonusOptions,
|
||||||
colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" },
|
colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" },
|
||||||
selectedColor: skill.system.colorDiceType,
|
showExplodeOn5: true,
|
||||||
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
visibility: game.settings.get("core", "rollMode")
|
visibility: game.settings.get("core", "rollMode")
|
||||||
}
|
}
|
||||||
@@ -199,6 +211,7 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
await rollNPCSkill(this.document, skill, {
|
await rollNPCSkill(this.document, skill, {
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
colorOverride: getValue("colorOverride") || null,
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -212,6 +225,9 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
static async #onRollNpcAttack(event, target) {
|
static async #onRollNpcAttack(event, target) {
|
||||||
const attack = this.document.items.get(target.dataset.itemId)
|
const attack = this.document.items.get(target.dataset.itemId)
|
||||||
if (!attack) return
|
if (!attack) return
|
||||||
|
const colorType = attack.system.colorDiceType || "white"
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
const v = i - 6
|
const v = i - 6
|
||||||
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
@@ -219,9 +235,11 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
||||||
{
|
{
|
||||||
skillName: attack.name, skillImg: attack.img, basePool: attack.system.damageDice, bonusOptions,
|
skillName: attack.name, skillImg: attack.img,
|
||||||
|
dicePool: attack.system.damageDice,
|
||||||
|
colorType, colorEmoji, threshold, bonusOptions,
|
||||||
|
showExplodeOn5: true,
|
||||||
colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" },
|
colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" },
|
||||||
selectedColor: attack.system.colorDiceType,
|
|
||||||
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
visibility: game.settings.get("core", "rollMode")
|
visibility: game.settings.get("core", "rollMode")
|
||||||
}
|
}
|
||||||
@@ -237,6 +255,7 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
|
|||||||
await rollNPCAttackDamage(this.document, attack, {
|
await rollNPCAttackDamage(this.document, attack, {
|
||||||
bonus: parseInt(getValue("bonus")) || 0,
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
colorOverride: getValue("colorOverride") || null,
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
explodeOn5: getValue("explodeOn5") === "true",
|
||||||
visibility: getValue("visibility"),
|
visibility: getValue("visibility"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,8 @@ export const TRAIT_TYPE_CHOICES = {
|
|||||||
"class-trait": "OATHHAMMER.TraitType.ClassTrait",
|
"class-trait": "OATHHAMMER.TraitType.ClassTrait",
|
||||||
"lineage-trait": "OATHHAMMER.TraitType.LineageTrait",
|
"lineage-trait": "OATHHAMMER.TraitType.LineageTrait",
|
||||||
"npc-trait": "OATHHAMMER.TraitType.NpcTrait",
|
"npc-trait": "OATHHAMMER.TraitType.NpcTrait",
|
||||||
"creature-trait": "OATHHAMMER.TraitType.CreatureTrait"
|
"creature-trait": "OATHHAMMER.TraitType.CreatureTrait",
|
||||||
|
"regiment-trait": "OATHHAMMER.TraitType.RegimentTrait"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NPC_SUBTYPES = {
|
export const NPC_SUBTYPES = {
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ export default class OathHammerParty extends foundry.abstract.TypeDataModel {
|
|||||||
|
|
||||||
schema.notes = new fields.HTMLField({ required: false, nullable: true, initial: "" })
|
schema.notes = new fields.HTMLField({ required: false, nullable: true, initial: "" })
|
||||||
|
|
||||||
// Ordered list of character actor IDs — position = marching order
|
// Ordered list of member entries — position = marching order
|
||||||
schema.memberRefs = new fields.ArrayField(
|
schema.memberRefs = new fields.ArrayField(
|
||||||
new fields.StringField({ required: true, nullable: false, blank: false })
|
new fields.SchemaField({
|
||||||
|
id: new fields.StringField({ required: true, nullable: false, blank: false }),
|
||||||
|
carriesLight: new fields.BooleanField({ initial: false }),
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
schema.treasury = new fields.SchemaField({
|
schema.treasury = new fields.SchemaField({
|
||||||
@@ -17,8 +20,19 @@ export default class OathHammerParty extends foundry.abstract.TypeDataModel {
|
|||||||
cp: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
cp: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
schema.maxSlots = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static migrateData(source) {
|
||||||
|
if (Array.isArray(source.memberRefs)) {
|
||||||
|
source.memberRefs = source.memberRefs.map(r =>
|
||||||
|
typeof r === "string" ? { id: r, carriesLight: false } : r
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return super.migrateData(source)
|
||||||
|
}
|
||||||
|
|
||||||
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Party"]
|
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Party"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default class OathHammerRegiment extends foundry.abstract.TypeDataModel {
|
|||||||
schema.movement = new fields.NumberField({ ...requiredInteger, initial: 60, min: 0, max: 500 })
|
schema.movement = new fields.NumberField({ ...requiredInteger, initial: 60, min: 0, max: 500 })
|
||||||
schema.supplyCost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
schema.supplyCost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
schema.recruitmentCost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
schema.recruitmentCost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
schema.mercenary = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
schema.leaderUuid = new fields.StringField({ required: false, nullable: true, initial: null })
|
schema.leaderUuid = new fields.StringField({ required: false, nullable: true, initial: null })
|
||||||
|
|
||||||
|
|||||||
@@ -915,7 +915,15 @@ export async function rollInitiativeCheck(actor, options = {}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NPC: Fate rank + initiativeBonus
|
// NPC: find Leadership skillnpc item, fall back to Fate rank + initiativeBonus
|
||||||
|
const leadershipSkill = actor.items.find(
|
||||||
|
i => i.type === "skillnpc" && i.name.toLowerCase() === "leadership"
|
||||||
|
)
|
||||||
|
if (leadershipSkill) {
|
||||||
|
return rollNPCSkill(actor, leadershipSkill, { bonus, visibility })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: Fate rank + initiativeBonus
|
||||||
const sys = actor.system
|
const sys = actor.system
|
||||||
const fateRank = sys.attributes?.fate?.rank ?? 1
|
const fateRank = sys.attributes?.fate?.rank ?? 1
|
||||||
const initBonus = sys.initiativeBonus ?? 0
|
const initBonus = sys.initiativeBonus ?? 0
|
||||||
@@ -967,7 +975,7 @@ export async function rollInitiativeCheck(actor, options = {}) {
|
|||||||
* @param {object} options
|
* @param {object} options
|
||||||
*/
|
*/
|
||||||
export async function rollNPCSkill(actor, skillItem, options = {}) {
|
export async function rollNPCSkill(actor, skillItem, options = {}) {
|
||||||
const { bonus = 0, colorOverride, visibility } = options
|
const { bonus = 0, colorOverride, visibility, explodeOn5 = false } = options
|
||||||
const sys = skillItem.system
|
const sys = skillItem.system
|
||||||
|
|
||||||
const colorType = colorOverride || sys.colorDiceType
|
const colorType = colorOverride || sys.colorDiceType
|
||||||
@@ -976,12 +984,14 @@ export async function rollNPCSkill(actor, skillItem, options = {}) {
|
|||||||
|
|
||||||
const totalDice = Math.max(sys.dicePool + bonus, 1)
|
const totalDice = Math.max(sys.dicePool + bonus, 1)
|
||||||
|
|
||||||
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
|
||||||
const diceHtml = _diceHtml(diceResults, threshold)
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
const modLine = bonus !== 0
|
const explodedCount = diceResults.filter(d => d.exploded).length
|
||||||
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
const modParts = []
|
||||||
: ""
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||||||
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
const content = `
|
const content = `
|
||||||
<div class="oh-roll-card">
|
<div class="oh-roll-card">
|
||||||
@@ -1025,6 +1035,7 @@ export async function rollNPCWeaponAttack(actor, weapon, options = {}) {
|
|||||||
|
|
||||||
const modParts = []
|
const modParts = []
|
||||||
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
if (diceResults.filter(d => d.exploded).length > 0) modParts.push(`💥 ${diceResults.filter(d => d.exploded).length} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||||||
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
const content = `
|
const content = `
|
||||||
@@ -1067,6 +1078,7 @@ export async function rollNPCWeaponDamage(actor, weapon, options = {}) {
|
|||||||
|
|
||||||
const modParts = []
|
const modParts = []
|
||||||
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
if (diceResults.filter(d => d.exploded).length > 0) modParts.push(`💥 ${diceResults.filter(d => d.exploded).length} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||||||
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
const content = `
|
const content = `
|
||||||
@@ -1098,7 +1110,7 @@ export async function rollNPCWeaponDamage(actor, weapon, options = {}) {
|
|||||||
* NPC armor dice roll — rolls actor's armorDice.value dice with armorDice.colorDiceType color.
|
* NPC armor dice roll — rolls actor's armorDice.value dice with armorDice.colorDiceType color.
|
||||||
*/
|
*/
|
||||||
export async function rollNPCArmor(actor, options = {}) {
|
export async function rollNPCArmor(actor, options = {}) {
|
||||||
const { bonus = 0, colorOverride, visibility } = options
|
const { bonus = 0, colorOverride, visibility, explodeOn5 = false } = options
|
||||||
const sys = actor.system
|
const sys = actor.system
|
||||||
|
|
||||||
const basePool = sys.armorDice?.value ?? 0
|
const basePool = sys.armorDice?.value ?? 0
|
||||||
@@ -1107,12 +1119,15 @@ export async function rollNPCArmor(actor, options = {}) {
|
|||||||
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
const totalDice = Math.max(basePool + bonus, 1)
|
const totalDice = Math.max(basePool + bonus, 1)
|
||||||
|
|
||||||
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
|
||||||
const diceHtml = _diceHtml(diceResults, threshold)
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
const modLine = bonus !== 0
|
const explodedCount = diceResults.filter(d => d.exploded).length
|
||||||
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
const modParts = []
|
||||||
: ""
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
|
||||||
|
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||||||
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
const label = game.i18n.localize("OATHHAMMER.Label.ArmorDice")
|
const label = game.i18n.localize("OATHHAMMER.Label.ArmorDice")
|
||||||
const content = `
|
const content = `
|
||||||
@@ -1144,20 +1159,23 @@ export async function rollNPCArmor(actor, options = {}) {
|
|||||||
* NPC spell cast — flat dice pool, no arcane stress, posts DV success/failure to chat.
|
* NPC spell cast — flat dice pool, no arcane stress, posts DV success/failure to chat.
|
||||||
*/
|
*/
|
||||||
export async function rollNPCSpell(actor, spell, options = {}) {
|
export async function rollNPCSpell(actor, spell, options = {}) {
|
||||||
const { dicePool = 3, bonus = 0, colorOverride, visibility } = options
|
const { dicePool = 3, bonus = 0, colorOverride, visibility, explodeOn5 = false } = options
|
||||||
const dv = spell.system.difficultyValue ?? 1
|
const dv = spell.system.difficultyValue ?? 1
|
||||||
const colorType = colorOverride || "white"
|
const colorType = colorOverride || "white"
|
||||||
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
const totalDice = Math.max(dicePool + bonus, 1)
|
const totalDice = Math.max(dicePool + bonus, 1)
|
||||||
|
|
||||||
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
|
||||||
const diceHtml = _diceHtml(diceResults, threshold)
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
const isSuccess = successes >= dv
|
const isSuccess = successes >= dv
|
||||||
|
|
||||||
const modLine = bonus !== 0
|
const explodedCount = diceResults.filter(d => d.exploded).length
|
||||||
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
const modParts = []
|
||||||
: ""
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
|
||||||
|
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||||||
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
const resultClass = isSuccess ? "roll-success" : "roll-failure"
|
const resultClass = isSuccess ? "roll-success" : "roll-failure"
|
||||||
const resultLabel = isSuccess
|
const resultLabel = isSuccess
|
||||||
@@ -1193,19 +1211,22 @@ export async function rollNPCSpell(actor, spell, options = {}) {
|
|||||||
* NPC miracle invocation — flat dice pool, no blocked tracking, posts DV success/failure to chat.
|
* NPC miracle invocation — flat dice pool, no blocked tracking, posts DV success/failure to chat.
|
||||||
*/
|
*/
|
||||||
export async function rollNPCMiracle(actor, miracle, options = {}) {
|
export async function rollNPCMiracle(actor, miracle, options = {}) {
|
||||||
const { dicePool = 3, bonus = 0, visibility } = options
|
const { dicePool = 3, bonus = 0, visibility, explodeOn5 = false } = options
|
||||||
const dv = 1
|
const dv = 1
|
||||||
const threshold = 4
|
const threshold = 4
|
||||||
const colorEmoji = "⬜"
|
const colorEmoji = "⬜"
|
||||||
const totalDice = Math.max(dicePool + bonus, 1)
|
const totalDice = Math.max(dicePool + bonus, 1)
|
||||||
|
|
||||||
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
|
||||||
const diceHtml = _diceHtml(diceResults, threshold)
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
const isSuccess = successes >= dv
|
const isSuccess = successes >= dv
|
||||||
|
|
||||||
const modLine = bonus !== 0
|
const explodedCount = diceResults.filter(d => d.exploded).length
|
||||||
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
const modParts = []
|
||||||
: ""
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
|
||||||
|
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||||||
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
const resultClass = isSuccess ? "roll-success" : "roll-failure"
|
const resultClass = isSuccess ? "roll-success" : "roll-failure"
|
||||||
const resultLabel = isSuccess
|
const resultLabel = isSuccess
|
||||||
@@ -1241,7 +1262,7 @@ export async function rollNPCMiracle(actor, miracle, options = {}) {
|
|||||||
* NPC attack damage roll — flat dice pool from the npcattack item, no Might.
|
* NPC attack damage roll — flat dice pool from the npcattack item, no Might.
|
||||||
*/
|
*/
|
||||||
export async function rollNPCAttackDamage(actor, attack, options = {}) {
|
export async function rollNPCAttackDamage(actor, attack, options = {}) {
|
||||||
const { bonus = 0, visibility } = options
|
const { bonus = 0, visibility, explodeOn5 = false } = options
|
||||||
const sys = attack.system
|
const sys = attack.system
|
||||||
const colorType = sys.colorDiceType || "white"
|
const colorType = sys.colorDiceType || "white"
|
||||||
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
@@ -1249,12 +1270,15 @@ export async function rollNPCAttackDamage(actor, attack, options = {}) {
|
|||||||
const totalDice = Math.max((sys.damageDice ?? 1) + bonus, 1)
|
const totalDice = Math.max((sys.damageDice ?? 1) + bonus, 1)
|
||||||
const ap = sys.ap ?? 0
|
const ap = sys.ap ?? 0
|
||||||
|
|
||||||
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5)
|
||||||
const diceHtml = _diceHtml(diceResults, threshold)
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
|
const explodedCount = diceResults.filter(d => d.exploded).length
|
||||||
const modParts = []
|
const modParts = []
|
||||||
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
if (ap > 0) modParts.push(`AP ${ap}`)
|
if (ap > 0) modParts.push(`AP ${ap}`)
|
||||||
|
if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
|
||||||
|
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||||||
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
const content = `
|
const content = `
|
||||||
|
|||||||
@@ -104,24 +104,45 @@ Hooks.once("init", function () {
|
|||||||
Hooks.once("ready", async function () {
|
Hooks.once("ready", async function () {
|
||||||
console.info("Oath Hammer | System Ready")
|
console.info("Oath Hammer | System Ready")
|
||||||
|
|
||||||
// Migration: remove orphaned items with removed types (lineage → actor field, ability → trait)
|
// Migration: remove orphaned items with removed types (lineage → actor field, ability → trait, regiment → actor type)
|
||||||
const removedTypes = new Set(["lineage", "ability"])
|
const removedTypes = new Set(["lineage", "ability", "regiment"])
|
||||||
for (const actor of game.actors) {
|
for (const actor of game.actors) {
|
||||||
const invalidItems = actor._source.items?.filter(i => removedTypes.has(i.type)) ?? []
|
const toDelete = []
|
||||||
if (invalidItems.length) {
|
|
||||||
console.info(`Oath Hammer | Migrating ${actor.name}: removing ${invalidItems.length} obsolete item(s)`)
|
// Catch items that failed validation and landed in invalidDocumentIds
|
||||||
await actor.deleteEmbeddedDocuments("Item", invalidItems.map(i => i._id))
|
for (const id of (actor.items.invalidDocumentIds ?? [])) {
|
||||||
|
const raw = actor.items.getInvalid(id)
|
||||||
|
if (raw && removedTypes.has(raw._source?.type)) toDelete.push(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also catch any that slipped through in _source (belt-and-suspenders)
|
||||||
|
for (const src of (actor._source.items ?? [])) {
|
||||||
|
if (removedTypes.has(src.type) && !toDelete.includes(src._id)) toDelete.push(src._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toDelete.length) {
|
||||||
|
console.info(`Oath Hammer | Migrating ${actor.name}: removing ${toDelete.length} obsolete item(s)`)
|
||||||
|
await actor.deleteEmbeddedDocuments("Item", toDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Purge invalid world items of removed types
|
||||||
for (const id of game.items.invalidDocumentIds) {
|
for (const id of game.items.invalidDocumentIds) {
|
||||||
const item = game.items.getInvalid(id)
|
const item = game.items.getInvalid(id)
|
||||||
if (item && removedTypes.has(item._source.type)) {
|
if (item && removedTypes.has(item._source?.type)) {
|
||||||
console.info(`Oath Hammer | Deleting world item: ${item._source.name} (${item._source.type})`)
|
console.info(`Oath Hammer | Deleting world item: ${item._source.name} (${item._source.type})`)
|
||||||
await item.delete()
|
await item.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Auto-link regiment (and army) actor tokens so they can be added to garrisons/armies
|
||||||
|
Hooks.on("preCreateActor", (actor, _data, _options, _userId) => {
|
||||||
|
if (actor.type === "regiment" || actor.type === "army") {
|
||||||
|
actor.updateSource({ "prototypeToken.actorLink": true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Handle "Roll Damage" button in weapon attack chat cards
|
// Handle "Roll Damage" button in weapon attack chat cards
|
||||||
Hooks.on("renderChatMessageHTML", (message, html) => {
|
Hooks.on("renderChatMessageHTML", (message, html) => {
|
||||||
const btn = html.querySelector("[data-action=\"rollWeaponDamage\"]")
|
const btn = html.querySelector("[data-action=\"rollWeaponDamage\"]")
|
||||||
|
|||||||
@@ -54,9 +54,13 @@
|
|||||||
<span class="res-sep">/</span>
|
<span class="res-sep">/</span>
|
||||||
{{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}}
|
{{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}}
|
||||||
</div>
|
</div>
|
||||||
<div class="character-resource">
|
<div class="character-resource character-resource--luck">
|
||||||
<span class="resource-label">{{localize "OATHHAMMER.Label.Luck"}}</span>
|
<span class="resource-label">{{localize "OATHHAMMER.Label.Luck"}}</span>
|
||||||
{{formInput systemFields.luck.fields.value value=system.luck.value name="system.luck.value" disabled=isPlayMode}}
|
<div class="luck-stepper">
|
||||||
|
<a data-action="adjustLuck" data-delta="-1" class="luck-btn">−</a>
|
||||||
|
{{formInput systemFields.luck.fields.value value=system.luck.value name="system.luck.value"}}
|
||||||
|
<a data-action="adjustLuck" data-delta="1" class="luck-btn">+</a>
|
||||||
|
</div>
|
||||||
<span class="res-sep">/</span>
|
<span class="res-sep">/</span>
|
||||||
{{formInput systemFields.luck.fields.max value=system.luck.max name="system.luck.max" disabled=isPlayMode}}
|
{{formInput systemFields.luck.fields.max value=system.luck.max name="system.luck.max" disabled=isPlayMode}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
<span class="col-order">#</span>
|
<span class="col-order">#</span>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
<span>{{localize "OATHHAMMER.Label.Class"}}</span>
|
<span>{{localize "OATHHAMMER.Label.Lineage"}}</span>
|
||||||
<span>{{localize "OATHHAMMER.Label.Level"}}</span>
|
|
||||||
<span>{{localize "OATHHAMMER.Label.Grit"}}</span>
|
<span>{{localize "OATHHAMMER.Label.Grit"}}</span>
|
||||||
|
<span data-tooltip="{{localize 'OATHHAMMER.Label.CarriesLight'}}"><i class="fa-solid fa-fire"></i></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
</li>
|
</li>
|
||||||
{{#each members as |member|}}
|
{{#each members as |member|}}
|
||||||
@@ -21,9 +21,13 @@
|
|||||||
<span class="item-name">
|
<span class="item-name">
|
||||||
<a data-action="openMember" data-actor-id="{{member.id}}">{{member.name}}</a>
|
<a data-action="openMember" data-actor-id="{{member.id}}">{{member.name}}</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="item-detail item-detail--small">{{member.classLabel}}</span>
|
<span class="item-detail item-detail--small">{{member.lineage}}</span>
|
||||||
<span class="item-detail">{{member.level}}</span>
|
|
||||||
<span class="item-detail">{{member.grit}}</span>
|
<span class="item-detail">{{member.grit}}</span>
|
||||||
|
<span class="item-detail item-detail--center">
|
||||||
|
<a data-action="toggleCarriesLight" data-idx="{{member.idx}}">
|
||||||
|
<i class="fa-solid fa-fire{{#unless member.carriesLight}} fa-faded{{/unless}}"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
<div class="item-actions">
|
<div class="item-actions">
|
||||||
{{#unless member.isFirst}}
|
{{#unless member.isFirst}}
|
||||||
<a data-action="moveMemberUp" data-idx="{{member.idx}}" data-tooltip="{{localize 'OATHHAMMER.Tooltip.MoveUp'}}"><i class="fa-solid fa-chevron-up"></i></a>
|
<a data-action="moveMemberUp" data-idx="{{member.idx}}" data-tooltip="{{localize 'OATHHAMMER.Tooltip.MoveUp'}}"><i class="fa-solid fa-chevron-up"></i></a>
|
||||||
|
|||||||
@@ -48,6 +48,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div><!-- /party-treasury -->
|
</div><!-- /party-treasury -->
|
||||||
|
|
||||||
|
<!-- Slots -->
|
||||||
|
<div class="party-slots">
|
||||||
|
<span class="party-slots-label">{{localize "OATHHAMMER.Label.Slots"}}</span>
|
||||||
|
<span class="party-slots-current">{{currentSlots}}</span>
|
||||||
|
<span class="party-slots-sep">/</span>
|
||||||
|
<input class="party-slots-max" type="number" name="system.maxSlots" value="{{system.maxSlots}}" min="0" {{#if isPlayMode}}disabled{{/if}} />
|
||||||
|
</div>
|
||||||
</div><!-- /party-header-body -->
|
</div><!-- /party-header-body -->
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
<span class="vital-label">{{localize "OATHHAMMER.Label.SupplyCost"}}</span>
|
<span class="vital-label">{{localize "OATHHAMMER.Label.SupplyCost"}}</span>
|
||||||
<span class="vital-value">
|
<span class="vital-value">
|
||||||
<input type="number" class="npc-num-input" name="system.supplyCost" value="{{system.supplyCost}}" min="0" {{#if isPlayMode}}disabled{{/if}} />
|
<input type="number" class="npc-num-input" name="system.supplyCost" value="{{system.supplyCost}}" min="0" {{#if isPlayMode}}disabled{{/if}} />
|
||||||
<span class="res-sep">gp / month</span>
|
<span class="res-sep">gp / day</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -83,6 +83,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mercenary -->
|
||||||
|
<div class="npc-vital regiment-mercenary-vital">
|
||||||
|
<span class="vital-label">{{localize "OATHHAMMER.Label.Mercenary"}}</span>
|
||||||
|
<span class="vital-value">
|
||||||
|
{{formInput systemFields.mercenary value=system.mercenary name="system.mercenary" disabled=isPlayMode}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div><!-- /row2 -->
|
</div><!-- /row2 -->
|
||||||
|
|
||||||
<!-- Leader -->
|
<!-- Leader -->
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<section data-tab="overview" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
<section data-tab="overview" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
|
|
||||||
|
<div class="settlement-overview-grid">
|
||||||
<fieldset class="currency-bar">
|
<fieldset class="currency-bar">
|
||||||
<legend>{{localize "OATHHAMMER.Label.Treasury"}}</legend>
|
<legend>{{localize "OATHHAMMER.Label.Treasury"}}</legend>
|
||||||
<div class="flexrow">
|
<div class="flexrow">
|
||||||
@@ -14,12 +15,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</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>
|
<fieldset>
|
||||||
<legend>{{localize "OATHHAMMER.Label.Founded"}}</legend>
|
<legend>{{localize "OATHHAMMER.Label.Founded"}}</legend>
|
||||||
{{formInput systemFields.founded value=system.founded name="system.founded" disabled=isPlayMode}}
|
{{formInput systemFields.founded value=system.founded name="system.founded" disabled=isPlayMode}}
|
||||||
@@ -33,13 +28,7 @@
|
|||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
|
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
|
||||||
{{#if isEditMode}}
|
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
||||||
<prose-mirror name="system.description" toggled="false" collaborate="false">
|
|
||||||
{{{system.description}}}
|
|
||||||
</prose-mirror>
|
|
||||||
{{else}}
|
|
||||||
<div class="editor-content">{{{enrichedDescription}}}</div>
|
|
||||||
{{/if}}
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -37,6 +37,11 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.ExplodeOn5"}}</label>
|
||||||
|
<input type="checkbox" name="explodeOn5" value="true" />
|
||||||
|
</div>
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="roll-visibility-block">
|
<fieldset class="roll-visibility-block">
|
||||||
|
|||||||
@@ -28,6 +28,13 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if showExplodeOn5}}
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.ExplodeOn5"}}</label>
|
||||||
|
<input type="checkbox" name="explodeOn5" value="true" />
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="roll-visibility-block">
|
<fieldset class="roll-visibility-block">
|
||||||
|
|||||||
Reference in New Issue
Block a user