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 @@ +
+
+ +
+ +
+
+
+ +
+ +
+
+ {{#if (eq this.type 'character')}} +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ {{/if}} +
+ +
+
+ \ 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|}} +
  1. +
    + +
    + {{item.roll}} +
    +
    + {{#if ../owner}} + + + {{/if}} +
    +
    +
  2. + {{/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 @@ -
-
    -
  • -

    {{ localize "OSE.HealthShort" }}

    -
    - - / - -
    -
  • -
  • -

    {{ localize "OSE.ArmorClassShort" }}

    -
    - -
    -
  • -
  • -

    {{ localize "OSE.Thac0" }}

    -
    - -
    -
  • -
  • -

    {{ localize "OSE.InitiativeShort" }}

    -
    - -
    -
  • -
  • -

    {{ localize "OSE.MovementShort" }}

    -
    - -
    -
  • -
-
{{!-- Scores --}}
- {{!-- Skills and abilities --}} -
-
-

{{localize 'OSE.panel.abilities'}}

-
- {{#if owner}} - + {{!-- Resource Tracking --}} +
+
+
+ + +
+
+
+
+ {{#if config.ascendingAC}} + + + {{else}} + + {{/if}}
-
    -
    - {{#each abilities as |item|}} -
  • -
    -
    -
    -

    - {{item.name~}} -

    -
    -
    - {{#if ../owner}} - - +
    +
      +
    • +

      + {{ localize "OSE.HitDiceShort" }} +

      +
      + +
      +
    • + {{#if config.individualInit}} +
    • +

      + {{ localize "OSE.InitiativeShort" }}

      +
      + +
      +
    • + {{/if}} +
    +
    +
    +
      +
    • +

      + {{localize 'OSE.MeleeShort'}}

      +
      +
      + {{#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}} +
  • +

    + {{localize 'OSE.MissileShort'}}

    +
    +
    + {{#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 --}}
    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 @@
  • - + {{#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|}} -
  • -
    -
    -
    -

    - {{item.name~}} -

    -
    -
    - {{#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|}} +
    1. +
      + + {{#if config.variableWeaponDamage}} +
      + {{item.data.damage}} +
      + {{/if}} +
      + {{item.data.qualities}} +
      +
      + {{item.data.weight}} +
      +
      + {{#if ../owner}} + + + + + + {{/if}} +
      +
      +
    2. + {{/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|}} +
    1. +
      + +
      + {{#if config.ascendingAC}} + {{item.data.aac}} + {{else}} + {{item.data.ac}} + {{/if}} +
      +
      + {{item.data.weight}} +
      +
      + {{#if ../owner}} + + + + + + {{/if}} +
      +
      +
    2. + {{/each}} +
    +
    +
    +
  • +
    {{localize "OSE.items.Misc"}}
    +
    {{localize "OSE.items.Quantity"}}
    +
    {{localize "OSE.items.Weight"}}
    +
    + +
    +
  • +
      + {{#each owned.items as |item|}} +
    1. +
      + +
      + {{#if item.data.quantity.max}}/{{item.data.quantity.max}}{{/if}} +
      +
      + {{item.data.weight}} +
      +
      + {{#if ../owner}} + + + {{/if}} +
      +
      +
    2. + {{/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.SpellDC'}}

      -
      - -
      -
    • -
    • -

      {{localize 'OSE.Level'}} 1

      -
      - - / - -
      -
    • -
    • -

      {{localize 'OSE.Level'}} 2

      -
      - - / - -
      -
    • -
    • -

      {{localize 'OSE.Level'}} 3

      -
      - - / - -
      -
    • -
    • -

      {{localize 'OSE.Level'}} 4

      -
      - - / - -
      -
    • -
    • -

      {{localize 'OSE.Level'}} 5

      -
      - - / - -
      -
    • -
    -
    -
    -
    -

    {{localize 'OSE.category.spells'}}

    +
    + {{#each spells as |spellGroup id|}} +
      +
    1. +
      {{localize "OSE.spells.Level"}} {{id}}
      +
      {{localize 'OSE.spells.Slots'}}
      +
      /
      - {{#if owner}} - - {{/if}} +
      -
    -
    - {{#each spells as |item|}} + + {{#each spellGroup as |item|}}
  • +
    + + +
    -
    -

    - {{item.name~}} -

    +
    + +

    + {{item.name~}} +

    +
    - {{#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" }}

      -
      +
      / @@ -10,11 +10,28 @@
    • -

      {{ localize "OSE.ArmorClassShort" }}

      +

      {{ localize "OSE.HitDiceShort" }} +

      - +
      +
    • +
    • + {{#if config.ascendingAC}} +

      + {{ localize "OSE.AscArmorClassShort" }}

      +
      +
      + {{else}} +

      + {{ localize "OSE.ArmorClassShort" }}

      +
      + +
      + {{/if}}
    • {{ localize "OSE.Thac0" }}

      @@ -23,15 +40,19 @@ data-dtype="Number" />
    + {{#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|}}
      1. @@ -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 --}}
    +
    + + +
    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' }}" />
  • + {{#if config.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 @@ +
    +
    + +

    {{item.name}}

    +
    + +
    + {{{data.description}}} +
    + +
    + {{#if hasAttack}}{{/if}} + + {{#if hasDamage}} + + {{/if}} + + {{#if hasSave}} + + {{/if}} + + {{#if data.formula}} + + {{/if}} +
    + +
    + {{#each data.properties}} + {{this}} + {{/each}} +
    +
    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 @@ +
    +
    + +
    +

    + +

    +
    +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + {{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/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 @@ +
    +
    + +
    +

    + +

    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + {{editor content=data.description target="data.description" button=true + owner=owner editable=editable}} +
    +
    +
    +
    \ No newline at end of file