Skip to content

Commit

Permalink
initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
SalieriC committed Feb 24, 2021
1 parent c2c98f5 commit f5f6a28
Show file tree
Hide file tree
Showing 14 changed files with 1,431 additions and 1,033 deletions.
52 changes: 37 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,9 @@
This is a selection of macros for Savage Worlds Players and GMs alike.
**Warning:** This module is currently a pre-release. It is currently in the making, please read carefully on how to use it and expect bugs.

## Pre-Release usage instructions
The macros are currently not accessible inside Foundry for development reasons. For the pre-release you need to import the macros manually. To do so:
1. Navigate to you Foundry data folder and find the module inside the modules folder, the folder is called `swim`.
2. Open the macros folder and find the macros you're interested in. (Or find the macros [here](https://github.com/SalieriC/SWADE-Immersive-Macros/tree/main/swim/macros) and skip 3.)
3. Open the macro using an IDE like Visual Studio Code or Notepad++.
4. Copy the entire code (CTRL+A and CTRL+C on Windows).
5. In Foundry create a new macro and paste the code in (CTRL+V on Windows).
6. Set the macro to be a script macro (this is very important).
7. Configure the module to your liking (not all sound effects have assets yet, you need to find your own) and start experimenting.
8. If you find bugs or have other suggestions please read the `Help needed!` section below.
9. Repeat this process when the module gets updated.
## Usage instructions
You'll find all the macros in a new compendium delivered by the module. It contains all the macros. You can either import the macros or replace the code of your existing macros. Once that is done you can start using them although I strongly suggest taking a look at the module settings. There are quite a few which might be interesting for you.
**Important:** You might need to update the macros you've imported when you update the module. I hope to find a method to automate this in the future.

## License
The Code of the macros and modules is licensed under the GPU 3.0 License ([see License](https://github.com/SalieriC/SWADE-Immersive-Macros/blob/main/LICENSE)).
Expand Down Expand Up @@ -76,14 +68,13 @@ This macro comes in two variants: SWADE and SWD. I like the SWD rules regarding
**SWADE:** To act you'll only need a success.

### (Un-)Stun
**Requirements:** None.
**Compatibility:**
**Requirements:**
- [Combat Utility Belt](https://foundryvtt.com/packages/combat-utility-belt/) for the status effects.

**Immersion setting:** SFX.
**Suggested icon:** `modules/swim/assets/icons/status_markers/2-Stunned.png`
**Description:**
This macro is very similar to the (Un-)Shake macro but handles Stunned. If the selected token (needs one selected) is not Stunned, it will be marked as such, including all the effects that come with it. Otherwise it will roll to unstun and adds/removes conditions according to the result. It is aware of Snake Eyes. It supports SFX on applying Stunned in the same way as (Un-)Shake.
This macro is very similar to the (Un-)Shake macro but handles Stunned. If the selected token (needs one selected) is not Stunned, it will be marked as such, including all the effects that come with it. Otherwise it will roll to unstun and adds/removes conditions according to the result. It is aware of Snake Eyes. It supports SFX on applying Stunned in the same way as (Un-)Shake. **Important:** Set up the path to your prone image to the exact same path as your prone image in the modules setting.

### Soak Damage
**Requirements:**
Expand Down Expand Up @@ -140,4 +131,35 @@ This macro is more or less the opposite of the Soak Damage macro. It offers func
It also supporst the Regeneration Special/Racial Ability but it must be set up as an Edge called "Fast Regeneration" or "Slow Regeneration". Then it adjusts the time that needs to be passed until a Natural Healing roll can be made. If your setting calls for longer or shorter periods of time until a Natural Healing roll can be made (Hellfrost comes to mind), then you can set this up in the modules settings.
It uses the sfx for Wounds, Inc.! and Healing.
The macro is also capable of removing Fatigue using a given number, which also supports a unique SFX.
<p align="center"> <img src="https://github.com/SalieriC/SWADE-Immersive-Macros/blob/main/img/macros/Personal%20Health%20Centre.jpg?raw=true"> </p>
<p align="center"> <img src="https://github.com/SalieriC/SWADE-Immersive-Macros/blob/main/img/macros/Personal%20Health%20Centre.jpg?raw=true"> </p>

### Token Vision
**Requirements:** None.
**Immersion setting:** None yet.
**Description:**
This macro is based on a macro from [@Sky#9453](https://github.com/Sky-Captain-13/foundry) and supported DnD vision and lighting. I altered it to suit Savage Worlds. I have to say though, that information on vision and illumination is very lackluster in SWADE with regards to VTT software. It works fine on an actual tabletop but not with dynamic lighting on VTTs. I had to bring some personal taste in but I tried to stay as true to the rules as I could.
The macro works different for players and GMs. For players it only shows options to equip light sources (activates them in the token settings), GMs are alos able to update the tokens vision. All options work for *all* tokens that are selected. Here are the options explained:
**Light Source:**
- No Change: Does not change the current settings.
- None: Deactivates all emit light settings.
- Candle: 2" radius of bright light.
- Lamp: 4" radius of bright light.
- Bullseye: 4" beam of bright light with an angle of 52.5 degree.(1)
- Torch: 4" radius of bright light.
- Flashlight: 10" beam of bright light in an angle of 52.2 degrees.(1)
**Vision Type:**
- No Change: Does not change the current settings.
- Pitch Darkness (0"): The token cannot see past itself.
- Dark (10"): Token has dim vision of 10".
- Dim: Token has dim vision of 1000" (the maximum allowed by Foundry).
- Low Light Vision: As dim.
- Infravision: As dim.
- Full Night Vision: Token has bright vision of 1000" (the maximum allowed by Foundry).
In general it is best to set up a universal/global light source instead of touching the vision type as that's what SWADE relies on. But the options are there in case the GM forgets to set it up and needs to act quickly.
(1) These options also lock the token rotation because the core rules uses portrait style tokens. If you use top-down view tokens instead you may want to change that in the macros code.

### Shuffle Action Deck
**Requirements:** None.
**Immersion setting:** System card deal sound.
**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.
Binary file modified swim.zip
Binary file not shown.
256 changes: 131 additions & 125 deletions swim/macros/(Un-)Shake (SWADE).js
Original file line number Diff line number Diff line change
@@ -1,150 +1,156 @@
// Checking for SWADE Spices & Flavours and setting up the Benny image.
let bennyImage = "systems/swade/assets/benny/benny-chip-front.png";
if (game.modules.get("swade-spices").active) {
let benny_Back = game.settings.get(
'swade-spices', 'bennyBack');
if (benny_Back) {
bennyImage = benny_Back;
}
}
// Setting up SFX path.
let shakenSFX = game.settings.get(
'swim', 'shakenSFX');

let bennies;
let bv;
main();

async function rollUnshake() {
async function main() {
// No Token is Selected
if (!token || canvas.tokens.controlled.length > 1) {
ui.notifications.error("Please select a single token first.");
return;
}

const edgeNames = ['combat reflexes', 'demon', 'undead', 'construct', 'undead (harrowed)'];
const actorAlias = speaker.alias;
// ROLL SPIRIT AND CHECK COMBAT REFLEXES
const r = await token.actor.rollAttribute('spirit');
const edges = token.actor.data.items.filter(function (item) {
return edgeNames.includes(item.name.toLowerCase()) && item.type === "edge";
});
let rollWithEdge = r.total;
let edgeText = "";
for (let edge of edges) {
rollWithEdge += 2;
edgeText = `<br/><i>+ ${edge.name}</i>`;
// Checking for SWADE Spices & Flavours and setting up the Benny image.
let bennyImage = "icons/commodities/currency/coin-embossed-octopus-gold.webp";
if (game.modules.get("swade-spices").active) {
let benny_Back = game.settings.get(
'swade-spices', 'bennyBack');
if (benny_Back) {
bennyImage = benny_Back;
}
}
// Setting up SFX path.
let shakenSFX = game.settings.get(
'swim', 'shakenSFX');

let chatData = `${actorAlias} rolled <span style="font-size:150%"> ${rollWithEdge} </span>`;
// Checking for a Critical Failure.
if (isSame_bool(r.dice) && isSame_numb(r.dice) === 1) {
ui.notifications.notify("You've rolled a Critical Failure!");
let chatData = `${actorAlias} rolled a <span style="font-size:150%"> Critical Failure! </span>`;
ChatMessage.create({ content: chatData });
}
else {
if (rollWithEdge <= 3) {
chatData += ` and is no longer Shaken but cannot act this turn.`;
token.actor.update({ "data.status.isShaken": false });
useBenny();
} else if (rollWithEdge >= 4) {
chatData += `, is no longer Shaken and may act normally.`;
token.actor.update({ "data.status.isShaken": false });
let bennies;
let bv;

async function rollUnshake() {

const edgeNames = ['combat reflexes', 'demon', 'undead', 'construct', 'undead (harrowed)'];
const actorAlias = speaker.alias;
// ROLL SPIRIT AND CHECK COMBAT REFLEXES
const r = await token.actor.rollAttribute('spirit');
const edges = token.actor.data.items.filter(function (item) {
return edgeNames.includes(item.name.toLowerCase()) && item.type === "edge";
});
let rollWithEdge = r.total;
let edgeText = "";
for (let edge of edges) {
rollWithEdge += 2;
edgeText = `<br/><i>+ ${edge.name}</i>`;
}

let chatData = `${actorAlias} rolled <span style="font-size:150%"> ${rollWithEdge} </span>`;
// Checking for a Critical Failure.
if (isSame_bool(r.dice) && isSame_numb(r.dice) === 1) {
ui.notifications.notify("You've rolled a Critical Failure!");
let chatData = `${actorAlias} rolled a <span style="font-size:150%"> Critical Failure! </span>`;
ChatMessage.create({ content: chatData });
}
else {
if (rollWithEdge <= 3) {
chatData += ` and is no longer Shaken but cannot act this turn.`;
token.actor.update({ "data.status.isShaken": false });
useBenny();
} else if (rollWithEdge >= 4) {
chatData += `, is no longer Shaken and may act normally.`;
token.actor.update({ "data.status.isShaken": false });
}
chatData += ` ${edgeText}`;
}
chatData += ` ${edgeText}`;
ChatMessage.create({ content: chatData });
}
ChatMessage.create({ content: chatData });
}

// Functions to determine a critical failure. This one checks if all dice rolls are the same.
function isSame_bool(d = []) {
return d.reduce((c, a, i) => {
if (i === 0) return true;
return c && a.total === d[i - 1].total;
}, true);
}
// Functions to determine a critical failure. This one checks if all dice rolls are the same.
function isSame_bool(d = []) {
return d.reduce((c, a, i) => {
if (i === 0) return true;
return c && a.total === d[i - 1].total;
}, true);
}

// Functions to determine a critical failure. This one checks what the number of the "same" was.
function isSame_numb(d = []) {
return d.reduce((c, a, i) => {
if (i === 0 || d[i - 1].total === a.total) return a.total;
return null;
}, 0);
}
// Functions to determine a critical failure. This one checks what the number of the "same" was.
function isSame_numb(d = []) {
return d.reduce((c, a, i) => {
if (i === 0 || d[i - 1].total === a.total) return a.total;
return null;
}, 0);
}

function useBenny() {
bv = checkBennies();
if (bv > 0) {
new Dialog({
title: 'Spend a Benny?',
content: `Do you want to spend a Benny to act immediately? (You have ${bv} Bennies left.)`,
buttons: {
one: {
label: "Yes.",
callback: (html) => {
spendBenny();
token.actor.update({ "data.status.isShaken": false });
function useBenny() {
bv = checkBennies();
if (bv > 0) {
new Dialog({
title: 'Spend a Benny?',
content: `Do you want to spend a Benny to act immediately? (You have ${bv} Bennies left.)`,
buttons: {
one: {
label: "Yes.",
callback: (html) => {
spendBenny();
token.actor.update({ "data.status.isShaken": false });
}
},
two: {
label: "No.",
callback: (html) => { return; },
}
},
two: {
label: "No.",
callback: (html) => { return; },
}
},
default: "No."
}).render(true)
}
else {
return;
default: "No."
}).render(true)
}
else {
return;
}
}
}

// Check for Bennies
function checkBennies() {
bennies = token.actor.data.data.bennies.value;
// Check for Bennies
function checkBennies() {
bennies = token.actor.data.data.bennies.value;

// Non GM token has <1 bennie OR GM user AND selected token has <1 benny
if ((!game.user.isGM && bennies < 1) || (game.user.isGM && bennies < 1 && game.user.getFlag("swade", "bennies") < 1)) {
ui.notifications.error("You have no more bennies left.");
}
if (game.user.isGM) {
bv = bennies + game.user.getFlag("swade", "bennies");
}
else {
bv = bennies;
// Non GM token has <1 bennie OR GM user AND selected token has <1 benny
if ((!game.user.isGM && bennies < 1) || (game.user.isGM && bennies < 1 && game.user.getFlag("swade", "bennies") < 1)) {
ui.notifications.error("You have no more bennies left.");
}
if (game.user.isGM) {
bv = bennies + game.user.getFlag("swade", "bennies");
}
else {
bv = bennies;
}
return bv;
}
return bv;
}

// Spend Benny function
async function spendBenny() {
bennies = token.actor.data.data.bennies.value;
//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.
if (game.user.isGM && bennies < 1) {
game.user.setFlag("swade", "bennies", game.user.getFlag("swade", "bennies") - 1)
} else {
token.actor.update({
"data.bennies.value": bennies - 1,
})
}
// Spend Benny function
async function spendBenny() {
bennies = token.actor.data.data.bennies.value;
//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.
if (game.user.isGM && bennies < 1) {
game.user.setFlag("swade", "bennies", game.user.getFlag("swade", "bennies") - 1)
} else {
token.actor.update({
"data.bennies.value": bennies - 1,
})
}

//Show the Benny Flip
if (game.dice3d) {
game.dice3d.showForRoll(new Roll("1dB").roll(), game.user, true, null, false);
}
//Show the Benny Flip
if (game.dice3d) {
game.dice3d.showForRoll(new Roll("1dB").roll(), game.user, true, null, false);
}

//Chat Message to let the everyone know a benny was spent
ChatMessage.create({
user: game.user._id,
content: `<p><img src="${bennyImage}"" width="25" height="25" /> ${game.user.name} spent a Benny and ${token.name} may act normally now.</p>`,
});
}
//Chat Message to let the everyone know a benny was spent
ChatMessage.create({
user: game.user._id,
content: `<p><img src="${bennyImage}"" width="25" height="25" /> ${game.user.name} spent a Benny and ${token.name} may act normally now.</p>`,
});
}

if (token.actor.data.data.status.isShaken === true) {
rollUnshake()
} else if (token) {
token.actor.update({ "data.status.isShaken": true })
if (shakenSFX) {
AudioHelper.play({ src: `${shakenSFX}` }, true);
if (token.actor.data.data.status.isShaken === true) {
rollUnshake()
} else if (token) {
token.actor.update({ "data.status.isShaken": true })
if (shakenSFX) {
AudioHelper.play({ src: `${shakenSFX}` }, true);
}
}
}
/// v.3.0.0 Original code by Shteff, altered by Forien and SalieriC#8263, thanks to Spacemandev for the help as well. Fixed by hirumatto.
/// v.3.0.1 Original code by Shteff, altered by Forien and SalieriC#8263, thanks to Spacemandev for the help as well. Fixed by hirumatto.
Loading

0 comments on commit f5f6a28

Please sign in to comment.