//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
version 1.01
- xedamage edit line 229,575 (patch 1.32 support)
- xecast edit line 236,466 (patch 1.32 support)
- xefx edit line 63,205,238 (patch 1.32 support)
- xepreload edit line 71 (patch 1.32 support)
- AutoIndex edit line 587,605 (patch 1.32 support)
- RegisterPlayerUnitEvent edit line 40 (patch 1.32 support)
Vexorian - XE, TimerUtils
Rising_Dusk - TerrainPathability, GroupUtils
Magtheridon96 - RegisterPlayerUnitEvent
Maker - Arcing Text Tag
Captain_Griffen - LightLeaklessDamageDetect
grim001 - AutoIndex
Warcraft 3 patch - 1.32 (SD)
[ENG] Import checkboxes:
[ ] Copy/Import dummy.mdx model
[ ] Copy all custom units
[ ] Copy all custom abilities
[ ] Copy all custom buffs
[ ] Copy Triggers
[ ] Configure xebasic: XE_DUMMY_UNITID
[ ] Configure AutoIndex: LeaveDetectAbilityID
[ ] Configure MagicStream, BuffSteal, BrillianceAura, SlowTime
[ ] Disable DEBUG_MODE
//edit line 587,605 patch 1.32
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. AutoIndex differs from
// other unit indexing libraries because it automatically assigns an ID to each
// unit as it enters the map, and automatically frees that ID as the unit leaves
// the map. This gives you several advantages as the user:
// -The GetUnitId function inlines directly to a GetUnitUserData call (or a
// LoadInteger call if the UseUnitUserData constant is set to false.)
// -You don't need to manually free IDs as units leave the map.
// -Detecting removing units to free their indexes is O(1), and less costly
// performance-wise than a timer scanning the map for removed units.
//
// If you turn on debug mode, AutoIndex will become slower, but it will be able
// to display several helpful error messages. It can detect the following issues:
// -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 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.
//
// AutoIndex provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game, and
// handle the creation or destruction of attached data or other things. Also
// included is the AutoStruct module, which automatically creates and destroys
// struct instances associated with units as they enter and leave the game.
//
//===========================================================================
// 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 save delay.
//
//===========================================================================
// 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(...)
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)+" was indexed with the ID "+I2S(GetUnitId(u)))
endfunction
function UnitLeavesMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" was deindexed with the ID "+I2S(GetUnitId(u)))
endfunction
function Init takes nothing returns nothing
call OnUnitIndexed(UnitEntersMap)
call OnUnitDeindexed(UnitLeavesMap)
endfunction
*/
// As you can see, it works perfectly fine to call GetUnitId() on a unit
// during either of these events.
//
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from needing
// 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. Ruturns 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 the AutoStruct module:
//===================================
//
// The AutoStruct module allows you to automatically create and destroy
// struct instances as units enter and leave the game. An instance of the
// implementing struct will be created each time a unit enters the game,
// and destroyed when that unit leaves the game. (You cannot create or dest-
// roy instances manually.) This means that you should consider each inst-
// ance of an AutoStruct to be "owned by" a specific unit.
//
// AutoStruct restrictions:
// -You may not implement AutoStruct in any struct that declares its
// own create, destroy, or operator[] methods.
// -You may not manually create or destroy structs implementing AutoStruct.
// -AutoStruct may be implemented in structs that extend interfaces or
// other structs, but only if the allocate() method takes no parameters.
// (The restriction of allocate() taking no parameters may be changed soon.)
// -AutoStruct will not work with structs that extend array, since they
// can't be created or destroyed.
//
// AutoStruct features:
// -An instance of the implementing struct will be created each time a
// unit enters the game. That instance will be destroyed when that unit
// leaves the game. The struct will always exist while the unit does.
// -You can retrieve an AutoStruct from a unit by using the syntax:
// local StructName mystruct = StructName[unit]
// //This inlines to a GetUnitId() call + array lookup.
// -You can refer to the unit that owns the instance by the member "me":
// //Outside of the struct: call BJDebugMsg(GetUnitName(mystruct.me))
// //From within the struct: call BJDebugMsg(GetUnitName(me))
// -You can use the optional methods onCreate and onDestroy to notice when
// instances of a struct implementing AutoStruct are created and destroyed.
// This is equivalent to detecting a unit entering and leaving the game.
/*
struct Example
private method onCreate takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" has entered the game!")
endmethod
private method onDestroy takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" has left the game!")
endmethod
implement AutoStruct
endstruct
*/
// -You can filter which units will recieve an AutoStruct by using the
// optional static method createFilter. The createFiler method must take
// a unit parameter and return a boolean (true if created, false if not).
/*
struct Example
private static method createFiler takes unit u returns boolean
return GetUnitTypeId(u) == 'hfoo' //Only Footmen will recieve this AutoStruct.
endmethod
implement AutoStruct
endstruct
*/
//
//===========================================================================
// Configuration:
//================
static if CI_NEW_MAP then
// external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
endif
//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
//ObjectMergermacro above. You can 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.
endglobals
private function UnitFilter takes unit u returns boolean
return true
endfunction
//Make this function return false for any unit-types you wish to be ignored.
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
//events. Use the unit u parameter to refer to the unit being filtered.
//You do not need to filter out xe dummy units; they are already filtered.
//===========================================================================
// AutoStruct module:
//====================
function interface AutoStructCreator takes unit u returns integer
function interface AutoStructDestroyer takes unit u returns nothing
module AutoStruct
private static thistype array data
unit me //The unit that "owns" this struct instance is referred to as "me".
static method operator [] takes unit u returns thistype
return data[GetUnitId(u)] //Return the struct instance associated with the unit.
endmethod
private static method create takes unit u returns thistype
local thistype this
static if thistype.createFilter.exists then //If the createFiler exists...
if not createFilter(u) then //If the unit fails the createFilter...
return 0 //Don't allocate a struct for this unit.
endif
endif
set this = allocate() //Allocate the struct.
set me = u //Assign the "me" unit of this struct to the entering unit.
set data[GetUnitId(u)] = this //Attach this instance to the unit.
static if thistype.onCreate.exists then //If onCreate exists...
call onCreate() //Call the onCreate() method for this struct.
endif
return this
endmethod
//create is private; the user doesn't create AutoStructs. They are created
//automatically when a unit enters the game and passes the createFilter.
private method destroy takes nothing returns nothing
endmethod
//destroy is private; the user doesn't destroy AutoStructs. They are destroyed
//automatically when their corresponding units leave the game.
private static method destroyer takes unit u returns nothing
local thistype this = thistype[u] //Get the instance of the struct for this unit.
if this != 0 then //If it has an instance...
call deallocate() //Deallocate it, calling onDestroy.
set .data[GetUnitId(me)] = 0 //Null the unit's associated struct instance.
set me = null //Null the unit reference.
endif
endmethod
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoStruct(thistype.create, thistype.destroyer)
//Pass pointers to the create and destroyer functions to AutoIndex, so
//that it can create/destroy instances as units are indexed/deindexed.
endmethod
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 AutoStructCreator array creators
private static AutoStructDestroyer array destroyers
private static integer autostructs_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
set indexfunc = func
//During initialization, evaluate the indexfunc for every preplaced unit.
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 addAutoStruct takes AutoStructCreator creator, AutoStructDestroyer destroyer returns nothing
set autostructs_n = autostructs_n + 1
set creators[autostructs_n] = creator
set destroyers[autostructs_n] = destroyer
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod
//Intercepts whenever RemoveUnit or 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
endmethod
//In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
//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.
//! runtextmacro optional TransportUnload()
//If TransportEvents is in the map, remove the dead unit from whatever transport it's in.
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.
//! runtextmacro optional TransportUnload()
//If TransportEvents is in the map, remove the leaving unit from whatever transport it's in.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
//! runtextmacro optional DestroyUnitLists()
//If UnitListModule is in the map, destroy all of the UnitLists associated with the leaving unit.
set n = autostructs_n
loop //Destroy AutoStructs for the leaving unit.
exitwhen n < 0
call destroyers[n].evaluate(u)
set n = n - 1
endloop
//! runtextmacro optional TransportClean()
//If TransportEvents is in the map, and the leaving unit is a
//transport, clean the transport- related data from the unit.
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
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //unit. These flags are necessary to detect
set animated[index] = false //when the 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
call GroupAddUnit(preplaced, u) //Add units that are created during initialization to the preplaced
//units group. This ensures that all units are noticed by OnUnitIndexed during initialization.
endif
loop //Create AutoStructs for the entering unit.
exitwhen n > autostructs_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
if getIndex(GetTriggerUnit()) == 0 then
call unitEntersMap(GetTriggerUnit())
endif //If the unit doesn't have an index at this point, assign it one.
//This is necessary to catch units with default-on autocast abilities
//when using GetUnitId on a newly created unit within an order event.
//! runtextmacro optional TransportUnloadCheck()
//If TransportEvents is in the map, check whether a unit is unloading.
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 = bj_MAX_PLAYER_SLOTS - 1
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(PLAYER_NEUTRAL_AGGRESSIVE), 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
return AutoIndex.getIndexDebug(u)
else
return AutoIndex.getIndex(u)
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
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* 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.wc3campaigns.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.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
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
set tT[0]=CreateTimer()
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==8191) 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
set hasht = InitHashtable()
endfunction
endlibrary
library SimError initializer init
//**************************************************************************************************
//*
//* SimError
//*
//* Mimic an interface error message
//* call SimError(ForPlayer, msg)
//* ForPlayer : The player to show the error
//* msg : The error
//*
//* To implement this function, copy this trigger and paste it in your map.
//* Unless of course you are actually reading the library from wc3c's scripts section, then just
//* paste the contents into some custom text trigger in your map.
//*
//**************************************************************************************************
//==================================================================================================
globals
private sound error
endglobals
//====================================================================================================
function SimError takes player ForPlayer, string msg returns nothing
set msg="\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"+msg+"|r"
if (GetLocalPlayer() == ForPlayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer( ForPlayer, 0.52, 0.96, 2.00, msg )
call StartSound( error )
endif
endfunction
private function init takes nothing returns nothing
set error=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
//call StartSound( error ) //apparently the bug in which you play a sound for the first time
//and it doesn't work is not there anymore in patch 1.22
endfunction
endlibrary
//edit line 40 patch 1.32
/**************************************************************
*
* RegisterPlayerUnitEvent
* v4.2.0.0
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must
* return false. They can return nothing as well.
*
* Disclaimer:
* -----------
*
* - Don't use TriggerSleepAction inside registered code.
*
* API:
* ----
*
* function RegisterPlayerUnitEvent
* takes
* playerunitevent whichEvent : The event you would like to register
* code whichFunction : The code you would like to register
* returns
* nothing
*
* - Registers code that will execute when an event fires.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = bj_MAX_PLAYER_SLOTS - 1
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
endlibrary
library Colour initializer init
globals
string array COLOUR
endglobals
private function init takes nothing returns nothing
set COLOUR[0] = "|cffff0303"
set COLOUR[1] = "|cff0042ff"
set COLOUR[2] = "|cff1be7ba"
set COLOUR[3] = "|cff550081"
set COLOUR[4] = "|cfffefc00"
set COLOUR[5] = "|cfffe890d"
set COLOUR[6] = "|cff21bf00"
set COLOUR[7] = "|cffe45caf"
set COLOUR[8] = "|cff939596"
set COLOUR[9] = "|cff7ebff1"
set COLOUR[10] = "|cff106247"
set COLOUR[11] = "|cff4f2b05"
set COLOUR[12] = "|cff9c0000"
set COLOUR[13] = "|cff0000c3"
set COLOUR[14] = "|cff00ebff"
set COLOUR[15] = "|cffbd00ff"
set COLOUR[16] = "|cffecce87"
set COLOUR[17] = "|cfff7a58b"
set COLOUR[18] = "|cffbfff81"
set COLOUR[19] = "|cffdbb8eb"
set COLOUR[20] = "|cff4f5055"
set COLOUR[21] = "|cffecf0ff"
set COLOUR[22] = "|cff00781e"
set COLOUR[23] = "|cffa56f34"
set COLOUR[24] = "|cff2e2d2e"
set COLOUR[25] = "|cff2e2d2e"
set COLOUR[26] = "|cff2e2d2e"
set COLOUR[27] = "|cff2e2d2e"
endfunction
endlibrary
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
xe
--
Q. Why xe?
A. As the caster system grew bigger vJass also appeared, there are also a lot
of things in the caster system that could have been done better but cannot
be fixed without dropping the caster system's function interface.
Q. No, really, WHY IS IT NAMED XE?
A. I have no idea.
Q. What's wrong with the caster system?
A. Instead of answering that I will list what's right with xe:
* It is modular. You can make a whole spell with just xebasic which is just
like a "constant package" that comes with the dummy model and unit. All the
other parts are disposable or replaceable.
Still, I made them and will probably keep making new modules as I advance,
right now xe can only do some basic functions, still cannot 100% replace
the caster system in functionality, certain parts like the parabolic
projectiles most notably, don't have a xe module yet, maybe later...
xebasic is so minimal I personally hope people could use it on their spells
and systems as a way to make it common to use these constants as constants of
that kind are often required to make things work.
* It is quite OOP, this is more related to the modules themselves, I wanted it
to exploit OOP for two reasons: 1) It prevents the 'ultra long function calls'
disease that has plagued the caster system since the beginning of time. 2) I
personally think it is easier this way instead of memorizing function names
and their argument lists.
xebasic
-------
xebasic is the nucleous of all xe, in order to use a xe module you would most
likely need xebasic. It is also meant to be the only part of xe that most users
would really need to tweak for their map.
This section is supposed to be a rapid guide on copying xebasic. The rest of
the xe modules should be rather easy to implement (just copy the 'trigger' that
contains it to the map).
1) Make a backup of your map.
2) Get vJass support. The most usual way would be using the newgen pack. There
are plenty of other ways. I for example do my vJass coding on Linux using just
jasshelper.exe, WINE and some editor tricks using a tool called Warcity.
Getting vJass to work is a wide area, if just installing and using the
"Jass newgen pack" doesn't work to you, please request/search help somehow. As
of now there really is no vJass support in OS/X, so you would need virtualization
and things of that style.
-- Note: You can have two maps open in the editor, and it is the only way to
make copy and paste work.
3) Copy the model, in this map's import manager you may find dummy.mdx, select
it and export it to some temp folder, then go to your map and import it, use
war3mapimported\dummy.mdx for path.
Please save the map immediatelly after importing the model to prevent it from
getting unimported due to a rare WE bug.
4) Copy the dummy unit: In the object editor under neutral passive, you may
find the dummy unit, select it, go to 'edit' then click copy. Now switch to
your map's object editor and use 'paste'.
5) Write down the rawcode: While you are in the object editor select your map's
recently pasted dummy unit, then go to the 'view' menu and click the option to
show values as raw data. Now take a look to the selected unit type. It will now
come with a code like "ewsp:e001", the last four characters of the code are
what matter, write them down.
6) Copy this map's xebasic trigger to your map.
7) Update your map's xebasic: change the value assigned to XE_DUMMY_UNITID to
'XXXX' where XXXX is the four character code you wrote down in step 5.
8) Save your map and pray, if it compiles correctly then it is done. Make sure
to test (after implementing) whatever needed you to install xebasic. If it
doesn't work correctly then it is likely you made a mistake when copying the
unit or the model or updating the rawcode.
9) Update the other values if you think it is necessary.
Using xebasic
-------------
Well, once you state your trigger/spell/system requires xebasic, just use
its constants, on a map that has implemented xebasic XE_DUMMY_UNITID will be
your key to creating dummy units, just remember to add it 'Aloc' ...
Contact
-------
I got a forum that's supposed to hold questions related to my systems at:
http://wc3campaigns.net/vexorian
I'd really appreciate that you used that forum for your questions, for starters
it is a lot more likely I would actually find the questions in that case.
Changelog
---------
0.9:
- Added xemissile.
- Added xedummy.
- xefx can now use xedummy to recycle dummy units.
- xepreload can now be used from struct initializers.
- Fixed a bug in xecast that would occur if recycledelay was shorter than
the duration of the spell cast by the dummy caster.
- Fixed a bug in xefx where hiddenReset did not properly assign the new fxpath.
- Optimized the xecollider collision detection.
- Fixed some errors in xefx and xecast documentation.
- new sample: Firestrike.
- Removed an xecast leak in the IllusionRune sample.
- Removed a handle id leak in SheepStaff sample.
0.8:
- No longer let xecollider hit corpses...
- When xecollider misses targetUnit (the unit dies, it will stop homing and
keep going on its current direction).
- angleSpeed no longer implicitly makes rotation automatic unless there is homing,
If you want rotation, set the boolean member rotation to true.
- xedamage: Added MAX_DAMAGE_FACTOR to prevent some deads, a debug message will
appear if the unit dies when being tested for a damage factor.
- xepreload: debug message when preloading an ability at a wrong time.
0.7:
- Requires jasshelper 0.A.2.4 or greater.
- xepreload will now show an useful error when the ability does not exist
(in debug mode).
- xepreload may now use TimerUtils to recycle its timer if you happen to have
TimerUtils in the map.
- xecast automatically resets mana and cooldown for the dummy before making it
cast stuff. If this behaviour concerns you, you may disable it through a
constant.
- Added targetUnit, setTargetPoint and forgetTarget to xecollider's
documentation.
- Added abilityLevel to xefx.
- Fixed a mistake in xefx's documentation that said "copy the xecast trigger"
- xedamage: Added a constant FACTOR_TEST_DAMAGE, which allows you to set the
amount of damage to do when testing for damage...
- xedamage: Added an isDummyDamage event response boolean to detect test damages.
- xedamage: Added a lot of things to documentation about the issues with armor.
- xedamage: fx is for sure not shown when there is no damage.
- xefx: added hiddenDestroy and hiddenReset
- new sample: Chains of fire.
0.6:
- The demo map is playable in patch 1.24.
- Only the samples and demomap system needed to be updated, xe's libraries
themselves have not changed.
0.5:
- xecollider.terminate now prevents events from firing.
- fixed a unit handle index kidnap in inRangeEnum (xecollider).
- fixed a small documentation bug about xecollider.terminate
- xecast's anti-AI protection now pauses the unit instead of removing the
ability (since that seemed to have bad side effects)
- xecast is now able to deal with units not visible to the player by setting
a constant to true.
- Due to technical split, xecast.castOnTarget now only takes unit and not
widget, old castOnTarget that takes widget has been renamed castOnWidgetTarget.
- xecollider now will not miss the detection of certain units that get inside
the range too fast anymore, however now creates a group per collider (damn) I
hope to find a better solution.
- Added a notice in xepreload's documentation about order events firing during
the preload.
- Added gem of double green fire
0.4:
- More documentation fixes.
- xecast no longer has double frees when using create/Basic/A and the
AOE/group methods.
- damageDestructablesInAOE now actually works and does not leak a xedamage
reference.
- damageTarget now returns true if it was succesful and false if it wasn't.
- xefx's recycle bin now extends array.
- added rune of illusions sample.
0.3:
- More documentation fixes.
- xefx requires xebasic in the library declaration (as it was supposed to)
- xefx now creates the effects at the correct place (used to consider pathing
during creation for some reason).
- xebasic sample uses 197.0 for max collision size since that's the default in
warcraft 3 (The default + 1)
- xebasic now includes an explanation for max collision size and its effects.
- added useSpecialEffect to xedamage
- included BoundSentinel
- added xecollider
- xedamage's required unittype field is not ignored anymore.
- Added the fireNovaStrike example.
0.2:
- Fixed a bug with xecast.castInPoint basically ignoring the arguments.
- Fixed a bug with xecast.createBasic ignoring the order id.
- Fixed documentation bugs.
- Added xedamage.
- Added sheep staff sample.
0.1 : Initial release
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 = 'e000'
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
xedamage
--------
When blizzard released UnitDamageTarget and UnitDamagePoint there were some
issue, both had a lot of parameters that were undocumented, but more importantly
DamagePoint was not a good enough solution, UnitDamagePoint causes issues with
apple players and it also misses ways to specify what sort of unit to target.
What many people missed was a way to specify these things in a similar way
to targets allowed in the object editor.
Determining and configuring valid targets and things like damage factors is
always a hassle, xedamage can automatize that process in a nice way.
xedamage is the successor of damageoptions it is also a little less messy.
bitflags are not used anymore, instead xedamage uses struct members to specify
most of it. So, when using xedamage, you may end up feeling like feeling a
table of fields. An example is worth a thousand of words:
local xedamage d=xedamage.create()
set d.damageAllies=true // also harm allies
set d.exception = UNIT_TYPE_FLYING // don't harm fliers
call d.factor ( UNIT_TYPE_STRUCTURE, 0.5) // half damage to buildings
set d.dtype = DAMAGE_TYPE_UNIVERSAL // Do universal damage.
//Execute AOE damage using those options:
call d.damageAOE(GetTriggerUnit(), point_x, point_y, 250.0, 450)
But there is more, xedamage also has a couple of members like isInUse() that
would add some event responses for the damaged unit event. That will allow you
to recognize when xedamage was in use to inflict the damage, the damagetype and
attacktype used, and even a custom tag that you could specify as a xedamage
field, this would allow you to have a bridge between xedamage and certain damage
detection systems that rely on such things.
About damage factors and dummy damages
----------------------------------------
Typically xedamage does some dummy damage to test the damage factor of a unit
because blizzard has not provided us with such a native... This damage is
invisible to the player, but not invisible to spells and damage detect systems
which could be a problem.
By default, the dummy damage is 0.01 and it is a common practice in these
spells and systems to ignore such low damage values. However, a bug from
blizzard makes it so we require a HUGE damage of 20.0 to be actually able to
detect all armor resistances. Something as low as 0.01 will not even detect
hero resistance. This blows. So if you want xedamage to consider resistance
correctly, you should use a test damage of 1/(armor reduction you wish to detect)
(change the FACTOR_TEST_DAMAGE constant in the xedamage trigger).
Of course, if you do this change, then you'll confuse spells/systems that
detect damage, that's the reason I have added a variable isDummyDamage to
xedamage (call it xedamage.isDummyDamage ) that you may read during a damage
event to ignore this dummy damage.
implementation
--------------
Just copy the xedamage trigger to your map.
xedamage object
----------------
xedamage fields include a bunch of boolean fields that you can set to
true/false, they hopefully got self-explaining names, I just list them and
their default values, remember I got a whole forum in wc3c, if you got doubts
don't forget to ask questions there:
boolean damageSelf = false
boolean damageAllies = false
boolean damageEnemies = true
boolean damageNeutral = true
boolean visibleOnly = false
boolean deadOnly = false
boolean alsoDead = false
boolean damageTrees = false
Something to notice is that damageTrees is probably only considered by AOE
damage and perhaps by some spells using xedamage to specify targets.
_______________________
boolean ranged = true
-----------------------
This is a special boolean field, it doesn't really determine valid targets
like the ones above, it merely determines if the damage should be considered
ranged, this merely determines how the AI reacts to the damage, a lot of people
don't care that much and just use true, that's the default.
___________________________________________________
damagetype dtype = DAMAGE_TYPE_UNIVERSAL
attacktype atype = ATTACK_TYPE_NORMAL
weapontype wtype = WEAPON_TYPE_WHOKNOWS
---------------------------------------------------
These fields determine the types to use in the damage native, the damage type
usually determines if the damage would be magical, universal (ultimate) or
physical, attacktype determines armor stuff, and weapon type determines sound.
There's some work on knowing what each combination does, for example:
http://www.wc3campaigns.net/showthread.php?t=100752
Some basic knowledge: For spells it is fine to use ATTACK_TYPE_NORMAL, and
that is default in xedamage, wtype usually doesn't need to be updated . dtype
is the important one, _UNIVERSAL makes the damage behave as a ultimate, there
is also _UNKNOWN which seems to ignore a lot of things, _FIRE, _LIGHTNING and
similar damagetypes are all magical, while DAMAGE_TYPE_NORMAL is physical.
Ultimate damage can harm both ethereal and spell immune units, magical damage
harms ethereal (with bonus) but cannot harm spell immune, physical damage can't
hurt ethereal units.
___________________________
integer tag = 0
---------------------------
This little field allows you to have a custom damage response CurrentDamageTag
(see bellow for event responses) basically, you can use whatever you want here
it all depends on what the thing that uses the event responses will do about it.
____________________________
unittype exception
unittype required
----------------------------
Learn a little about unittype, it is what blizzard calls unit classifications,
basically a unit can be a building, a flier, etc. exception specifies a
unittype that is required for a unit to receive damage. required, does the
opposite, for example:
set d.exception = UNIT_TYPE_FLYING
set d.required = UNIT_TYPE_SUMMONED
This xedamage instance can only hit ground summoned units.
* factor stuff:
Factor options in xedamage, specify some rules, if those rules are matched,
the damage will be multiplied by the specified factor, you can use negative
factor, half factor, etc. Notice that when using negative factors, these
things stack, so if you make a xedamage instance that does negative damage to
undead and negative damage to allies, it might do possitive damage to undead
allies.
If the total damage factor is 0.0 it is the same as adding an exception.
_____________________________
real allyfactor = 1.0
-----------------------------
If the xedamage can affect allies (damageAllies is true), then the damage
will be multiplied by allyfactor, for example, you can make a spell that does
half damage to allies. Or one that heals allies while hurting enemies.
_____________________________________________________________
method factor takes unittype ut, real fc returns nothing
-------------------------------------------------------------
This method allows you to add a specific factor for a unit type, by default
a xedamage instance allows up to three of these rules, you can increase this
cap by increasing MAX_SUB_OPTIONS in the top of the library.
For example:
call d.factor(UNIT_TYPE_STRUCTURE, 0.5)
call d.factor(UNIT_TYPE_SUMMONED, 2.0)
This instance of xedamage would do half damage to structures and double
damage to summoned units. Notice these things stack, so if for some reason
there was a "summoned building" in your map, it would do 100% damage.
_________________________________________________________________________
method abilityFactor takes integer abilityId, real fc returns nothing
-------------------------------------------------------------------------
Let's say you want a passive ability that makes you receive half damage
from fire spells, a way to do this is to make a whole damage detection system
and use xedamage's event responses to find out fire was used in the spell.
Then block the damage somehow... another way is to just change the spell so
when a unit has such passive ability, the damage is multiplied by 0.5 .
The 3 rules cap also works with abilityFactor and can as well be increased
by changing MAX_SUB_OPTIONS.
call d.abilityFactor( 'A000', 0.5)
____________________________________________________________________________
method useSpecialEffect takes string path, string attach returns nothing
----------------------------------------------------------------------------
This will make a special effect show up whenever succesful damage is done
using the xedamage object.
call d.useSpecialEffect("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl","origin")
xedamage methods
----------------
There would be little point in using all those fields without the methods that
make use of them.
____________________________________________________________________________________
method damageTarget takes unit source, unit target, real damage returns boolean
------------------------------------------------------------------------------------
A single unit targetting method, it will consider all the rules we just
reviewed when doing the damage, if the damage would get a factor of 0.0 it will
not perform any damage.
local unit u = GetTriggerUnit()
local unit t = GetSpellTargetUnit()
local xedamage d= xedamage.create()
set d.dtype = DAMAGE_TYPE_FIRE
set d.damageAllies = true
set d.allyfactor = -1.0
call d.damageTarget(u,t, 100)
call d.destroy()
This would be a simple spell that does 100.0 fire damage on enemy units or
heals for 100 hitpoints to allies.
This method returns true if non zero damage was done, and false otherwise.
____________________________________________________________________________________________
method damageTargetForceValue takes unit source, unit target, real damage returns nothing
---------------------------------------------------------------------------------------------
This is an analogue for damageTarget, but it will IGNORE every specified
field, and try to do the specified damage, no matter the circumstances, however
it will use the xedamage instances' dtype, atype and tag for the xedamage event
responses (see bellow).
This could be useful when you already know the factor
(you have previously used getTargetFactor)
__________________________________________________________________________
method allowedTarget takes unit source, unit target returns boolean
--------------------------------------------------------------------------
Returns true if xedamage would do a damage different than 0.0 in this case.
It is useful if you intend to use xedamage to configure a spell's allowed
targets.
if ( d.allowedTarget(u,t) ) then
//...
____________________________________________________________________________
method getTargetFactor takes unit source, unit target returns real
----------------------------------------------------------------------------
For applications similar to allowedTarget, this returns the whole damage
factor, so you can decide what to do based on it.
set fc = d.getTargetFactor(u, t)
________________________________________________________________________________________
method damageGroup takes unit source, group targetGroup, real damage returns integer
----------------------------------------------------------------------------------------
This method does what damageTarget does, but it executes it on a whole unit
group, probably faster than calling damageTarget on every unit in the group.
Notice this method will empty the provided group.
_________________________________________________________________________________________________
method damageAOE takes unit source, real x, real y, real radius, real damage returns integer
method damageAOELoc takes unit source, location loc, real radius, real damage returns integer
-------------------------------------------------------------------------------------------------
It will perform damage on units and destructables (if damageTrees is true)
that are inside the circle, notice collision sizes are considered. The returned
value is the number of targets that were affected by the function, the loc
version allows you to use a location instead of the more sane x,y coordinates.
______________________________________________________________________________________________________________
method damageDestructablesAOE takes unit source, real x, real y, real radius, real damage returns integer
method damageDestructablesAOELoc takes unit source, location loc, real radius, real damage returns integer
-----------------------------------------------------------------------------------------------------------
This one damages all the destructables in a circle, regardless of damageTrees
being true or not.
xedamage static method
-----------------------
________________________________________________________________________________________________
static method getDamageTypeFactor takes unit u, attacktype a, damagetype d returns real
------------------------------------------------------------------------------------------------
This method is used by one of the factor methods up there, thought it would be
useful to make it available as a public method, it just returns the factor that
a specific attacktype/damagetype couple would do on a certain unit u:
set fc = xedamage.getDamageTypeFactor( GetTriggerUnit(), ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE)
Would return 1.0 if the unit is a normal unit, 1.66 if it is ethereal and 0 if it is spell immune.
xedamage event responses
------------------------
______________________________________________________________________
boolean isDummyDamage
----------------------------------------------------------------------
This variable is true when xedamage has performed "dummy damage" see
above for more details.
______________________________________________________________________
static method isInUse takes nothing returns boolean
----------------------------------------------------------------------
This method will return true if xedamage was in use during a damaged event,
this would help you determine if the damage was inflicted by a xedamage call.
_______________________________________________________________________
readonly static damagetype CurrentDamageType=null
readonly static attacktype CurrentAttackType=null
readonly static integer CurrentDamageTag =0
-----------------------------------------------------------------------
When isInUse() returns true, you can use these event responses to determine
how was the damage inflicted, you can get the damage type, the attack type and
the tag (specified by the field tag in the xedamage object) of the call.
if (xedamage.isInUse() ) then
if(xedamage.CurrentDamageType == DAMAGE_TYPE_FIRE ) then
call BJDebugMsg(R2S(GetEventDamage())+" fire damage was inflicted to "+GetUnitName(GetTriggerUnit() ) )
endif
call BJDebugMsg("tag used: "+I2S(xedamage.CurrentDamageTag) )
endif
--
The FireNovaStrike sample attempts to be a quick example on
how to use xedamage.
//edit line 229,575 patch 1.32
library xedamage initializer init requires xebasic
//************************************************************************
// xedamage 0.8
// --------
// For all your damage and targetting needs.
//
//************************************************************************
//===========================================================================================================
globals
private constant integer MAX_SUB_OPTIONS = 4
private constant real FACTOR_TEST_DAMAGE = 0.01
// a low damage to do on units to test their damage factors for specific
// attacktype/damagetype combos.
// You'll need something as high as 20.0 to make it work well with armor resistances.
// (Yes, you read it correctly, 20 ...
//
// If you use such a large value, there may be conflicts with some things relying on damage
// (ie they are not away of the isDummyDamage tag that xedamage posseses.) which may be quite bad if you think about it...
// then maybe it is better to change it to 0.01 ... then will work fine, just fine - but it will generally ignore armor -
// I am leaving it as 0.01 by default, because honestly, I'd rather make it ignore armor than have a lot of people sending me
// rants about how they detect 20.0 damage where none is visible...
private constant real MAX_DAMAGE_FACTOR = 3.00
// The maximum damage factor in the map. I think 3 is fine.
//=======================================================
private constant real EPSILON = 0.000000001
private unit dmger
private constant integer MAX_SPACE = 8190 // MAX_SPACE/MAX_SUB_OPTIONS is the instance limit for xedamage, usually big enough...
endglobals
private keyword structInit
struct xedamage[MAX_SPACE]
//----
// fields and methods for a xedamage object, they aid determining valid targets and special
// damage factor conditions.
//
// Notice the default values.
//
boolean damageSelf = false // the damage and factor methods usually have a source unit parameter
// xedamage would consider this unit as immune unless you set damageSelf to true
boolean damageAllies = false // Alliance dependent target options.
boolean damageEnemies = true // *
boolean damageNeutral = true // *
boolean ranged = true // Is the attack ranged? This has some effect on the AI of the affected units
// true by default, you may not really need to modify this.
boolean visibleOnly = false // Should only units that are visible for source unit's owner be affected?
boolean deadOnly = false // Should only corpses be affected by "the damage"? (useful when using xedamage as a target selector)
boolean alsoDead = false // Should even corpses and alive units be considered?
boolean damageTrees = false //Also damage destructables? Notice this is used only in certain methods.
//AOE for example targets a circle, so it can affect the destructables
//in that circle, a custom spell using xedamage for targetting configuration
//could also have an if-then-else implemented so it can verify if it is true
//then affect trees manually.
//
// Damage type stuff:
// .dtype : the "damagetype" , determines if the spell is physical, magical or ultimate.
// .atype : the "attacktype" , deals with armor.
// .wtype : the "weapontype" , determines the sound effect to be played when damage is done.
//
// Please use common.j/blizzard.j/ some guide to know what damage/attack/weapon types can be used
//
damagetype dtype = DAMAGE_TYPE_UNIVERSAL
attacktype atype = ATTACK_TYPE_NORMAL
weapontype wtype = WEAPON_TYPE_WHOKNOWS
//
// Damage type 'tag' people might use xedamage.isInUse() to detect xedamage usage, there are other static
// variables like xedamage.CurrentDamageType and xedamage.CurrentDamageTag. The tag allows you to specify
// a custom id for the damage type ** Notice the tag would aid you for some spell stuff, for example,
// you can use it in a way similar to Rising_Dusk's damage system.
//
integer tag = 0
//
// if true, forceDamage will make xedamage ignore dtype and atype and try as hard as possible to deal 100%
// damage.
boolean forceDamage = false
//
// Ally factor! Certain spells probably have double purposes and heal allies while harming enemies. This
// field allows you to do such thing.
//
real allyfactor = 1.0
//
// field: .exception = SOME_UNIT_TYPE
// This field adds an exception unittype (classification), if the unit belongs to this unittype it will
// be ignored.
//
method operator exception= takes unittype ut returns nothing
set this.use_ex=true
set this.ex_ut=ut
endmethod
//
// field: .required = SOME_UNIT_TYPE
// This field adds a required unittype (classification), if the unit does not belong to this unittype
// it will be ignored.
//
method operator required= takes unittype ut returns nothing
set this.use_req=true
set this.req_ut=ut
endmethod
private boolean use_ex = false
private unittype ex_ut = null
private boolean use_req = false
private unittype req_ut = null
private unittype array fct[MAX_SUB_OPTIONS]
private real array fc[MAX_SUB_OPTIONS]
private integer fcn=0
//
// method .factor(SOME_UNIT_TYPE, factor)
// You might call factor() if you wish to specify a special damage factor for a certain classification,
// for example call d.factor(UNIT_TYPE_STRUCTURE, 0.5) makes xedamage do half damage to structures.
//
method factor takes unittype ut, real fc returns nothing
if(this.fcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to factor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of factor() calls")
return
endif
set this.fct[this.fcn] = ut
set this.fc[this.fcn] = fc
set this.fcn = this.fcn+1
endmethod
private integer array abifct[MAX_SUB_OPTIONS]
private real array abifc[MAX_SUB_OPTIONS]
private integer abifcn=0
//
// method .abilityFactor('abil', factor)
// You might call abilityFactor() if you wish to specify a special damage factor for units that have a
// certain ability/buff.
// for example call d.abilityFactor('A000', 1.5 ) makes units that have the A000 ability take 50% more
// damage than usual.
//
method abilityFactor takes integer abilityId, real fc returns nothing
if(this.abifcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to abilityFactor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of abilityFactor() calls")
return
endif
set this.abifct[this.abifcn] = abilityId
set this.abifc[this.abifcn] = fc
set this.abifcn = this.abifcn+1
endmethod
private boolean usefx = false
private string fxpath
private string fxattach
//
// method .useSpecialEffect("effect\\path.mdl", "origin")
// Makes it add (and destroy) an effect when damage is performed.
//
method useSpecialEffect takes string path, string attach returns nothing
set this.usefx = true
set this.fxpath=path
set this.fxattach=attach
endmethod
//********************************************************************
//* Now, the usage stuff:
//*
//================================================================================
// static method xedamage.isInUse() will return true during a unit damaged
// event in case this damage was caused by xedamage, in this case, you can
// read variables like CurrentDamageType, CurrentAttackType and CurrentDamageTag
// to be able to recognize what sort of damage was done.
//
readonly static damagetype CurrentDamageType=null
readonly static attacktype CurrentAttackType=null
readonly static integer CurrentDamageTag =0
private static integer inUse = 0
static method isInUse takes nothing returns boolean
return (inUse>0) //inline friendly.
endmethod
readonly static boolean isDummyDamage = false
//========================================================================================================
// This function calculates the damage factor caused by a certain attack and damage
// type, it is static : xedamage.getDamageTypeFactor(someunit, ATTAcK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, 100)
//
static method getDamageTypeFactor takes unit u, attacktype a, damagetype d returns real
local real hp=GetWidgetLife(u)
local real mana=GetUnitState(u,UNIT_STATE_MANA)
local real r
local real fc = FACTOR_TEST_DAMAGE
//Since a unit is in that point, we don't need checks.
call SetUnitX(dmger,GetUnitX(u))
call SetUnitY(dmger,GetUnitY(u))
call SetUnitOwner(dmger,GetOwningPlayer(u),false)
set r=hp
if (hp< FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR) then
call SetWidgetLife(u, hp + FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR )
set r = hp + FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR
set fc = GetWidgetLife(u)-hp + EPSILON
endif
set isDummyDamage = true
call UnitDamageTarget(dmger,u, fc ,false,false,a,d,null)
static if DEBUG_MODE then
if IsUnitType(u, UNIT_TYPE_DEAD) and (hp>0.405) then
call BJDebugMsg("xedamage: For some reason, the unit being tested by getDamageTypeFactor has died. Verify MAX_DAMAGE_FACTOR is set to a correct value. ")
endif
endif
set isDummyDamage = false
call SetUnitOwner(dmger,Player(PLAYER_NEUTRAL_PASSIVE),false)
if (mana>GetUnitState(u,UNIT_STATE_MANA)) then
//Unit had mana shield, return 1 and restore mana too.
call SetUnitState(u,UNIT_STATE_MANA,mana)
set r=1
else
set r= (r-GetWidgetLife(u)) / fc
endif
call SetWidgetLife(u,hp)
return r
endmethod
private method getTargetFactorCore takes unit source, unit target, boolean usetypes returns real
local player p=GetOwningPlayer(source)
local boolean allied=IsUnitAlly(target,p)
local boolean enemy =IsUnitEnemy(target,p)
local boolean neutral=allied
local real f
local real negf=1.0
local integer i
if(this.damageAllies != this.damageNeutral) then
set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_REQUEST ))
//I thought accuracy was not as important as speed , I think that REQUEST is false is enough to consider
// it neutral.
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_RESPONSE ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_XP ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_SPELLS ))
set allied= allied and not(neutral)
endif
if (not this.damageAllies) and allied then
return 0.0
elseif (not this.damageEnemies) and enemy then
return 0.0
elseif( (not this.damageSelf) and (source==target) ) then
return 0.0
elseif (not this.damageNeutral) and neutral then
return 0.0
elseif( this.use_ex and IsUnitType(target, this.ex_ut) ) then
return 0.0
elseif( this.visibleOnly and not IsUnitVisible(target,p) ) then
return 0.0
elseif ( this.deadOnly and not IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
elseif ( not(this.alsoDead) and IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
endif
set f=1.0
if ( IsUnitAlly(target,p) ) then
set f=f*this.allyfactor
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
if (this.use_req and not IsUnitType(target,this.req_ut)) then
return 0.0
endif
set i=.fcn-1
loop
exitwhen (i<0)
if( IsUnitType(target, this.fct[i] ) ) then
set f=f*this.fc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set i=.abifcn-1
loop
exitwhen (i<0)
if( GetUnitAbilityLevel(target,this.abifct[i] )>0 ) then
set f=f*this.abifc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set f=f*negf
if ( f<EPSILON) and (f>-EPSILON) then
return 0.0
endif
if( this.forceDamage or not usetypes ) then
return f
endif
set f=f*xedamage.getDamageTypeFactor(target,this.atype,this.dtype)
if ( f<EPSILON) and (f>-EPSILON) then
return 0.0
endif
return f
endmethod
//====================================================================
// With this you might decide if a unit is a valid target for a spell.
//
method getTargetFactor takes unit source, unit target returns real
return this.getTargetFactorCore(source,target,true)
endmethod
//======================================================================
// a little better, I guess
//
method allowedTarget takes unit source, unit target returns boolean
return (this.getTargetFactorCore(source,target,false)!=0.0)
endmethod
//=======================================================================
// performs damage to the target unit, for unit 'source'.
//
method damageTarget takes unit source, unit target, real damage returns boolean
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local real f = this.getTargetFactorCore(source,target,false)
local real pl
if(f!=0.0) then
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
set pl=GetWidgetLife(target)
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
if(pl != GetWidgetLife(target) ) then
if(usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
return true
endif
endif
return false
endmethod
//=======================================================================================
// The same as damageTarget, but it forces a specific damage value, good if you already
// know the target.
//
method damageTargetForceValue takes unit source, unit target, real damage returns nothing
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
if( usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
set .inUse = .inUse +1
call UnitDamageTarget(source,target, damage, true, .ranged, null, null, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
endmethod
//=====================================================================================
// Notice: this will not Destroy the group, but it will certainly empty the group.
//
method damageGroup takes unit source, group targetGroup, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local unit target
local real f
local integer count=0
local real hp
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
loop
set target=FirstOfGroup(targetGroup)
exitwhen (target==null)
call GroupRemoveUnit(targetGroup,target)
set f= this.getTargetFactorCore(source,target,false)
if (f!=0.0) then
set count=count+1
if(usefx) then
set hp = GetWidgetLife(target)
endif
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
if(usefx and (hp > GetWidgetLife(target)) ) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
endif
endloop
set .inUse = .inUse -1
set .CurrentDamageTag=tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return count
endmethod
private static xedamage instance
private integer countAOE
private unit sourceAOE
private real AOEx
private real AOEy
private real AOEradius
private real AOEdamage
private static boolexpr filterAOE
private static boolexpr filterDestAOE
private static group enumgroup
private static rect AOERect
private static method damageAOE_Enum takes nothing returns boolean
local unit target=GetFilterUnit()
local xedamage this=.instance //adopting a instance.
local real f
local real hp
if( not IsUnitInRangeXY(target,.AOEx, .AOEy, .AOEradius) ) then
set target=null
return false
endif
set f=.getTargetFactorCore(.sourceAOE, target, false)
if(f!=0.0) then
set .countAOE=.countAOE+1
if(this.usefx) then
set hp =GetWidgetLife(target)
endif
call UnitDamageTarget(.sourceAOE,target, f*this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
if(this.usefx and (hp > GetWidgetLife(target) ) ) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
endif
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
private static method damageAOE_DestructablesEnum takes nothing returns boolean
local destructable target=GetFilterDestructable()
local xedamage this=.instance //adopting a instance.
local real dx=.AOEx-GetDestructableX(target)
local real dy=.AOEy-GetDestructableY(target)
if( dx*dx + dy*dy >= .AOEradius+EPSILON ) then
set target=null
return false
endif
set .countAOE=.countAOE+1
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
call UnitDamageTarget(.sourceAOE,target, this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
//==========================================================================================
// will affect trees if damageTrees is true!
//
method damageAOE takes unit source, real x, real y, real radius, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius
set .AOEy=y
set .AOEdamage=damage
call GroupEnumUnitsInRange(.enumgroup,x,y,radius+XE_MAX_COLLISION_SIZE, .filterAOE)
if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
set .AOEradius=.AOEradius*.AOEradius
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
endif
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return .countAOE
endmethod
method damageAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageAOE(source, GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//==========================================================================================
// only affects trees, ignores damageTrees
//
method damageDestructablesAOE takes unit source, real x, real y, real radius, real damage returns integer
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius*radius
set .AOEy=y
set .AOEdamage=damage
//if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
//endif
return .countAOE
endmethod
method damageDestructablesAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageDestructablesAOE(source,GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//'friend' with the library init
static method structInit takes nothing returns nothing
set .AOERect= Rect(0,0,0,0)
set .filterAOE= Condition(function xedamage.damageAOE_Enum)
set .filterDestAOE = Condition( function xedamage.damageAOE_DestructablesEnum)
set .enumgroup = CreateGroup()
endmethod
endstruct
private function init takes nothing returns nothing
set dmger=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID , 0.,0.,0.)
call UnitAddAbility(dmger,'Aloc')
call xedamage.structInit()
endfunction
endlibrary
xecast
------
This one solves typical problems that require dummy casters. Things like the
AOE sleep, targetted warstomp, etc. It is object oriented, this just means that
you'll actually not just call functions but deal with xecast objects, change
their attributes and then order them to cast. It deals with the dirty things
like recycling and dealing with timing, etc.
implementation
--------------
Just copy the xecast trigger to your map.
xecast object
-------------
__________________________________________________________________________________________________
static method create takes nothing returns nothing
- ------
This is the create method, it will make a new xecast object for you to use:
set somevariable = xecast.create()
_________________________________________________________________________________________________
static method createBasic takes integer abilityID, integer orderid, player owner returns xecast
- - - - - - - - - - - -
An abbreviated constructor, allows you to quickly set the basic attributes
abilityID: the rawcode of the ability to cast.
Example: 'AHbz'
orderid : the orderid (integer) of the ability to cast.
Example: OrderId("blizzard")
owner : the player for this cast object (The one that gets credit for damage)
Example: ( GetOwningPlayer(GetTriggerUnit() ))
_________________________________________________________________________________________________
method destroy takes nothing returns nothing
-
Call destroy() on instances you are not going to use anymore, to prevent struct leaks that would
break your map. A simple use for xecast is to just keep one instance per dummy spell to prevent
having to care about destroying them. Another possibility is to use the A constructors.
Example: call somevariable.destroy()
__________________________________________________________________________________________________
static method createA takes nothing returns nothing
static method createBasicA takes integer abilityID, integer orderid, player owner returns xecast
-
These do the same as create and createBasic , the only difference is that the
object is destroyed automatically after every call to a cast method (See bellow).
_____________________________________________________________________________________________________
method castOnTarget takes unit target returns nothing
-
Tells the xecast object to cast its spell on the target unit. Can cast on units that are invisible
to the owner of the xecast if FORCE_INVISIBLE_CAST is set to true.
_____________________________________________________________________________________________________
method castOnWidgetTarget takes widget target returns nothing
-
Tells the xecast object to cast its spell on the target. target may be unit, item or destructable.
_____________________________________________________________________________________________________
method castOnPoint takes real x, real y returns nothing
method castOnLoc takes location loc returns nothing
method castInPoint takes real x, real y returns nothing
method castInLoc takes location loc returns nothing
- -----------------------------------------------
Instead of casting on a target unit/item/destructable these ones cast on a target point. OnPoint is
used for point-targeteable spells, while InPoint is used for spells that have no target. The Loc
versions allow you to use locations. Locations are useless most of the times, but if you want to use
them you can use the Loc versions.
Example: call somevar.castOnPoint( spellx, spelly )
_____________________________________________________________________________________________________
method castOnAOE takes real x, real y, real radius returns nothing
method castOnAOELoc takes location loc,real radius returns nothing
method castOnGroup takes group g returns nothing
- ---------------------------------------------------
Methods to cast the spell on multiple units, AOE takes a circle's center and radius, while Group
takes a unit group, notice that the unit group will get cleaned after calling this function, which
means it will be an empty unit group, no, it does not destroy the group automatically, just empties it
* List of attributes *
________________________________________________________________________________
integer abilityid
----
This one holds the ability to cast's ability Id.
Example: set somevar.abilityid='AHbz'
________________________________________________________________________________
integer level
----
The level of the ability to cast.
Example: set somevar.level = GetUnitAbilityLevel(u, spellid)
________________________________________________________________________________
real recycledelay
----
The recycle delay is the time to wait before recycling the dummy caster, if
it is 0.0 the ability will be considered instant.
A proper recycle delay is important since when a dummy caster is recycled
its owner becomes player passive. Every damage done by the casted spell will
not credit the correct player.
Some other spells need some time in order to cast correctly. Not to mention
the channeling ones that require the caster to last during that situation.
Example: set somevar.recycledelay=10.0
________________________________________________________________________________
player owningplayer
----
The player that owns the spell (Who gets credited for it)
Example: set somevar.owningplayer = Player(2)
________________________________________________________________________________
integer orderid (write-only)
----
The ability to cast's order id. eg 858029 or OrderId("blizzard")
________________________________________________________________________________
string orderstring (write-only)
----
The ability to cast's order string (eg "blizzard")
________________________________________________________________________________
boolean customsource
----
false by default, determines if you want the dummy caster to be placed at
a specific point when casting, this allows you to exploit blizz spell's eye
candy. Once customsource is true, you need to set sourcex,sourcey and
sourcez.
________________________________________________________________________________
real sourcex, sourcey, sourcez
----
The coordinates where you want to place the dummy caster, z is height and
is 0.0 by default. These are ignored if customsource is set to false.
________________________________________________________________________________
method setSourcePoint takes real x, real y, real z returns nothing
method setSourceLoc takes location loc, real z returns nothing
----
In case setting all that stuff manually takes too much lines for your taste
you can use these methods to set those values, they will automatically set
customsource to true.
//edit line 236,466 dummy cast time fix
library xecast initializer init requires xebasic
//******************************************************************************
// xecast 0.9
// ------
// Because dummy casters REALLY ARE this complicated!
//
//******************************************************************************
//==============================================================================
globals
private constant integer MAXINSTANCES = 8190
//this is a lot, unless you leak xecast objects (which is a bad thing)
private constant integer INITIAL_DUMMY_COUNT = 12
//don't allow to keep more than DUMMY_STACK_LIMIT innactive dummy units :
private constant integer DUMMY_STACK_LIMIT = 50
// If your map does not give visibility to all players, or
// for other reasons, you might want xecast to work on
// units that are not visible to the player, in that case
// change this to true, else it is just a performance loss.
private constant boolean FORCE_INVISIBLE_CAST = false
//When AUTO_RESET_MANA_COOLDOWN is set to true, xecast will reset
// the dummy unit's cooldown and mana before casting every spell.
// it is a performance penalty, so if you are sure that all dummy spells
// in your map got 0 mana and cooldown cost, you may set it to false.
private constant boolean AUTO_RESET_MANA_COOLDOWN = true
endglobals
//=========================================================================
// Please notice all textmacros in this library are considered private.
// in other words: DON'T RUN THOSE TEXTMACROS!
//
private keyword structinit
globals
private real EPSILON=0.001 //noticed in war3 this is the sort of precision we want...
endglobals
struct xecast[MAXINSTANCES]
public integer abilityid = 0 //ID (rawcode) of the ability to cast
public integer level = 1 //Level of the ability to cast
public real recycledelay = 0.0 //Please notice, some spells need a recycle delay
// This is, a time period before they get recycle.
// For example, some spells are not instant, there is
// also the problem with damaging spells, this recycle
// delay must be large enough to contain all the time
// in which the spell can do damage.
public player owningplayer=Player(15) //which player to credit for the ability cast?
//notice this can also affect what units are targeteable
//==================================================================================================
// You need an order id for the ability so the dummy unit is able to cast it, two ways to assign it
// set instance.orderid = 288883 //would assign an integer orderid
// set instance.orderstring = "chainlightning" //would assign an orderstring
// (as those in the object editor)
//
method operator orderid= takes integer v returns nothing
set .oid=v
endmethod
method operator orderstring= takes string s returns nothing
set .oid=OrderId(s)
endmethod
//=================================================================================================
// Finally, you can determine from which point to cast the ability: z is the height coordinate.
//
public boolean customsource=false //Use a custom casting source?
public real sourcex // Determine the casting source for the dummy spell, require customsource =true
public real sourcey // You might prefer to use the setSourcePoint method
public real sourcez=0.0 //
method setSourcePoint takes real x, real y, real z returns nothing
set .sourcex=x
set .sourcey=y
set .sourcez=z
set .customsource=true
endmethod
method setSourceLoc takes location loc, real z returns nothing
set .sourcex=GetLocationX(loc)
set .sourcey=GetLocationY(loc)
set .sourcez=z
set .customsource=true
endmethod
private boolean autodestroy = false
//========================================================================================================
// you are always allowed to use .create() but you can also use createBasic which sets some things that
// are usually necessary up.
//
public static method createBasic takes integer abilityID, integer orderid, player owner returns xecast
local xecast r=xecast.allocate()
if(r==0) then
debug call BJDebugMsg("Warning: unbelievable but you actually used all xecast instances in your map! Please make sure you are not forgetting to destroy those what you create intensively, if that's not the case, then you'll have to increase xecast MAXINSTANCES")
endif
set r.oid=orderid
set r.abilityid=abilityID
set r.owningplayer=owner
return r
endmethod
//========================================================================================================
// Just like the above one, but the instance will self destruct after a call to any cast method
// (recommended)
//
public static method createBasicA takes integer abilityID, integer orderid, player owner returns xecast
local xecast r=xecast.allocate()
if(r==0) then
debug call BJDebugMsg("Warning: unbelievable but you actually used all xecast instances in your map! Please make sure you are not forgetting to destroy those what you create intensively, if that's not the case, then you'll have to increase xecast MAXINSTANCES")
endif
set r.oid=orderid
set r.abilityid=abilityID
set r.owningplayer=owner
set r.autodestroy=true
return r
endmethod
//==========================================================================================================
// Just like create, but the struct instance self destructs after a call to any cast method
// (Recommended)
//
public static method createA takes nothing returns xecast
local xecast r=xecast.allocate()
set r.autodestroy=true
return r
endmethod
//==========================================================================================================
// So, create the dummy, assign options and cast the skill!
// .castOnTarget(u) : If you want to hit a unit u with the ability, supports FORCE_INVISIBLE_CAST.
// .castOnWidgetTarget(w) : If you want to hit a widget w with the ability.
// .castOnPoint(x,y) : If you want to hit a point (x,y) with the ability.
// .castInPoint(x,y) : For spells like warstomp which do not have a target.
// .castOnAOE(x,y,radius) : Classic area of effect cast. Considers collision size.
// .castOnGroup(g) : Cast unit the unit group g, notice it will empty the group yet not destroy it.
//
//**********************************************************************************************************
// The implementation of such methods follows:
private static unit array dummystack
private static integer top=0
private static unit instantdummy
private integer oid=0
private static timer gametime
private static timer T
private static unit array recycle
private static real array expiretime
private static integer rn=0
//==========================================================================================================
// private dorecycle method, sorry but I need this up here.
//
private static method dorecycle takes nothing returns nothing
local unit u =.recycle[0]
local integer l
local integer r
local integer p
local real lt
call IssueImmediateOrder(u,"stop")// bugfix, see: http://www.wc3c.net/showpost.php?p=1131163&postcount=5
call UnitRemoveAbility(u,GetUnitUserData(u))
call SetUnitUserData(u,0)
call SetUnitFlyHeight(u,0,0)
call PauseUnit(u,false)
if(.top==DUMMY_STACK_LIMIT) then
call RemoveUnit(u)
else
set .dummystack[.top]=u
set .top=.top+1
endif
set .rn=.rn-1
if(.rn==0) then
return
endif
set p=0
set lt=.expiretime[.rn]
loop
set l=p*2+1
exitwhen l>=.rn
set r=p*2+2
if(r>=.rn)then
if(.expiretime[l]<lt) then
set .expiretime[p]=.expiretime[l]
set .recycle[p]=.recycle[l]
set p=l
else
exitwhen true
endif
elseif (lt<=.expiretime[l]) and (lt<=.expiretime[r]) then
exitwhen true
elseif (.expiretime[l]<.expiretime[r]) then
set .expiretime[p]=.expiretime[l]
set .recycle[p]=.recycle[l]
set p=l
else
set .expiretime[p]=.expiretime[r]
set .recycle[p]=.recycle[r]
set p=r
endif
endloop
set .recycle[p]=.recycle[.rn]
set .expiretime[p]=lt
call TimerStart(.T, .expiretime[0]-TimerGetElapsed(.gametime), false, function xecast.dorecycle)
endmethod
private static trigger abilityRemove
// Repetitive process and no inline implemented for large functions, so for now it is a textmacro:
//! textmacro xecast_allocdummy
if(.recycledelay<EPSILON) then
set dummy=.instantdummy
call SetUnitOwner(dummy,.owningplayer,false)
elseif (.top>0) then
set .top=.top-1
set dummy=.dummystack[.top]
call SetUnitOwner(dummy,.owningplayer,false)
else
set dummy=CreateUnit(.owningplayer,XE_DUMMY_UNITID,0,0,0)
call TriggerRegisterUnitEvent(.abilityRemove,dummy,EVENT_UNIT_SPELL_ENDCAST)
call UnitAddAbility(dummy,'Aloc')
call UnitAddAbility(dummy,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(dummy,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(dummy,'Amov' ) //FIX
endif
call UnitAddAbility(dummy, abilityid)
static if AUTO_RESET_MANA_COOLDOWN then
call UnitResetCooldown(dummy)
call SetUnitState(dummy, UNIT_STATE_MANA, 10000.0)
endif
if(level>1) then
call SetUnitAbilityLevel(dummy, abilityid, level)
endif
//! endtextmacro
private static integer cparent
private static integer current
private static real cexpire
//! textmacro xecast_deallocdummy
if(.recycledelay>=EPSILON) then
set .cexpire=TimerGetElapsed(.gametime)+.recycledelay
set .current=.rn
set .rn=.rn+1
loop
exitwhen (.current==0)
set .cparent=(.current-1)/2
exitwhen (.expiretime[.cparent]<=.cexpire)
set .recycle[.current]=.recycle[.cparent]
set .expiretime[.current]=.expiretime[.cparent]
set .current=.cparent
endloop
set .expiretime[.current]=.cexpire
set .recycle[.current]=dummy
call SetUnitUserData(dummy,.abilityid)
call TimerStart(.T, .expiretime[0]-TimerGetElapsed(.gametime), false, function xecast.dorecycle)
else
call SetUnitUserData(dummy,0)
call SetUnitFlyHeight(dummy,0,0)
call UnitRemoveAbility(dummy,.abilityid)
endif
//! endtextmacro
method castOnTarget takes unit target returns nothing
local unit dummy
local unit tar
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,GetWidgetX(target))
call SetUnitY(dummy,GetWidgetY(target))
endif
static if (FORCE_INVISIBLE_CAST) then
call UnitShareVision(target, .owningplayer, true)
call IssueTargetOrderById(dummy,this.oid,target)
call UnitShareVision(target, .owningplayer, false)
else
call IssueTargetOrderById(dummy,this.oid,target)
endif
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
//accepts units, items and destructables, if you know it is
// a unit it is better to use castOnTarget since that would
// be able to use FORCE_INVISIBLE_CAST if necessary.
//
method castOnWidgetTarget takes widget target returns nothing
local unit dummy
local unit tar
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,GetWidgetX(target))
call SetUnitY(dummy,GetWidgetY(target))
endif
call IssueTargetOrderById(dummy,this.oid,target)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castOnPoint takes real x, real y returns nothing
local unit dummy
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,x)
call SetUnitY(dummy,y)
endif
call IssuePointOrderById(dummy,this.oid,x,y)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castOnLoc takes location loc returns nothing
//debug call BJDebugMsg("Warning: Locations are in use")
//nah but I should
call .castOnPoint(GetLocationX(loc),GetLocationY(loc))
endmethod
//ignores custom source x and y (for obvious reasons)
method castInPoint takes real x, real y returns nothing
local unit dummy
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitFlyHeight(dummy,.sourcez,0.0)
endif
call SetUnitX(dummy, x)
call SetUnitY(dummy, y)
call IssueImmediateOrderById(dummy,this.oid)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castInLoc takes location loc returns nothing
//debug call BJDebugMsg("Warning: Locations are in use")
//nah but I should
call .castInPoint(GetLocationX(loc),GetLocationY(loc))
endmethod
//===================================================================================================
// For method castOnAOE:
//
private static group enumgroup
private static real aoex
private static real aoey
private static real aoeradius
private static xecast cinstance
private static boolexpr aoefunc
// Might look wrong, but this is the way to make it consider collision size, a spell that
// got a target circle and uses this method will let the user know which units it will
// hit with the mass cast.
static method filterAOE takes nothing returns boolean
local unit u=GetFilterUnit()
if IsUnitInRangeXY(u, .aoex, .aoey, .aoeradius) then
call .cinstance.castOnTarget(u)
endif
set u=null
return false
endmethod
//
method castOnAOE takes real x, real y, real radius returns nothing
local boolean ad=this.autodestroy
if(ad) then
set this.autodestroy=false
endif
set .aoex=x
set .aoey=y
set .aoeradius=radius
set .cinstance=this
call GroupEnumUnitsInRange(.enumgroup,x,y,radius + XE_MAX_COLLISION_SIZE , .aoefunc)
if(ad) then
call this.destroy()
endif
endmethod
method castOnAOELoc takes location loc,real radius returns nothing
call .castOnAOE(GetLocationX(loc),GetLocationY(loc),radius)
endmethod
//==================================================================================================
// A quick and dirt castOnGroup method, perhaps it'll later have castOntarget inlined, but not now
//
method castOnGroup takes group g returns nothing
local boolean ad=this.autodestroy
local unit t
if(ad) then
set this.autodestroy=false
endif
loop
set t=FirstOfGroup(g)
exitwhen(t==null)
call GroupRemoveUnit(g,t)
call .castOnTarget(t)
endloop
if(ad) then
call this.destroy()
endif
endmethod
private static method removeAbility takes nothing returns boolean
local unit u=GetTriggerUnit()
if(GetUnitUserData(u)!=0) then
call PauseUnit(u,true)
endif
//This is necessary, picture a value for recycle delay that's higher than the casting time,
//for example if the spell does dps, if you leave the dummy caster with the ability and it
//is owned by an AI player it will start casting the ability on player units, so it is
// a good idea to pause it...
set u=null
return true
endmethod
//===================================================================================================
// structinit is a scope private keyword.
//
static method structinit takes nothing returns nothing
local integer i=INITIAL_DUMMY_COUNT+1
local unit u
set .aoefunc=Condition(function xecast.filterAOE)
set .enumgroup=CreateGroup()
set .abilityRemove = CreateTrigger()
loop
exitwhen (i==0)
set u=CreateUnit(Player(15),XE_DUMMY_UNITID,0,0,0)
call TriggerRegisterUnitEvent(.abilityRemove,u,EVENT_UNIT_SPELL_ENDCAST)
call UnitAddAbility(u,'Aloc')
call UnitAddAbility(u,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(u,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(u,'Amov' ) //FIX
set .dummystack[.top]=u
set .top=.top+1
set i=i-1
endloop
call TriggerAddCondition(.abilityRemove, Condition(function xecast.removeAbility ) )
set .top=.top-1
set .instantdummy=.dummystack[.top]
set .T=CreateTimer()
set .gametime=CreateTimer()
call TimerStart(.gametime,12*60*60,false,null)
endmethod
endstruct
private function init takes nothing returns nothing
call xecast.structinit()
endfunction
endlibrary
xefx
----
This module just allows you to have movable special effects, they are actually
dummy units, you can do plenty of things like changing their position, their
height, their rotation (in the xy and in the z axis as well), color, and things
like that. It is all about assigning attributes, the only two important methods
xefx objects have are create and destroy. There are other accessory methods.
implementation
--------------
Just copy the xefx trigger to your map.
xefx object
-------------
__________________________________________________________________________________________________
static method create takes real x, real y, real facing returns xefx
--
This is the create method, it will make a new xefx for you to use, there are
initial values you must specify, like the x,y coordinate and the facing angle.
facing is in radians.
Eg. set myfx = xefx.create()
__________________________________________________________________________________________________
method destroy takes nothing returns nothing
--
This just destroys your xefx object. (call myfx.destroy() )
* List of attributes *
________________________________________________________________________________
string fxpath
----
Determines the model of the special effect, yes, you may change it after
assigning it if necessary to change the model path.
Example: set myfx.path = "abilities\thisisamadeup\modelpath.mdl"
________________________________________________________________________________
method hiddenReset takes string newfxpath, real newfacing returns nothing
----
Resets the xefx with a new effect path and a new facing angle (radians).
Avoids playing the dead animation of the previous model, notice that it is
impossible to do this withouth playing the birth animation of the new model
and without playing the sound of the dead animation of the previous model.
Example: call myfx.hiddenReset("abilities\thisisamadeup\modelpath.mdl", 0.4)
________________________________________________________________________________
method hiddenDestroy takes nothing returns nothing
----
Destroys the xefx without playing the death animation of the model. Notice
that it is impossible to do this without playing the sound of the
dead animation.
Example: call myfx.hiddenDestroy()
________________________________________________________________________________
real x
real y
real z
----
Determine the position of your special effect, you can keep moving the
effect in a periodic loop, etc.
Example: set myfx.x=myfx.x + 65.0
set myfx.y=GetUnitY(u)
set myfx.z=JumpParabola(t)
________________________________________________________________________________
real xyangle
----
The angle in the xy plane, also called 'facing' angle. (Note: uses radians)
Example: set myfx.xyangle = AngleBetweenPoints(target, source)*bj_DEGTORAD
________________________________________________________________________________
real zangle
----
The angle in the z-axis (inclination?), (Note: uses radians)
Example: set myfx.zangle = bj_PI/2 //Now the model will look towards the sky
________________________________________________________________________________
integer red
integer green
integer blue
integer alpha
----
Well, the model's vertex coloring in RGB , a is the opacity value (use
values from 0 to 255 here)
Example: set myfx.red=255
set myfx.green=0
set myfx.blue=255
set myfx.alpha=128
______________________________________________________________________________________
method recolor takes integer r, integer g , integer b, integer a returns nothing
----
This one assigns all the color values in one pass.
________________________________________________________________________________
real scale (write-only)
----
Allows you to resize the xefx object, the default scale is 1.
Example: set myfx.scale=2.0 //double size (in fact 8x)
set myfx.scale=0.5 //half size (in fact 1/8x)
________________________________________________________________________________
player owner
----
For some reason you might want to change ownership of the effect, for
example, if you use abilityid (see bellow) and the ability does damage.
Example: set myfx.owner = GetOwningPlayer(GetTriggerUnit() )
________________________________________________________________________________
integer abilityid
----
Well, you may use a xefx object to grab a passive ability, perhaps you need
it for ye candy reasons or you want to use it as a damage dealer.
Example: set myfx.abilityid = 'Hphf'
________________________________________________________________________________
integer abilityLevel
----
And with this one, you change the level of that ability.
Example: set myfx.abilityLevel = GetUnitAbilityLevel( u, s)
________________________________________________________________________________
playercolor teamcolor
----
The team color to use for the model.
Example: set somevar.teamcolor=PLAYER_COLOR_RED
set somevar.teamcolor=GetPlayerColor(GetOwningPlayer(u))
________________________________________________________________________________
method flash takes string modelpath returns nothing
----
It shows the dead animation of the model specified by modelpath. This is
in case you need this sort of eye candy.
________________________________________________________________________________
method ARGBrecolor takes ARGB color returns nothing
----
If you got the ARGB library in your map, xefx then acquires the ARGBrecolor
method, that you can use to use an ARGB object to recolor the fx's model,
in a way similar to how recolor() works.
//edit line 63,205,238 patch 1.32
library xefx initializer init requires xebasic
//**************************************************
// xefx 0.7
// --------
// Recommended: ARGB (adds ARGBrecolor method)
// For your movable fx needs
//
//**************************************************
//==================================================
globals
private constant integer MAX_INSTANCES = 8190 //change accordingly.
private constant real RECYCLE_DELAY = 4.0
//recycling, in order to show the effect correctly, must wait some time before
//removing the unit.
private timer recycler
private timer NOW
endglobals
private struct recyclebin extends array
unit u
real schedule
static recyclebin end=0
static recyclebin begin=0
static method Recycle takes nothing returns nothing
call RemoveUnit(.begin.u) //this unit is private, systems shouldn't mess with it.
set .begin.u=null
set .begin=recyclebin(integer(.begin)+1)
if(.begin==.end) then
set .begin=0
set .end=0
else
call TimerStart(recycler, .begin.schedule-TimerGetElapsed(NOW), false, function recyclebin.Recycle)
endif
endmethod
endstruct
private function init takes nothing returns nothing
set recycler=CreateTimer()
set NOW=CreateTimer()
call TimerStart(NOW,43200,true,null)
endfunction
struct xefx[MAX_INSTANCES]
public integer tag=0
private unit dummy
private effect fx=null
private real zang=0.0
private integer r=255
private integer g=255
private integer b=255
private integer a=255
private integer abil=0
static method create takes real x, real y, real facing returns xefx
local xefx this=xefx.allocate()
set this.dummy= CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x,y, facing*bj_RADTODEG)
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
return this
endmethod
method operator owner takes nothing returns player
return GetOwningPlayer(this.dummy)
endmethod
method operator owner= takes player p returns nothing
call SetUnitOwner(this.dummy,p,false)
endmethod
method operator teamcolor= takes playercolor c returns nothing
call SetUnitColor(this.dummy,c)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(this.dummy,value,value,value)
endmethod
//! textmacro XEFX_colorstuff takes colorname, colorvar
method operator $colorname$ takes nothing returns integer
return this.$colorvar$
endmethod
method operator $colorname$= takes integer value returns nothing
set this.$colorvar$=value
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
//! endtextmacro
//! runtextmacro XEFX_colorstuff("red","r")
//! runtextmacro XEFX_colorstuff("green","g")
//! runtextmacro XEFX_colorstuff("blue","b")
//! runtextmacro XEFX_colorstuff("alpha","a")
method recolor takes integer r, integer g , integer b, integer a returns nothing
set this.r=r
set this.g=g
set this.b=b
set this.a=a
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
implement optional ARGBrecolor
method operator abilityid takes nothing returns integer
return this.abil
endmethod
method operator abilityid= takes integer a returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(a!=0) then
call UnitAddAbility(this.dummy,a)
endif
set this.abil=a
endmethod
method operator abilityLevel takes nothing returns integer
return GetUnitAbilityLevel( this.dummy, this.abil)
endmethod
method operator abilityLevel= takes integer newLevel returns nothing
call SetUnitAbilityLevel(this.dummy, this.abil, newLevel)
endmethod
method flash takes string fx returns nothing
call DestroyEffect(AddSpecialEffectTarget(fx,this.dummy,"origin"))
endmethod
method operator xyangle takes nothing returns real
return GetUnitFacing(this.dummy)*bj_DEGTORAD
endmethod
method operator xyangle= takes real value returns nothing
call SetUnitFacing(this.dummy,value*bj_RADTODEG)
endmethod
method operator zangle takes nothing returns real
return this.zang
endmethod
method operator zangle= takes real value returns nothing
local integer i=R2I(value*bj_RADTODEG+90.5)
set this.zang=value
if(i>=180) then
set i=179
elseif(i<0) then
set i=0
endif
call SetUnitAnimationByIndex(this.dummy, i )
endmethod
method operator x takes nothing returns real
return GetUnitX(this.dummy)
endmethod
method operator y takes nothing returns real
return GetUnitY(this.dummy)
endmethod
method operator z takes nothing returns real
return GetUnitFlyHeight(this.dummy)
endmethod
method operator z= takes real value returns nothing
call SetUnitFlyHeight(this.dummy,value,0)
endmethod
method operator x= takes real value returns nothing
call SetUnitX(this.dummy,value)
endmethod
method operator y= takes real value returns nothing
call SetUnitY(this.dummy,value)
endmethod
method operator fxpath= takes string newpath returns nothing
if (this.fx!=null) then
call DestroyEffect(this.fx)
endif
if (newpath=="") then
set this.fx=null
else
set this.fx=AddSpecialEffectTarget(newpath,this.dummy,"origin")
endif
endmethod
method hiddenReset takes string newfxpath, real newfacing returns nothing
local real x = GetUnitX(this.dummy)
local real y = GetUnitY(this.dummy)
local real z = this.z
local real za = this.zangle
local integer level = this.abilityLevel
set fxpath=null
call RemoveUnit(this.dummy)
set this.dummy= CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x,y, newfacing*bj_RADTODEG)
if(level != 0) then
call UnitAddAbility(this.dummy, abilityid)
endif
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
set this.z = z
set zangle = za
endmethod
private method onDestroy takes nothing returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(this.fx!=null) then
call DestroyEffect(this.fx)
set this.fx=null
endif
if (recyclebin.end==MAX_INSTANCES) then
//I'd like to see this happen...
call TimerStart(recycler,0,false,function recyclebin.Recycle)
call ExplodeUnitBJ(this.dummy)
else
set recyclebin.end.u=this.dummy
set recyclebin.end.schedule=TimerGetElapsed(NOW)+RECYCLE_DELAY
set recyclebin.end= recyclebin( integer(recyclebin.end)+1)
if( recyclebin.end==1) then
call TimerStart(recycler, RECYCLE_DELAY, false, function recyclebin.Recycle)
endif
call SetUnitOwner(this.dummy,Player(PLAYER_NEUTRAL_PASSIVE),false)
endif
set this.dummy=null
endmethod
method hiddenDestroy takes nothing returns nothing
call ShowUnit(dummy,false)
call destroy()
endmethod
endstruct
endlibrary
xemissile
----------
A specialization xefx, aids at creating targeted missiles with arc and
speed and that sort of stuff, notice you'd usually have to make a new
struct and extend xemissile from it so you can declare your own methods
for handling the hit event.
implementation
--------------
First of all, you need xefx, after you implement xefx, you may just copy
the xemissile trigger to your map.
I'd also recommend you to implement the BoundSentinel library to prevent
crashes related to the xemissiles moving too down in the map, you can find the
BoundSentinel in the "Extras" trigger category.
Notice this needs at least jasshelper 0.9.E.0 to compile.
This library has two objects you can extend, xemissile and xehomingmissile.
xehomingmissile extends xemissile and is only there to provide an alternate
constructor for your convenience. You could also create a normal xemissile
and then give it a homing target and it would work the same as if you created
a xehmoingmissile.
xemissile object
-----------------
__________________________________________________________________________________________________
static method create takes real x, real y, real z, real tx, real ty, real tz returns xemissile
- - - ----------
This is the create method, it will make a new xemissile object for you to
launch, If you are extending xemissile and wish to have a custom create method
on your struct, remember that you will need to call allocate(x,y,z, tx,ty,tz)
set somevariable = myxemissile.create( GetUnitX(u),GetUnitY(u),GetUnitFlyHeight(u)+LAUNCH_OFFSET, targetx,targety,0.0 )
xehomingmissile object
-----------------
__________________________________________________________________________________________________
static method create takes real x, real y, real z, unit target, real zoffset returns xehomingmissile
- - - ----------
Same as the xemissile create method, except that the target now is not a
point, but a unit, which will be hit at zoffset from its origin point.
set somevariable = myxehomingmissile.create( GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u)+LAUNCH_OFFSET, target, HIT_OFFSET )
both objects
-----------------
_________________________________________________________________________________________________
method launch takes real speed, real arc returns nothing
- - -----------------
This launches the missile, it is a seperate method from .create in order to
keep method argument lists reasonably short, the xemissile create method
already takes 6 arguments. You will probably never create an xemissile without
launching it immediately afterwards.
The speed value determines how fast the missile will move and can later be
changed mid-flight, the arc value determines at what angle the missile will be
launched and works the same way as object editor missile arc values.
Example: call somevariable.launch(1000.0, 0.25)
_________________________________________________________________________________________________
method terminate takes nothing returns nothing
- - --------------
Call terminate() when you wish to 'kill' the missile, it will call .destroy()
automatically, I recommend you not to call .destroy() manually. Use onDestroy
to detect when it was called. You can use .x and .y on onDestroy...
Example: call somevariable.terminate()
_________________________________________________________________________________________________
method setTargetPoint takes real tx, real ty, real tz returns nothing
- - ---------
This method sets the point towards which the xemissile will fly. This point
is already automatically set by the create method, if the xemissile has a
targetUnit set then this point will be constantly updated to the position of
that unit. You can also manually set the target point with this method to
redirect the missile, if you do the missile will forget any homing target
it may have had.
Example: call somevariable.setTargetPoint(GetSpellTargetX(),GetSpellTargetY(),0.0)
_____________________________________________________________________________________________________
delegate xefx
-
A xemissile object is practically also a xefx object, you may call all of
the members in xefx for your disposal, including x,y,z (to change the position)
and height of the missile) and fxpath (the model used by the missile) please,
read the xefx documentation as it includes a lot of members used by xemissile.
________________________________________________________________________________
real speed = 0.0
- ------------------------------------------------------
The automatic movement is just a vertical arc that homes in on the target,
just like WC3 attack and spell missiles.
Speed would be the distance a xemissile can move in a second and should be
a positive value or the missile will not cooperate. Speed is automatically
set by the launch method but you can also set it manually after launch.
unit targetUnit = null
real zoffset = 0.0
- ------------------------------------------------------
By setting these two values you can make an xemissile home in on a unit.
These values are automatically set by the xehomingmissile's create method,
but you can also set them manually. If you set the unit to null or if the
unit is removed, the missile will break its homing and land at the unit's
last known coordinates.
xemissile event methods
------------------------
These are methods you may declare on your custom struct in order to listen to
the xemissile events.
________________________________________________________________________________
method onHit takes nothing returns nothing defaults nothing
- - ------------- --------
The onHit method is called when the missile hits its target. The missile will
be destroyed afterwards unless you re-launch it.
________________________________________________________________________________
method loopControl takes nothing returns nothing defaults nothing
- ------- --------
This is a method that, if declared will be called every XE_ANIMATION_PERIOD
seconds, may allow you to save a timer loop.
library xemissile requires xefx, xebasic
//****************************************************************
//*
//* xemissile 0.9
//* -------------
//* A xemissile object is a special effect that moves like a
//* WC3's attack or spell missile.
//*
//* Please use .terminate() instead of .destroy() this ensures
//* that it will be safe to destroy it (else you would have to
//* worry about destroying it during the animation loop/etc.)
//*
//* This struct is used similarly to xecollider. Instead of just
//* creating the xemissile (which works, but it would only be a
//* xefx that can move like a missile) you probably need to make
//* it do something special when it reaches its target...
//* For this reason, you need to make a new struct extending
//* xemissile that declares an onHit method, you may also declare
//* a loopControl method.
//*
//****************************************************************
//===========================================================================
// So, this exists merely so you can declare your own event handler methods
// if interfaces make your brain blow out, please skip the next four lines.
//
private interface eventHandler
method onHit takes nothing returns nothing defaults nothing
method loopControl takes nothing returns nothing defaults nothing
endinterface
//===========================================================================
private struct missile extends eventHandler
private delegate xefx fx
// movement duration parameter.
private real time
// xy movement parameters.
private real mx
private real my
private real mvx
private real mvy
private real mvxy = 1.0
// z movement parameters.
private real mz
private real mvz
private real maz
private static location l = Location(0.0,0.0)
// target parameters.
private real tx = 0.0
private real ty = 0.0
private real tz = 0.0
private unit tu = null
public real zoffset = 0.0
private boolean update = true
private boolean launched = false
private boolean dead = false
public method operator x takes nothing returns real
return .mx
endmethod
public method operator y takes nothing returns real
return .my
endmethod
public method operator z takes nothing returns real
call MoveLocation(.l, .mx,.my)
return mz-GetLocationZ(.l)
endmethod
public method operator x= takes real r returns nothing
set .update=true
set .mx=r
endmethod
public method operator y= takes real r returns nothing
set .update=true
set .my=r
endmethod
public method operator z= takes real r returns nothing
set .update=true
call MoveLocation(.l, .mx,.my)
set .mz=r+GetLocationZ(.l)
endmethod
public method operator speed takes nothing returns real
return .mvxy/XE_ANIMATION_PERIOD
endmethod
public method operator speed= takes real newspeed returns nothing
local real factor=newspeed*XE_ANIMATION_PERIOD/.mvxy
if newspeed<=0.0 then
debug call BJDebugMsg("xemissile speed error: speed must be a non-zero positive value.")
return
endif
set .mvxy=newspeed*XE_ANIMATION_PERIOD
if .launched then
set .time=.time/factor
set .mvx=.mvx*factor
set .mvy=.mvy*factor
set .mvz=.mvz*factor
set .maz=.maz*factor*factor
endif
endmethod
public method operator targetUnit takes nothing returns unit
return this.tu
endmethod
public method operator targetUnit= takes unit u returns nothing
set .update=true
set .tu=u
set .tx=GetUnitX(u)
set .ty=GetUnitY(u)
call MoveLocation(.l, .tx,.ty)
set .tz=GetUnitFlyHeight(u)+GetLocationZ(.l)+.zoffset
endmethod
public method setTargetPoint takes real x, real y, real z returns nothing
set .update=true
set .tu=null
set .tx=x
set .ty=y
call MoveLocation(.l, .tx,.ty)
set .tz=z+GetLocationZ(.l)
endmethod
//-------- Missile launcher
public method launch takes real speed, real arc returns nothing
local real dx=.tx-.mx
local real dy=.ty-.my
local real d=SquareRoot(dx*dx+dy*dy)
local real a=Atan2(dy, dx)
local real dz=.tz-.mz
set .mvxy=speed*XE_ANIMATION_PERIOD
if speed<=0.0 then
debug call BJDebugMsg("xemissile launch error: speed must be a non-zero positive value.")
return
elseif d>0.0 then
set .time=d/speed
else
set .time=XE_ANIMATION_PERIOD
endif
set .mvz=( (d*arc)/(.time/4.0) + dz/.time )*XE_ANIMATION_PERIOD // Do some mathemagics to get a proper arc.
set .dead=.dead and not(.launched) // In case this is called from the onHit method to bounce the missile.
if not .dead and not .launched then
set .launched=true
set .V[.N]=this
set .N=.N+1
if(.N==1) then
call TimerStart(.T, XE_ANIMATION_PERIOD, true, xemissile.timerLoopFunction )
endif
endif
endmethod
//-------- Constructors and destructors
static method create takes real x, real y, real z, real a returns missile
local missile this=missile.allocate()
set .mx=x
set .my=y
call MoveLocation(.l, x,y)
set .mz=z+GetLocationZ(.l)
set .fx = xefx.create(x,y,a)
set .fx.z = z
return this
endmethod
private boolean silent=false
private method destroy takes nothing returns nothing
if(this.silent) then
call this.fx.hiddenDestroy()
else
call this.fx.destroy()
endif
call .deallocate()
endmethod
method terminate takes nothing returns nothing
set this.dead=true
set this.fx.zangle=0.0
set this.fxpath=""
endmethod
// declare hiddenDestroy so people don't call directly on the delegate xefx
method hiddenDestroy takes nothing returns nothing
set silent = true
call terminate()
endmethod
//-------- Main engine
private static timer T
private static integer N=0
private static xemissile array V
private static code timerLoopFunction //I use a code var so create can be above the timerloop function, more readable
private static method timerLoop takes nothing returns nothing
local integer i=0
local integer c=0
local thistype this
local real dx
local real dy
local real d
local real a
loop
exitwhen (i==.N )
set this=.V[i] //adopt-a-instance
if .dead then
set .launched=false
set this.fx.zangle=0.0
call .destroy()
else
if .tu!=null and GetUnitTypeId(.tu)!=0 then
set .update=true
set .tx=GetUnitX(.tu)
set .ty=GetUnitY(.tu)
call MoveLocation(.l, .tx,.ty)
set .tz=GetUnitFlyHeight(.tu)+GetLocationZ(.l)+.zoffset
endif
if .update then
set .update=false
set dx=.tx-.mx
set dy=.ty-.my
set d=SquareRoot(dx*dx+dy*dy)
set a=Atan2(dy,dx)
if d>0.0 then
set .time=d/.mvxy*XE_ANIMATION_PERIOD
else
set .time=XE_ANIMATION_PERIOD
endif
set .mvx=Cos(a)*.mvxy
set .mvy=Sin(a)*.mvxy
set .fx.xyangle=a
set .maz=2*((.tz-.mz)/.time/.time*XE_ANIMATION_PERIOD*XE_ANIMATION_PERIOD-(.mvz*XE_ANIMATION_PERIOD)/.time)
endif
set .mx=.mx+.mvx
set .my=.my+.mvy
set a=.maz/2.0
set .mvz=.mvz+a
set .mz=.mz+.mvz
set .mvz=.mvz+a
set .fx.x=.mx
set .fx.y=.my
call MoveLocation(.l, .mx,.my)
set .fx.z=.mz-GetLocationZ(.l)
set .fx.zangle=Atan2(.mvz, .mvxy)
set .time=.time-XE_ANIMATION_PERIOD
if .time<=0.0 then
set .dead=true
call this.onHit()
if .dead then
set .fxpath=""
endif
endif
set .V[c]=this
set c=c+1
if( this.loopControl.exists and not this.dead ) then
call this.loopControl()
endif
endif
set i=i+1
endloop
set .N=c
if(c==0) then
call PauseTimer(.T)
endif
endmethod
static method onInit takes nothing returns nothing
set .timerLoopFunction = (function missile.timerLoop)
set .T=CreateTimer()
endmethod
endstruct
//===========================================================================
struct xemissile extends missile
public static method create takes real x,real y,real z, real tx,real ty,real tz returns xemissile
local xemissile xm = xemissile.allocate(x,y,z, Atan2(ty-y,tx-x))
call xm.setTargetPoint(tx,ty,tz)
return xm
endmethod
endstruct
struct xehomingmissile extends xemissile
public static method create takes real x,real y,real z, unit target,real zoffset returns xehomingmissile
local xehomingmissile xm = xehomingmissile.allocate(x,y,z, GetUnitX(target), GetUnitY(target), 0.0)
set xm.zoffset=zoffset
set xm.targetUnit=target
return xm
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
xepreload
---------
xepreload attacks the ability preloading issue. It is a good idea to preload
abilities you are going to add to units during the game to avoid the typical
"first-cast freeze". xepreload exploits jasshelper's inline and a timer to
minimize the time spent preloading each ability.
Install
-------
Copy the xepreload trigger to your map.
Usage
-----
_____________________________________________________________________________
function XE_PreloadAbility takes integer abilid returns nothing
----
Preloads the ability, pass it an ability id (rawcode). Notice that this
function may only work during map init. In order to use it in a library's
initializer, make sure the library requires xepreload.
* Please notice that the ability removal might trigger certain order events, try
ignoring xe dummy units in those events if necessary.
//edit line 71 patch 1.32
library xepreload requires xebasic
//******************************************************************************
// xepreload 0.9
// ---------
// 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 modules' 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
call DestroyTimer(GetExpiredTimer())
endfunction
private struct init extends array
private static method onInit takes nothing returns nothing
local timer t=CreateTimer()
set dum = CreateUnit( Player(PLAYER_NEUTRAL_PASSIVE), 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
call TimerStart(t,0.,false,function kill)
set t=null
endmethod
endstruct
endlibrary
// Arcing Text Tag v1.0.0.3 by Maker
library FloatingTextArc
globals
private constant real SIZE_MIN = 0.014 // Minimum size of text
private constant real SIZE_BONUS = 0.010 // Text size increase
private constant real TIME_LIFE = 1.0 // How long the text lasts
private constant real TIME_FADE = 0.8 // When does the text start to fade
private constant real Z_OFFSET = 30 // Height above unit
private constant real Z_OFFSET_BON = 30 // How much extra height the text gains
private constant real VELOCITY = 1.5 // How fast the text move in x/y plane
private constant real ANGLE = bj_PI/2 // Movement angle of the text. Does not apply if
// ANGLE_RND is true
private constant boolean ANGLE_RND = true // Is the angle random or fixed
private timer TMR = CreateTimer()
endglobals
struct ArcingTextTag extends array
private texttag tt
private real as // angle, sin component
private real ac // angle, cos component
private real ah // arc height
private real t // time
private real x // origin x
private real y // origin y
private string s // text
private static integer array next
private static integer array prev
private static integer array rn
private static integer ic = 0 // Instance count
private static method update takes nothing returns nothing
local thistype this=next[0]
local real p
loop
set p = Sin(bj_PI*.t)
set .t = .t - 0.03125
set .x = .x + .as
set .y = .y + .ac
call SetTextTagPos(.tt, .x, .y, Z_OFFSET + Z_OFFSET_BON * p)
call SetTextTagText(.tt, .s, SIZE_MIN + SIZE_BONUS * p)
if .t <= 0 then
set .tt = null
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
set rn[this] = rn[0]
set rn[0] = this
if next[0]==0 then
call PauseTimer(TMR)
endif
endif
set this = next[this]
exitwhen this == 0
endloop
endmethod
public static method create takes string s, unit u returns thistype
local thistype this = rn[0]
static if ANGLE_RND then
local real a = GetRandomReal(0, 2*bj_PI)
else
local real a = ANGLE
endif
if this == 0 then
set ic = ic + 1
set this = ic
else
set rn[0] = rn[this]
endif
set next[this] = 0
set prev[this] = prev[0]
set next[prev[0]] = this
set prev[0] = this
set .s = s
set .x = GetUnitX(u)
set .y = GetUnitY(u)
set .t = TIME_LIFE
set .as = Sin(a)*VELOCITY
set .ac = Cos(a)*VELOCITY
set .ah = 0.
if IsUnitVisible(u, GetLocalPlayer()) then
set .tt = CreateTextTag()
call SetTextTagPermanent(.tt, false)
call SetTextTagLifespan(.tt, TIME_LIFE)
call SetTextTagFadepoint(.tt, TIME_FADE)
call SetTextTagText(.tt, s, SIZE_MIN)
call SetTextTagPos(.tt, .x, .y, Z_OFFSET)
endif
if prev[this] == 0 then
call TimerStart(TMR, 0.03125, true, function thistype.update)
endif
return this
endmethod
endstruct
endlibrary
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
private constant boolean ALLOW_OUTSIDE_PLAYABLE_MAP_AREA = false
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns boolean
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
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
local region r = CreateRegion()
local rect map
local rect rc
if ALLOW_OUTSIDE_PLAYABLE_MAP_AREA then
set map = GetWorldBounds()
else
set map = bj_mapInitialPlayableArea
endif
set minx = GetRectMinX(map)
set miny = GetRectMinY(map)
set maxx = GetRectMaxX(map)
set maxy = GetRectMaxY(map)
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
if ALLOW_OUTSIDE_PLAYABLE_MAP_AREA then
call RemoveRect(map)
endif
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddCondition(t, Condition(function dis))
set rc=null
set map = null
endfunction
endlibrary
library MagicStream uses AutoIndex, RegisterPlayerUnitEvent, TimerUtils, Colour, xecast, xepreload, xedamage, optional BoundSentinel, FloatingTextArc
//===================================================================
//Settings
//===================================================================
globals
private constant integer MAGIC_STREAM_ID = 'A002'
private constant integer MAGIC_STREAM_UPGRADED_ID = 'A01C'
private constant integer UPGRADE_SPELLBOOK_ID = 'A00Y'
private constant string LIGHTNING_TYPE = "HWPB"
private constant real BOUNCE_RADIUS = 350.00
private constant real LIGHTNING_DURATION = 1.50 //sec.
private constant real JUMP_PEREODIC = 0.25 //sec.
private constant real CHANCE_CAST_NEGATIVE = 0.50 //50% chance
private constant integer QUEST_TARGET = 45
private constant boolean PRELOAD = true
private constant string QUEST_COMPLETED_EFFECT = "Objects\\Spawnmodels\\Other\\ToonBoom\\ToonBoom.mdl"
private constant real UNIT_ORIGIN_Z = 45.00
private integer array DUMMY_ABILITY_ID[9]
private string array DUMMY_ABILITY_ORDER[9]
endglobals
private function dummyCastSetup takes nothing returns nothing
set DUMMY_ABILITY_ID[0] = 'A008'
set DUMMY_ABILITY_ORDER[0] = "innerfire"
set DUMMY_ABILITY_ID[1] = 'A00T'
set DUMMY_ABILITY_ORDER[1] = "rejuvination"
set DUMMY_ABILITY_ID[2] = 'A00U'
set DUMMY_ABILITY_ORDER[2] = "bloodlust"
set DUMMY_ABILITY_ID[3] = 'A009'
set DUMMY_ABILITY_ORDER[3] = "polymorph"
set DUMMY_ABILITY_ID[4] = 'A00A'
set DUMMY_ABILITY_ORDER[4] = "slow"
set DUMMY_ABILITY_ID[5] = 'A00I'
set DUMMY_ABILITY_ORDER[5] = "faeriefire"
set DUMMY_ABILITY_ID[6] = 'A00E'
set DUMMY_ABILITY_ORDER[6] = "cripple"
set DUMMY_ABILITY_ID[7] = 'A010'
set DUMMY_ABILITY_ORDER[7] = "curse"
set DUMMY_ABILITY_ID[8] = 'A00V'
set DUMMY_ABILITY_ORDER[8] = "invisibility"
set DUMMY_ABILITY_ID[9] = 'A000'
set DUMMY_ABILITY_ORDER[9] = "frostarmor"
endfunction
//Damage Options:
private function damageOptions takes xedamage xed returns nothing
set xed.dtype = DAMAGE_TYPE_MAGIC
set xed.atype = ATTACK_TYPE_NORMAL
set xed.wtype = WEAPON_TYPE_WHOKNOWS
set xed.visibleOnly = true //damage visible unit?
set xed.damageAllies = true //damage allies if true
call xed.factor(UNIT_TYPE_MAGIC_IMMUNE, 0.0)
call xed.factor(UNIT_TYPE_MECHANICAL, 0.0)
call xed.abilityFactor('Aeth', 0.0) //Ghost (Visible)
call xed.abilityFactor('Avul', 0.0) //Invulnerable (Neutral)
call xed.useSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", "origin")
endfunction
private function getDamage takes integer level, integer heroInt returns real
return heroInt * ( 1.25 + ( 0.5 * level ) ) //1.75, 2.25 , 2.75
endfunction
private function getJumpsCount takes integer level returns integer
return 2 + level
endfunction
//===================================================================
//End Settings
//===================================================================
private struct MagicStreamLightningEffect
private lightning wave
private integer ticks
private real alpha
private unit u0
private unit u1
private static method pereodic takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype d = GetTimerData(t)
local real ux0 = GetUnitX(d.u0)
local real uy0 = GetUnitY(d.u0)
local real uz0 = BlzGetUnitZ(d.u0) + GetUnitFlyHeight(d.u0) + UNIT_ORIGIN_Z
local real ux1 = GetUnitX(d.u1)
local real uy1 = GetUnitY(d.u1)
local real uz1 = BlzGetUnitZ(d.u1) + GetUnitFlyHeight(d.u1) + UNIT_ORIGIN_Z
if d.ticks <=0 then
call DestroyLightning(d.wave)
call ReleaseTimer(t)
call d.destroy()
else
set d.ticks = d.ticks - 1
call MoveLightningEx(d.wave, true, ux0, uy0, uz0, ux1, uy1, uz1)
if GetLightningColorA(d.wave) > d.alpha then
call SetLightningColor(d.wave, 1., 1., 1., GetLightningColorA(d.wave) - d.alpha)
endif
endif
set t = null
endmethod
static method create takes unit u0, unit u1 returns thistype
local real ux0 = GetUnitX(u0)
local real uy0 = GetUnitY(u0)
local real uz0 = BlzGetUnitZ(u0) + GetUnitFlyHeight(u0) + UNIT_ORIGIN_Z
local real ux1 = GetUnitX(u1)
local real uy1 = GetUnitY(u1)
local real uz1 = BlzGetUnitZ(u1) + GetUnitFlyHeight(u1) + UNIT_ORIGIN_Z
local timer tim = NewTimer()
local thistype data = thistype.allocate()
set data.u0 = u0
set data.u1 = u1
set data.ticks = R2I(LIGHTNING_DURATION / XE_ANIMATION_PERIOD)
set data.alpha = 1. / I2R(data.ticks)
set data.wave = AddLightningEx(LIGHTNING_TYPE, true, ux0, uy0, uz0, ux1, uy1, uz1)
call SetTimerData(tim, data)
call TimerStart(tim, XE_ANIMATION_PERIOD, true, function thistype.pereodic)
set tim = null
return data
endmethod
endstruct
struct MagicStream
unit cast
unit target
timer tim
integer lvl
group gr
integer count
boolean upgraded
integer casterId
static xecast xec
static xedamage xed
static integer array QUEST_PROGRESS
static boolean array QUEST_COMPLETED
private method questHandle takes unit targetHero returns nothing
local effect sfx
local ability abi
local string tooltip
local integer maxLevel
local integer n
if IsUnitType(targetHero,UNIT_TYPE_HERO) and not QUEST_COMPLETED[this.casterId] then
set QUEST_PROGRESS[this.casterId] = QUEST_PROGRESS[this.casterId] + 1
static if LIBRARY_FloatingTextArc then
call ArcingTextTag.create("|cffffd700"+"["+I2S(QUEST_PROGRESS[this.casterId])+"/"+I2S(QUEST_TARGET)+"]"+"|r", targetHero)
endif
if QUEST_PROGRESS[this.casterId] >= QUEST_TARGET then
set QUEST_COMPLETED[this.casterId] = true
call BJDebugMsg("Player " + COLOUR[GetPlayerId(GetOwningPlayer(this.cast))] + GetPlayerName(GetOwningPlayer(this.cast)) + "|r complete |cff99b4d1Magic Stream |rquest.")
set sfx = AddSpecialEffectTarget(QUEST_COMPLETED_EFFECT, this.cast, "origin")
call BlzSetSpecialEffectScale(sfx, 3.)
call DestroyEffect(sfx)
//'ANeg' fix (patch 1.32) :
set abi = BlzGetUnitAbility(this.cast,MAGIC_STREAM_ID)
set maxLevel = BlzGetAbilityIntegerField(abi,ABILITY_IF_LEVELS)
set n = 0
loop
exitwhen n == maxLevel
set tooltip = BlzGetAbilityTooltip(MAGIC_STREAM_UPGRADED_ID, n)
call BlzSetAbilityStringLevelField(abi, ABILITY_SLF_TOOLTIP_NORMAL, n, tooltip)
set tooltip = BlzGetAbilityExtendedTooltip(MAGIC_STREAM_UPGRADED_ID, n)
call BlzSetAbilityStringLevelField(abi, ABILITY_SLF_TOOLTIP_NORMAL_EXTENDED, n, tooltip)
set n = n + 1
endloop
call UnitAddAbility(this.cast,UPGRADE_SPELLBOOK_ID)
call BlzUnitDisableAbility(this.cast,UPGRADE_SPELLBOOK_ID,true,true)
endif
endif
set sfx = null
set abi = null
endmethod
private method singleEffect takes unit u0, unit u1 returns nothing
local integer rand = GetRandomInt(0, DUMMY_ABILITY_ID.size)
set xec.abilityid = DUMMY_ABILITY_ID[rand]
set xec.orderstring = DUMMY_ABILITY_ORDER[rand]
set xec.level = this.lvl
set xec.owningplayer = GetOwningPlayer(this.cast)
if xed.allowedTarget(this.cast, u1 ) then
call MagicStreamLightningEffect.create( u0, u1 )
call xed.damageTarget(this.cast, u1, getDamage(this.lvl, GetHeroInt(this.cast,true)))
call xec.castOnTarget(u1)
call this.questHandle(u1)
endif
endmethod
private static method chainJump takes nothing returns nothing
local timer tim = GetExpiredTimer()
local thistype d = GetTimerData(tim)
local group tempGroup = CreateGroup()
local unit first
local unit targetFound = null
call GroupEnumUnitsInRange(tempGroup, GetUnitX(d.target), GetUnitY(d.target), BOUNCE_RADIUS, null)
loop
set first = FirstOfGroup(tempGroup)
exitwhen first==null
if not IsUnitInGroup(first, d.gr) and xed.allowedTarget(d.cast, first ) then
call GroupClear(tempGroup)
set targetFound = first
endif
call GroupRemoveUnit(tempGroup, first)
endloop
call DestroyGroup(tempGroup)
if targetFound == null or ( d.count >= getJumpsCount(d.lvl) and not d.upgraded ) then
call ReleaseTimer(tim)
call d.destroy()
else
call GroupAddUnit(d.gr, targetFound)
call d.singleEffect(d.target, targetFound)
set d.target = targetFound
set d.count = d.count + 1
endif
set tim = null
set tempGroup = null
set targetFound = null
set first = null
endmethod
static method create takes unit caster, unit trget returns thistype
local thistype data = thistype.allocate()
set data.tim = NewTimer()
set data.cast = GetTriggerUnit()
set data.target = GetSpellTargetUnit()
set data.gr = CreateGroup()
set data.count = 0
set data.casterId = GetUnitId(data.cast)
if GetUnitAbilityLevel(data.cast, MAGIC_STREAM_ID) > 0 then
set data.lvl = GetUnitAbilityLevel(data.cast, MAGIC_STREAM_ID)
debug call BJDebugMsg("debug: create fade bolt")
endif
if GetUnitAbilityLevel(data.cast, MAGIC_STREAM_UPGRADED_ID) > 0 then
set data.lvl = GetUnitAbilityLevel(data.cast, MAGIC_STREAM_UPGRADED_ID)
set data.upgraded = true
debug call BJDebugMsg("debug: create fade bolt upgraded")
endif
call GroupAddUnit(data.gr, data.target)
call SetTimerData(data.tim, data)
call data.singleEffect(data.cast, data.target)
call TimerStart(data.tim, JUMP_PEREODIC, true, function thistype.chainJump)
return data
endmethod
private static method spellEffect takes nothing returns nothing
if GetSpellAbilityId() == MAGIC_STREAM_ID or GetSpellAbilityId() == MAGIC_STREAM_UPGRADED_ID then
call thistype.create(GetTriggerUnit(),GetSpellTargetUnit())
endif
endmethod
private static method onInit takes nothing returns nothing
local integer i = 0
call dummyCastSetup()
if PRELOAD then //preload enabled?
call XE_PreloadAbility(MAGIC_STREAM_ID)
call XE_PreloadAbility(MAGIC_STREAM_UPGRADED_ID)
call XE_PreloadAbility(UPGRADE_SPELLBOOK_ID)
loop
call XE_PreloadAbility(DUMMY_ABILITY_ID[i])
exitwhen i == DUMMY_ABILITY_ID.size
set i = i + 1
endloop
endif
//xe damage init:
set xed = xedamage.create()
call damageOptions(xed)
//xe cast init:
set xec = xecast.create()
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.spellEffect)
endmethod
endstruct
endlibrary
scope Alliance initializer init
private function init takes nothing returns nothing
call SetPlayerAllianceStateVisionBJ( Player(1), Player(0), true )
call SetPlayerAllianceStateControlBJ( Player(1), Player(0), true )
call SetPlayerAllianceStateFullControlBJ( Player(1), Player(0), true )
call SetPlayerAllianceStateVisionBJ( Player(6), Player(0), true )
call SetPlayerAllianceStateControlBJ( Player(6), Player(0), true )
call SetPlayerAllianceStateFullControlBJ( Player(6), Player(0), true )
call SetPlayerAllianceStateVisionBJ( Player(PLAYER_NEUTRAL_AGGRESSIVE), Player(0), true )
call SetPlayerAllianceStateControlBJ( Player(PLAYER_NEUTRAL_AGGRESSIVE), Player(0), true )
call SetPlayerAllianceStateFullControlBJ( Player(PLAYER_NEUTRAL_AGGRESSIVE), Player(0), true )
call SetPlayerAllianceStateAllyBJ(Player(1), Player(0), false)
call SetPlayerAllianceStateAllyBJ(Player(0), Player(1), false)
call SetPlayerAllianceStateAllyBJ(Player(0), Player(6), true)
call SetPlayerAllianceStateAllyBJ(Player(6), Player(0), true)
endfunction
endscope
scope StopRun initializer init
private function damaged takes nothing returns boolean
local unit t = GetTriggerUnit()
local unit u = GetEventDamageSource()
local real dmg = GetEventDamage()
if GetUnitAbilityLevel(t,'A00Q') > 0 and not IsUnitAlly(t,GetOwningPlayer(u)) and dmg > 0. and GetOwningPlayer(u)!=Player(PLAYER_NEUTRAL_AGGRESSIVE) and not IsUnitSelected(t,Player(0)) then
call IssueImmediateOrder(t,"stop")
endif
return true
endfunction
private function init takes nothing returns nothing
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DAMAGED, function damaged)
endfunction
endscope
scope DebugUnitZ initializer init
globals
unit SELECTED_UNIT = null
endglobals
private function select takes nothing returns nothing
set SELECTED_UNIT = GetTriggerUnit()
endfunction
private function printZ takes nothing returns nothing
debug call BJDebugMsg("Z: "+R2S(BlzGetUnitZ(SELECTED_UNIT)))
debug call BJDebugMsg("LocZ: "+R2S(GetLocationZ((GetUnitLoc(SELECTED_UNIT)))))
debug call BJDebugMsg("H: "+R2S(GetUnitFlyHeight(SELECTED_UNIT)))
endfunction
//===========================================================================
private function init takes nothing returns nothing
local trigger trg0 = CreateTrigger( )
local trigger trg1 = CreateTrigger( )
call TriggerRegisterPlayerSelectionEventBJ( trg0, Player(0), true )
call TriggerAddAction( trg0, function select )
call TriggerRegisterPlayerChatEvent( trg1, Player(0), "-z", true )
call TriggerAddAction( trg1, function printZ )
endfunction
endscope
function Trig_DamageDetect_Actions takes nothing returns nothing
local real damage = GetEventDamage()
local unit u = GetEventDamageSource()
local unit t = GetTriggerUnit()
local damagetype dt = BlzGetEventDamageType()
local attacktype at = BlzGetEventAttackType()
if damage > 0. then
if dt != DAMAGE_TYPE_NORMAL and at == ATTACK_TYPE_NORMAL then
call ArcingTextTag.create("|cffb9d1ea"+I2S(R2I(damage))+"|r", t)
else
call ArcingTextTag.create("|cffa52a2a"+I2S(R2I(damage))+"|r", t)
endif
endif
set u = null
set t = null
set dt = null
set at = null
endfunction
//===========================================================================
function InitTrig_DamageDetect takes nothing returns nothing
set gg_trg_DamageDetect = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_DamageDetect, EVENT_PLAYER_UNIT_DAMAGED )
call TriggerAddAction( gg_trg_DamageDetect, function Trig_DamageDetect_Actions )
endfunction
function Trig_ShowSpellName_Conditions takes nothing returns boolean
return GetUnitTypeId(GetTriggerUnit()) != XE_DUMMY_UNITID
endfunction
function Trig_ShowSpellName_Actions takes nothing returns nothing
local string abilityName = StringCase(GetAbilityName(GetSpellAbilityId()),true)
static if LIBRARY_FloatingTextArc then
call ArcingTextTag.create("|cffb9d1ea"+abilityName+"!|r",GetTriggerUnit())
endif
endfunction
//===========================================================================
function InitTrig_ShowSpellName takes nothing returns nothing
set gg_trg_ShowSpellName = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_ShowSpellName, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_ShowSpellName, Condition( function Trig_ShowSpellName_Conditions ) )
call TriggerAddAction( gg_trg_ShowSpellName, function Trig_ShowSpellName_Actions )
endfunction
library explodeUnit uses xefx
globals
private constant string LIGHTNING_TYPE = "FORK"
constant string MODEL = "Objects\\Spawnmodels\\Human\\HumanLargeDeathExplode\\HumanLargeDeathExplode.mdl"//"Abilities\\Weapons\\MeatwagonMissile\\MeatwagonMissile.mdl"
endglobals
globals
private location temp_loc = Location(0,0)
endglobals
function Parabola takes real h, real d, real x returns real
return (4 * h / d) * (d - x) * (x / d)
endfunction
function GetZ takes real x, real y returns real
call MoveLocation(temp_loc,x,y)
return GetLocationZ(temp_loc)
endfunction
struct Light
lightning l
timer tim
xefx d0
xefx d1
integer ticks
real lcolor
private static method lightningMove takes nothing returns nothing
local thistype this=GetTimerData(GetExpiredTimer())
if .ticks <=0 then
call DestroyLightning(.l)
call ReleaseTimer(.tim)
call .destroy()
else
set .ticks=.ticks-1
call MoveLightningEx(.l,true,.d0.x,.d0.y,.d0.z+GetZ(.d0.x,.d0.y),.d1.x,.d1.y,.d1.z+GetZ(.d1.x,.d1.y))
//call BJDebugMsg(R2S(.lcolor))
if GetLightningColorA(.l)>.lcolor then
call SetLightningColor(.l,1.,1.,1.,GetLightningColorA(.l)-.lcolor)
endif
endif
endmethod
static method createLighting takes xefx d0, xefx d1, real dur returns thistype
local thistype this = thistype.allocate()
set .tim = NewTimer()
set .d0=d0
set .d1=d1
set .ticks=R2I(dur/XE_ANIMATION_PERIOD + .5)
set .l=AddLightningEx(LIGHTNING_TYPE,true,.d0.x,.d0.y,.d0.z+GetZ(.d0.x,.d0.y),.d1.x,.d1.y,.d1.z+GetZ(.d1.x,.d1.y))
set .lcolor = 1./.ticks
call SetTimerData(.tim,this)
call TimerStart(.tim,XE_ANIMATION_PERIOD,true,function thistype.lightningMove)
return this
endmethod
endstruct
struct MeatFly
xefx meat
real angle
real dist
real range
real speed
real dur
timer tim
integer ticks
real maxheight
private static method pereodic takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if .dist > 0. then
set .dist = .dist - .speed
set meat.x = meat.x + .speed * Cos(.angle)
set meat.y = meat.y + .speed * Sin(.angle)
set meat.z = (Parabola(.maxheight,.range,.dist))
else
call meat.hiddenDestroy()
call ReleaseTimer(.tim)
call .destroy()
endif
endmethod
static method createMeat takes real x,real y, string model returns thistype
local thistype this = thistype.allocate()
set .angle = GetRandomReal(0.,360.) * bj_DEGTORAD
set .dur = GetRandomReal(1.25,1.75)
set .range = GetRandomReal(350.,850.)
set .dist = .range
set .ticks = R2I((.dur / XE_ANIMATION_PERIOD + .5 ))
set .speed = .range / .ticks
set .maxheight = GetRandomReal(150.,450.)
set .tim = NewTimer()
set .meat = xefx.create(x, y, .angle)
set .meat.fxpath = model
set .meat.scale = 1.
call meat.flash("Abilities\\Spells\\Items\\AIlb\\AIlbSpecialArt.mdl")
call SetTimerData(.tim, this)
call TimerStart(.tim, XE_ANIMATION_PERIOD, true, function thistype.pereodic)
return this
endmethod
endstruct
function Explode takes unit u returns nothing
local MeatFly d0
local MeatFly d1
local MeatFly d2
local real x =GetUnitX(u)
local real y = GetUnitY(u)
call ExplodeUnitBJ(u)
if u != null and not IsUnitType(u,UNIT_TYPE_MECHANICAL) then
call DestroyEffect(AddSpecialEffect("Objects\\Spawnmodels\\Human\\HumanLargeDeathExplode\\HumanLargeDeathExplode.mdl",x,y))
call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact.mdl",x,y))
//call RemoveUnit(u)
set d0 = MeatFly.createMeat(x,y, "Abilities\\Weapons\\MeatwagonMissile\\MeatwagonMissile.mdl")
set d1 = MeatFly.createMeat(x,y,MODEL)
set d2 = MeatFly.createMeat(x,y,MODEL)
call Light.createLighting(d0.meat,d1.meat,1)
call Light.createLighting(d0.meat,d2.meat,1)
//call Light.createLighting(d2.meat,d0.meat,1)
endif
endfunction
endlibrary
function Trig_createPeons_Actions takes nothing returns nothing
local integer i
//local real angle = 360. / COUNT
local real x
local real y
local real dx
local real dy
local real a
local real dist
local real tx
local real ty
local real COUNT = 5
if GetSpellAbilityId() == 'A00X' then
set tx = GetSpellTargetX()
set ty = GetSpellTargetY()
set i = 0
loop
exitwhen i ==COUNT//COUNT
set a = (( 360. / COUNT ) * i ) * bj_DEGTORAD
set x = tx + 175 * Cos(a)
set y = ty + 175 * Sin(a)
call CreateUnit(Player(GetRandomInt(0,1)),'opeo',x,y,a*bj_RADTODEG)
set i=i+1
endloop
endif
endfunction
//===========================================================================
function InitTrig_CreatePeons takes nothing returns nothing
set gg_trg_CreatePeons = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_CreatePeons, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddAction( gg_trg_CreatePeons, function Trig_createPeons_Actions )
endfunction
//**********************************************
//* Sheep staff.
//*
//* A quick sample for xecast.AOEloc
//*
//*
//----------------------------------------------
scope SheepStaff initializer init
private function spellIdMatch takes nothing returns boolean
return (GetSpellAbilityId()=='A01F')
endfunction
private function onSpellCast takes nothing returns nothing
local xecast xc = xecast.createA() //CreateA so we don't have to destroy the object after the cast.
local unit u = GetTriggerUnit()
local location loc = GetSpellTargetLoc() //The target point
call SetUnitAnimation(u, "attack") // Let's focus on the look of the item cast for a second...
//==============================================
// Here, we do assignments:
//
set xc.abilityid = 'A009' //* Cast ability A005
set xc.level = GetUnitAbilityLevel(u, 'A01F' ) //* Same level as trigger ability
set xc.orderstring = "polymorph" //* The order is polymorph
set xc.owningplayer = GetOwningPlayer(u) //* The owning player, determines targets and bounty credits
call xc.castOnAOELoc( loc, 200.0 ) //* Our castOnAOE call, using the location version
//* A radius of 200.0
// Since createA was used, no need to destroy the xecast object
call RemoveLocation(loc) //We need to clean the point.
set loc=null
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerAddCondition(t, Condition( function spellIdMatch) )
call TriggerAddAction(t, function onSpellCast)
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
set t=null
endfunction
endscope