Nombreuses corrections sur les fiches settlement/NPC
This commit is contained in:
@@ -646,19 +646,241 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.oathhammer .npc-main .npc-left {
|
.oathhammer .stress-controls {
|
||||||
min-width: 160px;
|
|
||||||
max-width: 160px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.oathhammer .stress-controls .stress-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border: 1px solid #535128;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(42, 26, 10, 0.06);
|
||||||
|
color: #2a1a0a;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.oathhammer .stress-controls .stress-btn:hover {
|
||||||
|
background: rgba(8, 74, 116, 0.15);
|
||||||
|
border-color: #084a74;
|
||||||
|
}
|
||||||
|
.oathhammer .stress-controls .arcane-stress-display {
|
||||||
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
|
font-size: calc(0.86rem * 1.1);
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2a1a0a;
|
||||||
|
min-width: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .stress-controls .arcane-stress-display.stress-at-limit {
|
||||||
|
color: #c0392b;
|
||||||
|
}
|
||||||
|
.oathhammer .stress-controls .stress-bonus-label {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
color: #535128;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .stress-controls .stress-bonus-input {
|
||||||
|
width: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-left {
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: 120px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-left .actor-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 170px;
|
||||||
|
-o-object-fit: cover;
|
||||||
|
object-fit: cover;
|
||||||
|
-o-object-position: center top;
|
||||||
|
object-position: center top;
|
||||||
}
|
}
|
||||||
.oathhammer .npc-main .npc-right {
|
.oathhammer .npc-main .npc-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .character-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
border-bottom: 1px solid #535128;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .character-name input {
|
||||||
|
flex: 1;
|
||||||
|
font-family: "Sherwood", "Palatino Linotype", serif;
|
||||||
|
font-size: calc(0.86rem * 1.1);
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 4px 10px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border: 1px solid #535128;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-label {
|
||||||
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2a1a0a;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 4.5rem;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-roll-label {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #084a74;
|
||||||
|
transition: color 0.15s;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-roll-label i {
|
||||||
|
margin-right: 2px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-roll-label:hover {
|
||||||
|
color: #c8a84b;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-value div.form-group {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-value input,
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-value .npc-num-input {
|
||||||
|
width: 2.8rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
padding: 1px 2px;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital .vital-value .res-sep {
|
||||||
|
opacity: 0.6;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital-grit .vital-value .grit-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
border: 1px solid #535128;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(83, 81, 40, 0.2);
|
||||||
|
color: #2a1a0a;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-main .npc-vitals-grid .npc-vital-grit .vital-value .grit-btn:hover {
|
||||||
|
background: #c8a84b;
|
||||||
|
border-color: #c8a84b;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-color-badge {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-color-select {
|
||||||
|
width: 88px;
|
||||||
|
max-width: 88px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
padding: 0 2px;
|
||||||
|
height: 20px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-subtype-badge {
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2a1a0a;
|
||||||
|
background: rgba(200, 168, 75, 0.2);
|
||||||
|
border: 1px solid rgba(200, 168, 75, 0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-subtype-select {
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
padding: 0 2px;
|
||||||
|
height: 20px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-skill-color {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-skill-color-white {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-skill-color-red {
|
||||||
|
color: #cc4444;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-skill-color-black {
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-skill-roll-btn {
|
||||||
|
color: #c8a84b;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border: 1px solid rgba(200, 168, 75, 0.4);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
text-align: center;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-skill-roll-btn:hover {
|
||||||
|
background: rgba(200, 168, 75, 0.15);
|
||||||
|
}
|
||||||
|
.oathhammer .npc-trait-type-badge {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: rgba(200, 168, 75, 0.15);
|
||||||
|
border: 1px solid rgba(200, 168, 75, 0.35);
|
||||||
|
color: #2a1a0a;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.npc-skill-dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
.oathhammer .item-list {
|
.oathhammer .item-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -772,10 +994,18 @@
|
|||||||
.oathhammer .item-list--weapon .item-entry {
|
.oathhammer .item-list--weapon .item-entry {
|
||||||
grid-template-columns: 24px 1fr 4.5rem 3.5rem 2rem 1.5rem 1.8rem 9rem;
|
grid-template-columns: 24px 1fr 4.5rem 3.5rem 2rem 1.5rem 1.8rem 9rem;
|
||||||
}
|
}
|
||||||
|
.oathhammer .item-list--weapons .item-list-header,
|
||||||
|
.oathhammer .item-list--weapons .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 6rem 4rem;
|
||||||
|
}
|
||||||
.oathhammer .item-list--armor .item-list-header,
|
.oathhammer .item-list--armor .item-list-header,
|
||||||
.oathhammer .item-list--armor .item-entry {
|
.oathhammer .item-list--armor .item-entry {
|
||||||
grid-template-columns: 24px 1fr 3.5rem 2.5rem 3.5rem 1.5rem 1.8rem 5.5rem;
|
grid-template-columns: 24px 1fr 3.5rem 2.5rem 3.5rem 1.5rem 1.8rem 5.5rem;
|
||||||
}
|
}
|
||||||
|
.oathhammer .item-list--armors .item-list-header,
|
||||||
|
.oathhammer .item-list--armors .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 6rem 4rem;
|
||||||
|
}
|
||||||
.oathhammer .item-list--ammo .item-list-header,
|
.oathhammer .item-list--ammo .item-list-header,
|
||||||
.oathhammer .item-list--ammo .item-entry {
|
.oathhammer .item-list--ammo .item-entry {
|
||||||
grid-template-columns: 24px 1fr 4rem 3.5rem;
|
grid-template-columns: 24px 1fr 4rem 3.5rem;
|
||||||
@@ -812,6 +1042,30 @@
|
|||||||
.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 1fr 7rem 3.5rem 3.5rem;
|
||||||
}
|
}
|
||||||
|
.oathhammer .item-list--npc-skill .item-list-header,
|
||||||
|
.oathhammer .item-list--npc-skill .item-entry {
|
||||||
|
grid-template-columns: 1.8rem 1fr 3.5rem 3.5rem 2rem 4.5rem;
|
||||||
|
}
|
||||||
|
.oathhammer .item-list--npc-weapon .item-list-header,
|
||||||
|
.oathhammer .item-list--npc-weapon .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 3.5rem 2.5rem 7rem;
|
||||||
|
}
|
||||||
|
.oathhammer .item-list--npc-trait .item-list-header,
|
||||||
|
.oathhammer .item-list--npc-trait .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 6rem 3.5rem;
|
||||||
|
}
|
||||||
|
.oathhammer .item-list--npc-armor .item-list-header,
|
||||||
|
.oathhammer .item-list--npc-armor .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 3rem 3rem 3.5rem;
|
||||||
|
}
|
||||||
|
.oathhammer .item-list--npc-equip .item-list-header,
|
||||||
|
.oathhammer .item-list--npc-equip .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 3.5rem;
|
||||||
|
}
|
||||||
|
.oathhammer .item-list--npc-attack .item-list-header,
|
||||||
|
.oathhammer .item-list--npc-attack .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 7rem 3rem 6rem;
|
||||||
|
}
|
||||||
.oathhammer .item-usage {
|
.oathhammer .item-usage {
|
||||||
font-size: calc(0.86rem * 0.9);
|
font-size: calc(0.86rem * 0.9);
|
||||||
color: #2a1a0a;
|
color: #2a1a0a;
|
||||||
@@ -909,6 +1163,10 @@
|
|||||||
background: rgba(192, 57, 43, 0.1);
|
background: rgba(192, 57, 43, 0.1);
|
||||||
border-color: rgba(192, 57, 43, 0.4);
|
border-color: rgba(192, 57, 43, 0.4);
|
||||||
}
|
}
|
||||||
|
.oathhammer .item-list--regiment .item-list-header,
|
||||||
|
.oathhammer .item-list--regiment .item-entry {
|
||||||
|
grid-template-columns: 24px 1fr 4rem 5rem 4rem 4.5rem;
|
||||||
|
}
|
||||||
.oathhammer .item-sheet-common {
|
.oathhammer .item-sheet-common {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
@@ -1007,6 +1265,20 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #084a74;
|
color: #084a74;
|
||||||
}
|
}
|
||||||
|
.oathhammer .item-sheet-common .enchantment-fieldset .enchant-cursed-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
color: #2a1a0a;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.oathhammer .item-sheet-common .enchantment-fieldset .enchant-cursed-label input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
.oathhammer .item-sheet-common .proficiency-section {
|
.oathhammer .item-sheet-common .proficiency-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -1036,6 +1308,91 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
accent-color: #084a74;
|
accent-color: #084a74;
|
||||||
}
|
}
|
||||||
|
.oathhammer .skillnpc-sheet .skillnpc-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.oathhammer .skillnpc-sheet .skillnpc-stats .form-group > label {
|
||||||
|
flex: 0 0 9rem;
|
||||||
|
}
|
||||||
|
.oathhammer .npcattack-sheet .npcattack-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.oathhammer .npcattack-sheet .npcattack-stats .form-group > label {
|
||||||
|
flex: 0 0 9rem;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-stats-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-stats-row .form-group > label {
|
||||||
|
flex: 0 0 6rem;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-armor-fields {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-armor-fields input[type="number"] {
|
||||||
|
width: 3rem;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-skill-row,
|
||||||
|
.oathhammer .regiment-sheet .regiment-attack-row,
|
||||||
|
.oathhammer .regiment-sheet .regiment-trait-row {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-skill-row input,
|
||||||
|
.oathhammer .regiment-sheet .regiment-attack-row input,
|
||||||
|
.oathhammer .regiment-sheet .regiment-trait-row input,
|
||||||
|
.oathhammer .regiment-sheet .regiment-skill-row select,
|
||||||
|
.oathhammer .regiment-sheet .regiment-attack-row select,
|
||||||
|
.oathhammer .regiment-sheet .regiment-trait-row select {
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
padding: 1px 3px;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-skill-row a.item-delete,
|
||||||
|
.oathhammer .regiment-sheet .regiment-attack-row a.item-delete,
|
||||||
|
.oathhammer .regiment-sheet .regiment-trait-row a.item-delete {
|
||||||
|
text-align: center;
|
||||||
|
color: #2a1a0a;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-skill-row a.item-delete:hover,
|
||||||
|
.oathhammer .regiment-sheet .regiment-attack-row a.item-delete:hover,
|
||||||
|
.oathhammer .regiment-sheet .regiment-trait-row a.item-delete:hover {
|
||||||
|
color: #c0392b;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-skill-header,
|
||||||
|
.oathhammer .regiment-sheet .regiment-attack-header {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
color: #2a1a0a;
|
||||||
|
opacity: 0.6;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-skill-row {
|
||||||
|
grid-template-columns: 1fr 3rem 6rem 1.5rem;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-attack-row {
|
||||||
|
grid-template-columns: 1fr 3.5rem 6rem 3rem 1fr 1.5rem;
|
||||||
|
}
|
||||||
|
.oathhammer .regiment-sheet .regiment-trait-row {
|
||||||
|
grid-template-columns: 1fr 2fr 1.5rem;
|
||||||
|
}
|
||||||
.oh-roll-card {
|
.oh-roll-card {
|
||||||
font-family: "Calibri", "Segoe UI", sans-serif;
|
font-family: "Calibri", "Segoe UI", sans-serif;
|
||||||
border: 1px solid #535128;
|
border: 1px solid #535128;
|
||||||
@@ -1044,6 +1401,9 @@
|
|||||||
background: rgba(245, 234, 208, 0.4);
|
background: rgba(245, 234, 208, 0.4);
|
||||||
}
|
}
|
||||||
.oh-roll-card .oh-roll-header {
|
.oh-roll-card .oh-roll-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
font-family: "BlueDragon", "Palatino Linotype", serif;
|
font-family: "BlueDragon", "Palatino Linotype", serif;
|
||||||
font-size: 0.86rem;
|
font-size: 0.86rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -1052,6 +1412,15 @@
|
|||||||
border-bottom: 1px solid rgba(83, 81, 40, 0.2);
|
border-bottom: 1px solid rgba(83, 81, 40, 0.2);
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
.oh-roll-card .oh-roll-header .oh-card-weapon-img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
-o-object-fit: contain;
|
||||||
|
object-fit: contain;
|
||||||
|
border: 1px solid rgba(83, 81, 40, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.oh-roll-card .oh-roll-info {
|
.oh-roll-card .oh-roll-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -1368,6 +1737,31 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.oh-weapon-dialog .weapon-header .weapon-badges .roll-color-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: calc(0.86rem * 0.9);
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
.oh-weapon-dialog .weapon-header .weapon-badges .color-badge-white {
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #555;
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
.oh-weapon-dialog .weapon-header .weapon-badges .color-badge-red {
|
||||||
|
background: rgba(231, 76, 60, 0.12);
|
||||||
|
color: #c0392b;
|
||||||
|
border-color: rgba(231, 76, 60, 0.35);
|
||||||
|
}
|
||||||
|
.oh-weapon-dialog .weapon-header .weapon-badges .color-badge-black {
|
||||||
|
background: rgba(44, 62, 80, 0.1);
|
||||||
|
color: #2c3e50;
|
||||||
|
border-color: rgba(44, 62, 80, 0.35);
|
||||||
|
}
|
||||||
.oh-weapon-dialog .weapon-header .damage-formula-badge {
|
.oh-weapon-dialog .weapon-header .damage-formula-badge {
|
||||||
font-family: "Calibri", "Segoe UI", sans-serif;
|
font-family: "Calibri", "Segoe UI", sans-serif;
|
||||||
font-size: calc(0.86rem * 0.9);
|
font-size: calc(0.86rem * 0.9);
|
||||||
@@ -1786,12 +2180,13 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.oathhammer .settlement-archetype-badge {
|
.oathhammer .settlement-archetype-badge {
|
||||||
font-size: calc(0.86rem * 0.85);
|
font-size: 0.86rem;
|
||||||
color: #2a1a0a;
|
font-weight: 600;
|
||||||
background: rgba(200, 168, 75, 0.15);
|
color: #150d05;
|
||||||
border: 1px solid rgba(200, 168, 75, 0.4);
|
background: rgba(200, 168, 75, 0.2);
|
||||||
|
border: 1px solid rgba(200, 168, 75, 0.55);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 1px 6px;
|
padding: 1px 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.oathhammer .settlement-stats {
|
.oathhammer .settlement-stats {
|
||||||
@@ -1903,3 +2298,33 @@
|
|||||||
.oathhammer .construct-toggle:hover {
|
.oathhammer .construct-toggle:hover {
|
||||||
color: #214621;
|
color: #214621;
|
||||||
}
|
}
|
||||||
|
.oathhammer .settlement-buildings-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.oathhammer .settlement-buildings-header .settlement-hint {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.oathhammer .settlement-buildings-header .collect-taxes-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: rgba(200, 168, 75, 0.2);
|
||||||
|
border: 1px solid rgba(200, 168, 75, 0.6);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: calc(0.86rem * 0.85);
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2a1a0a;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.oathhammer .settlement-buildings-header .collect-taxes-btn:hover {
|
||||||
|
background: rgba(200, 168, 75, 0.4);
|
||||||
|
border-color: #c8a84b;
|
||||||
|
}
|
||||||
|
.oathhammer .settlement-buildings-header .collect-taxes-btn i {
|
||||||
|
color: #c8a84b;
|
||||||
|
}
|
||||||
|
|||||||
196
lang/en.json
196
lang/en.json
@@ -15,10 +15,13 @@
|
|||||||
"Condition": "Oath Hammer Condition Sheet",
|
"Condition": "Oath Hammer Condition Sheet",
|
||||||
"Class": "Oath Hammer Class Sheet",
|
"Class": "Oath Hammer Class Sheet",
|
||||||
"Building": "Oath Hammer Building Sheet",
|
"Building": "Oath Hammer Building Sheet",
|
||||||
"Settlement": "Oath Hammer Settlement Sheet"
|
"Settlement": "Oath Hammer Settlement Sheet",
|
||||||
|
"SkillNPC": "Oath Hammer NPC Skill Sheet",
|
||||||
|
"NpcAttack": "Oath Hammer NPC Attack Sheet",
|
||||||
|
"Regiment": "Oath Hammer Regiment Sheet"
|
||||||
},
|
},
|
||||||
"Tab": {
|
"Tab": {
|
||||||
"Identity": "Identity",
|
"Identity": "Oaths / Traits",
|
||||||
"Skills": "Skills",
|
"Skills": "Skills",
|
||||||
"Combat": "Combat",
|
"Combat": "Combat",
|
||||||
"Magic": "Magic",
|
"Magic": "Magic",
|
||||||
@@ -26,7 +29,9 @@
|
|||||||
"Notes": "Notes",
|
"Notes": "Notes",
|
||||||
"Overview": "Overview",
|
"Overview": "Overview",
|
||||||
"Buildings": "Buildings",
|
"Buildings": "Buildings",
|
||||||
"Inventory": "Inventory"
|
"Inventory": "Inventory",
|
||||||
|
"Traits": "Traits",
|
||||||
|
"Garrison": "Garrison"
|
||||||
},
|
},
|
||||||
"Attribute": {
|
"Attribute": {
|
||||||
"Might": "Might",
|
"Might": "Might",
|
||||||
@@ -112,14 +117,27 @@
|
|||||||
},
|
},
|
||||||
"Building": {
|
"Building": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"skillCheck": { "label": "Skill Check" },
|
"skillCheck": {
|
||||||
"cost": { "label": "Cost (gp)" },
|
"label": "Skill Check"
|
||||||
"buildTime": { "label": "Build Time" },
|
},
|
||||||
"taxRevenue": { "label": "Tax Revenue / month" },
|
"cost": {
|
||||||
"constructed":{ "label": "Constructed" },
|
"label": "Cost (gp)"
|
||||||
|
},
|
||||||
"description":{ "label": "Description" },
|
"buildTime": {
|
||||||
"notes": { "label": "Notes" }
|
"label": "Build Time"
|
||||||
|
},
|
||||||
|
"taxRevenue": {
|
||||||
|
"label": "Tax Revenue / month"
|
||||||
|
},
|
||||||
|
"constructed": {
|
||||||
|
"label": "Constructed"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"notes": {
|
||||||
|
"label": "Notes"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ArmorType": {
|
"ArmorType": {
|
||||||
@@ -166,7 +184,9 @@
|
|||||||
"TraitType": {
|
"TraitType": {
|
||||||
"SpecialTrait": "Special Trait",
|
"SpecialTrait": "Special Trait",
|
||||||
"ClassTrait": "Class Trait",
|
"ClassTrait": "Class Trait",
|
||||||
"LineageTrait": "Lineage Trait"
|
"LineageTrait": "Lineage Trait",
|
||||||
|
"NpcTrait": "NPC Trait",
|
||||||
|
"CreatureTrait": "Creature Trait"
|
||||||
},
|
},
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"Blinded": "Blinded",
|
"Blinded": "Blinded",
|
||||||
@@ -220,9 +240,10 @@
|
|||||||
"Level": "Level",
|
"Level": "Level",
|
||||||
"XP": "Current XP",
|
"XP": "Current XP",
|
||||||
"TotalXP": "Total XP",
|
"TotalXP": "Total XP",
|
||||||
"Traits": "Special Traits",
|
"Traits": "Traits",
|
||||||
"Oaths": "Oaths",
|
"Oaths": "Oaths",
|
||||||
"Weapons": "Weapons",
|
"Weapons": "Weapons",
|
||||||
|
"Attacks": "Attacks",
|
||||||
"Armor": "Armor & Shields",
|
"Armor": "Armor & Shields",
|
||||||
"Ammunition": "Ammunition",
|
"Ammunition": "Ammunition",
|
||||||
"ItemSlots": "Item Slots",
|
"ItemSlots": "Item Slots",
|
||||||
@@ -243,14 +264,18 @@
|
|||||||
"Components": "Components",
|
"Components": "Components",
|
||||||
"Charges": "Charges",
|
"Charges": "Charges",
|
||||||
"NoWeapons": "No weapons equipped.",
|
"NoWeapons": "No weapons equipped.",
|
||||||
|
"NoAttacks": "No attacks defined.",
|
||||||
"NoArmor": "No armor or shields.",
|
"NoArmor": "No armor or shields.",
|
||||||
"NoSpells": "No spells known.",
|
"NoSpells": "No spells known.",
|
||||||
"NoMiracles": "No miracles known.",
|
"NoMiracles": "No miracles known.",
|
||||||
"MiracleBlocked": "Divine favour lost — no more miracles today.",
|
"MiracleBlocked": "Divine favour lost — no more miracles today.",
|
||||||
"NoEquipment": "No equipment.",
|
"NoEquipment": "No equipment.",
|
||||||
"NoTraits": "Drop traits here.",
|
"NoTraits": "No traits. Drag trait items here.",
|
||||||
"NoOaths": "No oaths yet.",
|
"NoOaths": "No oaths yet.",
|
||||||
"Enchantment": "Enchantment",
|
"Enchantment": "Enchantment",
|
||||||
|
"MagicQuality": "Quality",
|
||||||
|
"Cursed": "Cursed",
|
||||||
|
"ClassRestriction": "Restriction",
|
||||||
"Tenet": "Tenet",
|
"Tenet": "Tenet",
|
||||||
"Boon": "Boon",
|
"Boon": "Boon",
|
||||||
"Bane": "Bane",
|
"Bane": "Bane",
|
||||||
@@ -262,7 +287,6 @@
|
|||||||
"Lineage": "Lineage",
|
"Lineage": "Lineage",
|
||||||
"DropClass": "Drop Class Here",
|
"DropClass": "Drop Class Here",
|
||||||
"OpenClass": "Click to open class details",
|
"OpenClass": "Click to open class details",
|
||||||
"Traits": "Traits",
|
|
||||||
"Features": "Features",
|
"Features": "Features",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Type": "Type",
|
"Type": "Type",
|
||||||
@@ -300,7 +324,28 @@
|
|||||||
"TaxRevenue": "Tax Revenue",
|
"TaxRevenue": "Tax Revenue",
|
||||||
"Cost": "Cost",
|
"Cost": "Cost",
|
||||||
"Built": "Built",
|
"Built": "Built",
|
||||||
"Armors": "Armors & Shields"
|
"Armors": "Armors & Shields",
|
||||||
|
"ArmorDice": "Armor Dice",
|
||||||
|
"Skills": "Skills",
|
||||||
|
"NoSkills": "No skills. Drag skill items here.",
|
||||||
|
"DicePool": "Dice Pool",
|
||||||
|
"SkillNPCHint": "Drag NPC skill items here to define skill pools.",
|
||||||
|
"TraitNPCHint": "Drag trait items here.",
|
||||||
|
"EquipmentNPCHint": "Drag armor and equipment items here.",
|
||||||
|
"Threshold": "Threshold",
|
||||||
|
"GritMax": "Grit (Max)",
|
||||||
|
"Dice": "Dice",
|
||||||
|
"DiceColor": "Color",
|
||||||
|
"Special": "Special",
|
||||||
|
"Movement": "Move",
|
||||||
|
"Stats": "Stats",
|
||||||
|
"NoRegiments": "No regiments. Add one with the + button.",
|
||||||
|
"SkillName": "Skill name",
|
||||||
|
"AttackName": "Attack name",
|
||||||
|
"TraitName": "Trait name",
|
||||||
|
"Edit": "Edit",
|
||||||
|
"Delete": "Delete",
|
||||||
|
"Rank": "Rank"
|
||||||
},
|
},
|
||||||
"ColorDice": {
|
"ColorDice": {
|
||||||
"White": "White (4+)",
|
"White": "White (4+)",
|
||||||
@@ -314,9 +359,17 @@
|
|||||||
"Equipment": "New Equipment",
|
"Equipment": "New Equipment",
|
||||||
"Building": "New Building",
|
"Building": "New Building",
|
||||||
"Trait": "New Trait",
|
"Trait": "New Trait",
|
||||||
"Oath": "New Oath"
|
"Oath": "New Oath",
|
||||||
|
"NpcAttack": "New NPC Attack",
|
||||||
|
"Regiment": "New Regiment",
|
||||||
|
"RegimentSkill": "Add Skill",
|
||||||
|
"RegimentAttack": "Add Attack",
|
||||||
|
"RegimentTrait": "Add Trait"
|
||||||
},
|
},
|
||||||
"ToggleSheet": "Toggle Edit/Play Mode",
|
"ToggleSheet": "Toggle Edit/Play Mode",
|
||||||
|
"Tooltip": {
|
||||||
|
"RollArmor": "Roll Armor Dice"
|
||||||
|
},
|
||||||
"Action": {
|
"Action": {
|
||||||
"CastSpell": "Cast Spell",
|
"CastSpell": "Cast Spell",
|
||||||
"InvokeMiracle": "Invoke Miracle",
|
"InvokeMiracle": "Invoke Miracle",
|
||||||
@@ -558,10 +611,26 @@
|
|||||||
"label": "Attributes"
|
"label": "Attributes"
|
||||||
},
|
},
|
||||||
"grit": {
|
"grit": {
|
||||||
"label": "Grit"
|
"label": "Grit",
|
||||||
|
"fields": {
|
||||||
|
"max": {
|
||||||
|
"label": "Max Grit"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"label": "Grit"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"defense": {
|
"defense": {
|
||||||
"label": "Defense"
|
"label": "Defense",
|
||||||
|
"fields": {
|
||||||
|
"value": {
|
||||||
|
"label": "Defense"
|
||||||
|
},
|
||||||
|
"colorDiceType": {
|
||||||
|
"label": "Defense Dice Color"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"movement": {
|
"movement": {
|
||||||
"label": "Movement"
|
"label": "Movement"
|
||||||
@@ -580,6 +649,12 @@
|
|||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"label": "Notes"
|
"label": "Notes"
|
||||||
|
},
|
||||||
|
"subtype": {
|
||||||
|
"label": "Subtype"
|
||||||
|
},
|
||||||
|
"armorDice": {
|
||||||
|
"label": "Armor Dice"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -641,8 +716,13 @@
|
|||||||
},
|
},
|
||||||
"classRestriction": {
|
"classRestriction": {
|
||||||
"label": "Restriction"
|
"label": "Restriction"
|
||||||
|
},
|
||||||
|
"skillOverride": {
|
||||||
|
"label": "Attack Skill",
|
||||||
|
"hint": "Override the skill and attribute used for attack and damage rolls. Leave blank to auto-detect (Fighting for melee, Shooting for ranged)."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"SkillOverrideAuto": "Auto (Fighting / Shooting)"
|
||||||
},
|
},
|
||||||
"Armor": {
|
"Armor": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
@@ -922,6 +1002,9 @@
|
|||||||
"Greater": "Greater",
|
"Greater": "Greater",
|
||||||
"Legendary": "Legendary"
|
"Legendary": "Legendary"
|
||||||
},
|
},
|
||||||
|
"ClassRestriction": {
|
||||||
|
"None": "No restriction"
|
||||||
|
},
|
||||||
"OathType": {
|
"OathType": {
|
||||||
"Compassion": "Oath of Compassion",
|
"Compassion": "Oath of Compassion",
|
||||||
"Courage": "Oath of Courage",
|
"Courage": "Oath of Courage",
|
||||||
@@ -976,20 +1059,71 @@
|
|||||||
"MiracleBlocked": "Divine favour has been lost — you cannot invoke miracles until a new day."
|
"MiracleBlocked": "Divine favour has been lost — you cannot invoke miracles until a new day."
|
||||||
},
|
},
|
||||||
"SettlementArchetype": {
|
"SettlementArchetype": {
|
||||||
"CenterOfLearning": "Center of Learning",
|
"CenterOfLearning": "Center of Learning",
|
||||||
"DwarvenBorough": "Dwarven Borough",
|
"DwarvenBorough": "Dwarven Borough",
|
||||||
"FreeCity": "Free City",
|
"FreeCity": "Free City",
|
||||||
"GuildMunicipality": "Guild Municipality",
|
"GuildMunicipality": "Guild Municipality",
|
||||||
"NocklanderOutpost": "Nocklander Outpost",
|
"NocklanderOutpost": "Nocklander Outpost",
|
||||||
"PilgrimMission": "Pilgrim Mission",
|
"PilgrimMission": "Pilgrim Mission",
|
||||||
"PortTown": "Port Town",
|
"PortTown": "Port Town",
|
||||||
"VelathiColony": "Velathi Colony"
|
"VelathiColony": "Velathi Colony"
|
||||||
},
|
},
|
||||||
"Settlement": {
|
"Settlement": {
|
||||||
"BuildingHint": "Drag & drop building items here. Toggle the checkbox to mark them as constructed.",
|
"BuildingHint": "Drag & drop building items here. Toggle the checkbox to mark them as constructed.",
|
||||||
"InventoryHint": "Drag & drop weapons, armor, or equipment here to store them in the settlement reserve.",
|
"InventoryHint": "Drag & drop weapons, armor, or equipment here to store them in the settlement reserve.",
|
||||||
"NoBuildings": "No buildings yet. Drag building items here.",
|
"NoBuildings": "No buildings yet. Drag building items here.",
|
||||||
"NoInventory": "No stored inventory. Drag weapons, armor, or equipment here."
|
"NoInventory": "No stored inventory. Drag weapons, armor, or equipment here.",
|
||||||
|
"CollectTaxes": "Collect Taxes",
|
||||||
|
"CollectTaxesTooltip": "Roll tax revenue for all constructed buildings and total the result.",
|
||||||
|
"NoTaxRevenue": "No constructed buildings with tax revenue defined.",
|
||||||
|
"TotalRevenue": "Total Revenue"
|
||||||
|
},
|
||||||
|
"SkillNPC": {
|
||||||
|
"FIELDS": {
|
||||||
|
"dicePool": {
|
||||||
|
"label": "Dice Pool",
|
||||||
|
"hint": "Total number of dice rolled for this skill."
|
||||||
|
},
|
||||||
|
"colorDiceType": {
|
||||||
|
"label": "Dice Color",
|
||||||
|
"hint": "White = 4+, Red = 3+, Black = 2+"
|
||||||
|
},
|
||||||
|
"skillRef": {
|
||||||
|
"label": "Skill Reference",
|
||||||
|
"hint": "Optional link to a system skill for display purposes."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NpcAttack": {
|
||||||
|
"FIELDS": {
|
||||||
|
"damageDice": {
|
||||||
|
"label": "Damage Dice",
|
||||||
|
"hint": "Number of damage dice rolled (no Might added)."
|
||||||
|
},
|
||||||
|
"colorDiceType": {
|
||||||
|
"label": "Dice Color",
|
||||||
|
"hint": "White = 4+, Red = 3+, Black = 2+"
|
||||||
|
},
|
||||||
|
"ap": {
|
||||||
|
"label": "AP",
|
||||||
|
"hint": "Armor Penetration: penalty applied to the target's armor roll."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DiceColor": {
|
||||||
|
"White": "White (4+)",
|
||||||
|
"Red": "Red (3+)",
|
||||||
|
"Black": "Black (2+)"
|
||||||
|
},
|
||||||
|
"NpcSubtype": {
|
||||||
|
"Creature": "Creature",
|
||||||
|
"Npc": "NPC"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
@@ -1004,11 +1138,15 @@
|
|||||||
"magic-item": "Magic Item",
|
"magic-item": "Magic Item",
|
||||||
"trait": "Trait",
|
"trait": "Trait",
|
||||||
"oath": "Oath",
|
"oath": "Oath",
|
||||||
"class": "Class"
|
"class": "Class",
|
||||||
|
"skillnpc": "NPC Skill",
|
||||||
|
"npcattack": "NPC Attack",
|
||||||
|
"regiment": "Regiment",
|
||||||
|
"building": "Building"
|
||||||
},
|
},
|
||||||
"Actor": {
|
"Actor": {
|
||||||
"character": "Character",
|
"character": "Character",
|
||||||
"npc": "NPC",
|
"npc": "NPC / Creature",
|
||||||
"settlement": "Settlement"
|
"settlement": "Settlement"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,4 +325,58 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Magic tab — Arcane Stress row ──────────────────────────
|
||||||
|
.stress-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
.stress-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border: 1px solid @color-olive;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: fade(@color-dark, 6%);
|
||||||
|
color: @color-dark;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover { background: fade(@color-blue, 15%); border-color: @color-blue; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.arcane-stress-display {
|
||||||
|
font-family: @font-secondary;
|
||||||
|
font-size: @font-size-lg;
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-dark;
|
||||||
|
min-width: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&.stress-at-limit { color: #c0392b; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.stress-bonus-label {
|
||||||
|
margin-left: auto; // push threshold bonus to the right
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
color: @color-olive;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stress-bonus-input {
|
||||||
|
width: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,12 +121,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settlement inventory: simpler lists (no equip slot, no qty)
|
||||||
|
.item-list--weapons {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 6rem 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-list--armor {
|
.item-list--armor {
|
||||||
.item-list-header, .item-entry {
|
.item-list-header, .item-entry {
|
||||||
grid-template-columns: @item-img-size 1fr 3.5rem 2.5rem 3.5rem 1.5rem 1.8rem 5.5rem;
|
grid-template-columns: @item-img-size 1fr 3.5rem 2.5rem 3.5rem 1.5rem 1.8rem 5.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-list--armors {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 6rem 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-list--ammo {
|
.item-list--ammo {
|
||||||
.item-list-header, .item-entry {
|
.item-list-header, .item-entry {
|
||||||
grid-template-columns: @item-img-size 1fr 4rem 3.5rem;
|
grid-template-columns: @item-img-size 1fr 4rem 3.5rem;
|
||||||
@@ -180,6 +193,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── NPC-specific grid variants ───────────────────────────────
|
||||||
|
// NPC Skill list: [color emoji] [name] [pool] [threshold] [roll btn] [actions]
|
||||||
|
.item-list--npc-skill {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: 1.8rem 1fr 3.5rem 3.5rem 2rem 4.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPC Weapon list: [img] [name] [damage] [AP] [actions incl attack+dmg+edit+del]
|
||||||
|
.item-list--npc-weapon {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 3.5rem 2.5rem 7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPC Trait list: [img] [name] [type badge] [actions]
|
||||||
|
.item-list--npc-trait {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 6rem 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPC Armor list: [img] [name] [AV] [penalty] [actions]
|
||||||
|
.item-list--npc-armor {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 3rem 3rem 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPC Equipment list: [img] [name] [actions]
|
||||||
|
.item-list--npc-equip {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 3.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NPC Attack list: [img] [name] [damage] [AP] [actions: roll+edit+del]
|
||||||
|
.item-list--npc-attack {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 7rem 3rem 6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item-usage {
|
.item-usage {
|
||||||
font-size: @font-size-xs;
|
font-size: @font-size-xs;
|
||||||
color: @color-dark;
|
color: @color-dark;
|
||||||
@@ -273,3 +329,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Regiment list (settlement garrison tab) ──────────────────────────────────
|
||||||
|
.oathhammer {
|
||||||
|
.item-list--regiment {
|
||||||
|
.item-list-header, .item-entry {
|
||||||
|
grid-template-columns: @item-img-size 1fr 4rem 5rem 4rem 4.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -115,6 +115,25 @@
|
|||||||
color: @color-blue;
|
color: @color-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Enchantment fieldset ─────────────────────────────────────
|
||||||
|
.enchantment-fieldset {
|
||||||
|
.enchant-cursed-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-family: @font-secondary;
|
||||||
|
font-size: @font-size-base;
|
||||||
|
color: @color-dark;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Class proficiency checkboxes ────────────────────────────
|
// ── Class proficiency checkboxes ────────────────────────────
|
||||||
.proficiency-section {
|
.proficiency-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -150,3 +169,78 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SkillNPC sheet — vertical stack layout
|
||||||
|
.oathhammer .skillnpc-sheet {
|
||||||
|
.skillnpc-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
.form-group > label {
|
||||||
|
flex: 0 0 9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NpcAttack sheet — vertical stack layout (mirrors skillnpc)
|
||||||
|
.oathhammer .npcattack-sheet {
|
||||||
|
.npcattack-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
.form-group > label {
|
||||||
|
flex: 0 0 9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regiment sheet
|
||||||
|
.oathhammer .regiment-sheet {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.regiment-stats-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.form-group > label { flex: 0 0 6rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.regiment-armor-fields {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
input[type="number"] { width: 3rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.regiment-skill-row,
|
||||||
|
.regiment-attack-row,
|
||||||
|
.regiment-trait-row {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
input, select { font-size: @font-size-sm; padding: 1px 3px; }
|
||||||
|
a.item-delete { text-align: center; color: @color-dark; opacity: 0.4; &:hover { color: #c0392b; opacity: 1; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
.regiment-skill-header,
|
||||||
|
.regiment-attack-header {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
color: @color-dark;
|
||||||
|
opacity: 0.6;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.regiment-skill-row { grid-template-columns: 1fr 3rem 6rem 1.5rem; }
|
||||||
|
.regiment-attack-row { grid-template-columns: 1fr 3.5rem 6rem 3rem 1fr 1.5rem; }
|
||||||
|
.regiment-trait-row { grid-template-columns: 1fr 2fr 1.5rem; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,209 @@
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// NPC SHEET — NPC-specific layout
|
// NPC SHEET — layout, vitals, skills, traits
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
.oathhammer .npc-main {
|
.oathhammer .npc-main {
|
||||||
|
|
||||||
.npc-left {
|
.npc-left {
|
||||||
min-width: @npc-left-width;
|
min-width: @npc-left-width;
|
||||||
max-width: @npc-left-width;
|
max-width: @npc-left-width;
|
||||||
display: flex;
|
flex-shrink: 0;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
.actor-img {
|
||||||
gap: 4px;
|
width: 100%;
|
||||||
|
height: 170px;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center top;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.npc-right {
|
.npc-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name row
|
||||||
|
.character-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
border-bottom: 1px solid @color-olive;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
font-family: @font-primary;
|
||||||
|
font-size: @font-size-lg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vitals: 2×2 grid (Grit | Defense / Armor | Movement)
|
||||||
|
.npc-vitals-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: 4px 10px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border: 1px solid @color-olive;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
.npc-vital {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.vital-label {
|
||||||
|
font-family: @font-secondary;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-dark;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 4.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vital-roll-label {
|
||||||
|
cursor: pointer;
|
||||||
|
color: @color-blue;
|
||||||
|
transition: color 0.15s;
|
||||||
|
i { margin-right: 2px; font-size: 0.75rem; }
|
||||||
|
&:hover { color: @color-gold; text-decoration: underline; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.vital-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
div.form-group { display: contents; }
|
||||||
|
|
||||||
|
input, .npc-num-input {
|
||||||
|
width: 2.8rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
padding: 1px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.res-sep { opacity: 0.6; font-size: @font-size-xs; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.npc-vital-grit .vital-value {
|
||||||
|
.grit-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
border: 1px solid @color-olive;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: @color-olive-faint;
|
||||||
|
color: @color-dark;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
&:hover { background: @color-gold; border-color: @color-gold; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics fieldset grid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inline color badge (emoji in play mode)
|
||||||
|
.oathhammer .npc-color-badge {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inline color dropdown (in edit mode) — fixed width so it doesn't stretch the grid
|
||||||
|
.oathhammer .npc-color-select {
|
||||||
|
width: 88px;
|
||||||
|
max-width: 88px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
padding: 0 2px;
|
||||||
|
height: 20px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtype badge (play mode) / select (edit mode)
|
||||||
|
.oathhammer .npc-subtype-badge {
|
||||||
|
font-size: @font-size-base;
|
||||||
|
font-weight: 600;
|
||||||
|
color: @color-dark;
|
||||||
|
background: fade(@color-gold, 20%);
|
||||||
|
border: 1px solid fade(@color-gold, 50%);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oathhammer .npc-subtype-select {
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
padding: 0 2px;
|
||||||
|
height: 20px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// NPC SKILLS TAB
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Color emoji indicator per dice type
|
||||||
|
.oathhammer .npc-skill-color {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
.oathhammer .npc-skill-color-white { opacity: 0.75; }
|
||||||
|
.oathhammer .npc-skill-color-red { color: #cc4444; }
|
||||||
|
.oathhammer .npc-skill-color-black { color: #333; font-weight: bold; }
|
||||||
|
|
||||||
|
// Roll button in skill list
|
||||||
|
.oathhammer .npc-skill-roll-btn {
|
||||||
|
color: @color-gold;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border: 1px solid fade(@color-gold, 40%);
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
text-align: center;
|
||||||
|
justify-self: center;
|
||||||
|
&:hover { background: fade(@color-gold, 15%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// NPC TRAITS TAB
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Type badge used in traits list
|
||||||
|
.oathhammer .npc-trait-type-badge {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
padding: 1px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: fade(@color-gold, 15%);
|
||||||
|
border: 1px solid fade(@color-gold, 35%);
|
||||||
|
color: @color-dark;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// NPC SKILL DIALOG
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
.npc-skill-dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -284,6 +284,21 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
// Allow color badges here too (shared with .roll-info-block)
|
||||||
|
.roll-color-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: @font-size-xs;
|
||||||
|
font-weight: bold;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
.color-badge-white { background: #f0f0f0; color: #555; border-color: #ccc; }
|
||||||
|
.color-badge-red { background: fade(#e74c3c, 12%); color: #c0392b; border-color: fade(#e74c3c, 35%); }
|
||||||
|
.color-badge-black { background: fade(#2c3e50, 10%); color: #2c3e50; border-color: fade(#2c3e50, 35%); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.damage-formula-badge {
|
.damage-formula-badge {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
background: fade(#f5ead0, 40%);
|
background: fade(#f5ead0, 40%);
|
||||||
|
|
||||||
.oh-roll-header {
|
.oh-roll-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
font-family: @font-secondary;
|
font-family: @font-secondary;
|
||||||
font-size: @font-size-base;
|
font-size: @font-size-base;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -17,6 +20,15 @@
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
border-bottom: 1px solid @color-olive-faint;
|
border-bottom: 1px solid @color-olive-faint;
|
||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
|
|
||||||
|
.oh-card-weapon-img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
object-fit: contain;
|
||||||
|
border: 1px solid @color-olive-faint;
|
||||||
|
border-radius: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.oh-roll-info {
|
.oh-roll-info {
|
||||||
|
|||||||
@@ -42,12 +42,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.oathhammer .settlement-archetype-badge {
|
.oathhammer .settlement-archetype-badge {
|
||||||
font-size: @font-size-sm;
|
font-size: @font-size-base;
|
||||||
color: @color-dark;
|
font-weight: 600;
|
||||||
background: fade(@color-gold, 15%);
|
color: darken(@color-dark, 5%);
|
||||||
border: 1px solid fade(@color-gold, 40%);
|
background: fade(@color-gold, 20%);
|
||||||
|
border: 1px solid fade(@color-gold, 55%);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
padding: 1px 6px;
|
padding: 1px 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,3 +177,34 @@
|
|||||||
|
|
||||||
&:hover { color: darken(#3a7a3a, 15%); }
|
&:hover { color: darken(#3a7a3a, 15%); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.oathhammer .settlement-buildings-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
.settlement-hint { margin: 0; }
|
||||||
|
|
||||||
|
.collect-taxes-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: fade(@color-gold, 20%);
|
||||||
|
border: 1px solid fade(@color-gold, 60%);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: @font-size-sm;
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-dark;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: fade(@color-gold, 40%);
|
||||||
|
border-color: @color-gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
i { color: @color-gold; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
// Layout
|
// Layout
|
||||||
@portrait-height: 150px;
|
@portrait-height: 150px;
|
||||||
@left-panel-width: 180px;
|
@left-panel-width: 180px;
|
||||||
@npc-left-width: 160px;
|
@npc-left-width: 120px;
|
||||||
@item-img-size: 24px;
|
@item-img-size: 24px;
|
||||||
@item-sheet-img: 52px;
|
@item-sheet-img: 52px;
|
||||||
@label-min-width: 9rem;
|
@label-min-width: 9rem;
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ export { default as OathHammerOathSheet } from "./sheets/oath-sheet.mjs"
|
|||||||
export { default as OathHammerClassSheet } from "./sheets/class-sheet.mjs"
|
export { default as OathHammerClassSheet } from "./sheets/class-sheet.mjs"
|
||||||
export { default as OathHammerBuildingSheet } from "./sheets/building-sheet.mjs"
|
export { default as OathHammerBuildingSheet } from "./sheets/building-sheet.mjs"
|
||||||
export { default as OathHammerSettlementSheet } from "./sheets/settlement-sheet.mjs"
|
export { default as OathHammerSettlementSheet } from "./sheets/settlement-sheet.mjs"
|
||||||
|
export { default as OathHammerSkillNPCSheet } from "./sheets/skillnpc-sheet.mjs"
|
||||||
|
export { default as OathHammerNpcAttackSheet } from "./sheets/npcattack-sheet.mjs"
|
||||||
|
export { default as OathHammerRegimentSheet } from "./sheets/regiment-sheet.mjs"
|
||||||
export { default as OathHammerRollDialog } from "./roll-dialog.mjs"
|
export { default as OathHammerRollDialog } from "./roll-dialog.mjs"
|
||||||
export { default as OathHammerWeaponDialog } from "./weapon-dialog.mjs"
|
export { default as OathHammerWeaponDialog } from "./weapon-dialog.mjs"
|
||||||
export { default as OathHammerSpellDialog } from "./spell-dialog.mjs"
|
export { default as OathHammerSpellDialog } from "./spell-dialog.mjs"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
import { ARMOR_TYPE_CHOICES, WEAPON_PROFICIENCY_GROUPS } from "../../config/system.mjs"
|
import { ARMOR_TYPE_CHOICES, CLASS_RESTRICTION_CHOICES, SYSTEM, WEAPON_PROFICIENCY_GROUPS } from "../../config/system.mjs"
|
||||||
import { rollRarityCheck } from "../../rolls.mjs"
|
import { rollRarityCheck } from "../../rolls.mjs"
|
||||||
|
|
||||||
export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||||
@@ -57,6 +57,10 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun
|
|||||||
if (this.document.system.description !== undefined) {
|
if (this.document.system.description !== undefined) {
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true })
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true })
|
||||||
}
|
}
|
||||||
|
if (this.document.system.magicEffect !== undefined) {
|
||||||
|
context.enrichedMagicEffect = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.magicEffect ?? "", { async: true })
|
||||||
|
}
|
||||||
|
context.classRestrictionChoices = CLASS_RESTRICTION_CHOICES
|
||||||
// Armor-specific numeric selects
|
// Armor-specific numeric selects
|
||||||
context.armorValueChoices = Object.fromEntries(
|
context.armorValueChoices = Object.fromEntries(
|
||||||
Array.from({ length: 13 }, (_, i) => [i, String(i)])
|
Array.from({ length: 13 }, (_, i) => [i, String(i)])
|
||||||
@@ -71,6 +75,13 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun
|
|||||||
context.apChoices = Object.fromEntries(
|
context.apChoices = Object.fromEntries(
|
||||||
Array.from({ length: 7 }, (_, i) => [i, String(i)])
|
Array.from({ length: 7 }, (_, i) => [i, String(i)])
|
||||||
)
|
)
|
||||||
|
// Skill choices for weapon skill override (empty = auto-detect)
|
||||||
|
context.skillChoices = {
|
||||||
|
"": `— ${game.i18n.localize("OATHHAMMER.Weapon.SkillOverrideAuto")} —`,
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.SKILLS).map(([k, v]) => [k, game.i18n.localize(v.label)])
|
||||||
|
)
|
||||||
|
}
|
||||||
// Class proficiency choices (for class-sheet checkboxes)
|
// Class proficiency choices (for class-sheet checkboxes)
|
||||||
context.armorTypeChoices = ARMOR_TYPE_CHOICES
|
context.armorTypeChoices = ARMOR_TYPE_CHOICES
|
||||||
context.weaponGroupChoices = WEAPON_PROFICIENCY_GROUPS
|
context.weaponGroupChoices = WEAPON_PROFICIENCY_GROUPS
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
|
|||||||
_descTooltip: _stripHtml(parts.join(" "))
|
_descTooltip: _stripHtml(parts.join(" "))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
context.enrichedBackground = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.background ?? "", { async: true })
|
|
||||||
break
|
break
|
||||||
case "skills": {
|
case "skills": {
|
||||||
context.tab = context.tabs.skills
|
context.tab = context.tabs.skills
|
||||||
@@ -149,7 +148,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
|
|||||||
modifierName: `system.skills.${skillKey}.modifier`,
|
modifierName: `system.skills.${skillKey}.modifier`,
|
||||||
colorDiceName: `system.skills.${skillKey}.colorDice`,
|
colorDiceName: `system.skills.${skillKey}.colorDice`,
|
||||||
colorDiceTypeName: `system.skills.${skillKey}.colorDiceType`,
|
colorDiceTypeName: `system.skills.${skillKey}.colorDiceType`,
|
||||||
rankOptions: [0,1,2,3,4].map(v => ({ value: v, label: String(v), selected: v === sk.rank })),
|
rankOptions: [0,1,2,3,4,5,6].map(v => ({ value: v, label: String(v), selected: v === sk.rank })),
|
||||||
total: attrRanks[attr] + sk.rank,
|
total: attrRanks[attr] + sk.rank,
|
||||||
// legacy - kept for formInput compatibility
|
// legacy - kept for formInput compatibility
|
||||||
name: `system.skills.${skillKey}.rank`,
|
name: `system.skills.${skillKey}.rank`,
|
||||||
@@ -232,6 +231,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
|
|||||||
break
|
break
|
||||||
case "notes":
|
case "notes":
|
||||||
context.tab = context.tabs.notes
|
context.tab = context.tabs.notes
|
||||||
|
context.enrichedBackground = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.background ?? "", { async: true })
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description ?? "", { async: true })
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description ?? "", { async: true })
|
||||||
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes ?? "", { async: true })
|
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes ?? "", { async: true })
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,50 +1,56 @@
|
|||||||
import OathHammerActorSheet from "./base-actor-sheet.mjs"
|
import OathHammerActorSheet from "./base-actor-sheet.mjs"
|
||||||
import { rollInitiativeCheck } from "../../rolls.mjs"
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
import { rollInitiativeCheck, rollNPCSkill, rollNPCArmor, rollNPCSpell, rollNPCMiracle, rollNPCAttackDamage } from "../../rolls.mjs"
|
||||||
|
|
||||||
export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
||||||
/** @override */
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ["npc"],
|
classes: ["npc"],
|
||||||
position: {
|
position: { width: 720, height: "auto" },
|
||||||
width: 720,
|
window: { contentClasses: ["npc-content"] },
|
||||||
height: "auto",
|
|
||||||
},
|
|
||||||
window: {
|
|
||||||
contentClasses: ["npc-content"],
|
|
||||||
},
|
|
||||||
actions: {
|
actions: {
|
||||||
rollInitiative: OathHammerNPCSheet.#onRollInitiative,
|
rollInitiative: OathHammerNPCSheet.#onRollInitiative,
|
||||||
|
adjustGrit: OathHammerNPCSheet.#onAdjustGrit,
|
||||||
|
rollSkillNPC: OathHammerNPCSheet.#onRollSkillNPC,
|
||||||
|
rollArmor: OathHammerNPCSheet.#onRollArmor,
|
||||||
|
createSpell: OathHammerNPCSheet.#onCreateSpell,
|
||||||
|
createMiracle: OathHammerNPCSheet.#onCreateMiracle,
|
||||||
|
castNPCSpell: OathHammerNPCSheet.#onCastNPCSpell,
|
||||||
|
castNPCMiracle: OathHammerNPCSheet.#onCastNPCMiracle,
|
||||||
|
createNpcAttack: OathHammerNPCSheet.#onCreateNpcAttack,
|
||||||
|
rollNpcAttack: OathHammerNPCSheet.#onRollNpcAttack,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
main: {
|
main: { template: "systems/fvtt-oath-hammer/templates/actor/npc-sheet.hbs" },
|
||||||
template: "systems/fvtt-oath-hammer/templates/actor/npc-sheet.hbs",
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
},
|
skills: { template: "systems/fvtt-oath-hammer/templates/actor/npc-skills.hbs" },
|
||||||
tabs: {
|
combat: { template: "systems/fvtt-oath-hammer/templates/actor/npc-combat.hbs" },
|
||||||
template: "templates/generic/tab-navigation.hbs",
|
traits: { template: "systems/fvtt-oath-hammer/templates/actor/npc-traits.hbs" },
|
||||||
},
|
magic: { template: "systems/fvtt-oath-hammer/templates/actor/npc-magic.hbs" },
|
||||||
combat: {
|
equipment: { template: "systems/fvtt-oath-hammer/templates/actor/npc-equipment.hbs" },
|
||||||
template: "systems/fvtt-oath-hammer/templates/actor/npc-combat.hbs",
|
notes: { template: "systems/fvtt-oath-hammer/templates/actor/npc-notes.hbs" },
|
||||||
},
|
|
||||||
notes: {
|
|
||||||
template: "systems/fvtt-oath-hammer/templates/actor/npc-notes.hbs",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
tabGroups = { sheet: "skills" }
|
||||||
tabGroups = {
|
|
||||||
sheet: "combat",
|
|
||||||
}
|
|
||||||
|
|
||||||
#getTabs() {
|
#getTabs() {
|
||||||
|
const isNPC = this.document.system.subtype === "npc"
|
||||||
|
const hasMagic = this.document.items.some(i => i.type === "spell" || i.type === "miracle")
|
||||||
const tabs = {
|
const tabs = {
|
||||||
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "OATHHAMMER.Tab.Combat" },
|
skills: { id: "skills", group: "sheet", icon: "fa-solid fa-dice-d6", label: "OATHHAMMER.Tab.Skills" },
|
||||||
notes: { id: "notes", group: "sheet", icon: "fa-solid fa-book", label: "OATHHAMMER.Tab.Notes" },
|
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "OATHHAMMER.Tab.Combat" },
|
||||||
|
traits: { id: "traits", group: "sheet", icon: "fa-solid fa-star", label: "OATHHAMMER.Tab.Traits" },
|
||||||
|
notes: { id: "notes", group: "sheet", icon: "fa-solid fa-book", label: "OATHHAMMER.Tab.Notes" },
|
||||||
|
}
|
||||||
|
if (isNPC) {
|
||||||
|
tabs.equipment = { id: "equipment", group: "sheet", icon: "fa-solid fa-backpack", label: "OATHHAMMER.Tab.Equipment" }
|
||||||
|
}
|
||||||
|
if (hasMagic || !this.isPlayMode) {
|
||||||
|
tabs.magic = { id: "magic", group: "sheet", icon: "fa-solid fa-wand-sparkles", label: "OATHHAMMER.Tab.Magic" }
|
||||||
}
|
}
|
||||||
for (const v of Object.values(tabs)) {
|
for (const v of Object.values(tabs)) {
|
||||||
v.active = this.tabGroups[v.group] === v.id
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
v.cssClass = v.active ? "active" : ""
|
v.cssClass = v.active ? "active" : ""
|
||||||
}
|
}
|
||||||
return tabs
|
return tabs
|
||||||
@@ -54,6 +60,23 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
async _prepareContext() {
|
async _prepareContext() {
|
||||||
const context = await super._prepareContext()
|
const context = await super._prepareContext()
|
||||||
context.tabs = this.#getTabs()
|
context.tabs = this.#getTabs()
|
||||||
|
|
||||||
|
context.subtypeChoices = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.NPC_SUBTYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
)
|
||||||
|
context.subtypeLabels = context.subtypeChoices
|
||||||
|
|
||||||
|
const armorColor = this.document.system.armorDice?.colorDiceType ?? "white"
|
||||||
|
context.armorDiceEmoji = armorColor === "black" ? "⬛" : armorColor === "red" ? "🔴" : "⬜"
|
||||||
|
|
||||||
|
context.colorChoices = Object.fromEntries(
|
||||||
|
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])
|
||||||
|
)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,29 +84,265 @@ export default class OathHammerNPCSheet 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 "skills":
|
||||||
|
context.tab = context.tabs.skills
|
||||||
|
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
|
||||||
context.weapons = doc.itemTypes.weapon
|
context.npcAttacks = (doc.itemTypes.npcattack ?? []).map(a => ({
|
||||||
|
id: a.id, uuid: a.uuid, img: a.img, name: a.name, system: a.system,
|
||||||
|
_descTooltip: a.system.description?.replace(/<[^>]+>/g, "").slice(0, 300) ?? ""
|
||||||
|
}))
|
||||||
context.combatantInitiative = game.combat?.combatants.find(c => c.actor?.id === doc.id)?.initiative ?? null
|
context.combatantInitiative = game.combat?.combatants.find(c => c.actor?.id === doc.id)?.initiative ?? null
|
||||||
break
|
break
|
||||||
|
case "traits":
|
||||||
|
context.tab = context.tabs.traits
|
||||||
|
context.traits = (doc.itemTypes.trait ?? []).slice().sort((a, b) => a.name.localeCompare(b.name)).map(t => ({
|
||||||
|
id: t.id, uuid: t.uuid, img: t.img, name: t.name, system: t.system,
|
||||||
|
_descTooltip: t.system.description?.replace(/<[^>]+>/g, "").slice(0, 300) ?? ""
|
||||||
|
}))
|
||||||
|
break
|
||||||
|
case "magic":
|
||||||
|
context.tab = context.tabs.magic
|
||||||
|
context.spells = (doc.itemTypes.spell ?? []).map(s => ({
|
||||||
|
id: s.id, uuid: s.uuid, img: s.img, name: s.name, system: s.system,
|
||||||
|
_descTooltip: s.system.effect?.replace(/<[^>]+>/g, "").slice(0, 300) ?? ""
|
||||||
|
}))
|
||||||
|
context.miracles = (doc.itemTypes.miracle ?? []).map(m => ({
|
||||||
|
id: m.id, uuid: m.uuid, img: m.img, name: m.name, system: m.system,
|
||||||
|
_descTooltip: m.system.effect?.replace(/<[^>]+>/g, "").slice(0, 300) ?? ""
|
||||||
|
}))
|
||||||
|
break
|
||||||
|
case "equipment":
|
||||||
|
context.tab = context.tabs.equipment
|
||||||
|
context.armors = doc.itemTypes.armor ?? []
|
||||||
|
context.equipment = doc.itemTypes.equipment ?? []
|
||||||
|
break
|
||||||
case "notes":
|
case "notes":
|
||||||
context.tab = context.tabs.notes
|
context.tab = context.tabs.notes
|
||||||
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
|
||||||
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
|
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
if (!this.isEditable || !this.isEditMode) return
|
if (!this.isEditable || !this.isEditMode) return
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
|
||||||
if (data.type === "Item") {
|
if (data.type !== "Item") return
|
||||||
const item = await fromUuid(data.uuid)
|
const item = await fromUuid(data.uuid)
|
||||||
return this._onDropItem(item)
|
if (!item) return
|
||||||
}
|
const ALLOWED = new Set(["skillnpc", "npcattack", "trait", "armor", "equipment", "spell", "miracle"])
|
||||||
|
if (!ALLOWED.has(item.type)) return
|
||||||
|
return this._onDropItem(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onAdjustGrit(event, target) {
|
||||||
|
const delta = parseInt(target.dataset.delta, 10)
|
||||||
|
const current = this.document.system.grit?.value ?? 0
|
||||||
|
const max = this.document.system.grit?.max ?? current
|
||||||
|
await this.document.update({ "system.grit.value": Math.max(0, Math.min(max, current + delta)) })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onCreateNpcAttack() {
|
||||||
|
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.NpcAttack"), type: "npcattack" }])
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRollNpcAttack(event, target) {
|
||||||
|
const attack = this.document.items.get(target.dataset.itemId)
|
||||||
|
if (!attack) return
|
||||||
|
|
||||||
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
|
const v = i - 6
|
||||||
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
||||||
|
{
|
||||||
|
skillName: attack.name,
|
||||||
|
skillImg: attack.img,
|
||||||
|
dicePool: attack.system.damageDice,
|
||||||
|
colorEmoji: attack.system.colorEmoji,
|
||||||
|
colorType: attack.system.colorDiceType,
|
||||||
|
threshold: attack.system.threshold,
|
||||||
|
bonusOptions,
|
||||||
|
colorChoices: Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
),
|
||||||
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await foundry.applications.api.DialogV2.prompt({
|
||||||
|
window: { title: attack.name, resizable: true },
|
||||||
|
classes: ["fvtt-oath-hammer"],
|
||||||
|
position: { width: 420 },
|
||||||
|
content,
|
||||||
|
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-burst" },
|
||||||
|
})
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
const form = new DOMParser().parseFromString(result, "text/html")
|
||||||
|
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
|
||||||
|
|
||||||
|
await rollNPCAttackDamage(this.document, attack, {
|
||||||
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
|
visibility: getValue("visibility"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onCreateSpell() {
|
||||||
|
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Spell"), type: "spell" }])
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onCreateMiracle() {
|
||||||
|
this.document.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("OATHHAMMER.NewItem.Miracle"), type: "miracle" }])
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onCastNPCSpell(event, target) {
|
||||||
|
const spell = this.document.items.get(target.dataset.itemId)
|
||||||
|
if (!spell) return
|
||||||
|
|
||||||
|
const colorChoices = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
)
|
||||||
|
const poolOptions = Array.from({ length: 10 }, (_, i) => {
|
||||||
|
const v = i + 1
|
||||||
|
return { value: v, label: String(v), selected: v === 3 }
|
||||||
|
})
|
||||||
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
|
const v = i - 6
|
||||||
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-oath-hammer/templates/npc-magic-dialog.hbs",
|
||||||
|
{
|
||||||
|
itemName: spell.name, itemImg: spell.img,
|
||||||
|
dv: spell.system.difficultyValue,
|
||||||
|
poolOptions, bonusOptions, colorChoices, showColor: true,
|
||||||
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await foundry.applications.api.DialogV2.prompt({
|
||||||
|
window: { title: spell.name, resizable: true },
|
||||||
|
classes: ["fvtt-oath-hammer"],
|
||||||
|
position: { width: 420 },
|
||||||
|
content,
|
||||||
|
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-wand-sparkles" },
|
||||||
|
})
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
const form = new DOMParser().parseFromString(result, "text/html")
|
||||||
|
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
|
||||||
|
|
||||||
|
await rollNPCSpell(this.document, spell, {
|
||||||
|
dicePool: parseInt(getValue("dicePool")) || 3,
|
||||||
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
visibility: getValue("visibility"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onCastNPCMiracle(event, target) {
|
||||||
|
const miracle = this.document.items.get(target.dataset.itemId)
|
||||||
|
if (!miracle) return
|
||||||
|
|
||||||
|
const poolOptions = Array.from({ length: 10 }, (_, i) => {
|
||||||
|
const v = i + 1
|
||||||
|
return { value: v, label: String(v), selected: v === 3 }
|
||||||
|
})
|
||||||
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
|
const v = i - 6
|
||||||
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-oath-hammer/templates/npc-magic-dialog.hbs",
|
||||||
|
{
|
||||||
|
itemName: miracle.name, itemImg: miracle.img,
|
||||||
|
dv: null, showColor: false,
|
||||||
|
poolOptions, bonusOptions,
|
||||||
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await foundry.applications.api.DialogV2.prompt({
|
||||||
|
window: { title: miracle.name, resizable: true },
|
||||||
|
classes: ["fvtt-oath-hammer"],
|
||||||
|
position: { width: 420 },
|
||||||
|
content,
|
||||||
|
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-hands-praying" },
|
||||||
|
})
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
const form = new DOMParser().parseFromString(result, "text/html")
|
||||||
|
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
|
||||||
|
|
||||||
|
await rollNPCMiracle(this.document, miracle, {
|
||||||
|
dicePool: parseInt(getValue("dicePool")) || 3,
|
||||||
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
|
visibility: getValue("visibility"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRollArmor() {
|
||||||
|
const actor = this.document
|
||||||
|
const sys = actor.system
|
||||||
|
const colorType = sys.armorDice?.colorDiceType || "white"
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
|
const dicePool = sys.armorDice?.value ?? 0
|
||||||
|
|
||||||
|
const colorChoices = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
)
|
||||||
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
|
const v = i - 6
|
||||||
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
||||||
|
{
|
||||||
|
skillName: game.i18n.localize("OATHHAMMER.Label.ArmorDice"),
|
||||||
|
skillImg: actor.img,
|
||||||
|
dicePool,
|
||||||
|
colorEmoji,
|
||||||
|
colorType,
|
||||||
|
threshold,
|
||||||
|
bonusOptions,
|
||||||
|
colorChoices,
|
||||||
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await foundry.applications.api.DialogV2.prompt({
|
||||||
|
window: { title: game.i18n.localize("OATHHAMMER.Label.ArmorDice"), resizable: true },
|
||||||
|
classes: ["fvtt-oath-hammer"],
|
||||||
|
position: { width: 420 },
|
||||||
|
content,
|
||||||
|
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6" },
|
||||||
|
})
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
const form = new DOMParser().parseFromString(result, "text/html")
|
||||||
|
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
|
||||||
|
|
||||||
|
await rollNPCArmor(actor, {
|
||||||
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
visibility: getValue("visibility"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #onRollInitiative() {
|
static async #onRollInitiative() {
|
||||||
@@ -95,4 +354,53 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
|
|||||||
await rollInitiativeCheck(actor)
|
await rollInitiativeCheck(actor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onRollSkillNPC(event, target) {
|
||||||
|
const itemId = target.dataset.itemId
|
||||||
|
const item = this.document.items.get(itemId)
|
||||||
|
if (!item) return
|
||||||
|
|
||||||
|
const colorChoices = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
)
|
||||||
|
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
|
||||||
|
const v = i - 6
|
||||||
|
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
"systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs",
|
||||||
|
{
|
||||||
|
skillName: item.name,
|
||||||
|
skillImg: item.img,
|
||||||
|
dicePool: item.system.dicePool,
|
||||||
|
colorEmoji: item.system.colorEmoji,
|
||||||
|
colorType: item.system.colorDiceType,
|
||||||
|
threshold: item.system.threshold,
|
||||||
|
bonusOptions,
|
||||||
|
colorChoices,
|
||||||
|
rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes),
|
||||||
|
visibility: game.settings.get("core", "rollMode"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = await foundry.applications.api.DialogV2.prompt({
|
||||||
|
window: { title: item.name, resizable: true },
|
||||||
|
classes: ["fvtt-oath-hammer"],
|
||||||
|
position: { width: 420 },
|
||||||
|
content,
|
||||||
|
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6" },
|
||||||
|
})
|
||||||
|
if (!result) return
|
||||||
|
|
||||||
|
const form = new DOMParser().parseFromString(result, "text/html")
|
||||||
|
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
|
||||||
|
|
||||||
|
await rollNPCSkill(this.document, item, {
|
||||||
|
bonus: parseInt(getValue("bonus")) || 0,
|
||||||
|
colorOverride: getValue("colorOverride") || null,
|
||||||
|
visibility: getValue("visibility"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
module/applications/sheets/npcattack-sheet.mjs
Normal file
28
module/applications/sheets/npcattack-sheet.mjs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import OathHammerItemSheet from "./base-item-sheet.mjs"
|
||||||
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
|
export default class OathHammerNpcAttackSheet extends OathHammerItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["npcattack"],
|
||||||
|
position: { width: 460 },
|
||||||
|
window: { contentClasses: ["npcattack-content"] },
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/fvtt-oath-hammer/templates/item/npcattack-sheet.hbs" },
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.dicePoolChoices = Object.fromEntries(
|
||||||
|
Array.from({ length: 21 }, (_, i) => [i, String(i)])
|
||||||
|
)
|
||||||
|
context.colorChoices = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
80
module/applications/sheets/regiment-sheet.mjs
Normal file
80
module/applications/sheets/regiment-sheet.mjs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import OathHammerItemSheet from "./base-item-sheet.mjs"
|
||||||
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
|
export default class OathHammerRegimentSheet extends OathHammerItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["regiment"],
|
||||||
|
position: { width: 560, height: "auto" },
|
||||||
|
window: { contentClasses: ["regiment-content"] },
|
||||||
|
actions: {
|
||||||
|
addSkill: OathHammerRegimentSheet.#onAddSkill,
|
||||||
|
removeSkill: OathHammerRegimentSheet.#onRemoveSkill,
|
||||||
|
addAttack: OathHammerRegimentSheet.#onAddAttack,
|
||||||
|
removeAttack:OathHammerRegimentSheet.#onRemoveAttack,
|
||||||
|
addTrait: OathHammerRegimentSheet.#onAddTrait,
|
||||||
|
removeTrait: OathHammerRegimentSheet.#onRemoveTrait,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/fvtt-oath-hammer/templates/item/regiment-sheet.hbs" },
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.colorChoices = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
)
|
||||||
|
context.dicePoolChoices = Object.fromEntries(
|
||||||
|
Array.from({ length: 21 }, (_, i) => [i, String(i)])
|
||||||
|
)
|
||||||
|
context.apChoices = Object.fromEntries(
|
||||||
|
Array.from({ length: 7 }, (_, i) => [i, String(i)])
|
||||||
|
)
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Array helpers ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static async #onAddSkill() {
|
||||||
|
const skills = foundry.utils.deepClone(this.document.system.skills ?? [])
|
||||||
|
skills.push({ name: "", value: 2, colorDiceType: "white" })
|
||||||
|
await this.document.update({ "system.skills": skills })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRemoveSkill(event, target) {
|
||||||
|
const idx = parseInt(target.dataset.idx, 10)
|
||||||
|
const skills = foundry.utils.deepClone(this.document.system.skills ?? [])
|
||||||
|
skills.splice(idx, 1)
|
||||||
|
await this.document.update({ "system.skills": skills })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onAddAttack() {
|
||||||
|
const attacks = foundry.utils.deepClone(this.document.system.attacks ?? [])
|
||||||
|
attacks.push({ name: "", damageDice: 6, colorDiceType: "white", ap: 0, special: "" })
|
||||||
|
await this.document.update({ "system.attacks": attacks })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRemoveAttack(event, target) {
|
||||||
|
const idx = parseInt(target.dataset.idx, 10)
|
||||||
|
const attacks = foundry.utils.deepClone(this.document.system.attacks ?? [])
|
||||||
|
attacks.splice(idx, 1)
|
||||||
|
await this.document.update({ "system.attacks": attacks })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onAddTrait() {
|
||||||
|
const traits = foundry.utils.deepClone(this.document.system.traits ?? [])
|
||||||
|
traits.push({ name: "", description: "" })
|
||||||
|
await this.document.update({ "system.traits": traits })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRemoveTrait(event, target) {
|
||||||
|
const idx = parseInt(target.dataset.idx, 10)
|
||||||
|
const traits = foundry.utils.deepClone(this.document.system.traits ?? [])
|
||||||
|
traits.splice(idx, 1)
|
||||||
|
await this.document.update({ "system.traits": traits })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import OathHammerActorSheet from "./base-actor-sheet.mjs"
|
import OathHammerActorSheet from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
const ALLOWED_ITEM_TYPES = new Set(["building", "equipment", "weapon", "armor"])
|
const ALLOWED_ITEM_TYPES = new Set(["building", "equipment", "weapon", "armor", "regiment"])
|
||||||
|
|
||||||
export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -14,9 +14,11 @@ export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
|||||||
contentClasses: ["settlement-content"],
|
contentClasses: ["settlement-content"],
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
adjustCurrency: OathHammerSettlementSheet.#onAdjustCurrency,
|
adjustCurrency: OathHammerSettlementSheet.#onAdjustCurrency,
|
||||||
adjustQty: OathHammerSettlementSheet.#onAdjustQty,
|
adjustQty: OathHammerSettlementSheet.#onAdjustQty,
|
||||||
toggleConstructed: OathHammerSettlementSheet.#onToggleConstructed,
|
toggleConstructed: OathHammerSettlementSheet.#onToggleConstructed,
|
||||||
|
createRegiment: OathHammerSettlementSheet.#onCreateRegiment,
|
||||||
|
collectTaxes: OathHammerSettlementSheet.#onCollectTaxes,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +39,9 @@ export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
|||||||
inventory: {
|
inventory: {
|
||||||
template: "systems/fvtt-oath-hammer/templates/actor/settlement-inventory.hbs",
|
template: "systems/fvtt-oath-hammer/templates/actor/settlement-inventory.hbs",
|
||||||
},
|
},
|
||||||
|
garrison: {
|
||||||
|
template: "systems/fvtt-oath-hammer/templates/actor/settlement-garrison.hbs",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -46,9 +51,10 @@ export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
|||||||
|
|
||||||
#getTabs() {
|
#getTabs() {
|
||||||
const tabs = {
|
const tabs = {
|
||||||
overview: { id: "overview", group: "sheet", icon: "fa-solid fa-city", label: "OATHHAMMER.Tab.Overview" },
|
overview: { id: "overview", group: "sheet", icon: "fa-solid fa-city", label: "OATHHAMMER.Tab.Overview" },
|
||||||
buildings: { id: "buildings", group: "sheet", icon: "fa-solid fa-building", label: "OATHHAMMER.Tab.Buildings" },
|
buildings: { id: "buildings", group: "sheet", icon: "fa-solid fa-building", label: "OATHHAMMER.Tab.Buildings" },
|
||||||
inventory: { id: "inventory", group: "sheet", icon: "fa-solid fa-boxes-stacked", label: "OATHHAMMER.Tab.Inventory" },
|
inventory: { id: "inventory", group: "sheet", icon: "fa-solid fa-boxes-stacked", label: "OATHHAMMER.Tab.Inventory" },
|
||||||
|
garrison: { id: "garrison", group: "sheet", icon: "fa-solid fa-shield-halved", label: "OATHHAMMER.Tab.Garrison" },
|
||||||
}
|
}
|
||||||
for (const v of Object.values(tabs)) {
|
for (const v of Object.values(tabs)) {
|
||||||
v.active = this.tabGroups[v.group] === v.id
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
@@ -81,7 +87,11 @@ export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
|||||||
break
|
break
|
||||||
case "buildings":
|
case "buildings":
|
||||||
context.tab = context.tabs.buildings
|
context.tab = context.tabs.buildings
|
||||||
context.buildings = doc.itemTypes.building
|
context.buildings = doc.itemTypes.building.map(b => ({
|
||||||
|
id: b.id, uuid: b.uuid, img: b.img, name: b.name, system: b.system,
|
||||||
|
_descTooltip: b.system.description?.replace(/<[^>]+>/g, "").trim().slice(0, 400) ?? ""
|
||||||
|
}))
|
||||||
|
context.hasTaxBuildings = doc.itemTypes.building.some(b => b.system.constructed && b.system.taxRevenue?.trim())
|
||||||
break
|
break
|
||||||
case "inventory": {
|
case "inventory": {
|
||||||
context.tab = context.tabs.inventory
|
context.tab = context.tabs.inventory
|
||||||
@@ -90,6 +100,10 @@ export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
|||||||
context.equipments = doc.itemTypes.equipment
|
context.equipments = doc.itemTypes.equipment
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case "garrison":
|
||||||
|
context.tab = context.tabs.garrison
|
||||||
|
context.regiments = doc.itemTypes.regiment ?? []
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
@@ -129,4 +143,59 @@ export default class OathHammerSettlementSheet extends OathHammerActorSheet {
|
|||||||
if (!item) return
|
if (!item) return
|
||||||
await item.update({ "system.constructed": !item.system.constructed })
|
await item.update({ "system.constructed": !item.system.constructed })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onCreateRegiment() {
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [{
|
||||||
|
name: game.i18n.localize("OATHHAMMER.NewItem.Regiment"),
|
||||||
|
type: "regiment",
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onCollectTaxes() {
|
||||||
|
const actor = this.document
|
||||||
|
// Only constructed buildings with a non-empty taxRevenue formula
|
||||||
|
const taxBuildings = actor.itemTypes.building.filter(
|
||||||
|
b => b.system.constructed && b.system.taxRevenue?.trim()
|
||||||
|
)
|
||||||
|
if (!taxBuildings.length) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("OATHHAMMER.Settlement.NoTaxRevenue"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roll each building's formula individually, sum totals
|
||||||
|
const rolls = []
|
||||||
|
const lines = []
|
||||||
|
let total = 0
|
||||||
|
for (const b of taxBuildings) {
|
||||||
|
const r = new Roll(b.system.taxRevenue.trim())
|
||||||
|
await r.evaluate()
|
||||||
|
rolls.push(r)
|
||||||
|
total += r.total
|
||||||
|
lines.push(`<li><strong>${b.name}</strong> — ${b.system.taxRevenue} = <em>${r.total} gp</em></li>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card oh-weapon-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<span class="oh-roll-title">🏛 ${actor.name}</span>
|
||||||
|
<span class="oh-roll-subtitle">${game.i18n.localize("OATHHAMMER.Settlement.CollectTaxes")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<ul style="margin:4px 0;padding-left:1.2em;">${lines.join("")}</ul>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-result">
|
||||||
|
<span class="oh-roll-successes">${total} gp</span>
|
||||||
|
<span class="oh-roll-verdict">${game.i18n.localize("OATHHAMMER.Settlement.TotalRevenue")}</span>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
const msgData = {
|
||||||
|
speaker: ChatMessage.getSpeaker({ actor }),
|
||||||
|
content,
|
||||||
|
rolls,
|
||||||
|
sound: CONFIG.sounds.dice,
|
||||||
|
}
|
||||||
|
ChatMessage.applyRollMode(msgData, game.settings.get("core", "rollMode"))
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
module/applications/sheets/skillnpc-sheet.mjs
Normal file
37
module/applications/sheets/skillnpc-sheet.mjs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import OathHammerItemSheet from "./base-item-sheet.mjs"
|
||||||
|
import { SYSTEM } from "../../config/system.mjs"
|
||||||
|
|
||||||
|
export default class OathHammerSkillNPCSheet extends OathHammerItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["skillnpc"],
|
||||||
|
position: { width: 460 },
|
||||||
|
window: { contentClasses: ["skillnpc-content"] },
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/fvtt-oath-hammer/templates/item/skillnpc-sheet.hbs" },
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
// Build dicePool selector (0–20)
|
||||||
|
context.dicePoolChoices = Object.fromEntries(
|
||||||
|
Array.from({ length: 21 }, (_, i) => [i, String(i)])
|
||||||
|
)
|
||||||
|
// Color choices (localized labels)
|
||||||
|
context.colorChoices = Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)])
|
||||||
|
)
|
||||||
|
// Skill reference choices (optional)
|
||||||
|
context.skillRefChoices = {
|
||||||
|
"": `— ${game.i18n.localize("OATHHAMMER.Label.None")} —`,
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(SYSTEM.SKILLS).map(([k, v]) => [k, game.i18n.localize(v.label)])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ export default class OathHammerWeaponDialog {
|
|||||||
const actorSys = actor.system
|
const actorSys = actor.system
|
||||||
|
|
||||||
const isRanged = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
|
const isRanged = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
|
||||||
const skillKey = isRanged ? "shooting" : "fighting"
|
const skillKey = (sys.skillOverride && SYSTEM.SKILLS[sys.skillOverride]) ? sys.skillOverride : (isRanged ? "shooting" : "fighting")
|
||||||
const skillDef = SYSTEM.SKILLS[skillKey]
|
const skillDef = SYSTEM.SKILLS[skillKey]
|
||||||
const defaultAttr = skillDef.attribute
|
const defaultAttr = skillDef.attribute
|
||||||
const attrRank = actorSys.attributes[defaultAttr].rank
|
const attrRank = actorSys.attributes[defaultAttr].rank
|
||||||
@@ -53,7 +53,8 @@ export default class OathHammerWeaponDialog {
|
|||||||
const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4
|
const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4
|
||||||
const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
|
const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
|
||||||
const mightRank = actorSys.attributes.might.rank
|
const mightRank = actorSys.attributes.might.rank
|
||||||
const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
|
const damageAttrRank = actorSys.attributes[skillDef.attribute].rank
|
||||||
|
const baseDamageDice = sys.usesMight ? Math.max(damageAttrRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
|
||||||
|
|
||||||
const traitLabels = [...sys.traits].map(t => {
|
const traitLabels = [...sys.traits].map(t => {
|
||||||
const key = SYSTEM.WEAPON_TRAITS[t]
|
const key = SYSTEM.WEAPON_TRAITS[t]
|
||||||
@@ -329,8 +330,11 @@ export default class OathHammerWeaponDialog {
|
|||||||
const damageColorType = hasDeadly ? "black" : hasBrutal ? "red" : "white"
|
const damageColorType = hasDeadly ? "black" : hasBrutal ? "red" : "white"
|
||||||
const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4
|
const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4
|
||||||
const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
|
const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
|
||||||
const mightRank = actorSys.attributes.might.rank
|
const isRanged = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
|
||||||
const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
|
const damageSkillKey = (sys.skillOverride && SYSTEM.SKILLS[sys.skillOverride]) ? sys.skillOverride : (isRanged ? "shooting" : "fighting")
|
||||||
|
const damageAttrKey = SYSTEM.SKILLS[damageSkillKey].attribute
|
||||||
|
const damageAttrRank = actorSys.attributes[damageAttrKey].rank
|
||||||
|
const baseDamageDice = sys.usesMight ? Math.max(damageAttrRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
|
||||||
|
|
||||||
// Auto-bonuses from special properties
|
// Auto-bonuses from special properties
|
||||||
let autoDamageBonus = 0
|
let autoDamageBonus = 0
|
||||||
|
|||||||
@@ -177,6 +177,19 @@ export const MAGIC_QUALITY_CHOICES = {
|
|||||||
legendary: "OATHHAMMER.MagicQuality.Legendary"
|
legendary: "OATHHAMMER.MagicQuality.Legendary"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CLASS_RESTRICTION_CHOICES = {
|
||||||
|
berserker: "OATHHAMMER.Class.Berserker",
|
||||||
|
champion: "OATHHAMMER.Class.Champion",
|
||||||
|
delver: "OATHHAMMER.Class.Delver",
|
||||||
|
knight: "OATHHAMMER.Class.Knight",
|
||||||
|
mage: "OATHHAMMER.Class.Mage",
|
||||||
|
priest: "OATHHAMMER.Class.Priest",
|
||||||
|
scout: "OATHHAMMER.Class.Scout",
|
||||||
|
soldier: "OATHHAMMER.Class.Soldier",
|
||||||
|
spellblade: "OATHHAMMER.Class.Spellblade",
|
||||||
|
troubadour: "OATHHAMMER.Class.Troubadour",
|
||||||
|
}
|
||||||
|
|
||||||
export const RARITY_CHOICES = {
|
export const RARITY_CHOICES = {
|
||||||
always: "OATHHAMMER.Rarity.Always",
|
always: "OATHHAMMER.Rarity.Always",
|
||||||
common: "OATHHAMMER.Rarity.Common",
|
common: "OATHHAMMER.Rarity.Common",
|
||||||
@@ -198,9 +211,22 @@ export const RARITY_DV = {
|
|||||||
|
|
||||||
// Two types of trait per the rulebook terminology
|
// Two types of trait per the rulebook terminology
|
||||||
export const TRAIT_TYPE_CHOICES = {
|
export const TRAIT_TYPE_CHOICES = {
|
||||||
"special-trait": "OATHHAMMER.TraitType.SpecialTrait",
|
"special-trait": "OATHHAMMER.TraitType.SpecialTrait",
|
||||||
"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",
|
||||||
|
"creature-trait": "OATHHAMMER.TraitType.CreatureTrait"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NPC_SUBTYPES = {
|
||||||
|
"creature": "OATHHAMMER.NpcSubtype.Creature",
|
||||||
|
"npc": "OATHHAMMER.NpcSubtype.Npc"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DICE_COLOR_TYPES = {
|
||||||
|
"white": "OATHHAMMER.DiceColor.White",
|
||||||
|
"red": "OATHHAMMER.DiceColor.Red",
|
||||||
|
"black": "OATHHAMMER.DiceColor.Black"
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a trait's uses reset (none = passive/always on)
|
// When a trait's uses reset (none = passive/always on)
|
||||||
@@ -395,10 +421,13 @@ export const SYSTEM = {
|
|||||||
EQUIPMENT_TYPE_CHOICES,
|
EQUIPMENT_TYPE_CHOICES,
|
||||||
MAGIC_ITEM_TYPE_CHOICES,
|
MAGIC_ITEM_TYPE_CHOICES,
|
||||||
MAGIC_QUALITY_CHOICES,
|
MAGIC_QUALITY_CHOICES,
|
||||||
|
CLASS_RESTRICTION_CHOICES,
|
||||||
RARITY_CHOICES,
|
RARITY_CHOICES,
|
||||||
RARITY_DV,
|
RARITY_DV,
|
||||||
TRAIT_TYPE_CHOICES,
|
TRAIT_TYPE_CHOICES,
|
||||||
TRAIT_USAGE_PERIOD,
|
TRAIT_USAGE_PERIOD,
|
||||||
|
NPC_SUBTYPES,
|
||||||
|
DICE_COLOR_TYPES,
|
||||||
BUILDING_SKILL_CHOICES,
|
BUILDING_SKILL_CHOICES,
|
||||||
SETTLEMENT_ARCHETYPES,
|
SETTLEMENT_ARCHETYPES,
|
||||||
STATUS_EFFECTS,
|
STATUS_EFFECTS,
|
||||||
|
|||||||
@@ -12,3 +12,6 @@ export { default as OathHammerOath } from "./oath.mjs"
|
|||||||
export { default as OathHammerClass } from "./class.mjs"
|
export { default as OathHammerClass } from "./class.mjs"
|
||||||
export { default as OathHammerBuilding } from "./building.mjs"
|
export { default as OathHammerBuilding } from "./building.mjs"
|
||||||
export { default as OathHammerSettlement } from "./settlement.mjs"
|
export { default as OathHammerSettlement } from "./settlement.mjs"
|
||||||
|
export { default as OathHammerSkillNPC } from "./skillnpc.mjs"
|
||||||
|
export { default as OathHammerNpcAttack } from "./npcattack.mjs"
|
||||||
|
export { default as OathHammerRegiment } from "./regiment.mjs"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel
|
|||||||
// Total dice = attr rank + skill rank. Modifier = bonus (+) or penalty (-) dice.
|
// Total dice = attr rank + skill rank. Modifier = bonus (+) or penalty (-) dice.
|
||||||
// Color dice: type (white 4+, red 3+, black 2+) + count of colored dice in the pool.
|
// Color dice: type (white 4+, red 3+, black 2+) + count of colored dice in the pool.
|
||||||
const skillField = () => new fields.SchemaField({
|
const skillField = () => new fields.SchemaField({
|
||||||
rank: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 4 }),
|
rank: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }),
|
||||||
modifier: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
modifier: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
colorDiceType: new fields.StringField({ required: true, nullable: false, initial: "white",
|
colorDiceType: new fields.StringField({ required: true, nullable: false, initial: "white",
|
||||||
choices: { white: "OATHHAMMER.ColorDice.White", red: "OATHHAMMER.ColorDice.Red", black: "OATHHAMMER.ColorDice.Black" } }),
|
choices: { white: "OATHHAMMER.ColorDice.White", red: "OATHHAMMER.ColorDice.Red", black: "OATHHAMMER.ColorDice.Black" } }),
|
||||||
|
|||||||
@@ -9,16 +9,9 @@ export default class OathHammerNPC extends foundry.abstract.TypeDataModel {
|
|||||||
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
|
||||||
const attributeField = () => new fields.SchemaField({
|
// NPC (humanoid, needs light) vs Creature (monster, darkvision)
|
||||||
rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 4 })
|
schema.subtype = new fields.StringField({
|
||||||
})
|
required: true, initial: "creature", choices: SYSTEM.NPC_SUBTYPES
|
||||||
schema.attributes = new fields.SchemaField({
|
|
||||||
might: attributeField(),
|
|
||||||
toughness: attributeField(),
|
|
||||||
agility: attributeField(),
|
|
||||||
willpower: attributeField(),
|
|
||||||
intelligence: attributeField(),
|
|
||||||
fate: attributeField()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
schema.grit = new fields.SchemaField({
|
schema.grit = new fields.SchemaField({
|
||||||
@@ -26,8 +19,15 @@ export default class OathHammerNPC extends foundry.abstract.TypeDataModel {
|
|||||||
max: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 })
|
max: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Armor dice pool (value + color)
|
||||||
|
schema.armorDice = new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 20 }),
|
||||||
|
colorDiceType: new fields.StringField({ required: true, initial: "white", choices: SYSTEM.DICE_COLOR_TYPES })
|
||||||
|
})
|
||||||
|
|
||||||
schema.defense = new fields.SchemaField({
|
schema.defense = new fields.SchemaField({
|
||||||
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 })
|
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }),
|
||||||
|
colorDiceType: new fields.StringField({ required: true, initial: "white", choices: SYSTEM.DICE_COLOR_TYPES })
|
||||||
})
|
})
|
||||||
|
|
||||||
schema.movement = new fields.SchemaField({
|
schema.movement = new fields.SchemaField({
|
||||||
@@ -37,7 +37,7 @@ export default class OathHammerNPC extends foundry.abstract.TypeDataModel {
|
|||||||
schema.attackBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
|
schema.attackBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||||
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
|
schema.damageBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||||
schema.initiativeBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
|
schema.initiativeBonus = new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||||
schema.challengeRating = new fields.StringField({ required: true, nullable: false, initial: "1" })
|
schema.challengeRating = new fields.StringField({ required: true, nullable: false, initial: "1" })
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,5 @@ export default class OathHammerNPC extends foundry.abstract.TypeDataModel {
|
|||||||
|
|
||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
super.prepareDerivedData()
|
super.prepareDerivedData()
|
||||||
this.grit.max = this.attributes.might.rank + this.attributes.toughness.rank
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
module/models/npcattack.mjs
Normal file
42
module/models/npcattack.mjs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
|
export default class OathHammerNpcAttack extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
|
||||||
|
// Flat damage dice pool (no Might)
|
||||||
|
schema.damageDice = new fields.NumberField({
|
||||||
|
...requiredInteger, initial: 1, min: 0, max: 20
|
||||||
|
})
|
||||||
|
|
||||||
|
// Dice color: white (4+), red (3+), black (2+)
|
||||||
|
schema.colorDiceType = new fields.StringField({
|
||||||
|
required: true, initial: "white", choices: SYSTEM.DICE_COLOR_TYPES
|
||||||
|
})
|
||||||
|
|
||||||
|
// AP (Armor Penetration): penalty imposed on armor rolls
|
||||||
|
schema.ap = new fields.NumberField({
|
||||||
|
...requiredInteger, initial: 0, min: 0, max: 16
|
||||||
|
})
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOCALIZATION_PREFIXES = ["OATHHAMMER.NpcAttack"]
|
||||||
|
|
||||||
|
get threshold() {
|
||||||
|
return this.colorDiceType === "black" ? 2 : this.colorDiceType === "red" ? 3 : 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get colorEmoji() {
|
||||||
|
return this.colorDiceType === "black" ? "⬛" : this.colorDiceType === "red" ? "🔴" : "⬜"
|
||||||
|
}
|
||||||
|
|
||||||
|
get damageLabel() {
|
||||||
|
return `${this.colorEmoji} ${this.damageDice}d (${this.threshold}+)`
|
||||||
|
}
|
||||||
|
}
|
||||||
57
module/models/regiment.mjs
Normal file
57
module/models/regiment.mjs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
|
export default class OathHammerRegiment extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const { fields } = foundry.data
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: false, nullable: true, initial: "" })
|
||||||
|
schema.notes = new fields.StringField({ required: false, nullable: true, initial: "" })
|
||||||
|
|
||||||
|
schema.grit = new fields.SchemaField({
|
||||||
|
max: new fields.NumberField({ ...requiredInteger, initial: 20, min: 0, max: 200 }),
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.armorDice = new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0, max: 20 }),
|
||||||
|
colorDiceType: new fields.StringField({ required: true, nullable: false, initial: "white", choices: SYSTEM.DICE_COLOR_TYPES }),
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.movement = new fields.NumberField({ ...requiredInteger, initial: 60, min: 0, max: 500 })
|
||||||
|
|
||||||
|
// Embedded skill rows: [{name, value, colorDiceType}]
|
||||||
|
schema.skills = new fields.ArrayField(new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0, max: 6 }),
|
||||||
|
colorDiceType: new fields.StringField({ required: true, nullable: false, initial: "white", choices: SYSTEM.DICE_COLOR_TYPES }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Embedded attack rows: [{name, damageDice, colorDiceType, ap, special}]
|
||||||
|
schema.attacks = new fields.ArrayField(new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
damageDice: new fields.NumberField({ ...requiredInteger, initial: 6, min: 0, max: 20 }),
|
||||||
|
colorDiceType: new fields.StringField({ required: true, nullable: false, initial: "white", choices: SYSTEM.DICE_COLOR_TYPES }),
|
||||||
|
ap: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }),
|
||||||
|
special: new fields.StringField({ required: false, nullable: true, initial: "" }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Embedded trait rows: [{name, description}]
|
||||||
|
schema.traits = new fields.ArrayField(new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
description: new fields.StringField({ required: false, nullable: true, initial: "" }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Regiment"]
|
||||||
|
|
||||||
|
get colorEmoji() {
|
||||||
|
return { white: "⬜", red: "🔴", black: "⬛" }[this.armorDice.colorDiceType] ?? "⬜"
|
||||||
|
}
|
||||||
|
|
||||||
|
get armorLabel() {
|
||||||
|
return `${this.colorEmoji} ${this.armorDice.value}d`
|
||||||
|
}
|
||||||
|
}
|
||||||
36
module/models/skillnpc.mjs
Normal file
36
module/models/skillnpc.mjs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
|
export default class OathHammerSkillNPC extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
|
||||||
|
// Total dice pool for this skill (attribute + skill ranks combined)
|
||||||
|
schema.dicePool = new fields.NumberField({
|
||||||
|
required: true, nullable: false, integer: true, initial: 1, min: 0, max: 20
|
||||||
|
})
|
||||||
|
|
||||||
|
// Dice color: white (4+), red (3+), black (2+)
|
||||||
|
schema.colorDiceType = new fields.StringField({
|
||||||
|
required: true, initial: "white", choices: SYSTEM.DICE_COLOR_TYPES
|
||||||
|
})
|
||||||
|
|
||||||
|
// Optional reference to a system skill key (e.g. "fighting", "perception")
|
||||||
|
// Used for display/tooltip only — does not restrict the roll.
|
||||||
|
schema.skillRef = new fields.StringField({ required: false, nullable: true, initial: null })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOCALIZATION_PREFIXES = ["OATHHAMMER.SkillNPC"]
|
||||||
|
|
||||||
|
get threshold() {
|
||||||
|
return this.colorDiceType === "black" ? 2 : this.colorDiceType === "red" ? 3 : 4
|
||||||
|
}
|
||||||
|
|
||||||
|
get colorEmoji() {
|
||||||
|
return this.colorDiceType === "black" ? "⬛" : this.colorDiceType === "red" ? "🔴" : "⬜"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,7 +57,12 @@ export default class OathHammerWeapon extends foundry.abstract.TypeDataModel {
|
|||||||
// Enchantment description (displayed when isMagic is true)
|
// Enchantment description (displayed when isMagic is true)
|
||||||
schema.magicEffect = new fields.HTMLField({ required: false, textSearch: true })
|
schema.magicEffect = new fields.HTMLField({ required: false, textSearch: true })
|
||||||
// Class/lineage restriction, e.g. "Dwarves only" (empty = no restriction)
|
// Class/lineage restriction, e.g. "Dwarves only" (empty = no restriction)
|
||||||
schema.classRestriction = new fields.StringField({ required: true, nullable: false, initial: "" })
|
schema.classRestriction = new fields.StringField({ required: false, nullable: true, initial: null, choices: SYSTEM.CLASS_RESTRICTION_CHOICES })
|
||||||
|
|
||||||
|
// Override which skill (and its linked attribute) is used for attack rolls.
|
||||||
|
// Null / "" = auto-detect (fighting for melee, shooting for ranged).
|
||||||
|
// Use this for abilities like Magic Bolt that roll Magic+Willpower instead.
|
||||||
|
schema.skillOverride = new fields.StringField({ required: false, nullable: true, initial: null })
|
||||||
|
|
||||||
return schema
|
return schema
|
||||||
}
|
}
|
||||||
|
|||||||
336
module/rolls.mjs
336
module/rolls.mjs
@@ -242,7 +242,7 @@ export async function rollWeaponAttack(actor, weapon, options = {}) {
|
|||||||
const actorSys = actor.system
|
const actorSys = actor.system
|
||||||
|
|
||||||
const isRanged = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
|
const isRanged = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
|
||||||
const skillKey = isRanged ? "shooting" : "fighting"
|
const skillKey = (sys.skillOverride && SYSTEM.SKILLS[sys.skillOverride]) ? sys.skillOverride : (isRanged ? "shooting" : "fighting")
|
||||||
const skillDef = SYSTEM.SKILLS[skillKey]
|
const skillDef = SYSTEM.SKILLS[skillKey]
|
||||||
const defaultAttr = skillDef.attribute
|
const defaultAttr = skillDef.attribute
|
||||||
|
|
||||||
@@ -339,8 +339,11 @@ export async function rollWeaponDamage(actor, weapon, options = {}) {
|
|||||||
const colorEmoji = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
|
const colorEmoji = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
|
||||||
const colorLabel = hasDeadly ? "Black" : hasBrutal ? "Red" : "White"
|
const colorLabel = hasDeadly ? "Black" : hasBrutal ? "Red" : "White"
|
||||||
|
|
||||||
const mightRank = actorSys.attributes.might.rank
|
const isRangedDmg = sys.proficiencyGroup === "bows" || sys.proficiencyGroup === "throwing"
|
||||||
const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
|
const dmgSkillKey = (sys.skillOverride && SYSTEM.SKILLS[sys.skillOverride]) ? sys.skillOverride : (isRangedDmg ? "shooting" : "fighting")
|
||||||
|
const dmgAttrKey = SYSTEM.SKILLS[dmgSkillKey].attribute
|
||||||
|
const dmgAttrRank = actorSys.attributes[dmgAttrKey].rank
|
||||||
|
const baseDamageDice = sys.usesMight ? Math.max(dmgAttrRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
|
||||||
const totalDice = Math.max(baseDamageDice + sv + damageBonus + autoDamageBonus, 1)
|
const totalDice = Math.max(baseDamageDice + sv + damageBonus + autoDamageBonus, 1)
|
||||||
|
|
||||||
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold)
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold)
|
||||||
@@ -951,3 +954,330 @@ export async function rollInitiativeCheck(actor, options = {}) {
|
|||||||
|
|
||||||
return { successes, dv: 0, isSuccess: null }
|
return { successes, dv: 0, isSuccess: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// NPC SKILL ROLL
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Roll an NPC skill check (skillnpc item) and post to chat.
|
||||||
|
*
|
||||||
|
* @param {Actor} actor The NPC/creature actor
|
||||||
|
* @param {Item} skillItem The skillnpc item
|
||||||
|
* @param {object} options
|
||||||
|
*/
|
||||||
|
export async function rollNPCSkill(actor, skillItem, options = {}) {
|
||||||
|
const { bonus = 0, colorOverride, visibility } = options
|
||||||
|
const sys = skillItem.system
|
||||||
|
|
||||||
|
const colorType = colorOverride || sys.colorDiceType
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
|
|
||||||
|
const totalDice = Math.max(sys.dicePool + bonus, 1)
|
||||||
|
|
||||||
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
||||||
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
|
const modLine = bonus !== 0
|
||||||
|
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
||||||
|
: ""
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<img src="${skillItem.img}" class="oh-card-weapon-img" alt="${skillItem.name}" />
|
||||||
|
<span>${skillItem.name} — ${actor.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
|
||||||
|
</div>
|
||||||
|
${modLine}
|
||||||
|
<div class="oh-roll-dice">${diceHtml}</div>
|
||||||
|
<div class="oh-roll-result">
|
||||||
|
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const rollMode = visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
|
||||||
|
ChatMessage.applyRollMode(msgData, rollMode)
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
|
||||||
|
return { successes }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPC weapon attack roll — uses NPC's flat attackBonus as dice pool.
|
||||||
|
* Rolls white dice (4+) with optional bonus modifier.
|
||||||
|
*/
|
||||||
|
export async function rollNPCWeaponAttack(actor, weapon, options = {}) {
|
||||||
|
const { bonus = 0, visibility } = options
|
||||||
|
const sys = actor.system
|
||||||
|
const basePool = sys.attackBonus ?? 0
|
||||||
|
const totalDice = Math.max(basePool + bonus, 1)
|
||||||
|
const threshold = 4
|
||||||
|
const colorEmoji = "⬜"
|
||||||
|
|
||||||
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
||||||
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
|
const modParts = []
|
||||||
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card oh-weapon-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<img src="${weapon.img}" class="oh-card-weapon-img" alt="${weapon.name}" />
|
||||||
|
<span>${weapon.name} — ${game.i18n.localize("OATHHAMMER.Dialog.Attack")} (${actor.name})</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
|
||||||
|
</div>
|
||||||
|
${modLine}
|
||||||
|
<div class="oh-roll-dice">${diceHtml}</div>
|
||||||
|
<div class="oh-roll-result">
|
||||||
|
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const rollMode = visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
|
||||||
|
ChatMessage.applyRollMode(msgData, rollMode)
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
return { successes }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPC weapon damage roll — uses NPC damageBonus + weapon damageMod as dice pool.
|
||||||
|
*/
|
||||||
|
export async function rollNPCWeaponDamage(actor, weapon, options = {}) {
|
||||||
|
const { bonus = 0, visibility } = options
|
||||||
|
const sys = actor.system
|
||||||
|
const basePool = (sys.damageBonus ?? 0) + (weapon.system.damageMod ?? 0)
|
||||||
|
const totalDice = Math.max(basePool + bonus, 1)
|
||||||
|
const threshold = 4
|
||||||
|
const colorEmoji = "⬜"
|
||||||
|
|
||||||
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
||||||
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
|
const modParts = []
|
||||||
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card oh-weapon-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<img src="${weapon.img}" class="oh-card-weapon-img" alt="${weapon.name}" />
|
||||||
|
<span>${weapon.name} — ${game.i18n.localize("OATHHAMMER.Dialog.Damage")} (${actor.name})</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
|
||||||
|
<span>${game.i18n.localize("OATHHAMMER.Label.Damage")}: ${weapon.system.damageLabel}</span>
|
||||||
|
</div>
|
||||||
|
${modLine}
|
||||||
|
<div class="oh-roll-dice">${diceHtml}</div>
|
||||||
|
<div class="oh-roll-result">
|
||||||
|
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const rollMode = visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
|
||||||
|
ChatMessage.applyRollMode(msgData, rollMode)
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
return { successes }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPC armor dice roll — rolls actor's armorDice.value dice with armorDice.colorDiceType color.
|
||||||
|
*/
|
||||||
|
export async function rollNPCArmor(actor, options = {}) {
|
||||||
|
const { bonus = 0, colorOverride, visibility } = options
|
||||||
|
const sys = actor.system
|
||||||
|
|
||||||
|
const basePool = sys.armorDice?.value ?? 0
|
||||||
|
const colorType = colorOverride || sys.armorDice?.colorDiceType || "white"
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
|
const totalDice = Math.max(basePool + bonus, 1)
|
||||||
|
|
||||||
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
||||||
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
|
const modLine = bonus !== 0
|
||||||
|
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
||||||
|
: ""
|
||||||
|
|
||||||
|
const label = game.i18n.localize("OATHHAMMER.Label.ArmorDice")
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card oh-armor-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<img src="${actor.img}" class="oh-card-weapon-img" alt="${actor.name}" />
|
||||||
|
<span>${label} — ${actor.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
|
||||||
|
</div>
|
||||||
|
${modLine}
|
||||||
|
<div class="oh-roll-dice">${diceHtml}</div>
|
||||||
|
<div class="oh-roll-result ${successes > 0 ? "roll-success" : ""}">
|
||||||
|
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
|
||||||
|
<span class="oh-roll-verdict">−${successes} ${game.i18n.localize("OATHHAMMER.Roll.Damage")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const rollMode = visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice }
|
||||||
|
ChatMessage.applyRollMode(msgData, rollMode)
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
return { successes }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPC spell cast — flat dice pool, no arcane stress, posts DV success/failure to chat.
|
||||||
|
*/
|
||||||
|
export async function rollNPCSpell(actor, spell, options = {}) {
|
||||||
|
const { dicePool = 3, bonus = 0, colorOverride, visibility } = options
|
||||||
|
const dv = spell.system.difficultyValue ?? 1
|
||||||
|
const colorType = colorOverride || "white"
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
|
const totalDice = Math.max(dicePool + bonus, 1)
|
||||||
|
|
||||||
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
||||||
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
const isSuccess = successes >= dv
|
||||||
|
|
||||||
|
const modLine = bonus !== 0
|
||||||
|
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
||||||
|
: ""
|
||||||
|
|
||||||
|
const resultClass = isSuccess ? "roll-success" : "roll-failure"
|
||||||
|
const resultLabel = isSuccess
|
||||||
|
? game.i18n.localize("OATHHAMMER.Roll.Success")
|
||||||
|
: game.i18n.localize("OATHHAMMER.Roll.Failure")
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card oh-spell-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<img src="${spell.img}" class="oh-card-weapon-img" alt="${spell.name}" />
|
||||||
|
<span>${spell.name} (DV ${dv}) — ${actor.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
|
||||||
|
</div>
|
||||||
|
${modLine}
|
||||||
|
<div class="oh-roll-dice">${diceHtml}</div>
|
||||||
|
<div class="oh-roll-result ${resultClass}">
|
||||||
|
<span class="oh-roll-successes">${successes} / ${dv}</span>
|
||||||
|
<span class="oh-roll-verdict">${resultLabel}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const rollMode = visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls, sound: CONFIG.sounds.dice }
|
||||||
|
ChatMessage.applyRollMode(msgData, rollMode)
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
return { successes, dv, isSuccess }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPC miracle invocation — flat dice pool, no blocked tracking, posts DV success/failure to chat.
|
||||||
|
*/
|
||||||
|
export async function rollNPCMiracle(actor, miracle, options = {}) {
|
||||||
|
const { dicePool = 3, bonus = 0, visibility } = options
|
||||||
|
const dv = 1
|
||||||
|
const threshold = 4
|
||||||
|
const colorEmoji = "⬜"
|
||||||
|
const totalDice = Math.max(dicePool + bonus, 1)
|
||||||
|
|
||||||
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
||||||
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
const isSuccess = successes >= dv
|
||||||
|
|
||||||
|
const modLine = bonus !== 0
|
||||||
|
? `<div class="oh-roll-mods">${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}</div>`
|
||||||
|
: ""
|
||||||
|
|
||||||
|
const resultClass = isSuccess ? "roll-success" : "roll-failure"
|
||||||
|
const resultLabel = isSuccess
|
||||||
|
? game.i18n.localize("OATHHAMMER.Roll.Success")
|
||||||
|
: game.i18n.localize("OATHHAMMER.Roll.Failure")
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card oh-miracle-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<img src="${miracle.img}" class="oh-card-weapon-img" alt="${miracle.name}" />
|
||||||
|
<span>${miracle.name} — ${actor.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
|
||||||
|
</div>
|
||||||
|
${modLine}
|
||||||
|
<div class="oh-roll-dice">${diceHtml}</div>
|
||||||
|
<div class="oh-roll-result ${resultClass}">
|
||||||
|
<span class="oh-roll-successes">${successes} / ${dv}</span>
|
||||||
|
<span class="oh-roll-verdict">${resultLabel}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const rollMode = visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls, sound: CONFIG.sounds.dice }
|
||||||
|
ChatMessage.applyRollMode(msgData, rollMode)
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
return { successes, dv, isSuccess }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPC attack damage roll — flat dice pool from the npcattack item, no Might.
|
||||||
|
*/
|
||||||
|
export async function rollNPCAttackDamage(actor, attack, options = {}) {
|
||||||
|
const { bonus = 0, visibility } = options
|
||||||
|
const sys = attack.system
|
||||||
|
const colorType = sys.colorDiceType || "white"
|
||||||
|
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||||||
|
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||||||
|
const totalDice = Math.max((sys.damageDice ?? 1) + bonus, 1)
|
||||||
|
const ap = sys.ap ?? 0
|
||||||
|
|
||||||
|
const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false)
|
||||||
|
const diceHtml = _diceHtml(diceResults, threshold)
|
||||||
|
|
||||||
|
const modParts = []
|
||||||
|
if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`)
|
||||||
|
if (ap > 0) modParts.push(`AP ${ap}`)
|
||||||
|
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
<div class="oh-roll-card oh-weapon-card">
|
||||||
|
<div class="oh-roll-header">
|
||||||
|
<img src="${attack.img}" class="oh-card-weapon-img" alt="${attack.name}" />
|
||||||
|
<span>${attack.name} — ${game.i18n.localize("OATHHAMMER.Dialog.Damage")} (${actor.name})</span>
|
||||||
|
</div>
|
||||||
|
<div class="oh-roll-info">
|
||||||
|
<span>${colorEmoji} ${totalDice}d6 (${threshold}+)</span>
|
||||||
|
${ap > 0 ? `<span>AP ${ap}</span>` : ""}
|
||||||
|
</div>
|
||||||
|
${modLine}
|
||||||
|
<div class="oh-roll-dice">${diceHtml}</div>
|
||||||
|
<div class="oh-roll-result">
|
||||||
|
<span class="oh-roll-successes">${successes} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
const rollMode = visibility ?? game.settings.get("core", "rollMode")
|
||||||
|
const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls, sound: CONFIG.sounds.dice }
|
||||||
|
ChatMessage.applyRollMode(msgData, rollMode)
|
||||||
|
await ChatMessage.create(msgData)
|
||||||
|
return { successes }
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ Hooks.once("init", function () {
|
|||||||
trait: models.OathHammerTrait,
|
trait: models.OathHammerTrait,
|
||||||
oath: models.OathHammerOath,
|
oath: models.OathHammerOath,
|
||||||
"class": models.OathHammerClass,
|
"class": models.OathHammerClass,
|
||||||
building: models.OathHammerBuilding
|
building: models.OathHammerBuilding,
|
||||||
|
skillnpc: models.OathHammerSkillNPC,
|
||||||
|
npcattack: models.OathHammerNpcAttack,
|
||||||
|
regiment: models.OathHammerRegiment,
|
||||||
}
|
}
|
||||||
|
|
||||||
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
|
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet)
|
||||||
@@ -71,6 +74,9 @@ Hooks.once("init", function () {
|
|||||||
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.OathHammerOathSheet, { types: ["oath"], makeDefault: true, label: "OATHHAMMER.Sheet.Oath" })
|
||||||
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerClassSheet, { types: ["class"], makeDefault: true, label: "OATHHAMMER.Sheet.Class" })
|
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerClassSheet, { types: ["class"], makeDefault: true, label: "OATHHAMMER.Sheet.Class" })
|
||||||
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerBuildingSheet, { types: ["building"], makeDefault: true, label: "OATHHAMMER.Sheet.Building" })
|
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerBuildingSheet, { types: ["building"], makeDefault: true, label: "OATHHAMMER.Sheet.Building" })
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerSkillNPCSheet, { types: ["skillnpc"], makeDefault: true, label: "OATHHAMMER.Sheet.SkillNPC" })
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerNpcAttackSheet, { types: ["npcattack"], makeDefault: true, label: "OATHHAMMER.Sheet.NpcAttack" })
|
||||||
|
foundry.documents.collections.Items.registerSheet("fvtt-oath-hammer", applications.OathHammerRegimentSheet, { types: ["regiment"], makeDefault: true, label: "OATHHAMMER.Sheet.Regiment" })
|
||||||
|
|
||||||
CONFIG.statusEffects = STATUS_EFFECTS
|
CONFIG.statusEffects = STATUS_EFFECTS
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,10 @@
|
|||||||
"description",
|
"description",
|
||||||
"notes"
|
"notes"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"skillnpc": {},
|
||||||
|
"npcattack": {},
|
||||||
|
"regiment": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
|
|||||||
@@ -66,10 +66,4 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
{{!-- Background --}}
|
|
||||||
<fieldset>
|
|
||||||
<legend>{{localize "OATHHAMMER.Label.Background"}}</legend>
|
|
||||||
{{formInput systemFields.background enriched=enrichedBackground value=system.background name="system.background" toggled=true}}
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<section data-tab="notes" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
<section data-tab="notes" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Background"}}</legend>
|
||||||
|
{{formInput systemFields.background enriched=enrichedBackground value=system.background name="system.background" toggled=true}}
|
||||||
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
|
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
|
||||||
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
||||||
|
|||||||
@@ -1,29 +1,41 @@
|
|||||||
<section data-tab="combat" class="tab">
|
<section data-tab="combat" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
<div class="initiative-bar">
|
<div class="initiative-bar">
|
||||||
<a data-action="rollInitiative" class="initiative-roll-btn" data-tooltip="{{localize 'OATHHAMMER.Roll.InitiativeHint'}}">
|
<a data-action="rollInitiative" class="initiative-roll-btn" data-tooltip="{{localize 'OATHHAMMER.Roll.InitiativeHint'}}">
|
||||||
<i class="fa-solid fa-swords"></i> {{localize "OATHHAMMER.Dialog.RollInitiative"}}
|
<i class="fa-solid fa-swords"></i> {{localize "OATHHAMMER.Dialog.RollInitiative"}}
|
||||||
</a>
|
</a>
|
||||||
{{#if combatantInitiative}}<span class="initiative-score" data-tooltip="{{localize 'OATHHAMMER.Label.Initiative'}}">⚔ {{combatantInitiative}}</span>{{/if}}
|
{{#if combatantInitiative}}<span class="initiative-score" data-tooltip="{{localize 'OATHHAMMER.Label.Initiative'}}">⚔ {{combatantInitiative}}</span>{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{localize "OATHHAMMER.Label.Weapons"}}</legend>
|
<legend>{{localize "OATHHAMMER.Label.Attacks"}}
|
||||||
{{#if weapons.length}}
|
{{#unless isPlayMode}}<a data-action="createNpcAttack" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
|
||||||
<ul class="item-list">
|
</legend>
|
||||||
{{#each weapons as |weapon|}}
|
{{#if npcAttacks.length}}
|
||||||
<li class="item-entry flexrow" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}">
|
<ul class="item-list item-list--npc-attack">
|
||||||
<img src="{{weapon.img}}" class="item-img" />
|
<li class="item-list-header">
|
||||||
<span class="item-name">{{weapon.name}}</span>
|
<span></span>
|
||||||
<span class="item-detail">{{weapon.system.damageLabel}}</span>
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
<span class="item-detail">AP: {{weapon.system.ap}}</span>
|
<span>{{localize "OATHHAMMER.Label.Damage"}}</span>
|
||||||
{{#unless ../isPlayMode}}
|
<span title="Armor Penetration">AP</span>
|
||||||
<a data-action="edit" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-edit"></i></a>
|
<span></span>
|
||||||
<a data-action="delete" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
</li>
|
||||||
{{/unless}}
|
{{#each npcAttacks as |attack|}}
|
||||||
|
<li class="item-entry" data-item-id="{{attack.id}}" data-item-uuid="{{attack.uuid}}">
|
||||||
|
<img src="{{attack.img}}" class="item-img" />
|
||||||
|
<span class="item-name" {{#if attack._descTooltip}}data-tooltip="{{attack._descTooltip}}"{{/if}}>{{attack.name}}</span>
|
||||||
|
<span class="item-detail">{{attack.system.damageLabel}}</span>
|
||||||
|
<span class="item-detail">{{#if attack.system.ap}}{{attack.system.ap}}{{else}}—{{/if}}</span>
|
||||||
|
<div class="item-actions">
|
||||||
|
<a data-action="rollNpcAttack" data-item-id="{{attack.id}}" data-tooltip="{{localize 'OATHHAMMER.Dialog.Damage'}}"><i class="fa-solid fa-burst"></i></a>
|
||||||
|
<a data-action="edit" data-item-id="{{attack.id}}" data-item-uuid="{{attack.uuid}}"><i class="fa-solid fa-edit"></i></a>
|
||||||
|
<a data-action="delete" data-item-id="{{attack.id}}" data-item-uuid="{{attack.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p class="no-items">{{localize "OATHHAMMER.Label.NoWeapons"}}</p>
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoAttacks"}}</p>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
65
templates/actor/npc-equipment.hbs
Normal file
65
templates/actor/npc-equipment.hbs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<section data-tab="equipment" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
|
{{#unless isPlayMode}}
|
||||||
|
<p class="settlement-hint">{{localize "OATHHAMMER.Label.EquipmentNPCHint"}}</p>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{! Armor }}
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Armor"}}</legend>
|
||||||
|
{{#if armors.length}}
|
||||||
|
<ul class="item-list item-list--npc-armor">
|
||||||
|
<li class="item-list-header">
|
||||||
|
<span></span>
|
||||||
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span>AV</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Penalty"}}</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-detail">{{armor.system.armorValue}}</span>
|
||||||
|
<span class="item-detail">{{#if armor.system.penalty}}{{armor.system.penalty}}{{else}}—{{/if}}</span>
|
||||||
|
<div class="item-actions">
|
||||||
|
{{#unless ../isPlayMode}}
|
||||||
|
<a data-action="edit" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-edit"></i></a>
|
||||||
|
<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>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoArmor"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
{{! Equipment }}
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Equipment"}}</legend>
|
||||||
|
{{#if equipment.length}}
|
||||||
|
<ul class="item-list item-list--npc-equip">
|
||||||
|
<li class="item-list-header">
|
||||||
|
<span></span>
|
||||||
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span></span>
|
||||||
|
</li>
|
||||||
|
{{#each equipment as |item|}}
|
||||||
|
<li class="item-entry" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}">
|
||||||
|
<img src="{{item.img}}" class="item-img" />
|
||||||
|
<span class="item-name">{{item.name}}</span>
|
||||||
|
<div class="item-actions">
|
||||||
|
{{#unless ../isPlayMode}}
|
||||||
|
<a data-action="edit" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fa-solid fa-edit"></i></a>
|
||||||
|
<a data-action="delete" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoEquipment"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
69
templates/actor/npc-magic.hbs
Normal file
69
templates/actor/npc-magic.hbs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<section data-tab="magic" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Spells"}}
|
||||||
|
{{#unless isPlayMode}}<a data-action="createSpell" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
|
||||||
|
</legend>
|
||||||
|
{{#if spells.length}}
|
||||||
|
<ul class="item-list item-list--spell">
|
||||||
|
<li class="item-list-header">
|
||||||
|
<span></span>
|
||||||
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span>DV</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Tradition"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Range"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Duration"}}</span>
|
||||||
|
<span></span>
|
||||||
|
</li>
|
||||||
|
{{#each spells as |spell|}}
|
||||||
|
<li class="item-entry" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}">
|
||||||
|
<img src="{{spell.img}}" class="item-img" />
|
||||||
|
<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 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>
|
||||||
|
<div class="item-actions">
|
||||||
|
<a data-action="castNPCSpell" 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>
|
||||||
|
<a data-action="delete" data-item-id="{{spell.id}}" data-item-uuid="{{spell.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoSpells"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Miracles"}}
|
||||||
|
{{#unless isPlayMode}}<a data-action="createMiracle" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
|
||||||
|
</legend>
|
||||||
|
{{#if miracles.length}}
|
||||||
|
<ul class="item-list item-list--miracle">
|
||||||
|
<li class="item-list-header">
|
||||||
|
<span></span>
|
||||||
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.DivineTradition"}}</span>
|
||||||
|
<span></span>
|
||||||
|
</li>
|
||||||
|
{{#each miracles as |miracle|}}
|
||||||
|
<li class="item-entry" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}">
|
||||||
|
<img src="{{miracle.img}}" class="item-img" />
|
||||||
|
<span class="item-name" {{#if miracle._descTooltip}}data-tooltip="{{miracle._descTooltip}}"{{/if}}>{{miracle.name}}</span>
|
||||||
|
<span class="item-detail">{{miracle.system.divineTradition}}</span>
|
||||||
|
<div class="item-actions">
|
||||||
|
<a data-action="castNPCMiracle" data-item-id="{{miracle.id}}" title="{{localize 'OATHHAMMER.Action.InvokeMiracle'}}"><i class="fa-solid fa-hands-praying miracle-cast-icon"></i></a>
|
||||||
|
<a data-action="edit" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-edit"></i></a>
|
||||||
|
<a data-action="delete" data-item-id="{{miracle.id}}" data-item-uuid="{{miracle.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoMiracles"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</section>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<section data-tab="notes" class="tab">
|
<section data-tab="notes" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
|
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
|
||||||
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
<section class="npc-main npc-main-{{ifThen isPlayMode 'play' 'edit'}}">
|
<section class="npc-main npc-main-{{ifThen isPlayMode 'play' 'edit'}}">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{localize "OATHHAMMER.Label.NPC"}}</legend>
|
<legend>
|
||||||
|
{{#if isPlayMode}}
|
||||||
|
<span class="npc-subtype-badge npc-subtype-{{system.subtype}}">
|
||||||
|
{{localize (lookup subtypeLabels system.subtype)}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
<select name="system.subtype" class="npc-subtype-select">
|
||||||
|
{{selectOptions subtypeChoices selected=system.subtype}}
|
||||||
|
</select>
|
||||||
|
{{/if}}
|
||||||
|
</legend>
|
||||||
<div class="npc-pc flexrow">
|
<div class="npc-pc flexrow">
|
||||||
|
|
||||||
|
<!-- LEFT: portrait only -->
|
||||||
<div class="npc-left">
|
<div class="npc-left">
|
||||||
<img class="actor-img" src="{{actor.img}}" data-edit="img" data-action="editImage" data-tooltip="{{actor.name}}" />
|
<img class="actor-img" src="{{actor.img}}" data-edit="img" data-action="editImage" data-tooltip="{{actor.name}}" />
|
||||||
<fieldset>
|
|
||||||
<div class="flexrow character-resource">
|
|
||||||
<span class="resource-label">{{localize "OATHHAMMER.Label.Grit"}}</span>
|
|
||||||
{{formInput systemFields.grit.fields.value value=system.grit.value name="system.grit.value" disabled=isPlayMode}}
|
|
||||||
<span>/</span>
|
|
||||||
<input type="text" value="{{system.grit.max}}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="flexrow character-resource">
|
|
||||||
<span class="resource-label">{{localize "OATHHAMMER.Label.Defense"}}</span>
|
|
||||||
{{formInput systemFields.defense.fields.value value=system.defense.value name="system.defense.value" disabled=isPlayMode}}
|
|
||||||
</div>
|
|
||||||
<div class="flexrow character-resource">
|
|
||||||
<span class="resource-label">{{localize "OATHHAMMER.Label.Movement"}}</span>
|
|
||||||
{{formInput systemFields.movement.fields.base value=system.movement.base name="system.movement.base" disabled=isPlayMode}}
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT: name + vitals grid + stats -->
|
||||||
<div class="npc-right">
|
<div class="npc-right">
|
||||||
<div class="character-name">
|
<div class="character-name">
|
||||||
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
|
{{formInput fields.name value=source.name rootId=partId disabled=isPlayMode}}
|
||||||
@@ -28,39 +26,49 @@
|
|||||||
<i class="fa-solid fa-user-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
|
<i class="fa-solid fa-user-{{ifThen isPlayMode 'lock' 'pen'}}"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<fieldset>
|
|
||||||
<legend>{{localize "OATHHAMMER.Label.Stats"}}</legend>
|
<!-- Vitals: 2×2 grid -->
|
||||||
<div class="flexrow">
|
<div class="npc-vitals-grid">
|
||||||
<div class="form-group">
|
|
||||||
<label>{{localize "OATHHAMMER.Label.CR"}}</label>
|
<div class="npc-vital npc-vital-grit">
|
||||||
{{formInput systemFields.challengeRating value=system.challengeRating name="system.challengeRating" disabled=isPlayMode}}
|
<span class="vital-label">{{localize "OATHHAMMER.Label.Grit"}}</span>
|
||||||
</div>
|
<span class="vital-value">
|
||||||
<div class="form-group">
|
<a class="grit-btn" data-action="adjustGrit" data-delta="-1" data-tooltip="−1">−</a>
|
||||||
<label>{{localize "OATHHAMMER.Label.AttackBonus"}}</label>
|
<input type="number" class="npc-num-input" name="system.grit.value" value="{{system.grit.value}}" min="0" />
|
||||||
{{formInput systemFields.attackBonus value=system.attackBonus name="system.attackBonus" disabled=isPlayMode}}
|
<span class="res-sep">/</span>
|
||||||
</div>
|
{{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}}
|
||||||
<div class="form-group">
|
<a class="grit-btn" data-action="adjustGrit" data-delta="1" data-tooltip="+1">+</a>
|
||||||
<label>{{localize "OATHHAMMER.Label.DamageBonus"}}</label>
|
</span>
|
||||||
{{formInput systemFields.damageBonus value=system.damageBonus name="system.damageBonus" disabled=isPlayMode}}
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>{{localize "OATHHAMMER.Label.InitiativeBonus"}}</label>
|
|
||||||
{{formInput systemFields.initiativeBonus value=system.initiativeBonus name="system.initiativeBonus" disabled=isPlayMode}}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
<div class="npc-vital">
|
||||||
<legend>{{localize "OATHHAMMER.Label.Attributes"}}</legend>
|
<span class="vital-label{{#if isPlayMode}} vital-roll-label{{/if}}"
|
||||||
<div class="attributes-grid">
|
{{#if isPlayMode}}data-action="rollArmor" data-tooltip="OATHHAMMER.Tooltip.RollArmor"{{/if}}>
|
||||||
{{#each system.attributes as |attr key|}}
|
{{#if isPlayMode}}<i class="fa-solid fa-dice-d6"></i>{{/if}}
|
||||||
<div class="attribute-box">
|
{{localize "OATHHAMMER.Label.ArmorDice"}}
|
||||||
<label>{{localize (concat "OATHHAMMER.Attribute." (capitalize key))}}</label>
|
</span>
|
||||||
{{formInput (lookup ../systemFields.attributes.fields key).fields.rank value=attr.rank name=(concat "system.attributes." key ".rank") disabled=../isPlayMode}}
|
<span class="vital-value">
|
||||||
</div>
|
<input type="number" class="npc-num-input" name="system.armorDice.value" value="{{system.armorDice.value}}" min="0" {{#if isPlayMode}}disabled{{/if}} />
|
||||||
{{/each}}
|
{{#if isPlayMode}}
|
||||||
|
<span class="npc-color-badge">{{armorDiceEmoji}}</span>
|
||||||
|
{{else}}
|
||||||
|
<select name="system.armorDice.colorDiceType" class="npc-color-select">
|
||||||
|
{{selectOptions colorChoices selected=system.armorDice.colorDiceType}}
|
||||||
|
</select>
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
|
||||||
</div>
|
<div class="npc-vital">
|
||||||
|
<span class="vital-label">{{localize "OATHHAMMER.Label.Movement"}}</span>
|
||||||
|
<span class="vital-value">
|
||||||
|
{{formInput systemFields.movement.fields.base value=system.movement.base name="system.movement.base" disabled=isPlayMode}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- /npc-vitals-grid -->
|
||||||
|
|
||||||
|
</div><!-- /npc-right -->
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
39
templates/actor/npc-skills.hbs
Normal file
39
templates/actor/npc-skills.hbs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<section data-tab="skills" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
|
{{#unless isPlayMode}}
|
||||||
|
<p class="settlement-hint">{{localize "OATHHAMMER.Label.SkillNPCHint"}}</p>
|
||||||
|
{{/unless}}
|
||||||
|
{{#if skills.length}}
|
||||||
|
<ul class="item-list item-list--npc-skill">
|
||||||
|
<li class="item-list-header">
|
||||||
|
<span></span>
|
||||||
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span data-tooltip="{{localize 'OATHHAMMER.Label.DicePool'}}">{{localize "OATHHAMMER.Label.DicePool"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Threshold"}}</span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</li>
|
||||||
|
{{#each skills as |skill|}}
|
||||||
|
<li class="item-entry" data-item-id="{{skill.id}}" data-item-uuid="{{skill.uuid}}">
|
||||||
|
<span class="npc-skill-color npc-skill-color-{{skill.system.colorDiceType}}"
|
||||||
|
data-tooltip="{{skill.system.colorType}} ({{skill.system.threshold}})">
|
||||||
|
{{skill.system.colorEmoji}}
|
||||||
|
</span>
|
||||||
|
<span class="item-name">{{skill.name}}</span>
|
||||||
|
<span class="item-detail">{{skill.system.dicePool}}d</span>
|
||||||
|
<span class="item-detail">{{skill.system.threshold}}</span>
|
||||||
|
<a class="npc-skill-roll-btn" data-action="rollSkillNPC"
|
||||||
|
data-item-id="{{skill.id}}" data-item-uuid="{{skill.uuid}}"
|
||||||
|
data-tooltip="{{localize 'OATHHAMMER.Roll.RollSkill'}}">
|
||||||
|
<i class="fa-solid fa-dice-d6"></i>
|
||||||
|
</a>
|
||||||
|
<div class="item-actions">
|
||||||
|
<a data-action="edit" data-item-id="{{skill.id}}" data-item-uuid="{{skill.uuid}}"><i class="fa-solid fa-edit"></i></a>
|
||||||
|
<a data-action="delete" data-item-id="{{skill.id}}" data-item-uuid="{{skill.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoSkills"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</section>
|
||||||
36
templates/actor/npc-traits.hbs
Normal file
36
templates/actor/npc-traits.hbs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<section data-tab="traits" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
|
{{#unless isPlayMode}}
|
||||||
|
<p class="settlement-hint">{{localize "OATHHAMMER.Label.TraitNPCHint"}}</p>
|
||||||
|
{{/unless}}
|
||||||
|
{{#if traits.length}}
|
||||||
|
<ul class="item-list item-list--npc-trait">
|
||||||
|
<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 traits as |trait|}}
|
||||||
|
<li class="item-entry" data-item-id="{{trait.id}}" data-item-uuid="{{trait.uuid}}">
|
||||||
|
<img src="{{trait.img}}" class="item-img" />
|
||||||
|
<span class="item-name" {{#if trait._descTooltip}}data-tooltip="{{trait._descTooltip}}"{{/if}}>{{trait.name}}</span>
|
||||||
|
{{#if trait.system.traitType}}
|
||||||
|
<span class="npc-trait-type-badge npc-trait-type-{{trait.system.traitType}}">
|
||||||
|
{{localize (lookup ../traitTypeLabels trait.system.traitType)}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
<span></span>
|
||||||
|
{{/if}}
|
||||||
|
<div class="item-actions">
|
||||||
|
{{#unless ../isPlayMode}}
|
||||||
|
<a data-action="edit" data-item-id="{{trait.id}}" data-item-uuid="{{trait.uuid}}"><i class="fa-solid fa-edit"></i></a>
|
||||||
|
<a data-action="delete" data-item-id="{{trait.id}}" data-item-uuid="{{trait.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoTraits"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</section>
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
<section data-tab="buildings" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
<section data-tab="buildings" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
<p class="settlement-hint">{{localize "OATHHAMMER.Settlement.BuildingHint"}}</p>
|
<div class="settlement-buildings-header">
|
||||||
|
<p class="settlement-hint">{{localize "OATHHAMMER.Settlement.BuildingHint"}}</p>
|
||||||
|
{{#if hasTaxBuildings}}
|
||||||
|
<a data-action="collectTaxes" class="collect-taxes-btn" data-tooltip="{{localize 'OATHHAMMER.Settlement.CollectTaxesTooltip'}}">
|
||||||
|
<i class="fa-solid fa-coins"></i> {{localize "OATHHAMMER.Settlement.CollectTaxes"}}
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
{{#if buildings.length}}
|
{{#if buildings.length}}
|
||||||
<ul class="item-list item-list--buildings">
|
<ul class="item-list item-list--buildings">
|
||||||
@@ -14,7 +21,7 @@
|
|||||||
{{#each buildings as |building|}}
|
{{#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}}">
|
<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" />
|
<img src="{{building.img}}" class="item-img" />
|
||||||
<span class="item-name" data-tooltip="{{building.name}}">{{building.name}}</span>
|
<span class="item-name" {{#if building._descTooltip}}data-tooltip="{{building._descTooltip}}"{{else}}data-tooltip="{{building.name}}"{{/if}}>{{building.name}}</span>
|
||||||
<span class="item-constructed">
|
<span class="item-constructed">
|
||||||
{{#unless ../isPlayMode}}
|
{{#unless ../isPlayMode}}
|
||||||
<a data-action="toggleConstructed" data-item-id="{{building.id}}" class="construct-toggle">
|
<a data-action="toggleConstructed" data-item-id="{{building.id}}" class="construct-toggle">
|
||||||
@@ -30,7 +37,7 @@
|
|||||||
<span class="item-cost">{{building.system.cost}} gp</span>
|
<span class="item-cost">{{building.system.cost}} gp</span>
|
||||||
<div class="item-actions">
|
<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>
|
<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}}
|
<a data-action="delete" data-item-id="{{building.id}}" data-item-uuid="{{building.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
36
templates/actor/settlement-garrison.hbs
Normal file
36
templates/actor/settlement-garrison.hbs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<section data-tab="garrison" data-group="{{tab.group}}" class="tab {{tab.cssClass}}">
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Garrison"}}
|
||||||
|
{{#unless isPlayMode}}<a data-action="createRegiment" class="create-btn"><i class="fa-solid fa-plus"></i></a>{{/unless}}
|
||||||
|
</legend>
|
||||||
|
{{#if regiments.length}}
|
||||||
|
<ul class="item-list item-list--regiment">
|
||||||
|
<li class="item-list-header">
|
||||||
|
<span></span>
|
||||||
|
<span class="col-name">{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.GritMax"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.ArmorDice"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Movement"}}</span>
|
||||||
|
<span></span>
|
||||||
|
</li>
|
||||||
|
{{#each regiments as |regiment|}}
|
||||||
|
<li class="item-entry" data-item-id="{{regiment.id}}" data-item-uuid="{{regiment.uuid}}">
|
||||||
|
<img src="{{regiment.img}}" class="item-img" />
|
||||||
|
<span class="item-name">{{regiment.name}}</span>
|
||||||
|
<span class="item-detail">{{regiment.system.grit.max}}</span>
|
||||||
|
<span class="item-detail">{{regiment.system.armorLabel}}</span>
|
||||||
|
<span class="item-detail">{{regiment.system.movement}} ft</span>
|
||||||
|
<div class="item-actions">
|
||||||
|
<a data-action="edit" data-item-id="{{regiment.id}}" data-item-uuid="{{regiment.uuid}}" data-tooltip="{{localize 'OATHHAMMER.Label.Edit'}}"><i class="fa-solid fa-edit"></i></a>
|
||||||
|
<a data-action="delete" data-item-id="{{regiment.id}}" data-item-uuid="{{regiment.uuid}}" data-tooltip="{{localize 'OATHHAMMER.Label.Delete'}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoRegiments"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</section>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<span class="item-type">{{weapon.system.weaponType}}</span>
|
<span class="item-type">{{weapon.system.weaponType}}</span>
|
||||||
<div class="item-actions">
|
<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>
|
<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}}
|
<a data-action="delete" data-item-id="{{weapon.id}}" data-item-uuid="{{weapon.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<span class="item-type">{{localize armor.system.armorType}}</span>
|
<span class="item-type">{{localize armor.system.armorType}}</span>
|
||||||
<div class="item-actions">
|
<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>
|
<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}}
|
<a data-action="delete" data-item-id="{{armor.id}}" data-item-uuid="{{armor.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="item-actions">
|
<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>
|
<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}}
|
<a data-action="delete" data-item-id="{{equip.id}}" data-item-uuid="{{equip.uuid}}"><i class="fa-solid fa-trash"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -11,22 +11,6 @@
|
|||||||
<a data-action="adjustCurrency" data-field="system.currency.gold" data-delta="1" class="qty-btn">+</a>
|
<a data-action="adjustCurrency" data-field="system.currency.gold" data-delta="1" class="qty-btn">+</a>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="settlement-stats flexrow">
|
<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">
|
<div class="stat-item">
|
||||||
<label>{{localize "OATHHAMMER.Label.Territory"}}</label>
|
<label>{{localize "OATHHAMMER.Label.Territory"}}</label>
|
||||||
{{formInput systemFields.territory value=system.territory name="system.territory" disabled=isPlayMode}}
|
{{formInput systemFields.territory value=system.territory name="system.territory" disabled=isPlayMode}}
|
||||||
|
|||||||
38
templates/item/npcattack-sheet.hbs
Normal file
38
templates/item/npcattack-sheet.hbs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<section class="item-sheet-common npcattack-sheet">
|
||||||
|
<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="npcattack-stats">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.NpcAttack.FIELDS.damageDice.label"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.damageDice">
|
||||||
|
{{selectOptions dicePoolChoices selected=system.damageDice}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.NpcAttack.FIELDS.colorDiceType.label"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.colorDiceType">
|
||||||
|
{{selectOptions colorChoices selected=system.colorDiceType}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.NpcAttack.FIELDS.ap.label"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.ap">
|
||||||
|
{{selectOptions apChoices selected=system.ap}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.NpcAttack.FIELDS.description.label"}}</legend>
|
||||||
|
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
122
templates/item/regiment-sheet.hbs
Normal file
122
templates/item/regiment-sheet.hbs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<section class="item-sheet-common regiment-sheet">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<fieldset class="regiment-stats">
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Stats"}}</legend>
|
||||||
|
<div class="regiment-stats-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.Label.GritMax"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<input type="number" name="system.grit.max" value="{{system.grit.max}}" min="0" max="200" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.Label.ArmorDice"}}</label>
|
||||||
|
<div class="form-fields regiment-armor-fields">
|
||||||
|
<input type="number" name="system.armorDice.value" value="{{system.armorDice.value}}" min="0" max="20" />
|
||||||
|
<select name="system.armorDice.colorDiceType">
|
||||||
|
{{selectOptions colorChoices selected=system.armorDice.colorDiceType}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.Label.Movement"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<input type="number" name="system.movement" value="{{system.movement}}" min="0" max="500" /> ft
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="regiment-skills">
|
||||||
|
<legend>
|
||||||
|
{{localize "OATHHAMMER.Tab.Skills"}}
|
||||||
|
<a data-action="addSkill" class="create-btn" data-tooltip="{{localize 'OATHHAMMER.NewItem.RegimentSkill'}}"><i class="fa-solid fa-plus"></i></a>
|
||||||
|
</legend>
|
||||||
|
{{#if system.skills.length}}
|
||||||
|
<div class="regiment-skill-header regiment-skill-row">
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Rank"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.DiceColor"}}</span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
{{#each system.skills as |skill idx|}}
|
||||||
|
<div class="regiment-skill-row" data-idx="{{idx}}">
|
||||||
|
<input type="text" name="system.skills.{{idx}}.name" value="{{skill.name}}" placeholder="{{localize 'OATHHAMMER.Label.SkillName'}}" />
|
||||||
|
<input type="number" name="system.skills.{{idx}}.value" value="{{skill.value}}" min="1" max="6" />
|
||||||
|
<select name="system.skills.{{idx}}.colorDiceType">
|
||||||
|
{{selectOptions ../colorChoices selected=skill.colorDiceType}}
|
||||||
|
</select>
|
||||||
|
<a data-action="removeSkill" data-idx="{{idx}}" class="item-delete"><i class="fa-solid fa-times"></i></a>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoSkills"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="regiment-attacks">
|
||||||
|
<legend>
|
||||||
|
{{localize "OATHHAMMER.Label.Attacks"}}
|
||||||
|
<a data-action="addAttack" class="create-btn" data-tooltip="{{localize 'OATHHAMMER.NewItem.RegimentAttack'}}"><i class="fa-solid fa-plus"></i></a>
|
||||||
|
</legend>
|
||||||
|
{{#if system.attacks.length}}
|
||||||
|
<div class="regiment-attack-header regiment-attack-row">
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Name"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Dice"}}</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.DiceColor"}}</span>
|
||||||
|
<span>AP</span>
|
||||||
|
<span>{{localize "OATHHAMMER.Label.Special"}}</span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
{{#each system.attacks as |attack idx|}}
|
||||||
|
<div class="regiment-attack-row" data-idx="{{idx}}">
|
||||||
|
<input type="text" name="system.attacks.{{idx}}.name" value="{{attack.name}}" placeholder="{{localize 'OATHHAMMER.Label.AttackName'}}" />
|
||||||
|
<select name="system.attacks.{{idx}}.damageDice">
|
||||||
|
{{selectOptions ../dicePoolChoices selected=attack.damageDice}}
|
||||||
|
</select>
|
||||||
|
<select name="system.attacks.{{idx}}.colorDiceType">
|
||||||
|
{{selectOptions ../colorChoices selected=attack.colorDiceType}}
|
||||||
|
</select>
|
||||||
|
<select name="system.attacks.{{idx}}.ap">
|
||||||
|
{{selectOptions ../apChoices selected=attack.ap}}
|
||||||
|
</select>
|
||||||
|
<input type="text" name="system.attacks.{{idx}}.special" value="{{attack.special}}" placeholder="—" />
|
||||||
|
<a data-action="removeAttack" data-idx="{{idx}}" class="item-delete"><i class="fa-solid fa-times"></i></a>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoAttacks"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="regiment-traits">
|
||||||
|
<legend>
|
||||||
|
{{localize "OATHHAMMER.Tab.Traits"}}
|
||||||
|
<a data-action="addTrait" class="create-btn" data-tooltip="{{localize 'OATHHAMMER.NewItem.RegimentTrait'}}"><i class="fa-solid fa-plus"></i></a>
|
||||||
|
</legend>
|
||||||
|
{{#if system.traits.length}}
|
||||||
|
{{#each system.traits as |trait idx|}}
|
||||||
|
<div class="regiment-trait-row" data-idx="{{idx}}">
|
||||||
|
<input type="text" name="system.traits.{{idx}}.name" value="{{trait.name}}" placeholder="{{localize 'OATHHAMMER.Label.TraitName'}}" />
|
||||||
|
<input type="text" name="system.traits.{{idx}}.description" value="{{trait.description}}" placeholder="{{localize 'OATHHAMMER.Label.Description'}}" />
|
||||||
|
<a data-action="removeTrait" data-idx="{{idx}}" class="item-delete"><i class="fa-solid fa-times"></i></a>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<p class="no-items">{{localize "OATHHAMMER.Label.NoTraits"}}</p>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.Label.Description"}}</legend>
|
||||||
|
<prose-mirror name="system.description" toggled="false" collaborate="false">
|
||||||
|
{{{system.description}}}
|
||||||
|
</prose-mirror>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</section>
|
||||||
38
templates/item/skillnpc-sheet.hbs
Normal file
38
templates/item/skillnpc-sheet.hbs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<section class="item-sheet-common skillnpc-sheet">
|
||||||
|
<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="skillnpc-stats">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.SkillNPC.FIELDS.dicePool.label"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.dicePool">
|
||||||
|
{{selectOptions dicePoolChoices selected=system.dicePool}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.SkillNPC.FIELDS.colorDiceType.label"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.colorDiceType">
|
||||||
|
{{selectOptions colorChoices selected=system.colorDiceType}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.SkillNPC.FIELDS.skillRef.label"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.skillRef">
|
||||||
|
{{selectOptions skillRefChoices selected=system.skillRef}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "OATHHAMMER.SkillNPC.FIELDS.description.label"}}</legend>
|
||||||
|
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
@@ -6,6 +6,14 @@
|
|||||||
<div class="flexrow">
|
<div class="flexrow">
|
||||||
<div class="align-top">
|
<div class="align-top">
|
||||||
{{formField systemFields.proficiencyGroup value=system.proficiencyGroup name="system.proficiencyGroup" localize=true}}
|
{{formField systemFields.proficiencyGroup value=system.proficiencyGroup name="system.proficiencyGroup" localize=true}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.Weapon.FIELDS.skillOverride.label"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.skillOverride">
|
||||||
|
{{selectOptions skillChoices selected=system.skillOverride}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{localize "OATHHAMMER.Weapon.FIELDS.damageMod.label"}}</label>
|
<label>{{localize "OATHHAMMER.Weapon.FIELDS.damageMod.label"}}</label>
|
||||||
<div class="form-fields">
|
<div class="form-fields">
|
||||||
@@ -43,12 +51,29 @@
|
|||||||
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{#if system.isMagic}}
|
{{#if system.isMagic}}
|
||||||
<fieldset>
|
<fieldset class="enchantment-fieldset">
|
||||||
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
|
<legend>{{localize "OATHHAMMER.Label.Enchantment"}}</legend>
|
||||||
<div class="flexrow">
|
<div class="form-group">
|
||||||
{{formField systemFields.magicQuality value=system.magicQuality name="system.magicQuality" localize=true}}
|
<label>{{localize "OATHHAMMER.Label.MagicQuality"}}</label>
|
||||||
{{formField systemFields.isCursed value=system.isCursed name="system.isCursed"}}
|
<div class="form-fields">
|
||||||
{{formField systemFields.classRestriction value=system.classRestriction name="system.classRestriction"}}
|
<select name="system.magicQuality">
|
||||||
|
<option value="">—</option>
|
||||||
|
{{selectOptions systemFields.magicQuality.choices selected=system.magicQuality localize=true}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="enchant-cursed-label">
|
||||||
|
<input type="checkbox" name="system.isCursed" {{checked system.isCursed}}>
|
||||||
|
{{localize "OATHHAMMER.Label.Cursed"}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "OATHHAMMER.Label.ClassRestriction"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.classRestriction">
|
||||||
|
<option value="">— {{localize "OATHHAMMER.ClassRestriction.None"}} —</option>
|
||||||
|
{{selectOptions classRestrictionChoices selected=system.classRestriction localize=true}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}}
|
{{formInput systemFields.magicEffect enriched=enrichedMagicEffect value=system.magicEffect name="system.magicEffect" toggled=true}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
49
templates/npc-magic-dialog.hbs
Normal file
49
templates/npc-magic-dialog.hbs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<div class="oh-roll-dialog oh-weapon-dialog">
|
||||||
|
|
||||||
|
<div class="weapon-header">
|
||||||
|
<img src="{{itemImg}}" class="weapon-img-sm" alt="{{itemName}}" />
|
||||||
|
<div class="weapon-header-info">
|
||||||
|
<span class="weapon-name-lg">{{itemName}}</span>
|
||||||
|
<div class="weapon-badges">
|
||||||
|
{{#if dv}}<span class="roll-color-badge color-badge-white">DV {{dv}}</span>{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="attack-options-block">
|
||||||
|
<legend>{{localize "OATHHAMMER.Dialog.Options"}}</legend>
|
||||||
|
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.DicePool"}}</label>
|
||||||
|
<select name="dicePool">
|
||||||
|
{{#each poolOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.Modifier"}}</label>
|
||||||
|
<select name="bonus">
|
||||||
|
{{#each bonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if showColor}}
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.DiceColor"}}</label>
|
||||||
|
<select name="colorOverride">
|
||||||
|
<option value="">— {{localize "OATHHAMMER.Dialog.Default"}} (white) —</option>
|
||||||
|
{{selectOptions colorChoices selected=""}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="roll-visibility-block">
|
||||||
|
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
|
||||||
|
<select name="visibility">
|
||||||
|
{{selectOptions rollModes selected=visibility localize=true}}
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</div>
|
||||||
40
templates/npc-skill-dialog.hbs
Normal file
40
templates/npc-skill-dialog.hbs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<div class="oh-roll-dialog oh-weapon-dialog">
|
||||||
|
|
||||||
|
<div class="weapon-header">
|
||||||
|
<img src="{{skillImg}}" class="weapon-img-sm" alt="{{skillName}}" />
|
||||||
|
<div class="weapon-header-info">
|
||||||
|
<span class="weapon-name-lg">{{skillName}}</span>
|
||||||
|
<div class="weapon-badges">
|
||||||
|
<span class="roll-color-badge color-badge-{{colorType}}">{{colorEmoji}} {{dicePool}}d6 ({{threshold}}+)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="attack-options-block">
|
||||||
|
<legend>{{localize "OATHHAMMER.Dialog.Options"}}</legend>
|
||||||
|
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.Modifier"}}</label>
|
||||||
|
<select name="bonus">
|
||||||
|
{{#each bonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.DiceColor"}}</label>
|
||||||
|
<select name="colorOverride">
|
||||||
|
<option value="">— {{localize "OATHHAMMER.Dialog.Default"}} ({{colorType}}) —</option>
|
||||||
|
{{selectOptions colorChoices selected=colorType}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="roll-visibility-block">
|
||||||
|
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
|
||||||
|
<select name="visibility">
|
||||||
|
{{selectOptions rollModes selected=visibility localize=true}}
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</div>
|
||||||
31
templates/npc-weapon-roll-dialog.hbs
Normal file
31
templates/npc-weapon-roll-dialog.hbs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<div class="oh-roll-dialog oh-weapon-dialog">
|
||||||
|
|
||||||
|
<div class="weapon-header">
|
||||||
|
<img src="{{weaponImg}}" class="weapon-img-sm" alt="{{weaponName}}" />
|
||||||
|
<div class="weapon-header-info">
|
||||||
|
<span class="weapon-name-lg">{{weaponName}}</span>
|
||||||
|
<div class="weapon-badges">
|
||||||
|
<span class="roll-color-badge color-badge-white">⬜ {{basePool}}d6 (4+)</span>
|
||||||
|
<span class="damage-formula-badge">{{rollType}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<fieldset class="attack-options-block">
|
||||||
|
<legend>{{localize "OATHHAMMER.Dialog.Modifier"}}</legend>
|
||||||
|
<div class="roll-option-row">
|
||||||
|
<label>{{localize "OATHHAMMER.Dialog.AttackModifier"}}</label>
|
||||||
|
<select name="bonus">
|
||||||
|
{{#each bonusOptions}}<option value="{{value}}"{{#if selected}} selected{{/if}}>{{label}}</option>{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset class="roll-visibility-block">
|
||||||
|
<legend>{{localize "OATHHAMMER.Dialog.Visibility"}}</legend>
|
||||||
|
<select name="visibility">
|
||||||
|
{{selectOptions rollModes selected=visibility localize=true}}
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user