Merge branch 'master' into 012_equipped_flag

This commit is contained in:
Gesina Schwalbe 2020-12-31 15:07:11 +01:00
commit c2c0b63b9c
35 changed files with 1110 additions and 125 deletions

BIN
src/assets/DS4-DEF.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
src/assets/DS4-HP.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
src/assets/DS4-INI.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before After
Before After

BIN
src/assets/DS4-MR.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

BIN
src/assets/DS4-MSC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before After
Before After

BIN
src/assets/DS4-SPC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
src/assets/DS4-TSC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View file

@ -17,4 +17,5 @@
@import "scss/components/tabs";
@import "scss/components/items";
@import "scss/components/description";
@import "scss/components/character_values";
}

View file

@ -56,5 +56,30 @@
"DS4.ArmorMaterialTypeChain": "Chain",
"DS4.ArmorMaterialTypeChainAbbr": "Chain",
"DS4.ArmorMaterialTypePlate": "Plate",
"DS4.ArmorMaterialTypePlateAbbr": "Plate"
"DS4.ArmorMaterialTypePlateAbbr": "Plate",
"DS4.AttributeBody": "Body",
"DS4.AttributeMobility": "Mobility",
"DS4.AttributeMind": "Mind",
"DS4.TraitStrength": "Strength",
"DS4.TraitConstitution": "Constitution",
"DS4.TraitAgility": "Agility",
"DS4.TraitDexterity": "Dexterity",
"DS4.TraitIntellect": "Intellect",
"DS4.TraitAura": "Aura",
"DS4.CombatValuesHitPoints": "Hit Points",
"DS4.CombatValuesDefense": "Defense",
"DS4.CombatValuesInitiative": "Initiative",
"DS4.CombatValuesMovement": "Movement",
"DS4.CombatValuesMeleeAttack": "Melee Attack",
"DS4.CombatValuesRangedAttack": "Ranged Attack",
"DS4.CombatValuesSpellcasting": "Spellcasting",
"DS4.CombatValuesTargetedSpellcasting": "Targeted Spellcasting",
"DS4.BaseInfoRace": "Race",
"DS4.BaseInfoClass": "Class",
"DS4.BaseInfoHeroClass": "Hero Class",
"DS4.BaseInfoRacialAbilities": "Racial Abilites",
"DS4.ProgressionLevel": "Level",
"DS4.ProgressionExperiencePoints": "Experience Points",
"DS4.ProgressionTalentPoints": "Talent Points",
"DS4.ProgressionProgressPoints": "Progress Points"
}

View file

@ -1,26 +1,65 @@
export interface DS4ActorDataType {
attributes: DS4ActorDataAttributes;
traits: DS4ActorDataTraits;
combatValues: DS4ActorDataCombatValues;
baseInfo: DS4ActorDataBaseInfo;
progression: DS4ActorDataProgression;
}
interface DS4ActorDataAttributes {
body: BodyAttribute;
mobility: ExtensibleData<number>;
mind: ExtensibleData<number>;
mobility: ModifiableData<number>;
mind: ModifiableData<number>;
}
interface ExtensibleData<T> {
initial: T;
export interface ModifiableData<T> {
base: T;
mod: T;
total?: T;
}
interface UsableResource<T> {
total: T;
used: T;
}
interface CurrentData<T> extends ModifiableData<T> {
current: T;
}
// Blueprint in case we need more detailed differentiation
type BodyAttribute = ExtensibleData<number>;
type BodyAttribute = ModifiableData<number>;
interface DS4ActorDataTraits {
strength: ExtensibleData<number>;
constitution: ExtensibleData<number>;
agility: ExtensibleData<number>;
dexterity: ExtensibleData<number>;
intellect: ExtensibleData<number>;
aura: ExtensibleData<number>;
strength: ModifiableData<number>;
constitution: ModifiableData<number>;
agility: ModifiableData<number>;
dexterity: ModifiableData<number>;
intellect: ModifiableData<number>;
aura: ModifiableData<number>;
}
interface DS4ActorDataCombatValues {
hitPoints: CurrentData<number>;
defense: ModifiableData<number>;
initiative: ModifiableData<number>;
movement: ModifiableData<number>;
meleeAttack: ModifiableData<number>;
rangedAttack: ModifiableData<number>;
spellcasting: ModifiableData<number>;
targetedSpellcasting: ModifiableData<number>;
}
interface DS4ActorDataBaseInfo {
race: string;
class: string;
heroClass: string;
racialAbilities: string;
}
interface DS4ActorDataProgression {
level: number;
experiencePoints: number;
talentPoints: UsableResource<number>;
progressPoints: UsableResource<number>;
}

View file

@ -30,7 +30,7 @@ export class DS4ActorSheet extends ActorSheet<DS4ActorDataType, DS4Actor, DS4Ite
return mergeObject(super.defaultOptions, {
classes: ["ds4", "sheet", "actor"],
template: "systems/ds4/templates/actor/actor-sheet.hbs",
width: 600,
width: 725,
height: 600,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }],
});

View file

@ -1,43 +1,22 @@
import { DS4Item } from "../item/item";
import { DS4ItemDataType } from "../item/item-data";
import { DS4ActorDataType } from "./actor-data";
import { DS4ActorDataType, ModifiableData } from "./actor-data";
/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class DS4Actor extends Actor<DS4ActorDataType, DS4ItemDataType, DS4Item> {
/** @override */
prepareDerivedData(): void {
const data = this.data;
this._prepareCombatValues(data);
}
private _prepareCombatValues(data: ActorData<DS4ActorDataType>): void {
const hitPointsModifier = getProperty(data, "data.combatValues.hitPoints.modifier") || 0;
const actorData = data.data;
setProperty(
data,
"data.combatValues.hitPoints.max",
actorData.attributes.body.initial + actorData.traits.constitution.initial + 10 + hitPointsModifier,
const attributes = data.data.attributes;
Object.values(attributes).forEach(
(attribute: ModifiableData<number>) => (attribute.total = attribute.base + attribute.mod),
);
const defenseModifier = getProperty(data, "data.combatValues.defense.modifier") || 0;
setProperty(
data,
"data.combatValues.defense.value",
actorData.attributes.body.initial +
actorData.traits.constitution.initial +
this._getArmorValue() +
defenseModifier,
);
}
const traits = data.data.traits;
Object.values(traits).forEach((trait: ModifiableData<number>) => (trait.total = trait.base + trait.mod));
private _getArmorValue(): number {
return this.data["items"]
.filter((item) => ["armor", "shield"].includes(item.type))
.filter((item) => item.data.equipped)
.map((item) => item.data.armorValue)
.reduce((a, b) => a + b, 0);
const combatValues = data.data.combatValues;
Object.values(combatValues).forEach(
(combatValue: ModifiableData<number>) => (combatValue.total = combatValue.base + combatValue.mod),
);
}
}

View file

@ -10,7 +10,6 @@ export const DS4 = {
/**
* Define the set of acttack types that can be performed with weapon items
* @type {Object}
*/
attackTypes: {
melee: "DS4.AttackTypeMelee",
@ -19,8 +18,7 @@ export const DS4 = {
},
/**
* * Define the file paths to icon images
* @type {Object}
* Define the file paths to icon images
*/
attackTypesIcons: {
melee: "systems/ds4/assets/DS4-MAT.png",
@ -30,7 +28,6 @@ export const DS4 = {
/**
* Define the set of item availabilties
* @type {Object}
*/
itemAvailabilities: {
unset: "DS4.ItemAvailabilityUnset",
@ -43,8 +40,7 @@ export const DS4 = {
},
/**
* * Define the set of item types
* @type {Object}
* Define the set of item types
*/
itemTypes: {
weapon: "DS4.ItemTypeWeapon",
@ -55,8 +51,7 @@ export const DS4 = {
},
/**
* * Define the set of armor types, a character may only wear one item of each at any given time
* @type {Object}
* Define the set of armor types, a character may only wear one item of each at any given time
*/
armorTypes: {
body: "DS4.ArmorTypeBody",
@ -67,8 +62,7 @@ export const DS4 = {
},
/**
* * Define abbreviations for the armor types
* @type {Object}
* Define abbreviations for the armor types
*/
armorTypesAbbr: {
body: "DS4.ArmorTypeBodyAbbr",
@ -79,8 +73,7 @@ export const DS4 = {
},
/**
* * Define the set of armor materials, used to determine if a characer may wear the armor without additional penalties
* @type {Object}
* Define the set of armor materials, used to determine if a characer may wear the armor without additional penalties
*/
armorMaterialTypes: {
cloth: "DS4.ArmorMaterialTypeCloth",
@ -90,8 +83,7 @@ export const DS4 = {
},
/**
* * Define the abbreviations of armor materials
* @type {Object}
* Define the abbreviations of armor materials
*/
armorMaterialTypesAbbr: {
cloth: "DS4.ArmorMaterialTypeClothAbbr",
@ -99,4 +91,59 @@ export const DS4 = {
chain: "DS4.ArmorMaterialTypeChainAbbr",
plate: "DS4.ArmorMaterialTypePlateAbbr",
},
/**
* Define the set of attributes a character has
*/
attributes: {
body: "DS4.AttributeBody",
mobility: "DS4.AttributeMobility",
mind: "DS4.AttributeMind",
},
/**
* Define the set of traits a character has
*/
traits: {
strength: "DS4.TraitStrength",
constitution: "DS4.TraitConstitution",
agility: "DS4.TraitAgility",
dexterity: "DS4.TraitDexterity",
intellect: "DS4.TraitIntellect",
aura: "DS4.TraitAura",
},
/**
* Define the set of combat values a character has
*/
combatValues: {
hitPoints: "DS4.CombatValuesHitPoints",
defense: "DS4.CombatValuesDefense",
initiative: "DS4.CombatValuesInitiative",
movement: "DS4.CombatValuesMovement",
meleeAttack: "DS4.CombatValuesMeleeAttack",
rangedAttack: "DS4.CombatValuesRangedAttack",
spellcasting: "DS4.CombatValuesSpellcasting",
targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting",
},
/**
* Define the base info of a character
*/
baseInfo: {
race: "DS4.BaseInfoRace",
class: "DS4.BaseInfoClass",
heroClass: "DS4.BaseInfoHeroClass",
racialAbilities: "DS4.BaseInfoRacialAbilities",
},
/**
* Definme the progression info of a character
*/
progression: {
level: "DS4.ProgressionLevel",
experiencePoints: "DS4.ProgressionExperiencePoints",
talentPoints: "DS4.ProgressionTalentPoints",
progressPoints: "DS4.ProgressionProgressPoints",
},
};

View file

@ -58,6 +58,12 @@ Hooks.once("setup", function () {
"armorTypesAbbr",
"armorMaterialTypes",
"armorMaterialTypesAbbr",
"armorMaterialTypes",
"attributes",
"traits",
"combatValues",
"baseInfo",
"progression",
];
// Exclude some from sorting where the default order matters

View file

@ -0,0 +1,99 @@
import { DS4RollProvider, RollProvider } from "./roll-provider";
export function ds4test(testValue: number, rollOptions: RollOptions = new RollOptions()): RollResult {
const finalRollValue = testValue;
if (finalRollValue <= 20) {
return rollCheckSingleDie(finalRollValue, rollOptions);
} else {
return rollCheckMultipleDice(finalRollValue, rollOptions);
}
}
export function rollCheckSingleDie(
testValue: number,
rollOptions: RollOptions,
provider: RollProvider = new DS4RollProvider(),
): RollResult {
const roll = provider.getNextRoll();
const dice = [roll];
if (roll <= rollOptions.maxCritSucc) {
return new RollResult(testValue, RollResultStatus.CRITICAL_SUCCESS, dice);
} else if (roll >= rollOptions.minCritFail) {
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
} else {
if (roll <= testValue) {
return new RollResult(roll, RollResultStatus.SUCCESS, dice);
} else {
return new RollResult(0, RollResultStatus.FAILURE, dice);
}
}
}
export function rollCheckMultipleDice(
testValue: number,
rollOptions: RollOptions,
provider: RollProvider = new DS4RollProvider(),
): RollResult {
const finalCheck = testValue % 20;
const numberOfDice = Math.ceil(testValue / 20);
const dice = provider.getNextRolls(numberOfDice);
const firstResult = dice[0];
if (firstResult >= rollOptions.minCritFail) {
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
}
const partitionCallback = (prev: [Array<number>, Array<number>], cur: number) => {
if (cur <= rollOptions.maxCritSucc) {
prev[0].push(cur);
} else {
prev[1].push(cur);
}
return prev;
};
const [critSuccesses, otherRolls] = dice
.reduce(partitionCallback, [[], []])
.map((a) => a.sort((r1, r2) => r2 - r1));
const sortedRollResults: Array<number> = critSuccesses.concat(otherRolls);
const evaluationResult = sortedRollResults
.map((value, index) => {
if (index == numberOfDice - 1) {
if (value == 1) {
return finalCheck;
} else {
return value <= finalCheck ? value : 0;
}
} else {
if (value <= rollOptions.maxCritSucc) {
return 20;
} else {
return value;
}
}
})
.reduce((a, b) => a + b);
return new RollResult(evaluationResult, RollResultStatus.SUCCESS, sortedRollResults);
}
export class RollOptions {
public maxCritSucc = 1;
public minCritFail = 20;
}
export class RollResult {
constructor(public value: number, public status: RollResultStatus, public dice: Array<number>) {}
}
export enum RollResultStatus {
FAILURE,
SUCCESS,
CRITICAL_FAILURE,
CRITICAL_SUCCESS,
}

View file

@ -0,0 +1,16 @@
export class DS4RollProvider {
getNextRoll(): number {
return new Roll("1d20").roll().total;
}
getNextRolls(amount: number): Array<number> {
return Array(amount)
.fill(0)
.map(() => this.getNextRoll());
}
}
export interface RollProvider {
getNextRoll(): number;
getNextRolls(number: number): Array<number>;
}

View file

@ -0,0 +1,99 @@
header.sheet-header {
.character-values {
flex: 0 0 100%;
.attributes-traits {
margin-top: $margin-sm;
.attribute {
.attribute-label {
font-family: $font-heading;
font-size: 2em;
text-align: center;
}
.attribute-value {
border: 2px groove $c-border-groove;
line-height: 26px;
font-size: 1.5em;
text-align: center;
padding-left: 2px;
padding-right: 2px;
gap: 0;
input,
.attribute-value-total {
grid-column: span 2;
}
}
}
.trait {
.trait-label {
color: transparent;
font-family: $font-heading;
font-size: 2em;
text-align: center;
//text-shadow: -1px 1px 0 $c-black, 1px 1px 0 $c-black, 1px -1px 0 $c-black, -1px -1px 0 $c-black;
-webkit-text-stroke: 1px $c-black;
}
.trait-value {
border: 2px groove $c-border-groove;
font-size: 1.5em;
line-height: 26px;
text-align: center;
padding-left: 2px;
padding-right: 2px;
gap: 0;
input,
.trait-value-total {
grid-column: span 2;
}
}
}
}
.combat-values {
margin-top: $margin-sm;
.combat-value-with-formula {
display: grid;
place-items: center;
$size: 60px;
row-gap: $margin-sm;
.combat-value {
@include centered-content;
height: $size;
width: $size;
flex: 0 0 auto;
background-size: contain;
font-size: 1.5em;
&.hitPoints {
background-image: url("assets/DS4-HP.png");
}
&.defense {
background-image: url("assets/DS4-DEF.png");
}
&.initiative {
background-image: url("assets/DS4-INI.png");
}
&.movement {
background-image: url("assets/DS4-MR.png");
}
&.meleeAttack {
background-image: url("assets/DS4-MAT.png");
}
&.rangedAttack {
background-image: url("assets/DS4-RAT.png");
}
&.spellcasting {
background-image: url("assets/DS4-SPC.png");
}
&.targetedSpellcasting {
background-image: url("assets/DS4-TSC.png");
}
}
.combat-value-formula {
width: $size;
input {
text-align: center;
}
}
}
}
}
}

View file

@ -5,7 +5,7 @@
$header-top-margin: 5px;
header.sheet-header {
flex: 0 0 210px;
flex: 0 0 auto;
overflow: hidden;
display: flex;
flex-direction: row;

View file

@ -51,7 +51,6 @@
margin-top: 0px;
padding-top: 0px;
}
}
}
@ -61,4 +60,4 @@
padding-left: 1em;
border-bottom: 2px groove $c-border-groove;
font-weight: bold;
}
}

View file

@ -70,3 +70,7 @@
.flex-between {
justify-content: space-between;
}
.flex-around {
justify-content: space-around;
}

View file

@ -14,3 +14,8 @@
@mixin hide {
display: none;
}
@mixin centered-content {
display: grid;
place-items: center;
}

View file

@ -1,3 +1,6 @@
$padding-sm: 5px;
$padding-md: 10px;
$padding-lg: 20px;
$margin-sm: $padding-sm;
$margin-md: $padding-md;
$margin-lg: $padding-lg;

View file

@ -6,33 +6,95 @@
"templates": [],
"attributes": {
"body": {
"initial": 8
"base": 0,
"mod": 0
},
"mobility": {
"initial": 0
"base": 0,
"mod": 0
},
"mind": {
"initial": 0
"base": 0,
"mod": 0
}
},
"traits": {
"strength": {
"initial": 4
"base": 0,
"mod": 0
},
"constitution": {
"initial": 0
"base": 0,
"mod": 0
},
"agility": {
"initial": 0
"base": 0,
"mod": 0
},
"dexterity": {
"initial": 0
"base": 0,
"mod": 0
},
"intellect": {
"initial": 0
"base": 0,
"mod": 0
},
"aura": {
"initial": 0
"base": 0,
"mod": 0
}
},
"combatValues": {
"hitPoints": {
"base": 0,
"mod": 0,
"current": 0
},
"defense": {
"base": 0,
"mod": 0
},
"initiative": {
"base": 0,
"mod": 0
},
"movement": {
"base": 0,
"mod": 0
},
"meleeAttack": {
"base": 0,
"mod": 0
},
"rangedAttack": {
"base": 0,
"mod": 0
},
"spellcasting": {
"base": 0,
"mod": 0
},
"targetedSpellcasting": {
"base": 0,
"mod": 0
}
},
"baseInfo": {
"race": "",
"class": "",
"heroClass": "",
"racialAbilities": ""
},
"progression": {
"level": 0,
"experiencePoints": 0,
"talentPoints": {
"total": 0,
"used": 0
},
"progressPoints": {
"total": 0,
"used": 0
}
}
}

View file

@ -4,45 +4,230 @@
<img class="profile-img" src="{{actor.img}}" data-edit="img" title="{{actor.name}}" height="100" width="100" />
<div class="header-fields">
<h1 class="charname"><input name="name" type="text" value="{{actor.name}}" placeholder="Name" /></h1>
</div>
<div class="character-values">
{{!-- The grid classes are defined in scss/global/_grid.scss. To use, use both the "grid" and "grid-Ncol"
class where "N" can be any number from 1 to 12 and will create that number of columns. --}}
<div class="resources grid grid-2col">
<div class="base-infos grid grid-3col">
{{!-- "flex-group-center" is also defined in the _grid.scss file and it will add a small amount of
padding, a border, and will center all of its child elements content and text. --}}
<div class="resource flex-group-center">
<label for="data.health.value" class="resource-label">Health</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="data.health.value" value="{{data.health.value}}" data-dtype="Number" />
<span> / </span>
<input type="text" name="data.health.max" value="{{data.health.max}}" data-dtype="Number" />
<div class="base-info flex-group-center">
<label for="data.baseInfo.race" class="base-info-label">{{config.baseInfo.race}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.baseInfo.race" value="{{data.baseInfo.race}}"
data-dtype="String" />
</div>
</div>
<div class="resource flex-group-center">
<label for="data.power.value" class="resource-label">Power</label>
<div class="resource-content flexrow flex-center flex-between">
<input type="text" name="data.power.value" value="{{data.power.value}}" data-dtype="Number" />
<span> / </span>
<input type="text" name="data.power.max" value="{{data.power.max}}" data-dtype="Number" />
<div class="base-info flex-group-center">
<div class="grid grid-3col">
<div class="base-info flex-group-center">
<label for="data.progression.level"
class="base-info-label">{{config.progression.level}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.progression.level" value="{{data.progression.level}}"
data-dtype="Number" />
</div>
</div>
<div class="base-info flex-group-center">
<label for="data.progression.progressPoints"
class="base-info-label">{{config.progression.progressPoints}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.progression.progressPoints.used"
value="{{data.progression.progressPoints.used}}" data-dtype="Number" /><span> /
</span><input type="text" name="data.progression.progressPoints.total"
value="{{data.progression.progressPoints.total}}" data-dtype="Number" />
</div>
</div>
<div class="base-info flex-group-center">
<label for="data.progression.talentPoints"
class="base-info-label">{{config.progression.talentPoints}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.progression.talentPoints.used"
value="{{data.progression.talentPoints.used}}" data-dtype="Number" /><span> /
</span><input type="text" name="data.progression.talentPoints.total"
value="{{data.progression.talentPoints.total}}" data-dtype="Number" />
</div>
</div>
</div>
</div>
<div class="base-info flex-group-center">
<label for="data.baseInfo.class" class="base-info-label">{{config.baseInfo.class}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.baseInfo.class" value="{{data.baseInfo.class}}"
data-dtype="String" />
</div>
</div>
<div class="base-info flex-group-center">
<label for="data.baseInfo.racialAbilities"
class="base-info-label">{{config.baseInfo.racialAbilities}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.baseInfo.racialAbilities"
value="{{data.baseInfo.racialAbilities}}" data-dtype="String" />
</div>
</div>
<div class="base-info flex-group-center">
<label for="data.progression.experiencePoints"
class="base-info-label">{{config.progression.experiencePoints}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.progression.experiencePoints"
value="{{data.progression.experiencePoints}}" data-dtype="Number" />
</div>
</div>
<div class="base-info flex-group-center">
<label for="data.baseInfo.heroClass" class="base-info-label">{{config.baseInfo.heroClass}}</label>
<div class="base-info-content flexrow flex-center flex-between">
<input type="text" name="data.baseInfo.heroClass" value="{{data.baseInfo.heroClass}}"
data-dtype="String" />
</div>
</div>
</div>
{{!-- The grid classes are defined in scss/global/_grid.scss. To use, use both the "grid" and "grid-Ncol"
class where "N" can be any number from 1 to 12 and will create that number of columns. --}}
<div class="abilities grid grid-3col">
{{#each data.abilities as |ability key|}}
<div class="ability flexrow flex-group-center">
<label for="data.abilities.{{key}}.value" class="resource-label">{{key}}</label>
<input
type="text"
name="data.abilities.{{key}}.value"
value="{{ability.value}}"
data-dtype="Number"
/>
<span class="ability-mod rollable" data-roll="d20+@abilities.{{key}}.mod" data-label="{{key}}"
>{{numberFormat ability.mod decimals=0 sign=true}}</span
>
<div class="attributes-traits grid grid-3col">
<div class="attribute flexrow"><label for="data.attributes.body.total"
class="attribute-label ">{{config.attributes.body}}</label>
<div class="attribute-value flex15 grid grid-8col"><input type="number"
name="data.attributes.body.base" value='{{data.attributes.body.base}}'
data-dtype="Number" /><span> +
</span><input type="number" name="data.attributes.body.mod" value='{{data.attributes.body.mod}}'
data-dtype="Number" /><span> =
</span><span class="attribute-value-total">{{data.attributes.body.total}}</span></div>
</div>
<div class="attribute flexrow"><label for="data.attributes.mobility.total"
class="attribute-label">{{config.attributes.mobility}}</label>
<div class="attribute-value flex15 grid grid-8col"><input type="number"
name="data.attributes.mobility.base" value='{{data.attributes.mobility.base}}'
data-dtype="Number" /><span> +
</span><input type="number" name="data.attributes.mobility.mod"
value='{{data.attributes.mobility.mod}}' data-dtype="Number" /><span> =
</span><span class="attribute-value-total">{{data.attributes.mobility.total}}</span></div>
</div>
<div class="attribute flexrow"><label for="data.attributes.mind.total"
class="attribute-label">{{config.attributes.mind}}</label>
<div class="attribute-value flex15 grid grid-8col"><input type="number"
name="data.attributes.mind.base" value='{{data.attributes.mind.base}}'
data-dtype="Number" /><span> +
</span><input type="number" name="data.attributes.mind.mod" value='{{data.attributes.mind.mod}}'
data-dtype="Number" /><span> =
</span><span class="attribute-value-total">{{data.attributes.mind.total}}</span></div>
</div>
<div class="trait flexrow"><label for="data.traits.strength.total"
class="trait-label">{{config.traits.strength}}</label>
<div class="trait-value flex15 grid grid-8col"><input type="number" name="data.traits.strength.base"
value='{{data.traits.strength.base}}' data-dtype="Number" /><span> +
</span><input type="number" name="data.traits.strength.mod" value='{{data.traits.strength.mod}}'
data-dtype="Number" /><span> =
</span><span class="trait-value-total">{{data.traits.strength.total}}</span></div>
</div>
<div class="trait flexrow"><label for="data.traits.agility.total"
class="trait-label">{{config.traits.agility}}</label>
<div class="trait-value flex15 grid grid-8col"><input type="number" name="data.traits.agility.base"
value='{{data.traits.agility.base}}' data-dtype="Number" /><span> +
</span><input type="number" name="data.traits.agility.mod" value='{{data.traits.agility.mod}}'
data-dtype="Number" /><span> =
</span><span class="trait-value-total">{{data.traits.agility.total}}</span></div>
</div>
<div class="trait flexrow"><label for="data.traits.intellect.total"
class="trait-label">{{config.traits.intellect}}</label>
<div class="trait-value flex15 grid grid-8col"><input type="number"
name="data.traits.intellect.base" value='{{data.traits.intellect.base}}'
data-dtype="Number" /><span> +
</span><input type="number" name="data.traits.intellect.mod"
value='{{data.traits.intellect.mod}}' data-dtype="Number" /><span> =
</span><span class="trait-value-total">{{data.traits.intellect.total}}</span></div>
</div>
<div class="trait flexrow"><label for="data.traits.constitution.total"
class="trait-label">{{config.traits.constitution}}</label>
<div class="trait-value flex15 grid grid-8col"><input type="number"
name="data.traits.constitution.base" value='{{data.traits.constitution.base}}'
data-dtype="Number" /><span> +
</span><input type="number" name="data.traits.constitution.mod"
value='{{data.traits.constitution.mod}}' data-dtype="Number" /><span> =
</span><span class="trait-value-total">{{data.traits.constitution.total}}</span></div>
</div>
<div class="trait flexrow"><label for="data.traits.dexterity.total"
class="trait-label">{{config.traits.dexterity}}</label>
<div class="trait-value flex15 grid grid-8col"><input type="number"
name="data.traits.dexterity.base" value='{{data.traits.dexterity.base}}'
data-dtype="Number" /><span> +
</span><input type="number" name="data.traits.dexterity.mod"
value='{{data.traits.dexterity.mod}}' data-dtype="Number" /><span> =
</span><span class="trait-value-total">{{data.traits.dexterity.total}}</span></div>
</div>
<div class="trait flexrow"><label for="data.traits.aura.total"
class="trait-label">{{config.traits.aura}}</label>
<div class="trait-value flex15 grid grid-8col"><input type="number" name="data.traits.aura.base"
value='{{data.traits.aura.base}}' data-dtype="Number" /><span> +
</span><input type="number" name="data.traits.aura.mod" value='{{data.traits.aura.mod}}'
data-dtype="Number" /><span> =
</span><span class="trait-value-total">{{data.traits.aura.total}}</span></div>
</div>
</div>
<div class="combat-values flexrow flex-between">
<div class="combat-value-with-formula">
<div class="combat-value hitPoints"><span
class="combat-value-value-total">{{data.combatValues.hitPoints.total}}</span></div>
<div class="combat-value-formula flexrow"><input type="number"
name="data.combatValues.hitPoints.base" value='{{data.combatValues.hitPoints.base}}'
data-dtype="Number" /><input type="number" name="data.combatValues.hitPoints.mod"
value='{{data.combatValues.hitPoints.mod}}' data-dtype="Number" /></div>
</div>
<div class="combat-value-with-formula">
<div class="combat-value defense"><span
class="combat-value-value-total">{{data.combatValues.defense.total}}</span></div>
<div class="combat-value-formula flexrow"><input type="number" name="data.combatValues.defense.base"
value='{{data.combatValues.defense.base}}' data-dtype="Number" /><input type="number"
name="data.combatValues.defense.mod" value='{{data.combatValues.defense.mod}}'
data-dtype="Number" /></div>
</div>
<div class="combat-value-with-formula">
<div class="combat-value initiative"><span
class="combat-value-value-total">{{data.combatValues.initiative.total}}</span></div>
<div class="combat-value-formula flexrow"><input type="number"
name="data.combatValues.initiative.base" value='{{data.combatValues.initiative.base}}'
data-dtype="Number" /><input type="number" name="data.combatValues.initiative.mod"
value='{{data.combatValues.initiative.mod}}' data-dtype="Number" /></div>
</div>
<div class="combat-value-with-formula">
<div class="combat-value movement"><span
class="combat-value-value-total">{{data.combatValues.movement.total}}</span></div>
<div class="combat-value-formula flexrow"><input type="number"
name="data.combatValues.movement.base" value='{{data.combatValues.movement.base}}'
data-dtype="Number" /><input type="number" name="data.combatValues.movement.mod"
value='{{data.combatValues.movement.mod}}' data-dtype="Number" /></div>
</div>
<div class="combat-value-with-formula">
<div class="combat-value meleeAttack"><span
class="combat-value-value-total">{{data.combatValues.meleeAttack.total}}</span></div>
<div class="combat-value-formula flexrow"><input type="number"
name="data.combatValues.meleeAttack.base" value='{{data.combatValues.meleeAttack.base}}'
data-dtype="Number" /><input type="number" name="data.combatValues.meleeAttack.mod"
value='{{data.combatValues.meleeAttack.mod}}' data-dtype="Number" /></div>
</div>
<div class="combat-value-with-formula">
<div class="combat-value rangedAttack"><span
class="combat-value-value-total">{{data.combatValues.rangedAttack.total}}</span></div>
<div class="combat-value-formula flexrow"><input type="number"
name="data.combatValues.rangedAttack.base" value='{{data.combatValues.rangedAttack.base}}'
data-dtype="Number" /><input type="number" name="data.combatValues.rangedAttack.mod"
value='{{data.combatValues.rangedAttack.mod}}' data-dtype="Number" /></div>
</div>
<div class="combat-value-with-formula">
<div class="combat-value spellcasting"><span
class="combat-value-value-total">{{data.combatValues.spellcasting.total}}</span></div>
<div class="combat-value-formula flexrow"><input type="number"
name="data.combatValues.spellcasting.base" value='{{data.combatValues.spellcasting.base}}'
data-dtype="Number" /><input type="number" name="data.combatValues.spellcasting.mod"
value='{{data.combatValues.spellcasting.mod}}' data-dtype="Number" /></div>
</div>
<div class="combat-value-with-formula">
<div class="combat-value targetedSpellcasting"><span
class="combat-value-value-total">{{data.combatValues.targetedSpellcasting.total}}</span>
</div>
<div class="combat-value-formula flexrow"><input type="number"
name="data.combatValues.targetedSpellcasting.base"
value='{{data.combatValues.targetedSpellcasting.base}}' data-dtype="Number" /><input
type="number" name="data.combatValues.targetedSpellcasting.mod"
value='{{data.combatValues.targetedSpellcasting.mod}}' data-dtype="Number" /></div>
</div>
{{/each}}
</div>
</div>
</header>
@ -63,4 +248,4 @@
{{!-- Items Tab --}}
{{> systems/ds4/templates/actor/partials/items.hbs}}
</section>
</form>
</form>