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

Detecting when unit takes damage from certain spell?

Status
Not open for further replies.
Level 3
Joined
Mar 9, 2016
Messages
45
Hey folks, it's great to see the wc3 mapmaking active still! Haven't been involved since the realease of sc2, but thought I'd return and try an idea for a map out. But it seems I've forgotten a lot...

I want to make a spell that's like stormbolt and thunderclap combined. That is, a stormbolt that produces a thunderclap on it's target rather than the basic storm bolt single target stun.

My first idea was to create a storm bolt that does 1 damage and a 0.1 sec stun, and then detect when a unit is damaged by storm bolt, create an invisible dummy unit at the target location and order the dummy unit to cast thunderclap. But that doesn't work, it seems. There doesnt seem to be a way for the triggers to detect which spell damage comes from?

How should I go about making this spell? Thanks!

PS. I don't know JASS
 
Level 3
Joined
Mar 9, 2016
Messages
45
you need to trigger the stormbolt. Create a dummy missle and move it to the target. if the range between target and dummy becomes smaller than x, then do your thunderclap actions

Is that really the simplest way of doing it? Do I just use a simple move command then? And isn't the max movespeed 522? What if I want it to move faster than that?
 
Level 3
Joined
Mar 9, 2016
Messages
45
For that use "Unit has buff" Condition.

Actions--->
Unit - Create 1 Dummy at center of region....
Unit - Order last created unit to thunderclap....

I dont quite follow. When do I check for the buff?
Unit - Any Unit takes damage
Unit - Triggering unit has <storm bolt stun>
Unit - Create Dummy
Unit - Order Dummy to thunderclap

like that?

Thanks
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
Is that really the simplest way of doing it? Do I just use a simple move command then? And isn't the max movespeed 522? What if I want it to move faster than that?
no, you dont order the unit to move, you move the unit with setting its location periodically. Basically you "teleport" the unit in a very small interval and small range towards the target to make it look like movement
 
Level 3
Joined
Mar 9, 2016
Messages
45
no, you dont order the unit to move, you move the unit with setting its location periodically. Basically you "teleport" the unit in a very small interval and small range towards the target to make it look like movement

oh. I know how to do that, but it always looks so choppy when I do it... :s

also, on a side note, from what I remember there is an ability which is the "ideal" ability to base custom spells off of. I can't find it in the menus tho... Am I remembering incorrectly, or is there such an ability? thanks again for the quick replies :)
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
Channel is the ability you're searching for.
to make it look less choppy, decrease the interval and the offset. but dont go too low, otherwise you will get performance drops. 0.03 is the minimum you should take for the interval
 
Level 7
Joined
Oct 19, 2015
Messages
286
Acid bomb has a projectile and can slow the targets, so it can be made to look like thunderclap. The damage it deals is dealt over time though, although with a long enough interval it might only do it when it first hits. Drunken haze also has a projectile and can reduce attack and movement speed, but doesn't deal damage.

If you're using vJass, though, triggering spells like these is a piece of cake.
 
Level 3
Joined
Mar 9, 2016
Messages
45
if you do not want to stun target acid bomb is very popular as a base and its buff can be changed

The problem right now is just getting the different "phases" of the ability up and running... Mainly to connect the projectile hitting the target to trigger a thunderclap on it. Stun or no stun doesnt matter so much atm :)
 
Level 3
Joined
Mar 9, 2016
Messages
45
If you're using vJass, though, triggering spells like these is a piece of cake.

What do you mean exactly? Is it just a matter of adding a couple of lines to make it work, or what? Cause I think I could do that... I mean im really shit at programming, but I think I could manage that :)
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
What do you mean exactly? Is it just a matter of adding a couple of lines to make it work, or what? Cause I think I could do that... I mean im really shit at programming, but I think I could manage that :)
actually, you can do the same thing in GUI, just way less efficient. the problem with using a buff detection is, that you have a limited amount of creating such abilities, because using the same base ability twice could bug. I'd still recommend you to use a dummy missile for that.

EDIT: because i said "way less efficient": it is still efficient enough if you do it correctly and it wont lag at all
 
Level 3
Joined
Mar 9, 2016
Messages
45
actually, you can do the same thing in GUI, just way less efficient. the problem with using a buff detection is, that you have a limited amount of creating such abilities, because using the same base ability twice could bug. I'd still recommend you to use a dummy missile for that.

EDIT: because i said "way less efficient": it is still efficient enough if you do it correctly and it wont lag at all
ok, so if I understand you correctly, there is a way to do it in the GUI, using buff detection rather than your dummy missile method, correct?

Then, how do I detect buffs? I seriously cant find it in the menus :(
Also, how would the trigger look if done in vJass?

edit: im not entirely convinced about your dummy missile method... It seems very non-flexible when it comes to people teleporting/blinking away while the projectile is mid-air. If I want the missile to disjoint if anything like that would happen, it would be a lot of extra work, you know?

edit2: i think the vJass way might be the way to go if it's just a matter of adding a couple of lines after converting from GUI... If someone would just tell me what line(s) to add... kinda

edit3: srsly, the "Unit has Buff" conditions is not in my freaking editor, wtf!? I have:
Unit Classification Check
Unit-type Classification Check
Unit in Unit Group
Unit in Region
Unit is Alive
Unit is Dead
Unit is Paused
Unit is Hidden
Unit is an Illusion
Unit Sleeps
Unit Sleeps at Night
Unit is Sleeping
Unit Generates Alarms
Unit is being Transported
Unit is in Transport
Unit is Selected by Player
Unit Belongs to player
Unit Belongs to An Ally of Player
Unit Belongs to An Enemy of Player

Shouldnt "Unit has Buff" be in this freaking list???
 
Level 10
Joined
May 21, 2006
Messages
323
There is a pretty simple method with Bribes DDS. I use it and it is bullet proof. You set the damage of the stormbolt to 0.01 and in the gameplay constances you set all spell damage to 100% regardless of armor type etc. Then you can simply trigger DamageEventAmount = 0.01 and DamageSource = Mountain King. So evertime a unit gets 0.01 damage the system knows that this was from Stormbolt and then you just modify the damage to cause 100/200/300 or whatever damage.
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
ok, so if I understand you correctly, there is a way to do it in the GUI, using buff detection rather than your dummy missile method, correct?
nah, rather use the missile dummy :D

edit: im not entirely convinced about your dummy missile method... It seems very non-flexible when it comes to people teleporting/blinking away while the projectile is mid-air. If I want the missile to disjoint if anything like that would happen, it would be a lot of extra work, you know?
that is actually just one condition where you check if the distance between the dummy and the target is too big

edit2: i think the vJass way might be the way to go if it's just a matter of adding a couple of lines after converting from GUI... If someone would just tell me what line(s) to add... kinda
ehm.. that's not how you do it. As i said, you can do your stuff in GUI the same way you do it in vJass.
You can make use of "Custom Script:" instead of converting the GUI trigger, because it looks really messy then.

edit: "unit has specific buff" comes directly after "Unit Belongs to An Enemy of Player"
 
Level 3
Joined
Mar 9, 2016
Messages
45
There is a pretty simple method with Bribes DDS. I use it and it is bullet proof. You set the damage of the stormbolt to 0.01 and in the gameplay constances you set all spell damage to 100% regardless of armor type etc. Then you can simply trigger DamageEventAmount = 0.01 and DamageSource = Mountain King. So evertime a unit gets 0.01 damage the system knows that this was from Stormbolt and then you just modify the damage to cause 100/200/300 or whatever damage.

"DamageEventAmount" sounds like jass? do I need to know jass to do this?

and also, how would this work with other custom spells? Would I need to set the second custom spell dmg to 0.02 and the third to 0.03?

I have looked in to Weeps DDS but it doesnt seem to recognize which specific ability the damage is coming from... is this different?

Thanks! I love how active this community is. Way more active than the sc2 mapmaking community ;)
 
Level 3
Joined
Mar 9, 2016
Messages
45
nah, rather use the missile dummy :D


that is actually just one condition where you check if the distance between the dummy and the target is too big


ehm.. that's not how you do it. As i said, you can do your stuff in GUI the same way you do it in vJass.
You can make use of "Custom Script:" instead of converting the GUI trigger, because it looks really messy then.

edit: "unit has specific buff" comes directly after "Unit Belongs to An Enemy of Player"
Ok I'll check your method out.

K, what would those custom scripts be, then?

After Unit Belongs to An Enemy of Player" I have "Unit Group - Unit Group is Empty"... wtf
 
Level 13
Joined
Aug 19, 2014
Messages
1,111
Here is an example trigger for you.

  • OM Loop
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • For each (Integer Spell_Integers[3]) from 1 to Spell_Integers[2], do (Actions)
        • Loop - Actions
          • -------- we check if the caster has the buff --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Spell_Caster[Spell_Integers[3]] has buff Osmosis) Equal to True
            • Then - Actions
              • -------- then if it has we set the point where the caster are --------
              • Set Spell_Point[Spell_Integers[3]] = (Position of Spell_Caster[Spell_Integers[3]])
              • -------- a temp group with the aoe --------
              • Set Spell_Group[Spell_Integers[3]] = (Units within Spell_Radius[Spell_Integers[3]] of Spell_Point[Spell_Integers[3]] matching ((((Matching unit) is A structure) Equal to False) and ((((Matching unit) is Magic Immune) Equal to False) and ((((Matching unit) is alive) Equal to True) and (((Matching
              • -------- we pick units --------
              • Unit Group - Pick every unit in Spell_Group[Spell_Integers[3]] and do (Actions)
                • Loop - Actions
                  • -------- if they have the ice lance buff --------
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • ((Picked unit) has buff Poisoned) Equal to True
                    • Then - Actions
                      • -------- we double the damage --------
                      • Unit - Cause Spell_Caster[Spell_Integers[3]] to damage (Picked unit), dealing (Spell_Damage[Spell_Integers[3]] x 2.00) damage of attack type Magic and damage type Normal
                    • Else - Actions
                      • -------- else we do normal damage --------
                      • Unit - Cause Spell_Caster[Spell_Integers[3]] to damage (Picked unit), dealing Spell_Damage[Spell_Integers[3]] damage of attack type Magic and damage type Normal
              • -------- leak removing --------
              • Custom script: call RemoveLocation(udg_Spell_Point[udg_Spell_Integers[3]])
              • Custom script: call DestroyGroup(udg_Spell_Group[udg_Spell_Integers[3]])
            • Else - Actions
              • -------- if the caster dont got the buff, meaning the spell is turned off --------
              • -------- we decrease the index --------
              • Set Spell_Index[1] = (Spell_Index[1] - 1)
          • -------- and ofc a small if/then/else that checks the index so we can turn the trigger off when it's not needed --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Spell_Integers[1] Equal to 0
            • Then - Actions
              • Set Spell_Integers[2] = 0
              • Trigger - Turn off (This trigger)
            • Else - Actions
  • [/hidden]
When you create a Unit Group you will have to put If (All Conditions are True) then do (Then Actions) else do (Else Actions) above it. Then simply check if the picked unit in the Unit Group has an specific buff.
 
Level 3
Joined
Mar 9, 2016
Messages
45
Here is an example trigger for you.

  • OM Loop
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • For each (Integer Spell_Integers[3]) from 1 to Spell_Integers[2], do (Actions)
        • Loop - Actions
          • -------- we check if the caster has the buff --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Spell_Caster[Spell_Integers[3]] has buff Osmosis) Equal to True
            • Then - Actions
              • -------- then if it has we set the point where the caster are --------
              • Set Spell_Point[Spell_Integers[3]] = (Position of Spell_Caster[Spell_Integers[3]])
              • -------- a temp group with the aoe --------
              • Set Spell_Group[Spell_Integers[3]] = (Units within Spell_Radius[Spell_Integers[3]] of Spell_Point[Spell_Integers[3]] matching ((((Matching unit) is A structure) Equal to False) and ((((Matching unit) is Magic Immune) Equal to False) and ((((Matching unit) is alive) Equal to True) and (((Matching
              • -------- we pick units --------
              • Unit Group - Pick every unit in Spell_Group[Spell_Integers[3]] and do (Actions)
                • Loop - Actions
                  • -------- if they have the ice lance buff --------
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • ((Picked unit) has buff Poisoned) Equal to True
                    • Then - Actions
                      • -------- we double the damage --------
                      • Unit - Cause Spell_Caster[Spell_Integers[3]] to damage (Picked unit), dealing (Spell_Damage[Spell_Integers[3]] x 2.00) damage of attack type Magic and damage type Normal
                    • Else - Actions
                      • -------- else we do normal damage --------
                      • Unit - Cause Spell_Caster[Spell_Integers[3]] to damage (Picked unit), dealing Spell_Damage[Spell_Integers[3]] damage of attack type Magic and damage type Normal
              • -------- leak removing --------
              • Custom script: call RemoveLocation(udg_Spell_Point[udg_Spell_Integers[3]])
              • Custom script: call DestroyGroup(udg_Spell_Group[udg_Spell_Integers[3]])
            • Else - Actions
              • -------- if the caster dont got the buff, meaning the spell is turned off --------
              • -------- we decrease the index --------
              • Set Spell_Index[1] = (Spell_Index[1] - 1)
          • -------- and ofc a small if/then/else that checks the index so we can turn the trigger off when it's not needed --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Spell_Integers[1] Equal to 0
            • Then - Actions
              • Set Spell_Integers[2] = 0
              • Trigger - Turn off (This trigger)
            • Else - Actions
  • [/hidden]
When you create a Unit Group you will have to put If (All Conditions are True) then do (Then Actions) else do (Else Actions) above it. Then simply check if the picked unit in the Unit Group has an specific buff.

I dont see how this helps me? This spell doesnt have a projectile or anything?
 
Level 3
Joined
Mar 9, 2016
Messages
45
you can directly write jass code into the GUI interface

i tried ur "move unit instantly" method. it was choppy.
i also dont seem to be able to figure out the math to move a unit towards a point. How do i get the correct angle?
also, how do I change the animation of a spell? From what i remember it's as easy as changing some value from "spell" to "attack" if I want the spell to have an attack animation. I cant find the correct field.

The more I look around I think my editor is lacking functionality.
 
Level 14
Joined
Nov 17, 2010
Messages
1,265
edit3: srsly, the "Unit has Buff" conditions is not in my freaking editor, wtf!? I have:
Unit Classification Check
Unit-type Classification Check
Unit in Unit Group
Unit in Region
Unit is Alive
Unit is Dead
Unit is Paused
Unit is Hidden
Unit is an Illusion
Unit Sleeps
Unit Sleeps at Night
Unit is Sleeping
Unit Generates Alarms
Unit is being Transported
Unit is in Transport
Unit is Selected by Player
Unit Belongs to player
Unit Belongs to An Ally of Player
Unit Belongs to An Enemy of Player

Shouldnt "Unit has Buff" be in this freaking list???

Unit has specific buff should be right after unit belongs to an enemy of player. Do you have the latest patch and everything? 1.26 I think
 
Level 3
Joined
Mar 9, 2016
Messages
45
Unit has specific buff should be right after unit belongs to an enemy of player. Do you have the latest patch and everything? 1.26 I think

I dont think I did. I uninstalled both wc3 and wc3 TFT and downloaded them from battle.net instead (used CD's before) and now it seems i have some functionalities I was sure i was missing before. "unit has specific buff" being one of them... Hope this will end a lot of frustration!
 
Level 3
Joined
Mar 9, 2016
Messages
45
there you go, of course you can modify it yourself
thank you soo much, hopefully this will teach me what i did that I shouldnt have and what i didnt do that i should :)

and yh i was on an earlier version of the editor earlier... sigh ty alot
 
Level 3
Joined
Mar 9, 2016
Messages
45
If you want to get back to your thunderclap idea, replace the dummy ability with a target slow ability and there you go ;)

Hello again :) I've looked into the ability and I understand the gist of how it works now. However I have a few questions about the triggers

What is SB_Index for? I see that you use it to define other variables and stuff, but what's the reason for that?

What is SB_Group for? You pick all the units in that group at the start of the "Storm Bolt Loop" trigger, but since the only unit in the group is SB_Caster, why not just pick SB_Caster directly?

Also, the "Unit Indexer" is way, way above my head. If it has any impact on how the spell works, I'd love for you to explain that to me aswell :)

I really appriciate all the help, so a big thanks yet again! :)

edit: oh, also, is there a reason for dealing damage through triggers rather than through the spells themselves?
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
the unit indexer is not too complicated, basically, it gives every unit a unique value.
the SB_Index loads this value. So what does it do to index the variables?
if you would not index the variables, the spell would not work if more than 1 unit casts the spell at a time. thats where SB_Group comes in. If 2 units cast the ability at the same time, there will be 2 units in the group and each loop is separated. (due to the indexed variables)

example:
Paladin has the custom value "3"
-->
SB_Target[3] = target unit of ability being cast

Bloodmage has the custom value "7"
-->
SB_Target[7] = target unit of ability being cast

those units are in the SB_Group now.
You loop through the group and load their custom value
Loop1 will be the paladin and will load your 3
now you can take SB_Target[3] and will get the original target the paladin casted on, rather than getting overwritten by the bloodmage cast


EDIT: you usually let neutral units cast buffs/abilities on the enemies, for vision reason and so on. thats why you trigger the damage, that your hero actually deals the damage rather than the dummy
 
Level 3
Joined
Mar 9, 2016
Messages
45
the unit indexer is not too complicated, basically, it gives every unit a unique value.
the SB_Index loads this value. So what does it do to index the variables?
if you would not index the variables, the spell would not work if more than 1 unit casts the spell at a time. thats where SB_Group comes in. If 2 units cast the ability at the same time, there will be 2 units in the group and each loop is separated. (due to the indexed variables)

example:
Paladin has the custom value "3"
-->
SB_Target[3] = target unit of ability being cast

Bloodmage has the custom value "7"
-->
SB_Target[7] = target unit of ability being cast

those units are in the SB_Group now.
You loop through the group and load their custom value
Loop1 will be the paladin and will load your 3
now you can take SB_Target[3] and will get the original target the paladin casted on, rather than getting overwritten by the bloodmage cast


EDIT: you usually let neutral units cast buffs/abilities on the enemies, for vision reason and so on. thats why you trigger the damage, that your hero actually deals the damage rather than the dummy

Oh, I see. It works. However, it doesn't work when the same unit casts the spell in quick succession (i removed the cooldown), which was the next thing I was gonna ask about... Maybe that is because you seem to have put a few commands in the wrong order. For example like this:

Unit - Add a 1.00 second Generic expiration timer to SB_TempUnit
Set SB_TempUnit = (Last created unit)
you set the variable after given the unit the expiration timer. Could something like this cause the trigger to not run properly when the same unit casts the spell in quick succession? Or is it just something that the index isn't designed to handle?

I Tried fixing it myself by making the missile and target variables arrays of [99], adding an integer counter that went up whenever the ability was used, then storing that counter as a local variable at the start of the loop trigger. I guess this would work, but I cant finish the trigger because in order to call the local variable later, I have to write whole commands in jass, which I cant... (the local variables dont show up in the drop down menus you know). Wouldn't this allow for 99 instances of the spell running simultaneously?

Hope you understand what I mean, and thanks once again! :)
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
thats not a mistake, it just does not work this way. if you want to multicast the same ability on the same unit you would need to attach the data to the missile itself

EDIT: you are right, setting the variable after starting the timer is a mistake, however, that does not solve your problem. I attached a map where you can spam the ability without problems
 

Attachments

  • StormBolt.w3x
    23.4 KB · Views: 60
Level 7
Joined
Oct 19, 2015
Messages
286
I see the thread has continued in another direction since I last posted so I hope this won't be too off-topic. The reason I recommended vJass is because it makes it very easy to share and reuse code, so spells that would be difficult and/or tedious to write otherwise can be made easily by using existing systems that other users wrote.

For making spells, I for example highly recommend the xe package. I made your spell by using it and the code is really short (if it seems long that's because I added a lot of comments explaining how things work). I also used SpellEvent. Here it is:

JASS:
// we put vJass code into libraries, this makes things easier to organize
// since we don't have to put all our system code in the map header section
// the library requirements will make sure that code will get added in correct order
// the libraries have their own initializer (function that runs when the game starts)
// so we don't have to use an "InitTrig_" function for that.

library ProjectileThunderclap initializer Init requires SpellEvent, xemissile, xecast

    // in vJass, we can declare globals anywhere with a "globals/endglobals" block
    // this is a lot more practical nicer than using the GUI variable editor
    globals
        // in vJass, when using libraries, we can use the "private" keyword
        // this will prevent any name conflicts between libraries
        // even if they use functions or variables with the same name
        
        // usually, we start the code of each library with a calibration section
        // this makes it easy to tweak the spell without having to go through the code
        // it is good coding practice to only use variables in the code, no direct values
        
        // this is the spell that the hero casts
        private constant integer SPELL_ABILITY_ID = 'A000'
        
        // this is the thunderclap spell that the dummy caster will cast
        private constant integer DUMMY_ABILITY_ID = 'A001'
        private constant string DUMMY_ORDER_STRING = "thunderclap"
        
        // these values get used for the missile
        private constant real MISSILE_Z_OFFSET = 60.0
        private constant real MISSILE_SPEED = 1000.0
        private constant real MISSILE_ARC = 0.15
        private constant string MISSILE_MODEL = "customModel.mdl"

    endglobals

    // end of calibration section

    // in vJass, structs are basically multi-instanceable data and code containers
    // in this case we are declaring a struct which extends another struct
    // that way it gets to use all the code that the other struct contains
    // by extending xehomingmissile, we get all its missile movement code
    // so we don't have to write any such code ourselves
    private struct SpellMissile extends xehomingmissile
        // values declared in the struct are multi-instanceable
        // this means we don't have to worry about other missiles overwriting them
        // this value will store the caster of the spell on the missile
        private unit caster
        // this will store the level
        private integer level
        
        // functions inside structs are called "methods"
        // the difference is that methods get called on a specific struct instance
        // so if we have multiple missiles flying around at the same time,
        // we know for which one the code is being executed
        
        // a method with the name "onHit" will be called by xemissile code
        // automatically when the missile hits
        method onHit takes nothing returns nothing
            // we use an xecast struct to handle our dummy casters
            // note the "." syntax for calling methods on structs
            // first we must create an xecast instance by using a create method
            // in this case we use the "createA" method instead of "create"
            // this is an alternate constructor for xecast that makes an instance
            // which will clean itself up automatically once it is used
            local xecast xec = xecast.createA()
            // then, we setup all the xecast values
            // the "." syntax is the same for setting values as it is for calling methods
            set xec.abilityid = DUMMY_ABILITY_ID
            set xec.orderstring = DUMMY_ORDER_STRING
            // each method has an implicit "this" local value
            // we can use this value to acces data stored on this specific missile
            set xec.level = this.level
            set xec.owningplayer = GetOwningPlayer( this.caster )
            // once our xecast object is set up, we use it
            // by calling the appropriate method
            // since we used the "createA" xecast method to create it,
            // this will also automatically destroy it afterwards
            // so we don't have to worry about that
            call xec.castInPoint( this.x, this.y )
            // instead of creating an xecast object every time a missile hits,
            // we could create just one xecast in the Init function
            // and then reuse it for all the missiles
            // in that case we would need to use the "create" method instead of "createA"

            // an xemissile automatically terminates once it hits
            // so we don't have to destroy it ourselves
            // it is just good practice to null the handle values
            set caster = null
        endmethod
        
        // "static methods" are more like functions,
        // they are not specific for one struct instance
        // the method that creates a new instance is a static method,
        // it couldn't really be a normal method
        // since we don't have any instances yet until we call it
        static method create takes unit caster, unit target returns SpellMissile
            local real x = GetUnitX( caster )
            local real y = GetUnitY( caster )
            local real z = MISSILE_Z_OFFSET
            
            // the "allocate" method gets us a new struct instance
            // in this case, because the struct extends another struct
            // the allocate method must take some arguments
            // as they are defined in the xehomingmissile create method
            local SpellMissile this = SpellMissile.allocate( x,y,z, target,z )

            // once a missile is allocated, we can store values on it
            // here we store our custom values that we declared earlier
            set this.caster = caster
            set this.level = GetUnitAbilityLevel( caster, SPELL_ABILITY_ID )
            
            // we also have to setup the xemissile art
            set this.fxpath = MISSILE_MODEL
            
            // xemissile is coded as a two-step process
            // the missile must first be created as we did above
            // then it must be launched with a separate method
            call this.launch( MISSILE_SPEED, MISSILE_ARC )
            
            // the create method must always return the new instance
            // although in this case we don't use the returned value for anything
            // since we already launch the missile from inside the create method
            return this
        endmethod

    endstruct
    
    private function onCast takes nothing returns nothing
        // when the spell is cast, create a missile
        // the rest is handled by the missile code above
        call SpellMissile.create( SpellEvent.CastingUnit, SpellEvent.TargetUnit )
    endfunction

    private function Init takes nothing returns nothing
        // we call a function from the SpellEvent library to register our event
        // this is a bit less work than creating a trigger and giving it a condition
        // it is also more efficient since the SpellEvent library only uses one trigger
        call RegisterSpellEffectResponse( SPELL_ABILITY_ID, onCast )
    endfunction

endlibrary

Now, instead of using a dummy caster to cast thunder clap, we could take it a step further and also deal the damage in an area with triggers and then apply a triggered slow buff by using a buff system.
 
Level 3
Joined
Mar 9, 2016
Messages
45
check my last post ^

EDIT: you attach the variables to the missile, you make use of the missile's custom value instead of the caster's

amazing. you make this seem so simple :D

so the only difference is that you set SB_Index to "Custom value of SB_Missile" instead of "Custom value of SB_Caster"?

god, more questions pop up for every answer. Youre so nice to answer all of them so quickly!

I feel like I should know at least the basics of how this index system works... I dont understand how the "Unit Indexer" trigger links to the other triggers. The trigger itself just runs on map initialization and it never gets referenced in the Storm Bolt triggers. So how does it index units getting spawned/bought etc?

Where does the Custom Value come from, and how can it change or whatever? I dont see it manipulated anywhere in the triggers or anything.
 
Level 3
Joined
Mar 9, 2016
Messages
45
I see the thread has continued in another direction since I last posted so I hope this won't be too off-topic. The reason I recommended vJass is because it makes it very easy to share and reuse code, so spells that would be difficult and/or tedious to write otherwise can be made easily by using existing systems that other users wrote.

For making spells, I for example highly recommend the xe package. I made your spell by using it and the code is really short (if it seems long that's because I added a lot of comments explaining how things work). I also used SpellEvent. Here it is:


Now, instead of using a dummy caster to cast thunder clap, we could take it a step further and also deal the damage in an area with triggers and then apply a triggered slow buff by using a buff system.
Wow, thanks man! Being the noob that I am I simply pasted that stuff into a trigger and ofc I got an error message. So this is a "library" huh? Would you mind briefly explaining what a library is? :)
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
can be made easily by using existing systems that other users wrote.
but that is only an advantage if you have at least basic jass knowledge ;)
for him, i'd prefer GUI triggers.

amazing. you make this seem so simple :D

so the only difference is that you set SB_Index to "Custom value of SB_Missile" instead of "Custom value of SB_Caster"?

god, more questions pop up for every answer. Youre so nice to answer all of them so quickly!

I feel like I should know at least the basics of how this index system works... I dont understand how the "Unit Indexer" trigger links to the other triggers. The trigger itself just runs on map initialization and it never gets referenced in the Storm Bolt triggers. So how does it index units getting spawned/bought etc?

Where does the Custom Value come from, and how can it change or whatever? I dont see it manipulated anywhere in the triggers or anything.

The custom value is a basic thing in warcraft 3, you can basically attach an integer number to every unit with it. a unit indexer gives a unit a value that is unique.
for example: you have 3 units on your map on map initialization. the indexer will loop through them and give them the number 1 to 3 (or 0 to 2)
so:
Footman gets Custom value 1
Knight gets 2
Mountain King gets 3

now whenever a new unit enters the map, the counter will increase and the unit will get a unique number.

you create a bloodmage, he will get the custom value 4... and so on

EVERY UNIT will have a unique number, and you can make use of this by creating MUI spells
MUI means Multi Unit Indexing and is basically the ability to cast spells more than once in the same time by more units without bugging.

I will do an example here: A target ability that just deals damage after a 5 seconds delay.

Without MUI

  • Trig Cast
    • Events
      • Einheit - A unit starts the effect of an ability
    • Conditions
      • (Ability being cast) equal to YourAbility
    • Actions
      • Set Caster = (Triggering unit)
      • Set Target = (Target unit of ability being cast)
      • Set Time = 5.00
      • Trigger - Turn on Trig Loop <gen>
  • Trig Loop
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Set Time = (Time - 0.10)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • 'IF'-Conditions
          • Time smaller than 0.00
        • 'THEN'-Actions
          • Unit - Cause Caster to damage Target, dealing 500.00 damage of attack type Spell and damage type Normal
          • Trigger - Turn off (This trigger)
        • 'ELSE'-Actions
this works just fine as long as it's the only unit casting it. Why?
Imagine. A paladin casts the ability, so the Caster variable is set to the Paladin. Now, if the 5 second Time variable is not over, and a Bloodmage casts the ability, the Time will be set back to 5 and the Caster is now the Bloodmage, so the Paladin ability gets basically interrupted by the Bloodmage cast (the Bloodmage cast will now deal damage after the 5 seconds except if another unit casts this ability again and sets everything back :D)

Now, we do some fixes to get around this problem, we use Custom value and Arrays to make it MUI. I assume you know what an array is.

  • Trig Cast
    • Events
      • Unit - A unit starts the effect of an ability
    • Conditions
      • (Ability being cast) equal to YourAbility
    • Actions
      • Set Caster = (Triggering unit)
      • Set Index = (Custom value of Caster)
      • Set Target[Index] = (Target unit of ability being cast)
      • Set Time[Index] = 5.00
      • Unit Group - Add Caster to Group
      • Trigger - Turn on Trig Loop <gen>
  • Trig Loop
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in Group and do (Actions)
        • Loop - Actions
          • Set Caster = (Picked unit)
          • Set Index = (Custom value of Caster)
          • Set Time[Index] = (Time[Index] - 0.10)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • 'IF'-Conditions
              • Time[Index] smaller than 0.00
            • 'THEN'-Actions
              • Unit - Cause Caster to damage Target[Index], dealing 500.00 damage of attack type Spell and damage type Normal
              • Unit Group - Remove Caster from Group
            • 'ELSE'-Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • 'IF'-Conditions
          • (Group is empty) equal to True
        • 'THEN'-Actions
          • Trigger - Turn off (This trigger)
        • 'ELSE'-Actions
what is different here?
well lets take the example from before.
Paladin casts the ability and he has the custom value 5
so Index is 5 and the target and the time will be saved in the fifth array field.
Target[5] = Target Unit of Ability
Time[5] = 5.00

now, in the loop, the paladin is picked (because it got added before) and the Index will be setted to the custom value of the Paladin again, so Index = 5
Now he will load his Target and time properly.
Time[5] = Time[5] - 0.1
damage Target[5]

Let us do the same thing as before, the Bloodmage now casts his ability while the paladin's ability is not over yet.
what happens:
Index is set to the BLOODMAGES custom value, for example 8.
so we will save the target and the time in those variable fields.
Target[8] = Target Unit of Ability
Time[8] = 5.00
but we will NOT overwrite the Paladins variables, because they are saved in [5].
Bloodmage is added to the group and will loop properly with his Index (8) through the group



I hope that helps ;)
 
Level 3
Joined
Mar 9, 2016
Messages
45
TEXT

I hope that helps ;)

It helps a lot thank you :)

But I know the purpose of the indexer, what I dont understand is how it does what it does, which is important if I am gonna make my own spells (which I intend to do)

But its actually really great that you made that exact example trigger, because I was trying to make a very similar spell.

  • Rend Cast
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Rend
    • Actions
      • Set Rend_Caster = (Triggering unit)
      • Set Rend_Index = (Custom value of Rend_Caster)
      • Set Rend_Target[Rend_Index] = (Target unit of ability being cast)
      • Set Rend_Counter[Rend_Index] = (Custom value of Rend_Caster)
      • Unit Group - Add (Triggering unit) to Rend_Group
      • Unit - Create 1 Dummy for (Owner of (Triggering unit)) at (Position of (Triggering unit)) facing (Position of Rend_Target[Rend_Index])
      • Unit - Order (Last created unit) to Orc Shaman - Bloodlust Rend_Target[Rend_Index]
      • Trigger - Turn on Rend Loop <gen>
  • Rend Loop
    • Events
      • Time - Every 0.50 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in Rend_Group and do (Actions)
        • Loop - Actions
          • Set Rend_Index = (Custom value of Rend_Caster)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Rend_Target[Rend_Index] has buff Bloodlust) Equal to True
            • Then - Actions
              • Unit - Cause Rend_Caster to damage Rend_Target[Rend_Index], dealing (10.00 + (3.00 x (Real((Level of Rend for Rend_Caster))))) damage of attack type Chaos and damage type Normal
              • Special Effect - Create a special effect attached to the chest of Rend_Target[Rend_Index] using Objects\Spawnmodels\NightElf\NightElfBlood\NightElfBloodDruidoftheTalon.mdl
              • Special Effect - Create a special effect attached to the chest of Rend_Target[Rend_Index] using Objects\Spawnmodels\Orc\Orcblood\BattrollBlood.mdl
              • Special Effect - Create a special effect attached to the chest of Rend_Target[Rend_Index] using Objects\Spawnmodels\Critters\Albatross\CritterBloodAlbatross.mdl
            • Else - Actions
              • Trigger - Turn off (This trigger)
but it didnt work so well. I'm too tired to compare yours and mine atm, to see what went wrong :) but tomorrow i will

I guess what Im saying is that I want to know how this indexing works, because if I dont, I dont know what Im doing when making my own spells! Like just now with this Rend spell

(nevermind the leaks etc, i know the code isnt clean, was just trying to get it to function properly)
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
when setting Rend_Index to Custom Value of Rend_Caster in the periodic trigger, you did not define the Rend_Caster = (Picked Unit) before
i see what you try to do there, you want to turn off the trigger when the buff is gone, but that can cause problems, because there may be a case where you turn it off while the trigger is still running for another unit. What you do here: remove the "Turn off trigger" and replace it with Remove Rend_Caster from Rend_Group, because as soon as the ability has finished, you dont want to have the unit in the loop still, right? ;)
and after that you can check if your group is empty, if so, turn the trigger off because there is no instance running currently. Read my last post again carefully, i think it should answer all questions. if you have more specific ones or didnt understand sth correctly, just ask me ;)
 
Level 10
Joined
May 21, 2006
Messages
323
"DamageEventAmount" sounds like jass? do I need to know jass to do this?

and also, how would this work with other custom spells? Would I need to set the second custom spell dmg to 0.02 and the third to 0.03?

I have looked in to Weeps DDS but it doesnt seem to recognize which specific ability the damage is coming from... is this different?

This is Bribe's DDS http://www.hiveworkshop.com/forums/spells-569/gui-damage-engine-v3-6-0-1-a-201016/ It is completely configurable in GUI.

Yes, you got it, but the DDS can detect the damagetarget and source and so you can detect the Unit Type of the source and so you can specify by saying "DamageEventAmount = 0.01" and "Unittype of DamageSource of DamageSource = Mountain King" then set damage = 100.

You will need a lot of experimenting with the system until you get it but it works pretty well.

I usually modify the engine so that 0.01-0.30 damage = healing spells (you better trigger healing effects over damage, too instead of the "real" healing spells, 0.31 - 0.70 damage = spells and 0.70 - 1.00 = physical skill damage. Everything bigger than 1.00 = usual attack damage.
 
Level 3
Joined
Mar 9, 2016
Messages
45
when setting Rend_Index to Custom Value of Rend_Caster in the periodic trigger, you did not define the Rend_Caster = (Picked Unit) before
i see what you try to do there, you want to turn off the trigger when the buff is gone, but that can cause problems, because there may be a case where you turn it off while the trigger is still running for another unit. What you do here: remove the "Turn off trigger" and replace it with Remove Rend_Caster from Rend_Group, because as soon as the ability has finished, you dont want to have the unit in the loop still, right? ;)
and after that you can check if your group is empty, if so, turn the trigger off because there is no instance running currently. Read my last post again carefully, i think it should answer all questions. if you have more specific ones or didnt understand sth correctly, just ask me ;)

I did as you said, but still had some problems. If I cast rend on a second unit while the first was still bleeding, the first trigger would stop. It took me some time to realize that im not supposed to set the thing being tracked (the SB_Missile in the storm bolt case and the bleeding itself in this case) to an array and index it. I don't quite understand what makes it work this way yet... If you understand what I mean i'd appriciate if you explained :)

However, I did manage to solve it. since I cant give a Real value a custom value and track it that way, I created a Dummy unit, and used that unit's custom value to track. It looks like this:

  • Rend Cast
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Rend
    • Actions
      • Unit - Create 1 Dummy for (Owner of (Triggering unit)) at (Position of (Target unit of ability being cast)) facing Default building facing degrees
      • Set Rend_Dummy = (Last created unit)
      • Set Rend_Index = (Custom value of (Last created unit))
      • Unit Group - Add Rend_Dummy to Rend_Group
      • Unit - Add a 5.10 second Generic expiration timer to Rend_Dummy
      • Set Rend_Caster[Rend_Index] = (Triggering unit)
      • Set Rend_Target[Rend_Index] = (Target unit of ability being cast)
      • Unit - Move Rend_Dummy instantly to (Position of Rend_Target[Rend_Index])
      • Trigger - Turn on Rend Loop <gen>
  • Rend Loop
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in Rend_Group and do (Actions)
        • Loop - Actions
          • Set Rend_Dummy = (Picked unit)
          • Set Rend_Index = (Custom value of Rend_Dummy)
          • Set Rend_Time[Rend_Index] = (Rend_Time[Rend_Index] - 0.10)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Rend_Dummy is alive) Equal to False
            • Then - Actions
              • Unit Group - Remove Rend_Target[Rend_Index] from Rend_Group
            • Else - Actions
              • Unit - Move Rend_Dummy instantly to (Position of Rend_Target[Rend_Index])
              • Unit - Cause Rend_Caster[Rend_Index] to damage Rend_Target[Rend_Index], dealing (5.00 x (Real((Level of Rend for Rend_Caster[Rend_Index])))) damage of attack type Chaos and damage type Normal
              • Special Effect - Create a special effect attached to the chest of Rend_Target[Rend_Index] using Objects\Spawnmodels\Critters\Albatross\CritterBloodAlbatross.mdl
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Rend_Group is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
And it seems to work perfectly, with 2 different casters and 2 different targets simultaneously. So is this a good method? Are you proud of me senpai? :D

But yeah like I said I dont quite understand how/why this works without indexing the actual tracker (the dummy).
 
Level 3
Joined
Mar 9, 2016
Messages
45
This is Bribe's DDS http://www.hiveworkshop.com/forums/spells-569/gui-damage-engine-v3-6-0-1-a-201016/ It is completely configurable in GUI.

Yes, you got it, but the DDS can detect the damagetarget and source and so you can detect the Unit Type of the source and so you can specify by saying "DamageEventAmount = 0.01" and "Unittype of DamageSource of DamageSource = Mountain King" then set damage = 100.

You will need a lot of experimenting with the system until you get it but it works pretty well.

I usually modify the engine so that 0.01-0.30 damage = healing spells (you better trigger healing effects over damage, too instead of the "real" healing spells, 0.31 - 0.70 damage = spells and 0.70 - 1.00 = physical skill damage. Everything bigger than 1.00 = usual attack damage.

Thanks for the input, really appriciate it. I'll try to look into it, however this method GIMLI_2 is teaching me is already a mouthful :D
 
Level 17
Joined
Mar 21, 2011
Messages
1,597
yeah, thats the disadvantage of an index system, if you use the casting unit custom value as index itself, it will not be able to cast the ability more than once at a time. Why? Because the variables just get overwritten.
When Paladin with custom value 2 casts an ability, your setup looks like that:
set Target[2] = (Targeted Unit Of Ability Being Cast)
set Duration[2] = 10.00
...

Now, if the same unit casts the ability again (the Paladin), the old variables will just get overwritten, because he has the same custom value the whole game.
So Index is still 2 and your variables that are currently in action (like Duration[2] is currently on 2.50 in the first cast for example) will get overwritten and set back to 10.00.

There are ways around this problem, one being the way you did it. Attach the variables to a dummy you just created (what i mean with this is using the dummys custom value for each cast).
why does it work?
Well, when you create a dummy, its basically a unit, right? so the unit indexer fires and gives it a unique number.
So EVERY ability cast will have a unique number, even on the same caster. and instead of attaching the dummy to the caster, you attach the caster to the dummy. Exactly like you did it.

I hope you understood this. my english isnt that good ;)
 
Level 3
Joined
Mar 9, 2016
Messages
45
yeah, thats the disadvantage of an index system, if you use the casting unit custom value as index itself, it will not be able to cast the ability more than once at a time. Why? Because the variables just get overwritten.
When Paladin with custom value 2 casts an ability, your setup looks like that:
set Target[2] = (Targeted Unit Of Ability Being Cast)
set Duration[2] = 10.00
...

Now, if the same unit casts the ability again (the Paladin), the old variables will just get overwritten, because he has the same custom value the whole game.
So Index is still 2 and your variables that are currently in action (like Duration[2] is currently on 2.50 in the first cast for example) will get overwritten and set back to 10.00.
yeah, i realized that too!
There are ways around this problem, one being the way you did it. Attach the variables to a dummy you just created (what i mean with this is using the dummys custom value for each cast).
why does it work?
Well, when you create a dummy, its basically a unit, right? so the unit indexer fires and gives it a unique number.
So EVERY ability cast will have a unique number, even on the same caster. and instead of attaching the dummy to the caster, you attach the caster to the dummy. Exactly like you did it.

I hope you understood this. my english isnt that good ;)
oh i see. yeah, you dont need to make the dummy an array and index it yourself. the index system indexes it automatically. didnt think about that. Thank you sooo much, youve been really really helpful! :D
 
Status
Not open for further replies.
Top