Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
globals
// High enough so the unit is no longer visible, low enough so the
// game doesn't crash...
//
// I think you need 0.0 or soemthing negative prior to patch 1.22
//
private constant real EXTRA = 500.0
endglobals
//=========================================================================================
globals
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns nothing
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local region r=CreateRegion()
local rect rc
set minx=GetCameraBoundMinX() - EXTRA
set miny=GetCameraBoundMinY() - EXTRA
set maxx=GetCameraBoundMaxX() + EXTRA
set maxy=GetCameraBoundMaxY() + EXTRA
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddAction(t, function dis)
//this is not necessary but I'll do it anyway:
set t=null
set r=null
set rc=null
endfunction
endlibrary
//TESH.scrollpos=107
//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=0
//TESH.alwaysfold=0
library ListModule
//===========================================================================
// Information:
//==============
//
// This library provides the List module, which allows you to easily create
// a linked list of all of the allocated instances of a struct-type. Iterating
// through a linked list is about 12% faster than iteratating through an array
// in JASS. There is no faster method to iterate through a list of structs than
// the method used by this module. Aside from the marginal speed gain, the best
// use of this library is to hide some ugly low-level code from your structs.
// Rather than manually building and maintaining a list of struct instances,
// just implement the List module, and your code will become much prettier.
//
//===========================================================================
// How to use the List module:
//=============================
//
// Using the List module is pretty simple. First, implement it in your
// struct (preferably at the top to avoid unnecessary TriggerEvaluate calls).
// In the struct's create method, you must call listAdd(). In the onDestroy
// method, you must also call listRemove(). An example is shown below:
/*
struct Example
implement List
static method create takes nothing returns Example
local Example this = allocate()
call listAdd() //This method adds the instance to the list.
return this
endmethod
method onDestroy takes nothing returns nothing
call listRemove() //This method removes the instance from the list.
endmethod
endstruct
*/
// The requirement to call listAdd() and listRemove() will be done away
// with once JassHelper supports module onDestroy and module onCreate, but
// for now, it is not too much of a burden.
//
// Once this is done, your struct will gain all of the methods detailed
// in the API section. Below is an example of how to iterate through the list
// of allocated structs of the implementing struct-type:
/*
function IterationExample takes nothing returns nothing
local Example e = Example.first
loop
exitwhen e == 0
//Do something with e here.
set e = e.next
endloop
//Use .last and .prev instead to iterate backwards.
endmethod
*/
//
//===========================================================================
// List module API:
//==================
//
// (readonly)(static) first -> thistype
// This member contains the first instance of thistype in the list.
//
// (readonly)(static) last -> thistype
// This member contains the last instance of thistype in the list.
//
// (readonly)(static) count -> integer
// This member contains the number of allocated structs of thistype.
//
// (readonly) next -> thistype
// This member contains the next instance of thistype in the list.
//
// (readonly) prev -> thistype
// This member contains the previous instance of thistype in the list.
//
// listAdd()
// This method adds this instance to the list of structs of thistype.
// This should be called on each instance after it is allocated (within
// the create method).
//
// listRemove()
// This method removes this instance from the list of structs of thistype.
// This should be called on each instance before it is destroyed (within
// the onDestroy method).
//
// (static) listDestroy()
// This method destroys all the structs of thistype within the list.
//
//===========================================================================
module List
private static boolean destroying = false
private boolean inlist = false
readonly static integer count = 0
readonly thistype next = 0
readonly thistype prev = 0
static method operator first takes nothing returns thistype
return thistype(0).next
endmethod
static method operator last takes nothing returns thistype
return thistype(0).prev
endmethod
method listRemove takes nothing returns nothing
if not inlist then
return
endif
set inlist = false
set prev.next = next
set next.prev = prev
set count = count - 1
endmethod
method listAdd takes nothing returns nothing
if inlist or destroying then
return
endif
set inlist = true
set last.next = this
set prev = last
set thistype(0).prev = this
set count = count + 1
endmethod
static method listDestroy takes nothing returns nothing
local thistype this = last
set destroying = true
loop
exitwhen this == 0
call destroy()
set this = prev
endloop
set destroying = false
endmethod
endmodule
endlibrary
//TESH.scrollpos=29
//TESH.alwaysfold=0
library TerrainPathability initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This script can be used to detect the type of pathing at a specific point.
//* It is valuable to do it this way because the IsTerrainPathable is very
//* counterintuitive and returns in odd ways and aren't always as you would
//* expect. This library, however, facilitates detecting those things reliably
//* and easily.
//*
//******************************************************************************
//*
//* > function IsTerrainDeepWater takes real x, real y returns boolean
//* > function IsTerrainShallowWater takes real x, real y returns boolean
//* > function IsTerrainLand takes real x, real y returns boolean
//* > function IsTerrainPlatform takes real x, real y returns boolean
//* > function IsTerrainWalkable takes real x, real y returns boolean
//*
//* These functions return true if the given point is of the type specified
//* in the function's name and false if it is not. For the IsTerrainWalkable
//* function, the MAX_RANGE constant below is the maximum deviation range from
//* the supplied coordinates that will still return true.
//*
//* The IsTerrainPlatform works for any preplaced walkable destructable. It will
//* return true over bridges, destructable ramps, elevators, and invisible
//* platforms. Walkable destructables created at runtime do not create the same
//* pathing hole as preplaced ones do, so this will return false for them. All
//* other functions except IsTerrainWalkable return false for platforms, because
//* the platform itself erases their pathing when the map is saved.
//*
//* After calling IsTerrainWalkable(x, y), the following two global variables
//* gain meaning. They return the X and Y coordinates of the nearest walkable
//* point to the specified coordinates. These will only deviate from the
//* IsTerrainWalkable function arguments if the function returned false.
//*
//* Variables that can be used from the library:
//* [real] TerrainPathability_X
//* [real] TerrainPathability_Y
//*
globals
private constant real MAX_RANGE = 10.
private constant integer DUMMY_ITEM_ID = 'wolg'
endglobals
globals
private item Item = null
private rect Find = null
private item array Hid
private integer HidMax = 0
public real X = 0.
public real Y = 0.
endglobals
function IsTerrainDeepWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
function IsTerrainShallowWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
function IsTerrainLand takes real x, real y returns boolean
return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
endfunction
function IsTerrainPlatform takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
private function HideItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set Hid[HidMax] = GetEnumItem()
call SetItemVisible(Hid[HidMax], false)
set HidMax = HidMax + 1
endif
endfunction
function IsTerrainWalkable takes real x, real y returns boolean
//Hide any items in the area to avoid conflicts with our item
call MoveRectTo(Find, x, y)
call EnumItemsInRect(Find ,null, function HideItem)
//Try to move the test item and get its coords
call SetItemPosition(Item, x, y) //Unhides the item
set X = GetItemX(Item)
set Y = GetItemY(Item)
static if LIBRARY_IsTerrainWalkable then
//This is for compatibility with the IsTerrainWalkable library
set IsTerrainWalkable_X = X
set IsTerrainWalkable_Y = Y
endif
call SetItemVisible(Item, false)//Hide it again
//Unhide any items hidden at the start
loop
exitwhen HidMax <= 0
set HidMax = HidMax - 1
call SetItemVisible(Hid[HidMax], true)
set Hid[HidMax] = null
endloop
//Return walkability
return (X-x)*(X-x)+(Y-y)*(Y-y) <= MAX_RANGE*MAX_RANGE and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
private function Init takes nothing returns nothing
set Find = Rect(0., 0., 128., 128.)
set Item = CreateItem(DUMMY_ITEM_ID, 0, 0)
call SetItemVisible(Item, false)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
KNOCKBACK
VERSION 1.0
BY KRICZ
Credits:
Vexorian: vJass, BoundSentinel
Rising_Dusk: GroupUtils, TerrainPathability
grim001: ListModule
Knockback is a knockback system, which is used to push units away.
The System has much features such like detecting hits with destructables and other units.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
The syntax to call a new knockback on a unit is:
set myKnockback = Knockback.create(caster, target, distance, time, angle, kbType)
@caster: This is the unit where the knockback comes from.
@target: This is the unit which will be knocked back.
@distance: The distance the target will be knocked back.
@time: The time the unit requires until it reaches the distance
@angle: The angle of the knockback.
@kbType: The KnockbackType Struct which saves all the actions for several events (read more below).
This is a small example of a basic knockback:
set myKnockback = Knockback.create(GetTriggerUnit(), GetSpellTargetUnit(), 500, 2.5, GetUnitFacing(GetSpellTargetUnit()), kbType)
This would knockback the target of an spell 500 units away, which requires 2.5 seconds in the target unit's
facing angle.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
The KnockbackType Struct:
The KnockbackType Struct is a struct which stores all the actions a knockback can has.
This system features the following actions:
This funtion will be called, when a new knockback is started on an unit
private function interface onStart takes Knockback kb returns nothing
This function will be called whenever the units position will be updated
private function interface onLoop takes Knockback kb returns nothing
This function will be called whenever the knocked unit hits another unit
You can use the filterFunction function to filter out specified units
The system itself will filter out all units arround the target
private function interface onUnitHit takes Knockback kb, unit hit returns nothing
This is the filterFunction. You can use this to filter out specified units. The parameter
enum is the unit which will be filtered at the moment.
Use it like a normal FilterFunction which would return an boolean like:
return IsUnitType(enum) == UNIT_TYPE_HERO
private function interface filterFunction takes Knockback kb, unit enum returns boolean
This function will be called whenever the knocked unit hits a destructable
private function interface onDestructableHit takes Knockback kb, destructable hit returns nothing
This function will be called when the unit being knocked back dies. If it returns true,
the knockback will continue, else it will be stopped, calling the onEnd function below.
private function interface onTargetDeath takes Knockback kb returns boolean
This function will be called when a knockback ends
private function interface onEnd takes Knockback kb returns nothing
You don not need to declare a new KnockbackType whenever you create a new knockback.
Just create a global KnockbackType and assign its actions at map init and use this variable.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
Setup variables:
private constant real STANDARD_DEST_RADIUS / STANDARD_UNIT_RADIUS
This variables are used for gettings near units or destructables around the target.
You can change them while the unit is being knocked back using the aoeDest / aoeUnit variables
private constant real TIMER_INTERVAL
This is the interval, in which the position of units being knocked back are changed. The lower this value,
the smoother is the movement, but the higher this value, the better is the performance.
The standard value is 0.03125.
private constant boolean STOP_WHEN_IMPASSABLE
If this boolean is true, the knockback will instantly end, when a unit can not be moved on an impassable terrain.
For example cliffs, deep water or destructables. Please note, that the system will first run the onDestructableHit Action
before the unit receive its new position.
private constant USE_KNOCKBACK_EFFECTS
If this boolean is true, the system will add special effects depending on the terrain type it is currently being on.
EARTH_EFFECT and WATER_EFFECT are the effects being used, which are attached to KNOCKBACK_EFFECT_ATTACHPOINT.
If IGNORE_FLYING_UNITS is true, the system will not create these effects on flying units.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
User methods:
method destroy takes nothing returns nothing
This method (you can not see it in the code) destroys the current knockback and cleans up all variables.
Using this method will call the onEnd function.
method addSpecialEffect takes string fx, string attachPoint returns nothing
Adds or changes the effect attached to the target.
static method isUnitKnockedBack takes unit whichUnit returns boolean
This method returns true, if the given unit is being knocked back currently, else it returns false
method operator angle= takes real angle returns nothing
Changes the angle of the knockback.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
User variables:
real aoeDest / aoeUnit
These reals are used to get near units or destructables. You can
change these variables to every value you want while the unit is beeing knocked back.
integer array userData[3]
You can use this integer array to store some integer or other structs you may need in the onLoop function or whenever.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
Readonly variables:
These variables can not be changed. They are only for reading outside of the struct.
unit caster / target
Caster should be self-evident. Target is the unit beeing knocked back.
real timeOver
This variable saves the time the unit has been knocked back.
real currentSpeed
This variable is the current speed of the knockback.
real movedDistance
This variable saves the distance the has been knocked back.
real duration
This is the base duration used when the knockback was created.
real distance
This is the base distance used when the knockback was created.
real knockbackAngle
This variable saves the angle of the knockback.
//TESH.scrollpos=197
//TESH.alwaysfold=0
library Knockback requires ListModule, GroupUtils, TerrainPathability, optional BoundSentinel
//Please check the Knockback Info Trigger for more information about this system.
globals
public constant real TIMER_INTERVAL = 0.03125
private constant real STANDARD_DEST_RADIUS = 100.00
private constant real STANDARD_UNIT_RADIUS = 75.00
private constant boolean STOP_WHEN_IMPASSABLE = false
private constant boolean USE_KNOCKBACK_EFFECTS = true
//will only be used, if USE_KNOCKBACK_EFFECTS is true
private constant string EARTH_EFFECT = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
private constant string WATER_EFFECT = "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl"
private constant string KNOCKBACK_EFFECT_ATTACHPOINT = "origin"
private constant boolean IGNORE_FLYING_UNITS = true
endglobals
//The Action Respones
private function interface onStart takes Knockback kb returns nothing
private function interface onLoop takes Knockback kb returns nothing
private function interface onUnitHit takes Knockback kb, unit hit returns nothing
private function interface onDestructableHit takes Knockback kb, destructable hit returns nothing
private function interface onTargetDeath takes Knockback kb returns nothing
private function interface onEnd takes Knockback kb returns nothing
private function interface filterFunction takes Knockback kb, unit enum returns boolean
//The KnockbackType Struct
struct KnockbackType
onStart onStartAction = 0
onLoop onLoopAction = 0
onUnitHit onUnitHitAction = 0
onDestructableHit onDestructableHitAction = 0
onEnd onEndAction = 0
onTargetDeath onTargetDeathAction = 0
filterFunction filterFunc = 0
endstruct
//The Knockback Struct
struct Knockback
//public readonly variables
readonly unit caster = null
readonly unit target = null
readonly real timeOver = 0.00
readonly real currentSpeed = 0.00
readonly real movedDistance = 0.00
readonly real duration = 0.00
readonly real distance = 0.00
readonly real knockbackAngle = 0.00
//variables you can change while the unit is being knocked back
public real aoeUnit = STANDARD_UNIT_RADIUS
public real aoeDest = STANDARD_DEST_RADIUS
public integer array userData[3]
//private variables
private real deceleration = 0.00
private real cos = 0.00
private real sin = 0.00
private effect fx = null
private effect kbEffect = null
private integer kbEffectMode = 0
private boolean ranDeathAction = false
private boolean isGround = false
//the actions
private onStart onStartAction = 0
private onLoop onLoopAction = 0
private onUnitHit onUnitHitAction = 0
private onDestructableHit onDestructableHitAction = 0
private onEnd onEndAction = 0
private onTargetDeath onTargetDeathAction = 0
private filterFunction filterFunc = 0
//just some static variables to make the system faster/work
private static timer ticker = null
private static boolexpr unitFilter = null
private static boolexpr destFilter = null
private static code unitActions = null
private static code destActions = null
private static thistype temp = 0
private static rect destRect = Rect(0,0,1,1)
private static real tx = 0.00
private static real ty = 0.00
//ListModule
implement List
//saves the actions in the Knockback Struct (will be used in the create method)
private method assignActions takes KnockbackType kb returns nothing
set .onStartAction = kb.onStartAction
set .onLoopAction = kb.onLoopAction
set .onUnitHitAction = kb.onUnitHitAction
set .onDestructableHitAction = kb.onDestructableHitAction
set .onEndAction = kb.onEndAction
set .onTargetDeathAction = kb.onTargetDeathAction
set .filterFunc = kb.filterFunc
endmethod
//this is the filter for destructables around the target
private static method destFilterMethod takes nothing returns boolean
local real x = GetDestructableX(GetFilterDestructable())
local real y = GetDestructableY(GetFilterDestructable())
return (.tx-x)*(.tx-x) + (.ty-y)*(.ty-y) <= .temp.aoeDest * .temp.aoeDest
endmethod
//this method will run the onDestructableHit action for every destructable hit
private static method runDestActions takes nothing returns nothing
if .temp.onDestructableHitAction != 0 then
call .temp.onDestructableHitAction.evaluate(.temp, GetEnumDestructable())
endif
endmethod
//this method calls the user defined filter method
private static method filterMethod takes nothing returns boolean
if .temp.filterFunc != 0 then
return GetFilterUnit() != .temp.target and temp.filterFunc.evaluate(.temp, GetFilterUnit())
else
return GetFilterUnit() != .temp.target
endif
endmethod
//this method will run the onUnitHit action for every unit hit
private static method runUnitActions takes nothing returns nothing
if .temp.onUnitHitAction != 0 then
call .temp.onUnitHitAction.evaluate(.temp, GetEnumUnit())
endif
endmethod
//cleans up the struct and runs the onEnd actions
private method onDestroy takes nothing returns nothing
if .fx != null then
call DestroyEffect(.fx)
endif
static if USE_KNOCKBACK_EFFECTS then
call DestroyEffect(.kbEffect)
endif
if .onEndAction != 0 then
call .onEndAction.evaluate(this)
endif
call .listRemove()
endmethod
//this method will be called every TIMER_INTERVAL and update the units position as well as runs the onLoop action
private static method onExpire takes nothing returns nothing
local thistype this = .first
local real x
local real y
loop
exitwhen this == 0
//run the onLoop action
if .onLoopAction != 0 then
call .onLoopAction.evaluate(this)
endif
if IsUnitType(.target, UNIT_TYPE_DEAD) or GetUnitTypeId(.target) == 0 and not .ranDeathAction then
if .onTargetDeathAction != 0 then
call .onTargetDeathAction.evaluate(this)
endif
set .ranDeathAction = true
endif
//change the time which has been gone
set .timeOver = .timeOver + TIMER_INTERVAL
set .currentSpeed = .currentSpeed - .deceleration
set .movedDistance = .movedDistance + .currentSpeed
set x = GetUnitX(.target) + (.currentSpeed) * .cos
set y = GetUnitY(.target) + (.currentSpeed) * .sin
///check for destructables arround the target
set .tx = x
set .ty = y
call SetRect(.destRect, -.aoeDest, -.aoeDest, .aoeDest, .aoeDest)
call MoveRectTo(.destRect, x, y)
call EnumDestructablesInRect(.destRect, .destFilter, .destActions)
//check pathability and set new unit position if walkable
if IsTerrainWalkable(x, y) then
call SetUnitX(.target, x)
call SetUnitY(.target, y)
else
static if STOP_WHEN_UNPASSABLE then
call .destroy()
endif
endif
//change the knockback effect if the user want to use it
static if USE_KNOCKBACK_EFFECTS then
if .isGround or not IGNORE_FLYING_UNITS then
if .kbEffectMode == 2 and IsTerrainLand(x, y) then
call DestroyEffect(.kbEffect)
set .kbEffect = AddSpecialEffectTarget(EARTH_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
elseif .kbEffectMode == 1 and IsTerrainShallowWater(x, y) then
call DestroyEffect(.kbEffect)
set .kbEffect = AddSpecialEffectTarget(WATER_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
endif
endif
endif
//refresh group and get units arround target
call GroupRefresh(ENUM_GROUP)
set .temp = this
call GroupEnumUnitsInArea(ENUM_GROUP, x, y, .aoeUnit, .unitFilter)
call ForGroup(ENUM_GROUP, .unitActions)
//if the knockback ended because the target reached the end or the speed is smaller than 0
if .currentSpeed <= 0 or .movedDistance >= .distance then
call .destroy()
endif
//pause the timer if no knockback is active anymore
if .count < 1 then
call PauseTimer(.ticker)
endif
//get next instance
set this = .next
endloop
endmethod
//adds or changes the current effect attached to the target
public method addSpecialEffect takes string fx, string attachPoint returns nothing
if .fx != null then
call DestroyEffect(.fx)
endif
set .fx = AddSpecialEffectTarget(fx, .target, attachPoint)
endmethod
//changes the angle of the knockback
public method operator angle= takes real newAngle returns nothing
set .knockbackAngle = newAngle
set .cos = Cos(.knockbackAngle * bj_DEGTORAD)
set .sin = Sin(.knockbackAngle * bj_DEGTORAD)
endmethod
//use this method to check if a unit is being knocked back
static method isUnitKnockedBack takes unit whichUnit returns boolean
local thistype this = .first
if this == 0 then
return false
endif
loop
exitwhen this == 0
if .target == whichUnit then
return true
endif
set this = .next
endloop
return false
endmethod
//this method creates a new knockback
public static method create takes unit caster, unit target, real distance, real time, real angle, KnockbackType kbType returns thistype
local thistype this = thistype.allocate()
local real speed = 2 * distance / ((time / TIMER_INTERVAL) + 1)
local real deceleration = speed / (time / TIMER_INTERVAL)
local integer i = 0
//reset userData
loop
exitwhen i >= 2
set .userData[i] = 0
set i = i + 1
endloop
//set variables
set .caster = caster
set .target = target
set .knockbackAngle = angle
set .distance = distance
set .duration = time
set .currentSpeed = speed
set .deceleration = deceleration
set .knockbackAngle = angle
set .cos = Cos(.knockbackAngle * bj_DEGTORAD)
set .sin = Sin(.knockbackAngle * bj_DEGTORAD)
set .isGround = not IsUnitType(.target, UNIT_TYPE_FLYING)
//Adds the knockback effect when USE_KNOCKBACK_EFFECTS is set to true
static if USE_KNOCKBACK_EFFECTS then
if .isGround or not IGNORE_FLYING_UNITS then
if IsTerrainLand(GetUnitX(.target), GetUnitY(.target)) then
set .kbEffect = AddSpecialEffectTarget(EARTH_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
set .kbEffectMode = 1
elseif IsTerrainShallowWater(GetUnitX(.target), GetUnitY(.target)) then
set .kbEffect = AddSpecialEffectTarget(WATER_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
set .kbEffectMode = 2
endif
endif
endif
//add the event actions to the struct
call .assignActions(kbType)
//run the onStart action
if .onStartAction != 0 then
call .onStartAction.evaluate(this)
endif
//add the knockback to the List
call .listAdd()
//start the timer if the new instance is the only one
if .count == 1 then
call TimerStart(.ticker, TIMER_INTERVAL, true, function thistype.onExpire)
endif
return this
endmethod
//Initialization
private static method onInit takes nothing returns nothing
set .ticker = CreateTimer()
set .unitFilter = Condition(function thistype.filterMethod)
set .destFilter = Condition(function thistype.destFilterMethod)
set .unitActions = function thistype.runUnitActions
set .destActions = function thistype.runDestActions
endmethod
endstruct
endlibrary
//TESH.scrollpos=20
//TESH.alwaysfold=0
library AutoIndex
//===========================================================================
// Information:
//==============
//
// AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)
// to get get the unique value assigned to a particular unit. The GetUnitId
// function is extremely fast because it inlines directly to a GetUnitUserData
// call. AutoIndex automatically assigns an ID to each unit as it enters the
// map, and instantly frees that ID as the unit leaves the map. Detection of
// leaving units is accomplished in constant time without a periodic scan.
//
// AutoIndex uses UnitUserData by default. If something else in your map
// would conflict with that, you can set the UseUnitUserData configuration
// constant to false, and a hashtable will be used instead. Note that hash-
// tables are about 60% slower.
//
// If you turn on debug mode, AutoIndex will be able to display several
// helpful error messages. The following issues will be detected:
// -Passing a removed or decayed unit to GetUnitId
// -Code outside of AutoIndex has overwritten a unit's UserData value.
// -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
// AutoIndex provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game. Also
// included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
// you to fully utilize AutoIndex's enter/leave detection capabilities in
// conjunction with your structs.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
// ability for you. Close and re-open the map. After that, disable the macro
// to prevent the delay while saving.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
// So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
/*
globals
integer array IntegerData
real array RealData
SomeStruct array SomeStructData
englobals
function Example takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local integer id = GetUnitId(u)
//You now have a unique index for the unit, so you can
//attach or retrieve data about the unit using arrays.
set IntegerData[id] = 5
set RealData[id] = 25.0
set SomeStructData[id] = SomeStruct.create()
//If you have access to the same unit in another function, you can
//retrieve the data by using GetUnitId() and reading the arrays.
endfunction
*/
// The UnitFilter function in the configuration section is provided so that
// you can make AutoIndex completely ignore certain unit-types. Ignored units
// won't be indexed or fire indexed/deindexed events. You may want to filter out
// dummy casters or system-private units, especially ones that use UnitUserData
// internally. xe dummy units are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
// AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
/*
function UnitEntersMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")
endfunction //Using GetUnitId() during Indexed events works fine...
function UnitLeavesMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")
endfunction //So does using GetUnitId() during Deindexed events.
function Init takes nothing returns nothing
call OnUnitIndexed(UnitEntersMap)
call OnUnitDeindexed(UnitLeavesMap)
endfunction
*/
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from the need
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
// OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
// This function returns a unique ID in the range of 1-8190 for the
// specified unit. Returns 0 if a null unit was passed. This function
// inlines directly to GetUnitUserData or LoadInteger if debug mode
// is disabled. If debug mode is enabled, this function will print
// an error message when passed a decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
// This function returns a boolean indicating whether the specified
// unit has been indexed. The only time this will return false is
// for units you have filtered using the UnitFilter function, or
// for xe dummy units. You can use this function to easily detect
// dummy units and avoid performing certain actions on them.
//
// OnUnitIndexed(IndexFunc)
// This function accepts an IndexFunc, which must take a unit and
// return nothing. The IndexFunc will be fired instantly whenever
// a unit enters the map. You may use GetUnitId on the unit. When
// you call this function during map initialization, every existing
// unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
// Same as above, but runs whenever a unit is leaving the map. When
// this event runs, the unit still exists, but it will cease to exist
// as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// How to use AutoData:
//======================
//
// The AutoData module allows you to associate one or more instances
// of the implementing struct with units, as well as iterate through all
// of the instances associated with each unit.
//
// This association is accomplished through the "me" instance member,
// which the module will place in the implementing struct. Whichever unit
// you assign to "me" becomes the owner of that instance. You may change
// ownership by reassigning "me" to another unit at any time, or you may
// make the instance unowned by assigning "me" to null.
//
// AutoData implements the static method operator [] in your struct
// to allow you to access instances from their owning units. For example,
// you may type: local StructName s = StructName[u]. If u has been set
// to own an instance of StructName, s will be set to that instance.
//
// So, what happens if you assign the same owning unit to multiple
// instances? You may use 2D array syntax to access instances assigned to
// the same unit: local StructName s = StructName[u][n], where u is the
// owning unit, and n is the index beginning with 0 for each unit. You
// can access the size of a unit's instance list (i.e. the number of
// instances belonging to the unit) by using the .size instance member.
/*
struct Example
implement AutoData
static method create takes unit u returns Example
local Example this = allocate()
set me = u //Assigning the "me" member from AutoData.
return this
endmethod
endstruct
function Test takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local Example e1 = Example.create(u)
local Example e2 = Example.create(u)
local Example e3 = Example.create(u)
local Example e
call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.
set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.
set e = Example[u] //This is the fastest way to iterate the instances belonging
loop //to a specific unit, starting with the first instance.
exitwhen e == 0 //e will be assigned to 0 when no instances remain.
call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.
set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.
endloop //Thus, index + 1 is next, and index - 1 is previous.
endfunction //This trick allows you to avoid a local counter.
*/
// AutoData restrictions:
// -You may not implement AutoData in any struct which has already
// declared static or non-static method operator [].
// -AutoData will conflict with anything named "me", "size", or
// "index" in the implementing struct.
// -AutoData may not be implemented in structs that extend array.
// -You may not declare your own destroy method. (This restriction
// can be dropped as soon as JassHelper supports module onDestroy).
//
// AutoData information:
// -You do not need to null the "me" member when destroying an
// instance. That is done for you automatically during destroy().
// (But if you use deallocate(), you must null "me" manually.)
// -StructName[u] and StructName[u][0] refer to the same instance,
// which is the first instance that was associated with unit u.
// -StructName[u][StructName[u].size - 1] refers to the instance that
// was most recently associated with unit u.
// -Instances keep their relative order in the list when one is removed.
//
//===========================================================================
// How to use AutoCreate:
//=======================
//
// The AutoCreate module allows you to automatically create instances
// of the implementing struct for units as they enter the game. AutoCreate
// automatically implements AutoData into your struct. Any time an instance
// is automatically created for a unit, that instance's "me" member will be
// assigned to the entering unit.
//
// AutoCreate restrictions:
// -All of the same restrictions as AutoData.
// -If your struct's allocate() method takes parameters (i.e. the parent
// type's create method takes parameters), you must declare a create
// method and pass those extra parameters to allocate yourself.
//
// AutoCreate information:
// -You may optionally declare the createFilter method, which specifies
// which units should recieve an instance as they enter the game. If
// you do not declare it, all entering units will recieve an instance.
// -You may optionally declare the onCreate method, which will run when
// AutoCreate automatically creates an instance. (This is just a stand-
// in until JassHelper supports the onCreate method.)
// -You may declare your own create method, but it must take a single
// unit parameter (the entering unit) if you do so.
/*
struct Example
private static method createFilter takes unit u returns boolean
return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
endmethod
private method onCreate takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" entered the game!")
endmethod
implement AutoCreate
endstruct
*/
//===========================================================================
// How to use AutoDestroy:
//=========================
//
// The AutoDestroy module allows you to automatically destroy instances
// of the implementing struct when their "me" unit leaves the game. AutoDestroy
// automatically implements AutoData into your struct. You must assign a unit
// to the "me" member of an instance for this module to have any effect.
//
// AutoDestroy restrictions:
// -All of the same restrictions as AutoData.
//
// AutoDestroy information:
// -If you also implement AutoCreate in the same struct, remember that it
// assigns the "me" unit automatically. That means you can have fully
// automatic creation and destruction.
/*
struct Example
static method create takes unit u returns Example
local Example this = allocate()
set me = u //You should assign a unit to "me",
return this //otherwise AutoDestroy does nothing.
endmethod //Not necessary if using AutoCreate.
private method onDestroy takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" left the game!")
endmethod
implement AutoDestroy
endstruct
*/
//===========================================================================
// Configuration:
//================
////! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.
globals
private constant integer LeaveDetectAbilityID = 'lvdt'
//This rawcode must match the parameter after "Adef" in the
//ObjectMerger macro above. You may change both if you want.
private constant boolean UseUnitUserData = true
//If this is set to true, UnitUserData will be used. You should only set
//this to false if something else in your map already uses UnitUserData.
//A hashtable will be used instead, but it is about 60% slower.
private constant boolean SafeMode = true
//This is set to true by default so that GetUnitId() will ALWAYS work.
//If if this is set to false, GetUnitId() may fail to work in a very
//rare circumstance: creating a unit that has a default-on autocast
//ability, and using GetUnitId() on that unit as it enters the game,
//within a trigger that detects any order. Set this to false for a
//performance boost only if you think you can avoid this issue.
private constant boolean AutoDataFastMode = true
//If this is set to true, AutoData will utilize one hashtable per time
//it is implemented. If this is set to false, all AutoDatas will share
//a single hashtable, but iterating through the instances belonging to
//a unit will become about 12.5% slower. Your map will break if you
//use more than 255 hashtables simultaneously. Only set this to false
//if you suspect you will run out of hashtable instances.
endglobals
private function UnitFilter takes unit u returns boolean
return true
endfunction
//Make this function return false for any unit-types you want to ignore.
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
//events. The unit parameter "u" to refers to the unit being filtered.
//Do not filter out xe dummy units; they are automatically filtered.
//===========================================================================
// AutoData / AutoCreate / AutoDestroy modules:
//==============================================
function interface AutoCreator takes unit u returns nothing
function interface AutoDestroyer takes unit u returns nothing
globals
hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
endglobals //initialized and shared between all AutoData implementations.
module AutoData
private static hashtable ht
private static thistype array data
private static integer array listsize
private static key typeid //Good thing keys exist to identify each implementing struct.
private unit meunit
private integer id
readonly integer index //The user can avoid using a local counter because this is accessable.
static method operator [] takes unit u returns thistype
return data[GetUnitId(u)]
endmethod //This is as fast as retrieving an instance from a unit gets.
method operator [] takes integer index returns thistype
static if AutoDataFastMode then //If fast mode is enabled...
return LoadInteger(ht, id, index)
else //Each instance has its own hashtable to associate unit and index.
return LoadInteger(AutoData, id, index*8190+typeid)
endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
endmethod //Somehow, this version is 12.5% slower just because of the math.
private method setIndex takes integer index, thistype data returns nothing
static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
call SaveInteger(ht, id, index, data)
else
call SaveInteger(AutoData, id, index*8190+typeid, data)
endif
endmethod
private method remove takes nothing returns nothing
if meunit == null then //If the struct doesn't have an owner...
return //Nothing needs to be done.
endif
loop
exitwhen index == listsize[id] //The last value gets overwritten by 0.
call setIndex(index, this[index + 1]) //Shift each element down by one.
set this[index].index = index //Update the shifted instance's index.
set index = index + 1
endloop
set listsize[id] = listsize[id] - 1
set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
set meunit = null
endmethod
private method add takes unit u returns nothing
if meunit != null then //If the struct has an owner...
call remove() //remove it first.
endif
set meunit = u
set id = GetUnitId(u) //Cache GetUnitId for slight performance boost.
if data[id] == 0 then //If this is the first instance for this unit...
set data[id] = this //Update the value that thistype[u] returns.
endif
set index = listsize[id] //Remember the index for removal.
call setIndex(index, this) //Add to the array.
set listsize[id] = index + 1
endmethod
method operator me takes nothing returns unit
return meunit
endmethod
method operator me= takes unit u returns nothing
if u != null then //If assigning "me" a non-null value...
call add(u) //Add this instance to that unit's array.
else //If assigning "me" a null value...
call remove() //Remove this instance from that unit's array.
endif
endmethod
method operator size takes nothing returns integer
return listsize[id]
endmethod
method destroy takes nothing returns nothing
call deallocate()
call remove() //This makes removal automatic when an instance is destroyed.
endmethod
private static method onInit takes nothing returns nothing
static if AutoDataFastMode then //If fast mode is enabled...
set ht = InitHashtable() //Initialize one hashtable per instance.
else //If fast mode is disabled...
if AutoData == null then //If the hashtable hasn't been initialized yet...
set AutoData = InitHashtable() //Initialize the shared hashtable.
endif
endif
endmethod
endmodule
module AutoCreate
implement AutoData //AutoData is necessary for AutoCreate.
private static method creator takes unit u returns nothing
local thistype this
local boolean b = true //Assume that the instance will be created.
static if thistype.createFilter.exists then //If createFilter exists...
set b = createFilter(u) //evaluate it and update b.
endif
if b then //If the instance should be created...
static if thistype.create.exists then //If the create method exists...
set this = create(u) //Create the instance, passing the entering unit.
else //If the create method doesn't exist...
set this = allocate() //Just allocate the instance.
endif
set me = u //Assign the instance's owner as the entering unit.
static if thistype.onCreate.exists then //If onCreate exists...
call onCreate() //Call it, because JassHelper should do this anyway.
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoCreate(thistype.creator)
endmethod //During module initialization, pass the creator function to AutoIndex.
endmodule
module AutoDestroy
implement AutoData //AutoData is necessary for AutoDestroy.
static method destroyer takes unit u returns nothing
loop
exitwhen thistype[u] == 0
call thistype[u].destroy()
endloop
endmethod //Destroy each instance owned by the unit until none are left.
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoDestroy(thistype.destroyer)
endmethod //During module initialization, pass the destroyer function to AutoIndex.
endmodule
//===========================================================================
// AutoIndex struct:
//===================
function interface IndexFunc takes unit u returns nothing
hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
private keyword getIndex
private keyword getIndexDebug
private keyword isUnitIndexed
private keyword onUnitIndexed
private keyword onUnitDeindexed
struct AutoIndex
private static trigger enter = CreateTrigger()
private static trigger order = CreateTrigger()
private static trigger creepdeath = CreateTrigger()
private static group preplaced = CreateGroup()
private static timer allowdecay = CreateTimer()
private static hashtable ht
private static boolean array dead
private static boolean array summoned
private static boolean array animated
private static boolean array nodecay
private static boolean array removing
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
private static AutoCreator array creators
private static integer creators_n = -1
private static AutoDestroyer array destroyers
private static integer destroyers_n = -1
private static unit array allowdecayunit
private static integer allowdecay_n = -1
private static boolean duringinit = true
private static boolean array altered
private static unit array idunit
//===========================================================================
static method getIndex takes unit u returns integer
static if UseUnitUserData then
return GetUnitUserData(u)
else
return LoadInteger(ht, 0, GetHandleId(u))
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method getIndexDebug takes unit u returns integer
if u == null then
return 0
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return getIndex(u)
endmethod //If debug mode is enabled, use the getIndex method that shows errors.
private static method setIndex takes unit u, integer index returns nothing
static if UseUnitUserData then
call SetUnitUserData(u, index)
else
call SaveInteger(ht, 0, GetHandleId(u), index)
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method isUnitIndexed takes unit u returns boolean
return u != null and idunit[getIndex(u)] == u
endmethod
static method isUnitAnimateDead takes unit u returns boolean
return animated[getIndex(u)]
endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
//===========================================================================
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
set indexfunc = func
call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
static method addAutoCreate takes AutoCreator func returns nothing
set creators_n = creators_n + 1
set creators[creators_n] = func
endmethod
static method addAutoDestroy takes AutoDestroyer func returns nothing
set destroyers_n = destroyers_n + 1
set destroyers[destroyers_n] = func
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
static if UseUnitUserData then
if idunit[getIndex(whichUnit)] == whichUnit then
if getIndex(whichUnit) == data then
call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
else
call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
if idunit[data] != null then
call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
endif
set altered[data] = true
endif
endif
endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
endmethod //Displays an error message if outside code tries to alter a unit's index.
//===========================================================================
private static method allowDecay takes nothing returns nothing
local integer n = allowdecay_n
loop
exitwhen n < 0
set nodecay[getIndex(allowdecayunit[n])] = false
set allowdecayunit[n] = null
set n = n - 1
endloop
set allowdecay_n = -1
endmethod //Iterate through all the units in the stack and allow them to decay again.
private static method detectStatus takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer index = getIndex(u)
local integer n
if idunit[index] == u then //Ignore non-indexed units.
if not IsUnitType(u, UNIT_TYPE_DEAD) then
if dead[index] then //The unit was dead, but now it's alive.
set dead[index] = false //The unit has been resurrected.
//! runtextmacro optional RunAutoEvent("Resurrect")
//If AutoEvents is in the map, run the resurrection events.
if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
set summoned[index] = true //If the unit gained the summoned flag,
set animated[index] = true //it's been raised with Animate Dead.
//! runtextmacro optional RunAutoEvent("AnimateDead")
//If AutoEvents is in the map, run the Animate Dead events.
endif
endif
else
if not removing[index] and not dead[index] and not animated[index] then
set dead[index] = true //The unit was alive, but now it's dead.
set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
//! runtextmacro optional RunAutoEvent("Death")
//If AutoEvents is in the map, run the Death events.
elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
//If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
//If .animated was true and the unit is dead, the unit died and exploded.
//If .removing was true, the unit is being removed or replaced.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
set n = destroyers_n
loop //Destroy AutoDestroy structs for the leaving unit.
exitwhen n < 0
call destroyers[n].evaluate(u)
set n = n - 1
endloop
call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
set idunit[index] = null //Null this unit reference to prevent a leak.
endif
endif
endif
set u = null
return false
endmethod
//===========================================================================
private static method unitEntersMap takes unit u returns nothing
local integer index
local integer n = 0
if getIndex(u) != 0 then
return //Don't index a unit that already has an ID.
endif
static if LIBRARY_xebasic then
if GetUnitTypeId(u) == XE_DUMMY_UNITID then
return //Don't index xe dummy units.
endif
endif
if not UnitFilter(u) then
return //Don't index units that fail the unit filter.
endif
set index = create()
call setIndex(u, index) //Assign an index to the entering unit.
call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering unit.
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //Each of these flags are necessary to detect
set animated[index] = false //when a unit leaves the map.
set nodecay[index] = false
set removing[index] = false
debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
if duringinit then //If a unit enters the map during initialization...
call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
endif //all units are noticed by OnUnitIndexed during initialization.
loop //Create AutoCreate structs for the entering unit.
exitwhen n > creators_n
call creators[n].evaluate(u)
set n = n + 1
endloop
set n = 0
loop //Run the OnUnitIndexed events.
exitwhen n > indexfuncs_n
call indexfuncs[n].evaluate(u)
set n = n + 1
endloop
endmethod
private static method onIssuedOrder takes nothing returns boolean
static if SafeMode then //If SafeMode is enabled, perform this extra check.
if getIndex(GetTriggerUnit()) == 0 then //If the unit doesn't already have
call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
endif
endif
return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
endmethod
private static method initEnteringUnit takes nothing returns boolean
call unitEntersMap(GetFilterUnit())
return false
endmethod
//===========================================================================
private static method afterInit takes nothing returns nothing
set duringinit = false //Initialization is over; set a flag.
call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
call GroupClear(preplaced) //The preplaced units group is
call DestroyGroup(preplaced) //no longer needed, so clean it.
set preplaced = null
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = 15
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
call GroupEnumUnitsOfPlayer(g, Player(i), function AutoIndex.initEnteringUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(order, And(function AutoIndex.onIssuedOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
//The detectStatus method must also fire when a neutral hostile creep dies, in case it was
//sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, function AutoIndex.initEnteringUnit)
//The filter function of an EnterRegion trigger runs instantly when a unit is created.
call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
//After any time elapses, perform after-initialization actions.
call GroupClear(g)
call DestroyGroup(g)
call RemoveRect(bounds)
set g = null
set bounds = null
endmethod
endstruct
//===========================================================================
// User functions:
//=================
function GetUnitId takes unit u returns integer
static if DEBUG_MODE then //If debug mode is enabled...
return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
else //If debug mode is disabled...
return AutoIndex.getIndex(u) //call the normal, inlinable version.
endif
endfunction
function IsUnitIndexed takes unit u returns boolean
return AutoIndex.isUnitIndexed(u)
endfunction
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
endlibrary
//TESH.scrollpos=40
//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 = true
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=0
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=0
//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 = 'e001'
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
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=38
//TESH.alwaysfold=0
library SpellEvent initializer Init requires Table
//*****************************************************************
//* SPELL EVENT LIBRARY
//*
//* written by: Anitarf
//* requires: -Table
//*
//* Maps with many triggered spells require many triggers that run
//* on spell events and whenever a spell is cast, all those
//* triggers need to be evaluated by the game even though only one
//* actually needs to run. This library has been written to reduce
//* the number of triggers in such maps; instead of having a
//* trigger per spell, you can use this library's single trigger
//* to only run the code associated with the spell that's actually
//* being cast.
//*
//* Perhaps more significant than the marginal speed gain is the
//* feature that allows you to access all the spell event
//* responses from all spell events, something that the native
//* functions senselessly do not support. With this system you can
//* for example easily get the target unit of the spell on the
//* casting finish event.
//*
//* All functions following the Response function interface that
//* is defined at the start of this library can be used to respond
//* to damage events. Simply put them on the call list for any of
//* the spell events using the appropriate function:
//*
//* function RegisterSpellChannelResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellCastResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellEffectResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellFinishResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellEndCastResponse takes integer spellId, Response r returns nothing
//*
//* The first event occurs at the very start of the spell, when
//* the spell's casting time begins; most spells have 0 casting
//* time, so in most cases this first event occurs at the same
//* time as the second one, which runs when the unit actually
//* begins casting a spell by starting it's spell animation. The
//* third event occurs when the spell effect actually takes place,
//* which happens sometime into the unit's spell animation
//* depending on the unit's Animation - Cast Point property.
//* The fourth event runs if the unit finishes casting the spell
//* uninterrupted, which might be important for channeling spells.
//* The last event runs when the unit stops casting the spell,
//* regardless of whether it finished casting or was interrupted.
//*
//* If you specify a spell id when registering a function then
//* that function will only run when that ability is cast; only
//* one function per ability per event is supported, if you
//* register more functions then only the last one registered will
//* be called. If, however, you pass 0 as the ability id parameter
//* then the registered function will run for all spells. Up to
//* 8190 functions can be registered this way for each event.
//* These functions will be called before the ability's specific
//* function in the order they were registered.
//*
//* This library provides it's own event responses that work
//* better than the Blizzard's bugged native cast event responses.
//* They still aren't guaranteed to work after a wait, but aside
//* from that they will work in response functions no matter what
//* event they are registered to.
//*
//* Here are usage examples for all event responses:
//*
//* local integer a = SpellEvent.AbilityId
//* local unit u = SpellEvent.CastingUnit
//* local unit t = SpellEvent.TargetUnit
//* local item i = SpellEvent.TargetItem
//* local destructable d = SpellEvent.TargetDestructable
//* local location l = SpellEvent.TargetLoc
//* local real x = SpellEvent.TargetX
//* local real y = SpellEvent.TargetY
//* local boolean b = SpellEvent.CastFinished
//*
//* SpellEvent.TargetLoc is provided for odd people who insist on
//* using locations, note that if you use it you have to cleanup
//* the returned location yourself.
//*
//* SpellEvent.CastFinished boolean is intended only for the
//* EndCast event as it tells you whether the spell finished or
//* was interrupted.
//*
//*****************************************************************
// use the RegisterSpell*Response functions to add spell event responses to the library
public function interface Response takes nothing returns nothing
// ================================================================
private keyword casterTable
private keyword effectDone
private keyword init
private keyword get
private struct spellEvent
static HandleTable casterTable
boolean effectDone=false
integer AbilityId
unit CastingUnit
unit TargetUnit
item TargetItem=null
destructable TargetDestructable=null
real TargetX=0.0
real TargetY=0.0
boolean CastFinished=false
method operator TargetLoc takes nothing returns location
return Location(.TargetX, .TargetY)
endmethod
private static method create takes nothing returns spellEvent
return spellEvent.allocate()
endmethod
static method init takes nothing returns spellEvent
local spellEvent s=spellEvent.allocate()
set s.AbilityId = GetSpellAbilityId()
set s.CastingUnit = GetTriggerUnit()
set s.TargetUnit = GetSpellTargetUnit()
if s.TargetUnit != null then
set s.TargetX = GetUnitX(s.TargetUnit)
set s.TargetY = GetUnitY(s.TargetUnit)
else
set s.TargetDestructable = GetSpellTargetDestructable()
if s.TargetDestructable != null then
set s.TargetX = GetDestructableX(s.TargetDestructable)
set s.TargetY = GetDestructableY(s.TargetDestructable)
else
set s.TargetItem = GetSpellTargetItem()
if s.TargetItem != null then
set s.TargetX = GetItemX(s.TargetItem)
set s.TargetY = GetItemY(s.TargetItem)
else
set s.TargetX = GetSpellTargetX()
set s.TargetY = GetSpellTargetY()
endif
endif
endif
set spellEvent.casterTable[s.CastingUnit]=integer(s)
return s
endmethod
static method get takes unit caster returns spellEvent
return spellEvent(spellEvent.casterTable[caster])
endmethod
method onDestroy takes nothing returns nothing
call spellEvent.casterTable.flush(.CastingUnit)
set .CastingUnit=null
endmethod
endstruct
globals
spellEvent SpellEvent=0
endglobals
// ================================================================
//! textmacro spellEvent_make takes name
globals
private Response array $name$CallList
private integer $name$CallCount=0
private Table $name$Table
endglobals
private function $name$Calls takes nothing returns nothing
local integer i=0
local integer id=GetSpellAbilityId()
local spellEvent previous=SpellEvent
set SpellEvent=spellEvent.get(GetTriggerUnit())
loop
exitwhen i>=$name$CallCount
call $name$CallList[i].evaluate()
set i=i+1
endloop
if $name$Table.exists(id) then
call Response($name$Table[id]).evaluate()
endif
set SpellEvent=previous
endfunction
function RegisterSpell$name$Response takes integer spellId, Response r returns nothing
if spellId==0 then
set $name$CallList[$name$CallCount]=r
set $name$CallCount=$name$CallCount+1
else
set $name$Table[spellId]=integer(r)
endif
endfunction
//! endtextmacro
//! runtextmacro spellEvent_make("Channel")
//! runtextmacro spellEvent_make("Cast")
//! runtextmacro spellEvent_make("Effect")
//! runtextmacro spellEvent_make("Finish")
//! runtextmacro spellEvent_make("EndCast")
private function Channel takes nothing returns nothing
call spellEvent.init()
call ChannelCalls()
endfunction
private function Cast takes nothing returns nothing
call CastCalls()
endfunction
private function Effect takes nothing returns nothing
local spellEvent s=spellEvent.get(GetTriggerUnit())
if s!=0 and not s.effectDone then
set s.effectDone=true
call EffectCalls()
endif
endfunction
private function Finish takes nothing returns nothing
set spellEvent.get(GetTriggerUnit()).CastFinished=true
call FinishCalls()
endfunction
private function EndCast takes nothing returns nothing
call EndCastCalls()
call spellEvent.get(GetTriggerUnit()).destroy()
endfunction
// ================================================================
private function InitTrigger takes playerunitevent e, code c returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, e )
call TriggerAddAction(t, c)
set t=null
endfunction
private function Init takes nothing returns nothing
set spellEvent.casterTable=HandleTable.create()
set ChannelTable=Table.create()
set CastTable=Table.create()
set EffectTable=Table.create()
set FinishTable=Table.create()
set EndCastTable=Table.create()
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function Channel)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CAST, function Cast)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_EFFECT, function Effect)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_FINISH, function Finish)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function EndCast)
endfunction
endlibrary
//TESH.scrollpos=153
//TESH.alwaysfold=0
library UnitStatus initializer Init requires TimerUtils, AutoIndex, optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library exists because oftentimes, a mapmaker needs to apply a specific
//* status effect to a unit. If he were to do it on his own, he'd need to find a
//* way to get the status effects to stack properly with one another and with
//* multiple instances of themselves. This script does just that with the five
//* most useful status options in WC3 that cannot be reproduced perfectly with
//* just code and not actual in-game buffs.
//*
//* WARNING: This library uses the following buffs. If you use any of the listed
//* buffs in your map, they will not stack with this script's buffs and
//* it may not work. If you need these effects, then only use this
//* script to achieve them.
//* - Drunken Haze
//* - Soul Burn
//* - Ensnare
//* - Storm Bolt
//* - Hurl Boulder
//*
//******************************************************************************
//*
//* The following status effects are supported by this library.
//*
//* - Silence
//* This status disables a given unit's ability to cast spells. This works
//* properly on spell immune and normal units.
//*
//* Example Usage:
//* call SilenceUnit(u, true) //Silences a unit
//* call SilenceUnit(u, false) //Removes silence from a unit
//* call SilenceUnitTimed(u, 4.0) //Adds a timed silence to a unit
//*
//* - Disarm
//* This status disables a given unit's ability to attack and attack ground.
//* This will not work properly on spell immune units because of a Blizzard
//* bug. The function calls will return false when used on units that are
//* spell immune. This status disables both ranged and melee attacks.
//*
//* Example Usage:
//* call DisarmUnit(u, true) //Disarms a unit
//* call DisarmUnit(u, false) //Removes disarm from a unit
//* call DisarmUnitTimed(u, 4.0) //Adds a timed disarm to a unit
//*
//* - Ensnare
//* This status disables a given unit's ability to move. This works properly
//* on spell immune and normal units. Because of the nature of the ability
//* this is based upon, it will exhibit strange behavior on flying units. If
//* this is used on flying units, they will retain their flying height, but
//* be treated as ground units by the game. There is unfortunately no
//* workaround for this behavior. It is recommended to not use it on flying
//* units because for that reason.
//*
//* Example Usage:
//* call EnsnareUnit(u, true) //Ensnares a unit
//* call EnsnareUnit(u, false) //Removes ensnare from a unit
//* call EnsnareUnitTimed(u, 4.0) //Adds a timed ensnare to a unit
//*
//* - Stun
//* This status is identical in nature to the standard melee stun. A stunned
//* unit cannot move, attack, or cast spells. Both spell immune and normal
//* units can be stunned.
//*
//* Example Usage:
//* call StunUnit(u, true) //Ensnares a unit
//* call StunUnit(u, false) //Removes stun from a unit
//* call StunUnitTimed(u, 4.0) //Adds a timed stun to a unit
//*
//* - Disable
//* Disable is a unique status meant to be used as a replacement for pausing a
//* unit using the PauseUnit native. Pausing a unit has the negative side
//* effects of removing the unit's command card and not preserving queued
//* orders. Disabling a unit retains both of those sought features. Disable is
//* a non-graphical stun at its core that is always 'underneath' stun when
//* both are applied on a unit at once as listed below.
//*
//* Disable also interacts with stun in a unique way.
//* - If stun is used on a disabled unit, the unit becomes stunned instead.
//* - If stun ends on a disabled unit, the unit is disabled until the disable
//* ends.
//* - If disable is used on a stunned unit, the unit remains visibly stunned.
//*
//* Example Usage:
//* call DisableUnit(u, true) //Disables a unit
//* call DisableUnit(u, false) //Removes disable from a unit
//* call DisableUnitTimed(u, 4.0) //Adds a timed disable to a unit
//*
//* WARNING: These status effects, when used on invulnerable units, will have
//* absolutely no effect.
//*
//******************************************************************************
//*
//* There is a textmacro call below that runs a series of ObjectMerger calls
//* inside of an embedded .lua script. This sub-script generates all of the
//* abilities and buffs required by this library automatically for you. Enable
//* it by uncommenting the textmacro, saving your map, closing your map,
//* reopening your map, and commenting the macro again.
//*
//* Note that you, as the user, may edit any of the buff icons or tooltips to
//* your liking. It is not recommended to edit the data fields for the spells,
//* though. They are the way they are so that they will work.
//*
//* WARNING: The ObjectMerger call for Disarm's ability seems to be unable to
//* properly configure the "Data - Attacks Prevented" field. If you
//* find that the DisarmUnit call isn't functioning as intended, then
//* set that field to "None", save your map, set the field back to
//* "Melee, Ranged", and then save your map again. It should now work
//* properly.
//*
//* You may change the raw ids of any of the generated abilities as needed for
//* your map. (If there are conflicts) If you want to do so, then make sure that
//* you change the raw ids inside all affected ObjectMerger calls and the
//* constants in the globals block below.
//*
//* WARNING: If you choose to change the raw id for the Silence ability, do NOT
//* let the buff raw id match the ability raw id. If you do, the buff's
//* special effect fields will not show in-game. (This may or may not
//* even affect you, but it is worth noting regardless)
//*
//* xebasic is an optional requirement. If you have xebasic in your map, this
//* script will use xebasic's dummy unit id instead of the constant below. If
//* you do not have xebasic in your map, for this to work you will need to make
//* (if you have not already) a dummy unit caster for your map. Put its raw id
//* below in the DUMMY_UNITID constant field.
//*
//* Enjoy!
//*
//* Uncomment the following textmacro to create the abilities.
////! runtextmacro GenerateAbilities()
//! textmacro GenerateAbilities
//! externalblock extension=lua ObjectMerger $FILENAME$
//! i
//! i function set(field, value)
//! i makechange(current, field, value)
//! i end
//! i function setl(field, level, value)
//! i makechange(current, field, level, value)
//! i end
//! i
//! i setobjecttype("abilities")
//! i
//! i createobject("AHtb", "stun"); set("anam", "Stun Ability")
//! i set ("aani", "") ; set ("aart", "") ; set ("amat", "") ; set ("amsp", 0) ; setl("Htb1", 1, 0.0);
//! i setl("aran", 1, 99999.0); setl("acdn", 1, 0.0); setl("ahdu", 1, 0.0) ; setl("adur", 1, 0.0) ; set ("arlv", 6) ;
//! i set ("alev", 1) ; setl("amcs", 1, 0) ; set ("arac", "other"); setl("atar", 1, "notself");
//! i
//! i createobject("ANso", "&sil"); set("anam", "Silence Ability")
//! i set ("aart", "") ; setl("Nso1", 1, 0.0); setl("Nso3", 1, 0.0) ; setl("Nso2", 1, 99999.0) ; setl("aran", 1, 99999.0);
//! i setl("abuf", 1, "&SIL"); setl("acdn", 1, 0.0); setl("ahdu", 1, 0.0) ; setl("adur", 1, 0.0) ; set ("arlv", 1);
//! i set ("alev", 1) ; setl("amcs", 1, 0) ; set ("arac", "other"); setl("atar", 1, "notself");
//! i
//! i createobject("ANdh", "&arm"); set("anam", "Disarm Ability")
//! i set ("aart", "") ; set ("amat", "") ; set ("amac", 0.0) ; set ("amsp", 0) ; setl("Nsi1", 1, 3) ;
//! i setl("Nsi2", 1, 0.0) ; setl("Nsi3", 1, 0.0) ; setl("aare", 1, 0.0); setl("aran", 1, 99999.0); setl("abuf", 1, "&ARM");
//! i setl("acdn", 1, 0.0) ; setl("ahdu", 1, 0.0) ; setl("adur", 1, 0.0); set ("arlv", 6) ; set ("alev", 1) ;
//! i setl("amcs", 1, 0) ; set ("arac", "other") ; setl("atar", 1, "notself");
//! i
//! i createobject("ACen", "&ens"); set("anam", "Ensnare Ability")
//! i set ("aart", "") ; set ("amat", "") ; set ("amsp", 0) ; setl("Ens1", 1, -1.0); setl("Ens2", 1, -1.0);
//! i setl("aran", 1, 99999.0) ; setl("abuf", 1, "&EN1,&EN2"); setl("acdn", 1, 0.0); setl("ahdu", 1, 0.0) ; set ("aher", 1) ;
//! i setl("adur", 1, 0.0) ; set ("arlv", 6) ; set ("alev", 1) ; setl("amcs", 1, 0) ; set ("arac", "other");
//! i setl("atar", 1, "notself"); set ("areq", "") ; set("ansf", "") ;
//! i
//! i createobject("ACtb", "&dis"); set("anam", "Disable Ability")
//! i set ("aani", "") ; set ("aart", "") ; set ("amat", "") ; set ("amsp", 0) ; setl("Ctb1", 1, 0.0) ;
//! i setl("aran", 1, 99999.0) ; setl("abuf", 1, "&DIS"); setl("acdn", 1, 0.0); setl("ahdu", 1, 0.0); setl("adur", 1, 0.0) ;
//! i set ("aher", 1) ; set ("arlv", 6) ; set ("alev", 1) ; setl("amcs", 1, 0) ; set ("arac", "other");
//! i setl("atar", 1, "notself");
//! i
//! i setobjecttype("buffs")
//! i
//! i createobject("BNso", "&SIL"); set("fnam", "Disabled (Spells)")
//! i set("ftip", "Disabled (Spells)") ; set("fube", "This unit cannot cast spells.");
//! i set("fart", "ReplaceableTextures\\CommandButtons\\BTNCancel.blp"); set("ftat", "") ; set("fta0", "");
//! i
//! i createobject("BNdh", "&ARM"); set("fnam", "Disabled (Attacks)")
//! i set("ftip", "Disabled (Attacks)") ; set("fube", "This unit cannot attack.");
//! i set("fart", "ReplaceableTextures\\CommandButtons\\BTNCancel.blp"); set("ftat", "") ; set("fta0", "");
//! i
//! i createobject("Beng", "&EN1"); set("fnam", "Disabled (Movement)")
//! i set("ftip", "Disabled (Movement)") ; set("fube", "This unit cannot move." );
//! i set("fart", "ReplaceableTextures\\CommandButtons\\BTNCancel.blp"); set("ftat", "") ; set("frac", "other");
//! i
//! i createobject("Bena", "&EN2"); set("fnam", "Disabled (Movement)")
//! i set("ftip", "Disabled (Movement)") ; set("fube", "This unit cannot move.");
//! i set("fart", "ReplaceableTextures\\CommandButtons\\BTNCancel.blp"); set("ftat", "") ; set("fta0", "");
//! i set("frac", "other");
//! i
//! i createobject("BPSE", "&DIS"); set("fnam", "Disabled")
//! i set("ftip", "Disabled") ; set("fube", "This unit cannot do anything.");
//! i set("fart", "ReplaceableTextures\\CommandButtons\\BTNCancel.blp"); set("ftat", "") ; set("fta0", "")
//! i
//! endexternalblock
//! endtextmacro GenerateAbilities
globals
//General constants
private constant integer DUMMY_UNITID = 'e000' //Replace this with your dummy's id
//Stun constants
private constant integer STUN_ID = 'stun' //This needs to match the ObjectMerger call above
private constant integer STUN_ORDER_ID = 852095 //Order id to stun a unit
private constant integer STUN_BUFF_ID = 'BPSE' //Normal storm bolt stun buff
//Silence constants
private constant integer SILENCE_ID = '&sil' //This needs to match the ObjectMerger call above
private constant integer SILENCE_ORDER_ID = 852668 //Order id to soul burn a unit
private constant integer SILENCE_BUFF_ID = '&SIL' //Generated soul burn based buff
//Disarm constants
private constant integer DISARM_ID = '&arm' //This needs to match the ObjectMerger call above
private constant integer DISARM_ORDER_ID = 852585 //Order id to drunken haze a unit
private constant integer DISARM_BUFF_ID = '&ARM' //Generated drunken haze based buff
//Ensnare constants
private constant integer ENSNARE_ID = '&ens' //This needs to match the ObjectMerger call above
private constant integer ENSNARE_ORDER_ID = 852106 //Order id to ensnare a unit
private constant integer ENSNARE_BUFF_ID = '&EN1' //Generated ensnare based buff (ground)
private constant integer ENSNARE_BUFF_ID2 = '&EN2' //Generated ensnare based buff (air)
//Disable constants
private constant integer DISABLE_ID = '&dis' //This needs to match the ObjectMerger call above
private constant integer DISABLE_ORDER_ID = 852252 //Order id to hurl boulder at a unit
private constant integer DISABLE_BUFF_ID = '&DIS' //Generated hurl boulder based buff
endglobals
globals
private unit Caster = null //Dummy caster
private timer Temp = null //For callback referencing
endglobals
//******************************************************************************
//! textmacro UnitStatus_GenerateBase takes TYPE, CONSTANT, STRUCTNAME, INJECTPOSTBUFF, INJECTCHECK, INJECTPREBUFF
private struct clear$TYPE$
unit u = null
//Exists only for clearing on Add/Remove situations
static method create takes unit t returns thistype
local thistype c = thistype.allocate()
set c.u = t
return c
endmethod
endstruct
private function TimerRemove$TYPE$ takes nothing returns nothing
local clear$TYPE$ c = clear$TYPE$(GetTimerData(GetExpiredTimer()))
local unit whichUnit = c.u
if $TYPE$Counter[GetUnitId(c.u)] == 0 then
//Make sure we should still remove it
call UnitRemoveAbility(c.u, $CONSTANT$_BUFF_ID)
$INJECTPOSTBUFF$
endif
call ReleaseTimer(GetExpiredTimer())
call c.destroy()
set whichUnit = null
endfunction
function $TYPE$Unit takes unit whichUnit, boolean flag returns boolean
local integer id = GetUnitId(whichUnit)
local boolean b = true
if whichUnit == null then
//Target can't be null
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null unit given to $TYPE$Unit")
return false
endif
if flag then
if $TYPE$Counter[id] == 0 $INJECTCHECK$ then
//Buff the unit
$INJECTPREBUFF$
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), true)
set b = IssueTargetOrderById(Caster, $CONSTANT$_ORDER_ID, whichUnit)
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), false)
endif
if not b then
//Cast failed somehow
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Unit could not be buffed ($TYPE$Unit)")
return false
endif
set $TYPE$Counter[id] = $TYPE$Counter[id] + 1
elseif $TYPE$Counter[id] > 0 then //Only run this if unit is buffed at all
//Decrement Counter
set $TYPE$Counter[id] = $TYPE$Counter[id] - 1
if $TYPE$Counter[id] == 0 then
//Clear the buff
if GetUnitAbilityLevel(whichUnit, $CONSTANT$_BUFF_ID) == 0 then
//Remove it in a 0.01s callback because it hasn't been applied yet
set Temp = NewTimer()
call SetTimerData(Temp, integer(clear$TYPE$.create(whichUnit)))
call TimerStart(Temp, 0.01, false, function TimerRemove$TYPE$)
else
call UnitRemoveAbility(whichUnit, $CONSTANT$_BUFF_ID)
$INJECTPOSTBUFF$
endif
endif
else
//Unit doesn't have the buff we're trying to remove
return false
endif
return true
endfunction
private struct $STRUCTNAME$
timer t
unit tar
real dur
private static method end takes nothing returns nothing
call thistype(GetTimerData(GetExpiredTimer())).destroy()
endmethod
static method start takes unit target, real duration returns boolean
local thistype s
local boolean b = $TYPE$Unit(target, true)
if not b then
//Failed, return false
return false
endif
set s = thistype.allocate()
set s.tar = target
set s.dur = duration
set s.t = NewTimer()
call SetTimerData(s.t, integer(s))
call TimerStart(s.t, duration, false, function $STRUCTNAME$.end)
return true
endmethod
private method onDestroy takes nothing returns nothing
call $TYPE$Unit(.tar, false)
call ReleaseTimer(.t)
endmethod
endstruct
function $TYPE$UnitTimed takes unit whichUnit, real duration returns boolean
if duration <= 0.01 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Less than 0.01 duration given to $TYPE$UnitTimed")
return false
endif
return $STRUCTNAME$.start(whichUnit, duration)
endfunction
//! endtextmacro
//******************************************************************************
//Declare all necessary globals first
globals
private integer array StunCounter
private integer array SilenceCounter
private integer array DisarmCounter
private integer array EnsnareCounter
private integer array DisableCounter
endglobals
//Special functions for Stun
private function StunLingeringDisable takes unit u returns nothing
local integer id = GetUnitId(u)
if DisableCounter[id] > 0 then
//Add the disabled buff because it lingers on
call UnitShareVision(u, GetOwningPlayer(Caster), true)
call IssueTargetOrderById(Caster, DISABLE_ORDER_ID, u)
call UnitShareVision(u, GetOwningPlayer(Caster), false)
endif
endfunction
private function StunFinishDisable takes unit u returns nothing
local integer id = GetUnitId(u)
if DisableCounter[id] > 0 then
//Remove the disable buff for first refcount stun
call UnitRemoveAbility(u, DISABLE_BUFF_ID)
endif
endfunction
//Generates all of the base code
//! runtextmacro UnitStatus_GenerateBase("Stun" , "STUN" , "stun" , "call StunLingeringDisable(whichUnit)", "", "call StunFinishDisable(whichUnit)")
//! runtextmacro UnitStatus_GenerateBase("Silence", "SILENCE", "silence", "", "", "")
//! runtextmacro UnitStatus_GenerateBase("Disarm" , "DISARM" , "disarm" , "", "", "")
//! runtextmacro UnitStatus_GenerateBase("Ensnare", "ENSNARE", "ensnare", "call UnitRemoveAbility(whichUnit, ENSNARE_BUFF_ID2)", "", "")
//! runtextmacro UnitStatus_GenerateBase("Disable", "DISABLE", "disable", "", "and StunCounter[id] == 0", "")
private function Init takes nothing returns nothing
static if LIBRARY_xebasic then
set Caster = CreateUnit(Player(15), XE_DUMMY_UNITID, 0., 0., 0.)
else
set Caster = CreateUnit(Player(15), DUMMY_UNITID , 0., 0., 0.)
endif
call UnitRemoveAbility(Caster, 'Amov')
if GetUnitAbilityLevel(Caster, 'Aloc') == 0 then
call UnitAddAbility(Caster, 'Aloc') //xe dummies don't have this automatically
endif
//Add the abilities
call UnitAddAbility(Caster, STUN_ID)
call UnitAddAbility(Caster, SILENCE_ID)
call UnitAddAbility(Caster, DISARM_ID)
call UnitAddAbility(Caster, ENSNARE_ID)
call UnitAddAbility(Caster, DISABLE_ID)
endfunction
endlibrary
//TESH.scrollpos=29
//TESH.alwaysfold=0
scope Fireball initializer onInit
globals
KnockbackType FireBallKnockbackType
private constant string EFFECT = "Environment\\UndeadBuildingFire\\UndeadLargeBuildingFire1.mdl"
private constant string EXPLOSION_EFFECT = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
endglobals
//let us explode explosive barrels
function onDestHit takes Knockback kb, destructable hit returns nothing
if GetDestructableTypeId(hit) == 'LTex' then
call kb.destroy()
call DestroyEffect(AddSpecialEffect(EXPLOSION_EFFECT, GetDestructableX(hit), GetDestructableY(hit)))
call KillDestructable(hit)
call UnitDamagePoint(kb.caster, 0.15, 175., GetDestructableX(hit), GetDestructableY(hit), 150.00, false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_FIRE, null)
call RemoveDestructable(hit)
else
call KillDestructable(hit)
endif
endfunction
//this filters all units out that should not be knocked back
function filterFunc takes Knockback kb, unit enum returns boolean
return IsUnitEnemy(enum, GetOwningPlayer(kb.caster)) and not IsUnitType(enum, UNIT_TYPE_FLYING)
endfunction
//when the units hits another unit
function onUnitHit takes Knockback kb, unit hit returns nothing
local real angle = (Atan2((GetUnitY(kb.target)-GetUnitY(hit)), (GetUnitX(kb.target)-GetUnitX(hit))) - 180) * bj_RADTODEG
call UnitDamageTarget(kb.caster, hit, 7.5, false, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
if not Knockback.isUnitKnockedBack(hit) then
call Knockback.create(kb.caster, hit, kb.distance * 0.5, kb.duration * 0.5, angle, FireBallKnockbackType)
endif
endfunction
//start the knockback
private function onCast takes nothing returns nothing
local real angle = Atan2((GetUnitY(SpellEvent.TargetUnit)-GetUnitY(SpellEvent.CastingUnit)), (GetUnitX(SpellEvent.TargetUnit)-GetUnitX(SpellEvent.CastingUnit))) * bj_RADTODEG
local Knockback kb = Knockback.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit, 500., 1.5, angle, FireBallKnockbackType)
call kb.addSpecialEffect(EFFECT, "origin")
endfunction
//init
private function onInit takes nothing returns nothing
call RegisterSpellEffectResponse('A004', onCast)
set FireBallKnockbackType = KnockbackType.create()
set FireBallKnockbackType.onDestructableHitAction = onDestHit
set FireBallKnockbackType.onUnitHitAction = onUnitHit
set FireBallKnockbackType.filterFunc = filterFunc
//mehhhh
call Cheat("iseedeadpeople")
endfunction
endscope