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 = '0 ';
+ let optionsLevel = '0 ';
+ 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 += `${currentDices} `;
+ optionsLevel += `${i} `;
+ }
+ this.diceList = diceList;
+ this.diceFoundryList = diceFoundryList;
+ this.optionsDiceList = optionsDiceList;
+ this.optionsLevel = optionsLevel;
+
+ this.optionsStatusList = 'Not applicable Health NRG Delirium ';
+
+ }
+
+ /* -------------------------------------------- */
+ 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 += `${i} `
+ }
+ 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).
+ No defense
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 --}}
+
+
+
+
+
+
+
+ {{#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 @@
+
+
+
+
+ {{!-- Powers Tab --}}
+
+
+
+
+
+
+
+
+
+
+
+
+ Stat
+
+
+ Stat Modifier
+
+
+ Affected status
+
+
+ Status Modifier
+
+
+ {{#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}}
+
+
+
+
+
+
+ Qty
+
+
+ Weight
+
+
+ IDR
+
+
+ {{#each moneys as |money key|}}
+
+
+ {{money.name}}
+
+
+ {{money.data.quantity}}
+ ( - /+ )
+
+
+
+ {{money.data.weight}}
+
+
+ {{#if money.data.idrDice}}
+ {{money.data.idrDice}}
+ {{else}}
+ -
+ {{/if}}
+
+
+
+
+
+ {{/each}}
+
+
+
+
+
+
+ Attack
+
+
+ Damage
+
+
+ DMG
+
+
+ Ammo
+
+
+ Act/Deact
+
+
+ Weight
+
+
+ IDR
+
+
+ {{#each weapons as |weapon key|}}
+
+
+ {{weapon.name}}
+ {{upper weapon.data.statistic}}
+ {{upper weapon.data.damagestatistic}}
+ {{weapon.data.damageDice}}
+
+ {{#if (gt weapon.data.ammomax 0)}}
+ {{weapon.data.ammocurrent}}/{{weapon.data.ammomax}}
+ ( - /+ )
+
+ {{else}}
+ -
+
+ {{/if}}
+
+
+ {{#if (count weapon.data.effects)}}
+ {{#if weapon.data.activated}}
+ Deactivate
+ {{else}}
+ Activate
+ {{/if}}
+ {{else}}
+ -
+ {{/if}}
+
+ {{weapon.data.weight}}
+
+
+ {{#if weapon.data.idrDice}}
+ {{weapon.data.idrDice}}
+ {{else}}
+ -
+ {{/if}}
+
+
+
+
+
+ {{/each}}
+
+
+
+
+
+
+ Stat
+
+
+ DMG RES
+
+
+ Loc
+
+
+ Act/Deact
+
+
+ Weight
+
+
+ IDR
+
+
+ {{#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}}
+
+
+
+
+
+
+ Dice
+
+
+ Act/Deact
+
+
+ Weight
+
+
+ IDR
+
+
+ {{#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}}
+
+
+
+
+
+
+ Quantity
+
+
+ Act/Deact
+
+
+ Weight
+
+
+ IDR
+
+
+ {{#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 @@
+
+
+
+
+
+ {{#if (eq step "select-race")}}
+
Select a race from the list below
+
+ {{/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}}
+
+
+ {{/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}}
+
+ {{stat.label}} ({{stat.abbrev}})
+ Select it !
+
+ {{/if}}
+ {{else}}
+
+ {{stat.label}} ({{stat.abbrev}})
+ Select it !
+
+ {{/if}}
+ {{/each}}
+
+ {{/if}}
+
+ {{#if (eq step "select-race-perks")}}
+
Now select {{nbraceperks}} Perk(s) for your character
+
+
+ {{/if}}
+
+ {{#if (eq step "select-role")}}
+
Now select a Role for your character.
+
+
+ {{/if}}
+
+ {{#if (eq step "select-role-start-spec")}}
+
Choose 1 Specialisation at +1DT :
+
+
+ {{/if}}
+
+ {{#if (eq step "select-role-stat")}}
+
Choose 1 Stat at +1DT :
+
+
+ {{#each rolestats as |rolestat key|}}
+
+ {{rolestat.label}}
+ Select it !
+
+ {{/each}}
+
+ {{/if}}
+
+ {{#if (eq step "select-role-spec")}}
+
Now select a specialisation at +{{dt}}DT.
+
+
+ {{/if}}
+
+ {{#if (eq step "select-role-perk")}}
+
Now select a Perk.
+
+
+ {{/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 img}}
+
+
+
+ {{/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 @@
+
+
+
+
+
+
+ {{defenderName}} suffer damages from {{attackerName}} !
+ Damages (inc. armor+weapon) : {{finalDamage}}
+ Oppose damage roll with Chi
+ Apply damage
+
+
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 @@
+
+
+
+
+ {{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 @@
+
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 @@
+
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 @@
+
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 @@
+
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 @@
+
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 @@
+
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 @@
+
\ 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 @@
+
\ 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 @@
+
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 @@
+
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 @@
+
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 @@
+
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 @@
+
+
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}}
+
+
+ {{equip.data.quantity}}
+ ( - /+ )
+
+
+
+
+ {{#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}}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+ {{#select stat.value}}
+ {{{@root.optionsDiceList}}}
+ {{/select}}
+
+
+
\ 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 @@
+Effects
+
+
+
+ Drop Effects here !
+
+ {{#each data.effects as |effect idx|}}
+
+ {{effect.name}}
+
+
+ {{/each}}
+
+
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 @@
+
+
+
Description
+
+ {{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 --}}
+
+ Description
+ Details
+
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 @@
+Camping & Survival
+Communication & Information
+Equestrian
+Law/Security/Spy/Thief
+Medical
+Tools
+Weapons accessories
+Wizards materials
+Mounts
+Vehicles
+Clothing
+Ammo
+Misc
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}}
+Not applicable
+{{/if}}
+0
+d4
+d6
+d8
+d10
+d12
+d12 d4
+d12 d6
+d12 d8
+d12 d10
+d12 d12
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}}
+Not applicable
+{{/if}}
+Self Only
+Touch/Self
+Threat Zone
+Close
+Medium
+Long
+Extreme
+Line of Sight
+TZ/Close
+Close/Medium
+Medium/Long
+Long/Extreme
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}}
+Not applicable
+{{/if}}
+{{#if all}}
+All
+{{/if}}
+{{#if all}}
+Special
+{{/if}}
+AGI
+MND
+SOC
+STR
+PHY
+COM
+DEF
+STL
+PER
+FOC
+{{#if mr}}
+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)}}
+Effects&Hindrances
+
+{{/if}}
+
+{{#if (notEmpty armorsList)}}
+ Armors&Shields
+
+{{/if}}
+
+{{#if (notEmpty weaponsList)}}
+ Weapons
+
+{{/if}}
+
+{{#if (notEmpty equipmentsList)}}
+ Items
+
+{{/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 @@
+