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 += '