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

Runes V.2.3.

  • Like
Reactions: xIceShotx
Runes
(vJASSified)
By moyack. 2011.

attachment.php

Thanks Kam for the screenshot :)


A long time ago, I did by request a spell for the project Tides of Darkness. Now I decided to vJASSify it and this is the result.

This spell resembles the one in WC2, used by the ogre magi units.


Requirements:

Spell Description:

Warcraft II Strategy: Units said:
The exact nature of the Runes used by Ogre-Magi is unknown to the Humans, but their effect is obvious: anyone straying into the area warded by the Runes is struck by a fiery explosion capable of killing or injuring the hardiest unit. Each "Rune" consists of 5 distinct points, each of which will explode only once. The Runes are also indiscriminate-any unit, friend or foe, that runs over them will be affected. Only flying units are immune to the effects. Since the Runes "float" on top of water, they are pretty good against enemy ships, especially transports.

How to install:

  1. Open the test map.
  2. Copy the trigger and the ability used in this spell into you map. If you have a dummy unit on it, set the unit rawcode in the spell description and in the ability.
  3. Voila!! spell installed.


JASS:
//***************************************************************************************************************
//*                                                                                                             *
//*                                          Runes Spell (vJASSified).                                          *
//*                                                By Moyack.                                                   *
//*                                                  V.2.3.                                                     *
//*                                                                                                             *
//***************************************************************************************************************
//*

library Runes initializer init requires TimedLoop
//***************************************************************************************************************
//* The constant data where you can modify the ability properties
//*
globals
    private constant integer SpellID = 'A000' // Returns the spell ID. Spell based on Serpent Ward
    private constant integer RuneID  = 'o000' // Returns the dummy summoned unit ID.
    private constant attacktype AttackT = ATTACK_TYPE_SIEGE  // Sets the Attack type deal to the units
    private constant damagetype DamageT = DAMAGE_TYPE_NORMAL // Sets the Damage type deal to the units
endglobals

private constant function Range takes integer lvl returns real
    return 120. + 50. * (lvl - 1) // Returns the range where the runes will detect units
endfunction

private constant function Radius takes integer lvl returns real
    return 2 * Range(lvl) // Returns the radius where the runes will be placed
endfunction

private constant function Amount takes integer lvl returns integer
    return 4 + 1 * (lvl - 1) // Returns the number of runes in the at the perimeter
endfunction

private constant function Damage takes integer lvl returns real
    return 350. + 50. * (lvl - 1) // Returns the damage dealt to units near to the runes
endfunction

private constant function Duration takes integer lvl returns real
    return 40. + 5. * (lvl - 1) // Returns the runes timed life
endfunction

//***************************************************************************************************************
//* end constant data...
//*

private function GetFX takes integer id returns string 
    return GetAbilityEffectById(SpellID, EFFECT_TYPE_SPECIAL, id)
endfunction

private struct rune
    unit caster
    player player
    effect effect
    real x
    real y
    real duration
    integer level
    
    static unit dummy

    private method destroy takes nothing returns nothing
        call DestroyEffect(.effect)
        if .duration < Duration(.level) then
            call DestroyEffect(AddSpecialEffect(GetFX(1), .x, .y))
            call DestroyEffect(AddSpecialEffect(GetFX(2), .x, .y))
        endif
    endmethod
    
    private method onTimedLoop takes nothing returns boolean
        local unit u
        set .duration = .duration + TimedLoop_PERIOD
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, .x, .y, Range(.level), null)
        loop
            set u = FirstOfGroup(bj_lastCreatedGroup)
            exitwhen u == null
            if GetUnitState(.caster, UNIT_STATE_LIFE) > 0.405 then
                call UnitDamageTarget(.caster, u, Damage(.level), false, false, AttackT, DamageT, WEAPON_TYPE_ROCK_HEAVY_BASH)
                return false
            else
                call SetUnitOwner(.dummy, .player, false)
                call UnitDamageTarget(.dummy, u, Damage(.level), false, false, AttackT, DamageT, WEAPON_TYPE_ROCK_HEAVY_BASH)            
                return false
            endif
            call GroupRemoveUnit(bj_lastCreatedGroup, u)
        endloop
        if .duration > Duration(.level) then
            return false
        endif
        return true
    endmethod
    
    implement TimedLoop
    
    static method Start takes unit c, real x, real y returns nothing
        local thistype R = thistype.allocate()
        if IsPlayerAlly(GetLocalPlayer(), GetOwningPlayer(c)) then
            set R.effect = AddSpecialEffect(GetFX(0), x, y)
        else
            set R.effect = AddSpecialEffect("Abilities\\Spells\\NightElf\\TreeofLifeUpgrade\\TreeofLifeUpgradeTargetArt.mdl", x, y) // shows an empty effect
        endif
        set R.caster = c
        set R.player = GetOwningPlayer(c)
        set R.x = x
        set R.y = y
        set R.level = GetUnitAbilityLevel(c, SpellID)
        call R.startTimedLoop()
    endmethod

endstruct

//***************************************************************************************************************
//*                                                                                                             *
//*                                         Runes Casting Functions                                             *
//*                                                                                                             *
//***************************************************************************************************************

private function Conditions takes nothing returns boolean
    local unit c = GetSummoningUnit()
    local unit r = GetSummonedUnit()
    local real lx = GetUnitX(r)
    local real ly = GetUnitY(r) 
    local real fc = GetUnitFacing(c) * bj_DEGTORAD
    local real a = Amount(GetUnitAbilityLevel(c, SpellID))
    local real R = Radius(GetUnitAbilityLevel(c, SpellID))
    local real angle = 2 * bj_PI / a
    local integer count = 0
    local real x
    local real y
    if GetUnitTypeId(r) == RuneID then
        call RemoveUnit(r)
        call rune.Start(c, lx, ly)
        loop
            exitwhen count == a
            set x = lx + R * Cos(fc + count * angle)
            set y = ly + R * Sin(fc + count * angle)
            call rune.Start(c, x, y)
            set count = count + 1
        endloop
    endif
    set c = null
    set r = null
    return false
endfunction

private function SetDummy takes nothing returns nothing
    set rune.dummy = CreateUnit(Player(15), RuneID, 0,0,0)
    call ShowUnit(rune.dummy, false)
    call DestroyTimer(GetExpiredTimer())
endfunction

//===========================================================================
private function init takes nothing returns nothing
    local trigger t= CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SUMMON )
    call TriggerAddCondition( t, Condition( function Conditions ) )
    call Preload(GetFX(0))
    call Preload(GetFX(1))
    call Preload(GetFX(2))
    call TimerStart(CreateTimer(), 0., false, function SetDummy)
    set t = null
endfunction

endlibrary


Changelog:
  • 2.0: Initial Release
  • 2.1: Improved the killing credits and performance a little bit more.
  • 2.2: Simplified the code a little bit in the Grouping units, and if the caster is alive in that moment, the killing credit goes to him. Added to the resurrection some eyecandy :D
  • 2.3: Improved the code. Now it requires TimedLoop by Vexorian which will do the nasty looping things for me :)

Keywords:
Runes, WC2, explosion
Contents

Runes Test (Map)

Reviews
24th Nov 2011 Bribe: Fun spell! There is a lot of room for improvement in the code still as you said. Approved (3.5/5).

Moderator

M

Moderator

24th Nov 2011
Bribe: Fun spell! There is a lot of room for improvement in the code still as you said.

Approved (3.5/5).
 
Level 3
Joined
Aug 12, 2010
Messages
29
Changed the init/action/condition.

JASS:
//***************************************************************************************************************
//*                                                                                                             *
//*                                          Runes Spell (vJASSified).                                          *
//*                                                By Moyack.                                                   *
//*                                                  V.2.2.                                                     *
//*                                                                                                             *
//***************************************************************************************************************
//*

library Runes initializer init
//***************************************************************************************************************
//* The constant data where you can modify the ability properties
//*
globals
    private constant integer SpellID = 'A000' // Returns the spell ID. Spell based on Serpent Ward
    private constant integer RuneID  = 'o000' // Returns the dummy summoned unit ID.
    private constant real    dt      = 0.2    // the rate which the runes will detect an unit
endglobals

private constant function Range takes integer lvl returns real
    return 120. + 50. * (lvl - 1) // Returns the range where the runes will detect units
endfunction

private constant function Radius takes integer lvl returns real
    return 2 * Range(lvl) // Returns the radius where the runes will be placed
endfunction

private constant function Amount takes integer lvl returns integer
    return 4 + 1 * (lvl - 1) // Returns the number of runes in the at the perimeter
endfunction

private constant function Damage takes integer lvl returns real
    return 350. + 50. * (lvl - 1) // Returns the damage dealt to units near to the runes
endfunction

private constant function Duration takes integer lvl returns real
    return 40. + 5. * (lvl - 1) // Returns the runes timed life
endfunction

//***************************************************************************************************************
//* end constant data...
//*

globals
    private group G = CreateGroup()
endglobals

private function GetFX takes integer id returns string 
    return GetAbilityEffectById(SpellID, EFFECT_TYPE_SPECIAL, id)
endfunction

private struct rune
    unit c
    player p
    effect f
    real x
    real y
    real d = 0.
    integer l
    integer i
    
    private static integer counter = 0
    private static rune array runes
    private static integer do = 0
    private static integer id = 0
    static unit dummy = null
    
    private static method GetUnits takes nothing returns boolean
        local unit u = GetFilterUnit()
        if GetWidgetLife( u ) > 0.405 and IsUnitType(u, UNIT_TYPE_FLYING) == false then
            call SetUnitOwner(rune.dummy, rune.runes[rune.id].p, false)
            if GetWidgetLife(rune.runes[rune.id].c) > 0.405 then
                call UnitDamageTarget(rune.runes[rune.id].c, u, Damage(rune.runes[rune.id].l), false, false, ATTACK_TYPE_SIEGE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_ROCK_HEAVY_BASH)
            else
                call UnitDamageTarget(rune.dummy, u, Damage(rune.runes[rune.id].l), false, false, ATTACK_TYPE_SIEGE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_ROCK_HEAVY_BASH)            
            endif
            set rune.do = rune.do + 1
        endif
        set u = null
        return false
    endmethod

    private method onDestroy takes nothing returns nothing
        call DestroyEffect(.f)
        if .d < Duration(.l) then
            call DestroyEffect(AddSpecialEffect(GetFX(1), .x, .y))
            call DestroyEffect(AddSpecialEffect(GetFX(2), .x, .y))
        endif
        set rune.counter = rune.counter - 1
        set rune.runes[rune.counter].i = .i
        set rune.runes[.i] = rune.runes[rune.counter]
    endmethod
    
    static method Start takes unit c, real x, real y returns nothing
        local rune R = rune.allocate()
        if IsPlayerAlly(GetLocalPlayer(), GetOwningPlayer(c)) then
            set R.f = AddSpecialEffect(GetFX(0), x, y)
        else
            set R.f = AddSpecialEffect("Abilities\\Spells\\NightElf\\TreeofLifeUpgrade\\TreeofLifeUpgradeTargetArt.mdl", x, y)
        endif
        set R.c = c
        set R.p = GetOwningPlayer(c)
        set R.x = x
        set R.y = y
        set R.l = GetUnitAbilityLevel(c, SpellID)
        set R.i = rune.counter
        set rune.runes[rune.counter] = integer(R)
        set rune.counter = rune.counter + 1
    endmethod
    
    static method Update takes nothing returns nothing
        local integer i = 0
        loop
            exitwhen i >= rune.counter
            set rune.id = i
            set rune.do = 0
            set rune.runes[i].d = rune.runes[i].d + dt
            call GroupEnumUnitsInRange(G, rune.runes[i].x, rune.runes[i].y, Range(rune.runes[i].l), Condition(function rune.GetUnits))
            if rune.do > 0 or rune.runes[i].d >= Duration(rune.runes[i].l) then
                call rune.runes[i].destroy()
            endif
            set i = i + 1
        endloop
    endmethod
endstruct

//***************************************************************************************************************
//*                                                                                                             *
//*                                         Runes Casting Functions                                             *
//*                                                                                                             *
//***************************************************************************************************************

private function Actions takes nothing returns boolean
    local unit c = GetSummoningUnit()
    local unit r = GetSummonedUnit()
    local real lx = GetUnitX(r)
    local real ly = GetUnitY(r) 
    local real fc = GetUnitFacing(c) * bj_DEGTORAD
    local real a = Amount(GetUnitAbilityLevel(c, SpellID))
    local real R = Radius(GetUnitAbilityLevel(c, SpellID))
    local real angle = 2 * bj_PI / a
    local integer count = 0
    local real x
    local real y
    call RemoveUnit(r)
    call rune.Start(c, lx, ly)
    if GetUnitTypeId(r)==RuneID then
        loop
            exitwhen count == a
            set x = lx + R * Cos(fc + count * angle)
            set y = ly + R * Sin(fc + count * angle)
            call rune.Start(c, x, y)
            set count = count + 1
        endloop
    endif
    set c = null
    set r = null
    return false
endfunction

//===========================================================================
private function init takes nothing returns nothing
    local trigger t= CreateTrigger(  )
    local integer i= 0
    loop
        exitwhen (i<=bj_MAX_PLAYER_SLOTS)
        call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SUMMON,null)
        set i=1+1
    endloop
    call TriggerAddCondition( t, Filter( function Actions ) )
    call Preload(GetFX(0))
    call Preload(GetFX(1))
    call Preload(GetFX(2))
    set rune.dummy = CreateUnit(Player(15), RuneID, 0,0,0)
    call ShowUnit(rune.dummy, false)
    call TimerStart(CreateTimer(), dt, true, function rune.Update)
    set t = null
endfunction

endlibrary
 
There's no difference between a constant and non-constant function.
The only difference is a speed difference and the non-constant functions win hands down.
This is because of a Jass-rule: The more the text, the slower the code.

Few things:

- Make the struct extend an array and do the allocation/deallocation yourself
- Use longer names for the struct members. (It has to be readable)

StructNamesAreWrittenLikeThis
theycanalsobewrittenlikethis But that looks horrible

The first is good.

structMethodsAreWrittenLikeThis
structMembersAreWrittenLikeThis

Here's a tutorial written by Bribe: JPAG

Proper application is required for a resource to be approved.

Also, I'd suggest replacing the method onDestroy with a destroy method. You can do the deallocation in there too.

Allocation/Deallocation is done like this:

JASS:
private static integer ic = 0
private static integer ir = 0
private static thistype array rn

static method create takes nothing returns thistype
    local thistype this = ir
    
    if this == 0 then
        set ic = ic + 1
        set this = ic
    else
        set ir = rn[this]
    endif

    // do whatever actions you want after this

    return this
endmethod

method destroy takes nothing returns nothing
    set rn[this] = ir
    set ir = this
endmethod

And instead of using a GroupEnumUnitsInRange call with a boolexpr, you should enumerate the units with a null boolexpr and loop over units in the group with a First-Of-Group loop

JASS:
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, null)
loop
    set u = FirstOfGroup(bj_lastCreatedGroup)
    exitwhen u == null
    if (yourFilterConditionsHere) then
        // actions here
    endif
    call GroupRemoveUnit(bj_lastCreatedGroup, u)
endloop

Pretty simple :3
And that method is way faster.

edit
And as you can see, I used bj_lastCreatedGroup.
You don't need to create a global group. This could help you reduce the handle count.

edit
And default struct member values are so old-school ;o
 
Level 15
Joined
Feb 15, 2006
Messages
851
This brings back memories!

I think you could turn of the looping trigger if there are no runes around. You could store the range when the runes are cast, and not get it with a function every time you enum units.
Yes, I was cheking this in the time I was submitting this, I'll do an update with that change.

There's no difference between a constant and non-constant function.
The only difference is a speed difference and the non-constant functions win hands down.
This is because of a Jass-rule: The more the text, the slower the code.

Few things:

- Make the struct extend an array and do the allocation/deallocation yourself
- Use longer names for the struct members. (It has to be readable)

StructNamesAreWrittenLikeThis
theycanalsobewrittenlikethis But that looks horrible

The first is good.

structMethodsAreWrittenLikeThis
structMembersAreWrittenLikeThis

Here's a tutorial written by Bribe: JPAG

Proper application is required for a resource to be approved.

Also, I'd suggest replacing the method onDestroy with a destroy method. You can do the deallocation in there too.

Allocation/Deallocation is done like this:

JASS:
private static integer ic = 0
private static integer ir = 0
private static thistype array rn

static method create takes nothing returns thistype
    local thistype this = ir
    
    if this == 0 then
        set ic = ic + 1
        set this = ic
    else
        set ir = rn[this]
    endif

    // do whatever actions you want after this

    return this
endmethod

method destroy takes nothing returns nothing
    set rn[this] = ir
    set ir = this
endmethod

And instead of using a GroupEnumUnitsInRange call with a boolexpr, you should enumerate the units with a null boolexpr and loop over units in the group with a First-Of-Group loop

JASS:
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, null)
loop
    set u = FirstOfGroup(bj_lastCreatedGroup)
    exitwhen u == null
    if (yourFilterConditionsHere) then
        // actions here
    endif
    call GroupRemoveUnit(bj_lastCreatedGroup, u)
endloop

Pretty simple :3
And that method is way faster.

edit
And as you can see, I used bj_lastCreatedGroup.
You don't need to create a global group. This could help you reduce the handle count.

edit
And default struct member values are so old-school ;o
AS I mentioned, this has old coding style, so I'll check this. I usually avoid the first of group method because I thought it was very slow compared with a groupenum. Do you have a link about benchmark of this?

Why is the ability not simply point-target, so that you don't have to waste a summoned unit?
Because I tend to do the spell so the AI can use it properly by default, but now that I think the situation, I think I can base it in Cluster Rockets, now that this ability the AI uses in a more defensive and/or trapping way.
 
AS I mentioned, this has old coding style, so I'll check this. I usually avoid the first of group method because I thought it was very slow compared with a groupenum. Do you have a link about benchmark of this?

Lost it, but trust me. FoG loops were proven to be 3x faster than GroupEnums with boolexprs.
 
Level 21
Joined
Jul 2, 2009
Messages
2,934
Do you mind if I use this for my project's Ogre Mage? Well I'm going to have a Ogre Enchanter as well, in which are the spell caster units of the Ogres.
 
Top