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/.directory b/images/.directory
new file mode 100644
index 0000000..a68682e
--- /dev/null
+++ b/images/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2022,9,12,23,3,3.4699999999999998
+Version=4
+VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails
diff --git a/images/dice/.directory b/images/dice/.directory
new file mode 100644
index 0000000..737d67d
--- /dev/null
+++ b/images/dice/.directory
@@ -0,0 +1,4 @@
+[Dolphin]
+Timestamp=2022,7,19,23,15,34.73
+Version=4
+VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails
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_1.webp b/images/dice/d6_1.webp
new file mode 100644
index 0000000..fe80bf6
Binary files /dev/null and b/images/dice/d6_1.webp 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_2.webp b/images/dice/d6_2.webp
new file mode 100644
index 0000000..9a37e70
Binary files /dev/null and b/images/dice/d6_2.webp 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_3.webp b/images/dice/d6_3.webp
new file mode 100644
index 0000000..69f91b1
Binary files /dev/null and b/images/dice/d6_3.webp 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_4.webp b/images/dice/d6_4.webp
new file mode 100644
index 0000000..1261381
Binary files /dev/null and b/images/dice/d6_4.webp 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_5.webp b/images/dice/d6_5.webp
new file mode 100644
index 0000000..ac3c34a
Binary files /dev/null and b/images/dice/d6_5.webp 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/d6_6.webp b/images/dice/d6_6.webp
new file mode 100644
index 0000000..62a230b
Binary files /dev/null and b/images/dice/d6_6.webp 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/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/dark-stars-actor-sheet.js b/modules/dark-stars-actor-sheet.js
new file mode 100644
index 0000000..4178aef
--- /dev/null
+++ b/modules/dark-stars-actor-sheet.js
@@ -0,0 +1,224 @@
+/**
+ * Extend the basic ActorSheet with some very simple modifications
+ * @extends {ActorSheet}
+ */
+
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+
+/* -------------------------------------------- */
+export class DarkStarsActorSheet extends ActorSheet {
+
+ /** @override */
+ static get defaultOptions() {
+
+ return mergeObject(super.defaultOptions, {
+ classes: ["fvtt-dark-stars", "sheet", "actor"],
+ template: "systems/fvtt-dark-stars/templates/actor-sheet.html",
+ width: 960,
+ height: 720,
+ tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "skills" }],
+ dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
+ editScore: true
+ });
+ }
+
+ /* -------------------------------------------- */
+ async getData() {
+ const objectData = this.object.system
+ let actorData = duplicate(objectData)
+
+ let formData = {
+ title: this.title,
+ id: this.actor.id,
+ type: this.actor.type,
+ img: this.actor.img,
+ name: this.actor.name,
+ editable: this.isEditable,
+ cssClass: this.isEditable ? "editable" : "locked",
+ data: actorData,
+ limited: this.object.limited,
+ skills: this.actor.getSkills( ),
+ weapons: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getWeapons()) ),
+ armors: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getArmors())),
+ shields: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getShields())),
+ spells: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getLore())),
+ equipments: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquipmentsOnly()) ),
+ equippedWeapons: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquippedWeapons()) ),
+ equippedArmor: this.actor.getEquippedArmor(),
+ equippedShield: this.actor.getEquippedShield(),
+ feats: duplicate(this.actor.getFeats()),
+ subActors: duplicate(this.actor.getSubActors()),
+ race: duplicate(this.actor.getRace()),
+ moneys: duplicate(this.actor.getMoneys()),
+ encCapacity: this.actor.getEncumbranceCapacity(),
+ saveRolls: this.actor.getSaveRoll(),
+ conditions: this.actor.getConditions(),
+ description: await TextEditor.enrichHTML(this.object.system.biodata.description, {async: true}),
+ notes: await TextEditor.enrichHTML(this.object.system.biodata.notes, {async: true}),
+ containersTree: this.actor.containersTree,
+ encCurrent: this.actor.encCurrent,
+ 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;
+ }
+
+
+ /* -------------------------------------------- */
+ /** @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")
+ DarkStarsUtility.confirmDelete(this, li)
+ })
+ html.find('.item-add').click(ev => {
+ let dataType = $(ev.currentTarget).data("type")
+ this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true })
+ })
+
+ 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('.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('.roll-ability').click((event) => {
+ const abilityKey = $(event.currentTarget).data("ability-key");
+ this.actor.rollAbility(abilityKey);
+ });
+ html.find('.roll-skill').click((event) => {
+ const li = $(event.currentTarget).parents(".item")
+ const skillId = li.data("item-id")
+ this.actor.rollSkill(skillId)
+ });
+
+ html.find('.roll-weapon').click((event) => {
+ const li = $(event.currentTarget).parents(".item");
+ const skillId = li.data("item-id")
+ this.actor.rollWeapon(skillId)
+ });
+ html.find('.roll-armor-die').click((event) => {
+ this.actor.rollArmorDie()
+ });
+ html.find('.roll-shield-die').click((event) => {
+ this.actor.rollShieldDie()
+ });
+ html.find('.roll-target-die').click((event) => {
+ this.actor.rollDefenseRanged()
+ });
+
+ html.find('.roll-save').click((event) => {
+ const saveKey = $(event.currentTarget).data("save-key")
+ this.actor.rollSave(saveKey)
+ });
+
+
+ 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('.update-field').change(ev => {
+ const fieldName = $(ev.currentTarget).data("field-name");
+ let value = Number(ev.currentTarget.value);
+ this.actor.update( { [`${fieldName}`]: value } );
+ });
+ }
+
+ /* -------------------------------------------- */
+ /** @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!!!!")
+ const item = fromUuidSync(dragData.uuid)
+ if (item == undefined) {
+ item = this.actor.items.get( item.id )
+ }
+ let ret = await this.actor.preprocessItem( event, item, true )
+ if ( ret ) {
+ super._onDropItem(event, dragData)
+ }
+ }
+
+ /* -------------------------------------------- */
+ /** @override */
+ _updateObject(event, formData) {
+ // Update the Actor
+ return this.object.update(formData);
+ }
+}
diff --git a/modules/dark-stars-actor.js b/modules/dark-stars-actor.js
new file mode 100644
index 0000000..ef22ea7
--- /dev/null
+++ b/modules/dark-stars-actor.js
@@ -0,0 +1,808 @@
+/* -------------------------------------------- */
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+import { DarkStarsRollDialog } from "./dark-stars-roll-dialog.js";
+
+/* -------------------------------------------- */
+const coverBonusTable = { "nocover": 0, "lightcover": 2, "heavycover": 4, "entrenchedcover": 6 };
+const statThreatLevel = ["agi", "str", "phy", "com", "def", "per"]
+const __subkey2title = {
+ "melee-dmg": "Melee Damage", "melee-atk": "Melee Attack", "ranged-atk": "Ranged Attack",
+ "ranged-dmg": "Ranged Damage", "dmg-res": "Damare Resistance"
+}
+
+/* -------------------------------------------- */
+/* -------------------------------------------- */
+/**
+ * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
+ * @extends {Actor}
+ */
+export class DarkStarsActor 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 DarkStarsUtility.loadCompendium("fvtt-dark-stars.skills");
+ data.items = skills.map(i => i.toObject())
+ }
+ if (data.type == 'npc') {
+ }
+
+ return super.create(data, options);
+ }
+
+ /* -------------------------------------------- */
+ prepareBaseData() {
+ }
+
+ /* -------------------------------------------- */
+ async prepareData() {
+ super.prepareData();
+ }
+
+ /* -------------------------------------------- */
+ computeHitPoints() {
+ if (this.type == "character") {
+ let hp = duplicate(this.system.secondary.hp)
+ let max = (this.system.abilities.str.value + this.system.abilities.con.value) * 6
+ if (max != hp.max || hp.value > max) {
+ hp.max = max
+ hp.value = max // Init case
+ this.update({ 'system.secondary.hp': hp })
+ }
+ }
+ }
+ /* -------------------------------------------- */
+ computeEffortPoints() {
+ if (this.type == "character") {
+ let effort = duplicate(this.system.secondary.effort)
+ let max = (this.system.abilities.con.value + this.system.abilities.int.value) * 6
+ if (max != effort.max || effort.value > max) {
+ effort.max = max
+ effort.value = max // Init case
+ this.update({ 'system.secondary.effort': effort })
+ }
+ }
+ }
+
+ /* -------------------------------------------- */
+ prepareDerivedData() {
+
+ if (this.type == 'character' || game.user.isGM) {
+ this.system.encCapacity = this.getEncumbranceCapacity()
+ this.buildContainerTree()
+ this.computeHitPoints()
+ this.computeEffortPoints()
+ }
+
+ super.prepareDerivedData();
+ }
+
+ /* -------------------------------------------- */
+ _preUpdate(changed, options, user) {
+
+ super._preUpdate(changed, options, user);
+ }
+
+ /* -------------------------------------------- */
+ getEncumbranceCapacity() {
+ return 1;
+ }
+
+ /* -------------------------------------------- */
+ getMoneys() {
+ let comp = this.items.filter(item => item.type == 'money');
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ /* -------------------------------------------- */
+ getFeats() {
+ let comp = duplicate(this.items.filter(item => item.type == 'feat') || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ /* -------------------------------------------- */
+ getFeatsWithDie() {
+ let comp = duplicate(this.items.filter(item => item.type == 'feat' && item.system.isfeatdie) || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ getFeatsWithSL() {
+ let comp = duplicate(this.items.filter(item => item.type == 'feat' && item.system.issl) || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ /* -------------------------------------------- */
+ getLore() {
+ let comp = duplicate(this.items.filter(item => item.type == 'spell') || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ getEquippedWeapons() {
+ let comp = duplicate(this.items.filter(item => item.type == 'weapon' && item.system.equipped) || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ /* -------------------------------------------- */
+ getArmors() {
+ let comp = duplicate(this.items.filter(item => item.type == 'armor') || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ getEquippedArmor() {
+ let comp = this.items.find(item => item.type == 'armor' && item.system.equipped)
+ if (comp) {
+ return duplicate(comp)
+ }
+ return undefined
+ }
+ /* -------------------------------------------- */
+ getShields() {
+ let comp = duplicate(this.items.filter(item => item.type == 'shield') || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ getEquippedShield() {
+ let comp = this.items.find(item => item.type == 'shield' && item.system.equipped)
+ if (comp) {
+ return duplicate(comp)
+ }
+ return undefined
+ }
+ /* -------------------------------------------- */
+ getRace() {
+ let race = this.items.filter(item => item.type == 'race')
+ return race[0] ?? [];
+ }
+ /* -------------------------------------------- */
+ checkAndPrepareEquipment(item) {
+ }
+
+ /* -------------------------------------------- */
+ checkAndPrepareEquipments(listItem) {
+ for (let item of listItem) {
+ this.checkAndPrepareEquipment(item)
+ }
+ return listItem
+ }
+
+ /* -------------------------------------------- */
+ getConditions() {
+ let comp = duplicate(this.items.filter(item => item.type == 'condition') || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ /* -------------------------------------------- */
+ getWeapons() {
+ let comp = duplicate(this.items.filter(item => item.type == 'weapon') || []);
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp;
+ }
+ /* -------------------------------------------- */
+ getItemById(id) {
+ let item = this.items.find(item => item.id == id);
+ if (item) {
+ item = duplicate(item)
+ }
+ return item;
+ }
+
+ /* -------------------------------------------- */
+ getSkills() {
+ let comp = duplicate(this.items.filter(item => item.type == 'skill') || [])
+ for (let skill of comp) {
+ DarkStarsUtility.updateSkill(skill)
+ }
+ DarkStarsUtility.sortArrayObjectsByName(comp)
+ return comp
+ }
+
+ /* -------------------------------------------- */
+ getRelevantAbility(statKey) {
+ let comp = duplicate(this.items.filter(item => item.type == 'skill' && item.system.ability == ability) || []);
+ return comp;
+ }
+
+
+ /* -------------------------------------------- */
+ async equipItem(itemId) {
+ let item = this.items.find(item => item.id == itemId)
+ if (item && item.system) {
+ if (item.type == "armor") {
+ let armor = this.items.find(item => item.id != itemId && item.type == "armor" && item.system.equipped)
+ if (armor) {
+ ui.notifications.warn("You already have an armor equipped!")
+ return
+ }
+ }
+ if (item.type == "shield") {
+ let shield = this.items.find(item => item.id != itemId && item.type == "shield" && item.system.equipped)
+ if (shield) {
+ ui.notifications.warn("You already have a shield equipped!")
+ return
+ }
+ }
+ let update = { _id: item.id, "system.equipped": !item.system.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.items.filter(item => item.type == 'shield' || item.type == 'armor' || item.type == "weapon" || item.type == "equipment");
+ }
+ /* ------------------------------------------- */
+ getEquipmentsOnly() {
+ return duplicate(this.items.filter(item => item.type == "equipment") || [])
+ }
+
+ /* ------------------------------------------- */
+ getSaveRoll() {
+ return {
+ reflex: {
+ "label": "Reflex Save",
+ "img": "systems/fvtt-dark-stars/images/icons/saves/reflex_save.webp",
+ "value": this.system.abilities.agi.value + this.system.abilities.wit.value
+ },
+ fortitude: {
+ "label": "Fortitude Save",
+ "img": "systems/fvtt-dark-stars/images/icons/saves/fortitude_save.webp",
+ "value": this.system.abilities.str.value + this.system.abilities.con.value
+ },
+ willpower: {
+ "label": "Willpower Save",
+ "img": "systems/fvtt-dark-stars/images/icons/saves/will_save.webp",
+ "value": this.system.abilities.int.value + this.system.abilities.cha.value
+ }
+ }
+ }
+
+ /* ------------------------------------------- */
+ async buildContainerTree() {
+ let equipments = duplicate(this.items.filter(item => item.type == "equipment") || [])
+ for (let equip1 of equipments) {
+ if (equip1.system.iscontainer) {
+ equip1.system.contents = []
+ equip1.system.contentsEnc = 0
+ for (let equip2 of equipments) {
+ if (equip1._id != equip2.id && equip2.system.containerid == equip1.id) {
+ equip1.system.contents.push(equip2)
+ let q = equip2.system.quantity ?? 1
+ equip1.system.contentsEnc += q * equip2.system.weight
+ }
+ }
+ }
+ }
+
+ // Compute whole enc
+ let enc = 0
+ for (let item of equipments) {
+ //item.data.idrDice = DarkStarsUtility.getDiceFromLevel(Number(item.data.idr))
+ if (item.system.equipped) {
+ if (item.system.iscontainer) {
+ enc += item.system.contentsEnc
+ } else if (item.system.containerid == "") {
+ let q = item.system.quantity ?? 1
+ enc += q * item.system.weight
+ }
+ }
+ }
+ for (let item of this.items) { // Process items/shields/armors
+ if ((item.type == "weapon" || item.type == "shield" || item.type == "armor") && item.system.equipped) {
+ let q = item.system.quantity ?? 1
+ enc += q * item.system.weight
+ }
+ }
+
+ // Store local values
+ this.encCurrent = enc
+ this.containersTree = equipments.filter(item => item.system.containerid == "") // Returns the root of equipements without container
+
+ }
+
+ /* -------------------------------------------- */
+ async rollArmor(rollData) {
+ let armor = this.getEquippedArmor()
+ if (armor) {
+
+ }
+ return { armor: "none" }
+ }
+
+ /* -------------------------------------------- */
+ async incDecHP(formula) {
+ let dmgRoll = new Roll(formula+"[dark-starsorange]").roll({ async: false })
+ await DarkStarsUtility.showDiceSoNice(dmgRoll, game.settings.get("core", "rollMode"))
+ let hp = duplicate(this.system.secondary.hp)
+ hp.value = Number(hp.value) + Number(dmgRoll.total)
+ this.update({ 'system.secondary.hp': hp })
+ return Number(dmgRoll.total)
+ }
+
+ /* -------------------------------------------- */
+ getAbility(abilKey) {
+ return this.system.abilities[abilKey];
+ }
+
+ /* -------------------------------------------- */
+ async addObjectToContainer(itemId, containerId) {
+ let container = this.items.find(item => item.id == containerId && item.system.iscontainer)
+ let object = this.items.find(item => item.id == itemId)
+ if (container) {
+ if (object.system.iscontainer) {
+ ui.notifications.warn("Only 1 level of container allowed")
+ return
+ }
+ let alreadyInside = this.items.filter(item => item.system.containerid && item.system.containerid == containerId);
+ if (alreadyInside.length >= container.system.containercapacity) {
+ ui.notifications.warn("Container is already full !")
+ return
+ } else {
+ await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': containerId }])
+ }
+ } else if (object && object.system.containerid) { // remove from container
+ console.log("Removeing: ", object)
+ await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': "" }]);
+ }
+ }
+
+ /* -------------------------------------------- */
+ async preprocessItem(event, item, onDrop = false) {
+ 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)
+ return true
+ }
+
+ /* -------------------------------------------- */
+ async equipGear(equipmentId) {
+ let item = this.items.find(item => item.id == equipmentId);
+ if (item && item.system) {
+ let update = { _id: item.id, "system.equipped": !item.system.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.system.subactors) {
+ subActors.push(duplicate(game.actors.get(id)))
+ }
+ return subActors;
+ }
+ /* -------------------------------------------- */
+ async addSubActor(subActorId) {
+ let subActors = duplicate(this.system.subactors);
+ subActors.push(subActorId);
+ await this.update({ 'system.subactors': subActors });
+ }
+ /* -------------------------------------------- */
+ async delSubActor(subActorId) {
+ let newArray = [];
+ for (let id of this.system.subactors) {
+ if (id != subActorId) {
+ newArray.push(id);
+ }
+ }
+ await this.update({ 'system.subactors': newArray });
+ }
+
+ /* -------------------------------------------- */
+ syncRoll(rollData) {
+ this.lastRollId = rollData.rollId;
+ DarkStarsUtility.saveRollData(rollData);
+ }
+
+ /* -------------------------------------------- */
+ getOneSkill(skillId) {
+ let skill = this.items.find(item => item.type == 'skill' && item.id == skillId)
+ if (skill) {
+ skill = duplicate(skill);
+ }
+ return skill;
+ }
+
+ /* -------------------------------------------- */
+ async deleteAllItemsByType(itemType) {
+ let items = this.items.filter(item => item.type == itemType);
+ await this.deleteEmbeddedDocuments('Item', items);
+ }
+
+ /* -------------------------------------------- */
+ async addItemWithoutDuplicate(newItem) {
+ let item = this.items.find(item => item.type == newItem.type && item.name.toLowerCase() == newItem.name.toLowerCase())
+ if (!item) {
+ await this.createEmbeddedDocuments('Item', [newItem]);
+ }
+ }
+
+ /* -------------------------------------------- */
+ async incrementSkillExp(skillId, inc) {
+ let skill = this.items.get(skillId)
+ if (skill) {
+ await this.updateEmbeddedDocuments('Item', [{ _id: skill.id, 'system.exp': skill.system.exp + inc }])
+ let chatData = {
+ user: game.user.id,
+ rollMode: game.settings.get("core", "rollMode"),
+ whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')),
+ content: `
${this.name} has gained 1 exp in the skill ${skill.name} (exp = ${skill.system.exp})
= 25) {
+ await this.updateEmbeddedDocuments('Item', [{ _id: skill.id, 'system.exp': 0, 'system.explevel': skill.system.explevel + 1 }])
+ let chatData = {
+ user: game.user.id,
+ rollMode: game.settings.get("core", "rollMode"),
+ whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM')),
+ content: `${this.name} has gained 1 exp SL in the skill ${skill.name} (new exp SL : ${skill.system.explevel}) !
= 0) {
+ const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantity': newQ }]) // pdates one EmbeddedEntity
+ }
+ }
+ }
+ /* -------------------------------------------- */
+ async incDecAmmo(objetId, incDec = 0) {
+ let objetQ = this.items.get(objetId)
+ if (objetQ) {
+ let newQ = objetQ.system.ammocurrent + incDec;
+ if (newQ >= 0 && newQ <= objetQ.system.ammomax) {
+ const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.ammocurrent': newQ }]); // pdates one EmbeddedEntity
+ }
+ }
+ }
+
+ /* -------------------------------------------- */
+ isForcedAdvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.advantage)
+ }
+ isForcedDisadvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.disadvantage)
+ }
+ isForcedRollAdvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.rolladvantage)
+ }
+ isForcedRollDisadvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.rolldisadvantage)
+ }
+ isNoAdvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.noadvantage)
+ }
+ isNoAction() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.noaction)
+ }
+ isAttackDisadvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.attackdisadvantage)
+ }
+ isDefenseDisadvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.defensedisadvantage)
+ }
+ isAttackerAdvantage() {
+ return this.items.find(cond => cond.type == "condition" && cond.system.targetadvantage)
+ }
+
+ /* -------------------------------------------- */
+ getCommonRollData(abilityKey = undefined) {
+ let noAction = this.isNoAction()
+ if (noAction) {
+ ui.notifications.warn("You can't do any actions du to the condition : " + noAction.name)
+ return
+ }
+
+ let rollData = DarkStarsUtility.getBasicRollData()
+ rollData.alias = this.name
+ rollData.actorImg = this.img
+ rollData.actorId = this.id
+ rollData.img = this.img
+ rollData.featsDie = this.getFeatsWithDie()
+ rollData.featsSL = this.getFeatsWithSL()
+ rollData.armors = this.getArmors()
+ rollData.conditions = this.getConditions()
+ rollData.featDieName = "none"
+ rollData.featSLName = "none"
+ rollData.rollAdvantage = "none"
+ rollData.advantage = "none"
+ rollData.disadvantage = "none"
+ rollData.forceAdvantage = this.isForcedAdvantage()
+ rollData.forceDisadvantage = this.isForcedDisadvantage()
+ rollData.forceRollAdvantage = this.isForcedRollAdvantage()
+ rollData.forceRollDisadvantage = this.isForcedRollDisadvantage()
+ rollData.noAdvantage = this.isNoAdvantage()
+ if (rollData.defenderTokenId) {
+ let defenderToken = game.canvas.tokens.get(rollData.defenderTokenId)
+ let defender = defenderToken.actor
+
+ // Distance management
+ let token = this.token
+ if (!token) {
+ let tokens = this.getActiveTokens()
+ token = tokens[0]
+ }
+ if (token) {
+ const ray = new Ray(token.object?.center || token.center, defenderToken.center)
+ rollData.tokensDistance = canvas.grid.measureDistances([{ ray }], { gridSpaces: false })[0] / canvas.grid.grid.options.dimensions.distance
+ } else {
+ ui.notifications.info("No token connected to this actor, unable to compute distance.")
+ return
+ }
+ if (defender) {
+ rollData.forceAdvantage = defender.isAttackerAdvantage()
+ rollData.advantageFromTarget = true
+ }
+ }
+
+ if (abilityKey) {
+ rollData.ability = this.getAbility(abilityKey)
+ rollData.selectedKill = undefined
+ }
+
+ console.log("ROLLDATA", rollData)
+
+ return rollData
+ }
+
+ /* -------------------------------------------- */
+ rollAbility(abilityKey) {
+ let rollData = this.getCommonRollData(abilityKey)
+ rollData.mode = "ability"
+ if (rollData.target) {
+ ui.notifications.warn("You are targetting a token with a skill : please use a Weapon instead.")
+ return
+ }
+ DarkStarsUtility.rollDarkStars(rollData)
+ }
+
+ /* -------------------------------------------- */
+ rollSkill(skillId) {
+ let skill = this.items.get(skillId)
+ if (skill) {
+ if (skill.system.islore && skill.system.level == 0) {
+ ui.notifications.warn("You can't use Lore Skills with a SL of 0.")
+ return
+ }
+ skill = duplicate(skill)
+ DarkStarsUtility.updateSkill(skill)
+ let abilityKey = skill.system.ability
+ let rollData = this.getCommonRollData(abilityKey)
+ rollData.mode = "skill"
+ rollData.skill = skill
+ rollData.img = skill.img
+ if (rollData.target) {
+ ui.notifications.warn("You are targetting a token with a skill : please use a Weapon instead.")
+ return
+ }
+ this.startRoll(rollData)
+ }
+ }
+
+ /* -------------------------------------------- */
+ rollWeapon(weaponId) {
+ let weapon = this.items.get(weaponId)
+ if (weapon) {
+ weapon = duplicate(weapon)
+ let skill = this.items.find(item => item.name.toLowerCase() == weapon.system.skill.toLowerCase())
+ if (skill) {
+ skill = duplicate(skill)
+ DarkStarsUtility.updateSkill(skill)
+ let abilityKey = skill.system.ability
+ let rollData = this.getCommonRollData(abilityKey)
+ rollData.mode = "weapon"
+ rollData.skill = skill
+ rollData.weapon = weapon
+ rollData.img = weapon.img
+ if (!rollData.forceDisadvantage) { // This is an attack, check if disadvantaged
+ rollData.forceDisadvantage = this.isAttackDisadvantage()
+ }
+ /*if (rollData.weapon.system.isranged && rollData.tokensDistance > DarkStarsUtility.getWeaponMaxRange(rollData.weapon) ) {
+ ui.notifications.warn(`Your target is out of range of your weapon (max: ${DarkStarsUtility.getWeaponMaxRange(rollData.weapon)} - current : ${rollData.tokensDistance})` )
+ return
+ }*/
+ this.startRoll(rollData)
+ } else {
+ ui.notifications.warn("Unable to find the relevant skill for weapon " + weapon.name)
+ }
+ }
+ }
+
+ /* -------------------------------------------- */
+ rollDefenseMelee(attackRollData) {
+ let weapon = this.items.get(attackRollData.defenseWeaponId)
+ if (weapon) {
+ weapon = duplicate(weapon)
+ let skill = this.items.find(item => item.name.toLowerCase() == weapon.system.skill.toLowerCase())
+ if (skill) {
+ skill = duplicate(skill)
+ DarkStarsUtility.updateSkill(skill)
+ let abilityKey = skill.system.ability
+ let rollData = this.getCommonRollData(abilityKey)
+ rollData.defenderTokenId = undefined // Cleanup
+ rollData.mode = "weapondefense"
+ rollData.shield = this.getEquippedShield()
+ rollData.attackRollData = duplicate(attackRollData)
+ rollData.skill = skill
+ rollData.weapon = weapon
+ rollData.img = weapon.img
+ if (!rollData.forceDisadvantage) { // This is an attack, check if disadvantaged
+ rollData.forceDisadvantage = this.isDefenseDisadvantage()
+ }
+
+ this.startRoll(rollData)
+ } else {
+ ui.notifications.warn("Unable to find the relevant skill for weapon " + weapon.name)
+ }
+ } else {
+ ui.notifications.warn("Weapon not found ! ")
+ }
+ }
+
+ /* -------------------------------------------- */
+ rollDefenseRanged(attackRollData) {
+ let rollData = this.getCommonRollData()
+ rollData.defenderTokenId = undefined // Cleanup
+ rollData.mode = "rangeddefense"
+ if ( attackRollData) {
+ rollData.attackRollData = duplicate(attackRollData)
+ rollData.effectiveRange = DarkStarsUtility.getWeaponRange(attackRollData.weapon)
+ rollData.tokensDistance = attackRollData.tokensDistance // QoL copy
+ }
+ rollData.sizeDice = DarkStarsUtility.getSizeDice(this.system.biodata.size)
+ rollData.distanceBonusDice = 0 //Math.max(0, Math.floor((rollData.tokensDistance - rollData.effectiveRange) + 0.5))
+ rollData.hasCover = "none"
+ rollData.situational = "none"
+ rollData.useshield = false
+ rollData.shield = this.getEquippedShield()
+ this.startRoll(rollData)
+ }
+
+ /* -------------------------------------------- */
+ rollShieldDie() {
+ let shield = this.getEquippedShield()
+ if (shield) {
+ shield = duplicate(shield)
+ let rollData = this.getCommonRollData()
+ rollData.mode = "shield"
+ rollData.shield = shield
+ rollData.useshield = true
+ rollData.img = shield.img
+ this.startRoll(rollData)
+ }
+ }
+
+ /* -------------------------------------------- */
+ async rollArmorDie(rollData = undefined) {
+ let armor = this.getEquippedArmor()
+ if (armor) {
+ armor = duplicate(armor)
+ let reduce = 0
+ let multiply = 1
+ let disadvantage = false
+ let advantage = false
+ let messages = ["Armor applied"]
+
+ if (rollData) {
+ if (DarkStarsUtility.isArmorLight(armor) && DarkStarsUtility.isWeaponPenetrating(rollData.attackRollData.weapon)) {
+ return { armorIgnored: true, nbSuccess: 0, messages: ["Armor ignored : Penetrating weapons ignore Light Armors."] }
+ }
+ if (DarkStarsUtility.isWeaponPenetrating(rollData.attackRollData.weapon)) {
+ messages.push("Armor reduced by 1 (Penetrating weapon)")
+ reduce = 1
+ }
+ if (DarkStarsUtility.isWeaponLight(rollData.attackRollData.weapon)) {
+ messages.push("Armor with advantage (Light weapon)")
+ advantage = true
+ }
+ if (DarkStarsUtility.isWeaponHeavy(rollData.attackRollData.weapon)) {
+ messages.push("Armor with disadvantage (Heavy weapon)")
+ disadvantage = true
+ }
+ if (DarkStarsUtility.isWeaponHack(rollData.attackRollData.weapon)) {
+ messages.push("Armor reduced by 1 (Hack weapon)")
+ reduce = 1
+ }
+ if (DarkStarsUtility.isWeaponUndamaging(rollData.attackRollData.weapon)) {
+ messages.push("Armor multiplied by 2 (Undamaging weapon)")
+ multiply = 2
+ }
+ }
+ let diceColor = armor.system.absorprionroll
+ let armorResult = await DarkStarsUtility.getRollTableFromDiceColor(diceColor, false)
+ console.log("Armor log", armorResult)
+ let armorValue = Math.max(0, (Number(armorResult.text) + reduce) * multiply)
+ if (advantage || disadvantage) {
+ let armorResult2 = await DarkStarsUtility.getRollTableFromDiceColor(diceColor, false)
+ let armorValue2 = Math.max(0, (Number(armorResult2.text) + reduce) * multiply)
+ if (advantage) {
+ armorValue = (armorValue2 > armorValue) ? armorValue2 : armorValue
+ messages.push(`Armor advantage - Roll 1 = ${armorValue} - Roll 2 = ${armorValue2}`)
+ }
+ if (disadvantage) {
+ armorValue = (armorValue2 < armorValue) ? armorValue2 : armorValue
+ messages.push(`Armor disadvantage - Roll 1 = ${armorValue} - Roll 2 = ${armorValue2}`)
+ }
+ }
+ armorResult.armorValue = armorValue
+ if (!rollData) {
+ ChatMessage.create({ content: "Armor result : " + armorValue })
+ }
+ messages.push("Armor result : " + armorValue)
+ return { armorIgnored: false, nbSuccess: armorValue, rawArmor: armorResult.text, messages: messages }
+ }
+ return { armorIgnored: true, nbSuccess: 0, messages: ["No armor equipped."] }
+ }
+
+ /* -------------------------------------------- */
+ rollSave(saveKey) {
+ let saves = this.getSaveRoll()
+ let save = saves[saveKey]
+ if (save) {
+ save = duplicate(save)
+ let rollData = this.getCommonRollData()
+ rollData.mode = "save"
+ rollData.save = save
+ if (rollData.target) {
+ ui.notifications.warn("You are targetting a token with a save roll - Not authorized.")
+ return
+ }
+ this.startRoll(rollData)
+ }
+
+ }
+ /* -------------------------------------------- */
+ async startRoll(rollData) {
+ this.syncRoll(rollData)
+ let rollDialog = await DarkStarsRollDialog.create(this, rollData)
+ rollDialog.render(true)
+ }
+
+}
diff --git a/modules/dark-stars-combat.js b/modules/dark-stars-combat.js
new file mode 100644
index 0000000..e13d1d8
--- /dev/null
+++ b/modules/dark-stars-combat.js
@@ -0,0 +1,30 @@
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+
+/* -------------------------------------------- */
+export class DarkStarsCombat 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()
+ }
+ }
+
+}
diff --git a/modules/dark-stars-commands.js b/modules/dark-stars-commands.js
new file mode 100644
index 0000000..f096902
--- /dev/null
+++ b/modules/dark-stars-commands.js
@@ -0,0 +1,144 @@
+/* -------------------------------------------- */
+
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+import { DarkStarsRollDialog } from "./dark-stars-roll-dialog.js";
+
+/* -------------------------------------------- */
+const __saveFirstToKey = { r: "reflex", f: "fortitude", w: "willpower"}
+
+/* -------------------------------------------- */
+export class DarkStarsCommands {
+
+ static init() {
+ if (!game.system.cruciblerpg.commands) {
+ const crucibleCommands = new DarkStarsCommands();
+ crucibleCommands.registerCommand({ path: ["/rtarget"], func: (content, msg, params) => DarkStarsCommands.rollTarget(msg, params), descr: "Launch the target roll window" });
+ crucibleCommands.registerCommand({ path: ["/rsave"], func: (content, msg, params) => DarkStarsCommands.rollSave(msg, params), descr: "Performs a save roll" });
+ game.system.cruciblerpg.commands = crucibleCommands;
+ }
+ }
+
+ 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("crucibleCommands._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) {
+ DarkStarsCommands._chatAnswer(msg, command.descr);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /* -------------------------------------------- */
+ static _chatAnswer(msg, content) {
+ msg.whisper = [game.user.id];
+ msg.content = content;
+ ChatMessage.create(msg);
+ }
+
+ /* -------------------------------------------- */
+ static rollTarget(msg, params) {
+ const speaker = ChatMessage.getSpeaker()
+ let actor
+ if (speaker.token) actor = game.actors.tokens[speaker.token]
+ if (!actor) actor = game.actors.get(speaker.actor)
+ if (!actor) {
+ return ui.notifications.warn(`Select your actor to run the macro`)
+ }
+ actor.rollDefenseRanged()
+ }
+
+ /* -------------------------------------------- */
+ static rollSave(msg, params) {
+ console.log(msg, params)
+ if ( params.length == 0) {
+ ui.notifications.warn("/rsave command error : syntax is /rsave reflex, /rsave fortitude or /rsave willpower")
+ return
+ }
+ let saveKey = params[0].toLowerCase()
+ if ( saveKey.length > 0 && (saveKey[0] == "r" || saveKey[0] == "f" || saveKey[0] == "w")) {
+ const speaker = ChatMessage.getSpeaker()
+ let actor
+ if (speaker.token) actor = game.actors.tokens[speaker.token]
+ if (!actor) actor = game.actors.get(speaker.actor)
+ if (!actor) {
+ return ui.notifications.warn(`Select your actor to run the macro`)
+ }
+ actor.rollSave( __saveFirstToKey[saveKey[0]] )
+ } else {
+ ui.notifications.warn("/rsave syntax error : syntax is /rsave reflex, /rsave fortitude or /rsave willpower")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/dark-stars-hotbar.js b/modules/dark-stars-hotbar.js
new file mode 100644
index 0000000..708f81a
--- /dev/null
+++ b/modules/dark-stars-hotbar.js
@@ -0,0 +1,68 @@
+
+export class DarkStarsHotbar {
+
+ static async addToHotbar(item, slot) {
+ let command = `game.system.cruciblerpg.DarkStarsHotbar.rollMacro("${item.name}", "${item.type}");`;
+ let macro = game.macros.contents.find(m => (m.name === item.name) && (m.command === command));
+ if (!macro) {
+ macro = await Macro.create({
+ name: item.name,
+ type: "script",
+ img: item.img,
+ command: command
+ }, { displaySheet: false })
+ }
+ await game.user.assignHotbarMacro(macro, slot);
+ }
+
+ /**
+ * Create a macro when dropping an entity on the hotbar
+ * Item - open roll dialog for item
+ * Actor - open actor sheet
+ * Journal - open journal sheet
+ */
+ static initDropbar() {
+
+ Hooks.on("hotbarDrop", (bar, documentData, slot) => {
+
+ // Create item macro if rollable item - weapon, spell, prayer, trait, or skill
+ if (documentData.type == "Item") {
+ let item = fromUuidSync(documentData.uuid)
+ if (item == undefined) {
+ item = this.actor.items.get(documentData.uuid)
+ }
+ if (item && (item.type =="weapon" || item.type =="skill")) {
+ this.addToHotbar(item, slot)
+ return false
+ }
+ }
+
+ return true;
+ });
+ }
+
+ /** Roll macro */
+ static rollMacro(itemName, itemType, bypassData) {
+ const speaker = ChatMessage.getSpeaker()
+ let actor
+ if (speaker.token) actor = game.actors.tokens[speaker.token]
+ if (!actor) actor = game.actors.get(speaker.actor)
+ if (!actor) {
+ return ui.notifications.warn(`Select your actor to run the macro`)
+ }
+
+ let item = actor.items.find(it => it.name === itemName && it.type == itemType)
+ if (!item) {
+ return ui.notifications.warn(`Unable to find the item of the macro in the current actor`)
+ }
+
+ // Trigger the item roll
+ if (item.type === "weapon") {
+ return actor.rollWeapon(item.id)
+ }
+ if (item.type === "skill") {
+ return actor.rollSkill(item.id)
+ }
+ }
+
+}
diff --git a/modules/dark-stars-item-sheet.js b/modules/dark-stars-item-sheet.js
new file mode 100644
index 0000000..46c8bfa
--- /dev/null
+++ b/modules/dark-stars-item-sheet.js
@@ -0,0 +1,170 @@
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+
+/**
+ * Extend the basic ItemSheet with some very simple modifications
+ * @extends {ItemSheet}
+ */
+export class DarkStarsItemSheet extends ItemSheet {
+
+ /** @override */
+ static get defaultOptions() {
+
+ return mergeObject(super.defaultOptions, {
+ classes: ["fvtt-dark-stars", "sheet", "item"],
+ template: "systems/fvtt-dark-stars/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() {
+
+ if ( this.object.type == "skill") {
+ DarkStarsUtility.updateSkill(this.object)
+ }
+ let objectData = duplicate(this.object.system)
+
+ let itemData = objectData
+ let formData = {
+ title: this.title,
+ id: this.id,
+ type: this.object.type,
+ img: this.object.img,
+ name: this.object.name,
+ editable: this.isEditable,
+ cssClass: this.isEditable ? "editable" : "locked",
+ weaponSkills: DarkStarsUtility.getWeaponSkills(),
+ shieldSkills: DarkStarsUtility.getShieldSkills(),
+ description: await TextEditor.enrichHTML(this.object.system.description, {async: true}),
+ data: itemData,
+ limited: this.object.limited,
+ options: this.options,
+ owner: this.document.isOwner,
+ isGM: game.user.isGM
+ }
+
+ this.options.editable = !(this.object.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(DarkStarsUtility.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-dark-stars/templates/post-item.html', chatData).then(html => {
+ let chatOptions = DarkStarsUtility.chatDataSetup(html);
+ ChatMessage.create(chatOptions)
+ });
+ }
+
+ /* -------------------------------------------- */
+ /** @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);
+ });
+
+ // 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();
+ });
+
+ }
+
+
+
+ /* -------------------------------------------- */
+ get template() {
+ let type = this.item.type;
+ return `systems/fvtt-dark-stars/templates/item-${type}-sheet.html`;
+ }
+
+ /* -------------------------------------------- */
+ /** @override */
+ _updateObject(event, formData) {
+ return this.object.update(formData)
+ }
+}
\ No newline at end of file
diff --git a/modules/dark-stars-item.js b/modules/dark-stars-item.js
new file mode 100644
index 0000000..b824946
--- /dev/null
+++ b/modules/dark-stars-item.js
@@ -0,0 +1,25 @@
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+
+export const defaultItemImg = {
+ skill: "systems/fvtt-dark-stars/images/icons/icon_skill.webp",
+ armor: "systems/fvtt-dark-stars/images/icons/icon_armour.webp",
+ weapon: "systems/fvtt-dark-stars/images/icons/icon_weapon.webp",
+ equipment: "systems/fvtt-dark-stars/images/icons/icon_equipment.webp",
+ race: "systems/fvtt-dark-stars/images/icons/icon_race.webp",
+ money: "systems/fvtt-dark-stars/images/icons/icon_money.webp",
+}
+
+/**
+ * Extend the basic ItemSheet with some very simple modifications
+ * @extends {ItemSheet}
+ */
+export class DarkStarsItem extends Item {
+
+ constructor(data, context) {
+ if (!data.img) {
+ data.img = defaultItemImg[data.type];
+ }
+ super(data, context);
+ }
+
+}
diff --git a/modules/dark-stars-main.js b/modules/dark-stars-main.js
new file mode 100644
index 0000000..1238788
--- /dev/null
+++ b/modules/dark-stars-main.js
@@ -0,0 +1,116 @@
+/**
+ * DarkStars system
+ * Author: Uberwald
+ * Software License: Prop
+ */
+
+/* -------------------------------------------- */
+
+/* -------------------------------------------- */
+// Import Modules
+import { DarkStarsActor } from "./dark-stars-actor.js";
+import { DarkStarsItemSheet } from "./dark-stars-item-sheet.js";
+import { DarkStarsActorSheet } from "./dark-stars-actor-sheet.js";
+import { DarkStarsNPCSheet } from "./dark-stars-npc-sheet.js";
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+import { DarkStarsCombat } from "./dark-stars-combat.js";
+import { DarkStarsItem } from "./dark-stars-item.js";
+import { DarkStarsHotbar } from "./dark-star-shotbar.js"
+import { DarkStarsCommands } from "./dark-stars-commands.js"
+
+/* -------------------------------------------- */
+/* Foundry VTT Initialization */
+/* -------------------------------------------- */
+
+/************************************************************************************/
+Hooks.once("init", async function () {
+
+ console.log(`Initializing DarkStars RPG`);
+
+ game.system.darkstars = {
+ DarkStarsCommands
+ }
+
+ /* -------------------------------------------- */
+ // preload handlebars templates
+ DarkStarsUtility.preloadHandlebarsTemplates();
+
+ /* -------------------------------------------- */
+ // Set an initiative formula for the system
+ CONFIG.Combat.initiative = {
+ formula: "1d6",
+ decimals: 1
+ };
+
+ /* -------------------------------------------- */
+ game.socket.on("system.fvtt-dark-stars", data => {
+ DarkStarsUtility.onSocketMesssage(data)
+ });
+
+ /* -------------------------------------------- */
+ // Define custom Entity classes
+ CONFIG.Combat.documentClass = DarkStarsCombat
+ CONFIG.Actor.documentClass = DarkStarsActor
+ CONFIG.Item.documentClass = DarkStarsItem
+
+ /* -------------------------------------------- */
+ // Register sheet application classes
+ Actors.unregisterSheet("core", ActorSheet);
+ Actors.registerSheet("fvtt-dark-stars", DarkStarsActorSheet, { types: ["character"], makeDefault: true });
+ Actors.registerSheet("fvtt-dark-stars", DarkStarsNPCSheet, { types: ["npc"], makeDefault: false });
+
+ Items.unregisterSheet("core", ItemSheet);
+ Items.registerSheet("fvtt-dark-stars", DarkStarsItemSheet, { makeDefault: true });
+
+ DarkStarsUtility.init()
+});
+
+/* -------------------------------------------- */
+function welcomeMessage() {
+ ChatMessage.create({
+ user: game.user.id,
+ whisper: [game.user.id],
+ content: `
+ Welcome to the DarkStars RPG.
+ ` });
+}
+
+/* -------------------------------------------- */
+/* Foundry VTT Initialization */
+/* -------------------------------------------- */
+Hooks.once("ready", function () {
+
+ // 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();
+ DarkStarsUtility.ready()
+ DarkStarsCommands.init()
+})
+
+/* -------------------------------------------- */
+/* Foundry VTT Initialization */
+/* -------------------------------------------- */
+Hooks.on("chatMessage", (html, content, msg) => {
+ if (content[0] == '/') {
+ let regExp = /(\S+)/g;
+ let commands = content.match(regExp);
+ if (game.system.cruciblerpg.commands.processChatCommand(commands, content, msg)) {
+ return false;
+ }
+ }
+ return true;
+});
+
diff --git a/modules/dark-stars-npc-sheet.js b/modules/dark-stars-npc-sheet.js
new file mode 100644
index 0000000..fe219c5
--- /dev/null
+++ b/modules/dark-stars-npc-sheet.js
@@ -0,0 +1,209 @@
+/**
+ * Extend the basic ActorSheet with some very simple modifications
+ * @extends {ActorSheet}
+ */
+
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+
+/* -------------------------------------------- */
+export class DarkStarsNPCSheet extends ActorSheet {
+
+ /** @override */
+ static get defaultOptions() {
+
+ return mergeObject(super.defaultOptions, {
+ classes: ["dark-stars-rpg", "sheet", "actor"],
+ template: "systems/fvtt-dark-stars/templates/npc-sheet.html",
+ width: 640,
+ height: 720,
+ tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
+ dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
+ editScore: true
+ });
+ }
+
+ /* -------------------------------------------- */
+ async getData() {
+ const objectData = this.object.system
+ let actorData = duplicate(objectData)
+
+ let formData = {
+ title: this.title,
+ id: this.actor.id,
+ type: this.actor.type,
+ img: this.actor.img,
+ name: this.actor.name,
+ editable: this.isEditable,
+ cssClass: this.isEditable ? "editable" : "locked",
+ data: actorData,
+ limited: this.object.limited,
+ skills: this.actor.getSkills( ),
+ weapons: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getWeapons()) ),
+ armors: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getArmors())),
+ shields: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getShields())),
+ spells: this.actor.checkAndPrepareEquipments( duplicate(this.actor.getLore())),
+ equipments: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquipmentsOnly()) ),
+ equippedWeapons: this.actor.checkAndPrepareEquipments(duplicate(this.actor.getEquippedWeapons()) ),
+ equippedArmor: this.actor.getEquippedArmor(),
+ equippedShield: this.actor.getEquippedShield(),
+ feats: duplicate(this.actor.getFeats()),
+ subActors: duplicate(this.actor.getSubActors()),
+ race: duplicate(this.actor.getRace()),
+ moneys: duplicate(this.actor.getMoneys()),
+ encCapacity: this.actor.getEncumbranceCapacity(),
+ saveRolls: this.actor.getSaveRoll(),
+ conditions: this.actor.getConditions(),
+ containersTree: this.actor.containersTree,
+ encCurrent: this.actor.encCurrent,
+ 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;
+ }
+
+ /* -------------------------------------------- */
+ /** @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")
+ DarkStarsUtility.confirmDelete(this, li)
+ })
+ html.find('.item-add').click(ev => {
+ let dataType = $(ev.currentTarget).data("type")
+ this.actor.createEmbeddedDocuments('Item', [{ name: "NewItem", type: dataType }], { renderSheet: true })
+ })
+
+ 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('.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('.roll-ability').click((event) => {
+ const abilityKey = $(event.currentTarget).data("ability-key");
+ this.actor.rollAbility(abilityKey);
+ });
+ html.find('.roll-skill').click((event) => {
+ const li = $(event.currentTarget).parents(".item")
+ const skillId = li.data("item-id")
+ this.actor.rollSkill(skillId)
+ });
+
+ html.find('.roll-weapon').click((event) => {
+ const li = $(event.currentTarget).parents(".item");
+ const skillId = li.data("item-id")
+ this.actor.rollWeapon(skillId)
+ });
+ html.find('.roll-armor-die').click((event) => {
+ this.actor.rollArmorDie()
+ });
+ html.find('.roll-shield-die').click((event) => {
+ this.actor.rollShieldDie()
+ });
+ html.find('.roll-target-die').click((event) => {
+ this.actor.rollDefenseRanged()
+ });
+
+ html.find('.roll-save').click((event) => {
+ const saveKey = $(event.currentTarget).data("save-key")
+ this.actor.rollSave(saveKey)
+ });
+
+
+ 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('.update-field').change(ev => {
+ const fieldName = $(ev.currentTarget).data("field-name");
+ let value = Number(ev.currentTarget.value);
+ this.actor.update( { [`${fieldName}`]: value } );
+ });
+
+ }
+
+ /* -------------------------------------------- */
+ /** @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;
+ }
+
+ /* -------------------------------------------- */
+ /** @override */
+ _updateObject(event, formData) {
+ // Update the Actor
+ return this.object.update(formData);
+ }
+}
diff --git a/modules/dark-stars-roll-dialog.js b/modules/dark-stars-roll-dialog.js
new file mode 100644
index 0000000..2bab220
--- /dev/null
+++ b/modules/dark-stars-roll-dialog.js
@@ -0,0 +1,84 @@
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+
+export class DarkStarsRollDialog extends Dialog {
+
+ /* -------------------------------------------- */
+ static async create(actor, rollData) {
+
+ let options = { classes: ["DarkStarsDialog"], width: 540, height: 340, 'z-index': 99999 };
+ let html = await renderTemplate('systems/fvtt-dark-stars/templates/roll-dialog-generic.html', rollData);
+
+ return new DarkStarsRollDialog(actor, rollData, html, options);
+ }
+
+ /* -------------------------------------------- */
+ constructor(actor, rollData, html, options, close = undefined) {
+ let conf = {
+ title: (rollData.mode == "skill") ? "Skill" : "Attribute",
+ 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() {
+ DarkStarsUtility.rollDarkStars(this.rollData)
+ }
+
+ /* -------------------------------------------- */
+ async refreshDialog() {
+ const content = await renderTemplate("systems/fvtt-dark-stars/templates/roll-dialog-generic.html", this.rollData)
+ this.data.content = content
+ this.render(true)
+ }
+
+ /* -------------------------------------------- */
+ activateListeners(html) {
+ super.activateListeners(html);
+
+ var dialog = this;
+ function onLoad() {
+ }
+ $(function () { onLoad(); });
+
+ html.find('#advantage').change((event) => {
+ this.rollData.advantage = event.currentTarget.value
+ })
+ html.find('#disadvantage').change((event) => {
+ this.rollData.disadvantage = event.currentTarget.value
+ })
+ html.find('#rollAdvantage').change((event) => {
+ this.rollData.rollAdvantage = event.currentTarget.value
+ })
+ html.find('#useshield').change((event) => {
+ this.rollData.useshield = event.currentTarget.checked
+ })
+ html.find('#hasCover').change((event) => {
+ this.rollData.hasCover = event.currentTarget.value
+ })
+ html.find('#situational').change((event) => {
+ this.rollData.situational = event.currentTarget.value
+ })
+ html.find('#distanceBonusDice').change((event) => {
+ this.rollData.distanceBonusDice = Number(event.currentTarget.value)
+ })
+
+ }
+}
\ No newline at end of file
diff --git a/modules/dark-stars-token.js b/modules/dark-stars-token.js
new file mode 100644
index 0000000..6340ca8
--- /dev/null
+++ b/modules/dark-stars-token.js
@@ -0,0 +1,6 @@
+import { DarkStarsUtility } from "./dark-stars-utility.js";
+
+/* -------------------------------------------- */
+export class DarkStarsToken extends Token {
+
+}
diff --git a/modules/dark-stars-utility.js b/modules/dark-stars-utility.js
new file mode 100644
index 0000000..9c963b1
--- /dev/null
+++ b/modules/dark-stars-utility.js
@@ -0,0 +1,595 @@
+/* -------------------------------------------- */
+import { DarkStarsCombat } from "./dark-stars-combat.js";
+import { DarkStarsCommands } from "./dark-stars-commands.js";
+
+/* -------------------------------------------- */
+
+/* -------------------------------------------- */
+export class DarkStarsUtility {
+
+
+ /* -------------------------------------------- */
+ static async init() {
+ Hooks.on('renderChatLog', (log, html, data) => DarkStarsUtility.chatListeners(html));
+ /*Hooks.on("dropCanvasData", (canvas, data) => {
+ DarkStarsUtility.dropItemOnToken(canvas, data)
+ });*/
+
+ DarkStarsCommands.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);
+ })
+
+ this.gameSettings()
+
+ }
+
+ /*-------------------------------------------- */
+ static gameSettings() {
+ /*game.settings.register("fvtt-dark-stars", "dice-color-skill", {
+ name: "Dice color for skills",
+ hint: "Set the dice color for skills",
+ scope: "world",
+ config: true,
+ requiresReload: true ,
+ default: "#101010",
+ type: String
+ })
+
+ Hooks.on('renderSettingsConfig', (event) => {
+ const element = event.element[0].querySelector(`[name='fvtt-dark-stars.dice-color-skill']`)
+ if (!element) return
+ // Replace placeholder element
+ console.log("Element Found !!!!")
+ }) */
+ }
+
+ /*-------------------------------------------- */
+ static upperFirst(text) {
+ if (typeof text !== 'string') return text
+ return text.charAt(0).toUpperCase() + text.slice(1)
+ }
+
+ /*-------------------------------------------- */
+ static getSkills() {
+ return duplicate(this.skills)
+ }
+ /*-------------------------------------------- */
+ static getWeaponSkills() {
+ return duplicate(this.weaponSkills)
+ }
+ /*-------------------------------------------- */
+ static getShieldSkills() {
+ return duplicate(this.shieldSkills)
+ }
+
+ /* -------------------------------------------- */
+ static async ready() {
+ const skills = await DarkStarsUtility.loadCompendium("fvtt-dark-stars.skills")
+ this.skills = skills.map(i => i.toObject())
+ this.weaponSkills = duplicate(this.skills.filter(item => item.system.isweaponskill))
+ this.shieldSkills = duplicate(this.skills.filter(item => item.system.isshieldskill))
+
+ const rollTables = await DarkStarsUtility.loadCompendium("fvtt-dark-stars.rolltables")
+ this.rollTables = rollTables.map(i => i.toObject())
+
+ }
+
+ /* -------------------------------------------- */
+ static async loadCompendiumData(compendium) {
+ const pack = game.packs.get(compendium)
+ return await pack?.getDocuments() ?? []
+ }
+
+ /* -------------------------------------------- */
+ static async loadCompendium(compendium, filter = item => true) {
+ let compendiumData = await DarkStarsUtility.loadCompendiumData(compendium)
+ return compendiumData.filter(filter)
+ }
+
+
+ /* -------------------------------------------- */
+ static async chatListeners(html) {
+
+ html.on("click", '.view-item-from-chat', event => {
+ game.system.crucible.creator.openItemView(event)
+ })
+ html.on("click", '.roll-defense-melee', event => {
+ let rollId = $(event.currentTarget).data("roll-id")
+ let rollData = DarkStarsUtility.getRollData(rollId)
+ rollData.defenseWeaponId = $(event.currentTarget).data("defense-weapon-id")
+ let actor = game.canvas.tokens.get(rollData.defenderTokenId).actor
+ if (actor && (game.user.isGM || actor.isOwner)) {
+ actor.rollDefenseMelee(rollData)
+ }
+ })
+ html.on("click", '.roll-defense-ranged', event => {
+ let rollId = $(event.currentTarget).data("roll-id")
+ let rollData = DarkStarsUtility.getRollData(rollId)
+ let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
+ if (defender && (game.user.isGM || defender.isOwner)) {
+ defender.rollDefenseRanged(rollData)
+ }
+ })
+
+ }
+
+ /* -------------------------------------------- */
+ static async preloadHandlebarsTemplates() {
+
+ const templatePaths = [
+ 'systems/fvtt-dark-stars/templates/partials/editor-notes-gm.html',
+ 'systems/fvtt-dark-stars/templates/partials/partial-roll-select.html',
+ 'systems/fvtt-dark-stars/templates/partials/partial-actor-ability-block.html',
+ 'systems/fvtt-dark-stars/templates/partials/partial-actor-status.html',
+ 'systems/fvtt-dark-stars/templates/partials/partial-options-abilities.html',
+ 'systems/fvtt-dark-stars/templates/partials/partial-item-nav.html',
+ 'systems/fvtt-dark-stars/templates/partials/partial-item-description.html',
+ 'systems/fvtt-dark-stars/templates/partials/partial-actor-equipment.html'
+ ]
+ return loadTemplates(templatePaths);
+ }
+
+ /* -------------------------------------------- */
+ static removeChatMessageId(messageId) {
+ if (messageId) {
+ game.messages.get(messageId)?.delete();
+ }
+ }
+
+ static findChatMessageId(current) {
+ return DarkStarsUtility.getChatMessageId(DarkStarsUtility.findChatMessage(current));
+ }
+
+ static getChatMessageId(node) {
+ return node?.attributes.getNamedItem('data-message-id')?.value;
+ }
+
+ static findChatMessage(current) {
+ return DarkStarsUtility.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 DarkStarsUtility.findNodeMatching(current.parentElement, predicate);
+ }
+ return undefined;
+ }
+
+
+ /* -------------------------------------------- */
+ 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) {
+ for (let target of game.user.targets) {
+ return target
+ }
+ }
+ return undefined
+ }
+
+ /* -------------------------------------------- */
+ static async onSocketMesssage(msg) {
+ console.log("SOCKET MESSAGE", msg.name)
+ if (msg.name == "msg_update_roll") {
+ this.updateRollData(msg.data)
+ }
+ if (msg.name == "msg_gm_process_attack_defense") {
+ this.processSuccessResult(msg.data)
+ }
+ if (msg.name == "msg_gm_item_drop" && game.user.isGM) {
+ let actor = game.actors.get(msg.data.actorId)
+ let item
+ if (msg.data.isPack) {
+ item = await fromUuid("Compendium." + msg.data.isPack + "." + msg.data.itemId)
+ } else {
+ item = game.items.get(msg.data.itemId)
+ }
+ this.addItemDropToActor(actor, item)
+ }
+ }
+
+ /* -------------------------------------------- */
+ static chatDataSetup(content, modeOverride, isRoll = false, forceWhisper) {
+ let chatData = {
+ user: game.user.id,
+ rollMode: modeOverride || game.settings.get("core", "rollMode"),
+ content: content
+ };
+
+ if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => 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 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 async rollDarkStars(rollData) {
+
+ let actor = game.actors.get(rollData.actorId)
+
+ // ability/save/size => 0
+ let diceFormula
+ let startFormula = "0d6cs>=5[blue]"
+ if (rollData.ability) {
+ startFormula = String(rollData.ability.value) + "d6cs>=5[blue]"
+ }
+ if (rollData.save) {
+ startFormula = String(rollData.save.value) + "d6cs>=5[blue]"
+ }
+ if (rollData.sizeDice) {
+ let nb = rollData.sizeDice.nb + rollData.distanceBonusDice + this.getDiceFromCover(rollData.hasCover) + this.getDiceFromSituational(rollData.situational)
+ startFormula = String(nb) + String(rollData.sizeDice.dice) + "cs>=5[blue]"
+ }
+ diceFormula = startFormula
+
+ // skill => 2
+ // feat => 4
+ // bonus => 6
+ if (rollData.skill) {
+ let level = rollData.skill.system.level
+ if (rollData.skill.system.issl2) {
+ rollData.hasSLBonus = true
+ level += 2
+ if (level > 7) { level = 7 }
+ }
+ rollData.skill.system.skilldice = __skillLevel2Dice[level]
+ diceFormula += "+" + String(rollData.skill.system.skilldice) + "cs>=5[black]"
+
+ if (rollData.skill.system.skilltype == "complex" && rollData.skill.system.level == 0) {
+ rollData.complexSkillDisadvantage = true
+ rollData.rollAdvantage = "roll-disadvantage"
+ }
+
+ if (rollData.skill.system.isfeatdie) {
+ rollData.hasFeatDie = true
+ diceFormula += "+ 1d10cs>=5[dark-stars-purple]"
+ } else {
+ diceFormula += `+ 0d10cs>=5[dark-stars-purple]`
+ }
+ if (rollData.skill.system.bonusdice != "none") {
+ rollData.hasBonusDice = rollData.skill.system.bonusdice
+ diceFormula += `+ ${rollData.hasBonusDice}cs>=5[black]`
+ } else {
+ diceFormula += `+ 0d6cs>=5[black]`
+ }
+ } else {
+ diceFormula += `+ 0d8cs=>5 + 0d10cs>=5 + 0d6cs>=5`
+ }
+
+ // advantage => 8
+ let advFormula = "+ 0d8cs>=5"
+ if (rollData.advantage == "advantage1" || rollData.forceAdvantage) {
+ advFormula = "+ 1d8cs>=5[dark-stars-darkgreen]"
+ }
+ if (rollData.advantage == "advantage2") {
+ advFormula = "+ 2d8cs>=5[dark-stars-darkgreen]"
+ }
+ diceFormula += advFormula
+
+ // disadvantage => 10
+ let disFormula = "- 0d8cs>=5"
+ if (rollData.disadvantage == "disadvantage1" || rollData.forceDisadvantage) {
+ disFormula = "- 1d8cs>=5[red]"
+ }
+ if (rollData.disadvantage == "disadvantage2") {
+ disFormula = "- 2d8cs>=5[red]"
+ }
+ diceFormula += disFormula
+
+ // armor => 12
+ let skillArmorPenalty = 0
+ for (let armor of rollData.armors) {
+ if (armor.system.equipped) {
+ skillArmorPenalty += armor.system.skillpenalty
+ }
+ }
+ if (rollData.skill && rollData.skill.system.armorpenalty && skillArmorPenalty > 0) {
+ rollData.skillArmorPenalty = skillArmorPenalty
+ diceFormula += `- ${skillArmorPenalty}d8cs>=5`
+ } else {
+ diceFormula += `- 0d8cs>=5`
+ }
+
+ // shield => 14
+ if (rollData.useshield && rollData.shield) {
+ diceFormula += "+ 1" + String(rollData.shield.system.shielddie) + "cs>=5[yellow]"
+ } else {
+ diceFormula += " + 0d6cs>=5"
+ }
+
+ // Performs roll
+ console.log("Roll formula", diceFormula)
+ let myRoll = rollData.roll
+ if (!myRoll) { // New rolls only of no rerolls
+ myRoll = new Roll(diceFormula).roll({ async: false })
+ await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
+ }
+ rollData.rollOrder = 0
+ rollData.roll = myRoll
+ rollData.nbSuccess = myRoll.total
+
+ if (rollData.rollAdvantage == "none" && rollData.forceRollAdvantage) {
+ rollData.rollAdvantage = "roll-advantage"
+ }
+ if (rollData.rollAdvantage == "none" && rollData.forceRollDisadvantage) {
+ rollData.rollAdvantage = "roll-disadvantage"
+ }
+ if (rollData.rollAdvantage != "none") {
+
+ rollData.rollOrder = 1
+ rollData.rollType = (rollData.rollAdvantage == "roll-advantage") ? "Advantage" : "Disadvantage"
+ this.createChatWithRollMode(rollData.alias, {
+ content: await renderTemplate(`systems/fvtt-dark-stars/templates/chat-generic-result.html`, rollData)
+ })
+
+ rollData.rollOrder = 2
+ let myRoll2 = new Roll(diceFormula).roll({ async: false })
+ await this.showDiceSoNice(myRoll2, game.settings.get("core", "rollMode"))
+
+ rollData.roll = myRoll2 // Tmp switch to display the proper results
+ rollData.nbSuccess = myRoll2.total
+ this.createChatWithRollMode(rollData.alias, {
+ content: await renderTemplate(`systems/fvtt-dark-stars/templates/chat-generic-result.html`, rollData)
+ })
+ rollData.roll = myRoll // Revert the tmp switch
+ rollData.nbSuccess = myRoll.total
+
+ if (rollData.rollAdvantage == "roll-advantage") {
+ if (myRoll2.total > rollData.nbSuccess) {
+ hasChanged = true
+ rollData.roll = myRoll2
+ rollData.nbSuccess = myRoll2.total
+ }
+ } else {
+ if (myRoll2.total < rollData.nbSuccess) {
+ rollData.roll = myRoll2
+ rollData.nbSuccess = myRoll2.total
+ }
+ }
+ rollData.rollOrder = 3
+ }
+ rollData.nbSuccess = Math.max(0, rollData.nbSuccess)
+
+ rollData.isFirstRollAdvantage = false
+ // Manage exp
+ if (rollData.skill && rollData.skill.system.level > 0) {
+ let nbSkillSuccess = rollData.roll.terms[2].total
+ if (nbSkillSuccess == 0 || nbSkillSuccess == rollData.skill.system.level) {
+ actor.incrementSkillExp(rollData.skill.id, 1)
+ }
+ }
+
+ this.saveRollData(rollData)
+ actor.lastRoll = rollData
+
+ this.createChatWithRollMode(rollData.alias, {
+ content: await renderTemplate(`systems/fvtt-dark-stars/templates/chat-generic-result.html`, rollData)
+ })
+ console.log("Rolldata result", rollData)
+
+ // Message response
+ this.displayDefenseMessage(rollData)
+
+ // Manage defense result
+ this.processAttackDefense(rollData)
+ }
+
+ /* -------------------------------------------- */
+ static sortArrayObjectsByName(myArray) {
+ myArray.sort((a, b) => {
+ let fa = a.name.toLowerCase();
+ let fb = b.name.toLowerCase();
+ if (fa < fb) {
+ return -1;
+ }
+ if (fa > fb) {
+ return 1;
+ }
+ return 0;
+ })
+ }
+
+ /* -------------------------------------------- */
+ static getUsers(filter) {
+ return game.users.filter(filter).map(user => user.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-dark-stars", { 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"),
+ advantage: "none"
+ }
+ DarkStarsUtility.updateWithTarget(rollData)
+ return rollData
+ }
+
+ /* -------------------------------------------- */
+ static updateWithTarget(rollData) {
+ let target = DarkStarsUtility.getTarget()
+ if (target) {
+ rollData.defenderTokenId = target.id
+ }
+ }
+
+ /* -------------------------------------------- */
+ 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..90f2697
--- /dev/null
+++ b/styles/simple.css
@@ -0,0 +1,1497 @@
+ /* ==================== (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-dark-stars .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-dark-stars .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-dark-stars .sheet-header .header-fields {
+ -webkit-box-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+}
+
+.fvtt-dark-stars .sheet-header h1.charname {
+ height: 50px;
+ padding: 0px;
+ margin: 5px 0;
+ border-bottom: 0;
+}
+
+.fvtt-dark-stars .sheet-header h1.charname input {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+}
+
+.fvtt-dark-stars .sheet-tabs {
+ -webkit-box-flex: 0;
+ -ms-flex: 0;
+ flex: 0;
+}
+
+.fvtt-dark-stars .sheet-body,
+.fvtt-dark-stars .sheet-body .tab,
+.fvtt-dark-stars .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-dark-stars .tox .tox-editor-container {
+ background: #fff;
+}
+
+.fvtt-dark-stars .tox .tox-edit-area {
+ padding: 0 8px;
+}
+
+.fvtt-dark-stars .resource-label {
+ font-weight: bold;
+ text-transform: uppercase;
+}
+
+.fvtt-dark-stars .tabs {
+ height: 40px;
+ border-top: 1px solid #AAA;
+ border-bottom: 1px solid #AAA;
+ color: #000000;
+}
+
+.fvtt-dark-stars .tabs .item {
+ line-height: 40px;
+ font-weight: bold;
+}
+
+.fvtt-dark-stars .tabs .item.active {
+ text-decoration: underline;
+ text-shadow: none;
+}
+
+.fvtt-dark-stars .items-list {
+ list-style: none;
+ margin: 1px 0;
+ padding: 0;
+ overflow-y: auto;
+}
+
+.fvtt-dark-stars .items-list .item-header {
+ font-weight: bold;
+}
+
+.fvtt-dark-stars .items-list .item {
+ height: 30px;
+ line-height: 24px;
+ padding: 1px 0;
+ border-bottom: 1px solid #BBB;
+}
+
+.fvtt-dark-stars .items-list .item .item-image {
+ -webkit-box-flex: 0;
+ -ms-flex: 0 0 24px;
+ flex: 0 0 24px;
+ margin-right: 5px;
+}
+
+.fvtt-dark-stars .items-list .item img {
+ display: block;
+}
+
+.fvtt-dark-stars .items-list .item-name {
+ margin: 0;
+}
+
+.fvtt-dark-stars .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;
+ max-width: 24px;
+ height: 24px;
+ max-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,
+.ability-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-ability-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;
+}
+
+.abilities-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;
+}
+
+.ability-icon {
+ border: 0;
+ padding: 2px 2px 2px 2px;
+ max-width:32px;
+ max-height:32px;
+ width: auto;
+ height: auto;
+}
+.small-ability-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: 2px;
+ border: 1px ridge #846109;
+ display: inline-block;
+ cursor: pointer;
+ color: #ffffff;
+ margin: 2px 2px 2px 2px;
+ padding: 2px 2px 2px 2px;
+ 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-specialability,
+.drop-abilities,
+.drop-optionnal-abilities,
+.drop-virtue-vice-effect,
+.drop-virtue-vice,
+.drop-vice-virtue,
+.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/crucible_pause_logo.jpg);
+ height: 160px;
+ width: 160px;
+ top: -80px;
+ left: calc(50% - 132px);
+}
+
+#logo {
+ content : url(../images/ui/crucible_game_logo.png);
+ 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;
+}
+.ability-item {
+ flex-grow: 1;
+ justify-content: flex-start;
+ margin: 2px;
+}
+.ability-block {
+ min-width: 160px;
+}
+.ability-margin {
+ margin-left: 4px;
+ margin-top: 5px;
+}
+.combat-margin {
+ margin-left: 4px;
+ margin-top: 3px;
+}
+.item-ability-roll {
+ max-height: 42px;
+ min-height: 36px;
+}
+.item-ability-roll select, .item-ability-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-header-long {
+ flex-grow:2;
+ max-width: 14rem;
+ min-width: 14rem;
+}
+.item-name-label-header-long2 {
+ flex-grow:2;
+ max-width: 24rem;
+ min-width: 24rem;
+}
+.item-name-label {
+ flex-grow:2;
+ max-width: 10rem;
+ min-width: 10rem;
+}
+.item-name-label-long {
+ flex-grow:2;
+ max-width: 12rem;
+ min-width: 12rem;
+}
+.item-name-label-long2 {
+ flex-grow:2;
+ max-width: 22rem;
+ min-width: 22rem;
+}
+.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;
+}
+
+.dice-pool-stack {
+ flex: 1 1 5rem;
+ display: flex !important;
+ flex-grow: 0;
+ justify-content: flex-start;
+}
+.dice-pool-label {
+ margin-left: 4px;
+}
+.dice-pool-div {
+ border-left: 4px;
+ border-radius: 2px;
+ margin-bottom: 1rem;
+ background-color: #403f3e40;
+}
+
+.dice-pool-image {
+ border: 0;
+ margin-left: 4px;
+ min-width: 48px;
+ min-height: 48px;
+ max-width: 48px;
+ max-height: 48px;
+ flex-grow: 0;
+}
\ 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..7bbece4
--- /dev/null
+++ b/system.json
@@ -0,0 +1,214 @@
+{
+ "authors": [
+ {
+ "name": "Uberwald",
+ "flags": {}
+ }
+ ],
+ "description": "Dark Stars RPG system for FoundryVTT",
+ "esmodules": [
+ "modules/dark-stars-main.js"
+ ],
+ "gridDistance": 5,
+ "gridUnits": "m",
+ "languages": [
+ {
+ "lang": "en",
+ "name": "English",
+ "path": "lang/en.json",
+ "flags": {}
+ }
+ ],
+ "license": "LICENSE.txt",
+ "packs": [
+ {
+ "type": "Item",
+ "label": "Armors",
+ "name": "armor",
+ "path": "packs/armor.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Equipments",
+ "name": "equipment",
+ "path": "packs/equipment.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Shields",
+ "name": "shields",
+ "path": "packs/shields.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Weapons",
+ "name": "weapons",
+ "path": "packs/weapons.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Conditions",
+ "name": "conditions",
+ "path": "packs/conditions.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Currency",
+ "name": "currency",
+ "path": "packs/currency.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Lore - Air",
+ "name": "lore-air",
+ "path": "packs/lore-air.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Lore - Earth",
+ "name": "lore-earth",
+ "path": "packs/lore-earth.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Lore - Fire",
+ "name": "lore-fire",
+ "path": "packs/lore-fire.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Lore - Water",
+ "name": "lore-water",
+ "path": "packs/lore-water.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Lore - Shadow",
+ "name": "lore-shadow",
+ "path": "packs/lore-shadow.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Skills",
+ "name": "skills",
+ "path": "packs/skills.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Feats",
+ "name": "feats",
+ "path": "packs/feats.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Poisons",
+ "name": "poisons",
+ "path": "packs/poisons.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Powers - Class",
+ "name": "classpowers",
+ "path": "packs/classpowers.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Tricks & Traps",
+ "name": "trickstraps",
+ "path": "packs/trickstraps.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Action Tokens",
+ "name": "action-tokens",
+ "path": "packs/action-tokens.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "Item",
+ "label": "Powers - Monsters",
+ "name": "monster-powers",
+ "path": "packs/monster-powers.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ },
+ {
+ "type": "RollTable",
+ "label": "Rolltables",
+ "name": "rolltables",
+ "path": "packs/rolltables.db",
+ "system": "fvtt-dark-stars",
+ "private": false,
+ "flags": {}
+ }
+ ],
+ "primaryTokenAttribute": "secondary.hp",
+ "secondaryTokenAttribute": "secondary.effort",
+ "socket": true,
+ "styles": [
+ "styles/simple.css"
+ ],
+ "version": "10.0.0",
+ "compatibility": {
+ "minimum": "10",
+ "verified": "10",
+ "maximum": "10"
+ },
+ "title": "Dark Stars RPG",
+ "manifest": "https://www.uberwald.me/gitea/uberwald/fvtt-dark-stars",
+ "download": "https://www.uberwald.me/gitea/uberwald/fvtt-dark-stars",
+ "url": "https://www.uberwald.me/gitea/uberwald/",
+ "background": "images/ui/dark_stars_welcome_page.webp",
+ "id": "fvtt-dark-stars"
+}
\ No newline at end of file
diff --git a/template.json b/template.json
new file mode 100644
index 0000000..88d86a4
--- /dev/null
+++ b/template.json
@@ -0,0 +1,205 @@
+{
+ "Actor": {
+ "types": ["character", "npc"],
+ "templates": {
+ "biodata": {
+ "biodata": {
+ "class": "",
+ "age": 0,
+ "size": 0,
+ "weight": "",
+ "height": "",
+ "hair": "",
+ "sex": "",
+ "eyes": "",
+ "background": "",
+ "description": "",
+ "notes": "",
+ "gmnotes": ""
+ }
+ },
+ "core": {
+ "subactors": [],
+ "abilities": {
+ "str":{
+ "label": "Strength",
+ "abbrev": "str",
+ "col": 1,
+ "value": 1
+ },
+ "agi":{
+ "label": "Agility",
+ "abbrev": "agi",
+ "col": 1,
+ "value": 1
+ },
+ "dex":{
+ "label": "Dexterity",
+ "abbrev": "dex",
+ "col": 1,
+ "value": 1
+ },
+ "con":{
+ "label": "Constitution",
+ "abbrev": "con",
+ "col": 1,
+ "value": 1
+ },
+ "int":{
+ "label": "Intelligence",
+ "abbrev": "int",
+ "col": 2,
+ "value": 1
+ },
+ "wit":{
+ "label": "Wits",
+ "abbrev": "wit",
+ "col": 2,
+ "value": 1
+ },
+ "cha":{
+ "label": "Charisma",
+ "abbrev": "cha",
+ "col": 2,
+ "value": 1
+ }
+ },
+ "secondary": {
+ "hp": {
+ "label": "Hitpoint",
+ "abbrev": "hb",
+ "value": -1,
+ "max": 0
+ },
+ "effort": {
+ "label": "Effort",
+ "abbrev": "eff",
+ "value": -1,
+ "max": 0
+ }
+ },
+ "social": {
+ "fame": {
+ "label": "Fame",
+ "value": 0
+ },
+ "reputation": {
+ "label": "Reputation",
+ "value": 0
+ }
+ }
+ },
+ "npccore": {
+ "npctype": "",
+ "description": ""
+ }
+ },
+ "character": {
+ "templates": [ "biodata", "core" ]
+ },
+ "npc": {
+ "templates": [ "biodata", "core" ]
+ }
+ },
+ "Item": {
+ "types": [ "race", "skill", "armor", "shield", "equipment", "weapon", "money" , "feat", "spell", "condition", "poison"],
+ "poison": {
+ "description": ""
+ },
+ "condition": {
+ "advantage": false,
+ "disadvantage": false,
+ "rolladvantage": false,
+ "rolldisadvantage": false,
+ "loosehpround": false,
+ "loohproundvalue": 0,
+ "noadvantage": false,
+ "attackdisadvantage": false,
+ "defensedisadvantage": false,
+ "targetadvantage": false,
+ "noaction": false,
+ "description": ""
+ },
+ "race": {
+ "description": ""
+ },
+ "feat": {
+ "isfeatdie": false,
+ "issl": false,
+ "sl": 0,
+ "description": ""
+ },
+ "skill": {
+ "ability": "",
+ "armorpenalty": false,
+ "isproficient": false,
+ "isweaponskill": false,
+ "isshieldskill": false,
+ "isfeatdie": false,
+ "issl2": false,
+ "islore": false,
+ "skilltype": "",
+ "isinnate": false,
+ "bonusdice": "",
+ "background": 0,
+ "basic": 0,
+ "class": 0,
+ "exp": 0,
+ "explevel": 0,
+ "description": ""
+ },
+ "armor": {
+ "armortype": "",
+ "absorprionroll": "",
+ "damagedroll": "",
+ "isproficient": false,
+ "minstr": 0,
+ "skillpenalty": 0,
+ "equipped": false,
+ "cost": 0,
+ "description":""
+ },
+ "shield": {
+ "shielddie": "",
+ "skill": "",
+ "equipped": false,
+ "cost": 0,
+ "description":""
+ },
+ "equipment": {
+ "equiptype": "",
+ "cost": 0,
+ "quantity": 0,
+ "equipped": false,
+ "iscontainer": false,
+ "containercapacity": 0,
+ "containerid": "",
+ "description":""
+ },
+ "money" : {
+ "value": 0,
+ "quantity": 0,
+ "description": ""
+ },
+ "weapon": {
+ "isproficient": false,
+ "skill": "",
+ "qualities": "",
+ "flaws": "",
+ "damage": "",
+ "isranged": false,
+ "range": "",
+ "maxrange": "",
+ "minstr": 0,
+ "cost": 0,
+ "equipped": false,
+ "description": ""
+ },
+ "spell":{
+ "lore": "",
+ "circle": 1,
+ "range": "",
+ "description": ""
+ }
+ }
+}
diff --git a/templates/actors/actor-sheet.hbs b/templates/actors/actor-sheet.hbs
new file mode 100644
index 0000000..26edc76
--- /dev/null
+++ b/templates/actors/actor-sheet.hbs
@@ -0,0 +1,530 @@
+
\ No newline at end of file
diff --git a/templates/actors/npc-sheet.hbs b/templates/actors/npc-sheet.hbs
new file mode 100644
index 0000000..208c497
--- /dev/null
+++ b/templates/actors/npc-sheet.hbs
@@ -0,0 +1,413 @@
+
+
+ {{!-- Biography Tab --}}
+
+
+
+
+
+
Background :
+
+ {{editor data.biodata.description target="system.biodata.description" button=true owner=owner
+ editable=editable}}
+
+
+
Notes :
+
+ {{editor data.biodata.notes target="system.biodata.notes" button=true owner=owner editable=editable}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/apps/roll-dialog-generic.hbs b/templates/apps/roll-dialog-generic.hbs
new file mode 100644
index 0000000..e9d1241
--- /dev/null
+++ b/templates/apps/roll-dialog-generic.hbs
@@ -0,0 +1,167 @@
+
\ No newline at end of file
diff --git a/templates/chat/chat-attack-defense-result.hbs b/templates/chat/chat-attack-defense-result.hbs
new file mode 100644
index 0000000..4367f4b
--- /dev/null
+++ b/templates/chat/chat-attack-defense-result.hbs
@@ -0,0 +1,75 @@
+
+
+
+
+ {{#if img}}
+
+
+
+ {{/if}}
+
+
+
+
+
+
+ Fight result !
+ {{#if successDetails.fumbleDetails}}
+ Fumble ! : {{successDetails.fumbleDetails.data.text}}
+ {{/if}}
+
+
+ {{#if armorResult}}
+ Armor initial result : {{armorResult.rawArmor}}
+ {{#each armorResult.messages as |message idx|}}
+ {{message}}
+ {{/each}}
+ {{/if}}
+
+ {{#if successDetails.hack_vs_shields}}
+ Hack weapon : check shield !
+ {{/if}}
+
+ {{#if successDetails.entangle}}
+ Entangle weapon : attacker can entangle !
+ {{/if}}
+
+ {{#if successDetails.knockback}}
+ Knockback weapon : check knockback !
+ {{/if}}
+
+ {{#if successDetails.hack_armors}}
+ Hack weapon : check armor damage !
+ {{/if}}
+
+ {{#if successDetails.penetrating_impale}}
+ Penetrating weapon : apply the Impale condition !
+ {{/if}}
+
+ {{#if (or successDetails.critical_1 successDetails.critical_2)}}
+ Critical {{#if successDetails.critical_1}} 1 {{else}} 2 {{/if}} : {{successDetails.criticalText}}
+ {{/if}}
+
+ {{#if successDetails.attackerHPLossValue}}
+ Attacker has lost HP : {{successDetails.attackerHPLossValue}} HP
+ {{/if}}
+
+ {{#if successDetails.defenderHPLossValue}}
+ Defender has lost HP : {{successDetails.defenderHPLossValue}} HP
+ {{/if}}
+
+ Success details : {{successDetails.result}}
+
+ Final successes {{sumSuccess}}
+
+
+
+
+
+
+
diff --git a/templates/chat/chat-generic-result.hbs b/templates/chat/chat-generic-result.hbs
new file mode 100644
index 0000000..e6633de
--- /dev/null
+++ b/templates/chat/chat-generic-result.hbs
@@ -0,0 +1,149 @@
+
+
+
+
+ {{#if img}}
+
+
+
+ {{/if}}
+
+
+
+
+
+
+ {{#if (eq rollOrder 1)}}
+ Roll with {{rollType}} - Roll 1
+ {{/if}}
+ {{#if (eq rollOrder 2)}}
+ Roll with {{rollType}} - Roll 2
+ {{/if}}
+ {{#if (eq rollOrder 3)}}
+ Roll with {{rollType}} - Final result !
+ {{/if}}
+
+ {{#if save}}
+ Save : {{save.label}} - {{save.value}}d6
+ ({{#each roll.terms.0.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ {{#if sizeDice}}
+ Size/Range/Cover/Situational dices
+ ({{#each roll.terms.0.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ {{#if ability}}
+ Ability : {{ability.label}} - {{ability.value}}d6
+ ({{#each roll.terms.0.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ {{#if skill}}
+ Skill : {{skill.name}} - {{skill.data.skilldice}}
+ {{#if featSL}}
+ - with Feat SL +{{featSL}}
+ {{/if}}
+ ({{#each roll.terms.2.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ {{#if noAdvantage}}
+ No advantage due to condition : {{noAdvantage.name}}
+ {{else}}
+ {{#if (or (eq advantage "advantage1") forceAdvantage)}}
+ 1 Advantage Die !
+ ({{#each roll.terms.8.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+ {{#if (eq advantage "advantage2") }}
+ 2 Advantage Dice !
+ ({{#each roll.terms.8.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+ {{/if}}
+
+ {{#if (or (eq disadvantage "disadvantage1") forceDisadvantage)}}
+ 1 Disadvantage Die !
+ ({{#each roll.terms.10.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+ {{#if (eq disadvantage "disadvantage2")}}
+ 2 Disadvantage Dice !
+ ({{#each roll.terms.10.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+ {{#if (eq rollAdvantage "roll-advantage")}}
+ Roll with Advantage !
+ {{/if}}
+ {{#if (eq rollAdvantage "roll-disadvantage")}}
+ Roll with Disadvantage !
+ {{/if}}
+
+ {{#if skillArmorPenalty}}
+ Armor Penalty : {{skillArmorPenalty}} Disadvantage Dice
+ ({{#each roll.terms.12.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ {{#if hasBonusDice}}
+ Skill bonus dice : {{hasBonusDice}}
+ ({{#each roll.terms.6.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ {{#if complexSkillDisadvantage}}
+ Roll with Disadvantage because of Complex Skill at SL 0 !
+ {{/if}}
+
+ {{#if hasFeatDie}}
+ Feat Die : d10
+ ({{#each roll.terms.4.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ {{#if useshield}}
+ Shield : {{shield.name}} - {{shield.data.shielddie}}
+ ({{#each roll.terms.14.results as |die idx|}}
+ {{die.result}}
+ {{/each}})
+
+ {{/if}}
+
+ Number of successes {{nbSuccess}}
+
+
+
+
+
+
+
diff --git a/templates/chat/chat-opposed-fail.hbs b/templates/chat/chat-opposed-fail.hbs
new file mode 100644
index 0000000..4e71d14
--- /dev/null
+++ b/templates/chat/chat-opposed-fail.hbs
@@ -0,0 +1,11 @@
+
+
+
+
+ {{defenderName}} wins the opposition against {{attackerName}} !
+
+
+
diff --git a/templates/chat/chat-request-defense.hbs b/templates/chat/chat-request-defense.hbs
new file mode 100644
index 0000000..f5bda4d
--- /dev/null
+++ b/templates/chat/chat-request-defense.hbs
@@ -0,0 +1,46 @@
+
+
+
+
+{{#if img}}
+
+
+
+{{/if}}
+
+
+
+
+
+
+ {{#if isRangedAttack}}
+
{{defender.name}} is under Ranged attack. He must roll a Target Roll to defend himself.
+ {{else}}
+
{{defender.name}} is under Melee attack. He must roll a Defense Roll to defend himself.
+ {{/if}}
+
+
+ {{#if isRangedAttack}}
+
+ Roll Target !
+
+ {{else}}
+
+ {{#each defenderWeapons as |weapon idx|}}
+ {{weapon.name}}
+ {{/each}}
+
+ {{/if}}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/items/item-armor-sheet.hbs b/templates/items/item-armor-sheet.hbs
new file mode 100644
index 0000000..f4ebe36
--- /dev/null
+++ b/templates/items/item-armor-sheet.hbs
@@ -0,0 +1,92 @@
+
diff --git a/templates/items/item-condition-sheet.hbs b/templates/items/item-condition-sheet.hbs
new file mode 100644
index 0000000..a84066a
--- /dev/null
+++ b/templates/items/item-condition-sheet.hbs
@@ -0,0 +1,64 @@
+
\ No newline at end of file
diff --git a/templates/items/item-equipment-sheet.hbs b/templates/items/item-equipment-sheet.hbs
new file mode 100644
index 0000000..68b2708
--- /dev/null
+++ b/templates/items/item-equipment-sheet.hbs
@@ -0,0 +1,31 @@
+
diff --git a/templates/items/item-feat-sheet.hbs b/templates/items/item-feat-sheet.hbs
new file mode 100644
index 0000000..0751e74
--- /dev/null
+++ b/templates/items/item-feat-sheet.hbs
@@ -0,0 +1,35 @@
+
diff --git a/templates/items/item-money-sheet.hbs b/templates/items/item-money-sheet.hbs
new file mode 100644
index 0000000..e2c11d1
--- /dev/null
+++ b/templates/items/item-money-sheet.hbs
@@ -0,0 +1,28 @@
+
diff --git a/templates/items/item-poison-sheet.hbs b/templates/items/item-poison-sheet.hbs
new file mode 100644
index 0000000..9f587ae
--- /dev/null
+++ b/templates/items/item-poison-sheet.hbs
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/templates/items/item-race-sheet.hbs b/templates/items/item-race-sheet.hbs
new file mode 100644
index 0000000..9f587ae
--- /dev/null
+++ b/templates/items/item-race-sheet.hbs
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/templates/items/item-shield-sheet.hbs b/templates/items/item-shield-sheet.hbs
new file mode 100644
index 0000000..985f820
--- /dev/null
+++ b/templates/items/item-shield-sheet.hbs
@@ -0,0 +1,55 @@
+
diff --git a/templates/items/item-skill-sheet.hbs b/templates/items/item-skill-sheet.hbs
new file mode 100644
index 0000000..5f7c68e
--- /dev/null
+++ b/templates/items/item-skill-sheet.hbs
@@ -0,0 +1,105 @@
+
diff --git a/templates/items/item-spell-sheet.hbs b/templates/items/item-spell-sheet.hbs
new file mode 100644
index 0000000..61f5949
--- /dev/null
+++ b/templates/items/item-spell-sheet.hbs
@@ -0,0 +1,49 @@
+
diff --git a/templates/items/item-weapon-sheet.hbs b/templates/items/item-weapon-sheet.hbs
new file mode 100644
index 0000000..dcb6071
--- /dev/null
+++ b/templates/items/item-weapon-sheet.hbs
@@ -0,0 +1,84 @@
+
diff --git a/templates/items/post-item.hbs b/templates/items/post-item.hbs
new file mode 100644
index 0000000..1bad9c8
--- /dev/null
+++ b/templates/items/post-item.hbs
@@ -0,0 +1,8 @@
+
+
{{name}}
+ {{#if img}}
+
+ {{/if}}
+
Description :
+
{{{data.description}}}
+
diff --git a/templates/partials/editor-notes-gm.hbs b/templates/partials/editor-notes-gm.hbs
new file mode 100644
index 0000000..4c17392
--- /dev/null
+++ b/templates/partials/editor-notes-gm.hbs
@@ -0,0 +1,6 @@
+{{#if data.isGM}}
+GM Notes :
+
+ {{editor data.gmnotes target="system.gmnotes" button=true owner=owner editable=editable}}
+
+{{/if}}
diff --git a/templates/partials/partial-actor-ability-block.hbs b/templates/partials/partial-actor-ability-block.hbs
new file mode 100644
index 0000000..3e05740
--- /dev/null
+++ b/templates/partials/partial-actor-ability-block.hbs
@@ -0,0 +1,19 @@
+
+
+
+
+
+ {{#select ability.value}}
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ {{/select}}
+
+
\ No newline at end of file
diff --git a/templates/partials/partial-actor-equipment.hbs b/templates/partials/partial-actor-equipment.hbs
new file mode 100644
index 0000000..51b1f41
--- /dev/null
+++ b/templates/partials/partial-actor-equipment.hbs
@@ -0,0 +1,37 @@
+
+
+ {{#if (eq level 1)}}
+ {{equip.name}}
+ {{else}}
+ {{equip.name}}
+ {{/if}}
+
+
+ {{equip.system.quantity}}
+ ( - /+ )
+
+
+
+
+ -
+
+
+ {{#if equip.system.iscontainer}}
+ {{equip.system.contentsEnc}}
+ {{else}}
+ {{mul equip.system.weight equip.system.quantity}}
+ {{/if}}
+
+
+ -
+
+
+
+
+
diff --git a/templates/partials/partial-actor-status.hbs b/templates/partials/partial-actor-status.hbs
new file mode 100644
index 0000000..09985af
--- /dev/null
+++ b/templates/partials/partial-actor-status.hbs
@@ -0,0 +1,39 @@
+
+
+
+ HP
+
+
+
+ {{#if (eq type "character")}}
+ / {{data.secondary.hp.max}}
+ {{else}}
+ /
+ {{/if}}
+
+
+
+ Effort
+
+
+
+ {{#if (eq type "character")}}
+ / {{data.secondary.effort.max}}
+ {{else}}
+ /
+ {{/if}}
+
+
+
+
+ {{#each saveRolls as |save key|}}
+
+
+
+
+
+
+ {{/each}}
+
+
diff --git a/templates/partials/partial-item-description.hbs b/templates/partials/partial-item-description.hbs
new file mode 100644
index 0000000..26c9623
--- /dev/null
+++ b/templates/partials/partial-item-description.hbs
@@ -0,0 +1,8 @@
+
+
+
Description
+
+ {{editor description target="system.description" button=true owner=owner editable=editable}}
+
+
+
diff --git a/templates/partials/partial-item-nav.hbs b/templates/partials/partial-item-nav.hbs
new file mode 100644
index 0000000..95b52cd
--- /dev/null
+++ b/templates/partials/partial-item-nav.hbs
@@ -0,0 +1,5 @@
+{{!-- Sheet Tab Navigation --}}
+
+ Description
+ Details
+
diff --git a/templates/partials/partial-options-abilities.hbs b/templates/partials/partial-options-abilities.hbs
new file mode 100644
index 0000000..0efa008
--- /dev/null
+++ b/templates/partials/partial-options-abilities.hbs
@@ -0,0 +1,7 @@
+Agility
+Strength
+Dexterity
+Constitution
+Intelligence
+Wits
+Charisma
diff --git a/templates/partials/partial-options-equipment-types.hbs b/templates/partials/partial-options-equipment-types.hbs
new file mode 100644
index 0000000..ab60653
--- /dev/null
+++ b/templates/partials/partial-options-equipment-types.hbs
@@ -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/partials/partial-options-level.hbs b/templates/partials/partial-options-level.hbs
new file mode 100644
index 0000000..0fb86c0
--- /dev/null
+++ b/templates/partials/partial-options-level.hbs
@@ -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/partials/partial-options-range.hbs b/templates/partials/partial-options-range.hbs
new file mode 100644
index 0000000..e08a1e5
--- /dev/null
+++ b/templates/partials/partial-options-range.hbs
@@ -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/partials/partial-roll-select.hbs b/templates/partials/partial-roll-select.hbs
new file mode 100644
index 0000000..7c9e85f
--- /dev/null
+++ b/templates/partials/partial-roll-select.hbs
@@ -0,0 +1,4 @@
+
+
+