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

[JASS] Firewall

Status
Not open for further replies.
Hi, this is my spell, firewall. Creates a wall of fire and stuff, like diablo 2 firewall. It requires TimerUtils. Sometimes it bugs, so I wonder if I did something wrong. I post here to ask :)

JASS:
scope Firewall initializer OnInit

    globals
    private constant string  EFFECT_FIRE = "war3mapImported\\FireAura.mdx"       //Path of the model for the fire
    private constant string  EFFECT_PERIODIC = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"   //Path of the special effect that occurs interval.
    private constant string  EFFECT_BURN = "Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl"//Path of the special effect you want attached to burning units
    private constant integer DUMMY_ID = 'h007'      //Raw code of dummy ability
    private constant integer ABIL_ID = 'A042'       //Raw code of ability, should be based off stampede due to the nature of the spell.
    private constant real    INTERVAL = 0.5         //How often main explosion damages enemies
    private constant real    MAIN_DMG = 200.         //Damage per level from main explosion, per interval.
    private constant real    BURN_INTERVAL = .25     //How often the unit takes burn damage
    private constant real    BURN_DMG = 5.          //Damage per tick of burn(per level)
    //End of configurables
    
    private constant group GROUP = CreateGroup()
    private unit CASTER
    endglobals
    
    private struct FirewallMain
    unit caster
    real x1
    real y1
    real x2
    real y2
    real x3
    real y3
    real x4
    real y4
    real x5
    real y5
    real x6
    real y6
    real x7
    real y7
    effect sfx1
    effect sfx2
    effect sfx3
    effect sfx4
    effect sfx5
    effect sfx6
    effect sfx7
    real duration
    endstruct
    
    private struct FirewallBurn
    unit source
    unit burning
    effect burnsfx
    real duration
    endstruct
    
    native UnitAlive takes unit id returns boolean
    
    private function BurnHandler takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local FirewallBurn data = GetTimerData(t)
    
    if data.duration == 0.0 then
        call DestroyEffect(data.burnsfx)
        call data.destroy()
        call ReleaseTimer(GetExpiredTimer())
    else
        call Damage_Spell(data.source,data.burning,I2R(GetUnitAbilityLevel(data.source,ABIL_ID))*BURN_DMG)
        set data.duration = data.duration - .5
        call SetTimerData(t,data)
        call TimerStart(t,BURN_INTERVAL,false,function BurnHandler)
    endif
    
    set t = null
    endfunction
    
    private function FilterActions takes nothing returns boolean
    local timer t
    local  FirewallBurn data = FirewallBurn.create()
    
    if UnitAlive(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(CASTER)) == true and OrderId2String(GetUnitCurrentOrder(CASTER)) == "stampede" then
        set t = NewTimer()
        set data.source = CASTER
        set data.burning = GetFilterUnit()
        set data.burnsfx = AddSpecialEffectTarget(EFFECT_BURN,data.burning,"chest")
        set data.duration = 4.*I2R(GetUnitAbilityLevel(data.source,ABIL_ID))
        call Damage_Spell(data.source,data.burning,I2R(GetUnitAbilityLevel(data.source,ABIL_ID))*MAIN_DMG)
        call SetTimerData(t,data)
        call TimerStart(t,BURN_INTERVAL,false,function BurnHandler)
    else
        call data.destroy()
    endif
    
    set t = null
    return false
    endfunction
    
    
    private function Handler takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local FirewallMain data = GetTimerData(t)
    
    set CASTER = data.caster
    
    if  OrderId2String(GetUnitCurrentOrder(data.caster)) != "stampede" then
        call ReleaseTimer(GetExpiredTimer())
        call DestroyEffect(data.sfx1)
        call DestroyEffect(data.sfx2)
        call DestroyEffect(data.sfx3)
        call DestroyEffect(data.sfx4)
        call DestroyEffect(data.sfx5)
        call DestroyEffect(data.sfx6)
        call DestroyEffect(data.sfx7)
        call data.destroy()
    else
        call GroupEnumUnitsInRange(GROUP,data.x1,data.y1,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x2,data.y2,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x3,data.y3,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x4,data.y4,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x5,data.y5,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x6,data.y6,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x7,data.y7,174.,Filter(function FilterActions))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x1,data.y1))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x2,data.y2))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x3,data.y3))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x4,data.y4))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x5,data.y5))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x6,data.y6))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x7,data.y7))

        call SetTimerData(t,data)
        call TimerStart(t,INTERVAL,false,function Handler)
    endif
    
    set t = null
    endfunction
    
    private function Actions takes nothing returns nothing
    local real angle = GetUnitFacing(GetTriggerUnit())
    local timer t = NewTimer()
    local FirewallMain data = FirewallMain.create()
    set data.caster = GetTriggerUnit()
    set data.x1 = GetSpellTargetX()
    set data.y1 = GetSpellTargetY()
    set data.sfx1 = AddSpecialEffect(EFFECT_FIRE,data.x1,data.y1)    
    set data.x2 = data.x1 + 300. * Cos((angle + 90.)*bj_DEGTORAD)
    set data.y2 = data.y1 + 300. * Sin((angle + 90.)*bj_DEGTORAD)
    set data.sfx2 = AddSpecialEffect(EFFECT_FIRE,data.x2,data.y2)    
    set data.x3 = data.x1 + 600. * Cos((angle + 90.)*bj_DEGTORAD)
    set data.y3 = data.y1 + 600. * Sin((angle + 90.)*bj_DEGTORAD)
    set data.sfx3 = AddSpecialEffect(EFFECT_FIRE,data.x3,data.y3)    
    set data.x4 = data.x1 + 900. * Cos((angle + 90.)*bj_DEGTORAD)
    set data.y4 = data.y1 + 900. * Sin((angle + 90.)*bj_DEGTORAD)
    set data.sfx4 = AddSpecialEffect(EFFECT_FIRE,data.x4,data.y4)    
    set data.x5 = data.x1 + 300. * Cos((angle - 90.)*bj_DEGTORAD)
    set data.y5 = data.y1 + 300. * Sin((angle - 90.)*bj_DEGTORAD)
    set data.sfx5 = AddSpecialEffect(EFFECT_FIRE,data.x5,data.y5)    
    set data.x6 = data.x1 + 600. * Cos((angle - 90.)*bj_DEGTORAD)
    set data.y6 = data.y1 + 600. * Sin((angle - 90.)*bj_DEGTORAD)
    set data.sfx6 = AddSpecialEffect(EFFECT_FIRE,data.x6,data.y6)    
    set data.x7 = data.x1 + 900. * Cos((angle - 90.)*bj_DEGTORAD)
    set data.y7 = data.y1 + 900. * Sin((angle - 90.)*bj_DEGTORAD)
    set data.sfx7 = AddSpecialEffect(EFFECT_FIRE,data.x7,data.y7)
    
    set CASTER = data.caster
    
    call GroupEnumUnitsInRange(GROUP,data.x1,data.y1,150.,Filter(function FilterActions))
    call GroupEnumUnitsInRange(GROUP,data.x2,data.y2,150.,Filter(function FilterActions))
    call GroupEnumUnitsInRange(GROUP,data.x3,data.y3,150.,Filter(function FilterActions))
    call GroupEnumUnitsInRange(GROUP,data.x4,data.y4,150.,Filter(function FilterActions))
    call GroupEnumUnitsInRange(GROUP,data.x5,data.y5,150.,Filter(function FilterActions))
    call GroupEnumUnitsInRange(GROUP,data.x6,data.y6,150.,Filter(function FilterActions))
    call GroupEnumUnitsInRange(GROUP,data.x7,data.y7,150.,Filter(function FilterActions))
    call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x1,data.y1))
    call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x2,data.y2))
    call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x3,data.y3))
    call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x4,data.y4))
    call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x5,data.y5))
    call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x6,data.y6))
    call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x7,data.y7))
    
    call SetTimerData(t,data)
    call TimerStart(t,INTERVAL,false,function Handler)
    
    set t = null
    endfunction
    
    private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == ABIL_ID then
        call Actions()
    endif
    return false
    endfunction
    
    private function OnInit takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t,Condition(function Conditions))
    endfunction
endscope
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
JASS:
local timer t = NewTimer()
call SetTimerData(t,data)
call TimerStart(t,INTERVAL,false,function Handler)
---> call TimerStart(NewTimerEx(data), INTERVAL, false, function Handler)
- Also make the timer periodic instead of restarting it all the time.

I have to see it ingame to give an adequate advice, but this is most like a no-go.
JASS:
        call GroupEnumUnitsInRange(GROUP,data.x1,data.y1,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x2,data.y2,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x3,data.y3,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x4,data.y4,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x5,data.y5,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x6,data.y6,174.,Filter(function FilterActions))
        call GroupEnumUnitsInRange(GROUP,data.x7,data.y7,174.,Filter(function FilterActions))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x1,data.y1))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x2,data.y2))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x3,data.y3))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x4,data.y4))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x5,data.y5))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x6,data.y6))
        call DestroyEffect(AddSpecialEffect(EFFECT_PERIODIC,data.x7,data.y7))
 
Level 7
Joined
Mar 6, 2006
Messages
282
Since the Firewall follows the same angle with the same distance between each data.x and data.y, maybe it's possible to call just one of these:

JASS:
GroupEnumUnitsInRange(GROUP, x, y, range, null)

And then use a FirstOfGroup loop to filter the units who are in the firewall by checking their X and Y? Sec, I'll draw a picture for it.

y22w.jpg


KK, in that picture, the red circles represent each 'GroupEnumUnitsInRange'. They all get units in a circle, following the same line. What I'm suggesting is just doing one large 'GroupEnumUnitsInRange', and then filtering the units that would be in the blue box there, like an imaginary rect.

You would keep the same angle you have for the length of the rect, and you would use the angle between "Caster to Target point of ability being cast" to get the width of the rect. minimum Y would be ~150 coords away from the caster, while maximum Y would be ~150 coords closer to the caster.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Imo firewall is a very fascinating spell concept, hence I made up some ideas what such a spell could/should cover.
  • On cast the fire spreads from the center to the edge and not just pops up as complete wall.
  • Damages and kills destructables.
  • (Cannot be created on water)
  • Takes fly height into consideration.
  • Has a rather short damage interval ~0.25
  • First created flames should vanish first. --> It takes a short time to destroy the whole firewall.

All of these ideas can be implemented with ease. The only thing that really does matter is the way damage is dealt.
The highest accuracy would be a linked list, while each node represents one flame. Depending on the lenght of the wall that could be very costly though.
In contrast a simple GroupEnumUnitsInRect/Range would be much much more lightweight, but less accurate.

Please feel free to add further ideas and opinion to this list. I'm already considering to code this spell by myself ^^.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
press TAB a bit more please...

inside BurnHandler dont compare the real to 0.0, but do <=

no need to call Actions() from Conditions, put all inside Conditions into the body of if
If you declare local variables, declare them in Conditions as well(dont initialize them)
If you null local agents, null them preferably out of the if body, mainly if you initialize the locals from Agent in the Conditions

cache results of Sin and Cos, because you call that like 6x each, with the very same arguments, just call them twice(once for +, once for -) and store the result inside real variable, and reuse that

no need for local timer in Actions, you can just call TimerStart(NewTimerEx(data), ...)

no need to reattach data inside Handler function, because it is already there, reading it will not make it leave the timer, it is still attached to it
 
JASS:
GroupEnumUnitsInRange(GROUP, x, y, range, null)


You would keep the same angle you have for the length of the rect, and you would use the angle between "Caster to Target point of ability being cast" to get the width of the rect. minimum Y would be ~150 coords away from the caster, while maximum Y would be ~150 coords closer to the caster.

Alright I understand what you are saying but how would i calculate the rect?

Imo firewall is a very fascinating spell concept, hence I made up some ideas what such a spell could/should cover.
  • On cast the fire spreads from the center to the edge and not just pops up as complete wall.
  • Damages and kills destructables.
  • (Cannot be created on water)
  • Takes fly height into consideration.
  • Has a rather short damage interval ~0.25
  • First created flames should vanish first. --> It takes a short time to destroy the whole firewall.

Good idea, I will definetely do some of those once I get the code to not bug. When I used it in game it works good mostly, but if too many instances of the spell are going at once it bugs, so I'm assuming because I used too much timers, so I'm trying to fix that. I really like the one about the fire spreading from the center.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
I think the math you'll need to do to get units in an arbitrary polygon is more expensive than to just leave it the way it is.

Of course, the polygon is the correct solution, but in wc3 it might not be the best.

I've done something similar when building a hitscan spell, and I ran into trouble with real precision (32 bits was not enough)
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
There is a more general way to enum units in an arbitrary formed shape:

131949d1389497998-firewall-sel.png


First assume your rect is axis aligned. That makes the whole thing a lot easier:
1. Enum all units in the red circle.
2. To check if units are in the blue rect you need to loop through the group from #1 and check the following condition for them (uX, uY = coordinates of picked units, cX, cY = coordinates of circle center):
boolean isInRect = (cX-w < uX < cX+w && cY-h < uY < cY+h)
3. Damage all units for which the condition from #2 is true.

To do this for rects shapes which are not axis aligned you can just rotate the shape so it is axis aligned. Then you rotate the vector (uX,uY)-(cX,cY) by the same angle and then continue #2.

The formula to rotate a vector (x,y) by angle a is:
(newX, newY) = (cos a * x - sin a * y, sin a * x + cos a * y)

This works with all kinds of shapes, you only have to write down the formula for the axis aligned case once.
 

Attachments

  • sel.png
    sel.png
    7.8 KB · Views: 164
Level 14
Joined
Jun 27, 2008
Messages
1,325
Actually you can do it without trigonometry http://stackoverflow.com/questions/2752725/finding-whether-a-point-lies-inside-a-rectangle-or-not

Still, I'm not sure if either of these are more efficient than just enumerating multiple times.

Yea this is also possible with triangles or other shapes e.g. by using barycentric coordinates which dont require trigonometry. But my approach is usually very simple because all you have to do is to define the shape in an axis aligned form.

A non-trigonometrical approach is basically what Dysfunctional did but you use the edges of the shape as vectors and do a projection of the units position onto the vector. Then you can calculate the "heigth" of the projection which is positive or negative, which means the point is on the right or left side of the line.

I think there are many solutions to this probem. Just pick the one you find the most convenient :p
 
Status
Not open for further replies.
Top