Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
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 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
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 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.
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...
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
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
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.
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().
try to Cache the Triggering Player inside the private struct Surf
like this player owner
and set it inside the private function Actions takes nothing returns nothing
like this set data.owner = GetTriggerPlayer()
and also fix the code's indentation using Script Language Aligner by The_Witcher
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
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 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.
The struct don't have to extend array because we use the create method !!
That's a bad point of view !
You can use it to index a static members for example.
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.