/** * 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 Creature type specific data _prepareCreatureData(actorData) { const data = actorData; } //Prepare Ship type specific data _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' } } }; //replace 'stress' with calm if the setting is active if (game.settings.get("mosh", "useCalm") && action === 'stress') { action = 'calm'; } //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.` }, radiation: { android: `Catastro▒ic d⟑ta ▓loss de|/~ ⋥t⋱d`, human: `You stare into blackness and feel completely unable to pull yourself out of it.` } } }, //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.`, human: `You hit rock bottom.` }, pastCeiling: { android: `You sense unrecoverable data loss.`, human: `You feel a part of yourself drift away.` }, 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.` } }, //calm flavor text calm: { increase: { android: `You soft-reset, purging unnecessary background processes.`, human: `You feel a sense of calm wash over you.` }, increaseHeader: { android: `Calm Gained`, human: `Calm Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/macros/relieve_stress.png`, human: `systems/mosh/images/icons/ui/macros/relieve_stress.png` }, hitCeiling: { android: `You attain perfect focus and clarity.`, human: `You attain complete peace of mind.` }, pastCeiling: { android: `You are already as focused as possible.`, human: `You are already as calm as possible.` }, decrease: { android: `Power surges through your chest and you start to overheat.`, human: `You feel tightness in your chest and start to sweat.` }, decreaseHeader: { android: `Calm Lost`, human: `Calm Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/macros/gain_stress.png`, human: `systems/mosh/images/icons/ui/macros/gain_stress.png` }, hitFloor: { android: `System performance grinds to a halt.`, human: `You hit rock bottom.` }, pastFloor: { android: `You sense unrecoverable data loss.`, human: `You feel a part of yourself drift away.` } }, //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: `Your pain receptors indicate core damage.`, human: `Your pain receptors indicate core damage.` }, pastFloor: { android: `Your pain receptors indicate core damage.`, human: `Your pain receptors indicate core damage.` } }, //hits flavor text hits: { increase: { android: `@UUID[Compendium.mosh.macros_hotbar_1e.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}`, human: `@UUID[Compendium.mosh.macros_hotbar_1e.Macro.ZzKgfEmRdvDfyBMS]{Make a Wound Check}` }, increaseHeader: { android: `Damaged`, human: `Wounded` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/health.png`, human: `systems/mosh/images/icons/ui/attributes/health.png` }, hitCeiling: { android: `@UUID[Compendium.mosh.macros_hotbar_1e.Macro.NsRHfRuuNGPfkYVf]{Make a Death Save}`, human: `@UUID[Compendium.mosh.macros_hotbar_1e.Macro.NsRHfRuuNGPfkYVf]{Make a Death Save}` }, pastCeiling: { android: `@UUID[Compendium.mosh.macros_hotbar_1e.Macro.NsRHfRuuNGPfkYVf]{Make a Death Save}`, human: `@UUID[Compendium.mosh.macros_hotbar_1e.Macro.NsRHfRuuNGPfkYVf]{Make a Death Save}` }, decrease: { android: `System resources free up and you feel energized.`, human: `You feel a burst of energy.` }, decreaseHeader: { android: `Repaired`, human: `Mended` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/health.png`, human: `systems/mosh/images/icons/ui/attributes/health.png` }, hitFloor: { android: `You are now at full health.`, human: `You are now at full health.` }, pastFloor: { android: `You are already at full health.`, human: `You are already at full health.` } }, //strength flavor text strength: { check: { android: `You gain some confidence in your skills.`, human: `You gain some confidence in your skills.` }, increase: { android: `Data recovered. Central partition data restored.`, human: `You start to feel like yourself again.` }, increaseHeader: { android: `Strength Enhanced`, human: `Strength Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/strength.png`, human: `systems/mosh/images/icons/ui/attributes/strength.png` }, hitCeiling: { android: `You are now at maximum strength.`, human: `You are now at maximum strength.` }, pastCeiling: { android: `You are already at maximum strength.`, human: `You are already at maximum strength.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Strength Impaired`, human: `Strength Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/strength.png`, human: `systems/mosh/images/icons/ui/attributes/strength.png` }, hitFloor: { android: `You have no strength left.`, human: `You have no strength left.` }, pastFloor: { android: `Your strength cannot get any lower.`, human: `Your strength cannot get any lower.` } }, //speed flavor text speed: { check: { android: `You gain some confidence in your skills.`, human: `You gain some confidence in your skills.` }, increase: { android: `Data recovered. Central partition data restored.`, human: `You start to feel like yourself again.` }, increaseHeader: { android: `Speed Enhanced`, human: `Speed Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/speed.png`, human: `systems/mosh/images/icons/ui/attributes/speed.png` }, hitCeiling: { android: `You are now at maximum speed.`, human: `You are now at maximum speed.` }, pastCeiling: { android: `You are already at maximum speed.`, human: `You are already at maximum speed.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Speed Impaired`, human: `Speed Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/speed.png`, human: `systems/mosh/images/icons/ui/attributes/speed.png` }, hitFloor: { android: `You feel completely lethargic.`, human: `You feel completely lethargic.` }, pastFloor: { android: `Your speed cannot get any lower.`, human: `Your speed cannot get any lower.` } }, //intellect flavor text intellect: { check: { android: `You gain some confidence in your skills.`, human: `You gain some confidence in your skills.` }, increase: { android: `Data recovered. Central partition data restored.`, human: `You start to feel like yourself again.` }, increaseHeader: { android: `Intellect Enhanced`, human: `Intellect Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/intellect.png`, human: `systems/mosh/images/icons/ui/attributes/intellect.png` }, hitCeiling: { android: `You are now at maximum intellect.`, human: `You are now at maximum intellect.` }, pastCeiling: { android: `You are already at maximum intellect.`, human: `You are already at maximum intellect.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Intellect Impaired`, human: `Intellect Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/intellect.png`, human: `systems/mosh/images/icons/ui/attributes/intellect.png` }, hitFloor: { android: `You feel utterly confused.`, human: `You feel utterly confused.` }, pastFloor: { android: `Your intellect cannot get any lower.`, human: `Your intellect cannot get any lower.` } }, //combat flavor text combat: { check: { android: `You gain some confidence in your skills.`, human: `You gain some confidence in your skills.` }, increase: { android: `Data recovered. Central partition data restored.`, human: `You start to feel like yourself again.` }, increaseHeader: { android: `Combat Enhanced`, human: `Combat Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/combat.png`, human: `systems/mosh/images/icons/ui/attributes/combat.png` }, hitCeiling: { android: `You are now at maximum combat.`, human: `You are now at maximum combat.` }, pastCeiling: { android: `You are already at maximum combat.`, human: `You are already at maximum combat.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Combat Impaired`, human: `Combat Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/combat.png`, human: `systems/mosh/images/icons/ui/attributes/combat.png` }, hitFloor: { android: `You can't imagine fighting anymore.`, human: `You can't imagine fighting anymore.` }, pastFloor: { android: `Your combat cannot get any lower.`, human: `Your combat cannot get any lower.` } }, //instinct flavor text instinct: { check: { android: `You gain some confidence in your skills.`, human: `You gain some confidence in your skills.` }, increase: { android: `Data recovered. Central partition data restored.`, human: `You start to feel like yourself again.` }, increaseHeader: { android: `Instinct Enhanced`, human: `Instinct Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/instinct.png`, human: `systems/mosh/images/icons/ui/attributes/instinct.png` }, hitCeiling: { android: `You are now at maximum instinct.`, human: `You are now at maximum instinct.` }, pastCeiling: { android: `You are already at maximum instinct.`, human: `You are already at maximum instinct.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Instinct Impaired`, human: `Instinct Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/instinct.png`, human: `systems/mosh/images/icons/ui/attributes/instinct.png` }, hitFloor: { android: `You've lost your instincts.`, human: `You've lost your instincts.` }, pastFloor: { android: `Your instinct cannot get any lower.`, human: `Your instinct cannot get any lower.` } }, //loyalty flavor text loyalty: { check: { android: `You gain some confidence in your skills.`, human: `You gain some confidence in your skills.` }, increase: { android: `Data recovered. Central partition data restored.`, human: `You start to feel like yourself again.` }, increaseHeader: { android: `Loyalty Enhanced`, human: `Loyalty Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/loyalty.png`, human: `systems/mosh/images/icons/ui/attributes/loyalty.png` }, hitCeiling: { android: `You are now at maximum loyalty.`, human: `You are now at maximum loyalty.` }, pastCeiling: { android: `You are already at maximum loyalty.`, human: `You are already at maximum loyalty.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Loyalty Impaired`, human: `Loyalty Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/loyalty.png`, human: `systems/mosh/images/icons/ui/attributes/loyalty.png` }, hitFloor: { android: `You only care about yourself.`, human: `You only care about yourself.` }, pastFloor: { android: `Your loyalty cannot get any lower.`, human: `Your loyalty cannot get any lower.` } }, //sanity flavor text sanity: { check: { 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: `Sanity Enhanced`, human: `Sanity Increased` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/sanity.png`, human: `systems/mosh/images/icons/ui/attributes/sanity.png` }, hitCeiling: { android: `You are now at maximum sanity.`, human: `You are now at maximum sanity.` }, pastCeiling: { android: `You are already at maximum sanity.`, human: `You are already at maximum sanity.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Sanity Impaired`, human: `Sanity Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/sanity.png`, human: `systems/mosh/images/icons/ui/attributes/sanity.png` }, hitFloor: { android: `You've lost your mind.`, human: `You've lost your mind.` }, pastFloor: { android: `Your sanity cannot get any lower.`, human: `Your sanity cannot get any lower.` } }, //fear flavor text fear: { check: { 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: `Bravery Enhanced`, human: `Bravery Improved` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/fear.png`, human: `systems/mosh/images/icons/ui/attributes/fear.png` }, hitCeiling: { android: `You are now at maximum fear.`, human: `You are now at maximum fear.` }, pastCeiling: { android: `You are already at maximum fear.`, human: `You are already at maximum fear.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Bravery Impaired`, human: `Bravery Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/fear.png`, human: `systems/mosh/images/icons/ui/attributes/fear.png` }, hitFloor: { android: `You are afraid of everything.`, human: `You are afraid of everything.` }, pastFloor: { android: `Your fear cannot get any lower.`, human: `Your fear cannot get any lower.` } }, //body flavor text body: { check: { 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: `Body Enhanced`, human: `Body Strengthened` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/body.png`, human: `systems/mosh/images/icons/ui/attributes/body.png` }, hitCeiling: { android: `You are now at maximum body.`, human: `You are now at maximum body.` }, pastCeiling: { android: `You are already at maximum body.`, human: `You are already at maximum body.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Body Impaired`, human: `Body Weakened` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/body.png`, human: `systems/mosh/images/icons/ui/attributes/body.png` }, hitFloor: { android: `Your body feels weak and fragile.`, human: `Your body feels weak and frail.` }, pastFloor: { android: `Your body cannot get any lower.`, human: `Your body cannot get any lower.` } }, //armor flavor text armor: { check: { 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: `Armor Enhanced`, human: `Armor Gained` }, increaseImg: { android: `systems/mosh/images/icons/ui/attributes/armor.png`, human: `systems/mosh/images/icons/ui/attributes/armor.png` }, hitCeiling: { android: `You are now at maximum armor.`, human: `You are now at maximum armor.` }, pastCeiling: { android: `You are already at maximum armor.`, human: `You are already at maximum armor.` }, decrease: { android: `Central partition damage detected. Unrecoverable sectors found.`, human: `You feel a part of yourself drift away.` }, decreaseHeader: { android: `Armor Impaired`, human: `Armor Lost` }, decreaseImg: { android: `systems/mosh/images/icons/ui/attributes/armor.png`, human: `systems/mosh/images/icons/ui/attributes/armor.png` }, hitFloor: { android: `Nothing protects you now.`, human: `Nothing protects you now.` }, pastFloor: { android: `Your armor cannot get any lower.`, human: `Your armor cannot get any lower.` } } }, //macro flavor text (embedding actions) macro: { wound: { bleeding: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.1DD8i6eCS6nx2Ip0]{Bleeding}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.1DD8i6eCS6nx2Ip0]{Bleeding}` }, bleeding_dis: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.xr2o2PU5vdrR6fxQ]{Bleeding [-]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.xr2o2PU5vdrR6fxQ]{Bleeding [-]}` }, bleeding_adv: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.tFcWNddtZvlv7tsg]{Bleeding [+]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.tFcWNddtZvlv7tsg]{Bleeding [+]}` }, blunt_force: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.TAjlQjA5AAy3qYL3]{Blunt Force}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.TAjlQjA5AAy3qYL3]{Blunt Force}` }, blunt_force_dis: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.k0zf8ZGivRguc0wb]{Blunt Force [-]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.k0zf8ZGivRguc0wb]{Blunt Force [-]}` }, blunt_force_adv: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.oL3GH0HoEPlP8vzG]{Blunt Force [+]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.oL3GH0HoEPlP8vzG]{Blunt Force [+]}` }, fire_explosives: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.bZi1qKmcKLFvnhZ2]{Fire & Explosives}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.bZi1qKmcKLFvnhZ2]{Fire & Explosives}` }, fire_explosives_dis: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.7rYhbDAaFeok1Daq]{Fire & Explosives [-]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.7rYhbDAaFeok1Daq]{Fire & Explosives [-]}` }, fire_explosives_adv: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.dJnQKDf0AlwK27QD]{Fire & Explosives [+]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.dJnQKDf0AlwK27QD]{Fire & Explosives [+]}` }, gore_massive: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.S9nnHKWYGSQmjQdp]{Gore}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.S9nnHKWYGSQmjQdp]{Gore}` }, gore_massive_dis: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.eQPuDgwv8evetFIk]{Gore [-]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.eQPuDgwv8evetFIk]{Gore [-]}` }, gore_massive_adv: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.DuVjNlE4lsnR7Emc]{Gore [+]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.DuVjNlE4lsnR7Emc]{Gore [+]}` }, gunshot: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.XgCOLv9UunBddUyW]{Gunshot}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.XgCOLv9UunBddUyW]{Gunshot}` }, gunshot_dis: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.LTpa1ZYVZl4m9k6z]{Gunshot [-]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.LTpa1ZYVZl4m9k6z]{Gunshot [-]}` }, gunshot_adv: { android: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.fnVATRHYJEUlS3pR]{Gunshot [+]}`, human: `@UUID[Compendium.mosh.macros_triggered_1e.Macro.fnVATRHYJEUlS3pR]{Gunshot [+]}` } } } }; //check to see if this address exists in the library, return the action parameter if not if (checkNested(textLibrary,type,context,action)) { //set full path to include class type if (this.type === 'character') { 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 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 100s 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 || rollTarget === 0) { //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 = ''; } //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 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 woundText = ``; let chatId = randomID(); let rollTarget = null; let valueAddress = []; let specialRoll = null; let firstEdition = game.settings.get('mosh','firstEdition'); let useCalm = game.settings.get('mosh','useCalm'); let androidPanic = game.settings.get('mosh','androidPanic'); let tableResultNumber = null; let secondRoll = false; let rollResult2 = null; let parsedRollResult2 = null; //customize this roll if its a unique use-case //panic check if (tableId === 'panicCheck') { //set special roll value for use later specialRoll = tableId; //assign variables depending on settings if (firstEdition) { if (androidPanic && this.system.class.value.toLowerCase() === 'android') { if (useCalm) { tableId = 'GCtYeCCQVQJ5M6SE'; aimFor = 'low'; zeroBased = true; checkCrit = true; rollAgainst = 'system.other.stress.value'; comparison = '<'; } else { tableId = 'aBnY19jlhPXzibCt'; aimFor = 'high'; zeroBased = false; checkCrit = false; rollAgainst = 'system.other.stress.value'; comparison = '>'; } } else { if (useCalm) { tableId = 'MOYI6Ntj5OVFYk06'; aimFor = 'low'; zeroBased = true; checkCrit = true; rollAgainst = 'system.other.stress.value'; comparison = '<'; } else { tableId = 'ypcoikqHLhnc9tNs'; aimFor = 'high'; zeroBased = false; checkCrit = false; rollAgainst = 'system.other.stress.value'; comparison = '>'; } } } else { if (androidPanic && this.system.class.value.toLowerCase() === 'android') { if (useCalm) { tableId = 'VW6HQ29T7zClNIZ6'; aimFor = 'low'; zeroBased = true; checkCrit = true; rollAgainst = 'system.other.stress.value'; comparison = '<'; } else { tableId = 'egJ11m2mJM3HBd6d'; aimFor = 'high'; zeroBased = false; checkCrit = false; rollAgainst = 'system.other.stress.value'; comparison = '>'; } } else { if (useCalm) { tableId = 'kqKpQAXyLTEEyz6Z'; aimFor = 'low'; zeroBased = true; checkCrit = true; rollAgainst = 'system.other.stress.value'; comparison = '<'; } else { tableId = '1vCm4ElRPotQXgNB'; aimFor = 'high'; zeroBased = false; checkCrit = false; rollAgainst = 'system.other.stress.value'; comparison = '>'; } } } //assign rollString if its a partial if (rollString === '[-]' || rollString === '' || rollString === '[+]') { //if 1e and no calm, then 1d20 if (firstEdition && !useCalm) {rollString = '1d20' + rollString;} //if 0e and no calm, then 2d10 if (!firstEdition && !useCalm) {rollString = '2d10' + rollString;} //if calm, then 1d100 if (useCalm) {rollString = '1d100' + rollString;} } } //bounce this request away if certain parameters are NULL //if rollString is STILL blank, redirect player to choose the roll if (!rollString) { //init vars let rollDie = ''; //set rollDie //if 1e and no calm, then 1d20 if (firstEdition) {rollDie = '1d20';} //if 0e and no calm, then 2d10 if (!firstEdition) {rollDie = '2d10';} //if calm, then 1d100 if (useCalm) {rollDie = '1d100';} //run the choose attribute function let chosenRollType = await this.chooseAdvantage('Panic Check',rollDie); //set variables rollString = chosenRollType[0]; } //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',''); //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]; } //table specific customizations //if a table has details in parenthesis, lets remove them if (tableName.includes(' (')) { //extract dice needed tableName = tableName.substr(0,tableName.indexOf(' (')); } //if a wound table, add a wound to the player and prepare text for the final message if (tableName.slice(-5) === 'Wound') { let addWound = await this.modifyActor('system.hits.value',1,null,false); woundText = addWound[1]; } //pull stat to roll against, if needed if(rollAgainst || rollAgainst === 0){ //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); if(game.settings.get('mosh', 'panicDieTheme') != ""){ //We're going to check if the theme field is blank. Otherwise, don't use this. //set panic die color let dsnTheme = game.settings.get('mosh','panicDieTheme'); //apply theme if this is a panic check if (tableName === 'Panic Check') { parsedRollString = parsedRollString + '[' + dsnTheme + ']'; } } //roll the dice let rollResult = await new Roll(parsedRollString).evaluate(); //interpret the results let parsedRollResult = this.parseRollResult(rollString,rollResult,zeroBased,checkCrit,rollTarget,comparison); //if this is a panic check, we may need to roll again OR add modifiers to our result total //roll a second die if needed if (!parsedRollResult.success && specialRoll === 'panicCheck' && !firstEdition && !useCalm) { //determine the rollString let rollString2 = '2d10'; //add modifiers if needed //0e modifier: + Stress - Resolve if (specialRoll === 'panicCheck' && !firstEdition && !useCalm) { rollString2 = rollString2 + ' + ' + this.system.other.stress.value + ' - ' + this.system.other.resolve.value } //Calm modifier: + Stress - Resolve if (specialRoll === 'panicCheck' && useCalm) { rollString2 = rollString2 + ' + ' + this.system.other.resolve.value } //roll second dice rollResult2 = await new Roll(rollString2).evaluate(); //roll second set of dice parsedRollResult2 = this.parseRollResult(rollString2,rollResult2,false,false,null,null); //set marker for HTML secondRoll = true; //set table result number tableResultNumber = parsedRollResult2.total } //set table result number if null if(!tableResultNumber) {tableResultNumber = parsedRollResult.total;} //fetch the table result //get table result let tableResult = tableData.getResultsForRoll(tableResultNumber); //make any custom changes to chat message //panic check #19 customiziation if (tableName === 'Panic Check' && tableResultNumber === 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 //get main flavor text flavorText = this.getFlavorText('table',tableName.replaceAll('& ','').replaceAll(' ','_').toLowerCase(),'success'); //append 0e crit success effect if (!firstEdition && !useCalm && parsedRollResult.success && parsedRollResult.critical) { flavorText = flavorText + ` Relieve 1 Stress.

@UUID[Compendium.mosh.macros_triggered_1e.Macro.qbq694JMbXeZrHjj]{-1 Stress}`; } //append Calm effects for Critical Panic Success if (useCalm && parsedRollResult.success && parsedRollResult.critical) { flavorText = flavorText + ` Gain 1d10 Calm.

@UUID[Compendium.mosh.macros_triggered_1e.Macro.k2TtLFOG9mGaWVx3]{+1d10 Calm}`; } //append Calm effects for Critical Panic Failure if (useCalm && !parsedRollResult.success && parsedRollResult.critical) { tableResult[0].text = tableResult[0].text + `

You lose 1d10 Calm because you critically failed.

@UUID[Compendium.mosh.macros_triggered_1e.Macro.jHyqXb2yDFTNWxpy]{-1d10 Calm}`; } //generate chat message //prepare data let messageData = { actor: this, tableResult: tableResult, parsedRollResult: parsedRollResult, tableName: tableName, tableImg: tableImg, msgDesc: msgDesc, flavorText: flavorText, woundText: woundText, secondRoll: secondRoll, parsedRollResult2: parsedRollResult2 }; //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}); if(game.modules.get("dice-so-nice").active){ //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 attribute = ``; 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 = 232; //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 = 125; //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, 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 specialRoll = ``; let checkCrit = true; let zeroBased = true; 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(); let firstEdition = game.settings.get('mosh','firstEdition'); let useCalm = game.settings.get('mosh','useCalm'); let androidPanic = game.settings.get('mosh','androidPanic'); //customize this roll if its a unique use-case //rest save if (attribute === 'restSave') { //1e rest save if (firstEdition) { //set special roll value for use later specialRoll = attribute; //disable criticals for this roll checkCrit = false; //lets figure out the actors worst save and update this roll accordingly //get current save values let sanitySave = Number(this.system.stats.sanity.value) + Number(this.system.stats.sanity.mod || 0); let fearSave = Number(this.system.stats.fear.value) + Number(this.system.stats.fear.mod || 0); let bodySave = Number(this.system.stats.body.value) + Number(this.system.stats.body.mod || 0); //get the lowest value let minSave = Math.min(sanitySave,fearSave,bodySave); //set attribute to the first one matching the lowest (since actor may have 2 with the lowest) if (sanitySave === minSave) { //set attribute attribute = 'sanity'; } else if (fearSave === minSave) { //set attribute attribute = 'fear'; } else { //set attribute attribute = 'body'; } //0e Rest save } else { //set special roll value for use later specialRoll = attribute; //disable criticals for this roll checkCrit = false; //set attribute attribute = 'fear'; } } //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 && !specialRoll) { //run the choose attribute function let chosenAttributes = await this.chooseAttribute(rollString,aimFor); //set variables rollString = chosenAttributes[0]; aimFor = chosenAttributes[1]; attribute = chosenAttributes[2]; //if null, zero them out } //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,checkCrit,zeroBased,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 for attacks if (weapon) { //override message header msgHeader = weapon.name; //override header image msgImgPath = weapon.img; let dsnTheme = 0; if(game.settings.get('mosh', 'damageDiceTheme') != ""){ //We're going to check if the theme field is blank. Otherwise, don't use this. //set damage dice color 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" && this.type === 'character') { //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 if (parsedRollResult.success === false && this.type === 'character') { //if first edition if (firstEdition) { //if calm not enabled if (!useCalm) { if(game.settings.get('mosh','autoStress')){ //If the automatic stress option is enabled //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 { flavorText = 'You sense the weight of your setbacks.'; } //if 0e } else { //if calm not enabled if (!useCalm) { //on Save failure if (attribute === 'sanity' || attribute === 'fear' || attribute === 'body' || attribute === 'armor') { if(game.settings.get('mosh','autoStress')){ //If the automatic stress option is enabled //gain 1 stress 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 { //output standard failure flavorText = 'You sense the weight of your setbacks.'; } } } //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','gunshot'); //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(' '); } //prepare flavor text for special rolls } else if (specialRoll) { //rest save if (specialRoll === 'restSave') { //1e rest save if (firstEdition) { //calm outcome if (useCalm) { //override message header msgHeader = `Rest Save`; //override header image msgImgPath = `systems/mosh/images/icons/ui/macros/rest_save.png`; //prep text based on success or failure if (parsedRollResult.success === false && this.type === 'character') { //set fail text flavorText = 'You sense the weight of your setbacks.'; } else if (parsedRollResult.success === true && this.type === 'character') { //calculate stress reduction let onesValue = Number(String(parsedRollResult.total).charAt(String(parsedRollResult.total).length-1)); //decrease stress by ones place of roll value and retrieve the flavor text from the result let removeStress = await this.modifyActor('system.other.stress.value',onesValue,null,false); flavorText = removeStress[1]; } //no calm outcome } else { //override message header msgHeader = `Rest Save`; //override header image msgImgPath = `systems/mosh/images/icons/ui/macros/rest_save.png`; //prep text based on success or failure if (parsedRollResult.success === false && this.type === 'character') { if(game.settings.get('mosh','autoStress')){ //If the automatic stress option is enabled //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 if (parsedRollResult.success === true && this.type === 'character') { //calculate stress reduction let onesValue = -1 * Number(String(parsedRollResult.total).charAt(String(parsedRollResult.total).length-1)); //decrease stress by ones place of roll value and retrieve the flavor text from the result let removeStress = await this.modifyActor('system.other.stress.value',onesValue,null,false); flavorText = removeStress[1]; } } //0e rest save } else { //calm outcome if (useCalm) { //override message header msgHeader = `Rest Save`; //override header image msgImgPath = `systems/mosh/images/icons/ui/macros/rest_save.png`; //prep text based on success or failure if (parsedRollResult.success === false && this.type === 'character') { //set fail text flavorText = 'You sense the weight of your setbacks.'; } else if (parsedRollResult.success === true && this.type === 'character') { //calculate stress reduction let succeedBy = Math.floor((rollTarget-parsedRollResult.total)/10); //double it if critical if (parsedRollResult.critical) {succeedBy = succeedBy * 2;} //decrease stress by ones place of roll value and retrieve the flavor text from the result let removeStress = await this.modifyActor('system.other.stress.value',succeedBy,null,false); flavorText = removeStress[1]; } //no calm outcome } else { //override message header msgHeader = `Rest Save`; //override header image msgImgPath = `systems/mosh/images/icons/ui/macros/rest_save.png`; //prep text based on success or failure if (parsedRollResult.success === false && this.type === 'character') { if(game.settings.get('mosh','autoStress')){ //If the automatic stress option is enabled //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 if (parsedRollResult.success === true && this.type === 'character') { //calculate stress reduction let succeedBy = -1 * Math.floor((rollTarget-parsedRollResult.total)/10); //double it if critical if (parsedRollResult.critical) {succeedBy = succeedBy * 2;} //decrease stress by ones place of roll value and retrieve the flavor text from the result let removeStress = await this.modifyActor('system.other.stress.value',succeedBy,null,false); flavorText = removeStress[1]; } } } } //prepare flavor text for regular checks } else { //prep text based on success or failure if (parsedRollResult.success === false && this.type === 'character') { //if first edition if (firstEdition) { //if calm not enabled if (!useCalm) { if(game.settings.get('mosh','autoStress')){ //If the automatic stress option is enabled //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 { flavorText = 'You sense the weight of your setbacks.'; } //if 0e } else { //if calm not enabled if (!useCalm) { //on Save failure if (attribute === 'sanity' || attribute === 'fear' || attribute === 'body' || attribute === 'armor') { if(game.settings.get('mosh','autoStress')){ //If the automatic stress option is enabled //gain 1 stress 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 { //output standard failure flavorText = 'You sense the weight of your setbacks.'; } } } else if (parsedRollResult.success === true && this.type === 'character') { //flavor text = generic roll success flavorText = this.getFlavorText('attribute',attribute,'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, firstEdition: game.settings.get('mosh','firstEdition'), useCalm: game.settings.get('mosh','useCalm'), androidPanic: game.settings.get('mosh','androidPanic') }; //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}); if(game.modules.get("dice-so-nice").active){ //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 fieldPrefix = ``; let getWound = false; 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(); let halfDamage = false; let firstEdition = game.settings.get('mosh','firstEdition'); let useCalm = game.settings.get('mosh','useCalm'); let androidPanic = game.settings.get('mosh','androidPanic'); //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); //get max value for this field, if it exists modifyMaximum = fieldMax.reduce((a, v) => a[v], this); //get current value for this field modifyCurrent = fieldValue.reduce((a, v) => a[v], this); //check to see if this is a min/max part of a main field if (fieldAddress.slice(-3) === `min`) { fieldPrefix = `Minimum `; } else if (fieldAddress.slice(-3) === `max`) { fieldPrefix = `Maximum `; } //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 || modifyMinimum === 0) { if(modifyNew < modifyMinimum) { modifyNew = modifyMinimum; } } //cap max if(modifyMaximum || modifyMaximum === 0) { 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; //if health hits zero, reset to next hp bar if (firstEdition && fieldId === 'health' && modifyNew === 0) { //set marker for later getWound = true; //reset hp modifyNew = modifyMaximum + modifySurplus; } //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 = fieldPrefix + this.getFlavorText('attribute',fieldId,'increaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'increaseImg'); } else if (modifyChange < 0) { msgFlavor = this.getFlavorText('attribute',fieldId,'decrease'); msgChange = 'decreased'; msgHeader = fieldPrefix + this.getFlavorText('attribute',fieldId,'decreaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'decreaseImg'); } //detect if half damage has been taken if (!firstEdition && (-1 * modifyChange) > (modifyMaximum/2)) { halfDamage = true; } //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 default message outcome if (msgAction === 'increase' || msgAction === 'decrease') { msgOutcome = fieldPrefix + fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}.`; //set message outcome for stress going from < 20 to > 20 } else if (fieldId === 'stress' && modifyCurrent < modifyMaximum && modifySurplus > 0) { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` ` + fieldPrefix + fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}. Reduce the most relevant Stat or Save by ${modifySurplus}.`; //set message outcome for stress going from 20 to > 20 } else if (fieldId === 'stress' && modifyCurrent === modifyMaximum && modifySurplus > 0) { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` Reduce the most relevant Stat or Save by ${modifySurplus}.`; //set message outcome for health reaches zero or goes past it, and you have wounds remaining } else if (getWound) { //can this player take a wound and not die? if (this.system.hits.value + 1 === this.system.hits.max) { //you are dead! msgOutcome = this.getFlavorText('attribute','hits','hitCeiling'); } else { //you are wounded! msgOutcome = `Your health has hit zero and you must take a wound. Your health has been reset to ${modifyNew}.

` + this.getFlavorText('attribute','hits','increase'); } } else { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` ` + fieldPrefix + 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, halfDamage: halfDamage }; //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 || modifyMinimum === 0) { if(modifyNew < modifyMinimum) { modifyNew = modifyMinimum; } } //cap max if(modifyMaximum || modifyMaximum === 0) { 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; //if health hits zero, reset to next hp bar if (firstEdition && fieldId === 'health' && modifyNew === 0) { //set marker for later getWound = true; //reset hp modifyNew = modifyMaximum + modifySurplus; //increase wounds by 1 this.update({'system.stats.hits.value': this.system.hits.value + 1}); } //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 = fieldPrefix + this.getFlavorText('attribute',fieldId,'increaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'increaseImg'); } else if (modifyChange < 0) { msgFlavor = this.getFlavorText('attribute',fieldId,'decrease'); msgChange = 'decreased'; msgHeader = fieldPrefix + this.getFlavorText('attribute',fieldId,'decreaseHeader'); msgImgPath = this.getFlavorText('attribute',fieldId,'decreaseImg'); } //detect if half damage has been taken if (!firstEdition && (-1 * modifyChange) > (modifyMaximum/2)) { halfDamage = true; } //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 default message outcome if (msgAction === 'increase' || msgAction === 'decrease') { msgOutcome = fieldPrefix + fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}.`; //set message outcome for stress going from < 20 to > 20 } else if (fieldId === 'stress' && modifyCurrent < modifyMaximum && modifySurplus > 0) { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` ` + fieldPrefix + fieldLabel.reduce((a, v) => a[v], this) + ` ` + msgChange + ` from ${modifyCurrent} to ${modifyNew}. Reduce the most relevant Stat or Save by ${modifySurplus}.`; //set message outcome for stress going from 20 to > 20 } else if (fieldId === 'stress' && modifyCurrent === modifyMaximum && modifySurplus > 0) { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` Reduce the most relevant Stat or Save by ${modifySurplus}.`; //set message outcome for health reaches zero or goes past it, and you have wounds remaining } else if (getWound) { //can this player take a wound and not die? if (this.system.hits.value + 1 === this.system.hits.max) { //you are dead! msgOutcome = this.getFlavorText('attribute','hits','hitCeiling'); } else { //you are wounded! msgOutcome = `Your health has hit zero and you must take a wound. Your health has been reset to ${modifyNew}.

` + this.getFlavorText('attribute','hits','increase'); } } else { msgOutcome = this.getFlavorText('attribute',fieldId,msgAction) + ` ` + fieldPrefix + 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, halfDamage: halfDamage }; //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}); if(game.modules.get("dice-so-nice").active){ //wait for dice await game.dice3d.waitFor3DAnimationByMessageID(chatId); } } //return modification values return [msgFlavor,msgOutcome,msgChange]; } } //central function to modify an actors items | TAKES 'olC4JytslvUrQN8g',1 | RETURNS change details, and optional chat message async modifyItem(itemId,addAmount) { //init vars let currentLocation = ''; let itemLocation = ''; let messageTemplate = ``; let messageContent = ``; let oldValue = 0; let newValue = 0; let flavorText = ``; let chatId = randomID(); //find where this item is located //get current compendium let compendium = game.packs; //loop through each compendium compendium.forEach(function(pack){ //is this a pack of items? if (pack.metadata.type === 'Item') { //log where we are currentLocation = pack.metadata.id; //loop through each pack to find the right table pack.index.forEach(function(item) { //is this our table? if (item._id === itemId) { //grab the table location itemLocation = currentLocation; } }); } }); //get table data let itemData = await game.packs.get(itemLocation).getDocument(itemId); //add or increase the count of the item, depending on type, if the actor has it if (this.items.getName(itemData.name)) { //if this is an item, increase the count if (itemData.type === 'item') { //get current quantity oldValue = this.items.getName(itemData.name).system.quantity; newValue = oldValue + addAmount; //increase severity of the condition this.items.getName(itemData.name).update({'system.quantity': newValue}); //create message text flavorText = `Quantity has increased from ` + oldValue + ` to ` + newValue + `.`; //if this is a condition, increase the severity } else if (itemData.type === 'condition') { //get current severity oldValue = this.items.getName(itemData.name).system.severity; newValue = oldValue + addAmount; //increase severity of the condition this.items.getName(itemData.name).update({'system.severity': newValue}); //create message text flavorText = this.getFlavorText('item','condition','increase') + `Severity has increased from ` + oldValue + ` to ` + newValue + `.`; //if this is a weapon or armor, add another one } else if (itemData.type === 'weapon' || itemData.type === 'armor') { //add item to the players inventory await this.createEmbeddedDocuments('Item', [itemData]); //create message text flavorText = `You add another one of these to your inventory.`; } } else { //if this is an item, add it if (itemData.type === 'item') { //give the character the item await this.createEmbeddedDocuments('Item', [itemData]); //increase severity of the condition this.items.getName(itemData.name).update({'system.quantity': addAmount}); //create message text flavorText = `You add ` + addAmount + ` of these to your inventory..`; //if this is a condition, add it } else if (itemData.type === 'condition') { //give the character the item await this.createEmbeddedDocuments('Item', [itemData]); //increase severity of the condition this.items.getName(itemData.name).update({'system.severity': addAmount}); //create message text flavorText = this.getFlavorText('item','condition','add') + `, with a severity of ` + addAmount + `.`; //if this is a weapon or armor, add it } else if (itemData.type === 'weapon' || itemData.type === 'armor') { //add item to the players inventory await this.createEmbeddedDocuments('Item', [itemData]); //create message text flavorText = `You add this to your inventory.`; } } //generate chat message //get item name let msgHeader = itemData.name; //get item image let msgImgPath = itemData.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 ChatMessage.create({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); } //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}); } //take bleeding damage async takeBleedingDamage() { //init vars let chatId = randomID(); //determine bleeding amount let healthLost = this.items.getName("Bleeding").system.severity*-1; //run the function for the player's 'Selected Character' let modification = await this.modifyActor('system.health.value',healthLost,null,false); //get flavor text let msgFlavor = this.getFlavorText('item','condition','bleed'); let msgOutcome = modification[1]; //create chat message text let messageContent = `
Health Lost
${msgFlavor}

${msgOutcome}
`; //push message ChatMessage.create({ id: chatId, user: game.user.id, speaker: {actor: this.id, token: this.token, alias: this.name}, content: messageContent },{keepId:true}); } //take radiation damage async takeRadiationDamage() { //init vars let chatId = randomID(); //reduce all stats and saves by 1 this.modifyActor('system.stats.strength.value',-1,null,false); this.modifyActor('system.stats.speed.value',-1,null,false); this.modifyActor('system.stats.intellect.value',-1,null,false); this.modifyActor('system.stats.combat.value',-1,null,false); this.modifyActor('system.stats.sanity.value',-1,null,false); this.modifyActor('system.stats.fear.value',-1,null,false); this.modifyActor('system.stats.body.value',-1,null,false); //get flavor text let msgFlavor = this.getFlavorText('item','condition','radiation'); let msgOutcome = `All stats and saves decreased by 1.`; //create chat message text let messageContent = `
Radiation Damage
${msgFlavor}

${msgOutcome}
`; //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); }); } }