/** * Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system. * @extends {Actor} */ export class MothershipActor extends Actor { /** * Augment the basic actor data with additional dynamic data. */ prepareData() { super.prepareData(); const actorData = this; const data = actorData.system; const flags = actorData.flags; // Make separate methods for each Actor type (character, npc, etc.) to keep // things organized. if (actorData.type === 'character') this._prepareCharacterData(actorData); else if (actorData.type === 'creature') this._prepareCreatureData(actorData); else if (actorData.type === 'ship') this._prepareShipData(actorData); } /** * Prepare Character type specific data */ _prepareCharacterData(actorData) { const data = actorData.system; let armorPoints = 0; let damageReduction = 0; const armors = this.getEmbeddedCollection("Item").filter(e => "armor" === e.type); for (let armor of armors) { if (armor.system.equipped) { armorPoints += armor.system.armorPoints; damageReduction += armor.system.damageReduction; } } data.stats.armor.mod = armorPoints; data.stats.armor.total = armorPoints+data.stats.armor.value; data.stats.armor.damageReduction = damageReduction; } /** * Prepare Character type specific data */ _prepareCreatureData(actorData) { const data = actorData; } _prepareShipData(actorData) { const data = actorData; } //central flavor text library for all chat messages getFlavorText (type,context,action) { //create function to check the library for a value const checkNested = function(obj = {}){ const args = Array.prototype.slice.call(arguments, 1); for (let i = 0; i < args.length; i++) { if (!obj || !obj.hasOwnProperty(args[i])) { return false; } obj = obj[args[i]]; }; return true; } let test = { level1:{ level2:{ level3:'level3' } } }; //create library let textLibrary = { //rolltable flavor text table: { death_save: { roll: { android: `You knock on death's door.`, human: `You knock on death's door.` } }, blunt_force_wound: { roll: { android: `You brace for the worst.`, human: `You brace for the worst.` } }, bleeding_wound: { roll: { android: `You brace for the worst.`, human: `You brace for the worst.` } }, gunshot_wound: { roll: { android: `You brace for the worst.`, human: `You brace for the worst.` } }, fire_explosives_wound: { roll: { android: `You brace for the worst.`, human: `You brace for the worst.` } }, gore_massive_wound: { roll: { android: `You brace for the worst.`, human: `You brace for the worst.` } }, panic_check: { roll: { android: `You lose motor control for a moment as your sensory inputs flicker.`, human: `Your heartbeat races out of control and you start to feel dizzy.` }, success: { android: `System resources free up and you regain control.`, human: `You take a deep breath and regain your composure.` } } }, //condition flavor text item: { condition: { add: { android: `You now suffer from this condition`, human: `You now suffer from this condition` }, increase: { android: `Your condition worsens.`, human: `Your condition worsens.` }, bleed: { android: `Your sensors detect significant nanofluid loss.`, human: `You feel dizzy as you bleed out.` } } }, //attribute flavor text attribute: { //stress flavor text stress: { increase: { android: `Power surges through your chest and you start to overheat.`, human: `You feel tightness in your chest and start to sweat.` }, increaseHeader: { android: `Stress Gained`, human: `Stress Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/macros/gain_stress.png`, human: `systems/mosh/images/icons/ui/macros/gain_stress.png` }, hitCeiling: { android: `System performance grinds to a halt. Reduce the most relevant Stat or Save by {{{modifySurplus}}}.`, human: `You hit rock bottom. Reduce the most relevant Stat or Save by {{{modifySurplus}}}.` }, pastCeiling: { android: `You sense unrecoverable data loss. Reduce the most relevant Stat or Save by {{{modifySurplus}}}.`, human: `You feel a part of yourself drift away. Reduce the most relevant Stat or Save by {{{modifySurplus}}}.` }, decrease: { android: `You soft-reset, purging unnecessary background processes.`, human: `You feel a sense of calm wash over you.` }, decreaseHeader: { android: `Stress Relieved`, human: `Stress Relieved` }, decreaseImg: { android: `systems/mosh/images/icons/ui/macros/relieve_stress.png`, human: `systems/mosh/images/icons/ui/macros/relieve_stress.png` }, hitFloor: { android: `You attain perfect focus and clarity. `, human: `You attain complete peace of mind.` }, pastFloor: { android: `You are already as focused as possible.`, human: `You are already as calm as possible.` } }, //health flavor text health: { increase: { android: `System resources free up and you feel energized.`, human: `You feel a burst of energy.` }, increaseHeader: { android: `Health Gained`, human: `Health Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/health.png`, human: `systems/mosh/images/icons/ui/attributes/health.png` }, hitCeiling: { android: `You are now at full health.`, human: `You are now at full health.` }, pastCeiling: { android: `You are already at full health.`, human: `You are already at full health.` }, decrease: { android: `Your pain receptors indicate core damage.`, human: `You wince from the pain.` }, decreaseHeader: { android: `Health Lost`, human: `Health Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/health.png`, human: `systems/mosh/images/icons/ui/attributes/health.png` }, hitFloor: { android: `You gain a wound and now have {{{(modifyNew)}}} Health.

@UUID[Compendium.mosh.macros_hotbar.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}`, human: `You gain a wound and now have {{{(modifyNew)}}} Health.

@UUID[Compendium.mosh.macros_hotbar.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}` }, pastFloor: { android: `You gain a wound and now have {{{(modifyNew)}}} Health.

@UUID[Compendium.mosh.macros_hotbar.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}`, human: `You gain a wound and now have {{{(modifyNew)}}} Health.

@UUID[Compendium.mosh.macros_hotbar.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}` } }, //hits flavor text hits: { increase: { android: `System resources free up and you feel energized.`, human: `You feel a burst of energy.` }, increaseHeader: { android: `Mended`, human: `Mended` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/health.png`, human: `systems/mosh/images/icons/ui/attributes/health.png` }, hitCeiling: { android: `You are now at full health.`, human: `You are now at full health.` }, pastCeiling: { android: `You are already at full health.`, human: `You are already at full health.` }, decrease: { android: `Your pain receptors indicate permanent damage and you gain a wound.

@UUID[Compendium.mosh.macros_hotbar.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}`, human: `You scream out from immense pain and you gain a wound.

@UUID[Compendium.mosh.macros_hotbar.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}` }, decreaseHeader: { android: `Wounded`, human: `Wounded` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/health.png`, human: `systems/mosh/images/icons/ui/attributes/health.png` }, hitFloor: { android: `You have died. Roll up a new character.`, human: `You have died. Roll up a new character.` }, pastFloor: { android: `You have died. Roll up a new character.`, human: `You have died. Roll up a new character.` } }, //stat/save flavor text stat: { check: { android: `You gain some confidence in your skills.`, human: `You gain some confidence in your skills.` }, save: { android: `You gain some confidence in your abilities.`, human: `You gain some confidence in your abilities.` }, increase: { android: `Data recovered. Central partition data restored.`, human: `You start to feel like yourself again.` }, increaseHeader: { android: `{{{fieldLabel}}} Gained`, human: `{{{fieldLabel}}} Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/{{{fieldId}}}.png`, human: `systems/mosh/images/icons/ui/attributes/{{{fieldId}}}.png` }, hitCeiling: { android: `You are now at maximum {{{fieldLabel}}}.`, human: `You are now at maximum {{{fieldLabel}}}.` }, pastCeiling: { android: `You are already at maximum {{{fieldLabel}}}.`, human: `You are already at maximum {{{fieldLabel}}}.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `{{{fieldLabel}}} Lost`, human: `{{{fieldLabel}}} Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/{{{fieldId}}}.png`, human: `systems/mosh/images/icons/ui/attributes/{{{fieldId}}}.png` }, hitFloor: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, pastFloor: { android: `Your {{{fieldLabel}}} cannot get any lower.`, human: `Your {{{fieldLabel}}} cannot get any lower.` } }, //radiation flavor text radiation: { damage: { android: `Catastro▒ic d⟑ta ▓loss de|/~ ⋥t⋱d`, human: `You stare into blackness and feel completely unable to pull yourself out of it.` } } }, //macro flavor text (embedding actions) macro: { wound: { bleeding: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.1DD8i6eCS6nx2Ip0]{Bleeding}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.1DD8i6eCS6nx2Ip0]{Bleeding}` }, bleeding_dis: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.tFcWNddtZvlv7tsg]{Bleeding [-]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.tFcWNddtZvlv7tsg]{Bleeding [-]}` }, bleeding_adv: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.xr2o2PU5vdrR6fxQ]{Bleeding [+]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.xr2o2PU5vdrR6fxQ]{Bleeding [+]}` }, blunt_force: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.TAjlQjA5AAy3qYL3]{Blunt Force}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.TAjlQjA5AAy3qYL3]{Blunt Force}` }, blunt_force_dis: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.oL3GH0HoEPlP8vzG]{Blunt Force [-]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.oL3GH0HoEPlP8vzG]{Blunt Force [-]}` }, blunt_force_adv: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.k0zf8ZGivRguc0wb]{Blunt Force [+]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.k0zf8ZGivRguc0wb]{Blunt Force [+]}` }, fire_explosives: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.bZi1qKmcKLFvnhZ2]{Fire & Explosives}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.bZi1qKmcKLFvnhZ2]{Fire & Explosives}` }, fire_explosives_dis: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.dJnQKDf0AlwK27QD]{Fire & Explosives [-]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.dJnQKDf0AlwK27QD]{Fire & Explosives [-]}` }, fire_explosives_adv: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.7rYhbDAaFeok1Daq]{Fire & Explosives [+]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.7rYhbDAaFeok1Daq]{Fire & Explosives [+]}` }, gore_massive: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.S9nnHKWYGSQmjQdp]{Gore}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.S9nnHKWYGSQmjQdp]{Gore}` }, gore_massive_dis: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.DuVjNlE4lsnR7Emc]{Gore [-]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.DuVjNlE4lsnR7Emc]{Gore [-]}` }, gore_massive_adv: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.eQPuDgwv8evetFIk]{Gore [+]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.eQPuDgwv8evetFIk]{Gore [+]}` }, gunshot: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.XgCOLv9UunBddUyW]{Gunshot}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.XgCOLv9UunBddUyW]{Gunshot}` }, gunshot_dis: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.fnVATRHYJEUlS3pR]{Gunshot [-]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.fnVATRHYJEUlS3pR]{Gunshot [-]}` }, gunshot_adv: { android: `@UUID[Compendium.mosh.macros_triggered.Macro.LTpa1ZYVZl4m9k6z]{Gunshot [+]}`, human: `@UUID[Compendium.mosh.macros_triggered.Macro.LTpa1ZYVZl4m9k6z]{Gunshot [+]}` } } } }; if (checkNested(textLibrary,type,context,action)) { //set full path to include class type if (this.system.class.value.toLowerCase() === 'android') { //return class appropriate text return textLibrary[type][context][action].android; } else { //return class appropriate text return textLibrary[type][context][action].human; } } else { return action; } }; //central roll parsing function | TAKES '1d10 [+]','low' | RETURNS '{1d10,1d10}kh' parseRollString(rollString,aimFor) { //init vars let rollDice = ``; let rollTemplate = ``; let rollStringParsed = ``; //translate rollString into foundry roll string format if (rollString.includes('[')) { //extract dice needed rollDice = rollString.substr(0,rollString.indexOf('[')).trim().concat(',',rollString.substr(0,rollString.indexOf('[')).trim()); //set template based on adv or dis if (rollString.includes('[-]')) { //use appropriate keep setting if (aimFor === 'low') { rollTemplate = '{[diceSet]}kh'; } else { rollTemplate = '{[diceSet]}kl'; } } else if (rollString.includes('[+]')) { //use appropriate keep setting if (aimFor === 'low') { rollTemplate = '{[diceSet]}kl'; } else { rollTemplate = '{[diceSet]}kh'; } } //make foundry roll string rollStringParsed = rollTemplate.replace('[diceSet]',rollDice); } else { rollStringParsed = rollString; } //return string in foundry format return rollStringParsed; } //central roll parsing function | TAKES '1d100',[Foundry roll object],true,true,41,'<' | RETURNS enriched Foundry roll object parseRollResult(rollString,rollResult,zeroBased,checkCrit,rollTarget,comparison) { //init vars let doubles = new Set([0, 11, 22, 33, 44, 55, 66, 77, 88, 99]); let enrichedRollResult = rollResult; let newTotal = 0; let diceFormula = ``; let compareIcon = ``; let outcome = ``; let outcomeHtml = ``; let diceIcon = ``; let diceBlock = ``; let critHighlight = ``; let rollHtml = ``; //init new fields in enriched roll result enrichedRollResult.critical = false; enrichedRollResult.success = false; enrichedRollResult.outcomeHtml = ``; enrichedRollResult.rollHtml = ``; //alter roll result object //change data point: change each 100 or 10 result to zero if (zeroBased) { //1d10 changes if (rollString.substr(0,rollString.indexOf("[")).trim() === '1d10' || rollString === '1d10') { //loop through dice enrichedRollResult.dice.forEach(function(roll){ //loop through each result roll.results.forEach(function(die) { //change any 10s to 0s if (die.result === 10) {die.result = 0;} }); }); //1d100 changes } else if (rollString.substr(0,rollString.indexOf("[")).trim() === '1d100' || rollString === '1d100') { //loop through dice enrichedRollResult.dice.forEach(function(roll){ //loop through each result roll.results.forEach(function(die) { //change any 10s to 0s if (die.result === 100) {die.result = 0;} }); }); } //pick a new winner if [-] or [+] if (rollString.includes("[")) { //if [-] pick a new lowest number if (rollResult.formula.includes("kl")) { //set result value newTotal = Math.min(enrichedRollResult.dice[0].results[0].result,enrichedRollResult.dice[1].results[0].result); //if [+] pick a new highest number } else if (rollResult.formula.includes("kh")) { //set result value newTotal = Math.max(enrichedRollResult.dice[0].results[0].result,enrichedRollResult.dice[1].results[0].result); } //use new value if a regular roll } else { //set result value newTotal = enrichedRollResult.dice[0].results[0].result; } //update final roll result enrichedRollResult._total = newTotal; } //enrich roll result object //add data point: detect critical if (checkCrit) { //check for crit if (doubles.has(enrichedRollResult.total)) { enrichedRollResult.critical = true; } else { enrichedRollResult.critical = false; } } //add data point: detect success/failure if (rollTarget) { //check for auto failure if (enrichedRollResult.total >= 90) { //result >= 90 is a failure enrichedRollResult.success = false; } else { //compare values based on compararison setting if (comparison === '<') { //check against being under the target if (enrichedRollResult.total < rollTarget) { //result >= target is a failure enrichedRollResult.success = true; } else { //result < target is a success enrichedRollResult.success = false; } } else { //check against being over the target if (enrichedRollResult.total > rollTarget) { //result < target is a failure enrichedRollResult.success = true; } else { //result < target is a success enrichedRollResult.success = false; } } } //add data point: outcome HTML //prepare outcome //success if (enrichedRollResult.success) { outcome = `SUCCESS!`; } else { outcome = `FAILURE!`; } //crit if (enrichedRollResult.critical) { outcome = `CRITICAL ` + outcome; } //make HTML outcomeHtml = `
${outcome}
`; //update final roll html string enrichedRollResult.outcomeHtml = outcomeHtml; } //add data point: interactive roll HTML //prepare variables //make comparison icon if (comparison === '<') { compareIcon = ''; } else { compareIcon = ''; } //prepare formula if (rollTarget) { //show dice against target diceFormula = rollString + ' ' + compareIcon + ' ' + rollTarget; } else { //just show the dice diceFormula = rollString; } //prepare dice block //loop through rolls enrichedRollResult.dice.forEach(function(roll){ //add header for this roll diceBlock = diceBlock + `
`; //add formula and result for this roll diceBlock = diceBlock + `
${roll.formula} ${roll.total.toString()}
`; //loop through dice roll.results.forEach(function(die) { //set highlight if crit is asked for if (checkCrit) { //check for crit if (doubles.has(die.result)) { //check for success if (rollTarget) { //check for auto failure if (die.result >= 90) { //result >= 90 is a failure, no highlight needed critHighlight = ' min'; } else { //compare values based on compararison setting if (comparison === '<') { //check against being under the target if (die.result < rollTarget) { //result >= target is a failure critHighlight = ' max'; } else { //result < target is a success critHighlight = ' min'; } } else { //check against being over the target if (die.result > rollTarget) { //result < target is a failure critHighlight = ' max'; } else { //result < target is a success critHighlight = ' min'; } } } } } else { //no highlight needed critHighlight = ''; } } else { //no highlight needed critHighlight = ''; } //add header for this die diceBlock = diceBlock + `
    `; //prepare dice icon if (roll.faces === 100 || roll.faces === 5) { diceIcon = `10`; } else { diceIcon = roll.faces.toString(); } //add formula and result for this die diceBlock = diceBlock + `
  1. ${die.result.toString()}
  2. `; //add footer for this die diceBlock = diceBlock + `
`; }); //add footer for this roll diceBlock = diceBlock + `
`; }); //set final roll variables in to template rollHtml = `
${diceFormula}

${enrichedRollResult.total}

`; //update final roll html string enrichedRollResult.rollHtml = rollHtml; //return the enriched roll result object return enrichedRollResult; } //central table rolling function | TAKES 'W36WFIpCfMknKgHy','1d10','low',true,true,41,'<' | RETURNS chat message showing roll table result async rollTable(tableId,rollString,aimFor,zeroBased,checkCrit,rollAgainst,comparison) { //init vars let currentLocation = ''; let tableLocation = ''; let messageTemplate = ``; let messageContent = ``; let msgDesc = ``; let flavorText = ``; let chatId = randomID(); let rollTarget = null; let valueAddress = []; //find where this table is located //get current compendium let compendium = game.packs; //loop through each compendium compendium.forEach(function(pack){ //is this a pack of rolltables? if (pack.metadata.type === 'RollTable') { //log where we are currentLocation = pack.metadata.id; //loop through each pack to find the right table pack.index.forEach(function(table) { //is this our table? if (table._id === tableId) { //grab the table location tableLocation = currentLocation; } }); } }); //get table data let tableData = await game.packs.get(tableLocation).getDocument(tableId); //get table name let tableName = tableData.name; //get table name let tableImg = tableData.img; //get table result let tableDie = tableData.formula.replace('-1',''); //bounce this request away if certain parameters are NULL //if rollString is STILL blank, redirect player to choose the roll if (!rollString) { //run the choose attribute function let chosenRollType = await this.chooseAdvantage(tableName,tableDie); //set variables rollString = chosenRollType[0]; } //pull stat to roll against, if needed if(rollAgainst){ //turn string address into array valueAddress = rollAgainst.split('.'); //set rollTarget rollTarget = valueAddress.reduce((a, v) => a[v], this); } //roll the dice //parse the roll string let parsedRollString = this.parseRollString(rollString,aimFor); //set panic die color let dsnTheme = game.settings.get('mosh','panicDieTheme'); //roll the dice let rollResult = await new Roll(parsedRollString + '[' + dsnTheme + ']').evaluate(); //interpret the results let parsedRollResult = this.parseRollResult(rollString,rollResult,zeroBased,checkCrit,rollTarget,comparison); //fetch the table result //get table result let tableResult = tableData.getResultsForRoll(parsedRollResult.total); //make any custom changes to chat message //panic check #19 customiziation if (tableName === 'Panic Check' && parsedRollResult.total === 19) { if (this.system.class.value.toLowerCase() === 'android') { tableResult[0].text = tableResult[0].text.replace("HEART ATTACK / SHORT CIRCUIT (ANDROIDS).","SHORT CIRCUIT."); } else { tableResult[0].text = tableResult[0].text.replace("HEART ATTACK / SHORT CIRCUIT (ANDROIDS).","HEART ATTACK."); } } //assign message description text msgDesc = this.getFlavorText('table',tableName.replaceAll('& ','').replaceAll(' ','_').toLowerCase(),'roll'); //assign flavor text flavorText = this.getFlavorText('table',tableName.replaceAll('& ','').replaceAll(' ','_').toLowerCase(),'success'); //generate chat message //prepare data let messageData = { actor: this, tableResult: tableResult, parsedRollResult: parsedRollResult, tableName: tableName, tableImg: tableImg, msgDesc: msgDesc, flavorText: flavorText }; //prepare template messageTemplate = 'systems/mosh/templates/chat/rollTable.html'; //render template messageContent = await renderTemplate(messageTemplate, messageData); //make message let macroMsg = await rollResult.toMessage({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); //wait for dice await game.dice3d.waitFor3DAnimationByMessageID(chatId); } //central adding addribute function | TAKES '1d10','low' | RETURNS player selected attribute. If parameters are null, it asks the player. async chooseAttribute(rollString,aimFor) { //wrap the whole thing in a promise, so that it waits for the form to be interacted with return new Promise(async (resolve) => { //init vars let playerItems = this.items; let skill = ``; let skillValue = 0; let buttonDesc = ``; //create HTML for this window //header let dialogDesc = `

Select a Stat

Choose the Stat that best suits the nature of this Skill Check. You will add your skill bonus to the Stat value for this roll (giving you a higher number to roll under).
`; //create button header if needed if (!rollString) { buttonDesc = `

Select your roll type:

`; } else { buttonDesc = ``; } //create final dialog data const dialogData = { title: `Choose a Stat`, content: dialogDesc + buttonDesc, buttons: {} }; //add adv/normal/dis buttons if we need a rollString if (!rollString) { //we need to generate a roll string //Advantage dialogData.buttons.button1 = { label: `Advantage`, callback: (html) => { rollString = `1d100 [+]`; aimFor = `low`; attribute = html.find("input[name='stat']:checked").attr("value"); resolve([rollString, aimFor, attribute]); }, icon: `` }; //Normal dialogData.buttons.button2 = { label: `Normal`, callback: (html) => { rollString = `1d100`; aimFor = `low`; attribute = html.find("input[name='stat']:checked").attr("value"); resolve([rollString, aimFor, attribute]); }, icon: `` }; //Disadvantage dialogData.buttons.button3 = { label: `Disadvantage`, callback: (html) => { rollString = `1d100 [-]`; aimFor = `low`; attribute = html.find("input[name='stat']:checked").attr("value"); resolve([rollString, aimFor, attribute]); }, icon: `` }; //add a next button if we dont need a rollString } else { dialogData.buttons.button1 = { label: `Next`, callback: (html) => { aimFor = `low`; attribute = html.find("input[name='stat']:checked").attr("value"); resolve([rollString, aimFor, attribute]); }, icon: `` }; } //render dialog const dialog = new Dialog(dialogData,{width: 600, height: 500}).render(true); }); } //central adding skill function | TAKES '1d10','low' | RETURNS player selected skill + value. If parameters are null, it asks the player. async chooseSkill(dlgTitle,rollString) { //wrap the whole thing in a promise, so that it waits for the form to be interacted with return new Promise(async (resolve) => { //init vars let playerItems = this.items; let skill = ``; let skillValue = 0; let buttonDesc = ``; //create HTML for this window //header let skillHeader = `

Add a Skill?

If you have a Skill that is relevant to the task at hand, you can add the Skill’s bonus to your Stat or Save before making your roll (giving you a higher number to roll under).
`; //skill template let skillRow = ` `; //make list of skill using template //create skillList string let skillList = ``; //create skill counter let skillCount = 0; //create dialog pixel counter let dialogHeight = 192; //loop through and create skill rows for (let item of playerItems) { //check if this is a skill if (item.type === "skill") { //set temprow as template let tempRow = skillRow; //replace ID tempRow = tempRow.replaceAll("[RADIO_ID]",item.name); //replace value tempRow = tempRow.replace("[RADIO_VALUE]",item.system.bonus); //replace img tempRow = tempRow.replace("[RADIO_IMG]",item.img); //replace name tempRow = tempRow.replace("[RADIO_NAME]",item.name); //replace desc tempRow = tempRow.replace("[RADIO_DESC]",item.system.description.replace("

",": ")); //add to skillList skillList = skillList + tempRow; //increment skill count skillCount++; //increment pixel counter dialogHeight = dialogHeight + 77; } } //check if there are no skills, and adjust prompt height accordingly if (skillCount === 0) { //set window height dialogHeight = 105; //make skill header blank skillHeader = ``; } //create button header if needed if (!rollString) { buttonDesc = `

Select your roll type:

`; } else { buttonDesc = ``; } //create final dialog data const dialogData = { title: dlgTitle, content: skillHeader + skillList + buttonDesc, buttons: {} }; //add adv/normal/dis buttons if we need a rollString if (!rollString) { //we need to generate a roll string //Advantage dialogData.buttons.button1 = { label: `Advantage`, callback: (html) => { rollString = `1d100 [+]`; skill = html.find("input[name='skill']:checked").attr("id"); skillValue = html.find("input[name='skill']:checked").attr("value"); resolve([rollString, skill, skillValue]); }, icon: `` }; //Normal dialogData.buttons.button2 = { label: `Normal`, callback: (html) => { rollString = `1d100`; skill = html.find("input[name='skill']:checked").attr("id"); skillValue = html.find("input[name='skill']:checked").attr("value"); resolve([rollString, skill, skillValue]); }, icon: `` }; //Disadvantage dialogData.buttons.button3 = { label: `Disadvantage`, callback: (html) => { rollString = `1d100 [-]`; skill = html.find("input[name='skill']:checked").attr("id"); skillValue = html.find("input[name='skill']:checked").attr("value"); resolve([rollString, skill, skillValue]); }, icon: `` }; //add a next button if we dont need a rollString } else { dialogData.buttons.button1 = { label: `Next`, callback: (html) => { skill = html.find("input[name='skill']:checked").attr("id"); skillValue = html.find("input[name='skill']:checked").attr("value"); resolve([rollString, aimFor, skill, skillValue]); }, icon: `` }; } //render dialog const dialog = new Dialog(dialogData,{width: 600, height: dialogHeight}).render(true); }); } //central adding skill function | TAKES 'Body Save','1d10' | RETURNS player selected rollString. async chooseAdvantage(dlgTitle,die) { //wrap the whole thing in a promise, so that it waits for the form to be interacted with return new Promise(async (resolve) => { //init vars let rollString = ``; //make diceRoll variants let dieAdv = die + ` [+]`; let dieDis = die + ` [-]`; //create final dialog data const dialogData = { title: dlgTitle, content: `

Select your roll type:

`, buttons: {} }; //add buttons //Advantage dialogData.buttons.button1 = { label: `Advantage`, callback: (html) => { rollString = dieAdv; resolve([rollString]); }, icon: `` }; //Normal dialogData.buttons.button2 = { label: `Normal`, callback: (html) => { rollString = die; resolve([rollString]); }, icon: `` }; //Disadvantage dialogData.buttons.button3 = { label: `Disadvantage`, callback: (html) => { rollString = dieDis; resolve([rollString]); }, icon: `` }; //render dialog const dialog = new Dialog(dialogData,{width: 600, height: 105}).render(true); }); } //central check rolling function | TAKES '1d10','low','combat','Geology',10,[weapon item] | RETURNS chat message showing check result async rollCheck(rollString,aimFor,attribute,skill,skillValue,weapon) { //init vars let messageTemplate = ``; let messageContent = ``; let parsedDamageString = null; let damageResult = null; let parsedDamageResult = null; let critFail = false; let critMod = ``; let outcomeVerb = ``; let flavorText = ``; let needsDesc = false; let woundEffect = ``; let msgHeader = ``; let msgImgPath = ``; let chatId = randomID(); //if this is a weapon roll if (weapon) { //check to see if this weapon uses ammo if (weapon.system.useAmmo === true) { //if the weapon has enough shots remaining to shoot if (weapon.system.curShots >= weapon.system.shotsPerFire) { //reduce shots by shotsPerFire weapon.system.curShots -= weapon.system.shotsPerFire; //update players weapon this.updateEmbeddedDocuments('Item', [weapon]); //if the weapon doesn't have enough shots remaining to shoot } else { //if the weapon has enough ammo remaining to shoot if (weapon.system.ammo + weapon.system.curShots >= weapon.system.shotsPerFire) { //tell player we need to reload and ask what to do let t = await this.askReload(weapon._id); //exit function return; //if the weapon doesn't have enough ammo remaining to shoot } else { //tell player we are out of ammo let t = await this.outOfAmmo(); //exit function return; } } } } //bounce this request away if certain parameters are NULL //if attribute is blank, redirect player to choose an attribute if (!attribute) { //run the choose attribute function let chosenAttributes = await this.chooseAttribute(rollString,aimFor); //set variables rollString = chosenAttributes[0]; aimFor = chosenAttributes[1]; attribute = chosenAttributes[2]; } //if skill is blank and actor is a character, redirect player to choose a skill if (!skill && this.type === 'character') { //run the choose attribute function let chosenSkills = await this.chooseSkill(this.system.stats[attribute].rollLabel,rollString); //set variables rollString = chosenSkills[0]; skill = chosenSkills[1]; skillValue = chosenSkills[2]; } //if rollString is STILL blank, redirect player to choose the roll if (!rollString) { //run the choose attribute function let chosenRollType = await this.chooseAdvantage(this.system.stats[attribute].rollLabel,'1d100'); //set variables rollString = chosenRollType[0]; } //make the rollTarget value //retrieve the attribute let rollTarget = this.system.stats[attribute].value //add the mod value rollTarget = Number(rollTarget) + (Number(this.system.stats[attribute].mod) || 0); //add the skill value rollTarget = Number(rollTarget) + Number(skillValue || 0); //roll the dice //parse the roll string let parsedRollString = this.parseRollString(rollString,aimFor); //roll the dice let rollResult = await new Roll(parsedRollString).evaluate(); //interpret the results let parsedRollResult = this.parseRollResult(rollString,rollResult,true,true,rollTarget,'<'); //prep damage dice in case its needed if(weapon && parsedRollResult.success) { //parse the roll string parsedDamageString = this.parseRollString(weapon.system.damage,'high'); } //set chat message text //message header msgHeader = this.system.stats[attribute].rollLabel; //set header image msgImgPath = 'systems/mosh/images/icons/ui/attributes/' + attribute + '.png'; //set roll result as greater than or less than if (parsedRollResult.success) { outcomeVerb = `rolled`; } else { outcomeVerb = `did not roll`; } //prepare flavor text if (weapon) { //override message header msgHeader = weapon.name; //override header image msgImgPath = weapon.img; //set damage dice color let dsnTheme = game.settings.get('mosh','damageDiceTheme'); //set crit damage effect if (parsedRollResult.success === true && parsedRollResult.critical === true) { if (game.settings.get('mosh','critDamage') === 'doubleDamage') { critMod = ` * 2`; } else if (game.settings.get('mosh','critDamage') === 'doubleDice') { critMod = ` + ` + parsedDamageString + '[' + dsnTheme + ']'; } else if (game.settings.get('mosh','critDamage') === 'weaponValue') { critMod = ` + ` + weapon.system.critDmg + '[' + dsnTheme + ']'; } } //flavor text = the attack roll result if (parsedRollResult.success === true) { //if success if (weapon.system.damage === "Str/10") { //determine the damage string flavorText = 'You strike your target for [[floor(' + this.system.stats.strength.value + '/10)]] damage.'; } else { flavorText = 'You inflict [[' + parsedDamageString + '[' + dsnTheme + ']' + critMod + ']] points of damage.'; } } else { //increase stress by 1 and retrieve the flavor text from the result let addStress = await this.modifyActor('system.other.stress.value',1,null,false); flavorText = addStress[1]; //if critical failure, make sure to ask for panic check if (parsedRollResult.critical === true) { //set crit fail critFail = true; } } //determine if this roll needs a description area if (weapon.system.description || weapon.system.woundEffect) { needsDesc = true; } //create wound effect string if (weapon.system.woundEffect) { //start with string as is woundEffect = weapon.system.woundEffect; //prepare array for looping //replace ' [-]' and ' [+]' woundEffect = woundEffect.replaceAll(' [-]','_dis').replaceAll(' [+]','_adv'); //simplify wounds woundEffect = woundEffect.replace('Bleeding','bleeding'); woundEffect = woundEffect.replace('Blunt Force','blunt_force'); woundEffect = woundEffect.replace('Fire & Explosives','fire_explosives'); woundEffect = woundEffect.replace('Gore & Massive','gore_massive'); woundEffect = woundEffect.replace('Gunshot','bleeding'); //split string let woundArray = woundEffect.split(' '); //loop through this string and replace each wound effect with macro UUID woundArray.forEach((element, index, array) => { array[index] = this.getFlavorText('macro', 'wound', element); }); //combine back into string woundEffect = woundArray.join(' '); } } else { //prep text based on success or failure if (parsedRollResult.success === false) { //increase stress by 1 and retrieve the flavor text from the result let addStress = await this.modifyActor('system.other.stress.value',1,null,false); flavorText = addStress[1]; //if critical failure, make sure to ask for panic check if (parsedRollResult.critical === true) { //set crit fail critFail = true; } } else { //flavor text = generic roll success if (this.system.stats[attribute].rollLabel.includes(" Save")) { //set final footer flavorText = this.getFlavorText('attribute','stat','save'); } else { //set final footer flavorText = this.getFlavorText('attribute','stat','check'); } } } //generate chat message //prepare data let messageData = { actor: this, parsedRollResult: parsedRollResult, skill: skill, skillValue: skillValue, weapon: weapon, msgHeader: msgHeader, msgImgPath: msgImgPath, outcomeVerb: outcomeVerb, attribute: this.system.stats[attribute].label, flavorText: flavorText, needsDesc: needsDesc, woundEffect: woundEffect, critFail: critFail }; //prepare template messageTemplate = 'systems/mosh/templates/chat/rollCheck.html'; //render template messageContent = await renderTemplate(messageTemplate, messageData); //make message let macroMsg = await rollResult.toMessage({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); //wait for dice await game.dice3d.waitFor3DAnimationByMessageID(chatId); } //central function to modify actors | TAKES 'system.other.stress.value',-1,'-1d5',true | RETURNS change details, and optional chat message async modifyActor(fieldAddress,modValue,modRollString,outputChatMsg) { //init vars let messageTemplate = ``; let messageContent = ``; let msgHeader = ``; let msgImgPath = ``; let modifyMinimum = null; let modifyMaximum = null; let modifyCurrent = null; let modifyChange = 0; let modifyNew = null; let modifyDifference = null; let modifySurplus = null; let msgAction = ``; let msgFlavor = ``; let msgOutcome = ``; let msgChange = ``; let chatId = randomID(); //get information about this field from the actor //set path for important fields //field value let fieldValue = fieldAddress.split('.'); //fieldMin let fieldMin = fieldAddress.split('.'); fieldMin.pop(); fieldMin.push("min"); //fieldMax let fieldMax = fieldAddress.split('.'); fieldMax.pop(); fieldMax.push("max"); //fieldLabel let fieldLabel = fieldAddress.split('.'); fieldLabel.pop(); fieldLabel.push("label"); //fieldId let fieldId = fieldValue[fieldValue.length-2]; //get min value for this field, if it exists modifyMinimum = (fieldMin.reduce((a, v) => a[v], this) || null); //get max value for this field, if it exists modifyMaximum = (fieldMax.reduce((a, v) => a[v], this) || null); //get current value for this field modifyCurrent = fieldValue.reduce((a, v) => a[v], this); //calculate the change, whether from a value, roll (can only be one, it will check modValue first) //apply the modValue directly with no roll if (modValue) { //update modChange modifyChange = modValue; //calculate impact to the actor //set the new value modifyNew = modifyCurrent + modifyChange; //restrict new value based on min/max //cap min if(modifyMinimum) { if(modifyNew < modifyMinimum) { modifyNew = modifyMinimum; } } //cap max if(modifyMaximum) { if(modifyNew > modifyMaximum) { modifyNew = modifyMaximum; } } //measure difference between old and new value modifyDifference = modifyNew - modifyCurrent; //measure any surplus if we exceeded min/max modifySurplus = modifyChange - modifyDifference; //update actor //prepare update JSON let updateData = JSON.parse(`{"` + fieldAddress + `": ` + modifyNew + `}`); //update field this.update(updateData); //create modification text (for chat message or return values) //get flavor text if (modifyChange > 0) { msgFlavor = this.getFlavorText('attribute',fieldId,'increase'); msgChange = 'increased'; msgHeader = this.getFlavorText('attribute',fieldId,'increaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'increaseImg'); } else if (modifyChange < 0) { msgFlavor = this.getFlavorText('attribute',fieldId,'decrease'); msgChange = 'decreased'; msgHeader = this.getFlavorText('attribute',fieldId,'decreaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'decreaseImg'); } //get modification description //calculate change type if (modifySurplus < 0) { msgAction = 'pastFloor'; } else if (modifySurplus > 0) { msgAction = 'pastCeiling'; } else if (modifySurplus === 0 && modifyNew === modifyMinimum && modifyChange != 0) { msgAction = 'hitFloor'; } else if (modifySurplus === 0 && modifyNew === modifyMaximum && modifyChange != 0) { msgAction = 'hitCeiling'; } else if (modifyChange > 0) { msgAction = 'increase'; } else if (modifyChange < 0) { msgAction = 'decrease'; } //set message outcome if (msgAction === 'increase' || msgAction === 'decrease') { msgOutcome = fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}.`; } else if (modifyDifference === 0 && modifySurplus != 0) { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction); } else { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` ` + fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}.`; } //push message if asked if (outputChatMsg) { //generate chat message //prepare data let messageData = { actor: this, msgHeader: msgHeader, msgImgPath: msgImgPath, msgFlavor: msgFlavor, msgOutcome: msgOutcome }; //prepare template messageTemplate = 'systems/mosh/templates/chat/modifyActor.html'; //render template messageContent = await renderTemplate(messageTemplate, messageData); //push message ChatMessage.create({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); } //return modification values return [msgFlavor,msgOutcome,msgChange]; //calculate change from the modRollString } else { //roll the dice //parse the roll string let parsedRollString = this.parseRollString(modRollString,'low'); //roll the dice let rollResult = await new Roll(parsedRollString).evaluate(); //interpret the results let parsedRollResult = this.parseRollResult(modRollString,rollResult,false,false,null,null); //update modChange modifyChange = modifyChange + parsedRollResult.total; //calculate impact to the actor //set the new value modifyNew = modifyCurrent + modifyChange; //restrict new value based on min/max //cap min if(modifyMinimum) { if(modifyNew < modifyMinimum) { modifyNew = modifyMinimum; } } //cap max if(modifyMaximum) { if(modifyNew > modifyMaximum) { modifyNew = modifyMaximum; } } //measure difference between old and new value modifyDifference = modifyNew - modifyCurrent; //measure any surplus if we exceeded min/max modifySurplus = modifyChange - modifyDifference; //update actor //prepare update JSON let updateData = JSON.parse(`{"` + fieldAddress + `": ` + modifyNew + `}`); //update field this.update(updateData); //create modification text (for chat message or return values) //get flavor text if (modifyChange > 0) { msgFlavor = this.getFlavorText('attribute',fieldId,'increase'); msgChange = 'increased'; msgHeader = this.getFlavorText('attribute',fieldId,'increaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'increaseImg'); } else if (modifyChange < 0) { msgFlavor = this.getFlavorText('attribute',fieldId,'decrease'); msgChange = 'decreased'; msgHeader = this.getFlavorText('attribute',fieldId,'decreaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'decreaseImg'); } //get modification description //calculate change type if (modifySurplus < 0) { msgAction = 'pastFloor'; } else if (modifySurplus > 0) { msgAction = 'pastCeiling'; } else if (modifySurplus === 0 && modifyNew === modifyMinimum && modifyChange != 0) { msgAction = 'hitFloor'; } else if (modifySurplus === 0 && modifyNew === modifyMaximum && modifyChange != 0) { msgAction = 'hitCeiling'; } else if (modifyChange > 0) { msgAction = 'increase'; } else if (modifyChange < 0) { msgAction = 'decrease'; } //set message outcome if (msgAction === 'increase' || msgAction === 'decrease') { msgOutcome = fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}.`; } else if (modifyDifference === 0 && modifySurplus != 0) { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction); } else { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` ` + fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}.`; } //push message if asked if (outputChatMsg) { //generate chat message //prepare data let messageData = { actor: this, parsedRollResult: parsedRollResult, msgHeader: msgHeader, msgImgPath: msgImgPath, msgFlavor: msgFlavor, modRollString: modRollString, msgOutcome: msgOutcome }; //prepare template messageTemplate = 'systems/mosh/templates/chat/modifyActor.html'; //render template messageContent = await renderTemplate(messageTemplate, messageData); //make message let macroMsg = await rollResult.toMessage({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); //wait for dice await game.dice3d.waitFor3DAnimationByMessageID(chatId); } //return modification values return [msgFlavor,msgOutcome,msgChange]; } } //central function to modify an actors items | TAKES 'system.other.stress.value',-1,'-1d5',true | RETURNS change details, and optional chat message async modifyItem(itemName,addAmount) { //init vars let messageTemplate = ``; let messageContent = ``; let newValue = 0; let msgFlavor = ``; let chatId = randomID(); //add or increase the count of the item, depending on type if (this.items.getName(itemName)) { //if this is an item, increase the count if (this.items.getName(itemName).type === 'item') { //get current quantity oldValue = this.items.getName(itemName).system.quantity; newValue = newValue + addAmount; //increase severity of the condition this.items.getName(itemName).update({'system.quantity': newValue}); //create message text msgFlavor = `Quantity has increased from ` + oldValue + ` to ` + newValue + `.`; //if this is a condition, increase the severity } else if (this.items.getName(itemName).type === 'condition') { //get current severity oldValue = this.items.getName(itemName).system.severity; newValue = newValue + addAmount; //increase severity of the condition this.items.getName(itemName).update({'system.severity': newValue}); //create message text msgFlavor = this.getFlavorText('item','condition','increase') + `Severity has increased from ` + oldValue + ` to ` + newValue + `.`; //if this is a weapon or armor, add another one } else if (this.items.getName(itemName).type === 'weapon' || this.items.getName(itemName).type === 'armor') { //add item to the players inventory const itemData = game.items.getName(itemName).toObject(); await this.createEmbeddedDocuments('Item', [itemData]); //create message text msgFlavor = `You add another one of these to your inventory.`; } } else { //if this is an item, add it if (game.items.getName(itemName).type === 'item') { //give the character the item const itemData = game.items.getName(itemName).toObject(); await this.createEmbeddedDocuments('Item', [itemData]); //increase severity of the condition this.items.getName(itemName).update({'system.quantity': addAmount}); //create message text msgFlavor = `You add ` + addAmount + ` of these to your inventory..`; //if this is a condition, add it } else if (game.items.getName(itemName).type === 'condition') { //give the character the item const itemData = game.items.getName(itemName).toObject(); await this.createEmbeddedDocuments('Item', [itemData]); //increase severity of the condition this.items.getName(itemName).update({'system.severity': addAmount}); //create message text msgFlavor = this.getFlavorText('item','condition','add') + `, with a severity of ` + addAmount + `.`; //if this is a weapon or armor, add it } else if (game.items.getName(itemName).type === 'weapon' || this.items.getName(itemName).type === 'armor') { //add item to the players inventory const itemData = game.items.getName(itemName).toObject(); await this.createEmbeddedDocuments('Item', [itemData]); //create message text msgFlavor = `You add this to your inventory.`; } } //generate chat message //get item name let msgHeader = this.items.getName(itemName).name; //get item image let msgImgPath = this.items.getName(itemName).img; //prepare data let messageData = { actor: this, msgHeader: msgHeader, msgImgPath: msgImgPath, flavorText: flavorText }; //prepare template messageTemplate = 'systems/mosh/templates/chat/modifyItem.html'; //render template messageContent = await renderTemplate(messageTemplate, messageData); //make message let macroMsg = await rollResult.toMessage({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); //wait for dice await game.dice3d.waitFor3DAnimationByMessageID(chatId); } //central adding skill function | TAKES 'Body Save','1d10' | RETURNS player selected rollString. async askReload(itemId) { //wrap the whole thing in a promise, so that it waits for the form to be interacted with return new Promise(async (resolve) => { //create final dialog data const dialogData = { title: `Weapon Issue`, content: `

Out of ammo, you need to reload.


`, buttons: {} }; //add buttons //Advantage dialogData.buttons.roll = { label: `Reload`, callback: () => this.reloadWeapon(itemId), icon: `` }; //Normal dialogData.buttons.cancel = { label: `Cancel`, callback: () => { }, icon: `` }; //render dialog const dialog = new Dialog(dialogData).render(true); }); } //central adding skill function | TAKES 'Body Save','1d10' | RETURNS player selected rollString. async outOfAmmo() { //wrap the whole thing in a promise, so that it waits for the form to be interacted with return new Promise(async (resolve) => { //create final dialog data const dialogData = { title: `Weapon Issue`, content: `

Out of ammo.


`, buttons: {} }; //add buttons //Ok dialogData.buttons.cancel = { label: `Ok`, callback: () => { }, icon: '' }; //render dialog const dialog = new Dialog(dialogData).render(true); }); } //reload weapon async reloadWeapon(itemId) { //init vars let messageTemplate = ``; let messageContent = ``; let msgBody = ``; let chatId = randomID(); //dupe item to work with let item = duplicate(this.getEmbeddedDocument('Item',itemId)); //reload if (!item.system.useAmmo) { //exit function (it should not be possible to get here) return; } else { //do we need to reload? if (item.system.curShots === item.system.shots) { //exit function (it should not be possible to get here) return; } else { //put curShots back into the ammo pool item.system.ammo += item.system.curShots; //figure out how much we can reload (full shots, or less if we don't have enough ammo) let reloadAmount = Math.min(item.system.ammo, item.system.shots); //reload the weapon //set curShots to reload amount item.system.curShots = reloadAmount; //remove reload amount from ammo item.system.ammo -= reloadAmount; //update the item this.updateEmbeddedDocuments('Item', [item]); //set message body text msgBody = `Weapon reloaded.`; } } //generate chat message //prepare data let messageData = { actor: this, item: item, msgBody: msgBody }; //prepare template messageTemplate = 'systems/mosh/templates/chat/reload.html'; //render template messageContent = await renderTemplate(messageTemplate, messageData); //push message ChatMessage.create({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); } // print description printDescription(itemId, options = { event: null }) { let item = duplicate(this.getEmbeddedDocument('Item',itemId)); this.chatDesc(item); } // Print the item description into the chat. chatDesc(item) { let itemName = item.name?.charAt(0).toUpperCase() + item.name?.toLowerCase().slice(1); if (!item.name && isNaN(itemName)) itemName = item.charAt(0)?.toUpperCase() + item.toLowerCase().slice(1); var rollInsert = ''; if(item.system.roll){ let r = new Roll(item.system.roll, {}); r.evaluate({async: false}); rollInsert = '\
'+item.system.roll+'
\
\
'+r._total+'
\
'; } var templateData = { actor: this, stat: { name: itemName.toUpperCase() }, item: item, insert: rollInsert, onlyDesc: true, }; let chatData = { user: game.user.id, speaker: { actor: this.id, token: this.token, alias: this.name } }; let rollMode = game.settings.get("core", "rollMode"); if (["gmroll", "blindroll"].includes(rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM"); /* if (this.data.type == "creature") { chatData.whisper = game.user._id; } */ let template = 'systems/mosh/templates/chat/itemRoll.html'; renderTemplate(template, templateData).then(content => { chatData.content = content; ChatMessage.create(chatData); }); } }