Name | Type | is_array | initial_value |
//TESH.scrollpos=98
//TESH.alwaysfold=0
scope MapInit initializer init
globals
private hashtable H = InitHashtable()
private unit TESTER_1
private unit TESTER_2
private trigger TRIG
private player P
private integer TEST_ID = 'H001'
endglobals
private function T1_Actions takes nothing returns nothing
if GetWidgetLife(TESTER_1) > .405 then
call SetUnitState(TESTER_1, UNIT_STATE_LIFE, GetUnitState(TESTER_1, UNIT_STATE_MAX_LIFE))
call SetUnitState(TESTER_1, UNIT_STATE_MANA, GetUnitState(TESTER_1, UNIT_STATE_MAX_MANA))
call UnitResetCooldown( TESTER_1 )
call UnitRemoveBuffs(TESTER_1, true, true)
endif
if GetWidgetLife(TESTER_2) > .405 then
call SetUnitState(TESTER_2, UNIT_STATE_LIFE, GetUnitState(TESTER_2, UNIT_STATE_MAX_LIFE))
call SetUnitState(TESTER_2, UNIT_STATE_MANA, GetUnitState(TESTER_2, UNIT_STATE_MAX_MANA))
call UnitResetCooldown( TESTER_2 )
call UnitRemoveBuffs(TESTER_2, true, true)
endif
endfunction
private function T2_Actions takes nothing returns nothing
local string s = GetEventPlayerChatString()
local integer i = S2I(SubString(s, 7, 9))
if i <= 0 then
call SimError(P,"Hero Level cannot goes below 0")
elseif i > 10 then
call SetHeroLevel(TESTER_1,10,true)
call SetHeroLevel(TESTER_2,10,true)
else
call SetHeroLevel(TESTER_1,i,true)
call SetHeroLevel(TESTER_2,i,true)
endif
set s = ""
endfunction
private function T3_Actions takes nothing returns nothing
local unit u = GetDyingUnit()
call ReviveHero(u,GetUnitX(u),GetUnitY(u),true)
set u = null
endfunction
private function register takes nothing returns nothing
local unit u = GetEnumUnit()
call SaveReal(H,GetHandleId(u),1,GetUnitX(u))
call SaveReal(H,GetHandleId(u),2,GetUnitY(u))
call TriggerRegisterUnitEvent(TRIG,u,EVENT_UNIT_DEATH)
set u = null
endfunction
private function revivecreep takes nothing returns nothing
local unit u = GetDyingUnit()
local integer id = GetUnitTypeId(u)
local real x
local real y
if HaveSavedReal(H,GetHandleId(u),1) then
set x = LoadReal(H,GetHandleId(u),1)
set y = LoadReal(H,GetHandleId(u),2)
call FlushChildHashtable(H,GetHandleId(u))
set u = CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE),id,x,y,270)
call SaveReal(H,GetHandleId(u),1,x)
call SaveReal(H,GetHandleId(u),2,y)
call TriggerRegisterUnitEvent(TRIG,u,EVENT_UNIT_DEATH)
endif
set u = null
endfunction
private function creepcheck takes nothing returns boolean
return IsUnitType(GetFilterUnit(),UNIT_TYPE_HERO) == false and IsUnitType(GetFilterUnit(),UNIT_TYPE_SUMMONED) == false
endfunction
private function CreepSpawn takes nothing returns nothing
local group g = CreateGroup()
local boolexpr b = Filter(function creepcheck)
set TRIG = CreateTrigger()
call TriggerAddAction(TRIG,function revivecreep)
call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,b)
call ForGroup(g,function register)
call DestroyGroup(g)
call DestroyBoolExpr(b)
set b = null
set g = null
endfunction
private function init takes nothing returns nothing
local trigger t1 = CreateTrigger()
local trigger t2 = CreateTrigger()
local trigger t3 = CreateTrigger()
call SetGameSpeed( MAP_SPEED_FASTEST )
call SetMapFlag(MAP_LOCK_SPEED, true)
call FogEnable(false)
call FogMaskEnable(false)
set P = Player(0)
set TESTER_1 = CreateUnit( P, TEST_ID, 0, 0, 270 )
set TESTER_2 = CreateUnit( P, TEST_ID, 0, 0, 270 )
call SetHeroLevel( TESTER_1, 3, false )
call SetHeroLevel( TESTER_2, 3, false )
call TriggerRegisterPlayerEvent(t1, P, EVENT_PLAYER_END_CINEMATIC)
call TriggerAddAction( t1, function T1_Actions )
call TriggerRegisterPlayerChatEvent( t2, P, "-level ", false )
call TriggerAddAction( t2, function T2_Actions )
call TriggerRegisterUnitEvent( t3, TESTER_1, EVENT_UNIT_DEATH )
call TriggerRegisterUnitEvent( t3, TESTER_2, EVENT_UNIT_DEATH )
call TriggerAddAction( t3, function T3_Actions )
call ExecuteFunc(SCOPE_PRIVATE+"CreepSpawn")
set t1 = null
set t2 = null
set t3 = null
endfunction
endscope
//TESH.scrollpos=13
//TESH.alwaysfold=0
scope HandleCounter initializer init
globals
private constant real DELAY = 0.5
private constant real INTERVAL= 0.25
private leaderboard LDB
endglobals
private function onUpdate takes nothing returns nothing
local location L = Location(0,0)
call LeaderboardSetItemValue(LDB, LeaderboardGetPlayerIndex(LDB, Player(0)), GetHandleId(L)-0x100000)
call RemoveLocation(L)
set L = null
endfunction
private function start takes nothing returns nothing
local timer t = GetExpiredTimer()
set LDB = CreateLeaderboard()
call LeaderboardSetLabel(LDB,"Handle Counter")
call LeaderboardDisplay(LDB,true)
call PlayerSetLeaderboard(Player(0), LDB)
call LeaderboardAddItem(LDB, "Handle Counter", 0, Player(0))
call LeaderboardResizeBJ(LDB)
call TimerStart(t,INTERVAL,true,function onUpdate)
set t = null
endfunction
private function init takes nothing returns nothing
local timer t = NewTimer()
call TimerStart(t,DELAY,false,function start)
set t = null
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
library SimError initializer init
globals
private sound error
endglobals
function SimError takes player ForPlayer, string msg returns nothing
set msg="\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"+msg+"|r"
if (GetLocalPlayer() == ForPlayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer( ForPlayer, 0.52, 0.96, 2.00, msg )
call StartSound( error )
endif
endfunction
private function init takes nothing returns nothing
set error=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
endfunction
endlibrary
//TESH.scrollpos=162
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=224
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=28
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'h000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
//TESH.scrollpos=69
//TESH.alwaysfold=0
library xepreload initializer init requires xebasic, optional TimerUtils
//******************************************************************************
// xepreload 0.8
// ---------
// Ah, the joy of preloading abilities, it is such a necessary evil...
// Notice you are not supposed to use this system in places outside map init
//
// This one does the preloading and tries to minimize the hit on loading time
// for example, it only needs one single native call per ability preloaded.
//
//******************************************************************************
//==============================================================================
globals
private unit dum=null
endglobals
private keyword DebugIdInteger2IdString
//inline friendly (when debug mode is off..)
function XE_PreloadAbility takes integer abilid returns nothing
call UnitAddAbility(dum, abilid)
static if DEBUG_MODE then
if(dum==null) then
call BJDebugMsg("XE_PreloadAbility: do not load abilities after map init or during structs' onInit")
elseif GetUnitAbilityLevel(dum, abilid) == 0 then
call BJDebugMsg("XE_PreloadAbility: Ability "+DebugIdInteger2IdString.evaluate(abilid)+" does not exist.")
endif
endif
endfunction
// ................................................................................
//================================================================================
// Convert a integer id value into a 4-letter id code.
// * Taken from cheats.j so I don't have to code it again.
// * Used only on debug so making a whole library for it seemed silly
// * Private so people don't begin using xepreload just to call this function....
// * It will not work correctly if you paste this code in the custom script section
// due to the infamous % bug. Then again, if you do that then you probably
// deserve it....
//
private function DebugIdInteger2IdString takes integer value returns string
local string charMap = ".................................!.#$%&'()*+,-./0123456789:;<=>.@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~................................................................................................................................."
local string result = ""
local integer remainingValue = value
local integer charValue
local integer byteno
set byteno = 0
loop
set charValue = ModuloInteger(remainingValue, 256)
set remainingValue = remainingValue / 256
set result = SubString(charMap, charValue, charValue + 1) + result
set byteno = byteno + 1
exitwhen byteno == 4
endloop
return result
endfunction
//--------------------------------
private function kill takes nothing returns nothing
call RemoveUnit(dum)
set dum=null
static if (LIBRARY_TimerUtils ) then
call ReleaseTimer( GetExpiredTimer() )
else
call DestroyTimer(GetExpiredTimer())
endif
endfunction
private function init takes nothing returns nothing
local timer t
set dum = CreateUnit( Player(15), XE_DUMMY_UNITID, 0,0,0)
if( dum == null) then
debug call BJDebugMsg("xePreload : XE_DUMMY_UNITID ("+DebugIdInteger2IdString.evaluate(XE_DUMMY_UNITID)+") not added correctly to the map.")
endif
static if (LIBRARY_TimerUtils) then
set t=NewTimer()
else
set t=CreateTimer()
endif
call TimerStart(t,0.0,false,function kill)
set t=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=6
//TESH.alwaysfold=0
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
//TESH.scrollpos=4
//TESH.alwaysfold=0
How to import
==================================
1. Copy the ability "Water Ball" <'A000'> from Object Editor - Abilities to your map
###If already has dummy unit model, skip to Step 3.####
2. Import the dummy unit model <dummy.mdx> from Import Manager to your map
###If already has dummy unit, skip to Step 4.####
3. Copy the dummy unit "Dummy" <'h000'> from Object Editor - Units to your map
4. Copy all required libraries, already included in Libraries folder
a checklist for required libraries:
- SimError
- TimerUtils
- GroupUtils
- xebasic
- xepreload
5. Copy the spell trigger "WaterBall" to your map
6. Configure the spell,
mainly change the ABIL_ID to match the rawcode of the ability on your map
and to change the DUMMY_ID, you can change it at xebasic library.
7. Spell is ready to use
//TESH.scrollpos=0
//TESH.alwaysfold=0
// Spell : Water Ball
// by : Dark_Axl
// For the Spell Contest #1 at BlizzVault.com
// Library Required: SimError, xebasic, xepreload, TimerUtils, GroupUtils
// Optional Library: SpellEffectEvent
// Model Required : dummy.mdx
// Credits Goes to : Vexorian, for the SimError, xe, TimerUtils and dummy.mdx
// Rising_Dusk, for the GroupUtils
//
// Spell Description:
// Target an area, then launches a series of missile into random point in target area, each missile deals damage to an area, and
// has a chance to explode, dealing more damage. The spell ends when the caster dies or the duration has ended.
scope WaterBall initializer init
globals
private constant integer ABIL_ID = 'A000'
// Define the spell's rawcode ID.
private constant integer DUMMY_ID = XE_DUMMY_UNITID
// Define the dummy unit's rawcode ID.
private constant integer FLY_ID = XE_HEIGHT_ENABLER
// Define the Crow Form's rawcode ID.
private constant string ORDER = "channel"
// Define the orderstring of the spell.
private constant real PERIOD_T = 0.03125
// Define interval period of timer. Higher value will make missile look sloppy when move, Lower value might cause lag.
private constant boolean CHANNEL = true
// Define if its channeling or not, if set to true, the caster has to channel the ability for the spell to take effect.
// if set to false, it will spawn the missile from the startpoint of cast, not follow.
private constant attacktype ATT_TYPE = ATTACK_TYPE_NORMAL
// Define the attacktype of the damage dealt
private constant damagetype DMG_TYPE = DAMAGE_TYPE_NORMAL
// Define the damagetype of the damage dealt
private constant integer MAX_MISSILE = 200
// Define the amount of maximum missile per caster per cast at one time, needed for struct member's array size
private constant string MISSILE_SFX = "Abilities\\Weapons\\SeaElementalMissile\\SeaElementalMissile.mdl"
// Define the SFX attached to the missile, put "" if not use.
private constant string MISSILE_ATT = "origin"
// Define the attachment point of the MISSILE_SFX
private constant string SPAWN_SFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
// Define the SFX played when the missile spawns, put "" if not use.
private constant string HIT_SFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
// Define the SFX played if the missile not explode, put "" if not use.
private constant string EXPLODE_SFX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
// Define the SFX played if the missile explode, put "" if not use.
private constant boolean ON_PATH = true
// Define if the caster must stand on certain pathable to be able to cast.
private constant pathingtype PATH_TYPE = PATHING_TYPE_FLOATABILITY
// Define the pathing type checked if ON_PATH = true.
// other pathingtype:
//PATHING_TYPE_WALKABILITY
//PATHING_TYPE_ANY
//PATHING_TYPE_AMPHIBIOUSPATHING
//PATHING_TYPE_BLIGHTPATHING
//PATHING_TYPE_BUILDABILITY
//PATHING_TYPE_FLYABILITY
//PATHING_TYPE_PEONHARVESTPATHING
private constant string ERROR_MSG = "Can't cast while not on water"
// Define the error message shown if the ON_PATH check return false.
private player TEMP_PLAYER
private boolexpr ENEMY_FILTER
endglobals
// Defines the delay between each missile spawn.
private constant function Spawn_Delay takes integer level returns real
return 0.1
endfunction
// Defines the duration of the spell. Number of the missiles is Duration / Spawn_Delay
private constant function Duration takes integer level returns real
return 2. + level
endfunction
// Defines the missile's size scale, value 1. = 100%.
private constant function Scale takes integer level returns real
return level*.1 + .9
endfunction
// Defines the radius of possible missile spawn area around caster.
private constant function MisAOE takes integer level returns real
return 150.
endfunction
// Defines the damage dealt by each missile normally.
private constant function HitDamage takes integer level returns real
return level*3.333 + 10
endfunction
// Defines the bonus damage dealt by each missile when explode.
private constant function ExpDamage takes integer level returns real
return level*3.333 + 10
endfunction
// Defines the chance that the missile explode, ranges from 0 to 1, with value 1. = 100%.
private constant function ExpChance takes integer level returns real
return 0.3
endfunction
// Defines the AOE of the spells, should match with the Area of Effect of the spell in Object Editor.
private constant function Area takes integer level returns real
return 200.
endfunction
// Defines the AOE of normal damage when missile hit.
private constant function HitAOE takes integer level returns real
return 200.
endfunction
// Defines the AOE of explosion damage when missile hit.
private constant function ExpAOE takes integer level returns real
return 200.
endfunction
// Defines the missile's fly time. This mean all missiles will reach target point after a certain equal delay
private constant function MisSpeed takes integer level returns real
return 0.5
endfunction
// Defines the minimum flying height of the missile.
private constant function MinHeight takes integer level returns real
return 100.
endfunction
// Defines the maximum flying height of the missile.
private constant function MaxHeight takes integer level returns real
return 400.
endfunction
// Defines the minimum horizontal deviation of the missile while moving.
private constant function MinWidth takes integer level returns real
return 100.
endfunction
// Defines the maximum horizontal deviation of the missile while moving.
private constant function MaxWidth takes integer level returns real
return 200.
endfunction
// use TEMP_PLAYER as reference to the caster's owner, and GetFilterUnit() as the filtered unit.
// currently it targets enemy units, alive, not magic immune and not flying unit.
private function Enemy_Check takes nothing returns boolean
local unit u = GetFilterUnit()
local boolean b = IsUnitEnemy(u,TEMP_PLAYER) and IsUnitType(u,UNIT_TYPE_DEAD) and not IsUnitType(u,UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u,UNIT_TYPE_FLYING)
set u = null
return b
endfunction
//===============================THE SPELL PART====================================//
//==============================DON'T EDIT BELOW ==================================//
// this function is used to determine the parabolic movement for both xy and z plane
private constant function GetParabolaZ takes real x, real d, real h returns real
return 4 * h * x * (d - x) / (d * d)
endfunction
// this struct is used both as missile struct and cast struct
private struct Data
unit cast
player p
timer t
group g
real del
real spa
real minh
real maxh
real mind
real maxd
real r
real scale
real msdelay
real dmgA
real dmgB
real chance
real aoeA
real aoeB
unit array missile[MAX_MISSILE]
effect array sfx[MAX_MISSILE]
real array sin[MAX_MISSILE]
real array cos[MAX_MISSILE]
real array sin_c[MAX_MISSILE]
real array cos_c[MAX_MISSILE]
real array speed[MAX_MISSILE]
real array x1[MAX_MISSILE]
real array y1[MAX_MISSILE]
real array x2[MAX_MISSILE]
real array y2[MAX_MISSILE]
real array dis[MAX_MISSILE]
real array dur[MAX_MISSILE]
real array h[MAX_MISSILE]
real array d[MAX_MISSILE]
integer index
endstruct
// this part move the missile, and dealt the damage in the end.
private function move_t takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data data = GetTimerData(t)
local unit v
local real x2
local real y2
local real d
local real h
local real x
local integer i = 1
loop
exitwhen i > data.index
//substract "speed" from "remaining distance"
set data.dur[i] = data.dur[i] - data.speed[i]
set x2 = data.x2[i] + data.dur[i]*data.cos[i]
set y2 = data.y2[i] + data.dur[i]*data.sin[i]
set x = SquareRoot((y2-data.y1[i])*(y2-data.y1[i])+(x2-data.x1[i])*(x2-data.x1[i]))
//function GetParabolaZ is used to calculate curve both in xy and z plane.
set d = GetParabolaZ(x,data.dis[i],data.d[i])
set h = GetParabolaZ(x,data.dis[i],data.h[i])
set x2 = x2 + d*data.cos_c[i]
set y2 = y2 + d*data.sin_c[i]
call SetUnitX(data.missile[i],x2)
call SetUnitY(data.missile[i],y2)
call SetUnitFlyHeight(data.missile[i],h,0)
//if .dur[i] <= 0, means target is reached, deals damage and reindexing.
if data.dur[i] <= 0 then
// this part deals hit damage
set TEMP_PLAYER = data.p
call GroupClear(data.g)
call GroupEnumUnitsInRange(data.g,x2,y2,data.aoeA,ENEMY_FILTER)
loop
set v = FirstOfGroup(data.g)
exitwhen v == null
call GroupRemoveUnit(data.g,v)
call UnitDamageTarget(data.cast,v,data.dmgA,false,true,ATT_TYPE,DMG_TYPE,null)
endloop
call DestroyEffect(AddSpecialEffect(HIT_SFX,x2,y2))
// this part check for explosion chance and damage
if GetRandomReal(0,1) <= data.chance then
call GroupClear(data.g)
set TEMP_PLAYER = data.p
call GroupEnumUnitsInRange(data.g,x2,y2,data.aoeB,ENEMY_FILTER)
loop
set v = FirstOfGroup(data.g)
exitwhen v == null
call GroupRemoveUnit(data.g,v)
call UnitDamageTarget(data.cast,v,data.dmgB,false,true,ATT_TYPE,DMG_TYPE,null)
endloop
call DestroyEffect(AddSpecialEffect(EXPLODE_SFX,x2,y2))
endif
endif
set i = i + 1
endloop
// reindexing
set i = 1
loop
exitwhen i > data.index
if data.dur[i] <= 0 then
call DestroyEffect(data.sfx[i])
call KillUnit(data.missile[i])
call RemoveUnit(data.missile[i])
set data.missile[i] = data.missile[data.index]
set data.dur[i] = data.dur[data.index]
set data.sfx[i] = data.sfx[data.index]
set data.sin[i] = data.sin[data.index]
set data.cos[i] = data.cos[data.index]
set data.sin_c[i] = data.sin_c[data.index]
set data.cos_c[i] = data.cos_c[data.index]
set data.x1[i] = data.x1[data.index]
set data.y1[i] = data.y1[data.index]
set data.x2[i] = data.x2[data.index]
set data.y2[i] = data.y2[data.index]
set data.dis[i] = data.dis[data.index]
set data.h[i] = data.h[data.index]
set data.d[i] = data.d[data.index]
set data.speed[i] = data.speed[data.index]
set data.sfx[data.index] = null
set data.missile[data.index] = null
set data.index = data.index - 1
endif
set i = i + 1
endloop
//if index ever reaches zero, means spells end, cleanup.
if data.index <= 0 then
call ReleaseTimer(t)
call ReleaseGroup(data.g)
set data.cast = null
set data.t = null
set data.p = null
set data.g = null
call data.destroy()
endif
set t = null
endfunction
// this part checks for the channel part, and spawn the missile.
private function effect_t takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data data = GetTimerData(t)
local integer side
local real dis
local real ang
// this part check if the caster is still alive AND channeling the spell, if not, spell effect ends.
set data.dur[0] = data.dur[0] - data.del
if data.dur[0] < 0 or IsUnitType(data.cast,UNIT_TYPE_DEAD) or (GetUnitCurrentOrder(data.cast) != OrderId(ORDER) and CHANNEL) then
call ReleaseTimer(t)
set t = null
return
endif
//this part setup the missile trajectory
set data.index = data.index + 1
set dis = GetRandomReal(0,data.spa)
set ang = GetRandomReal(0,360)
set data.x1[data.index] = data.x1[0] + Cos(ang*0.0174533)*dis
set data.y1[data.index] = data.y1[0] + Sin(ang*0.0174533)*dis
set dis = GetRandomReal(0,data.r)
set ang = GetRandomReal(0,360)
set data.x2[data.index] = data.x2[0] + Cos(ang*0.0174533)*dis
set data.y2[data.index] = data.y2[0] + Sin(ang*0.0174533)*dis
set ang = Atan2((data.y2[data.index]-data.y1[data.index]),(data.x2[data.index]-data.x1[data.index]))
//.dis is used for parabola calculation, while .dur is used to store instance duration in distance before impact
set data.dis[data.index] = SquareRoot((data.y2[data.index]-data.y1[data.index])*(data.y2[data.index]-data.y1[data.index])+(data.x2[data.index]-data.x1[data.index])*(data.x2[data.index]-data.x1[data.index]))
set data.dur[data.index] = data.dis[data.index]
set data.h[data.index] = GetRandomReal(data.minh,data.maxh)
set data.d[data.index] = GetRandomReal(data.mind,data.maxd)
set data.speed[data.index]= data.dis[data.index]*PERIOD_T/data.msdelay
//this determine whether the missile curve left or right side
set side = GetRandomInt(0,1)
if side == 0 then
set side = -1
endif
set data.sin[data.index] = Sin(ang+bj_PI)
set data.cos[data.index] = Cos(ang+bj_PI)
set data.sin_c[data.index] = Sin(ang+(side*0.5*bj_PI))
set data.cos_c[data.index] = Cos(ang+(side*0.5*bj_PI))
call DestroyEffect(AddSpecialEffect(SPAWN_SFX,data.x1[data.index],data.y1[data.index]))
//preparing the missile(s)
set data.missile[data.index] = CreateUnit(data.p,DUMMY_ID,data.x1[data.index],data.y1[data.index],ang)
set data.sfx[data.index] = AddSpecialEffectTarget(MISSILE_SFX,data.missile[data.index],MISSILE_ATT)
call SetUnitScale(data.missile[data.index],data.scale,0,0)
call UnitAddAbility(data.missile[data.index],FLY_ID)
call UnitRemoveAbility(data.missile[data.index],FLY_ID)
// to make sure the missile looks launched from ground/water or z = 0
call SetUnitFlyHeight(data.missile[data.index],0,0)
//start missile timer if there is missile ready to run
if data.index == 1 then
call TimerStart(data.t,PERIOD_T,true,function move_t)
endif
set t = null
endfunction
// this part setup the variable and constant
private function postcast_t takes nothing returns nothing
local unit u = GetTriggerUnit()
local timer t = NewTimer()
local integer lv = GetUnitAbilityLevel(u,ABIL_ID)
local Data data = Data.create()
//this part setup all parameter used in spell based on function provided at top
set data.cast = u
set data.p = GetOwningPlayer(u)
set data.scale = Scale(lv)
set data.del = Spawn_Delay(lv)
set data.dur[0] = Duration(lv)
set data.spa = MisAOE(lv)
set data.x1[0] = GetUnitX(u)
set data.y1[0] = GetUnitY(u)
set data.x2[0] = GetSpellTargetX()
set data.y2[0] = GetSpellTargetY()
set data.minh = MinHeight(lv)
set data.maxh = MaxHeight(lv)
set data.mind = MinWidth(lv)
set data.maxd = MaxWidth(lv)
set data.msdelay= MisSpeed(lv)
set data.r = Area(lv)
set data.index = 0
set data.t = NewTimer()
set data.g = NewGroup()
set data.dmgA = HitDamage(lv)
set data.dmgB = ExpDamage(lv)
set data.chance = ExpChance(lv)
set data.aoeA = HitAOE(lv)
set data.aoeB = ExpAOE(lv)
//start the timer for channeling part
call SetTimerData(t,data)
call SetTimerData(data.t,data)
call TimerStart(t,data.del,true,function effect_t)
set t = null
set u = null
endfunction
// this part does the pathingtype check and cancel the spell respectively, only if ON_PATH == true.
static if ON_PATH then
private function precast_t takes nothing returns boolean
local unit u = GetTriggerUnit()
if GetSpellAbilityId() == ABIL_ID and IsTerrainPathable(GetUnitX(u),GetUnitY(u),PATH_TYPE) then
call PauseUnit(u,true)
call IssueImmediateOrder(u,"stop")
call PauseUnit(u,false)
call SimError(GetOwningPlayer(u),ERROR_MSG)
endif
set u = null
return false
endfunction
endif
//this is the condition of the spell cast
private function Abil_Check takes nothing returns boolean
return GetSpellAbilityId() == ABIL_ID
endfunction
//this is the initialization of the spell trigger
private function init takes nothing returns nothing
local trigger trig
//this part setup for the pre-cast check
static if ON_PATH then
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_CAST )
call TriggerAddCondition( trig , Condition( function precast_t ) )
endif
//this part setup for the spell effect itself
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(ABIL_ID, function postcast_t)
else
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( trig, Condition( function Abil_Check ) )
call TriggerAddAction( trig, function postcast_t )
endif
//this is setup for boolexpr used in group enumeration for damage effect
set ENEMY_FILTER = Filter(function Enemy_Check)
//preload ability and effect
call XE_PreloadAbility(ABIL_ID)
call Preload(MISSILE_SFX)
call Preload(HIT_SFX)
call Preload(EXPLODE_SFX)
call Preload(SPAWN_SFX)
set trig = null
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
// Spell : Water Ball
// by : Axleion, also known as Dark_Axl
// For the Spell Contest #1 at BlizzVault.com
// Library Required: SimError
// Model Required : dummy.mdx
// Credits Goes to : Vexorian, for the SimError, and dummy.mdx
// Spell Description:
// Target an area, then launches a series of missile into random point in target area, each missile deals damage to an area, and
// has a chance to explode, dealing more damage. The spell ends when the caster dies or the duration has ended.
scope WaterBall initializer init
globals
private constant integer ABIL_ID = 'A000' // Spell's rawcode ID.
private constant integer DUMMY_ID = 'h000' // Dummy unit's rawcode ID.
private constant integer FLY_ID = 'Amrf' // Crow Form's rawcode ID
private constant integer LOC_ID = 'Aloc' // Locust's rawcode ID
private constant string ORDER = "channel" // the orderstring of the spell.
private constant real PERIOD_T = 0.03 // Define period delay of timer.
private constant real DELAY = 0.10 // Define the delay between each spawn of the missile
private constant boolean CHANNEL = true // Define its channeling or not
// if false, it will spawn the missile from the startpoint of cast, not follow.
private constant attacktype ATT_TYPE = ATTACK_TYPE_NORMAL // Define the attacktype of the damage dealt
private constant damagetype DMG_TYPE = DAMAGE_TYPE_NORMAL // Define the damagetype of the damage dealt
// private constant string MISSILE_SFX = "Abilities\\Spells\\NightElf\\SpiritOfVengeance\\SpiritOfVengeanceBirthMissile.mdl"
private constant string MISSILE_SFX = "Abilities\\Weapons\\SeaElementalMissile\\SeaElementalMissile.mdl"
// Define the SFX attached to the missile, put "" if not use.
private constant string SPAWN_SFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
// Define the SFX played when the missile spawns, put "" if not use.
private constant string HIT_SFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
// Define the SFX played if the missile not explode, put "" if not use.
private constant string EXPLODE_SFX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
// Define the SFX played if the missile explode, put "" if not use.
private constant boolean ON_PATH = true
// Define if the caster must stand on certain pathable to cast.
private constant string ERROR_MSG = "Must be cast on water"
// Define the error message shown if the cast is cancelled.
private constant pathingtype PATH_TYPE = PATHING_TYPE_FLOATABILITY
// the path that defined if ON_PATH is true, will cancel the cast if stand on defined path.
// other pathingtype:
//PATHING_TYPE_WALKABILITY
//PATHING_TYPE_ANY
//PATHING_TYPE_AMPHIBIOUSPATHING
//PATHING_TYPE_BLIGHTPATHING
//PATHING_TYPE_BUILDABILITY
//PATHING_TYPE_FLYABILITY
//PATHING_TYPE_PEONHARVESTPATHING
private constant hashtable HASH = InitHashtable()
private player TEMP_PLAYER
private boolexpr ENEMY_FILTER
endglobals
private function Duration takes integer level returns real
return 2. + level // Defines the duration of the spell. Number of the missiles is Duration / DELAY
endfunction
private function Scale takes integer level returns real
return level*.1 + .9 // Defines the missile's size scale, in fraction.
endfunction
private function Height takes integer level returns real
return 200. // Defines the height the missile is raised before fired.
endfunction
private function MisAOE takes integer level returns real
return 150. // Defines the radius of possible missile spawn area.
endfunction
private function HRise takes integer level returns real
return 800. // Defines the rate the height of the missile rise.
endfunction
private function HitDamage takes integer level returns real
return level*3.333 + 10 // Defines the damage dealt by each missile normally.
endfunction
private function ExpDamage takes integer level returns real
return level*3.333 + 10 // Defines the bonus damage dealt by each missile when explode.
endfunction
private function ExpChance takes integer level returns real
return 0.3 // Defines the chance that the missile explode, ranges from 0 to 1.
endfunction
private function Area takes integer level returns real
return 200. // Defines the AOE of the spells, should match with the Area of Effect of the spell in Object Editor.
endfunction
private function HitAOE takes integer level returns real
return 200. // Defines the AOE of normal hit.
endfunction
private function ExpAOE takes integer level returns real
return 200. // Defines the AOE of explosion hit.
endfunction
private function MisSpeed takes integer level returns real
return level*100 + 700. // Defines the distance covered by the missile in each second.
endfunction
// use TEMP_PLAYER as reference to the caster's owner, and GetFilterUnit() (or variable v) as the filtered unit.
// currently it targets enemy units, alive, and not flying unit.
private function Enemy_Check takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(),TEMP_PLAYER) and GetWidgetLife(GetFilterUnit()) > .405 and not IsUnitType(GetFilterUnit(),UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_FLYING)
endfunction
//===============================THE SPELL PART====================================//
//==============DON'T EDIT BELOW UNLESS YOU KNOW WHAT ARE YOU DOING================//
private struct Data
unit cast
unit missile
player p
effect sfx
integer num
real rise
real spa
real x1
real y1
real x2
real y2
real dis
real ang
real h
real r
real scale
real speed
real dmgA
real dmgB
real chance
real aoeA
real aoeB
endstruct
// this part move the missile, and dealt the damage in the end.
private function move_t takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data data = LoadInteger(HASH,GetHandleId(t),0)
local group g
local unit v
set data.x1 = data.x1 + data.speed*Cos(data.ang)
set data.y1 = data.y1 + data.speed*Sin(data.ang)
set data.h = data.h*(data.dis-data.speed)/data.dis
call SetUnitX(data.missile,data.x1)
call SetUnitY(data.missile,data.y1)
call SetUnitFlyHeight(data.missile,data.h,0)
set data.dis = data.dis - data.speed
if data.dis <= 30 then
set TEMP_PLAYER = GetOwningPlayer(data.missile)
set g = NewGroup()
call GroupEnumUnitsInRange(g,data.x2,data.y2,data.aoeA,ENEMY_FILTER)
loop
set v = FirstOfGroup(g)
exitwhen v == null
call GroupRemoveUnit(g,v)
call UnitDamageTarget(data.cast,v,data.dmgA,false,true,ATT_TYPE,DMG_TYPE,WEAPON_TYPE_WHOKNOWS)
endloop
call DestroyEffect(AddSpecialEffect(HIT_SFX,data.x2,data.y2))
if GetRandomReal(0,1) <= data.chance then
call GroupClear(g)
call GroupEnumUnitsInRange(g,data.x2,data.y2,data.aoeB,ENEMY_FILTER)
loop
set v = FirstOfGroup(g)
exitwhen v == null
call GroupRemoveUnit(g,v)
call UnitDamageTarget(data.cast,v,data.dmgB,false,true,ATT_TYPE,DMG_TYPE,WEAPON_TYPE_WHOKNOWS)
endloop
call DestroyEffect(AddSpecialEffect(EXPLODE_SFX,data.x2,data.y2))
endif
call ReleaseGroup(g)
call DestroyEffect(data.sfx)
call KillUnit(data.missile)
call RemoveUnit(data.missile)
call data.destroy()
call FlushChildHashtable(HASH,GetHandleId(t))
call ReleaseTimer(t)
set g = null
set v = null
endif
set t = null
endfunction
// this part just switch between the rising part to moving part.
private function start_t takes nothing returns nothing
local timer t = GetExpiredTimer()
call TimerStart(t,PERIOD_T,true,function move_t)
set t = null
endfunction
// this part checks for the channel part, and spawn the missile.
private function effect_t takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data data = GetTimerData(t)
local Data ball
local player p
local timer e
local real dis
local real ang
if GetWidgetLife(data.cast) < .405 or (GetUnitCurrentOrder(data.cast) != OrderId(ORDER) and CHANNEL) then
set data.cast = null
set data.p = null
call data.destroy()
call ReleaseTimer(t)
set t = null
return
endif
set e = NewTimer()
set ball = Data.create()
set ball.p = data.p
set dis = GetRandomReal(0,data.spa)
set ang = GetRandomReal(0,360)
set ball.x1 = data.x1 + Cos(ang*0.0174533)*dis
set ball.y1 = data.y1 + Sin(ang*0.0174533)*dis
set dis = GetRandomReal(0,data.r)
set ang = GetRandomReal(0,360)
set ball.x2 = data.x2 + Cos(ang*0.0174533)*dis
set ball.y2 = data.y2 + Sin(ang*0.0174533)*dis
set ball.ang = Atan2((ball.y2-ball.y1),(ball.x2-ball.x1))
set ball.dis = SquareRoot((ball.y2-ball.y1)*(ball.y2-ball.y1)+(ball.x2-ball.x1)*(ball.x2-ball.x1))
set ball.cast = data.cast
set ball.dmgA = data.dmgA
set ball.dmgB = data.dmgB
set ball.aoeA = data.aoeA
set ball.aoeB = data.aoeB
set ball.speed = data.speed
set ball.chance = data.chance
set ball.h = data.h
call DestroyEffect(AddSpecialEffect(SPAWN_SFX,ball.x1,ball.y1))
set ball.missile = CreateUnit(p,DUMMY_ID,ball.x1,ball.y1,data.ang)
set ball.sfx = AddSpecialEffectTarget(MISSILE_SFX,ball.missile,"origin")
call SetUnitScale(ball.missile,data.scale,data.scale,data.scale)
call UnitAddAbility(ball.missile,FLY_ID)
call UnitAddAbility(ball.missile,LOC_ID)
call UnitRemoveAbility(ball.missile,FLY_ID)
call SetUnitFlyHeight(ball.missile,0,0) // to make sure the missile start from below
call SetUnitFlyHeight(ball.missile,data.h,data.rise)
call SaveInteger(HASH,GetHandleId(e),0,ball)
call TimerStart(e,data.h/data.rise,false,function start_t)
set data.num = data.num - 1
if data.num <= 0 then
call data.destroy()
call FlushChildHashtable( HASH, GetHandleId(t))
call ReleaseTimer(t)
endif
set t = null
set p = null
set e = null
endfunction
// this part setup the variable and constant
private function postcast_t takes nothing returns nothing
local unit u = GetTriggerUnit()
local timer t = NewTimer()
local integer lv = GetUnitAbilityLevel(u,ABIL_ID)
local Data data = Data.create()
set data.cast = u
set data.p = GetOwningPlayer(u)
set data.scale = Scale(lv)
set data.num = R2I(Duration(lv)/DELAY)
set data.spa = MisAOE(lv)
set data.x1 = GetUnitX(u)
set data.y1 = GetUnitY(u)
set data.x2 = GetSpellTargetX()
set data.y2 = GetSpellTargetY()
set data.ang = Atan2((data.y2-data.y1),(data.x2-data.x1))*57.29578
set data.h = Height(lv)
set data.rise = HRise(lv)
set data.r = Area(lv)
set data.speed = MisSpeed(lv)*PERIOD_T
set data.dmgA = HitDamage(lv)
set data.dmgB = ExpDamage(lv)
set data.chance = ExpChance(lv)
set data.aoeA = HitAOE(lv)
set data.aoeB = ExpAOE(lv)
call SetTimerData(t,data)
// call SaveInteger(HASH,GetHandleId(t),0,data)
call TimerStart(t,DELAY,true,function effect_t)
set t = null
set u = null
endfunction
// this part does the pathingtype check and cancel the spell respectively.
private function precast_t takes nothing returns nothing
local unit u = GetTriggerUnit()
local player p
static if ON_PATH then
if IsTerrainPathable(GetUnitX(u),GetUnitY(u),PATH_TYPE) then
call PauseUnit(u,true)
call IssueImmediateOrder(u,"stop")
call PauseUnit(u,false)
set p = GetOwningPlayer(u)
call SimError(p,ERROR_MSG)
set p = null
endif
endif
set u = null
endfunction
//this is the condition of the spell cast
private function Abil_Check takes nothing returns boolean
return GetSpellAbilityId() == ABIL_ID
endfunction
private function init takes nothing returns nothing
local trigger precast = CreateTrigger()
local trigger postcast = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( precast, EVENT_PLAYER_UNIT_SPELL_CAST )
call TriggerRegisterAnyUnitEventBJ( postcast, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( precast , Condition( function Abil_Check ) )
call TriggerAddCondition( postcast, Condition( function Abil_Check ) )
call TriggerAddAction( precast, function precast_t )
call TriggerAddAction( postcast, function postcast_t )
set ENEMY_FILTER = Filter(function Enemy_Check)
call XE_PreloadAbility(ABIL_ID)
call Preload(MISSILE_SFX)
call Preload(HIT_SFX)
call Preload(EXPLODE_SFX)
call Preload(SPAWN_SFX)
set precast = null
set postcast = null
endfunction
endscope