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

Punitive Surge 1.02

Punitive Surge v1.02 by Maker


IconDescription
PR_3.jpg
PR_2.jpg

Additionally, the caster leaves a trace of himself, quickly vanishing shadows.



JASS:
// * Punitive Surge v1.01 by Maker            *
// * Spell idea by Apheraz Lucent             *

// * The caster rushes to the targeted point  *
// * executing a spinning attack damaging     *
// * enemies. The caster leaves a quickly     *
// * fading "tail" of shadows of himself.     *


scope PunitiveSurge initializer Punitive_Surge

    globals

        // How fast the unit moves at the beginning
        private constant real INITIAL_SPEED = 12.
        // Speed multiplier between loops, how fast the unit accelerates
        private constant real ACCELERATION = 1.08
        // Maximum charge speed
        private constant real MAX_SPEED = 30.
        // Damage per ability level
        private constant real DAMAGE = 100.
        // Damage bonus per ability level, not applied at level 1
        private constant real BONUS_DAMAGE = 50
        // Update interval for moving loops. 1/loopTime = Frames per second
        private constant real LOOP_TIME = 0.03125
        // AoE of the spell
        private constant real RADIUS = 128.
        // Animation speed for the attack ( 1.00 = normal speed )
        private constant real ANIMATION_SPEED = 0.75
        
        
        
        // Raw code of the ability
        private constant integer ABILITYCODE = 'A000'
        // The dummy's raw code
        private constant integer DUMMYCODE = 'E001'
        // The pathing item's raw code
        private constant integer ITEMCODE = 'I000'
        
        // How transparent the dummies are at beginning, 255 means 100% visible, 177,5 = 50% visible
        private constant integer DUMMY_INITIAL_FADE = 175
        // How fast the dummies fade
        private constant integer FADE_RATE = 10
        // ^Play around with those values to get to desired^
        // ^tail fade time and tail length^
        
        // Vertex color values for dummy. Default caster color = 255, 255, 255
        // 0 , 0 , 0 = completely black
        private constant integer RED = 100
        private constant integer GREEN = 100
        private constant integer BLUE = 100
        
        // The animation that the dummies play
        private constant integer ANIMATION = 6
        // Attack type of the ability
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
        // Damage type of the attack
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
        // Effect on targets hit
        private constant string HIT_EFFECT = "Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodFootman.mdl"
        // Effect on caster
        private constant string CASTER_WEAPON_EFFECT = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"
        // Effect attachment points for caster
        private constant string ATTACH_1 = "weapon"
        private constant string ATTACH_2 = "right hand"
        // Effect attachment point for targets
        private constant string ATTACH_3 = "chest"
        // How many different sound effects are used
        private constant integer SOUNDS = 3
        // How loud the sounds will be. 255 = 100% volume, 0 = 0% volume
        private constant integer SOUND_VOLUME = 255
        
        //** DONT CHANGE THE VARIABLES BELOW **//
        
        private unit U
        private real R
        private player P
        private group UG
        
        private sound array sounds
        
        private integer casters = 0
        private integer dummies = 0
        
        private timer casterTimer = CreateTimer()
        private timer dummyTimer = CreateTimer()
        
        private group casterGroup = CreateGroup()
        private group dummyGroup = CreateGroup()
        
        private item itemPathing
        
        private hashtable rageHash = InitHashtable()
        
    endglobals
    

    // Retuns the damage done by the spell
    private constant function getDamage takes integer i returns real
        return DAMAGE + BONUS_DAMAGE * (i - 1)
    endfunction
    
    // Returns distance between two points
    private function getDistance takes real x1 , real x2 , real y1 , real y2 returns real
        local real dx = x2 - x1
        local real dy = y2 - y1
        return SquareRoot(dx * dx + dy * dy )
    endfunction
    
    // Returns an angle between two points
    private function getAngle takes real x1 , real x2 , real y1 , real y2 returns real
        return Atan2(y2 - y1 , x2 - x1 )
    endfunction
    
    // Playes a sound on unit
    function playSoundOnUnit takes unit u , sound soundHandle returns nothing
        call AttachSoundToUnit( soundHandle , u )
        call SetSoundVolume( soundHandle , SOUND_VOLUME )
        call StartSound( soundHandle )
    endfunction
    
    // Filters unwanted units from getting damaged
    // Damages targets and applies sound and sfx
    private function unitFilter takes nothing returns boolean
        local unit v = GetFilterUnit()
        local sound s = sounds[GetRandomInt(1 , SOUNDS)]
        
        if not(IsUnitType( v , UNIT_TYPE_DEAD )) and IsUnitEnemy( v , P ) and not(IsUnitInGroup(v, UG )) and not (IsUnitType( v, UNIT_TYPE_STRUCTURE ) ) then
            call UnitDamageTarget( U , v , R , false , false , ATTACK_TYPE , DAMAGE_TYPE , WEAPON_TYPE_WHOKNOWS )
            call DestroyEffect(AddSpecialEffectTarget( HIT_EFFECT , v , "ATTACH_3" ) )
            call GroupAddUnit( UG , v )
            call playSoundOnUnit( v , s )
        endif
            
        set v = null
        set s = null
        return  true
    endfunction
    
    // Fades out and removes dummies
    private function dummyLoop takes nothing returns nothing
        local unit u = GetEnumUnit()
        local integer i1 = GetHandleId(u)
        local integer i2 = LoadInteger(rageHash , i1 , StringHash("fade") )
        if i2 > 0 then
            set i2 = i2 - FADE_RATE
            call SetUnitVertexColor(u , RED , GREEN , BLUE , i2 )
            call SaveInteger(rageHash , i1 , StringHash("fade") , i2 )
        else
            set dummies = dummies - 1
            call GroupRemoveUnit( dummyGroup , u )
            call FlushChildHashtable(rageHash , i1)
            call RemoveUnit(u)
            
            if dummies == 0 then
                call PauseTimer(dummyTimer)
            endif
        endif
        set u = null
    endfunction
    
    private function dummyTimerExpire takes nothing returns nothing
        call ForGroup( dummyGroup , function dummyLoop )
    endfunction
    
    // Moves caster and checks collision
    private function casterLoop takes nothing returns nothing
        local unit u = GetEnumUnit()
        local unit v
        local integer i1 = GetHandleId(u)
        local integer i2
        local boolean b = LoadBoolean(rageHash , i1 , StringHash("boolean") )
        local group g = CreateGroup()
        local real r1 = GetUnitX(u)
        local real r2 = GetUnitY(u)
        local real r3 = LoadReal(rageHash , i1 , StringHash("x") )
        local real r4 = LoadReal(rageHash , i1 , StringHash("y") )
        local real r5 = getDistance( r1 , r3 , r2 , r4 )
        local real r6
        local real r7
        local real r8
        
        // Checks that the caster isn't dead and that the caster isn't close to
        // the cast target point yet
        if not (IsUnitType( u , UNIT_TYPE_DEAD) ) and r5 > MAX_SPEED and b == true then
        
            set r5 = LoadReal(rageHash , i1 , StringHash("speed") ) * ACCELERATION
            set r6 = LoadReal(rageHash , i1 , StringHash("angle") )
            
            // Limits max speed
            if r5 > MAX_SPEED then
                set r5 = MAX_SPEED
            endif
            
            call SaveReal(rageHash , i1 , StringHash("speed") , r5 )
            
            // Pathability is done by moving an item to the point the caster
            // is supposed to move to. If the item's coordinates after moving it
            // do not match the move to point's coordinates, the point is unpathable.
            // If the point is pathable, move the caster there.
            // If not, stop the spell.
            // Prevents the caster from passing strcutures, trees, cliffs, pathing
            // blockers and going off the map.
            set r7 = r1 + r5 * Cos(r6)
            set r8 = r2 + r5 * Sin(r6)
            
            call SetItemVisible( itemPathing , true )
            call SetItemPosition( itemPathing , r7 , r8 )
            
            set r5 = GetItemX(itemPathing)
            set r6 = GetItemY(itemPathing)
            call SetItemVisible( itemPathing , false )
            
            if r7 == r5 and r8 == r6 then
                
                call SetUnitX(u , r7)
                call SetUnitY(u , r8)
                
                // Set globals so they can be passed used in the filter function
                set U = u
                set R = getDamage(GetUnitAbilityLevel( u , ABILITYCODE ))
                set P = GetOwningPlayer(u)
                set UG = LoadGroupHandle(rageHash , i1 , StringHash("group") )
                
                call GroupEnumUnitsInRange( g , r7 , r8 , RADIUS , function unitFilter )
                
                // Create a fading "tail" dummy
                set v = CreateUnit( Player(15) , DUMMYCODE , r1 , r2 , GetUnitFacing(u) )
                set i2 = GetHandleId(v)
                set dummies = dummies + 1
                
                call GroupAddUnit( dummyGroup , v )
                call SetUnitColor( v , GetPlayerColor( P ) )
                call SetUnitTimeScale( v , ANIMATION_SPEED )
                call SetUnitAnimationByIndex( v , ANIMATION )
                call SetUnitVertexColor( v , RED , GREEN , BLUE , DUMMY_INITIAL_FADE )
                
                call SaveInteger(rageHash , i2 , StringHash("fade") , DUMMY_INITIAL_FADE )
            
                if dummies == 1 then
                    call TimerStart( dummyTimer , LOOP_TIME , true , function dummyTimerExpire )
                endif
                
                set v = null
                
            else
                call SaveBoolean(rageHash , i1 , StringHash("boolean") , false )
            endif
        else
            set casters = casters - 1
            call GroupRemoveUnit( casterGroup , u )
            call DestroyGroup(LoadGroupHandle(rageHash, i1 , StringHash("group") ) )
            call DestroyEffect(LoadEffectHandle(rageHash , i1 , StringHash("wEffect1") ) )
            call DestroyEffect(LoadEffectHandle(rageHash , i1 , StringHash("wEffect2") ) )
            call FlushChildHashtable(rageHash , i1)
            
            if casters == 0 then
                call PauseTimer(casterTimer)
            endif
        endif
        
        call DestroyGroup(g)
        
        set u = null
        set g = null
        
    endfunction
    
     private function casterTimerExpire takes nothing returns nothing
        call ForGroup( casterGroup , function casterLoop )
    endfunction

    // This runs when the caster begins casting the ability.
    // Begins casting -event is chose over Starts the effect - event
    // since there's casting delay betweeb those events (default = 0.5 seconds).
    // Begins castin -event fires sooner. It does not bug even if the unit gets a stop
    // order during the ast time.
    private function Actions takes nothing returns nothing
    
        local unit u = GetTriggerUnit()
        local integer i = GetHandleId(u)
        local integer i2 = GetUnitAbilityLevel( u , ABILITYCODE ) 
        local real x1 = GetUnitX(u)
        local real y1 = GetUnitY(u)
        local real x2 = GetSpellTargetX()
        local real y2 = GetSpellTargetY()
        local real r1 = getDamage(i2)
        local real r2
        local real r3
        local real r4
        
        // Checks that the target point is pathable.
        // If not, use the nearest pathable point as the new
        // target point.
        call SetItemVisible( itemPathing , true )
        call SetItemPosition( itemPathing , x2 , y2 )
        
        set r3 = GetItemX(itemPathing)
        set r4 = GetItemY(itemPathing)
        
        if r3 == x2 and r4 == y2 then
            set r2 = getAngle( x1 , x2 , y1 , y2 )
        else
            set r2 = getAngle( x1 , r3 , y1 , r4 )
            set x2 = r3
            set y2 = r4
        endif
        
        call SetUnitTimeScale( u , ANIMATION_SPEED )
        call SetItemVisible( itemPathing , false )
        call GroupAddUnit(casterGroup , u)
        
        set casters = casters + 1
        set UG = CreateGroup()
        
        call SaveReal(rageHash , i , StringHash("x") , x2 )
        call SaveReal(rageHash , i , StringHash("y") , y2 )
        call SaveReal(rageHash , i , StringHash("angle") , r2 )
        call SaveReal(rageHash , i , StringHash("damage") , r1 )
        call SaveReal(rageHash , i , StringHash("speed") , INITIAL_SPEED )
        call SaveGroupHandle(rageHash , i , StringHash("group") , UG )
        call SaveBoolean(rageHash , i , StringHash("boolean") , true )
        call SaveEffectHandle(rageHash , i , StringHash("wEffect1") , AddSpecialEffectTarget(CASTER_WEAPON_EFFECT , u , ATTACH_1 ) )
        call SaveEffectHandle(rageHash , i , StringHash("wEffect2") , AddSpecialEffectTarget(CASTER_WEAPON_EFFECT , u , ATTACH_2 ) )
        
        if casters == 1 then
            call TimerStart( casterTimer , LOOP_TIME , true , function casterTimerExpire )
        endif
        
        set u = null
    endfunction
    
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == ABILITYCODE
    endfunction

    private function Punitive_Surge takes nothing returns nothing
        local trigger t = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CAST )
        call TriggerAddCondition( t , Condition( function Conditions ) )
        call TriggerAddAction( t , function Actions )
        
        set itemPathing = CreateItem( ITEMCODE , 0 , 0 )
        call SetItemVisible( itemPathing , false )

        set sounds[1] = gg_snd_MetalHeavySliceFlesh1
        set sounds[2] = gg_snd_MetalHeavySliceFlesh2
        set sounds[3] = gg_snd_MetalHeavySliceFlesh3
        
        set t = null
    endfunction

endscope


My first uploaded JASS spell, originally a request by Apheraz Lucent. Also loosely based on Ssasuke32's Dash Example spell.

Use JNGP if you want to edit the spell.


1.00
Uploaded
1.01
Small code improvements
More configurables
Uses custom item
Changed animation speed of caster
1.02
The dummies now play their animation at the same speed as the caster
Changed default sound volume to 100%




Apheraz Lucent for spell idea


Keywords:
punitive surge, maker, jump, charge
Contents

Charge (Map)

Reviews
12:58, 19th Oct 2010 TriggerHappy: Neat spell but your coding could use a few improvements. Your globals which are configurable should be constants (and named IN_ALL_CAPS). Same goes for your functions which are meant to be configured, they...

Moderator

M

Moderator

12:58, 19th Oct 2010
TriggerHappy:

Neat spell but your coding could use a few improvements.
  • Your globals which are configurable should be constants (and named IN_ALL_CAPS). Same goes for your functions which are meant to be configured, they should be constant functions.
  • It would be best if you used some sort of group recycling (see GroupUtils on wc3c).

Overall I thinks it's good enough to get approved.
 
Level 6
Joined
May 20, 2010
Messages
94
Well, I'm no jass-pro, so I'm not able to judge your coding. But I like the way you present your spell:
  • Screenshot
  • Youtube-Video
  • Code in Hidden-Tags
  • Usage of Tables and Boxes, I really like these boxes.

I would love to rate your spell when some other guys checked your code, just write a privat message and I'll check again and rate.
 
Level 9
Joined
Dec 12, 2007
Messages
489
nice code...
but:
1. since you're using vJASS, how bout using struct with TimerUtils maybe?
2. for the idea of customizability,
JASS:
        call SetSoundVolume( soundHandle , 200 )
that '200' need to be stored into global perhaps?

JASS:
        local sound s = sounds[GetRandomInt(0 , 2)]
that '2' might need to be changed into global, as if a user add some more sounds, he/she might not know that they need to change that '2'.

JASS:
            call DestroyEffect(AddSpecialEffectTarget( hitEffect , v , "chest" ) )
that "chest" could be stored in global too.

JASS:
            call SetUnitVertexColor(u , 100 , 100 , 100 , i2 )
JASS:
                call SetUnitVertexColor(v , 100 , 100 , 100 , dummyInitialFade )
in those line, you can add few globals to make this more customizable, as some user would like to change the color too, not just fading.

JASS:
        set itemPathing = CreateItem( 'ratc' , 0 , 0 )
that 'ratc' could be stored inside a global too, if the user modify the standard item, does it has no impact to the pathing?

those "store into global" thing are just an idea, as some user will just copy+paste the spell, only look the spell from start to end, then just change the vars you provide on the top.

3. you didn't null the trigger "t" in your init.

well just that, hehe
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Code Review:

  • You should definitely look into GroupUtils like TriggerHappy mentioned. It would allow you to recycle a unit group for UG instead of creating a new one each time.
  • Instead of using a local group variable, g, you should either have a global group variable used purely for that group enumeration or use ENUM_GROUP if you're using GroupUtils. This avoid having to use DestroyGroup (which you don't seem to be doing it all to g unless I'm being blind)
  • I suggest that you use TerrainPathability instead of your own pathability check.
  • I have yet another library to suggest, SoundUtils. This avoids having all those weird problems with sounds and makes it easier for people to import your spell as they don't need to import the global sounds as well.
  • If you want to become more skilled with vJass, you should look into using structs. By using structs, you wouldn't need a hashtable for this, unless I'm mistaken about something. A useful tutorial to look at would be this.
  • You could make more things configurable, such as vertex color of the dummy unit. If you don't want to use TerrainPathability, you should make the raw id of the item configurable.
  • JASS:
    call SetUnitColor( v , GetPlayerColor( GetOwningPlayer(u) ) )
    ->
    JASS:
    call SetUnitColor( v , GetPlayerColor( P ) )
  • You could null the local variable v after call GroupAddUnit(dummyGroup , v ) to avoid nulling it unnecessarily.
  • You could preload special effects.
The spell looks pretty nice in the video.

Edit: Wow, I'm pretty slow.
 
Last edited:
Level 22
Joined
Nov 14, 2008
Messages
3,256
To add more to this:

the initializing function should be private

the event should be spell effect not cast ( you may recognize this from GUI )

I think you should do the getangle and getdist inside the other functions instead of making separate functions

For me it looks like the height is changing in the video but I can't find such a call made :p

goody
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
Thaks to everyone for the suggestion. I applied some of them.

I'll look into using external libraries.

the event should be spell effect not cast

Did you check the comment in the trigger? There's casting delay between Begins casting and Starts the effect. By default it is 0,5 seconds. So to get by that 0,5 second delay I use Bagins casting. In the ability which is based on Channel, I have set "Disables other abilities" to true. So Begins casting works better with this spell than Starts the effect.

For me it looks like the height is changing in the video but I can't find such a call made :p

It's just the animation that makes it look like the height is changing :)
 
Level 30
Joined
Jan 31, 2010
Messages
3,551
Thanks for crediting me for idea, name and description :3
Just please, change the my name of my Shannequa to something else. Or use the following text:

Swirling in the darkness, the [HERO] is able to perform a move of intense dexterity, quickly traveling towards targeted point and slashing any enemy foolish enough to stand in [his/her] way. Maximum traveling range slightly increases with each level.
 
Level 8
Joined
Aug 2, 2008
Messages
193
lol, when I copy the trigger and change the raw codes I get this:
193808-albums3934-picture36956.jpg


(This is my first time importing a Jass spell)

Thanks!


Do you have the JNGP?
If not, get it and use this edtiro instead of the normal Wc3 Editor, which doesn't support vJass.


@Spell:
Looks nice, but the coding could be better, using structs and Systems like TimerUtils, GroupUtils etc.
 
Level 30
Joined
Jan 31, 2010
Messages
3,551
As a part of my thanks for showing me JNGP, here is edited, new description, along with several icon links. Yours is stretched for some reason.

Swirling in the Darkness and clashing out the weak, caster is able to perform a dashing move towards the desired destination. While traveling, shadows of hero will be summoned to slash any enemy units they encounter, dealing damage.

BTNAdvancedStrengthOfTheMoon.png
BTNDoom.png
BTNCleavingAttack.png


It's not ,,Point she's facing", because it's a targetable spell ^^
 
Level 1
Joined
Jun 30, 2012
Messages
1
Hmm...

first time importing a spell here, so...

To get the ability, I'd need to manually create the hero/ability/item in my own map first, right? It's not possible to import all object data without wiping my own map, am I right?(I know it sounds kinda lazy of me, but I don't want to accidently import and wipe ;_; )
 
Level 8
Joined
May 19, 2016
Messages
146
This Spell is awesome but it doesnt Work over 128x128 Area in my Map. I changed the Code that said ''playable 128'' but it still wont work over 128 :(
help
 
Level 39
Joined
Feb 27, 2007
Messages
5,010
This Spell is awesome but it doesnt Work over 128x128 Area in my Map. I changed the Code that said ''playable 128'' but it still wont work over 128 :(
help
I don't entirely understand what you want to do but:
  • the "RADIUS" variable you changed from 128 to some new value is not related to the physical size of your map in the editor.
  • RADIUS's value determines the distance at which targets are found in this line: call GroupEnumUnitsInRange( g , r7 , r8 , RADIUS , function unitFilter)
  • Because of the way GroupEnumUnitsInRange works, a unit will not be 'picked' by the enum unless the origin of its model is within RADIUS of the center. It does not take unit collision size into account, so it's possible that a unit with a large model/collision size/selection circle looked like it should be close enough but was not actually picked. You can get around this by making RADIUS bigger or altering the code to check multiple times but that's not particularly efficient.
 
Level 8
Joined
May 19, 2016
Messages
146
Hmm i didnt check that but the Spell works perfectly fine. but if i walk my Unit to the Top of the Large Map the Spell doesnt Work anymore. He just does the Animaton and effect but not the forward Dash anymore.
 
Level 39
Joined
Feb 27, 2007
Messages
5,010
You’re going to have to post an example map. There’s no reason that should be happening with the code in this spell. It uses an item to check pathability which is the only time the spell can end early. You are probably trying to cast the spell into an unpathable area.
 
Level 8
Joined
May 19, 2016
Messages
146
Ok here. I put the Spell in this Test Map and the Spell Works in the Heroes Area. but if you go to the Right Goldmine the Spell stops ''mid-air'' and at a specific point on the Map the Spell wont work anymore. If you go back to the Heroes Start Area it works again.
(I renamed the Sounds)
 

Attachments

  • Test_Punitive_Surge.w3m
    258.3 KB · Views: 6
Level 39
Joined
Feb 27, 2007
Messages
5,010
The item pathability check is wonky. The OP checked to make sure that the item coordinates and the 'anticipated' coordinates were exactly equal. Checking if two reals are exactly equal is a terrible idea because they almost never are. In this case, every step of the way the item x/y position was 0.001 to 0.01 away from the target coordinates, which was causing the spell to end early. Why only those parts of the map caused the position to vary by that amount? No idea. There's other stuff that I would fix in this resource but it's not likely ever to be updated so whatever. Just do this:

JASS:
//in the function casterLoop change this...
if r7 == r5 and r8 == r6 then

//to this...
if (RAbsBJ(r7-r5) < 0.01 and RAbsBJ(r8-r6) < 0.01) then
 
Top