diff --git a/src/assets/back.png b/src/assets/back.png
new file mode 100644
index 0000000..9b6f056
Binary files /dev/null and b/src/assets/back.png differ
diff --git a/src/assets/heart_empty.png b/src/assets/heart_empty.png
new file mode 100644
index 0000000..99578eb
Binary files /dev/null and b/src/assets/heart_empty.png differ
diff --git a/src/assets/heart_full.png b/src/assets/heart_full.png
new file mode 100644
index 0000000..ed2da98
Binary files /dev/null and b/src/assets/heart_full.png differ
diff --git a/src/assets/shield.png b/src/assets/shield.png
new file mode 100644
index 0000000..049f92a
Binary files /dev/null and b/src/assets/shield.png differ
diff --git a/src/lang/en.json b/src/lang/en.json
index 7089dbe..53dc372 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -2,6 +2,14 @@
"OSE.Edit": "Edit",
"OSE.Delete": "Delete",
"OSE.Add": "Add",
+ "OSE.Ok": "Ok",
+ "OSE.Cancel": "Cancel",
+ "OSE.Roll": "Roll",
+
+ "OSE.Formula": "Formula",
+ "OSE.SitMod": "Situational Modifier",
+ "OSE.RollMode": "Roll Mode",
+ "OSE.RollExample": "Roll Example",
"OSE.Name": "Name",
"OSE.Class": "Class",
@@ -9,11 +17,18 @@
"OSE.Alignment": "Alignment",
"OSE.Level": "Level",
"OSE.Experience": "Experience",
+ "OSE.ExperienceBonus": "Bonus Experience",
"OSE.Treasure": "Treasure type",
"OSE.Size": "Size",
"OSE.Morale": "Morale",
+ "OSE.Retainer": "Retainer",
"OSE.Appearing": "NA",
+ "OSE.Attack": "Attack",
+ "OSE.Loyalty": "Loyalty Rating",
+ "OSE.LoyaltyShort": "LR",
+
+ "OSE.AbilityCheck": "Ability Check",
"OSE.scores.str.long": "Strength",
"OSE.scores.str.short": "STR",
"OSE.scores.wis.long": "Wisdom",
@@ -27,39 +42,95 @@
"OSE.scores.cha.long": "Charisma",
"OSE.scores.cha.short": "CHA",
+ "OSE.SavingThrow": "Save",
"OSE.saves.death.short": "D",
- "OSE.saves.death.long": "Death",
- "OSE.saves.wands.short": "W",
- "OSE.saves.wands.long": "Wands",
+ "OSE.saves.death.long": "Death, Poison",
+ "OSE.saves.wand.short": "W",
+ "OSE.saves.wand.long": "Wand",
"OSE.saves.paralysis.short": "P",
- "OSE.saves.paralysis.long": "Paralysis",
+ "OSE.saves.paralysis.long": "Paralysis, Petrify",
"OSE.saves.breath.short": "B",
- "OSE.saves.breath.long": "Breath",
- "OSE.saves.spells.short": "S",
- "OSE.saves.spells.long": "Spells",
+ "OSE.saves.breath.long": "Dragon Breath",
+ "OSE.saves.spell.short": "S",
+ "OSE.saves.spell.long": "Rod, Staff, Spell",
"OSE.Health": "Hit Points",
+ "OSE.HealthMax": "Maximum Hit Points",
"OSE.HealthShort": "HP",
"OSE.HitDice": "Hit Dice",
"OSE.HitDiceShort": "HD",
- "OSE.Movement": "Movement",
- "OSE.MovementShort": "MOV",
- "OSE.SpecialMovement": "Special Movement",
+ "OSE.Movement": "Movement Rate",
+ "OSE.MovementEncounter": "Encounter Movement Rate",
+ "OSE.MovementEncounterShort": "ENC",
+ "OSE.MovementOverland": "Overland Movement Rate",
+ "OSE.MovementOverlandShort": "OVE",
+ "OSE.MovementShort": "MV",
"OSE.ArmorClass": "Armor Class",
"OSE.ArmorClassShort": "AC",
- "OSE.SpellDC": "DC",
+ "OSE.AscArmorClassShort": "AAC",
+ "OSE.SpellDC": "Spell DC",
+ "OSE.SpellDCShort": "DC",
"OSE.Thac0": "THAC0",
+ "OSE.ABShort": "AB",
+ "OSE.AB": "Attack Bonus",
+ "OSE.MeleeShort": "MEL",
+ "OSE.Melee": "Melee",
+ "OSE.MeleeBonus": "Melee Bonus",
+ "OSE.MissileShort": "MIS",
+ "OSE.Missile": "Missile",
+ "OSE.MissileBonus": "Missile Bonus",
"OSE.Initiative": "Initiative",
"OSE.InitiativeShort": "INIT",
"OSE.Attacks": "Attacks Usable per Round",
"OSE.AttacksShort": "ATT",
+ "OSE.Spellcaster": "Spellcaster",
"OSE.category.attributes": "Attributes",
"OSE.category.inventory": "Inventory",
+ "OSE.category.abilities": "Abilities",
"OSE.category.spells": "Spells",
"OSE.category.notes": "Notes",
"OSE.panel.abilities": "Abilities",
- "OSE.panel.equipment": "Equipment"
+ "OSE.panel.equipment": "Equipment",
+
+ "OSE.Setting.IndividualInit": "Individual Initiative",
+ "OSE.Setting.IndividualInitHint": "Initiative is rolled for each actor and modified by its DEX score",
+ "OSE.Setting.AscendingAC": "Ascending Armor Class",
+ "OSE.Setting.AscendingACHint": "The more the better",
+ "OSE.Setting.Morale": "Enable Monster Morale checks",
+ "OSE.Setting.MoraleHint": "Morale Rating is shown on monster sheets",
+ "OSE.Setting.THAC0Attacks": "Attacks with THAC0",
+ "OSE.Setting.THAC0AttacksHint": "Attacks are resolved using the THAC0 value, not compatible with AAC",
+ "OSE.Setting.VariableWeaponDamage": "Variable Weapon Damage",
+ "OSE.Setting.VariableWeaponDamageHint": "Weapons have different damage dice",
+
+ "OSE.items.Equip": "Equip",
+ "OSE.items.Unequip": "Unequip",
+ "OSE.items.Misc": "Misc",
+ "OSE.items.Weapons": "Weapons",
+ "OSE.items.Armors": "Armors",
+ "OSE.items.Weight": "Wgt.",
+ "OSE.items.Qualities": "Qualities",
+ "OSE.items.Notes": "Notes",
+ "OSE.items.Cost": "Cost",
+ "OSE.items.Quantity": "Qt.",
+ "OSE.items.Roll": "Roll",
+ "OSE.items.Damage": "Damage",
+ "OSE.items.Melee": "Melee",
+ "OSE.items.Missile": "Missile",
+ "OSE.items.Slow": "Slow",
+ "OSE.items.ArmorAC": "AC",
+ "OSE.items.ArmorAAC": "AAC",
+
+ "OSE.spells.Memorized": "Memorized",
+ "OSE.spells.Cast": "Cast",
+ "OSE.spells.Range": "Range",
+ "OSE.spells.Slots": "Slots",
+ "OSE.spells.Class": "Class",
+ "OSE.spells.Duration": "Duration",
+ "OSE.spells.Level": "Level",
+
+ "OSE.abilities.Requirements": "Requirements"
}
\ No newline at end of file
diff --git a/src/lang/fr.json b/src/lang/fr.json
index 1ff3656..66ee21d 100644
--- a/src/lang/fr.json
+++ b/src/lang/fr.json
@@ -44,7 +44,6 @@
"OSE.HitDiceShort": "DV",
"OSE.Movement": "Mouvement",
"OSE.MovementShort": "MOUV",
- "OSE.SpecialMovement": "Mouvement Spécial",
"OSE.ArmorClass": "Classe d'Armure",
"OSE.ArmorClassShort": "CA",
"OSE.SpellDC": "DF",
diff --git a/src/module/actor/actor-sheet.js b/src/module/actor/actor-sheet.js
new file mode 100644
index 0000000..f4c59c1
--- /dev/null
+++ b/src/module/actor/actor-sheet.js
@@ -0,0 +1,178 @@
+import { OseActor } from "./entity.js";
+import { OseEntityTweaks } from "../dialog/entity-tweaks.js";
+
+export class OseActorSheet extends ActorSheet {
+ constructor(...args) {
+ super(...args);
+ }
+ /* -------------------------------------------- */
+
+ getData() {
+ const data = super.getData();
+
+ data.config = CONFIG.OSE;
+ // Settings
+ data.config.ascendingAC = game.settings.get("ose", "ascendingAC");
+
+ // Prepare owned items
+ this._prepareItems(data);
+
+ return data;
+ }
+
+ /**
+ * Organize and classify Owned Items for Character sheets
+ * @private
+ */
+ _prepareItems(data) {
+ // Partition items by category
+ let [items, weapons, armors, abilities, spells] = data.items.reduce(
+ (arr, item) => {
+ // Classify items into types
+ if (item.type === "item") arr[0].push(item);
+ else if (item.type === "weapon") arr[1].push(item);
+ else if (item.type === "armor") arr[2].push(item);
+ else if (item.type === "ability") arr[3].push(item);
+ else if (item.type === "spell") arr[4].push(item);
+ return arr;
+ },
+ [[], [], [], [], []]
+ );
+
+ // Sort spells by level
+ var sortedSpells = {};
+ for (var i = 0; i < spells.length; i++) {
+ let lvl = spells[i].data.lvl;
+ if (!sortedSpells[lvl]) sortedSpells[lvl] = [];
+ sortedSpells[lvl].push(spells[i]);
+ }
+ // Assign and return
+ data.owned = {
+ items: items,
+ weapons: weapons,
+ armors: armors,
+ };
+ data.abilities = abilities;
+ data.spells = sortedSpells;
+ }
+
+ _onItemSummary(event) {
+ event.preventDefault();
+ let li = $(event.currentTarget).parents(".item"),
+ item = this.actor.getOwnedItem(li.data("item-id")),
+ description = TextEditor.enrichHTML(item.data.data.description);
+ // Toggle summary
+ if (li.hasClass("expanded")) {
+ let summary = li.parents(".item-entry").children(".item-summary");
+ summary.slideUp(200, () => summary.remove());
+ } else {
+ let div = $(`
${description}
`);
+ li.parents(".item-entry").append(div.hide());
+ div.slideDown(200);
+ }
+ li.toggleClass("expanded");
+ }
+
+
+ activateListeners(html) {
+ // Item summaries
+ html
+ .find(".item .item-name h4")
+ .click((event) => this._onItemSummary(event));
+
+ html.find(".saving-throw .attribute-name a").click((ev) => {
+ let actorObject = this.actor;
+ let element = event.currentTarget;
+ let save = element.parentElement.parentElement.dataset.save;
+ actorObject.rollSave(save, { event: event });
+ });
+
+ //Toggle Spells
+ html.find(".item-cast").click(async (ev) => {
+ const li = $(ev.currentTarget).parents(".item");
+ const item = this.actor.getOwnedItem(li.data("itemId"));
+ await this.actor.updateOwnedItem({
+ _id: li.data("itemId"),
+ data: {
+ cast: !item.data.data.cast,
+ },
+ });
+ });
+ //Toggle Equipment
+ html.find(".item-memorize").click(async (ev) => {
+ const li = $(ev.currentTarget).parents(".item");
+ const item = this.actor.getOwnedItem(li.data("itemId"));
+ await this.actor.updateOwnedItem({
+ _id: li.data("itemId"),
+ data: {
+ memorized: !item.data.data.memorized,
+ },
+ });
+ });
+
+ html.find(".item-image").click(async (ev) => {
+ const li = $(ev.currentTarget).parents(".item");
+ const item = this.actor.getOwnedItem(li.data("itemId"));
+ item.roll();
+ });
+
+ super.activateListeners(html);
+ }
+
+ // Override to set resizable initial size
+ async _renderInner(...args) {
+ const html = await super._renderInner(...args);
+ this.form = html[0];
+
+ // Resize resizable classes
+ let resizable = html.find(".resizable");
+ if (resizable.length == 0) {
+ return;
+ }
+ resizable.each((_, el) => {
+ let heightDelta = this.position.height - this.options.height;
+ el.style.height = `${heightDelta + parseInt(el.dataset.baseSize)}px`;
+ });
+ return html;
+ }
+
+ async _onResize(event) {
+ super._onResize(event);
+ let html = $(event.path);
+ let resizable = html.find(".resizable");
+ resizable.each((_, el) => {
+ let heightDelta = this.position.height - this.options.height;
+ el.style.height = `${heightDelta + parseInt(el.dataset.baseSize)}px`;
+ });
+ }
+
+ _onConfigureActor(event) {
+ event.preventDefault();
+ new OseEntityTweaks(this.actor, {
+ top: this.position.top + 40,
+ left: this.position.left + (this.position.width - 400) / 2,
+ }).render(true);
+ }
+
+ /**
+ * Extend and override the sheet header buttons
+ * @override
+ */
+ _getHeaderButtons() {
+ let buttons = super._getHeaderButtons();
+
+ // Token Configuration
+ const canConfigure = game.user.isGM || this.actor.owner;
+ if (this.options.editable && canConfigure) {
+ buttons = [
+ {
+ label: "Tweaks",
+ class: "configure-actor",
+ icon: "fas fa-dice",
+ onclick: (ev) => this._onConfigureActor(ev),
+ },
+ ].concat(buttons);
+ }
+ return buttons;
+ }
+}
diff --git a/src/module/actor/character-sheet.js b/src/module/actor/character-sheet.js
index f99fa3b..493674e 100644
--- a/src/module/actor/character-sheet.js
+++ b/src/module/actor/character-sheet.js
@@ -1,9 +1,10 @@
import { OseActor } from "./entity.js";
+import { OseActorSheet } from "./actor-sheet.js";
/**
* Extend the basic ActorSheet with some very simple modifications
*/
-export class OseActorSheetCharacter extends ActorSheet {
+export class OseActorSheetCharacter extends OseActorSheet {
constructor(...args) {
super(...args);
}
@@ -19,7 +20,7 @@ export class OseActorSheetCharacter extends ActorSheet {
classes: ["ose", "sheet", "actor", "character"],
template: "systems/ose/templates/actors/character-sheet.html",
width: 450,
- height: 560,
+ height: 530,
resizable: true,
tabs: [
{
@@ -31,81 +32,36 @@ export class OseActorSheetCharacter extends ActorSheet {
});
}
- /* -------------------------------------------- */
-
- // Override to set resizable initial size
- async _renderInner(...args) {
- const html = await super._renderInner(...args);
- this.form = html[0];
-
- // Resize resizable classes
- let resizable = html.find('.resizable');
- resizable.each((_, el) => {
- let heightDelta = this.position.height - (this.options.height);
- el.style.height = `${heightDelta + parseInt(el.dataset.baseSize)}px`;
- });
- return html;
- }
-
/**
* Prepare data for rendering the Actor sheet
* The prepared data object contains both the actor data as well as additional sheet options
*/
getData() {
const data = super.getData();
-
- data.config = CONFIG.OSE;
for (let [a, score] of Object.entries(data.data.scores)) {
data.data.scores[a].label = game.i18n.localize(`OSE.scores.${a}`);
}
- // Prepare owned items
- this._prepareItems(data);
- // DEBUG
- return data;
- }
-
- /**
- * Organize and classify Owned Items for Character sheets
- * @private
- */
- _prepareItems(data) {
- // Partition items by category
- let [inventory, abilities, spells] = data.items.reduce(
- (arr, item) => {
- // Classify items into types
- if (item.type === "item") arr[0].push(item);
- if (item.type === "ability") arr[1].push(item);
- else if (item.type === "spell") arr[2].push(item);
- return arr;
- },
- [[], [], [], []]
+ // Settings
+ data.config.variableWeaponDamage = game.settings.get(
+ "ose",
+ "variableWeaponDamage"
);
+ data.config.ascendingAC = game.settings.get("ose", "ascendingAC");
+ data.config.individualInit = game.settings.get("ose", "individualInit");
- // Assign and return
- data.inventory = inventory;
- data.spells = spells;
- data.abilities = abilities;
+ data.mods = this.actor.computeModifiers();
+ return data;
}
/* -------------------------------------------- */
- _onItemSummary(event) {
+ async _onQtChange(event) {
event.preventDefault();
- let li = $(event.currentTarget).parents(".item"),
- item = this.actor.getOwnedItem(li.data("item-id")),
- description = TextEditor.enrichHTML(item.data.data.description);
- // Toggle summary
- if ( li.hasClass("expanded") ) {
- let summary = li.parents('.item-entry').children(".item-summary");
- summary.slideUp(200, () => summary.remove());
- } else {
- let div = $(`${description}
`);
- li.parents('.item-entry').append(div.hide());
- div.slideDown(200);
- }
- li.toggleClass("expanded");
+ const itemId = event.currentTarget.closest(".item").dataset.itemId;
+ const item = this.actor.getOwnedItem(itemId);
+ return item.update({ "data.quantity.value": parseInt(event.target.value) });
}
/**
@@ -143,20 +99,38 @@ export class OseActorSheetCharacter extends ActorSheet {
return this.actor.createOwnedItem(itemData);
});
- // Item summaries
- html.find('.item .item-name h4').click(event => this._onItemSummary(event));
+ //Toggle Equipment
+ html.find(".item-toggle").click(async (ev) => {
+ const li = $(ev.currentTarget).parents(".item");
+ const item = this.actor.getOwnedItem(li.data("itemId"));
+ await this.actor.updateOwnedItem({
+ _id: li.data("itemId"),
+ data: {
+ equipped: !item.data.data.equipped,
+ },
+ });
+ });
+
+ html
+ .find(".quantity input")
+ .click((ev) => ev.target.select())
+ .change(this._onQtChange.bind(this));
+
+ html.find(".ability-score .attribute-name a").click((ev) => {
+ let actorObject = this.actor;
+ let element = event.currentTarget;
+ let score = element.parentElement.parentElement.dataset.score;
+ actorObject.rollCheck(score, { event: event });
+ });
+
+ html.find(".attack a").click(ev => {
+ let actorObject = this.actor;
+ let element = event.currentTarget;
+ let attack = element.parentElement.parentElement.dataset.attack;
+ actorObject.rollAttack(attack, { event: event });
+ })
// Handle default listeners last so system listeners are triggered first
super.activateListeners(html);
}
-
- async _onResize(event) {
- super._onResize(event);
- let html = $(event.path);
- let resizable = html.find('.resizable');
- resizable.each((_, el) => {
- let heightDelta = this.position.height - (this.options.height);
- el.style.height = `${heightDelta + parseInt(el.dataset.baseSize)}px`;
- });
- }
}
diff --git a/src/module/actor/entity.js b/src/module/actor/entity.js
index 42d55c8..2874697 100644
--- a/src/module/actor/entity.js
+++ b/src/module/actor/entity.js
@@ -1,20 +1,141 @@
+import { OseDice } from '../dice.js';
+
export class OseActor extends Actor {
/**
* Extends data from base Actor class
*/
- prepareData() {
- super.prepareData();
- return this.data;
- }
- /* -------------------------------------------- */
+
+ /* -------------------------------------------- */
/* Socket Listeners and Handlers
/* -------------------------------------------- */
- /** @override */
- async createOwnedItem(itemData, options) {
- return super.createOwnedItem(itemData, options);
- }
/* -------------------------------------------- */
/* Rolls */
/* -------------------------------------------- */
+ rollSave(save, options = {}) {
+ const label = game.i18n.localize(`OSE.saves.${save}.long`);
+ const rollParts = ['1d20'];
+
+ const data = {...this.data, ...{
+ rollData : {
+ type: 'Save',
+ stat: save
+ }
+ }};
+
+ // Roll and return
+ return OseDice.Roll({
+ event: options.event,
+ parts: rollParts,
+ data: data,
+ speaker: ChatMessage.getSpeaker({ actor: this }),
+ flavor: `${label} ${game.i18n.localize('OSE.SavingThrow')}`,
+ title: `${label} ${game.i18n.localize('OSE.SavingThrow')}`,
+ });
+ }
+
+ rollCheck(score, options = {}) {
+ const label = game.i18n.localize(`OSE.scores.${score}.long`);
+ const rollParts = ['1d20'];
+
+ const data = {...this.data, ...{
+ rollData : {
+ type: 'Check',
+ stat: score
+ }
+ }};
+
+ // Roll and return
+ return OseDice.Roll({
+ event: options.event,
+ parts: rollParts,
+ data: data,
+ speaker: ChatMessage.getSpeaker({ actor: this }),
+ flavor: `${label} ${game.i18n.localize('OSE.AbilityCheck')}`,
+ title: `${label} ${game.i18n.localize('OSE.AbilityCheck')}`,
+ });
+ }
+
+ rollAttack(attack, options={}) {
+ const label = game.i18n.localize(`OSE.${attack}`);
+ const rollParts = ['1d20',];
+
+ const mods = this.computeModifiers();
+ if (attack == 'Missile') {
+ rollParts.push(
+ '+',
+ mods.dex.toString(),
+ '+',
+ this.data.data.thac0.mod.missile.toString()
+ );
+ } else if (attack == 'Melee') {
+ rollParts.push(
+ '+',
+ mods.str.toString(),
+ '+',
+ this.data.data.thac0.mod.melee.toString()
+ );
+ }
+ if (game.settings.get('ose', 'ascendingAC')) {
+ rollParts.push('+', this.data.data.thac0.bba.toString());
+ }
+
+ const data = {...this.data, ...{
+ rollData : {
+ type: 'Attack',
+ stat: attack,
+ mods: mods
+ }
+ }};
+
+ // Roll and return
+ return OseDice.Roll({
+ event: options.event,
+ parts: rollParts,
+ data: data,
+ speaker: ChatMessage.getSpeaker({ actor: this }),
+ flavor: `${label} ${game.i18n.localize('OSE.Attack')}`,
+ title: `${label} ${game.i18n.localize('OSE.Attack')}`,
+ });
+ }
+
+ computeModifiers() {
+ let _valueToMod = (val) => {
+ switch (val) {
+ case 3:
+ return -3;
+ case 4:
+ case 5:
+ return -2;
+ case 6:
+ case 7:
+ case 8:
+ return -1;
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ return 0;
+ case 13:
+ case 14:
+ case 15:
+ return 1;
+ case 16:
+ case 17:
+ return 2;
+ case 18:
+ return 3;
+ default:
+ return 0;
+ }
+ };
+ return {
+ str: _valueToMod(this.data.data.scores.str.value),
+ int: _valueToMod(this.data.data.scores.int.value),
+ dex: _valueToMod(this.data.data.scores.dex.value),
+ cha: _valueToMod(this.data.data.scores.cha.value),
+ wis: _valueToMod(this.data.data.scores.wis.value),
+ con: _valueToMod(this.data.data.scores.con.value),
+ }
+ }
}
diff --git a/src/module/actor/monster-sheet.js b/src/module/actor/monster-sheet.js
index 4918b4a..54ee4ec 100644
--- a/src/module/actor/monster-sheet.js
+++ b/src/module/actor/monster-sheet.js
@@ -1,9 +1,10 @@
import { OseActor } from "./entity.js";
+import { OseActorSheet } from "./actor-sheet.js";
/**
* Extend the basic ActorSheet with some very simple modifications
*/
-export class OseActorSheetMonster extends ActorSheet {
+export class OseActorSheetMonster extends OseActorSheet {
constructor(...args) {
super(...args);
}
@@ -31,25 +32,6 @@ export class OseActorSheetMonster extends ActorSheet {
});
}
- /* -------------------------------------------- */
-
- // Override to set resizable initial size
- async _renderInner(...args) {
- const html = await super._renderInner(...args);
- this.form = html[0];
-
- // Resize resizable classes
- let resizable = html.find('.resizable');
- if (resizable.length == 0) {
- return;
- }
- resizable.each((_, el) => {
- let heightDelta = this.position.height - (this.options.height);
- el.style.height = `${heightDelta + parseInt(el.dataset.baseSize)}px`;
- });
- return html;
- }
-
/**
* Prepare data for rendering the Actor sheet
* The prepared data object contains both the actor data as well as additional sheet options
@@ -57,58 +39,48 @@ export class OseActorSheetMonster extends ActorSheet {
getData() {
const data = super.getData();
- data.config = CONFIG.OSE;
+ // Settings
+ data.config.morale = game.settings.get('ose', 'morale');
- // Prepare owned items
- this._prepareItems(data);
-
- // DEBUG
return data;
}
- /**
- * Organize and classify Owned Items for Character sheets
- * @private
- */
- _prepareItems(data) {
- // Partition items by category
- let [inventory, abilities, spells] = data.items.reduce(
- (arr, item) => {
- // Classify items into types
- if (item.type === "item") arr[0].push(item);
- if (item.type === "ability") arr[1].push(item);
- else if (item.type === "spell") arr[2].push(item);
- return arr;
- },
- [[], [], [], []]
- );
-
- // Assign and return
- data.inventory = inventory;
- data.spells = spells;
- data.abilities = abilities;
- }
-
-
- _onItemSummary(event) {
- event.preventDefault();
- let li = $(event.currentTarget).parents(".item"),
- item = this.actor.getOwnedItem(li.data("item-id")),
- description = TextEditor.enrichHTML(item.data.data.description);
- // Toggle summary
- if ( li.hasClass("expanded") ) {
- let summary = li.parents('.item-entry').children(".item-summary");
- summary.slideUp(200, () => summary.remove());
- } else {
- let div = $(`${description}
`);
- li.parents('.item-entry').append(div.hide());
- div.slideDown(200);
- }
- li.toggleClass("expanded");
- }
-
/* -------------------------------------------- */
+ async _chooseItemType(
+ choices = ['weapon', 'armor', 'shield', 'gear'],
+ ) {
+ let templateData = { upper: '', lower: '', types: choices },
+ dlg = await renderTemplate(
+ 'templates/sidebar/entity-create.html',
+ templateData,
+ );
+ //Create Dialog window
+ return new Promise((resolve) => {
+ new Dialog({
+ title: '',
+ content: dlg,
+ buttons: {
+ ok: {
+ label: game.i18n.localize('OSE.Ok'),
+ icon: ' ',
+ callback: (html) => {
+ resolve({
+ type: html.find('select[name="type"]').val(),
+ name: html.find('input[name="name"]').val(),
+ });
+ },
+ },
+ cancel: {
+ icon: ' ',
+ label: game.i18n.localize('OSE.Cancel'),
+ },
+ },
+ default: 'ok',
+ }).render(true);
+ });
+ }
+
/**
* Activate event listeners using the prepared sheet HTML
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM
@@ -135,30 +107,35 @@ export class OseActorSheetMonster extends ActorSheet {
event.preventDefault();
const header = event.currentTarget;
const type = header.dataset.type;
- const itemData = {
- name: `New ${type.capitalize()}`,
- type: type,
- data: duplicate(header.dataset),
- };
- delete itemData.data["type"];
- return this.actor.createOwnedItem(itemData);
- });
- html.find(".item-name").click((event) => {
- this._onItemSummary(event);
+ // item creation helper func
+ let createItem = function (
+ type,
+ name = `New ${type.capitalize()}`,
+ ) {
+ const itemData = {
+ name: name ? name : `New ${type.capitalize()}`,
+ type: type,
+ data: duplicate(header.dataset),
+ };
+ delete itemData.data['type'];
+ return itemData;
+ };
+
+ // Getting back to main logic
+ if (type == 'choice') {
+ const choices = header.dataset.choices.split(',');
+ this._chooseItemType(choices).then((dialogInput) => {
+ const itemData = createItem(dialogInput.type, dialogInput.name);
+ this.actor.createOwnedItem(itemData, {});
+ });
+ return;
+ }
+ const itemData = createItem(type);
+ return this.actor.createOwnedItem(itemData, {});
});
// Handle default listeners last so system listeners are triggered first
super.activateListeners(html);
}
-
- async _onResize(event) {
- super._onResize(event);
- let html = $(event.path);
- let resizable = html.find('.resizable');
- resizable.each((_, el) => {
- let heightDelta = this.position.height - (this.options.height);
- el.style.height = `${heightDelta + parseInt(el.dataset.baseSize)}px`;
- });
- }
}
diff --git a/src/module/dialog/entity-tweaks.js b/src/module/dialog/entity-tweaks.js
new file mode 100644
index 0000000..01c03ac
--- /dev/null
+++ b/src/module/dialog/entity-tweaks.js
@@ -0,0 +1,58 @@
+// eslint-disable-next-line no-unused-vars
+import { OseActor } from '../actor/entity.js';
+
+export class OseEntityTweaks extends FormApplication {
+ static get defaultOptions() {
+ const options = super.defaultOptions;
+ options.id = 'sheet-tweaks';
+ options.template =
+ 'systems/ose/templates/actors/dialogs/tweaks-dialog.html';
+ options.width = 380;
+ return options;
+ }
+
+ /* -------------------------------------------- */
+
+ /**
+ * Add the Entity name into the window title
+ * @type {String}
+ */
+ get title() {
+ return `${this.object.name}: OSE Tweaks`;
+ }
+
+ /* -------------------------------------------- */
+
+ /**
+ * Construct and return the data object used to render the HTML template for this form application.
+ * @return {Object}
+ */
+ getData() {
+ let data = this.object.data;
+ if (this.object.data.type === 'character') {
+ data.isCharacter = true;
+ }
+ return data;
+ }
+
+ /* -------------------------------------------- */
+
+ /** @override */
+ activateListeners(html) {
+ super.activateListeners(html);
+ }
+
+ /**
+ * This method is called upon form submission after form data is validated
+ * @param event {Event} The initial triggering submission event
+ * @param formData {Object} The object of validated form data with which to update the object
+ * @private
+ */
+ async _updateObject(event, formData) {
+ event.preventDefault();
+ // Update the actor
+ this.object.update(formData);
+ // Re-draw the updated sheet
+ this.object.sheet.render(true);
+ }
+}
diff --git a/src/module/dice.js b/src/module/dice.js
new file mode 100644
index 0000000..9e37804
--- /dev/null
+++ b/src/module/dice.js
@@ -0,0 +1,170 @@
+export class OseDice {
+ static digestResult(data, roll) {
+ let details = "";
+
+ // ATTACKS
+ let die = roll.parts[0].total;
+ if (data.rollData.type == "Attack") {
+ if (game.settings.get("ose", "ascendingAC")) {
+ let bba = data.data.thac0.bba;
+ bba +=
+ data.rollData.stat == "Melee"
+ ? data.data.thac0.mod.melee + data.rollData.mods.str
+ : data.data.thac0.mod.missile + data.rollData.mods.dex;
+ details = `Failure (${bba})
`;
+ if (die == 1) {
+ return details;
+ }
+ details = `Hits AC ${roll.total} (${bba})
`;
+ } else {
+ // B/X Historic THAC0 Calculation
+ let thac = data.data.thac0.value;
+ thac -=
+ data.rollData.stat == "Melee"
+ ? data.data.thac0.mod.melee + data.rollData.mods.str
+ : data.data.thac0.mod.missile + data.rollData.mods.dex;
+
+ details = `Failure (${thac})
`;
+ if (thac - roll.total > 9) {
+ return details;
+ }
+ details = `Hits AC ${Math.clamped(thac - roll.total,-3,9)} (${thac})
`;
+ }
+ } else if (data.rollData.type == "Save") {
+ // SAVING THROWS
+ let sv = data.data.saves[data.rollData.stat].value;
+ if (roll.total >= sv) {
+ details = `Success! (${sv})
`;
+ } else {
+ details = `Failure (${sv})
`;
+ }
+ } else if (data.rollData.type == "Check") {
+ // SCORE CHECKS
+ let sc = data.data.scores[data.rollData.stat].value;
+ if (die == 1 || (roll.total <= sc && die < 20)) {
+ details = `Success! (${sc})
`;
+ } else {
+ details = `Failure (${sc})
`;
+ }
+ }
+ return details;
+ }
+
+ static async sendRoll(
+ parts = [],
+ data = {},
+ title = null,
+ flavor = null,
+ speaker = null,
+ form = null
+ ) {
+ const template = "systems/ose/templates/chat/roll-attack.html";
+
+ let chatData = {
+ user: game.user._id,
+ speaker: speaker,
+ };
+
+ let templateData = {
+ title: title,
+ flavor: flavor,
+ data: data,
+ };
+
+ // Optionally include a situational bonus
+ if (form !== null) data["bonus"] = form.bonus.value;
+ if (data["bonus"]) parts.push(data["bonus"]);
+
+ const roll = new Roll(parts.join(""), data).roll();
+
+ // Convert the roll to a chat message and return the roll
+ let rollMode = game.settings.get("core", "rollMode");
+ rollMode = form ? form.rollMode.value : rollMode;
+
+ templateData.details = OseDice.digestResult(data, roll);
+ roll.render().then((r) => {
+ templateData.rollOSE = r;
+ renderTemplate(template, templateData).then((content) => {
+ chatData.content = content;
+ chatData.sound = CONFIG.sounds.dice;
+ if (game.dice3d) {
+ game.dice3d
+ .showForRoll(
+ roll,
+ game.user,
+ true,
+ chatData.whisper,
+ chatData.blind
+ )
+ .then((displayed) => ChatMessage.create(chatData));
+ } else {
+ ChatMessage.create(chatData);
+ }
+ });
+ });
+
+ return roll;
+ }
+
+ // eslint-disable-next-line no-unused-vars
+ static async Roll({
+ parts = [],
+ data = {},
+ options = {},
+ event = null,
+ speaker = null,
+ flavor = null,
+ title = null,
+ item = false,
+ } = {}) {
+ let rollMode = game.settings.get("core", "rollMode");
+ let rolled = false;
+
+ const template = "systems/ose/templates/chat/roll-dialog.html";
+ let dialogData = {
+ formula: parts.join(" "),
+ data: data,
+ rollMode: rollMode,
+ rollModes: CONFIG.Dice.rollModes,
+ };
+
+ let buttons = {
+ ok: {
+ label: game.i18n.localize("OSE.Roll"),
+ icon: ' ',
+ callback: (html) => {
+ roll = OseDice.sendRoll(
+ parts,
+ data,
+ title,
+ flavor,
+ speaker,
+ html[0].children[0]
+ );
+ },
+ },
+ cancel: {
+ icon: ' ',
+ label: game.i18n.localize("OSE.Cancel"),
+ },
+ };
+
+ if (!item) delete buttons.raise;
+
+ const html = await renderTemplate(template, dialogData);
+ let roll;
+
+ //Create Dialog window
+ return new Promise((resolve) => {
+ new Dialog({
+ title: title,
+ content: html,
+ buttons: buttons,
+ default: "ok",
+ close: () => {
+ resolve(rolled ? roll : false);
+ },
+ }).render(true);
+ });
+ }
+}
diff --git a/src/module/helpers.js b/src/module/helpers.js
new file mode 100644
index 0000000..3613183
--- /dev/null
+++ b/src/module/helpers.js
@@ -0,0 +1,26 @@
+export const registerHelpers = async function () {
+ // Handlebars template helpers
+ Handlebars.registerHelper("eq", function (a, b) {
+ return a == b;
+ });
+
+ Handlebars.registerHelper("add", function (lh, rh) {
+ return parseInt(lh) + parseInt(rh);
+ });
+
+ Handlebars.registerHelper("subtract", function (lh, rh) {
+ return parseInt(rh) - parseInt(lh);
+ });
+
+ Handlebars.registerHelper("divide", function (lh, rh) {
+ return Math.floor(parseFloat(lh) / parseFloat(rh));
+ });
+
+ Handlebars.registerHelper("mult", function (lh, rh) {
+ return parseInt(lh) * parseInt(rh);
+ });
+
+ Handlebars.registerHelper("counter", function (status, value, max) {
+ return status ? Math.clamped((100.0 * value) / max, 0, 100) : Math.clamped(100 - (100.0 * value) / max, 0, 100);
+ });
+};
diff --git a/src/module/item/entity.js b/src/module/item/entity.js
index 0c23cc0..7e1444b 100644
--- a/src/module/item/entity.js
+++ b/src/module/item/entity.js
@@ -12,4 +12,93 @@ export class OseItem extends Item {
prepareData() {
super.prepareData();
}
+
+ getChatData(htmlOptions) {
+ const data = duplicate(this.data.data);
+
+ // Rich text description
+ data.description = TextEditor.enrichHTML(data.description, htmlOptions);
+
+ // Item properties
+ const props = [];
+ const labels = this.labels;
+
+ if (this.data.type == "weapon") {
+ props.push(data.qualities);
+ }
+ if (this.data.type == "spell") {
+ props.push(
+ `${data.class} ${data.lvl}`,
+ data.range,
+ data.duration
+ );
+ }
+ if (data.hasOwnProperty("equipped")) {
+ props.push(data.equipped ? "Equipped" : "Not Equipped");
+ }
+
+ // Filter properties and return
+ data.properties = props.filter((p) => !!p);
+ return data;
+ }
+
+ rollWeapon() {
+ if (this.data.data.missile) {
+ this.actor.rollAttack('Missile');
+ return true;
+ } else if (this.data.data.melee) {
+ this.actor.rollAttack('Melee');
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Roll the item to Chat, creating a chat card which contains follow up attack or damage roll options
+ * @return {Promise}
+ */
+ async roll({ configureDialog = true } = {}) {
+ console.log(this.data)
+ if (this.data.type == 'weapon') {
+ if (this.rollWeapon()) return;
+ }
+ // Basic template rendering data
+ const token = this.actor.token;
+ const templateData = {
+ actor: this.actor,
+ tokenId: token ? `${token.scene._id}.${token.id}` : null,
+ item: this.data,
+ data: this.getChatData(),
+ labels: this.labels,
+ isHealing: this.isHealing,
+ hasDamage: this.hasDamage,
+ isSpell: this.data.type === "spell",
+ hasSave: this.hasSave,
+ };
+
+ // Render the chat card template
+ const template = `systems/ose/templates/chat/item-card.html`;
+ const html = await renderTemplate(template, templateData);
+
+ // Basic chat message data
+ const chatData = {
+ user: game.user._id,
+ type: CONST.CHAT_MESSAGE_TYPES.OTHER,
+ content: html,
+ speaker: {
+ actor: this.actor._id,
+ token: this.actor.token,
+ alias: this.actor.name,
+ },
+ };
+
+ // Toggle default roll mode
+ let rollMode = game.settings.get("core", "rollMode");
+ if (["gmroll", "blindroll"].includes(rollMode))
+ chatData["whisper"] = ChatMessage.getWhisperRecipients("GM");
+ if (rollMode === "blindroll") chatData["blind"] = true;
+
+ // Create the chat message
+ return ChatMessage.create(chatData);
+ }
}
diff --git a/src/module/preloadTemplates.js b/src/module/preloadTemplates.js
index 9bbff1c..4b4c546 100644
--- a/src/module/preloadTemplates.js
+++ b/src/module/preloadTemplates.js
@@ -7,6 +7,7 @@ export const preloadHandlebarsTemplates = async function () {
//Sheet tabs
'systems/ose/templates/actors/partials/character-header.html',
'systems/ose/templates/actors/partials/character-attributes-tab.html',
+ 'systems/ose/templates/actors/partials/character-abilities-tab.html',
'systems/ose/templates/actors/partials/character-spells-tab.html',
'systems/ose/templates/actors/partials/character-inventory-tab.html',
diff --git a/src/module/settings.js b/src/module/settings.js
new file mode 100644
index 0000000..4a01fcc
--- /dev/null
+++ b/src/module/settings.js
@@ -0,0 +1,47 @@
+export const registerSettings = function () {
+ game.settings.register('ose', 'individualInit', {
+ name: game.i18n.localize('OSE.Setting.IndividualInit'),
+ hint: game.i18n.localize('OSE.Setting.IndividualInitHint'),
+ default: false,
+ scope: 'world',
+ type: Boolean,
+ config: true
+ });
+
+ game.settings.register('ose', 'ascendingAC', {
+ name: game.i18n.localize('OSE.Setting.AscendingAC'),
+ hint: game.i18n.localize('OSE.Setting.AscendingACHint'),
+ default: false,
+ scope: 'world',
+ type: Boolean,
+ config: true
+ });
+
+ game.settings.register('ose', 'morale', {
+ name: game.i18n.localize('OSE.Setting.Morale'),
+ hint: game.i18n.localize('OSE.Setting.MoraleHint'),
+ default: false,
+ scope: 'world',
+ type: Boolean,
+ config: true
+ });
+
+ game.settings.register('ose', 'thac0Attacks', {
+ name: game.i18n.localize('OSE.Setting.THAC0Attacks'),
+ hint: game.i18n.localize('OSE.Setting.THAC0AttacksHint'),
+ default: false,
+ scope: 'world',
+ type: Boolean,
+ config: true
+ });
+
+ game.settings.register('ose', 'variableWeaponDamage', {
+ name: game.i18n.localize('OSE.Setting.VariableWeaponDamage'),
+ hint: game.i18n.localize('OSE.Setting.VariableWeaponDamageHint'),
+ default: false,
+ scope: 'world',
+ type: Boolean,
+ config: true
+ });
+}
+
\ No newline at end of file
diff --git a/src/ose.js b/src/ose.js
index 5edf4c1..2836cb1 100644
--- a/src/ose.js
+++ b/src/ose.js
@@ -6,19 +6,8 @@ import { preloadHandlebarsTemplates } from "./module/preloadTemplates.js";
import { OseActor } from "./module/actor/entity.js";
import { OseItem } from "./module/item/entity.js";
import { OSE } from "./module/config.js";
-
-// Handlebars template helpers
-Handlebars.registerHelper("eq", function (a, b) {
- return a == b;
-});
-
-Handlebars.registerHelper("add", function (lh, rh) {
- return parseInt(lh) + parseInt(rh);
-});
-
-Handlebars.registerHelper("subtract", function (lh, rh) {
- return parseInt(rh) - parseInt(lh);
-});
+import { registerSettings } from './module/settings.js';
+import { registerHelpers } from './module/helpers.js';
/* -------------------------------------------- */
/* Foundry VTT Initialization */
@@ -35,6 +24,13 @@ Hooks.once("init", async function () {
};
CONFIG.OSE = OSE;
+
+ // Custom Handlebars helpers
+ registerHelpers();
+
+ // Register custom system settings
+ registerSettings();
+
CONFIG.Actor.entityClass = OseActor;
CONFIG.Item.entityClass = OseItem;
diff --git a/src/scss/actor-base.scss b/src/scss/actor-base.scss
index 628191e..dd546f4 100644
--- a/src/scss/actor-base.scss
+++ b/src/scss/actor-base.scss
@@ -12,7 +12,7 @@
border: 1px solid $colorDark;
.panel-title {
color: whitesmoke;
- background: $colorDark;
+ background: $darkBackground;
line-height: 14px;
height: 20px;
text-align: center;
@@ -32,7 +32,7 @@
padding: 0;
}
}
-
+
/* Header Summary Details */
.header-details {
h1 {
@@ -74,44 +74,79 @@
}
}
.sheet-tabs {
- color: whitesmoke;
- background: $colorDark;
- margin: 4px 0 0;
- border: 1px solid $colorDark;
+ position: absolute;
+ transform: rotate(90deg);
+ top: 365px;
+ right: -169px;
+ width: 320px;
+ z-index: -1;
.item {
- padding: 2px;
+ padding: 2px 10px 0;
+ margin-left: -5px;
+ text-indent: 4px;
+ background: url("/ui/parchment.jpg");
+ border-top-right-radius: 4px;
+ border-top-left-radius: 80px;
+ box-shadow: 0 0 6px 1px $colorDark;
+ font-size: 12px;
+ filter: brightness(0.9);
&.active {
- background: $colorTan;
+ filter: none;
+ z-index: 1;
+ font-weight: bold;
text-shadow: none;
+ margin-bottom: -1px;
}
}
}
.sheet-body {
padding: 5px 0;
- border: 1px solid $colorDark;
- border-top: none;
- height: calc(100% - 175px);
+ height: calc(100% - 140px);
.attributes {
margin: 0;
padding: 0;
.attribute {
- margin: 10px;
+ position: relative;
+ margin: 8px;
border: 1px solid $colorTan;
+ box-shadow: 0 0 2px $colorTan;
.attribute-name {
color: whitesmoke;
padding: 2px;
margin: 0;
- background: $colorDark;
+ border: 1px solid $colorDark;
+ background: $darkBackground;
text-align: center;
}
+ &.attribute-secondaries {
+ margin: 10px 5px;
+ }
+ &.ability-score {
+ height: 40px;
+ .attribute-value {
+ line-height: 36px;
+ }
+ }
.attribute-value {
+ text-align: center;
padding: 4px;
- display: flex;
- flex-direction: row;
+ .sep {
+ flex: 0 0 5px;
+ line-height: 24px;
+ }
+ }
+ .attribute-mod {
+ position: absolute;
+ color: $colorTan;
+ right: 5px;
+ top: -5px;
+ font-size: 13px;
}
}
}
.attribute-group {
+ flex: 0 0 105px;
+ margin: auto 0;
.attributes {
.attribute {
display: flex;
@@ -119,64 +154,151 @@
.attribute-name {
width: 40px;
margin: 0;
- line-height: 28px;
+ line-height: 35px;
+ a {
+ margin: auto;
+ }
}
+ &.saving-throw .attribute-name {
+ line-height: 16px;
+ width: 80px;
+ }
+ .attribute-value {
+ width: 45px;
+ flex-grow: 0;
+ input {
+ padding: 0;
+ }
+ }
+ }
+ }
+ }
+ .inventory {
+ overflow: auto;
+ .header-spells {
+ line-height: 30px;
+ }
+ .item-titles {
+ text-align: center;
+ padding: 4px;
+ border: 1px solid $colorDark;
+ box-shadow: 0 0 5px $colorDark;
+ .item-name {
+ text-align: left;
+ text-indent: 8px;
+ }
+ font-weight: 300;
+ font-size: 13px;
+ background: $darkBackground;
+ color: white;
+ input {
+ color: white;
+ margin: auto;
+ }
+ }
+ .item-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ & > * {
+ line-height: 30px;
+ }
+ .item-summary {
+ font-size: 13px;
+ padding: 0 4px;
+ line-height: 20px;
+ }
+ .item-header {
+ @extend %header-field !optional;
+ padding: 0px;
+ margin-bottom: 0px;
+ }
+ .item-entry {
+ &:nth-child(even) {
+ .item {
+ background: rgba(0, 0, 0, 0.1);
+ }
+ }
+ }
+ .item {
+ line-height: 30px;
+ height: 30px;
+ overflow: hidden;
+ }
+ .item-equipped {
+ grid-area: item-equipped;
+ justify-self: center;
+ }
+ .item-name {
+ text-indent: 8px;
+ text-align: left;
+ overflow: hidden;
+ height: 30px;
+ margin: 0;
+ line-height: 30px;
+ &:hover .item-image {
+ background-image: url("/icons/svg/d20-grey.svg") !important;
+ cursor: pointer;
+ }
+ .item-image {
+ flex-basis: 30px;
+ flex-grow: 0;
+ background-size: contain;
+ background-repeat: no-repeat;
+ &:hover {
+ background-image: url("/icons/svg/d20-black.svg") !important;
+ }
+ }
+ h4 {
+ margin: 0;
+ }
+ }
+ }
+ .field-longer {
+ text-indent: 8px;
+ text-align: left;
+ flex-basis: 150px;
+ font-size: 12px;
+ flex-grow: 0;
+ }
+ .field-long {
+ flex-basis: 65px;
+ flex-grow: 0;
+ text-align: center;
+ font-size: 12px;
+ }
+ .field-short {
+ font-size: 12px;
+ flex-basis: 45px;
+ flex-grow: 0;
+ text-align: center;
+ &.quantity {
+ display: flex;
+ input {
+ margin: 3px 0;
+ border-bottom: none;
+ }
+ }
+ }
+ .item-controls {
+ font-size: 12px;
+ flex-basis: 60px;
+ flex-grow: 0;
+ text-align: right;
+ margin-right: 4px;
+ .item-unequipped {
+ color: rgba(0, 0, 0, 0.2);
+ }
+ }
+ &.spells {
+ .item-controls {
+ flex-basis: 30px;
}
}
}
.editor {
height: 300px;
- }
- .inventory {
- .item-entry {
- padding: 0;
- margin: 0;
- list-style: none;
- .item {
- .item-image {
- flex: 0 0 24px;
- height: 24px;
- background-size: cover;
- }
- .item-name {
- line-height: 24px;
- height: 24px;
- overflow: hidden;
- h4 {
- text-indent: 4px;
- margin: 0;
- cursor: pointer;
- &:hover {
- color: whitesmoke;
- background: linear-gradient(
- 45deg,
- rgba(0, 0, 0, 0.5),
- transparent
- );
- }
- }
- }
- .item-controls {
- line-height: 24px;
- flex: 0 0 32px;
- margin: 0 3px;
- .fas {
- color: $colorTan;
- font-size: 12px;
- &:hover {
- color: $colorDark;
- }
- }
- }
- }
- .item-summary {
- font-size: 12px;
- padding: 0 4px;
- }
- &:nth-child(odd) {
- background: rgba(0, 0, 0, 0.1);
- }
- }
+ padding: 4px;
}
}
}
diff --git a/src/scss/apps.scss b/src/scss/apps.scss
index e69de29..0222691 100644
--- a/src/scss/apps.scss
+++ b/src/scss/apps.scss
@@ -0,0 +1,124 @@
+.ose.chat-block {
+ margin: 0;
+ .chat-title {
+ background: $darkBackground;
+ border: 1px solid black;
+ border-radius: 3px;
+ color: white;
+ padding: 2px;
+ box-shadow: 0 0 2px #FFF inset;
+ text-align: center;
+ margin: 4px 0;
+ font-size: 16px;
+ }
+ .chat-details {
+ padding: 4px;
+ font-size: 13px;
+ .roll-result {
+ text-align: center;
+ &.roll-success {
+ color: #18520b;
+ }
+ &.roll-fail {
+ color: #aa0200;
+ }
+ }
+ }
+}
+
+.ose.chat-card {
+ font-style: normal;
+ font-size: 12px;
+
+ .card-header {
+ padding: 3px 0;
+ border-top: 2px groove #fff;
+ border-bottom: 2px groove #fff;
+
+ img {
+ flex: 0 0 36px;
+ margin-right: 5px;
+ }
+
+ h3 {
+ flex: 1;
+ margin: 0;
+ line-height: 36px;
+ color: $colorOlive;
+ &:hover {
+ color: #111;
+ text-shadow: 0 0 10px red;
+ }
+ }
+ }
+
+ .card-content {
+ margin: 5px 0;
+
+ h3 {
+ font-size: 12px;
+ margin: 0;
+ font-weight: bold;
+ }
+
+ > * {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+ }
+ }
+
+ .card-buttons {
+ margin: 5px 0;
+
+ span {
+ display: block;
+ line-height: 28px;
+ text-align: center;
+ border: 1px solid $colorTan;
+ }
+
+ button {
+ font-size: 12px;
+ height: 24px;
+ line-height: 20px;
+ margin: 2px 0;
+ }
+ }
+
+ .card-footer {
+ padding: 3px 0 0;
+ border-top: 2px groove #fff;
+
+ span {
+ border-right: 2px groove #fff;
+ padding: 0 5px 0 0;
+ font-size: 10px;
+
+ &:last-child {
+ border-right: none;
+ padding-right: 0;
+ }
+ }
+ }
+}
+
+.dice-roll .dice-total {
+ &.success {
+ color: inherit;
+ background: #c7d0c0;
+ border: 1px solid #006c00;
+ }
+ &.failure {
+ color: inherit;
+ background: #ffdddd;
+ border: 1px solid #6e0000;
+ }
+ &.critical {
+ color: green;
+ }
+ &.fumble {
+ color: red;
+ }
+}
diff --git a/src/scss/character.scss b/src/scss/character.scss
index bdb073b..b91b422 100644
--- a/src/scss/character.scss
+++ b/src/scss/character.scss
@@ -5,35 +5,66 @@
/* ----------------------------------------- */
.ose.sheet.actor.character {
min-width: 450px;
- min-height: 590px;
+ min-height: 550px;
/* ----------------------------------------- */
/* Sheet Header */
/* ----------------------------------------- */
.sheet-header {
+ .xp-bonus {
+ top: -15px;
+ right: 3px;
+ color: $colorTan;
+ font-size: 10px;
+ position: absolute;
+ }
}
/* ----------------------------------------- */
/* Sheet Body */
/* ----------------------------------------- */
.sheet-body {
- }
-
-
- .abilities {
- .panel-content {
- height: 250px;
- overflow: auto;
+ .health {
+ &.armor-class {
+ background: url('/systems/ose/assets/shield.png') no-repeat center;
+ background-size: 70px;
+ }
+ margin: 10px 0;
+ height: 70px;
+ position: relative;
+ input {
+ font-size: 16px;
+ font-weight: bolder;
+ text-shadow: 0 0 2px white, 0 1px 2px white, 1px 0 2px white, 1px 1px 2px white;
+ }
+ .health-top {
+ border-bottom: none;
+ position: absolute;
+ font-size: 24px;
+ top: 10px;
+ width: 70px;
+ left: calc(50% - 35px);
+ }
+ .health-bottom {
+ border-bottom: none;
+ position: absolute;
+ bottom: 8px;
+ width: 40px;
+ right: calc(50% + -20px);
+ }
+ .health-empty {
+ background: url('/systems/ose/assets/heart_empty.png') no-repeat center;
+ background-size: 70px;
+ background-position: top;
+ }
+ .health-full {
+ background: url('/systems/ose/assets/heart_full.png') no-repeat center;
+ background-size: 70px;
+ background-position: bottom;
+ }
}
}
-
- /* ----------------------------------------- */
- /* Inventory */
- /* ----------------------------------------- */
- .inventory {
-
- }
/* ----------------------------------------- */
/* Item Controls */
/* ----------------------------------------- */
diff --git a/src/scss/core.scss b/src/scss/core.scss
index d90a3f0..c146942 100644
--- a/src/scss/core.scss
+++ b/src/scss/core.scss
@@ -37,4 +37,4 @@
.resizable {
overflow: auto;
}
-}
+}
\ No newline at end of file
diff --git a/src/scss/item.scss b/src/scss/item.scss
index c1deb83..9463920 100644
--- a/src/scss/item.scss
+++ b/src/scss/item.scss
@@ -1,5 +1,37 @@
.ose.sheet.item {
- .editor {
- height: 255px;
+ .profile-img {
+ border: none;
+ flex: 0 0 84px;
+ height: 84px;
}
+ .sheet-body {
+ .stats {
+ flex: 0 0 90px;
+ border-right: 1px groove rgba(0, 0, 0, 0.2);
+ padding-right: 2px;
+ .form-group {
+ margin: 2px;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ label {
+ background: rgba(0, 0, 0, 0.1);
+ padding: 0 4px;
+ }
+ input {
+ border-bottom: none;
+ margin: auto 0;
+ }
+ }
+ .block-input {
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ }
+ }
+ .editor {
+ height: 240px;
+ }
+ .weapon-editor .editor {
+ height: 215px;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/scss/monster.scss b/src/scss/monster.scss
index 96cb216..75f0482 100644
--- a/src/scss/monster.scss
+++ b/src/scss/monster.scss
@@ -1,21 +1,23 @@
.ose.actor.monster {
- min-height: 565px;
- min-width: 460px;
+ min-height: 565px;
+ min-width: 460px;
.sheet-body {
.editor {
height: 300px;
}
- }
- .abilities {
- .panel-content {
- height: 230px;
- overflow: auto;
+ .attributes .attribute.health {
+ min-width: 75px;
}
}
- .attribute-row {
- padding: 2px;
- .abilities {
- margin: 2px;
+ .attribute-group {
+ .attacks-description {
+ margin: 2px;
+ padding: 0;
+ text-align: center;
+ label {
+ color: $colorTan;
+ font-size: 10px;
}
+ }
}
}
diff --git a/src/scss/variables.scss b/src/scss/variables.scss
index bd6ec1b..a04b483 100644
--- a/src/scss/variables.scss
+++ b/src/scss/variables.scss
@@ -3,7 +3,8 @@
/* Sheet Styles */
/* ----------------------------------------- */
-$colorDark: #191813;
+$darkBackground: url('/systems/ose/assets/back.png');
+$colorDark: rgba(0, 0, 0, 0.9);
$colorFaint: #c9c7b8;
$colorBeige: #b5b3a4;
$colorTan: #7a7971;
diff --git a/src/template.json b/src/template.json
index bf2f7e1..0841ef6 100644
--- a/src/template.json
+++ b/src/template.json
@@ -1,7 +1,78 @@
{
"Actor": {
"types": ["character", "monster"],
+ "templates": {
+ "common": {
+ "retainer": {
+ "enabled": false,
+ "loyalty": 0
+ },
+ "hp": {
+ "hd": "",
+ "value": 20,
+ "max": 20
+ },
+ "ac": {
+ "naked": 0,
+ "value": 0,
+ "mod": 0
+ },
+ "aac": {
+ "naked": 0,
+ "value": 0,
+ "mod": 0
+ },
+ "thac0": {
+ "value": 19,
+ "bba": 0,
+ "mod": {
+ "missile": 0,
+ "melee": 0
+ }
+ },
+ "saves": {
+ "death": 10,
+ "wand": 10,
+ "paralysis": 10,
+ "breath": 10,
+ "spell": 10
+ },
+ "movement": {
+ "base": 120
+ }
+ },
+ "spellcaster": {
+ "spells": {
+ "enabled": false,
+ "1": {
+ "value": 0,
+ "max": 0
+ },
+ "2": {
+ "value": 0,
+ "max": 0
+ },
+ "3": {
+ "value": 0,
+ "max": 0
+ },
+ "4": {
+ "value": 0,
+ "max": 0
+ },
+ "5": {
+ "value": 0,
+ "max": 0
+ },
+ "6": {
+ "value": 0,
+ "max": 0
+ }
+ }
+ }
+ },
"character": {
+ "templates": ["common", "spellcaster"],
"details": {
"biography": "",
"class": "",
@@ -9,7 +80,11 @@
"alignment": "",
"literate": false,
"level": 1,
- "xp": 0
+ "xp": {
+ "next": 0,
+ "value": 0,
+ "bonus": 0
+ }
},
"scores": {
"str": {
@@ -42,53 +117,6 @@
"silver": 0,
"copper": 0
},
- "spells": {
- "dc": 0,
- "lvl1": {
- "value": 0,
- "max": 0
- },
- "lvl2": {
- "value": 0,
- "max": 0
- },
- "lvl3": {
- "value": 0,
- "max": 0
- },
- "lvl4": {
- "value": 0,
- "max": 0
- },
- "lvl5": {
- "value": 0,
- "max": 0
- }
- },
- "hp": {
- "hd": "",
- "value": 20,
- "max": 20
- },
- "ac": {
- "value": 0,
- "mod": 0
- },
- "thac0": {
- "value": 19,
- "mod": 0
- },
- "saves": {
- "D": 10,
- "W": 10,
- "P": 10,
- "B": 10,
- "S": 10
- },
- "movement": {
- "base": 0,
- "encounter": 0
- },
"initative": {
"value": 0,
"mod": 0
@@ -96,6 +124,7 @@
"languages": []
},
"monster": {
+ "templates": ["common", "spellcaster"],
"details": {
"biography": "",
"alignment": "",
@@ -105,72 +134,52 @@
"appearing": "",
"morale": 0
},
- "saves": {
- "D": 10,
- "W": 10,
- "P": 10,
- "B": 10,
- "S": 10
- },
- "thac0": {
- "value": 19,
- "mod": 0
- },
- "hp": {
- "hd": "",
- "max": 0,
- "value": 0
- },
- "ac": {
- "value": 0,
- "mod": 0
- },
- "attacks": {
- "value": 1
- },
- "movement": {
- "base": 0,
- "encounter": 0
- },
- "spells": {
- "dc": 0,
- "lvl1": {
- "value": 0,
- "max": 0
- },
- "lvl2": {
- "value": 0,
- "max": 0
- },
- "lvl3": {
- "value": 0,
- "max": 0
- },
- "lvl4": {
- "value": 0,
- "max": 0
- },
- "lvl5": {
- "value": 0,
- "max": 0
- }
- }
+ "attacks": ""
}
},
"Item": {
- "types": ["item", "spell", "ability"],
+ "types": ["item", "weapon", "armor", "spell", "ability"],
"item": {
"description": "",
- "quantity": 1,
+ "quantity": {
+ "value": 1,
+ "max": 0
+ },
+ "cost": 0,
+ "weight": 80
+ },
+ "weapon": {
+ "description": "",
+ "damage": "1d6",
+ "qualities": "",
+ "slow": false,
+ "missile": true,
+ "melee": true,
+ "cost": 0,
+ "equipped": false,
+ "weight": 0
+ },
+ "armor": {
+ "description": "",
+ "ac": 9,
+ "aac": 10,
+ "cost": 0,
+ "equipped": false,
"weight": 0
},
"spell": {
"lvl": 1,
- "class": "",
- "description": ""
+ "class": "Magic-User",
+ "duration": "",
+ "range": "",
+ "roll": "",
+ "description": "",
+ "memorized": false,
+ "cast": false
},
"ability": {
"requirements": "",
+ "roll": "",
"description": ""
}
}
diff --git a/src/templates/actors/character-sheet.html b/src/templates/actors/character-sheet.html
index a878aa4..30b6431 100644
--- a/src/templates/actors/character-sheet.html
+++ b/src/templates/actors/character-sheet.html
@@ -9,12 +9,17 @@
{{localize "OSE.category.attributes"}}
-
- {{localize "OSE.category.inventory"}}
+
+ {{localize "OSE.category.abilities"}}
+ {{#if data.spells.enabled}}
{{localize "OSE.category.spells"}}
+ {{/if}}
+
+ {{localize "OSE.category.inventory"}}
+
{{localize "OSE.category.notes"}}
@@ -25,12 +30,17 @@
{{> "systems/ose/templates/actors/partials/character-attributes-tab.html"}}
-
- {{> "systems/ose/templates/actors/partials/character-inventory-tab.html"}}
+
+ {{> "systems/ose/templates/actors/partials/character-abilities-tab.html"}}
+ {{#if data.spells.enabled}}
{{> "systems/ose/templates/actors/partials/character-spells-tab.html"}}
+ {{/if}}
+
+ {{> "systems/ose/templates/actors/partials/character-inventory-tab.html"}}
+
{{editor content=data.details.biography target="data.details.biography"
button=true owner=owner editable=editable}}
diff --git a/src/templates/actors/dialogs/tweaks-dialog.html b/src/templates/actors/dialogs/tweaks-dialog.html
new file mode 100644
index 0000000..a6284f3
--- /dev/null
+++ b/src/templates/actors/dialogs/tweaks-dialog.html
@@ -0,0 +1,69 @@
+
+
\ No newline at end of file
diff --git a/src/templates/actors/monster-sheet.html b/src/templates/actors/monster-sheet.html
index 9786ce7..88abee4 100644
--- a/src/templates/actors/monster-sheet.html
+++ b/src/templates/actors/monster-sheet.html
@@ -9,9 +9,11 @@
{{localize "OSE.category.attributes"}}
+ {{#if data.spells.enabled}}
{{localize "OSE.category.spells"}}
+ {{/if}}
{{localize "OSE.category.notes"}}
@@ -22,9 +24,11 @@
{{> "systems/ose/templates/actors/partials/monster-attributes-tab.html"}}
+ {{#if data.spells.enabled}}
{{> "systems/ose/templates/actors/partials/character-spells-tab.html"}}
+ {{/if}}
{{editor content=data.details.biography target="data.details.biography"
button=true owner=owner editable=editable}}
diff --git a/src/templates/actors/partials/character-abilities-tab.html b/src/templates/actors/partials/character-abilities-tab.html
new file mode 100644
index 0000000..aa09111
--- /dev/null
+++ b/src/templates/actors/partials/character-abilities-tab.html
@@ -0,0 +1,36 @@
+
+
+
{{localize 'OSE.panel.abilities'}}
+
+ {{#if owner}}
+
+ {{/if}}
+
+
+
+ {{#each abilities as |item|}}
+
+
+
+
+ {{item.roll}}
+
+
+ {{#if ../owner}}
+
+
+ {{/if}}
+
+
+
+ {{/each}}
+
+
\ No newline at end of file
diff --git a/src/templates/actors/partials/character-attributes-tab.html b/src/templates/actors/partials/character-attributes-tab.html
index c87817d..6b8f2e4 100644
--- a/src/templates/actors/partials/character-attributes-tab.html
+++ b/src/templates/actors/partials/character-attributes-tab.html
@@ -1,146 +1,237 @@
-
{{!-- Scores --}}
-
- {{ localize "OSE.scores.str.short" }}
+
+
-
+
+
{{mods.str}}
-
- {{ localize "OSE.scores.int.short" }}
+
+
-
+
+
{{mods.int}}
-
- {{ localize "OSE.scores.wis.short" }}
+
+
-
+
+
{{mods.wis}}
-
- {{ localize "OSE.scores.dex.short" }}
+
+
-
+
+
{{mods.dex}}
-
- {{ localize "OSE.scores.con.short" }}
+
+
-
+
+
{{mods.con}}
-
- {{ localize "OSE.scores.cha.short" }}
+
+
- {{!-- Skills and abilities --}}
-
-
-
{{localize 'OSE.panel.abilities'}}
-
- {{#if owner}}
-
+ {{!-- Resource Tracking --}}
+
+
-
-
- {{#each abilities as |item|}}
-
-
-
-
- {{#if ../owner}}
-
-
+
+
+
+
+
+
+
+ {{#if config.ascendingAC}}
+ {{add data.thac0.mod.melee (add mods.str data.thac0.bba)}}
+ {{else}}
+ {{subtract data.thac0.mod.melee (subtract mods.str data.thac0.value)}}
{{/if}}
- {{/each}}
-
-
+ {{#if config.ascendingAC}}
+
+ {{ localize "OSE.ABShort"}}
+
+
+
+ {{else}}
+
+ {{ localize "OSE.Thac0"}}
+
+
+
+ {{/if}}
+
+
+
+
+ {{#if config.ascendingAC}}
+ {{add data.thac0.mod.missile (add mods.dex data.thac0.bba)}}
+ {{else}}
+ {{subtract data.thac0.mod.missile (subtract mods.dex data.thac0.value)}}
+ {{/if}}
+
+
+
+
+
+
+
+
+
+ {{localize 'OSE.MovementEncounterShort'}}
+
+
+ {{divide data.movement.base 3}}
+
+
+
+
+
+ {{ localize "OSE.MovementShort" }}
+
+
+
+
+
+
+ {{localize 'OSE.MovementOverlandShort'}}
+
+
+ {{divide data.movement.base 5}}
+
+
+
+
+
{{!-- Saving throws --}}
-
- {{ localize "OSE.saves.death.short" }}
+
+
-
-
- {{ localize "OSE.saves.wands.short" }}
+
+
-
-
- {{ localize "OSE.saves.paralysis.short" }}
+
+
-
+
-
- {{ localize "OSE.saves.breath.short" }}
+
+
-
+
-
- {{ localize "OSE.saves.spells.short" }}
+
+
-
+
diff --git a/src/templates/actors/partials/character-header.html b/src/templates/actors/partials/character-header.html
index 67cf726..b3dc32b 100644
--- a/src/templates/actors/partials/character-header.html
+++ b/src/templates/actors/partials/character-header.html
@@ -27,9 +27,12 @@
{{localize 'OSE.Level'}}
-
{{localize 'OSE.Experience'}}
+ {{#if data.details.xp.bonus}}
+ +{{data.details.xp.bonus}}%
+ {{/if}}
\ No newline at end of file
diff --git a/src/templates/actors/partials/character-inventory-tab.html b/src/templates/actors/partials/character-inventory-tab.html
index b370525..851f9e4 100644
--- a/src/templates/actors/partials/character-inventory-tab.html
+++ b/src/templates/actors/partials/character-inventory-tab.html
@@ -1,29 +1,141 @@
-
-
- {{#each inventory as |item|}}
-
-
-
-
- {{#if ../owner}}
-
-
- {{/if}}
-
+
+
+
+ {{localize "OSE.items.Weapons"}}
+ {{#if config.variableWeaponDamage}}
+ {{localize "OSE.items.Damage"}}
+ {{/if}}
+ {{localize "OSE.items.Qualities"}}
+ {{localize "OSE.items.Weight"}}
+
- {{/each}}
+
+ {{#each owned.weapons as |item|}}
+
+
+
+ {{#if config.variableWeaponDamage}}
+
+ {{item.data.damage}}
+
+ {{/if}}
+
+ {{item.data.qualities}}
+
+
+ {{item.data.weight}}
+
+
+ {{#if ../owner}}
+
+
+
+
+
+ {{/if}}
+
+
+
+ {{/each}}
+
-
+
+
+ {{localize "OSE.items.Armors"}}
+ {{#if config.ascendingAC}}
+ {{localize "OSE.items.ArmorAAC"}}
+ {{else}}
+ {{localize "OSE.items.ArmorAC"}}
+ {{/if}}
+ {{localize "OSE.items.Weight"}}
+
+
+
+ {{#each owned.armors as |item|}}
+
+
+
+
+ {{#if config.ascendingAC}}
+ {{item.data.aac}}
+ {{else}}
+ {{item.data.ac}}
+ {{/if}}
+
+
+ {{item.data.weight}}
+
+
+ {{#if ../owner}}
+
+
+
+
+
+ {{/if}}
+
+
+
+ {{/each}}
+
+
+
+
+ {{localize "OSE.items.Misc"}}
+ {{localize "OSE.items.Quantity"}}
+ {{localize "OSE.items.Weight"}}
+
+
+
+ {{#each owned.items as |item|}}
+
+
+
+
+ {{#if item.data.quantity.max}}/{{item.data.quantity.max}} {{/if}}
+
+
+ {{item.data.weight}}
+
+
+ {{#if ../owner}}
+
+
+ {{/if}}
+
+
+
+ {{/each}}
+
+
+
\ No newline at end of file
diff --git a/src/templates/actors/partials/character-spells-tab.html b/src/templates/actors/partials/character-spells-tab.html
index c18b4dd..3e3a08a 100644
--- a/src/templates/actors/partials/character-spells-tab.html
+++ b/src/templates/actors/partials/character-spells-tab.html
@@ -1,93 +1,42 @@
-
-
-
-
{{localize 'OSE.category.spells'}}
+
-
- {{#each spells as |item|}}
+
+ {{#each spellGroup as |item|}}
+
- {{#if ../owner}}
-
-
+ {{#if ../../owner}}
+
+
{{/if}}
{{/each}}
-
-
+
+ {{/each}}
+
\ No newline at end of file
diff --git a/src/templates/actors/partials/monster-attributes-tab.html b/src/templates/actors/partials/monster-attributes-tab.html
index 3cdd12e..2b18606 100644
--- a/src/templates/actors/partials/monster-attributes-tab.html
+++ b/src/templates/actors/partials/monster-attributes-tab.html
@@ -2,7 +2,7 @@
{{ localize "OSE.HealthShort" }}
-
+ {{#if data.retainer.enabled}}
- {{ localize "OSE.AttacksShort" }}
+ {{ localize "OSE.LoyaltyShort" }}
+
-
+ {{/if}}
- {{ localize "OSE.MovementShort" }}
+ {{ localize "OSE.MovementShort" }}
+
@@ -40,17 +61,18 @@
{{!-- Skills and abilities --}}
-
-
-
{{localize 'OSE.panel.abilities'}}
-
- {{#if owner}}
-
- {{/if}}
-
-
-
-
+
+
+
+ {{localize 'OSE.panel.abilities'}} & {{localize 'OSE.panel.equipment'}}
+
+ {{#if owner}}
+
+ {{/if}}
+
+
+
{{#each abilities as |item|}}
@@ -62,31 +84,17 @@
{{#if ../owner}}
-
-
{{/if}}
{{/each}}
-
-
-
- {{!-- Equipment --}}
-
-
-
{{localize 'OSE.panel.equipment'}}
-
- {{#if owner}}
-
- {{/if}}
-
-
-
-
- {{#each inventory as |item|}}
+ {{#each owned as |section| }}
+ {{#each section as |item|}}
@@ -96,50 +104,61 @@
- {{#if ../owner}}
-
-
{{/if}}
{{/each}}
-
-
+ {{/each}}
+
+
{{!-- Saving throws --}}
+
+ {{ localize "OSE.Attacks" }}
+
+
-
- {{ localize "OSE.saves.death.short" }}
+
+
-
-
- {{ localize "OSE.saves.wands.short" }}
+
+
-
-
- {{ localize "OSE.saves.paralysis.short" }}
+
+
-
+
-
- {{ localize "OSE.saves.breath.short" }}
+
+
-
+
-
- {{ localize "OSE.saves.spells.short" }}
+
+
-
+
diff --git a/src/templates/actors/partials/monster-header.html b/src/templates/actors/partials/monster-header.html
index 5fb5835..7e78968 100644
--- a/src/templates/actors/partials/monster-header.html
+++ b/src/templates/actors/partials/monster-header.html
@@ -31,10 +31,12 @@
placeholder="{{ localize 'OSE.Experience' }}" />
{{localize 'OSE.Experience'}}
+ {{#if config.morale}}
{{localize 'OSE.Morale'}}
+ {{/if}}
\ No newline at end of file
diff --git a/src/templates/chat/item-card.html b/src/templates/chat/item-card.html
new file mode 100644
index 0000000..fc1d239
--- /dev/null
+++ b/src/templates/chat/item-card.html
@@ -0,0 +1,38 @@
+
+
+
+
+ {{{data.description}}}
+
+
+
+ {{#if hasAttack}}{{ localize "OSE.Attack" }} {{/if}}
+
+ {{#if hasDamage}}
+
+ {{#if isHealing}}{{ localize "OSE.Healing" }}
+ {{else}}{{localize "OSE.Damage" }}{{/if}}
+
+ {{/if}}
+
+ {{#if hasSave}}
+
+ {{ localize "OSE.SavingThrow" }} {{labels.save}}
+
+ {{/if}}
+
+ {{#if data.formula}}
+ {{ localize "OSE.OtherFormula"}}
+ {{/if}}
+
+
+
+
diff --git a/src/templates/chat/roll-attack.html b/src/templates/chat/roll-attack.html
new file mode 100644
index 0000000..81cca7a
--- /dev/null
+++ b/src/templates/chat/roll-attack.html
@@ -0,0 +1,7 @@
+
+
+
{{title}}
+ {{#if details}}
{{{details}}}
{{/if}}
+ {{#if rollOSE}}
{{{rollOSE}}}
{{/if}}
+
+
\ No newline at end of file
diff --git a/src/templates/chat/roll-dialog.html b/src/templates/chat/roll-dialog.html
new file mode 100644
index 0000000..8c7a079
--- /dev/null
+++ b/src/templates/chat/roll-dialog.html
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/src/templates/items/ability-sheet.html b/src/templates/items/ability-sheet.html
index 2704d00..b675edf 100644
--- a/src/templates/items/ability-sheet.html
+++ b/src/templates/items/ability-sheet.html
@@ -3,17 +3,30 @@
- {{editor content=data.description target="data.description"
- button=true owner=owner editable=editable}}
+
+
+
+ {{editor content=data.description target="data.description" button=true
+ owner=owner editable=editable}}
+
+
-
+
\ No newline at end of file
diff --git a/src/templates/items/armor-sheet.html b/src/templates/items/armor-sheet.html
new file mode 100644
index 0000000..b6db292
--- /dev/null
+++ b/src/templates/items/armor-sheet.html
@@ -0,0 +1,44 @@
+
\ No newline at end of file
diff --git a/src/templates/items/item-sheet.html b/src/templates/items/item-sheet.html
index 2704d00..3aaaed9 100644
--- a/src/templates/items/item-sheet.html
+++ b/src/templates/items/item-sheet.html
@@ -3,17 +3,36 @@
- {{editor content=data.description target="data.description"
- button=true owner=owner editable=editable}}
+
+
+
+ {{editor content=data.description target="data.description" button=true
+ owner=owner editable=editable}}
+
+
-
+
\ No newline at end of file
diff --git a/src/templates/items/spell-sheet.html b/src/templates/items/spell-sheet.html
index 2704d00..8634ca2 100644
--- a/src/templates/items/spell-sheet.html
+++ b/src/templates/items/spell-sheet.html
@@ -3,17 +3,48 @@
- {{editor content=data.description target="data.description"
- button=true owner=owner editable=editable}}
+
+
+
+ {{editor content=data.description target="data.description" button=true
+ owner=owner editable=editable}}
+
+
-
+
\ No newline at end of file
diff --git a/src/templates/items/weapon-sheet.html b/src/templates/items/weapon-sheet.html
new file mode 100644
index 0000000..2d0dd08
--- /dev/null
+++ b/src/templates/items/weapon-sheet.html
@@ -0,0 +1,59 @@
+
\ No newline at end of file