• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Custom Multishot v2.2c + Orbs add-on v1.3.1

Custom Multishot is a fully triggered Multishot.

Caution:
1) If you want a unit to use Multishot, but you want it to get it later on in the game - give it the Multishot initially, but
set the taegets allowed to 1.
When the time comes - increase the amount of targets to whatever you want.

2)Don't use the "advanced version", unless you really have the "Magic immune units resist ultimates" set to "true", or you aren't using an "ultimate" as a missile skill (hero skill, requiring 6lvl), or you haven't set the "ALWAYS_USE_ADVANCED" to true.

3) If you are going to use a dummy as a shooter - set udg_MS_Source to the unit you want to see as "shooter" upon Multishot hit.
Also don't make 1 dummy cast Multishot too fast. You need to wait for the 1-st Multishot to end until you make it cast Multishot again, cuz the 2-nd one will overwrite the 1-st and it will get messy.
I strongly recommend using MS only 1-ce per dummy (unless you keep track of how many units have been hit by the Multishot, and allow it to cast it again only when all the units have been hit).


Version 2.2:
1) Restructured the Multishot. Got rid of some useless parts, split it into several shorted functions (for easier reading), moved the DamProperties struct and TableClear functions into another library, so "ExecuteFunc" is no longer used.
2) Added a boolean, telling you if the damage is dealt by Multishot or not, so now you can properly block damage trough Damage Engine, and no longer need to use the in-built damage block.
3) Now the original damage source deals the damage to the unit, instead of the dummy (thanks to (2)).
4) Fixed several bugs + some other improvements.
5) Multishot now counts all its "chains" too. If you call Multishot, after a unit has been hit by Multishot missile - the MS_UnitsInGroup counts the total amount of units hit by this "chain", instead of counting only the last bunch.

B) Fixed bug (when alwaysHitMainTarget was set to false - main target was never being hit); Updated the sample triggers; Made the offset angle use deg again (instead of rads).

C) Fixed a bug where units get one-shotted by units by Multishot, if they have Multishot themselves.

Version 2.1.0.4:
- Improved the damage blocking a bit. Now units don't get healed to full HP upon taking "enchanted" damage.

Version 2.1.0.3:
- Prevented a possible crash when shooting a enemy, which has acid bomb debuff.

Version 2.1.0.2:
- Now udg_MS_Current_Group, udg_MS_UnitsInGroup and udg_MS_Dummy are set in the end of Multishot.
Thanks to that, spells like "Chain Reaction" (a demo spell) are now possible, since you "know" how many units have been shot, and WHICH units have been shot :p
(it's a small update, but it really opens up a lot of possibilities)

Version 2.1.0.1:
1) Changed the WANT_DAMAGE_BLOCK check to be a static if
2) Changed init_range to initRange
3) I put the debug actions into a static if block (instead of writing "debug" on every row)
4) Changed the UnitDamageTargetBJ to UnitDamageTarget
5) Changed the IsUnitAliveBJ(secondaryTarget) to not IsUnitType(secondaryTarget, UNIT_TYPE_DEAD)
6) Stopped nullifiying arguments, taken by the functions.

Version 2.1:
1) Added the "old" version of Multishot as 2 more functions. "MultishotBasic" and "MultishotBasicAdvanced". They act like MultishotTarget, but don't require a boolexpr to work (they are lighter, and easier to use (especially for GUI users)). They are pre-set to target only enemies.
2) Changed the way MultishotAdvanced was calculating the coordinates of the missile - made it a bit lighter.
3) Made the dummy disapear (and clearing its struct + table) as soon as all of its targets have been hit, instead of having to wait 'DUMMY_LIFE_TIME' till that happens. Now 'DUMMY_LIFE_TIME' is 'MAX_DUMMY_LIFE_TIME', and can be set to bigger values with no concequences.
4) If the shooter is a dummy - the same DamProperties struct is re-used if it casts more than one Multishot.
5) Some other minor changes in the code like:
- replaced some 'ifs' with 'static ifs'
- started saving the dummy's id into the timer's hashtable, instead of the dummy itself.
- the table clearing timer is now part of the struct, instead of locally declared (so I can destroy it when all the targets have been hit).

Version 2.0.2:
- Added a configuration to the Advanced library "ALWAYS_USE_ADVANCED", which makes the Advanced multishot functions always use the advanced version's timer, which allowes the Multishot to be used, even if you don't have a DDS on the map.

Version 2.0.1:
1) Made the advanced version more accurate - now it doesn't start a one-shot timer with fixed amount of time (the distance, divided by the speed), it starts a looping timer (for all the multishot advanced instances), and it periodically checks the distance from the missile towards its target.
2) Cleared a refference leak (from the boolexpr).
3) Improved a bit the parabola, shown in debug mode.
4) Now if the Advanced Multishot is called, when the add-on library isn't there - normal multishot will be called, instead of 'nothing'.

Version 2:
1) I split the simple from the advanced version. Now the advanced one is an optional add-on library.
2) Fixed bug (which appeared in v1.9) - the Multishot wasn't working properly if WANT_DAMAGE_BLOCK was set to false.
3) Combined the 2 textmacros into 1, taking more arguments.
4) Now getting the base damage is seperated in another function (had to do that to simplify the textmacro a bit)
5) ADDED A BOOLEXPR CONFIGURATION! Now you can make the Multishot target anything you want (not only enemies).
6) Updated the documentation.

Version 1.9:
1) Now, when a dummy unit is set as the shooter - no dummy is created, and as source of the Multishot is considered the current value of udg_MS_Source.
2) I split the Multishot working function (textmacro) into 2 textmacros - 1 when targeting a unit, and 1 when targeting a point, and each has simple and advanced version.
Now the point targeting version doesn't do unneeded things.
3) I removed the Multishot function. Now there are 4 functions: MultishotTarget, MultishotPoint, MultishotTargetAdvanced and MultishotPointAdvanced.
Thanks to that, instead of one function, taking 17 arguments, there are 4 functions, taking 11-13 arguments each (and you just call the one you need).
4) Added another configuration boolean (in the trigger, not function) - "WANT_DAMAGE_BLOCK". When set to true - the Multishot will do the damage blocking by itself. Set it to false only if you have a more advanced DDS, and you want to block the damage by yourself (I haven't tested it for issues if done that way).

Version 1.8.4.2:
- If you input a number higher than 1000 as targets allowed - it will automatically be set to 0 (since it will hit all of the unit its allowed to anyways, but calling it with 0 is lighter than calling it with a limited amount of targets).

Version 1.8.4.1:
1) Instead of calculating (targets - count) every time - I simply set targets to targets - count before the loop.
2) Now if the user has put some huge number (like 3000) as targets allowed - it will no longer do the full loop (from 1 to 3000). Now it will loop the "left" units, and then jump straight to the "right" units.

Version 1.8.4:
1) Completely changed the way it picks its targets when it has a limit how many units to hit.
- Before it was putting all the units into 2 arrays (left and right), and then it was looping trought the arrays to hit half of the units from 1 of the arrays, and the other half - from the other; it was starting from the smaller array.
Now it's putting all the units into one array, and it fills half of it with units from "left", and the other half - with units from "right". If there aren't enough units in right - it will put units from "left" in their spots. But if later on in the loop - "right" units appear - they will have privilege over the spots, so they will take them, thus it's balancing itself.
- Now the code executed is a lot shorter (when calling Multishot, with limited amount of targets).
- Now it has a chance to hit either units from "left", either from "right" when attacking "static" (not moving) targets (and the left and right groups are always the same), while before - if targets allowed was set to 1 - it would've always been hitting either left, either right, without a chance to hit the other side (it was hitting the array with less units in it).

2) Changed the textmacro of Multishot to use $FUNC$ instead of $PART_ONE$, $PART_TWO$ and $PART_THREE$, by changing the other "Shoot" takes its variables in.

Version 1.8.3:
1) Made the damage type configurable, even if the damage is based on the unit's normal attack.
2) Changed the way "debug" mode displays the parabola (it looks better now, in my opinion).
3) Started making "Orb" bonus (add-on) library v1. Will try to finish it with the next update.

B) Greatly improved the add-on library (still incomplete) - Improved efficiency, and added the Orb of Darkness Effect.

Version 1.8.2.2:
1) Added a preview of the parabola, while in debug mode.
2) Fixed a bug of the sample map (nothing was wrong with the multishot, just 1 of the DEMO triggers wasn't 100% correct) - when medusa was shooting enemies while she had no orb effect - Multishot wasn't runing (I had forgotten to put the "set i = i + 1")

Version 1.8.2.1:
1) Fixed a minor bug with the "On Hit" event not runing the 1-st time Multishot is used on the map. (1-st setting the Event variable to 1, and then to 0, instead of 1-st to 0 and then to 1)
2) Made the damage block function run only if the multishot's damage isn't fixed.
This makes the trigger lighter!
BUT it also doesn't let you use "fixed" damage values when you use "On Damage" for Multishot Event.
You'd either need to use a more advanced DDS, that would do the damage blocking in the calling trigger, either you'd need to "fix" the unit's damage in the object editor.
However, this change was applied, so useless actions (damage blocking) isn't done when a unit has taken no damage :p

B) updated the sample map, showing how you can simulate orb effects (added only Orb of Frost's effect, but I think it's enough to get the idea how to do the rest :p)

C) fixed a minor miscalculation in the orb example - when an orb was being sold - a copy of it was appearing on the ground.
- Added another orb effect (example), and an example how to make the 1-st orb in inventory be the orb effect used.

Version 1.8.2:
1) Removed the "main target takes" and the "other targets take" configurations.
2) Added udg_MS_Damage, and udg_MS_UnitsInGroup variables for the "On Hit" event.
3) Started Dealing the Multishot damage after the "On Hit" event, instead of before that.
4) Now the damage dealt by multishot can be configured inside the "On Hit" event (by setting the udg_MS_Damage variable).

Version 1.8.1:
1) Made the damage type configurable
2) Made the missile speed configuration seperate for every instance of multishot (instead of having a constant value for all the multishots).
3) Made the "Advanced version" also configurable inside the function.
4) Made a point-targeted version
5) Changed the attack type from integer to attacktype variable.
(now the multishot function takes in total 19 arguments)
6) Fixed a bug, which has appeared in version 1.7 - if there were 0 targets left or right from the main target, and there was a limit to how many targets it can shoot at once - it wasn't hitting anything :p

Version 1.8:
1) COMPLETELY DIFFERENT way to use Multishot. Now you make the triggers for it by yourself, and just call a function for it to happen.
All the configurations are done within this function
2) Due to 1, now people can make spells, using multishot (it's not used ONLY for 'normal' multishot purposes).
3) Added 1 more variable - 'MS_Missile' to the 'On hit' event. To use it, you need custom scripts tho.
4) Now changing the DDS it uses is extremely simple - you just change 4 variable names in one textmacro.

Versiont 1.7.2:
1) Greatly improved the "Multishot hit" event - now it registeres:
- the shooter
- the target (this one was there before as well)
- the dummy
- unit group, containing all of the units, shot by 'this' multishot
- the main target of the shot.
This improves its functionality 100's of times (from what it used to be)
2) Now the damage dealt to the main target, and to the 'other' targets is also configurable. You can make the main target take 100% damage, and the rest - 70%. Or you can make all take 70% (or whatever % you want)
3) Made it configurable if the player wants the "on multishot hit" event to happen, or not.
4) Made the dummy's spawn time configurable
5) Few other syntax improvements + a little bug fix (was related to the on hit event).

Version 1.7.1:
- Combined the "Simple" and the "Advanced" version of the system into one (by a textmacro). Now you only need to set one constant boolean to chose which version to use :)

Version 1.7.0.1:
1) Made the Multishot functions private
2) Fixed bug - wasn't destroying a struct if the target wasn't magic immune.
3) Readability improvements.

Version 1.7 (1-st release in the spells section):
1) Re-configured the Multishot to be in vJASS
- Merged the configurations trigger with the main trigger.
- Started saving structs with several members into the hashtables, instead of everything into the hashtable.
- Made it configurable differently for different players.
2) Now if a unit has more than 1 immunity (Which I find extremely pointless)- they all get returned, instead of only 1 of them.

Version 1.3.1:
- Fixed a "bug" - dummies were getting summoned all the time, even if the Multishot missile wasn't an Orb one.

Version 1.3:
1) I no longer create and destroy triggers dynamically. I simply use the DDS for that purpose.
2) Before if the target was killed by the multishot - the dummy wasn't getting destroyed, and neither was the trigger.
3) Added Mask of Death to the triggered orb effects.

- Added another example function in the MultishoRts - "MultishotOrb". It pretty much does what's needed for a unit to be able to use Orbs with Multishot.

Version 1.2.1:
1) Improved the add-on library a bit (made it shorter, and more readable)
2) Added documentation to the add-on library.

Version 1.2:
1) Finished the orb add-on library
2) Completely changed the way it works - now, instead of triggering all of the orb effects, a "special" dummy is created, it's given the orb ability, and it hits the target unit (then it's removed after 0.01 seconds)
Now it stacks with normal orbs, and it doesn't mess up some "normal" skills.

Version 1.1.1:
1) Added more orb effects to the bonus library (only orb that's not working at the moment is orb of venom - I am trying to think of a better way how to make it work without fully triggering the damage)
2) Fixed some minor things (mostly in the object editor), related to the add-on library.

Version 1.1:
1) Made the add-on library (which still isn't 100% complete) use Unit arrays instead of unit groups, so now it functions properly, even if units are removed from the game.
2) Made the Orb of Darkness removal check 10 times lighter than before (I was getting some lag before, so I improved it) - now it doesn't do 12 checks for each unit, every 0.1 seconds, it simply does as many checks as it needs to for each unit.

Version 1 - Started working on it in Multishot v1.8.3


The library:
JASS:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------- CUSTOM MULTISHOT BY WereElf ------------------------------------------ //
// This is simply a Multishot, which is a lot better than the Barrage based one.                                    //
// The differences between this Multishot and the Barrage based one are:                                            //
//                                                                                                                  //
// 1) This multishot can be configured to hit only enemies infront of the shooter, while Barrage based can only     //
// target ALL the enemies in range (certain amount of them).                                                        //
//                                                                                                                  //
// 2) This multishot works with Orb effects (if you use the "Orbs" add-on library.                                  //
//                                                                                                                  //
// 3) You can make your own "orb effects" when you use this Multishot, since you get a "On Hit" event.              //
//                                                                                                                  //
// 4) You can set the targets allowed to 1 and 2, while for the Barrage based one - the minimum is 3.               //
//                                                                                                                  //
// 5) You can use this multishot for SPELLS!                                                                        //
//                                                                                                                  //
// 6) You can put some weird configurations for this multishot (like - hit allied buildings, and enemy units).      //
//                                                                                                                  //
// I could keep on listing the possibillities, offered by this library, but I wouldn't. I pointed out the most      //
// important ones.                                                                                                  //
//                                                                                                                  //
// HOW TO MAKE IT WORK:                                                                                             //
//                                                                                                                  //
// 0.a) You need a damage detecting system on your map. The multishot is built, using 1 of the most basic DDS(es),  //
// so it will work with any DDS. However, if you are using a different one - you need to go to the bottom of this   //
// library, and change the variables in the last textmacro to the ones, used by the other DDS.                      //
//                                                                                                                  //
// 0.b) You need to have a dummy on your map.                                                                       //
//                                                                                                                  //
// 1) Make a custom buff on your map, which will be used ONLY by the Multishot skills.                              //
//                                                                                                                  //
// 2) Make as many as you need skills, based on Acid Bomb, make them cost 0 mana, deal 0 damage, and make them      //
// APPLY THE BUFF YOU MADE IN STEP (1).                                                                             //
//                                                                                                                  //
// 3) Scroll down to the globals block of this library, and set the variables to "your" values.                     //
//                                                                                                                  //
// 4) When you want to make a trigger, using Multishot, just call "MultishotTarget" or "MultishotPoint", according  //
// to your needs.                                                                                                   //
//                                                                                                                  //
// 5) You could make a library with some pre-set Multishot functions (like MultishoRt in the sample map), so you    //
// don't have to put all the arguments when you call Multishot, but simply the ones you need.                       //
//                                                                                                                  //
// If you have the AdvancedMultishot library (add-on), you can also use "MultishotTargetAdvanced" and               //
// "MultishotPointAdvanced". The advanced functions are able to hit magic immune enemies, even if you have "Magic   //
// immune units resist ultimates" set to true.                                                                      //
//                                                                                                                  //
// THE ARGUMENTS THE FUNCTIONS TAKE:                                                                                //
//                                                                                                                  //
// MultishotTarg (both normal and the advanced) take:                                                               //
// 1 (unit) - Shooter - who is shooting                                                                             //
// 2 (unit) - Target - who is the original target of the shot                                                       //
// 3 (real) - Damage - the damage the shot should deal                                                              //
// 4 (integer) - Targets - how many targets its allowed to shoot at once                                            //
// 5 (real) - Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want //
// ALL the units in range to get shot                                                                               //
// 6 (real) - Range - the range multishot chooses enemies from.                                                     //
// 7 (boolean) - Always hits main targer - does the main target always get shot, or it only has a chance to get     //
// shot (if more than the max amount of units are in range)                                                         //
// 8 (attacktype) - Attack type - the attack type of the shooter. Its used for correct armor estimation, and thats  //
// the type of damage dealt in the end.                                                                             //
// 9 (integer) - Missile ability - the ability id of the Multishots missile.                                        //
// 10 (real) - Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45,    //
// multishot will act like the target is at 90 degrees.                                                             //
// 11 (boolean) - Fixed damage - if you want to deal some fixed value of damage, not based on the damage dealt -    //
// set this to true, otherwise - false.                                                                             //
// 12 (damagetype) - Damage type - if "Fixed damage" is set to false - this will act as "DAMAGE_TYPE_NORMAL", no    //
// matter what. Otherwise - this is the damage type multishot deals.                                                //
// 13 (boolexpr) - Conditions - What units shall it target                                                          //
// NEXT ONE IS ONLY FOR THE ADVANCED VERSION                                                                        //
// 14 (real) - Missile speed - simply put-in the missiles speed                                                     //
//                                                                                                                  //
// MultishotPoint (both normal and the advanced) take:                                                              //
// 1 (unit) Caster - who is shooting                                                                                //
// 2 (real) Damage - the damage dealt                                                                               //
// 3 (integer) Targets - how many targets its allowed to shoot at once                                              //
// 4 (real) Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want   //
// ALL the units in range to get shot                                                                               //
// 5 (real) Range - the range multishot chooses enemies from                                                        //
// 6 (attacktype) Attack Type - the attack type of the damage dealt                                                 //
// 7 (integer) Missile ability - the ability id of the Multishots missile                                           //
// 8 (real) Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45,       //
// multishot will act like the target is at 90 degrees                                                              //
// 9 (damagetype) Damage type - the damage type of the damage dealth                                                //
// 10 (real) Target X - the x of the targeted point                                                                 //
// 11 (real) Target Y - the y of the targeted point                                                                 //
// 12 (boolexpr) - Conditions - What units shall it target                                                          //
// NEXT ONE IS ONLY FOR THE ADVANCED VERSION                                                                        //
// 13 (real) Missile speed - simply put-in the missiles speed                                                       //
//                                                                                                                  //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
library MultiMisc

    public struct DamProperties
        real amount
        attacktype aType
        damagetype dType
        unit source
        unit mainTarget
        group shotGroup
        integer missile
        integer unitsShot = 0
        boolean advanced
        integer alreadyShot = 0
        boolean preDummy
        unit dummy
        timer clearer

        static method create takes nothing returns DamProperties
            local DamProperties DP = DamProperties.allocate()
            set DP.shotGroup = CreateGroup()
            set DP.clearer = CreateTimer()
            set DP.mainTarget = null
            return DP
        endmethod

        method onDestroy takes nothing returns nothing
            call GroupClear(this.shotGroup)
            call DestroyGroup(this.shotGroup)
            call DestroyTimer(this.clearer)
        endmethod
    endstruct

    public function ClearLeaks takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer tid = GetHandleId(t)
        local integer id = LoadInteger(Multishot_Table, tid, 'dmid')
        local DamProperties damage = LoadInteger(Multishot_Table, id, 'damp')
        call UnitApplyTimedLife(damage.dummy, 'BTLF', 0.01)
        call DamProperties.destroy(damage)
        call FlushChildHashtable(Multishot_Table, id)
        call FlushChildHashtable(Multishot_Table, tid)
        set t = null
    endfunction

endlibrary

library Multishot initializer Init requires MultiMisc, optional AdvancedMultishot

// DEBUG_MULTISHOT displays how the parabola looks like
// WANT_DAMAGE_BLOCK is a boolean. If set to true - Multishot will automatically block the incoming damage. Set it to false only
// if you want to do the damage block by yourself in the Multishot calling function.
// HP_BONUS_ABILITY is the Item ability, giving 30000 HP to the target unit for the damage block, and for the 'armor test'
// BUFF_APPLIED_ID is the Id of the buff, applied by the Multishot.
// DUMMY_ID is the Id of your dummy unit.
// TEST_DAMAGE_AMOUNT is the amount of damage the target takes to estimate its armor. Higher values give more precise results, but they may instantly kill the target.
// SPELL_ORDER is the string of the order, issued to the dummy to make it cast the multishot arrow. You could change it if
// you are using another spell as base, but acidbomb is the only good spell for this purpose.
// DUMMY_LIFE_TIME is the time the dummy stays alive. Set this to an amount, enough for the multishot to hit all its targets.
// Don't touch the rest.
    globals
        private constant boolean DEBUG_MULTISHOT = false
        private constant boolean WANT_DAMAGE_BLOCK = false
        private constant integer HP_BONUS_ABILITY = 'A02P'
        private constant integer BUFF_APPLIED_ID = 'B005'
        private constant integer DUMMY_ID = 'h00C'
        private constant real MAX_COLLISION_SIZE = 196.00
        private constant real TEST_DAMAGE_AMOUNT = 15000.00
        public constant string SPELL_ORDER = "acidbomb"
        public constant boolean WANT_MULTISHOT_HIT_EVENT = true
        public constant real MAX_DUMMY_LIFE_TIME = 20.00 // higher values allow slower missiles.
        public boolean Damage = false
        public hashtable Table = InitHashtable()
        private group g = CreateGroup()
        private effect array debug_effect // used only when DEBUG_MULTISHOT is true
    endglobals

// This sctuct is used when blocking the original damage.
    private struct DamBlocker
        real health
        unit u

        static method create takes unit u returns DamBlocker
            local DamBlocker DB
            set DB = DamBlocker.allocate()
            set DB.u = u
            set DB.health = GetWidgetLife(u)
            return DB
        endmethod
  
        static method BlockDamage takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = LoadInteger(Table, GetHandleId(t), 'blcr')
            call UnitRemoveAbility(this.u, HP_BONUS_ABILITY)
            call SetWidgetLife(this.u, this.health)
            call thistype.destroy(this)
            call FlushChildHashtable(Table, GetHandleId(t))
            call DestroyTimer(t)
            set t = null
        endmethod
    endstruct

// Calculates the unit's base damage, by multiplaying the damage dealt by the target's Armor rating
    private function GetBaseDamage takes MultiMisc_DamProperties damage, real dam returns nothing
        local timer time
        local real max_hp
        local DamBlocker DB = DamBlocker.create(damage.mainTarget)
        if GetUnitAbilityLevel(damage.mainTarget, 'BNab') > 0 then
            call UnitRemoveAbility(damage.mainTarget, 'BNab')
        endif
        static if WANT_DAMAGE_BLOCK then
            if GetUnitAbilityLevel(DB.u, HP_BONUS_ABILITY) == 0 then
                set time = CreateTimer()
                call SaveInteger(Table, GetHandleId(time), 'blcr', DB)
                call TimerStart(time, 0.00, false, function DamBlocker.BlockDamage)
                set time = null
            endif
        endif
        call UnitAddAbility(damage.mainTarget, HP_BONUS_ABILITY)
        set max_hp = GetUnitState(damage.mainTarget, UNIT_STATE_MAX_LIFE)
        call SetWidgetLife(damage.mainTarget, max_hp)
        call UnitDamageTarget(damage.dummy, damage.mainTarget, TEST_DAMAGE_AMOUNT, true, false, damage.aType, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
        set damage.amount = (TEST_DAMAGE_AMOUNT/(max_hp - GetWidgetLife(damage.mainTarget)))*dam
        call SetWidgetLife(damage.mainTarget, max_hp)
        static if not WANT_DAMAGE_BLOCK then
            call UnitRemoveAbility(damage.mainTarget, HP_BONUS_ABILITY)
            call SetWidgetLife(damage.mainTarget, DB.health)
            call DamBlocker.destroy(DB)
        endif
    endfunction

    function DrawParabola takes real range, real arc, real x, real y, real tarx, real tary, real ofst returns nothing
        local real a
        local real b
        local real m = range*Sin(arc/2)
        local real parabola = (range-(range*Cos(arc/2 * bj_DEGTORAD)*Cos(arc/2 * bj_DEGTORAD)))/( 4.00 * Cos(arc/2 * bj_DEGTORAD))
        local integer count = 0
        local real tx = x
        local real ty = y
        local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(tary-y,tarx-x) + ofst*bj_DEGTORAD
        local real xFocus = x + parabola*Cos(init_ang)
        local real yFocus = y + parabola*Sin(init_ang)
        loop
            exitwhen count > 20
            set x = tx - Cos(init_ang)*parabola + Cos(init_ang + bj_PI/2)*(count - 10)*m/10
            set y = ty - Sin(init_ang)*parabola + Sin(init_ang + bj_PI/2)*(count - 10)*m/10
            set b = SquareRoot(4*parabola*parabola + (count - 10)*m*(count - 10)*m/100)
            set a = (init_ang + bj_PI/2) - Atan2(yFocus - y, xFocus - x)
            if a == bj_PI/2 then
                set a = parabola
            else
                set a = b*Cos(a)/Sin(2*a)
            endif
            set x = x + Cos(init_ang)*a
            set y = y + Sin(init_ang)*a
            call DestroyEffect(debug_effect[count])
            set debug_effect[count] = AddSpecialEffect("Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl", x, y)
            set count = count + 1
        endloop
    endfunction

    function ConfigureDamProp takes unit u, damagetype dType, attacktype aType, unit mainTarget, integer missile, boolean advanced returns MultiMisc_DamProperties
        local MultiMisc_DamProperties this
        if GetUnitTypeId(u) == DUMMY_ID then
            set this = LoadInteger(Table, GetHandleId(u), 'damp') + 0
            if this == 0 then
                set this = MultiMisc_DamProperties.create()
                call SaveInteger(Table, GetHandleId(u), 'damp', this)
            endif
            set this.dummy = u
            set this.source = udg_MS_Source
            set this.preDummy = true
        else
            set this = MultiMisc_DamProperties.create()
            set this.dummy = CreateUnit(GetOwningPlayer(u), DUMMY_ID, GetUnitX(u), GetUnitY(u), 0.00)
            call SaveInteger(Table, GetHandleId(this.dummy), 'damp', this)
            call UnitApplyTimedLife(this.dummy, 'BTLF', MAX_DUMMY_LIFE_TIME + 0.05)
            set this.source = u
            set this.preDummy = false
        endif
        call UnitAddAbility(this.dummy, missile)
        set this.dType = dType
        set this.aType = aType
        set this.mainTarget = mainTarget
        set this.missile = missile
        set this.advanced = advanced
        call SaveInteger(Table, GetHandleId(this.clearer), 'dmid', GetHandleId(this.dummy))
        call TimerStart(this.clearer, MAX_DUMMY_LIFE_TIME, false, function MultiMisc_ClearLeaks)
        return this
    endfunction

    function ShootAllTargets takes MultiMisc_DamProperties damage, real range, real arc, real tarx, real tary, real ofst, boolexpr condition, real speed returns nothing
        local real x = GetUnitX(damage.dummy)
        local real y = GetUnitY(damage.dummy)
        local player p = GetOwningPlayer(damage.source)
        local real xTarget
        local real yTarget
        local unit FoG
        local real parabola = (range-(range*Cos(arc/2 * bj_DEGTORAD)*Cos(arc/2 * bj_DEGTORAD)))/( 4.00 * Cos(arc/2 * bj_DEGTORAD))
        local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(tary-y,tarx-x) + ofst*bj_DEGTORAD
        local real xFocus = x + parabola*Cos(init_ang)
        local real yFocus = y + parabola*Sin(init_ang)
        local real tempAngle
        local boolean b
        call GroupEnumUnitsInRange(g, x, y, range + MAX_COLLISION_SIZE, condition)
        if damage.mainTarget != null and IsUnitInGroup(damage.mainTarget, damage.shotGroup) then
            call GroupRemoveUnit(g, damage.mainTarget)
        endif
        call GroupRemoveUnit(g, damage.source)
        loop
            set FoG = FirstOfGroup(g)
            exitwhen FoG == null
            set xTarget = GetUnitX(FoG)
            set yTarget = GetUnitY(FoG)
            set tempAngle = Atan2(yTarget-yFocus,xTarget-xFocus) - init_ang
            if condition == null then
                set b = IsUnitEnemy(FoG, p) and not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            else
                set b = not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            endif
            if b then
                if speed > 0 then
                    static if LIBRARY_AdvancedMultishot then
                        call MS_Shoot(damage, x, y, speed, FoG)
                    endif
                else
                    call IssueTargetOrder(damage.dummy, SPELL_ORDER, FoG)
                endif
                call GroupAddUnit(damage.shotGroup, FoG)
                set damage.unitsShot = damage.unitsShot + 1
            endif
            call GroupRemoveUnit(g, FoG)
        endloop
    endfunction

    function ShootRandomTargets takes MultiMisc_DamProperties damage, integer targets, real range, real arc, real tarx, real tary, real ofst, boolexpr condition, real speed returns nothing
        local integer left_c = 0
        local integer right_c = 0
        local unit array units
        local real x = GetUnitX(damage.dummy)
        local real y = GetUnitY(damage.dummy)
        local player p = GetOwningPlayer(damage.source)
        local unit FoG
        local integer dice
        local real xTarget
        local real yTarget
        local real parabola = (range-(range*Cos(arc/2 * bj_DEGTORAD)*Cos(arc/2 * bj_DEGTORAD)))/( 4.00 * Cos(arc/2 * bj_DEGTORAD))
        local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(tary-y,tarx-x) + ofst*bj_DEGTORAD
        local real xFocus = x + parabola*Cos(init_ang)
        local real yFocus = y + parabola*Sin(init_ang)
        local real tempAngle
        local boolean b
        if damage.mainTarget != null and IsUnitInGroup(damage.mainTarget, damage.shotGroup) then
            call GroupRemoveUnit(g, damage.mainTarget)
            set targets = targets - 1
        endif
        call GroupEnumUnitsInRange(g, x, y, range + MAX_COLLISION_SIZE, condition)
        call GroupRemoveUnit(g, damage.source)
        loop
            set FoG = FirstOfGroup(g)
            exitwhen FoG == null
            set xTarget = GetUnitX(FoG)
            set yTarget = GetUnitY(FoG)
            set tempAngle = Atan2(yTarget-yFocus,xTarget-xFocus) - init_ang
            if R2I(tempAngle/(2*bj_PI)) != 0 then
                set tempAngle = ModuloReal(tempAngle, 2*bj_PI)
            endif
            if condition == null then
                set b = IsUnitEnemy(FoG, p) and not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            else
                set b = not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            endif
            if b then
                if tempAngle >= 0 and tempAngle < bj_PI then
                    set left_c = left_c + 1
                    set dice = GetRandomInt(1, left_c)
                    if dice <= targets and ( dice <= (targets + 1)/2 or (units[left_c] == null and left_c <= targets)) then
                        if left_c <= targets and ( left_c <= (targets + 1)/2 or units[left_c] == null ) then
                            set units[left_c] = units[dice]
                        endif
                        set units[dice] = FoG
                    endif
                else
                    set right_c = right_c + 1
                    set dice = GetRandomInt(1, right_c)
                    if dice <= targets and ( dice <= (targets + 1)/2 or (units[targets + 1 - right_c] == null and right_c <= targets)) then
                        if right_c <= targets and ( right_c <= (targets + 1)/2 or units[targets + 1 - right_c] == null ) then
                            set units[targets + 1 - right_c] = units[targets + 1 - dice]
                        endif
                        set units[targets + 1 - dice] = FoG
                    endif
                endif
            endif
            call GroupRemoveUnit(g, FoG)
        endloop
        set dice = 1
        loop
            exitwhen dice > targets
            if units[dice] != null then
                if speed > 0 then
                    static if LIBRARY_AdvancedMultishot then
                        call MS_Shoot(damage, x, y, speed, units[dice])
                    endif
                else
                    call IssueTargetOrder(damage.dummy, SPELL_ORDER, units[dice])
                endif
                call GroupAddUnit(damage.shotGroup, units[dice])
                set damage.unitsShot = damage.unitsShot + 1
                set units[dice] = null
            elseif targets - right_c > dice then
                set dice = targets - right_c - 1
            endif
            set dice = dice + 1
        endloop
    endfunction

    function MultishotTarget takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType, boolexpr condition returns nothing
        local MultiMisc_DamProperties damage
        if not Damage then
            static if DEBUG_MULTISHOT then
                call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
            endif
            set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
            if fixedDamage then
                set damage.amount = initialDamage
            else
                call GetBaseDamage(damage, initialDamage)
            endif
            if alwaysHitsMainTarget then
                call IssueTargetOrder(damage.dummy, SPELL_ORDER, damage.mainTarget)
                call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                set damage.unitsShot = damage.unitsShot + 1
                if targets == 1 then
                    set udg_MS_Current_Group = damage.shotGroup
                    set udg_MS_UnitsInGroup = damage.unitsShot
                    set udg_MS_Dummy = damage.dummy
                    return
                endif
            endif
            if targets == 0 or targets > 1000 then
                call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, 0.00)
            else
                call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, 0.00)
            endif
            set udg_MS_Current_Group = damage.shotGroup
            set udg_MS_UnitsInGroup = damage.unitsShot
            set udg_MS_Dummy = damage.dummy
        endif
    endfunction

    function MultishotTargetAdvanced takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType, boolexpr condition, real shotSpeed returns nothing
        local MultiMisc_DamProperties damage
        static if LIBRARY_AdvancedMultishot then
            if not Damage then
                static if DEBUG_MULTISHOT then
                    call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
                endif
                set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
                if fixedDamage then
                    set damage.amount = initialDamage
                else
                    call GetBaseDamage(damage, initialDamage)
                endif
                if alwaysHitsMainTarget then
                    call MS_Shoot(damage, GetUnitX(damage.dummy), GetUnitY(damage.dummy), shotSpeed, multiTarget)
                    call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                    set damage.unitsShot = damage.unitsShot + 1
                    if targets == 1 then
                        set udg_MS_Current_Group = damage.shotGroup
                        set udg_MS_UnitsInGroup = damage.unitsShot
                        set udg_MS_Dummy = damage.dummy
                        return
                    endif
                endif
                if targets == 0 or targets > 1000 then
                    call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, shotSpeed)
                else
                    call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, shotSpeed)
                endif
                set udg_MS_Current_Group = damage.shotGroup
                set udg_MS_UnitsInGroup = damage.unitsShot
                set udg_MS_Dummy = damage.dummy
            endif
        else
            call MultishotTarget(shooter, multiTarget, initialDamage, targets, arc, initRange, alwaysHitsMainTarget, attackType, missileArt, offsetAngle, fixedDamage, damageType, condition)
        endif
    endfunction

    function MultishotPoint takes unit shooter, real initialDamage, integer targets, real arc, real initRange, attacktype attackType, integer missileArt, real offsetAngle, damagetype damageType, real xSpell, real ySpell, boolexpr condition returns nothing
        local MultiMisc_DamProperties damage
        static if DEBUG_MULTISHOT then
            call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), xSpell, ySpell, offsetAngle)
        endif
        set damage = ConfigureDamProp(shooter, damageType, attackType, null, missileArt, false)
        set damage.amount = initialDamage
        if targets == 0 or targets > 1000 then
            call ShootAllTargets(damage, initRange, arc, xSpell, ySpell, offsetAngle, condition, 0.00)
        else
            call ShootRandomTargets(damage, targets, initRange, arc, xSpell, ySpell, offsetAngle, condition, 0.00)
        endif
        set udg_MS_Current_Group = damage.shotGroup
        set udg_MS_UnitsInGroup = damage.unitsShot
        set udg_MS_Dummy = damage.dummy
    endfunction

    function MultishotPointAdvanced takes unit shooter, real initialDamage, integer targets, real arc, real initRange, attacktype attackType, integer missileArt, real offsetAngle, damagetype damageType, real xSpell, real ySpell, boolexpr condition, real shotSpeed returns nothing
        local MultiMisc_DamProperties damage
        static if LIBRARY_AdvancedMultishot then
            static if DEBUG_MULTISHOT then
                call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), xSpell, ySpell, offsetAngle)
            endif
            set damage = ConfigureDamProp(shooter, damageType, attackType, null, missileArt, false)
            set damage.amount = initialDamage
            if targets == 0 or targets > 1000 then
                call ShootAllTargets(damage, initRange, arc, xSpell, ySpell, offsetAngle, condition, shotSpeed)
            else
                call ShootRandomTargets(damage, targets, initRange, arc, xSpell, ySpell, offsetAngle, condition, shotSpeed)
            endif
            set udg_MS_Current_Group = damage.shotGroup
            set udg_MS_UnitsInGroup = damage.unitsShot
            set udg_MS_Dummy = damage.dummy
        else
            call MultishotPoint(shooter, initialDamage, targets, arc, initRange, attackType, missileArt, offsetAngle, damageType, xSpell, ySpell, condition)
        endif
    endfunction

    function MultishotLight takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType returns nothing
        local MultiMisc_DamProperties damage
        if not Damage then
            static if DEBUG_MULTISHOT then
                call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
            endif
            set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
            if fixedDamage then
                set damage.amount = initialDamage
            else
                call GetBaseDamage(damage, initialDamage)
            endif
            if alwaysHitsMainTarget then
                call IssueTargetOrder(damage.dummy, SPELL_ORDER, damage.mainTarget)
                call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                set damage.unitsShot = damage.unitsShot + 1
                if targets == 1 then
                    set udg_MS_Current_Group = damage.shotGroup
                    set udg_MS_UnitsInGroup = damage.unitsShot
                    set udg_MS_Dummy = damage.dummy
                    return
                endif
            endif
            if targets == 0 or targets > 1000 then
                call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, 0.00)
            else
                call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, 0.00)
            endif
            set udg_MS_Current_Group = damage.shotGroup
            set udg_MS_UnitsInGroup = damage.unitsShot
            set udg_MS_Dummy = damage.dummy
        endif
    endfunction

    function MultishotLightAdvanced takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType, real shotSpeed returns nothing
        static if LIBRARY_AdvancedMultishot then
            local MultiMisc_DamProperties damage
            if not Damage then
                static if DEBUG_MULTISHOT then
                    call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
                endif
                set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
                if fixedDamage then
                    set damage.amount = initialDamage
                else
                    call GetBaseDamage(damage, initialDamage)
                endif
                if alwaysHitsMainTarget then
                    call MS_Shoot(damage, GetUnitX(damage.dummy), GetUnitY(damage.dummy), shotSpeed, multiTarget)
                    call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                    set damage.unitsShot = damage.unitsShot + 1
                    if targets == 1 then
                        set udg_MS_Current_Group = damage.shotGroup
                        set udg_MS_UnitsInGroup = damage.unitsShot
                        set udg_MS_Dummy = damage.dummy
                        return
                    endif
                endif
                if targets == 0 or targets > 1000 then
                    call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, shotSpeed)
                else
                    call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, shotSpeed)
                endif
                set udg_MS_Current_Group = damage.shotGroup
                set udg_MS_UnitsInGroup = damage.unitsShot
                set udg_MS_Dummy = damage.dummy
            endif
        else
            call MultishotLight(shooter, multiTarget, initialDamage, targets, arc, initRange, alwaysHitsMainTarget, attackType, missileArt, offsetAngle, fixedDamage, damageType)
        endif
    endfunction

//! textmacro MulthishotOnHit takes SOURCE, VICTIM, AMOUNT, EVENT

// Checks if a unit, that has taken damage, has the Buff, applied by the Multishot. If it has it - applies the MS damage.
// When a unit is hit by the multishot's missile - the buff is removed, and the damage is applied
// It also sets a variable to 0.00 and then to 1.00 for the Extra triggers to run.
    private function MultishotDelay takes nothing returns boolean
        local MultiMisc_DamProperties damage
        if GetUnitAbilityLevel($VICTIM$, BUFF_APPLIED_ID ) > 0 then
            call UnitRemoveAbility( $VICTIM$, BUFF_APPLIED_ID )
            static if AdvancedMultishot_ALWAYS_USE_ADVANCED then
                if damage.advanced then
                    return false
                endif
            endif
            set damage = LoadInteger(Table, GetHandleId($SOURCE$), 'damp')
            set udg_MS_Damage = damage.amount
            if WANT_MULTISHOT_HIT_EVENT then
                set udg_MS_Dummy = $SOURCE$
                set udg_MS_Hit_Unit = $VICTIM$
                set udg_MS_Source = damage.source
                set udg_MS_Current_Group = damage.shotGroup
                set udg_MS_Main_Target = damage.mainTarget
                set udg_MS_Missile = damage.missile
                set udg_MS_UnitsInGroup = damage.unitsShot
                set udg_MS_Hit_Event = 1.00
                set udg_MS_Hit_Event = 0.00
            endif
            set Damage = true
            call UnitDamageTarget( udg_MS_Source, $VICTIM$, udg_MS_Damage, true, true, damage.aType, damage.dType, null )
            set Damage = false
            set damage.alreadyShot = damage.alreadyShot + 1
            if damage.alreadyShot == damage.unitsShot then
                call TimerStart(damage.clearer, 0.00, false, function MultiMisc_ClearLeaks)
            endif
        endif
        return false
    endfunction

// Initiation Trigger :P
    function Init takes nothing returns nothing
        set gg_trg_Multishot_v2_2_c = CreateTrigger(  )
        call TriggerRegisterVariableEvent( gg_trg_Multishot_v2_2_c, "$EVENT$", EQUAL, 0 ) // change the 0 to something else if needed.
        call TriggerAddCondition( gg_trg_Multishot_v2_2_c, Condition( function MultishotDelay ) )
    endfunction
//! endtextmacro

//! runtextmacro MulthishotOnHit("udg_DamageEventSource","udg_DamageEventTarget","udg_DamageEventAmount","udg_DamageEvent")

endlibrary

And the add-on library:
JASS:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*  ----------------------- ORBS = ADD-ON LIBRARY FOR WereElf's CUSOM MULTISHOT (By WereElf) ------------------------

This library allows MS to work with Orb effects for units with the Multishot ability. When it's used - ALL the units,
shot get affected by the orb effect.
The way it works is simple: When a unit is hit by Multishot - this library checks if the hit was done by a "special"
missile type. If it is - it summons a SPECIAL DUMMY, with enabled attack, gives it the right orb ability, and makes it
attack the target. A trigger is created. When the unit takes damage from the special dummy - the dummy is ordered to stop,
the trigger is destroyed, and the dummy is given a timed life.

NOTE: don't use the missile abilities, used for the orbs for normal Multishot, UNLESS you want the units' normal attacks
to act like the orbs.

REQUIREMENTS:
1) WereElf's Custom Multishot library (ofcourse)
2) A SPECIAL dummy, with attack enabled!!
3) Object editor:
- Each orb (of the ones listed below), which you want to use on your map must have 2 versions - a normal one (which is
already there), and one custom, for the Multishot
- Each orb type needs TWO custom orb abilities - 1 for the dummy, and one for the item.
* For the dummy - it should have its damage bonus set to 0.
* For the item - it needs to have its properties WITHOUT the effect of the orb. Since we don't want the orb effect
to apply before the shot has reached the unit.
- Each orb type requires a missile ability, with different Id, and different missile art (the orb's missile art)
4) You need to make a trigger: when a unit, which uses the multishot picks up an orb - replace it with the custom orb.
And when such orb is dropped - replace it with normal one.
5) Give the orb abilities, with no effect (just damage bonus) to the items.
6) The WANT_MULTISHOT_HIT_EVENT in the Multishot library must be set to true.
7) When all of the above is done - set the variables in the globals block below.

HOW TO USE:
- Before calling the Multishot function for a hero (or a unit with inventory) - call "GetActualMissile" function, and
give as arguments ( the shooter, the Id of the unit's default missile )
*/
library OrbsAddOn initializer Init
    globals
// The ID of the SPECIAL dummy (you need to make a dummy with enabled attack)
        constant integer ORB_DUMMY = 'h000'
// How much life do you want mask of death to steal on units with multishot
        constant real LIFE_STEAL = 0.20 // from each unit hit

// The IDs of the Orb ITEMS.
        constant integer ORB_OF_FROST = 'I001'
        constant integer ORB_OF_CORRUPTION = 'I003'
        constant integer ORB_OF_DARKNESS = 'I000'
        constant integer ORB_OF_LIGHTNING = 'I005'
        constant integer ORB_OF_FIRE = 'I002'
        constant integer ORB_OF_SLOW = 'I006'
        constant integer ORB_OF_VENOM = 'I007'
        constant integer MASK_OF_DEATH = 'I004'

// The IDs of the Orb MISSILES.
        constant integer FROST_MISSILE = 'A008'
        constant integer CORRUPTION_MISSILE = 'A00E'
        constant integer DARKNESS_MISSILE = 'A00H'
        constant integer LIGHTNING_MISSILE = 'A00L'
        constant integer FIRE_MISSILE = 'A00N'
        constant integer SLOW_MISSILE = 'A00Q'
        constant integer VENOM_MISSILE = 'A00O'
        constant integer LIFE_STEAL_MISSILE = 'A00W'

// The IDs of the Dummy Orbs (with the effect, without the damage bonus)
        constant integer DUMMY_FROST = 'A00T'
        constant integer DUMMY_CORRUPTION = 'A00F'
        constant integer DUMMY_DARKNESS = 'A00J'
        constant integer DUMMY_LIGHTNING = 'A009'
        constant integer DUMMY_SLOW = 'A00V'
        constant integer DUMMY_VENOM = 'A00U'

// Used for triggering the Orb of Fire
        private group FireGroup = CreateGroup()
    endglobals

// When this function is called - it checks all of the inventory slots if they contain orbs.
// If an orb is found - the missile Id of the orb is returned (and the loop stops)
// If no orb is found - it returns the unit's default missile.
    function GetActualMissile takes unit u, integer default_missile returns integer
        local integer i = 0
        local integer j
        loop
            exitwhen i > bj_MAX_INVENTORY
            set j = GetItemTypeId(UnitItemInSlot(u, i))
            if j == ORB_OF_FROST then
                return FROST_MISSILE
            elseif j == ORB_OF_CORRUPTION then
                return CORRUPTION_MISSILE
            elseif j == ORB_OF_DARKNESS then
                return DARKNESS_MISSILE
            elseif j == ORB_OF_LIGHTNING then
                return LIGHTNING_MISSILE
            elseif j == ORB_OF_FIRE then
                return FIRE_MISSILE
            elseif j == ORB_OF_VENOM then
                return VENOM_MISSILE
            elseif j == ORB_OF_SLOW then
                return SLOW_MISSILE
            elseif j == MASK_OF_DEATH then
                return LIFE_STEAL_MISSILE
            endif
            set i = i + 1
        endloop
        return default_missile
    endfunction

// ORB OF FIRE EFFECT
// Simply triggers the splash.
    private function OrbOfFire takes nothing returns nothing
        local real x = GetUnitX(udg_MS_Hit_Unit)
        local real y = GetUnitY(udg_MS_Hit_Unit)
        local unit u
        local player p = GetOwningPlayer(udg_MS_Source)
        call GroupEnumUnitsInRange(FireGroup, x, y, 175, null)
        loop
            set u = FirstOfGroup(FireGroup)
            exitwhen u == null
            if u != udg_MS_Hit_Unit and IsUnitEnemy( u , p ) then
                call UnitDamageTarget(udg_MS_Dummy, u, udg_MS_Damage/5, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            endif
            call GroupRemoveUnit(FireGroup, u)
        endloop
        set p = null
    endfunction

// LIFE STEAL EFFECT
// Heals the shooter for % of the damage dealt. Special effect appears only when the main target is hit,
    private function LifeSteal takes nothing returns nothing
        local real hp = GetWidgetLife(udg_MS_Source)
        call SetWidgetLife(udg_MS_Source, hp + LIFE_STEAL*udg_MS_Damage)
        if udg_MS_Hit_Unit == udg_MS_Main_Target then
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\VampiricAura\\VampiricAuraTarget.mdl", udg_MS_Source, "origin"))
        endif
    endfunction

// Orb function caller
// When the special dummy hits the target unit - the trigger is destroyed, and the dummy is given 0.01 sec timed life.
    private function DummyHit takes nothing returns boolean
        if GetUnitTypeId(udg_DamageEventSource) == ORB_DUMMY then
            call IssueImmediateOrder(udg_DamageEventSource, "stop")
            call UnitApplyTimedLife(udg_DamageEventSource, 'BTLF', 0.01)
        endif
        return false
    endfunction

// When a unit is hit by the Multishot - it checks if the missile is any of the orb missiles.
// If it is - it summons the special dummy, gives it the orb ability, and makes it attack the unit.
// It also creates a trigger to register when the dummy hits the unit.
// Unless it's orb of fire. In that case - it calls the function above.

//! textmacro OrbDummy takes TYPE, EXTRA
set d = CreateUnit(GetOwningPlayer(udg_MS_Source), ORB_DUMMY, GetUnitX(udg_MS_Hit_Unit) - 50, GetUnitY(udg_MS_Hit_Unit), 0.00)
call UnitApplyTimedLife(d, 'BTLF', 1)
call UnitAddAbility( d, DUMMY_$TYPE$ )
$EXTRA$
call IssueTargetOrder( d, "attack", udg_MS_Hit_Unit )
set d = null
//! endtextmacro

    private function Trig_On_Hit_Actions takes nothing returns boolean
        local unit d
        if udg_MS_Missile == FIRE_MISSILE then
            call OrbOfFire()
            return false
        elseif udg_MS_Missile == LIFE_STEAL_MISSILE then
            call LifeSteal()
            return false
        endif
        if udg_MS_Missile == CORRUPTION_MISSILE then
            //! runtextmacro OrbDummy("CORRUPTION","")
        elseif udg_MS_Missile == DARKNESS_MISSILE then
            //! runtextmacro OrbDummy("DARKNESS","")
        elseif udg_MS_Missile == LIGHTNING_MISSILE then
            //! runtextmacro OrbDummy("LIGHTNING","")
        elseif udg_MS_Missile == FROST_MISSILE then
            //! runtextmacro OrbDummy("FROST","")
        elseif udg_MS_Missile == SLOW_MISSILE then
            //! runtextmacro OrbDummy("SLOW","")
        elseif udg_MS_Missile == VENOM_MISSILE then
            //! runtextmacro OrbDummy("VENOM","call UnitAddAbility( d, 'Apo2' )")
        endif
        return false
    endfunction

//===========================================================================
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        set gg_trg_Custom_Orb_Effects = CreateTrigger()
        call TriggerRegisterVariableEvent( gg_trg_Custom_Orb_Effects, "udg_MS_Hit_Event", EQUAL, 0 )
        call TriggerAddCondition( gg_trg_Custom_Orb_Effects, Condition(function Trig_On_Hit_Actions) )
        call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 0 )
        call TriggerAddCondition( t, Condition(function DummyHit))
        set t = null
    endfunction

endlibrary

Advanced add-on library:
JASS:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// --------------------------------- ADVANCED - PART OF WereElf's CUSTOM MULTISHOT -------------------------------- //
// This library is used to make Multishot able to target, and damage Magic Immune enemies, when they can resist     //
// ultimates (a gameplay constant is used for that).                                                                //
// When you have this library + the Multishot library, you can use "MultishotTargetAdvaned" and                     //
// "MultishotPointAdvanced" functions.                                                                              //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
library AdvancedMultishot requires MultiMisc

    globals
// ALWAYS_USE_ADVANCED allows you to use ONLY the advanced version, even if the enemies are NOT immune to magic.
// It's still only used only when calling the Advanced function.
        public constant boolean ALWAYS_USE_ADVANCED = false
        private timer Looper = CreateTimer()
        private boolean On = false
        private constant real REFRESH_RATE = 0.03125
    endglobals

// This struct is also used against Magic Immune enemies - it removes their immunity, so the Multishot missile can be cast
// and then it returns their immunities.
    private struct ImmunityList
        boolean main = false
        boolean i1 = false
        boolean i2 = false
        boolean i3 = false
        boolean i4 = false
        unit u

// 'Amim', 'ACm2', 'ACm3' and 'ACmi' are magic immunities. They are usually constant, unless you use only custom magic immunities.
        static method create takes unit u returns ImmunityList
            local ImmunityList IL = ImmunityList.allocate()
            set IL.u = u
            if GetUnitAbilityLevel(u, 'Amim') > 0 then
                call UnitRemoveAbility(u, 'Amim')
                set IL.i1 = true
                set IL.main = true
            endif
            if GetUnitAbilityLevel(u, 'ACm2') > 0 then
                call UnitRemoveAbility(u, 'ACm2')
                set IL.i2 = true
                set IL.main = true
            endif
            if GetUnitAbilityLevel(u, 'ACm3') > 0 then
                call UnitRemoveAbility(u, 'ACm3')
                set IL.i3 = true
                set IL.main = true
            endif
            if GetUnitAbilityLevel(u, 'ACmi') > 0 then
                call UnitRemoveAbility(u, 'ACmi')
                set IL.i4 = true
                set IL.main = true
            endif
            return IL
        endmethod
   
        method onDestroy takes nothing returns nothing
            if .i1 then
                call UnitAddAbility(.u, 'Amim')
            endif
            if .i2 then
                call UnitAddAbility(.u, 'ACm2')
            endif
            if .i3 then
                call UnitAddAbility(.u, 'ACm3')
            endif
            if .i4 then
                call UnitAddAbility(.u, 'ACmi')
            endif
        endmethod
    endstruct
 
    private struct TimeManager
        real x
        real y
        real speed
        unit targ
        MultiMisc_DamProperties damage
 
        readonly thistype next
        readonly thistype prev
        static method operator first takes nothing returns thistype
            return thistype(0).next
        endmethod
        static method operator last takes nothing returns thistype
            return thistype(0).prev
        endmethod

        static method create takes real x, real y, unit u, real speed, MultiMisc_DamProperties DP returns thistype
            local thistype this = thistype.allocate()
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set this.prev = thistype(0)
            set thistype(0).next = this
            set this.x = x
            set this.y = y
            set this.speed = speed*REFRESH_RATE
            set this.targ = u
            set this.damage = DP
            return this
        endmethod

        method onDestroy takes nothing returns nothing
            if this.first == this.last then
                set On = false
            endif
            set this.next.prev = this.prev
            set this.prev.next = this.next
        endmethod

        static method refresh takes nothing returns nothing
            local thistype this = thistype.first
            local real x
            local real y
            local real h
            local real w
            local real dist
            local real a
            loop
                exitwhen this == 0
                if not IsUnitType(this.targ, UNIT_TYPE_DEAD) then
                    set x = GetUnitX(this.targ)
                    set y = GetUnitY(this.targ)
                    set w = x - this.x
                    set h = y - this.y
                    set dist = w*w + h*h
                    if dist <= this.speed*this.speed then
                        set udg_MS_Damage = this.damage.amount
                            if Multishot_WANT_MULTISHOT_HIT_EVENT then
                            set udg_MS_Dummy = this.damage.dummy
                            set udg_MS_Hit_Unit = this.targ
                            set udg_MS_Source = this.damage.source
                            set udg_MS_Current_Group = this.damage.shotGroup
                            set udg_MS_Main_Target = this.damage.mainTarget
                            set udg_MS_Missile = this.damage.missile
                            set udg_MS_UnitsInGroup = this.damage.unitsShot
                            set udg_MS_Hit_Event = 1.00
                            set udg_MS_Hit_Event = 0.00
                        endif
                        set Multishot_Damage = true
                        call UnitDamageTarget( this.damage.source, this.targ, udg_MS_Damage, true, false, this.damage.aType, this.damage.dType, null )
                        set Multishot_Damage = false
                        set this.damage.alreadyShot = this.damage.alreadyShot + 1
                        if this.damage.alreadyShot == this.damage.unitsShot then
                            call TimerStart(this.damage.clearer, 0.00, false, function MultiMisc_ClearLeaks)
                        endif
                        call thistype.destroy(this)
                    else
                        set a = Atan2(h, w)
                        set this.x = this.x + this.speed*Cos(a)
                        set this.y = this.y + this.speed*Sin(a)
                    endif
                else
                    call thistype.destroy(this)
                endif
                set this = this.next
            endloop
            if On then
                call TimerStart(Looper, REFRESH_RATE, false, function TimeManager.refresh)
            endif
        endmethod
    endstruct

// This function removes the unit's magic immunities (so it can shoot them), shoots them, and if they have had magic immunity
// it calculates the time the shot needs to reach the target (roughly), and calls the function above to start a timer,
// Which will artificially damage the unit, when the timer expires.
// Then it returns the units' immunities.
    function MS_Shoot takes MultiMisc_DamProperties DP, real x, real y, real shotSpeed, unit target returns nothing
        local real delay
        local ImmunityList IL = ImmunityList.create(target)
        local TimeManager TM
        call IssueTargetOrder(DP.dummy, Multishot_SPELL_ORDER, target)
        if IL.main or ALWAYS_USE_ADVANCED then
            set TM = TimeManager.create(x, y, target, shotSpeed, DP)
            if not On then
                call TimerStart(Looper, REFRESH_RATE, false, function TimeManager.refresh)
                set On = true
            endif
        endif
        call ImmunityList.destroy(IL)
    endfunction

endlibrary

Example MultishoRt library:
JASS:
//! novjass
MultishotTarg (both normal and the advanced) take:
1 (unit) - Shooter - who is shooting
2 (unit) - Target - who is the original target of the shot
3 (real) - Damage - the damage the shot should deal
4 (integer) - Targets - how many targets its allowed to shoot at once
5 (real) - Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want ALL the units in range to get shot
6 (real) - Range - the range multishot chooses enemies from.
7 (boolean) - Always hits main targer - does the main target always get shot, or it only has a chance to get shot (if more than the max amount of units are in range)
8 (attacktype) - Attack type - the attack type of the shooter. Its used for correct armor estimation, and thats the type of damage dealt in the end.
9 (integer) - Missile ability - the ability id of the Multishots missile.
10 (real) - Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45, multishot will act like the target is at 90 degrees.
11 (boolean) - Fixed damage - if you want to deal some fixed value of damage, not based on the damage dealt - set this to true, otherwise - false.
12 (damagetype) - Damage type - if "Fixed damage" is set to false - this will act as "DAMAGE_TYPE_NORMAL", no matter what. Otherwise - this is the damage type multishot deals.
13 (boolexpr) - Conditions - What units shall it target
'NEXT ONE IS ONLY FOR THE ADVANCED VERSION'
14 (real) - Missile speed - simply put-in the missiles speed

MultishotPoint (both normal and the advanced) take:
1 (unit) Caster - who is shooting
2 (real) Damage - the damage dealt
3 (integer) Targets - how many targets its allowed to shoot at once
4 (real) Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want ALL the units in range to get shot
5 (real) Range - the range multishot chooses enemies from
6 (attacktype) Attack Type - the attack type of the damage dealt
7 (integer) Missile ability - the ability id of the Multishots missile
8 (real) Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45, multishot will act like the target is at 90 degrees
9 (damagetype) Damage type - the damage type of the damage dealth
10 (real) Target X - the x of the targeted point
11 (real) Target Y - the y of the targeted point
12 (boolexpr) - Conditions - What units shall it target
'NEXT ONE IS ONLY FOR THE ADVANCED VERSION'
13 (real) Missile speed - simply put-in the missiles speed

MultishotBasic takes the same arguments as MultishotTarget, without the boolexpr. Its set to target only enemies.
//! endnovjass

library MultishoRt requires Multishot, optional OrbsAddOn

    globals
        private player ConditionPlayer
    endglobals

    function FilterEnemies takes nothing returns boolean
        return IsUnitEnemy(GetFilterUnit(), ConditionPlayer)
    endfunction
 
    function FilterAllies takes nothing returns boolean
        return IsUnitAlly(GetFilterUnit(), ConditionPlayer) and GetFilterUnit() != GetTriggerUnit()
    endfunction

    function MultishotNormal takes integer targets, real arc, real range, attacktype attackType, integer missile returns nothing
        call MultishotLightAdvanced(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventAmount, targets, arc, range, true, attackType, missile, 0.00, false, DAMAGE_TYPE_NORMAL, 900.00)
    endfunction

    function MultishotSpell takes unit caster, unit target, integer targets, real arc, real range, real damage, integer missile returns nothing
        set ConditionPlayer = GetOwningPlayer(caster)
        call MultishotTarget(caster, target, damage, targets, arc, range, true, ATTACK_TYPE_NORMAL, missile, 0.00, true, DAMAGE_TYPE_MAGIC, Condition(function FilterEnemies))
    endfunction
 
    function MultishotPointShort takes unit caster, real x, real y, real damage, integer missile returns nothing
        set ConditionPlayer = GetOwningPlayer(caster)
        call MultishotPoint(caster, damage, 0, 25, 1000, ATTACK_TYPE_NORMAL, missile, 0.00, DAMAGE_TYPE_MAGIC, x, y, Condition(function FilterEnemies))
    endfunction
 
    function MultishotHeal takes unit caster, location loc, real amount, integer missile returns nothing
        set ConditionPlayer = GetOwningPlayer(caster)
        call MultishotPointAdvanced(caster, amount, 0, 90, 500, ATTACK_TYPE_NORMAL, missile, 0.00, DAMAGE_TYPE_NORMAL, GetLocationX(loc), GetLocationY(loc), Condition(function FilterAllies), 500.00)
    endfunction
 
    function MultishotOrb takes integer targets, real arc, real range, integer missile returns nothing
        local real speed
        local integer temp = missile
        static if LIBRARY_OrbsAddOn then
            set missile = GetActualMissile(udg_DamageEventSource, missile)
            if missile == temp then
                set speed = 900
            else
                set speed = 1050
            endif
        else
            set speed = 900
        endif
        set ConditionPlayer = GetOwningPlayer(udg_DamageEventSource)
        call MultishotLightAdvanced(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventAmount, targets, arc, range, true, ATTACK_TYPE_HERO, missile, 0.00, false, DAMAGE_TYPE_NORMAL, speed)
    endfunction
 
endlibrary

Example triggers [color = "orange"]I haven't updated these for a while, so they may be differnt in the sample map, but will keep them here just as examples xP[/color]:
  • Multishot caller
    • Events
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • (Level of Multishot Never Miss for DamageEventSource) Greater than 0
    • Actions
      • Custom script: local integer dice
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of DamageEventSource) Equal to Death Archer
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • DamageEventAmount Greater than or equal to 125.00
            • Then - Actions
              • Custom script: call MultishotNormal( 0 ,55.00, 900.00, ATTACK_TYPE_CHAOS, 'A001')
            • Else - Actions
              • Custom script: call MultishotNormal( 7 , 55.00, 900.00, ATTACK_TYPE_CHAOS, 'A00P')
          • Custom script: if not Multishot_Damage then
          • Set DamageEventAmount = 0.00
          • Set DamageEventType = DamageTypeBlocked
          • Custom script: endif
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Unit-type of DamageEventSource) Equal to Elite Night Elf Archer
            • Then - Actions
              • Custom script: set dice = GetRandomInt(2, 4)
              • Custom script: call MultishotNormal( dice , 60.00, 600.00, ATTACK_TYPE_PIERCE, 'A00P')
              • Custom script: if not Multishot_Damage then
              • Set DamageEventAmount = 0.00
              • Set DamageEventType = DamageTypeBlocked
              • Custom script: endif
            • Else - Actions
  • Multishot spell
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Rocket wave
    • Actions
      • Custom script: call MultishotSpell(GetTriggerUnit(), GetSpellTargetUnit(), 400, 'A004')
  • Multishot point
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Ice age
    • Actions
      • Custom script: call MultishotPointShort(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY(), 250, 'A006')
Not showing the Demo Triggers from the sample map here :p

I have this Multishot in pure JASS as well here. However - the version there is 1.6.3

And last, but not least, special thanks to:
Wietlol, who helped me think of a way to detect units' armor; Zwiebelchen, who taught me how to use timers properly; SAUS, who helped me find a way to save different configurations for different units; And now, Chaosy too, for making me improve my multishot (he incidentally gave me some ideas with his comments) :D; I also want to thank Anitarf for helping me improve some parts of it

Keywords:
Custom, Multishot, Multi, Shot, Configurable, Triggered.
Contents

Multishot Advanced (Map)

Reviews
08:21, 31st May 2016 Bribe: Approved 2.5/5. It could be higher - however, there area couple of points against it. 1) There a lot of multishot skills already in existence 2) This could be improved by using a damage modifying DDS such as PDD or...

In what "direction" should this Multishot develop?


  • Total voters
    20

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
Map is attached.
Also, Moonglaive bifurcates into two Moonglaives and hitting two targets rather than hopping to one target.

Multishot callers:
Abilities/Multishot Caller
Abilities/Multishot Caller copy
 

Attachments

  • (2)EchoIsles.w3x
    1,017.9 KB · Views: 40

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
Multishot:
JASS:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// --------------------------------- ADVANCED - PART OF WereElf's CUSTOM MULTISHOT -------------------------------- //
// This library is used to make Multishot able to target, and damage Magic Immune enemies, when they can resist     //
// ultimates (a gameplay constant is used for that).                                                                //
// When you have this library + the Multishot library, you can use "MultishotTargetAdvaned" and                     //
// "MultishotPointAdvanced" functions.                                                                              //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
library AdvancedMultishot requires MultiMisc

    globals
// ALWAYS_USE_ADVANCED allows you to use ONLY the advanced version, even if the enemies are NOT immune to magic.
// It's still only used only when calling the Advanced function.
        public constant boolean ALWAYS_USE_ADVANCED = false
        private timer Looper = CreateTimer()
        private boolean On = false
        private constant real REFRESH_RATE = 0.03125
    endglobals

// This struct is also used against Magic Immune enemies - it removes their immunity, so the Multishot missile can be cast
// and then it returns their immunities.
    private struct ImmunityList
        boolean main = false
        boolean i1 = false
        boolean i2 = false
        boolean i3 = false
        boolean i4 = false
        unit u

// 'Amim', 'ACm2', 'ACm3' and 'ACmi' are magic immunities. They are usually constant, unless you use only custom magic immunities.
        static method create takes unit u returns ImmunityList
            local ImmunityList IL = ImmunityList.allocate()
            set IL.u = u
            if GetUnitAbilityLevel(u, 'Amim') > 0 then
                call UnitRemoveAbility(u, 'Amim')
                set IL.i1 = true
                set IL.main = true
            endif
            if GetUnitAbilityLevel(u, 'ACm2') > 0 then
                call UnitRemoveAbility(u, 'ACm2')
                set IL.i2 = true
                set IL.main = true
            endif
            if GetUnitAbilityLevel(u, 'ACm3') > 0 then
                call UnitRemoveAbility(u, 'ACm3')
                set IL.i3 = true
                set IL.main = true
            endif
            if GetUnitAbilityLevel(u, 'ACmi') > 0 then
                call UnitRemoveAbility(u, 'ACmi')
                set IL.i4 = true
                set IL.main = true
            endif
            return IL
        endmethod
     
        method onDestroy takes nothing returns nothing
            if .i1 then
                call UnitAddAbility(.u, 'Amim')
            endif
            if .i2 then
                call UnitAddAbility(.u, 'ACm2')
            endif
            if .i3 then
                call UnitAddAbility(.u, 'ACm3')
            endif
            if .i4 then
                call UnitAddAbility(.u, 'ACmi')
            endif
        endmethod
    endstruct
 
    private struct TimeManager
        real x
        real y
        real speed
        unit targ
        MultiMisc_DamProperties damage
 
        readonly thistype next
        readonly thistype prev
        static method operator first takes nothing returns thistype
            return thistype(0).next
        endmethod
        static method operator last takes nothing returns thistype
            return thistype(0).prev
        endmethod

        static method create takes real x, real y, unit u, real speed, MultiMisc_DamProperties DP returns thistype
            local thistype this = thistype.allocate()
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set this.prev = thistype(0)
            set thistype(0).next = this
            set this.x = x
            set this.y = y
            set this.speed = speed*REFRESH_RATE
            set this.targ = u
            set this.damage = DP
            return this
        endmethod

        method onDestroy takes nothing returns nothing
            if this.first == this.last then
                set On = false
            endif
            set this.next.prev = this.prev
            set this.prev.next = this.next
        endmethod

        static method refresh takes nothing returns nothing
            local thistype this = thistype.first
            local real x
            local real y
            local real h
            local real w
            local real dist
            local real a
            loop
                exitwhen this == 0
                if not IsUnitType(this.targ, UNIT_TYPE_DEAD) then
                    set x = GetUnitX(this.targ)
                    set y = GetUnitY(this.targ)
                    set w = x - this.x
                    set h = y - this.y
                    set dist = w*w + h*h
                    if dist <= this.speed*this.speed then
                        set udg_MS_Damage = this.damage.amount
                            if Multishot_WANT_MULTISHOT_HIT_EVENT then
                            set udg_MS_Dummy = this.damage.dummy
                            set udg_MS_Hit_Unit = this.targ
                            set udg_MS_Source = this.damage.source
                            set udg_MS_Current_Group = this.damage.shotGroup
                            set udg_MS_Main_Target = this.damage.mainTarget
                            set udg_MS_Missile = this.damage.missile
                            set udg_MS_UnitsInGroup = this.damage.unitsShot
                            set udg_MS_Hit_Event = 1.00
                            set udg_MS_Hit_Event = 0.00
                        endif
                        set Multishot_Damage = true
                        call UnitDamageTarget( this.damage.source, this.targ, udg_MS_Damage, true, false, this.damage.aType, this.damage.dType, null )
                        set Multishot_Damage = false
                        set this.damage.alreadyShot = this.damage.alreadyShot + 1
                        if this.damage.alreadyShot == this.damage.unitsShot then
                            call TimerStart(this.damage.clearer, 0.00, false, function MultiMisc_ClearLeaks)
                        endif
                        call thistype.destroy(this)
                    else
                        set a = Atan2(h, w)
                        set this.x = this.x + this.speed*Cos(a)
                        set this.y = this.y + this.speed*Sin(a)
                    endif
                else
                    call thistype.destroy(this)
                endif
                set this = this.next
            endloop
            if On then
                call TimerStart(Looper, REFRESH_RATE, false, function TimeManager.refresh)
            endif
        endmethod
    endstruct

// This function removes the unit's magic immunities (so it can shoot them), shoots them, and if they have had magic immunity
// it calculates the time the shot needs to reach the target (roughly), and calls the function above to start a timer,
// Which will artificially damage the unit, when the timer expires.
// Then it returns the units' immunities.
    function MS_Shoot takes MultiMisc_DamProperties DP, real x, real y, real shotSpeed, unit target returns nothing
        local real delay
        local ImmunityList IL = ImmunityList.create(target)
        local TimeManager TM
        call IssueTargetOrder(DP.dummy, Multishot_SPELL_ORDER, target)
        if IL.main or ALWAYS_USE_ADVANCED then
            set TM = TimeManager.create(x, y, target, shotSpeed, DP)
            if not On then
                call TimerStart(Looper, REFRESH_RATE, false, function TimeManager.refresh)
                set On = true
            endif
        endif
        call ImmunityList.destroy(IL)
    endfunction

endlibrary

JASS:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ----------------------------------------- CUSTOM MULTISHOT BY WereElf ------------------------------------------ //
// This is simply a Multishot, which is a lot better than the Barrage based one.                                    //
// The differences between this Multishot and the Barrage based one are:                                            //
//                                                                                                                  //
// 1) This multishot can be configured to hit only enemies infront of the shooter, while Barrage based can only     //
// target ALL the enemies in range (certain amount of them).                                                        //
//                                                                                                                  //
// 2) This multishot works with Orb effects (if you use the "Orbs" add-on library.                                  //
//                                                                                                                  //
// 3) You can make your own "orb effects" when you use this Multishot, since you get a "On Hit" event.              //
//                                                                                                                  //
// 4) You can set the targets allowed to 1 and 2, while for the Barrage based one - the minimum is 3.               //
//                                                                                                                  //
// 5) You can use this multishot for SPELLS!                                                                        //
//                                                                                                                  //
// 6) You can put some weird configurations for this multishot (like - hit allied buildings, and enemy units).      //
//                                                                                                                  //
// I could keep on listing the possibillities, offered by this library, but I wouldn't. I pointed out the most      //
// important ones.                                                                                                  //
//                                                                                                                  //
// HOW TO MAKE IT WORK:                                                                                             //
//                                                                                                                  //
// 0.a) You need a damage detecting system on your map. The multishot is built, using 1 of the most basic DDS(es),  //
// so it will work with any DDS. However, if you are using a different one - you need to go to the bottom of this   //
// library, and change the variables in the last textmacro to the ones, used by the other DDS.                      //
//                                                                                                                  //
// 0.b) You need to have a dummy on your map.                                                                       //
//                                                                                                                  //
// 1) Make a custom buff on your map, which will be used ONLY by the Multishot skills.                              //
//                                                                                                                  //
// 2) Make as many as you need skills, based on Acid Bomb, make them cost 0 mana, deal 0 damage, and make them      //
// APPLY THE BUFF YOU MADE IN STEP (1).                                                                             //
//                                                                                                                  //
// 3) Scroll down to the globals block of this library, and set the variables to "your" values.                     //
//                                                                                                                  //
// 4) When you want to make a trigger, using Multishot, just call "MultishotTarget" or "MultishotPoint", according  //
// to your needs.                                                                                                   //
//                                                                                                                  //
// 5) You could make a library with some pre-set Multishot functions (like MultishoRt in the sample map), so you    //
// don't have to put all the arguments when you call Multishot, but simply the ones you need.                       //
//                                                                                                                  //
// If you have the AdvancedMultishot library (add-on), you can also use "MultishotTargetAdvanced" and               //
// "MultishotPointAdvanced". The advanced functions are able to hit magic immune enemies, even if you have "Magic   //
// immune units resist ultimates" set to true.                                                                      //
//                                                                                                                  //
// THE ARGUMENTS THE FUNCTIONS TAKE:                                                                                //
//                                                                                                                  //
// MultishotTarg (both normal and the advanced) take:                                                               //
// 1 (unit) - Shooter - who is shooting                                                                             //
// 2 (unit) - Target - who is the original target of the shot                                                       //
// 3 (real) - Damage - the damage the shot should deal                                                              //
// 4 (integer) - Targets - how many targets its allowed to shoot at once                                            //
// 5 (real) - Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want //
// ALL the units in range to get shot                                                                               //
// 6 (real) - Range - the range multishot chooses enemies from.                                                     //
// 7 (boolean) - Always hits main targer - does the main target always get shot, or it only has a chance to get     //
// shot (if more than the max amount of units are in range)                                                         //
// 8 (attacktype) - Attack type - the attack type of the shooter. Its used for correct armor estimation, and thats  //
// the type of damage dealt in the end.                                                                             //
// 9 (integer) - Missile ability - the ability id of the Multishots missile.                                        //
// 10 (real) - Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45,    //
// multishot will act like the target is at 90 degrees.                                                             //
// 11 (boolean) - Fixed damage - if you want to deal some fixed value of damage, not based on the damage dealt -    //
// set this to true, otherwise - false.                                                                             //
// 12 (damagetype) - Damage type - if "Fixed damage" is set to false - this will act as "DAMAGE_TYPE_NORMAL", no    //
// matter what. Otherwise - this is the damage type multishot deals.                                                //
// 13 (boolexpr) - Conditions - What units shall it target                                                          //
// NEXT ONE IS ONLY FOR THE ADVANCED VERSION                                                                        //
// 14 (real) - Missile speed - simply put-in the missiles speed                                                     //
//                                                                                                                  //
// MultishotPoint (both normal and the advanced) take:                                                              //
// 1 (unit) Caster - who is shooting                                                                                //
// 2 (real) Damage - the damage dealt                                                                               //
// 3 (integer) Targets - how many targets its allowed to shoot at once                                              //
// 4 (real) Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want   //
// ALL the units in range to get shot                                                                               //
// 5 (real) Range - the range multishot chooses enemies from                                                        //
// 6 (attacktype) Attack Type - the attack type of the damage dealt                                                 //
// 7 (integer) Missile ability - the ability id of the Multishots missile                                           //
// 8 (real) Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45,       //
// multishot will act like the target is at 90 degrees                                                              //
// 9 (damagetype) Damage type - the damage type of the damage dealth                                                //
// 10 (real) Target X - the x of the targeted point                                                                 //
// 11 (real) Target Y - the y of the targeted point                                                                 //
// 12 (boolexpr) - Conditions - What units shall it target                                                          //
// NEXT ONE IS ONLY FOR THE ADVANCED VERSION                                                                        //
// 13 (real) Missile speed - simply put-in the missiles speed                                                       //
//                                                                                                                  //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
library MultiMisc

    public struct DamProperties
        real amount
        attacktype aType
        damagetype dType
        unit source
        unit mainTarget
        group shotGroup
        integer missile
        integer unitsShot = 0
        boolean advanced
        integer alreadyShot = 0
        boolean preDummy
        unit dummy
        timer clearer

        static method create takes nothing returns DamProperties
            local DamProperties DP = DamProperties.allocate()
            set DP.shotGroup = CreateGroup()
            set DP.clearer = CreateTimer()
            set DP.mainTarget = null
            return DP
        endmethod

        method onDestroy takes nothing returns nothing
            call GroupClear(this.shotGroup)
            call DestroyGroup(this.shotGroup)
            call DestroyTimer(this.clearer)
        endmethod
    endstruct

    public function ClearLeaks takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer tid = GetHandleId(t)
        local integer id = LoadInteger(Multishot_Table, tid, 'dmid')
        local DamProperties damage = LoadInteger(Multishot_Table, id, 'damp')
        call UnitApplyTimedLife(damage.dummy, 'BTLF', 0.01)
        call DamProperties.destroy(damage)
        call FlushChildHashtable(Multishot_Table, id)
        call FlushChildHashtable(Multishot_Table, tid)
        set t = null
    endfunction

endlibrary

library Multishot initializer Init requires MultiMisc, optional AdvancedMultishot

// DEBUG_MULTISHOT displays how the parabola looks like
// WANT_DAMAGE_BLOCK is a boolean. If set to true - Multishot will automatically block the incoming damage. Set it to false only
// if you want to do the damage block by yourself in the Multishot calling function.
// HP_BONUS_ABILITY is the Item ability, giving 30000 HP to the target unit for the damage block, and for the 'armor test'
// BUFF_APPLIED_ID is the Id of the buff, applied by the Multishot.
// DUMMY_ID is the Id of your dummy unit.
// TEST_DAMAGE_AMOUNT is the amount of damage the target takes to estimate its armor. Higher values give more precise results, but they may instantly kill the target.
// SPELL_ORDER is the string of the order, issued to the dummy to make it cast the multishot arrow. You could change it if
// you are using another spell as base, but acidbomb is the only good spell for this purpose.
// DUMMY_LIFE_TIME is the time the dummy stays alive. Set this to an amount, enough for the multishot to hit all its targets.
// Don't touch the rest.
    globals
        private constant boolean DEBUG_MULTISHOT = false
        private constant boolean WANT_DAMAGE_BLOCK = false
        private constant integer HP_BONUS_ABILITY = 'A03O'
        private constant integer BUFF_APPLIED_ID = 'B00G'
        private constant integer DUMMY_ID = 'h00G'
        private constant real MAX_COLLISION_SIZE = 196.00
        private constant real TEST_DAMAGE_AMOUNT = 15000.00
        public constant string SPELL_ORDER = "acidbomb"
        public constant boolean WANT_MULTISHOT_HIT_EVENT = true
        public constant real MAX_DUMMY_LIFE_TIME = 20.00 // higher values allow slower missiles.
        public boolean Damage = false
        public hashtable Table = InitHashtable()
        private group g = CreateGroup()
        private effect array debug_effect // used only when DEBUG_MULTISHOT is true
    endglobals

// This sctuct is used when blocking the original damage.
    private struct DamBlocker
        real health
        unit u

        static method create takes unit u returns DamBlocker
            local DamBlocker DB
            set DB = DamBlocker.allocate()
            set DB.u = u
            set DB.health = GetWidgetLife(u)
            return DB
        endmethod
 
        static method BlockDamage takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype this = LoadInteger(Table, GetHandleId(t), 'blcr')
            call UnitRemoveAbility(this.u, HP_BONUS_ABILITY)
            call SetWidgetLife(this.u, this.health)
            call thistype.destroy(this)
            call FlushChildHashtable(Table, GetHandleId(t))
            call DestroyTimer(t)
            set t = null
        endmethod
    endstruct

// Calculates the unit's base damage, by multiplaying the damage dealt by the target's Armor rating
    private function GetBaseDamage takes MultiMisc_DamProperties damage, real dam returns nothing
        local timer time
        local real max_hp
        local DamBlocker DB = DamBlocker.create(damage.mainTarget)
        if GetUnitAbilityLevel(damage.mainTarget, 'BNab') > 0 then
            call UnitRemoveAbility(damage.mainTarget, 'BNab')
        endif
        static if WANT_DAMAGE_BLOCK then
            if GetUnitAbilityLevel(DB.u, HP_BONUS_ABILITY) == 0 then
                set time = CreateTimer()
                call SaveInteger(Table, GetHandleId(time), 'blcr', DB)
                call TimerStart(time, 0.00, false, function DamBlocker.BlockDamage)
                set time = null
            endif
        endif
        call UnitAddAbility(damage.mainTarget, HP_BONUS_ABILITY)
        set max_hp = GetUnitState(damage.mainTarget, UNIT_STATE_MAX_LIFE)
        call SetWidgetLife(damage.mainTarget, max_hp)
        call UnitDamageTarget(damage.dummy, damage.mainTarget, TEST_DAMAGE_AMOUNT, true, false, damage.aType, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
        set damage.amount = (TEST_DAMAGE_AMOUNT/(max_hp - GetWidgetLife(damage.mainTarget)))*dam
        call SetWidgetLife(damage.mainTarget, max_hp)
        static if not WANT_DAMAGE_BLOCK then
            call UnitRemoveAbility(damage.mainTarget, HP_BONUS_ABILITY)
            call SetWidgetLife(damage.mainTarget, DB.health)
            call DamBlocker.destroy(DB)
        endif
    endfunction

    function DrawParabola takes real range, real arc, real x, real y, real tarx, real tary, real ofst returns nothing
        local real a
        local real b
        local real m = range*Sin(arc/2)
        local real parabola = (range-(range*Cos(arc/2 * bj_DEGTORAD)*Cos(arc/2 * bj_DEGTORAD)))/( 4.00 * Cos(arc/2 * bj_DEGTORAD))
        local integer count = 0
        local real tx = x
        local real ty = y
        local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(tary-y,tarx-x) + ofst*bj_DEGTORAD
        local real xFocus = x + parabola*Cos(init_ang)
        local real yFocus = y + parabola*Sin(init_ang)
        loop
            exitwhen count > 20
            set x = tx - Cos(init_ang)*parabola + Cos(init_ang + bj_PI/2)*(count - 10)*m/10
            set y = ty - Sin(init_ang)*parabola + Sin(init_ang + bj_PI/2)*(count - 10)*m/10
            set b = SquareRoot(4*parabola*parabola + (count - 10)*m*(count - 10)*m/100)
            set a = (init_ang + bj_PI/2) - Atan2(yFocus - y, xFocus - x)
            if a == bj_PI/2 then
                set a = parabola
            else
                set a = b*Cos(a)/Sin(2*a)
            endif
            set x = x + Cos(init_ang)*a
            set y = y + Sin(init_ang)*a
            call DestroyEffect(debug_effect[count])
            set debug_effect[count] = AddSpecialEffect("Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl", x, y)
            set count = count + 1
        endloop
    endfunction

    function ConfigureDamProp takes unit u, damagetype dType, attacktype aType, unit mainTarget, integer missile, boolean advanced returns MultiMisc_DamProperties
        local MultiMisc_DamProperties this
        if GetUnitTypeId(u) == DUMMY_ID then
            set this = LoadInteger(Table, GetHandleId(u), 'damp') + 0
            if this == 0 then
                set this = MultiMisc_DamProperties.create()
                call SaveInteger(Table, GetHandleId(u), 'damp', this)
            endif
            set this.dummy = u
            set this.source = udg_MS_Source
            set this.preDummy = true
        else
            set this = MultiMisc_DamProperties.create()
            set this.dummy = CreateUnit(GetOwningPlayer(u), DUMMY_ID, GetUnitX(u), GetUnitY(u), 0.00)
            call SaveInteger(Table, GetHandleId(this.dummy), 'damp', this)
            call UnitApplyTimedLife(this.dummy, 'BTLF', MAX_DUMMY_LIFE_TIME + 0.05)
            set this.source = u
            set this.preDummy = false
        endif
        call UnitAddAbility(this.dummy, missile)
        set this.dType = dType
        set this.aType = aType
        set this.mainTarget = mainTarget
        set this.missile = missile
        set this.advanced = advanced
        call SaveInteger(Table, GetHandleId(this.clearer), 'dmid', GetHandleId(this.dummy))
        call TimerStart(this.clearer, MAX_DUMMY_LIFE_TIME, false, function MultiMisc_ClearLeaks)
        return this
    endfunction

    function ShootAllTargets takes MultiMisc_DamProperties damage, real range, real arc, real tarx, real tary, real ofst, boolexpr condition, real speed returns nothing
        local real x = GetUnitX(damage.dummy)
        local real y = GetUnitY(damage.dummy)
        local player p = GetOwningPlayer(damage.source)
        local real xTarget
        local real yTarget
        local unit FoG
        local real parabola = (range-(range*Cos(arc/2 * bj_DEGTORAD)*Cos(arc/2 * bj_DEGTORAD)))/( 4.00 * Cos(arc/2 * bj_DEGTORAD))
        local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(tary-y,tarx-x) + ofst*bj_DEGTORAD
        local real xFocus = x + parabola*Cos(init_ang)
        local real yFocus = y + parabola*Sin(init_ang)
        local real tempAngle
        local boolean b
        call GroupEnumUnitsInRange(g, x, y, range + MAX_COLLISION_SIZE, condition)
        if damage.mainTarget != null and IsUnitInGroup(damage.mainTarget, damage.shotGroup) then
            call GroupRemoveUnit(g, damage.mainTarget)
        endif
        call GroupRemoveUnit(g, damage.source)
        loop
            set FoG = FirstOfGroup(g)
            exitwhen FoG == null
            set xTarget = GetUnitX(FoG)
            set yTarget = GetUnitY(FoG)
            set tempAngle = Atan2(yTarget-yFocus,xTarget-xFocus) - init_ang
            if condition == null then
                set b = IsUnitEnemy(FoG, p) and not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            else
                set b = not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            endif
            if b then
                if speed > 0 then
                    static if LIBRARY_AdvancedMultishot then
                        call MS_Shoot(damage, x, y, speed, FoG)
                    endif
                else
                    call IssueTargetOrder(damage.dummy, SPELL_ORDER, FoG)
                endif
                call GroupAddUnit(damage.shotGroup, FoG)
                set damage.unitsShot = damage.unitsShot + 1
            endif
            call GroupRemoveUnit(g, FoG)
        endloop
    endfunction

    function ShootRandomTargets takes MultiMisc_DamProperties damage, integer targets, real range, real arc, real tarx, real tary, real ofst, boolexpr condition, real speed returns nothing
        local integer left_c = 0
        local integer right_c = 0
        local unit array units
        local real x = GetUnitX(damage.dummy)
        local real y = GetUnitY(damage.dummy)
        local player p = GetOwningPlayer(damage.source)
        local unit FoG
        local integer dice
        local real xTarget
        local real yTarget
        local real parabola = (range-(range*Cos(arc/2 * bj_DEGTORAD)*Cos(arc/2 * bj_DEGTORAD)))/( 4.00 * Cos(arc/2 * bj_DEGTORAD))
        local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(tary-y,tarx-x) + ofst*bj_DEGTORAD
        local real xFocus = x + parabola*Cos(init_ang)
        local real yFocus = y + parabola*Sin(init_ang)
        local real tempAngle
        local boolean b
        if damage.mainTarget != null and IsUnitInGroup(damage.mainTarget, damage.shotGroup) then
            call GroupRemoveUnit(g, damage.mainTarget)
            set targets = targets - 1
        endif
        call GroupEnumUnitsInRange(g, x, y, range + MAX_COLLISION_SIZE, condition)
        call GroupRemoveUnit(g, damage.source)
        loop
            set FoG = FirstOfGroup(g)
            exitwhen FoG == null
            set xTarget = GetUnitX(FoG)
            set yTarget = GetUnitY(FoG)
            set tempAngle = Atan2(yTarget-yFocus,xTarget-xFocus) - init_ang
            if R2I(tempAngle/(2*bj_PI)) != 0 then
                set tempAngle = ModuloReal(tempAngle, 2*bj_PI)
            endif
            if condition == null then
                set b = IsUnitEnemy(FoG, p) and not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            else
                set b = not IsUnitType(FoG, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(tempAngle)))*(( 2.00*parabola)/( 1 - Cos(tempAngle))) and IsUnitInRangeXY(FoG, x, y, range)
            endif
            if b then
                if tempAngle >= 0 and tempAngle < bj_PI then
                    set left_c = left_c + 1
                    set dice = GetRandomInt(1, left_c)
                    if dice <= targets and ( dice <= (targets + 1)/2 or (units[left_c] == null and left_c <= targets)) then
                        if left_c <= targets and ( left_c <= (targets + 1)/2 or units[left_c] == null ) then
                            set units[left_c] = units[dice]
                        endif
                        set units[dice] = FoG
                    endif
                else
                    set right_c = right_c + 1
                    set dice = GetRandomInt(1, right_c)
                    if dice <= targets and ( dice <= (targets + 1)/2 or (units[targets + 1 - right_c] == null and right_c <= targets)) then
                        if right_c <= targets and ( right_c <= (targets + 1)/2 or units[targets + 1 - right_c] == null ) then
                            set units[targets + 1 - right_c] = units[targets + 1 - dice]
                        endif
                        set units[targets + 1 - dice] = FoG
                    endif
                endif
            endif
            call GroupRemoveUnit(g, FoG)
        endloop
        set dice = 1
        loop
            exitwhen dice > targets
            if units[dice] != null then
                if speed > 0 then
                    static if LIBRARY_AdvancedMultishot then
                        call MS_Shoot(damage, x, y, speed, units[dice])
                    endif
                else
                    call IssueTargetOrder(damage.dummy, SPELL_ORDER, units[dice])
                endif
                call GroupAddUnit(damage.shotGroup, units[dice])
                set damage.unitsShot = damage.unitsShot + 1
                set units[dice] = null
            elseif targets - right_c > dice then
                set dice = targets - right_c - 1
            endif
            set dice = dice + 1
        endloop
    endfunction

    function MultishotTarget takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType, boolexpr condition returns nothing
        local MultiMisc_DamProperties damage
        if not Damage then
            static if DEBUG_MULTISHOT then
                call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
            endif
            set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
            if fixedDamage then
                set damage.amount = initialDamage
            else
                call GetBaseDamage(damage, initialDamage)
            endif
            if alwaysHitsMainTarget then
                call IssueTargetOrder(damage.dummy, SPELL_ORDER, damage.mainTarget)
                call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                set damage.unitsShot = damage.unitsShot + 1
                if targets == 1 then
                    set udg_MS_Current_Group = damage.shotGroup
                    set udg_MS_UnitsInGroup = damage.unitsShot
                    set udg_MS_Dummy = damage.dummy
                    return
                endif
            endif
            if targets == 0 or targets > 1000 then
                call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, 0.00)
            else
                call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, 0.00)
            endif
            set udg_MS_Current_Group = damage.shotGroup
            set udg_MS_UnitsInGroup = damage.unitsShot
            set udg_MS_Dummy = damage.dummy
        endif
    endfunction

    function MultishotTargetAdvanced takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType, boolexpr condition, real shotSpeed returns nothing
        local MultiMisc_DamProperties damage
        static if LIBRARY_AdvancedMultishot then
            if not Damage then
                static if DEBUG_MULTISHOT then
                    call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
                endif
                set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
                if fixedDamage then
                    set damage.amount = initialDamage
                else
                    call GetBaseDamage(damage, initialDamage)
                endif
                if alwaysHitsMainTarget then
                    call MS_Shoot(damage, GetUnitX(damage.dummy), GetUnitY(damage.dummy), shotSpeed, multiTarget)
                    call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                    set damage.unitsShot = damage.unitsShot + 1
                    if targets == 1 then
                        set udg_MS_Current_Group = damage.shotGroup
                        set udg_MS_UnitsInGroup = damage.unitsShot
                        set udg_MS_Dummy = damage.dummy
                        return
                    endif
                endif
                if targets == 0 or targets > 1000 then
                    call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, shotSpeed)
                else
                    call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, condition, shotSpeed)
                endif
                set udg_MS_Current_Group = damage.shotGroup
                set udg_MS_UnitsInGroup = damage.unitsShot
                set udg_MS_Dummy = damage.dummy
            endif
        else
            call MultishotTarget(shooter, multiTarget, initialDamage, targets, arc, initRange, alwaysHitsMainTarget, attackType, missileArt, offsetAngle, fixedDamage, damageType, condition)
        endif
    endfunction

    function MultishotPoint takes unit shooter, real initialDamage, integer targets, real arc, real initRange, attacktype attackType, integer missileArt, real offsetAngle, damagetype damageType, real xSpell, real ySpell, boolexpr condition returns nothing
        local MultiMisc_DamProperties damage
        static if DEBUG_MULTISHOT then
            call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), xSpell, ySpell, offsetAngle)
        endif
        set damage = ConfigureDamProp(shooter, damageType, attackType, null, missileArt, false)
        set damage.amount = initialDamage
        if targets == 0 or targets > 1000 then
            call ShootAllTargets(damage, initRange, arc, xSpell, ySpell, offsetAngle, condition, 0.00)
        else
            call ShootRandomTargets(damage, targets, initRange, arc, xSpell, ySpell, offsetAngle, condition, 0.00)
        endif
        set udg_MS_Current_Group = damage.shotGroup
        set udg_MS_UnitsInGroup = damage.unitsShot
        set udg_MS_Dummy = damage.dummy
    endfunction

    function MultishotPointAdvanced takes unit shooter, real initialDamage, integer targets, real arc, real initRange, attacktype attackType, integer missileArt, real offsetAngle, damagetype damageType, real xSpell, real ySpell, boolexpr condition, real shotSpeed returns nothing
        local MultiMisc_DamProperties damage
        static if LIBRARY_AdvancedMultishot then
            static if DEBUG_MULTISHOT then
                call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), xSpell, ySpell, offsetAngle)
            endif
            set damage = ConfigureDamProp(shooter, damageType, attackType, null, missileArt, false)
            set damage.amount = initialDamage
            if targets == 0 or targets > 1000 then
                call ShootAllTargets(damage, initRange, arc, xSpell, ySpell, offsetAngle, condition, shotSpeed)
            else
                call ShootRandomTargets(damage, targets, initRange, arc, xSpell, ySpell, offsetAngle, condition, shotSpeed)
            endif
            set udg_MS_Current_Group = damage.shotGroup
            set udg_MS_UnitsInGroup = damage.unitsShot
            set udg_MS_Dummy = damage.dummy
        else
            call MultishotPoint(shooter, initialDamage, targets, arc, initRange, attackType, missileArt, offsetAngle, damageType, xSpell, ySpell, condition)
        endif
    endfunction

    function MultishotLight takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType returns nothing
        local MultiMisc_DamProperties damage
        if not Damage then
            static if DEBUG_MULTISHOT then
                call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
            endif
            set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
            if fixedDamage then
                set damage.amount = initialDamage
            else
                call GetBaseDamage(damage, initialDamage)
            endif
            if alwaysHitsMainTarget then
                call IssueTargetOrder(damage.dummy, SPELL_ORDER, damage.mainTarget)
                call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                set damage.unitsShot = damage.unitsShot + 1
                if targets == 1 then
                    set udg_MS_Current_Group = damage.shotGroup
                    set udg_MS_UnitsInGroup = damage.unitsShot
                    set udg_MS_Dummy = damage.dummy
                    return
                endif
            endif
            if targets == 0 or targets > 1000 then
                call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, 0.00)
            else
                call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, 0.00)
            endif
            set udg_MS_Current_Group = damage.shotGroup
            set udg_MS_UnitsInGroup = damage.unitsShot
            set udg_MS_Dummy = damage.dummy
        endif
    endfunction

    function MultishotLightAdvanced takes unit shooter, unit multiTarget, real initialDamage, integer targets, real arc, real initRange, boolean alwaysHitsMainTarget, attacktype attackType, integer missileArt, real offsetAngle, boolean fixedDamage, damagetype damageType, real shotSpeed returns nothing
        static if LIBRARY_AdvancedMultishot then
            local MultiMisc_DamProperties damage
            if not Damage then
                static if DEBUG_MULTISHOT then
                    call DrawParabola(initRange, arc, GetUnitX(shooter), GetUnitY(shooter), GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle)
                endif
                set damage = ConfigureDamProp(shooter, damageType, attackType, multiTarget, missileArt, false)
                if fixedDamage then
                    set damage.amount = initialDamage
                else
                    call GetBaseDamage(damage, initialDamage)
                endif
                if alwaysHitsMainTarget then
                    call MS_Shoot(damage, GetUnitX(damage.dummy), GetUnitY(damage.dummy), shotSpeed, multiTarget)
                    call GroupAddUnit(damage.shotGroup, damage.mainTarget)
                    set damage.unitsShot = damage.unitsShot + 1
                    if targets == 1 then
                        set udg_MS_Current_Group = damage.shotGroup
                        set udg_MS_UnitsInGroup = damage.unitsShot
                        set udg_MS_Dummy = damage.dummy
                        return
                    endif
                endif
                if targets == 0 or targets > 1000 then
                    call ShootAllTargets(damage, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, shotSpeed)
                else
                    call ShootRandomTargets(damage, targets, initRange, arc, GetUnitX(multiTarget), GetUnitY(multiTarget), offsetAngle, null, shotSpeed)
                endif
                set udg_MS_Current_Group = damage.shotGroup
                set udg_MS_UnitsInGroup = damage.unitsShot
                set udg_MS_Dummy = damage.dummy
            endif
        else
            call MultishotLight(shooter, multiTarget, initialDamage, targets, arc, initRange, alwaysHitsMainTarget, attackType, missileArt, offsetAngle, fixedDamage, damageType)
        endif
    endfunction

//! textmacro MulthishotOnHit takes SOURCE, VICTIM, AMOUNT, EVENT

// Checks if a unit, that has taken damage, has the Buff, applied by the Multishot. If it has it - applies the MS damage.
// When a unit is hit by the multishot's missile - the buff is removed, and the damage is applied
// It also sets a variable to 0.00 and then to 1.00 for the Extra triggers to run.
    private function MultishotDelay takes nothing returns boolean
        local MultiMisc_DamProperties damage
        if GetUnitAbilityLevel($VICTIM$, BUFF_APPLIED_ID ) > 0 then
            call UnitRemoveAbility( $VICTIM$, BUFF_APPLIED_ID )
            static if AdvancedMultishot_ALWAYS_USE_ADVANCED then
                if damage.advanced then
                    return false
                endif
            endif
            set damage = LoadInteger(Table, GetHandleId($SOURCE$), 'damp')
            set udg_MS_Damage = damage.amount
            if WANT_MULTISHOT_HIT_EVENT then
                set udg_MS_Dummy = $SOURCE$
                set udg_MS_Hit_Unit = $VICTIM$
                set udg_MS_Source = damage.source
                set udg_MS_Current_Group = damage.shotGroup
                set udg_MS_Main_Target = damage.mainTarget
                set udg_MS_Missile = damage.missile
                set udg_MS_UnitsInGroup = damage.unitsShot
                set udg_MS_Hit_Event = 1.00
                set udg_MS_Hit_Event = 0.00
            endif
            set Damage = true
            call UnitDamageTarget( udg_MS_Source, $VICTIM$, udg_MS_Damage, true, true, damage.aType, damage.dType, null )
            set Damage = false
            set damage.alreadyShot = damage.alreadyShot + 1
            if damage.alreadyShot == damage.unitsShot then
                call TimerStart(damage.clearer, 0.00, false, function MultiMisc_ClearLeaks)
            endif
        endif
        return false
    endfunction

// Initiation Trigger :p
    function Init takes nothing returns nothing
        set gg_trg_Multishot_v2_2_c = CreateTrigger(  )
        call TriggerRegisterVariableEvent( gg_trg_Multishot_v2_2_c, "$EVENT$", EQUAL, 0 ) // change the 0 to something else if needed.
        call TriggerAddCondition( gg_trg_Multishot_v2_2_c, Condition( function MultishotDelay ) )
    endfunction
//! endtextmacro

//! runtextmacro MulthishotOnHit("udg_DamageEventSource","udg_DamageEventTarget","udg_DamageEventAmount","udg_DamageEvent")

endlibrary

JASS:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*  ----------------------- ORBS = ADD-ON LIBRARY FOR WereElf's CUSOM MULTISHOT (By WereElf) ------------------------

This library allows MS to work with Orb effects for units with the Multishot ability. When it's used - ALL the units,
shot get affected by the orb effect.
The way it works is simple: When a unit is hit by Multishot - this library checks if the hit was done by a "special"
missile type. If it is - it summons a SPECIAL DUMMY, with enabled attack, gives it the right orb ability, and makes it
attack the target. A trigger is created. When the unit takes damage from the special dummy - the dummy is ordered to stop,
the trigger is destroyed, and the dummy is given a timed life.

NOTE: don't use the missile abilities, used for the orbs for normal Multishot, UNLESS you want the units' normal attacks
to act like the orbs.

REQUIREMENTS:
1) WereElf's Custom Multishot library (ofcourse)
2) A SPECIAL dummy, with attack enabled!!
3) Object editor:
- Each orb (of the ones listed below), which you want to use on your map must have 2 versions - a normal one (which is
already there), and one custom, for the Multishot
- Each orb type needs TWO custom orb abilities - 1 for the dummy, and one for the item.
* For the dummy - it should have its damage bonus set to 0.
* For the item - it needs to have its properties WITHOUT the effect of the orb. Since we don't want the orb effect
to apply before the shot has reached the unit.
- Each orb type requires a missile ability, with different Id, and different missile art (the orb's missile art)
4) You need to make a trigger: when a unit, which uses the multishot picks up an orb - replace it with the custom orb.
And when such orb is dropped - replace it with normal one.
5) Give the orb abilities, with no effect (just damage bonus) to the items.
6) The WANT_MULTISHOT_HIT_EVENT in the Multishot library must be set to true.
7) When all of the above is done - set the variables in the globals block below.

HOW TO USE:
- Before calling the Multishot function for a hero (or a unit with inventory) - call "GetActualMissile" function, and
give as arguments ( the shooter, the Id of the unit's default missile )
*/
library OrbsAddOn initializer Init
    globals
// The ID of the SPECIAL dummy (you need to make a dummy with enabled attack)
        constant integer ORB_DUMMY = 'h00F'
// How much life do you want mask of death to steal on units with multishot
        constant real LIFE_STEAL = 0.20 // from each unit hit

/// The IDs of the Orb ITEMS.
        constant integer ORB_OF_FROST = 'I001'
        constant integer ORB_OF_CORRUPTION = 'I003'
        constant integer ORB_OF_DARKNESS = 'I000'
        constant integer ORB_OF_LIGHTNING = 'I005'
        constant integer ORB_OF_FIRE = 'I002'
        constant integer ORB_OF_SLOW = 'I006'
        constant integer ORB_OF_VENOM = 'I007'
        constant integer MASK_OF_DEATH = 'I004'

// The IDs of the Orb MISSILES.
        constant integer FROST_MISSILE = 'A035'
        constant integer CORRUPTION_MISSILE = 'A032'
        constant integer DARKNESS_MISSILE = 'A033'
        constant integer LIGHTNING_MISSILE = 'A036'
        constant integer FIRE_MISSILE = 'A034'
        constant integer SLOW_MISSILE = 'A037'
        constant integer VENOM_MISSILE = 'A038'
        constant integer LIFE_STEAL_MISSILE = 'A031'

// The IDs of the Dummy Orbs (with the effect, without the damage bonus)
        constant integer DUMMY_FROST = 'A03B'
        constant integer DUMMY_CORRUPTION = 'A03A'
        constant integer DUMMY_DARKNESS = 'A039'
        constant integer DUMMY_LIGHTNING = 'A03C'
        constant integer DUMMY_SLOW = 'A03E'
        constant integer DUMMY_VENOM = 'A03D'

// Used for triggering the Orb of Fire
        private group FireGroup = CreateGroup()
    endglobals

// When this function is called - it checks all of the inventory slots if they contain orbs.
// If an orb is found - the missile Id of the orb is returned (and the loop stops)
// If no orb is found - it returns the unit's default missile.
    function GetActualMissile takes unit u, integer default_missile returns integer
        local integer i = 0
        local integer j
        loop
            exitwhen i > bj_MAX_INVENTORY
            set j = GetItemTypeId(UnitItemInSlot(u, i))
            if j == ORB_OF_FROST then
                return FROST_MISSILE
            elseif j == ORB_OF_CORRUPTION then
                return CORRUPTION_MISSILE
            elseif j == ORB_OF_DARKNESS then
                return DARKNESS_MISSILE
            elseif j == ORB_OF_LIGHTNING then
                return LIGHTNING_MISSILE
            elseif j == ORB_OF_FIRE then
                return FIRE_MISSILE
            elseif j == ORB_OF_VENOM then
                return VENOM_MISSILE
            elseif j == ORB_OF_SLOW then
                return SLOW_MISSILE
            elseif j == MASK_OF_DEATH then
                return LIFE_STEAL_MISSILE
            endif
            set i = i + 1
        endloop
        return default_missile
    endfunction

// ORB OF FIRE EFFECT
// Simply triggers the splash.
    private function OrbOfFire takes nothing returns nothing
        local real x = GetUnitX(udg_MS_Hit_Unit)
        local real y = GetUnitY(udg_MS_Hit_Unit)
        local unit u
        local player p = GetOwningPlayer(udg_MS_Source)
        call GroupEnumUnitsInRange(FireGroup, x, y, 175, null)
        loop
            set u = FirstOfGroup(FireGroup)
            exitwhen u == null
            if u != udg_MS_Hit_Unit and IsUnitEnemy( u , p ) then
                call UnitDamageTarget(udg_MS_Dummy, u, udg_MS_Damage/5, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            endif
            call GroupRemoveUnit(FireGroup, u)
        endloop
        set p = null
    endfunction

// LIFE STEAL EFFECT
// Heals the shooter for % of the damage dealt. Special effect appears only when the main target is hit,
    private function LifeSteal takes nothing returns nothing
        local real hp = GetWidgetLife(udg_MS_Source)
        call SetWidgetLife(udg_MS_Source, hp + LIFE_STEAL*udg_MS_Damage)
        if udg_MS_Hit_Unit == udg_MS_Main_Target then
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\VampiricAura\\VampiricAuraTarget.mdl", udg_MS_Source, "origin"))
        endif
    endfunction

// Orb function caller
// When the special dummy hits the target unit - the trigger is destroyed, and the dummy is given 0.01 sec timed life.
    private function DummyHit takes nothing returns boolean
        if GetUnitTypeId(udg_DamageEventSource) == ORB_DUMMY then
            call IssueImmediateOrder(udg_DamageEventSource, "stop")
            call UnitApplyTimedLife(udg_DamageEventSource, 'BTLF', 0.01)
        endif
        return false
    endfunction

// When a unit is hit by the Multishot - it checks if the missile is any of the orb missiles.
// If it is - it summons the special dummy, gives it the orb ability, and makes it attack the unit.
// It also creates a trigger to register when the dummy hits the unit.
// Unless it's orb of fire. In that case - it calls the function above.

//! textmacro OrbDummy takes TYPE, EXTRA
set d = CreateUnit(GetOwningPlayer(udg_MS_Source), ORB_DUMMY, GetUnitX(udg_MS_Hit_Unit) - 50, GetUnitY(udg_MS_Hit_Unit), 0.00)
call UnitApplyTimedLife(d, 'BTLF', 1)
call UnitAddAbility( d, DUMMY_$TYPE$ )
$EXTRA$
call IssueTargetOrder( d, "attack", udg_MS_Hit_Unit )
set d = null
//! endtextmacro

    private function Trig_On_Hit_Actions takes nothing returns boolean
        local unit d
        if udg_MS_Missile == FIRE_MISSILE then
            call OrbOfFire()
            return false
        elseif udg_MS_Missile == LIFE_STEAL_MISSILE then
            call LifeSteal()
            return false
        endif
        if udg_MS_Missile == CORRUPTION_MISSILE then
            //! runtextmacro OrbDummy("CORRUPTION","")
        elseif udg_MS_Missile == DARKNESS_MISSILE then
            //! runtextmacro OrbDummy("DARKNESS","")
        elseif udg_MS_Missile == LIGHTNING_MISSILE then
            //! runtextmacro OrbDummy("LIGHTNING","")
        elseif udg_MS_Missile == FROST_MISSILE then
            //! runtextmacro OrbDummy("FROST","")
        elseif udg_MS_Missile == SLOW_MISSILE then
            //! runtextmacro OrbDummy("SLOW","")
        elseif udg_MS_Missile == VENOM_MISSILE then
            //! runtextmacro OrbDummy("VENOM","call UnitAddAbility( d, 'Apo2' )")
        endif
        return false
    endfunction

//===========================================================================
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        set gg_trg_Custom_Orb_Effects = CreateTrigger()
        call TriggerRegisterVariableEvent( gg_trg_Custom_Orb_Effects, "udg_MS_Hit_Event", EQUAL, 0 )
        call TriggerAddCondition( gg_trg_Custom_Orb_Effects, Condition(function Trig_On_Hit_Actions) )
        call TriggerRegisterVariableEvent( t, "udg_DamageEvent", EQUAL, 0 )
        call TriggerAddCondition( t, Condition(function DummyHit))
        set t = null
    endfunction

endlibrary

JASS:
//! novjass
MultishotTarg (both normal and the advanced) take:
1 (unit) - Shooter - who is shooting
2 (unit) - Target - who is the original target of the shot
3 (real) - Damage - the damage the shot should deal
4 (integer) - Targets - how many targets its allowed to shoot at once
5 (real) - Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want ALL the units in range to get shot
6 (real) - Range - the range multishot chooses enemies from.
7 (boolean) - Always hits main targer - does the main target always get shot, or it only has a chance to get shot (if more than the max amount of units are in range)
8 (attacktype) - Attack type - the attack type of the shooter. Its used for correct armor estimation, and thats the type of damage dealt in the end.
9 (integer) - Missile ability - the ability id of the Multishots missile.
10 (real) - Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45, multishot will act like the target is at 90 degrees.
11 (boolean) - Fixed damage - if you want to deal some fixed value of damage, not based on the damage dealt - set this to true, otherwise - false.
12 (damagetype) - Damage type - if "Fixed damage" is set to false - this will act as "DAMAGE_TYPE_NORMAL", no matter what. Otherwise - this is the damage type multishot deals.
13 (boolexpr) - Conditions - What units shall it target
'NEXT ONE IS ONLY FOR THE ADVANCED VERSION'
14 (real) - Missile speed - simply put-in the missiles speed

MultishotPoint (both normal and the advanced) take:
1 (unit) Caster - who is shooting
2 (real) Damage - the damage dealt
3 (integer) Targets - how many targets its allowed to shoot at once
4 (real) Arc - this determines the width of the area multishot chooses targets from. Set it to 180 if you want ALL the units in range to get shot
5 (real) Range - the range multishot chooses enemies from
6 (attacktype) Attack Type - the attack type of the damage dealt
7 (integer) Missile ability - the ability id of the Multishots missile
8 (real) Offset angle - (example:) if the main target is at 45 deg from the shooter, if this is set to 45, multishot will act like the target is at 90 degrees
9 (damagetype) Damage type - the damage type of the damage dealth
10 (real) Target X - the x of the targeted point
11 (real) Target Y - the y of the targeted point
12 (boolexpr) - Conditions - What units shall it target
'NEXT ONE IS ONLY FOR THE ADVANCED VERSION'
13 (real) Missile speed - simply put-in the missiles speed

MultishotBasic takes the same arguments as MultishotTarget, without the boolexpr. Its set to target only enemies.
//! endnovjass

library MultishoRt requires Multishot, optional OrbsAddOn

    globals
        private player ConditionPlayer
    endglobals

    function FilterEnemies takes nothing returns boolean
        return IsUnitEnemy(GetFilterUnit(), ConditionPlayer)
    endfunction
 
    function FilterAllies takes nothing returns boolean
        return IsUnitAlly(GetFilterUnit(), ConditionPlayer) and GetFilterUnit() != GetTriggerUnit()
    endfunction

    function MultishotNormal takes integer targets, real arc, real range, attacktype attackType, integer missile returns nothing
        call MultishotLightAdvanced(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventAmount, targets, arc, range, true, attackType, missile, 0.00, false, DAMAGE_TYPE_NORMAL, 900.00)
    endfunction

    function MultishotSpell takes unit caster, unit target, integer targets, real arc, real range, real damage, integer missile returns nothing
        set ConditionPlayer = GetOwningPlayer(caster)
        call MultishotTarget(caster, target, damage, targets, arc, range, true, ATTACK_TYPE_NORMAL, missile, 0.00, true, DAMAGE_TYPE_MAGIC, Condition(function FilterEnemies))
    endfunction
 
    function MultishotPointShort takes unit caster, real x, real y, real damage, integer missile returns nothing
        set ConditionPlayer = GetOwningPlayer(caster)
        call MultishotPoint(caster, damage, 0, 25, 1000, ATTACK_TYPE_NORMAL, missile, 0.00, DAMAGE_TYPE_MAGIC, x, y, Condition(function FilterEnemies))
    endfunction
 
    function MultishotHeal takes unit caster, location loc, real amount, integer missile returns nothing
        set ConditionPlayer = GetOwningPlayer(caster)
        call MultishotPointAdvanced(caster, amount, 0, 90, 500, ATTACK_TYPE_NORMAL, missile, 0.00, DAMAGE_TYPE_NORMAL, GetLocationX(loc), GetLocationY(loc), Condition(function FilterAllies), 500.00)
    endfunction
 
    function MultishotOrb takes integer targets, real arc, real range, integer missile returns nothing
        local real speed
        local integer temp = missile
        static if LIBRARY_OrbsAddOn then
            set missile = GetActualMissile(udg_DamageEventSource, missile)
            if missile == temp then
                set speed = 900
            else
                set speed = 1050
            endif
        else
            set speed = 900
        endif
        set ConditionPlayer = GetOwningPlayer(udg_DamageEventSource)
        call MultishotLightAdvanced(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventAmount, targets, arc, range, true, ATTACK_TYPE_HERO, missile, 0.00, false, DAMAGE_TYPE_NORMAL, speed)
    endfunction
 
endlibrary

Demo triggers/Special:
JASS:
scope BouncingLight2

globals
    private player CondPlayer
    private group CondGroup
    constant integer MAX_TARGETS_ALLOWED = 2 + GetUnitAbilityLevel(udg_MS_Source, 'A03N')
endglobals

private function BouncingLightCond takes nothing returns boolean
    return (IsUnitEnemy(GetFilterUnit(), CondPlayer) and not IsUnitInGroup(GetFilterUnit(), CondGroup))
endfunction

function Trig_Bouncing_Light_2_Conditions takes nothing returns boolean
    local real x
    local real y
    local integer pl
    local real a
    local integer t
    if udg_MS_Missile == 'A012' then
        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        set pl = GetPlayerId(CondPlayer)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        set CondGroup = udg_MS_Current_Group
        if GetRandomInt(1, 100) <= 10 then
            set t = 2
        else
            set t = 1
        endif
        call MultishotPointAdvanced(udg_MS_Dummy, 400, t, 180, 300, ATTACK_TYPE_CHAOS, 'A012', 0.00, DAMAGE_TYPE_UNIVERSAL, x + 100, y + 100, Condition(function BouncingLightCond), 900.00)
    elseif udg_MS_Missile == 'A02X' and udg_MS_UnitsInGroup < MAX_TARGETS_ALLOWED then
        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        set CondGroup = udg_MS_Current_Group
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        call MultishotPointAdvanced(udg_MS_Dummy, udg_MS_Damage*0.8*(1.10+(0.10*GetUnitAbilityLevel(udg_MS_Source, 'A03N'))), 1, 180, 300, ATTACK_TYPE_PIERCE, 'A02X', 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond),900.00)
    /*elseif GetUnitTypeId(udg_MS_Source) == 'N00D' and udg_MS_UnitsInGroup < MAX_TARGETS_ALLOWED then
        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        set CondGroup = udg_MS_Current_Group
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        call MultishotPointAdvanced(udg_MS_Dummy, udg_MS_Damage*0.8, 1, 180, 300, ATTACK_TYPE_HERO, udg_MS_Missile, 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond),900.00)
    */endif
    return false
endfunction

//===========================================================================
function InitTrig_Special takes nothing returns nothing
    set gg_trg_Special = CreateTrigger(  )
    call TriggerRegisterVariableEvent( gg_trg_Special, "udg_MS_Hit_Event", EQUAL, 0 )
    call TriggerAddCondition( gg_trg_Special, Condition( function Trig_Bouncing_Light_2_Conditions ) )
endfunction

endscope

Caller triggers:
  • Multishot Caller
    • Events
      • Game - DamageEvent becomes Equal to 1.00
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • (Level of Death Pact (Drow Summoner) for DamageEventSource) Greater than (>) 0
    • Actions
      • Custom script: local integer lvl
      • Custom script: set lvl = GetUnitAbilityLevel(udg_DamageEventSource, 'A02I')
      • Set SoulRelease_Level = (Level of Death Pact (Drow Summoner) for DamageEventSource)
      • Set SoulRelease_Souls = (Load 0 of (Key SoulRelease_Handle) from SoulRelease_Hashtable)
      • Set MS_Damage = (MS_Damage + ((2.00 x (Real(SoulRelease_Level))) x (Real(SoulRelease_Souls))))
      • Custom script: call MultishotOrb( 1, 00.00, 600.00, 'A02Z')
      • Custom script: if not Multishot_Damage then
      • Set DamageEventAmount = 0.00
      • Set DamageEventType = DamageTypeBlocked
      • Custom script: endif
  • Multishot Caller
    • Events
      • Game - DamageEvent becomes Equal to 1.00
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • (Level of Death Pact (Drow Summoner) for DamageEventSource) Greater than (>) 0
    • Actions
      • Custom script: local integer lvl
      • Custom script: set lvl = GetUnitAbilityLevel(udg_DamageEventSource, 'A02I')
      • Set SoulRelease_Level = (Level of Death Pact (Drow Summoner) for DamageEventSource)
      • Set SoulRelease_Souls = (Load 0 of (Key SoulRelease_Handle) from SoulRelease_Hashtable)
      • Set MS_Damage = (MS_Damage + ((2.00 x (Real(SoulRelease_Level))) x (Real(SoulRelease_Souls))))
      • Custom script: call MultishotOrb( 1, 00.00, 600.00, 'A02Z')
      • Custom script: if not Multishot_Damage then
      • Set DamageEventAmount = 0.00
      • Set DamageEventType = DamageTypeBlocked
      • Custom script: endif
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I think you should change this...
  • Multishot Caller
    • Events
      • Game - DamageEvent becomes Equal to 1.00
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • (Level of Death Pact (Drow Summoner) for DamageEventSource) Greater than (>) 0
    • Actions
      • Custom script: local integer lvl
      • Custom script: set lvl = GetUnitAbilityLevel(udg_DamageEventSource, 'A02I')
      • Set SoulRelease_Level = (Level of Death Pact (Drow Summoner) for DamageEventSource)
      • Set SoulRelease_Souls = (Load 0 of (Key SoulRelease_Handle) from SoulRelease_Hashtable)
      • Set MS_Damage = (MS_Damage + ((2.00 x (Real(SoulRelease_Level))) x (Real(SoulRelease_Souls))))
      • Custom script: call MultishotOrb( 1, 00.00, 600.00, 'A02Z')
      • Custom script: if not Multishot_Damage then
      • Set DamageEventAmount = 0.00
      • Set DamageEventType = DamageTypeBlocked
      • Custom script: endif
into this
  • Multishot Caller
    • Events
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • Unit ype of DamageEventSource equal to DrowSummoner
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Level of Death Pact (Drow Summoner) for DamageEventSource) Greater than (>) 0
        • Then - Actions
          • Set SoulRelease_Level = (Level of Death Pact (Drow Summoner) for DamageEventSource)
          • Set SoulRelease_Souls = (Load 0 of (Key SoulRelease_Handle) from SoulRelease_Hashtable)
          • Set MS_Damage = (MS_Damage + ((2.00 x (Real(SoulRelease_Level))) x (Real(SoulRelease_Souls))))
          • Custom script: call MultishotOrb( 1, udg_MS_Damage, 00.00, 600.00, 'A02Z')
        • Else - Actions
          • Custom script: call MultishotOrb( 1, udg_DamageEventAmount, 00.00, 600.00, 'A02Z')
      • Custom script: if not Multishot_Damage then
      • Set DamageEventAmount = 0.00
      • Set DamageEventType = DamageTypeBlocked
      • Custom script: endif
And change the MultishotOrb function from:
JASS:
    function MultishotOrb takes integer targets, real arc, real range, integer missile returns nothing
        local real speed
        local integer temp = missile
        static if LIBRARY_OrbsAddOn then
            set missile = GetActualMissile(udg_DamageEventSource, missile)
            if missile == temp then
                set speed = 900
            else
                set speed = 1050
            endif
        else
            set speed = 900
        endif
        set ConditionPlayer = GetOwningPlayer(udg_DamageEventSource)
        call MultishotLightAdvanced(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventAmount, targets, arc, range, true, ATTACK_TYPE_HERO, missile, 0.00, false, DAMAGE_TYPE_NORMAL, speed)
    endfunction
to:
JASS:
    function MultishotOrb takes integer targets, real damage, real arc, real range, integer missile returns nothing
        local real speed
        local integer temp = missile
        static if LIBRARY_OrbsAddOn then
            set missile = GetActualMissile(udg_DamageEventSource, missile)
            if missile == temp then
                set speed = 900
            else
                set speed = 1050
            endif
        else
            set speed = 900
        endif
        set ConditionPlayer = GetOwningPlayer(udg_DamageEventSource)
        call MultishotLightAdvanced(udg_DamageEventSource, udg_DamageEventTarget, damage, targets, arc, range, true, ATTACK_TYPE_HERO, missile, 0.00, false, DAMAGE_TYPE_NORMAL, speed)
    endfunction

Don't forget that the MultishoRt library is there just to make your life easier, so you don't have to give 13 arguments every time.
Feel free to add, remove, and edit functions in it whenever you need.
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
The first five functions of Multishot Caller were disabled, I thought trigger-tagging it here would detect that. At any rate, I deleted those lines altogether but the problem persisted.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I checked your map (again). I think the easiest way to get your problems solved would be if you give me a DETAILED describtion of the skills you want to make, and I'll just post here the code for them xP
If the information you give me isn't detailed enough - I will do the things you haven't described in a way I imagine, which may be different than the way you imagine.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I didn't realize what did you mean by modifying MultishotOrb, I'll modify it now.

I posted the same caller trigger twice, so here is the second caller that I forgot to post:
  • Multishot Caller Copy
    • Events
      • Game - DamageEvent becomes Equal to 1.00
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • (Level of Moonglaive Mastery for DamageEventSource) Greater than (>) 0
    • Actions
      • Custom script: call MultishotOrb( 1, 00.00, 600.00, 'A02X')
      • Custom script: if not Multishot_Damage then
      • Set DamageEventAmount = 0.00
      • Set DamageEventType = DamageTypeBlocked
      • Custom script: endif
The first caller trigger was meant to increase the Drow's multishot damage for each time it had used the Death Pact ability, the SoulRelease_Souls counts the number the ability was used.

The second caller trigger is meant to shoot a Moonglaive that hops a number of times equal to a Moonglaive ability the hero has.
 
Level 12
Joined
Jan 2, 2016
Messages
973
This one should look like this:
  • Multishot Caller Copy
    • Events
      • Game - DamageModifierEvent becomes Equal to 1.00
    • Conditions
      • And - All (Conditions) are true
        • Conditions
          • IsDamageSpell Equal to False
          • (Unit-type of (Triggering unit)) Equal to Huntress // Or however your unit is called
    • Actions
      • Custom script: if not Multishot_Damage then
      • Custom script: call MultishotOrb( 1, udg_DamageEventAmount, 00.00, 600.00, 'A02X')
      • Set DamageEventAmount = 0.00
      • Set DamageEventType = DamageTypeBlocked
      • Custom script: endif
And then change the "On hit" event from:
JASS:
    elseif udg_MS_Missile == 'A02X' and udg_MS_UnitsInGroup < MAX_TARGETS_ALLOWED then
        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        set CondGroup = udg_MS_Current_Group
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        call MultishotPointAdvanced(udg_MS_Dummy, udg_MS_Damage*0.8*(1.10+(0.10*GetUnitAbilityLevel(udg_MS_Source, 'A03N'))), 1, 180, 300, ATTACK_TYPE_PIERCE, 'A02X', 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond),900.00)
    endif
to
JASS:
    elseif udg_MS_UnitsInGroup <= GetUnitAbilityLevel(udg_MS_Source, 'A03N') then
        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        set CondGroup = udg_MS_Current_Group
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        call MultishotPointAdvanced(udg_MS_Dummy, udg_MS_Damage*0.8*(1.10+(0.10*GetUnitAbilityLevel(udg_MS_Source, 'A03N'))), 1, 180, 300, ATTACK_TYPE_PIERCE, udg_MS_Missile, 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond),900.00)
    endif
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I think your modifications did fix the Moonglaive because it hops more than once now, but it still split into two Moonglaives after hitting the main target.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I just did and didn't notice Moonglaive-split so far, so I think that did fix something.

Edit: Multishot restores health for the shooter even if the hit unit is a structure or mechanical.
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
Moonglaive-like abilities hop between invulnerable targets interminably. Using ALWAYS_USE_ADVANCED doesn't resolve this.
The culprit here is probably the target not being added to udg_MS_UnitsInGroup because the system doesn't regard it as hit because it's invulnerable.

Edit: it still hops interminably after removing the invulnerability.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Well, I'd need to see it to figure out what's causing the problem. It would've been nice if Blizzard had provided us with "IsUnitInvulrnable" flag, but they haven't.
You could try to make one of these yourself:
JASS:
function IsUnitInvulrnable takes unit u returns boolean
    return (GetUnitAbilityLevel(u, 'Avul') > 0 or GetUnitAbilityLevel(u, 'Bvul') > 0)
endfunction
And add "and not IsUnitInvulrnable(GetFilterUnit())" to the boolexpr you are using.

EDITED according to the post below.
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
The error seems concomitant with DDS' recursion error message, here is the trigger:
JASS:
//TESH.scrollpos=26
//TESH.alwaysfold=0
scope BouncingLight2

globals
    private player CondPlayer
    private group CondGroup
    constant integer MAX_TARGETS_ALLOWED = 2 + GetUnitAbilityLevel(udg_MS_Source, 'A03N')
endglobals

private function BouncingLightCond takes nothing returns boolean
    return (IsUnitEnemy(GetFilterUnit(), CondPlayer) and not IsUnitInGroup(GetFilterUnit(), CondGroup)) and not IsUnitInvulrnable(GetFilterUnit())
endfunction

function Trig_Bouncing_Light_2_Conditions takes nothing returns boolean
    local real x
    local real y
    //local integer pl
    local real a
    local integer t
    if (udg_MS_Missile == 'A02H') and (udg_MS_UnitsInGroup <= 3) then //and (udg_MS_Dummy == udg_MS_Source) then

        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        //set pl = GetPlayerId(CondPlayer)
        set CondGroup = udg_MS_Current_Group
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        if GetRandomInt(1, 100) <= 20 then
            set t = 2
            if GetRandomInt(1, 100) <= 40 then
                set t = 3
            endif
        else
            set t = 1
        endif
        set t = 1
        if GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MAX_MANA) > 0 then
            call SetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA, GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA) - 26)
            call DisableTrigger( GetTriggeringTrigger() )
            call UnitDamageTarget(udg_MS_Source, udg_MS_Hit_Unit, 26, true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            call EnableTrigger( GetTriggeringTrigger() )
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl", udg_MS_Hit_Unit, "overhead"))
        endif
        call MultishotPointAdvanced(udg_MS_Dummy, udg_MS_Damage*0.8, 1, 180, 900, ATTACK_TYPE_CHAOS, udg_MS_Missile, 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond), 1500.00)
    /*elseif udg_MS_UnitsInGroup <= 1+GetUnitAbilityLevel(udg_MS_Source, 'A03N') then
        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        set CondGroup = udg_MS_Current_Group
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        call MultishotPointAdvanced(udg_MS_Dummy, udg_MS_Damage*(0.50+(0.10*GetUnitAbilityLevel(udg_MS_Source, 'A03N'))), 1, 180, 300, ATTACK_TYPE_HERO, udg_MS_Missile, 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond),900.00)
    elseif GetUnitTypeId(udg_MS_Source) == 'N00D' and udg_MS_UnitsInGroup < MAX_TARGETS_ALLOWED then
        set x = GetUnitX(udg_MS_Hit_Unit)
        set y = GetUnitY(udg_MS_Hit_Unit)
        set CondGroup = udg_MS_Current_Group
        set CondPlayer = GetOwningPlayer(udg_MS_Source)
        call SetUnitPosition(udg_MS_Dummy, x, y)
        call MultishotPointAdvanced(udg_MS_Dummy, udg_MS_Damage*0.8, 1, 180, 300, ATTACK_TYPE_HERO, udg_MS_Missile, 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond),900.00)
    */endif
    return false
endfunction

//===========================================================================
function InitTrig_Special takes nothing returns nothing
    set gg_trg_Special = CreateTrigger(  )
    call TriggerRegisterVariableEvent( gg_trg_Special, "udg_MS_Hit_Event", EQUAL, 0 )
    call TriggerAddCondition( gg_trg_Special, Condition( function Trig_Bouncing_Light_2_Conditions ) )
endfunction

endscope

The culprit (although I followed the DDS' message advice by disabling then enabling the trigger when dealing the damage):
JASS:
        if GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MAX_MANA) > 0 then
            call SetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA, GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA) - 26)
            call DisableTrigger( GetTriggeringTrigger() )
            call UnitDamageTarget(udg_MS_Source, udg_MS_Hit_Unit, 26, true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            call EnableTrigger( GetTriggeringTrigger() )
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl", udg_MS_Hit_Unit, "overhead"))
        endif

Also, I'll exploit this chance to repost a shortcoming:
Multishot restores health for the shooter even if the hit unit is a structure or mechanical.

Edit: by the way, how can I use DDS' critical with MS but excluding the Instant attack? I made a trigger but it doesn't work:

JASS:
function Advanced_Arcane_Tower_Crit_Actions takes nothing returns nothing
    if GetUnitTypeId(udg_DamageEventSource) == 'h00m' and GetRandomInt(0,100) < 30 and Multishot_Damage then
        set udg_DamageEventAmount = udg_DamageEventAmount * 3
        set udg_DamageEventType = udg_DamageTypeCriticalStrike
    endif
endfunction

//===========================================================================
function InitTrig_Advanced_Arcane_Tower_Crit takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_DamageModifierEvent", EQUAL, 1.00)
    call TriggerAddAction( t, function Advanced_Arcane_Tower_Crit_Actions )
endfunction

The MS Instant attack deals massive damage when concurring with a DDS critical event. I tried restricting the event with a Multishot_Damage condition like the trigger above but it didn't resolve (the critical does never occur).
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, wait... we have a whole different situation here..
You just need to replace
JASS:
        if GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MAX_MANA) > 0 then
            call SetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA, GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA) - 26)
            call DisableTrigger( GetTriggeringTrigger() )
            call UnitDamageTarget(udg_MS_Source, udg_MS_Hit_Unit, 26, true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            call EnableTrigger( GetTriggeringTrigger() )
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl", udg_MS_Hit_Unit, "overhead"))
        endif
with
JASS:
    local real mana
////////////
        if GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MAX_MANA) > 0 then
            set mana = GetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA)
            if mana <= 26 then
                set udg_MS_Damage = udg_MS_Damage + mana
                call SetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA, 0)
            else
                set udg_MS_Damage = udg_MS_Damage + 26
                call SetUnitState(udg_MS_Hit_Unit, UNIT_STATE_MANA, mana - 26)
            endif
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl", udg_MS_Hit_Unit, "overhead"))
        endif
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
This seems robust and works fine.
Can you look into the critical issue?
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I might want to add some actions to the critical event that the normal critical can't apply. I also thought of modifying udg_MS_Damage but I wanted DDS to manage the event because it provides DamageEventType and damage tags.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Well then, if you want the critical to have a seperate chance to happen for each unit hit - what you have already done should be working. Although it may stack several texttags above your tower at once.
Otherwise the simplest thing you can do would be to add the critical condition in the Multishot calling trigger, and trigger the text tag yourself.
JASS:
if GetUnitTypeId(udg_DamageEventSource) == 'h00m' and not Multishot_Damage then
    if GetRandomInt(1, 100) <= 30 then
        call Multishot/*...*/, udg_DamageEventAmount*3,/*...*/)
        call CriticalTextTag(3) //you'd need to make such function yourself
    else
        call Multishot/*...*/, udg_DamageEventAmount,/*...*/)
    endif
    set DamageEventAmount = 0.00
    set DamageEventType = DamageTypeBlocked
endif
 
Level 12
Joined
Jan 2, 2016
Messages
973
I know it's been a while, but I just made a little update on my Multishot. In fact I built a new one from scratch.
It's a lot shorter, a lot simpler and with a lot less functionalities and settings.
Pros: It's shorter, cleaner, more readable, "faster", easier to use, doesn't require any additional resources or Map Editing programs.
Cons: It's not as versatile. It can be used only for basic attacks.
So I was wondering if I should submit it as a new resource, or update this one.... although I am considering to update this one, using the new functions as well, it will get a bit shorter, simpler and cleaner.

Here's the code for my new Multishot:
JASS:
library Multishot
        globals
        public hashtable Table = InitHashtable()
        public boolean Damage = false
    endglobals
    function ClearMemory takes nothing returns nothing
        local integer i = GetHandleId(GetTriggerUnit())
        call Multisettings.destroy(LoadInteger(Table, i , 'mtsh'))
        call FlushChildHashtable(Table, i)
    endfunction
    struct Multisettings
        real damage
        attacktype aType
        damagetype dType
        unit source
        trigger clearer
       
        static method create takes unit s, unit dmy, real d returns Multisettings
            local Multisettings ms = Multisettings.allocate()
            set ms.damage = d
            set ms.aType = BlzGetEventAttackType()
            set ms.dType = BlzGetEventDamageType()
            set ms.source = s
            set ms.clearer = CreateTrigger()
            call TriggerRegisterUnitEvent( ms.clearer, dmy, EVENT_UNIT_DEATH )
            call TriggerAddAction(ms.clearer, function ClearMemory)
            return ms
        endmethod
        method onDestroy takes nothing returns nothing
            call DestroyTrigger(this.clearer)
        endmethod
    endstruct
   
    function MultishotBasic takes real arc, integer missile returns nothing
        local unit shooter = GetEventDamageSource()
        local unit target = GetTriggerUnit()
        local real xS = GetUnitX(shooter)
        local real yS = GetUnitY(shooter)
        local real xT = GetUnitX(target)
        local real yT = GetUnitY(target)
        local real ang = Atan2(xT - xS, yT - yS) - bj_PI/2
        local real range = GetUnitAcquireRange(shooter)
        local real x = range * Sin(arc*bj_DEGTORAD/2) //it's Sin instead of Cos so I don't have to add 90 to the angle
        local real y = range * Cos(arc*bj_DEGTORAD/2) //it's Cos instead of Sin so I don't have to add 90 to the angle
        local real damage = GetEventDamage()*(BlzGetUnitArmor(target)*0.06 + 1)
        local group g = CreateGroup()
        local unit dummy = CreateUnit(GetOwningPlayer(shooter), 'dumy', xS, yS, ang)
        local real a = y/(x*x)
        local player p = GetOwningPlayer(shooter)
        local real b
        local real c
        local Multisettings ms = Multisettings.create(shooter, dummy, damage)
        call SaveInteger(Table, GetHandleId(dummy), 'mtsh', ms)
        call UnitApplyTimedLife(dummy, 'BTLF', 5.00)
        call UnitAddAbility(dummy, missile)
        call IssueTargetOrder(dummy, "acidbomb", target)
        call BlzSetEventDamage(0)
        call GroupEnumUnitsInRange(g,xS,yS,range + 64.0, null)
        call GroupRemoveUnit(g, target)
        loop
            set target = FirstOfGroup(g)
            exitwhen target == null
            call GroupRemoveUnit(g, target)
            if IsUnitEnemy(target, p) then
                set xT = GetUnitX(target)
                set yT = GetUnitY(target)
                set b = Atan2(xT - xS, yT - yS) - ang
                set c = SquareRoot((xT - xS)*(xT - xS) + (yT - yS)*(yT - yS))
                if a*c*c*Cos(b)*Cos(b) <= Sin(b)*c then
                    call IssueTargetOrder(dummy, "acidbomb", target)
                endif
            endif
        endloop
        call DestroyGroup(g)
        set g = null
        set dummy = null
        set shooter = null
        set target = null
    endfunction
endlibrary
function OnMultishotHit takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local Multisettings ms
    if (GetUnitAbilityLevel(u, 'SYMB') > 0) then
        call UnitRemoveAbility(u, 'SYMB')
        set ms = LoadInteger(Multishot_Table, GetHandleId(GetEventDamageSource()), 'mtsh')
        set Multishot_Damage = true
        call UnitDamageTarget(ms.source, u, ms.damage, true, true, ms.aType, ms.dType, null)
        set Multishot_Damage = false
    endif
    set u = null
endfunction
//===========================================================================
function InitTrig_Multishot takes nothing returns nothing
    set gg_trg_Multishot = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Multishot, EVENT_PLAYER_UNIT_DAMAGED )
    call TriggerAddAction( gg_trg_Multishot, function OnMultishotHit )
endfunction

And my Multishot caller function:
JASS:
function Trig_MultishotCaller_Actions takes nothing returns nothing
    if(GetUnitAbilityLevel(GetEventDamageSource(), 'MTSH') > 0) and not Multishot_Damage and BlzGetEventAttackType() != ATTACK_TYPE_NORMAL then
        call MultishotBasic(45, 'SYMS')
    endif
endfunction
//===========================================================================
function InitTrig_MultishotCaller takes nothing returns nothing
    set gg_trg_MultishotCaller = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_MultishotCaller, EVENT_PLAYER_UNIT_DAMAGED )
    call TriggerAddAction( gg_trg_MultishotCaller, function Trig_MultishotCaller_Actions )
endfunction

More details about the differences:
- I stopped using Damage Detecting Systems and I'm now using the native Damage Detection.
- Using normal coordinate system instead of polar one - it looks much simpler
- I significantly cut down the amount of arguments required (to 2) - now only angle and missile can be set up, rest of the arguments are default ones.
- Can no longer be used for spells.
- Can no longer work with Orbs (although I think I can easily update it to make it work with the add-on library)
- Uses the unit's attack and damage types automatically.
- No longer using workarounds to get the unit's armor and to prevent the initial damage.
- No longer supporting maps where ultimates can't target spell immune enemies.
- Can no longer hit limited amount of enemies.

So yes, I sacrificed a lot of functionality for simplicity. And therefore I'm wondering if I should submit it as a new resource, or just update this one.
 
Level 4
Joined
Dec 30, 2019
Messages
55
Im making map (in W3 Reforged) with a lot of creeps and that library is exactly what i need. Awesome multishot's multispells are so good. But i can't use it because getting bug only with that code. After any change of default map or just resaving map abilities and whole engine stop working correctly. Missiles stop dealing damage, multishot arrows disappear, spells with bouncing stop bouncing and so on. As i get .w3x file changing after recompilation. But i didnt find any problems with other JASS Libraries like Bribe's systems or DDS. Im attaching recompiled map here.
---some hours later---
I have found issue thru MPQ editor. After saving it adds exta _ (underscope) to each custom variable. Look at that -
Any ideas how to fix it?
 

Attachments

  • Custom Multishot v2.2c_bugged.w3x
    107.5 KB · Views: 22
  • bugged_splitshot.jpg
    bugged_splitshot.jpg
    415.1 KB · Views: 52
Last edited:
Level 4
Joined
Dec 30, 2019
Messages
55
There is no bug with that version - Custom Multishot (COMEPLETE) Is it possible to implement with Bribe's damage system?
I changed all the GDD variables but is doesnt works correct:
vJASS:
function Hit_Damage takes nothing returns nothing
    local integer id = GetHandleId(udg_DamageEventSource)
    local real damage = LoadReal(udg_Table, id, 'dama')
    local integer a_type = LoadInteger(udg_Table, id, 'atyp')
    call UnitRemoveBuffBJ( 'B005', udg_DamageEventTarget )
    call UnitDamageTargetBJ( udg_DamageEventSource, udg_DamageEventTarget, damage, ConvertAttackType(a_type), DAMAGE_TYPE_NORMAL )
endfunction

function Multishot_Delay takes nothing returns boolean
    if ( not ( UnitHasBuffBJ(udg_DamageEventTarget, 'B005') == true ) ) then
        return false
    endif
    call Hit_Damage()
    return true
endfunction

function TableClear takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = LoadUnitHandle(udg_Table, GetHandleId(t), 'dumy')
    call FlushChildHashtable(udg_Table, GetHandleId(u))
    call FlushChildHashtable(udg_Table, GetHandleId(t))
    call DestroyTimer(t)
    set t = null
    set u = null
endfunction

function DamageBlock takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = LoadUnitHandle(udg_Table, GetHandleId(t), 'targ')
    local real hp = LoadReal(udg_Table, GetHandleId(t), 'life')
    call UnitRemoveAbility(u, 'A02P')
    call SetWidgetLife(u, hp)
    call FlushChildHashtable(udg_Table, GetHandleId(t))
    call DestroyTimer(t)
    set t = null
    set u = null
endfunction

function DamageApply takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit targ = LoadUnitHandle(udg_Table, id, 'targ')
    local unit dummy = LoadUnitHandle(udg_Table, id, 'dumy')
    local real dam = LoadReal(udg_Table, id, 'dmge')
    local integer a_type = LoadInteger(udg_Table, id, 'atyp')
    call UnitDamageTarget(dummy, targ, dam, true, false, ConvertAttackType(a_type), DAMAGE_TYPE_NORMAL, null)
    call FlushChildHashtable(udg_Table, id)
    call DestroyTimer(t)
    set t = null
    set targ = null
    set dummy = null
endfunction

function ImmunityPart takes unit u, integer id, integer old_immunity returns integer
    if( GetUnitAbilityLevel(u, id) > 0 ) then
        call UnitRemoveAbility(u,id)
        return id
    endif
    return old_immunity
endfunction

function FakeDamage takes unit targ, real dam, unit d_dealer, real delay, integer a_type returns nothing
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)
    call SaveUnitHandle(udg_Table, id, 'targ', targ)
    call SaveUnitHandle(udg_Table, id, 'dumy', d_dealer)
    call SaveReal(udg_Table, id, 'dmge', dam)
    call SaveInteger(udg_Table, id, 'atyp', a_type)
    call TimerStart(t, delay, false, function DamageApply)
    set t = null
    set targ = null
    set d_dealer = null
endfunction

function Shoot takes unit target, real damage, unit dummy, integer a_type, real x1, real y1, real x2, real y2 returns nothing
    local integer immunity = 0
    local real delay
    set immunity = ImmunityPart(target, 'Amim', immunity)
    set immunity = ImmunityPart(target, 'ACm2', immunity)
    set immunity = ImmunityPart(target, 'ACm3', immunity)
    set immunity = ImmunityPart(target, 'ACmi', immunity)
    call IssueTargetOrder(dummy, "acidbomb", target)
    if immunity != 0 then
        set delay = SquareRoot(((y2-y1)*(y2-y1))+((x2-x1)*(x2-x1)))/900.00
        call FakeDamage(target, damage, dummy, delay, a_type)
        call UnitAddAbility(target, immunity)
    endif
    set target = null
    set dummy = null
endfunction

function Multishot takes nothing returns nothing
    local unit m_targ = udg_DamageEventTarget
    local unit shooter = udg_DamageEventSource
    local real x_u = GetUnitX(shooter)
    local real y_u = GetUnitY(shooter)
    local real x_t = GetUnitX(m_targ)
    local real y_t = GetUnitY(m_targ)
    local real x_p
    local real y_p
    local unit s_targ
    local real health = GetWidgetLife(m_targ)
    local timer time = CreateTimer()
    local integer id = GetUnitTypeId(shooter)
    local integer targets = LoadInteger(udg_Table, id, 'targ')
    local integer a_type = LoadInteger(udg_Table, id, 'atyp')
    local real range = LoadReal(udg_Table, id, 'rang')
    local real dist = LoadReal(udg_Table, id, 'para')
    local boolean upg = LoadBoolean(udg_Table, id, 'upgr')
    local boolean al_hits_main_tar = LoadBoolean(udg_Table, id, 'ahmt')
    local integer missile = LoadInteger(udg_Table, id, 'misl')
    local real ofst_ang = LoadReal(udg_Table, id, 'ofst')
    local real dam
    local real init_ang = Atan2(y_t-y_u,x_t-x_u) + ofst_ang
    local unit dummy = CreateUnit(GetOwningPlayer(shooter), 'h00C', x_u, y_u, 0.00)
    local real max_hp
    local group g
    local integer count
    local integer d_id = GetHandleId(dummy)
    local timer table_clear = CreateTimer()
    local integer left_c
    local integer right_c
    local unit array left
    local unit array right
    local integer dice
    set x_p = x_u + dist*Cos(init_ang)
    set y_p = y_u + dist*Sin(init_ang)
    set init_ang = (RSignBJ(dist)-1)*bj_PI/2 + init_ang
    if upg then
        set range = range + ( 200.00 * I2R(GetPlayerTechCountSimple('Reib', GetOwningPlayer(shooter))) )
    endif
    call UnitApplyTimedLife(dummy, 'BTLF', 2.00)
    call UnitAddAbility(dummy, missile)
    call UnitAddAbility(m_targ, 'A02P')
    set max_hp = GetUnitState(m_targ, UNIT_STATE_MAX_LIFE)
    call SetWidgetLife(m_targ, max_hp)
    call UnitDamageTargetBJ(dummy, m_targ, 15000.00, ConvertAttackType(a_type), DAMAGE_TYPE_NORMAL)
    set dam = (15000.00/(max_hp - GetWidgetLife(m_targ))) * udg_DamageEventAmount
    call SetWidgetLife(m_targ, max_hp)
    call SaveReal(udg_Table, d_id, 'dama', dam)
    call SaveInteger(udg_Table, d_id, 'atyp', a_type)
    call SaveUnitHandle(udg_Table, GetHandleId(time), 'targ', m_targ)
    call SaveReal(udg_Table, GetHandleId(time), 'life', health)
    call TimerStart(time, 0.00, false, function DamageBlock)
    set time = null
    if al_hits_main_tar then
        call Shoot(m_targ, dam, dummy, a_type, x_u , y_u , x_t , y_t)
    endif
    if IsPlayerAlly(GetOwningPlayer(shooter), GetOwningPlayer(m_targ)) then
        if not al_hits_main_tar then
            call Shoot(m_targ, dam, dummy, a_type, x_u , y_u , x_t , y_t)
        endif
    else
        set g = CreateGroup()
        set s_targ = null
        if targets == 0 then
            call GroupEnumUnitsInRange(g, x_u , y_u , range, null)
                loop
                    set s_targ = FirstOfGroup(g)
                    exitwhen s_targ == null
                    if s_targ != m_targ or not al_hits_main_tar then
                        set x_t = GetUnitX(s_targ)
                        set y_t = GetUnitY(s_targ)
                        set ofst_ang = Atan2(y_t-y_p,x_t-x_p)-init_ang
                        if IsPlayerEnemy(GetOwningPlayer(shooter), GetOwningPlayer(s_targ)) and IsUnitAliveBJ(s_targ) and (x_t-x_p)*(x_t-x_p)+(y_t-y_p)*(y_t-y_p) <= (( 2.00 * dist) / ( 1 - Cos(ofst_ang)))*((2.00 * dist) / ( 1 - Cos(ofst_ang))) then
                            call Shoot(s_targ, dam, dummy, a_type, x_u , y_u , x_t , y_t)
                        endif
                    endif
                    call GroupRemoveUnit(g, s_targ)
                endloop
        else
            if al_hits_main_tar then
                set count = 1
            else
                set count = 0
            endif
            set left_c = -1
            set right_c = -1
            call GroupEnumUnitsInRange(g, x_u, y_u, range, null)
                loop
                    set s_targ = FirstOfGroup(g)
                    exitwhen s_targ == null
                    if s_targ != m_targ or not al_hits_main_tar then
                        set x_t = GetUnitX(s_targ)
                        set y_t = GetUnitY(s_targ)
                        set ofst_ang = ModuloReal(Atan2(y_t-y_p,x_t-x_p)-init_ang, 2*bj_PI)
                        if IsPlayerEnemy(GetOwningPlayer(shooter), GetOwningPlayer(s_targ)) and IsUnitAliveBJ(s_targ) and (x_t-x_p)*(x_t-x_p)+(y_t-y_p)*(y_t-y_p) <= (( 2.00 * dist) / ( 1 - Cos(ofst_ang)))*(( 2.00 * dist) / ( 1 - Cos(ofst_ang))) then
                            if ofst_ang >= 0 or ofst_ang < bj_PI then
                                set left_c = left_c+1
                                set left[left_c] = s_targ
                            else
                                set right_c = right_c+1
                                set right[right_c] = s_targ
                            endif
                        endif
                    endif
                    call GroupRemoveUnit(g, s_targ)
                endloop
            if ( left_c > right_c ) then
                loop
                    exitwhen right_c == -1
                    if count <= targets/2 then
                        set dice = GetRandomInt(0, right_c)
                        set x_t = GetUnitX(right[dice])
                        set y_t = GetUnitY(right[dice])
                        call Shoot(right[dice], dam, dummy, a_type, x_u, y_u, x_t, y_t)
                        set count = count + 1
                        set right[dice] = right[right_c]
                    endif
                    set right[right_c] = null
                    set right_c = right_c - 1
                endloop
                loop
                    exitwhen left_c == -1
                    if count < targets then
                        set dice = GetRandomInt(0, left_c)
                        set x_t = GetUnitX(left[dice])
                        set y_t = GetUnitY(left[dice])
                        call Shoot(left[dice], dam, dummy, a_type, x_u, y_u, x_t, y_t)
                        set count = count + 1
                        set left[dice] = left[left_c]
                    endif
                    set left[left_c] = null
                    set left_c = left_c - 1
                endloop
            else
                loop
                    exitwhen left_c == -1
                    if count <= targets/2 then
                        set dice = GetRandomInt(0, left_c)
                        set x_t = GetUnitX(left[dice])
                        set y_t = GetUnitY(left[dice])
                        call Shoot(left[dice], dam, dummy, a_type, x_u, y_u, x_t, y_t)
                        set count = count + 1
                        set left[dice] = left[left_c]
                    endif
                    set left[left_c] = null
                    set left_c = left_c - 1
                endloop
                loop
                    exitwhen right_c == -1
                    if count < targets then
                        set dice = GetRandomInt(0, right_c)
                        set x_t = GetUnitX(right[dice])
                        set y_t = GetUnitY(right[dice])
                        call Shoot(right[dice], dam, dummy, a_type, x_u, y_u, x_t, y_t)
                        set count = count + 1
                        set right[dice] = right[right_c]
                    endif
                    set right[right_c] = null
                    set right_c = right_c - 1
                endloop
            endif
        endif
        call DestroyGroup(g)
        set g = null
    endif
    call SaveUnitHandle(udg_Table, GetHandleId(table_clear), 'dumy', dummy)
    call TimerStart(table_clear, 1.9, false, function TableClear)
    set table_clear = null
    set m_targ = null
    set shooter = null
    set time = null
    set dummy = null
endfunction

function Trig_Multishot_Conditions takes nothing returns boolean
    if ( not ( GetUnitAbilityLevelSwapped('A000', udg_DamageEventSource) > 0 ) ) then
        return false
    endif
    call Multishot()
    return false
endfunction

//===========================================================================
function InitTrig_Multishot takes nothing returns nothing
    local trigger delay
    set gg_trg_Multishot = CreateTrigger(  )
    call TriggerRegisterVariableEvent( gg_trg_Multishot, "udg_DamageEvent", EQUAL, 1 )
    call TriggerAddCondition( gg_trg_Multishot, Condition( function Trig_Multishot_Conditions ) )
    set delay = CreateTrigger(  )
    call TriggerRegisterVariableEvent( delay, "udg_DamageEvent", EQUAL, 1 )
    call TriggerAddCondition( delay, Condition( function Multishot_Delay ) )
    set delay = null
endfunction
 
Level 12
Joined
Jan 2, 2016
Messages
973
There is no bug with that version - Custom Multishot (COMEPLETE) Is it possible to implement with Bribe's damage system?
I changed all the GDD variables but is doesnt works correct:

I started working on an improved version, but I kind a put it at halt until Reforged is released, because I was hoping they'd add more things I could use to improve it further.
I currently have a working version, which has a lot less customization options than the original, but is much shorter and lighter (which makes it less prone to bugs). It can only be used as a basic multishot attack, and not for spells (See the comment above yours, although I kind a improved it a bit since I wrote that comment).
Can you wait until reforged is released, will the simplified version work for your needs, or do you want me to finish my improved version asap, even if there is a chance that it just gets changed along with reforged's release?
 
Top