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

Terror Rising v1.5

Hi everybody,

Today I upload a spell that uses my FearSystem.
This spell is Terror Rising (thanks to kakuzu for the name !).

The description of the spell : The caster create a electricity bolt that travels 425/600/775 units and every 175 units the bolt launch a explosion that deal 25/50/75 damages to enemy units and fear those units during 1.2/1.4/1.6 second.

The fear and the damage stacks if the unit take many explosion of the bolt meaning that a unit taking two explosions will be feared during (1.2-0.5)+1.2 = 1.9 second and will take 25+25 = 50 damages for the level 1 of the spell.
Note : There is a -0.5 because there is an explosion every 0.5 second.

This system requires the FearSystem, it is not optional.

Here is the code of the spell :
JASS:
library TerrorRising requires FearSystem/*By me*/, BoundSentinel //By Vex
/*
FearSystem : http://www.hiveworkshop.com/forums/spells-569/fear-system-v-2-7-a-243755/
BoundSentinel : http://www.wc3c.net/showthread.php?t=102576
*/
//CONFIGURATION
    globals
        private constant real FPS = 0.0312500
        //The distance between two explosions.
        private constant real BETWEEN_UNITS = 175.
        //The time between two explosions.
        private constant real TIME_BETWEEN = 0.5
        //It means that the speed of the orb is BETWEEN_UNITS/TIME_BETWEEN
        private constant integer SPELL_ID = 'U000'
        private constant integer DUMMY_ID = 'd000'
        private constant real AOE = 175.
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        //The path of the special effect attached to fear status
        private constant string FEAR_PATH = "Abilities\\Spells\\Undead\\Curse\\CurseTarget.mdl"
        private constant string FEAR_ATTACH = "overhead"
        //The path of the effect that will spawn on the units when it takes damage
        private constant string UNIT_PATH = "Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdl"
        private constant string UNIT_ATTACH = "origin"
        private constant string EXPLOSION_PATH = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
    endglobals
    
    private constant function Damage takes integer level returns real
        return 25.*level
    endfunction
    
    private constant function Explosion_Number takes integer level returns integer
        return 2+level
    endfunction
    
    private constant function Time_Fear takes integer level returns real
        return 1. + 0.2*level
    endfunction
    
    private function Unit_Filter takes player source, unit targ returns boolean
        return (not IsUnitType(targ, UNIT_TYPE_MAGIC_IMMUNE)) and (UnitAlive(targ)) and IsUnitEnemy(targ, source)
    endfunction
//END CONFIGURATION -> DONUT TOUCH ANYTHING BELOW

    private struct Explosion extends array
        unit u
        unit caster
        player owner
        integer steps
        real temp
        real fear
        real X
        real Y
        real dmg
        thistype prev
        thistype next
        static integer count
        static timer period
        static group g
        
        method destroy takes nothing returns nothing
            if this.next != 0 then
                set this.next.prev = this.prev
            endif
            set this.prev.next = this.next
            set this.prev = thistype(0).prev
            set thistype(0).prev = this
            if thistype(0).next == 0 then
                call PauseTimer(period)
            endif
            call UnitApplyTimedLife(u, 'BTLF', 0.01)
            set this.u = null
            set this.caster = null
            set this.owner = null
        endmethod
        
        static method periodic takes nothing returns nothing
            local Fear F
            local thistype this = thistype(0).next
            local unit v
            local real x
            local real y
            loop
                exitwhen this == 0
                set this.temp = this.temp - FPS
                set x = GetUnitX(this.u) + this.X
                set y = GetUnitY(this.u) + this.Y
                call SetUnitX(this.u, x)
                call SetUnitY(this.u, y)
                if this.temp <= 0 then
                    call DestroyEffect( AddSpecialEffect(EXPLOSION_PATH, x, y) )
                    call GroupEnumUnitsInRange(g, x, y, AOE, null)
                    loop
                        set v = FirstOfGroup(g)
                        exitwhen v==null
                        call GroupRemoveUnit(g, v)
                        if Unit_Filter(this.owner, v) then
                            call DestroyEffect( AddSpecialEffectTarget(UNIT_PATH,v,UNIT_ATTACH) )
                            if Fear.isFeared(v) then
                                set F = Fear.get(v)
                                set F.time = this.fear + F.time
                            else
                                set F = Fear.create()
                                set F.targ = v
                                set F.path = FEAR_PATH
                                set F.attach = FEAR_ATTACH
                                set F.time = this.fear
                                call F.start()
                                call F.destroy()
                            endif
                            call UnitDamageTarget(this.caster, v, this.dmg, true, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                        endif
                    endloop
                    set this.steps = this.steps - 1
                    set this.temp = TIME_BETWEEN
                endif
                if this.steps == 0 then
                    call this.destroy()
                endif
                set this = this.next
            endloop
        endmethod
        
        static method cond takes nothing returns boolean
            local thistype this
            local real angle
            local real tx
            local real ty
            local integer i
            if GetSpellAbilityId() == SPELL_ID then
                //Allocate
                if thistype(0).prev == 0 then
                    set count = count + 1
                    set this = count
                else
                    set this = thistype(0).prev
                    set thistype(0).prev = thistype(0).prev.prev
                endif
                if thistype(0).next == 0 then
                    call TimerStart(period, FPS, true, function thistype.periodic)
                else
                    set thistype(0).next.prev = this
                endif
                set this.next = thistype(0).next
                set thistype(0).next = this
                set this.prev = thistype(0)
                //End Allocate
                set this.caster = GetTriggerUnit()
                set i = GetUnitAbilityLevel(this.caster,SPELL_ID)
                set tx = GetSpellTargetX()
                set ty = GetSpellTargetY()
                set angle = Atan2(ty-GetUnitY(this.caster), tx-GetUnitX(this.caster))
                set this.owner = GetTriggerPlayer()
                set this.u = CreateUnit(this.owner, DUMMY_ID, tx, ty, angle*bj_RADTODEG)
                set this.steps = Explosion_Number(i)
                set this.temp = TIME_BETWEEN
                set this.fear = Time_Fear(i)
                set this.X = BETWEEN_UNITS*Cos(angle)*FPS
                set this.Y = BETWEEN_UNITS*Sin(angle)*FPS
                set this.dmg = Damage(i)
            endif
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.cond))
            call Preload(FEAR_PATH)
            call Preload(EXPLOSION_PATH)
            call Preload(UNIT_PATH)
            set period = CreateTimer()
            set count = 0
            set g = CreateGroup()
            set t = null
        endmethod
    endstruct
endlibrary

How to import :
- You will first need JNGP.
- Copy the three library inside the folder Requirements.
Note : You can use Table by Bribe or no Table at all too (even if I recommend to use one).
Actually I have trouble with Table by Bribe just use Table by Vex and nothing more if you can at the moment.
- Copy the trigger called TerrorRising
- Copy the abilities linked to the FearSystem called 'BEAR FORM FEAR' and 'MORPH FEAR' and 'DISABLE_ATTACK'. Then change the id inside the code of the FearSystem.
- Copy the dummy unit called 'DUMMY_EXPLOSION' and change the id inside the spell.
- Copy the ability FearExplosion. Then change the id inside the code of the spell.
- You're ready to go :)

Credits to :
- Vexorian -> Table, JassHelper, BoundSentinels
- Bribe -> Table
- Maker -> Help in FearSystem
- Chobibo -> Help in FearSystem
- Kakuzu -> Help for the name :3

Give me credits and to the other if you use ;)

v1.0 :
- Initial Release

v1.1 :
- Implemented BoundSentinels
- Struct members optimized.

v1.2 :
- Trigger optimization.

v1.3 :
- Added the Unit_Filter function
- Trigger optimization.

v1.4 :
- I don't remember what I've done sorry ;)

v1.5 :
- A good name finally !


Keywords:
Explosion, vJASS, JESP, Fear, Terror, Rising, Terror Rising, Malhorne
Contents

Just another Warcraft III map (Map)

Reviews
20:49, 3rd Jan 2014 BPower: Changes made, approved. One minor thing: The dummy unit still uses upgrades. (old)review: http://www.hiveworkshop.com/forums/spells-569/fearexplosion-v1-3-a-244427/index4.html#post2466426

Moderator

M

Moderator

20:49, 3rd Jan 2014
BPower: Changes made, approved.

One minor thing: The dummy unit still uses upgrades.

(old)review: http://www.hiveworkshop.com/forums/spells-569/fearexplosion-v1-3-a-244427/index4.html#post2466426
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
set angle = Atan2(ty-GetUnitY(this.caster), tx-GetUnitX(this.caster)) instead of set x = GetUnitX(this.caster) and set y = GetUnitY(this.caster) since x and y are only used once.

Add some explanation in the global settings block, to make it more user friendly.
Especially for those two: private constant real BETWEEN_UNITS and private constant real TIME_BETWEEN

call SetUnitPosition(this.u, x + this.X, y + this.Y) could be SetUnitX/Y, but then you have to use something like WorldBounds

set p = GetOwningPlayer(this.caster) could be done in your cond method. --> this.owner = GetTriggerPlayer()
Which also allows you to set this.u = CreateUnit(GetOwningPlayer(this.caster),... --> set this.u = CreateUnit(this.owner, ...

GetWidgetLife(v)>0.405 --> not (IsUnitType(v, UNIT_TYPE_DEAD)) or native UnitAlive

null this.caster in the end

not IsUnitAlly(v, p) --> IsUnitEnemy(v, p) :) I don't know if here is a difference, it just looks better.

Optional:
  • You could use xe, Dummy or Missile for Dummy recycling. (Probably you won't have to call CreateUnit more than 10 times for this spell with one of those)
  • SpellEffectEvent
  • WorldBounds
  • CTL (You did a good job with your timer so w/e)

After all these 3 are pretty standart resources and having them does hurt noone.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
JASS:
            call Preload(FEAR_PATH)
            call Preload(EXPLOSION_PATH)
            call Preload(UNIT_PATH)

lol wut? :D

JASS:
    private struct Explosion extends array
        unit u
        unit caster
        integer steps
        real temp
        real fear
        real X
        real Y
        real dmg
        thistype prev
        thistype next
        static integer count
        static timer period
        static group g

into

JASS:
    private struct Explosion extends array
        private unit u
        private unit caster
        private integer steps
        private real temp
        private real fear
        private real X
        private real Y
        private real dmg
        private thistype prev
        private thistype next
        private static integer count
        private static timer period
        private static group g

JASS:
                call SetUnitPosition(this.u, x + this.X, y + this.Y)

into

JASS:
                call SetUnitX(this.u, x + this.X)
                call SetUnitY(this.u, y + this.Y)

you are creating TriggerExecute, place destroy method above create
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
SetUnitX is always superior, and moreso for missiles, you dont need to stop orders or check pathing, you just need to move the missile
also calling SetUnitPosition has potential to lagg after several casts because of how it works
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
you are creating TriggerExecute , place destroy method above create
Above the periodic method.

@Malhorne
Try queuing the death animation instead of creating multiple dummies to play the animation.
 
Yes but it needs WorldBounds and I like to put resources with not many things needed :/
I'll see this after...

JASS:
static if LIBRARY.WorldBounds then

It is a missile so SetUnitX/Y instead of SetUnitPosition is negligible.

This is contradicting. The fact it's a missile is exactly why you should use SetUnitX/Y, SetUnitPosition is SO SLOW (compared to setunitx).
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
I think he meant dependencies on external libraries, but either way, Bounds Sentinel ain't so bad to import right?
 
Right, but I'd rather use Bounds Sentinel to save a few lines haha :D

Yeah.

Anyway here are some things I noticed.
  • GetOwningPlayer(this.caster)could be stored in the struct.
  • GetWidgetLife(v)>0.405could be replaced with UnitAlive. Make sure to declare native UnitAlive takes unit id returns booleanin your script somewhere or else you will get errors. The reasons are minor (small performance, readability) to implement this, but I think it's worth it.
  • I really think you should be using a timer system, like TimerUtils if you plan on using dynamic timers instead of one. If you insist on using one timer, I know there are some single timer loops around. The reason being that people generally want one of two things in their maps when it comes to utilizing timers. One, timer recycling (dynamic timers), or two a single timer for their map. Your spell currently fits into neither :( Not a big deal, but something to consider.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
BoundsSentinel different to WorldBounds. While WorldBounds only gives information about map bounds, BoundsSentinel moves units back in if they leave those.
If you plan to use one timer per instance, I wouldn't use TimerUtils since your timer timeout is 0.03125000, therefore I would go with CTL.
It's not recommended to change this timeout to higher values anyways for instance 0.1, because it would look stupid.

Edited
 
Last edited:
Level 22
Joined
Sep 24, 2005
Messages
4,821
If there is a standard library here for projectile movement, I think you should use that, and just use TimerUtils for the fear pulses.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
For vjass resources, it's always good to use shared resources, since having 20 mini missile systems in a map would slow the map down.

Anyway, let's wait for the mods to decide what to do, mine was only suggestions so no need to take it seriously :D. The spell looks great btw, good job!
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I create only one dummy.

It really create a triggerexecute if I put the destroy method after ?


Anyway I'll add the WorldBounds.
Someone has a link ?

yes, test it yourself, force error just after endstruct(put a behind that) and check how the code is called.

I dont think its that big of a deal that he uses one Timer, its one timer, even with 0.00325 s period, is still better than using TimerUtils here, because if you start new instance every cast, and you spam it moderatelly, it will lagg out, because you will have dozens of Timers running.

CTL is not bad, but its quite complicated and will generate a looot of here useless code
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
I dont think its that big of a deal that he uses one Timer, its one timer, even with 0.00325 s period, is still better than using TimerUtils here, because if you start new instance every cast, and you spam it moderatelly, it will lagg out, because you will have dozens of Timers running.

Yeah, but that wasn't the reason why I told him to use TimerUtils, I thought that he should use an external library for the periodic movement, most probably an approved projectile library in the hive. The fear pulses are configurable so doing something like fearing every 4 seconds is better handled by a single timer per projectile.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
JASS:
set tx = GetSpellTargetX()
set ty = GetSpellTargetY()
set this.u = CreateUnit(this.owner, DUMMY_ID, tx, ty, angle)
-->
JASS:
set this.tx = GetSpellTargetX()
set this.ty = GetSpellTargetY()
set this.u = CreateUnit(this.owner, DUMMY_ID, this.tx, this.ty, angle)
lets you do:
JASS:
set x = GetUnitX(this.u) + this.X
set y = GetUnitY(this.u) + this.Y
-->
JASS:
set this.tx = this.tx + this.X
set this.ty = this.ty + this.Y
and you get rid of local real x,y,tx,ty
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
You can always test it. I agree though on caching the initial coordinates, it would make the projectile trajectory smoother since it will ignore any inconsistencies (I doubt it, since he's already switched to SetUnitXY) due to pathing correction. Cached data incremented per period with the offset delta is a good solution, I bow to you sir.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
The Fear system is pretty cool and I love Fear.isFeared :).

I didn't find anything wrong inside the code, but some things I would like to mention:
  • You don't have to create these members in onInit, just do it right away.
    static timer period = CreateTimer() and static group g = CreateGroup and static integer count = 0
  • As I said before you could use an x and y member for the missile instead of calling GetUnitX/Y each loop.
  • unit v will be nulled when the group is empty because of the FirstOfGroup. You don't have to do it yourselfset v = null
  • Imo the TargetFilter should be configurable. --> private function FilterUnits takes takes player, unit returns boolean instead of

    if IsUnitEnemy(v, this.owner) and not IsUnitType(v, UNIT_TYPE_DEAD) then Also when using IsUnitType, you have to check GetUnitTypeId != 0 aswell.
:grin:Time_Fear the _ doesn't follow the JPAG, but actually I like it aswell.
 
Top