diff --git a/README.md b/README.md index debd6fc..0c21108 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# fvtt-imperium5 +# fvtt-pegasus-rpg diff --git a/fonts/middlesaxonytext.ttf b/fonts/middlesaxonytext.ttf new file mode 100755 index 0000000..ceb8483 Binary files /dev/null and b/fonts/middlesaxonytext.ttf differ diff --git a/fonts/zag_bold.otf b/fonts/zag_bold.otf new file mode 100755 index 0000000..4088867 Binary files /dev/null and b/fonts/zag_bold.otf differ diff --git a/fonts/zag_regular.otf b/fonts/zag_regular.otf new file mode 100755 index 0000000..0574594 Binary files /dev/null and b/fonts/zag_regular.otf differ diff --git a/images/dice/cancel_icon.webp b/images/dice/cancel_icon.webp new file mode 100644 index 0000000..186c23f Binary files /dev/null and b/images/dice/cancel_icon.webp differ diff --git a/images/dice/d10-grey.svg b/images/dice/d10-grey.svg new file mode 100644 index 0000000..0777f00 --- /dev/null +++ b/images/dice/d10-grey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/dice/d10black.svg b/images/dice/d10black.svg new file mode 100644 index 0000000..048209d --- /dev/null +++ b/images/dice/d10black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/dice/d6_1.png b/images/dice/d6_1.png new file mode 100644 index 0000000..ff08092 Binary files /dev/null and b/images/dice/d6_1.png differ diff --git a/images/dice/d6_2.png b/images/dice/d6_2.png new file mode 100644 index 0000000..69c2955 Binary files /dev/null and b/images/dice/d6_2.png differ diff --git a/images/dice/d6_3.png b/images/dice/d6_3.png new file mode 100644 index 0000000..66a0fa6 Binary files /dev/null and b/images/dice/d6_3.png differ diff --git a/images/dice/d6_4.png b/images/dice/d6_4.png new file mode 100644 index 0000000..58a8dc9 Binary files /dev/null and b/images/dice/d6_4.png differ diff --git a/images/dice/d6_5.png b/images/dice/d6_5.png new file mode 100644 index 0000000..d2f7543 Binary files /dev/null and b/images/dice/d6_5.png differ diff --git a/images/dice/d6_6.png b/images/dice/d6_6.png new file mode 100644 index 0000000..caaf47e Binary files /dev/null and b/images/dice/d6_6.png differ diff --git a/images/dice/perspective-dice-five.webp b/images/dice/perspective-dice-five.webp new file mode 100644 index 0000000..8e26f2e Binary files /dev/null and b/images/dice/perspective-dice-five.webp differ diff --git a/images/icons/.directory b/images/icons/.directory new file mode 100644 index 0000000..b68f2ff --- /dev/null +++ b/images/icons/.directory @@ -0,0 +1,6 @@ +[Dolphin] +SortRole=creationtime +Timestamp=2021,4,13,9,23,48.267 +Version=4 +ViewMode=1 +VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails diff --git a/images/icons/locked.svg b/images/icons/locked.svg new file mode 100644 index 0000000..6033b6c --- /dev/null +++ b/images/icons/locked.svg @@ -0,0 +1,60 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..077404a --- /dev/null +++ b/lang/en.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/modules/imperium5-actor-sheet.js b/modules/imperium5-actor-sheet.js new file mode 100644 index 0000000..e3f8c6c --- /dev/null +++ b/modules/imperium5-actor-sheet.js @@ -0,0 +1,354 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ + +import { Imperium5Utility } from "./imperium5-utility.js"; +import { Imperium5RollDialog } from "./imperium5-roll-dialog.js"; + +/* -------------------------------------------- */ +export class PegasusActorSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-pegasus-rpg", "sheet", "actor"], + template: "systems/fvtt-pegasus-rpg/templates/actor-sheet.html", + width: 920, + height: 720, + tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "combat" }], + dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }], + editScore: true + }); + } + + /* -------------------------------------------- */ + async getData() { + const objectData = PegasusUtility.data(this.object); + + let actorData = duplicate(PegasusUtility.templateData(this.object)); + + let formData = { + title: this.title, + id: objectData.id, + type: objectData.type, + img: objectData.img, + name: objectData.name, + editable: this.isEditable, + cssClass: this.isEditable ? "editable" : "locked", + data: actorData, + effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)), + limited: this.object.limited, + specs: this.actor.getSpecs( ), + optionsDiceList: PegasusUtility.getOptionsDiceList(), + optionsLevel: PegasusUtility.getOptionsLevel(), + weapons: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getWeapons()) ), + armors: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getArmors())), + shields: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getShields()) ), + equipments: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquipmentsOnly()) ), + perks: duplicate(this.actor.getPerks()), + abilities: duplicate(this.actor.getAbilities()), + activePerks: duplicate(this.actor.getActivePerks()), + powers: duplicate(this.actor.getPowers()), + subActors: duplicate(this.actor.getSubActors()), + race: duplicate(this.actor.getRace()), + role: duplicate(this.actor.getRole()), + effects: duplicate(this.actor.getEffects()), + moneys: duplicate(this.actor.getMoneys()), + encCapacity: this.actor.getEncumbranceCapacity(), + containersTree: this.actor.containersTree, + encCurrent: this.actor.encCurrent, + encHindrance: this.actor.encHindrance, + options: this.options, + owner: this.document.isOwner, + editScore: this.options.editScore, + isGM: game.user.isGM + } + this.formData = formData; + + console.log("PC : ", formData, this.object); + return formData; + } + + /* -------------------------------------------- */ + async openGenericRoll() { + let rollData = PegasusUtility.getBasicRollData() + rollData.alias = "Dice Pool Roll", + rollData.mode = "generic" + rollData.title = `Dice Pool Roll` + rollData.img = "icons/dice/d12black.svg" + + let rollDialog = await PegasusRollDialog.create( this.actor, rollData); + rollDialog.render( true ); + } + + /* -------------------------------------------- */ + async rollIDR( itemId, diceValue) { + let item = this.actor.data.items.get( itemId) ?? {name: "Unknown"} + let myRoll = new Roll(diceValue+"x").roll({ async: false }) + await PegasusUtility.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")) + let chatData = { + user: game.user.id, + rollMode: game.settings.get("core", "rollMode"), + whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')), + content: `${this.actor.name} has roll IDR for ${item.name} : ${myRoll.total}` + } + ChatMessage.create(chatData) + } + + /* -------------------------------------------- */ + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) return; + + html.bind("keydown", function(e) { // Ignore Enter in actores sheet + if (e.keyCode === 13) return false; + }); + + // Update Inventory Item + html.find('.item-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + const item = this.actor.items.get( itemId ); + item.sheet.render(true); + }); + // Delete Inventory Item + html.find('.item-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + PegasusUtility.confirmDelete(this, li); + }); + + html.find('.spec-group-activate').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + this.actor.specPowerActivate( itemId) + }); + html.find('.spec-group-deactivate').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + this.actor.specPowerDeactivate( itemId) + }); + + html.find('.equip-activate').click(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + this.actor.equipActivate( itemId) + }); + html.find('.equip-deactivate').click(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + this.actor.equipDeactivate( itemId) + }); + + html.find('.effect-used').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + this.actor.perkEffectUsed( itemId) + }); + + html.find('.perk-status').change(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + this.actor.updatePerkStatus( itemId, ev.currentTarget.value) + }); + + html.find('.power-cost-spent').change(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + this.actor.updatePowerSpentCost( itemId, ev.currentTarget.value) + }); + + html.find('.power-dmg-roll').click(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + this.actor.powerDmgRoll( itemId ) + }) + + html.find('.perk-used').change(ev => { + const li = $(ev.currentTarget).parents(".item") + let itemId = li.data("item-id") + let index = Number($(ev.currentTarget).data("use-index") ) + this.actor.updatePerkUsed( itemId, index, ev.currentTarget.checked ) + }); + + html.find('.subactor-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let actorId = li.data("actor-id"); + let actor = game.actors.get( actorId ); + actor.sheet.render(true); + }); + + html.find('.subactor-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let actorId = li.data("actor-id"); + this.actor.delSubActor(actorId); + }); + + html.find('.quantity-minus').click(event => { + const li = $(event.currentTarget).parents(".item"); + this.actor.incDecQuantity( li.data("item-id"), -1 ); + } ); + html.find('.quantity-plus').click(event => { + const li = $(event.currentTarget).parents(".item"); + this.actor.incDecQuantity( li.data("item-id"), +1 ); + } ); + + html.find('.ammo-minus').click(event => { + const li = $(event.currentTarget).parents(".item") + this.actor.incDecAmmo( li.data("item-id"), -1 ); + } ); + html.find('.ammo-plus').click(event => { + const li = $(event.currentTarget).parents(".item") + this.actor.incDecAmmo( li.data("item-id"), +1 ) + } ); + + html.find('.momentum-minus').click(event => { + this.actor.modifyMomentum( -1 ) + } ) + html.find('.momentum-plus').click(event => { + this.actor.modifyMomentum( 1 ) + } ) + + html.find('.unarmed-attack').click((event) => { + this.actor.rollUnarmedAttack(); + }); + html.find('.generic-pool-roll').click((event) => { + this.openGenericRoll() + } ); + html.find('.attack-melee').click((event) => { + this.actor.rollPool( 'com'); + }); + html.find('.attack-ranged').click((event) => { + this.actor.rollPool( 'agi'); + }); + html.find('.defense-roll').click((event) => { + this.actor.rollPool( 'def', true); + }); + html.find('.damage-melee').click((event) => { + this.actor.rollPool( 'str'); + }); + html.find('.damage-ranged').click((event) => { + this.actor.rollPool( 'per'); + }); + html.find('.damage-resistance').click((event) => { + this.actor.rollPool( 'phy'); + }); + + html.find('.roll-stat').click((event) => { + const statId = $(event.currentTarget).data("stat-key"); + this.actor.rollStat(statId); + }); + html.find('.roll-mr').click((event) => { + this.actor.rollMR(); + }); + html.find('.roll-idr').click((event) => { + const diceValue = $(event.currentTarget).data("dice-value") + const li = $(event.currentTarget).parents(".item") + this.rollIDR( li.data("item-id"), diceValue) + }) + + html.find('.roll-spec').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const specId = li.data("item-id"); + this.actor.rollSpec(specId); + }); + html.find('.power-roll').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const powerId = li.data("item-id"); + this.actor.rollPower(powerId); + }); + html.find('.weapon-roll').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const weaponId = li.data("item-id"); + this.actor.rollWeapon(weaponId); + }); + html.find('.armor-roll').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const armorId = li.data("item-id"); + this.actor.rollArmor(armorId); + }); + + html.find('.weapon-damage-roll').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const weaponId = li.data("item-id"); + this.actor.rollWeapon(weaponId, true); + }); + + html.find('.weapon-damage').click((event) => { + const li = $(event.currentTarget).parents(".item"); + const weapon = this.actor.getOwnedItem(li.data("item-id")); + this.actor.rollDamage(weapon, 'damage'); + }); + + html.find('.lock-unlock-sheet').click((event) => { + this.options.editScore = !this.options.editScore; + this.render(true); + }); + html.find('.item-link a').click((event) => { + const itemId = $(event.currentTarget).data("item-id"); + const item = this.actor.getOwnedItem(itemId); + item.sheet.render(true); + }); + html.find('.item-equip').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + this.actor.equipItem( li.data("item-id") ); + this.render(true); + }); + html.find('.power-activate').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + this.actor.activatePower( li.data("item-id") ); + this.render(true); + }); + + html.find('.change-worstfear').change(ev => { + this.actor.manageWorstFear( ev.currentTarget.checked ) + }); + html.find('.change-desires').change(ev => { + this.actor.manageDesires( ev.currentTarget.checked ) + }); + + html.find('.update-field').change(ev => { + const fieldName = $(ev.currentTarget).data("field-name"); + let value = Number(ev.currentTarget.value); + this.actor.update( { [`${fieldName}`]: value } ); + }); + html.find('.perk-active').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + this.actor.activatePerk( li.data("item-id") ); + this.render(true); + }); + + } + + /* -------------------------------------------- */ + /** @override */ + setPosition(options = {}) { + const position = super.setPosition(options); + const sheetBody = this.element.find(".sheet-body"); + const bodyHeight = position.height - 192; + sheetBody.css("height", bodyHeight); + return position; + } + + /* -------------------------------------------- */ + async _onDropItem(event, dragData) { + console.log(">>>>>> DROPPED!!!!") + let item = await PegasusUtility.searchItem( dragData) + if (item == undefined) { + item = this.actor.items.get( dragData.data._id ) + } + this.actor.preprocessItem( event, item, true ) + super._onDropItem(event, dragData) + } + + /* -------------------------------------------- */ + /** @override */ + _updateObject(event, formData) { + // Update the Actor + return this.object.update(formData); + } +} diff --git a/modules/imperium5-actor.js b/modules/imperium5-actor.js new file mode 100644 index 0000000..1a20422 --- /dev/null +++ b/modules/imperium5-actor.js @@ -0,0 +1,1303 @@ +/* -------------------------------------------- */ +import { Imperium5Utility } from "./imperium5-utility.js"; +import { Imperium5RollDialog } from "./imperium5-roll-dialog.js"; + +/* -------------------------------------------- */ +const coverBonusTable = { "nocover": 0, "lightcover": 2, "heavycover": 4, "entrenchedcover": 6 }; +const statThreatLevel = [ "agi", "str", "phy", "com", "def", "per" ] +/* -------------------------------------------- */ +/* -------------------------------------------- */ +/** + * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. + * @extends {Actor} + */ +export class PegasusActor extends Actor { + + /* -------------------------------------------- */ + /** + * Override the create() function to provide additional SoS functionality. + * + * This overrided create() function adds initial items + * Namely: Basic skills, money, + * + * @param {Object} data Barebones actor data which this function adds onto. + * @param {Object} options (Unused) Additional options which customize the creation workflow. + * + */ + + static async create(data, options) { + + // Case of compendium global import + if (data instanceof Array) { + return super.create(data, options); + } + // If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic + if (data.items) { + let actor = super.create(data, options); + return actor; + } + + if (data.type == 'character') { + const skills = await PegasusUtility.loadCompendium("fvtt-weapons-of-the-gods.skills"); + data.items = skills.map(i => i.toObject()); + } + if (data.type == 'npc') { + } + + return super.create(data, options); + } + + /* -------------------------------------------- */ + prepareBaseData() { + } + + /* -------------------------------------------- */ + async prepareData() { + super.prepareData(); + } + + /* -------------------------------------------- */ + prepareDerivedData() { + + if (this.type == 'character') { + this.computeNRGHealth(); + this.data.data.encCapacity = this.getEncumbranceCapacity() + this.buildContainerTree() + } + + super.prepareDerivedData(); + } + + /* -------------------------------------------- */ + _preUpdate(changed, options, user) { + + super._preUpdate(changed, options, user); + } + + /* -------------------------------------------- */ + getEncumbranceCapacity() { + return this.data.data.statistics.str.value * 25 + } + + /* -------------------------------------------- */ + getActivePerks() { + let perks = this.data.items.filter(item => item.type == 'perk' && item.data.data.active); + return perks; + } + /* -------------------------------------------- */ + getAbilities() { + let ab = this.data.items.filter(item => item.type == 'ability'); + return ab; + } + /* -------------------------------------------- */ + getPerks() { + let comp = this.data.items.filter(item => item.type == 'perk'); + return comp; + } + /* -------------------------------------------- */ + getEffects() { + let comp = this.data.items.filter(item => item.type == 'effect'); + return comp; + } + /* -------------------------------------------- */ + getPowers() { + let comp = this.data.items.filter(item => item.type == 'power'); + return comp; + } + /* -------------------------------------------- */ + getMoneys() { + let comp = this.data.items.filter(item => item.type == 'money'); + return comp; + } + /* -------------------------------------------- */ + getArmors() { + let comp = duplicate(this.data.items.filter(item => item.type == 'armor') || []); + return comp; + } + /* -------------------------------------------- */ + getShields() { + let comp = this.data.items.filter(item => item.type == 'shield') + return comp; + } + getRace() { + let race = this.data.items.filter(item => item.type == 'race') + return race[0] ?? []; + } + getRole() { + let role = this.data.items.filter(item => item.type == 'role') + return role[0] ?? []; + } + /* -------------------------------------------- */ + checkAndPrepareEquipment(item) { + if ( item.data.resistance ) { + item.data.resistanceDice = PegasusUtility.getDiceFromLevel(item.data.resistance) + } + if ( item.data.idr ) { + item.data.idrDice = PegasusUtility.getDiceFromLevel(item.data.idr) + } + if ( item.data.damage) { + item.data.damageDice = PegasusUtility.getDiceFromLevel(item.data.damage) + } + if( item.data.level) { + item.data.levelDice = PegasusUtility.getDiceFromLevel(item.data.level) + } + } + + /* -------------------------------------------- */ + checkAndPrepareEquipments(listItem) { + for (let item of listItem) { + this.checkAndPrepareEquipment(item) + } + return listItem + } + + /* -------------------------------------------- */ + getWeapons() { + let comp = duplicate(this.data.items.filter(item => item.type == 'weapon') || []); + return comp; + } + /* -------------------------------------------- */ + getItemById(id) { + let item = this.data.items.find(item => item.id == id); + if (item) { + item = duplicate(item) + if (item.type == 'specialisation') { + item.data.dice = PegasusUtility.getDiceFromLevel(item.data.level); + } + } + return item; + } + + /* -------------------------------------------- */ + getSpecs() { + let comp = duplicate(this.data.items.filter(item => item.type == 'specialisation') || []); + for (let c of comp) { + c.data.dice = PegasusUtility.getDiceFromLevel(c.data.level); + } + return comp; + } + + /* -------------------------------------------- */ + async manageWorstFear(flag) { + if (flag) { + let effect = await PegasusUtility.getEffectFromCompendium("Worst Fear") + effect.data.worstfear = true + this.createEmbeddedDocuments('Item', [effect]) + } else { + let effect = this.data.items.find(item => item.type == "effect" && item.data.data.worstfear) + if (effect) { + this.deleteEmbeddedDocuments('Item', [effect.id]) + } + } + } + /* -------------------------------------------- */ + async manageDesires(flag) { + if (flag) { + let effect = await PegasusUtility.getEffectFromCompendium("Desires") + effect.data.desires = true + this.createEmbeddedDocuments('Item', [effect]) + } else { + let effect = this.data.items.find(item => item.type == "effect" && item.data.data.desires) + if (effect) { + this.deleteEmbeddedDocuments('Item', [effect.id]) + } + } + } + + /* -------------------------------------------- */ + getRelevantSpec(statKey) { + let comp = duplicate(this.data.items.filter(item => item.type == 'specialisation' && item.data.data.statistic == statKey) || []); + for (let c of comp) { + c.data.dice = PegasusUtility.getDiceFromLevel(c.data.level); + } + return comp; + } + + /* -------------------------------------------- */ + async activatePerk(perkId) { + let item = this.data.items.find(item => item.id == perkId); + if (item && item.data.data) { + let update = { _id: item.id, "data.active": !item.data.data.active }; + await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity + } + } + + /* -------------------------------------------- */ + async activatePower(itemId) { + let item = this.data.items.find(item => item.id == itemId) + if (item && item.data.data) { + + let nrg = duplicate(this.data.data.nrg) + if (!item.data.data.activated) { // Current value + + if (item.data.data.costspent > nrg.value || item.data.data.costspent > nrg.max) { + return ui.notifications.warn("Not enough NRG to activate the Power " + item.name) + } + nrg.activated += item.data.data.costspent + nrg.value -= item.data.data.costspent + nrg.max -= item.data.data.costspent + await this.update({ 'data.nrg': nrg }) + + let effects = [] + for (let effect of item.data.data.effectsgained) { + effect.data.powerId = itemId // Link to the perk, in order to dynamically remove them + effects.push(effect) + } + if (effects.length) { + await this.createEmbeddedDocuments('Item', effects) + } + } else { + nrg.activated -= item.data.data.costspent + nrg.max += item.data.data.costspent + await this.update({ 'data.nrg': nrg }) + + let toRem = [] + for (let item of this.data.items) { + if (item.type == 'effect' && item.data.data.powerId == itemId) { + toRem.push(item.id) + } + } + if (toRem.length) { + await this.deleteEmbeddedDocuments('Item', toRem) + } + } + let update = { _id: item.id, "data.activated": !item.data.data.activated } + await this.updateEmbeddedDocuments('Item', [update]) // Updates one EmbeddedEntity + } + } + + /* -------------------------------------------- */ + async equipItem(itemId) { + let item = this.data.items.find(item => item.id == itemId); + if (item && item.data.data) { + let update = { _id: item.id, "data.equipped": !item.data.data.equipped }; + await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity + } + } + + /* -------------------------------------------- */ + compareName(a, b) { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + } + + /* ------------------------------------------- */ + getEquipments() { + return this.data.items.filter(item => item.type == 'shield' || item.type == 'armor' || item.type == "weapon" || item.type == "equipment"); + } + /* ------------------------------------------- */ + getEquipmentsOnly() { + return duplicate(this.data.items.filter(item => item.type == "equipment") || []) + } + + /* ------------------------------------------- */ + computeThreatLevel() { + let tl = 0 + for(let key of statThreatLevel) { // Init with concerned stats + tl += PegasusUtility.getDiceValue( this.data.data.statistics[key].value ) + } + let powers = duplicate( this.getPowers() || []) + if ( powers.length > 0 ) { // Then add some mental ones of powers + tl += PegasusUtility.getDiceValue( this.data.data.statistics.foc.value ) + tl += PegasusUtility.getDiceValue( this.data.data.statistics.mnd.value ) + } + tl += PegasusUtility.getDiceValue( this.data.data.mr.value ) + let specThreat = this.data.items.filter( it => it.type == "specialisation" && it.data.data.isthreatlevel) || [] + for (let spec of specThreat) { + tl += PegasusUtility.getDiceValue( spec.data.data.level ) + } + tl += this.data.data.nrg.absolutemax + this.data.data.secondary.health.max + this.data.data.secondary.delirium.max + tl += this.getPerks().length * 5 + + let weapons = this.getWeapons() + for(let weapon of weapons) { + tl += PegasusUtility.getDiceValue(weapon.data.damage) + } + let armors = this.getArmors() + for(let armor of armors) { + tl += PegasusUtility.getDiceValue(armor.data.resistance) + } + let shields = this.getShields() + for(let shield of shields) { + tl += PegasusUtility.getDiceValue(shield.data.level) + } + let abilities = duplicate(this.getAbilities()) + for (let ability of abilities) { + tl += ability.data.threatlevel + } + let equipments = this.getEquipmentsOnly() + for (let equip of equipments) { + tl += equip.data.threatlevel + } + if ( tl != this.data.data.biodata.threatlevel) { + this.update( {'data.biodata.threatlevel': tl} ) + } + } + + /* ------------------------------------------- */ + async buildContainerTree() { + let equipments = duplicate(this.data.items.filter(item => item.type == "equipment") || []) + for (let equip1 of equipments) { + if (equip1.data.iscontainer) { + equip1.data.contents = [] + equip1.data.contentsEnc = 0 + for (let equip2 of equipments) { + if (equip1._id != equip2._id && equip2.data.containerid == equip1._id) { + equip1.data.contents.push(equip2) + let q = equip2.data.quantity ?? 1 + equip1.data.contentsEnc += q *equip2.data.weight + } + } + } + } + + // Compute whole enc + let enc = 0 + for (let item of equipments) { + item.data.idrDice = PegasusUtility.getDiceFromLevel( Number(item.data.idr)) + if (item.data.equipped) { + if (item.data.iscontainer) { + enc += item.data.contentsEnc + } else if (item.data.containerid == "") { + let q = item.data.quantity ?? 1 + enc += q * item.data.weight + } + } + } + for (let item of this.data.items) { // Process items/shields/armors + if ((item.type == "weapon" || item.type == "shield" || item.type == "armor") && item.data.data.equipped) { + let q = item.data.data.quantity ?? 1 + enc += q * item.data.data.weight + } + } + + // Store local values + this.encCurrent = enc + this.containersTree = equipments.filter(item => item.data.containerid == "") // Returns the root of equipements without container + + // Manages slow effect + let overCapacity = Math.floor(this.encCurrent / this.getEncumbranceCapacity()) + this.encHindrance = Math.floor(this.encCurrent / this.getEncumbranceCapacity()) + + //console.log("Capacity", overCapacity, this.encCurrent / this.getEncumbranceCapacity() ) + let effect = this.data.items.find(item => item.type == "effect" && item.data.data.slow) + if (overCapacity >= 4) { + if (!effect) { + effect = await PegasusUtility.getEffectFromCompendium("Slowed") + effect.data.slow = true + this.createEmbeddedDocuments('Item', [effect]) + } + } else { + if (effect) { + this.deleteEmbeddedDocuments('Item', [effect.id]) + } + } + } + + /* -------------------------------------------- */ + modifyMomentum(incDec) { + let momentum = duplicate(this.data.data.momentum) + momentum.value += incDec + if (momentum.value >= 0 ) { + this.update({ 'data.momentum': momentum }) + let chatData = { + user: game.user.id, + rollMode: game.settings.get("core", "rollMode"), + whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')) + } + if (incDec > 0) { + chatData.content = `
${this.name} has gained a Momentum${this.name} has used a Momentum true) { + let array = Array.from(this.getEmbeddedCollection("ActiveEffect").values()); + return Array.from(this.getEmbeddedCollection("ActiveEffect").values()).filter(it => matching(it)); + } + /* -------------------------------------------- */ + getEffectByLabel(label) { + return this.getActiveEffects().find(it => it.data.label == label); + } + /* -------------------------------------------- */ + getEffectById(id) { + return this.getActiveEffects().find(it => it.id == id); + } + + /* -------------------------------------------- */ + getAttribute(attrKey) { + return this.data.data.attributes[attrKey]; + } + + /* -------------------------------------------- */ + async addObjectToContainer(itemId, containerId) { + let container = this.data.items.find(item => item.id == containerId && item.data.data.iscontainer) + let object = this.data.items.find(item => item.id == itemId) + console.log("Found", container, object) + if (container) { + if (object.data.data.iscontainer) { + ui.notifications.warn("Only 1 level of container allowed") + return + } + let alreadyInside = this.data.items.filter(item => item.data.data.containerid && item.data.data.containerid == containerId); + if (alreadyInside.length >= container.data.data.containercapacity) { + ui.notifications.warn("Container is already full !") + return + } else { + await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'data.containerid': containerId }]) + } + } else if (object && object.data.data.containerid) { // remove from container + console.log("Removeing: ", object) + await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'data.containerid': "" }]); + } + } + + /* -------------------------------------------- */ + async preprocessItem(event, item, onDrop = false) { + console.log("Pre-process !!!", item) + if (item.data.type == 'race') { + this.applyRace(item.data) + } else if (item.data.type == 'ability') { + this.applyAbility(item.data, [], true) + if (!onDrop) { + await this.createEmbeddedDocuments('Item', [item.data]) + return + } + } else { + if (!onDrop) { + await this.createEmbeddedDocuments('Item', [item.data]) + return + } + } + + let dropID = $(event.target).parents(".item").attr("data-item-id") // Only relevant if container drop + let objectID = item.id || item._id + this.addObjectToContainer(objectID, dropID) + } + + /* -------------------------------------------- */ + async equipGear(equipmentId) { + let item = this.data.items.find(item => item.id == equipmentId); + if (item && item.data.data) { + let update = { _id: item.id, "data.equipped": !item.data.data.equipped }; + await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity + } + } + /* -------------------------------------------- */ + getInitiativeScore(combatId, combatantId) { + if (this.type == 'character') { + this.rollMR(true, combatId, combatantId) + } + console.log("Init required !!!!") + return -1; + } + + /* -------------------------------------------- */ + getSubActors() { + let subActors = []; + for (let id of this.data.data.subactors) { + subActors.push(duplicate(game.actors.get(id))) + } + return subActors; + } + /* -------------------------------------------- */ + async addSubActor(subActorId) { + let subActors = duplicate(this.data.data.subactors); + subActors.push(subActorId); + await this.update({ 'data.subactors': subActors }); + } + /* -------------------------------------------- */ + async delSubActor(subActorId) { + let newArray = []; + for (let id of this.data.data.subactors) { + if (id != subActorId) { + newArray.push(id); + } + } + await this.update({ 'data.subactors': newArray }); + } + + /* -------------------------------------------- */ + syncRoll(rollData) { + let linkedRollId = PegasusUtility.getDefenseState(this.id); + if (linkedRollId) { + rollData.linkedRollId = linkedRollId; + } + this.lastRollId = rollData.rollId; + PegasusUtility.saveRollData(rollData); + } + + /* -------------------------------------------- */ + getStat(statKey) { + let stat + if (statKey == 'mr') { + stat = duplicate(this.data.data.mr); + } else { + stat = duplicate(this.data.data.statistics[statKey]); + } + stat.dice = PegasusUtility.getDiceFromLevel(stat.value); + return stat; + } + + /* -------------------------------------------- */ + getOneSpec(specId) { + let spec = this.data.items.find(item => item.type == 'specialisation' && item.id == specId) + if (spec) { + spec = duplicate(spec); + spec.data.dice = PegasusUtility.getDiceFromLevel(spec.data.level); + } + return spec; + } + + /* -------------------------------------------- */ + specPowerActivate(specId) { + let spec = this.getOneSpec(specId) + if (spec) { + let powers = [] + for (let power of spec.data.powers) { + power.data.specId = specId + powers.push(power) + } + if (powers.length > 0) { + this.createEmbeddedDocuments('Item', powers) + } + this.updateEmbeddedDocuments('Item', [{ _id: specId, 'data.powersactivated': true }]) + } + } + + /* -------------------------------------------- */ + specPowerDeactivate(specId) { + let toRem = [] + for (let power of this.data.items) { + if (power.type == "power" && power.data.data.specId && power.data.data.specId == specId) { + toRem.push(power.id) + } + } + if (toRem.length > 0) { + this.deleteEmbeddedDocuments('Item', toRem) + } + this.updateEmbeddedDocuments('Item', [{ _id: specId, 'data.powersactivated': false }]) + } + + /* -------------------------------------------- */ + equipActivate(itemId) { + let item = this.items.get(itemId) + if (item) { + let effects = [] + for (let effect of item.data.data.effects) { + effect.data.itemId = itemId // Keep link + effects.push(effect) + } + if (effects.length > 0) { + this.createEmbeddedDocuments('Item', effects) + } + this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'data.activated': true }]) + } + } + + /* -------------------------------------------- */ + equipDeactivate(itemId) { + let toRem = [] + for (let item of this.data.items) { + if (item.data.data.itemId && item.data.data.itemId == itemId) { + toRem.push(item.id) + } + } + if (toRem.length > 0) { + this.deleteEmbeddedDocuments('Item', toRem) + } + this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'data.activated': false }]) + } + + /* -------------------------------------------- */ + async perkEffectUsed(itemId) { + let effect = this.items.get(itemId) + if (effect) { + PegasusUtility.createChatWithRollMode(effect.name, { + content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-effect-used.html`, effect.data) + }); + + this.deleteEmbeddedDocuments('Item', [effect.id]) + } + } + + /* -------------------------------------------- */ + disableWeaverPerk(perk) { + if (perk.data.data.isweaver) { + for (let spec of this.data.items) { + if (spec.type == 'specialisation' && spec.data.data.ispowergroup) { + this.specPowerDeactivate(spec.id) + } + } + } + } + + /* -------------------------------------------- */ + enableWeaverPerk(perk) { + if (perk.data.data.isweaver) { + for (let spec of this.data.items) { + if (spec.type == 'specialisation' && spec.data.data.ispowergroup) { + this.specPowerActivate(spec.id) + } + } + } + } + + /* -------------------------------------------- */ + async cleanPerkEffects(itemId) { + let effects = [] + for (let item of this.data.items) { + if (item.type == "effect" && item.data.data.perkId == itemId) { + effects.push(item.id) + } + } + if (effects.length > 0) { + console.log("DELET!!!!", effects, this) + await this.deleteEmbeddedDocuments('Item', effects) + } + } + + /* -------------------------------------------- */ + async updatePerkUsed(itemId, index, checked) { + let item = this.items.get(itemId) + if (item && index) { + let key = "data.used" + index + await this.updateEmbeddedDocuments('Item', [{ _id: itemId, [`${key}`]: checked }]) + item = this.items.get(itemId) // Refresh + if (item.data.data.nbuse == "next1action" && item.data.data.used1) { + this.cleanPerkEffects(itemId) + } + if (item.data.data.nbuse == "next2action" && item.data.data.used1 && item.data.data.used2) { + this.cleanPerkEffects(itemId) + } + if (item.data.data.nbuse == "next3action" && item.data.data.used1 && item.data.data.used2 && item.data.data.used3) { + this.cleanPerkEffects(itemId) + } + } + } + + /* -------------------------------------------- */ + async updatePowerSpentCost(itemId, value) { + let item = this.items.get(itemId) + if (item && value) { + value = Number(value) || 0 + await this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'data.costspent': value }]) + } + } + + /* -------------------------------------------- */ + async updatePerkStatus(itemId, status) { + let item = this.items.get(itemId) + if (item) { + + if (item.data.data.status == status) return;// Ensure we are really changing the status + + let updateOK = true + if (status == "ready") { + await this.cleanPerkEffects(itemId) + await this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'data.used1': false, 'data.used2': false, 'data.used3': false }]) + if (item.data.data.features.nrgcost.flag) { + let nrg = duplicate(this.data.data.nrg) + nrg.activated -= item.data.data.features.nrgcost.value + nrg.max += item.data.data.features.nrgcost.value + await this.update({ 'data.nrg': nrg }) + } + if (item.data.data.features.bonushealth.flag) { + let health = duplicate(this.data.data.secondary.health) + health.value -= Number(item.data.data.features.bonushealth.value) || 0 + health.max -= Number(item.data.data.features.bonushealth.value) || 0 + await this.update({ 'data.secondary.health': health }) + } + if (item.data.data.features.bonusdelirium.flag) { + let delirium = duplicate(this.data.data.secondary.delirium) + delirium.value -= Number(item.data.data.features.bonusdelirium.value) || 0 + delirium.max -= Number(item.data.data.features.bonusdelirium.value) || 0 + await this.update({ 'data.secondary.delirium': delirium }) + } + if (item.data.data.features.bonusnrg.flag) { + let nrg = duplicate(this.data.data.nrg) + nrg.value -= Number(item.data.data.features.bonusnrg.value) || 0 + nrg.max -= Number(item.data.data.features.bonusnrg.value) || 0 + await this.update({ 'data.nrg': nrg }) + } + this.disableWeaverPerk(item) + PegasusUtility.createChatWithRollMode(item.name, { + content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-perk-ready.html`, { name: this.name, perk: item }) + }); + + } + if (status == "activated") { + // Add effects linked to the perk + let effects = [] + for (let effect of item.data.data.effectsgained) { + effect.data.perkId = itemId // Link to the perk, in order to dynamically remove them + effect.data.isUsed = false // Flag to indicate removal when used in a roll window + effects.push(effect) + } + if (effects.length) { + await this.createEmbeddedDocuments('Item', effects) + } + // Manage additional flags + if (item.data.data.features.nrgcost.flag) { + if ((this.data.data.nrg.value >= item.data.data.features.nrgcost.value) && (this.data.data.nrg.max >= item.data.data.features.nrgcost.value)) { + let nrg = duplicate(this.data.data.nrg) + nrg.activated += item.data.data.features.nrgcost.value + nrg.value -= item.data.data.features.nrgcost.value + nrg.max -= item.data.data.features.nrgcost.value + await this.update({ 'data.nrg': nrg }) + } else { + updateOK = false + ui.notifications.warn("Not enough NRG to activate the Perk " + item.name) + } + } + if (item.data.data.features.bonushealth.flag) { + let health = duplicate(this.data.data.secondary.health) + health.value += Number(item.data.data.features.bonushealth.value) || 0 + health.max += Number(item.data.data.features.bonushealth.value) || 0 + await this.update({ 'data.secondary.health': health }) + } + if (item.data.data.features.bonusdelirium.flag) { + let delirium = duplicate(this.data.data.secondary.delirium) + delirium.value += Number(item.data.data.features.bonusdelirium.value) || 0 + delirium.max += Number(item.data.data.features.bonusdelirium.value) || 0 + await this.update({ 'data.secondary.delirium': delirium }) + } + if (item.data.data.features.bonusnrg.flag) { + let nrg = duplicate(this.data.data.nrg) + nrg.value += Number(item.data.data.features.bonusnrg.value) || 0 + nrg.max += Number(item.data.data.features.bonusnrg.value) || 0 + await this.update({ 'data.nrg': nrg }) + } + this.enableWeaverPerk(item) + } + if (updateOK) { + await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'data.status': status }]) + } + } + } + + /* -------------------------------------------- */ + async deleteAllItemsByType(itemType) { + let items = this.data.items.filter(item => item.type == itemType); + await this.deleteEmbeddedDocuments('Item', items); + } + + /* -------------------------------------------- */ + async addItemWithoutDuplicate(newItem) { + let item = this.data.items.find(item => item.type == newItem.type && item.name.toLowerCase() == newItem.name.toLowerCase()) + if (!item) { + await this.createEmbeddedDocuments('Item', [newItem]); + } + } + + /* -------------------------------------------- */ + async computeNRGHealth() { + if (this.isToken) return + + if (this.isOwner || game.user.isGM) { + let updates = {} + let phyDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.phy.value) + this.data.data.secondary.health.bonus + this.data.data.statistics.phy.mod; + if (phyDiceValue != this.data.data.secondary.health.max) { + updates['data.secondary.health.max'] = phyDiceValue + } + if (this.computeValue) { + updates['data.secondary.health.value'] = phyDiceValue + } + let mndDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.mnd.value) + this.data.data.secondary.delirium.bonus + this.data.data.statistics.mnd.mod; + if (mndDiceValue != this.data.data.secondary.delirium.max) { + updates['data.secondary.delirium.max'] = mndDiceValue + } + if (this.computeValue) { + updates['data.secondary.delirium.value'] = mndDiceValue + } + let stlDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.stl.value) + this.data.data.secondary.stealthhealth.bonus + this.data.data.statistics.stl.mod; + if (stlDiceValue != this.data.data.secondary.stealthhealth.max) { + updates['data.secondary.stealthhealth.max'] = stlDiceValue + } + if (this.computeValue) { + updates['data.secondary.stealthhealth.value'] = stlDiceValue + } + + let socDiceValue = PegasusUtility.getDiceValue(this.data.data.statistics.soc.value) + this.data.data.secondary.socialhealth.bonus + this.data.data.statistics.soc.mod; + if (socDiceValue != this.data.data.secondary.socialhealth.max) { + updates['data.secondary.socialhealth.max'] = socDiceValue + } + if (this.computeValue) { + updates['data.secondary.socialhealth.value'] = socDiceValue + } + + let nrgValue = PegasusUtility.getDiceValue(this.data.data.statistics.foc.value) + this.data.data.nrg.mod + this.data.data.statistics.foc.mod + if (nrgValue != this.data.data.nrg.absolutemax) { + updates['data.nrg.absolutemax'] = nrgValue + } + if (this.computeValue) { + updates['data.nrg.max'] = nrgValue + updates['data.nrg.value'] = nrgValue + } + + nrgValue = PegasusUtility.getDiceValue(this.data.data.statistics.mnd.value) + this.data.data.statistics.mnd.mod; + if (nrgValue != this.data.data.combat.stunthreshold) { + updates['data.combat.stunthreshold'] = nrgValue + } + + let momentum = this.data.data.statistics.foc.value + this.data.data.statistics.foc.mod + if (momentum != this.data.data.momentum.max) { + updates['data.momentum.value'] = 0 + updates['data.momentum.max'] = momentum + } + + let mrLevel = (this.data.data.statistics.agi.value + this.data.data.statistics.str.value) - this.data.data.statistics.phy.value + mrLevel = (mrLevel < 1) ? 1 : mrLevel; + if (mrLevel != this.data.data.mr.value) { + updates['data.mr.value'] = mrLevel + } + + let race = this.getRace() + if (race && race.name && (race.name != this.data.data.biodata.racename)) { + updates['data.biodata.racename'] = race.name + } + let role = this.getRole() + if (role && role.name && (role.name != this.data.data.biodata.rolename)) { + updates['data.biodata.rolename'] = role.name + } + //console.log("UPD", updates, this.data.data.biodata) + await this.update(updates) + + this.computeThreatLevel() + } + + if (this.isOwner || game.user.isGM) { + // Update current hindrance level + let hindrance = this.data.data.combat.hindrancedice + if (this.data.data.secondary.health.value < 0) { + hindrance += Math.abs(this.data.data.secondary.health.value) + } + if (this.data.data.secondary.delirium.value < 0) { + hindrance += Math.abs(this.data.data.secondary.delirium.value) + } + this.data.data.combat.hindrancedice = hindrance + } + } + + /* -------------------------------------------- */ + async modStat(key, inc = 1) { + let stat = duplicate(this.data.data.statistics[key]) + stat.mod += parseInt(inc) + await this.update({ [`data.statistics.${key}`]: stat }) + } + + /* -------------------------------------------- */ + async valueStat(key, inc = 1) { + key = key.toLowerCase() + let stat = duplicate(this.data.data.statistics[key]) + stat.value += parseInt(inc) + await this.update({ [`data.statistics.${key}`]: stat }) + } + + /* -------------------------------------------- */ + async addIncSpec(spec, inc = 1) { + console.log("Using spec : ", spec, inc) + let specExist = this.data.items.find(item => item.type == 'specialisation' && item.name.toLowerCase() == spec.name.toLowerCase()) + if (specExist) { + specExist = duplicate(specExist) + specExist.data.level += inc; + let update = { _id: specExist._id, "data.level": specExist.data.level }; + await this.updateEmbeddedDocuments('Item', [update]); + } else { + spec.data.level += inc; + await this.createEmbeddedDocuments('Item', [spec]); + } + } + + /* -------------------------------------------- */ + async incDecQuantity(objetId, incDec = 0) { + let objetQ = this.data.items.get(objetId) + if (objetQ) { + let newQ = objetQ.data.data.quantity + incDec + if (newQ >= 0) { + const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.quantity': newQ }]) // pdates one EmbeddedEntity + } + } + } + /* -------------------------------------------- */ + async incDecAmmo(objetId, incDec = 0) { + let objetQ = this.data.items.get(objetId) + if (objetQ) { + let newQ = objetQ.data.data.ammocurrent + incDec; + if ( newQ >= 0 && newQ <= objetQ.data.data.ammomax) { + const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'data.ammocurrent': newQ }]); // pdates one EmbeddedEntity + } + } + } + + /* -------------------------------------------- */ + async applyAbility(ability, updates = [], directUpdate = false) { + // manage stat bonus + if (ability.data.affectedstat != "notapplicable") { + let stat = duplicate(this.data.data.statistics[ability.data.affectedstat]) + stat.mod += Number(ability.data.statmodifier) + updates[`data.statistics.${ability.data.affectedstat}`] = stat + } + // manage status bonus + if (ability.data.statusaffected != "notapplicable") { + if (ability.data.statusaffected == 'nrg') { + let nrg = duplicate(this.data.data.nrg) + nrg.mod += Number(ability.data.statusmodifier) + updates[`data.nrg`] = nrg + } + if (ability.data.statusaffected == 'health') { + let health = duplicate(this.data.data.secondary.health) + health.bonus += Number(ability.data.statusmodifier) + updates[`data.secondary.health`] = health + } + if (ability.data.statusaffected == 'delirium') { + let delirium = duplicate(this.data.data.secondary.delirium) + delirium.bonus += Number(ability.data.statusmodifier) + updates[`data.secondary.delirium`] = delirium + } + } + if (directUpdate) { + await this.update(updates) + } + let newItems = [] + if (ability.data.effectsgained) { + for (let effect of ability.data.effectsgained) { + newItems.push(effect); + } + } + if (ability.data.powersgained) { + for (let power of ability.data.powersgained) { + newItems.push(power); + } + } + if (ability.data.specialisations) { + for (let spec of ability.data.specialisations) { + newItems.push(spec); + } + } + if (ability.data.attackgained) { + for (let weapon of ability.data.attackgained) { + newItems.push(weapon); + } + } + if (ability.data.armorgained) { + for (let armor of ability.data.armorgained) { + newItems.push(armor); + } + } + await this.createEmbeddedDocuments('Item', newItems) + } + + /* -------------------------------------------- */ + async applyRace(race) { + let updates = { 'data.biodata.racename': race.name } + let newItems = [] + await this.deleteAllItemsByType('race') + newItems.push(race); + + for (let ability of race.data.abilities) { + newItems.push(ability) + this.applyAbility(ability, updates) + } + if (race.data.perksgained) { + for (let power of race.data.perks) { + newItems.push(power); + } + } + + await this.update(updates) + await this.createEmbeddedDocuments('Item', newItems) + console.log("Updates", updates, newItems) + console.log("Updated actor", this) + } + + /* -------------------------------------------- */ + getIncreaseStatValue(updates, statKey) { + let stat = duplicate(this.data.data.statistics[statKey]) + stat.value += 1; + updates[`data.statistics.${statKey}`] = stat + } + + /* -------------------------------------------- */ + async applyRole(role) { + console.log("ROLE", role) + + let updates = { 'data.biodata.rolename': role.name } + let newItems = [] + await this.deleteAllItemsByType('role') + newItems.push(role); + + this.getIncreaseStatValue(updates, role.data.statincrease1) + this.getIncreaseStatValue(updates, role.data.statincrease2) + + //newItems = newItems.concat(duplicate(role.data.specialisationsplus1)) + newItems = newItems.concat(duplicate(role.data.specialperk)) + + await this.update(updates) + await this.createEmbeddedDocuments('Item', newItems) + } + + + /* -------------------------------------------- */ + addHindrancesList(effectsList) { + if (this.data.data.combat.stunlevel > 0) { + effectsList.push({ label: "Stun Hindrance", type: "hindrance", applied: false, value: this.data.data.combat.stunlevel }) + } + if (this.data.data.combat.hindrancedice > 0) { + effectsList.push({ label: "Health/Delirium Hindrance", type: "hindrance", applied: false, value: this.data.data.combat.hindrancedice }) + } + let overCapacity = Math.floor(this.encCurrent / this.getEncumbranceCapacity()) + if (overCapacity > 0) { + effectsList.push({ label: "Encumbrance Hindrance", type: "hindrance", applied: false, value: overCapacity }) + } + let effects = this.data.items.filter(item => item.type == 'effect') + for (let effect of effects) { + effect = duplicate(effect) + if (effect.data.hindrance) { + effectsList.push({ label: effect.name, type: "effect", applied: false, effect: effect, value: effect.data.effectlevel }) + } + } + } + + /* -------------------------------------------- */ + /* ROLL SECTION + /* -------------------------------------------- */ + + /* -------------------------------------------- */ + addEffects(rollData) { + let effects = this.data.items.filter(item => item.type == 'effect') + for (let effect of effects) { + effect = duplicate(effect) + if (!effect.data.hindrance + && (effect.data.stataffected != "notapplicable" || effect.data.specaffected.length > 0) + && effect.data.stataffected != "special") { + if (effect.data.effectstatlevel) { + effect.data.effectlevel = this.data.data.statistics[effect.data.effectstat].value + } + rollData.effectsList.push({ label: effect.name, type: "effect", applied: false, effect: effect, value: effect.data.effectlevel }) + } + } + } + + /* -------------------------------------------- */ + addArmorsShields(rollData, statKey = "none", useShield = false) { + if (statKey == 'phy') { + let armors = this.getArmors() + for (let armor of armors) { + rollData.armorsList.push({ label: `Armor ${armor.name}`, type: "other", applied: false, value: armor.data.resistance }) + } + } + if (useShield) { + let shields = this.data.items.filter(item => item.type == "shield" && item.data.data.equipped) + for (let sh of shields) { + rollData.armorsList.push({ label: `Shield ${sh.name}`, type: "other", applied: false, value: sh.data.data.level }) + } + } + } + addWeapons(rollData, statKey) { + let weapons = this.getWeapons() + for (let weapon of weapons) { + if (weapon.data.equipped && weapon.data.statistic == statKey) { + rollData.weaponsList.push({ label: `Attack ${weapon.name}`, type: "attack", applied: false, weapon: weapon, value: 0 }) + } + if (weapon.data.equipped && weapon.data.enhanced && weapon.data.enhancedstat == statKey) { + rollData.weaponsList.push({ label: `Enhanced Attack ${weapon.name}`, type: "enhanced", applied: false, weapon: weapon, value: weapon.data.enhancedlevel }) + } + if (weapon.data.equipped && weapon.data.damagestatistic == statKey) { + rollData.weaponsList.push({ label: `Damage ${weapon.name}`, type: "damage", applied: false, weapon: weapon, value: weapon.data.damage }) + } + } + } + addEquipments(rollData, statKey) { + let equipments = this.getEquipmentsOnly() + for (let equip of equipments) { + if (equip.data.equipped && equip.data.stataffected == statKey) { + rollData.equipmentsList.push({ label: `Item ${equip.name}`, type: "item", applied: false, equip: equip, value: equip.data.level }) + } + } + } + + /* -------------------------------------------- */ + getCommonRollData(statKey = undefined, useShield = false) { + let rollData = PegasusUtility.getBasicRollData() + rollData.alias = this.name + rollData.actorImg = this.img + rollData.actorId = this.id + rollData.img = this.img + rollData.activePerks = duplicate(this.getActivePerks()) + + if (statKey) { + rollData.statKey = statKey + rollData.stat = this.getStat(statKey) + rollData.statDicesLevel = rollData.stat.value + rollData.statMod = rollData.stat.mod + rollData.specList = this.getRelevantSpec(statKey) + rollData.selectedSpec = "0" + if (statKey.toLowerCase() == "mr") { + rollData.img = "systems/fvtt-pegasus-rpg/images/icons/MR.webp" + } else { + rollData.img = `systems/fvtt-pegasus-rpg/images/icons/${rollData.stat.abbrev}.webp` + } + } + + this.addEffects(rollData) + this.addArmorsShields(rollData, statKey, useShield) + this.addWeapons(rollData, statKey, useShield) + this.addEquipments(rollData, statKey) + + return rollData + } + + /* -------------------------------------------- */ + async startRoll(rollData) { + this.syncRoll(rollData); + //console.log("ROLL DATA", rollData) + let rollDialog = await PegasusRollDialog.create(this, rollData); + console.log(rollDialog); + rollDialog.render(true); + + } + + /* -------------------------------------------- */ + powerDmgRoll(itemId) { + let power = this.data.items.get(itemId) + if (power) { + power = duplicate(power) + this.rollPool(power.data.dmgstatistic) + } + } + + /* -------------------------------------------- */ + rollPool(statKey, useShield = false) { + let stat = this.getStat(statKey) + if (stat) { + let rollData = this.getCommonRollData(statKey, useShield) + rollData.mode = "stat" + rollData.title = `Roll : ${stat.label} ` + rollData.img = "icons/dice/d12black.svg" + + this.startRoll(rollData) + } else { + ui.notifications.warn("Statistic not found !"); + } + } + + /* -------------------------------------------- */ + rollUnarmedAttack() { + let stat = this.getStat('com') + if (stat) { + let rollData = this.getCommonRollData(statKey) + rollData.mode = "stat" + rollData.title = `Unarmed Attack`; + rollData.damages = this.getStat('str'); + + this.startRoll(rollData); + } else { + ui.notifications.warn("Statistic not found !"); + } + } + + /*-------------------------------------------- */ + rollStat(statKey) { + let stat = this.getStat(statKey); + if (stat) { + let rollData = this.getCommonRollData(statKey) + rollData.mode = "stat" + rollData.title = `Stat ${stat.label}`; + + this.startRoll(rollData) + } else { + ui.notifications.warn("Statistic not found !"); + } + } + + /* -------------------------------------------- */ + async rollSpec(specId) { + let spec = this.getOneSpec(specId) + if (spec) { + let rollData = this.getCommonRollData(spec.data.statistic) + rollData.mode = "spec" + rollData.title = `Spec. : ${spec.name} ` + rollData.specList = [spec] + rollData.selectedSpec = spec._id + rollData.specName = spec.name + rollData.img = spec.img + rollData.specDicesLevel = spec.data.level + this.startRoll(rollData) + } else { + ui.notifications.warn("Specialisation not found !"); + } + } + + /* -------------------------------------------- */ + async rollMR(isInit = false, combatId = 0, combatantId = 0) { + let mr = duplicate(this.data.data.mr) + if (mr) { + mr.dice = PegasusUtility.getDiceFromLevel(mr.value); + + let rollData = this.getCommonRollData("mr") + rollData.mode = "MR" + rollData.img = "systems/fvtt-pegasus-rpg/images/icons/MR.webp" + rollData.isInit = isInit + rollData.combatId = combatId + rollData.combatantId = combatantId + console.log("MR ROLL", rollData) + this.startRoll(rollData); + } else { + ui.notifications.warn("MR not found !"); + } + } + + /* -------------------------------------------- */ + async rollArmor(armorId) { + let armor = this.data.items.get(armorId) + + if (armor) { + let rollData = this.getCommonRollData(armor.data.statistic) + + armor = duplicate(armor); + this.checkAndPrepareEquipment(armor); + + rollData.mode = "armor" + rollData.armor = armor + rollData.title = `Armor : ${armor.name}` + rollData.isResistance = true; + rollData.img = armor.img + rollData.otherDicesLevel = armor.data.resistance + + this.startRoll(rollData); + } else { + ui.notifications.warn("Armor not found !", weaponId); + } + } + + /* -------------------------------------------- */ + async rollPower(powerId) { + let power = this.data.items.get(powerId) + + if (power) { + power = duplicate(power) + let rollData = this.getCommonRollData(power.data.statistic) + + rollData.mode = "power" + rollData.power = power + rollData.title = `Power : ${power.name}` + rollData.img = power.img + + this.startRoll(rollData); + } else { + ui.notifications.warn("Power not found !", powerId); + } + } +} diff --git a/modules/imperium5-combat.js b/modules/imperium5-combat.js new file mode 100644 index 0000000..352736f --- /dev/null +++ b/modules/imperium5-combat.js @@ -0,0 +1,38 @@ +import { Imperium5Utility } from "./imperium5pegasus-utility.js"; + +/* -------------------------------------------- */ +export class PegasusCombat extends Combat { + + /* -------------------------------------------- */ + async rollInitiative(ids, formula = undefined, messageOptions = {} ) { + ids = typeof ids === "string" ? [ids] : ids; + for (let cId = 0; cId < ids.length; cId++) { + const c = this.combatants.get(ids[cId]); + let id = c._id || c.id; + let initBonus = c.actor ? c.actor.getInitiativeScore( this.id, id ) : -1; + await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: initBonus } ]); + } + + return this; + } + + /* -------------------------------------------- */ + _onUpdate(changed, options, userId) { + } + + /* -------------------------------------------- */ + static async checkTurnPosition() { + while (game.combat.turn > 0) { + await game.combat.previousTurn() + } + } + + /* -------------------------------------------- */ + static async decInitBy10( combatantId, value) { + const combatant = game.combat.combatants.get(combatantId) + let initValue = combatant.initiative + value + await game.combat.setInitiative(combatantId, initValue) + setTimeout( this.checkTurnPosition, 400) // The setInitiative is no more blocking for unknown reason + } + +} diff --git a/modules/imperium5-commands.js b/modules/imperium5-commands.js new file mode 100644 index 0000000..f332e11 --- /dev/null +++ b/modules/imperium5-commands.js @@ -0,0 +1,123 @@ +/* -------------------------------------------- */ + +import { Imperium5Utility } from "./imperium5-utility.js"; +import { Imperium5RollDialog } from "./imperium5-roll-dialog.js"; + +/* -------------------------------------------- */ +export class PegasusCommands { + + static init() { + if (!game.system.pegasus.commands) { + const pegasusCommands = new PegasusCommands(); + pegasusCommands.registerCommand({ path: ["/char"], func: (content, msg, params) => pegasusCommands.createChar(msg), descr: "Create a new character" }); + pegasusCommands.registerCommand({ path: ["/pool"], func: (content, msg, params) => pegasusCommands.poolRoll(msg), descr: "Generic Roll Window" }); + game.system.pegasus.commands = pegasusCommands; + } + } + constructor() { + this.commandsTable = {}; + } + + /* -------------------------------------------- */ + registerCommand(command) { + this._addCommand(this.commandsTable, command.path, '', command); + } + + /* -------------------------------------------- */ + _addCommand(targetTable, path, fullPath, command) { + if (!this._validateCommand(targetTable, path, command)) { + return; + } + const term = path[0]; + fullPath = fullPath + term + ' ' + if (path.length == 1) { + command.descr = `${fullPath}: ${command.descr}`; + targetTable[term] = command; + } + else { + if (!targetTable[term]) { + targetTable[term] = { subTable: {} }; + } + this._addCommand(targetTable[term].subTable, path.slice(1), fullPath, command) + } + } + + /* -------------------------------------------- */ + _validateCommand(targetTable, path, command) { + if (path.length > 0 && path[0] && command.descr && (path.length != 1 || targetTable[path[0]] == undefined)) { + return true; + } + console.warn("pegasusCommands._validateCommand failed ", targetTable, path, command); + return false; + } + + + /* -------------------------------------------- */ + /* Manage chat commands */ + processChatCommand(commandLine, content = '', msg = {}) { + // Setup new message's visibility + let rollMode = game.settings.get("core", "rollMode"); + if (["gmroll", "blindroll"].includes(rollMode)) msg["whisper"] = ChatMessage.getWhisperRecipients("GM"); + if (rollMode === "blindroll") msg["blind"] = true; + msg["type"] = 0; + + let command = commandLine[0].toLowerCase(); + let params = commandLine.slice(1); + + return this.process(command, params, content, msg); + } + + /* -------------------------------------------- */ + process(command, params, content, msg) { + return this._processCommand(this.commandsTable, command, params, content, msg); + } + + /* -------------------------------------------- */ + _processCommand(commandsTable, name, params, content = '', msg = {}, path = "") { + console.log("===> Processing command") + let command = commandsTable[name]; + path = path + name + " "; + if (command && command.subTable) { + if (params[0]) { + return this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path) + } + else { + this.help(msg, command.subTable); + return true; + } + } + if (command && command.func) { + const result = command.func(content, msg, params); + if (result == false) { + RdDCommands._chatAnswer(msg, command.descr); + } + return true; + } + return false; + } + + /* -------------------------------------------- */ + async createChar(msg) { + game.system.pegasus.creator = new PegasusActorCreate(); + game.system.pegasus.creator.start(); + } + + /* -------------------------------------------- */ + static _chatAnswer(msg, content) { + msg.whisper = [game.user.id]; + msg.content = content; + ChatMessage.create(msg); + } + + /* -------------------------------------------- */ + async poolRoll( msg) { + let rollData = PegasusUtility.getBasicRollData() + rollData.alias = "Dice Pool Roll", + rollData.mode = "generic" + rollData.title = `Dice Pool Roll`; + + let rollDialog = await PegasusRollDialog.create( this, rollData); + rollDialog.render( true ); + } + +} \ No newline at end of file diff --git a/modules/imperium5-item-sheet.js b/modules/imperium5-item-sheet.js new file mode 100644 index 0000000..014076d --- /dev/null +++ b/modules/imperium5-item-sheet.js @@ -0,0 +1,509 @@ +import { Imperium5Utility } from "./imperium5-utility.js"; + +/** + * Extend the basic ItemSheet with some very simple modifications + * @extends {ItemSheet} + */ +export class PegasusItemSheet extends ItemSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-pegasus-rpg", "sheet", "item"], + template: "systems/fvtt-pegasus-rpg/templates/item-sheet.html", + dragDrop: [{ dragSelector: null, dropSelector: null }], + width: 620, + height: 550, + tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}] + }); + } + + /* -------------------------------------------- */ + _getHeaderButtons() { + let buttons = super._getHeaderButtons(); + // Add "Post to chat" button + // We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry! + buttons.unshift( + { + class: "post", + icon: "fas fa-comment", + onclick: ev => { } + }) + return buttons + } + + /* -------------------------------------------- */ + /** @override */ + setPosition(options = {}) { + const position = super.setPosition(options); + const sheetBody = this.element.find(".sheet-body"); + const bodyHeight = position.height - 192; + sheetBody.css("height", bodyHeight); + if (this.item.type.includes('weapon')) { + position.width = 640; + } + return position; + } + + /* -------------------------------------------- */ + async getData() { + const objectData = PegasusUtility.data(this.object); + + let itemData = foundry.utils.deepClone(PegasusUtility.templateData(this.object)); + let formData = { + title: this.title, + id: this.id, + type: objectData.type, + img: objectData.img, + name: objectData.name, + editable: this.isEditable, + cssClass: this.isEditable ? "editable" : "locked", + optionsDiceList: PegasusUtility.getOptionsDiceList(), + optionsStatusList: PegasusUtility.getOptionsStatusList(), + data: itemData, + limited: this.object.limited, + options: this.options, + owner: this.document.isOwner, + mr: (this.object.type == 'specialisation'), + isGM: game.user.isGM + } + + this.options.editable = !(this.object.data.origin == "embeddedItem"); + console.log("ITEM DATA", formData, this); + return formData; + } + + + /* -------------------------------------------- */ + _getHeaderButtons() { + let buttons = super._getHeaderButtons(); + buttons.unshift({ + class: "post", + icon: "fas fa-comment", + onclick: ev => this.postItem() + }); + return buttons + } + + /* -------------------------------------------- */ + postItem() { + let chatData = duplicate(PegasusUtility.data(this.item)); + if (this.actor) { + chatData.actor = { id: this.actor.id }; + } + // Don't post any image for the item (which would leave a large gap) if the default image is used + if (chatData.img.includes("/blank.png")) { + chatData.img = null; + } + // JSON object for easy creation + chatData.jsondata = JSON.stringify( + { + compendium: "postedItem", + payload: chatData, + }); + + renderTemplate('systems/fvtt-pegasus-rpg/templates/post-item.html', chatData).then(html => { + let chatOptions = PegasusUtility.chatDataSetup(html); + ChatMessage.create(chatOptions) + }); + } + + /* -------------------------------------------- */ + async viewSubitem(ev) { + let field = $(ev.currentTarget).data('type'); + let idx = Number($(ev.currentTarget).data('index')); + let itemData = this.object.data.data[field][idx]; + if (itemData.name != 'None') { + let spec = await Item.create(itemData, { temporary: true }); + spec.data.origin = "embeddedItem"; + new PegasusItemSheet(spec).render(true); + } + } + + /* -------------------------------------------- */ + async deleteSubitem(ev) { + let field = $(ev.currentTarget).data('type'); + let idx = Number($(ev.currentTarget).data('index')); + let oldArray = this.object.data.data[field]; + let itemData = this.object.data.data[field][idx]; + if (itemData.name != 'None') { + let newArray = []; + for (var i = 0; i < oldArray.length; i++) { + if (i != idx) { + newArray.push(oldArray[i]); + } + } + this.object.update({ [`data.${field}`]: newArray }); + } + } + + /* -------------------------------------------- */ + async manageSpec() { + let itemData = this.object.data.data.specialisation[0]; + if (itemData.name != 'None') { + let spec = await Item.create(itemData, { temporary: true }); + spec.data.origin = "embeddedItem"; + new PegasusItemSheet(spec).render(true); + } + } + + /* -------------------------------------------- */ + /** @override */ + activateListeners(html) { + super.activateListeners(html); + + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) return; + + + // Update Inventory Item + html.find('.item-edit').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + const item = this.object.options.actor.getOwnedItem(li.data("item-id")); + item.sheet.render(true); + }); + + html.find('.delete-spec').click(ev => { + this.object.update({ "data.specialisation": [{ name: 'None' }] }); + }); + + html.find('.delete-subitem').click(ev => { + this.deleteSubitem(ev); + }); + + html.find('.stat-choice-flag').click(ev => { + let idx = $(ev.currentTarget).data("stat-idx"); + let array = duplicate(this.object.data.data.statincreasechoice); + array[Number(idx)].flag = !array[Number(idx)].flag; + this.object.update({ "data.statincreasechoice": array }); + }); + + // Update Inventory Item + html.find('.item-delete').click(ev => { + const li = $(ev.currentTarget).parents(".item"); + let itemId = li.data("item-id"); + let itemType = li.data("item-type"); + }); + + html.find('.view-subitem').click(ev => { + this.viewSubitem(ev); + }); + + html.find('.view-spec').click(ev => { + this.manageSpec(); + }); + + } + + /* -------------------------------------------- */ + async addAbility(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + console.log("ABB", event, item, dataItem) + if (event.toElement.className == 'drop-abilities') { + let abilityArray = duplicate(this.object.data.data.abilities); + abilityArray.push(newItem); + await this.object.update({ 'data.abilities': abilityArray }); + } + if (event.toElement.className == 'drop-optionnal-abilities') { + let abilityArray = duplicate(this.object.data.data.optionnalabilities); + abilityArray.push(newItem); + await this.object.update({ 'data.optionnalabilities': abilityArray }); + } + } + + /* -------------------------------------------- */ + async addRacePerk(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + if (event.toElement.className == 'drop-race-perk') { + let perkArray = duplicate(this.object.data.data.perks); + perkArray.push(newItem); + await this.object.update({ 'data.perks': perkArray }); + } + } + + /* -------------------------------------------- */ + async addSpecialisation(item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + let specArray = [newItem]; + await this.object.update({ 'data.specialisation': specArray }); + } + + /* -------------------------------------------- */ + async addRoleSpecialisation(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + console.log("Add spec", event, newItem); + if (event.toElement.className == 'drop-spec1') { + let specArray = duplicate(this.object.data.data.specialisationsplus1); + specArray.push(newItem); + await this.object.update({ 'data.specialisationsplus1': specArray }); + } + if (event.toElement.className == 'drop-spec2') { + let specArray = duplicate(this.object.data.data.specincrease); + specArray.push(newItem); + await this.object.update({ 'data.specincrease': specArray }); + } + } + + /* -------------------------------------------- */ + async addRolePerk(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + console.log("Add spec", event, newItem); + if (event.toElement.className == 'drop-perk2') { + let perkArray = duplicate(this.object.data.data.perks); + perkArray.push(newItem); + await this.object.update({ 'data.perks': perkArray }); + } + if (event.toElement.className == 'drop-specialperk1') { + let perkArray = duplicate(this.object.data.data.specialperk); + perkArray.push(newItem); + await this.object.update({ 'data.specialperk': perkArray }); + } + } + + /* -------------------------------------------- */ + async addPower(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + if (event.toElement.className == 'drop-spec-power') { + let powArray = duplicate(this.object.data.data.powers); + powArray.push(newItem); + await this.object.update({ 'data.powers': powArray }); + } + } + + /* -------------------------------------------- */ + async addAbilityPower(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + if (event.toElement.className == 'drop-ability-power') { + let powArray = duplicate(this.object.data.data.powersgained); + powArray.push(newItem); + await this.object.update({ 'data.powersgained': powArray }); + } + } + /* -------------------------------------------- */ + async addAbilityEffect(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + if (event.toElement.className == 'drop-ability-effect') { + let powArray = duplicate(this.object.data.data.effectsgained); + powArray.push(newItem); + await this.object.update({ 'data.effectsgained': powArray }); + } + } + + /* -------------------------------------------- */ + async addAbilitySpec(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + if (event.toElement.className == 'drop-ability-spec') { + let powArray = duplicate(this.object.data.data.specialisations); + powArray.push(newItem); + await this.object.update({ 'data.specialisations': powArray }); + } + } + /* -------------------------------------------- */ + async addAbilityWeaponArmor(event, item, dataItem) { + let newItem = duplicate(item.data); + newItem._id = randomID(dataItem.id.length); + if (event.toElement.className == 'drop-ability-weapon') { + let weaponArray = duplicate(this.object.data.data.attackgained); + weaponArray.push(newItem); + await this.object.update({ 'data.attackgained': weaponArray }); + } + if (event.toElement.className == 'drop-ability-armor') { + let armorArray = duplicate(this.object.data.data.armorgained); + armorArray.push(newItem); + await this.object.update({ 'data.armorgained': armorArray }); + } + } + + /* -------------------------------------------- */ + async addPerkSpecialisation(event, item, dataItem) { + let newItem = duplicate(item.data); + if (event.toElement.className == 'drop-spec-perk') { + //console.log("PER SPEC", event) + let key = event.toElement.dataset["key"]; + if (key == 'affectedspec') { + await this.object.update({ 'data.features.affectedspec.value': newItem.name }); + } else { + await this.object.update({ 'data.features.gainspecdice.value': newItem.name }); + } + } + } + + /* -------------------------------------------- */ + async addPerkEffect(event, item, dataItem) { + let newItem = duplicate(item.data) + if (event.toElement.className == 'drop-perk-effect') { + let effectArray = duplicate(this.object.data.data.effectsgained) + effectArray.push(newItem) + await this.object.update({ 'data.effectsgained': effectArray }) + } + } + + /* -------------------------------------------- */ + async addEffectPower(event, item, dataItem) { + let newItem = duplicate(item.data) + if (event.toElement.className == 'drop-power-effect') { + let effectArray = duplicate(this.object.data.data.effectsgained) + effectArray.push(newItem); + await this.object.update({ 'data.effectsgained': effectArray }) + } + } + + /* -------------------------------------------- */ + async addEffectSpec(event, item, dataItem) { + let newItem = duplicate(item.data); + if (event.toElement.className == 'drop-effect-spec') { + let specArray = duplicate(this.object.data.data.recoveryrollspec); + specArray.push(newItem); + await this.object.update({ 'data.recoveryrollspec': specArray }); + } + if (event.toElement.className == 'drop-effect-specaffected') { + let specArray = duplicate(this.object.data.data.specaffected); + specArray.push(newItem); + await this.object.update({ 'data.specaffected': specArray }); + } + } + + /* -------------------------------------------- */ + async addEffectItem(event, item, dataItem) { + let newItem = duplicate(item.data); + if (event.toElement.className == 'drop-equipment-effect') { + let effectArray = duplicate(this.object.data.data.effects); + effectArray.push(newItem); + await this.object.update({ 'data.effects': effectArray }); + } + } + + /* -------------------------------------------- */ + async _onDrop(event) { + + if (this.object.type == 'weapon' || this.object.type == 'armor' || this.object.type == 'shield' + || this.object.type == 'equipment') { + let data = event.dataTransfer.getData('text/plain'); + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem); + if (item.data.type == 'effect') { + return this.addEffectItem(event, item, dataItem); + } + } + } + + if (this.object.type == 'power') { + let data = event.dataTransfer.getData('text/plain'); + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem); + if (item.data.type == 'effect') { + return this.addEffectPower(event, item, dataItem); + } + } + } + + if (this.object.type == 'effect') { + let data = event.dataTransfer.getData('text/plain'); + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem); + if (item.data.type == 'specialisation') { + return this.addEffectSpec(event, item, dataItem); + } + } + } + + if (this.object.type == 'race') { + let data = event.dataTransfer.getData('text/plain'); + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem); + if (item.data.type == 'ability') { + return this.addAbility(event, item, dataItem); + } + if (item.data.type == 'perk') { + return this.addRacePerk(event, item, dataItem); + } + } + } + + if (this.object.type == 'perk') { + let data = event.dataTransfer.getData('text/plain') + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem) + if (item.data.type == 'specialisation') { + return this.addPerkSpecialisation(event, item, dataItem) + } + if (item.data.type == 'effect') { + return this.addPerkEffect(event, item, dataItem); + } + } + } + + if (this.object.type == 'specialisation') { + let data = event.dataTransfer.getData('text/plain'); + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem); + if (item.data.type == 'power') { + return this.addPower(event, item, dataItem); + } + } + } + if (this.object.type == 'ability') { + let data = event.dataTransfer.getData('text/plain'); + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem); + if (item.data.type == 'effect') { + return this.addAbilityEffect(event, item, dataItem); + } + if (item.data.type == 'power') { + return this.addAbilityPower(event, item, dataItem); + } + if (item.data.type == 'specialisation') { + return this.addAbilitySpec(event, item, dataItem); + } + if (item.data.type == 'weapon' || item.data.type == 'armor') { + return this.addAbilityWeaponArmor(event, item, dataItem); + } + } + } + + if (this.object.type == 'role') { + let data = event.dataTransfer.getData('text/plain'); + if (data) { + let dataItem = JSON.parse(data); + let item = await PegasusUtility.searchItem(dataItem); + if (item.data.type == 'specialisation') { + return this.addRoleSpecialisation(event, item, dataItem); + } + if (item.data.type == 'perk') { + return this.addRolePerk(event, item, dataItem); + } + } + } + + ui.notifications.warn("This item can not be dropped over another item"); + } + + /* -------------------------------------------- */ + get template() { + let type = this.item.type; + return `systems/fvtt-pegasus-rpg/templates/item-${type}-sheet.html`; + } + + /* -------------------------------------------- */ + /** @override */ + _updateObject(event, formData) { + return this.object.update(formData); + } +} diff --git a/modules/imperium5-item.js b/modules/imperium5-item.js new file mode 100644 index 0000000..ffdcd82 --- /dev/null +++ b/modules/imperium5-item.js @@ -0,0 +1,32 @@ + +export const defaultItemImg = { + specialisation: "systems/fvtt-pegasus-rpg/images/icons/icon_spec.webp", + perk: "systems/fvtt-pegasus-rpg/images/icons/icon_perk.webp", + ability: "systems/fvtt-pegasus-rpg/images/icons/icon_raceability.webp", + armor: "systems/fvtt-pegasus-rpg/images/icons/icon_armour.webp", + weapon: "systems/fvtt-pegasus-rpg/images/icons/icon_weapon.webp", + equipment: "systems/fvtt-pegasus-rpg/images/icons/icon_equipment.webp", + effect: "systems/fvtt-pegasus-rpg/images/icons/icon_effect.webp", + race: "systems/fvtt-pegasus-rpg/images/icons/icon_race.webp", + power: "systems/fvtt-pegasus-rpg/images/icons/icon_power.webp", + armour: "systems/fvtt-pegasus-rpg/images/icons/icon_armour.webp", + equipment: "systems/fvtt-pegasus-rpg/images/icons/icon_equipment.webp", + weapon: "systems/fvtt-pegasus-rpg/images/icons/icon_meleeweapon.webp", + shield: "systems/fvtt-pegasus-rpg/images/icons/icon_shield.webp", + money: "systems/fvtt-pegasus-rpg/images/icons/icon_money.webp", +} + +/** + * Extend the basic ItemSheet with some very simple modifications + * @extends {ItemSheet} + */ +export class PegasusItem extends Item { + + constructor(data, context) { + if (!data.img) { + data.img = defaultItemImg[data.type]; + } + super(data, context); + } + +} diff --git a/modules/imperium5-main.js b/modules/imperium5-main.js new file mode 100644 index 0000000..e76dac8 --- /dev/null +++ b/modules/imperium5-main.js @@ -0,0 +1,128 @@ +/** + * imperium5 system + * Author: Uberwald + * Software License: Prop + */ + +/* -------------------------------------------- */ + +/* -------------------------------------------- */ +// Import Modules +import { Imperium5Actor } from "./imperium5-actor.js"; +import { Imperium5ItemSheet } from "./imperium5-item-sheet.js"; +import { Imperium5ActorSheet } from "./imperium5-actor-sheet.js"; +import { Imperium5Utility } from "./imperium5-utility.js"; +import { Imperium5Combat } from "./imperium5-combat.js"; +import { Imperium5Item } from "./imperium5-item.js"; + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ + +/************************************************************************************/ +Hooks.once("init", async function () { + console.log(`Initializing Pegasus RPG`); + + /* -------------------------------------------- */ + // preload handlebars templates + PegasusUtility.preloadHandlebarsTemplates(); + + /* -------------------------------------------- */ + game.settings.register("fvtt-pegasus-rpg", "dice-max-level", { + name: "Maximum level value for dices lists", + hint: "Se the maximum level value for dices lists", + scope: "world", + config: true, + default: 20, + type: Number + }); + + /* -------------------------------------------- */ + // Set an initiative formula for the system + CONFIG.Combat.initiative = { + formula: "1d6", + decimals: 1 + }; + + /* -------------------------------------------- */ + game.socket.on("system.fvtt-pegasus-rpg", data => { + PegasusUtility.onSocketMesssage(data) + }); + + /* -------------------------------------------- */ + // Define custom Entity classes + CONFIG.Combat.documentClass = PegasusCombat + CONFIG.Actor.documentClass = PegasusActor + CONFIG.Item.documentClass = PegasusItem + //CONFIG.Token.objectClass = PegasusToken + game.system.pegasus = { }; + + /* -------------------------------------------- */ + // Register sheet application classes + Actors.unregisterSheet("core", ActorSheet); + Actors.registerSheet("fvtt-pegasus", PegasusActorSheet, { types: ["character"], makeDefault: true }); + Actors.registerSheet("fvtt-pegasus", PegasusNPCSheet, { types: ["npc"], makeDefault: false }); + + Items.unregisterSheet("core", ItemSheet); + Items.registerSheet("fvtt-pegasus", PegasusItemSheet, { makeDefault: true }); + + PegasusUtility.init(); + +}); + +/* -------------------------------------------- */ +function welcomeMessage() { + ChatMessage.create({ + user: game.user.id, + whisper: [game.user.id], + content: `
+ Welcome to the Pegasus Engine CORE RPG. +
Created by GMD Online +

The Pegasus Engine is a available for free on our website. It is also available as a PDF and in Print format at an affordable price.

+

This project has been made possible thanks to all the Official GMD Members and Patreon Members that have supported me and as a result made it possible to supply this interface for free.

+

In return I have made available a fully detailed Compendium for FREE for all members, which can be obtained from the Members page on my website.

+

You too can become a supporter for future projects and enjoy amazing rewards. +
Sign up Here : https://www.gmdonline.co.uk/gmdmemberspage/

+

GMD Online, GMD CORE RPG logo are © 2018 CORE Worlds and Game Rules © 2001. Interface © 2021 All rights reserved.

+

Enjoy and become the hero you were born to be!

+ ` }); +} + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ +Hooks.once("ready", function () { + + PegasusUtility.ready(); + // User warning + if (!game.user.isGM && game.user.character == undefined) { + ui.notifications.info("Warning ! No character linked to your user !"); + ChatMessage.create({ + content: "WARNING The player " + game.user.name + " is not linked to a character !", + user: game.user._id + }); + } + + // CSS patch for v9 + if (game.version) { + let sidebar = document.getElementById("sidebar"); + sidebar.style.width = "min-content"; + } + + welcomeMessage(); +}); + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ +Hooks.on("chatMessage", (html, content, msg) => { + if (content[0] == '/') { + let regExp = /(\S+)/g; + let commands = content.match(regExp); + if (game.system.pegasus.commands.processChatCommand(commands, content, msg)) { + return false; + } + } + return true; +}); + diff --git a/modules/imperium5-roll-dialog.js b/modules/imperium5-roll-dialog.js new file mode 100644 index 0000000..a963c69 --- /dev/null +++ b/modules/imperium5-roll-dialog.js @@ -0,0 +1,239 @@ +import { Imperium5Utility } from "./imperium5-utility.js"; + +export class PegasusRollDialog extends Dialog { + + /* -------------------------------------------- */ + static async create(actor, rollData ) { + + let options = { classes: ["PegasusDialog"], width: 620, height: 380, 'z-index': 99999 }; + let html = await renderTemplate('systems/fvtt-pegasus-rpg/templates/roll-dialog-generic.html', rollData); + + return new PegasusRollDialog(actor, rollData, html, options ); + } + + /* -------------------------------------------- */ + constructor(actor, rollData, html, options, close = undefined) { + let conf = { + title: (rollData.mode == "skill") ? "Skill" : "Roll", + content: html, + buttons: { + roll: { + icon: '', + label: "Roll !", + callback: () => { this.roll() } + }, + cancel: { + icon: '', + label: "Cancel", + callback: () => { this.close() } + } }, + close: close + } + + super(conf, options); + + this.actor = actor; + this.rollData = rollData; + } + + /* -------------------------------------------- */ + roll () { + PegasusUtility.rollPegasus( this.rollData ) + } + + + /* -------------------------------------------- */ + manageEffects( effectIdx, toggled) { + let effect = this.rollData.effectsList[effectIdx] + if (effect) { + effect.applied = toggled + + let level, genre, idVal + if (effect.type == 'hindrance' ) { + level = effect.value + genre = 'positive' + idVal = "#hindranceDicesLevel" + } + if (effect.type == 'effect' ) { + let effectData = effect.effect + level = effectData.data.effectlevel + genre = effectData.data.genre + effectData.data.isUsed = toggled + if (effectData.data.bonusdice) { + idVal = "#bonusDicesLevel" + } + if (effectData.data.reducedicevalue || effectData.data.statdice) { + idVal = "#statDicesLevel" + } + if (effectData.data.otherdice) { + idVal = "#otherDicesLevel" + } + if (effectData.data.hindrance) { + idVal = "#hindranceDicesLevel" + genre = 'positive' // Dynamic fix + } + } + // Now process the dice level update + let newLevel = Number($(idVal).val()) + console.log("Ongoing", newLevel, toggled, idVal ) + if (toggled) { + if ( genre == 'positive') { + newLevel += Number(level) + }else { + newLevel -= Number(level) + } + }else { + if ( genre == 'positive') { + newLevel -= Number(level) + }else { + newLevel += Number(level) + } + } + newLevel = (newLevel<0) ? 0 : newLevel + $(idVal).val(newLevel) + } + //console.log("Effect", effect, toggled) + this.rollData.statDicesLevel = Number($('#statDicesLevel').val()) + this.rollData.specDicesLevel = Number($('#specDicesLevel').val()) + this.rollData.bonusDicesLevel = Number($('#bonusDicesLevel').val()) + this.rollData.hindranceDicesLevel = Number($('#hindranceDicesLevel').val()) + this.rollData.otherDicesLevel = Number($('#otherDicesLevel').val()) + } + + /* -------------------------------------------- */ + manageArmors( armorIdx, toggled) { + let armor = this.rollData.armorsList[armorIdx] + if (armor) { + armor.applied = toggled + if (armor.type == 'other' ) { + if (toggled) { + this.rollData.otherDicesLevel += Number(armor.value) + } else { + this.rollData.otherDicesLevel -= Number(armor.value) + this.rollData.otherDicesLevel = (this.rollData.otherDicesLevel<0) ? 0 : this.rollData.otherDicesLevel + } + $("#otherDicesLevel").val(this.rollData.otherDicesLevel) + } + } + console.log("Armor", armorIdx, toggled) + } + + /* -------------------------------------------- */ + manageWeapons( weaponIdx, toggled) { + let weapon = this.rollData.weaponsList[weaponIdx] + if (weapon) { + if (toggled) { + this.rollData.weaponName = weapon.weapon.name + } else { + this.rollData.weaponName = undefined + } + weapon.applied = toggled + if (weapon.type == 'damage' || weapon.type == 'enhanced' ) { + if (toggled) { + this.rollData.otherDicesLevel += Number(weapon.value) + } else { + this.rollData.weaponName = undefined + this.rollData.otherDicesLevel -= Number(weapon.value) + this.rollData.otherDicesLevel = (this.rollData.otherDicesLevel<0) ? 0 : this.rollData.otherDicesLevel + } + $("#otherDicesLevel").val(this.rollData.otherDicesLevel) + } + } + console.log("Weapon", weaponIdx, toggled, this.rollData.otherDicesLevel, weapon) + } + + /* -------------------------------------------- */ + manageEquip( equipIdx, toggled) { + let equip = this.rollData.equipmentsList[equipIdx] + if (equip) { + equip.applied = toggled + let idVal = "#otherDicesLevel" // Default + if (equip.equip.data.bonusdice) { + idVal = "#bonusDicesLevel" + } + if (equip.equip.data.statdice) { + idVal = "#statDicesLevel" + } + if (equip.equip.data.otherdice) { + idVal = "#otherDicesLevel" + } + let newLevel = Number($(idVal).val()) + if (toggled) { + newLevel += Number(equip.value) + } else { + newLevel -= Number(equip.value) + } + newLevel = (newLevel <0) ? 0 : newLevel + $(idVal).val(newLevel) + // Then refresh + this.rollData.statDicesLevel = Number($('#statDicesLevel').val()) + this.rollData.specDicesLevel = Number($('#specDicesLevel').val()) + this.rollData.bonusDicesLevel = Number($('#bonusDicesLevel').val()) + this.rollData.hindranceDicesLevel = Number($('#hindranceDicesLevel').val()) + this.rollData.otherDicesLevel = Number($('#otherDicesLevel').val()) + } + } + + /* -------------------------------------------- */ + activateListeners(html) { + super.activateListeners(html); + + var dialog = this; + function onLoad() { + } + $(function () { onLoad(); }); + + html.find('#specList').change(async (event) => { + this.rollData.selectedSpec = event.currentTarget.value + let spec = this.rollData.specList.find(item => item._id == this.rollData.selectedSpec) + if ( spec) { + this.rollData.specDiceLevel = spec.data.level + this.rollData.specName = spec.name + $('#specDicesLevel').val(this.rollData.specDiceLevel) + } else { + this.rollData.specName = undefined + $('#specDicesLevel').val(0) + } + const content = await renderTemplate("systems/fvtt-pegasus-rpg/templates/roll-dialog-generic.html", this.rollData) + this.data.content = content + this.render(true) + }); + html.find('#statDicesLevel').change((event) => { + this.rollData.statDicesLevel = Number(event.currentTarget.value) + }); + html.find('#specDicesLevel').change((event) => { + this.rollData.specDicesLevel = Number(event.currentTarget.value) + }); + html.find('#bonusDicesLevel').change((event) => { + this.rollData.bonusDicesLevel = Number(event.currentTarget.value) + }); + html.find('#hindranceDicesLevel').change((event) => { + this.rollData.hindranceDicesLevel = Number(event.currentTarget.value) + }); + html.find('#otherDicesLevel').change((event) => { + this.rollData.otherDicesLevel = Number(event.currentTarget.value) + }); + html.find('.effect-clicked').change((event) => { + let toggled = event.currentTarget.checked + let effectIdx = $(event.currentTarget).data("effect-idx") + this.manageEffects( effectIdx, toggled) + }); + html.find('.armor-clicked').change((event) => { + let toggled = event.currentTarget.checked + let armorIdx = $(event.currentTarget).data("armor-idx") + this.manageArmors( armorIdx, toggled) + }); + html.find('.weapon-clicked').change((event) => { + let toggled = event.currentTarget.checked + let weaponIdx = $(event.currentTarget).data("weapon-idx") + this.manageWeapons( weaponIdx, toggled) + }); + html.find('.equip-clicked').change((event) => { + let toggled = event.currentTarget.checked + let equipIdx = $(event.currentTarget).data("equip-idx") + this.manageEquip( equipIdx, toggled) + }); + + + } +} \ No newline at end of file diff --git a/modules/imperium5-utility.js b/modules/imperium5-utility.js new file mode 100644 index 0000000..c7e49aa --- /dev/null +++ b/modules/imperium5-utility.js @@ -0,0 +1,741 @@ +/* -------------------------------------------- */ +import { Imperium5Combat } from "./imperium5-combat.js"; +import { Imperium5Commands } from "./imperium5-commands.js"; + +/* -------------------------------------------- */ +const __level2Dice = ["d0", "d4", "d6", "d8", "d10", "d12"]; +const __name2DiceValue = { "0": 0, "d0": 0, "d4": 4, "d6": 6, "d8": 8, "d10": 10, "d12": 12 } + +/* -------------------------------------------- */ +export class Imperium5Utility { + + + /* -------------------------------------------- */ + static async init() { + Hooks.on('renderChatLog', (log, html, data) => PegasusUtility.chatListeners(html)); + Hooks.on("getCombatTrackerEntryContext", (html, options) => { + PegasusUtility.pushInitiativeOptions(html, options); + }); + Hooks.on("dropCanvasData", (canvas, data) => { + PegasusUtility.dropItemOnToken(canvas, data) + }); + + this.rollDataStore = {} + this.defenderStore = {} + this.diceList = []; + this.diceFoundryList = []; + this.optionsDiceList = ""; + this.buildDiceLists(); + PegasusCommands.init(); + + Handlebars.registerHelper('count', function (list) { + return list.length; + }) + Handlebars.registerHelper('includes', function (array, val) { + return array.includes(val); + }) + Handlebars.registerHelper('upper', function (text) { + return text.toUpperCase(); + }) + Handlebars.registerHelper('lower', function (text) { + return text.toLowerCase() + }) + Handlebars.registerHelper('upperFirst', function (text) { + if (typeof text !== 'string') return text + return text.charAt(0).toUpperCase() + text.slice(1) + }) + Handlebars.registerHelper('notEmpty', function (list) { + return list.length > 0; + }) + Handlebars.registerHelper('mul', function (a, b) { + return parseInt(a) * parseInt(b); + }) + } + + /* -------------------------------------------- */ + static pushInitiativeOptions(html, options) { + console.log('Option pushed....') + options.push({ name: "Apply -10", condition: true, icon: '', callback: target => { PegasusCombat.decInitBy10(target.data('combatant-id'), -10); } }) + } + + /* -------------------------------------------- */ + static getSpecs() { + return this.specs; + } + + /* -------------------------------------------- */ + static async ready() { + const specs = await PegasusUtility.loadCompendium("fvtt-pegasus-rpg.specialisations"); + this.specs = specs.map(i => i.toObject()); + } + + /* -------------------------------------------- */ + static async addItemDropToActor(actor, item) { + actor.preprocessItem("none", item, false) + let chatData = { + user: game.user.id, + rollMode: game.settings.get("core", "rollMode"), + whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')), + content: `
The item ${item.name} has been dropped on the actor ${actor.name}= token.x && x <= (token.x + token.width) + && y >= token.y && y <= (token.y + token.height)) { + let item = await this.searchItem(data) + if (game.user.isGM || token.actor.isOwner) { + this.addItemDropToActor(token.actor, item) + } else { + game.socket.emit("system.fvtt-pegasus-rpg", { name: "msg_gm_item_drop", data: { actorId: token.actor.id, itemId: item.id, isPack: item.pack } }) + } + return + } + } + } + + /* -------------------------------------------- */ + static async loadCompendiumData(compendium) { + const pack = game.packs.get(compendium); + return await pack?.getDocuments() ?? []; + } + + /* -------------------------------------------- */ + static async loadCompendium(compendium, filter = item => true) { + let compendiumData = await PegasusUtility.loadCompendiumData(compendium); + return compendiumData.filter(filter); + } + + /* -------------------------------------------- */ + static buildDiceLists() { + let maxLevel = game.settings.get("fvtt-pegasus-rpg", "dice-max-level"); + let diceList = ["0"]; + let diceValues = [0]; + let diceFoundryList = ["d0"]; + let diceLevel = 1; + let concat = ""; + let concatFoundry = ""; + let optionsDiceList = ''; + let optionsLevel = ''; + for (let i = 1; i <= maxLevel; i++) { + let currentDices = concat + __level2Dice[diceLevel]; + diceList.push(currentDices); + diceFoundryList.push(concatFoundry + __level2Dice[diceLevel] + "x"); + if (__level2Dice[diceLevel] == "d12") { + concat = concat + "d12 "; + concatFoundry = concatFoundry + "d12x, "; + diceLevel = 1; + } else { + diceLevel++; + } + optionsDiceList += ``; + optionsLevel += ``; + } + this.diceList = diceList; + this.diceFoundryList = diceFoundryList; + this.optionsDiceList = optionsDiceList; + this.optionsLevel = optionsLevel; + + this.optionsStatusList = ''; + + } + + /* -------------------------------------------- */ + static getOptionsStatusList() { + return this.optionsStatusList; + } + /* -------------------------------------------- */ + static getOptionsDiceList() { + return this.optionsDiceList; + } + /* -------------------------------------------- */ + static getOptionsLevel() { + return this.optionsLevel; + } + + /* -------------------------------------------- */ + static computeAttackDefense(defenseRollId) { + let defenseRollData = this.getRollData(defenseRollId); + let attackRollData = this.getRollData(defenseRollData.linkedRollId); + let defender = game.actors.get(defenseRollData.actorId); + defender.processDefenseResult(defenseRollData, attackRollData); + } + + /* -------------------------------------------- */ + static applyDamage(defenseRollId) { + let defenseRollData = this.getRollData(defenseRollId); + let defender = game.actors.get(defenseRollData.actorId); + defender.applyDamageLoss(defenseRollData.finalDamage); + } + + /* -------------------------------------------- */ + static applyNoDefense(actorId, attackRollId) { + let attackRollData = this.getRollData(attackRollId); + let defender = game.actors.get(actorId); + defender.processNoDefense(attackRollData); + } + + /* -------------------------------------------- */ + static async chatListeners(html) { + + html.on("click", '.chat-create-actor', event => { + game.system.pegasus.creator.processChatEvent(event); + }); + html.on("click", '.view-item-from-chat', event => { + game.system.pegasus.creator.openItemView(event) + }); + } + + /* -------------------------------------------- */ + static async preloadHandlebarsTemplates() { + + const templatePaths = [ + 'systems/fvtt-pegasus-rpg/templates/editor-notes-gm.html', + 'systems/fvtt-pegasus-rpg/templates/partial-roll-select-effects.html', + 'systems/fvtt-pegasus-rpg/templates/partial-options-statistics.html', + 'systems/fvtt-pegasus-rpg/templates/partial-options-level.html', + 'systems/fvtt-pegasus-rpg/templates/partial-options-range.html', + 'systems/fvtt-pegasus-rpg/templates/partial-options-equipment-types.html', + 'systems/fvtt-pegasus-rpg/templates/partial-equipment-effects.html', + 'systems/fvtt-pegasus-rpg/templates/partial-actor-stat-block.html', + 'systems/fvtt-pegasus-rpg/templates/partial-actor-status.html', + 'systems/fvtt-pegasus-rpg/templates/partial-item-nav.html', + 'systems/fvtt-pegasus-rpg/templates/partial-item-description.html', + 'systems/fvtt-pegasus-rpg/templates/partial-actor-equipment.html' + ] + return loadTemplates(templatePaths); + } + + /* -------------------------------------------- */ + static async getEffectFromCompendium(effectName) { + effectName = effectName.toLowerCase() + let effect = game.items.contents.find(item => item.type == 'effect' && item.name.toLowerCase() == effectName) + if (!effect) { + let effects = await this.loadCompendium('fvtt-pegasus.effect', item => item.name.toLowerCase() == effectName) + let objs = effects.map(i => i.toObject()) + effect = objs[0] + } else { + effect = duplicate(effect); + } + + console.log("Effect", effect) + return effect + } + + /* -------------------------------------------- */ + static removeChatMessageId(messageId) { + if (messageId) { + game.messages.get(messageId)?.delete(); + } + } + + static findChatMessageId(current) { + return PegasusUtility.getChatMessageId(PegasusUtility.findChatMessage(current)); + } + + static getChatMessageId(node) { + return node?.attributes.getNamedItem('data-message-id')?.value; + } + + static findChatMessage(current) { + return PegasusUtility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id')); + } + + static findNodeMatching(current, predicate) { + if (current) { + if (predicate(current)) { + return current; + } + return PegasusUtility.findNodeMatching(current.parentElement, predicate); + } + return undefined; + } + + /* -------------------------------------------- */ + static templateData(it) { + return PegasusUtility.data(it)?.data ?? {} + } + + /* -------------------------------------------- */ + static data(it) { + if (it instanceof Actor || it instanceof Item || it instanceof Combatant) { + return it.data; + } + return it; + } + + /* -------------------------------------------- */ + static getDiceValue(level = 0) { + let diceString = this.diceList[level] + let diceTab = diceString.split(" ") + let diceValue = 0 + for (let dice of diceTab) { + diceValue += __name2DiceValue[dice] + } + return diceValue + } + + /* -------------------------------------------- */ + static getDiceFromLevel(level = 0) { + level = Number(level) + return this.diceList[level]; + } + /* -------------------------------------------- */ + static getFoundryDiceFromLevel(level = 0) { + level = Number(level) + //console.log(this.diceFoundryList); + return this.diceFoundryList[level]; + } + + /* -------------------------------------------- */ + static createDirectOptionList(min, max) { + let options = {}; + for (let i = min; i <= max; i++) { + options[`${i}`] = `${i}`; + } + return options; + } + + /* -------------------------------------------- */ + static buildListOptions(min, max) { + let options = "" + for (let i = min; i <= max; i++) { + options += `` + } + return options; + } + + /* -------------------------------------------- */ + static getTarget() { + if (game.user.targets && game.user.targets.size == 1) { + for (let target of game.user.targets) { + return target; + } + } + return undefined; + } + + /* -------------------------------------------- */ + static getDefenseState(actorId) { + return this.defenderStore[actorId]; + } + + /* -------------------------------------------- */ + static async updateDefenseState(defenderId, rollId) { + this.defenderStore[defenderId] = rollId; + if (game.user.character && game.user.character.id == defenderId) { + let defender = game.actors.get(defenderId); + let chatData = { + user: game.user.id, + alias: defender.name, + rollMode: game.settings.get("core", "rollMode"), + whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')), + content: `
${defender.name} is under attack. He must roll a skill/weapon/technique to defend himself or suffer damages (button below). + u.id); + if (chatData.rollMode === "blindroll") chatData["blind"] = true; + else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user]; + + if (forceWhisper) { // Final force ! + chatData["speaker"] = ChatMessage.getSpeaker(); + chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper); + } + + return chatData; + } + + /* -------------------------------------------- */ + static async loadCompendiumData(compendium) { + const pack = game.packs.get(compendium); + return await pack?.getDocuments() ?? []; + } + + /* -------------------------------------------- */ + static async loadCompendium(compendium, filter = item => true) { + let compendiumData = await this.loadCompendiumData(compendium); + //console.log("Compendium", compendiumData); + return compendiumData.filter(filter); + } + + /* -------------------------------------------- */ + static async showDiceSoNice(roll, rollMode) { + if (game.modules.get("dice-so-nice")?.active) { + if (game.dice3d) { + let whisper = null; + let blind = false; + rollMode = rollMode ?? game.settings.get("core", "rollMode"); + switch (rollMode) { + case "blindroll": //GM only + blind = true; + case "gmroll": //GM + rolling player + whisper = this.getUsers(user => user.isGM); + break; + case "roll": //everybody + whisper = this.getUsers(user => user.active); + break; + case "selfroll": + whisper = [game.user.id]; + break; + } + await game.dice3d.showForRoll(roll, game.user, true, whisper, blind); + } + } + } + + /* -------------------------------------------- */ + static removeUsedPerkEffects(rollData) { + // De-actived used effects from perks + let toRem = [] + for (let effect of rollData.effectsList) { + if (effect.effect.data.perkId && effect.effect.data.isUsed) { + toRem.push(effect.effect._id) + } + } + if (toRem.length > 0) { + let actor = game.actors.get(rollData.actorId) + actor.deleteEmbeddedDocuments('Item', toRem) + } + } + + /* -------------------------------------------- */ + static async rollPegasus(rollData) { + + let dicePool = [{ name: "stat", level: 0, statmod: 0 }, { name: "spec", level: 0 }, { name: "bonus", level: 0 }, { name: "hindrance", level: 0 }, { name: "other", level: 0 }]; + if (rollData.stat) { + dicePool[0].level += Number(rollData.stat.value); + dicePool[0].statmod = Number(rollData.stat.mod); + } + if (rollData.statDicesLevel) { + dicePool[0].level = rollData.statDicesLevel; + } + if (rollData.selectedSpec && rollData.selectedSpec != "0") { + rollData.spec = rollData.specList.find(item => item._id == rollData.selectedSpec); + rollData.spec.data.dice = PegasusUtility.getDiceFromLevel(rollData.spec.data.level); + } + if (rollData.spec) { + dicePool[1].level += Number(rollData.spec.data.level); + } + if (rollData.specDicesLevel) { + dicePool[1].level = rollData.specDicesLevel; + } + if (rollData.bonusDicesLevel) { + dicePool[2].level += Number(rollData.bonusDicesLevel); + } + if (rollData.hindranceDicesLevel) { + dicePool[3].level += Number(rollData.hindranceDicesLevel); + } + if (rollData.otherDicesLevel) { + dicePool[4].level += Number(rollData.otherDicesLevel); + } + + let diceFormulaTab = []; + for (let diceGroup of dicePool) { + diceFormulaTab.push(this.getFoundryDiceFromLevel(diceGroup.level)) + } + let diceFormula = '{' + diceFormulaTab.join(', ') + '}kh'; + + // Performs roll + let myRoll = rollData.roll; + if (!myRoll) { // New rolls only of no rerolls + myRoll = new Roll(diceFormula).roll({ async: false }); + console.log("ROLL : ", diceFormula) + await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")); + rollData.roll = myRoll + } + + // Final score and keep data + rollData.finalScore = myRoll.total + dicePool[0].statmod; + + if (rollData.damages) { + let dmgFormula = this.getFoundryDiceFromLevel(rollData.damages.value) + let dmgRoll = new Roll(dmgFormula).roll({ async: false }); + await this.showDiceSoNice(dmgRoll, game.settings.get("core", "rollMode")); + rollData.dmgResult = dmgRoll.total; + } + + this.createChatWithRollMode(rollData.alias, { + content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-generic-result.html`, rollData) + }); + + // Init stuf + if (rollData.isInit) { + let combat = game.combats.get(rollData.combatId) + combat.updateEmbeddedDocuments("Combatant", [{ _id: rollData.combatantId, initiative: rollData.finalScore }]); + } + + //this.removeUsedPerkEffects( rollData) // Unused for now + + // And save the roll + this.saveRollData(rollData); + } + + /* -------------------------------------------- */ + static getDamageDice(result) { + if (result < 0) return 0; + return Math.floor(result / 5) + 1; + } + + /* ------------------------- ------------------- */ + static async updateRoll(rollData) { + + let diceResults = rollData.diceResults; + let sortedRoll = []; + for (let i = 0; i < 10; i++) { + sortedRoll[i] = 0; + } + for (let dice of diceResults) { + sortedRoll[dice.result]++; + } + let index = 0; + let bestRoll = 0; + for (let i = 0; i < 10; i++) { + if (sortedRoll[i] > bestRoll) { + bestRoll = sortedRoll[i]; + index = i; + } + } + let bestScore = (bestRoll * 10) + index + rollData.bestScore = bestScore + rollData.finalScore = bestScore + rollData.negativeModifier + rollData.positiveModifier + + this.saveRollData(rollData) + + this.createChatWithRollMode(rollData.alias, { + content: await renderTemplate(`systems/fvtt-weapons-of-the-gods/templates/chat-generic-result.html`, rollData) + }); + } + + /* ------------------------- ------------------- */ + static async rerollDice(actorId, diceIndex = -1) { + let actor = game.actors.get(actorId); + let rollData = actor.getRollData(); + + if (diceIndex == -1) { + rollData.hasWillpower = actor.decrementWillpower(); + rollData.roll = undefined; + } else { + let myRoll = new Roll("1d6").roll({ async: false }); + await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode")); + console.log("Result: ", myRoll); + + rollData.roll.dice[0].results[diceIndex].result = myRoll.total; // Patch + rollData.nbStrongHitUsed++; + } + this.rollFraggedKingdom(rollData); + } + + /* -------------------------------------------- */ + static getUsers(filter) { + return game.users.filter(filter).map(user => user.data._id); + } + /* -------------------------------------------- */ + static getWhisperRecipients(rollMode, name) { + switch (rollMode) { + case "blindroll": return this.getUsers(user => user.isGM); + case "gmroll": return this.getWhisperRecipientsAndGMs(name); + case "selfroll": return [game.user.id]; + } + return undefined; + } + /* -------------------------------------------- */ + static getWhisperRecipientsAndGMs(name) { + let recep1 = ChatMessage.getWhisperRecipients(name) || []; + return recep1.concat(ChatMessage.getWhisperRecipients('GM')); + } + + /* -------------------------------------------- */ + static blindMessageToGM(chatOptions) { + let chatGM = duplicate(chatOptions); + chatGM.whisper = this.getUsers(user => user.isGM); + chatGM.content = "Blinde message of " + game.user.name + "
" + chatOptions.content; + console.log("blindMessageToGM", chatGM); + game.socket.emit("system.fvtt-pegasus-rgp", { msg: "msg_gm_chat_message", data: chatGM }); + } + + + /* -------------------------------------------- */ + static async searchItem(dataItem) { + let item + if (dataItem.pack) { + item = await fromUuid("Compendium." + dataItem.pack + "." + dataItem.id) + } else { + item = game.items.get(dataItem.id) + } + return item + } + + /* -------------------------------------------- */ + static split3Columns(data) { + + let array = [[], [], []]; + if (data == undefined) return array; + + let col = 0; + for (let key in data) { + let keyword = data[key]; + keyword.key = key; // Self-reference + array[col].push(keyword); + col++; + if (col == 3) col = 0; + } + return array; + } + + /* -------------------------------------------- */ + static createChatMessage(name, rollMode, chatOptions) { + switch (rollMode) { + case "blindroll": // GM only + if (!game.user.isGM) { + this.blindMessageToGM(chatOptions); + + chatOptions.whisper = [game.user.id]; + chatOptions.content = "Message only to the GM"; + } + else { + chatOptions.whisper = this.getUsers(user => user.isGM); + } + break; + default: + chatOptions.whisper = this.getWhisperRecipients(rollMode, name); + break; + } + chatOptions.alias = chatOptions.alias || name; + ChatMessage.create(chatOptions); + } + + /* -------------------------------------------- */ + static getBasicRollData() { + let rollData = { + rollId: randomID(16), + rollMode: game.settings.get("core", "rollMode"), + bonusDicesLevel: 0, + hindranceDicesLevel: 0, + otherDicesLevel: 0, + statDicesLevel: 0, + specDicesLevel: 0, + effectsList: [], + armorsList: [], + weaponsList: [], + equipmentsList: [], + optionsDiceList: PegasusUtility.getOptionsDiceList() + } + PegasusUtility.updateWithTarget(rollData) + return rollData + } + + /* -------------------------------------------- */ + static updateWithTarget(rollData) { + let objectDefender + let target = PegasusUtility.getTarget(); + if (target) { + let defenderActor = game.actors.get(target.data.actorId) + objectDefender = PegasusUtility.data(defenderActor) + objectDefender = mergeObject(objectDefender, target.data.actorData) + rollData.defender = objectDefender + rollData.attackerId = this.id + rollData.defenderId = objectDefender._id + defenderActor.addHindrancesList(rollData.effectsList) + } + } + + /* -------------------------------------------- */ + static createChatWithRollMode(name, chatOptions) { + this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions); + } + + /* -------------------------------------------- */ + static async confirmDelete(actorSheet, li) { + let itemId = li.data("item-id"); + let msgTxt = "

Are you sure to remove this Item ?"; + let buttons = { + delete: { + icon: '', + label: "Yes, remove it", + callback: () => { + actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]); + li.slideUp(200, () => actorSheet.render(false)); + } + }, + cancel: { + icon: '', + label: "Cancel" + } + } + msgTxt += "

"; + let d = new Dialog({ + title: "Confirm removal", + content: msgTxt, + buttons: buttons, + default: "cancel" + }); + d.render(true); + } + +} \ No newline at end of file diff --git a/styles/simple.css b/styles/simple.css new file mode 100644 index 0000000..c562fa5 --- /dev/null +++ b/styles/simple.css @@ -0,0 +1,1447 @@ + /* ==================== (A) Fonts ==================== */ + + :root { + /* =================== 1. ACTOR SHEET FONT STYLES =========== */ + --window-header-title-font-size: 1.3rem; + --window-header-title-font-weight: normal; + --window-header-title-color: #f5f5f5; + + --major-button-font-size: 1.05rem; + --major-button-font-weight: normal; + --major-button-color: #dadada; + + --tab-header-font-size: 1.0rem; + --tab-header-font-weight: 700; + --tab-header-color: #403f3e; + --tab-header-color-active: #4a0404; + + --actor-input-font-size: 0.8rem; + --actor-input-font-weight: 500; + --actor-input-color: black; + + --actor-label-font-size: 0.8rem; + --actor-label-font-weight: 700; + --actor-label-color: #464331c4; + + /* =================== 2. DEBUGGING HIGHLIGHTERS ============ */ + --debug-background-color-red: #ff000054; + --debug-background-color-blue: #1d00ff54; + --debug-background-color-green: #54ff0054; + + --debug-box-shadow-red: inset 0 0 2px red; + --debug-box-shadow-blue: inset 0 0 2px blue; + --debug-box-shadow-green: inset 0 0 2px green; + } + +/*@import url("https://fonts.googleapis.com/css2?family=Martel:wght@400;800&family=Roboto:wght@300;400;500&display=swap");*/ +/* Global styles & Font */ +.window-app { + text-align: justify; + font-size: 16px; + letter-spacing: 1px; +} + +/* Fonts */ +.sheet header.sheet-header h1 input, .window-app .window-header, #actors .directory-list, #navigation #scene-list .scene.nav-item { + font-size: 1.0rem; +} /* For title, sidebar character and scene */ +.sheet nav.sheet-tabs { + font-size: 0.8rem; +} /* For nav and title */ +.window-app input, .foundryvtt-vadentis .item-form, .sheet header.sheet-header .flex-group-center.flex-compteurs, .sheet header.sheet-header .flex-group-center.flex-fatigue, select, button, .item-checkbox, #sidebar, #players, #navigation #nav-toggle { + font-size: 0.8rem; +} + +.window-header{ + background: rgba(0,0,0,0.75); +} + +.window-app.sheet .window-content { + margin: 0; + padding: 0; +} +.strong-text{ + font-weight: bold; +} + +.tabs .item.active, .blessures-list li ul li:first-child:hover, a:hover { + text-shadow: 1px 0px 0px #ff6600; +} + +.rollable:hover, .rollable:focus { + color: #000; + text-shadow: 0 0 10px red; + cursor: pointer; +} +input:disabled { + color:#1c2058; +} +select:disabled { + color:#1c2058; +} +table {border: 1px solid #7a7971;} + +.grid, .grid-2col { + display: grid; + grid-column: span 2 / span 2; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + margin: 10px 0; + padding: 0; +} + +.grid-3col { + grid-column: span 3 / span 3; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.grid-4col { + grid-column: span 4 / span 4; + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.grid-5col { + grid-column: span 5 / span 5; + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.grid-6col { + grid-column: span 5 / span 5; + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.grid-7col { + grid-column: span 7 / span 7; + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + +.grid-8col { + grid-column: span 8 / span 8; + grid-template-columns: repeat(8, minmax(0, 1fr)); +} + +.grid-9col { + grid-column: span 9 / span 9; + grid-template-columns: repeat(9, minmax(0, 1fr)); +} + +.grid-10col { + grid-column: span 10 / span 10; + grid-template-columns: repeat(10, minmax(0, 1fr)); +} + +.grid-11col { + grid-column: span 11 / span 11; + grid-template-columns: repeat(11, minmax(0, 1fr)); +} + +.grid-12col { + grid-column: span 12 / span 12; + grid-template-columns: repeat(12, minmax(0, 1fr)); +} + +.flex-group-center, +.flex-group-left, +.flex-group-right { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + text-align: center; + padding: 5px; +} + +.flex-group-left { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + text-align: left; +} + +.flex-group-right { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + text-align: right; +} + +.flex-center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; +} + +.table-create-actor { + font-size: 0.8rem; +} + +.flex-between { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.flex-shrink { + flex: 'flex-shrink' ; +} + +/* Styles limited to foundryvtt-vadentis sheets */ + +.fvtt-pegasus-rpg .sheet-header { + -webkit-box-flex: 0; + -ms-flex: 0 0 210px; + flex: 0 0 210px; + overflow: hidden; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + margin-bottom: 10px; +} + +.fvtt-pegasus-rpg .sheet-header .profile-img { + -webkit-box-flex: 0; + -ms-flex: 0 0 128px; + flex: 0 0 128px; + width: 196px; + height: auto; + max-height:260px; + margin-top: 0px; + margin-right: 10px; + object-fit: cover; + object-position: 50% 0; +} + +.button-img { + vertical-align: baseline; + width: 8%; + height: 8%; + max-height: 48px; + border-width: 0; + border: 1px solid rgba(0, 0, 0, 0); +} +.button-img:hover { + color: rgba(255, 255, 128, 0.7); + border: 1px solid rgba(255, 128, 0, 0.8); + cursor: pointer; +} + +.button-effect-img { + vertical-align: baseline; + width: 16px; + max-height: 16px; + height: 16; + border-width: 0; +} + +.small-button-container { + height: 16px; + width: 16px; + border: 0; + vertical-align: bottom; +} + +.fvtt-pegasus-rpg .sheet-header .header-fields { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.fvtt-pegasus-rpg .sheet-header h1.charname { + height: 50px; + padding: 0px; + margin: 5px 0; + border-bottom: 0; +} + +.fvtt-pegasus-rpg .sheet-header h1.charname input { + width: 100%; + height: 100%; + margin: 0; +} + +.fvtt-pegasus-rpg .sheet-tabs { + -webkit-box-flex: 0; + -ms-flex: 0; + flex: 0; +} + +.fvtt-pegasus-rpg .sheet-body, +.fvtt-pegasus-rpg .sheet-body .tab, +.fvtt-pegasus-rpg .sheet-body .tab .editor { + height: 100%; + font-size: 0.8rem; +} + +.editor { + border: 2; + height: 300px; + padding: 0 3px; +} + +.medium-editor { + border: 2; + height: 240px; + padding: 0 3px; +} + +.small-editor { + border: 2; + height: 120px; + padding: 0 3px; +} + +.fvtt-pegasus-rpg .tox .tox-editor-container { + background: #fff; +} + +.fvtt-pegasus-rpg .tox .tox-edit-area { + padding: 0 8px; +} + +.fvtt-pegasus-rpg .resource-label { + font-weight: bold; + text-transform: uppercase; +} + +.fvtt-pegasus-rpg .tabs { + height: 40px; + border-top: 1px solid #AAA; + border-bottom: 1px solid #AAA; + color: #000000; +} + +.fvtt-pegasus-rpg .tabs .item { + line-height: 40px; + font-weight: bold; +} + +.fvtt-pegasus-rpg .tabs .item.active { + text-decoration: underline; + text-shadow: none; +} + +.fvtt-pegasus-rpg .items-list { + list-style: none; + margin: 1px 0; + padding: 0; + overflow-y: auto; +} + +.fvtt-pegasus-rpg .items-list .item-header { + font-weight: bold; +} + +.fvtt-pegasus-rpg .items-list .item { + height: 30px; + line-height: 24px; + padding: 1px 0; + border-bottom: 1px solid #BBB; +} + +.fvtt-pegasus-rpg .items-list .item .item-image { + -webkit-box-flex: 0; + -ms-flex: 0 0 24px; + flex: 0 0 24px; + margin-right: 5px; +} + +.fvtt-pegasus-rpg .items-list .item img { + display: block; +} + +.fvtt-pegasus-rpg .items-list .item-name { + margin: 0; +} + +.fvtt-pegasus-rpg .items-list .item-controls { + -webkit-box-flex: 0; + -ms-flex: 0 0 86px; + flex: 0 0 86px; + text-align: right; +} + + +/* ======================================== */ +/* Sheet */ +.window-app.sheet .window-content .sheet-header{ + background: url("../images/ui/pc_sheet_bg.webp") +} +/* background: #011d33 url("../images/ui/fond1.webp") repeat left top;*/ +/*color: rgba(168, 139, 139, 0.5);*/ + +.window-app.sheet .window-content .sheet-header input[type="text"], .window-app.sheet .window-content .sheet-header input[type="number"], .window-app.sheet .window-content .sheet-header input[type="password"], .window-app.sheet .window-content .sheet-header input[type="date"], .window-app.sheet .window-content .sheet-header input[type="time"] { + color: rgba(36, 37, 37, 0.75); + background: rgba(245, 245, 241, 0.95); + border: 1 none; + margin-bottom: 0.25rem; + margin-left: 2px; +} + +.window-app.sheet .window-content .sheet-body input[type="text"], .window-app.sheet .window-content .sheet-body input[type="number"], .window-app.sheet .window-content .sheet-body input[type="password"], .window-app.sheet .window-content .sheet-body input[type="date"], .window-app.sheet .window-content .sheet-body input[type="time"] { + color: rgba(36, 37, 37, 0.75); + background: rgba(245, 245, 241, 0.95); + border: 1 none; + margin-bottom: 0.25rem; + margin-left: 2px; +} + +.window-app.sheet .window-content .sheet-body select, .window-app.sheet .window-content .sheet-header select { + color: rgba(36, 37, 37, 0.75); + background: rgba(245, 245, 241, 0.95); + border: 1 none; + margin-bottom: 0.25rem; + margin-left: 2px; +} + +.window-app .window-content, .window-app.sheet .window-content .sheet-body{ + font-size: 0.8rem; + background: url("../images/ui/pc_sheet_bg.webp") repeat left top; +} + +/* background: rgba(245,245,240,0.6) url("../images/ui/sheet_background.webp") left top;*/ + +section.sheet-body{padding: 0.25rem 0.5rem;} + +.sheet header.sheet-header .profile-img { + object-fit: cover; + object-position: 50% 0; + margin: 0.5rem 0 0.5rem 0.5rem; + padding: 0; +} + +.sheet nav.sheet-tabs { + font-size: 0.70rem; + font-weight: bold; + height: 3rem; + flex: 0 0 3rem; + margin: 0; + padding: 0 0 0 0.25rem; + text-align: center; + text-transform: uppercase; + line-height: 1.5rem; + border-top: 0 none; + border-bottom: 0 none; + background-color:black; + color:beige; +} + +/* background: rgb(245,245,240) url("../images/ui/fond4.webp") repeat left top;*/ + +nav.sheet-tabs .item { + position: relative; + padding: 0 0.25rem; +} + +nav.sheet-tabs .item:after { + content: ""; + position: absolute; + top: 0; + right: 0; + height: 2rem; + width: 1px; + border-right: 1px dashed rgba(52, 52, 52, 0.25); +} + +.sheet .tab[data-tab] { + padding: 0; +} + +section.sheet-body:after { + content: ""; + display: block; + clear: both; +} + +.sheet header.sheet-header .flex-compteurs {text-align: right;} +.sheet header.sheet-header .resource-content {width: 2rem;} + +.select-diff { + display: inline-block; + text-align: left; + width: 50px; +} + +.window-app.sheet .window-content .tooltip:hover .tooltiptext { + top: 2rem; + left: 2rem; + margin: 0; + padding: 0.25rem; +} + +.window-app.sheet .window-content .carac-value, .window-app.sheet .window-content .competence-xp { + margin: 0.05rem; + flex-basis: 3rem; + text-align: center; +} + +/* ======================================== */ +/* Global UI elements */ + +/* ======================================== */ + +h1, h2, h3, h4 { + font-weight: bold; +} + +ul, ol { + margin: 0; + padding: 0; +} +ul, li { + list-style-type: none; +} + +.sheet li { + margin: 0.010rem; + padding: 0.25rem; +} +.header-fields li { + margin: 0; + padding: 0; +} + +.alterne-list > .list-item:hover { + background: rgba(100, 100, 50, 0.25); +} +.alterne-list > .list-item:nth-child(even) { + background: rgba(80, 60, 0, 0.10); +} +.alterne-list > .list-item:nth-child(odd) { + background: rgb(160, 130, 100, 0.05); +} + +.specialisation-label { + font-size: 0.8rem; +} + +.carac-label, +.attr-label { + font-weight: bold; +} + +.list-item { + margin: 0.125rem; + box-shadow: inset 0px 0px 1px #00000096; + border-radius: 0.25rem; + padding: 0.125rem; + flex: 1 1 5rem; + display: flex !important; +} +.list-item-shadow { + background:rgba(87, 60, 32, 0.35); + flex-grow: 0; + flex-wrap: nowrap; + justify-content: flex-start; +} +.list-item-shadow2 { + background:rgba(87, 60, 32, 0.25); + flex-grow: 0; + flex-wrap: nowrap; + justify-content: flex-start; +} +.item-display-show { + display: block; +} +.item-display-hide { + display: none; +} +.conteneur-type { + background: rgb(200, 10, 100, 0.25); +} +.item-quantite { + margin-left: 0.5rem; +} +.list-item-margin1 { + margin-left: 1rem; +} +.list-item-margin2 { + margin-left: 2rem; +} +.list-item-margin3 { + margin-left: 3rem; +} +.list-item-margin4 { + margin-left: 4rem; +} + +.sheet-competence-img { + width: 24px; + height: 24px; + flex-grow: 0; + margin-right: 0.25rem; +} +.competence-column { + flex-direction: column; + align-content: flex-start; + justify-content: flex-start; + flex-grow: 0; + flex-basis: 1; +} +.competence-header { + align-content: flex-start; + justify-content: flex-start; + font-weight: bold; + flex-grow: 0; +} +.secondaire-label, +.arme-label, +.generic-label, +.competence-label, +.devotion-label, +.sort-label, +.technique-label, +.stat-label, +.arme-label, +.armure-label, +.equipement-label, +.description-label { + flex-grow: 2; + margin-left: 4px; +} +.status-header-label { + margin-left: 2px; +} +.roll-dialog-label { + margin: 4px 0; + min-width: 96px; +} +.short-label { + flex-grow: 1; +} +.keyword-label { + font-size: 0.85rem; +} + +.item-sheet-label { + flex-grow: 1; +} + +.item-text-long-line { + flex-grow: 3; +} + +.score-label { + flex-grow: 2; + align-content: center; +} + +.attribut-value, +.carac-value { + flex-grow: 0; + flex-basis: 64px; + margin-right: 4px; + margin-left: 4px; +} +.sante-value, +.competence-value { + flex-grow: 0; + flex-basis: 2rem; + margin-right: 0.25rem; + margin-left: 0.25rem; +} +.description-value { + flex-grow: 0; + flex-basis: 4rem; + margin-right: 0.25rem; + margin-left: 0.25rem; +} +.competence-xp { + flex-grow: 0; + flex-basis: 2rem; + margin-right: 0.25rem; + margin-left: 0.25rem; +} +.blessures-title { + font-weight: bold; +} +.alchimie-title { + font-weight: bold; +} +.blessure-data { + flex-direction: row; + align-content: flex-start; + justify-content: flex-start; +} +.blessures-soins { + flex-grow: 0; + flex-basis: 32px; + margin-right: 4px; + margin-left: 4px; +} +.blessures-loc { + flex-grow: 0; + flex-basis: 96px; + margin-right: 4px; + margin-left: 4px; +} +.pointsreve-value { + flex-grow: 0; + flex-basis: 64px; + margin-right: 4px; + margin-left: 4px; +} + +.input-sante-header, +.stress-style { + flex-grow: 0; + flex-basis: 64px; + margin-right: 4px; + margin-left: 4px; +} + +.small-label { + margin-top: 5px; +} + +.padd-right { + margin-right: 8px; +} +.padd-left { + margin-left: 8px; +} + +.stack-left { + align-items:center; + flex-shrink: 1; + flex-grow: 0; +} +.npc-stat-label { + flex-grow: 2; +} + +.packed-left { + white-space: nowrap; + flex-grow: 0; +} + +.input-numeric-short { + width: 40px; + max-width: 40px; + flex-grow: 0; + flex-shrink: 0; + flex-basis: 40px; + margin-right: 0.25rem; + margin-left: 0.25rem; +} + +.stats-table { + align-content: flex-start; +} + +/* ======================================== */ +.tokenhudext { + display: flex; + flex: 0 !important; + font-weight: 600; +} +.tokenhudext.left { + justify-content: flex-start; + flex-direction: column; + position: absolute; + top: 2.75rem; + right: 4rem; +} +.tokenhudext.right { + justify-content: flex-start; + flex-direction: column; + position: absolute; + top: 2.75rem; + left: 4rem; +} +.control-icon.tokenhudicon { + width: fit-content; + height: fit-content; + min-width: 6rem; + flex-basis: auto; + padding: 0; + line-height: 1rem; + margin: 0.25rem; +} +.control-icon.tokenhudicon.right { + margin-left: 8px; +} +#token-hud .status-effects.active{ + z-index: 2; +} +/* ======================================== */ +.item-checkbox { + height: 25px; + border: 1px solid #736953a6; + border-left: none; + font-weight: 500; + font-size: 1rem; + color: black; + padding-top: 5px; + margin-right: 0px; + width: 45px; + position: relative; + left: 0px; + text-align: center; +} + + +.flex-actions-bar { + flex-grow: 2; +} + +/* ======================================== */ +/* Sidebar CSS */ +#sidebar { + font-size: 1rem; + background-position: 100%; + color: rgba(220,220,220,0.75); +} + +/* background: rgb(105,85,65) url("../images/ui/texture_feuille_perso_onglets.webp") no-repeat right bottom;*/ + +#sidebar.collapsed { + height: 470px !important; +} + +#sidebar-tabs > .collapsed, #chat-controls .chat-control-icon { + color: rgba(220,220,220,0.75); + text-shadow: 1px 1px 0 rgba(0,0,0,0.75); +} + +.sidebar-tab .directory-list .entity { + border-top: 1px dashed rgba(0,0,0,0.25); + border-bottom: 0 none; + padding: 0.25rem 0; +} + +.sidebar-tab .directory-list .entity:hover { + background: rgba(0,0,0,0.05); + cursor: pointer; +} +.chat-message-header { + background: rgba(220,220,210,0.5); + font-size: 1.1rem; + height: 48px; + text-align: center; + vertical-align: middle; + display: flex; + align-items: center; +} + +.chat-message .message-header .flavor-text, .chat-message .message-header .whisper-to { + font-size: 0.9rem; +} +.chat-actor-name { + padding: 4px; +} + +.chat-img { + width: 64px; + height: 64px; +} + +.roll-dialog-header { + height: 52px; +} + +.actor-icon { + float: left; + width: 48px; + height: 48px; + padding: 2px 6px 2px 2px; +} + +.padding-dice { + padding-top: .2rem; + padding-bottom: .2rem; +} + +.dice-image { + box-sizing: border-box; + border: none; + border-radius: 0; + max-width: 100%; +} + +.dice-image-reroll { + background-color:rgba(115, 224, 115, 0.25); + border-color: #011d33; + box-sizing: border-box; + border: 1px; + border-radius: 0%; + max-width: 100%; +} + +.chat-dice { + width: 15%; + height: 15%; + font-size: 15px; + padding: 10px; + padding-bottom: 20px; + padding-top: .2rem; + padding-bottom: .2rem; +} + +.div-river-full { + height: 5rem; + align-items: flex-start; +} + +.div-river { + align-content: center; + margin-left: 8px; + align-content:space-around; + justify-content: space-around; +} + +.div-center { + align-self: center; +} + +.chat-message { + background: rgba(220,220,210,0.5); + font-size: 0.9rem; +} + +.chat-message.whisper { + background: rgba(220,220,210,0.75); + border: 2px solid #545469; +} + +.chat-message .chat-icon { + border: 0; + padding: 2px 6px 2px 2px; + float: left; + width: 64px; + height: 64px; +} + +.stat-icon { + border: 0; + padding: 2px 2px 2px 2px; + max-width:32px; + max-height:32px; + width: auto; + height: auto; +} +.small-stat-icon { + border: 0; + padding: 2px 2px 2px 2px; + max-width:16px; + max-height:16px; + width: auto; + height: auto; +} +.combat-icon { + border: 0; + padding: 2px 2px 2px 2px; + max-width:24px; + max-height:24px; + width: auto; + height: auto; +} + +#sidebar-tabs { + flex: 0 0 32px; + box-sizing: border-box; + margin: 0 0 5px; + border-bottom: 1px solid rgba(0,0,0,0); + box-shadow: inset 0 0 2rem rgba(0,0,0,0.5); +} + +#sidebar-tabs > .item.active { + border: 1px solid rgba(114,98,72,1); + background: rgba(30, 25, 20, 0.75); + box-shadow: 0 0 6px inset rgba(114,98,72,1); +} + +#sidebar #sidebar-tabs i{ + width: 25px; + height: 25px; + display: inline-block; + background-position:center; + background-size:cover; + text-shadow: 1px 1px 0 rgba(0,0,0,0.75); + +} + +/*--------------------------------------------------------------------------*/ +/* Control, Tool, hotbar & navigation */ + +#controls .scene-control, #controls .control-tool { + box-shadow: 0 0 3px #000; + margin: 0 0 8px; + border-radius: 0; + background: rgba(30, 25, 20, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; +} + +#controls .scene-control.active, #controls .control-tool.active, #controls .scene-control:hover, #controls .control-tool:hover { + background: rgba(72, 46, 28, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; + box-shadow: 0 0 3px #ff6400; +} + +#hotbar #action-bar #macro-list { + border: 1px solid rgba(72, 46, 28, 1); + box-shadow: 2px 2px 5px #000000; +} + +#hotbar #action-bar .macro { + border-image: url(img/ui/bg_control.jpg) 21 repeat; + border-image-slice: 6 6 6 6 fill; + border-image-width: 6px 6px 6px 6px; + border-image-outset: 0px 0px 0px 0px; + border-radius: 0px; +} + +#hotbar .bar-controls { + background: rgba(30, 25, 20, 1); + border: 1px solid rgba(72, 46, 28, 1); +} + +#players { + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; + background: rgba(30, 25, 20, 1); +} + +#navigation #scene-list .scene.nav-item.active { + background: rgba(72, 46, 28, 1); +} + +#navigation #scene-list .scene.nav-item { + background: rgba(30, 25, 20, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; +} + +#navigation #scene-list .scene.view, #navigation #scene-list .scene.context { + background: rgba(72, 46, 28, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; + box-shadow: 0 0 3px #ff6400; +} + +#navigation #nav-toggle { + background: rgba(30, 25, 20, 1); + background-origin: padding-box; + border-image: url(img/ui/footer-button.png) 10 repeat; + border-image-width: 4px; + border-image-outset: 0px; +} + +/* Tooltip container */ +.tooltip { + position: relative; + display: inline-block; + /*border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ +} + +/* Tooltip text */ +.tooltip .tooltiptext { + text-align: left; + background: rgba(231, 229, 226, 0.9); + width: 150px; + padding: 3px 0; + font-size: 0.9rem; + + /* Position the tooltip text */ + top: 1px; + position: absolute; + z-index: 1; + + /* Fade in tooltip */ + visibility: hidden; + opacity: 0; + transition: opacity 0.3s; +} + +.tooltip .ttt-fatigue{ + width: 360px; + + background: rgba(30, 25, 20, 0.9); + border-image: url(img/ui/bg_control.jpg) 21 repeat; + border-image-slice: 6 6 6 6 fill; + border-image-width: 6px 6px 6px 6px; + border-image-outset: 0px 0px 0px 0px; + border-radius: 0px; + + font-size: 0.8rem; + padding: 3px 0; +} + +.tooltip .ttt-ajustements { + width: 150px; + background: rgba(220,220,210,0.95); + border-radius: 6px; + font-size: 0.9rem; + padding: 3px 0; +} + +.tooltip-nobottom { + border-bottom: unset; /* If you want dots under the hoverable text */ +} +.tooltip .ttt-xp { + width: 250px; + background: rgba(220,220,210,0.95); + border-radius: 6px; + font-size: 0.9rem; + padding: 3px 0; +} + +/* Show the tooltip text when you mouse over the tooltip container */ +.tooltip:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +.river-button { + box-shadow: inset 0px 1px 0px 0px #a6827e; + background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%); + background-color: #7d5d3b00; + border-radius: 3px; + border: 2px ridge #846109; + display: inline-block; + cursor: pointer; + color: #ffffff; + font-size: 0.8rem; + padding: 2px 4px 0px 4px; + text-decoration: none; + text-shadow: 0px 1px 0px #4d3534; + position: relative; + margin:4px; +} + +.chat-card-button { + box-shadow: inset 0px 1px 0px 0px #a6827e; + background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%); + background-color: #7d5d3b00; + border-radius: 3px; + border: 2px ridge #846109; + display: inline-block; + cursor: pointer; + color: #ffffff; + font-size: 0.8rem; + padding: 4px 12px 0px 12px; + text-decoration: none; + text-shadow: 0px 1px 0px #4d3534; + position: relative; + margin:2px; +} + +.chat-card-button:hover { + background: linear-gradient(to bottom, #800000 5%, #3e0101 100%); + background-color: red; +} +.chat-card-button:active { + position:relative; + top:1px; +} + +.plus-minus-button { + box-shadow: inset 0px 1px 0px 0px #a6827e; + background: linear-gradient(to bottom, #21374afc 5%, #152833ab 100%); + background-color: #7d5d3b00; + border-radius: 3px; + border: 2px ridge #846109; + display: inline-block; + cursor: pointer; + color: #ffffff; + padding: 0px 6px 0px 6px; + text-decoration: none; + text-shadow: 0px 1px 0px #4d3534; + position: relative; + margin:0px; +} + +.river-button:hover, +.plus-minus-button:hover, +.chat-card-button:hover { + background: linear-gradient(to bottom, #800000 5%, #3e0101 100%); + background-color: red; +} + +.plus-minus-button:active, +.chat-card-button:active { + position:relative; + top:1px; +} + +.plus-minus { + font-size: 0.9rem; + font-weight: bold; +} + +.ul-level1 { + padding-left: 2rem; +} + +.drop-equipment-effect, +.drop-power-effect, +.drop-perk-effect, +.drop-ability-effect, +.drop-effect-specaffected, +.drop-effect-spec, +.drop-ability-weapon, +.drop-ability-armor, +.drop-race-perk, +.drop-spec-perk, +.drop-ability-power, +.drop-ability-spec, +.drop-spec-power, +.drop-abilities, +.drop-optionnal-abilities, +.drop-specialperk1, +.drop-perk2, +.drop-spec1 , +.drop-spec2 { + background: linear-gradient(to bottom, #6c95b9fc 5%, #105177ab 100%); + background-color: #7d5d3b00; + border-radius: 3px; + border: 2px ridge #846109; +} + +/*************************************************************/ +#pause +{ + font-size: 2rem; +} +#pause > h3 +{ + color: #CCC +} +#pause > img { + content: url(../images/ui/pegasus_logo_v1.webp); + height: 160px; + width: 256px; + top: -80px; + left: calc(50% - 132px); +} + +#logo { + content : url(../images/ui/pegasus_logo_v1.webp); + width: 100px; + height: 60px; +} + +.dice-cell { + padding-left: 12px; + padding-right: 12px; + width: 60px; + text-align: center; +} + +.dice-formula, +.dice-total { + height: 54px; + position:relative; +} + +/* =================== 1. ACTOR SHEET FONT STYLES =========== *//* +Agility AGI: #02a41d Also Used for Ranged Damage +Mind MND: #a100fe +Social SOC: #fd7100 +Strength STR: #5f3d00 Also Used For Melee Damage +Physique PHY: #990304 Also used For Damage Resistance +Combat COM: 0136ff Also Used for Melee Attack +Defence DEF: #88826a Also used in the Defence on Combat Tab +Stealth STL: #505050 +Perception PER: #f9c801 Also Used for Ranged Damage +Focus FOC: #ff0084 +*/ +.color-class-black { + background-color: black; + background: black; +} +.color-class-agi, +.color-class-range { + background-color: #02a41d; + background: #02a41d; +} +.color-class-pool { + background-color:#c5c3c3; +} +.color-class-mnd { + background-color: #a100fe; +} +.color-class-soc { + background-color: #fd7100; +} +.color-class-str, +.color-class-meleedmg { + background-color: #5f3d00; +} +.color-class-phy, +.color-class-dmgres { + background-color: #990304; +} +.color-class-mr { + background-color: #050505; +} +.color-class-com, +.color-class-melee { + background-color: #0136ff; +} +.color-class-def, +.color-class-defence { + background-color: #88826a; +} +.color-class-stl { + background-color: #505050; +} +.color-class-per, +.color-class-ranged { + background-color: #f9c801; +} +.color-class-foc { + background-color: #ff0084; +} +.color-class-common { + background: rgba(185, 183, 40, 0.45); +} +.status-small-label { + font-size: 0.65rem; +} +.combat-button { + min-height: 26px; + max-height: 26px; + margin-top: 4px; +} +.no-grow { + flex-grow: 1; + max-width: 32px; +} +.status-col-name { + max-width: 72px; +} +.status-block { + max-width: 216px; +} +.momentum-block { + max-width: 128px; + justify-content: flex-start; +} +.stat-item { + flex-grow: 1; + justify-content: flex-start; + margin: 2px; +} +.stat-block { + min-width: 160px; +} +.stat-margin { + margin-left: 4px; + margin-top: 5px; +} +.combat-margin { + margin-left: 4px; + margin-top: 3px; +} +.stat-text-white { + color: white; +} +.item-stat-roll { + max-height: 42px; + min-height: 36px; +} +.item-stat-roll select, .item-stat-roll input { + margin-top: 4px; + margin-right: 2px; +} +.table-momentum { + background: none; + border: 0; +} +.img-no-border { + max-width: 48px; + max-height: 48px; + border: 0; +} +.items-title-bg { + margin-top: 6px; + background: black; + color: white; +} +.items-title-text { + margin-left: 4px; +} +.lock-icon { + width:16px; + height: 16px; +} +.item-sheet-img { + width: 64px; + height: auto; +} +.item-name-img { + flex-grow:1; + max-width: 2rem; + min-width: 2rem; +} +.item-name-label-header { + flex-grow:2; + max-width: 12rem; + min-width: 12rem; +} +.item-name-label { + flex-grow:2; + max-width: 10rem; + min-width: 10rem; +} +.item-name-label-level2 { + flex-grow:2; + max-width: 9rem; + min-width: 9rem; +} +.item-field-label-short { + flex-grow:1; + max-width: 4rem; + min-width: 4rem; +} +.item-field-label-medium { + flex-grow:1; + max-width: 6rem; + min-width: 6rem; +} +.item-field-label-long { + flex-grow:1; + max-width: 8rem; + min-width: 8rem; +} +.item-control-end { + align-self: flex-end; +} +.alternate-list { + margin-top: 4px; + flex-wrap: nowrap; +} +.item-filler { + flex-grow: 6; + flex-shrink: 7; +} +.item-controls-fixed { + min-width:2rem; + max-width: 2rem; +} \ No newline at end of file diff --git a/styles/unused.html b/styles/unused.html new file mode 100644 index 0000000..5f8d30b --- /dev/null +++ b/styles/unused.html @@ -0,0 +1,60 @@ +{{!-- Carac Tab --}} +
+ +
+
+
+ +
+
+ +
+ +
    +
  • +

    {{data.momentum.label}}

    + + +
  • +
+ +
+ +
+ +
+ + +
    +
  • + +

    {{data.mr.label}}

    +
    + + +
  • + +
+ + +
  • + {{#each data.secondary as |stat2 key|}} + {{#if stat2.iscombat}} + +

    {{stat2.label}} :

    +
    + Cur +  Max + {{/if}} +{{/each}} +
  • +
  • +

    {{data.momentum.label}}:

    + Cur +  Max +
  • diff --git a/system.json b/system.json new file mode 100644 index 0000000..842fdf2 --- /dev/null +++ b/system.json @@ -0,0 +1,38 @@ +{ + "author": "Uberwald", + "compatibleCoreVersion": "9", + "description": "Imperium 5 RPG system for FoundryVTT", + "download": "https://www.uberwald.me/data/files/fvtt-imperium5/fvtt-imperium5.zip", + "esmodules": [ + "modules/imperium5-main.js" + ], + "gridDistance": 5, + "gridUnits": "m", + "languages": [ + { + "lang": "en", + "name": "English", + "path": "lang/en.json" + } + ], + "library": false, + "license": "LICENSE.txt", + "manifest": "https://www.uberwald.me/data/files/fvtt-imperium5/system.json", + "manifestPlusVersion": "1.0.0", + "media": [], + "minimumCoreVersion": "0.8.0", + "name": "fvtt-imperium5", + "packs": [ + ], + "primaryTokenAttribute": "secondary.health", + "secondaryTokenAttribute": "secondary.delirium", + "socket": true, + "styles": [ + "styles/simple.css" + ], + "templateVersion": 1, + "title": "Imperium5 RPG", + "url": "https://www.uberwald.me/data/files/fvtt-imperium5", + "version": "0.0.1", + "background" : "./images/ui/imperium5_welcome_page.webp" +} diff --git a/template.json b/template.json new file mode 100644 index 0000000..2557e41 --- /dev/null +++ b/template.json @@ -0,0 +1,424 @@ +{ + "Actor": { + "types": ["character", "npc"], + "templates": { + "biodata": { + "biodata": { + "name": "", + "age": 0, + "size": "", + "weight": "", + "hair": "", + "sex": "", + "eyes": "", + "description": "", + "worstfear": "", + "worstfearactive": false, + "desires": "", + "desiresactive": false, + "preferredhand": "", + "catchphrase": "", + "catchphraseused": false, + "catchphrasetrigger": "", + "charactervalue": 0, + "level": 0, + "threatlevel": 0, + "cdp": 0, + "cdpused": 0, + "notes": "", + "gmnotes": "", + "racename": "", + "rolename": "" + } + }, + "core": { + "race": [], + "abilities": [], + "subactors": [], + "statistics": { + "agi":{ + "label": "Agility", + "abbrev": "AGI", + "level": 1, + "value": 1, + "mod": 0, + "col": 1 + }, + "mnd":{ + "label": "Mind", + "abbrev": "MND", + "level": 1, + "value": 1, + "col": 1, + "mod": 0 + }, + "soc":{ + "label": "Social", + "abbrev": "SOC", + "level": 1, + "value": 1, + "col": 1, + "mod": 0 + }, + "str":{ + "label": "Strength", + "abbrev": "STR", + "level": 1, + "value": 1, + "col": 1, + "mod": 0 + }, + "phy":{ + "label": "Physique", + "abbrev": "PHY", + "level": 1, + "value": 1, + "col": 1, + "mod": 0 + }, + "com":{ + "label": "Combat", + "abbrev": "COM", + "level": 1, + "value": 1, + "col": 2, + "mod": 0 + }, + "def":{ + "label": "Defence", + "abbrev": "DEF", + "level": 1, + "value": 1, + "col": 2, + "mod": 0 + }, + "stl":{ + "label": "Stealth", + "abbrev": "STL", + "level": 1, + "value": 1, + "col": 2, + "mod": 0 + }, + "per":{ + "label": "Perception", + "abbrev": "PER", + "level": 1, + "value": 1, + "col": 2, + "mod": 0 + }, + "foc":{ + "label": "Focus", + "abbrev": "FOC", + "level": 1, + "value": 1, + "col": 2, + "mod": 0 + } + }, + "nrg": { + "label": "NRG", + "type": "value", + "absolutemax": 0, + "value": 0, + "max": 0, + "mod": 0, + "activated": 0 + }, + "mr": { + "label": "MR (Initiative)", + "type": "dice", + "value": 0, + "mod": 0 + }, + "momentum": { + "label": "Momentum", + "type": "value", + "value": 0, + "max": 0 + }, + "secondary": { + "health": { + "label": "Health", + "value": 0, + "type": "value", + "ismax": true, + "iscombat": true, + "bonus": 0, + "max": 0 + }, + "delirium": { + "label": "Delirium", + "value": 0, + "type": "value", + "ismax": true, + "iscombat": true, + "bonus": 0, + "max": 0 + }, + "stealthhealth": { + "label": "STL Health", + "type": "value", + "value": 0, + "ismax": true, + "bonus": 0, + "max": 0 + }, + "socialhealth": { + "label": "SOC Health", + "type": "value", + "value": 0, + "ismax": true, + "bonus": 0, + "max": 0 + } + }, + "combat": { + "bonusdice": 0, + "otherdice": 0, + "hindrancedice": 0, + "stunlevel": 0, + "stunthreshold": 0 + } + }, + "npccore": { + "npctype": "", + "description": "" + } + }, + "character": { + "templates": [ "biodata", "core" ] + }, + "npc": { + "templates": [ "npccore" ] + } + }, + "Item": { + "types": [ "race", "role", "ability", "specialisation", "perk", "power" , "armor", "shield", "equipment", "weapon", "effect", "money"], + "effect": { + "type": "", + "genre": "", + "effectlevel": 0, + "reducedicevalue": false, + "stataffected": "", + "specaffected": [], + "statdice": false, + "bonusdice": false, + "otherdice": false, + "hindrance" : false, + "resistedby": "", + "recoveryroll": false, + "recoveryrollstat": "", + "recoveryrollspec": [], + "effectstatlevel": false, + "effectstat": "", + "description": "" + }, + "race": { + "description": "", + "environment": "", + "society_culture": "", + "outlook": "", + "selectablestats": false, + "statsonlyonce": false, + "numberstats": 0, + "abilities": [], + "optionnalabilities": [], + "nboptionnal": 0, + "perksgained": false, + "perksall": false, + "perksnumber": 0, + "perks": [], + "statistics": "" + }, + "role": { + "statincrease1": "", + "statincrease2": "", + "messagespecplus1": "", + "specialisationsplus1": [], + "powers1": [], + "MR": 0, + "specialperk": [], + "specincrease": [], + "perks": [], + "description": "" + }, + "ability": { + "affectedstat": "str", + "statmodifier": 1, + "statlevelincrease": 0, + "statusaffected": "", + "statusmodifier": 0, + "powersgained": [], + "specialisations": [], + "effectsgained": [], + "aoe": "", + "affectedcircumstances": "", + "affectedspecialisations": "", + "nrgcost": 0, + "opponenthindrance": 0, + "attackgained": [], + "armorgained": [], + "threatlevel": 0, + "description": "" + }, + "specialisation": { + "statistic": "", + "level": 1, + "ispowergroup": false, + "powersource": "", + "powersactivated": false, + "powers": [], + "isthreatlevel": false, + "description": "" + }, + "perk": { + "level": 1, + "active": false, + "duration": "", + "isweaver": false, + "effectsgained": [], + "features": { + "nrgcost": { + "label": "NRG cost to use", + "flag": false, + "type": "number", + "isvalid": true, + "value": 0 + }, + "range": { + "label": "Range", + "flag": false, + "type": "range", + "isvalid": true, + "value": "" + }, + "nbtargets": { + "label": "# Targets", + "flag": false, + "type": "string", + "isvalid": true, + "value": "" + }, + "bonushealth": { + "label": "Bonus to Health", + "flag": false, + "type": "string", + "isvalid": true, + "value": "" + }, + "bonusnrg": { + "label": "Bonus to NRG", + "flag": false, + "type": "string", + "isvalid": true, + "value": "" + }, + "bonusdelirium": { + "label": "Bonus to Delirium", + "flag": false, + "type": "string", + "isvalid": true, + "value": "" + } + }, + "status": "", + "nbuse": "", + "used1":false, + "used2":false, + "used3":false + }, + "power": { + "rollneeded": false, + "statistic": "", + "cost": 0, + "costspent": 0, + "range": "", + "action": "", + "type": "", + "powersource": "", + "effects": "", + "activated": false, + "purchasedeffects": "", + "effectsgained": [], + "dmgroll": false, + "dmgstatistic": "", + "description": "" + }, + "armor": { + "statistic": "", + "resistance": "", + "weight": 0, + "cost": 0, + "idr": "", + "equipped": false, + "locationprotected": "", + "effects": [], + "activated": false, + "description":"" + }, + "shield": { + "deftype": "", + "level": "", + "weight": 0, + "cost": 0, + "idr": "", + "equipped": false, + "effects": [], + "activated": false, + "description":"" + }, + "equipment": { + "type": "", + "cost": 0, + "weight": 0, + "idr": "", + "quantity": 0, + "equipped": false, + "stataffected": "", + "level": 0, + "statdice": false, + "bonusdice": false, + "otherdice": false, + "effects": [], + "activated": false, + "iscontainer": false, + "containercapacity": 0, + "containerid": "", + "threatlevel": 0, + "description":"" + }, + "money" : { + "value": 0, + "quantity": 0, + "weight": 0, + "idr":"", + "description": "" + }, + "weapon": { + "statistic": "", + "damagestatistic": "", + "damage": "", + "cost": 0, + "weight": 0, + "idr": "", + "range": "", + "maxrange": "", + "ao": "", + "enhanced": false, + "enhancedstat": "", + "enhancedlevel": 0, + "damagetype": "", + "damagetypelevel": 0, + "vehicledamagetype": "", + "vehicledamagetypelevel": 0, + "ammocurrent": 0, + "ammomax": 0, + "equipped": false, + "effects": [], + "activated": false, + "description": "" + } + } +} diff --git a/templates/actor-sheet.html b/templates/actor-sheet.html new file mode 100644 index 0000000..efa57a7 --- /dev/null +++ b/templates/actor-sheet.html @@ -0,0 +1,879 @@ +
    + + {{!-- Sheet Header --}} +
    +
    +

    +
    + +
    + +
    +
    +
      + {{#each data.statistics as |stat key|}} + {{#if (eq stat.col 1)}} + {{> systems/fvtt-pegasus-rpg/templates/partial-actor-stat-block.html stat=stat key=key}} + {{/if}} + {{/each}} +
    • + + + + +

      Dice Pool

      +
      +
    • +
    +
    + +
    +
      + {{#each data.statistics as |stat key|}} + {{#if (eq stat.col 2)}} + {{> systems/fvtt-pegasus-rpg/templates/partial-actor-stat-block.html stat=stat key=key}} + {{/if}} + {{/each}} +
    • + + + + +

      MR

      +
      + + +
    • +
    +
    + +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-actor-status.html}} +
    + + + +
    +
    +
    +
    +
    + + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
    + + {{!-- Combat Tab --}} +
    +
    + +
    + + + + + + + +
    + +

    Stun

    +
    +
      +
    • + Current + + Threshold + +
    • +
    +
    +
    + +
    +
      +
    • + +

      +
      + + + + + + + + + + + + + + + + + + + + + + + + +
    • + {{#each perks as |perk key|}} +
    • + + {{perk.name}} + + {{perk.data.level}} + + {{#if perk.data.features.range.flag}} + {{perk.data.features.range.value}} + {{else}} +  -  + + {{/if}} + {{#if perk.data.features.nbtargets.flag}} + {{perk.data.features.nbtargets.value}} + {{else}} +  -  + {{/if}} + + + + + + {{#if (ne perk.data.status "ready")}} + {{#if (eq perk.data.nbuse "next1action")}} + +   +   + {{/if}} + {{#if (eq perk.data.nbuse "next2action")}} + + +   + {{/if}} + {{#if (eq perk.data.nbuse "next3action")}} + + + + {{/if}} + {{else}} + + + + {{/if}} +
       
      +
      + +
      +
    • + {{/each}} +
    +
    + +
      +
    • + +

      +
      + + + + + + + + + + + + +
    • + + {{#each effects as |effect key|}} +
    • + + {{effect.name}} + {{effect.data.effectlevel}} + {{upperFirst effect.data.type}} + {{upperFirst effect.data.genre}} + {{upper effect.data.stataffected}} +
       
      +
      + +
      +
    • + {{/each}} +
    +
    +
    + + {{!-- Other Tab --}} +
    + +
      +
    • + +

      +
      + + + + + + + + + +
    • + {{#each specs as |spec key|}} +
    • + + {{spec.name}} + {{upper spec.data.statistic}} + {{spec.data.dice}} + {{#if spec.data.ispowergroup}} + {{#if spec.data.powersactivated}} + Deactivate + {{else}} + Activate + {{/if}} + {{else}} +  -  + {{/if}} +
       
      +
      + +
      +
    • + {{/each}} +
    +
    + +
    + +
    + + {{!-- Powers Tab --}} +
    + +
    + +
      +
    • +

      {{data.nrg.label}}

      + Activated +  Current +  Mod +  Max + / {{data.nrg.absolutemax}} +
    • +
    + + + +
      +
    • + +

      +
      + + + + + + + + + + + + +
    • + {{#each abilities as |ability key|}} +
    • + + {{ability.name}} + + {{upper ability.data.affectedstat}} + {{ability.data.statmodifier}} + {{upperFirst ability.data.statusaffected}} + {{ability.data.statusmodifier}} + +
       
      +
      + +
      +
    • + {{/each}} +
    + +
    +
    + + {{!-- Equipement Tab --}} +
    + +
    +

    Encumbrance

    + Current : {{encCurrent}} + Capacity : {{encCapacity}} + Hindrance : {{encHindrance}} +
    + +
      +
    • + +

      +
      + + + + + + + + + +
    • + {{#each moneys as |money key|}} +
    • + + {{money.name}} + + + + + + + + {{#if money.data.idrDice}} + {{money.data.idrDice}} + {{else}} +  -  + {{/if}} + + +
       
      +
      + +
      +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + + + + + + + + + + + + + + + + +
    • + {{#each weapons as |weapon key|}} +
    • + + {{weapon.name}} + + + + + {{#if (gt weapon.data.ammomax 0)}} + + {{else}} + + + {{/if}} + + + {{#if (count weapon.data.effects)}} + {{#if weapon.data.activated}} + Deactivate + {{else}} + Activate + {{/if}} + {{else}} +  -  + {{/if}} + + + + + {{#if weapon.data.idrDice}} + {{weapon.data.idrDice}} + {{else}} +  -  + {{/if}} + + +
       
      + +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + + + + + + + + + + + + + +
    • + {{#each armors as |armor key|}} +
    • + + {{armor.name}} + {{upper armor.data.statistic}} + {{armor.data.resistanceDice}} + {{armor.data.locationprotected}} + + + {{#if (count armor.data.effects)}} + {{#if armor.data.activated}} + Deactivate + {{else}} + Activate + {{/if}} + {{else}} +  -  + {{/if}} + + + {{armor.data.weight}} + + {{#if armor.data.idrDice}} + {{armor.data.idrDice}} + {{else}} +  -  + {{/if}} + + +
       
      + +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + + + + + + + +
    • + {{#each shields as |shield key|}} +
    • + + {{shield.name}} + {{shield.data.levelDice}} + + + {{#if (count shield.data.effects)}} + {{#if shield.data.activated}} + Deactivate + {{else}} + Activate + {{/if}} + {{else}} +  -  + {{/if}} + + + {{shield.data.weight}} + + {{#if shield.data.idrDice}} + {{shield.data.idrDice}} + {{else}} +  -  + {{/if}} + +
       
      + +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + + + + + + + +
    • + {{#each containersTree as |equip key|}} + {{> systems/fvtt-pegasus-rpg/templates/partial-actor-equipment.html equip=equip level=1}} +
        + {{#each equip.data.contents as |subgear key|}} + {{> systems/fvtt-pegasus-rpg/templates/partial-actor-equipment.html equip=subgear level=2}} + {{/each}} +
      + {{/each}} +
    + +
    + +
    + + {{!-- Biography Tab --}} +
    +
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    +
    +
    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + + +
      + +
      +
    • +
    • + + + +
      + +
      +
    • +
    +
    +
    + +

    Psychology :

    +
      +
    • + + + +
    • +
    • + + + +
    • +
    + +

    Catchphrase :

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    + +

    Development :

    +
      +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    • + + +
    • +
    + +
    +

    Background :

    +
    + {{editor content=data.biodata.description target="data.biodata.description" button=true owner=owner + editable=editable}} +
    +
    +

    Notes :

    +
    + {{editor content=data.biodata.notes target="data.biodata.notes" button=true owner=owner editable=editable}} +
    +
    + +
    + + + \ No newline at end of file diff --git a/templates/chat-create-actor.html b/templates/chat-create-actor.html new file mode 100644 index 0000000..3b2a9af --- /dev/null +++ b/templates/chat-create-actor.html @@ -0,0 +1,143 @@ +
    + {{alias}} +

    {{name}}

    +
    + +
    +
    + + {{#if (eq step "select-race")}} +
    Select a race from the list below
    + + {{#each races as |race index|}} + + + + + {{/each}} +
    {{race.name}}Select it !
    + {{/if}} + + {{#if (eq step "select-race-optionnal")}} +
    The selected Race has some optionnal abilities. Select one ability by clicking the relevant button below
    + Remaining abilities to select : {{nboptionnal}} +
    + + {{#each optionnalabilities as |ability index|}} + + + + + {{/each}} +
    {{ability.name}}Select it !
    + {{/if}} + + {{#if (eq step "select-race-stats")}} +
    Select Stats to gain +1 modifier :
    + Remaining stats to select : {{numberstats}} +
    + + {{#each stats as |stat key|}} + {{#if @root.statsonlyonce}} + {{#if stat.used}} + {{else}} + + + + + {{/if}} + {{else}} + + + + + {{/if}} + {{/each}} +
    {{stat.label}} ({{stat.abbrev}})Select it !
    {{stat.label}} ({{stat.abbrev}})Select it !
    + {{/if}} + + {{#if (eq step "select-race-perks")}} +
    Now select {{nbraceperks}} Perk(s) for your character +
    + + {{#each raceperks as |perk index|}} + + + + + {{/each}} +
    {{perk.name}}Select it !
    + {{/if}} + + {{#if (eq step "select-role")}} +
    Now select a Role for your character. +
    + + {{#each roles as |role index|}} + + + + + {{/each}} +
    {{role.name}}Select it !
    + {{/if}} + + {{#if (eq step "select-role-start-spec")}} +
    Choose 1 Specialisation at +1DT : +
    + + {{#each rolestartspec as |spec index|}} + + + + + {{/each}} +
    {{spec.name}}Select it !
    + {{/if}} + + {{#if (eq step "select-role-stat")}} +
    Choose 1 Stat at +1DT : +
    + + {{#each rolestats as |rolestat key|}} + + + + + {{/each}} +
    {{rolestat.label}}Select it !
    + {{/if}} + + {{#if (eq step "select-role-spec")}} +
    Now select a specialisation at +{{dt}}DT. +
    + + {{#each rolespec as |spec index|}} + + + + + {{/each}} +
    {{spec.name}}Select it !
    + {{/if}} + + {{#if (eq step "select-role-perk")}} +
    Now select a Perk. +
    + + {{#each roleperks as |perk index|}} + + + + + {{/each}} +
    {{perk.name}}Select it !
    + {{/if}} + + {{#if (eq step "character-end")}} +
    Follow the next steps from the rulebook page 50 !. You can now spend 150 CDPs to customise your character. +
    + {{/if}} + + +
    diff --git a/templates/chat-effect-used.html b/templates/chat-effect-used.html new file mode 100644 index 0000000..f7c166e --- /dev/null +++ b/templates/chat-effect-used.html @@ -0,0 +1,7 @@ +
    +

    {{name}}

    + {{#if img}} + + {{/if}} +
    Effect {{name}} has ben used and removed from the available effects.
    +
    diff --git a/templates/chat-generic-result.html b/templates/chat-generic-result.html new file mode 100644 index 0000000..658ae08 --- /dev/null +++ b/templates/chat-generic-result.html @@ -0,0 +1,54 @@ +
    + {{#if actorImg}} + {{alias}} + {{/if}} +

    {{alias}}

    +
    + +
    + + {{#if img}} +
    + {{name}} +
    + {{/if}} + +
    +
    + +
    +
      + {{#if power}} +
    • Power : {{power.name}}
    • + {{/if}} + {{#if isDamage}} +
    • Weapon Damage Dice : {{weapon.data.damageDice}}
    • + {{/if}} + {{#if isResistance}} +
    • Armor Resistance Dice : {{armor.data.resistanceDice}}
    • + {{/if}} + {{#if stat}} +
    • Statistic : {{stat.label}}
    • + {{/if}} + {{#if spec}} +
    • Specialisation : {{spec.name}}
    • + {{/if}} + + {{#if weaponName}} +
    • Weapon : {{weaponName}}
    • + {{/if}} + + {{#if isResistance}} +
    • Defense Result : {{finalScore}} + {{else}} + {{#if isDamage}} +
    • Damages : {{finalScore}} + {{else}} +
    • Final Result : {{finalScore}} + {{/if}} + {{/if}} + +
    +
    + +
    diff --git a/templates/chat-opposed-damage.html b/templates/chat-opposed-damage.html new file mode 100644 index 0000000..e6f27a6 --- /dev/null +++ b/templates/chat-opposed-damage.html @@ -0,0 +1,15 @@ +
    + {{alias}} +

    {{defenderName}}

    +
    + +
    + +
    + +
    diff --git a/templates/chat-opposed-fail.html b/templates/chat-opposed-fail.html new file mode 100644 index 0000000..4e71d14 --- /dev/null +++ b/templates/chat-opposed-fail.html @@ -0,0 +1,11 @@ +
    + {{alias}} +

    {{defenderName}}

    +
    + +
    +
    + {{defenderName}} wins the opposition against {{attackerName}} ! +
    + +
    diff --git a/templates/chat-perk-ready.html b/templates/chat-perk-ready.html new file mode 100644 index 0000000..8925b2a --- /dev/null +++ b/templates/chat-perk-ready.html @@ -0,0 +1,7 @@ +
    +

    {{name}}

    + {{#if img}} + + {{/if}} +
    {{name}} has Just Deactivated the Perk: {{perk.name}}, make sure to manually delete all Effects provided by this Perk from Targets.
    +
    diff --git a/templates/editor-notes-gm.html b/templates/editor-notes-gm.html new file mode 100644 index 0000000..f3b3218 --- /dev/null +++ b/templates/editor-notes-gm.html @@ -0,0 +1,6 @@ +{{#if data.isGM}} +

    GM Notes :

    +
    + {{editor content=data.gmnotes target="data.gmnotes" button=true owner=owner editable=editable}} +
    +{{/if}} diff --git a/templates/item-ability-sheet.html b/templates/item-ability-sheet.html new file mode 100644 index 0000000..ef9da6c --- /dev/null +++ b/templates/item-ability-sheet.html @@ -0,0 +1,152 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • +
    • + +
    • + +
    • + +
    • +
    • + +
    • + +
    • +
    • +
    • +
        +
      • +
      • + {{#each data.effectsgained as |effect idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + +
    • +
    • +
    • +
        +
      • +
      • + {{#each data.powersgained as |power idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + +
    • +
    • +
        +
      • +
      • + {{#each data.specialisations as |spec idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + + + + +
    • + +
    • + +
    • +
    • +
        +
      • +
      • + {{#each data.attackgained as |weapon idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + + +
    • +
    • +
        +
      • +
      • + {{#each data.armorgained as |armor idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + + +
    +
    + +
    +
    diff --git a/templates/item-armor-sheet.html b/templates/item-armor-sheet.html new file mode 100644 index 0000000..b6fd3c2 --- /dev/null +++ b/templates/item-armor-sheet.html @@ -0,0 +1,63 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    + +
    +
      +
    • + +
    • + +
    • + +
    • +
    • + +
    • + + {{> systems/fvtt-pegasus-rpg/templates/partial-equipment-effects.html}} + +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    +
    + +
    +
    diff --git a/templates/item-effect-sheet.html b/templates/item-effect-sheet.html new file mode 100644 index 0000000..48fe87f --- /dev/null +++ b/templates/item-effect-sheet.html @@ -0,0 +1,149 @@ +
    +
    + +
    +

    + +

    +
    +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + {{#if data.effectstatlevel}} +
    • + +
    • + {{else}} +
    • + +
    • + {{/if}} + +
    • +
    • +
        +
      • +
      • + {{#each data.specaffected as |spec idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + +
    • + +
    • + + {{#if (eq data.genre "positive")}} + +
    • +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + {{else}} + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + {{#if data.recoveryroll}} +
    • + +
    • +
    • +
        +
      • + +
      • +
      • +
      • + {{#each data.recoveryrollspec as |spec idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + {{/if}} + + {{/if}} + +
    +
    + +
    +
    diff --git a/templates/item-equipment-sheet.html b/templates/item-equipment-sheet.html new file mode 100644 index 0000000..0d66d2d --- /dev/null +++ b/templates/item-equipment-sheet.html @@ -0,0 +1,86 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + {{> systems/fvtt-pegasus-rpg/templates/partial-equipment-effects.html}} + +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • + {{#if data.iscontainer}} +
    • + +
    • + {{/if}} +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + +
    +
    diff --git a/templates/item-money-sheet.html b/templates/item-money-sheet.html new file mode 100644 index 0000000..da8430c --- /dev/null +++ b/templates/item-money-sheet.html @@ -0,0 +1,33 @@ +
    +
    + +
    +

    +
    +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + +
    +
    diff --git a/templates/item-perk-sheet.html b/templates/item-perk-sheet.html new file mode 100644 index 0000000..d353c99 --- /dev/null +++ b/templates/item-perk-sheet.html @@ -0,0 +1,112 @@ +
    +
    + +
    +

    +
    +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • + +
    • + +
    • +
    • + +
    • +
    • + +
    • + +
    • +
    • +
    • +
        +
      • +
      • + {{#each data.effectsgained as |effect idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + + {{#each data.features as |feature key|}} + {{#if feature.isvalid}} +
    • + + +
    • + {{#if feature.flag}} +
        +
      • + {{#if (eq feature.type "statdice")}} + + {{/if}} + {{#if (eq feature.type "range")}} + + {{/if}} + {{#if (eq feature.type "number")}} + + {{/if}} + {{#if (eq feature.type "string")}} + + {{/if}} + {{#if (eq feature.type "dropspec")}} +
          +
        • +
        • + +
        + {{/if}} + {{#if (eq feature.type "text")}} +
        + {{editor content=features.value target="data.features.{{key}}.value" button=true owner=owner editable=editable}} +
        + {{/if}} +
      • +
      + {{/if}} + {{/if}} + {{/each}} +
    + +
    + +
    +
    diff --git a/templates/item-power-sheet.html b/templates/item-power-sheet.html new file mode 100644 index 0000000..abb84fb --- /dev/null +++ b/templates/item-power-sheet.html @@ -0,0 +1,136 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + +
    +
    + + {{editor content=data.description target="data.description" button=true owner=owner editable=editable}} +
    +
    + + {{editor content=data.effects target="data.effects" button=true owner=owner editable=editable}} +
    +
    + + {{editor content=data.purchasedeffects target="data.purchasedeffects" button=true owner=owner + editable=editable}} +
    +
    + +
    +
      +
    • + +
    • + {{#if data.rollneeded}} +
    • + +
    • + {{/if}} + +
    • + + +
    • + +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • + +
    • + + +
    • + {{#if data.dmgroll}} +
    • + +
    • + {{/if}} + +
    • +
    • +
    • +
        +
      • +
        +
      • + {{#each data.effectsgained as |effect idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + +
    +
    + +
    +
    \ No newline at end of file diff --git a/templates/item-race-sheet.html b/templates/item-race-sheet.html new file mode 100644 index 0000000..ec9bb16 --- /dev/null +++ b/templates/item-race-sheet.html @@ -0,0 +1,134 @@ +
    +
    + +
    +

    +
    +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + +
    + + +
    + {{editor content=data.description target="data.description" button=true owner=owner editable=editable}} +
    + +
    + {{editor content=data.environment target="data.environment" button=true owner=owner editable=editable}} +
    + +
    + {{editor content=data.society_culture target="data.society_culture" button=true owner=owner editable=editable}} +
    + +
    + {{editor content=data.outlook target="data.outlook" button=true owner=owner editable=editable}} +
    +
    + +
    +
      +
    • +
    • +
        +
      • +
        +
      • + {{#each data.abilities as |ability idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    + +
      +
    • + +
    • + {{#if data.selectablestats}} +
    • + +
    • +
    • + +
    • + {{/if}} +
    + +
      +
    • + +
    • + +
    • +
    • +
        +
      • +
        +
      • + {{#each data.optionnalabilities as |ability idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    + +
      +
    • + +
    • + {{#if data.perksgained}} +
    • + +
    • +
    • + +
    • + {{#if data.perksall}} + {{else}} +
        +
      • +
        +
      • + {{#each data.perks as |perk idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      + {{/if}} + {{/if}} +
    + +
    + +
    +
    \ No newline at end of file diff --git a/templates/item-role-sheet.html b/templates/item-role-sheet.html new file mode 100644 index 0000000..b0aa6eb --- /dev/null +++ b/templates/item-role-sheet.html @@ -0,0 +1,114 @@ +
    +
    + +
    +

    + +

    +
    +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
        +
      • + +
      • +
      • + +
      • +
      • +
      • + {{#each data.specialisationsplus1 as |spec idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      +
    • + +
    • +
        +
      • +
      • + {{#each data.specialperk as |perk idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      + +
    • +
    • +
        +
      • + {{#each data.statincreasechoice as |stat idx|}} + + + {{/each}} +
      • +
      +
    • +
    • +
        +
      • +
      • + {{#each data.specincrease as |spec idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      + +
    • +
    • +
        +
      • +
      • + {{#each data.perks as |perk idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      + +
    +
    + +
    +
    diff --git a/templates/item-shield-sheet.html b/templates/item-shield-sheet.html new file mode 100644 index 0000000..5614e11 --- /dev/null +++ b/templates/item-shield-sheet.html @@ -0,0 +1,55 @@ +
    +
    + +
    +

    +
    +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • +
    • + +
    • + + {{> systems/fvtt-pegasus-rpg/templates/partial-equipment-effects.html}} + +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + +
    +
    diff --git a/templates/item-specialisation-sheet.html b/templates/item-specialisation-sheet.html new file mode 100644 index 0000000..c5c7c1c --- /dev/null +++ b/templates/item-specialisation-sheet.html @@ -0,0 +1,62 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • + +
    • + +
    • +
    • + +
    • + {{#if data.ispowergroup}} +
    • + +
    • +
    • +
    • +
        +
      • +
      • + {{#each data.powers as |power idx|}} +
      • + +
        + +
        +
      • + {{/each}} +
      + + {{/if}} +
    • + +
    • +
    +
    +
    +
    diff --git a/templates/item-weapon-sheet.html b/templates/item-weapon-sheet.html new file mode 100644 index 0000000..3341e46 --- /dev/null +++ b/templates/item-weapon-sheet.html @@ -0,0 +1,145 @@ +
    +
    + +
    +

    +
    +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-item-nav.html}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-pegasus-rpg/templates/partial-item-description.html}} + +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • +
    • + +
    • +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + + {{> systems/fvtt-pegasus-rpg/templates/partial-equipment-effects.html}} + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + {{#if data.enhanced}} +
    • + +
    • +
    • + +
    • + {{/if}} + +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + +
    +
    diff --git a/templates/npc-sheet.html b/templates/npc-sheet.html new file mode 100644 index 0000000..6bfa5f1 --- /dev/null +++ b/templates/npc-sheet.html @@ -0,0 +1,184 @@ +
    + + {{!-- Sheet Header --}} +
    +
    +
    + +

    +
    +
    +
    + + {{!-- Sheet Tab Navigation --}} + + + {{!-- Sheet Body --}} +
    + + {{!-- Carac Tab --}} +
    + Unlocked/Locked{{#if editScore}}Unlocked{{else}}Locked{{/if}} + +
    +
    + +

    Type

    +
    + + + +
    +

    Traits List

    +
      + {{#each traits as |trait key|}} +
    • + + {{trait.name}} + {{trait.data.data.type}} +
      + + +
      +
    • + {{/each}} +
    +
    + +
    + +
    +

    Stats & Numbers

    +
      + {{#each data.spec as |spec key|}} +
    • + {{spec.label}} + +
    • + {{/each}} +
    +
    + +
    +
    + + + {{!-- Defence Tab --}} +
    +
    + +
    + {{#each data.fight as |fight key|}} +
      +
    • + {{fight.label}} + +
    • + {{#each fight.derivated as |derivated keydev|}} +
    • + {{derivated.label}} + +
    • + {{/each}} +
    + {{/each}} +
    + +

    Weapons

    + + +
    +
    + + {{!-- Traits Tab --}} +
    + + +
    + + {{!-- Features Tab --}} +
    +
    + +

    Equipment

    + + +
    +
    + + {{!-- Notes Tab --}} +
    +
    +

    Description :

    +
    + {{editor content=data.description target="data.description" button=true owner=owner editable=editable}} +
    +
    +

    Notes :

    +
    + {{editor content=data.notes target="data.notes" button=true owner=owner editable=editable}} +
    +
    + {{>"systems/fvtt-fragged-kingdom/templates/editor-notes-gm.html"}} +
    +
    + +
    +
    + diff --git a/templates/partial-actor-equipment.html b/templates/partial-actor-equipment.html new file mode 100644 index 0000000..01158d3 --- /dev/null +++ b/templates/partial-actor-equipment.html @@ -0,0 +1,49 @@ +
  • + + {{#if (eq level 1)}} + {{equip.name}} + {{else}} + {{equip.name}} + {{/if}} + + + + + + {{#if (count equip.data.effects)}} + {{#if equip.data.activated}} + Deactivate + {{else}} + Activate + {{/if}} + {{else}} +  -  + {{/if}} + + + {{#if equip.data.iscontainer}} + {{equip.data.contentsEnc}} + {{else}} + {{mul equip.data.weight equip.data.quantity}} + {{/if}} + + + {{#if equip.data.idrDice}} + {{equip.data.idrDice}} + {{else}} +  -  + {{/if}} + + +
     
    +
    + {{#if (eq level 1)}} + {{#if equip.data.equipped}}{{else}}{{/if}} + {{/if}} + +
    +
  • diff --git a/templates/partial-actor-stat-block.html b/templates/partial-actor-stat-block.html new file mode 100644 index 0000000..d2cdb82 --- /dev/null +++ b/templates/partial-actor-stat-block.html @@ -0,0 +1,16 @@ +
  • + + + + +

    {{stat.abbrev}}

    +
    + + +
  • \ No newline at end of file diff --git a/templates/partial-actor-status.html b/templates/partial-actor-status.html new file mode 100644 index 0000000..7fd3734 --- /dev/null +++ b/templates/partial-actor-status.html @@ -0,0 +1,70 @@ + + + \ No newline at end of file diff --git a/templates/partial-equipment-effects.html b/templates/partial-equipment-effects.html new file mode 100644 index 0000000..1736d89 --- /dev/null +++ b/templates/partial-equipment-effects.html @@ -0,0 +1,16 @@ +
  • +
  • +
  • + +
  • diff --git a/templates/partial-item-description.html b/templates/partial-item-description.html new file mode 100644 index 0000000..a78040b --- /dev/null +++ b/templates/partial-item-description.html @@ -0,0 +1,8 @@ +
    +
    + +
    + {{editor content=data.description target="data.description" button=true owner=owner editable=editable}} +
    +
    +
    diff --git a/templates/partial-item-nav.html b/templates/partial-item-nav.html new file mode 100644 index 0000000..95b52cd --- /dev/null +++ b/templates/partial-item-nav.html @@ -0,0 +1,5 @@ +{{!-- Sheet Tab Navigation --}} + diff --git a/templates/partial-options-equipment-types.html b/templates/partial-options-equipment-types.html new file mode 100644 index 0000000..ab60653 --- /dev/null +++ b/templates/partial-options-equipment-types.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/templates/partial-options-level.html b/templates/partial-options-level.html new file mode 100644 index 0000000..0fb86c0 --- /dev/null +++ b/templates/partial-options-level.html @@ -0,0 +1,14 @@ +{{#if notapplicable}} + +{{/if}} + + + + + + + + + + + diff --git a/templates/partial-options-range.html b/templates/partial-options-range.html new file mode 100644 index 0000000..e08a1e5 --- /dev/null +++ b/templates/partial-options-range.html @@ -0,0 +1,15 @@ +{{#if notapplicable}} + +{{/if}} + + + + + + + + + + + + diff --git a/templates/partial-options-statistics.html b/templates/partial-options-statistics.html new file mode 100644 index 0000000..a187abf --- /dev/null +++ b/templates/partial-options-statistics.html @@ -0,0 +1,22 @@ +{{#if notapplicable}} + +{{/if}} +{{#if all}} + +{{/if}} +{{#if all}} + +{{/if}} + + + + + + + + + + +{{#if mr}} + +{{/if}} diff --git a/templates/partial-roll-select-effects.html b/templates/partial-roll-select-effects.html new file mode 100644 index 0000000..b324929 --- /dev/null +++ b/templates/partial-roll-select-effects.html @@ -0,0 +1,102 @@ + +{{#if (notEmpty effectsList)}} + + +{{/if}} + +{{#if (notEmpty armorsList)}} + + +{{/if}} + +{{#if (notEmpty weaponsList)}} + + +{{/if}} + +{{#if (notEmpty equipmentsList)}} + + +{{/if}} diff --git a/templates/post-item.html b/templates/post-item.html new file mode 100644 index 0000000..1bad9c8 --- /dev/null +++ b/templates/post-item.html @@ -0,0 +1,8 @@ +
    +

    {{name}}

    + {{#if img}} + + {{/if}} +

    Description :

    +

    {{{data.description}}}

    +
    diff --git a/templates/roll-dialog-generic.html b/templates/roll-dialog-generic.html new file mode 100644 index 0000000..75c582d --- /dev/null +++ b/templates/roll-dialog-generic.html @@ -0,0 +1,88 @@ +
    +
    + {{#if img}} + + {{/if}} +

    {{title}}

    +
    + +
    + +
    + +
    + Stat Dice : + +  + {{statMod}} +
    + + {{#if specList}} +
    + Spec : + +   +
    + {{/if}} + +
    + Spec Dice : + +   +
    + +
    + Bonus Dice : + +   +
    + +
    + Hindrance Dice : + +   +
    + +
    + Other Dice : + +   +
    + +
    + +
    + {{> systems/fvtt-pegasus-rpg/templates/partial-roll-select-effects.html}} +
    + +
    + +