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

Arrow Spells

Status
Not open for further replies.
Level 2
Joined
Mar 29, 2006
Messages
24
Is there a way to make arrow spells (autocast spells who can used instead o attack (flaming arrows and such)) in GUI?
if so please say it :)
 
Level 9
Joined
Jul 27, 2006
Messages
652
seen it

iv seen this before...
unit has no attack but an ability called shoot arrow
when auto cast is on she attacks normaly but uses a custom arrow that flies forwards...

its in a spell map on this site...
search arrow in the system section and i think you'll find it...

should help you... if not i'll look it up for you...
 
Level 2
Joined
Mar 29, 2006
Messages
24
sorry for bump this tread up but i mean like silencer (from dota) or something an effect on your normall attack :)
 
Level 11
Joined
Jul 12, 2005
Messages
764
Well, it's very complex, needs many triggers, and if you want it in GUI, it is very buggy.
To start with, every autocast-ability has different order strings like "flamingarrowson", "flamingarrowsoff", these are used when you right click on the ability button, you know... This way, you can detect, and catch the unit's order in the trigger:

Event
Unit is issued an order
Cond
(Level of Flaming Arrows for Triggering Unit) > 0
Actions
Set UnitVar = Triggering Unit
If Issued order == "flamingarrowson"
Set Ordered = "on"
If Issued order == "flamingarrowsoff"
Set Ordered = ""

(Ordered is a string variable, but you can also use an integer var, and set the values to 0 and 1, or whatever. UnitVar is a unit variable, just to store that unit.)
Now, in an other trigger, you detect if the unit attacks an other unit:

Event
A unit is attacked
Cond
Attacking Unit == UnitVar
Ordered is not equal to "" (empty string)
Actions
Here goes the effect you want...

Now we detected if a unit is autocasting, but the effect still doesn't occur when you CAST the ability. So you need an other trigger for casting:

Event
A unit starts the effect of an ability
Cond
Ability being cast == Flaming Arrows
Actions
Here goes the effect you want...


Well that's it. But as i said, its buggy. JASS would solve all the problems.
 
Level 11
Joined
Jul 12, 2005
Messages
764
PurplePoot said:
Shouldnt you be able to detect on cast? Or do autocasts not fire A Unit xxxs an Ability?
No they don't.
donut3.5 said:
Couldn't you just attach a buff to the autocast effect, and do
A unit is attacked
Wait 1 seconds
If triggering unit has buff then do blah blah else do nothing?
It's not accurate I think. What if you attack a unit from 100 range. It takes the damage, has the buff, but the effect you want comes a sec later? Hmm...

As I said, JASS would do it.
 
Level 2
Joined
Mar 29, 2006
Messages
24
woha that is complex :shock:
I dont understand JASS at all, i already read the jass tutorial but still dont get it, thanks for your help :)
Maybe i need to reread the tutorial?
 
Level 11
Joined
Jul 12, 2005
Messages
764
Here:
JASS:
//======================================================================
//This catches the turn on/off order
//======================================================================
function Trig_ArrowSpellsOrder_Conditions takes nothing returns boolean
    return GetIssuedOrderId() == OrderId("flamingarrows") or GetIssuedOrderId() == OrderId("unflamingarrows")
endfunction

function Trig_ArrowSpellsOrder_Actions takes nothing returns nothing
    local unit flamer = GetTriggerUnit()
    if GetIssuedOrderId() == OrderId("flamingarrows") then
        call SetHandleInt(flamer,"arrowon",1)
    else
        call SetHandleInt(flamer,"arrowon",0)
    endif
    set flamer = null
endfunction

//======================================================================
//This is the "projectile hit detection" trigger - this makes the effect
//======================================================================
function Trig_ArrowSpells_Hit_Conditions takes nothing returns boolean
    return GetEventDamageSource() == GetHandleUnit(GetTriggeringTrigger(), "DamageSource")
endfunction
function Trig_ArrowSpells_Hit_Actions takes nothing returns nothing
    local unit flamer = GetEventDamageSource()
    local unit target = GetTriggerUnit()
    local trigger dmgtrig = GetTriggeringTrigger()
    //
    //All the effects go here...
    //Here, it creates a special effect on the target:
    call DestroyEffect(AddSpecialEffectTarget("abilities\\weapons\\DemolisherMissile\\DemolisherMissile.mdl",target,"origin"))
    //
    call SetHandleHandle(flamer, "DamageTrigger", null)
    call FlushHandleLocals(dmgtrig)
    call DestroyTrigger(dmgtrig)
    set target = null
    set flamer = null
    set dmgtrig = null
endfunction

//======================================================================
//This is the 'unit is attacked' trigger
//======================================================================
function Trig_ArrowSpellsAttack_Conditions takes nothing returns boolean
    return GetHandleInt(GetAttacker(),"arrowon") == 1 and GetHandleTrigger(GetAttacker(), "DamageTrigger") == null
endfunction
function Trig_ArrowSpellsAttack_Actions takes nothing returns nothing
    local unit flamer = GetAttacker()
    local unit target = GetTriggerUnit()
    local trigger dmgtrig = CreateTrigger()
    call SetHandleHandle(flamer, "DamageTrigger", dmgtrig)
    call SetHandleHandle(dmgtrig, "DamageSource", flamer)
    call TriggerRegisterUnitEvent(dmgtrig, target, EVENT_UNIT_DAMAGED)
    call TriggerAddCondition(dmgtrig, Condition(function Trig_ArrowSpells_Hit_Conditions))
    call TriggerAddAction(dmgtrig, function Trig_ArrowSpells_Hit_Actions)
    call TriggerSleepAction(2)
    call FlushHandleLocals(dmgtrig)
    call DestroyTrigger(dmgtrig)
    call SetHandleHandle(flamer, "DamageTrigger", null)
    set target = null
    set flamer = null
    set dmgtrig = null
endfunction

//======================================================================
//This is the 'spell effect' trigger
//======================================================================
function Trig_ArrowSpellsCast_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'AHfa'
endfunction
function Trig_ArrowSpellsCast_Actions takes nothing returns nothing
    local unit flamer = GetTriggerUnit()
    local unit target = GetSpellTargetUnit()
    local trigger dmgtrig = CreateTrigger()
    call SetHandleHandle(flamer, "DamageTrigger", dmgtrig)
    call SetHandleHandle(dmgtrig, "DamageSource", flamer)
    call TriggerRegisterUnitEvent(dmgtrig, target, EVENT_UNIT_DAMAGED)
    call TriggerAddCondition(dmgtrig, Condition(function Trig_ArrowSpells_Hit_Conditions))
    call TriggerAddAction(dmgtrig, function Trig_ArrowSpells_Hit_Actions)
    call TriggerSleepAction(2)
    call FlushHandleLocals(dmgtrig)
    call DestroyTrigger(dmgtrig)
    call SetHandleHandle(flamer, "DamageTrigger", null)
    set target = null
    set flamer = null
    set dmgtrig = null
endfunction

//===========================================================================
function InitTrig_ArrowSpells takes nothing returns nothing
    local trigger ArrowSpellsOrder = CreateTrigger()
    local trigger ArrowSpellsAttack = CreateTrigger()
    local trigger ArrowSpellsCast = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(ArrowSpellsOrder, EVENT_PLAYER_UNIT_ISSUED_ORDER )
    call TriggerAddCondition(ArrowSpellsOrder, Condition( function Trig_ArrowSpellsOrder_Conditions ) )
    call TriggerAddAction(ArrowSpellsOrder, function Trig_ArrowSpellsOrder_Actions )
    call TriggerRegisterAnyUnitEventBJ(ArrowSpellsAttack, EVENT_PLAYER_UNIT_ATTACKED )
    call TriggerAddCondition(ArrowSpellsAttack, Condition( function Trig_ArrowSpellsAttack_Conditions ) )
    call TriggerAddAction(ArrowSpellsAttack, function Trig_ArrowSpellsAttack_Actions )
    call TriggerRegisterAnyUnitEventBJ(ArrowSpellsCast, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(ArrowSpellsCast, Condition( function Trig_ArrowSpellsCast_Conditions ) )
    call TriggerAddAction(ArrowSpellsCast, function Trig_ArrowSpellsCast_Actions )
endfunction
As you see, there are three triggers in one. Sry i won't explain it. Analize the code and you will understand everything...
But there are a few things to be done:
1. Be sure, the ability does some damage, no matter if it deals 0.5, but make sure it does.
2. Do not flush the handles from units in other triggers, as it will interfere with this one.
 
Level 10
Joined
Jul 14, 2004
Messages
463
Ah, yes, I think I understand it, thanks! :)
Not that easy... well, but you should be able to do the projectil damage effects like this:
JASS:
//====================================================================== 
//This is the "projectile hit detection" trigger - this makes the effect 
//====================================================================== 

function **** takes nothing returns boolean
  return GetHandleInt(GetEventDamageSource(),"arrowon") == 1
endfunction

function **** takes nothing returns nothing
  //local unit flamer = GetEventDamageSource()
  local unit target = GetTriggerUnit()
  local trigger dmgtrig = GetTriggeringTrigger()

  //All the effects go here...
  //Here, it creates a special effect on the target:
  call DestroyEffect(AddSpecialEffectTarget("abilities\\weapons\\DemolisherMissile\\DemolisherMissile.mdl",target,"origin"))

  //call SetHandleHandle(flamer, "DamageTrigger", null)
  //call FlushHandleLocals(dmgtrig)
  call DestroyTrigger(dmgtrig)
  set target = null
  //set flamer = null
  set dmgtrig = null
endfunction
...
I made comments out of lines that should not be necessary if the condition works as I want it to work. Of course you can also delete the corresponding lines in the "gets attacked" trigger, which save the handles "DamageTrigger" and "DamageSource" as well then. Oh and the unit in the condition leaks, doesn't it? What do you think, does it work (did not try it out myself until now)?
 
Level 11
Joined
Jul 12, 2005
Messages
764
You're right with removing the 'flamer' in the damagetrig IN THIS CASE. But if you want sthg complex effect (or maybe more damage), you need the attacker.

Storing "DamageSource" and "DamageTrigger" is the key moment of the whole thing! :D This makes it 99% (i haven't found any) bugless.

In which condition does the unit leak?

And, it works properly! I wouldn't release such a complex code without testing it... :p

Oh, and i just realized that the DamageTrigger's function name is ****. ??? Somehow it changed. Ok i'll edit the code.

LOOOOOL!!! :D:D It recognized the Spells(_)Hit as a vulgar word :D and changed it to ****. :D:D
 
Level 3
Joined
Jan 3, 2005
Messages
24
Hey paso, someone who listens to TFK wooooh, i dont listen to them anymore, but they still rock.

As for the spell, use the Event: Unit takes damage, and then check if they have the buff after taking the damage, it will work, but could be buggy if the unit is attacked by a large amount of units, then it will proc multiple times
 
Level 11
Joined
Jul 12, 2005
Messages
764
Hey guys, I found sthg. My script gets VERY buggy when the units gets out of mana... Think about it.
So I admit i've sinned, I broke up a dota 6.XX code to see how Impetus works. I copied the script, OPTIMIZED it, and i think you should take a look at it. I think it's better than mine. It uses the "buff-detection" that so many people here advised, and it doesn't get buggy.
Sry, i don't know who the real author is (i bet it's not IceFrog), so i give a credit to Anonymus. It is very tricky with that condition, i like it... :D
JASS:
function Trig_ArrowSpells_Conditions takes nothing returns boolean
    return GetLearnedSkill() == 'A00B' and GetLearnedSkillLevel() == 1
endfunction

function ArrowSpells_Damage takes nothing returns nothing
    local unit target = GetTriggerUnit()
    local unit attacker = GetEventDamageSource()
    if GetUnitAbilityLevel(target, 'B001')>0 and GetUnitAbilityLevel(attacker, 'A00B')>0  then
        call DestroyTrigger(GetTriggeringTrigger())
        //
        //Effects come here:
        //
    endif
    set target = null
    set attacker = null
endfunction

function ArrowSpells_Condition takes nothing returns boolean
    if GetTriggerEventId() == EVENT_PLAYER_UNIT_ATTACKED then
        return GetUnitAbilityLevel(GetAttacker(), 'A00B')>0 and GetHandleInt(GetAttacker(),"arrowon")==1 and not IsUnitType(GetTriggerUnit(), UNIT_TYPE_STRUCTURE)
    elseif GetTriggerEventId() == EVENT_UNIT_ISSUED_ORDER then
        if GetIssuedOrderId() == OrderId("poisonarrows") then
            call SetHandleInt(GetTriggerUnit(), "arrowon", 1)
        elseif GetIssuedOrderId() == OrderId("unpoisonarrows") then
            call SetHandleInt(GetTriggerUnit(), "arrowon", 0)
        endif
    elseif GetTriggerEventId() == EVENT_UNIT_SPELL_EFFECT then
        return GetSpellAbilityId()== 'A00B'
    endif
    return false
endfunction

function ArrowSpells_Actions takes nothing returns nothing
    local unit target = null
    local trigger dmgtrig = CreateTrigger()
    if GetTriggerEventId() == EVENT_UNIT_SPELL_EFFECT then
        set target = GetSpellTargetUnit()
    else
        set target = GetTriggerUnit()
    endif
    call TriggerRegisterUnitEvent(dmgtrig, target, EVENT_UNIT_DAMAGED)
    call TriggerAddAction(dmgtrig, function ArrowSpells_Damage)
    call TriggerSleepAction(2)
    call DestroyTrigger(dmgtrig)
    set dmgtrig = null
    set target = null
endfunction

function Trig_ArrowSpells_Actions takes nothing returns nothing
    local trigger t = CreateTrigger()
    local unit attacker = GetTriggerUnit()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
    call TriggerRegisterUnitEvent(t, attacker, EVENT_UNIT_SPELL_EFFECT)
    call TriggerRegisterUnitEvent(t, attacker, EVENT_UNIT_ISSUED_ORDER)
    call TriggerAddCondition(t, Condition(function ArrowSpells_Condition))
    call TriggerAddAction(t, function ArrowSpells_Actions)
    set t = null
    set attacker = null
endfunction

//===========================================================================
function InitTrig_ArrowSpells takes nothing returns nothing
    set gg_trg_ArrowSpells = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_ArrowSpells, EVENT_PLAYER_HERO_SKILL)
    call TriggerAddCondition(gg_trg_ArrowSpells, Condition(function Trig_ArrowSpells_Conditions))
    call TriggerAddAction(gg_trg_ArrowSpells, function Trig_ArrowSpells_Actions)
endfunction
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
I would just implant a mana check into the conditions, rather than doing UGLY buff detection that will cause more than 1 unit using the same spell to interfere with eachother. Thus, that method of checking is only good for games such as AoS maps where there is only 1 unit that is able to use that ability at any one time.

:)shock:, I wonder what would happen if you tried to run that in DotA's Same Hero mode or whatever its called... looks like it would cause multi-hitting)
 
Level 10
Joined
Jul 14, 2004
Messages
463
paskovich said:
You're right with removing the 'flamer' in the damagetrig IN THIS CASE. But if you want sthg complex effect (or maybe more damage), you need the attacker.

Storing "DamageSource" and "DamageTrigger" is the key moment of the whole thing! :D This makes it 99% (i haven't found any) bugless.
I still don't understand why you should store DamageSource and DamageTrigger in GameCache. If you want to use the attacker, you can just use the function GetDamageSource() (like I did in my version of the condition). And what should you need the dmgtrig for? Blocking it? You could also do that using an extended condition (e.g. checking the target's custom value). GameCache is not that fast, as far as I know. Well, in this case this won't play an important role since the spell won't be casted that often.


paskovich said:
In which condition does the unit leak?
I'm not completely sure since I'm not a leak-specialist, but what's with this:
JASS:
function Trig_ArrowSpells_Hit_Conditions takes nothing returns boolean
return GetEventDamageSource() == GetHandleUnit(GetTriggeringTrigger(), "DamageSource")
endfunction

Oh and this Dota-Version really looks like it would cause multi-damage and on the other hand it's very difficult to read because of its more or less sensless multievents. I like yours better! :)
 
Level 11
Joined
Jul 12, 2005
Messages
764
Ok first of all, "Why storing DamageSource?":
Let's think it over. We have a trigger with EVENT_UNIT_ATTACKED. This fires every time, the unit is attacked by an other one. The trigger creates an other one, that detects the damage caused by any unit. What if the unit is attacked, and while the projectile is still "flying", the unit is damaged by an other (probably melee) unit. Storing the attacker, and checking it in the condition precludes these (exrteme) situations to happen.

"Why storing the DmgTrigger?":
What if the unit misses? The damage trigger is created, and still waiting for the event. The unit attacks again, but this time it does not miss, an other damage trigger is created. Now there are 2 active damage triggers. (Repeat: Storing the trigger, and checking it in the condition precludes these (exrteme) situations to happen.)

And i don't think that condition leaks. But I think PurplePoot can tell this. He's the expert... :p

And also an other thing about my script: It does not work if the ability has a cooldown... It is a bigger problem than mana.
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
course it doesnt leak, or not in any way that I can tell.

We can basically break leaks down into 4 types

A) Handle Leak

B) Null Leak

C) Gamecache Leak

D) String Leak

a Handle Leak is when we drop all references to a from-that-point-on-useless handle (thus not a trigger condition, etc), but leave the handle lying around, thus wasting memory

a Null Leak occurs when you have a local variable that is a handle, which you do not (set <name> = null) when you are done with it. The leak itself should not make much difference in performance, but there is no reason not to prevent it, unless the solution is slower than the leak

a Gamecache Leak is when you are using the Handle Vars and omit to FlushHandleLocals (or different names for different systems) at the end.

a String Leak occurs once for every string displayed, and is unpreventable

As you can probably see, that does not fall under any of these
 
Level 10
Joined
Jul 14, 2004
Messages
463
Thanks for the leak help, seems clear to me now, PurplePoot! :)

Now again to our main discussion (btw, if you don't like to discuss your script any longer, just say so and I'll keep quite!):
paskovich said:
Ok first of all, "Why storing DamageSource?":
Let's think it over. We have a trigger with EVENT_UNIT_ATTACKED. This fires every time, the unit is attacked by an other one. The trigger creates an other one, that detects the damage caused by any unit. What if the unit is attacked, and while the projectile is still "flying", the unit is damaged by an other (probably melee) unit. Storing the attacker, and checking it in the condition precludes these (exrteme) situations to happen.
The trigger does not fire everytime actually, at least not its actions, because there is the condition, that the attacker has the arrow ability turned on. To check this, you don't need to store the attacker itself if you do the check like I did in my version of the condition. This way you will also only fire once for each missile, because there can always impact only one missile from one attacker at the same time.

"Why storing the DmgTrigger?":
What if the unit misses? The damage trigger is created, and still waiting for the event. The unit attacks again, but this time it does not miss, an other damage trigger is created. Now there are 2 active damage triggers. (Repeat: Storing the trigger, and checking it in the condition precludes these (exrteme) situations to happen.)
You already kill old created triggers after a short wait in your script, don't you? The only possibility that this does not work is that the caster fires again before the first missile impacts, am I right?
Well, usually not the case but you did well in preventing everything. Slowly but surely
I get behind the quality of your script. :)
I myself normally try to optimize my spells for my special single case and not as multicompatible as possible - another way of working.

And also an other thing about my script: It does not work if the ability has a cooldown... It is a bigger problem than mana.
Hm, you're again right. Not that easy if you still want to take care of the possibility of more than one missile of the same unit in the air. If you exclude this possibility, it should be enough to check the cooldown in the "unit is attacked" trigger, I think.

I look forward to your examples making my ideas not work anymore! :wink:
 
Level 11
Joined
Jul 12, 2005
Messages
764
...there can always impact only one missile from one attacker at the same time
In some extreme situations (and of course when there are more units with the ability) this CAN happen! Each attacker has the ability, each of them "arrowon" set to 1, now what if they attack the same unit in the same second? This "storing-the-attacker" thing is only a safety action that is useful in maps more simple units have the ability (for exaple in a melee map), and the player controls and orders them in a group. But if you have only one unit with the ability (like in hero arena maps), it's not neccessary of course.

You're right with the trigger-storing thing, weird things (double effect) can happen if the unit's attack cooldown is somewhere below 2 (or how much we wait in the trigger to destroy the damage trigger).
 
Status
Not open for further replies.
Top