Spell Soul Pounder

Hatebreeder

So many apples
Reaction score
381
Description:
Summons a Sphere of leeching mana and unleashes it against enemy units in a 275 AOE of a target location. It aims for units with a mana source, pounding on it's very soul, damaging, stealing mana and growing with every mana point it feast on, though it also attacks units with no mana source (with the penalty of not growing at all) and it moves from unit to unit, if there are any other in a 300 AOE around the unit it is leeching on - else it will vanish.
Requirements:
- Jass New Gen Editor
- Warcraft 1.24 Patch
-Cohadar's Timer Ticker (Also found in Demo Map)

SoulPounderStart.jpg

SoulPounderEffect.jpg

SoulPounderEnd.jpg
Code:
JASS:
scope SoulPounder initializer Init
    globals
        private constant integer ID    = 'A000' //Spell Raw Code
        private constant integer DUMMY = 'u001' //Dummy Raw Code
        
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
        private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
        
        private constant real BAIL     = 75. //Distance in front of Caster - required for the creation of the dummy
        private constant real ARC      = 1.4 // Dummy Arc movement
        
        private constant string ERRORMSG = "There are no targets in this area." //Displays message if no unit is detected
        private constant string SFX    = "Abilities\\Spells\\NightElf\\ManaBurn\\ManaBurnTarget.mdl" //Impact SFX
        private constant string ATTACH = "origin" //SFX attachmentpoint
        
        private group CHECK   = CreateGroup()
    endglobals

    private function Parabola takes real DistCur, real DistMax returns real //parabola function by shadow1500
    local real N = (DistCur * 2) / DistMax - 1
        return (-N * N + 1) * (DistMax / ARC)
    endfunction
    
    private function SetUnitXY takes unit u, real x, real y returns nothing //boarder safty
        if x<GetRectMaxX(bj_mapInitialPlayableArea) and x>GetRectMinX(bj_mapInitialPlayableArea) and y<GetRectMaxY(bj_mapInitialPlayableArea) and y>GetRectMinY(bj_mapInitialPlayableArea) then
            call SetUnitX(u,x)
            call SetUnitY(u,y)
        endif
    endfunction
    
    private constant function UnitFilter takes nothing returns boolean //basic unit Filter
        return IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) == false and IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE) == false and IsUnitEnemy(GetFilterUnit(),bj_groupEnumOwningPlayer) == true
    endfunction
        
    private function Radius takes integer Lvl returns real //initial Area of Effect
        return 275. + (0. * Lvl)
    endfunction
        
private struct Data
    
    private method Radius_Move takes nothing returns real //Area of effect after each bounce
        return 300. + (0. * .Lvl)
    endmethod
    
    private method Speed takes nothing returns real //Speed of the projectile
        return 17. + (0. * .Lvl)
    endmethod
    
    private method DamageAmount takes nothing returns real // Base Damage
        return 45. * .Lvl
    endmethod
    
    private method Increase takes nothing returns real // stolen mana (in %)
        return 0.05 + ( 0.02 * .Lvl )
    endmethod
    
    private method Jumps takes nothing returns integer // max jumps
        return 3 + ( 2 * .Lvl)
    endmethod    
    
        unit Caster
        unit Dummy
        unit Target
        real MaxX
        real MaxY
        real Damage
        group Group
        integer Max = 0
        integer Lvl
        static method Timer takes nothing returns boolean
            local Data this = TT_GetData()
            local real DX = GetUnitX(.Dummy)
            local real DY = GetUnitY(.Dummy)
            local real TX = GetUnitX(.Target)
            local real TY = GetUnitY(.Target)
            local real Angle = Atan2(TY - DY,TX - DX)
            local real X = DX + .Speed() * Cos(Angle)
            local real Y = DY + .Speed() * Sin(Angle)
            local real DistX
            local real DistY
            local real MaxDist
            local real CurDist
            local real Height
            local real Mana
            local integer Count = 0
            local integer Check = 0
            local unit Picked
            local unit array Choose
            
            set DistX = TX - DX
            set DistY = TY - DY
            set CurDist = SquareRoot(DistX * DistX + DistY * DistY)
            set DistX = TX - .MaxX
            set DistY = TY - .MaxY
            set MaxDist = SquareRoot(DistX * DistX + DistY * DistY)
            set Height = Parabola(CurDist,MaxDist)
            
            call SetUnitXY(.Dummy,X,Y)
            call SetUnitFlyHeight(.Dummy,Height,0)
            call SetUnitFacing(.Dummy,Angle * bj_RADTODEG)
            
            if .Target == null then
                call .destroy()
                    return true
            endif
            
            if CurDist <= .Speed() then
            
                if IsUnitType(.Target,UNIT_TYPE_DEAD) == false then
                    if GetUnitState(.Target,UNIT_STATE_MANA) >= 1 then
                        set Mana = GetUnitState(.Target,UNIT_STATE_MANA) * .Increase()
                        set .Damage = .Damage + Mana
                    
                        call SetUnitState(.Target,UNIT_STATE_MANA,GetUnitState(.Target,UNIT_STATE_MANA) - Mana)
                        call UnitDamageTarget(.Caster,.Target,.Damage,true,true,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
                        call SetUnitScale(.Dummy,1. + (Mana/.Damage),1. + (Mana/.Damage),1. + (Mana/.Damage))
                    else
                        call UnitDamageTarget(.Caster,.Target,.Damage,true,true,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
                    endif
        
                    call DestroyEffect(AddSpecialEffectTarget(SFX,.Target,ATTACH))
                endif
                
                set bj_groupEnumOwningPlayer = GetOwningPlayer(.Caster)
                call GroupEnumUnitsInRange(.Group,TX,TY,.Radius_Move(),Condition(function UnitFilter))
                    loop
                        set Picked = FirstOfGroup(.Group)
                            exitwhen Picked == null
                                if IsUnit(Picked,.Target) == false then
                                    set Count = Count + 1
                                    set Choose[Count] = Picked
                                endif
                            call GroupRemoveUnit(.Group,Picked)
                    endloop
                    
                set Picked = null
                    
                if Count > 0 and .Max < .Jumps() then
                    set .MaxX   = GetUnitX(.Target)
                    set .MaxY   = GetUnitY(.Target)
                    set .Target = Choose[GetRandomInt(1,Count)]
                    
                        loop
                            exitwhen Check >= Count
                                set Check = Check + 1
                                    set Choose[Check] = null
                        endloop
                        
                    set .Max = .Max + 1
                    
                else
                    call .destroy()
                        return true
                endif
            endif
        return false
        endmethod
        
        static method create takes unit c,real x, real y returns Data
            local Data this = Data.allocate()
            local real X
            local real Y
            local real TX = x
            local real TY = y
            local real Angle
            local integer Count = 0
            local integer Check = 0
            local unit array Choose
            local unit Picked

                set .Caster = c
                set .Lvl    = GetUnitAbilityLevel(.Caster,ID)
                set X       = GetUnitX(.Caster)
                set Y       = GetUnitY(.Caster)
                set Angle   = Atan2(TY - Y,TX - X)
                set .MaxX   = X + BAIL * Cos(Angle)
                set .MaxY   = Y + BAIL * Sin(Angle)
                set .Damage = .DamageAmount()
                
                if .Group == null then
                    set .Group = CreateGroup()
                else
                    call GroupClear(.Group)
                endif
                
                set bj_groupEnumOwningPlayer = GetOwningPlayer(.Caster)
                call GroupEnumUnitsInRange(.Group,TX,TY,Radius(.Lvl),Condition(function UnitFilter))
                    loop
                        set Picked = FirstOfGroup(.Group)
                            exitwhen Picked == null
                                    set Count = Count + 1
                                    set Choose[Count] = Picked
                            call GroupRemoveUnit(.Group,Picked)
                    endloop
                    
                set Picked = null
                    
                    set .Target = Choose[GetRandomInt(1,Count)]
                    set .Dummy  = CreateUnit(GetOwningPlayer(.Caster),DUMMY,.MaxX,.MaxY,Angle * bj_RADTODEG)
                    
                    loop
                        exitwhen Check >= Count
                            set Check = Check + 1
                                set Choose[Check] = null
                    endloop
                    
                    call TT_Start(function Data.Timer,this)
            return this
        endmethod
        
        private method onDestroy takes nothing returns nothing
            call KillUnit(.Dummy)
        endmethod
    endstruct
    
private function CheckCast takes nothing returns boolean
local unit Caster
    if GetSpellAbilityId() == ID then
        set Caster = GetTriggerUnit()
        set bj_groupEnumOwningPlayer = GetOwningPlayer(Caster)
        call GroupEnumUnitsInRange(CHECK,GetSpellTargetX(),GetSpellTargetY(),Radius(GetUnitAbilityLevel(Caster,ID)),Condition(function UnitFilter))
            if FirstOfGroup(CHECK) == null then
                call SimError(GetOwningPlayer(Caster),ERRORMSG)
                call IssueImmediateOrder(Caster,"stop")
                    return false
            endif
        call GroupClear(CHECK)
    endif
set Caster = null
    return true
endfunction
    
private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == ID then
        call Data.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
    endif
return false
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    local trigger Trig = CreateTrigger(  )
    local trigger Trg  = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( Trg , EVENT_PLAYER_UNIT_SPELL_CAST )
        call TriggerRegisterAnyUnitEventBJ( Trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( Trg , Condition( function CheckCast ) )
        call TriggerAddCondition( Trig, Condition( function Conditions ) )
    set Trg  = null
    set Trig = null
endfunction
endscope
I don't care about credits as long as you don't claim this your own.
Comments? =P
 

Attachments

  • SoulPounder.w3x
    57.8 KB · Views: 540

Kenny

Back for now.
Reaction score
202
Cool spell. Pretty basic in function, but it would suit many different types of maps.

Few things I picked up on:

- Your RADIUS, RADIUS_MOVE and SPEED constant globals. I think they would be better off as constant functions. Also I would like to see speed in units per second, not units per interval.

- You should get rid of the bj_DEGTORAD and bj_RADTODEG's. They are pretty useless and easy to fix.

- Attacktype, damagetype and weapontype should be configurable.

- From the looks of it, you can probably just use a global group instead of one per instance. Right now it looks like your not even destroying the dynamic groups, and creating one more every time the struct instance is reused.

- I'm pretty sure you shouldn't be using null as the boolexpr in your GroupEnum functions. Make a basic filter (like filtering for alive units), and then make a configurable function for users, instead of having it done internally.

- You can change the use of Data to thistype, so it is easier for people to change the struct name (if they want). However, this isn't really necessary.

- I'd also like to see this spell use KT2 and GTrigger. You have the best of the best available for you to use, so you may as well use them. Again, not necessary, but would be nice.
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211

Great feedback, really.

As for the spell:

You might want to add WC3 patch 1.24b as requirements in case someone (god forbid) uses a cracked version, or if they (like me) for some reason can't connect to battlenet and patch unless they reinstall WC3 each time they feel like connecting.

One thing you might feel like mentioning is whether or not this can hit the same target twice, and perhaps add a configurable boolean to allow/disallow that?

> You can change the use of Data to thistype, so it is easier for people to change the struct name (if they want). However, this isn't really necessary.

Why is it so popular to abuse thistype for this kind of thing nowadays?
 

quraji

zap
Reaction score
144
Soul Pounder, eh? Sounds kinky.

Can you unbold and smallerize the description? It destroyed my eyes :p

Remove the bj_RADTODEG and bj_DEGTORADs (you convert to deg then back to rad):
JASS:

            local real Angle = bj_RADTODEG * Atan2(TY - DY,TX - DX)
            local real X = DX + SPEED * Cos(Angle * bj_DEGTORAD)
            local real Y = DY + SPEED * Sin(Angle * bj_DEGTORAD)


Other than that I don't see anything wrong with this as long as it works, although there's some optimization to be done (that I'm too lazy to point out).

Cool, anyways.
 

Hatebreeder

So many apples
Reaction score
381
Cool spell. Pretty basic in function, but it would suit many different types of maps.

Few things I picked up on:

- Your RADIUS, RADIUS_MOVE and SPEED constant globals. I think they would be better off as constant functions. Also I would like to see speed in units per second, not units per interval.

- You should get rid of the bj_DEGTORAD and bj_RADTODEG's. They are pretty useless and easy to fix.

- Attacktype, damagetype and weapontype should be configurable.

- From the looks of it, you can probably just use a global group instead of one per instance. Right now it looks like your not even destroying the dynamic groups, and creating one more every time the struct instance is reused.

- I'm pretty sure you shouldn't be using null as the boolexpr in your GroupEnum functions. Make a basic filter (like filtering for alive units), and then make a configurable function for users, instead of having it done internally.

- You can change the use of Data to thistype, so it is easier for people to change the struct name (if they want). However, this isn't really necessary.

- I'd also like to see this spell use KT2 and GTrigger. You have the best of the best available for you to use, so you may as well use them. Again, not necessary, but would be nice.

Changed the script for most of the things you mentioned.

- You can change the use of Data to thistype, so it is easier for people to change the struct name (if they want). However, this isn't really necessary.

Didn't do this, since I never realy had to use this.
Though, i might implement this, when I can use this extra.

- I'd also like to see this spell use KT2 and GTrigger. You have the best of the best available for you to use, so you may as well use them. Again, not necessary, but would be nice.

Since TT and KT2 are similar systems, it wouldn't be much of a pain to search and replace this by yourself.
Same goes for GTrigger (Although it is good, i don't want to increase the list of requirements).

- From the looks of it, you can probably just use a global group instead of one per instance. Right now it looks like your not even destroying the dynamic groups, and creating one more every time the struct instance is reused.

I tried this. But meh, sometimes weird things happen when spam casting.
Did I "dynamically destroy" the group in the struct in the new script?

Also I would like to see speed in units per second, not units per interval.

Speed per second? you mean something like:
JASS:
Speed() / TT_PERIOD

this?

EDIT: forgot to mention that i have updated Demo map and Code and:
One thing you might feel like mentioning is whether or not this can hit the same target twice, and perhaps add a configurable boolean to allow/disallow that?
I also had this in Mind. Though, hitting a unit twice would happen in an instant - I would have to think of a whole new function of some sort to create a nice smooth jump over the same unit.
And to be honest, I don't feel like thinking about that right now.

Soul Pounder, eh? Sounds kinky.

Should I have called it "Soul F*cker"?
Also, i think Pound means to beat something/somebody up - at least this was the first thing i had in mind when i was creating the spell :3
 

Kenny

Back for now.
Reaction score
202
Didn't do this, since I never realy had to use this.
Though, i might implement this, when I can use this extra.

As I said, it isn't necessary, but it is neater (in my opinion).

I tried this. But meh, sometimes weird things happen when spam casting.
Did I "dynamically destroy" the group in the struct in the new script?

You sure you were doing it right? You are currently going through the unit groups instantly, so there should be no problem.

And you should clear a group before you destroy it. Or even better, do some group recycling within the script, something like this:

JASS:
private struct Data
        unit Caster
        unit Dummy
        unit Target
        real MaxX
        real MaxY
        real Damage
        group Group // Get rid of the = CreateGroup() here.
        integer Max = 0

        static method Timer takes nothing returns boolean
            local Data this = TT_GetData()
            local real DX = GetUnitX(.Dummy)
            local real DY = GetUnitY(.Dummy)
            local real TX = GetUnitX(.Target)
            local real TY = GetUnitY(.Target)
            local real Angle = Atan2(TY - DY,TX - DX)
            local real X = DX + Speed() * Cos(Angle)
            local real Y = DY + Speed() * Sin(Angle)
            local real DistX
            local real DistY
            local real MaxDist
            local real CurDist
            local real Height
            local real Mana
            local integer Count = 0
            local integer Check = 0
            local unit Picked
            local unit array Choose
            
            set DistX = TX - DX
            set DistY = TY - DY
            set CurDist = SquareRoot(DistX * DistX + DistY * DistY)
            set DistX = TX - .MaxX
            set DistY = TY - .MaxY
            set MaxDist = SquareRoot(DistX * DistX + DistY * DistY)
            set Height = Parabola(CurDist,MaxDist)
            
            call SetUnitXY(.Dummy,X,Y)
            call SetUnitFlyHeight(.Dummy,Height,0)
            call SetUnitFacing(.Dummy,Angle)
            
            if CurDist <= Speed() then
                if GetUnitState(.Target,UNIT_STATE_MANA) >= 1 then
                    set Mana = GetUnitState(.Target,UNIT_STATE_MANA) * Increase(GetUnitAbilityLevel(.Caster,ID))
                    set .Damage = .Damage + Mana
                    
                    call SetUnitState(.Target,UNIT_STATE_MANA,GetUnitState(.Target,UNIT_STATE_MANA) - Mana)
                    call UnitDamageTarget(.Caster,.Target,.Damage,true,true,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
                    call SetUnitScale(.Dummy,1 + (Mana/.Damage),1 + (Mana/.Damage),1 + (Mana/.Damage))
                else
                    call UnitDamageTarget(.Caster,.Target,.Damage,true,true,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
                endif
        
                call DestroyEffect(AddSpecialEffectTarget(SFX,.Target,ATTACH))
                
                call GroupEnumUnitsInRange(.Group,TX,TY,Radius_Move(),Condition(function UnitFilter))
                    loop
                        set Picked = FirstOfGroup(.Group)
                            exitwhen Picked == null
                                if IsUnitEnemy(Picked,GetOwningPlayer(.Caster)) == true and IsUnit(Picked,.Target) == false then
                                    set Count = Count + 1
                                    set Choose[Count] = Picked
                                endif
                            call GroupRemoveUnit(.Group,Picked)
                    endloop
                    
                set Picked = null
                    
                if Count > 0 and .Max < Jumps(GetUnitAbilityLevel(.Caster,ID)) then
                    set .MaxX   = GetUnitX(.Target)
                    set .MaxY   = GetUnitY(.Target)
                    set .Target = Choose[GetRandomInt(1,Count)]
                    
                        loop
                            exitwhen Check >= Count
                                set Check = Check + 1
                                    set Choose[Check] = null
                        endloop
                        
                    set .Max = .Max + 1
                    
                else
                    call .destroy()
                        return true
                endif
            endif
        return false
        endmethod
        
        static method create takes unit c,real x, real y returns Data
            local Data this = Data.allocate()
            local real X
            local real Y
            local real TX = x
            local real TY = y
            local real Angle
            local integer Count = 0
            local integer Check = 0
            local unit array Choose
            local unit Picked
            
                set .Caster = c
                set X       = GetUnitX(.Caster)
                set Y       = GetUnitY(.Caster)
                set Angle   = Atan2(TY - Y,TX - X)
                set .MaxX   = X + BAIL * Cos(Angle)
                set .MaxY   = Y + BAIL * Sin(Angle)
                set .Damage = DamageAmount(GetUnitAbilityLevel(.Caster,ID))

                if .Group == null then
                    set .Group = CreateGroup() // Create a new group if this struct instance has not been reached before.
                else
                    call GroupClear(.Group) // Otherwise just reuse the one you already have.
                endif
                
                call GroupEnumUnitsInRange(.Group,TX,TY,Radius(),Condition(function UnitFilter))
                    loop
                        set Picked = FirstOfGroup(.Group)
                            exitwhen Picked == null
                                if IsUnitEnemy(Picked,GetOwningPlayer(.Caster)) == true then
                                    set Count = Count + 1
                                    set Choose[Count] = Picked
                                endif
                            call GroupRemoveUnit(.Group,Picked)
                    endloop
                    
                set Picked = null
                    
                if Count > 0 then
                    set .Target = Choose[GetRandomInt(1,Count)]
                    set .Dummy  = CreateUnit(GetOwningPlayer(.Caster),DUMMY,.MaxX,.MaxY,Angle)
                    loop
                        exitwhen Check >= Count
                            set Check = Check + 1
                                set Choose[Check] = null
                    endloop
                    call TT_Start(function Data.Timer,this)
                else
                    call .destroy()
                endif
            return this
        endmethod
        
        private method onDestroy takes nothing returns nothing
            // You don&#039;t need to destroy the group anymore <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />.
            call KillUnit(.Dummy)
        endmethod
    endstruct


As for the speed per second, yes I meant something like that. So Speed() would return something like 800.00 units per second, then multiply it by TT_Period.

I also had this in Mind. Though, hitting a unit twice would happen in an instant - I would have to think of a whole new function of some sort to create a nice smooth jump over the same unit.
And to be honest, I don't feel like thinking about that right now.

What I think he meant was: does this hit the same unit twice throughout the duration of the spell. So if there are two units, will it jump between the two until there are no more jumps left, or is it like chain lightning and just stop after it hits each one once? I'm pretty sure it is the the first option.

And since you got rid of the bj_DEGTORAD and stuff, you need to fix up for SetUnitFacing() function and your CreateUnit() function. Currently you are still using a radians angle for the units facing.

Oh and you can probably just inline your actions function and do it all in the conditions function, like so:

JASS:
private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == ID then
        call Data.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
    endif

    return false
endfunction
 

Hatebreeder

So many apples
Reaction score
381
And since you got rid of the bj_DEGTORAD and stuff, you need to fix up for SetUnitFacing() function and your CreateUnit() function. Currently you are still using a radians angle for the units facing.

Oh and you can probably just inline your actions function and do it all in the conditions function, like so:

JASS:
private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == ID then
        call Data.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
    endif

    return false
endfunction

Oh damn, i forgot to change to degrees >_<

Btw, do the natives "GetSpellTargetX and GetSpellTargetY" respond to the event in it's condition?
Else i would be glad to take over that idea !

EDIT: Updated Code and Map.
There shouldn't be a problem with the SetUnitFacing and the Create Function.
Also, implemented Kenny!'s Group recycle Code.

There is still a problem with the Speed in seconds...
I just can't make it work the way i want it to (Meaning, that the Spell malfuncs when i try to convert Speed per instance to speed per second).
 

Kenny

Back for now.
Reaction score
202
Btw, do the natives "GetSpellTargetX and GetSpellTargetY" respond to the event in it's condition?
Else i would be glad to take over that idea !

I'm pretty sure GetSpellTargetLoc() works, so those two should as well. You can always test it. It would only take a few minutes.

As for the speed in units per second, do something like this:

JASS:
private constant function Speed takes integer lvl returns real //Speed of the projectile
    return 800.00 + (0.00 * lvl)
endfunction

// and when you use it:

local real X = DX + (Speed() * TT_Period) * Cos(Angle)
local real Y = DY + (Speed() * TT_Period) * Sin(Angle) // Should be using * not /


Also, I just realised that all your constant functions should be taking 'integer lvl' or something along that line, to make the spell easily configurable for X amount of levels.
 

Hatebreeder

So many apples
Reaction score
381
I'm pretty sure GetSpellTargetLoc() works, so those two should as well. You can always test it. It would only take a few minutes.

As for the speed in units per second, do something like this:

JASS:

private constant function Speed takes integer lvl returns real //Speed of the projectile
    return 800.00 + (0.00 * lvl)
endfunction

// and when you use it:

local real X = DX + (Speed() * TT_Period) * Cos(Angle)
local real Y = DY + (Speed() * TT_Period) * Sin(Angle) // Should be using * not /


Also, I just realised that all your constant functions should be taking 'integer lvl' or something along that line, to make the spell easily configurable for X amount of levels.

I've done this.
But the projectile moves strangly, if not even at all.
>_<
 

Hatebreeder

So many apples
Reaction score
381
Changed the Code for it to be more customizable, and got rid of the "actions" block and instead done all the actions in the "conditions" block.

Updated Code and Re-uploaded the Demo Map.
 

Kenny

Back for now.
Reaction score
202
JASS:
private function Conditions takes nothing returns boolean
local Data Soul
    if GetSpellAbilityId() == ID then
        set Soul = Data.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
    else
        call Soul.destroy()
    endif
return false
endfunction


That doesn't really work. Just do it this way:

JASS:
private function Conditions takes nothing returns boolean
    if GetSpellAbilityId() == ID then
        call Data.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
    endif

    return false
endfunction


Also, you can just use x and y (from the parameters) in the create method, you don't need to set them to a new variable and use them.

You should also have a struct member for the level of the ability for the caster, if will save a couple of function calls.
 

Hatebreeder

So many apples
Reaction score
381
Hmmmz...
I changed the Conditions thingy.
Will be approved in the state it is right now?
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
"Discribtion" is spelled "Description".

In the Data.create method:

JASS:
local integer Lvl = GetUnitAbilityLevel(.Caster,ID)


.Caster does not yet exist, you should change it to "c".

You should probably combine the FirstOfGroup loop into the UnitFilter, or the UnitFilter into the FirstOfGroup loop, so you can avoid going through every unit twice.

I saw you stop the spell on spell effect if there are no units in the targeted area, you could probably add a spell-cast event, and have that order the unit to stop if there are no valid units, that way your spell can save the unit the casting time and mana from an invalid cast. (You could check out the SimError library to display a warning message if you like).

Since you are getting the caster's ability level on every callback, you could try using the level as a struct member. The only difference would be that if your hero/unit happens to increase the skill's level while it's casting, it wouldn't increase in intensity halfway through the spell (a minor con).
This would also allow you to make your Radius, Radius_Move, Speed, DamageAmount, Increase, and Jumps functions all methods. They wouldn't need to take the Level argument, since they could use the .level struct member.

It's very well coded, I like the simple Condition-Action-func. ;)

I'll approve it once you fix the .Caster problem. :)
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
Functions that do not return the same value every time they are called can not be constant functions.

Since you've already added the Lvl as a struct member, a simple change would be to simply move it into the struct, change:

JASS:
constant function NAME takes integer Level returns....


to

JASS:
method NAME takes nothing returns....


and to call the function, simply switch

JASS:
NAME(.Lvl)


with

JASS:
.NAME()


You could also improve your enum filter even further by passing the data instance as a global variable. This would allow you to compare whether the picked unit is the target or not, etc. You would only have to transfer the Count integer and the Choose unit array (which I missed on the first look-through, it will leak as a local variable) to a struct member.

JASS:
                    call IssueImmediateOrder(.Caster,&quot;stop&quot;)


This would only work on EVENT_PLAYER_UNIT_SPELL_CAST events, not EVENT_PLAYER_UNIT_SPELL_EFFECT events. You'd have to add a second trigger and condition in order to save the unit some mana/casting time.
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
Nice work on the changes. A few more things I caught though. (Sorry to continuously point out the errors!! :p)

JASS:
        private constant group CHECK   = CreateGroup()


Constant for globals means it will be inlined. You don't want it to use a new group every time you use CHECK, do you? (Just make it private group CHECK).

function UnitFilter and function Parabola can't be constant either, as they take arguments (UnitFilter does stuff with a not-constant function, something constant functions can't do) and use them.

You could move Radius into the methods area too, since it only takes the struct-member Lvl argument. Seeing how you've done it for all the other similar functions, it makes sense to do them all the same way.

Your Timer method could be private (not that you need to, the struct is private).

JASS:
local unit Picked
...
        set Picked = FirstOfGroup(CHECK)
            if Picked == null then
...
set Picked = null


You could convert this all into [ljass]if FirstOfGroup(CHECK) == null then[/ljass]

JASS:
local real X
local real Y
local integer Level
...
        set X      = GetSpellTargetX()
        set Y      = GetSpellTargetY()
        set Level  = GetUnitAbilityLevel(Caster,ID)
        call GroupEnumUnitsInRange(CHECK,X,Y,Radius(Level),Condition(function UnitFilter))


could all be converted into
JASS:
call GroupEnumUnitsInRange(CHECK,GetSpellTargetX(),GetSpellTargetY(),Radius(GetUnitAbilityLevel(Caster,ID)),Condition(function UnitFilter))


There's no sense creating variables when you only use the value once. ;)
 

Hatebreeder

So many apples
Reaction score
381
kay I'll change the constant stuff.

the Radius() is not a method since a struct is not created in the CAST event.
Which doesn't do much of a difference, or?

Will also change the variable stuff in the CAST Conditions Block.

Wait for the next Update ;)

Will it be ready for approval when I'm done?
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
the Radius() is not a method since a struct is not created in the CAST event.
Which doesn't do much of a difference, or?

You are right. I didn't look where it was used, sorry.

Will it be ready for approval when I'm done?

I can't say, I'll have to look through the code a little more, and then test the map, to make sure everything's working right. ;)
 

Azlier

Old World Ghost
Reaction score
461
I have two great suggestions. Get rid of TT, and use T32 (or if you're lazy, KT2, it has the exact same syntax as TT with two words changed). Also, use GTrigger.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      • Ghan
        Administrator - Servers are fun

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top