Roast my code! It's actually a cobbled mess of other people's code (Kurt, GM Goss and Will M) but it works! I have used SimpleSOund in this script not realising I could have just been using the --a function (will have to add your own sound if you use this)... Here's a working explodey ThunderWave that moves any tokens that fail their save 10ft. I haven't tested properly with resistant/immune edge cases. Feel free to fix or make it more elegant... !script {{
--/|GM Goss' Thunder Wave script based on Kurt's Burning Hands script.
--/|Have tacked on Will M.'s Shove script mechanics to make ThunderWave work as intended (I think)
--/|YMMV
--&spellName|Thunderwave
--/|Note: because the spell will be cast at a min level of 1, we use 2 here to get the total dice (2+1=3)
--&spellL0DamageDice|1
--/|Prompt for the spell level. Update this to account for the minimum level of the spell
--=SpellLevel|?{Spell Slot Level?|1|2|3|4|5|6|7|8|9}
--&spellDamageDieType|8
--&damageType|thunder
--&saveType|constitution
--/|Source Token is the caster, and target token is the "end of the line". Can be a non-creature (no represents) token.
--#sourcetoken|@{selected|token_id}
-->GetAndCheckSlotInformation|
-->DeductSpellSlot|
--/|Get all of the tokens on the page so we can cache their positions
--~|array;pagetokens;alltokens;@{selected|token_id}
--#leftsub|Save DC @{selected|spell_save_dc}
--#rightsub|Slot Level: [$SpellLevel]
--#title|[&spellName]
--/|Calculate damage based on spell slot.
--=DamageDice|[$SpellLevel.Total] + [&spellL0DamageDice]
--=Damage|[$DamageDice.Total]d[&spellDamageDieType]
--=HalfDamage|[$Damage.Total] \ 2
--=DoubleDamage|[$Damage.Total] * 2
--=QuarterDamage|[$Damage.Total] \ 4
--/|Since we want to be able to hover over a roll and see the dice details, output the rolled damage at the
--/|top of the card. If all critters make their save, the half damage roll won't contain the details.
--+|[c][b]Damage Roll: [/b][$Damage][/c]
--+| 
--/|Create an array to hold the tokens that are intersected by the line.
--~|array;define;tokensHit;
-->createTokenLookup|
--/|Prompt for a direction for hte cast, and define arrays that indicate the relative spaces that will be hit.
--&direction|?{Direction|⬆ (UP),U|↗ (UP-RIGHT),UR|➡ (RIGHT),R|↘ (DOWN-RIGHT),DR|⬇ (DOWN),D|↙ (DOWN-LEFT),DL|⬅ (LEFT),L|&#x2196 (UP-LEFT);,UL|}
--~|array;define;U;0,-1;0,-2;0,-3;-1,-1;-1,-2;-1,-3;1,-1;1,-2;1,-3
--~|array;define;D;0,1;0,2;0,3;-1,1;-1,2;-1,3;1,1;1,2;1,3
--~|array;define;R;1,0;2,0;3,0;1,-1;2,-1;3,-1;1,1;2,1;3,1
--~|array;define;L;-1,0;-2,0-;-3,0;-1,-1;-2,-1;-3,-1;-1,1;-2,1;-3,1
--~|array;define;UR;1,-1;1,-2;1,-3;2,-1;2,-2;2,-3;3,-1;3,-2;3,-3
--~|array;define;DR;1,1;1,2;1,3;2,1;2,2;2,3;3,1;3,2;3,3
--~|array;define;UL;-1,-1;-1,-2;-1,-3;-2,-1;-2,-2;-2,-3;-3,-1;-3,-2;-3,-3
--~|array;define;DL;-1,1;-1,2;-1,3;-2,1;-2,2;-2,3;-3,1;-3,2;-3,3
--/|Call the "checkForTokenHits" subroutine. The passed parameter will be the per-space VFX. Leave blank for none.
-->checkForTokenHits|burn-smoke
--@splay| ThunderWave
--/|The first item in the array will be a blank dummy item, so remove it.
--~|array;removeat;tokensHit;0
--/|Loop through the tokensHit tokens and roll saves for each one and apply damage
--~tokenid|array;getfirst;tokensHit
--?[&tokenid] -eq ArrayError|endOutput
--:loopDisplay|
--=SaveRoll|1d20 + [*[&tokenid]:[&saveType]_save_bonus]
--/|Compare the save roll to the save DC and either apply full or half damage
--?"[*[&tokenid]:npc_immunities]" -inc "[&damageType]"|Immune
--?"[*[&tokenid]:npc_resistances]" -inc "[&damageType]"|Resistant
--?"[*[&tokenid]:npc_vulnerabilities]" -inc "[&damageType]"|Vulnerable
--?[$SaveRoll.Total] -lt @{selected|spell_save_dc}|>ApplyDamageTokenmod;[&tokenid];1;-[$Damage.Total]|>ApplyDamageTokenmod;[&tokenid];1;-[$HalfDamage.Total]
--?[$SaveRoll.Total] -ge @{selected|spell_save_dc}|madeSave
--/|Here are various damage applications if the creature is immune, resistant, or vulnerable. In some cases, we will reuse output lines,
--/|for example, a resistant creature that fails its save will jump to "madeSave", since that is the correct damage amount. (half), while
--/|a vulnerable creature that makes its save will jump to "FailedSave" since that will be normal damage.
--/|Output a line for a failed saving throw (we will also jump here for a vulnerable creature that MAKES its save)
--:FailedSave|
--+[*[&tokenid]:t-name]:|Save [$SaveRoll] [r][$Damage] [&damageType][/r]
-->MOVE_TOKEN|@{selected|token_id};[&tokenid];2
--^afterSave|
--:Immune|
--+[*[&tokenid]:t-name]:|is not affected by the spell!
--^afterSave|
--:Resistant|
--?[$SaveRoll.Total] -lt @{selected|spell_save_dc}|>ApplyDamageTokenmod;[&tokenid];1;-[$HalfDamage.Total]|>ApplyDamageTokenmod;[&tokenid];1;-[$QuarterDamage.Total]
--?[$SaveRoll.Total] -lt @{selected|spell_save_dc}|madeSave
--+[*[&tokenid]:t-name]:|Save [$SaveRoll] [r][$QuarterDamage] [&damageType][/r]
--^afterSave|
--:Vulnerable|
--?[$SaveRoll.Total] -lt @{selected|spell_save_dc}|>ApplyDamageTokenmod;[&tokenid];1;-[$DoubleDamage.Total]|>ApplyDamageTokenmod;[&tokenid];1;-[$Damage.Total]
--?[$SaveRoll.Total] -ge @{selected|spell_save_dc}|FailedSave
--+[*[&tokenid]:t-name]:|Save [$SaveRoll] [r][$DoubleDamage] [&damageType][/r]
--^afterSave|
--/|Output a line for a successful saving throw
--:madeSave|
--+[*[&tokenid]:t-name]:|Save [$SaveRoll] [r][$HalfDamage] [&damageType][/r]
--:afterSave|
--~tokenid|array;getnext;tokensHit
--?[&tokenid] -ne ArrayError|loopDisplay
--:endOutput|
--X|
--:ApplyDamageTokenmod|Parameters are tokenid;bar#;amount
--@token-mod|_ignore-selected _ids [%1%] _set bar[%2%]_value|[%3%]
--<|
--:ApplyDamageAlterbars|Parameters are tokenid;bar#;amount
--@alter|_target|[%1%] _bar|[%2%] _amount|-[%3%] _show|none
--<|
--:checkForTokenHits|parameter is vfx descriptor
--=baseX|[*S:t-left] - 1 \ 70
--=baseY|[*S:t-top] - 1 \ 70
--~offset|array;getfirst;?{Direction}
--:checkTokenLoop|
--?"[&offset]" -eq "ArrayError"|endCheckForTokenHits
--~split|string;split;,;[&offset]
--=thisX|[$baseX] + [&split1]
--=thisY|[$baseY] + [&split2]
--/|IF we passed a visual effect specifier in %1%, create a point VFX in the square
--?"X[%1%]X" -ne "XX" |[
--=VX|[$thisX] * 70 + 35
--=VY|[$thisY] * 70 + 35
--vpoint|[$VX] [$VY] [%1%]
--]|
-->checkTokens|[$thisX];[$thisY]
--~offset|array;getnext;?{Direction}
--^checkTokenLoop|
--:endCheckForTokenHits|
--<|
--/|Reads the variables created by createTokenLookup to find any tokens that occupy a given space.
--:checkTokens|x;y
--?"X[&tok[%1%]-[%2%]]X" -ne "XX"|[
--~split|string;split;!;[&tok[%1%]-[%2%]]
--%P|1;[$splitCount.Total];1
--=var|split[&P]
--?"X[&[$var.RollText]]X" -eq "XX"|%
--~exists|array;indexof;tokensHit;[&[$var.RollText]]
--?[&exists] -ne ArrayError|skipAdd
--?[&[$var.RollText]] -eq ArrayError|skipAdd
--~|array;add;tokensHit;[&[$var.RollText]]
--:skipAdd|
--%|
--<|
--/|Creates a series of string variables with names like 'tok12-15' which represent the various squares that are
--/|occupied by tokens on the page. Handle 1x1, 2x2, and 3x3 tokens by simply adding extra squares in the appropriate
--/|pattern around the center point location. The result is that if there are two tokens at 12,15, the tok12-15 string
--/|will contain something like "-asln34njfn2nafd!-sdnfklaserfs" (IDs separated by exclamation points) that we can use to
--/|quickly evaluate hits by essentially asking "what is in square 12x15" by just reading the variable.
--:createTokenLookup|
--~tokenid|array;getfirst;alltokens
--:tokenSetupLoop|
--?[&tokenid] -eq ArrayError|endSetupLoop
--=tLeft|[*[&tokenid]:t-left] - 1 \ 70
--=tTop|[*[&tokenid]:t-top] - 1 \ 70
--=tWidth|[*[&tokenid]:t-width]
--?[$tWidth] -eq 70|[
--&tok[$tLeft]-[$tTop]|+[&tokenid]!
--/+[*[&tokenid]:character_name]|At [$tLeft],[$tTop] : [&tok[$tLeft.Total]-[$tTop.Total]]
--]|
--?[$tWidth] -eq 140|[
--=NX|[$tLeft] + 1
--=NY|[$tTop] + 1
--&tok[$tLeft]-[$tTop]|+[&tokenid]!
--&tok[$tLeft]-[$NY]|+[&tokenid]!
--&tok[$NX]-[$tTop]|+[&tokenid]!
--&tok[$NX]-[$NY]|+[&tokenid]!
--]|
--?[$tWidth] -eq 210|[
--=PX|[$tLeft] - 1
--=PY|[$tTop] - 1
--=NX|[$tLeft] + 1
--=NY|[$tTop] + 1
--&tok[$tLeft]-[$tTop]|+[&tokenid]!
--&tok[$PX]-[$PY]|+[&tokenid]!
--&tok[$tLeft]-[$PY]|+[&tokenid]!
--&tok[$NX]-[$PY]|+[&tokenid]!
--&tok[$PX]-[$tTop]|+[&tokenid]!
--&tok[$NX]-[$tTop]|+[&tokenid]!
--&tok[$PX]-[$NY]|+[&tokenid]!
--&tok[$tLeft]-[$NY]|+[&tokenid]!
--&tok[$NX]-[$NY]|+[&tokenid]!
--]|
--~tokenid|array;getnext;alltokens
--^tokenSetupLoop|
--:endSetupLoop|
--<|
--:MOVE_TOKEN|STokenid, TTokenId, Units
--&TId1|[%1%]
--&TId2|[%2%]
--&Units|[%3%]
--/| Angle function returns a string variable (&) with lots of decimal points.
--~A|math;angle;[&TId1];[&TId2]
--/| I've found that TokenMod chokes on a movement angle with lots of decimal points, so rounding is needed
--/|$Ar will now contain a rounded integer version of &A
--~Ar|math;round;[&A]
--/|&Angle will end up being one of the common directions which may work better for grids
--?[$Ar] -gt 337.5 -or [$Ar] -le 22.5|&Angle;0
--?[$Ar] -gt 22.5 -and [$Ar] -le 67.5|&Angle;45
--?[$Ar] -gt 67.5 -and [$Ar] -le 112.5|&Angle;90
--?[$Ar] -gt 112.5 -and [$Ar] -le 157.5|&Angle;135
--?[$Ar] -gt 157.5 -and [$Ar] -le 202.5|&Angle;180
--?[$Ar] -gt 202.5 -and [$Ar] -le 247.5|&Angle;225
--?[$Ar] -gt 247.5 -and [$Ar] -le 292.5|&Angle;270
--?[$Ar] -gt 292.5 -and [$Ar] -le 337.5|&Angle;315
--/| Added for dealing with a SC 1.39a bug (returns negative angles beteen 270 and 360)
--?[$Ar] -gt -90 -and [$Ar] -le -67.5|&Angle;270
--?[$Ar] -gt -67.5 -and [$Ar] -le -22.5|&Angle;315
--?[$Ar] -gt -22.5 -and [$Ar] -le 0|&Angle;0
--&UnitsFactor|1
--?[&Angle] -eq 45|&UnitsFactor;1.4142
--?[&Angle] -eq 135|&UnitsFactor;1.4142
--?[&Angle] -eq 225|&UnitsFactor;1.4142
--?[&Angle] -eq 315|&UnitsFactor;1.4142
--=U|[&Units] * [&UnitsFactor]
--/|*Angles|[&Angle] ([&A]) ([$Ar])
--/| Time to call the magic TokenMod function to make the target token move across the screen
--@token-mod|_move =[&Angle]|[$U.Raw]g _ids [&TId2] _ignore-selected
--<|
--:GetAndCheckSlotInformation|
--=SlotLevel|[$SpellLevel]
--=SlotsTotal|[*S:lvl[$SlotLevel]_slots_total]
--=SlotsExpended|[*S:lvl[$SlotLevel]_slots_expended]
--?[$SlotsExpended.Total] -lt 0|NoSlotsLeft
--?[$SlotsExpended.Total] -eq [$SlotsTotal.Total]|NoSlotsLeft
--/|@roll20AM|_audio,play,nomenu|Fire Blast
--<|
--:DeductSpellSlot|
--=SlotsExpended|[$SlotsExpended] +1
--@setattr|_charid [*S:character_id] _lvl[$SlotLevel]_slots_expended|[$SlotsExpended] _silent
--=SlotsRemaining|[$SlotsTotal] - [$SlotsExpended]
--+|[c][b]Level [$SpellLevel.Total] Spell Slots Left: [/b][$SlotsRemaining][/c]
--<|
--X|
--:NoSlotsLeft|
--+|[b][*S:character_name] has no level [$SlotLevel.Total] spell slots available.[/b]
--X|NoSlotsLeftStop
}}