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

[v]Jass - Poly Slash v0.2

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
skill-2.gif
Poly Slash
Description
The hero divides up into several copies of himself, which will dash towards any enemy in the selected area to deliver a furious strike. Each enemy will only be striked once.


Level 1
Level 2Level 3

Damage: 70
Damage: 110Damage: 150

Copies: 3
Copies: 4Copies: 5

Area of Effect: 250
Area of Effect: 350Area of Effect: 450

This spell requires Jass New Gen Pack. Download it from here. Credit is given to Risin_Dusk for Group Utils.

Spell Code:
JASS:
library PolySlash initializer Init requires TNTK, GroupUtils

    globals
    
        private constant integer SID = 'pLsL'
        //The rawcode of the ability
         
        private constant real TRAVEL_SPEED = 8.43
        //The movement speed of the "mirror images"
        
        private constant real MOVE_ANIMATION_TIME = 1.133
        //The duration of the animation which a mirror image performes
        //while it is moving around
        
        private constant real MOVE_ANIMATION_TIME_MOD = 1.5
        //The animation de- or acceleration. Having a value of 1.0 means
        //that the animation is played with regualr speed, a value of 2.0
        //means the animation is played twice as fast as normal
        
        private constant boolean FORCE_REPICK = true
        //When the mirror images are reuniting back into the caster, it
        //will be automatically picked for the controlling player
        //if set to true
                        
        private constant string MOVE_ANIMATION_NAME = "Attack Slam"
        //The name of the animation a mirror image performs while moving around
        
        private constant string CASTER_SFX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
        //The path of a special effect which is being attached to the mirror images
        
        private constant string TARGET_SFX = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
        //The path of a special effect which is being displayed when a target unit gets hit
        
        private constant string CASTER_SFX_ATTACHMENT = "weapon"
        //Where should the special effect be attached to the mirror image
        
        private constant string TARGET_SFX_ATTACHMENT = "origin"
        //Where should the special effect be attached to the target
        
        //Selfexplaining damage options...
        private constant attacktype ATTACK = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WEAPON = null
        
    endglobals
    
    //The amount of damage depending of the ability level being inflicted to a target when hit 
    private constant function GetDamage takes real level returns real
        return 40 * level + 30
    endfunction
    
    //The amount of mirror images spawned per level
    private constant function GetCopies takes integer level returns integer
        return 1 * level + 2
    endfunction 
    
    //The radius of the Area of Effect
    private constant function GetAoERange takes real level returns real
        return 100 * level + 150
    endfunction
        
    //_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
    //Spellcode begins here. PLEASE DO NOT TOUCH ! Thank you !
    //_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
        
    //Forward struct declaration
    private keyword polyslash_data
        
    globals
        private filterfunc ENUM_FILTER //Used to acquire valid targets
        private player     TMP_PLAYER  //Used as paramter substitue
        private group      TMP_GROUP   //Used filter invalid units off the selection
        private constant real TM_RATIO = MOVE_ANIMATION_TIME / MOVE_ANIMATION_TIME_MOD
                                       //Ratio between speed modifier an animation time
        private integer    TMP_COUNTER //Used to count the number of targets
    endglobals

    //Customized knockback for this spell
    private struct polyslash_move extends TNTKnock
    
        effect sfx
        real   anim_time = .0
        
        method operator psd takes nothing returns polyslash_data
            return polyslash_data(.data)
        endmethod
        
        method operator psd= takes polyslash_data psd returns nothing
            set .data = integer(psd)
        endmethod
        
        //Responsible for movement and animations
        method onKnock takes nothing returns nothing
            
            call SetUnitX( .knocker, .x )
            call SetUnitY( .knocker, .y )
            call SetUnitFacing( .knocker, .radiants*bj_RADTODEG )
            
            if anim_time <= .0 then
                call SetUnitAnimation( .knocker, MOVE_ANIMATION_NAME )
                set .anim_time = TM_RATIO
            else
                set .anim_time = .anim_time - .runtime
            endif

            
        endmethod
                
        //Manages the target selection and ends a knockback
        method onBreak takes nothing returns boolean
                    
            //If true a mirror image reached its target unit
            if super.onBreak() then
            
                //If the caster was the target this instance can be destroyed
                if .target == .psd.caster then
                    return true
                endif
            
                //Damage the current target and play the effect
                call UnitDamageTarget( .psd.caster, .target, .psd.damage, false, false, ATTACK, DAMAGE, WEAPON )
                call DestroyEffect( AddSpecialEffectTarget( TARGET_SFX, .target, TARGET_SFX_ATTACHMENT ))
                    
                //Leave the old target behind ang acquire a new one if possible
                if .psd.target_count > 0 then
                    call .psd.UpdateGroupState()
                    set .target = FirstOfGroup(.psd.targets)
                    call GroupRemoveUnit(.psd.targets, .target )
                    set .psd.target_count = .psd.target_count -1
                else
                    //If all targets recieved their damage return to the caster
                    set .target = .psd.caster
                endif
            
            endif
            
            //The mirror image is still on its way
            return false
            
        endmethod
        
        //Customized knockback dstructor method
        method onDestroy takes nothing returns nothing
        
            //Clean up stuff
            call RemoveUnit(.knocker)
            call DestroyEffect(.sfx)
            call SetUnitVertexColor( .psd.caster, 255, 255, 255, 255 / .psd.copy_count )
        
            //If this is the first unit which should return to the caster then we must
            //unhide the caster again
            if .psd.target_count == 0 then
                call ShowUnit( .psd.caster, true )
                static if FORCE_REPICK then 
                    if GetLocalPlayer() == GetOwningPlayer(.psd.caster) then
                        call SelectUnit( .psd.caster, true )
                    endif
                endif
            endif
        
            //If there are no mirror images left we can destroy the remaining instances
            set .psd.copy_count = .psd.copy_count -1
            if .psd.copy_count == 0 then
                call .psd.destroy()
            endif
        
        endmethod
        
    endstruct
    
    //Sorta managing struct
    private struct polyslash_data
    
        unit caster
        group targets
        
        integer target_count 
        integer copy_count
        real damage
        
        method onDestroy takes nothing returns nothing
            call ReleaseGroup(.targets)
            //call ShowUnit(.caster, true)
        endmethod
        
        //Loops through the selection and removes invalid units
        static method UpdateGroupState_enum takes nothing returns nothing
            
            local unit u = GetEnumUnit()
            
            if (u == null) or (GetWidgetLife(u) < 0.405) then
                call GroupRemoveUnit( TMP_GROUP, u )
            else
                set TMP_COUNTER = TMP_COUNTER +1
            endif
            
            set u = null
            
        endmethod
                
        //Starts the check loop
        method UpdateGroupState takes nothing returns nothing
            set TMP_COUNTER = 0
            set TMP_GROUP = .targets
            call ForGroup(.targets, function thistype.UpdateGroupState_enum )
            set .target_count = TMP_COUNTER
        endmethod
        
        //Creates and configures a mirror image
        method setup_copy takes real x, real y, unit t returns nothing
            
            local unit u = CreateUnit( Player(13), GetUnitTypeId(.caster), x, y, 0. )
            local polyslash_move psm = polyslash_move.create()
            //Create the unit and its knockback instance
            
            set psm.psd = this
            set psm.sfx = AddSpecialEffectTarget( CASTER_SFX, u, CASTER_SFX_ATTACHMENT )
            call psm.StartHomingTimed(u, t, x, y, TRAVEL_SPEED )
            //Setup the knockback
                
            call UnitAddAbility( u, 'Aloc' )
            call SetUnitTimeScale( u, MOVE_ANIMATION_TIME_MOD )
            call SetUnitColor( u, GetPlayerColor( TMP_PLAYER ))
            call SetUnitVertexColor( u, 255, 255, 255, 50 + 255 / .copy_count )
            call UnitShareVision( u, TMP_PLAYER , true )
            
            //Setup the unit
                        
        endmethod
        
        //Setup for the spell
        static method create takes unit ca, real x, real y returns thistype
        
            local thistype this = thistype.allocate()
            local unit t
            local integer i = GetUnitAbilityLevel(ca, SID)
            local real cx = GetUnitX(ca)
            local real cy = GetUnitY(ca)
                        
            set .caster = ca
            set .targets = NewGroup()
            set .damage = GetDamage(i)
            set .copy_count = GetCopies(i)
                        
            set TMP_PLAYER = GetOwningPlayer(ca)
            set TMP_COUNTER = 0
            call GroupEnumUnitsInArea( .targets, x, y, GetAoERange(i), ENUM_FILTER )
            call GroupRemoveUnit( .targets, .caster )
            set .target_count = TMP_COUNTER
            //Acquire target selection, level depeing stuff, coords, etc...
            
            call SetUnitX( ca, x )
            call SetUnitY( ca, y )
            call ShowUnit( ca, false )
            //Setup the caster
            
            //In case there is no valid target we will just send a mirror image to the
            //target location
            if .target_count == 0 then
            
                set .copy_count = 1
                call .setup_copy( cx, cy, ca )

            else
            
                //In case there would be more mirror images than targets
                //we will just leave out the obsolete ones
                if .copy_count > .target_count then
                    set .copy_count = .target_count
                endif
                                    
                //Creates the required mirror images
                set i = 0
                loop
                    exitwhen i == .copy_count                    
                    call .UpdateGroupState()
                    set t = FirstOfGroup( .targets )
                    call GroupRemoveUnit( .targets, t)
                    call .setup_copy( cx, cy, t )
                    set i = i+1
                endloop
                call .UpdateGroupState()
            
            endif
                                   
            set t = null
            return this
            
        endmethod
    
    endstruct
    
    //Filter which specifies if a unit is a target or not
    private function TargetFilter takes nothing returns boolean
    
        local unit u = GetFilterUnit()
        
        if IsUnitEnemy(u,TMP_PLAYER) and not (      /*
      */   GetWidgetLife(u) < 0.405              or /*
      */   IsUnitType(u, UNIT_TYPE_STRUCTURE)    or /*
      */   IsUnitType(u, UNIT_TYPE_FLYING)       or /*
      */   IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) or /*
      */   IsUnitType(u, UNIT_TYPE_ANCIENT)      or /*
      */   IsUnitInvisible(u, TMP_PLAYER)        )  /*
      */then
            set TMP_COUNTER = TMP_COUNTER +1
            set u = null
            return true
        endif
      
        set u = null
        return true
    
    endfunction
    
    //Launches the spell
    private function onCast takes nothing returns boolean
        if GetSpellAbilityId() == SID then
            call polyslash_data.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY() )
        endif
        return false
    endfunction
    
    //Initializer....
    private function Init takes nothing returns nothing
    
        local trigger trig = CreateTrigger()
    
        set ENUM_FILTER = Filter( function TargetFilter )
        call Preload(CASTER_SFX)
        call Preload(TARGET_SFX)
        call PreloadStart()
        
        call TriggerAddCondition( trig, Filter( function onCast ) )
        call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    
    endfunction
    
endlibrary

VersionChanges

v 0.1
-Initial Release

v 0.2
  • Adjusted the Damage settings.
  • Removed the obsolete onDestroy from polyslash_data
  • Replaced IsUnitType() with GetWidgetLife()
  • Replaced EVENT_UNIT_SPELL_CAST with EVENT_UNIT_SPELL_EFFECT
  • Replaced TriggerAction with TriggerCondition.

Keywords:
Poly, Slash, Sword, Sowrdmaster, mirror image, Polyslash, jump, moving
Contents

Poly Slash Test Enviorment (Map)

Reviews
9th Nov 2011 Bribe: It's just another knockback. There are hundreds of these. Yours uses lots of function interfaces, bad API and for what benefit over the others? Your "onLoop" method is also called via function interface making this much slower...

Moderator

M

Moderator

9th Nov 2011
Bribe: It's just another knockback. There are hundreds of these. Yours uses lots of function interfaces, bad API and for what benefit over the others?

Your "onLoop" method is also called via function interface making this much slower and bulkier than it needs to be.

Rising_Dusk's knockback is better and I consider this redundant in its favor.
 
Reading the code.
First of all, good job on using a library ;)

edit

Uhm..

JASS:
private constant attacktype ATTACK = null
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON = WEAPON_TYPE_WHOKNOWS

->

JASS:
private constant attacktype ATTACK = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON = null


Another thing.. don't use onDestroy..

Also, to check if a unit is alive, you could simply use this: GetWidgetLife(u)>=0.405

Finally, don't use TriggerAddAction because TriggerAddCondition is better and faster :)
You just have to return false at the end ^^

Oh and:

call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_CAST )
->
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )

Also, when it comes to locals, try to make the names as short as possible for efficieny :D
 
Level 9
Joined
Aug 2, 2008
Messages
219
Thanks for your hints Magtheridon96 :)

Regarding the damage options my memory didnt serve me very well, i might change that later.

Since i'm trying to exploit OOP i need the onDestroy in the polyslash_move struct but i might get rid of the onDestroy in polyslash_data.

If i recall correctly there was once an issue with GetWidgetLife(u)>=0.405 and thats why i prefer GetUnitState(). And i think technically there is not much different about those two natives.

In future i will prefer TriggerConditions over TriggerActions :)

Is there a big difference between EVENT_PLAYER_UNIT_SPELL_CAST and EVENT_PLAYER_UNIT_SPELL_EFFECT ? I will change it anyway.

Im going to update this as soon as a mod reviewd this.
 
Level 17
Joined
Mar 17, 2009
Messages
1,349
Nice to see you still active Thanathos :)
& I see your back baassee ;)

Yeah the GetWidgetLife is preferable although GetUnitState would work... it's just better coding (speedwise it BARELY differs, but we tend to squeeze as much speed as possible :p). But in my opinion, not much of a big deal.

But the spell cast is an issue you need to fix ;)

EDIT:
& in my opinion, start fixing before the mod checks it out... why wait? :p
 
Last edited:
Level 9
Joined
Aug 2, 2008
Messages
219
Updated the spell to v0.2

Fixed all issues mentioned by Magtheridon96, except the onDestroy problem (For more information see changelog).
I've tried to do it the way Luorax said, but i somehow got malfunctions and errors, which is probably because of the interface or the multiple struct inheritations. However i will repair this as fast as possible.

Further thanks and rep+ to you all for your hints :)

And its also nice to see you again seed :)
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
I've tried to do it the way Luorax said, but i somehow got malfunctions and errors, which is probably because of the interface or the multiple struct inheritations. However i will repair this as fast as possible.

Exactly that's what causes errors :D I'll check the code again when the TSM vs Fnatic match ends and will come back with some suggestions.

EDIT: okay, so that's how I'd do this:

1, Add this to the basic interface (Ext): method doDestroy takes nothing returns nothing defaults nothing

2, rename onDestroy() in struct "Indexable" to destroy, call this.doDestroy() and then call this.deallocate()

3, rename onDestroy() in struct "TNTKnock" and in the move struct to doDestroy()

4, call super.doDestroy() at the end of the doDestroy() method in struct move.

I think that's all.

EDIT2: however I just realised that the "doDestroy" method in TNTKnockback is useless. You're initializing those members in the create method(s) and nulling them is useless. You should simply remove that method and you don't have to call super.doDestroy() in your move struct.
And a hotkey would be fine :p Like E or R
 
Last edited:
Level 9
Joined
Aug 2, 2008
Messages
219
Well that would do it but its kinda pointless.

The idea of replacing onDestroy with destroy and calling deallocate instead is to boost the performance by getting rid of the trigger mechanism introduced Vex to manage the destruction of a struct instance. If this works successfully you avoid the slower triggers and have just some nested function calls instead.

If i now would use an interface method for destroying my structs JassHelper will add an trigger mechanism (similar to that one introduced by Vex) that takes care of the calls of that certain method. So i would just change the names of the methods while the desired performance boost will simply not occur.

Further i think that Vex's built in destructor would be still more efficient than a custom mechanism, since Vex's destructor is optimized while the custom mechanism i just an "all purpose" interface method.

All i know is that your way would be at least that efficient than Vex's method, to make a definite statement i need to compare the compiled version of your mechanism and Vex's destructor.

Besides, have you ever seen a vJass script which has been turned into normal jass by JassHelper ? It looks horrible xD
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
I did not know that interfaces use triggers :p But you know, we learn something every day :D (However I should have known, it's just simple logic)
But there's still no need for the Knockback onDestroy method.
Yea, vJASS looks horrible in normal JASS. However array structs are okayish.
 
Top