commit ecef402160d2271a618ca17086337c061813eeea Author: LeRatierBretonnien Date: Thu Dec 15 21:44:23 2022 +0100 HERO6 - First import diff --git a/README.md b/README.md new file mode 100644 index 0000000..c849519 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# fvtt-dark-stars + diff --git a/fonts/NotoSans-Black.ttf b/fonts/NotoSans-Black.ttf new file mode 100644 index 0000000..298c240 Binary files /dev/null and b/fonts/NotoSans-Black.ttf differ diff --git a/fonts/NotoSans-BlackItalic.ttf b/fonts/NotoSans-BlackItalic.ttf new file mode 100644 index 0000000..5b5c4ba Binary files /dev/null and b/fonts/NotoSans-BlackItalic.ttf differ diff --git a/fonts/NotoSans-Bold.ttf b/fonts/NotoSans-Bold.ttf new file mode 100644 index 0000000..3e68bc2 Binary files /dev/null and b/fonts/NotoSans-Bold.ttf differ diff --git a/fonts/NotoSans-BoldItalic.ttf b/fonts/NotoSans-BoldItalic.ttf new file mode 100644 index 0000000..4b56351 Binary files /dev/null and b/fonts/NotoSans-BoldItalic.ttf differ diff --git a/fonts/NotoSans-ExtraBold.ttf b/fonts/NotoSans-ExtraBold.ttf new file mode 100644 index 0000000..ce25444 Binary files /dev/null and b/fonts/NotoSans-ExtraBold.ttf differ diff --git a/fonts/NotoSans-ExtraBoldItalic.ttf b/fonts/NotoSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000..1575114 Binary files /dev/null and b/fonts/NotoSans-ExtraBoldItalic.ttf differ diff --git a/fonts/NotoSans-ExtraLight.ttf b/fonts/NotoSans-ExtraLight.ttf new file mode 100644 index 0000000..ebddc56 Binary files /dev/null and b/fonts/NotoSans-ExtraLight.ttf differ diff --git a/fonts/NotoSans-ExtraLightItalic.ttf b/fonts/NotoSans-ExtraLightItalic.ttf new file mode 100644 index 0000000..2e0a876 Binary files /dev/null and b/fonts/NotoSans-ExtraLightItalic.ttf differ diff --git a/fonts/NotoSans-Italic.ttf b/fonts/NotoSans-Italic.ttf new file mode 100644 index 0000000..eedc5e4 Binary files /dev/null and b/fonts/NotoSans-Italic.ttf differ diff --git a/fonts/NotoSans-Light.ttf b/fonts/NotoSans-Light.ttf new file mode 100644 index 0000000..9f9453e Binary files /dev/null and b/fonts/NotoSans-Light.ttf differ diff --git a/fonts/NotoSans-LightItalic.ttf b/fonts/NotoSans-LightItalic.ttf new file mode 100644 index 0000000..0e67cb9 Binary files /dev/null and b/fonts/NotoSans-LightItalic.ttf differ diff --git a/fonts/NotoSans-Medium.ttf b/fonts/NotoSans-Medium.ttf new file mode 100644 index 0000000..02dad4e Binary files /dev/null and b/fonts/NotoSans-Medium.ttf differ diff --git a/fonts/NotoSans-MediumItalic.ttf b/fonts/NotoSans-MediumItalic.ttf new file mode 100644 index 0000000..def607c Binary files /dev/null and b/fonts/NotoSans-MediumItalic.ttf differ diff --git a/fonts/NotoSans-Regular.ttf b/fonts/NotoSans-Regular.ttf new file mode 100644 index 0000000..973bc2e Binary files /dev/null and b/fonts/NotoSans-Regular.ttf differ diff --git a/fonts/NotoSans-SemiBold.ttf b/fonts/NotoSans-SemiBold.ttf new file mode 100644 index 0000000..182ac5d Binary files /dev/null and b/fonts/NotoSans-SemiBold.ttf differ diff --git a/fonts/NotoSans-SemiBoldItalic.ttf b/fonts/NotoSans-SemiBoldItalic.ttf new file mode 100644 index 0000000..ea5812a Binary files /dev/null and b/fonts/NotoSans-SemiBoldItalic.ttf differ diff --git a/fonts/NotoSans-Thin.ttf b/fonts/NotoSans-Thin.ttf new file mode 100644 index 0000000..6d5ce81 Binary files /dev/null and b/fonts/NotoSans-Thin.ttf differ diff --git a/fonts/NotoSans-ThinItalic.ttf b/fonts/NotoSans-ThinItalic.ttf new file mode 100644 index 0000000..04f5278 Binary files /dev/null and b/fonts/NotoSans-ThinItalic.ttf differ diff --git a/fonts/OFL.txt b/fonts/OFL.txt new file mode 100644 index 0000000..90b7332 --- /dev/null +++ b/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2015-2021 Google LLC. All Rights Reserved. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. 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/images/icons/.directory b/images/icons/.directory new file mode 100644 index 0000000..1631026 --- /dev/null +++ b/images/icons/.directory @@ -0,0 +1,5 @@ +[Dolphin] +Timestamp=2022,12,4,9,33,49.136 +Version=4 +ViewMode=2 +VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails diff --git a/images/ui/background_01.webp b/images/ui/background_01.webp new file mode 100644 index 0000000..8d503fa Binary files /dev/null and b/images/ui/background_01.webp differ diff --git a/images/ui/logo_circle_black_01.webp b/images/ui/logo_circle_black_01.webp new file mode 100644 index 0000000..c83b857 Binary files /dev/null and b/images/ui/logo_circle_black_01.webp differ diff --git a/images/ui/logo_circle_yellow_01.webp b/images/ui/logo_circle_yellow_01.webp new file mode 100644 index 0000000..9c63a7d Binary files /dev/null and b/images/ui/logo_circle_yellow_01.webp differ diff --git a/images/ui/logo_hex_yellow_01.webp b/images/ui/logo_hex_yellow_01.webp new file mode 100644 index 0000000..bc3b4d1 Binary files /dev/null and b/images/ui/logo_hex_yellow_01.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/hero6-actor-sheet.js b/modules/hero6-actor-sheet.js new file mode 100644 index 0000000..569a6f9 --- /dev/null +++ b/modules/hero6-actor-sheet.js @@ -0,0 +1,224 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ + +import { Hero6Utility } from "./hero6-utility.js"; + +/* -------------------------------------------- */ +export class Hero6ActorSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-hero-system-6", "sheet", "actor"], + template: "systems/fvtt-hero-system-6/templates/actor-sheet.hbs", + 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") + Hero6Utility.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/hero6-actor.js b/modules/hero6-actor.js new file mode 100644 index 0000000..7dedf29 --- /dev/null +++ b/modules/hero6-actor.js @@ -0,0 +1,808 @@ +/* -------------------------------------------- */ +import { Hero6Utility } from "./hero6-utility.js"; +import { Hero6RollDialog } from "./hero6-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 Hero6Actor 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 Hero6Utility.loadCompendium("fvtt-hero-system-6.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'); + Hero6Utility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getFeats() { + let comp = duplicate(this.items.filter(item => item.type == 'feat') || []); + Hero6Utility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getFeatsWithDie() { + let comp = duplicate(this.items.filter(item => item.type == 'feat' && item.system.isfeatdie) || []); + Hero6Utility.sortArrayObjectsByName(comp) + return comp; + } + getFeatsWithSL() { + let comp = duplicate(this.items.filter(item => item.type == 'feat' && item.system.issl) || []); + Hero6Utility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getLore() { + let comp = duplicate(this.items.filter(item => item.type == 'spell') || []); + Hero6Utility.sortArrayObjectsByName(comp) + return comp; + } + getEquippedWeapons() { + let comp = duplicate(this.items.filter(item => item.type == 'weapon' && item.system.equipped) || []); + Hero6Utility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getArmors() { + let comp = duplicate(this.items.filter(item => item.type == 'armor') || []); + Hero6Utility.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') || []); + Hero6Utility.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') || []); + Hero6Utility.sortArrayObjectsByName(comp) + return comp; + } + /* -------------------------------------------- */ + getWeapons() { + let comp = duplicate(this.items.filter(item => item.type == 'weapon') || []); + Hero6Utility.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) { + Hero6Utility.updateSkill(skill) + } + Hero6Utility.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-hero-system-6/images/icons/saves/reflex_save.webp", + "value": this.system.abilities.agi.value + this.system.abilities.wit.value + }, + fortitude: { + "label": "Fortitude Save", + "img": "systems/fvtt-hero-system-6/images/icons/saves/fortitude_save.webp", + "value": this.system.abilities.str.value + this.system.abilities.con.value + }, + willpower: { + "label": "Willpower Save", + "img": "systems/fvtt-hero-system-6/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 = Hero6Utility.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 Hero6Utility.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; + Hero6Utility.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 = Hero6Utility.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 + } + Hero6Utility.rollHero6(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) + Hero6Utility.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) + Hero6Utility.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 > Hero6Utility.getWeaponMaxRange(rollData.weapon) ) { + ui.notifications.warn(`Your target is out of range of your weapon (max: ${Hero6Utility.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) + Hero6Utility.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 = Hero6Utility.getWeaponRange(attackRollData.weapon) + rollData.tokensDistance = attackRollData.tokensDistance // QoL copy + } + rollData.sizeDice = Hero6Utility.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 (Hero6Utility.isArmorLight(armor) && Hero6Utility.isWeaponPenetrating(rollData.attackRollData.weapon)) { + return { armorIgnored: true, nbSuccess: 0, messages: ["Armor ignored : Penetrating weapons ignore Light Armors."] } + } + if (Hero6Utility.isWeaponPenetrating(rollData.attackRollData.weapon)) { + messages.push("Armor reduced by 1 (Penetrating weapon)") + reduce = 1 + } + if (Hero6Utility.isWeaponLight(rollData.attackRollData.weapon)) { + messages.push("Armor with advantage (Light weapon)") + advantage = true + } + if (Hero6Utility.isWeaponHeavy(rollData.attackRollData.weapon)) { + messages.push("Armor with disadvantage (Heavy weapon)") + disadvantage = true + } + if (Hero6Utility.isWeaponHack(rollData.attackRollData.weapon)) { + messages.push("Armor reduced by 1 (Hack weapon)") + reduce = 1 + } + if (Hero6Utility.isWeaponUndamaging(rollData.attackRollData.weapon)) { + messages.push("Armor multiplied by 2 (Undamaging weapon)") + multiply = 2 + } + } + let diceColor = armor.system.absorprionroll + let armorResult = await Hero6Utility.getRollTableFromDiceColor(diceColor, false) + console.log("Armor log", armorResult) + let armorValue = Math.max(0, (Number(armorResult.text) + reduce) * multiply) + if (advantage || disadvantage) { + let armorResult2 = await Hero6Utility.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 Hero6RollDialog.create(this, rollData) + rollDialog.render(true) + } + +} diff --git a/modules/hero6-combat.js b/modules/hero6-combat.js new file mode 100644 index 0000000..5874f1a --- /dev/null +++ b/modules/hero6-combat.js @@ -0,0 +1,30 @@ +import { Hero6Utility } from "./hero6-utility.js"; + +/* -------------------------------------------- */ +export class Hero6Combat 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/hero6-commands.js b/modules/hero6-commands.js new file mode 100644 index 0000000..1919ba9 --- /dev/null +++ b/modules/hero6-commands.js @@ -0,0 +1,144 @@ +/* -------------------------------------------- */ + +import { Hero6Utility } from "./hero6-utility.js"; +import { Hero6RollDialog } from "./hero6-roll-dialog.js"; + +/* -------------------------------------------- */ +const __saveFirstToKey = { r: "reflex", f: "fortitude", w: "willpower"} + +/* -------------------------------------------- */ +export class Hero6Commands { + + static init() { + if (!game.system.Hero6.commands) { + const Hero6Commands = new Hero6Commands(); + Hero6Commands.registerCommand({ path: ["/rtarget"], func: (content, msg, params) => Hero6Commands.rollTarget(msg, params), descr: "Launch the target roll window" }); + Hero6Commands.registerCommand({ path: ["/rsave"], func: (content, msg, params) => Hero6Commands.rollSave(msg, params), descr: "Performs a save roll" }); + game.system.Hero6.commands = Hero6Commands; + } + } + + 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("Hero6Commands._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) { + Hero6Commands._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/hero6-config.js b/modules/hero6-config.js new file mode 100644 index 0000000..1e04044 --- /dev/null +++ b/modules/hero6-config.js @@ -0,0 +1,21 @@ + +export const Hero6_CONFIG = { + + rollCharac : { + "str": "Strength", + "dex": "Dexterity", + "pre": "Presence", + "int": "Intelligence", + "con": "Constitution", + "ego": "Ego" + }, + skillType: { + "agility": "Agility", + "interaction": "Interaction", + "intellect": "Intellect" , + "background": "Background" , + "combat": "Combat" , + } + + +} \ No newline at end of file diff --git a/modules/hero6-hotbar.js b/modules/hero6-hotbar.js new file mode 100644 index 0000000..7c81217 --- /dev/null +++ b/modules/hero6-hotbar.js @@ -0,0 +1,68 @@ + +export class Hero6Hotbar { + + static async addToHotbar(item, slot) { + let command = `game.system.cruciblerpg.Hero6Hotbar.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/hero6-item-sheet.js b/modules/hero6-item-sheet.js new file mode 100644 index 0000000..e214127 --- /dev/null +++ b/modules/hero6-item-sheet.js @@ -0,0 +1,166 @@ +import { Hero6Utility } from "./hero6-utility.js"; + +/** + * Extend the basic ItemSheet with some very simple modifications + * @extends {ItemSheet} + */ +export class Hero6ItemSheet extends ItemSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["fvtt-hero-system-6", "sheet", "item"], + template: "systems/fvtt-hero-system-6/templates/items/item-sheet.hbs", + 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() { + + let objectData = duplicate(this.object.system) + + 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: Hero6Utility.getWeaponSkills(), + shieldSkills: Hero6Utility.getShieldSkills(), + description: await TextEditor.enrichHTML(this.object.system.description, {async: true}), + config: game.system.Hero6.config, + system: objectData, + 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(Hero6Utility.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-hero-system-6/templates/items/post-item.hbs', chatData).then(html => { + let chatOptions = Hero6Utility.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-hero-system-6/templates/items/item-${type}-sheet.hbs`; + } + + /* -------------------------------------------- */ + /** @override */ + _updateObject(event, formData) { + return this.object.update(formData) + } +} \ No newline at end of file diff --git a/modules/hero6-item.js b/modules/hero6-item.js new file mode 100644 index 0000000..7ede244 --- /dev/null +++ b/modules/hero6-item.js @@ -0,0 +1,27 @@ +import { Hero6Utility } from "./hero6-utility.js"; + +export const defaultItemImg = { + skill: "systems/fvtt-hero-system-6/images/icons/skill.webp", + armor: "systems/fvtt-hero-system-6/images/icons/armor.webp", + equipment: "systems/fvtt-hero-system-6/images/icons/equipment.webp", + weapon: "systems/fvtt-hero-system-6/images/icons/melee.webp", + perk: "systems/fvtt-hero-system-6/images/icons/perk.webp", + ability: "systems/fvtt-hero-system-6/images/icons/ability.webp", + genetic: "systems/fvtt-hero-system-6/images/icons/genetic.webp", + cyber: "systems/fvtt-hero-system-6/images/icons/cyber.webp" + } + +/** + * Extend the basic ItemSheet with some very simple modifications + * @extends {ItemSheet} + */ +export class Hero6Item extends Item { + + constructor(data, context) { + if (!data.img) { + data.img = defaultItemImg[data.type]; + } + super(data, context); + } + +} diff --git a/modules/hero6-main.js b/modules/hero6-main.js new file mode 100644 index 0000000..dd3431a --- /dev/null +++ b/modules/hero6-main.js @@ -0,0 +1,118 @@ +/** + * Hero6 system + * Author: Uberwald + * Software License: Prop + */ + +/* -------------------------------------------- */ + +/* -------------------------------------------- */ +// Import Modules +import { Hero6Actor } from "./hero6-actor.js"; +import { Hero6ItemSheet } from "./hero6-item-sheet.js"; +import { Hero6ActorSheet } from "./hero6-actor-sheet.js"; +import { Hero6NPCSheet } from "./hero6-npc-sheet.js"; +import { Hero6Utility } from "./hero6-utility.js"; +import { Hero6Combat } from "./hero6-combat.js"; +import { Hero6Item } from "./hero6-item.js"; +import { Hero6Hotbar } from "./hero6-hotbar.js" +import { Hero6Commands } from "./hero6-commands.js" +import { Hero6_CONFIG } from "./hero6-config.js"; + +/* -------------------------------------------- */ +/* Foundry VTT Initialization */ +/* -------------------------------------------- */ + +/************************************************************************************/ +Hooks.once("init", async function () { + + console.log(`Initializing Hero6 RPG`); + + game.system.Hero6 = { + Hero6Commands, + config: Hero6_CONFIG + } + + /* -------------------------------------------- */ + // preload handlebars templates + Hero6Utility.preloadHandlebarsTemplates(); + + /* -------------------------------------------- */ + // Set an initiative formula for the system + CONFIG.Combat.initiative = { + formula: "1d6", + decimals: 1 + }; + + /* -------------------------------------------- */ + game.socket.on("system.fvtt-hero-system-6", data => { + Hero6Utility.onSocketMesssage(data) + }); + + /* -------------------------------------------- */ + // Define custom Entity classes + CONFIG.Combat.documentClass = Hero6Combat + CONFIG.Actor.documentClass = Hero6Actor + CONFIG.Item.documentClass = Hero6Item + + /* -------------------------------------------- */ + // Register sheet application classes + Actors.unregisterSheet("core", ActorSheet); + Actors.registerSheet("fvtt-hero-system-6", Hero6ActorSheet, { types: ["character"], makeDefault: true }); + Actors.registerSheet("fvtt-hero-system-6", Hero6NPCSheet, { types: ["npc"], makeDefault: false }); + + Items.unregisterSheet("core", ItemSheet); + Items.registerSheet("fvtt-hero-system-6", Hero6ItemSheet, { makeDefault: true }); + + Hero6Utility.init() +}); + +/* -------------------------------------------- */ +function welcomeMessage() { + ChatMessage.create({ + user: game.user.id, + whisper: [game.user.id], + content: `
+ Welcome to the Hero6 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(); + Hero6Utility.ready() + Hero6Commands.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/hero6-npc-sheet.js b/modules/hero6-npc-sheet.js new file mode 100644 index 0000000..c7d7684 --- /dev/null +++ b/modules/hero6-npc-sheet.js @@ -0,0 +1,209 @@ +/** + * Extend the basic ActorSheet with some very simple modifications + * @extends {ActorSheet} + */ + +import { Hero6Utility } from "./hero6-utility.js"; + +/* -------------------------------------------- */ +export class Hero6NPCSheet extends ActorSheet { + + /** @override */ + static get defaultOptions() { + + return mergeObject(super.defaultOptions, { + classes: ["hero6-rpg", "sheet", "actor"], + template: "systems/fvtt-hero-system-6/templates/npc-sheet.hbs", + 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") + Hero6Utility.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/hero6-roll-dialog.js b/modules/hero6-roll-dialog.js new file mode 100644 index 0000000..eb6507b --- /dev/null +++ b/modules/hero6-roll-dialog.js @@ -0,0 +1,84 @@ +import { Hero6Utility } from "./hero6-utility.js"; + +export class Hero6RollDialog extends Dialog { + + /* -------------------------------------------- */ + static async create(actor, rollData) { + + let options = { classes: ["Hero6Dialog"], width: 540, height: 340, 'z-index': 99999 }; + let html = await renderTemplate('systems/fvtt-hero-system-6/templates/roll-dialog-generic.hbs', rollData); + + return new Hero6RollDialog(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() { + Hero6Utility.rollHero6(this.rollData) + } + + /* -------------------------------------------- */ + async refreshDialog() { + const content = await renderTemplate("systems/fvtt-hero-system-6/templates/roll-dialog-generic.hbs", 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/hero6-utility.js b/modules/hero6-utility.js new file mode 100644 index 0000000..dc2efec --- /dev/null +++ b/modules/hero6-utility.js @@ -0,0 +1,599 @@ +/* -------------------------------------------- */ +import { Hero6Combat } from "./hero6-combat.js"; +import { Hero6Commands } from "./hero6-commands.js"; + +/* -------------------------------------------- */ +const __locationNames = { head: "Head", chest: "Chest", abdomen: "Abdomen", leftarm: "Left Arm", rightarm: "Right Arm", leftleg: "Left Leg", rightleg: "Right Leg" } +/* -------------------------------------------- */ +export class Hero6Utility { + + + /* -------------------------------------------- */ + static async init() { + Hooks.on('renderChatLog', (log, html, data) => Hero6Utility.chatListeners(html)); + /*Hooks.on("dropCanvasData", (canvas, data) => { + Hero6Utility.dropItemOnToken(canvas, data) + });*/ + + Hero6Commands.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); + }) + Handlebars.registerHelper('locationLabel', function (key) { + return __locationNames[key] + }) + + + this.gameSettings() + + } + + /*-------------------------------------------- */ + static gameSettings() { + /*game.settings.register("fvtt-hero-system-6", "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-hero-system-6.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 Hero6Utility.loadCompendium("fvtt-hero-system-6.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 Hero6Utility.loadCompendium("fvtt-hero-system-6.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 Hero6Utility.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 = Hero6Utility.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 = Hero6Utility.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-hero-system-6/templates/partials/editor-notes-gm.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-roll-select.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-actor-ability-block.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-actor-status.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-options-abilities.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-item-nav.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-item-description.hbs', + 'systems/fvtt-hero-system-6/templates/partials/partial-actor-equipment.hbs' + ] + return loadTemplates(templatePaths); + } + + /* -------------------------------------------- */ + static removeChatMessageId(messageId) { + if (messageId) { + game.messages.get(messageId)?.delete(); + } + } + + static findChatMessageId(current) { + return Hero6Utility.getChatMessageId(Hero6Utility.findChatMessage(current)); + } + + static getChatMessageId(node) { + return node?.attributes.getNamedItem('data-message-id')?.value; + } + + static findChatMessage(current) { + return Hero6Utility.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 Hero6Utility.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 += `` + } + 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 rollHero6(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[hero6-purple]" + } else { + diceFormula += `+ 0d10cs>=5[hero6-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[hero6-darkgreen]" + } + if (rollData.advantage == "advantage2") { + advFormula = "+ 2d8cs>=5[hero6-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-hero-system-6/templates/chat-generic-result.hbs`, 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-hero-system-6/templates/chat-generic-result.hbs`, 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-hero-system-6/templates/chat-generic-result.hbs`, 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-hero-system-6", { 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" + } + Hero6Utility.updateWithTarget(rollData) + return rollData + } + + /* -------------------------------------------- */ + static updateWithTarget(rollData) { + let target = Hero6Utility.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/packs/action-tokens.db b/packs/action-tokens.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/armor.db b/packs/armor.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/classpowers.db b/packs/classpowers.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/conditions.db b/packs/conditions.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/currency.db b/packs/currency.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/equipment.db b/packs/equipment.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/feats.db b/packs/feats.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/lore-air.db b/packs/lore-air.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/lore-earth.db b/packs/lore-earth.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/lore-fire.db b/packs/lore-fire.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/lore-shadow.db b/packs/lore-shadow.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/lore-water.db b/packs/lore-water.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/monster-powers.db b/packs/monster-powers.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/poisons.db b/packs/poisons.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/rolltables.db b/packs/rolltables.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/shields.db b/packs/shields.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/skills.db b/packs/skills.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/trickstraps.db b/packs/trickstraps.db new file mode 100644 index 0000000..e69de29 diff --git a/packs/weapons.db b/packs/weapons.db new file mode 100644 index 0000000..e69de29 diff --git a/styles/simple.css b/styles/simple.css new file mode 100644 index 0000000..6571b66 --- /dev/null +++ b/styles/simple.css @@ -0,0 +1,1514 @@ + /* ==================== (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; + color: rgba(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 0.95); + +} + +/* Fonts */ +.sheet header.sheet-header h1 input, .window-app .window-header, #actors .directory-list, #navigation #scene-list .scene.nav-item { + font-size: 1.0rem; + color: rgba(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 0.95); +} /* For title, sidebar character and scene */ +.sheet nav.sheet-tabs { + font-size: 0.8rem; + color: rgba(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 0.95); +} /* For nav and title */ +.window-app input, .fvtt-dark-stars .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; + color: rgba(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 0.95); +} + +.window-header{ + background: rgba(0,0,0,0.75); +} +.dialog .window-content { + color: #ccdbe6; +} +.dialog-content, .dialog-buttons, .form-fields { + color: #ccdbe6; +} +.window-app.sheet .window-content { + margin: 0; + padding: 0; + color: #ccdbe6; +} +.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(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 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(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 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(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 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; + color: rgba(228, 240, 240, 0.75); + background: rgba(66, 66, 64, 0.95); +} + +/* 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 --}} +
+ +
+
+
+ +
+
+ +
+ +
    +
  • +

    {{data.momentum.label}}

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

    {{data.mr.label}}

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

    {{stat2.label}} :

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

    {{data.momentum.label}}:

    + Cur +  Max +
  • diff --git a/system.json b/system.json new file mode 100644 index 0000000..f901989 --- /dev/null +++ b/system.json @@ -0,0 +1,97 @@ +{ + "authors": [ + { + "name": "Uberwald", + "flags": {} + } + ], + "description": "Hero System v6 for FoundryVTT (Official)", + "esmodules": [ + "modules/hero6-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-hero-system-6", + "private": false, + "flags": {} + }, + { + "type": "Item", + "label": "Equipments", + "name": "equipment", + "path": "packs/equipment.db", + "system": "fvtt-hero-system-6", + "private": false, + "flags": {} + }, + { + "type": "Item", + "label": "Shields", + "name": "shields", + "path": "packs/shields.db", + "system": "fvtt-hero-system-6", + "private": false, + "flags": {} + }, + { + "type": "Item", + "label": "Weapons", + "name": "weapons", + "path": "packs/weapons.db", + "system": "fvtt-hero-system-6", + "private": false, + "flags": {} + }, + { + "type": "Item", + "label": "Currency", + "name": "currency", + "path": "packs/currency.db", + "system": "fvtt-hero-system-6", + "private": false, + "flags": {} + }, + { + "type": "Item", + "label": "Skills", + "name": "skills", + "path": "packs/skills.db", + "system": "fvtt-hero-system-6", + "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": "Hero System v6 for FoundrtVTT (Official)", + "manifest": "https://www.uberwald.me/gitea/uberwald/fvtt-hero-system-6/raw/branch/main/system.json", + "download": "https://www.uberwald.me/gitea/uberwald/fvtt-hero-system-6/archive/fvtt-hero-system-6-v10.0.0.zip", + "url": "https://www.uberwald.me/gitea/uberwald/", + "background": "images/ui/hro6_welcome_page.webp", + "id": "fvtt-hero-system-6" +} \ No newline at end of file diff --git a/template.json b/template.json new file mode 100644 index 0000000..3acc7f1 --- /dev/null +++ b/template.json @@ -0,0 +1,312 @@ +{ + "Actor": { + "types": [ + "character" + ], + "templates": { + "biodata": { + "biodata": { + "species": "", + "age": 0, + "gender": 0, + "character": "", + "religion": "", + "weight": "", + "height": "", + "background": "", + "description": "", + "notes": "", + "gmnotes": "" + } + }, + "characteristics": { + "characteristics": { + "str": { + "label": "Strength", + "value": 10, + "base": 10 + }, + "dex": { + "label": "Dexterity", + "value": 10, + "base": 10 + }, + "con": { + "label": "Constitution", + "value": 10, + "base": 10 + }, + "int": { + "label": "Intelligence", + "value": 10, + "base": 10 + }, + "ego": { + "label": "Ego", + "value": 10, + "base": 10 + }, + "pre": { + "label": "Presence", + "value": 10, + "base": 10 + }, + "ocv": { + "label": "OCV", + "base": 3, + "autoMod": "0", + "userMod": "0", + "value": 3, + "modifier": 1 + }, + "dcv": { + "label": "DCV", + "base": 3, + "autoMod": "0", + "userMod": "0", + "value": 3, + "modifier": 1 + }, + "omcv": { + "label": "OMCV", + "base": 3, + "autoMod": "0", + "userMod": "0", + "value": 3, + "modifier": 1 + }, + "dmcv": { + "label": "DMCV", + "base": 3, + "autoMod": "0", + "userMod": "0", + "value": 3, + "modifier": 1 + }, + "spd": { + "label": "Speed", + "value": 2, + "base": 2 + }, + "pd": { + "label": "PD", + "value": 2, + "base": 2 + }, + "ed": { + "label": "ED", + "value": 2, + "base": 2 + }, + "rec": { + "label": "REC", + "value": 4, + "base": 4 + }, + "end": { + "label": "REC", + "value": 20, + "max": 20 + }, + "stun": { + "label": "STUN", + "value": 20, + "max": 20 + }, + "body": { + "label": "Body", + "value": 10, + "max": 10, + "loc": { + "Head": 0, + "Left Hand": 0, + "Right Hand": 0, + "Left Shoulder": 0, + "Right Shoulder": 0, + "Left Arm": 0, + "Right Arm": 0, + "Chest": 0, + "Stomach": 0, + "Vitals": 0, + "Left Thigh": 0, + "Right Thigh": 0, + "Left Leg": 0, + "Right Leg": 0, + "Left Foot": 0, + "Right Foot": 0 + } + }, + "running": { + "label": "Running", + "value": 12, + "base": 12 + }, + "swimming": { + "label": "Swimming", + "value": 4, + "base": 4 + }, + "leaping": { + "label": "Leaping", + "value": 4, + "base": 4 + }, + "flying": { + "label": "Flying", + "value": 0, + "base": 0 + } + } + }, + "core": { + + } + }, + "character": { + "templates": [ + "biodata", + "core" + ] + } + }, + "Item": { + "types": [ + "skill", + "perk", + "talent", + "power", + "advantage", + "limitation", + "complication", + "equipment", + "attack", + "defense", + "maneuver", + "movement", + "misc" + ], + "templates": { + "common": { + "description": "", + "effects": "", + "cost": "", + "value": 0, + "modifiers": [] + }, + "power": { + "description": "", + "cost": "", + "modifiers": [], + "type": "", + "effect": 0, + "levels": 0, + "end": 0, + "items": {} + } + }, + "advantage": { + "templates": [ + "common" + ] + }, + "limitation": { + "templates": [ + "common" + ] + }, + "skill": { + "skilltype": "", + "characteristic": "", + "base": 0, + "skillroll": 0, + "levels": 0, + "templates": [ + "common" + ], + "plusonecost": 0 + }, + "power": { + "templates": [ + "power" + ] + }, + "equipment": { + "templates": [ + "power" + ], + "quantity": 1, + "weight": 0, + "moneycost": 0 + }, + "attack": { + "templates": [ + "common" + ], + "defense": "pd", + "killing": 0, + "piercing": 0, + "penetrating": 0, + "advantages": 0, + "dice": 1, + "toHitMod": 0, + "extraDice": "zero", + "uses": "ocv", + "targets": "dcv", + "end": 0, + "stunX": 0, + "class": "physical" + }, + "defense": { + "templates": [ + "common" + ], + "defenseType": "pd", + "active": "false", + "resistant": "false", + "hardened": 0, + "impenetrable": 0, + "value": 0 + }, + "maneuver": { + "templates": [ + "common" + ], + "phase": 0, + "ocv": "+0", + "dcv": "+0", + "active": false + }, + "movement": { + "templates": [ + "common" + ], + "class": "Running", + "editable": true, + "base": 0, + "value": 0, + "mod": 0, + "velBase": 0, + "velValue": 0 + }, + "perk": { + "templates": [ + "common" + ] + }, + "talent": { + "templates": [ + "common" + ] + }, + "complication": { + "templates": [ + "common" + ] + }, + "martialart": { + "templates": [ + "common" + ] + } + } +} \ No newline at end of file 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 @@ +
    + + {{!-- Sheet Header --}} +
    +
    +

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

    Class

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

      {{equippedArmor.name}}

      +
      +
    • + {{/if}} + {{#if equippedShield}} +
    • + + +

      {{equippedShield.name}}

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

      Target Roll

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

      +
      + + + + + + + + + +
    • + {{#each skills as |skill key|}} +
    • + + {{skill.name}} + {{upper skill.system.ability}} + {{skill.system.skilldice}} +  -  +
       
      +
      + +
      +
    • + {{/each}} +
    +
    + + {{!-- Combat Tab --}} +
    +
    + +
    +
      +
    • + +

      +
      + + + + + + +
    • + {{#each equippedWeapons as |weapon key|}} +
    • + + {{weapon.name}} + + {{weapon.system.ability}} + + {{perk.system.range}} + +
       
      +
      + +
      +
    • + {{/each}} +
    +
    + +
    +
      +
    • + +

      +
      + + + + + + + + + +
    • + {{#each feats as |feat key|}} +
    • + + {{feat.name}} + + {{upperFirst feat.system.isfeatdie}} + {{upperFirst feat.system.issl}} + {{feat.system.sl}} + +
       
      +
      + +
      +
    • + {{/each}} +
    +
    + +
    +
      +
    • + +

      +
      +
    • + {{#each conditions as |condition key|}} +
    • + + {{condition.name}} +
       
      +
      + +
      +
    • + {{/each}} +
    +
    + +
    +
    + + + {{!-- Lore Tab --}} +
    + +
    + +
      +
    • + +

      +
      + + + + + + + + + +
    • + + {{#each spells as |spell key|}} +
    • + + + {{spell.name}} + + {{upperFirst spell.system.lore}} + {{upperFirst spell.system.circle}} + {{upperFirst spell.system.range}} +
       
      +
      + +
      +
    • + {{/each}} + +
    + +
    +
    + + {{!-- Equipement Tab --}} +
    + +
    +

    Encumbrance

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

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

      +
      + + + + + + +
      + +
      +
    • + {{#each weapons as |weapon key|}} +
    • + + {{weapon.name}} + + + +
       
      + +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + + +
       
      +
      + +
      +
    • + {{#each armors as |armor key|}} +
    • + + {{armor.name}} + {{upper armor.system.armortype}} + {{armor.system.absorprionroll}} + +
       
      + +
    • + {{/each}} +
    + + + +
      +
    • + +

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

    Background :

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

    Notes :

    +
    + {{editor notes target="system.biodata.notes" button=true owner=owner editable=editable}} +
    +
    + +
    + +
    +
    \ 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 @@ +
    + + {{!-- Sheet Header --}} +
    +
    +

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

    Class

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

      {{equippedArmor.name}}

      +
      +
    • + {{/if}} + {{#if equippedShield}} +
    • + + +

      {{equippedShield.name}}

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

      Target Roll

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

      +
      + + + + + + + + + +
    • + {{#each skills as |skill key|}} +
    • + + {{skill.name}} + {{upper skill.system.ability}} + {{skill.system.skilldice}} +  -  +
       
      +
      + +
      +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + +
    • + {{#each equippedWeapons as |weapon key|}} +
    • + + {{weapon.name}} + + {{weapon.system.ability}} + + {{perk.system.range}} + +
       
      +
      + +
      +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + + + + +
    • + {{#each feats as |feat key|}} +
    • + + {{feat.name}} + + {{upperFirst feat.system.isfeatdie}} + {{upperFirst feat.system.issl}} + {{feat.system.sl}} + +
       
      +
      + +
      +
    • + {{/each}} +
    + +
      +
    • + +

      +
      +
    • + {{#each conditions as |condition key|}} +
    • + + {{condition.name}} + +
       
      +
       
      +
      + +
      +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + +
      + +
      +
    • + {{#each weapons as |weapon key|}} +
    • + + {{weapon.name}} + + + +
       
      + +
    • + {{/each}} +
    + +
      +
    • + +

      +
      + + + + + + + +
       
      +
      + +
      +
    • + {{#each armors as |armor key|}} +
    • + + {{armor.name}} + {{upper armor.system.armortype}} + {{armor.system.absorprionroll}} + +
       
      + +
    • + {{/each}} +
    + + + +
      +
    • + +

      +
      + + + +
       
      +
      + +
      + +
    • + {{#each containersTree as |equip key|}} + {{> systems/fvtt-crucible-rpg/templates/partial-actor-equipment.html equip=equip level=1}} +
        + {{#each equip.data.contents as |subgear key|}} + {{> systems/fvtt-crucible-rpg/templates/partial-actor-equipment.html equip=subgear level=2}} + {{/each}} +
      + {{/each}} +
    +
    + +
    + + {{!-- 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 @@ +
    +
    + {{#if img}} + + {{/if}} +

    {{title}}

    +
    + +
    + + {{#if sizeDice}} +
    + Size basic dices : + {{sizeDice.nb}}{{sizeDice.dice}} +
    + +
    + Distance bonus dice(s) : + +
    + {{/if}} + + {{#if hasCover}} +
    + Cover : + +
    + {{/if}} + + {{#if situational}} +
    + Situational : + +
    + {{/if}} + + + {{#if save}} +
    + {{save.label}} : + {{save.value}}d6 +
    + {{/if}} + + {{#if ability}} +
    + Ability : + {{ability.value}}d6 +
    + {{/if}} + + {{#if weapon}} +
    + Weapon : + {{weapon.name}} +
    + {{/if}} + + {{#if shield}} +
    + Use shield ? : + +
    +
    + {{shield.name}} : + {{shield.data.shielddie}} +
    + {{/if}} + + {{#if skill}} +
    + Skill : + {{skill.name}} - {{skill.data.skilldice}} +
    +
    + Feature die or SL+2? : + {{#if skill.data.isfeatdie}} Yes {{else}} No {{/if}} +
    + {{/if}} + + {{#if noAdvantage}} +
    + No advantage due to condition : {{noAdvantage.name}} +
    + {{else}} +
    + Advantage : + +
    + {{/if}} + +
    + Disadvantage : + +
    + +
    + Roll with Advantage/Disadvantage : + +
    + + {{#if forceAdvantage}} +
    + 1 Advantage from condition : {{forceAdvantage.name}} + {{#if advantageFromTarget}} (Provided by targetted actor) {{/if}} + +
    + {{/if}} + {{#if forceDisadvantage}} +
    + 1 Disadvantage from condition : {{forceDisadvantage.name}} +
    + {{/if}} + {{#if forceRollAdvantage}} +
    + Roll Advantage from condition : {{forceRollAdvantage.name}} +
    + {{/if}} + {{#if forceRollDisadvantage}} +
    + Roll Disadvantage from condition : {{forceRollDisadvantage.name}} +
    + {{/if}} + + +
    + +
    \ 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 actorImg}} + {{alias}} + {{/if}} +

    {{alias}}

    +
    + +
    + + {{#if img}} +
    + {{name}} +
    + {{/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 actorImg}} + {{alias}} + {{/if}} +

    {{alias}}

    +
    + +
    + + {{#if img}} +
    + {{name}} +
    + {{/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 @@ +
    + {{alias}} +

    {{defenderName}}

    +
    + +
    +
    + {{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 actorImg}} + {{alias}} + {{/if}} +

    {{alias}}

    +
    + +
    + +{{#if img}} +
    + {{name}} +
    +{{/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}} + + + + + +
    + + \ 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..5db22be --- /dev/null +++ b/templates/items/item-armor-sheet.hbs @@ -0,0 +1,71 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-description.hbs}} + +
    + +
    +
      +
    • + +
    • + + {{#each system.locations as |location key|}} +
    • + + + + {{#if location.protected}} +
    • +
        +
      • + +
      • +
      • + +
      • +
      +
    • + {{/if}} + {{/each}} + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    +
    +
    + +
    +
    diff --git a/templates/items/item-equipment-sheet.hbs b/templates/items/item-equipment-sheet.hbs new file mode 100644 index 0000000..a7d13fa --- /dev/null +++ b/templates/items/item-equipment-sheet.hbs @@ -0,0 +1,34 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-nav.hbs}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-description.hbs}} + +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    +
    + +
    +
    diff --git a/templates/items/item-money-sheet.hbs b/templates/items/item-money-sheet.hbs new file mode 100644 index 0000000..fb5bdf2 --- /dev/null +++ b/templates/items/item-money-sheet.hbs @@ -0,0 +1,28 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-nav.hbs}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-description.hbs}} + +
    +
      +
    • + +
    • +
    • + +
    • +
    +
    + +
    +
    diff --git a/templates/items/item-perk-sheet.hbs b/templates/items/item-perk-sheet.hbs new file mode 100644 index 0000000..009131a --- /dev/null +++ b/templates/items/item-perk-sheet.hbs @@ -0,0 +1,19 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-nav.hbs}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-description.hbs}} + +
    +
    +
    +
    diff --git a/templates/items/item-skill-sheet.hbs b/templates/items/item-skill-sheet.hbs new file mode 100644 index 0000000..f79b650 --- /dev/null +++ b/templates/items/item-skill-sheet.hbs @@ -0,0 +1,54 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-hero-system-6/templates/partials/partial-item-nav.hbs}} + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-hero-system-6/templates/partials/partial-item-description.hbs}} + +
    +
      +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    +
    +
    +
    diff --git a/templates/items/item-weapon-sheet.hbs b/templates/items/item-weapon-sheet.hbs new file mode 100644 index 0000000..e3e0994 --- /dev/null +++ b/templates/items/item-weapon-sheet.hbs @@ -0,0 +1,107 @@ +
    +
    + +
    +

    +
    +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-nav.hbs}} + + + {{!-- Sheet Body --}} +
    + + {{> systems/fvtt-dark-stars/templates/partials/partial-item-description.hbs}} + +
    + +
    +
      + +
    • + +
    • + +
    • + +
    • +
    • + +
    • + + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    • + +
    +
    +
    + +
    +
    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 @@ +
  • + +

    {{ability.label}}

    +
    + +
  • \ 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}} + + + + + +  -  + + + {{#if equip.system.iscontainer}} + {{equip.system.contentsEnc}} + {{else}} + {{mul equip.system.weight equip.system.quantity}} + {{/if}} + + +  -  + + +
     
    +
    + {{#if (eq level 1)}} + {{#if equip.system.equipped}}{{else}}{{/if}} + {{/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 @@ +