• 🏆 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

Moderator

M

Moderator

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 Damage Engine instead of Weep's GDD. The way you have it set up the unit's life and max life values will give the wrong readings and you could even ignore any other system's damage dealt to that unit in between the time you add the life ability and remove it.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
As you make use of vJass, please ensure that you have a proper encapsulation.
There is the private keyword for your needs.

You also want to present a proper user configuration block.

local unit dummy = CreateUnit(GetOwningPlayer(shooter), 'h00C', x_u, y_u, 0.00)
^Please refrain from using hardcoded stuff. 'h00C' should be configurable in a global block. There stored into a constant integer variable.
There is freaking a lot of these in your code.

call UnitDamageTargetBJ(dummy, m_targ, 15000.00, ConvertAttackType(conf.attack_type), DAMAGE_TYPE_NORMAL)
uhm hem hem....

Your code shows no consistency.
There are underscores, PascalCase, camelCase all caps in all combinations.
But I couldn't identify when you used what and why.
You may want to take a look into the JPAG.

Currently this is on status Need Fix.
I still let it officially run as pending, because I hope you'll update asap.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Yeah, I udated the functions to be private before I even saw your comment :D
(also fixed a bug) xP


EDIT: Okay, I fixed my constant globals to follow JPAG.
I also made the dummy ID configurable, as well as the test damage, dealt to the target for the armor estimation.

I changed the names of some of the variables, however I didn't change ALL variables to follow JPAG. Some of them are readable enough as they are :p

I also didn't make the Magic Immunity skills configurable, since they are constant.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
I recommend using a missile system.
Thought, I suppose that would make the spell too simple.

What would the missile system change?
Instead of "Shoot" function - I'll have some other function for creating the missile, and I wouldn't have the damaging function(s).
Version 1.6.3 of the system is in JASS, and it has a "Simple" version - it doesn't have "Shoot" function, the method, that removes and re-adds immunities, and the 2 functions for dealing the damage to magic immune enemies.
It simply just orders the dummy to acidbomb the target.
So a missile system would just remove the need for the 'on damage' event.

However, I did re-make the "Advanced" version of the system to be in vJASS, cuz there is a gameplay constant "Magic Immune units resist ultimates". When it's set to "true" - the "Simple" version can't damage Magic Immune enemies.

Now that I think about it - I could merge the 2 versions, and allow players to choose which one to use, with an if. But that will be for the next update :p

EDIT: I have a question. Will it be better if I put the 'if' in the "MultishotConditions" function, and call the simple or the advanced version, according to a constant boolean. This way I'll copy-paste the main function, and in the copy will replace all the "call Shoot(...)" with "call IssueTargetOrder(...)".
Or should I simply add the 'if's before each calling of the Shooting function? This way the trigger will not get much longer, but I think the system will become a bit heavier (since it will be checking the 'if' for every target)

EDIT 2: Actually I could do it the 1-st way, using a textmacro. This way - the trigger will gain only 5-6 extra rows of code. While still checking the boolean only once. Is that the best option?

EDIT 3: And.. done - updated to version 1.7.1 - it is both the simple and the advanced version into one. Choose a version by simply setting 1 boolean.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
I must disagree.
1) The way I've made it - the game (not some system) does the movement for me.
Thus, the way I'm doing it is lighter than a missile system
2) Then my system would've required ANOTHER system, thus making the total amount of script MUCH longer.
3) I would've needed to change the missile system's code to register the "multishot hit" event.

The only advantage a missile system (way of doing it) has is that it doesn't require a dummy to cast acid bomb, and the multishot will stack with inbuilt acid bomb, or other abilities (applying actual buff) based on acid bomb.
It still works with them, but it removes the "normal" buff from the ability, thus interrupting their effect.

EDIT: Version 1 of this multishot was GUI, required 3-4 triggers (main trigger, trigger to nullify the original damage, 'on hit' trigger, and in later version I added a trigger for damaging magic immune enemies), but their combined lenght was really really short.
However - it wasn't configurable for different unit types. You had to copy-paste the trigger, and change the stuff in the copy directly (and have a trigger for each unit type).
Anyways.. my point is - if you need this multishot for a single unit/unit-type (or you want it configured the same way for all the units), and you have a more-advanced DDS - it will become extremely short.
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
*following opinion is based on not reading the code, barely looking in the object editor and drawing conclusions from that*

Instead of copying a folder of triggers over to your map you have to create a multitude of a abilities instead. Which requires more work from the user in both effort and time.
I fail to see how that is to a benefit for anyone. Seems like Acid bomb is what you use to make the arcing missile without code, which is clever but requires extra work since you can't detect when it reaches it's target effectively.

I personally aim for zero object editor requirements when I create something. I don't think I've ever used more than two, a dummy and an ability to cast the spell at most.

You got 8 steps to import a spell, that's horrible.
It should be:
  • Copy code
  • change the dummy unit code integer dummy = 'code'
  • done
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
I may be wrong, but don't missile systems also require making a dummy for the missile's model?
True that they may use Vexorian's dummy, but that's not the point.

For my multishot to work you only need (1) an ability, (2) a buff, and (3) an ability, giving a lot of HP.
More abilities are required ONLY if you want different missile arts for different units.
Otherwise these 3 are the only things that need creating in the object editor.
The other thing is CHANGING the units' attack to "instant". (This could've been avoided with my Attack is launched library, but it's still incomplete :p)

And I can detect when acid bomb reaches the target:
Since it has a custom buff - I detect the event "a unit is damaged". The units may take 0 damage, but the event is still registered.
I check if the damaged unit has the buff, applied by the multishot skill. If they do - I remove it, and deal the damage needed.
That's pretty "efficient way to detect when it reaches its target" if you ask me.
I use the "unprecise timer method" only if the unit is immune to magic, and only for the "Advanced version".

And well, the list of requirements could be reduced...
Like.. I could make it work automatically for configured units, even if they don't have a multishot skill.
However, it doesn't look pretty when my multishot misses - no arrows spawn (since the units take no damage, and I use "a unit is damaged" event to make it run).
I could've used my "Attack is launched" library, but it's kind a incomplete.. still doesn't work with units, who have their attack speed altered + I can't get the unit's damage with it.

Step (0) is a requirement, step (2) is "make a dummy if you don't have one alreay", and steps 6, 7, and 8 are in the trigger editor.

EDIT: perhaps I could make it a bit different:
make a public function for the multishot, and you enter the values you need when calling the function.
This would greatly reduce the trigger lenght... Maybe I'll think about it.

......

Which do you prefer:
To be setting the configurations of a unit-type in the begining of the game, and then just change them with triggers when you need.
(If you don't want them changed mid-game - you don't need to do anything else)

OR

Make triggers like this:
Event: a unit takes damage
Condition: damage source has ability (multishot)
Actions:
if the damage source is unit of type '1' then:
set var 1 = x
set var 2 = c
set var 3 = v
etc...
else if the damage source is of type '2' then:
bla bla bla
endif
call Multishot(var 1, var 2, var 3, ....)


In my opinion - the 1-st way of doing it is simpler, but the 2-nd way is more user-friendly (if they know what they are doing), since they aren't stuck to unit-type based multishot. They can give whatever conditions they want. And they can even make the multishot have a chance to happen :p

EDIT 2: I actually started applying this change... would you really like to call a function, which takes 12 arguments to work? :D
Tho you could make some function, that calls the main function, with some of the arguments pre-set xP (something like UnitDamageTargetBJ)
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Well, not exactly.
Basically - yes, but it hits only units in a parabolic shape towards the target (+ the offset angle).
I also register the event when a unit gets hit by the multishot (but this is kind a simple to do with the missile system)

By the way, I kind a re-made my system to do what I said in the last post.
What do you think?
now the main function is quite shorter, and the multishot is configured like this:
  • Test trig
    • Events
      • Game - GDD_Event becomes Equal to 0.00
    • Conditions
      • (Level of Multishot Never Miss for GDD_DamageSource) Greater than 0
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of GDD_DamageSource) Equal to Death Archer
        • Then - Actions
          • Custom script: call Multishot(udg_GDD_DamageSource, udg_GDD_DamagedUnit, udg_GDD_Damage, 7 , 50.00, 900, true, 1, 1, 5, 'A001', 0)
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Unit-type of GDD_DamageSource) Equal to Elite Night Elf Archer
            • Then - Actions
              • Custom script: call Multishot(udg_GDD_DamageSource, udg_GDD_DamagedUnit, udg_GDD_Damage, 3 , 40.00, 600, true, 1, 1, 2, 'A00P', 0)
            • Else - Actions
Which version do you like more? The original or this one?
 

Attachments

  • Custom Multishot v1.7.2B.w3x
    65.1 KB · Views: 202
Level 12
Joined
Jan 2, 2016
Messages
973
Alraight, I'm awaiting your opinion about which way of doing things is better.
Just keep in mind, that I haven't updated the original post with the "B" version yet.
And also: It kind a started working as soon as I changed all the variables, so I haven't really tested the "B" version for bugs. It seemed to work when I tried it tho :p

@ Chaosy - will you also make it like me - make it hit all enemies, matching condition if "targets allowed" are set to 0, and otherwise - make it hit limited amount of units (which are random every shot, and take equal amount of units form the left, and from the right of the main target)?
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
I made a somewhat quick test and it works fairly decently.
JASS:
//! zinc
	library MultiShotCore requires Missile, FieldOfView
	{
	
		constant real VIEW_ANGLE = 120;
		unit temp;
		
		struct MultiShot extends array
		{
			module MissileStruct;
		}
		
		function MyFilter(unit source, unit target) -> boolean
		{
			return IsUnitInSightOfUnit(source, target, VIEW_ANGLE) && source != target;
		}
	
		function Actions()
		{
			Missile m;
			unit u = GetEnumUnit();
			real x, y, dx, dy, angle, dist, mathX, mathY;
			if(MyFilter(temp, u))
			{
				x = GetUnitX(temp);
				y = GetUnitY(temp);
				dx = GetUnitX(u);
				dy = GetUnitY(u);
				mathX = dx - x;
				mathY = dy - y;
				angle = Atan2(mathY, mathX);
				dist = SquareRoot(mathX * mathX + mathY * mathY);
				m = Missile.create(x, y, 50, angle, dist, 50);
				m.target = u;
				m.speed = 10;
				m.arc = bj_PI / 8;
				m.damage = 25;
				m.model = "Abilities\\Weapons\\BloodElfMissile\\BloodElfMissile.mdl";
				MultiShot.launch(m);
			}
		}
	
		function isAttacked()
		{
			unit source = GetAttacker();
			real x = GetUnitX(source);
			real y = GetUnitY(source);
			group g = CreateGroup();
			IssueImmediateOrder(source, "stop");
			SetUnitAnimation(source, "attack");
			GroupEnumUnitsInRange(g, x, y, 500, null);
			temp = source;
			ForGroup(g, function Actions);
		}

		function onInit()
		{
			trigger t = CreateTrigger();
			TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED);
			TriggerAddAction(t, function isAttacked);
		}
	}
//! endzinc

The only issue is that I am using the is attacked event which is abusable.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Yeah, I was using "a unit is attacked" event in the begining as well.
So here are the things what your system is lacking:
1) I don't see you set the max amount of targets anywhere.
1.b) Even if you were doing that, you'd still need to make the targets random.
2) Your system always deals the same amount of damage, instead of damage, based on the unit's actual damage.
2.b) Even if you were using a dds to acomplish (2) - you'd still need to estimate the unit's armor, to deal the right amount of damage to all the units.
3) You can't configure the unit's attack type
4) You can't configure how much damage does the main target take, and how much do the secondary targets take
5) As you said - it's abusable.

As I said in some previous post - the 1-st version of this multishot wasn't configurable, and was about as short as yours. It was something like:
Event: a unit takes damage
Condition: damage source has multishot
Actions: (set some variables)
give 30k HP to the target, and heal it to max
make a dummy, and make it damage the target for 25k dmg (chaos)
see how much armor does the target have, and heal it back to max
Custom script: call DamageBlock() - a function in the map's header.
Pick all the units in range and do actions
if they are in the right area - they get shot, else nothing.

And that was about it. I assure you, that if you were to implement what I listed as downsides - your system will become about as long as mine :p (tho it may look shorter, since you are using zinc)

By the way.. about the "B" bersion - a function could be made "MultishoRt()" (multi short or multishot short)
JASS:
function MultishoRt takes integer targets, real arc, real range, integer attackType returns nothing
    call Multishot(udg_GDD_DamageSource, udg_GDD_DamagedUnit, udg_GDD_Damage, targets , arc, range, true, 1, 1, attackType, 'A00P', 0)
endfunction
This way it will need only 4 arguments (5 if I add the missile art to it too), and it will pretty much do the same thing, but it will be easier to write. People can call the normal function if they need better configurations :p

EDIT:
I made "B" version official, but I updated some other stuff as well, and now the version is 1.8
Whole new worlds (of ways to use it) open up :)

EDIT 2:
JASS:
function Multishot takes unit shooter, unit multiTarget, real initialDamage, real parabola, real range, integer missileArt, real offsetAngle returns nothing
    local real xShooter = GetUnitX(shooter)
    local real yShooter = GetUnitY(shooter)
    local real xTarget = GetUnitX(multiTarget)
    local real yTarget = GetUnitY(multiTarget)
    local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(yTarget-yShooter,xTarget-xShooter) + offsetAngle
    local real xFocus = xShooter + parabola*Cos(init_ang)
    local real yFocus = yShooter + parabola*Sin(init_ang)
    local timer table_clear = CreateTimer()
    local real temp_ang
    local unit dummy = CreateUnit(GetOwningPlayer(shooter), DUMMY_ID, xShooter, yShooter, 0.00)
    call UnitApplyTimedLife(dummy, 'BTLF', DUMMY_LIFE_TIME)
    call UnitAddAbility(dummy, missileArt)
    call SaveReal(Table, GetHandleId(dummy), 'dama', initialDamage)
    call GroupEnumUnitsInRange(g, xShooter , yShooter , range, null)
    loop
        set multiTarget = FirstOfGroup(g)
        exitwhen multiTarget == null
        set xTarget = GetUnitX(multiTarget)
        set yTarget = GetUnitY(multiTarget)
        set temp_ang = Atan2(yTarget-yFocus,xTarget-xFocus)-init_ang
        if IsPlayerEnemy(GetOwningPlayer(shooter), GetOwningPlayer(multiTarget)) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(temp_ang)))*(( 2.00*parabola)/( 1 - Cos(temp_ang))) then
            call IssueTargetOrder(dummy, "acidbomb", multiTarget)
        endif
        call GroupRemoveUnit(g, multiTarget)
    endloop
    call SaveUnitHandle(Table, GetHandleId(table_clear), 'dumy', dummy)
    call TimerStart(table_clear, DUMMY_LIFE_TIME-0.05, false, function TableClear)
    set table_clear = null
    set shooter = null
    set dummy = null
endfunction
And I'll have 2 more functions (+ the DDS):
1 for the "when shot reaches target", and 1 for clearing the dummy's table.
(it would've been simpler, if I was shooting enemies in a cone, instead of a parabola)
But your system has 2 more libraries, so yeah...
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Hmm, I'm having big plans for the next update (or perhaps I will do it in few stages - several updates, each doing part of the big change).
Part of the changes will make the Multishot shorter, and easier to configure, while other part of the changes will make it longer, and require more configurations, BUT they will make it A LOT more functional.
Please tell me what do you think about the following ideas (for changes):

1) I wanna make 1 more library called "Advanced", and put all the bonus functions, which are needed only for the advanced version there.
At the moment everything is stuffed into 1 library, yet not all of the functions are used at once. The "Advanced" functions are used only if "advanced" multishot is called, but I don't know too many people who need the advanced version (apart from me), so putting its functions in another library will make the Multishot library shorter and people wouldn't have to look into the functions, which aren't used.

DONE!

2) I'm thinking to stop using a textmacro, or at least make the textmacro "smarter".
If I do it the 1-st way - I'd need to copy-paste the main function 2-4 times, BUT it will also reduce the amount of configurations needed for multishot to work.

At the moment there is only one "Multishot" function, which takes 17 arguments, but NOT ALL OF THEM ARE USED AT THE SAME TIME.

So when I split the Multishot in the main trigger - the amount of configurations will reduce, but the library lenght will increase (if I don't figure a way to do it with a textmacro).

DONE! (now I just wanna try to put the 2 textmacros back into one xP done!)

3) I'm planing to make it not spawn a dummy for the Multishot if a dummy is the "shooter".
DONE!

4) I'm thinking to add a boolexpr configuration.
At the moment - it only hits enemies. If there was a boolexpr configuration - this multishot could be used for who knows what!
I can think of an example - make a healing wave, that heals all the friendly units infront of the caster.
Or make it unable to target structures.
Or make it unable to target flying units / or able to target ONLY flying units.

It will make the configuration a bit more difficult, but it will really break the limits of its uses.

DONE!

5) (Only for the advanced version) Instead of starting a one-shot timer, with time = the distance between the shooter and the magic immune target divided by the missile's speed, I could start a repeating (every 0.03125 seconds) timer, which will be keeping track what distance has the shot passed, and the distance between the MS begining location, and the current location of the target, thus the shot will deal its damage at the right time, even if the target is moving.
And only 1 timer + a linked list will be used to do that.

DONE!

Okay, I finally feel that it's complete.
I will keep fixing bugs (if any are found), but at the moment I don't know what could be improved, without "damaging" another part of it.
Every aspect of the Multishot is controlable - the range, the damage, the missile art, the arc, the targets allowed, etc..

I could make it a bit lighter, by adding some pre-set configurations, but until now I was doing the oposite - getting rid of the pre-set configurations in order to make it more configurable.
In the 1-st versions - everything was hard-coded, but the user didn't have to do (almost) anything to get Multishot to work.
While now all Multishot is actually doing is:
1) Estimates target's armor rating, and calculates what was the base damage amount dealt to it. (Only when the damage dealt isn't fixed, and only for the targeted function)
2) Blocks the original damage, dealt to the target (Only when (1) runs, and a boolean is set to true)
3) Calculates the distance between the peak of the parabola and its focus.
4) Creates a dummy, gives it the Multishot missile.
5) Makes the dummy cast Multishot on the targets, inside the parabola.
6) Always chooses random targets, when there is a limit to the targets allowed (while still using FoG method, instead of ForGroup).
7) Registers when the shot has reached the enemy and 'may' run an event.
8) Deals the correct damage to all the units.

In the begining, while I was still struggling to make it work, it was quite difficult as I didn't know how to make it deal the same 'base' damage to all the units. Wietlol helped me with that :p
Then I found out that it doesn't shoot magic immune enemies, and I had to think of a way to make it able to shoot them, AND damage them.
Then people told me to make it configurable (as it had only the hard-coded pre-set configurations, which had to be changed inside the code)

I really didn't like the barrage based Multishot, cuz it was shooting units behind the shooter...
Version 1.8 broke the limits of the Multishot, and it could be used for spells too! And different units of the same type could be configured differently, while before units of the same type had the same configurations.
But since that version it also became harder to use as people needed to create their events for when Multishot runs, while before that version - they don't have to do almost anything (just set some variables)

Version 2 is another important version, cuz since then people could filter what units Multishot is allowed to target! However, that configuration also came with a price... I had to change "GroupEnumUnitsInRange(g, x, y, range, null)" to "GroupEnumUnitsInRange(g, x, y, range, condition)", and that condition makes it heavier. But I really don't know how could've I done the "dynamic" filtering inside the FoG loop xP.
And that version also became harder to configure, since some people, using GUI know how to use some custom scripts, but this configuration can't be achieved with simple custom script in GUI xP.

I did my best, trying to make is as simple to use as possible. I really hope that the MultishoRt library is helpful. Feel free to edit it to your liking. You can add your own functions to it if you need to, and you can change the ones I've already put there. I kind a added that library just as an example anyways.


Anyways, feel free to tell me what you want to see in this Multishot, and I'll try to implement it.
~Perhaps I will make it easier to use for GUI users.... ~
~Or another possible improvement would be to count how many units have been hit by the Multishot, and destroy the DP struct, and the dummy once all the shots have reached their targets. However, this would be kind a simple for the advanced version, but the simple one may have some bugs, since if a unit dies before the shot has reached it - it will never register when the unit has been hit xP~
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I'm believe in custom projectile systems like the two I submitted myself,
mainly because of the huge and easy customization possibilities.

Yet this seems to be really cool system. Honestly I don't even know how to rate it yet.

I just wrote down some quick thoughts.

You could replace the wrapper function UnitDamageTargetBJ with native UnitDamageTarget.
However JassHelper eventually inlines it.

function MultishotPoint takes unit shooter, real initialDamage, integer targets, real arc, real init_range, attacktype attackType, integer missileArt, real offsetAngle, damagetype damageType, real xSpell, real ySpell, boolexpr condition returns nothing
^initialDamage, init_range ... --> initRange

JASS:
    debug local real a
    debug local real b
    debug local real x
    debug local real y
    debug local real m = range*Sin(arc/2)
    debug set count = 0
    debug loop
        debug exitwhen count > 20
        debug set x = xShooter - Cos(init_ang)*parabola + Cos(init_ang + bj_PI/2)*(count - 10)*m/10
        debug set y = yShooter - Sin(init_ang)*parabola + Sin(init_ang + bj_PI/2)*(count - 10)*m/10
        debug set b = SquareRoot(4*parabola*parabola + (count - 10)*m*(count - 10)*m/100)
        debug set a = (init_ang + bj_PI/2) - Atan2(yFocus - y, xFocus - x)
        debug if a == bj_PI/2 then
            debug set a = parabola
        debug else
            debug set a = b*Cos(a)/Sin(2*a)
        debug endif
        debug set x = x + Cos(init_ang)*a
        debug set y = y + Sin(init_ang)*a
        debug call DestroyEffect(debug_effect[count])
        debug set debug_effect[count] = AddSpecialEffect("Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl", x, y)
        debug set count = count + 1
    debug endloop
^Cool debug option, but should be inside a static if DEBUG_MULTISOT then.

IsUnitAliveBJ(secondaryTarget) ... please. Either go with not IsUnitType(u, UNIT_TYPE_DEAD) or native UnitAlive

Is if WANT_DAMAGE_BLOCK then the actual damage on unit damaged event?
Is it required to hardcode it into your system or can't you use for example Bribes Damage Engine
which allows damage amount modification and nullification.
 
Level 12
Joined
Jan 2, 2016
Messages
973
You could replace the wrapper function UnitDamageTargetBJ with native UnitDamageTarget.
However JassHelper eventually inlines it.
Okay, I guess I could do that :p
I haven't really bothered changing it till now, cuz it's essentially the same thing, just takes less arguments.

DONE!

function MultishotPoint takes unit shooter, real initialDamage, integer targets, real arc, real init_range, attacktype attackType, integer missileArt, real offsetAngle, damagetype damageType, real xSpell, real ySpell, boolexpr condition returns nothing
^initialDamage, init_range ... --> initRange
will do.
DONE!

JASS:
    debug local real a
    debug local real b
    debug local real x
    debug local real y
    debug local real m = range*Sin(arc/2)
    debug set count = 0
    debug loop
        debug exitwhen count > 20
        debug set x = xShooter - Cos(init_ang)*parabola + Cos(init_ang + bj_PI/2)*(count - 10)*m/10
        debug set y = yShooter - Sin(init_ang)*parabola + Sin(init_ang + bj_PI/2)*(count - 10)*m/10
        debug set b = SquareRoot(4*parabola*parabola + (count - 10)*m*(count - 10)*m/100)
        debug set a = (init_ang + bj_PI/2) - Atan2(yFocus - y, xFocus - x)
        debug if a == bj_PI/2 then
            debug set a = parabola
        debug else
            debug set a = b*Cos(a)/Sin(2*a)
        debug endif
        debug set x = x + Cos(init_ang)*a
        debug set y = y + Sin(init_ang)*a
        debug call DestroyEffect(debug_effect[count])
        debug set debug_effect[count] = AddSpecialEffect("Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl", x, y)
        debug set count = count + 1
    debug endloop
^Cool debug option, but should be inside a static if DEBUG_MULTISOT then.
Do I need to create a constant boolean "DEBUG_MULTISHOT" or static if, with DEBUG_ in the begining automatically gets value "true" in debug mode?
I'm still kind a new with static ifs.

DONE!

IsUnitAliveBJ(secondaryTarget) ... please. Either go with not IsUnitType(u, UNIT_TYPE_DEAD) or native UnitAlive
I didn't know there was such thing as "UNIT_TYPE_DEAD". I'll make it use that :)
And is there a native "UnitAlive"? "IsUnitAliveBJ" checks if the unit's HP is under 0, it doesn't use a native "UnitAlive", so I thought there isn't such native.
Didn't find "UnitAlive" in the common.j
DONE!

Is if WANT_DAMAGE_BLOCK then the actual damage on unit damaged event?
Is it required to hardcode it into your system or can't you use for example Bribes Damage Engine
which allows damage amount modification and nullification.

I added the damage block in case people are using a DDS, which doesn't have damage blocking option (like the GDD which I used when creating the system).
I'll add it into a static if, instead of normal if.
And if people are using a more advanced DDS - they can simply set it to false and do the damage block by themselves :p
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Do I need to create a constant boolean "DEBUG_MULTISHOT" or static if, with DEBUG_ in the begining automatically gets value "true" in debug mode?
I'm still kind a new with static ifs.
You'll need a constant boolean DEBUG_WHATEVER
The DEBUG_ prefix is optional, essentially does nothing also doesn't inherit any value in
debug mode.
private constant boolean HI = true would also work, but is not a very logical variable name.

Didn't find "UnitAlive" in the common.j
:) It's not documentated there. It's in the common.ai.
JassHelper allows you to declare that native if you wish so. ( link )
native UnitAlive takes unit id returns boolean

I added the damage block in case people are using a DDS, which doesn't have damage blocking option (like the GDD which I used when creating the system).
I'll add it into a static if, instead of normal if.
And if people are using a more advanced DDS - they can simply set it to false and do the damage block by themselves :p
understood.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I fixed everything you mentioned, and later I decided to add another sample spell, but then I noticed that I need to change something in the code to make the new spell work, so I updated it again.
You can check it out now :p
I'm quite happy with how the "Chain Reaction" turned out to be :D (tho I had to use the normal Multishot functions.. couldn't make it work with the short ones, but that's kind a the point why there are so many configurations... Sooner or later they will all end up being used (maybe without 1 or 2, but SOME people could sure benefit from their exsistance))

By the way, in 2-3 days I'll get on the ship, and I wouldn't be able to update anything for ~4 months, so please tell me if there is anything else bothering you :p

EDIT:
By the way, here is something interesting, that definately can't be achieved with barrage based Multishot :D
JASS:
function Trig_AndrAla_on_ranged_hit_Actions takes nothing returns boolean
    local real lvl
    if udg_MS_Missile == 'A03I' then
        set lvl = 0.15 - I2R(GetUnitAbilityLevel(udg_MS_Source, 'A03H'))*0.025
        set udg_MS_Damage = udg_MS_Damage*((1 + lvl) - (udg_MS_UnitsInGroup*lvl))
    endif
    return false
endfunction
What this does is:
targets1 lvl2 lvl3 lvl4 lvl
1100%100%100%100%
287.5%90%92.5%95%
375%80%85%90%
462.5%70%77.5%85%
550%60%70%80%
So no matter the level, if only 1 unit is hit - it deals full damage to it, but for every other target - the damage is being reduced.
However - leveling the skill reduces the amount of the damage reduction :)
Try coding that with 2 rows of code for barrage multishot >:)
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I imported the system into a map with a Damage Detection already implemented and the multishot only launches after the damage is inflicted, I hope you could implement a different Damage Detection.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Well, I have. Scroll down to the bottom of the library and change the variables (in the textmacro call) to the ones, used by the DDS you are using.
JASS:
//! runtextmacro MulthishotOnHit("udg_GDD_DamageSource","udg_GDD_DamagedUnit","udg_GDD_Damage","udg_GDD_Event")
^ This row.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I modified it as you suggested but it didn't work so I manually changed each variable to its peer in the DDS and now the multishot only launches after the damage is inflicted.
One thing I forgot to mention is that before and after the modification the original target unit is also hit with the multishot.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
MS main triggers:
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

  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
// FORCE_DESTROY_DAM_PROP destroys the DamProperties struct Multishot uses, when all of the targets has been hit.
// However, since DamProperties has been declared lower into the script - the destruction is called trough
// trigger evaluate (or execute, not sure). Thus this allowes you to chose wether to forcefully destroy it,
// or let the timer (that's responsible for the destruction) do its job.
  private constant boolean FORCE_DESTROY_DAM_PROP = 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
  set .u = null
  endmethod
  endstruct
  
  private struct TimeManager
  real x
  real y
  real speed
  unit targ
  unit d
  Multishot_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, unit d, Multishot_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.d = d
  set this.damage = DP
  return this
  endmethod

  method onDestroy takes nothing returns nothing
  if this.first == this.last then
  set On = false
  call PauseTimer(Looper)
  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
  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.d
  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
  call UnitDamageTarget( this.d, this.targ, udg_MS_Damage, true, false, this.damage.aType, this.damage.dType, WEAPON_TYPE_WHOKNOWS )
  set this.damage.alreadyShot = this.damage.alreadyShot + 1
  if this.damage.alreadyShot == this.damage.unitsShot then
  static if FORCE_DESTROY_DAM_PROP then
  call Multishot_DamProperties.destroy(this.damage)
  endif
  static if not ALWAYS_USE_ADVANCED then
  call FlushChildHashtable(Multishot_Table, GetHandleId(this.d))
  endif
  if not this.damage.preDummy then
  call UnitApplyTimedLife(this.d, 'BTLF', 0.01)
  endif
  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
  set this = this.next
  endloop
  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 unit dummy, real damage, real x, real y, real shotSpeed, Multishot_DamProperties DP, unit target returns nothing
  local real delay
  local ImmunityList IL = ImmunityList.create(target)
  local TimeManager TM
  call IssueTargetOrder(dummy, Multishot_SPELL_ORDER, target)
  if IL.main or ALWAYS_USE_ADVANCED then
  set TM = TimeManager.create(x, y, target, shotSpeed, dummy, DP)
  if not On then
  call TimerStart(Looper, REFRESH_RATE, true, function TimeManager.refresh)
  endif
  endif
  call ImmunityList.destroy(IL)
  set target = null
  set dummy = null
  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 Multishot initializer Init requires 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.
// AVERAGE_SHOT_SPEED is the missle speed of the Multishot's projectile. It's used only if the target is Immune to Magic.
// in the Gameplay Constants. (false = lighter version; true = a bit heavier version)
// 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 an amount, enough for the multishot to hit all its targets.
// Don't touch the other 2.
  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 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 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 = DamBlocker.allocate()
  set DB.u = u
  set DB.health = GetWidgetLife(u)
  set u = null
  return DB
  endmethod
  endstruct

// This struct holds the damage's properties.
  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
  timer clearer

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

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

// Once DUMMY_LIFE_TIME seconds have expired (since the multishot's been activated - this clears the dummy's hashtable
// since it's expected that all the targets have already been hit.
// If you want to have slower missiles - you need to re-configure this time in the main trigger.
// You'd also need to increase the dummy's Expiration time.
  private function TableClear takes nothing returns nothing
  local timer t = GetExpiredTimer()
  local integer tid = GetHandleId(t)
  local integer id = LoadInteger(Table, tid, 'dmid')
  call DamProperties.destroy(LoadInteger(Table, id, 'damp'))
  call FlushChildHashtable(Table, id)
  call FlushChildHashtable(Table, tid)
  set t = null
  endfunction

// Blocks the damage, dealt to the original target. Also removes the HP bonus ability.
  private function DamageBlock takes nothing returns nothing
  local timer t = GetExpiredTimer()
  local DamBlocker DB = LoadInteger(Table, GetHandleId(t), 'blcr')
  call UnitRemoveAbility(DB.u, HP_BONUS_ABILITY)
  call SetWidgetLife(DB.u, DB.health)
  call DamBlocker.destroy(DB)
  call FlushChildHashtable(Table, GetHandleId(t))
  call DestroyTimer(t)
  set t = null
  endfunction

// Calculates the unit's base damage, by multiplaying the damage dealt by the target's Armor rating
  private function GetBaseDamage takes unit d, 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
  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(d, 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 WANT_DAMAGE_BLOCK then
  set time = CreateTimer()
  call SaveInteger(Table, GetHandleId(time), 'blcr', DB)
  call TimerStart(time, 0.00, false, function DamageBlock)
  set time = null
  else
  call UnitRemoveAbility(damage.mainTarget, HP_BONUS_ABILITY)
  call SetWidgetLife(damage.mainTarget, DB.health)
  call DamBlocker.destroy(DB)
  endif
  endfunction

//! textmacro Multishot takes FUNC, SETX, SETY, INITX, INITY, ENDIF, ELSE, MAINTAR, IF1, GETDAM, FUNC2, IFAHMT, GAU, SDUS, IF3, NULLD, RET, IF4, SC1, ST, ADV, COND, ENEMY
// Calculates the distance between the focus of the parabola and its peak (the shooter is at the peak)
  local real parabola = (initRange-(initRange*Pow(Cos(arc/2 * bj_DEGTORAD),2.00)))/( 4.00 * Cos(arc/2 * bj_DEGTORAD))
// Increases range, so it'd have collision in mine
  local real range = initRange + 64.00
  local real xShooter = GetUnitX(shooter)
  local real yShooter = GetUnitY(shooter)
// For the point version - it sets the target x/y to the targeted position, and for the target version,
// it sets them to the coordinates of the target.
  local real xTarget = $INITX$
  local real yTarget = $INITY$
  local real init_ang = (RSignBJ(parabola) - 1)*bj_PI/2 + Atan2(yTarget-yShooter,xTarget-xShooter) + offsetAngle
  local real xFocus = xShooter + parabola*Cos(init_ang)
  local real yFocus = yShooter + parabola*Sin(init_ang)
  local unit secondaryTarget
  local DamProperties damage
  local real temp_ang
  local integer count
  local integer left_c
  local integer right_c
  local unit array units
  local integer dice
  local unit dummy
// debug actions to illustrtate the parabola
  static if DEBUG_MULTISHOT then
  local real a
  local real b
  local real x
  local real y
  local real m = initRange*Sin(arc/2)
  set count = 0
  loop
  exitwhen count > 20
  set x = xShooter - Cos(init_ang)*parabola + Cos(init_ang + bj_PI/2)*(count - 10)*m/10
  set y = yShooter - 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
  endif
// end of debug
// if a dummy is the shooter - is sets the "source" to the current value of udg_MS_Source,
// else it creates a dummy and uses it to cast the missiles
  if GetUnitTypeId(shooter) == DUMMY_ID then
  set damage = LoadInteger(Table, GetHandleId(shooter), 'damp')
  if damage == 0 then
  set damage = DamProperties.create()
  call SaveInteger(Table, GetHandleId(shooter), 'damp', damage)
  endif
  set dummy = shooter
  set damage.source = udg_MS_Source
  set damage.preDummy = true
  else
  set damage = DamProperties.create()
  set dummy = CreateUnit(GetOwningPlayer(shooter), DUMMY_ID, xShooter, yShooter, 0.00)
  static if not AdvancedMultishot_ALWAYS_USE_ADVANCED then
  call SaveInteger(Table, GetHandleId(dummy), 'damp', damage)
  endif
  call UnitApplyTimedLife(dummy, 'BTLF', MAX_DUMMY_LIFE_TIME + 0.05)
  set damage.source = shooter
  set damage.preDummy = false
  endif
  call UnitAddAbility(dummy, missileArt)
  set damage.dType = damageType
  set damage.aType = attackType
  set damage.mainTarget = $MAINTAR$
  set damage.missile = missileArt
  set damage.advanced = $ADV$
// The next rows are for the targeted version - they calculate the base damage, if the initial damage
// is NOT preset, but it's equal to the damage dealt
  $IF1$
  set damage.amount = initialDamage
  $ELSE$
  $GETDAM$
  $ENDIF$
  call SaveInteger(Table, GetHandleId(damage.clearer), 'dmid', GetHandleId(dummy))
  call TimerStart(damage.clearer, MAX_DUMMY_LIFE_TIME, false, function TableClear)
// The next ones are also for the targeted version - they shoot the main target if alwaysHitsMainTarget is true.
// Also, if targets allowed is set to true - Multishot ends.
  $IFAHMT$
  $FUNC2$
  $GAU$
  $SDUS$
  $IF3$
  $NULLD$
  $RET$
  $ENDIF$
  $ENDIF$
// if targets allowed are equal to 0 or greater than 1000, it shoots all the units, matching the conditions.
  if targets == 0 or targets > 1000 then
  call GroupEnumUnitsInRange(g, xShooter , yShooter , range, $COND$)
  loop
  set secondaryTarget = FirstOfGroup(g)
  exitwhen secondaryTarget == null
// next if is for the target version - it doesn't allow the main target to get shot twice
  $IF4$
  set xTarget = GetUnitX(secondaryTarget)
  set yTarget = GetUnitY(secondaryTarget)
  set temp_ang = Atan2(yTarget-yFocus,xTarget-xFocus)-init_ang
  if $ENEMY$not IsUnitType(secondaryTarget, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(temp_ang)))*(( 2.00*parabola)/( 1 - Cos(temp_ang))) then
  call $FUNC$, secondaryTarget)
  call GroupAddUnit(damage.shotGroup, secondaryTarget)
  set damage.unitsShot = damage.unitsShot + 1
  endif
  $ENDIF$
  call GroupRemoveUnit(g, secondaryTarget)
  endloop
  else
// Again for the targeted version - if a unit has already been shot - it sets the count to 1, else to 0
  $IFAHMT$
  $SC1$
  $ST$
  $ELSE$
  set count = 0
  $ENDIF$
  set left_c = 0
  set right_c = 0
  call GroupEnumUnitsInRange(g, xShooter, yShooter, range, $COND$)
  loop
  set secondaryTarget = FirstOfGroup(g)
  exitwhen secondaryTarget == null
// And again - it doesn't allow the main target to get shot twice
  $IF4$
  set xTarget = GetUnitX(secondaryTarget)
  set yTarget = GetUnitY(secondaryTarget)
  set temp_ang = Atan2(yTarget-yFocus,xTarget-xFocus)-init_ang
  if R2I(temp_ang/(2*bj_PI)) != 0 then
  set temp_ang = ModuloReal(temp_ang , 2*bj_PI)
  endif
  if $ENEMY$not IsUnitType(secondaryTarget, UNIT_TYPE_DEAD) and (xTarget-xFocus)*(xTarget-xFocus)+(yTarget-yFocus)*(yTarget-yFocus) <= (( 2.00*parabola)/( 1 - Cos(temp_ang)))*(( 2.00*parabola)/( 1 - Cos(temp_ang))) then
  if temp_ang >= 0 and temp_ang < 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] = secondaryTarget
  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] = secondaryTarget
  endif
  endif
  endif
  $ENDIF$
  call GroupRemoveUnit(g, secondaryTarget)
  endloop
  set dice = 1
  loop
  exitwhen dice > targets
  if units[dice] != null then
  $SETX$
  $SETY$
  call $FUNC$, units[dice])
  call GroupAddUnit(damage.shotGroup, units[dice])
  set count = count + 1
  set units[dice] = null
  elseif targets - right_c > dice then
  set dice = targets - right_c - 1
  endif
  set dice = dice + 1
  endloop
  set damage.unitsShot = count
  endif
  set udg_MS_Current_Group = damage.shotGroup
  set udg_MS_UnitsInGroup = damage.unitsShot
  set udg_MS_Dummy = dummy
  set dummy = null
//! endtextmacro

  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
  //! runtextmacro Multishot("IssueTargetOrder(dummy, SPELL_ORDER","","","GetUnitX(multiTarget)","GetUnitY(multiTarget)","endif","else","multiTarget","if fixedDamage then","call GetBaseDamage(dummy, damage, initialDamage)","call IssueTargetOrder(dummy, SPELL_ORDER, multiTarget)","if alwaysHitsMainTarget then","call GroupAddUnit(damage.shotGroup, multiTarget)","set damage.unitsShot = damage.unitsShot + 1","if targets == 1 then","set dummy = null","return","if secondaryTarget != multiTarget or not alwaysHitsMainTarget then","set count = 1","set targets = targets - 1","false","condition","")
  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
  static if LIBRARY_AdvancedMultishot then
  //! runtextmacro Multishot("MS_Shoot(dummy, damage.amount, xShooter, yShooter, shotSpeed, damage","set xTarget = GetUnitX(units[dice])","set xTarget = GetUnitY(units[dice])","GetUnitX(multiTarget)","GetUnitY(multiTarget)","endif","else","multiTarget","if fixedDamage then","call GetBaseDamage(dummy, damage, initialDamage)","call MS_Shoot(dummy, damage.amount, xShooter, yShooter, shotSpeed, damage, multiTarget)","if alwaysHitsMainTarget then","call GroupAddUnit(damage.shotGroup, multiTarget)","set damage.unitsShot = damage.unitsShot + 1","if targets == 1 then","set dummy = null","return","if secondaryTarget != multiTarget or not alwaysHitsMainTarget then","set count = 1","set targets = targets - 1","true","condition","")
  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
  //! runtextmacro Multishot("IssueTargetOrder(dummy, SPELL_ORDER","","","xSpell","ySpell","","","null","","","","","","","","","","","","","false","condition","")
  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
  static if LIBRARY_AdvancedMultishot then
  //! runtextmacro Multishot("MS_Shoot(dummy, damage.amount, xShooter, yShooter, shotSpeed, damage","set xTarget = GetUnitX(units[dice])","set yTarget = GetUnitY(units[dice])","xSpell","ySpell","","","null","","","","","","","","","","","","","true","condition","")
  else
  call MultishotPoint(shooter, initialDamage, targets, arc, initRange, attackType, missileArt, offsetAngle, damageType, xSpell, ySpell, condition)
  endif
  endfunction

  function MultishotBasic 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
  //! runtextmacro Multishot("IssueTargetOrder(dummy, SPELL_ORDER","","","GetUnitX(multiTarget)","GetUnitY(multiTarget)","endif","else","multiTarget","if fixedDamage then","call GetBaseDamage(dummy, damage, initialDamage)","call IssueTargetOrder(dummy, SPELL_ORDER, multiTarget)","if alwaysHitsMainTarget then","call GroupAddUnit(damage.shotGroup, multiTarget)","set damage.unitsShot = damage.unitsShot + 1","if targets == 1 then","set dummy = null","return","if secondaryTarget != multiTarget or not alwaysHitsMainTarget then","set count = 1","set targets = targets - 1","false","null","IsUnitEnemy(secondaryTarget, GetOwningPlayer(shooter)) and ")
  endfunction

  function MultishotBasicAdvanced 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
  //! runtextmacro Multishot("MS_Shoot(dummy, damage.amount, xShooter, yShooter, shotSpeed, damage","set xTarget = GetUnitX(units[dice])","set xTarget = GetUnitY(units[dice])","GetUnitX(multiTarget)","GetUnitY(multiTarget)","endif","else","multiTarget","if fixedDamage then","call GetBaseDamage(dummy, damage, initialDamage)","call MS_Shoot(dummy, damage.amount, xShooter, yShooter, shotSpeed, damage, multiTarget)","if alwaysHitsMainTarget then","call GroupAddUnit(damage.shotGroup, multiTarget)","set damage.unitsShot = damage.unitsShot + 1","if targets == 1 then","set dummy = null","return","if secondaryTarget != multiTarget or not alwaysHitsMainTarget then","set count = 1","set targets = targets - 1","true","null","IsUnitEnemy(secondaryTarget, GetOwningPlayer(shooter)) and ")
  else
  call MultishotLight(shooter, multiTarget, initialDamage, targets, arc, initRange, alwaysHitsMainTarget, attackType, missileArt, offsetAngle, fixedDamage, damageType)
  endif
  endfunction

//! textmacro MulthishotOnHit takes SOURCE, VICTIM, AMOUNT, EVENT
// 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 HitDamage takes nothing returns nothing
  local DamProperties damage = LoadInteger(Table, GetHandleId($SOURCE$), 'damp')
  call UnitRemoveAbility( $VICTIM$, BUFF_APPLIED_ID )
  static if AdvancedMultishot_ALWAYS_USE_ADVANCED then
  if damage.advanced then
  return
  endif
  endif
  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
  call UnitDamageTarget( $SOURCE$, $VICTIM$, udg_MS_Damage, true, false, damage.aType, damage.dType, WEAPON_TYPE_WHOKNOWS )
  set damage.alreadyShot = damage.alreadyShot + 1
  if damage.alreadyShot == damage.unitsShot then
  call DamProperties.destroy(damage)
  call FlushChildHashtable(Table, GetHandleId($SOURCE$))
  if not damage.preDummy then
  call UnitApplyTimedLife($SOURCE$, 'BTLF', 0.01)
  endif
  endif
  endfunction

// Checks if a unit, that has taken damage, has the Buff, applied by the Multishot. If it has it - it calls the damaging function.
  private function MultishotDelay takes nothing returns boolean
  if GetUnitAbilityLevel($VICTIM$, BUFF_APPLIED_ID ) > 0 then
  call HitDamage()
  endif
  return false
  endfunction

// Initiation Trigger :P
  function Init takes nothing returns nothing
  set gg_trg_Multishot_v2_1_0_3 = CreateTrigger(  )
  call TriggerRegisterVariableEvent( gg_trg_Multishot_v2_1_0_3, "$EVENT$", EQUAL, 0 ) // change the 0 to something else if needed.
  call TriggerAddCondition( gg_trg_Multishot_v2_1_0_3, 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)
  //call DestroyTrigger(GetTriggeringTrigger())
  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
  set ConditionPlayer = GetOwningPlayer(udg_DamageEventSource)
  call MultishotBasicAdvanced(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 MultishotBasicAdvanced(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventAmount, targets, arc, range, true, ATTACK_TYPE_HERO, missile, 0.00, false, DAMAGE_TYPE_NORMAL, speed)
  endfunction
   
endlibrary

Example caller:
  • Multishot caller
    • Events
    • Game - DamageEvent becomes Equal to 0.00
    • Conditions
    • (Unit-type of DamageEventSource) Equal to (==) Priestess of the Moon
    • Actions
    • Custom script: call MultishotNormal( 4 , 60.00, 600.00, ATTACK_TYPE_PIERCE, 'A02Z')
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I see your problem.
1) You need to set the field "Combat - Attack 1 - Weapon Type (ua1w)" to "Instant" for the Priestess of the Moon.
This way it will stop spawning arrows before the Multishot kicks in.
2) You either need to set private constant boolean WANT_DAMAGE_BLOCK to true OR you need to block the damage in your calling trigger (if your DDS is able to block damage).
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
That made it work but what if I wanted a unit to have multishot when I cast a certain spell on it? Is there a way to change a unit's Weapon Type to Instant using triggers?
 
Level 12
Joined
Jan 2, 2016
Messages
973
There are many ways to "solve" this problem, but the "correct" answer really depends on what EXACTLY are you trying to do. Anyways.. here are 2 of the things you could do:
1) Make all ranged units have "Instant" attack, and make them have multishot, with 1 target allowed, unless they have the buff. When they have the buff - use multishot with more than 1 target(s) allowed.
2) You could make all ranged units have their "Attack 2" set to "Instant", and when you give them the buff, also give them an item ability, based on some orb, which enables them to use Attack 2 instead. And when calling multishot add a condition "the unit has this abillity".
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
How about creating a dummy unit with its 1st attack set to "Instant" then giving it the multishot ability and order it to attack the target?
One should add a way to detect the original shooter so they could detect damage and kill events.
 
Level 12
Joined
Jan 2, 2016
Messages
973
I don't think it works this way...
Anyways... you could do something else:
make another boolexpr function into the MultishoRt library:
JASS:
  function FilterEnemies2 takes nothing returns boolean
  return IsUnitEnemy(GetFilterUnit(), ConditionPlayer) and GetFilterUnit() != udg_DamageEventTarget
  endfunction
and then use
  • Custom script: call MultishotTarget(udg_DamageEventSource, udg_DamageEventTarget, udg_DamageEventAmount, 4, 60, 600, false, ATTACK_TYPE_PIERCE, 'A02Z', 0.00, false, DAMAGE_TYPE_NORMAL, Condition(function FilterEnemies2))
It will look a bit odd this way, cuz it will 1-st hit the original target, and then hit the other ones, but it's kind a simpler than the other solutions xP
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I'm not sure how will this solve my problem.
Multishot restores the main target unit to full health if the shooter uses the 'Chain Lightning (new) + Searing Arrows' trick.
 
Level 12
Joined
Jan 2, 2016
Messages
973
EDIT: Okay, I updated the Multishot to version 2.1.0.4
Now it should work better when you use "enchanted" damage. :p
You could download the new map or you could just replace the "GetBaseDamage" function (in the main library) with this version of it:
JASS:
  private function GetBaseDamage takes unit d, 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 DamageBlock)
  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(d, 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
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
Your modification works fine, although Chain Lightning ability fires both attacks instantaneously.
Can you code the Huntress' Moonglaive ability with orbs?
 
Level 12
Joined
Jan 2, 2016
Messages
973
Perhaps if you post your map, I could have a look at what's happening and come up with some solution. It's kind a hard for me to imagine what's happening there and why xP

Anyways, making a triggered Huntress' Moonglavie ability is quite easy with this Multishot. If you've checked the abilities I have on the sample map - there is one that shoots 5 enemies, and the missiles start bouncing around the enemies, hitting each enemy only once. The moonglavie ability would be triggered in a similar way, but there you'd need to count how many units it has hit, and reduce the damage with every bounce.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
What I'm trying to do is dissimilar to the spell you mentioned in that it his a limited number of hops rather than a chance-controlled splitting. The hero gains the ability normally by learning it.
The problem is assigning udg_MS_Source to a new shooter makes it difficult for me to read the variable that controls number of Moonglaive hops remaining. Is there a way to refer to the original shooter?
I think passing the number of hops variable to the new shooter is possible but the problem is Mask of Death's effect will restore the new udg_MS_Source's health instead of the original shooter's health.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, here is how your triggers will look like:
JASS:
//in the initiation trigger:
call MultishotBasic(/*your configurations*/)

//The boolexpr:
function NotHitYet takes nothing returns boolean
    return (IsUnitEnemy(GetFilterUnit(), udg_TempPlayer) and not IsUnitInGroup(GetFilterUnit(), udg_TempGroup))
endfunction

//and the trigger "On hit":
local real coef = 0.7 // the damage redunction value per bounce
local unit u = LoadUnitHandle(Multishot_Table, GetHandleId(udg_MS_Dummy), 'cstr')
local real x = GetUnitX(udg_MS_Hit_Unit)
local real y = y
local integer count
if u == null then
    set u = udg_MS_Source
    call SaveUnitHandle(Multishot_Table, GetHandleId(udg_MS_Dummy), 'cstr', u)
else
    set udg_MS_Source = u
endif
set udg_TempGroup = LoadGroupHandle(Multishot_Table, GetHandleId(u), 'ahgr')
if udg_TempGroup == null then
    set udg_TempGroup = CreateGroup()
    call SaveGroupHandle(Multishot_Table, GetHandleId(u), 'ahgr')
    set count = 0
else
    set count = LoadInteger(Multishot_Table, GetHandleId(u), 'cont')
endif
set count = count + 1
if count >= MAX_TARGETS_ALLOWED then
    call DestroyGroup(udg_TempGroup)
    call FlushChildHashtable(Multishot_Table, GetHandleId(u))
else
    call GroupAddUnit(udg_TempGroup, udg_MS_Hit_Unit)
    call SaveInteger(Multishot_Table, GetHandleId(u), 'cont', count)
    set udg_TempPlayer = GetOwningPlayer(u)
    call SetUnitX(udg_MS_Dummy, x)
    call SetUnitY(udg_MS_Dummy, y)
    call MultishotPoint(udg_MS_Dummy, udg_MS_Damage*coef, 1, 180, 300, ATTACK_TYPE_PIERCE, 'AXXX', 0, DAMAGE_TYPE_NORMAL, x+50, y, Condition(function NotHitYet))
endif
set u = null
I haven't really tested this trigger. Kind a wrote it directly here, but it should work, at least the way I imagine it xP
And don't worry about flushing the dummy's hashtable - it kind a happens on its own.

If it doesn't work tho, try to change this (In the main library):
JASS:
// end of debug
// if a dummy is the shooter - is sets the "source" to the current value of udg_MS_Source,
// else it creates a dummy and uses it to cast the missiles
  if GetUnitTypeId(shooter) == DUMMY_ID then
      set damage = LoadInteger(Table, GetHandleId(shooter), 'damp')
      if damage == 0 then
          set damage = DamProperties.create()
          call SaveInteger(Table, GetHandleId(shooter), 'damp', damage)
      endif

// into this:

// end of debug
// if a dummy is the shooter - is sets the "source" to the current value of udg_MS_Source,
// else it creates a dummy and uses it to cast the missiles
  if GetUnitTypeId(shooter) == DUMMY_ID then
      set damage = LoadInteger(Table, GetHandleId(shooter), 'damp') + 0  //here is the only difference. The + 0
      if damage == 0 then
          set damage = DamProperties.create()
          call SaveInteger(Table, GetHandleId(shooter), 'damp', damage)
      endif
I think I have gotten some error before, when loading an integer and comparing it to 0. It was null, not 0, and I had a bug xP
Tho I'm not completely sure if it happened with integers...
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I adopted your code into Chain Reaction 2 trigger but the second hop only happens at random chances, and it mostly won't cause any damage. I thought MS_Damage was causing the second problem but the problem persists after I replaced MS_Damage with immediate values.

Here is Chain Reaction 2 trigger:
JASS:
scope ChainFrost2

globals
    private player CondPlayer
    private group CondGroup
endglobals

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


//and the trigger "On hit":
    local real coef = 1.7 // the damage redunction value per bounce
    local unit u = LoadUnitHandle(Multishot_Table, GetHandleId(udg_MS_Dummy), 'cstr')
    local real x = GetUnitX(udg_MS_Hit_Unit)
    local real y = GetUnitY(udg_MS_Hit_Unit)
    local integer count
    local integer MAX_TARGETS_ALLOWED = 2

    if udg_MS_Missile == 'A010' then
    if u == null then
        set u = udg_MS_Source
        call SaveUnitHandle(Multishot_Table, GetHandleId(udg_MS_Dummy), 'cstr', u)
    else
        set udg_MS_Source = u
    endif
    set CondGroup = LoadGroupHandle(Multishot_Table, GetHandleId(u), 'ahgr')
    if CondGroup == null then
        set CondGroup = CreateGroup()
        call SaveGroupHandle(Multishot_Table, GetHandleId(u), 'ahgr', CondGroup)
        set count = 0
    else
        set count = LoadInteger(Multishot_Table, GetHandleId(u), 'cont')
    endif
    set count = count + 1
    if count >= MAX_TARGETS_ALLOWED then
        call DestroyGroup(CondGroup)
        call FlushChildHashtable(Multishot_Table, GetHandleId(u))
    else
        call GroupAddUnit(CondGroup, udg_MS_Hit_Unit)
        call SaveInteger(Multishot_Table, GetHandleId(u), 'cont', count)
        set CondPlayer = GetOwningPlayer(u)
        call SetUnitX(udg_MS_Dummy, x)
        call SetUnitY(udg_MS_Dummy, y)
        call MultishotPoint(udg_MS_Dummy, (1000+(500*count)), 1, 180, 300, ATTACK_TYPE_PIERCE, 'A010', 0, DAMAGE_TYPE_NORMAL, x+50, y, Condition(function NotHitYet))
    endif
        set u = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Chain_Reaction_2_Copy takes nothing returns nothing
    set gg_trg_Chain_Reaction_2_Copy = CreateTrigger(  )
    call TriggerRegisterVariableEvent( gg_trg_Chain_Reaction_2_Copy, "udg_MS_Hit_Event", EQUAL, 0 )
    call TriggerAddCondition( gg_trg_Chain_Reaction_2_Copy, Condition( function Trig_Chain_Frost_2_Conditions ) )
endfunction

endscope
I recommend changing the number of targets for Chain Reaction to 1 instead of 3 for easier observation.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Hmm, I can't seem to get it working either, I will have a closer look at the problem tomorrow :p
May as well rebuild the whole thing... It's a bit messy the way it is now...

EDIT: Okay, I found out what was wrong:
I am destroying the dummy as soon as all the targets have been hit, thus if you've set the targets to 1 - it will get destroyed 0.01 seconds after you call Multishot.
I've fixed this, but I have started rebuilding the Multishot anyways, thus I will need more time until I post the updated version.

However, in the mean time, you could create a new dummy every time you hit a target, and call Multishot, making it the source xP
This way the old dummy will die, but the new one will replace it.
Just don't forget to link the dummy to the original source :p
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I can wait for that. I just hope you expand the system as much as possible.

EDIT: If I alter damage using a DDS other than Multishot's DDS, then the initial, instant shot is affected as well.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
EDIT: Okay, I reworked the Multishot, and I'm uploading the finished version in few mins.
This multishot was one of the 1-st triggers I wanted to make, yet it took me time until I was able to make it work, thus I wrote it while I was still new to JASS (this was my 3-rd JASS trigger). The way I had written it was really bothering me, but now it's a bit better :) (Tho I didn't write it from scratch.. I still used my old codes.. just fixed some parts of it).

The differences between version 2.1.0.4 and 2.2 are:
1) Now the Damage Source is dealing the damage to the target, instead of the Dummy. I added a condition to the Multishot function itself, so this wouldn't start an endless loop :p
2) Fixed several bugs (of which I wasn't really aware before). I had really written the code in some weird way :D
3) Added a boolean, so now you can block the damage via the DDS, without blocking the damage from the Multishot (needed this boolean to finish (1) as well).
4) Changed the structure of Multishot. Now instead of having 6 super long functions - there are 6 short functions, which call other functions, thus reading the Multishot is a bit easier (at least in my opinion).
Also moved the DamProperties struct, as well as the Leak clearing function into another library, required by both Multishot, and its advanced version, thus it no longer uses "ExecuteFunc".
5) Started using Damage Engine in the sample map, instead of GDD (cuz I had to test some stuff, and was too lazy to change it back later). Now you can see how to properly block the damage, with the Damage Engine, instead of using the Multishot's in-built damage blocker.
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I used the Moonglaive (MultishotLight) but it didn't apply an orb effect when the hero (Lady Vashj) picked up an orb.
Can you add a boolean to MultishotLight so that when it is set to true the Moonglaive would hop to an already 'hopped' target if there are no more 'unhopped' targets? For example: if the Moonglaive has 3 remaining hops and there only 2 more unhopped targets, the Moonglaive would hop the first, the second, and then hopping back the first.

Also, is there a way to modify the multishot damage using the new DDS without affecting the Instant attack?
 
Level 12
Joined
Jan 2, 2016
Messages
973
So you want to make a Moonglavie, which stacks with orb effects?
Well, you can make that yourself, using the Multishot as it is now (I don't need to change anything in the libraries). You just need to know what/how to do it.
I suppose I could make a sample trigger for that....
And well, you don't need a DDS to modify Multishot's damage. Just during the MS_Event, set MS_Damage to the value you want. The damage is applied AFTER the MS_Event, and the damage amount is equal to the value of MS_Damage. So if you change that value - it will change the Multishot's damage to whatever you've set it to.

EDIT: Here is a little tip how you could make the Moonglavie + Orb effect work:
Change the condition for Moonglavie from if udg_MS_Missile == 'A014' then to if GetUnitAbilityLevel( udg_MS_Source, 'XXXX') > 0 then //where 'XXXX' is some passive ability, indicating that the unit has Moonglavie
And when calling Multishot inside this trigger, change the missile id from 'A014' to udg_MS_Missile

EDIT 2: I actually could really lighten up some of the sample triggers, after updating to version 2.2... Now most of the stuff I was manually doing before is automated xP
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I'm not sure how to change the missile ID.

Edit: Line 53 in MultishoRts has udg_GDD_DamageSource.
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I updated it to version 2.2b.
Updated the sample triggers, and added a Moonglavie, which supports Orbs.
Also fixed the udg_GDD_DamageSource (now it's udg_DamageEventSource)
And I changed the offset angle to deg again, instead of rads.
And last, but not least - I fixed a bug, which I had caused when I updated the version to 2.2 - the main target was NEVER getting shot if "alwaysHitMainTarget" was set to false.

EDIT: Actually I just remembered you could use udg_MS_UnitsInGroup instead of this "count". You don't need it xP
It should look like this:
JASS:
    elseif udg_MS_Missile == 'A014' 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_PIERCE, 'A014', 0, DAMAGE_TYPE_NORMAL, x + 100, y + 100, Condition(function BouncingLightCond),900.00)
    elseif GetUnitTypeId(udg_MS_Source) == 'Emoo' 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
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Thanks for reporting that!
I fixed it :)
Keep reporting if you find any other bugs :p
I will even give you +rep for reporting this ^_^

Uploaded the fixed version.
For some reason I had the damaged unit damage itself for the armor test. And when that unit has multishot itself - it started multishoting itself, and the Damage Engine was killing the unit, or something, and was displaying that error message.
Now I made the dummy deal the test damage. Also the GetBaseDamage function requires one argument less :p
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I'm glad I could help.

You didn't configure Cheat Death Ability & Detect Damage Ability in Damage Engine's Configuration trigger. Are you using this one?

Why are you using "DamageModifierEvent equal to 0.00" in your demos? Shouldn't "DamageEvent equal to 1.00" be more proper?

Edit: I just noticed that you're modifying damage in these demos, but still, shouldn't DamageModifierEvent equal to 1.00 rather than 0.00?
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
Yes, it should be... (I think)
Everything was working even like this, so I didn't bother fixing it.
These triggers are demo triggers anyways... When you are making your own spells - you can do the right thing xP
I've made these demo triggers to show part of the things you can make, using my Multishot.

I am using some similar triggers on my map, but they are way more advanced than they are in this demo version :D

Anyways... I've originally created the Multishot, using GDD, not DamageEngine, so the Demo Triggers may have some wrong variables, or values like the ones you mentioned. I wouldn't bother fixing them (for now), unless any bugs are found, or unless I release another update (tho I'm kind a happy with how it turned out, and may not release any updates soon).
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
Problem is when I updated my GDD and Multishot system in my map, all multishots hit the maintarget; the shooter's projectiles seem to overlay each other.
 
Top