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

Surf and Condense v1.07

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: JesusHipster
Contains 2 spells, Surf and Condense. They are two spells from the Naga Overseer hero in my map, Enfo's TS: FFB. As such, I have decided to upload the model for the hero too, since these spells were designed with this model/hero in mind.

Requirements:
JNGP
KBS by kenny(included in demo map)
TimerUtils by Vexorian(included in demo map)

Optional Requirements:
AIDS by Jesus4Lyf
KT2 by Jesus4Lyf
MSX by Jesus4Lyf

Spell Descriptions:
Ability type: Castable.
Can target: Self, enemies.
Effect: The hero rides a giant wave, gaining 1022 ms and being able to knockback enemies close to him. While surfing, the hero leaves a trail of water behind him, that slows and damages nearby enemies.

Many don't realize it, but being a Naga Overseer is a very stressful position. Sometimes, Nasatar really needs a vacation. And what better way to relax than riding a huge wave that drowns your enemies, while you take a nap?

Increases with level: Duration, Damage, Slow Amount, Mana cost.
Decreases with level: None.


Ability type: Passive.
Can target: Enemies.
Effect: Summons a Water Orb on spellcasts.

Whenever the Overseer casts a spell, he condenses the excess moisture to create an orb of water that orbits around him, dealing damage to enemies and amplifying magic damage done to them by 25%(This effect does not stack). Units can only be hit once per orb.

Damage: Level of ability cast*Intelligence
Number of revolutions around hero: Level of ability cast


Triggers
[jass=Surf]scope SurfS initializer OnInit
//Surf by Strikest
//Requires TimerUtils by Vexorian(http://www.wc3c.net/showthread.php?t=101322), KBS by kenny(http://www.thehelper.net/threads/knockback-system-kbs.96837/)
//Optionally uses AIDS,KT, and MSX by Jesus4Lyf(http://www.thehelper.net/threads/movespeedx.119384/)


globals
private constant integer ABIL_ID = 'A002' //Raw code of ability.
private constant integer UNIT_ID = 'n000' //Raw code of the puddle dummy unit.
private constant integer MOVE_ID = 'A003' //Raw code of the movement speed bonus ability.
private constant integer SLOW_ID = 'S000' //Raw code of the slow aura ability.
private constant integer DMG_ID = 'A001' //Raw code of the damage over time ability.
private constant integer ORDER_ID = 852177 //Order ID of the damage over time ability.
private constant string EFFECT = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveMissile.mdl" //Path of the desired special effect you want attached to the caster.
private constant string ATTACH = "origin" //String of the desired attachment point for the caster.
private constant string KB_SFX = "war3mapImported\\SlideWater.mdx" //Path of the desired special effect you want attached to knocked back units.
private constant real KB_AOE = 170. //Area of effect around caster that units will be knocked back.
private constant real KB_DISTANCE = 124. //Distance the enemy will be knocked back.
private constant real MINIMUM_DISTANCE = 30. //Minimum distance required for another puddle to spawn.
private constant real TIMER_INTERVAL = 50. //Timer interval.
private constant real KB_DURATION = 0.6 //Duration of knockback.
private constant boolean ALLOW_NO_COLLISION = false //Choose whether or not you want to turn collision off for the caster.



private constant real TREE_AOE = 0.0
private constant boolean ALLOW_MOVE = false
private constant boolean CHECK_PATHING = false
//Copied from kenny's KBS trigger
//** - TREE_AOE - This determines whether or not trees will be knocked down. **
//** For trees to be knocked down, a positive number (real) must **
//** be used, such as 150.00, which would give a radius of 150.00 **
//** in which trees will be knocked down. **
//** For trees to not be knocked down, a negative number (real) **
//** must be used, such as -150.00, which would create a radius **
//** that if a tree comes within it, the unit will stop moving. **
//** For none of those effects, the number 0 (0.00) can be used. **
//** This will just cause the units to "bounce" off trees.
//** -ALLOW_MOVE - This boolean will decided whether or not you want the unit **
//** to have the ability to move while being knocked back. **
//** "true" will allow them to move, while "false" will not. **
//** -CHECK_PATHING - A boolean that, if true, will check for unpathable terrain **
//** such as a wall or cliff, or where doodads may be. If false **
//** it will ignore these changes and the unit will be pushed **
//** along the wall, cliff or doodad.

private constant group GROUP = CreateGroup( )//Don't touch.
private unit CASTER //Don't touch this either.
endglobals

private constant function DURATION takes integer level returns integer
return 12 + level * 2
endfunction

private constant function BONUS_SPEED takes integer level returns real //Amount of speed gained through MSX trigger. Requires MSX. Be careful with this, if the value is too high, it might bug.
return 500.
endfunction

private constant function FILTER_UNITS takes unit u returns boolean //Filter which units will get knocked back.
return IsUnitEnemy( u, GetOwningPlayer( CASTER ) )
endfunction
//////////////////////////////////////////////////////NO TOUCHY PAST THIS POINT UNLESS YOU KNOW WHAT YOU ARE DOING/////////////////////////////////////////////////////////////////////////////

private struct Surf
unit cast
real x
real y
integer dur
effect attach
endstruct

native UnitAlive takes unit id returns boolean

private function FilterActions takes nothing returns boolean
local unit u = GetFilterUnit( )
local real x1 = GetUnitX( CASTER )
local real y1 = GetUnitY( CASTER )
local real x2 = GetUnitX( u )
local real y2 = GetUnitY( u )
local real a = bj_RADTODEG * Atan2( y2 - y1, x2 - x1 )

if UnitAlive(u) then
if FILTER_UNITS(u) then
call KBS_BeginEx( u, KB_DISTANCE, KB_DURATION, a, KB_SFX, TREE_AOE, ALLOW_MOVE, CHECK_PATHING )
endif
endif
set u = null
return false
endfunction

private function Handler takes nothing returns nothing
local timer t = GetExpiredTimer( )
local Surf data = GetTimerData( t )
local real x1 = data.x
local real y1 = data.y
local real x2 = GetUnitX( data.cast )
local real y2 = GetUnitY( data.cast )
local real d = ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 )
local unit u

if data.dur <= 0 then
call DestroyEffect( data.attach )
if ALLOW_NO_COLLISION then
call SetUnitPathing( data.cast, true )
endif
static if LIBRARY_MSX then
call MSX_RemoveAllSpeed( data.cast )
endif
call UnitRemoveAbility( data.cast, MOVE_ID )
call data.destroy( )
call ReleaseTimer( t )
else
set CASTER = data.cast
call GroupEnumUnitsInRange( GROUP, GetUnitX( data.cast ), GetUnitY( data.cast ), KB_AOE, Filter( function FilterActions ) )
if d >= MINIMUM_DISTANCE * MINIMUM_DISTANCE then
set u = CreateUnit( GetOwningPlayer( data.cast ), UNIT_ID, x2, y2, 0. )
call SetUnitAbilityLevel( u, SLOW_ID, GetUnitAbilityLevel( data.cast, ABIL_ID ) )
call SetUnitAbilityLevel( u, DMG_ID, GetUnitAbilityLevel( data.cast, ABIL_ID ) )
call IssueImmediateOrderById( u, ORDER_ID )
call UnitApplyTimedLife( u, 'BTLF', ( I2R( data.dur ) * .02 ) )
set data.x = x2
set data.y = y2
endif

set data.dur = data.dur - 1
call SetTimerData( t, data )
call TimerStart( t, 1./TIMER_INTERVAL, false, function Handler )
endif

set t = null
set u = null
endfunction


private function Actions takes nothing returns nothing
local Surf data = Surf.create( )

set data.cast = GetTriggerUnit( )
set data.x = GetUnitX( data.cast )
set data.y = GetUnitY( data.cast )
set data.attach = AddSpecialEffectTarget( EFFECT, data.cast, ATTACH )
set data.dur = 50 * DURATION( GetUnitAbilityLevel( data.cast, ABIL_ID ) )

call UnitAddAbility( data.cast, MOVE_ID )

static if LIBRARY_MSX then
call MSX_SetPureSpeed( data.cast, BONUS_SPEED( GetUnitAbilityLevel( data.cast, ABIL_ID ) ) )
endif

if ALLOW_NO_COLLISION then
call SetUnitPathing( data.cast, false )
endif

call TimerStart(NewTimerEx(data), 1/TIMER_INTERVAL, false, function Handler )

endfunction

private function OnSpell takes nothing returns boolean
if GetSpellAbilityId( ) == ABIL_ID then
call Actions( )
endif
return false
endfunction

private function OnInit takes nothing returns nothing
local trigger trig = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( trig, Condition( function OnSpell ) )
set trig = null
endfunction

endscope[/code]


[jass=Condense]scope Condense initializer OnInit
//Condense by Strikest
//Requires TimerUtils by Vexorian(http://www.wc3c.net/showthread.php?t=101322)
globals
//Configurables
private constant integer ABIL_ID = 'A000' //Raw code of ability
private constant integer DBFF_ID = 'A004' //Raw code of magic amplification ability
private constant integer ORB_ID = 'h001' //Raw code of the water orb dummy
private constant integer CROW_ID = 'Amrf' //Raw code of Crow Form ability in your map. Should be 'Amrf', unless you modified it.
private constant string EFFECT = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" //Path of the special effect model when orb makes contact
private constant string ATTACH_POINT = "origin" //String of the attachment point you want
private constant boolean BOOLEAN_INCLUDE_GREEN_INT = true //Choose whether or not to include green int bonuses in damage calculations( If factoring int ).
private constant real AOE = 116. //Area of effect of the orb
private constant real ORB_DISTANCE = 400. //Distance of orb away from caster.
private constant real ROTATION_SPEED = .03125 //Lower number = faster orb rotation, and vice versa.

private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL //Attacktype you want
private constant damagetype DMG_TYPE = DAMAGE_TYPE_NORMAL //Damagetype you want

private constant group GROUP = CreateGroup( )//Dont touch this
private group GROUP2 //Or this
private unit CASTER //Or this
private integer CAST_LVL //Nor this
endglobals

private constant function DAMAGE takes integer levelofpassive, integer levelofcastspell, integer heroint returns real //Configure damage in this function
return levelofcastspell * heroint + 0.
endfunction

private constant function AMOUNT_OF_REVOLUTIONS takes integer levelofpassive, integer levelofcastspell returns integer //Configure how many times the orb will make a complete revolution around the caster
return levelofcastspell
endfunction

private constant function DURATION_NORMAL takes integer levelofpassive, integer levelofcastspell, integer heroint returns real //Configure the duration of the debuff for non - hero units
return 10.
endfunction

private constant function DURATION_HERO takes integer levelofpassive, integer levelofcastspell, integer heroint returns real //Configure the duration of the debuff for hero units
return 5.
endfunction

private constant function FILTER_UNITS takes unit u returns boolean //Filter which units will get affected by the orb.
return IsUnitEnemy( u, GetOwningPlayer( CASTER ) ) and not IsUnitInGroup( u, GROUP2 ) and not IsUnitType( u, UNIT_TYPE_MAGIC_IMMUNE )
endfunction

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~NO TOUCHY PAST THIS POINT UNLESS YOU KNOW WHAT YOU ARE DOING~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

private struct Cdnse
unit cast
unit orb
integer lvl
integer dur
real a
group g

static method create takes nothing returns Cdnse
local Cdnse data = Cdnse.allocate( )
if data.g == null then
set data.g = CreateGroup( )
endif
return data
endmethod

method onDestroy takes nothing returns nothing
call GroupClear( .g )
endmethod
endstruct

private struct Dbuff
unit u = null
endstruct

native UnitAlive takes unit id returns boolean

private function Handler2 takes nothing returns nothing
local timer t = GetExpiredTimer( )
local Dbuff data = GetTimerData( t )

call UnitRemoveAbility( data.u, DBFF_ID )

call data.destroy( )
call ReleaseTimer( t )

set t = null
endfunction

private function FilterActions takes nothing returns boolean
local unit u = GetFilterUnit( )
local integer i = GetUnitAbilityLevel( CASTER, ABIL_ID )
local integer int = GetHeroInt( CASTER, BOOLEAN_INCLUDE_GREEN_INT )
local real damage = DAMAGE( i, CAST_LVL, int )
local Dbuff data
local timer t

if UnitAlive(u) then
if FILTER_UNITS(u) then
call UnitDamageTarget( CASTER, u, damage, false, false, ATK_TYPE, DMG_TYPE, WEAPON_TYPE_WHOKNOWS )
call GroupAddUnit( GROUP2, u )
call DestroyEffect( AddSpecialEffectTarget( EFFECT, u, ATTACH_POINT ) )
//If you are capable enough, you can add any extra effects you want here
if GetUnitAbilityLevel( u, DBFF_ID ) != 1 then
call UnitAddAbility( u, DBFF_ID )
set data = Dbuff.create( )
set data.u = u
set t = NewTimer( )
call SetTimerData( t, data )
if IsUnitType( u, UNIT_TYPE_HERO ) then
call TimerStart( t, DURATION_HERO( i, CAST_LVL, int ), false, function Handler2 )
else
call TimerStart( t, DURATION_NORMAL( i, CAST_LVL, int ), false, function Handler2 )
endif
endif
endif
endif
set u = null
set t = null
return false
endfunction

private function Handler takes nothing returns nothing
local timer t = GetExpiredTimer( )
local Cdnse data = GetTimerData( t )
local real x
local real y
if data.dur <= 0 then
call RemoveUnit( data.orb )
call data.destroy( )
call ReleaseTimer( t )
else
set data.a = data.a + 4.
set x = GetUnitX( data.cast ) + 400. * Cos( data.a * bj_DEGTORAD )
set y = GetUnitY( data.cast ) + 400. * Sin( data.a * bj_DEGTORAD )
call SetUnitX( data.orb, x )
call SetUnitY( data.orb, y )
if GetUnitFlyHeight( data.cast ) > 0 then
call SetUnitFlyHeight( data.orb, GetUnitFlyHeight( data.cast ), 0 )
endif
set CASTER = data.cast
set CAST_LVL = data.lvl
set GROUP2 = data.g
call GroupEnumUnitsInRange( GROUP, x, y, AOE, Filter( function FilterActions ) )
set data.dur = data.dur - 4
call SetTimerData( t, data )
call TimerStart( t, ROTATION_SPEED, false, function Handler )
endif

set t = null
endfunction

private function Actions takes nothing returns nothing
local unit cast = GetTriggerUnit( )
local real casx = GetUnitX( cast )
local real casy = GetUnitY( cast )
local real a = GetRandomReal( 0., 360. ) + GetUnitFacing( cast )
local real tarx
local real tary
local timer t = NewTimer( )
local Cdnse data = Cdnse.create( )

set tarx = casx + ORB_DISTANCE * Cos( a * bj_DEGTORAD )
set tary = casy + ORB_DISTANCE * Sin( a * bj_DEGTORAD )
set data.cast = cast
set data.lvl = GetUnitAbilityLevel( cast, GetSpellAbilityId( ) )
set data.dur = 360 * AMOUNT_OF_REVOLUTIONS( GetUnitAbilityLevel( cast, ABIL_ID ), data.lvl )
set data.a = a
set data.orb = CreateUnit( GetOwningPlayer( cast ), ORB_ID, tarx, tary, 0.0 )

call SetUnitPathing( data.orb, false )
call UnitAddAbility( data.orb, CROW_ID )

call SetTimerData( t, data )
call TimerStart( t, ROTATION_SPEED, false, function Handler )

set cast = null
set t = null
endfunction

private function OnSpell takes nothing returns boolean
if GetUnitAbilityLevel( GetTriggerUnit( ), ABIL_ID ) != 0 then
call Actions( )
endif
return false
endfunction

private function OnInit takes nothing returns nothing
local trigger trig = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( trig, Condition( function OnSpell ) )
set trig = null
endfunction

endscope[/code]


Credits:
-Grendel for the HeroSealedPython model and its portrait.
DonDustin for the BlueBasiliskMissile.
MajorSonnWaitts for the Bubbles model.
Frankster for the OrbWaterX model.
kenny for rhe KBS system, Dust and SlideWater models.
JesusHipster for rhe WaterBlast model.
Vexorian for vJass and TimerUtils.
Jesus4Lyf for AIDS, KT, and MSX
PeeKay for the Condense icon.
67chrome for the Hero icon.

Every thing is coded in vJass, and is MUI, leakless and lagless(hopefully). Follows the JESP standard. Importing instructions included in testmap.

v1.01 & v1.02:
Fixed up the indenting a little.
Cached GetFilterUnit() and removed use of the SquareRoot native.(tnx Radamantus)
v1.03:
Optimized the code as per bowser499's suggestions
v1.04:
Made Condense not affect Magic Immune units.
Added new configurable to Condense: the duration is now editable.
v1.05:
Made changes that Magtheridon96 outlined.
v1.06: Made changes BPower outlined.
v1.07: Made changes Malhorne outlined.


Keywords:
Water, Orb, Naga, Ice, Aqua, Blue, Liquid, Surf, Fast, Wave, Orbit
Contents

Surf and Condense by Strikest (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 00:40 24.01.2014 BPower: Currently set to "Need Fix", because there are too many unsolved issues. You can find the new review here...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.

00:40 24.01.2014
BPower:
Currently set to "Need Fix", because there are too many unsolved issues. You can find the new review here:
http://www.hiveworkshop.com/forums/spells-569/surf-condense-v1-07-a-227652/index2.html#post2477070


00:45, 22nd Jan 2013
Magtheridon96:

Surf

  • You can get rid of 3 lines of code by changing the NewTimer()/SetTimerData(t,data)/set t = null chain into a single call to NewTimerEx(data).
  • Some of the indentation is messed up in places :/ (Example: All the code inside the Filter/Handler functions and the struct members.)
  • Spaces between function arguments bro, spaces :v
  • You can optionally change the method of the group enumeration to a FirstOfGroup loop for readability. I think it makes the whole concept of picking units and iterating over them more clear for beginners when reading your code. This is totally optional though.
  • The order id should probably be configurable in the globals block.
  • Timer interval should be configurable. The 50 value you have in there would then be (1./INTERVAL)
  • Although we can debate for hours on end about how nulling struct members is practically useless, or about how it's a fine practice, it's most definitely not going to hurt anyone. :/
  • .6 -> 0.6 (Neatness. Also, it might as well be configurable along with all your other constants)
  • The 170. range specified in the Handler should also probably be configurable. Currently, this value is practically model-specific, so I think it should be configurable from the globals block or possibly even from a function in case multiple unit types are going to be casting this spell.
  • Could you insert, at the very least, a chunk of documentation at the top of the code that outlines the requirements and links to them?
  • You could make the unit filtering condition inside the FilterActions function configurable from the top of the script. It would pretty much just be a function that takes a unit and some other arguments, then returns a boolean after evaluating that unit. This enhances the flexibility of the spell in the case where you have users who are actually going to listen to you and not touch the code under that magical comment barrier you placed :p
  • You can merge OnSpell and Actions if you wish to.

Condense

  • Indentation inside the structs is messed up.
  • Nulling the cast and orb members of the Cdnse struct wouldn't hurt :c
  • I see what you did there with the group g inside the Cdnse struct. I like it :)
  • Indentation in general is all messed up. (After a function declaration, you should indent)
  • WEAPON_TYPE_WHOKNOWS == null
  • As I said earlier, the FirstOfGroup loop for enumeration is cool, but totally optional.
  • Also as I said earlier, you could make the unit filters configurable from the top of the code.
  • NewTimerEx(data) could also be used inside the FilterActions function and the Actions function.
  • There's one thing I don't understand about most people and that is the usage of the period 0.0325 as opposed to 0.03125. Could you explain it to me? I never really got it, and I never asked before. Sometimes, I would assume it's a typo or a miscommunication :/
  • Since you're modifying the angle a manually through addition and converting it to radians, I think working with radians directly shouldn't be too much of a hassle. I also think that the rotational speed should be configurable.
  • The 400 should probably be configurable as well.
  • You can merge OnSpell and Actions if you wish to.
  • A chunk of documentation outlining the requirements would also be nice here.
 
The code indention is a bit messy.
In globals and functions , you have 5 spaces per block w/ should be 4
you dont have any indention in the struct and function
cache the GetFilterUnit() so it will be faster
local real d = SquareRoot((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
Remove the square root,then square the required distance
and lot more needed changes

Also why AIDS?you can use Unit Indexer by Nestharus
 
In globals and functions , you have 5 spaces per block w/ should be 4
... No comment.

cache the GetFilterUnit() so it will be faster
alright done.

Remove the square root,then square the required distance
Could you elaborate more please?
EDIT: Ah nvm I get it. Done.

Also why AIDS?you can use Unit Indexer by Nestharus
I like AIDS better. Because Damage is awesome.
 
1. Use IssueImmediateOrderById instead of IssueImmediateOrder.
2. Null the local trigger trig inside the function OnInit.
3. Re-code the Filter(function FilterActions), make a FirstOfGroup loop instead. It is a lot more faster. The proof.
4. Don't use call ReleaseTimer(GetExpiredTimer()) when you have local timer t = GetExpiredTimer(). Use it, make that string look like that: call ReleaseTimer(t).
5. IsUnitEnemy(u,GetOwningPlayer(CASTER)) == true - no need to write == true.

4/5 for now.
 
1. Use IssueImmediateOrderById instead of IssueImmediateOrder .
Done.

2. Null the local trigger trig inside the function OnInit .
I don't think that really does anything at all, but it doesn't hurt so, done.

3. Re-code the Filter(function FilterActions) , make a FirstOfGroup loop instead.
I like Filter(function FilterActions) better, mostly because I feel it's easier to understand for people. Plus I doubt the speed difference is enough to make it worth it to recode.

4. Don't use call ReleaseTimer(GetExpiredTimer()) when you have local timer t = GetExpiredTimer().
Done.

5. IsUnitEnemy(u,GetOwningPlayer(CASTER)) == true - no need to write == true .
Done.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Please take this into consideration JPAG.

For now I just went quickly through Surf.
- Use call TimerStart(NewTimerEx(data), .02, false function Handler) an advice mag already gave you 1 year ago. Also 0.02 should be set to a variable (TIMER_INTERVAL)
- I would recommend a FirstOfGroup loop. It has better read-ability and is faster the bigger the picked number of unit is.
If you want stick to your way:

Hu?? Put unit u = GetFilterUnit() to the top and use it.
JASS:
        local real x2 = GetUnitX( GetFilterUnit( ) )
        local real y2 = GetUnitY( GetFilterUnit( ) )
        local real a = bj_RADTODEG * Atan2( y2 - y1, x2 - x1 )
        local unit u = GetFilterUnit( )
- Please null struct members in the end.
- private constant integer ORDER_ID = 852177 //Order ID of ability. Which ablilty? Make sure your comments provide a context.
- Use one periodic timer instead of re-starting it every 0.02 seconds.
- TIMER_INTERVAL should be set to 0.02 and not 50.
- use a player variable as member of the struct --> data.owner = GetTriggerPlayer() to get rid of tons of unnessesary GetOwningPlayer() calls.

Once major flaws are corrected we will also come to stuff like:
- if ALLOW_NO_COLLISION then --> static if ALLOW_NO_COLLISION then

There is more and we will get to that when I have more time (if you want).
Please also go through all of Mags suggestions again. Some of them are still not covered in the recent version.

Edit: I will give you a small push. (Can contain typos, because I never tried to save it.)
JASS:
    private constant function FilterUnits takes unit u, player p returns boolean
        return IsUnitEnemy(u, p)//More conditions????
    endfunction

    private function Handler takes nothing returns nothing
        local Surf data = GetTimerData(GetExpiredTimer())
        local real x = GetUnitX(data.cast)
        local real y = GetUnitY(data.cast)
        local real angle
        local unit u
        //.......
        if data.dur <= 0 then
            call DestroyEffect(data.attach)
            
            static if LIBRARY_MSX then
                call MSX_RemoveAllSpeed(data.cast)
            endif
            
            static if ALLOW_NO_COLLISION then
                call SetUnitPathing(data.cast, true)
            endif
            
            call UnitRemoveAbility(data.cast, MOVE_ID)
            call data.destroy()
            /*
            *   Null every struct member, which can leak a handle.
            */
            set data.attach = null
            set data.owner  = null
            set data.cast   = null
            call ReleaseTimer(GetExpiredTimer())
        else
            set data.dur = data.dur - 1 
            /*
            *   IsUnitInRangeXY I guess the name is selfexplaining.
            *   data.x and data.y are the center x/y of the last created dummy.
            */
            if not IsUnitInRangeXY(data.cast, data.x, data.y, MINIMUM_DISTANCE) then
                set data.x = x
                set data.y = y
                //set u = Create new dummy
                // ....
            endif
            
            call GroupEnumUnitsInRange(GROUP, x, y, KB_AOE, null)
            loop
                set u = FirstOfGroup(GROUP)
                exitwhen u == null
                call GroupRemoveUnit(GROUP, u)
                /*
                *   FilterUnits is what you called FILTER_UNITS.
                */
                if UnitAlive(u) then
                    if FilterUnits(u, data.owner) then
                        set angle = bj_RADTODEG * Atan2(GetUnitY(u) - y, GetUnitX(u) - x)
                        call KBS_BeginEx(u, KB_DISTANCE, KB_DURATION, a, KB_SFX, TREE_AOE, ALLOW_MOVE, CHECK_PATHING)
                    endif
                endif
            endloop
        endif
    endfunction
    
    private function Actions takes nothing returns nothing
        local Surf data = Surf.create()
        local integer level
        set data.cast   = GetTriggerUnit()
        /*
        *   A player variable is very usefull for the
        *   FilterUnits function. You get rid of native GetOwningPlayer()
        */
        set data.owner  = GetTriggerPlayer()
        set data.x      = GetUnitX(data.cast)
        set data.y      = GetUnitY(data.cast)
        set data.attach = AddSpecialEffectTarget(EFFECT, data.cast, ATTACH)
        
        set level = GetUnitAbilityLevel(data.cast, ABIL_ID)
        set data.dur  = GetDuration(level)
        
        call UnitAddAbility(data.cast, MOVE_ID)
        
        static if LIBRARY_MSX then
            call MSX_SetPureSpeed(data.cast, BONUS_SPEED(level)))
        endif
        /*
        *   The JassHelper will ignore this block
        *   if the boolean ALLOW_NO_COLLISION is false.
        */
        static if ALLOW_NO_COLLISION then
            call SetUnitPathing(data.cast, false)
        endif
        /*
        *   NewTimerEx(instance)
        */
        call TimerStart(NewTimerEx(data), TIMER_INTERVAL, true, function Handler)
    endfunction
 
Last edited:
Please take this into consideration JPAG.

meh. too inconsequential

For now I just went quickly through Surf.
- Use call TimerStart(NewTimerEx(data), .02, false function Handler) an advice mag already gave you 1 year ago. Also 0.02 should be set to a variable (TIMER_INTERVAL)

okay

- I would recommend a FirstOfGroup loop. It has better read-ability and is faster the bigger the picked number of unit is.
If you want stick to your way:

Hu?? Put unit u = GetFilterUnit() to the top and use it.
JASS:
        local real x2 = GetUnitX( GetFilterUnit( ) )
        local real y2 = GetUnitY( GetFilterUnit( ) )
        local real a = bj_RADTODEG * Atan2( y2 - y1, x2 - x1 )
        local unit u = GetFilterUnit( )

okay.

- Please null struct members in the end.

where? in the struct code itself? or u mean like, set data.u = null

- private constant integer ORDER_ID = 852177 //Order ID of ability. Which ablilty? Make sure your comments provide a context.

ok

- Use one periodic timer instead of re-starting it every 0.02 seconds.

Periodic timers confuse me, sorry.

- TIMER_INTERVAL should be set to 0.02 and not 50.

? 50 is easier for users.(my code does 1/TIMER_INTERVAL, giving you .02).

- use a player variable as member of the struct --> data.owner = GetTriggerPlayer() to get rid of tons of unnessesary GetOwningPlayer() calls.

I thought it was better to have smaller structs? So only store stuff you need to store?
 
Last edited:
Level 18
Joined
Sep 14, 2012
Messages
3,413
private constant real TIMER_INTERVAL = 50. //Timer interval.
I suggest 32 rather than 50.

JASS:
local real x1 = data.x
        local real y1 = data.y
Just why XD ?

I suggest you to inline the func :
JASS:
private function OnSpell takes nothing returns boolean
        if GetSpellAbilityId( ) == ABIL_ID then
            call Actions( )
        endif
        return false
    endfunction


local integer i = GetUnitAbilityLevel( cast, GetSpellAbilityId( ) )
No use since you'll store it in the struct datas.

JASS:
unit cast = null
        unit orb = null
Why ?
There is exactly the same thing after.
 
private constant real TIMER_INTERVAL = 50. //Timer interval.
I suggest 32 rather than 50.

why?

JASS:
local real x1 = data.x
        local real y1 = data.y
Just why XD ?

What?

I suggest you to inline the func :
JASS:
private function OnSpell takes nothing returns boolean
        if GetSpellAbilityId( ) == ABIL_ID then
            call Actions( )
        endif
        return false
    endfunction

but then i gotta make nulls structs

local integer i = GetUnitAbilityLevel( cast, GetSpellAbilityId( ) )
No use since you'll store it in the struct datas.

ok

JASS:
unit cast = null
        unit orb = null
Why ?
There is exactly the same thing after.

yeah i know i thought thats what the above commenters meant by nulling struct members lol.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
SurfS
  • Please null all struct members which potentially could leak a handle once you deallocate the struct instance --> caster, attach
  • function names should follow the JPAG convention, I will make this clear once and for all. I don't approve your resource aslong as the function names stay as they are now.
  • The timeout should be calculated in the global block or directly set to the desired value. Also the timer should be periodic
    call TimerStart(NewTimerEx(data), 1/TIMER_INTERVAL, false, function Handler )
  • if ALLOW_NO_COLLISION then -->static if ALLOW_NO_COLLISION then
  • 50 * DURATION the 50 has to be represented in the globals in logical context to the timer interval.
  • There is advantage in setting those to locals.
    JASS:
            local real x1 = data.x
            local real y1 = data.y
  • casterX/Y set to x2 y2 use them instead call GroupEnumUnitsInRange( GROUP, GetUnitX( data.cast ), GetUnitY( data.cast ), KB_AOE, Filter( function FilterActions ) )
  • Redundant if you use a periodic timer.
    JASS:
                call SetTimerData( t, data )
                call TimerStart( t, 1./TIMER_INTERVAL, false, function Handler
  • Apart from what I meantioned the coding is on an average level. I recomment to use a FoG loop and add a player member to the struct. OnSpell could be merged with Actions

Condense
  • Move the native UnitAlive above all functions, not every JassHelper allows double defined native, hence users have to find them quickly.
  • JPAG
  • Null struct members.
  • local real tarx/y can be set directly in function Actions
  • Use NewTimerEx(data) instead of SetTimerData
  • The orb unit could have the crow ability added in the object editor
  • Just No! method onDestroy
  • In function Handler x and y should be initialized directly.
  • The rotation radius should be configurable
  • Again Fog and a periodic timer are highly recommended.
  • Overall avarage coding, I saw more tiny things which could be done better. We can discuss them later.
  • static method create takes nothing returns Cdnse, if you write the create method yourself the struct could also exentds array.
 
Last edited:
Top