diff --git a/lang/en.json b/lang/en.json index 3a523ca..0452e42 100644 --- a/lang/en.json +++ b/lang/en.json @@ -101,6 +101,7 @@ "dishonored.roll.power.rune": "|#| Runes", "dishonored.roll.power.mana": "Costs |#| Mana to use", "dishonored.roll.item.value": "|#| Coins", + "dishonored.roll.item.quantity": "|#|x Items", diff --git a/module/actors/actor.js b/module/actors/actor.js index bf4e22e..29bb8e5 100644 --- a/module/actors/actor.js +++ b/module/actors/actor.js @@ -1,20 +1,125 @@ -/** - * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. - * @extends {Actor} - */ +import { + DishonoredRollDialog +} from '../apps/roll-dialog.js' +import { + DishonoredRoll +} from '../roll.js' + export class DishonoredActor extends Actor { prepareData() { super.prepareData(); + // const actorData = this.data; + } +} - const actorData = this.data; - const data = actorData.data; - const flags = actorData.flags; +export class DishonoredSharedActorFunctions { - if (actorData.type === 'character') this._prepareCharacterData(actorData); + // This function renders all the tracks. This will be used every time the character sheet is loaded. It is a vital element as such it runs before most other code! + dishonoredRenderTracks(html, stressTrackMax, voidPointsMax, expPointsMax, momentumMax) { + var i; + // Checks if details for the Stress Track was included, this should happen in all cases! + if (stressTrackMax) { + for (i = 0; i < stressTrackMax; i++) { + if (i + 1 <= html.find('#total-stress')[0].value) { + html.find('[id^="stress"]')[i].setAttribute("data-selected", "true"); + html.find('[id^="stress"]')[i].style.backgroundColor = "#191813"; + html.find('[id^="stress"]')[i].style.color = "#ffffff"; + } else { + html.find('[id^="stress"]')[i].removeAttribute("data-selected"); + html.find('[id^="stress"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; + html.find('[id^="stress"]')[i].style.color = ""; + } + } + } + // Checks if details for the Void Track was included, this should happen for all Characters! + if (voidPointsMax) { + for (i = 0; i < voidPointsMax; i++) { + if (i + 1 <= html.find('#total-void')[0].value) { + html.find('[id^="void"]')[i].setAttribute("data-selected", "true"); + html.find('[id^="void"]')[i].style.backgroundColor = "#191813"; + html.find('[id^="void"]')[i].style.color = "#ffffff"; + } else { + html.find('[id^="void"]')[i].removeAttribute("data-selected"); + html.find('[id^="void"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; + html.find('[id^="void"]')[i].style.color = ""; + } + } + } + // Checks if details for the Experience Track was included, this should happen for all Characters! + if (expPointsMax) { + for (i = 0; i < expPointsMax; i++) { + if (i + 1 <= html.find('#total-exp')[0].value) { + html.find('[id^="exp"]')[i].setAttribute("data-selected", "true"); + html.find('[id^="exp"]')[i].style.backgroundColor = "#191813"; + html.find('[id^="exp"]')[i].style.color = "#ffffff"; + } else { + html.find('[id^="exp"]')[i].removeAttribute("data-selected"); + html.find('[id^="exp"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; + html.find('[id^="exp"]')[i].style.color = ""; + } + } + } + // Checks if details for the Momentum Track was included, this should happen for all Characters! + if(momentumMax) { + for (i = 0; i < 6; i++) { + if (i + 1 <= html.find('#total-mom')[0].value) { + html.find('[id^="mom"]')[i].setAttribute("data-selected", "true"); + html.find('[id^="mom"]')[i].style.backgroundColor = "#191813"; + html.find('[id^="mom"]')[i].style.color = "#ffffff"; + } else { + html.find('[id^="mom"]')[i].removeAttribute("data-selected"); + html.find('[id^="mom"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; + html.find('[id^="mom"]')[i].style.color = ""; + } + } + } } - _prepareCharacterData(actorData) { - const data = actorData.data; - // console.log(data); - } + // This handles performing a skill test using the "Perform Check" button. + async rollSkillTest(event, checkTarget, selectedSkill, selectedStyle, speaker) { + event.preventDefault(); + // This creates a dialog to gather details regarding the roll and waits for a response + let rolldialog = await DishonoredRollDialog.create(); + if (rolldialog) { + let dicePool = rolldialog.get("dicePoolSlider"); + let focusTarget = parseInt(rolldialog.get("dicePoolFocus")); + // Once the response has been collected it then sends it to be rolled. + let dishonoredRoll = new DishonoredRoll(); + dishonoredRoll.performSkillTest(dicePool, checkTarget, focusTarget, selectedSkill, selectedStyle, speaker); + } + } + + // This handles performing an "item" roll by clicking the item's image. + async rollGenericItem(event, type, id, speaker) { + event.preventDefault(); + var item = speaker.items.get(id); + let dishonoredRoll = new DishonoredRoll(); + // It will send it to a different method depending what item type was sent to it. + switch(type) { + case "item": + dishonoredRoll.performItemRoll(item, speaker); + break; + case "focus": + dishonoredRoll.performFocusRoll(item, speaker); + break; + case "bonecharm": + dishonoredRoll.performBonecharmRoll(item, speaker); + break; + case "weapon": + dishonoredRoll.performWeaponRoll(item, speaker); + break; + case "armor": + dishonoredRoll.performArmorRoll(item, speaker); + break; + case "talent": + dishonoredRoll.performTalentRoll(item, speaker); + break; + case "contact": + dishonoredRoll.performContactRoll(item, speaker); + break; + case "power": + dishonoredRoll.performPowerRoll(item, speaker); + break; + } + } } \ No newline at end of file diff --git a/module/actors/sheets/character-sheet.js b/module/actors/sheets/character-sheet.js index 60f94eb..12d3164 100644 --- a/module/actors/sheets/character-sheet.js +++ b/module/actors/sheets/character-sheet.js @@ -1,13 +1,7 @@ import { - DishonoredRollDialog -} from '../../apps/roll-dialog.js' -import { - DishonoredRoll -} from '../../roll.js' -/** - * Extend the basic ActorSheet with some very simple modifications - * @extends {ActorSheet} - */ + DishonoredSharedActorFunctions +} from '../actor.js' + export class DishonoredCharacterSheet extends ActorSheet { /** @override */ @@ -29,9 +23,8 @@ export class DishonoredCharacterSheet extends ActorSheet { /** @override */ getData() { const data = super.getData(); - data.dtypes = ["String", "Number", "Boolean"]; - //Ensure skill and style values don't weigh over the max of 8 and minimum of 4. + //Ensure skill and style values don't weigh over the max of 8. if (data.data.skills.fight.value > 8) data.data.skills.fight.value = 8; if (data.data.skills.move.value > 8) data.data.skills.move.value = 8; if (data.data.skills.study.value > 8) data.data.skills.study.value = 8; @@ -44,11 +37,17 @@ export class DishonoredCharacterSheet extends ActorSheet { if (data.data.styles.forcefully.value > 8) data.data.styles.forcefully.value = 8; if (data.data.styles.quietly.value > 8) data.data.styles.quietly.value = 8; if (data.data.styles.swiftly.value > 8) data.data.styles.swiftly.value = 8; + + // Checks if any values are larger than their relevant max, if so, set to max. if (data.data.void.value > data.data.void.max) data.data.void.value = data.data.void.max; if (data.data.stress.value > data.data.stress.max) data.data.stress.value = data.data.stress.max; - if (data.data.mana.value > data.data.mana.max) data.data.mana.value = data.data.mana.max; + // For some reason - this is treated as a string, so this enforce use of integers here. + if (parseInt(data.data.mana.value) > parseInt(data.data.mana.max)) data.data.mana.value = data.data.mana.max; + // Checks if mana max is not equal to double the void max, if it isn't, set it so. + if (data.data.mana.max != 2*data.data.void.max) data.data.mana.max = 2*data.data.void.max; + //Ensure skill and style values aren't lower than 4. if (data.data.skills.fight.value < 4) data.data.skills.fight.value = 4; if (data.data.skills.move.value < 4) data.data.skills.move.value = 4; if (data.data.skills.study.value < 4) data.data.skills.study.value = 4; @@ -61,16 +60,15 @@ export class DishonoredCharacterSheet extends ActorSheet { if (data.data.styles.forcefully.value < 4) data.data.styles.forcefully.value = 4; if (data.data.styles.quietly.value < 4) data.data.styles.quietly.value = 4; if (data.data.styles.swiftly.value < 4) data.data.styles.swiftly.value = 4; + + // Checks if any values are below their theoretical minimum, if so - set it to the very minimum. if (data.data.void.value < 0) data.data.void.value = 0; if (data.data.void.max < 1) data.data.void.max = 1; if (data.data.stress.value < 0) data.data.stress.value = 0; if (data.data.experience < 0) data.data.experience = 0; if (data.data.mana.value < 0) data.data.mana.value = 0; if (data.data.mana.max < 2) data.data.mana.max = 2; - - if (data.data.mana.max != 2*data.data.void.max) data.data.mana.max = 2*data.data.void.max; - return data; } @@ -79,10 +77,16 @@ export class DishonoredCharacterSheet extends ActorSheet { /** @override */ activateListeners(html) { super.activateListeners(html); + + // Opens the class DishonoredSharedActorFunctions for access at various stages. + let dishonoredActor = new DishonoredSharedActorFunctions() - // This creates a dynamic Void Point tracker. It polls for the hidden control "max-void" and for the value, creates a new div for each and places it under a child called "bar-void-renderer" - var voidPointsMax = html.find('#max-void')[0].value; + // We use i alot in for loops. Best to assign it now for use later in multiple places. var i; + + // This creates a dynamic Void Point tracker. It polls for the hidden control "max-void" and for the value, + // creates a new div for each and places it under a child called "bar-void-renderer" + var voidPointsMax = html.find('#max-void')[0].value; for (i = 1; i <= voidPointsMax; i++) { var div = document.createElement("DIV"); div.className = "voidbox"; @@ -92,13 +96,14 @@ export class DishonoredCharacterSheet extends ActorSheet { html.find('#bar-void-renderer')[0].appendChild(div); } - // This creates a dynamic Stress tracker. It polls for the value of the survive skill, adds any protection from armor. With the total value, creates a new div for each and places it under a child called "bar-stress-renderer". - // It also has a check that if the max-stress hidden field is not equal to this calculated Max Stress value, to make it so and submit the form. + // This creates a dynamic Stress tracker. It polls for the value of the survive skill, adds any protection from armor. + // With the total value, creates a new div for each and places it under a child called "bar-stress-renderer". var stressTrackMax = parseInt(html.find('#survive')[0].value); var armor = html.find('[id^="protectval-armor"]'); for (i = 0; i < armor.length; i++) { stressTrackMax += parseInt(armor[i].innerHTML); } + // This checks that the max-stress hidden field is equal to the calculated Max Stress value, if not it makes it so and submits the form. if (html.find('#max-stress')[0].value != stressTrackMax) { html.find('#max-stress')[0].value = stressTrackMax; @@ -113,8 +118,9 @@ export class DishonoredCharacterSheet extends ActorSheet { html.find('#bar-stress-renderer')[0].appendChild(div); } - // This creates a dynamic Experience tracker. For this it uses a max value of 30. This can be configured here. It creates a new div for each and places it under a child called "bar-void-renderer" - var expPointsMax = 30; + // This creates a dynamic Experience tracker. For this it uses a max value of 30. This can be configured here. + // It creates a new div for each and places it under a child called "bar-void-renderer" + var expPointsMax = game.settings.get("FVTT-Dishonored", "maxNumberOfExperience"); var i; for (i = 1; i <= expPointsMax; i++) { var div = document.createElement("DIV"); @@ -125,28 +131,47 @@ export class DishonoredCharacterSheet extends ActorSheet { html.find('#bar-exp-renderer')[0].appendChild(div); } - // This allows for each item-edit image to link open an item sheet. + // This creates a dynamic Momentum tracker. Dishonored only has 6 momentum, so this should never be changed. But this can be configured here. + // It creates a new div for each and places it under a child called "bar-mom-renderer" + var momPointsMax = 6; + var i; + for (i = 1; i <= momPointsMax; i++) { + var div = document.createElement("DIV"); + div.className = "mombox"; + div.id = "mom-" + i; + div.innerHTML = i; + div.style = "width: calc(100% / " + momPointsMax + ");" + html.find('#bar-mom-renderer')[0].appendChild(div); + } + + // Fires the function dishonoredRenderTracks as soon as the parameters exist to do so. + dishonoredActor.dishonoredRenderTracks(html, stressTrackMax, voidPointsMax, expPointsMax, momPointsMax); + + // This allows for each item-edit image to link open an item sheet. This uses Simple Worldbuilding System Code. html.find('.item-edit').click(ev => { const li = $(ev.currentTarget).parents(".item"); const item = this.actor.getOwnedItem(li.data("itemId")); item.sheet.render(true); }); - // This if statement checks if the form is editable, if not it hides some controls and, renders the tracks and then aborts any more of the script. + // This if statement checks if the form is editable, if not it hides controls used by the owner, then aborts any more of the script. if (!this.options.editable) { + // This hides the ability to Perform a Skill Test for the character for (i = 0; i < html.find('.check-button').length; i++) { html.find('.check-button')[i].style.display = 'none'; } + // This hides the ability to change the amount of Void Points the character has for (i = 0; i < html.find('.voidchange').length; i++) { html.find('.voidchange')[i].style.display = 'none'; } + // This hides all add item images for (i = 0; i < html.find('.add-item').length; i++) { html.find('.add-item')[i].style.display = 'none'; } + // This hides all remove item images for (i = 0; i < html.find('.item-delete').length; i++) { html.find('.item-delete')[i].style.display = 'none'; } - barRenderer(); return; }; @@ -154,10 +179,10 @@ export class DishonoredCharacterSheet extends ActorSheet { html.find('.cs-item-img').click(ev =>{ var itemType = $(ev.currentTarget).parents(".item")[0].getAttribute("data-item-type"); var itemId = $(ev.currentTarget).parents(".item")[0].getAttribute("data-item-id"); - this.rollGenericItem(event, itemType, itemId); + dishonoredActor.rollGenericItem(event, itemType, itemId, this.actor); }) - // Allows item-create images to create an item of a type defined individually by each button. + // Allows item-create images to create an item of a type defined individually by each button. This uses code found via the Foundry VTT System Tutorial. html.find('.item-create').click(ev => { event.preventDefault(); const header = event.currentTarget; @@ -173,80 +198,87 @@ export class DishonoredCharacterSheet extends ActorSheet { return this.actor.createOwnedItem(itemData); }); - // Allows item-delete images to allow deletion of the selected item. + // Allows item-delete images to allow deletion of the selected item. This uses Simple Worldbuilding System Code. html.find('.item-delete').click(ev => { const li = $(ev.currentTarget).parents(".item"); this.actor.deleteOwnedItem(li.data("itemId")); li.slideUp(200, () => this.render(false)); }); - + // Reads if a experience track box has been clicked, and if it has will either: set the value to the clicked box, or reduce the value by one. + // This check is dependent on various requirements, see comments in code. html.find('[id^="exp"]').click(ev => { var newTotalObject = $(ev.currentTarget)[0]; var newTotal = newTotalObject.id.replace(/\D/g, ''); - if (newTotalObject.getAttribute("data-value") == 1) { + // data-selected stores whether the track box is currently activated or not. This checks that the box is activated + if (newTotalObject.getAttribute("data-selected") === "true") { + // Now we check that the "next" track box is not activated. + // If there isn't one, or it isn't activated, we only want to decrease the value by 1 rather than setting the value. var nextCheck = 'exp-' + (parseInt(newTotal) + 1); - if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-value") != 1) { + if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-selected") != "true") { html.find('#total-exp')[0].value = html.find('#total-exp')[0].value - 1; - barRenderer(); this.submit(); - } else { + } + // If it isn't caught by the if, the next box is likely activated. If something happened, its safer to set the value anyway. + else { var total = html.find('#total-exp')[0].value; if (total != newTotal) { html.find('#total-exp')[0].value = newTotal; - barRenderer(); this.submit(); } } - } else { + } + // If the clicked box wasn't activated, we need to activate it now. + else { var total = html.find('#total-exp')[0].value; if (total != newTotal) { html.find('#total-exp')[0].value = newTotal; - barRenderer(); this.submit(); } } }); + // Reads if a momentum track box has been clicked, and if it has will either: set the value to the clicked box, or reduce the value by one. + // See line 186-220 for a more detailed break down on the context of each scenario. Momentum uses the same logic. html.find('[id^="mom"]').click(ev => { var newTotalObject = $(ev.currentTarget)[0]; var newTotal = newTotalObject.id.substring(4); - if (newTotalObject.getAttribute("data-value") == 1) { + if (newTotalObject.getAttribute("data-selected") === "true") { var nextCheck = 'mom-' + (parseInt(newTotal) + 1); - if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-value") != 1) { + if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-selected") != "true") { html.find('#total-mom')[0].value = html.find('#total-mom')[0].value - 1; - barRenderer(); + this.submit(); } else { var total = html.find('#total-mom')[0].value; if (total != newTotal) { html.find('#total-mom')[0].value = newTotal; - barRenderer(); + this.submit(); } } } else { var total = html.find('#total-mom')[0].value; if (total != newTotal) { html.find('#total-mom')[0].value = newTotal; - barRenderer(); + this.submit(); } } }); + // Reads if a stress track box has been clicked, and if it has will either: set the value to the clicked box, or reduce the value by one. + // See line 186-220 for a more detailed break down on the context of each scenario. Stress uses the same logic. html.find('[id^="stress"]').click(ev => { var newTotalObject = $(ev.currentTarget)[0]; var newTotal = newTotalObject.id.substring(7); - if (newTotalObject.getAttribute("data-value") == 1) { + if (newTotalObject.getAttribute("data-selected") === "true") { var nextCheck = 'stress-' + (parseInt(newTotal) + 1); console.log(html.find('#'+nextCheck)[0]); - if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-value") != 1) { + if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-selected") != "true") { html.find('#total-stress')[0].value = html.find('#total-stress')[0].value - 1; - barRenderer(); this.submit(); } else { var total = html.find('#total-stress')[0].value; if (total != newTotal) { html.find('#total-stress')[0].value = newTotal; - barRenderer(); this.submit(); } } @@ -254,26 +286,25 @@ export class DishonoredCharacterSheet extends ActorSheet { var total = html.find('#total-stress')[0].value; if (total != newTotal) { html.find('#total-stress')[0].value = newTotal; - barRenderer(); this.submit(); } } }); + // Reads if a void track box has been clicked, and if it has will either: set the value to the clicked box, or reduce the value by one. + // See line 186-220 for a more detailed break down on the context of each scenario. Void uses the same logic. html.find('[id^="void"]').click(ev => { var newTotalObject = $(ev.currentTarget)[0]; var newTotal = newTotalObject.id.replace(/\D/g, ''); - if (newTotalObject.getAttribute("data-value") == 1) { + if (newTotalObject.getAttribute("data-selected") === "true") { var nextCheck = 'void-' + (parseInt(newTotal) + 1); - if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-value") != 1) { + if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-selected") != "true") { html.find('#total-void')[0].value = html.find('#total-void')[0].value - 1; - barRenderer(); this.submit(); } else { var total = html.find('#total-void')[0].value; if (total != newTotal) { html.find('#total-void')[0].value = newTotal; - barRenderer(); this.submit(); } } @@ -281,13 +312,12 @@ export class DishonoredCharacterSheet extends ActorSheet { var total = html.find('#total-void')[0].value; if (total != newTotal) { html.find('#total-void')[0].value = newTotal; - barRenderer(); this.submit(); } } }); - + // If the decrease-void-max button is clicked it removes a point off the max-void and two points from max-mana. html.find('[id="decrease-void-max"]').click(ev => { html.find('#max-void')[0].value--; html.find('#max-mana')[0].value--; @@ -295,6 +325,7 @@ export class DishonoredCharacterSheet extends ActorSheet { this.submit(); }); + // If the increase-void-max button is clicked it adds a point to the max-void and two points to max-mana. html.find('[id="increase-void-max"]').click(ev => { html.find('#max-void')[0].value++; html.find('#max-mana')[0].value++; @@ -302,20 +333,27 @@ export class DishonoredCharacterSheet extends ActorSheet { this.submit(); }); + // Turns the Skill checkboxes into essentially a radio button. It removes any other ticks, and then checks the new skill. + // Finally a submit is required as data has changed. html.find('.skill-roll-selector').click(ev => { for (i = 0; i <= 5; i++) { html.find('.skill-roll-selector')[i].checked = false; } $(ev.currentTarget)[0].checked = true; + this.submit(); }); + // Turns the Style checkboxes into essentially a radio button. It removes any other ticks, and then checks the new style. + // Finally a submit is required as data has changed. html.find('.style-roll-selector').click(ev => { for (i = 0; i <= 5; i++) { html.find('.style-roll-selector')[i].checked = false; } $(ev.currentTarget)[0].checked = true; + this.submit(); }); + // If the check-button is clicked it grabs the selected skill and the selected style and fires the method rollSkillTest. See actor.js for further info. html.find('.check-button').click(ev => { for (i = 0; i <= 5; i++) { if (html.find('.skill-roll-selector')[i].checked === true) { @@ -332,124 +370,7 @@ export class DishonoredCharacterSheet extends ActorSheet { } } var checkTarget = parseInt(selectedSkillValue) + parseInt(selectedStyleValue); - this.rollSkillTest(event, checkTarget, selectedSkill, selectedStyle); + dishonoredActor.rollSkillTest(event, checkTarget, selectedSkill, selectedStyle, this.actor); }); - - - - function barRenderer() { - var voidPointsMax = html.find('#max-void')[0].value; - var stressTrackMax = parseInt(html.find('#survive')[0].value); - var armor = html.find('[id^="protectval-armor"]'); - for (i = 0; i < armor.length; i++) { - stressTrackMax += parseInt(armor[i].innerHTML); - } - for (i = 0; i < 6; i++) { - if (i + 1 <= html.find('#total-mom')[0].value) { - html.find('[id^="mom"]')[i].setAttribute("data-value", "1"); - html.find('[id^="mom"]')[i].style.backgroundColor = "#191813"; - html.find('[id^="mom"]')[i].style.color = "#ffffff"; - } else { - html.find('[id^="mom"]')[i].setAttribute("data-value", "0"); - html.find('[id^="mom"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; - html.find('[id^="mom"]')[i].style.color = ""; - } - } - for (i = 0; i < stressTrackMax; i++) { - if (i + 1 <= html.find('#total-stress')[0].value) { - html.find('[id^="stress"]')[i].setAttribute("data-value", "1"); - html.find('[id^="stress"]')[i].style.backgroundColor = "#191813"; - html.find('[id^="stress"]')[i].style.color = "#ffffff"; - } else { - html.find('[id^="stress"]')[i].setAttribute("data-value", "0"); - html.find('[id^="stress"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; - html.find('[id^="stress"]')[i].style.color = ""; - } - } - for (i = 0; i < voidPointsMax; i++) { - if (i + 1 <= html.find('#total-void')[0].value) { - html.find('[id^="void"]')[i].setAttribute("data-value", "1"); - html.find('[id^="void"]')[i].style.backgroundColor = "#191813"; - html.find('[id^="void"]')[i].style.color = "#ffffff"; - } else { - html.find('[id^="void"]')[i].setAttribute("data-value", "0"); - html.find('[id^="void"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; - html.find('[id^="void"]')[i].style.color = ""; - } - } - for (i = 0; i < expPointsMax; i++) { - if (i + 1 <= html.find('#total-exp')[0].value) { - html.find('[id^="exp"]')[i].setAttribute("data-value", "1"); - html.find('[id^="exp"]')[i].style.backgroundColor = "#191813"; - html.find('[id^="exp"]')[i].style.color = "#ffffff"; - } else { - html.find('[id^="exp"]')[i].setAttribute("data-value", "0"); - html.find('[id^="exp"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; - html.find('[id^="exp"]')[i].style.color = ""; - } - } - - } - - barRenderer(); - - } - - async updateVoidPoint(val) { - let updated = await DishonoredCharacter._changeVoidPoint(); - } - - async rollSkillTest(event, checkTarget, selectedSkill, selectedStyle) { - event.preventDefault(); - let rolldialog = await DishonoredRollDialog.create(); - if (rolldialog) { - let dicePool = rolldialog.get("dicePoolSlider"); - let focusTarget = parseInt(rolldialog.get("dicePoolFocus")); - let dishonoredRoll = new DishonoredRoll(); - dishonoredRoll.performSkillTest(dicePool, checkTarget, focusTarget, selectedSkill, selectedStyle, this.actor); - } - } - - async rollGenericItem(event, type, id) { - event.preventDefault(); - var item = this.actor.items.get(id); - let dishonoredRoll = new DishonoredRoll(); - switch(type) { - case "item": - dishonoredRoll.performItemRoll(item, this.actor); - break; - case "focus": - dishonoredRoll.performFocusRoll(item, this.actor); - break; - case "bonecharm": - dishonoredRoll.performBonecharmRoll(item, this.actor); - break; - case "weapon": - dishonoredRoll.performWeaponRoll(item, this.actor); - break; - case "armor": - dishonoredRoll.performArmorRoll(item, this.actor); - break; - case "talent": - dishonoredRoll.performTalentRoll(item, this.actor); - break; - case "contact": - dishonoredRoll.performContactRoll(item, this.actor); - break; - case "power": - dishonoredRoll.performPowerRoll(item, this.actor); - break; - } - } - - /* -------------------------------------------- */ - - /** @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; } } \ No newline at end of file diff --git a/module/actors/sheets/npc-sheet.js b/module/actors/sheets/npc-sheet.js index 6dc014e..7135e87 100644 --- a/module/actors/sheets/npc-sheet.js +++ b/module/actors/sheets/npc-sheet.js @@ -1,13 +1,7 @@ import { - DishonoredRollDialog -} from '../../apps/roll-dialog.js' -import { - DishonoredRoll -} from '../../roll.js' -/** - * Extend the basic ActorSheet with some very simple modifications - * @extends {ActorSheet} - */ + DishonoredSharedActorFunctions +} from '../actor.js' + export class DishonoredNPCSheet extends ActorSheet { /** @override */ @@ -31,7 +25,7 @@ export class DishonoredNPCSheet extends ActorSheet { const data = super.getData(); data.dtypes = ["String", "Number", "Boolean"]; - //Ensure skill and style values don't weigh over the max of 8 and minimum of 4. + //Ensure skill and style values don't weigh over the max of 8. if (data.data.skills.fight.value > 8) data.data.skills.fight.value = 8; if (data.data.skills.move.value > 8) data.data.skills.move.value = 8; if (data.data.skills.study.value > 8) data.data.skills.study.value = 8; @@ -44,9 +38,11 @@ export class DishonoredNPCSheet extends ActorSheet { if (data.data.styles.forcefully.value > 8) data.data.styles.forcefully.value = 8; if (data.data.styles.quietly.value > 8) data.data.styles.quietly.value = 8; if (data.data.styles.swiftly.value > 8) data.data.styles.swiftly.value = 8; - if (data.data.stress.value > data.data.stress.max) data.data.stress.value = data.data.stress.max; + // Checks if stress is larger than its max, if so, set to max. + if (data.data.stress.value > data.data.stress.max) data.data.stress.value = data.data.stress.max; + //Ensure skill and style values aren't lower than 4. if (data.data.skills.fight.value < 4) data.data.skills.fight.value = 4; if (data.data.skills.move.value < 4) data.data.skills.move.value = 4; if (data.data.skills.study.value < 4) data.data.skills.study.value = 4; @@ -59,10 +55,10 @@ export class DishonoredNPCSheet extends ActorSheet { if (data.data.styles.forcefully.value < 4) data.data.styles.forcefully.value = 4; if (data.data.styles.quietly.value < 4) data.data.styles.quietly.value = 4; if (data.data.styles.swiftly.value < 4) data.data.styles.swiftly.value = 4; + + // Checks if stress is below 0, if so - set it to 0. if (data.data.stress.value < 0) data.data.stress.value = 0; - if (data.data.experience < 0) data.data.experience = 0; - return data; } @@ -71,9 +67,15 @@ export class DishonoredNPCSheet extends ActorSheet { /** @override */ activateListeners(html) { super.activateListeners(html); + + // Opens the class DishonoredSharedActorFunctions for access at various stages. + let dishonoredActor = new DishonoredSharedActorFunctions() + // We use i alot in for loops. Best to assign it now for use later in multiple places. var i; + // This creates a dynamic Stress tracker. It polls for the value of the survive skill, adds any protection from armor. + // With the total value, creates a new div for each and places it under a child called "bar-stress-renderer". var stressTrackMax = parseInt(html.find('#survive')[0].value); var armor = html.find('[id^="protectval-armor"]'); for (i = 0; i < armor.length; i++) { @@ -93,92 +95,99 @@ export class DishonoredNPCSheet extends ActorSheet { html.find('#bar-stress-renderer')[0].appendChild(div); } - // Update Inventory Item + // Fires the function dishonoredRenderTracks as soon as the parameters exist to do so. + dishonoredActor.dishonoredRenderTracks(html, stressTrackMax); + + // This allows for each item-edit image to link open an item sheet. This uses Simple Worldbuilding System Code. html.find('.item-edit').click(ev => { const li = $(ev.currentTarget).parents(".item"); const item = this.actor.getOwnedItem(li.data("itemId")); item.sheet.render(true); }); - // Everything below here is only needed if the sheet is editable + // This if statement checks if the form is editable, if not it hides controls used by the owner, then aborts any more of the script. if (!this.options.editable) { + // This hides the ability to Perform a Skill Test for the character for (i = 0; i < html.find('.check-button').length; i++) { html.find('.check-button')[i].style.display = 'none'; } + // This hides all add item images for (i = 0; i < html.find('.add-item').length; i++) { html.find('.add-item')[i].style.display = 'none'; } + // This hides all remove item images for (i = 0; i < html.find('.item-delete').length; i++) { html.find('.item-delete')[i].style.display = 'none'; } - barRenderer(); return; }; + // This allows for all items to be rolled, it gets the current targets type and id and sends it to the rollGenericItem function. html.find('.cs-item-img').click(ev =>{ var itemType = $(ev.currentTarget).parents(".item")[0].getAttribute("data-item-type"); var itemId = $(ev.currentTarget).parents(".item")[0].getAttribute("data-item-id"); - this.rollGenericItem(event, itemType, itemId); + dishonoredActor.rollGenericItem(event, itemType, itemId, this.actor); }) - // Add Inventory Item + // Allows item-create images to create an item of a type defined individually by each button. This uses code found via the Foundry VTT System Tutorial. html.find('.item-create').click(ev => { event.preventDefault(); const header = event.currentTarget; - // Get the type of item to create. const type = header.dataset.type; - // Grab any data associated with this control. const data = duplicate(header.dataset); - // Initialize a default name. const name = `New ${type.capitalize()}`; - // Prepare the item object. const itemData = { name: name, type: type, data: data }; - // Remove the type from the dataset since it's in the itemData.type prop. delete itemData.data["type"]; - - // Finally, create the item! return this.actor.createOwnedItem(itemData); }); - // Delete Inventory Item + // Allows item-delete images to allow deletion of the selected item. This uses Simple Worldbuilding System Code. html.find('.item-delete').click(ev => { const li = $(ev.currentTarget).parents(".item"); this.actor.deleteOwnedItem(li.data("itemId")); li.slideUp(200, () => this.render(false)); }); + // Reads if a stress track box has been clicked, and if it has will either: set the value to the clicked box, or reduce the value by one. + // This check is dependent on various requirements, see comments in code. html.find('[id^="stress"]').click(ev => { var newTotalObject = $(ev.currentTarget)[0]; var newTotal = newTotalObject.id.substring(7); - if (newTotalObject.getAttribute("data-value") == 1) { + // data-selected stores whether the track box is currently activated or not. This checks that the box is activated + if (newTotalObject.getAttribute("data-selected") === "true") { + // Now we check that the "next" track box is not activated. + // If there isn't one, or it isn't activated, we only want to decrease the value by 1 rather than setting the value. var nextCheck = 'stress-' + (parseInt(newTotal) + 1); console.log(html.find('#'+nextCheck)[0]); - if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-value") != 1) { + if (!html.find('#'+nextCheck)[0] || html.find('#'+nextCheck)[0].getAttribute("data-selected") != "true") { html.find('#total-stress')[0].value = html.find('#total-stress')[0].value - 1; - barRenderer(); this.submit(); - } else { + } + // If it isn't caught by the if, the next box is likely activated. If something happened, its safer to set the value anyway. + else { var total = html.find('#total-stress')[0].value; if (total != newTotal) { html.find('#total-stress')[0].value = newTotal; - barRenderer(); this.submit(); } } - } else { + } + // If the clicked box wasn't activated, we need to activate it now. + else { var total = html.find('#total-stress')[0].value; if (total != newTotal) { html.find('#total-stress')[0].value = newTotal; - barRenderer(); this.submit(); } } }); + // Turns the Skill checkboxes into essentially a radio button. It removes any other ticks, and then checks the new skill. + // Finally a submit is required as data has changed. html.find('.skill-roll-selector').click(ev => { for (i = 0; i <= 5; i++) { html.find('.skill-roll-selector')[i].checked = false; @@ -186,6 +195,8 @@ export class DishonoredNPCSheet extends ActorSheet { $(ev.currentTarget)[0].checked = true; }); + // Turns the Style checkboxes into essentially a radio button. It removes any other ticks, and then checks the new style. + // Finally a submit is required as data has changed. html.find('.style-roll-selector').click(ev => { for (i = 0; i <= 5; i++) { html.find('.style-roll-selector')[i].checked = false; @@ -193,6 +204,7 @@ export class DishonoredNPCSheet extends ActorSheet { $(ev.currentTarget)[0].checked = true; }); + // If the check-button is clicked it grabs the selected skill and the selected style and fires the method rollSkillTest. See actor.js for further info. html.find('.check-button').click(ev => { for (i = 0; i <= 5; i++) { if (html.find('.skill-roll-selector')[i].checked === true) { @@ -209,90 +221,7 @@ export class DishonoredNPCSheet extends ActorSheet { } } var checkTarget = parseInt(selectedSkillValue) + parseInt(selectedStyleValue); - this.rollSkillTest(event, checkTarget, selectedSkill, selectedStyle); + dishonoredActor.rollSkillTest(event, checkTarget, selectedSkill, selectedStyle, this.actor); }); - - - - function barRenderer() { - var stressTrackMax = parseInt(html.find('#survive')[0].value); - var armor = html.find('[id^="protectval-armor"]'); - for (i = 0; i < armor.length; i++) { - stressTrackMax += parseInt(armor[i].innerHTML); - } - for (i = 0; i < stressTrackMax; i++) { - if (i + 1 <= html.find('#total-stress')[0].value) { - html.find('[id^="stress"]')[i].setAttribute("data-value", "1"); - html.find('[id^="stress"]')[i].style.backgroundColor = "#191813"; - html.find('[id^="stress"]')[i].style.color = "#ffffff"; - } else { - html.find('[id^="stress"]')[i].setAttribute("data-value", "0"); - html.find('[id^="stress"]')[i].style.backgroundColor = "rgb(255, 255, 255, 0.3)"; - html.find('[id^="stress"]')[i].style.color = ""; - } - } - - } - - barRenderer(); - - } - - async updateVoidPoint(val) { - let updated = await DishonoredCharacter._changeVoidPoint(); - } - - async rollSkillTest(event, checkTarget, selectedSkill, selectedStyle) { - event.preventDefault(); - let rolldialog = await DishonoredRollDialog.create(); - if (rolldialog) { - let dicePool = rolldialog.get("dicePoolSlider"); - let focusTarget = parseInt(rolldialog.get("dicePoolFocus")); - let dishonoredRoll = new DishonoredRoll(); - dishonoredRoll.performSkillTest(dicePool, checkTarget, focusTarget, selectedSkill, selectedStyle, this.actor); - } - } - - async rollGenericItem(event, type, id) { - event.preventDefault(); - var item = this.actor.items.get(id); - let dishonoredRoll = new DishonoredRoll(); - switch(type) { - case "item": - dishonoredRoll.performItemRoll(item, this.actor); - break; - case "focus": - dishonoredRoll.performFocusRoll(item, this.actor); - break; - case "bonecharm": - dishonoredRoll.performBonecharmRoll(item, this.actor); - break; - case "weapon": - dishonoredRoll.performWeaponRoll(item, this.actor); - break; - case "armor": - dishonoredRoll.performArmorRoll(item, this.actor); - break; - case "talent": - dishonoredRoll.performTalentRoll(item, this.actor); - break; - case "contact": - dishonoredRoll.performContactRoll(item, this.actor); - break; - case "power": - dishonoredRoll.performPowerRoll(item, this.actor); - break; - } - } - - /* -------------------------------------------- */ - - /** @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; } } \ No newline at end of file diff --git a/module/apps/roll-dialog.js b/module/apps/roll-dialog.js index e49fc80..54c32a0 100644 --- a/module/apps/roll-dialog.js +++ b/module/apps/roll-dialog.js @@ -1,9 +1,12 @@ export class DishonoredRollDialog { static async create() { + // Grab the RollDialog HTML file/ const html = await renderTemplate("systems/FVTT-Dishonored/templates/apps/dicepool.html"); + // Create a new promise for the HTML above. return new Promise((resolve) => { let formData = null; + // Create a new dialog. const dlg = new Dialog({ title: game.i18n.localize('dishonored.apps.dicepoolwindow'), content: html, @@ -19,6 +22,7 @@ export class DishonoredRollDialog { default: "roll", close: () => {} }); + // Render the dialog dlg.render(true); }); } diff --git a/module/dishonored.js b/module/dishonored.js index 79c3a9f..e1c2f24 100644 --- a/module/dishonored.js +++ b/module/dishonored.js @@ -38,12 +38,33 @@ import { /* -------------------------------------------- */ Hooks.once("init", async function() { + // Splash Screen console.log(`Initializing Dishonored Tabletop Roleplaying Game System`); + console.log(' @@') + console.log(' @ @@') + console.log('@ @@ @ @@ @@@@') + console.log(' @@ @@@ @@@ @@@ @@@@') + console.log(' @@@@ @@@@@@@@@@@@@@@@@@@@ @@ @@@@@') + console.log(' @@@@ @@@@ @@ @@@ @@@@@') + console.log(' @@@@@ @ @@@@@@@@ @@@ @@@@@ @@') + console.log(' @@@ @@@@ @@@@@@@ @@@@@ @@@@@@ @@@@') + console.log(' @@@@ @@@@ @ @@@@@@ @@@@@') + console.log(' @@@@ @ @@@@@@ @@@ @@@') + console.log(' @@@@@@@@@@@@@@@@ @@@') + console.log(' @@@@ @@@@ @@@') + console.log(' @@ @@@@@ @@@ @@@@@ @@') + console.log(' @@@@ @@@ @@@@@ @@') + console.log(' @@@ @@@@ @@@') + console.log(' @@@@ @ @@@@@@@@@@@@@@ @@') + console.log(' @@ @@ @@@@@ @@@') + console.log(' @@@@ @@ @@@') + console.log(' @@ @@@@@@') + console.log(' @ @@@@') + console.log(' @@@') + console.log(' @@@') + console.log(' @@') - /** - * Set an initiative formula for the system - * @type {String} - */ + // Define initiative for the system. CONFIG.Combat.initiative = { formula: "@styles.swiftly.value", decimals: 0 @@ -90,11 +111,36 @@ Hooks.once("init", async function() { // Register system settings game.settings.register("FVTT-Dishonored", "multipleComplications", { - name: 'Allow Multiple Complications?', + name: 'Multiple Complications:', hint: 'The rulebook states "Any die which rolled 20 causes a complication". This is slightly unclear and as of Version 8 of the PDF, this is still not clear - likely due to the incredible rarity. Enabling this will allow roles to display "There were x Complications" if multiple 20s are rolled. Disabling will just state a single complication.', scope: "world", type: Boolean, default: true, config: true }); + + game.settings.register("FVTT-Dishonored", "send2ActorPermissionLevel", { + name: 'Send2Actor User Role:', + hint: 'The contact item type has the ability to create an NPC, who should be allowed to see & use this functionality?', + scope: "world", + type: String, + default: "ASSISTANT", + config: true, + choices: { + "NONE": "Switch Off Send2Actor", + "PLAYER": "Players", + "TRUSTED": "Trusted Players", + "ASSISTANT": "Assistant Gamemaster", + "GAMEMASTER": "Gamemasters", + } + }); + + game.settings.register("FVTT-Dishonored", "maxNumberOfExperience", { + name: 'Maximum amount of Experience:', + hint: 'Max number of experience that can be given to a character. 30 is default, anything past 50 becomes almost unreadable.', + scope: "world", + type: Number, + default: 30, + config: true + }); }); \ No newline at end of file diff --git a/module/items/contact-sheet.js b/module/items/contact-sheet.js index 80966ca..a234671 100644 --- a/module/items/contact-sheet.js +++ b/module/items/contact-sheet.js @@ -46,19 +46,21 @@ export class DishonoredContactSheet extends ItemSheet { // Everything below here is only needed if the sheet is editable if (!this.options.editable) { - for (i = 0; i < html.find('.send2actor-button').length; i++) { - html.find('.send2actor-button')[i].style.display = 'none'; - } + html.find('.send2actor-button')[0].style.display = 'none'; return; } - html.find('.send2actor-button').click(ev => { - var name = $("[data-appid="+appId+"]").find('#name')[0].value; - var description = $("[data-appid="+appId+"]").find('.editor-content')[0].innerHTML; - var img = $("[data-appid="+appId+"]").find('.item-img')[0].getAttribute("src"); - this.send2Actor(name, description, img); - }); - + if (!game.user.hasRole(game.settings.get("FVTT-Dishonored", "send2ActorPermissionLevel"))) { + html.find('.send2actor-button')[0].style.display = 'none'; + } + else { + html.find('.send2actor-button').click(ev => { + var name = $("[data-appid="+appId+"]").find('#name')[0].value; + var description = $("[data-appid="+appId+"]").find('.editor-content')[0].innerHTML; + var img = $("[data-appid="+appId+"]").find('.item-img')[0].getAttribute("src"); + this.send2Actor(name, description, img).then(created => ui.notifications.info("NPC with the name: '"+name+"' has been created!")); + }); + } } async send2Actor(name, description, img) { diff --git a/module/items/focus-sheet.js b/module/items/focus-sheet.js index be417f5..a98d895 100644 --- a/module/items/focus-sheet.js +++ b/module/items/focus-sheet.js @@ -23,7 +23,7 @@ export class DishonoredFocusSheet extends ItemSheet { data.dtypes = ["String", "Number", "Boolean"]; if (data.data.rating > 5) data.data.rating = 5; - if (data.data.rating < 0) data.data.rating = 0; + if (data.data.rating < 2) data.data.rating = 2; return data; } diff --git a/module/roll.js b/module/roll.js index f33f487..1b8bd16 100644 --- a/module/roll.js +++ b/module/roll.js @@ -1,39 +1,54 @@ export class DishonoredRoll { async performSkillTest(dicePool, checkTarget, focusTarget, selectedSkill, selectedStyle, speaker) { + // Define some variables that we will be using later. let i; - let result = []; - for (i = 1; i <= dicePool; i++) { - let r = new Roll("d20"); - result.push(r.roll()._result); - } - let numberOfRegularSuccess = result.filter(x => x <= checkTarget && x > focusTarget).length; - let numberOfComplications = result.filter(x => x == 20).length; - let numberOfCriticalSuccess = result.filter(x => x <= focusTarget).length; - let actualSuccess = numberOfRegularSuccess + 2 * numberOfCriticalSuccess; + let result = 0; let diceString = ""; - for (i = 0; i <= result.length - 1; i++) { - if (result[i] <= focusTarget) { - diceString += '
  • ' + result[i] + '
  • '; - } else if (result[i] == 20) { - diceString += '
  • ' + result[i] + '
  • '; - } else { - diceString += '
  • ' + result[i] + '
  • '; + let success = 0; + let complication = 0; + // Define r as our dice roll we want to perform (1d20, 2d20, 3d20, 4d20 or 5d20). We will then roll it. + let r = new Roll(dicePool+"d20") + r.roll(); + // Now for each dice in the dice pool we want to check what the individual result was. + for (i = 0; i < dicePool; i++) { + result = r.dice[0].rolls[i].roll; + // If the result is less than or equal to the focus, that counts as 2 successes and we want to show the dice as green. + if (result <= focusTarget) { + diceString += '
  • ' + result + '
  • '; + success += 2; + } + // If the result is less than or equal to the target (the style and skill added together), that counts as 1 success but we want to show the dice as normal. + else if (result <= checkTarget) { + diceString += '
  • ' + result + '
  • '; + success += 1; + } + // If the result is 20, than we want to count it as a complication. We also want to show it as red! + else if (result == 20) { + diceString += '
  • ' + result + '
  • '; + complication += 1; + } + // If none of the above is true, the dice failed to do anything and is treated as normal. + else { + diceString += '
  • ' + result + '
  • '; } } - - if (actualSuccess == 1) { - var actualSuccessText = actualSuccess + game.i18n.format("dishonored.roll.success"); + // Here we want to check if the success was exactly one (as "1 Successes" doesn't make grammatical sense). We create a string for the Successes. + if (success == 1) { + var successText = success + game.i18n.format("dishonored.roll.success"); } else { - var actualSuccessText = actualSuccess + game.i18n.format("dishonored.roll.successPlural"); + var successText = success + game.i18n.format("dishonored.roll.successPlural"); } + // Check if we allow multiple complications, or if only one complication ever happens. const multipleComplicationsAllowed = game.settings.get("FVTT-Dishonored", "multipleComplications"); - if (numberOfComplications >= 1) { - if (numberOfComplications > 1 && multipleComplicationsAllowed === true) { + // If there is any complications, we want to crate a string for this. If we allow multiple complications and they exist, we want to pluralise this also. + // If no complications exist then we don't even show this box. + if (complication >= 1) { + if (complication > 1 && multipleComplicationsAllowed === true) { var localisedPluralisation = game.i18n.format("dishonored.roll.complicationPlural") - var complicationText = '

    ' + localisedPluralisation.replace('|#|', numberOfComplications) + '

    '; + var complicationText = '

    ' + localisedPluralisation.replace('|#|', complication) + '

    '; } else { var complicationText = '

    ' + game.i18n.format("dishonored.roll.complication") + '

    '; } @@ -41,8 +56,10 @@ export class DishonoredRoll { var complicationText = ''; } + // Set the flavour to "[Skill] [Style] Skill Test". This shows the chat what type of test occured. let flavor = game.i18n.format("dishonored.actor.skill." + selectedSkill) + " " + game.i18n.format("dishonored.actor.style." + selectedStyle) + game.i18n.format("dishonored.roll.test"); + // Build a dynamic html using the variables from above. let html = `
    @@ -63,24 +80,26 @@ export class DishonoredRoll {
    ` + complicationText + - `

    ` + actualSuccessText + `

    + `

    ` + successText + `

    - ` - ChatMessage.create({ - user: game.user._id, - isRoll: true, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - flavor: flavor, - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + ` + // Check if the dice3d module exists (Dice So Nice). If it does, post a roll in that and then send to chat after the roll has finished. If not just send to chat. + if(game.dice3d) { + game.dice3d.showForRoll(r).then(displayed => { + this.sendToChat(speaker, html, r, flavor); + }); + } + else { + this.sendToChat(speaker, html, r, flavor); + }; } async performItemRoll(item, speaker) { - console.log(item); + // Create variable div and populate it with localisation to use in the HTML. + var variablePrompt = game.i18n.format("dishonored.roll.item.quantity"); + let variable = `
    `+variablePrompt.replace('|#|', item.data.data.quantity)+`
    `; + // Create dynamic tags div and populate it with localisation to use in the HTML. if (item.data.data.cost > 0) { var costLocalisation = game.i18n.format("dishonored.roll.item.value"); var valueTag = "
    "+costLocalisation.replace('|#|', item.data.data.cost)+"
    "; @@ -88,90 +107,35 @@ export class DishonoredRoll { else { var valueTag = ''; } - let html = ` -
    -
    -
    - -

    `+item.data.name+`

    -
    -
    `+item.data.data.description+`
    -
    - `+valueTag+` -
    -
    -
    - ` - ChatMessage.create({ - user: game.user._id, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + // Send the divs to populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description, variable, valueTag).then(html=>this.sendToChat(speaker, html)); } async performFocusRoll(item, speaker) { - console.log(item); - var variablePrompt = game.i18n.format("dishonored.roll.focus.rating"); - let html = ` -
    -
    -
    - -

    `+item.data.name+`

    -
    -
    `+variablePrompt.replace('|#|', item.data.data.rating)+`
    -
    `+item.data.data.description+`
    -
    -
    - ` - ChatMessage.create({ - user: game.user._id, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + // Create variable div and populate it with localisation to use in the HTML. + let variablePrompt = game.i18n.format("dishonored.roll.focus.rating"); + let variable = `
    `+variablePrompt.replace('|#|', item.data.data.rating)+`
    `; + // Send the divs to populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description, variable).then(html=>this.sendToChat(speaker, html)); } async performBonecharmRoll(item, speaker) { - console.log(item); - let html = ` -
    -
    -
    - -

    `+item.data.name+`

    -
    -
    `+item.data.data.description+`
    -
    -
    - ` - ChatMessage.create({ - user: game.user._id, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + // Populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description).then(html=>this.sendToChat(speaker, html)); } async performWeaponRoll(item, speaker) { - console.log(item); + // Create variable div and populate it with localisation to use in the HTML. var variablePrompt = game.i18n.format("dishonored.roll.weapon.damage"); + let variable = `
    `+variablePrompt.replace('|#|', item.data.data.damage)+`
    `; + // Create dynamic tags div and populate it with localisation to use in the HTML. if (item.data.data.cost > 0) { var costLocalisation = game.i18n.format("dishonored.roll.item.value"); - var valueTag = "
    "+costLocalisation.replace('|#|', item.data.data.cost)+"
    "; + var tags = "
    "+costLocalisation.replace('|#|', item.data.data.cost)+"
    "; } else { - var valueTag = ''; + var tags = ''; } - var tags = ''; - var i; if (item.data.data.qualities.armorpierce) tags += "
    "+game.i18n.format("dishonored.actor.belonging.weapon.armorpierce")+"
    "; if (item.data.data.qualities.awkward) tags += "
    "+game.i18n.format("dishonored.actor.belonging.weapon.awkward")+"
    "; if (item.data.data.qualities.blast) tags += "
    "+game.i18n.format("dishonored.actor.belonging.weapon.blast")+"
    "; @@ -183,36 +147,15 @@ export class DishonoredRoll { if (item.data.data.qualities.mine) tags += "
    "+game.i18n.format("dishonored.actor.belonging.weapon.mine")+"
    "; if (item.data.data.qualities.rangeddistant) tags += "
    "+game.i18n.format("dishonored.actor.belonging.weapon.distant")+"
    "; if (item.data.data.qualities.rangednearby) tags += "
    "+game.i18n.format("dishonored.actor.belonging.weapon.nearby")+"
    "; - - let html = ` -
    -
    -
    - -

    `+item.data.name+`

    -
    -
    `+variablePrompt.replace('|#|', item.data.data.damage)+`
    - -
    - `+valueTag+` - `+tags+` -
    -
    -
    - ` - ChatMessage.create({ - user: game.user._id, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + // Send the divs to populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description, variable, tags).then(html=>this.sendToChat(speaker, html)); } async performArmorRoll(item, speaker) { - console.log(item); - var variablePrompt = game.i18n.format("dishonored.roll.armor.protect"); + // Create variable div and populate it with localisation to use in the HTML. + let variablePrompt = game.i18n.format("dishonored.roll.armor.protect"); + let variable = `
    `+variablePrompt.replace('|#|', item.data.data.protection)+`
    `; + // Create dynamic tags div and populate it with localisation to use in the HTML. if (item.data.data.cost > 0) { var costLocalisation = game.i18n.format("dishonored.roll.item.value"); var valueTag = "
    "+costLocalisation.replace('|#|', item.data.data.cost)+"
    "; @@ -220,85 +163,28 @@ export class DishonoredRoll { else { var valueTag = ''; } - var value = game.i18n.format("dishonored.roll.item.value"); - //
    `+value.replace('|#|', item.data.data.cost)+`
    - let html = ` -
    -
    -
    - -

    `+item.data.name+`

    -
    -
    `+variablePrompt.replace('|#|', item.data.data.protection)+`
    -
    `+item.data.data.description+`
    -
    - `+valueTag+` -
    -
    -
    - ` - ChatMessage.create({ - user: game.user._id, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + // Send the divs to populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description, variable, valueTag).then(html=>this.sendToChat(speaker, html)); } async performTalentRoll(item, speaker) { - console.log(item); + // Create variable div and populate it with localisation to use in the HTML. var variablePrompt = game.i18n.format("dishonored.roll.talent.type"); - let html = ` -
    -
    -
    - -

    `+item.data.name+`

    -
    -
    `+variablePrompt.replace('|#|', item.data.data.type)+`
    -
    `+item.data.data.description+`
    -
    -
    - ` - ChatMessage.create({ - user: game.user._id, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + var variable = `
    `+variablePrompt.replace('|#|', item.data.data.type)+`
    `; + // Send the divs to populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description, variable).then(html=>this.sendToChat(speaker, html)); } async performContactRoll(item, speaker) { - console.log(item); + // Create variable div and populate it with localisation to use in the HTML. var variablePrompt = game.i18n.format("dishonored.roll.contact.relation"); - let html = ` -
    -
    -
    - -

    `+item.data.name+`

    -
    -
    `+variablePrompt.replace('|#|', item.data.data.relationship)+`
    -
    `+item.data.data.description+`
    -
    -
    - ` - ChatMessage.create({ - user: game.user._id, - speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, - sound: "sounds/dice.wav" - }).then(msg => { - return msg - }); + var variable = `
    `+variablePrompt.replace('|#|', item.data.data.relationship)+`
    `; + // Send the divs to populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description, variable).then(html=>this.sendToChat(speaker, html)); } async performPowerRoll(item, speaker) { - console.log(item); + // Create variable div and populate it with localisation to use in the HTML. if (item.data.data.manacost > 0) { var localisedContent = game.i18n.format("dishonored.roll.power.mana"); var variablePrompt = "
    "+localisedContent.replace('|#|', item.data.data.manacost)+"
    "; @@ -307,28 +193,48 @@ export class DishonoredRoll { var variablePrompt = ''; } var runeValue = game.i18n.format("dishonored.roll.power.rune"); + // Create dynamic tags div and populate it with localisation to use in the HTML. + var tags = `
    `+runeValue.replace('|#|', item.data.data.runecost)+`
    `; + // Send the divs to populate a HTML template and sends to chat. + this.genericItemTemplate(item.data.img, item.data.name, item.data.data.description, variablePrompt, tags).then(html=>this.sendToChat(speaker, html)); + } + + async genericItemTemplate(img, name, description, variable, tags) { + // Checks if the following are empty/undefined. If so sets to blank. + let descField = description ? description : ''; + let tagField = tags ? tags : ''; + let varField = variable ? variable : ''; + // Builds a generic HTML template that is used for all items. let html = `
    - -

    `+item.data.name+`

    + +

    `+name+`

    - `+variablePrompt+` -
    `+item.data.data.description+`
    + `+varField+` +
    `+descField+`
    -
    `+runeValue.replace('|#|', item.data.data.runecost)+`
    + `+tagField+`
    - ` + `; + // Returns it for the sendToChat to utilise. + return html; + } + + async sendToChat(speaker, content, roll, flavor) { + // Send's Chat Message to foundry, if items are missing they will appear as false or undefined and this not be rendered. ChatMessage.create({ user: game.user._id, speaker: ChatMessage.getSpeaker({ actor: speaker }), - content: html, + flavor: flavor, + content: content, + roll: roll, sound: "sounds/dice.wav" }).then(msg => { - return msg + return msg; }); } } \ No newline at end of file diff --git a/styles/dishonored.css b/styles/dishonored.css index 5d0f12a..a04cd99 100644 --- a/styles/dishonored.css +++ b/styles/dishonored.css @@ -62,6 +62,10 @@ flex-direction: row; } +.dishonored .mombar { + width: 100%; +} + .dishonored .mombox { width: calc(100%/6); height: 14px; diff --git a/system.json b/system.json index 2153e62..c16ae4e 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "name": "FVTT-Dishonored", "title": "Dishonored Roleplaying Game Unofficial", "description": "An unofficial rendition of the Modiphius tabletop role playing game system.", - "version": "0.2.1", + "version": "0.2.2", "minimumCoreVersion": "0.6.4", "compatibleCoreVersion": "0.7.0", "templateVersion": 1, diff --git a/template.json b/template.json index 9ca0857..5c14096 100644 --- a/template.json +++ b/template.json @@ -8,10 +8,6 @@ "value": 0, "max": 4 }, - "mana": { - "value": 0, - "max": 6 - }, "truth1": "", "skills": { "fight": { @@ -83,6 +79,10 @@ "experience": 0, "templates": ["common"], "age": 21, + "mana": { + "value": 0, + "max": 6 + }, "truth2": "", "archetype": "", "outlook": "", @@ -115,7 +115,7 @@ }, "focus": { "templates": ["common"], - "rating": 0 + "rating": 2 }, "bonecharm": { "templates": ["common"] diff --git a/templates/actors/character-sheet.html b/templates/actors/character-sheet.html index 2fbf969..4e80ab8 100644 --- a/templates/actors/character-sheet.html +++ b/templates/actors/character-sheet.html @@ -110,12 +110,7 @@
    -
    1
    -
    2
    -
    3
    -
    4
    -
    5
    -
    6
    +