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

Dark Lightning Nova v1.3

2lag6xc.jpg


Most, if not everything about the spell is configurable - just check out the comments in the trigger

The spell uses
- 5 imports (2 models, 3 textures)
- 1 Dummy Unit (visibility)
- 1 trigger

and requires
- JNGP 1.5e or better (I didn't use structs so it should be really easy to convert this to JASS if you want)

Known bugs
- none yet i think lolz

JASS:
scope DarkLightningNova initializer init

/***********************************************************************************/
/*               Dark Lightning Nova v1.3                                          */
/*                      by: msongyyy                                               */
/*                                                                                 */
/*               requires: JNGP 1.5e or better                                     */
/*                         WorldBounds by Nestharus                                */
/*                                                                                 */
/* Big thanks to JetFangInferno for the DarkLightningNova and DarkLightning Models */
/*                                                                                 */
/* HOW TO IMPORT Dark Lightning Nova                                               */
/* 1. Copy the dummy/spell into your map                                           */
/* 2. Import the icon/models to the correct paths                                  */
/* 3. Set the first 4 configurables to the right path                              */   
/*    the DLN_NOVA = the imported DarkLightningNova path                           */
/*    the DLN_LIGHT = the imported DarkLightning path                              */
/*    the DLN_ID = Spell Rawcode ID for Dark Lightning Nova                        */
/*    the DLN_VISION_DUMMY = Unit Rawcode ID for Vision Dummy                      */
/* 4. Set the rest of the configurables to your liking ^^                          */
/*                                                                                 */
/*                                                                                 */
/*           ENJOY - PLEASE REPORT ALL BUGS TO ME                                  */
/*           AT HIVEWORKSHOP.COM                                                   */
/*                                                                                 */
/***********************************************************************************/   
                    
    globals                
        private constant string NOVA = "war3mapImported\\DarkLightningNova.mdx"     
        private constant string LIGHT = "war3mapImported\\DarkLightning.mdx"       
        private constant integer ID = 'A000'
        private constant integer VISION_DUMMY = 'h001'
        
        
/***********************************************************************************/
/*                                                                                 */
/*                  START OF CONFIGURABLES :)                                      */
/*  LEVEL = Level of DLN for unit - not really a configurable lol                  */
/*                                                                                 */
/*  NOVA_AOE = AoE real of the initial lightning                                   */                              
/*  LIGHT_AOE = AoE real of the lightning lines after initial                      */                                                         
/*                                                                                 */
/*  MAX_DIST_INC = Increment of max distance traveled by LEVEL                     */                                                                
/*  MAX_DIST_BASE = Base distance traveled by DLN                                  */                                                
/*  MAX_DIST = MAX_DIST_INC * LEVEL + MAX_DIST_BASE                                */                                                               
/*                                                                                 */
/*  NDMG_INC = Increment of damage of initial lightning by LEVEL                   */                                                                   
/*  NDMG_BASE = Base damage of initial lightning                                   */                                               
/*  NOVA_DAMAGE = NDMG_INC * LEVEL + NDMG_BASE (Total Damage)                      */
/*                                                                                 */
/*  LDMG_INC = Increment of damage of lightning after initial by LEVEL             */
/*  LDMG_BASE = Base damage of lightning after initial                             */
/*  LIGHT_DAMAGE = LDMG_INC * LEVEL + LDMG_BASE (Total Damage)                     */
/*                                                                                 */
/*  TIME_INC = How often each lighting strike fires off                            */
/*  DIST_INC = Distance between each instance                                      */
/*                                                                                 */
/***********************************************************************************/
        
        private constant real NOVA_AOE = 250.00
        private constant real LIGHT_AOE = 135.00
        
        private constant real MAX_DIST_INC = 500.00
        private constant real MAX_DIST_BASE = 500.00
        
        private constant real NDMG_INC = 100.00
        private constant real NDMG_BASE = 50.00
        
        private constant real LDMG_INC = 50.00
        private constant real LDMG_BASE = 25.00
        
        private constant real DIST_INC = 50.00
        private constant real TIME_INC = .05
        
        private timer TMR = CreateTimer()
    endglobals
    
/***********************************************************************************/
/* DLN_MAX_DIST = Max distance traveled by DLN                                     */
/***********************************************************************************/
    
    private constant function MAX_DIST takes real level returns real
        return level*MAX_DIST_INC + MAX_DIST_BASE
    endfunction
                     
/***********************************************************************************/
/* DLN_NOVA_DAMAGE = Dealt by the initial lightning                                */
/***********************************************************************************/
    
    private constant function NOVA_DAMAGE takes real level returns real
        return level*NDMG_INC + NDMG_BASE 
    endfunction
    
/***********************************************************************************/
/* DLN_LIGHT_DAMAGE = Damage dealt by each of the lightning strikes after the      */
/* initial lightning                                                               */
/***********************************************************************************/
                    
    private constant function LIGHT_DAMAGE takes real level returns real
        return level*LDMG_INC + LDMG_BASE 
    endfunction

/***********************************************************************************/
/* DLN_LEVEL = Just gets the level of DLN for a unit to make code a lil cleaner :) */
/***********************************************************************************/
    
    private function LEVEL takes unit u returns real 
        return I2R(GetUnitAbilityLevel(u, ID))
    endfunction
    
/***********************************************************************************/
/*                                                                                 */
/*               END OF CONFIGURABLES, DON'T TOUCH PAST HERE :(                    */
/*                                                                                 */
/***********************************************************************************/

    globals
        private group g = CreateGroup()
        private unit fog

        private unit array caster
        private unit array dummy
        private player array play
        private real array x
        private real array y
        private real array dist
        private real array ang

        private integer array next
        private integer array prev
        private integer array rn
        private integer ic = 0
    endglobals

    private function dlnLight takes nothing returns nothing
        local integer this = next[0]

        loop
            if next[0] == 0 then
                call PauseTimer(TMR)
            endif
            
            if dist[this] > MAX_DIST(LEVEL(caster[this])) then
                set caster[this] = null
                set play[this] = null
                set prev[next[this]] = prev[this]
                set next[prev[this]] = next[this]
                set rn[this] = rn[0]
                set rn[0] = this
                return
            else
                if dist[this] == 0 then
                    call DestroyEffect(AddSpecialEffect(NOVA, x[this], y[this]))
                    call DestroyEffect(AddSpecialEffect(LIGHT, x[this], y[this]))
                    call GroupEnumUnitsInRange(g, x[this], y[this], NOVA_AOE, null)
                    loop
                        set fog = FirstOfGroup(g)
                        exitwhen fog == null
                        if IsUnitEnemy(fog, GetOwningPlayer(caster[this])) then
                            call UnitDamageTarget(caster[this], fog, NOVA_DAMAGE(LEVEL(caster[this])), false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
                        endif
                        call GroupRemoveUnit(g, fog)
                    endloop
                
                elseif dist[this] >= NOVA_AOE and dist[this] <= MAX_DIST(LEVEL(caster[this])) then
                    call DestroyEffect(AddSpecialEffect(LIGHT, x[this], y[this]))
                    call GroupEnumUnitsInRange(g, x[this], y[this], LIGHT_AOE, null)
                    loop
                        set fog = FirstOfGroup(g)
                        exitwhen fog == null
                        if IsUnitEnemy(fog, GetOwningPlayer(caster[this])) then
                            call UnitDamageTarget(caster[this], fog, LIGHT_DAMAGE(LEVEL(caster[this])), false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
                        endif
                        call GroupRemoveUnit(g, fog)
                    endloop
                endif
            endif
            
            set dummy[this] = CreateUnit(play[this], VISION_DUMMY, x[this], y[this], 0)
            call UnitApplyTimedLife(dummy[this], 'BTLF', .6)
            set dummy[this] = null
            set dist[this] = dist[this] + DIST_INC
            set x[this] = x[this] + DIST_INC * Cos(ang[this])
            set y[this] = y[this] + DIST_INC * Sin(ang[this])
            
            if x[this] > I2R(WorldBounds.maxX) then
                set x[this] = I2R(WorldBounds.maxX) - 25
            endif
            if x[this] < I2R(WorldBounds.minX) then
                set x[this] = I2R(WorldBounds.minX) + 25
            endif
            if y[this] > I2R(WorldBounds.maxY) then
                set y[this] = I2R(WorldBounds.maxY) - 25
            endif
            if y[this] < I2R(WorldBounds.minY) then
                set y[this] = I2R(WorldBounds.minY) + 25
            endif
            
            set this = next[this]
            exitwhen this == 0
        endloop
    endfunction

    private function dlnCreate takes unit u, real xc, real yc, real r returns nothing
        local integer this = rn[0]

        if this == 0 then
            set ic = ic + 1
            set this = ic
        else
            set rn[0] = rn[this]
        endif

        set caster[this] = u
        set dist[this] = 0
        set x[this] = xc
        set y[this] = yc
        set ang[this] = r
        set play[this] = GetOwningPlayer(caster[this])

        set next[this] = 0
        set prev[this] = prev[0]
        set next[prev[0]] = this
        set prev[0] = this
        
        if prev[this] == 0 then
            call TimerStart(TMR, TIME_INC, true, function dlnLight)
        endif
    endfunction

    private function dlnNova takes nothing returns boolean
        local unit lvUnit = GetTriggerUnit()
        local real lvAng = Atan2(GetSpellTargetY() - GetUnitY(lvUnit), GetSpellTargetX() - GetUnitX(lvUnit))
        
        if GetSpellAbilityId() == ID then
            call dlnCreate(lvUnit, GetSpellTargetX(), GetSpellTargetY(), lvAng)
        endif
        
        set lvUnit = null
        return false
    endfunction

    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()

        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function dlnNova))

        set t = null
    endfunction
    
endscope

History: Originally this is a skill from my custom mod Night Elves, but after learning some new things about structs work - I had an epiphany to make recreate a spell using the basics. The one in this map is coded a lot better than the one in Night Elves I have to admit. I did not copy any one else's idea and for some reason if there is a spell similar to this that had come before the creation of this - all I have to say is - I didn't know about it and everything in this spell is my own idea work. For any veteran coders - this is a really simple spell, but I'm still happy and proud. I've really enjoyed recreating this from the start.

Changelot:
v1.0 - 6/16/2013 initial upload
v1.1 - 6/16/2013 Got rid of locations, and inlined GetPlayableMapRect() (Thanks to dimf!!)
v1.2 - 6/17/2013 Got rid of prefixes, made global vars and functions private, and used GetSpellTargetX, Y, and changed comment indentations (Thanks to Mr_Bean and Almia!!)
v1.3 - 6/22/2013 Changed some minor things, like getting the angle in rad and converting it multiple times for no reason. Purge outlined for me in his review (Thanks Purge!!)

HUGE THANKS TO:
JetFangInferno - DarkLightningNova and DarkLightning models (The spell is garbage without these visual eyecandy)
Maker and Nestharus have indirectly helped me in breaking down structure of structs and linked lists - which has led to the Triggers in this.
Hiveworkshop - thanks to the amazing community for their support and help
Blizzard - ofc for this game and the icon

Keywords:
Dark, Lightning, Nova, Thunder, Magic, Sorceress, Wizard, Evil
Contents

Dark Lightning Nova v1.3 (Map)

Reviews
16:36, 22nd Jun 2013 PurgeandFire: See my review: http://www.hiveworkshop.com/forums/2365426-post15.html Changes made. Approved.

Moderator

M

Moderator

16:36, 22nd Jun 2013
PurgeandFire: See my review:
http://www.hiveworkshop.com/forums/2365426-post15.html

Changes made. Approved.
 
Level 8
Joined
Feb 3, 2013
Messages
277
1. the API is odd (have not seen something like that before)
2 why you got 2 global blocks? you can combine them

effects seem nice however

I have no prior programming knowledge/experience before JASS, I don't know the norms and conventions so I just copy the styles or do what I think looks cute.

It's just to separate the configurables with the ones that are not lol

U should get rid of all locations. U should almost never need locations in jass. Also u should inline the GetPlayableMapRect function.

Gotcha, and sorry what does inline mean?
 
1) Make all globals of this spell private.
2) Remove the prefixes if you did above
3) Make the timer global not constant(well)
4) Make all constant functions and functions private.
5) Use http://www.hiveworkshop.com/forums/jass-resources-412/snippet-worldbounds-180494/
6) Index the caster owner ( player array)
I see you have indexed the owner,but you are not using it in the If-blocks in the loop.
7) If you want a this syntax, why not do this in struct then?
8) move the exitwhen this == 0 above the loop code(after the loop) because you are always running an end instance.
9) Instead of checking the instances with prev[this] to start a timer,use a instance counter instead,whereas every instance,it increments the counter, every instance ends, it decrements.
10)
JASS:
       local location lvLoc = GetSpellTargetLoc()
        local real x2 = GetLocationX(lvLoc)
        local real y2 = GetLocationY(lvLoc)
->
JASS:
local real x2 = GetSpellTargetX()
local real y2 = GetSpellTargetY()

Its a nice spell,but it has to do the above changes. Btw, Your Comments are not indented properly.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Firstly, nice documentation :D (although the comment blocks shouldn't be indented as much; for readability).

As mentioned above, you should make all your globals private. Then you can remove the prefix from them. You may want to make the spell's raw ID public if you need to access it externally, but that is rarely done.

Btw, Your Comments are not indented properly.

There is no standard for commenting, although it is more readable if the comment blocks aren't indented.
 
Level 8
Joined
Feb 3, 2013
Messages
277
^^ Okay Almia and Mr_Bean, thanks a lot for the comments I'll get those things done.

EDIT:
Almia said:
1) Make all globals of this spell private.
2) Remove the prefixes if you did above
3) Make the timer global not constant(well)
4) Make all constant functions and functions private.
5) Use [Snippet] WorldBounds
6) Index the caster owner ( player array)
I see you have indexed the owner,but you are not using it in the If-blocks in the loop.
7) If you want a this syntax, why not do this in struct then?
8) move the exitwhen this == 0 above the loop code(after the loop ) because you are always running an end instance.
9) Instead of checking the instances with prev[this] to start a timer,use a instance counter instead,whereas every instance,it increments the counter, every instance ends, it decrements.
10)

1. done
2. done
3. does it matter?
4. done
5. does it matter?
6. I use it for caster damage target and isunitenemy player
7. I wanted to prove to myself that structs aren't as magical as I thought they were
8. hm.. not sure it matters whether its at top or very bottom, it'll exit before any evaluations are made won't it?
9. sorry I don't understanddddd wai I have to do that.
10. done :)
 
Last edited:
Level 29
Joined
Oct 24, 2012
Messages
6,543
2) yes it will make things shorter and faster.
5) it will help narrow things down to a better area for any map this is used on. so it will make it easier for the ppl that use this.
7) structs are nice to use. yes u can do it without them but using them is both faster and more efficient.
8)if its at the top it will run one less instance
9) that is basic indexing. it should keep from bugs happening.
 
Level 8
Joined
Feb 3, 2013
Messages
277
2) yes it will make things shorter and faster.
5) it will help narrow things down to a better area for any map this is used on. so it will make it easier for the ppl that use this.
7) structs are nice to use. yes u can do it without them but using them is both faster and more efficient.
8)if its at the top it will run one less instance
9) that is basic indexing. it should keep from bugs happening.

2. constant Timer or function prefixes? I took care of the prefixes
5. gotcha, how can I set this up?

do I do something like if
if x[this] > WorldBounds.maxX then
set x[this] = WorldBounds.maxX
endif

EDIT: constant timer vs timer - if there is only one timer running does it even matter which one i use?
5 is done. I changed the WorldBounds to bj_mapInitialPlayableArea so game doesn't crash for people
okay I honestly don't understand 7, 8, and 9
I'm doing what a struct does for me in vJASS compiler, what difference will it make if I do it or the computer does it.

local integer i = 0

loop
exitwhen i == 1
blah
blah
blah
set i = i + 1
endloop

vs

loop
blah
blah
blah
set i = i + 1
exitwhen i = 1
endloop

mine's the 2nd loop, you're telling me the 1st loop will exit first - even though in order for the exit evaluation to trigger it needs to go back to the top of the loop? Plus it looks like the same thing to me - unless there's something about loops i dont understand.
how do I achieve 9, can I get an explicit example?

but please, I'm not trying to be rude - I just don't get it and I'm asking questions - correct me.
 
Last edited:
Level 29
Joined
Oct 24, 2012
Messages
6,543
2. constant Timer or function prefixes? I took care of the prefixes
5. gotcha, how can I set this up?

do I do something like if
if x[this] > WorldBounds.maxX then
set x[this] = WorldBounds.maxX
endif

EDIT: constant timer vs timer - if there is only one timer running does it even matter which one i use?
5 is done. I changed the WorldBounds to bj_mapInitialPlayableArea so game doesn't crash for people
okay I honestly don't understand 7, 8, and 9
I'm doing what a struct does for me in vJASS compiler, what difference will it make if I do it or the computer does it.

it creates extra code when u dont use structs. structs compile into faster simpler code if u use the extends array.
also it does the indexing for u so its less variables as well.
try to look at some of my systems in my sig. they all have structs in them

local integer i = 0

loop
exitwhen i == 1
blah
blah
blah
set i = i + 1
endloop

vs

loop
blah
blah
blah
set i = i + 1
exitwhen i = 1
endloop

in that case it doesnt really matter but the conventional way is keep it at the top.

mine's the 2nd loop, you're telling me the 1st loop will exit first - even though in order for the exit evaluation to trigger it needs to go back to the top of the loop? Plus it looks like the same thing to me - unless there's something about loops i dont understand.
how do I achieve 9, can I get an explicit example?

no that was a miscommunication made by me. it was just for convention.
i thought u had a nested loop lol i didnt really look at the code. i just looked at the questions u had on what almia said.

but please, I'm not trying to be rude - I just don't get it and I'm asking questions - correct me.

thats ok asking for help is never rude. i dont mind helping ppl lol. kinda y i have so many posts on this site.
 
The reason why I want you to move that to the top is because it is a Linked List.
If an instance ends,you are still running the function even though it is already ended. That is in my opinion. Its better if you use exitwhen as the first code in the loop.

Its like:
JASS:
loop
    exitwhen this == 0 // If is 0,below codes are not called.
    call BJDebugMsg("HAHAHA")
    set this = n[this]
endloop]
VS
[code=jass]
loop
    call BJDebugMsg("HAHAHA") //even though the instance is already ended,it is still executed.
    set n[this] = this
    exitwhen this == 0

Btw,it also execute the functions even though the instances are empty.
 
Level 8
Joined
Feb 3, 2013
Messages
277
The reason why I want you to move that to the top is because it is a Linked List.
If an instance ends,you are still running the function even though it is already ended. That is in my opinion. Its better if you use exitwhen as the first code in the loop.

Its like:
JASS:
loop
    exitwhen this == 0 // If is 0,below codes are not called.
    call BJDebugMsg("HAHAHA")
    set this = n[this]
endloop]
VS
[code=jass]
loop
    call BJDebugMsg("HAHAHA") //even though the instance is already ended,it is still executed.
    set n[this] = this
    exitwhen this == 0

Btw,it also execute the functions even though the instances are empty.

I guess you are right about it running once no matter what. But I don't think I can really do anything to change that.

But what do you mean functions will run when instances are empty?

btw Almia, thanks for your comments - I appreciate it a lot. :)
 
  • In the initialization function, call TriggerAddCondition(t, function dlnNova) should be call TriggerAddCondition(t, Condition(function dlnNova)), or else it might not work on certain compilers.
  • This function:
    JASS:
        private function dlnNova takes nothing returns boolean
            local unit lvUnit = GetTriggerUnit()
            local real x1 = GetUnitX(lvUnit)
            local real y1 = GetUnitY(lvUnit)
            local real x2 = GetSpellTargetX()
            local real y2 = GetSpellTargetY()
            local real lvAng = bj_RADTODEG * Atan2(y2 - y1, x2 - x1)
            
            if GetSpellAbilityId() == ID then
                call dlnCreate(lvUnit, x2, y2, lvAng)
            endif
            
            set lvUnit = null
            return false
        endfunction
    Can be compressed down to:
    JASS:
    private function dlnNova takes nothing returns boolean
        local unit lvUnit = GetTriggerUnit()
        local real x2 = GetSpellTargetX()
        local real y2 = GetSpellTargetY()
        local real lvAng = bj_RADTODEG * Atan2(y2 - GetUnitY(lvUnit), x2 - GetUnitX(lvUnit))
            
        if GetSpellAbilityId() == ID then
            call dlnCreate(lvUnit, x2, y2, lvAng)
        endif
        return false
    endfunction
    Alternatively, you can compress the entire thing down to:
    JASS:
    private function dlnNova takes nothing returns boolean
        local unit u = GetTriggerUnit()
        if GetSpellAbilityId() == ID then
            call dlnCreate(u, GetSpellTargetX(), GetSpellTargetY(), bj_DEGTORAD*Atan2(GetSpellTargetY() - GetUnitY(u), GetSpellTargetX() - GetUnitX(u)
        endif
        set u = null
        return false
    endfunction
    That one might be slightly slower since it has extra function calls, but it is viable depending on your guidelines for declaring locals. For some people, 2 or more calls of the same function (such as GetTriggerUnit()) indicates that you should declare a local. For others, it is 3 or more calls. I personally would choose the one that has the locals, since the difference is negligible.
  • In dlnCreate, set play[this] = GetOwningPlayer(caster[this]) can simply be set play[this] = GetTriggerPlayer()
  • In the function dlnLight, you use GetOwningPlayer(caster[this]) when you can just use play[this]
  • When you make lvAng, you convert radians to degrees. However, when you actually use it in the loop script, you convert the degrees to radians. You should just leave lvAng as radians and then remove the multiplication in the loop script.
  • When you have this: elseif dist[this] > MAX_DIST(LEVEL(caster[this])) then, I'm assuming you are ending that instance because that spell instance is over. However, after that block you still end up creating a dummy unit and setting its x and y, etc. You shouldn't create the dummy if the spell instance is over.

Make those changes and I think it should be approvable.
 
Top