diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0696292..73436b4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,12 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
-## [0.9.0] - Unreleased
-###
+## [0.9.0] - 2021-07-05
+**Possibly** compatible with Foundry 0.8.x but I can't say for sure.
+### You need to update these macros:
+- Ammo Management (enhanced)
+- SWIM: Ammo Management
### Added
- Now also features the ability to play SFX only without the need for ammunition. For this just set the Ammo on the weapon to be `NONE` (exactly like this). This may be useful if you want SFX for melee weapons. It uses the same sfx path structure (see below), so you'll have the same sfx for melee and ranged attacks unfortunately.
- Chase Setup macro for a quick setup that takes only a click. Also supports cleaning the table.
-- Six chase layouts, one for foot/riding chases, one for (ground) vehicle chases and one for ship (watercraft, aircraft and spacecraft) chases/battles with regular and modern theme each. See the
+- Six chase layouts, one for foot/riding chases, one for (ground) vehicle chases and one for ship (watercraft, aircraft and spacecraft) chases/battles with regular and modern theme each. See the [Chase Scenes documentation](https://github.com/SalieriC/SWADE-Immersive-Macros/blob/main/documentation/Chase%20Scenes.md) for details.
+- Immersion macro to play SFX when using powers in BR2. It does not manage Power Points, just plays a sound effect when using a power.
## [0.8.0] - 2021-07-01
**Possibly** compatible with Foundry 0.8.x but I can't say for sure.
diff --git a/README.md b/README.md
index 06ddcfa6..d9097eee 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ In this repository you can find a number of assets already.
- **SFX:** (see their file name for origin)
- - [Fesliyan Studios](www.fesliyanstudios.com), all used with explicit permission (thank you so much), [see their License here](https://www.fesliyanstudios.com/policy).
- - [Orange Free Sounds](https://orangefreesounds.com/), all used unter the [CC BY-NC 4.0 License](https://creativecommons.org/licenses/by-nc/4.0/), converted to *.ogg and possibly altered (see file name), Author is in the file names as well.
+- **Six chase layouts**, one for foot/riding chases, one for (ground) vehicle chases and one for ship (watercraft, aircraft and spacecraft) chases/battles with regular and modern theme
Assets are used in the macros. You can also set up your own assets in the modules configuration. There you can change file paths.
@@ -113,6 +114,18 @@ It comes with optional BR2 integration for the shooting/using ammo part. How the
I can't include that many sfx right now. It is rather difficult to find good weapon sounds which I am allowed to include and I don't have the funds to buy them for this module. I'm not gonna recommend any particular way on how to get adequate sound effects, figuring that out is on you. Just know that there are many resources you should be able to use out there. As a general lead: Look for PC game modifications which alter the weapon sounds in that specific game. You may not be allowed to use the sfx from the game directly (depends on the game of course), but those from mods are usually not a problem but check that before you do take them. I cannot be held responsible for any breach of contract or copyright you commit it is on you to check whether or not you're allowed to use the sfx you find.
You may also want to take a look at [SoundFx Library](https://foundryvtt.com/packages/soundfxlibrary) by Cris. It comes with a few bow sounds which may be sufficient for your standard medieval fantasy settings.
+### Power Sounds
+**Immersion setting:** SFX.
+**Requirements:**
+- [Better Rolls 2 (BR2)](https://foundryvtt.com/packages/betterrolls-swade2)
+**Description:**
+This does not manage your Power Points but it does automatically play SFX when using a power. For this you need to set up some World Global Actions in Better Rolls 2. Take a look at the BR2 integration section of the [Shooting & Reloading setup documentation](https://github.com/SalieriC/SWADE-Immersive-Macros/blob/main/documentation/Shooting%20%26%20Reloading%20Setup.md) for a general idea how that works.
+Put simply: Set up an additional stat called "sfx" in your world, activate it in a power and fill it with the relative path to the sfx. Then import the "SWIM: Powers SFX" macro in your world and copy-paste [this](https://github.com/SalieriC/SWADE-Immersive-Macros/blob/main/swim/assets/imports/BR2-spellcasting-integration.json) in a new World Global action in the BR2 settings. You need to adjust all instances of "Spellcasting" with the Skill(s) used for using powers in your setting and set up a new World Global Action for each.
+Again, see the Shooting & Reloading documentation linked above for more details on BR2 integration if you feel lost.
+**Wait, where do I get weapon sound effects?**
+I can't include that many sfx right now. It is rather difficult to find good magic sounds which I am allowed to include and I don't have the funds to buy them for this module. I'm not gonna recommend any particular way on how to get adequate sound effects, figuring that out is on you. Just know that there are many resources you should be able to use out there. As a general lead: Look for PC game modifications which alter the magic sounds in that specific game. You may not be allowed to use the sfx from the game directly (depends on the game of course), but those from mods are usually not a problem but check that before you do take them. I cannot be held responsible for any breach of contract or copyright you commit it is on you to check whether or not you're allowed to use the sfx you find.
+You may also want to take a look at [SoundFx Library](https://foundryvtt.com/packages/soundfxlibrary) by Cris. It comes with a few bow sounds which may be sufficient for your standard medieval fantasy settings.
+
### Fear Table
**Requirements:** None.
**Immersion setting:** SFX.
@@ -189,6 +202,13 @@ In general it is best to set up a universal/global light source instead of touch
**Description:**
A very basic macro that resets the roll table from which the action cards are drawn. It's mainly there to fix issues when the table doesn't reset during combat.
+### Chase Setup
+**Requirements:** Chase layouts/scenes (included in a compendium) and a roll table with card images.
+**Immersion setting:** System card deal sound.
+**Description:**
+This macro can set up and clean your chase scenes with cards. It places the cards automatically in the correct position. See the [Chase Scenes documentation](https://github.com/SalieriC/SWADE-Immersive-Macros/blob/main/documentation/Chase%20Scenes.md) for details.
+*This requires the chase scenes that come in a compendium with this module.*
+
### Raise Calculator & Raise Calculator (Dynamic)
**Requirements:** None.
**Immersion setting:** None.
diff --git a/documentation/Chase Scenes.md b/documentation/Chase Scenes.md
index 23fa75f9..4e3a842c 100644
--- a/documentation/Chase Scenes.md
+++ b/documentation/Chase Scenes.md
@@ -1,6 +1,7 @@
# Chase Layouts
In SWIM included is a set of scenes for use with the Chase Setup macro. The macro will automatically place the cards on the correct position of the chase layout and can also shuffle them back in your card deck. Setting up a chase thus becomes a matter of seconds with only the click on a button inside the macro.
As with the Rest of the module these layouts are licensed under the [Savage Worlds Fan License](https://www.peginc.com/licensing/). But these scenes contain detailed information about specific rulings, thus I asked Shane Lacy Hensley for permission to publish these. I was given permission to do so under the Fan License. Please note that this permission may be revoked by PEG at any time. Should this happen I will remove these layouts from this module and replace them with generic ones that do not contain any rulings.
+In the module settings you can set up a default deck of cards to draw from but you can still choose another deck if you like. The roll table must have the images of cards as Journal Entries in it. If you're now sure, just duplicate the action deck from the system and rename it to "Chase Deck".
## Custom Fonts
The layouts use the following fonts:
@@ -22,6 +23,4 @@ You can set up a custom background for these layouts by simply editing the scene
You *must not* change these properties, otherwise the macro will be unable to place your cards in the correct position. Foundry VTT may change your Scene Dimensions on setting up another background image, so double check that the original values keep in place.
## Editing the layouts
-You can edit these layouts manually by opening them in a image editor like GIMP. I can not and will not provide the original files but by adding your own background image in such an editor will avoid conflicts with changing scene dimensions.
-
-## Known Bugs
\ No newline at end of file
+You can edit these layouts manually by opening them in a image editor like GIMP. I can not and will not provide the original files but by adding your own background image in such an editor will avoid conflicts with changing scene dimensions.
\ No newline at end of file
diff --git a/swim.zip b/swim.zip
index b5963c6e..107a4b33 100644
Binary files a/swim.zip and b/swim.zip differ
diff --git a/swim/module.json b/swim/module.json
index 5f7e59e7..805a104d 100644
--- a/swim/module.json
+++ b/swim/module.json
@@ -4,7 +4,7 @@
"description": "
READ THE README! Doing so is very important!
A simple module to configure some stuff in the SWADE System on Foundry VTT. Please consider donating.
",
"minimumCoreVersion": "0.7.4",
"compatibleCoreVersion": "0.7.10",
- "version": "0.8.0",
+ "version": "0.9.0",
"author": "SalieriC",
"dependencies": [
{
diff --git a/swim/packs/swade-immersive-macros.db b/swim/packs/swade-immersive-macros.db
index 9c00ce75..476125e3 100755
--- a/swim/packs/swade-immersive-macros.db
+++ b/swim/packs/swade-immersive-macros.db
@@ -16,6 +16,6 @@
{"name":"Shuffle Action Deck","permission":{"default":0,"wpWSO3unowg8siJN":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"furnace":{"runAsGM":false},"exportSource":{"world":"savage-worlds-50-fatoms","system":"swade","coreVersion":"0.7.7","systemVersion":"0.14.1"},"core":{"sourceId":"Macro.yyHpyfnck6yL9pmK"}},"scope":"global","command":"const table = game.tables.entities.find(t => t.name === \"Action Cards\");\ntable.reset();\nui.notifications.info(\"Action Deck shuffled.\");\nAudioHelper.play({ src: `systems/swade/assets/card-flip.wav` }, true);","author":"wpWSO3unowg8siJN","img":"systems/swade/assets/ui/wildcard.svg","actorIds":[],"_id":"pZxQv6ZOUDsj3ocg"}
{"name":"Deviation","permission":{"default":0,"wpWSO3unowg8siJN":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.kqNehEXmaItvZ0ZU"}},"scope":"global","command":"const chatimage = \"https://raw.githubusercontent.com/brunocalado/mestre-digital/master/Foundry%20VTT/Macros/Savage%20Worlds/icons/clock.webp\";\n\n/* Deviation p99 SWADE\nIf a blast template misses, it deviates 1d6″\nfor thrown weapons (such as grenades) and\n2d6″ for fired projectiles. Multiply by 2 if the\nattack was made at Medium Range, 3 if Long,\nand 4 for Extreme.\n\nNext roll a d12 and read it like a clock\nfacing to determine the direction the missile\ndeviates. A weapon can never deviate more\nthan half the distance to the original target\n(that keeps it from going behind the thrower).\n\nsource: https://raw.githubusercontent.com/brunocalado/mestre-digital/master/Foundry%20VTT/Macros/Savage%20Worlds/Deviation.js; altered by SalieriC.\nicon: icons/weapons/artillery/cannon-engraved-gold.webp\n*/\n\nlet coreRules = false;\nif (game.modules.get(\"swade-core-rules\")?.active) {coreRules = true;}\n\ngetRequirements();\n\nfunction getRequirements() {\n let template = `\n Weapon Type \n \n Range \n \n `;\n new Dialog({\n title: \"Deviation\",\n content: template,\n buttons: {\n ok: {\n label: \"Go!\",\n callback: async (html) => {\n rollForIt(html);\n },\n }\n },\n }).render(true);\n}\n\nfunction rollForIt(html) {\n const weapontype=html.find('input[name=\"weapontype\"]:checked').val();\n const range=html.find('input[name=\"range\"]:checked').val();\n let deviation;\n \n if (weapontype=='thrown') {\n deviation = diceRoll('1d6', range);\n } else {\n deviation = diceRoll('2d6', range);\n }\n}\n\nfunction diceRoll(die, range) {\n const rangeMultiplier = rangeCheck(range);\n let direction = new Roll('1d12').roll();\n let roll = new Roll(die).roll();\n let message = `Deviation `;\n if (coreRules === true) {message = `@Compendium[swade-core-rules.swade-rules.xxEcWExtn36PPxg0]{Deviation} `;}\n message += `
Move the blast ${roll.total*rangeMultiplier}\" to ${direction.total} O'Clock.
`;\n if (directionCheck(direction.total)) {\n message += `
A weapon can never deviate more than half the distance to the original target (that keeps it from going behind the thrower).
`;\n }\n message += `
`;\n if (coreRules === true) {message += `
`}\n\n let tempChatData = {\n type: CHAT_MESSAGE_TYPES.ROLL,\n roll: roll,\n rollMode: game.settings.get(\"core\", \"rollMode\"),\n content: message\n }; \n ChatMessage.create(tempChatData); \n return roll.total;\n}\n\nfunction rangeCheck(range) {\n if (range=='short') {\n return 1;\n } else if (range=='medium') {\n return 2;\n } else if (range=='long') {\n return 3;\n } else if (range=='extreme') {\n return 4;\n }\n}\n\nfunction directionCheck(direction) {\n console.log(direction);\n if (direction==4 || direction==5 || direction==6 || direction==7 || direction==8) {\n return true\n } else {\n return false\n } \n // v. 1.0.0 - Original code by brunocalado, modified by SalieriC#8263.\n // Image source: https://freesvg.org/analogue-clock-vector-graphics\n}","author":"wpWSO3unowg8siJN","img":"icons/weapons/thrown/dynamite-simple-brown.webp","actorIds":[],"_id":"tniGXuJmELBtXcXq"}
{"_id":"vIEcQWeP08RgMpq4","name":"Personal Health Centre","permission":{"default":0,"wpWSO3unowg8siJN":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.SjOf7Sp0D4ljIWfQ"}},"scope":"global","command":"main();\n\nfunction main() {\n // Check if a token is selected.\n if ((!token || canvas.tokens.controlled.length > 1)) {\n ui.notifications.error(\"Please select a single token first.\");\n return;\n }\n // Checking for SWADE Spices & Flavours and setting up the Benny image.\n let bennyImage = \"icons/commodities/currency/coin-embossed-octopus-gold.webp\";\n if (game.modules.get(\"swade-spices\")?.active) {\n let benny_Back = game.settings.get(\n 'swade-spices', 'bennyBack');\n if (benny_Back) {\n bennyImage = benny_Back;\n }\n }\n // Setting SFX\n let woundedSFX = game.settings.get(\n 'swim', 'woundedSFX');\n let incapSFX = game.settings.get(\n 'swim', 'incapSFX');\n let healSFX = game.settings.get(\n 'swim', 'healSFX');\n let looseFatigueSFX = game.settings.get(\n 'swim', 'looseFatigueSFX');\n let potionSFX = game.settings.get(\n 'swim', 'potionSFX');\n\n // Declairing variables and constants.\n const wv = token.actor.data.data.wounds.value;\n const wm = token.actor.data.data.wounds.max;\n const fv = token.actor.data.data.fatigue.value;\n const fm = token.actor.data.data.fatigue.max;\n //Checking for Edges (and Special/Racial Abilities)\n let natHeal_time = game.settings.get(\n 'swim', 'natHeal_time');\n const fastHealer = token.actor.data.items.find(function (item) {\n return ((item.name.toLowerCase() === \"fast healer\") && item.type === \"edge\");\n });\n if (fastHealer) { natHeal_time = \"three days\" };\n const reg_slow = token.actor.data.items.find(function (item) {\n return ((item.name.toLowerCase() === \"slow regeneration\") && item.type === \"ability\");\n });\n if (reg_slow) { natHeal_time = \"day\" };\n const reg_fast = token.actor.data.items.find(function (item) {\n return ((item.name.toLowerCase() === \"fast regeneration\") && item.type === \"ability\");\n });\n if (reg_fast) { natHeal_time = \"round\" };\n const elan = token.actor.data.items.find(function (item) {\n return item.name.toLowerCase() === \"elan\" && item.type === \"edge\";\n });\n //Checking for Health Potions\n const healthPotionOptions = game.settings.get(\n 'swim', 'healthPotionOptions');\n const healthPotionsSplit = healthPotionOptions.split(', ');\n const hasHealthPotion = token.actor.data.items.find(function (item) {\n return (healthPotionsSplit.includes(item.name) && item.type === \"gear\" && item.data.quantity > 0)\n });\n //Find owned Health potions.\n const ownedHealthPotions = healthPotionsSplit.filter(potion => token.actor.data.items.some(item => item.name === potion && item.type === \"gear\" && item.data.quantity > 0));\n //Set up a list of Health Potions to choose from.\n let healthPotionList;\n for (let healthPotion of ownedHealthPotions) {\n healthPotionList += `${healthPotion} `;\n }\n\n//Checking for Fatigue Potions\nconst fatiguePotionOptions = game.settings.get(\n 'swim', 'fatiguePotionOptions');\nconst fatiguePotionsSplit = fatiguePotionOptions.split(', ');\nconst hasFatiguePotion = token.actor.data.items.find(function (item) {\n return (fatiguePotionsSplit.includes(item.name) && item.type === \"gear\" && item.data.quantity > 0)\n});\n//Find owned Fatigue potions.\nconst ownedFatiguePotions = fatiguePotionsSplit.filter(potion => token.actor.data.items.some(item => item.name === potion && item.type === \"gear\" && item.data.quantity > 0));\n//Set up a list of Fatigue Potions to choose from.\nlet fatiguePotionList;\nfor (let fatiguePotion of ownedFatiguePotions) {\n fatiguePotionList += `${fatiguePotion} `;\n}\n\n let bennies = token.actor.data.data.bennies.value;\n let bv;\n bv = checkBennies();\n let numberWounds;\n let rounded;\n let elanBonus;\n let setWounds;\n let genericHealWounds;\n let genericHealFatigue;\n let buttons_main;\n let md_text\n\n // Adjusting buttons and Main Dialogue text\n if (fv < 1 && wv < 1) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Nevermind...\",\n callback: (html) => { },\n }\n }\n }\n else if (fv > 0 && wv < 1 && !hasFatiguePotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Cure Fatigue\",\n callback: (html) => {\n genericRemoveFatigue();\n }\n }\n }\n }\n else if (fv > 0 && wv < 1 && hasFatiguePotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Cure Fatigue\",\n callback: (html) => {\n genericRemoveFatigue();\n }\n },\n two: {\n label: \"Potion\",\n callback: (html) => {\n useFatiguePotion();\n }\n }\n }\n }\n else if (fv < 1 && wv > 0 && !hasHealthPotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Natural Healing\",\n callback: (html) => {\n numberWounds = wv;\n rollNatHeal();\n }\n },\n two: {\n label: \"Direct Healing\",\n callback: (html) => {\n genericRemoveWounds();\n }\n }\n }\n }\n else if (fv < 1 && wv > 0 && hasHealthPotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Natural Healing\",\n callback: (html) => {\n numberWounds = wv;\n rollNatHeal();\n }\n },\n two: {\n label: \"Direct Healing\",\n callback: (html) => {\n genericRemoveWounds();\n }\n },\n three: {\n label: \"Potion\",\n callback: (html) => {\n useHealthPotion();\n }\n }\n }\n }\n else if (wv > 0 && fv > 0 && !hasFatiguePotion && !hasHealthPotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Natural Healing\",\n callback: (html) => {\n numberWounds = wv;\n rollNatHeal();\n }\n },\n two: {\n label: \"Direct Healing\",\n callback: (html) => {\n genericRemoveWounds();\n }\n },\n four: {\n label: \"Cure Fatigue\",\n callback: (html) => {\n genericRemoveFatigue();\n }\n }\n }\n }\n else if (wv > 0 && fv > 0 && !hasFatiguePotion && hasHealthPotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Natural Healing\",\n callback: (html) => {\n numberWounds = wv;\n rollNatHeal();\n }\n },\n two: {\n label: \"Direct Healing\",\n callback: (html) => {\n genericRemoveWounds();\n }\n },\n three: {\n label: \"Potion (heal)\",\n callback: (html) => {\n useHealthPotion();\n }\n },\n four: {\n label: \"Cure Fatigue\",\n callback: (html) => {\n genericRemoveFatigue();\n }\n },\n }\n }\n else if (wv > 0 && fv > 0 && hasFatiguePotion && !hasHealthPotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Natural Healing\",\n callback: (html) => {\n numberWounds = wv;\n rollNatHeal();\n }\n },\n two: {\n label: \"Direct Healing\",\n callback: (html) => {\n genericRemoveWounds();\n }\n },\n three: {\n label: \"Cure Fatigue\",\n callback: (html) => {\n genericRemoveFatigue();\n }\n },\n four: {\n label: \"Potion (Fatigue)\",\n callback: (html) => {\n useFatiguePotion();\n }\n },\n }\n }\n else if (wv > 0 && fv > 0 && hasFatiguePotion && hasHealthPotion) {\n md_text = ``;\n buttons_main = {\n one: {\n label: \"Natural Healing\",\n callback: (html) => {\n numberWounds = wv;\n rollNatHeal();\n }\n },\n two: {\n label: \"Direct Healing\",\n callback: (html) => {\n genericRemoveWounds();\n }\n },\n three: {\n label: \"Potion (heal)\",\n callback: (html) => {\n useHealthPotion();\n }\n },\n four: {\n label: \"Cure Fatigue\",\n callback: (html) => {\n genericRemoveFatigue();\n }\n },\n five: {\n label: \"Potion (Fatigue)\",\n callback: (html) => {\n useFatiguePotion();\n }\n }\n }\n }\n\n // Check for Bennies\n function checkBennies() {\n bennies = token.actor.data.data.bennies.value;\n\n // Non GM token has <1 bennie OR GM user AND selected token has <1 benny\n if ((!game.user.isGM && bennies < 1) || (game.user.isGM && bennies < 1 && game.user.getFlag(\"swade\", \"bennies\") < 1)) {\n ui.notifications.error(\"You have no more bennies left. Wounds will be applied now...\");\n }\n if (game.user.isGM) {\n bv = bennies + game.user.getFlag(\"swade\", \"bennies\");\n }\n else {\n bv = bennies;\n }\n return bv;\n }\n\n // This is the main function that handles the Vigor roll.\n async function rollNatHeal() {\n\n const edgeNames = ['fast healer'];\n const actorAlias = speaker.alias;\n // Roll Vigor and check for Fast Healer.\n const r = await token.actor.rollAttribute('vigor');\n const edges = token.actor.data.items.filter(function (item) {\n return edgeNames.includes(item.name.toLowerCase()) && (item.type === \"edge\" || item.type === \"ability\");\n });\n let rollWithEdge = r.total;\n let edgeText = \"\";\n for (let edge of edges) {\n rollWithEdge += 2;\n edgeText += `+ ${edge.name} `;\n }\n\n // Apply +2 if Elan is present and if it is a reroll.\n if (typeof elanBonus === \"number\") {\n rollWithEdge += 2;\n edgeText = edgeText + `+ Elan .`;\n }\n\n // Roll Vigor including +2 if Fast Healer is present and another +2 if this is a reroll.\n let chatData = `${actorAlias} rolled ${rollWithEdge} `;\n rounded = Math.floor(rollWithEdge / 4);\n\n // Making rounded 0 if it would be negative.\n if (rounded < 0) {\n rounded = 0;\n }\n\n // Checking for a Critical Failure.\n if (isSame_bool(r.dice) && isSame_numb(r.dice) === 1) {\n ui.notifications.notify(\"You've rolled a Critical Failure!\");\n let chatData = `${actorAlias} rolled a Critical Failure! and takes another Wound! See the rules on Natural Healing for details.`;\n applyWounds();\n ChatMessage.create({ content: chatData });\n }\n else {\n if (rounded < 1) {\n bv = checkBennies();\n chatData += ` and is unable to heal any Wounds.`;\n if (bv < 1) {\n return;\n }\n else {\n dialogReroll();\n }\n } else if ((rounded === 1 && numberWounds > 1) || (rounded === 2 && numberWounds > 2)) {\n chatData += ` and heals ${rounded} of his ${numberWounds} Wounds.`;\n if (bv < 1) {\n removeWounds();\n }\n else {\n dialogReroll();\n };\n } else if ((rounded > 1 && rounded >= numberWounds && numberWounds < 3) || (rounded === 1 && numberWounds === 1)) {\n chatData += ` and heals all of his Wounds.`;\n removeWounds();\n }\n chatData += ` ${edgeText}`;\n\n ChatMessage.create({ content: chatData });\n }\n }\n\n // Functions to determine a critical failure. This one checks if all dice rolls are the same.\n function isSame_bool(d = []) {\n return d.reduce((c, a, i) => {\n if (i === 0) return true;\n return c && a.total === d[i - 1].total;\n }, true);\n }\n\n // Functions to determine a critical failure. This one checks what the number of the \"same\" was.\n function isSame_numb(d = []) {\n return d.reduce((c, a, i) => {\n if (i === 0 || d[i - 1].total === a.total) return a.total;\n return null;\n }, 0);\n }\n\n // Spend Benny function\n async function spendBenny() {\n bennies = token.actor.data.data.bennies.value;\n //Subtract the spend, use GM benny if user is GM and token has no more bennies left or spend token benny if user is player and/or token has bennies left.\n if (game.user.isGM && bennies < 1) {\n game.user.setFlag(\"swade\", \"bennies\", game.user.getFlag(\"swade\", \"bennies\") - 1)\n } else {\n token.actor.update({\n \"data.bennies.value\": bennies - 1,\n })\n }\n\n //Show the Benny Flip\n if (game.dice3d) {\n game.dice3d.showForRoll(new Roll(\"1dB\").roll(), game.user, true, null, false);\n }\n\n //Chat Message to let the everyone know a benny was spent\n ChatMessage.create({\n user: game.user._id,\n content: ` ${game.user.name} spent a Benny for ${token.name}.
`,\n });\n }\n\n // Function containing the reroll Dialogue\n function dialogReroll() {\n bv = checkBennies();\n if (bv > 0) {\n new Dialog({\n title: 'Reroll',\n content: ``,\n buttons: {\n one: {\n label: \"Reroll\",\n callback: (html) => {\n spendBenny();\n if (!!elan) {\n elanBonus = 2;\n }\n rollNatHeal();\n }\n },\n two: {\n label: \"No\",\n callback: (html) => {\n if (rounded < 1) {\n ui.notifications.notify(\"As you wish.\");\n }\n else {\n ui.notifications.notify(\"As you wish, Wounds will be removed now.\");\n }\n removeWounds();\n }\n }\n },\n default: \"one\"\n }).render(true);\n }\n else {\n ui.notifications.notify(\"You have no more bennies.\");\n removeWounds();\n }\n }\n\n // Main Dialogue\n new Dialog({\n title: 'Personal Health Centre',\n content: md_text,\n buttons: buttons_main,\n default: \"one\",\n }).render(true);\n\n function removeWounds() {\n if (genericHealWounds) {\n if (genericHealWounds > wv) {\n genericHealWounds = wv;\n ui.notifications.error(`You can't heal more wounds than you have, healing all Wounds instead now...`);\n }\n setWounds = wv - genericHealWounds;\n token.actor.update({ \"data.wounds.value\": setWounds });\n ui.notifications.notify(`${genericHealWounds} Wound(s) healed.`);\n }\n else {\n if (rounded === 1) {\n setWounds = wv - 1;\n if (setWounds < 0) {\n setWounds = 0;\n }\n token.actor.update({ \"data.wounds.value\": setWounds });\n ui.notifications.notify(\"One Wound healed.\");\n }\n if (rounded >= 2) {\n setWounds = wv - 2;\n if (setWounds < 0) {\n setWounds = 0\n }\n token.actor.update({ \"data.wounds.value\": setWounds });\n ui.notifications.notify(\"Two Wounds healed.\");\n }\n }\n if (healSFX && genericHealWounds > 0 || healSFX && rounded > 0) {\n AudioHelper.play({ src: `${healSFX}` }, true);\n }\n }\n\n // Healing from a source other than Natural Healing\n function genericRemoveWounds() {\n new Dialog({\n title: 'Direct Healing',\n content: ``,\n buttons: {\n one: {\n label: \"Heal Wounds\",\n callback: (html) => {\n genericHealWounds = Number(html.find(\"#numWounds\")[0].value);\n removeWounds();\n }\n }\n },\n default: \"one\",\n render: ([dialogContent]) => {\n dialogContent.querySelector(`input[name=\"num\"`).focus();\n dialogContent.querySelector(`input[name=\"num\"`).select();\n },\n }).render(true);\n }\n\n // Healing from a source other than Natural Healing\n function useHealthPotion() {\n new Dialog({\n title: 'Healing Potion',\n content: ``,\n buttons: {\n one: {\n label: \"Use Potion\",\n callback: async(html) => {\n genericHealWounds = Number(html.find(\"#numWounds\")[0].value);\n let selectedPotion = String(html.find(\"[name=potionName]\")[0].value);\n let potion_to_update = token.actor.items.find(i => i.name === selectedPotion);\n let potion_icon = potion_to_update.data.img;\n await token.actor.updateOwnedItem({_id: potion_to_update.id, \"data.quantity\": potion_to_update.data.data.quantity - 1})\n if (potion_to_update.data.data.quantity < 1){\n potion_to_update.delete();\n }\n ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: ` ${token.name} uses a ${selectedPotion} to heal ${genericHealWounds} wound(s).`\n })\n if (potionSFX) {\n let audioDuration = AudioHelper.play({ src: `${potionSFX}` }, true)._duration;\n await wait(audioDuration*1000);\n }\n removeWounds();\n }\n }\n },\n default: \"one\",\n render: ([dialogContent]) => {\n dialogContent.querySelector(`input[name=\"num\"`).focus();\n dialogContent.querySelector(`input[name=\"num\"`).select();\n },\n }).render(true);\n }\n\n // Healing from a source other than Natural Healing\n function useFatiguePotion() {\n new Dialog({\n title: 'Potion to cure Fatigue',\n content: ``,\n buttons: {\n one: {\n label: \"Use Potion\",\n callback: async(html) => {\n genericHealFatigue = Number(html.find(\"#numFatigue\")[0].value);\n let selectedPotion = String(html.find(\"[name=potionName]\")[0].value);\n let potion_to_update = token.actor.items.find(i => i.name === selectedPotion);\n let potion_icon = potion_to_update.data.img;\n await token.actor.updateOwnedItem({_id: potion_to_update.id, \"data.quantity\": potion_to_update.data.data.quantity - 1})\n if (potion_to_update.data.data.quantity < 1){\n potion_to_update.delete();\n }\n ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: ` ${token.name} uses a ${selectedPotion} to cure ${genericHealFatigue} level(s) of Fatigue.`\n })\n if (potionSFX) {\n let audioDuration = AudioHelper.play({ src: `${potionSFX}` }, true)._duration;\n await wait(audioDuration*1000);\n }\n RemoveFatigue();\n }\n }\n },\n default: \"one\",\n render: ([dialogContent]) => {\n dialogContent.querySelector(`input[name=\"num\"`).focus();\n dialogContent.querySelector(`input[name=\"num\"`).select();\n },\n }).render(true);\n }\n\n // Removing Fatigue\n function genericRemoveFatigue() {\n new Dialog({\n title: 'Cure Fatigue',\n content: ``,\n buttons: {\n one: {\n label: \"Cure Fatigue\",\n callback: async(html) => {\n genericHealFatigue = Number(html.find(\"#numFatigue\")[0].value);\n RemoveFatigue();\n await ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: `${token.name} lost ${genericHealFatigue} Level(s) of Fatigue.`\n })\n }\n }\n },\n default: \"one\",\n render: ([dialogContent]) => {\n dialogContent.querySelector(`input[name=\"num\"`).focus();\n dialogContent.querySelector(`input[name=\"num\"`).select();\n },\n }).render(true);\n }\n\n function RemoveFatigue() {\n if (genericHealFatigue > fv) {\n genericHealFatigue = fv;\n ui.notifications.error(`You can't cure more Fatigue than you have, curing all Fatigue instead now...`);\n }\n let setFatigue = fv - genericHealFatigue;\n token.actor.update({ \"data.fatigue.value\": setFatigue });\n ui.notifications.notify(`${genericHealFatigue} Level(s) of Fatigue cured.`);\n if (looseFatigueSFX && genericHealFatigue > 0) {\n AudioHelper.play({ src: `${looseFatigueSFX}` }, true);\n }\n }\n\n function applyWounds() {\n setWounds = wv + 1\n if (setWounds <= wm) {\n token.actor.update({ \"data.wounds.value\": setWounds });\n if (woundedSFX) {\n AudioHelper.play({ src: `${woundedSFX}` }, true);\n }\n }\n else {\n token.actor.update({ \"data.wounds.value\": wm });\n game.cub.addCondition(\"Incapacitated\");\n if (incapSFX) {\n AudioHelper.play({ src: `${incapSFX}` }, true);\n }\n }\n }\n\n async function wait(ms) {\n return new Promise(resolve => {\n setTimeout(resolve, ms);\n });\n }\n // v.3.1.1 By SalieriC#8263; fixing bugs supported by FloRad#2142. Potion usage inspired by grendel111111#1603; asynchronous playback of sfx by Freeze#2689.\n}","author":"wpWSO3unowg8siJN","img":"icons/commodities/materials/bowl-powder-blue.webp","actorIds":[]}
-{"name":"New Macro","permission":{"default":0,"0KMJjNMbqQRfm9Y9":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.fj8L2PMCcSe76fzi"}},"scope":"global","command":"getRequirements();\n\nfunction getRequirements() {\n let cardsList = \"\";\n let defaultDeck = game.settings.get(\n 'swim', 'chaseDeck');\n if (defaultDeck) {\n let defaultOption = game.tables.getName(`${defaultDeck}`)\n if (defaultOption) {\n cardsList += `${defaultOption.name} `;\n }\n }\n //Filter tables to only include those likely to be set up.\n const options = game.tables.filter(t =>\n t.name !== `${defaultDeck}` && (\n t.name.includes(`Chase`) ||\n t.name.includes(`Deck`) ||\n t.name.includes(`Cards`))\n );\n Array.from(options).map((el) => {\n cardsList += `${el.data.name} `\n });\n\n let template = `\n Table to Draw From: ${cardsList}
\n Number of Cards to Draw:
\n `/*`\n \n Height: \n Width: \n
\n `*/;\n new Dialog({\n title: \"Chase Layout Manager\",\n content: template,\n buttons: {\n ok: {\n label: ` Draw`,\n callback: async (html) => {\n makeChase(html);\n },\n },\n reset: {\n label: ` Reset`,\n callback: async (html) => {\n resetChase(html);\n }\n },\n cancel: {\n label: ` Cancel`,\n },\n },\n }).render(true);\n}\n\nasync function makeChase(html) {\n let tableName = html.find(\"#tableName\")[0].value;\n let cardsToDraw = html.find(\"#drawAmt\")[0].value;\n let _height = 300;\n let _width = 200;\n if (cardsToDraw > 18) {\n ui.notifications.error(\"You can't set up more than 18 cards on this layout.\")\n ui.notifications.notify(\"Setting up 18 cards instead.\")\n cardsToDraw = 18;\n }\n\n let cardDraws = (\n await game.tables\n .find((el) => el.data.name == tableName)\n .drawMany(cardsToDraw, { displayChat: false })\n ).results;\n \n AudioHelper.play({ src: `systems/swade/assets/card-flip.wav` }, true);\n \n for (let i = 0; i < cardsToDraw; i++) {\n let xPosition = 500 + i % 9 * 200;\n let yPosition = (i > 8) ? 1500 : 1200;\n /*if (i > 8) {\n yStart = 1500;\n }*/\n await Tile.create({\n img: cardDraws[i].img,\n width: _width,\n height: _height,\n x: xPosition,\n y: yPosition,\n 'flags.swim.isChaseCard': true\n });\n }\n}\n\nasync function resetChase(html) {\n let tableName = html.find(\"#tableName\")[0].value;\n const table = await game.tables.find((t) => t.data.name === tableName);\n table.reset();\n AudioHelper.play({ src: `systems/swade/assets/card-flip.wav` }, true);\n /*const chaseCards = await canvas.scene.data.tiles.filter(t => t.flags?.swim?.isChaseCard === true);\n if (chaseCards.length) {\n for await (const card of chaseCards) {\n await card.delete();\n }*/\n const delete_ids = canvas.scene.data.tiles\n .filter(t => !!t.flags?.swim?.isChaseCard)\n .map(t => t._id);\n\n await canvas.scene.deleteEmbeddedEntity(\"Tile\", delete_ids);\n ui.notifications.info(`All tiles from ${tableName} have been shuffled into the deck.`)\n//v.1.0.0 by SalieriC#8263, mildly inspired by a macro from brunocalado#1650, assisted by Kekilla#7036\n}","author":"0KMJjNMbqQRfm9Y9","img":"icons/skills/movement/figure-running-gray.webp","actorIds":[],"_id":"CeyVN3VzGtlD9Tl5"}
-{"$$deleted":true,"_id":"CeyVN3VzGtlD9Tl5"}
{"name":"Chase Setup","permission":{"default":0,"0KMJjNMbqQRfm9Y9":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.fj8L2PMCcSe76fzi"}},"scope":"global","command":"//To be used with the included chase layouts.\n\ngetRequirements();\n\nfunction getRequirements() {\n let cardsList = \"\";\n let defaultDeck = game.settings.get(\n 'swim', 'chaseDeck');\n if (defaultDeck) {\n let defaultOption = game.tables.getName(`${defaultDeck}`)\n if (defaultOption) {\n cardsList += `${defaultOption.name} `;\n }\n }\n //Filter tables to only include those likely to be set up.\n const options = game.tables.filter(t =>\n t.name !== `${defaultDeck}` && (\n t.name.includes(`Chase`) ||\n t.name.includes(`Deck`) ||\n t.name.includes(`Cards`))\n );\n Array.from(options).map((el) => {\n cardsList += `${el.data.name} `\n });\n\n let template = `\n Table to Draw From: ${cardsList}
\n Number of Cards to Draw:
\n `/*`\n \n Height: \n Width: \n
\n `*/;\n new Dialog({\n title: \"Chase Layout Manager\",\n content: template,\n buttons: {\n ok: {\n label: ` Draw`,\n callback: async (html) => {\n makeChase(html);\n },\n },\n reset: {\n label: ` Reset`,\n callback: async (html) => {\n resetChase(html);\n }\n },\n cancel: {\n label: ` Cancel`,\n },\n },\n }).render(true);\n}\n\nasync function makeChase(html) {\n let tableName = html.find(\"#tableName\")[0].value;\n let cardsToDraw = html.find(\"#drawAmt\")[0].value;\n let _height = 300;\n let _width = 200;\n if (cardsToDraw > 18) {\n ui.notifications.error(\"You can't set up more than 18 cards on this layout.\")\n ui.notifications.notify(\"Setting up 18 cards instead.\")\n cardsToDraw = 18;\n }\n\n let cardDraws = (\n await game.tables\n .find((el) => el.data.name == tableName)\n .drawMany(cardsToDraw, { displayChat: false })\n ).results;\n \n AudioHelper.play({ src: `systems/swade/assets/card-flip.wav` }, true);\n \n for (let i = 0; i < cardsToDraw; i++) {\n let xPosition = 500 + i % 9 * 200;\n let yPosition = (i > 8) ? 1500 : 1200;\n /*if (i > 8) {\n yStart = 1500;\n }*/\n await Tile.create({\n img: cardDraws[i].img,\n width: _width,\n height: _height,\n x: xPosition,\n y: yPosition,\n 'flags.swim.isChaseCard': true\n });\n }\n}\n\nasync function resetChase(html) {\n let tableName = html.find(\"#tableName\")[0].value;\n const table = await game.tables.find((t) => t.data.name === tableName);\n table.reset();\n AudioHelper.play({ src: `systems/swade/assets/card-flip.wav` }, true);\n /*const chaseCards = await canvas.scene.data.tiles.filter(t => t.flags?.swim?.isChaseCard === true);\n if (chaseCards.length) {\n for await (const card of chaseCards) {\n await card.delete();\n }*/\n const delete_ids = canvas.scene.data.tiles\n .filter(t => !!t.flags?.swim?.isChaseCard)\n .map(t => t._id);\n\n await canvas.scene.deleteEmbeddedEntity(\"Tile\", delete_ids);\n ui.notifications.info(`All tiles from ${tableName} have been shuffled into the deck.`)\n//v.1.0.0 by SalieriC#8263, mildly inspired by a macro from brunocalado#1650, assisted by Kekilla#7036\n}","author":"0KMJjNMbqQRfm9Y9","img":"icons/skills/movement/figure-running-gray.webp","actorIds":[],"_id":"zYpdx0AvaMv2hz0e"}
+{"_id":"49TiCaAgArpHsAhz","name":"SWIM: Ammo usage","permission":{"default":0,"wpWSO3unowg8siJN":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"furnace":{"runAsGM":false},"core":{"sourceId":"Macro.mMb87BLxl6JfQXQ1"}},"scope":"global","command":"checkWeapon();\n\nasync function wait(ms) {\n return new Promise(resolve => {\n setTimeout(resolve, ms);\n });\n}\n\nasync function checkWeapon() {\n //Don't execute the macro on a reroll by checking if the old_rolls is empty:\n if (message.data.flags['betterrolls-swade2'].render_data.trait_roll.old_rolls.length >= 1) { return; }\n //Check whether or not the weapon is suitable for the shooting macro\n if (\n (item.type === \"weapon\" &&\n //i.data.data.range !== \"0\" && i.data.data.range !== \"\" &&\n item.data.data.ammo.trim() !== \"\" &&\n item.data.data.quantity > 0) ||\n (item.type === \"weapon\" &&\n //i.data.data.range !== \"0\" && i.data.data.range !== \"\" &&\n item.data.data.additionalStats.isConsumable &&\n item.data.data.additionalStats.isConsumable.value === true /*&&\n //Ignore quantity to get a notification below for BR2 integration.\n item.data.data.quantity > 0*/)\n ) { shoot(); }\n else { return; }\n}\n\nasync function shoot() {\n //let [shots, weapon, ammo, sil] = getValues(html);\n let item_weapon = item;\n //Stop if the item is not a weapon:\n if (item_weapon.type != \"weapon\") { return; }\n //Get ammo loaded in the weapon and amount of shots provided by BR2 as well as a silenced state:\n let item_ammo;\n if (item_weapon.data.data.additionalStats.loadedAmmo) {\n let loaded_ammo = item_weapon.data.data.additionalStats.loadedAmmo.value;\n item_ammo = actor.items.getName(`${loaded_ammo}`);\n }\n //Setting the amount of shots based on RoF:\n let traitDice = message.data.flags['betterrolls-swade2'].render_data.trait_roll.dice;\n //console.log(traitDice);\n //console.log(message.data.flags['betterrolls-swade2'].render_data);\n let rate_of_fire = traitDice.length;\n if (actor.data.data.wildcard === true) { rate_of_fire = rate_of_fire - 1; }\n //console.log(rate_of_fire);\n let shots = message.data.flags['betterrolls-swade2'].render_data.used_shots;\n //failsafe to guss amount of shots in case BR2 return zero or undefined:\n if (shots === 0 || !shots) {\n if (rate_of_fire === 1) { shots = 1; }\n if (rate_of_fire === 2) { shots = 5; }\n if (rate_of_fire === 3) { shots = 10; }\n if (rate_of_fire === 4) { shots = 20; }\n if (rate_of_fire === 5) { shots = 40; }\n if (rate_of_fire === 6) { shots = 50; }\n }\n\n let sil = false;\n if (item_weapon.data.data.additionalStats.silenced && item_weapon.data.data.additionalStats.silenced.value === true) {\n sil = true;\n }\n // Getting sfxDelay from game settings\n let sfxDelay = game.settings.get(\n 'swim', 'sfxDelay');\n // Getting the sfx from the weapon provided by BR2:\n let sfx_shot;\n let sfx_silenced;\n let sfx_shot_auto;\n let sfx_silenced_auto;\n let sfx_empty;\n if (item_weapon.data.data.additionalStats.sfx) {\n let sfx = item_weapon.data.data.additionalStats.sfx.value.split(`|`);\n sfx_shot = sfx[1];\n sfx_silenced = sfx[3];\n sfx_shot_auto = sfx[2];\n sfx_silenced_auto = sfx[4];\n sfx_empty = sfx[5];\n }\n // Getting Weapon and loaded ammo\n const weaponIMG = item_weapon.data.img;\n let currentAmmo\n if (item_weapon.data.data.additionalStats.loadedAmmo) {\n currentAmmo = item_weapon.data.data.additionalStats.loadedAmmo.value;\n }\n\n // Calculating shots to expend\n const currentCharges = parseInt(item_weapon.data.data.currentShots);\n const newCharges = currentCharges - shots;\n //If no ammo needed, only play SFX\n if (item_weapon.data.data.ammo === \"NONE\" && item_weapon.data.data.additionalStats.sfx) {\n // Play sound effects\n if (sil === true && sfx_silenced) {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots === 3) {\n //console.log(\"I AM HERE!\");\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots > 3 && sfx_silenced_auto) {\n AudioHelper.play({ src: `${sfx_silenced_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n }\n else {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots === 3) {\n //console.log(\"I AM HERE!\");\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots > 3 && sfx_shot_auto) {\n AudioHelper.play({ src: `${sfx_shot_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n }\n else if (item_weapon.data.data.additionalStats.isConsumable && item_weapon.data.data.additionalStats.isConsumable.value === true) {\n //Get Skill from BR2. This returns as \"Skill dx\" so we need to filter that later...\n let usedSkill = message.data.flags['betterrolls-swade2'].render_data.skill_title;\n //We assume that all consumable weapons use \"Athletics\", \"Athletics (Throwing)\", \"Athletics (Explosives)\" or \"Throwing\" and only proceed if one of these skills was used. This is where we filter with .includes().\n if (usedSkill.includes(\"Athletics\") === false &&\n usedSkill.includes(\"Athletics (Throwing)\") === false &&\n usedSkill.includes(\"Athletics (Explosives)\") === false &&\n usedSkill.includes(\"Throwing\") === false) { return; }\n const currentQuantity = parseInt(item_weapon.data.data.quantity);\n if (currentQuantity <= 0) {\n return ui.notifications.error(`You don't have a ${item_weapon.name} left.`);\n }\n const newQuantity = currentQuantity - shots;\n const updates = [\n { _id: item_weapon.id, \"data.quantity\": `${newQuantity}` },\n ];\n // Updating the consumable weapon\n await actor.updateOwnedItem(updates);\n // Deleting the consumable weapon if it was the last (disabled because it breaks rerolls in BR2)\n /*if (newQuantity <= 0) {\n item_weapon.delete();\n }*/\n // Creating the Chat message\n ChatMessage.create({\n speaker: {\n alias: actor.name\n },\n content: ` ${actor.name} uses ${shots} ${item_weapon.name}(s) and has ${newQuantity} left.`\n })\n // Play sound effects\n if (sfx_shot) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n //Stuff for weapons with \"doesn't require reload action\" checked:\n else if (item_weapon.data.data.autoReload === true) {\n //Throw error if no ammo is left.\n if (item_ammo && item_ammo.data.data.quantity <= 0) { return ui.notifications.error(`You don't have a ${item_weapon.name} left.`); }\n //Failsafe in case no ammo is provided.\n else if (!item_ammo) { return ui.notifications.error(`Please define your desired Ammo in \"Loaded Ammo\" first.`); }\n else {\n //Setting new constants to overwrite the old ones\n const currentCharges = parseInt(item_ammo.data.data.quantity);\n const newCharges = currentCharges - shots;\n //Setting up the updates\n const updates = [\n { _id: item_ammo.id, \"data.quantity\": `${newCharges}` },\n ];\n // Updating the Weapon\n actor.updateOwnedItem(updates);\n //Creating the chat message\n ChatMessage.create({\n speaker: {\n alias: actor.name\n },\n content: ` ${actor.name} fires ${shots} ${currentAmmo} round(s) from a ${item_weapon.name} and has ${newCharges} left .`\n })\n //Playing the SFX\n // Play sound effects\n if (sil === true && sfx_silenced) {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots === 3) {\n //console.log(\"I AM HERE!\");\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots > 3 && sfx_silenced_auto) {\n AudioHelper.play({ src: `${sfx_silenced_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n }\n else {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots === 3) {\n //console.log(\"I AM HERE!\");\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots > 3 && sfx_shot_auto) {\n AudioHelper.play({ src: `${sfx_shot_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n }\n }\n // Check if enough bullets are in the weapon to fire the given amount of shots if this is not a consumable weapon and does require loading action.\n else if (currentCharges < shots && item_weapon.data.data.autoReload === false) {\n ui.notifications.error(\"You have insufficient ammunition.\");\n if (sfx_empty && currentCharges === 0) {\n AudioHelper.play({ src: `${sfx_empty}` }, true);\n }\n return;\n }\n else {\n const updates = [\n { _id: item_weapon.id, \"data.currentShots\": `${newCharges}` },\n ];\n // Updating the Weapon\n actor.updateOwnedItem(updates);\n // Creating the Chat message\n if (!currentAmmo) {\n ChatMessage.create({\n speaker: {\n alias: actor.name\n },\n content: ` ${actor.name} fires ${shots} round(s) from a ${item_weapon.name} and has ${newCharges} left .`\n })\n } else {\n ChatMessage.create({\n speaker: {\n alias: actor.name\n },\n content: ` ${actor.name} fires ${shots} ${currentAmmo} round(s) from a ${item_weapon.name} and has ${newCharges} left .`\n })\n }\n // Play sound effects\n if (sil === true && sfx_silenced) {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots === 3) {\n //console.log(\"I AM HERE!\");\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots > 3 && sfx_silenced_auto) {\n AudioHelper.play({ src: `${sfx_silenced_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n }\n else {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_sshot}` }, true);\n }\n else if (shots === 3) {\n //console.log(\"I AM HERE!\");\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots > 3 && sfx_shot_auto) {\n AudioHelper.play({ src: `${sfx_shot_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n }\n //V. 3.1.0 by SalieriC#8263 with help from javierrivera#4813.\n}","author":"wpWSO3unowg8siJN","img":"icons/weapons/crossbows/crossbow-long-brown.webp","actorIds":[]}
+{"_id":"gB5U6GYNh86mA8F2","name":"Ammo Management (enhanced) [READ THE DOC!]","permission":{"default":0,"wpWSO3unowg8siJN":3},"type":"script","flags":{"combat-utility-belt":{"macroTrigger":""},"core":{"sourceId":"Macro.XEYSVDTmQli1bqlu"}},"scope":"global","command":"// Preset for SFX of weapons (Without the //):\n//RELOAD|FIRE|AUTOFIRE|SILENCED|SILENCEDAUTOFIRE|EMPTY\n\nlet dialogID = \"\";\n\nasync function wait(ms) {\n return new Promise(resolve => {\n setTimeout(resolve, ms);\n });\n}\n\nasync function weaponDialog() {\n if (!token) return ui.notifications.error(\"Please select a token first\");\n\n const actor = token.actor;\n const weapons = actor.items.filter(i =>\n (i.type === \"weapon\" &&\n //i.data.data.range !== \"0\" && i.data.data.range !== \"\" &&\n i.data.data.ammo.trim() !== \"\" &&\n i.data.data.quantity > 0) ||\n (i.type === \"weapon\" &&\n //i.data.data.range !== \"0\" && i.data.data.range !== \"\" &&\n i.data.data.additionalStats.isConsumable &&\n i.data.data.additionalStats.isConsumable.value === true &&\n i.data.data.quantity > 0)\n );\n\n if (weapons.length === 0) return ui.notifications.error(\"You have no reloadable or consumable weapons.\");\n\n let html = getHTML();\n let content = getContent();\n let buttons = getButtons();\n\n let dialog = new Dialog({\n content, buttons, title: `Attack Dialog`\n }, {\n width: 400,\n });\n\n dialogID = dialog.appId;\n\n await dialog._render(true);\n activeListeners();\n\n function getHTML() {\n let html = document.getElementById(`app-${dialogID}`)?.getElementsByTagName(`select`);\n if (html === undefined) return undefined;\n else return Array.from(html).map(h => h.value);\n }\n function getContent() {\n let selectedWeapon = html !== undefined\n ? html[0]\n : weapons[0].id;\n\n //console.log(html, selectedWeapon, weapons);\n\n //Get ammo and filter for the ammo the token actually owns.\n let ammo = weapons\n .find(w => w.id === selectedWeapon).data.data.ammo.trim().split(`|`)\n .filter(a => !!actor.items.getName(a));\n\n let rate_of_fire = parseInt(weapons.find(w => w.id === selectedWeapon).data.data.rof);\n let defaultShots = 1;\n if (rate_of_fire === 2) { defaultShots = 5; }\n if (rate_of_fire === 3) { defaultShots = 10; }\n if (rate_of_fire === 4) { defaultShots = 20; }\n if (rate_of_fire === 5) { defaultShots = 40; }\n if (rate_of_fire === 6) { defaultShots = 50; }\n\n /*\n let defaultSingleReload = false;\n let isSingleReload = parseInt(weapons.find(w => w.id === selectedWeapon).data.name.includes(\"Revolver\"));\n if (isSingleReload === true){defaultSingleReload = true;}\n */\n\n return `\n \n `\n // \n }\n function getButtons() {\n return {\n a: {\n label: \"Shoot\", callback: shoot,\n },\n b: {\n label: \"Reload\", callback: reload,\n }\n }\n }\n function activeListeners() {\n document.getElementById(\"weapon\").onchange = update;\n }\n async function update() {\n html = getHTML();\n dialog.data.content = getContent();\n dialog.data.buttons = getButtons();\n await dialog._render(true);\n activeListeners();\n }\n async function shoot(html) {\n let [shots, weapon, ammo] = getValues(html);\n let item_weapon = actor.items.get(weapon);\n let item_ammo = actor.items.getName(`${ammo}`);\n // Getting sfxDelay from game settings\n let sfxDelay = game.settings.get(\n 'swim', 'sfxDelay');\n // Getting the sfx from the selected weapon\n let sfx_shot/* = stuff*/;\n let sfx_silenced/* = stuff*/;\n let sfx_shot_auto/* = stuff*/;\n let sfx_silenced_auto/* = stuff*/;\n let sfx_empty;\n if (item_weapon.data.data.additionalStats.sfx) {\n let sfx = item_weapon.data.data.additionalStats.sfx.value.split(`|`);\n sfx_shot = sfx[1];\n sfx_silenced = sfx[3];\n sfx_shot_auto = sfx[2];\n sfx_silenced_auto = sfx[4];\n sfx_empty = sfx[5];\n }\n // Setting a boolean depending on whether or not a weapon is silenced\n let sil = false;\n if (item_weapon.data.data.additionalStats.silenced && item_weapon.data.data.additionalStats.silenced.value === true) {\n sil = true;\n }\n // Getting Weapon and loaded ammo\n const weaponIMG = item_weapon.data.img;\n let currentAmmo\n if (item_weapon.data.data.additionalStats.loadedAmmo) {\n currentAmmo = item_weapon.data.data.additionalStats.loadedAmmo.value;\n }\n\n // Calculating shots to expend\n const currentCharges = parseInt(item_weapon.data.data.currentShots);\n const newCharges = currentCharges - shots;\n //If no ammo needed, only play SFX\n if (item_weapon.data.data.ammo === \"NONE\" && item_weapon.data.data.additionalStats.sfx) {\n // Play sound effects\n if (sil === true && sfx_silenced) {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots === 3) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots > 3 && sfx_silenced_auto) {\n AudioHelper.play({ src: `${sfx_silenced_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n }\n else {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots === 3) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots > 3 && sfx_shot_auto) {\n AudioHelper.play({ src: `${sfx_shot_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n }\n else if (item_weapon.data.data.additionalStats.isConsumable && item_weapon.data.data.additionalStats.isConsumable.value === true) {\n const currentQuantity = parseInt(item_weapon.data.data.quantity);\n if (currentQuantity <= 0) {\n return ui.notifications.error(`You don't have a ${item_weapon.name} left.`);\n }\n const newQuantity = currentQuantity - shots;\n const updates = [\n { _id: item_weapon.id, \"data.quantity\": `${newQuantity}` },\n ];\n // Updating the consumable weapon\n await actor.updateOwnedItem(updates);\n // Deleting the consumable weapon if it was the last\n if (newQuantity <= 0) {\n item_weapon.delete();\n }\n // Creating the Chat message\n ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: ` ${token.name} uses ${shots} ${item_weapon.name}(s) and has ${newQuantity} left.`\n })\n // Play sound effects\n if (sfx_shot) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n //Stuff for weapons with \"doesn't require reload action\" checked:\n else if (item_weapon.data.data.autoReload === true) {\n //Throw error if no ammo is left.\n if (item_ammo.data.data.quantity <= 0) { return ui.notifications.error(`You don't have a ${item_ammo.name} left.`); }\n else {\n //Setting new constants to overwrite the old ones\n const currentCharges = parseInt(item_ammo.data.data.quantity);\n const newCharges = currentCharges - shots;\n //Setting up the updates\n const updates = [\n { _id: item_ammo.id, \"data.quantity\": `${newCharges}` },\n ];\n // Updating the Weapon\n actor.updateOwnedItem(updates);\n //Creating the chat message\n ChatMessage.create({\n speaker: {\n alias: actor.name\n },\n content: ` ${actor.name} fires ${shots} ${currentAmmo} round(s) from a ${item_weapon.name} and has ${newCharges} left .`\n })\n //Playing the SFX\n // Play sound effects\n if (sil === true && sfx_silenced) {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots === 3) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots > 3 && sfx_silenced_auto) {\n AudioHelper.play({ src: `${sfx_silenced_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n }\n else {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots === 3) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots > 3 && sfx_shot_auto) {\n AudioHelper.play({ src: `${sfx_shot_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n }\n }\n // Check if enough bullets are in the weapon to fire the given amount of shots if this is not a consumable weapon and does require loading action.\n else if (currentCharges < shots && item_weapon.data.data.autoReload === false) {\n ui.notifications.error(\"You have insufficient ammunition.\")\n if (sfx_empty && currentCharges === 0) {\n AudioHelper.play({ src: `${sfx_empty}` }, true);\n }\n return;\n }\n else {\n const updates = [\n { _id: item_weapon.id, \"data.currentShots\": `${newCharges}` },\n ];\n // Updating the Weapon\n actor.updateOwnedItem(updates);\n // Creating the Chat message\n if (!currentAmmo) {\n ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: ` ${token.name} fires ${shots} round(s) from a ${item_weapon.name} and has ${newCharges} left .`\n })\n } else {\n ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: ` ${token.name} fires ${shots} ${currentAmmo} round(s) from a ${item_weapon.name} and has ${newCharges} left .`\n })\n }\n // Play sound effects\n if (sil === true && sfx_silenced) {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots === 3) {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n else if (shots > 3 && sfx_silenced_auto) {\n AudioHelper.play({ src: `${sfx_silenced_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_silenced}` }, true);\n }\n }\n else {\n if (shots === 2) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots === 3) {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n await wait(`${sfxDelay}`);\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n else if (shots > 3 && sfx_shot_auto) {\n AudioHelper.play({ src: `${sfx_shot_auto}` }, true);\n }\n else {\n AudioHelper.play({ src: `${sfx_shot}` }, true);\n }\n }\n }\n\n // console.log(\"Shoot | \", shots, weapon, ammo, sil, item_weapon);\n }\n function reload(html) {\n let [shots, weapon, ammo, singleReload] = getValues(html);\n // If no ammo left throw an error message.\n if (!ammo) {\n return ui.notifications.error(\"You have no ammo left to reload this weapon.\");\n }\n let item_weapon = actor.items.get(weapon);\n // Do not allow consumable weapons to be reloaded\n if (item_weapon.data.data.additionalStats.isConsumable && item_weapon.data.data.additionalStats.isConsumable.value === true) {\n return ui.notifications.error(\"You cannot reload consumable weapons, please use Shooting instead.\");\n }\n let item_ammo = actor.items.getName(`${ammo}`);\n //console.log(weapon, item_weapon, ammo, item_ammo);\n const oldAmmo = item_weapon.data.data.additionalStats.loadedAmmo.value;\n let item_oldAmmo;\n if (!oldAmmo) {\n item_oldAmmo = item_ammo;\n }\n else {\n item_oldAmmo = actor.items.getName(`${oldAmmo}`);\n }\n // We suspect that the ammo to reload is the same as the previously loaded one. If not chgType will tell the code to swap the ammo.\n let chgType = false;\n if (item_oldAmmo != item_ammo) {\n chgType = true;\n }\n // Getting the sfx from the selected weapon\n let sfx_reload;\n if (item_weapon.data.data.additionalStats.sfx) {\n let sfx = item_weapon.data.data.additionalStats.sfx.value.split(`|`);\n sfx_reload = sfx[0];\n }\n // Getting images from items\n const weaponIMG = item_weapon.data.img;\n const ammoIMG = item_ammo.data.img;\n\n // Getting current numbers\n const currentCharges = parseInt(item_weapon.data.data.currentShots);\n const maxCharges = parseInt(item_weapon.data.data.shots);\n const requiredCharges = parseInt(item_weapon.data.data.shots - currentCharges);\n const availableAmmo = parseInt(item_ammo.data.data.quantity);\n const oldAmmoQuantity = parseInt(item_oldAmmo.data.data.quantity);\n // Variables for recharging procedure\n let amountToRecharge;\n let newCharges;\n let newAmmo;\n let oldAmmoRefill;\n // Checking if the Ammo is a charge pack. If not or additionalStat is not present ignore it. Charge Packs can only refill if curr and max shots are equal.\n if (item_ammo.data.data.additionalStats.isPack && item_ammo.data.data.additionalStats.isPack.value === true) {\n // Charge Packs only use 1 Quantity to fully charge the weapon\n amountToRecharge = parseInt(item_weapon.data.data.shots);\n newCharges = amountToRecharge;\n newAmmo = availableAmmo - 1;\n //Refill old Charge Pack if it is still full (current and max shots are equal)\n if (chgType === true && currentCharges === maxCharges) {\n oldAmmoRefill = oldAmmoQuantity + 1;\n }\n else if (chgType === true && currentCharges != maxCharges) {\n oldAmmoRefill = oldAmmoQuantity;\n }\n }\n // Checking if user selected to change the ammo type. This is only relevant if not a Charge Pack, if it is, it's already handled above.\n else if (chgType === true) {\n // When changing Ammo type, remaining shots should not become the new Ammo Type.\n amountToRecharge = parseInt(item_weapon.data.data.shots);\n //Change the amount to recharge to 1 if singleReload is checked.\n if (singleReload === true) { amountToRecharge = 1; }\n newCharges = amountToRecharge;\n newAmmo = availableAmmo - amountToRecharge;\n oldAmmoRefill = oldAmmoQuantity + currentCharges;\n }\n else {\n // If the quantity of ammo is less than the amount required, use whatever is left.\n amountToRecharge = Math.min(availableAmmo, requiredCharges);\n //Change the amount to recharge to 1 if singleReload is checked.\n if (singleReload === true) { amountToRecharge = 1; }\n newCharges = currentCharges + amountToRecharge;\n newAmmo = availableAmmo - amountToRecharge;\n }\n // Check if there is ammo left to reload.\n if (availableAmmo < 1) {\n ui.notifications.notify(\"You are out of ammunition.\")\n }\n else if (chgType === true) {\n const updates = [\n { _id: item_weapon.id, \"data.currentShots\": `${newCharges}`, \"data.additionalStats.loadedAmmo.value\": `${ammo}` },\n { _id: item_ammo.id, \"data.quantity\": `${newAmmo}` },\n { _id: item_oldAmmo.id, \"data.quantity\": `${oldAmmoRefill}` },\n ];\n\n actor.updateOwnedItem(updates);\n ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: ` ${token.name} reloads his/her ${item_weapon.name} with ${item_ammo.name}.`\n })\n if (sfx_reload) {\n AudioHelper.play({ src: `${sfx_reload}` }, true)\n }\n }\n else {\n const updates = [\n { _id: item_weapon.id, \"data.currentShots\": `${newCharges}`, \"data.additionalStats.loadedAmmo.value\": `${ammo}` },\n { _id: item_ammo.id, \"data.quantity\": `${newAmmo}` },\n ];\n\n actor.updateOwnedItem(updates);\n ChatMessage.create({\n speaker: {\n alias: token.name\n },\n content: ` ${token.name} reloads his/her ${item_weapon.name} with ${item_ammo.name}.`\n })\n if (sfx_reload) {\n AudioHelper.play({ src: `${sfx_reload}` }, true)\n }\n }\n\n // Ammo with no more bullets left are NOT deleted because that could cause issues when trying to change the ammo.\n }\n function getValues(html) {\n return [\n html.find(`#shots`)[0].valueAsNumber,\n html.find(`#weapon`)[0].value,\n html.find(`#ammo`)[0].value,\n html.find(`#singleReload`)[0].checked,\n ];\n }\n // V. 3.1.0 By SalieriC#8263. Dialogue Framework: Kekilla#7036\n}\n\nweaponDialog();","author":"wpWSO3unowg8siJN","img":"icons/weapons/crossbows/crossbow-white.webp","actorIds":[]}