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

Firewall

This spell was inspired by Flux's Wall of Death. The interesting difference between the two is
that here we piggyback on Blizzard's enter/leave region code via the RegionAddCell native which in my opinion has better performance than doing polling (GroupEnumUnitsInRange(...)).

[Misc] - How To Import A Spell/System

firewall.j
JASS:
library Firewall requires /*
    */ Ht /* http://www.hiveworkshop.com/threads/t-real-x-vs-t-x-real.288833/ */

globals
    private constant integer SPELL_ID = 'A000'
    private constant string FIRE_MODEL = "Doodads\\Cinematic\\FireRockSmall\\FireRockSmall.mdx"

    private constant real TAU = 2.0 * bj_PI
    private constant real DT = 1.0 / 32.0

    // UnitDamageTarget arguments
    private constant boolean MELEE_ATTACK = false
    private constant boolean RANGE_ATTACK = true
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL // spell
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL // ignore armor value
    private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals

private function firewall_width_for takes integer level returns real
    return (6.0 * 48.0) + (level - 1) * 48.0 // must be multiple of 48.0
endfunction
private function firewall_height_for takes integer level returns real
    return (3.0 * 48.0) + (level - 1) * 48.0 // must be multiple of 48.0
endfunction
private function damage_per_second_for takes integer level returns real
    return 25.0 + (level - 1) * 30.0
endfunction
private function damage_interval_for takes integer level returns real
    // 50.0 dmg per second => 25.0 after 0.5, another 25.0 after 0.5
    return 0.5 // seconds
endfunction
private function duration_for takes integer level returns real
    return 20.0 // seconds
endfunction

native UnitAlive takes unit u returns boolean
private keyword Firewall

private function targets_allowed takes Firewall this, unit target returns boolean
    return UnitAlive(target) /*
    */ and IsUnitEnemy(target, this.caster_owner)
endfunction


globals
    private location loc = Location(0.0, 0.0)
    private destructable platform
endglobals

private struct Firewall
    static thistype array ul
    static integer ulc = 0

    static integer array xs
    static integer array ys
    static integer xyc = 0

    static Ht ht
    static timer tmr = CreateTimer()
    static group tmp_group = CreateGroup()
    static group swap_group

    unit caster
    player caster_owner
    integer level
    region reg
    trigger trg
    group g
    real damage
    real dmg_per_interval
    integer interval_max_ticks
    integer interval_ticks
    integer dur_max_ticks
    integer dur_ticks

    // effect list
    effect e
    thistype e_prev
    thistype e_next

    static code on_spell_effect_handler
    static code on_enter_leave_region_handler
    static code update_loop_handler
    private static method onInit takes nothing returns nothing
        local trigger t

        set ht = Ht.create()

        call ExecuteFunc("s__" + "thistype" + "_set_handlers")
        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddAction(t, on_spell_effect_handler)
    endmethod

    static method on_spell_effect takes nothing returns nothing
        local thistype this
        local real cx
        local real cy
        local real tx
        local real ty
        local real dist
        local real wall_width
        local integer wall_width_x48
        local integer wall_height_x48
        local real x0
        local real y0
        local real x1
        local real y1
        local real ang
        local real ang_octant
        local integer octant
        local real ox0
        local real oy0
        local real ox1
        local real oy1
        local real ox
        local real oy
        local real x
        local real y
        local real z
        local integer i
        local integer j

        local integer i_x0
        local integer i_y0
        local integer i_x1
        local integer i_y1
        local integer dx
        local integer dy
        local integer sx
        local integer sy
        local integer err
        local real ed
        local integer e2
        local integer x2
        local integer y2
        local real width

        local real duration
        local real interval
        local real damage

        local thistype e_node

        if SPELL_ID != GetSpellAbilityId() then
            return
        endif

        call ht.select()

        set this = allocate()
        set this.caster = GetTriggerUnit()
        set this.caster_owner = GetTriggerPlayer()
        set this.level = GetUnitAbilityLevel(this.caster, SPELL_ID)
        set this.reg = CreateRegion()

        set cx = GetUnitX(this.caster)
        set cy = GetUnitY(this.caster)
        set tx = GetSpellTargetX()
        set ty = GetSpellTargetY()

        set ang = Atan2(ty - cy, tx - cx)
        set ang_octant = ang
        if ang_octant < 0.0  then
            set ang_octant = ang_octant + TAU
        endif
        set octant = R2I(ang_octant / (TAU / 8.0))

        set cx = cx + Cos(ang) * 96.0
        set cy = cy + Sin(ang) * 96.0
        set wall_width = firewall_width_for(this.level)
        set tx = cx + Cos(ang) * wall_width
        set ty = cy + Sin(ang) * wall_width

        set x0 = cx / 32.0
        set y0 = cy / 32.0
        set x1 = tx / 32.0
        set y1 = ty / 32.0

        // switch to octant 0 from (x0, y0, x1, y1)
        if octant == 0 then
            set ox0 = x0
            set oy0 = y0
            set ox1 = x1
            set oy1 = y1

        elseif octant == 1 then
            set ox0 = y0
            set oy0 = x0
            set ox1 = y1
            set oy1 = x1

        elseif octant == 2 then
            set ox0 = y0
            set oy0 = -x0
            set ox1 = y1
            set oy1 = -x1

        elseif octant == 3 then
            set ox0 = -x0
            set oy0 = y0
            set ox1 = -x1
            set oy1 = y1

        elseif octant == 4 then
            set ox0 = -x0
            set oy0 = -y0
            set ox1 = -x1
            set oy1 = -y1

        elseif octant == 5 then
            set ox0 = -y0
            set oy0 = -x0
            set ox1 = -y1
            set oy1 = -x1

        elseif octant == 6 then
            set ox0 = -y0
            set oy0 = x0
            set ox1 = -y1
            set oy1 = x1

        else // if octant == 7 then
            set ox0 = x0
            set oy0 = -y0
            set ox1 = x1
            set oy1 = -y1

        endif
        set x0 = ox0
        set y0 = oy0
        set x1 = ox1
        set y1 = oy1


        set i_x0 = R2I(x0)
        set i_y0 = R2I(y0)
        set i_x1 = R2I(x1)
        set i_y1 = R2I(y1)

        set dx = i_x1 - i_x0
        if dx < 0 then
            set dx = -dx
        endif

        if i_x0 < i_x1 then
            set sx = 1
        else
            set sx = -1
        endif

        set dy = i_y1 - i_y0
        if dy < 0 then
            set dy = -dy
        endif

        if i_y0 < i_y1 then
            set sy = 1
        else
            set sy = -1
        endif

        set err = dx - dy // error value e_xy

        if dx + dy == 0 then
            set ed = 1.0
        else
            set ed = SquareRoot(dx * dx + dy * dy)
        endif

        set width = (/*width:*/ 4.0 + 1.0) / 2.0
        set xyc = 0
        loop
            set xyc = xyc + 1
            set xs[xyc] = i_x0
            set ys[xyc] = i_y0

            set e2 = err
            set x2 = i_x0

            if 2 * e2 >= -dx then

                set e2 = e2 + dy
                set y2 = i_y0
                loop
                    exitwhen e2 >= ed * width or (i_y1 != y2 and dx <= dy)

                    set y2 = y2 + sy

                    set xyc = xyc + 1
                    set xs[xyc] = i_x0
                    set ys[xyc] = i_y0

                    set e2 = e2 + dx
                endloop

                if i_x0 == i_x1 then
                    exitwhen true
                endif

                set e2 = err
                set err = err - dy
                set i_x0 = i_x0 + sx
            endif

            if 2 * e2 <= dy then

                set e2 = dx - e2
                loop
                    exitwhen e2 >= ed * width or (x1 == x2 and dx >= dy)

                    set x2 = x2 + sx
                    set xyc = xyc + 1
                    set xs[xyc] = i_x0
                    set ys[xyc] = i_y0

                    set e2 = e2 + dy
                endloop

                if i_y0 == i_y1 then
                    exitwhen true
                endif

                set err = err + dx
                set i_y0 = i_y0 + sy
            endif

        endloop


        // switch from octant zero to (x, y)
        set i = 1
        if octant == 0 then
            loop
                exitwhen i > xyc
                set ox = xs[i] * 32.0
                set oy = ys[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop

        elseif octant == 1 then
            loop
                exitwhen i > xyc
                set ox = ys[i] * 32.0
                set oy = xs[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop

        elseif octant == 2 then
            loop
                exitwhen i > xyc
                set ox = -ys[i] * 32.0
                set oy = xs[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop

        elseif octant == 3 then
            loop
                exitwhen i > xyc
                set ox = -xs[i] * 32.0
                set oy = ys[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop

        elseif octant == 4 then
            loop
                exitwhen i > xyc
                set ox = -xs[i] * 32.0
                set oy = -ys[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop

        elseif octant == 5 then
            loop
                exitwhen i > xyc
                set ox = -ys[i] * 32.0
                set oy = -xs[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop

        elseif octant == 6 then
            loop
                exitwhen i > xyc
                set ox = ys[i] * 32.0
                set oy = -xs[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop

        else // if octant == 7 then
            loop
                exitwhen i > xyc
                set ox = xs[i] * 32.0
                set oy = -ys[i] * 32.0
                call RegionAddCell(this.reg, ox, oy)
                set i = i + 1
            endloop
        endif

        set wall_width_x48 = R2I(wall_width / 48.0)
        set wall_height_x48 = R2I(firewall_height_for(this.level) / 48.0)

        // we are the sentinel node
        set this.e_prev = this
        set this.e_next = this

        set i = 0
        loop
            exitwhen i >= wall_height_x48

            set j = 0
            loop
                exitwhen j >= wall_width_x48

                set x = cx + Cos(ang) * (j * 48.0)
                set y = cy + Sin(ang) * (j * 48.0)
                call MoveLocation(loc, x, y)
                set z = GetLocationZ(loc) + (i * 48.0)

                set e_node = allocate()

                // add special effect with z
                set platform = CreateDestructableZ('OTip', x, y, z, 0.0, 1.0, 0)
                set e_node.e = AddSpecialEffect(FIRE_MODEL, x, y)
                call RemoveDestructable(platform)

                set e_node.e_prev = this.e_prev
                set e_node.e_next = this.e_next
                set this.e_prev = e_node
                set e_node.e_prev.e_next = e_node
                set e_node.e_next.e_prev = e_node

                set j = j + 1
            endloop

            set i = i + 1
        endloop

        set this.trg = CreateTrigger()
        call TriggerRegisterEnterRegion(this.trg, this.reg, null)
        call TriggerRegisterLeaveRegion(this.trg, this.reg, null)
        call TriggerAddAction(this.trg, on_enter_leave_region_handler)

        if this.g == null then
            set this.g = CreateGroup()
        endif

        set interval = damage_interval_for(this.level)
        set duration = duration_for(this.level)
        set damage = damage_per_second_for(this.level)

        set this.interval_max_ticks = R2I(interval / DT)
        set this.interval_ticks = 0
        set this.dmg_per_interval = damage / (1.0 / interval)
        set this.dur_max_ticks = R2I(duration * (1.0 / DT))
        set this.dur_ticks = 0

        set ht[ht.h2i(this.trg)].int = this

        set ulc = ulc + 1
        set ul[ulc] = this
        if ulc == 1 then
            call TimerStart(tmr, DT, true, update_loop_handler)
        endif

        set platform = null
    endmethod

    static method on_enter_leave_region takes nothing returns nothing
        local thistype this = ht.select()[ht.h2i(GetTriggeringTrigger())].int
        local unit uu = GetTriggerUnit()
        local eventid ee = GetTriggerEventId()

        if ee == EVENT_GAME_ENTER_REGION then
            if targets_allowed(this, uu) then
                call GroupAddUnit(this.g, uu)
                call UnitDamageTarget(this.caster, uu, this.dmg_per_interval, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
            endif

        elseif ee == EVENT_GAME_LEAVE_REGION then
            call GroupRemoveUnit(this.g, uu)

        endif

        set uu = null
    endmethod

    method destroy2 takes integer i returns nothing
        local thistype e_node
        local thistype next

        set ul[i] = ul[ulc]
        set ulc = ulc - 1

        if ulc <= 0 then
            call PauseTimer(tmr)
        endif

        set this.caster = null
        call RemoveRegion(this.reg)
        set this.reg = null
        call DestroyTrigger(this.trg)
        set this.trg = null
        call GroupClear(this.g)

        set e_node = this.e_next
        if e_node != this then
            loop
                call DestroyEffect(e_node.e)
                set e_node.e = null

                set next = e_node.e_next
                call e_node.deallocate()

                set e_node = next
                exitwhen e_node == this.e_next
            endloop
        endif

        call this.deallocate()
    endmethod

    static method update_loop takes nothing returns nothing
        local thistype this
        local unit uu
        local integer i

        set i = 1
        loop
            exitwhen i > ulc
            set this = ul[i]

            set this.interval_ticks = this.interval_ticks + 1
            if this.interval_ticks >= this.interval_max_ticks then
                set this.interval_ticks = 0

                loop
                    set uu = FirstOfGroup(this.g)
                    exitwhen uu == null
                    call GroupRemoveUnit(this.g, uu)
                    call GroupAddUnit(tmp_group, uu)

                    if targets_allowed(this, uu) then
                        call UnitDamageTarget(this.caster, uu, this.dmg_per_interval, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                    endif
                endloop

                set swap_group = this.g
                set this.g = tmp_group
                set tmp_group = swap_group
            endif

            set this.dur_ticks = this.dur_ticks + 1
            if this.dur_ticks >= this.dur_max_ticks then
                call this.destroy2(i)
                set i = i - 1
            endif

            set i = i + 1
        endloop
    endmethod

    static method set_handlers takes nothing returns nothing
        set on_spell_effect_handler = function thistype.on_spell_effect
        set on_enter_leave_region_handler = function thistype.on_enter_leave_region
        set update_loop_handler = function thistype.update_loop
    endmethod

endstruct

endlibrary

ht.j
JASS:
library Ht

//! novjass

// Usage:

globals
    Ht ht
endglobals

function main takes nothing returns nothing
    local integer hid
    local integer my_int

    // on init
    set ht = Ht.create()

    // in "create"
        call ht.select()
        set hid = ht.h2i(/*CreateTrigger()*/ null)
        set my_int = 42
        set ht[hid].int = my_int

        // inlined:
        set ht.select()[ht.h2i(/*CreateTrigger()*/ null)].int = 42

    // in "handler"
        call ht.select()
        set hid = ht.h2i(/*GetTriggeringTrigger()*/ null)
        set my_int = ht[hid].int

        // inlined:
        set my_int = ht.select()[ht.h2i(/*CreateTrigger()*/ null)].int

        if ht[hid].int_e() then
            call BJDebugMsg(I2S(my_int))

            call ht[hid].int_d()
            if not ht[hid].int_e() then
                call BJDebugMsg("removed " + I2S(my_int))
            endif
        endif
endfunction

//! endnovjass

globals
    private hashtable ht = InitHashtable()
endglobals

struct Ht
    private static integer pk1
    private static integer pk2

    static method h2i takes handle h returns integer
        return GetHandleId(h)
    endmethod
    static method s2i takes string s returns integer
        return StringHash(s)
    endmethod

    // set the parent key, and make 2 copies of it
    method select takes nothing returns thistype
        set pk1 = this
        set pk2 = this
        return this
    endmethod

    method operator[] takes integer i returns thistype
        return this + i
    endmethod

    method operator int= takes integer x returns nothing
        call SaveInteger(ht, pk1, this - pk2, x) // this - pk2 = i
    endmethod
    method operator int takes nothing returns integer
        return LoadInteger(ht, pk1, this - pk2)
    endmethod
    method int_e takes nothing returns boolean // int-exists?
        return HaveSavedInteger(ht, pk1, this - pk2)
    endmethod
    method int_d takes nothing returns nothing // int-delete/destroy
        call RemoveSavedInteger(ht, pk1, this - pk2)
    endmethod

endstruct

endlibrary
Contents

Firewall (Map)

Reviews
IcemanBo
Approved. I'm personaly not a fan of some things (mentioned in post above) and I would not use it myself just because of the custom requirements already, but the spell itself works fine. The destructable destruction should be considered as well. I...
Just use table over always writing own system. :s We will endup each maker writing his own libraries and having redundandy and conflicts everywhere when going like this.

Erm, when reading code I realized it maybe could just take usage of the line segment enumeration, and outsourcing the enum work, then the region maths is not needed.
Then also no cell-limitation would matter.
Still, could you explain me why you took "48"? My thought is 3*32/2 but I'm not sure it's required.

I think a cool feature of the fire wall would be, if it optionaly could also burn destructables or trees that are in range. (example [Snippet] IsDestructableTree)

The config seems good, but in case of update you might add little comment mark to easily see what needs to be touched.
 
Approved.

I'm personaly not a fan of some things (mentioned in post above) and I would not use it myself just because of the custom requirements already, but the spell itself works fine. The destructable destruction should be considered as well. I rate it 3/5.

I added a "How to import" link to the description, please do something similar next time your own. : )
 
Top