Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_CameraForced_Actions takes nothing returns nothing
local camerasetup c = GetCurrentCameraSetup()
local player p = GetTriggerPlayer()
local real Dist = CameraSetupGetField(c, CAMERA_FIELD_TARGET_DISTANCE ) + 200
if (GetLocalPlayer() == p) then
// Use only local code (no net traffic) within this block to avoid desyncs.
call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, Dist, 0.10)
call SetCameraField(CAMERA_FIELD_FARZ, 5000., 0.10)
endif
set p = null
set c = null
set Dist = 0.00
endfunction
//===========================================================================
function InitTrig_CameraForced takes nothing returns nothing
set gg_trg_CameraForced = CreateTrigger( )
call TriggerRegisterPlayerEvent(gg_trg_CameraForced, Player(0), EVENT_PLAYER_END_CINEMATIC)
call TriggerAddAction( gg_trg_CameraForced, function Trig_CameraForced_Actions )
set gg_trg_CameraForced = null
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
//
// _ ___ ___ ___ _______________________________________________
// /_\ |_ _| \/ __| || A D V A N C E D I N D E X I N G ||
// / _ \ | || |) \__ \ || A N D ||
// /_/ \_\___|___/|___/ || D A T A S T O R A G E ||
// By Jesus4Lyf ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// v 1.1.0
// What is AIDS?
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// AIDS assigns unique integers between 1 and 8191 to units which enter
// the map. These can be used for arrays and data attaching.
//
// AIDS also allows you to define structs which are created automatically
// when units enter the map, and filtering which units should be indexed
// as well as for which units these structs should be created.
//
// How to implement?
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Simply create a new trigger object called AIDS, go to 'Edit -> Convert
// to Custom Text', and replace everything that's there with this script.
//
// Save the map, close it, reopen it, and then delete the "!" from the
// FAR left side of the next lines (so "external" will line up with this line):
// external ObjectMerger w3a Adef AIDS anam "State Detection" ansf "(AIDS)" aart "" arac 0
//
// At the top of the script, there is a 'UnitIndexingFilter' constant
// function. If the function returns true for the unit, then that unit
// will be automatically indexed. Setting this to true will automatically
// index all units. Setting it to false will disable automatic indexing.
//
// Functions:
// ¯¯¯¯¯¯¯¯¯¯¯¯
// function GetUnitId takes unit u returns integer
// - This returns the index of an indexed unit. This will return 0
// if the unit has not been indexed.
// - This function inlines. It does not check if the unit needs an
// index. This function is for the speed freaks.
// - Always use this if 'UnitIndexingFilter' simply returns true.
//
// function GetUnitIndex takes unit u returns integer
// - This will return the index of a unit if it has one, or assign
// an index if the unit doesn't have one (and return the new index).
// - Use this if 'UnitIndexingFilter' doesn't return true.
//
// function GetIndexUnit takes integer index returns unit
// - This returns the unit which has been assigned the 'index'.
//
// AIDS Structs:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// - Insert: //! runtextmacro AIDS() at the top of a struct to make it
// an AIDS struct.
// - AIDS structs cannot be created or destroyed manually. Instead, they
// are automatically created when an appropriate unit enters the map.
// - You cannot give members default values in their declaration.
// (eg: private integer i=5 is not allowed)
// - You cannot use array members.
// - AIDS structs must "extend array". This will remove some unused
// functions and enforce the above so there will be no mistakes.
// - There are four optional methods you can use in AIDS structs:
// - AIDS_onCreate takes nothing returns nothing
// - This is called when the struct is 'created' for the unit.
// - In here you can assign members their default values, which
// you would usually assign in their declarations.
// (eg: set this.i=5)
// - AIDS_onDestroy takes nothing returns nothing
// - This is called when the struct is 'destroyed' for the unit.
// - This is your substitute to the normal onDestroy method.
// - AIDS_filter takes unit u returns boolean
// - This is similar to the constant filter in the main system.
// - Each unit that enters the map will be tested by each AIDS
// struct filter. If it returns true for that unit, that unit
// will be indexed if it was not already, the AIDS struct will
// have its AIDS_onCreate method called, and later have its
// AIDS_onDestroy method called when the index is recycled.
// - Not declaring this will use the default AIDS filter instead.
// - AIDS_onInit takes nothing returns nothing
// - This is because I stole your onInit function with my textmacro.
// - You can use '.unit' from any AIDS struct to get the unit for which
// the struct is for.
// - The structs id will be the units index, so getting the struct for
// a unit inlines to a single native call, and you can typecast between
// different AIDS structs. This is the premise of AIDS.
// - Never create or destroy AIDS structs directly.
// - You can call .AIDS_addLock() and AIDS_removeLock() to increase or
// decrease the lock level on the struct. If a struct's lock level is
// not 0, it will not be destroyed until it is reduced to 0. Locks just
// put off AIDS struct destruction in case you wish to attach to a timer
// or something which must expire before the struct data disappears.
// Hence, not freeing all locks will leak the struct (and index).
//
// PUI and AutoIndex:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// - AIDS includes the PUI textmacros and the AutoIndex module, because
// these systems are not compatible with AIDS but have valid and distinct
// uses.
// - The PUI textmacros are better to use for spells than AIDS structs,
// because they are not created for all units, just those targetted by
// the spell (or whatever else is necessary).
// - The AutoData module is good for very simple array syntax for data
// attachment (although I don't recommend that people actually use it,
// it's here mostly for compatability). Note that unlike the PUI textmacros,
// units must pass the AIDS filter in order for this module to work with
// them. This is exactly as the same as in AutoIndex itself (AutoIndex
// has a filter too).
//
// Thanks:
// ¯¯¯¯¯¯¯¯¯
// - Romek, for writing 90% of this user documentation, challenging my
// interface, doing some testing, suggesting improvements and inspiring
// me to re-do my code to include GetUnitIndex as non-inlining.
// - grim001, for writing the AutoData module, and AutoIndex. I used the
// on-enter-map method that he used. Full credits for the AutoData module.
// - Cohadar, for writing his PUI textmacros. Full credits to him for these,
// except for my slight optimisations for this system.
// Also, I have used an optimised version of his PeriodicRecycler from
// PUI in this system to avoid needing a RemoveUnitEx function.
// - Vexorian, for helping Cohadar on the PUI textmacro.
// - Larcenist, for suggesting the AIDS acronym. Originally he suggested
// 'Alternative Index Detection System', but obviously I came up with
// something better. In fact, I'd say it looks like the acronym was
// an accident. Kinda neat, don't you think? :P
//
// Final Notes:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// - With most systems, I wouldn't usually add substitutes for alternative
// systems. However, UnitData systems are an exception, because they
// are incompatible with eachother. Since using this system forbids
// people from using the real PUI or AutoIndex, and a lot of resources
// use either of these, it made sense not to break them all.
//
// - If this documentation confused you as to how to use the system, just
// leave everything as default and use GetUnitId everywhere.
//
// - To use this like PUI (if you don't like spamming indices) simply
// make the AIDS filter return false, and use GetUnitIndex.
//
library AIDS initializer InitAIDS
//==============================================================================
// Configurables
//
globals
private constant boolean USE_PERIODIC_RECYCLER = false
private constant real PERIOD = 0.03125 // Recycles 32 units/second max.
// Lower to be able to recycle faster.
// Only used if USE_PERIODIC_RECYCLER
// is set to true.
private constant integer LEAVE_DETECTION_ABILITY = 'AIDS'
endglobals
private function UnitIndexingFilter takes unit u returns boolean
return true
endfunction
//==============================================================================
// System code
//
globals
// The unit stored at an index.
private unit array IndexUnit
private integer array LockLevel
endglobals
//==============================================================================
globals
// Recycle stack
private integer array RecycledIndex
private integer MaxRecycledIndex = 0
// Previous highest index
private integer MaxIndex = 0
endglobals
//==============================================================================
globals
private integer array DecayingIndex
private integer MaxDecayingIndex=0
private integer DecayChecker=0
endglobals
globals
private timer UndefendTimer=CreateTimer()
private integer array UndefendIndex
private integer UndefendStackIndex=0
endglobals
globals
private integer array UndefendExpiringIndex
private integer UndefendExpiringIndexLevel=0
endglobals
//==============================================================================
globals
// The Add/Remove stack (or assign/recycle stack).
//
// Indexing can become recusive since units can be created on index
// assignment or deallocation.
// To support this, a stack is used to store the event response results.
private integer ARStackLevel=0
private integer array ARStackIndex
private unit array ARStackUnit
// A later discovery revealed that the Add/Remove stack did not need to be
// used for deallocation. The alternative used works fine...
endglobals
public constant function GetEnteringIndexUnit takes nothing returns unit
return ARStackUnit[ARStackLevel]
endfunction
public function GetIndexOfEnteringUnit takes nothing returns integer
// Called in AIDS structs when units do not pass the initial AIDS filter.
if ARStackIndex[ARStackLevel]==0 then
// Get new index, from recycler first, else new.
// Store the current index on the (new) top level of the AR stack.
if MaxRecycledIndex==0 then // Get new.
set MaxIndex=MaxIndex+1
set ARStackIndex[ARStackLevel]=MaxIndex
else // Get from recycle stack.
set ARStackIndex[ARStackLevel]=RecycledIndex[MaxRecycledIndex]
set MaxRecycledIndex=MaxRecycledIndex-1
endif
// Store index on unit.
call SetUnitUserData(ARStackUnit[ARStackLevel],ARStackIndex[ARStackLevel])
set IndexUnit[ARStackIndex[ARStackLevel]]=ARStackUnit[ARStackLevel]
// Add index to recycle list.
set MaxDecayingIndex=MaxDecayingIndex+1
set DecayingIndex[MaxDecayingIndex]=ARStackIndex[ARStackLevel]
endif
return ARStackIndex[ARStackLevel]
endfunction
public constant function GetIndexOfEnteringUnitAllocated takes nothing returns integer
// Called in AIDS structs when units have passed the initial AIDS filter.
return ARStackIndex[ARStackLevel]
endfunction
public constant function GetDecayingIndex takes nothing returns integer
static if USE_PERIODIC_RECYCLER then
return DecayingIndex[DecayChecker]
else
return UndefendExpiringIndex[UndefendExpiringIndexLevel]
endif
endfunction
//==============================================================================
globals
// For structs and such which need to do things on unit index assignment.
private trigger OnEnter=CreateTrigger()
// The same, but for when units pass the initial filter anyway.
private trigger OnEnterAllocated=CreateTrigger()
// For structs and such which need to do things on unit index deallocation.
private trigger OnDeallocate=CreateTrigger()
endglobals
public function RegisterOnEnter takes boolexpr b returns triggercondition
return TriggerAddCondition(OnEnter,b)
endfunction
public function RegisterOnEnterAllocated takes boolexpr b returns triggercondition
return TriggerAddCondition(OnEnterAllocated,b)
endfunction
public function RegisterOnDeallocate takes boolexpr b returns triggercondition
return TriggerAddCondition(OnDeallocate,b)
endfunction
//==============================================================================
function GetIndexUnit takes integer index returns unit
debug if index==0 then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the unit of index 0.")
debug elseif IndexUnit[index]==null then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the unit of unassigned index.")
debug endif
return IndexUnit[index]
endfunction
function GetUnitId takes unit u returns integer
debug if u==null then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the id (inlines) of null unit.")
debug elseif GetUnitUserData(u)==0 then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to use GetUnitId (inlines) when you should be using GetUnitIndex (unit didn't pass filter).")
debug endif
return GetUnitUserData(u)
endfunction
globals//locals
private integer getindex
endglobals
function GetUnitIndex takes unit u returns integer // Cannot be recursive.
debug if u==null then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the index of null unit.")
debug endif
set getindex=GetUnitUserData(u)
if getindex==0 then
// Get new index, from recycler first, else new.
// Store the current index in getindex.
if MaxRecycledIndex==0 then // Get new.
set MaxIndex=MaxIndex+1
set getindex=MaxIndex
else // Get from recycle stack.
set getindex=RecycledIndex[MaxRecycledIndex]
set MaxRecycledIndex=MaxRecycledIndex-1
endif
// Store index on unit.
call SetUnitUserData(u,getindex)
set IndexUnit[getindex]=u
static if USE_PERIODIC_RECYCLER then
// Add index to recycle list.
set MaxDecayingIndex=MaxDecayingIndex+1
set DecayingIndex[MaxDecayingIndex]=getindex
else
// Add leave detection ability.
call UnitAddAbility(ARStackUnit[ARStackLevel],LEAVE_DETECTION_ABILITY)
call UnitMakeAbilityPermanent(ARStackUnit[ARStackLevel],true,LEAVE_DETECTION_ABILITY)
endif
// Do not fire things here. No AIDS structs will be made at this point.
endif
return getindex
endfunction
//==============================================================================
public function AddLock takes integer index returns nothing
set LockLevel[index]=LockLevel[index]+1
endfunction
public function RemoveLock takes integer index returns nothing
set LockLevel[index]=LockLevel[index]-1
static if not USE_PERIODIC_RECYCLER then
if GetUnitUserData(IndexUnit[index])==0 and LockLevel[index]==0 then
// Increment stack for recursion.
set UndefendExpiringIndexLevel=UndefendExpiringIndexLevel+1
set UndefendExpiringIndex[UndefendExpiringIndexLevel]=index
// Fire things.
call TriggerEvaluate(OnDeallocate)
// Decrement stack for recursion.
set UndefendExpiringIndexLevel=UndefendExpiringIndexLevel-1
// Add the index to the recycler stack.
set MaxRecycledIndex=MaxRecycledIndex+1
set RecycledIndex[MaxRecycledIndex]=index
// Null the unit.
set IndexUnit[index]=null
endif
endif
endfunction
//==============================================================================
static if USE_PERIODIC_RECYCLER then
private function PeriodicRecycler takes nothing returns nothing
if MaxDecayingIndex>0 then
set DecayChecker=DecayChecker+1
if DecayChecker>MaxDecayingIndex then
set DecayChecker=1
endif
if GetUnitUserData(IndexUnit[DecayingIndex[DecayChecker]])==0 then
if LockLevel[DecayingIndex[DecayChecker]]==0 then
// Fire things.
call TriggerEvaluate(OnDeallocate)
// Add the index to the recycler stack.
set MaxRecycledIndex=MaxRecycledIndex+1
set RecycledIndex[MaxRecycledIndex]=DecayingIndex[DecayChecker]
// Null the unit.
set IndexUnit[DecayingIndex[DecayChecker]]=null
// Remove index from decay list.
set DecayingIndex[DecayChecker]=DecayingIndex[MaxDecayingIndex]
set MaxDecayingIndex=MaxDecayingIndex-1
endif
endif
endif
endfunction
else
private function UndefendFilter takes nothing returns boolean
return IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD)
endfunction
private function OnUndefendTimer takes nothing returns nothing
loop
exitwhen UndefendStackIndex==0
set UndefendStackIndex=UndefendStackIndex-1
set UndefendExpiringIndex[0]=UndefendIndex[UndefendStackIndex]
if IndexUnit[UndefendExpiringIndex[0]]!=null then
if GetUnitUserData(IndexUnit[UndefendExpiringIndex[0]])==0 then
if LockLevel[UndefendExpiringIndex[0]]==0 then
// Fire things.
call TriggerEvaluate(OnDeallocate)
// Add the index to the recycler stack.
set MaxRecycledIndex=MaxRecycledIndex+1
set RecycledIndex[MaxRecycledIndex]=UndefendExpiringIndex[0]
// Null the unit.
set IndexUnit[UndefendExpiringIndex[0]]=null
endif
endif
endif
endloop
endfunction
globals//locals
private integer UndefendFilterIndex
endglobals
private function OnUndefend takes nothing returns boolean
if GetIssuedOrderId()==852056 then // If undefend then...
set UndefendFilterIndex=GetUnitUserData(GetOrderedUnit())
if UndefendIndex[UndefendStackIndex-1]!=UndefendFilterIndex then // Efficiency perk.
set UndefendIndex[UndefendStackIndex]=UndefendFilterIndex
set UndefendStackIndex=UndefendStackIndex+1
call TimerStart(UndefendTimer,0,false,function OnUndefendTimer)
endif
endif
return false
endfunction
endif
//==============================================================================
public function IndexEnum takes nothing returns boolean // Can be recursive...
// Start by adding another level on the AR stack (for recursion's sake).
set ARStackLevel=ARStackLevel+1
// Store the current unit on the (new) top level of the AR stack.
set ARStackUnit[ARStackLevel]=GetFilterUnit()
if GetUnitUserData(ARStackUnit[ARStackLevel])==0 then // Has not been indexed.
if UnitIndexingFilter(ARStackUnit[ARStackLevel]) then
// Get new index, from recycler first, else new.
// Store the current index on the (new) top level of the AR stack.
if MaxRecycledIndex==0 then // Get new.
set MaxIndex=MaxIndex+1
set ARStackIndex[ARStackLevel]=MaxIndex
else // Get from recycle stack.
set ARStackIndex[ARStackLevel]=RecycledIndex[MaxRecycledIndex]
set MaxRecycledIndex=MaxRecycledIndex-1
endif
// Store index on unit.
call SetUnitUserData(ARStackUnit[ARStackLevel],ARStackIndex[ARStackLevel])
set IndexUnit[ARStackIndex[ARStackLevel]]=ARStackUnit[ARStackLevel]
static if USE_PERIODIC_RECYCLER then
// Add index to recycle list.
set MaxDecayingIndex=MaxDecayingIndex+1
set DecayingIndex[MaxDecayingIndex]=ARStackIndex[ARStackLevel]
else
// Add leave detection ability.
call UnitAddAbility(ARStackUnit[ARStackLevel],LEAVE_DETECTION_ABILITY)
call UnitMakeAbilityPermanent(ARStackUnit[ARStackLevel],true,LEAVE_DETECTION_ABILITY)
endif
// Fire things.
call TriggerEvaluate(OnEnter)
else
// The unit did not pass the filters, so does not need to be auto indexed.
// However, for certain AIDS structs, it may still require indexing.
// These structs may index the unit on their creation.
// We flag that an index must be assigned by setting the current index to 0.
set ARStackIndex[ARStackLevel]=0
// Fire things.
call TriggerEvaluate(OnEnter)
endif
endif
// Decrement the stack.
set ARStackLevel=ARStackLevel-1
return false
endfunction
//==============================================================================
private function InitAIDS takes nothing returns nothing
local region r=CreateRegion()
local group g=CreateGroup()
local integer n=15
static if USE_PERIODIC_RECYCLER then
call TimerStart(UndefendTimer,PERIOD,true,function PeriodicRecycler)
else
local trigger t=CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t,Player(n),EVENT_PLAYER_UNIT_ISSUED_ORDER,Filter(function UndefendFilter))
call SetPlayerAbilityAvailable(Player(n),LEAVE_DETECTION_ABILITY,false)
// Capture "undefend" orders.
exitwhen n==0
set n=n-1
endloop
set n=15
call TriggerAddCondition(t,Filter(function OnUndefend))
set t=null
endif
// This must be done first, due to recursion. :)
call RegionAddRect(r,GetWorldBounds())
call TriggerRegisterEnterRegion(CreateTrigger(),r,Filter(function IndexEnum))
set r=null
loop
call GroupEnumUnitsOfPlayer(g,Player(n),Filter(function IndexEnum))
//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.
exitwhen n==0
set n=n-1
endloop
call DestroyGroup(g)
set g=null
endfunction
//==============================================================================
public struct DEFAULT extends array
method AIDS_onCreate takes nothing returns nothing
endmethod
method AIDS_onDestroy takes nothing returns nothing
endmethod
static method AIDS_filter takes unit u returns boolean
return UnitIndexingFilter(u)
endmethod
static method AIDS_onInit takes nothing returns nothing
endmethod
endstruct
//===========================================================================
// Never create or destroy AIDS structs directly.
// Also, do not initialise members except by using the AIDS_onCreate method.
//===========================================================================
//! textmacro AIDS
// This magic line makes default methods get called which do nothing
// if the methods are otherwise undefined.
private static delegate AIDS_DEFAULT AIDS_DELEGATE=0
//-----------------------------------------------------------------------
// Gotta know whether or not to destroy on deallocation...
private boolean AIDS_instanciated
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns thistype
return GetUnitId(whichUnit)
endmethod
method operator unit takes nothing returns unit
// Allows structVar.unit to return the unit.
return GetIndexUnit(this)
endmethod
//-----------------------------------------------------------------------
method AIDS_addLock takes nothing returns nothing
call AIDS_AddLock(this)
endmethod
method AIDS_removeLock takes nothing returns nothing
call AIDS_RemoveLock(this)
endmethod
//-----------------------------------------------------------------------
private static method AIDS_onEnter takes nothing returns boolean
// At this point, the unit might not have been assigned an index.
if thistype.AIDS_filter(AIDS_GetEnteringIndexUnit()) then
// Flag it for destruction on deallocation.
set thistype(AIDS_GetIndexOfEnteringUnit()).AIDS_instanciated=true
// Can use inlining "Assigned" function now, as it must be assigned.
call thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_onCreate()
endif
return false
endmethod
private static method AIDS_onEnterAllocated takes nothing returns boolean
// At this point, the unit must have been assigned an index.
if thistype.AIDS_filter(AIDS_GetEnteringIndexUnit()) then
// Flag it for destruction on deallocation. Slightly faster!
set thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_instanciated=true
// Can use inlining "Assigned" function now, as it must be assigned.
call thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_onCreate()
endif
return false
endmethod
private static method AIDS_onDeallocate takes nothing returns boolean
if thistype(AIDS_GetDecayingIndex()).AIDS_instanciated then
call thistype(AIDS_GetDecayingIndex()).AIDS_onDestroy()
// Unflag destruction on deallocation.
set thistype(AIDS_GetDecayingIndex()).AIDS_instanciated=false
endif
return false
endmethod
//-----------------------------------------------------------------------
private static method onInit takes nothing returns nothing
call AIDS_RegisterOnEnter(Filter(function thistype.AIDS_onEnter))
call AIDS_RegisterOnEnterAllocated(Filter(function thistype.AIDS_onEnterAllocated))
call AIDS_RegisterOnDeallocate(Filter(function thistype.AIDS_onDeallocate))
// Because I robbed you of your struct's onInit method.
call thistype.AIDS_onInit()
endmethod
//! endtextmacro
endlibrary
library PUI uses AIDS
//===========================================================================
// Allowed PUI_PROPERTY TYPES are: unit, integer, real, boolean, string
// Do NOT put handles that need to be destroyed here (timer, trigger, ...)
// Instead put them in a struct and use PUI textmacro
//===========================================================================
//! textmacro PUI_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
$VISIBILITY$ struct $NAME$
private static unit array pui_unit
private static $TYPE$ array pui_data
//-----------------------------------------------------------------------
// Returns default value when first time used
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns $TYPE$
local integer pui = GetUnitId(whichUnit) // Changed from GetUnitIndex.
if .pui_unit[pui] != whichUnit then
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = $DEFAULT$
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, $TYPE$ whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
endmethod
endstruct
//! endtextmacro
//===========================================================================
// Never destroy PUI structs directly.
// Use .release() instead, will call .destroy()
//===========================================================================
//! textmacro PUI
private static unit array pui_unit
private static integer array pui_data
private static integer array pui_id
//-----------------------------------------------------------------------
// Returns zero if no struct is attached to unit
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns integer
local integer pui = GetUnitId(whichUnit) // Changed from GetUnitIndex.
// Switched the next two lines for optimisation.
if .pui_unit[pui] != whichUnit then
if .pui_data[pui] != 0 then
// recycled index detected
call .destroy(.pui_data[pui])
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endif
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
// This will overwrite already attached struct if any
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, integer whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
call .destroy(.pui_data[pui])
endif
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
set .pui_id[whichData] = pui
endmethod
//-----------------------------------------------------------------------
// If you do not call release struct will be destroyed when unit handle gets recycled
//-----------------------------------------------------------------------
method release takes nothing returns nothing
local integer pui= .pui_id[integer(this)]
call .destroy()
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endmethod
//! endtextmacro
endlibrary
library AutoIndex uses AIDS
module AutoData
private static thistype array data
// Fixed up the below to use thsitype instead of integer.
static method operator []= takes unit u, thistype i returns nothing
set .data[GetUnitId(u)] = i //Just attaching a struct to the unit
endmethod //using the module's thistype array.
static method operator [] takes unit u returns thistype
return .data[GetUnitId(u)] //Just returning the attached struct.
endmethod
endmodule
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ GT ~~ GTrigger ~~ By Jesus4Lyf ~~ Version 1.05 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// What is GTrigger?
// - GTrigger is an event system that replaces the cumbersome WC3
// event system.
// - GTrigger only launches the necessary threads instead of x threads,
// where x is the number of times the event type occurs in the map.
//
// =Pros=
// - Instead of having 16 events (for "16" players) per use of an,
// event type, you have 0 per use and 16 total for that event type.
// - If you have 100 events of one type in your map, instead of firing
// 100 triggers each time any spell is cast, you fire only what's needed.
// - GTrigger is faster to code with, more efficient to execute, and just
// better programming practises and nicer code all round.
//
// =Cons=
// - If a trigger with a GTrigger event is destroyed, it must have its
// event unregistered first or it will leak an event (slows firing down).
// - Shouldn't use "wait" actions anywhere in the triggers.
//
// Functions:
// // General
// - GT_UnregisterTriggeringEvent()
//
// // Ability events
// - GT_RegisterStartsEffectEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_RegisterBeginsChanellingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_RegisterBeginsCastingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_RegisterStopsCastingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_RegisterFinishesCastingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_RegisterLearnsAbilityEvent(trigger, abilityid) (returns the trigger passed in)
// // Order events // (can use String2OrderIdBJ("OrderString") for orderid
// - GT_RegisterTargetOrderEvent(trigger, orderid) (returns the trigger passed in)
// - GT_RegisterPointOrderEvent(trigger, orderid) (returns the trigger passed in)
// - GT_RegisterNoTargetOrderEvent(trigger, orderid) (returns the trigger passed in)
// // Item events
// - GT_RegisterItemUsedEvent(trigger, itemtypeid) (returns the trigger passed in)
// - GT_RegisterItemAcquiredEvent(trigger, itemtypeid) (returns the trigger passed in)
// - GT_RegisterItemDroppedEvent(trigger, itemtypeid) (returns the trigger passed in)
// // Unit events
// - GT_RegisterUnitDiesEvent(trigger, unittypeid) (returns the trigger passed in)
//
// // Ability Events
// - GT_UnregisterSpellEffectEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_UnregisterBeginsChanellingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_UnregisterBeginsCastingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_UnregisterStopsCastingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_UnregisterFinishesCastingEvent(trigger, abilityid) (returns the trigger passed in)
// - GT_UnregisterLearnsAbilityEvent(trigger, abilityid) (returns the trigger passed in)
// // Order events // (can use String2OrderIdBJ("OrderString") for orderid
// - GT_UnregisterTargetOrderEvent(trigger, orderid) (returns the trigger passed in)
// - GT_UnregisterPointOrderEvent(trigger, orderid) (returns the trigger passed in)
// - GT_UnregisterNoTargetOrderEvent(trigger, orderid) (returns the trigger passed in)
// // Item events
// - GT_UnregisterItemUsedEvent(trigger, itemtypeid) (returns the trigger passed in)
// - GT_UnregisterItemAcquiredEvent(trigger, itemtypeid) (returns the trigger passed in)
// - GT_UnregisterItemDroppedEvent(trigger, itemtypeid) (returns the trigger passed in)
// // Unit events
// - GT_UnregisterUnitDiesEvent(trigger, unittypeid) (returns the trigger passed in)
//
// Alternative interface (not recommended):
// If you aren't familiar with how this works, you shouldn't use it.
// All funcs must return false. (That is the only reason it isn't recommended.)
// // General
// - GT_RemoveTriggeringAction() // Use this to remove actions.
// // Ability Events
// - GT_AddStartsEffectAction(func, abilityid)
// - GT_AddBeginsChanellingActon(func, abilityid)
// - GT_AddBeginsCastingAction(func, abilityid)
// - GT_AddStopsCastingAction(func, abilityid)
// - GT_AddFinishesCastingAction(func, abilityid)
// - GT_AddLearnsAbilityAction(func, abilityid)
// // Order events // (can use String2OrderIdBJ("OrderString") for orderid
// - GT_AddTargetOrderAction(func, orderid)
// - GT_AddPointOrderAction(func, orderid)
// - GT_AddNoTargetOrderAction(func, orderid)
// // Item events
// - GT_AddItemUsedAction(func, itemtypeid)
// - GT_AddItemAcquiredAction(func, itemtypeid)
// - GT_AddItemDroppedAction(func, itemtypeid)
// // Unit events
// - GT_AddUnitDiesAction(func, unittypeid)
//
// Details:
// - Due to the storage method, only 8191 GTrigger events are possible at any one time.
//
// Thanks:
// - Daxtreme: For voluntarily testing this system and the UnitDies event idea.
// - kenny!: For the Order and Learns Ability event ideas.
//
// How to import:
// - Create a trigger named GT.
// - Convert it to custom text and replace the whole trigger text with this.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library GT initializer Init
//////////////
// Pointers //
////////////////////////////////////////////////////////////////////////////
// Assigned to abilities, and point to trigger grouping linked lists.
//
// Use:
// GetPointer --> int (pointer)
// FreePointer(int (pointer))
// set PointerTarget[int (pointer)]=int (list link)
// PointerTarget[int (pointer)] --> int (list link)
globals
// Pointer
private integer array PointerTarget
private integer PointerMax=0
// Spare Pointer Stack
private integer array NextPointer
private integer NextPointerMaxPlusOne=1
endglobals
private function GetPointer takes nothing returns integer
if NextPointerMaxPlusOne==1 then
set PointerMax=PointerMax+1
return PointerMax
endif
set NextPointerMaxPlusOne=NextPointerMaxPlusOne-1
return NextPointer[NextPointerMaxPlusOne]
endfunction
private function FreePointer takes integer pointer returns nothing
set PointerTarget[pointer]=0
set NextPointer[NextPointerMaxPlusOne]=pointer
set NextPointerMaxPlusOne=NextPointerMaxPlusOne+1
endfunction
///////////////////////////////////
// Trigger Grouping Linked Lists //
////////////////////////////////////////////////////////////////////////////
// Contains a chain of triggers to be executed together.
//
// Use:
// GetMem() --> int (mem)
// FreeMem(int (mem))
// Link(int (pointer), int (mem))
// Unlink(int (pointer), int (mem))
globals
// Spare Link Stack
private integer array NextMem
private integer NextMemMaxPlusOne=1
// Linked list
private trigger array Trig
private integer array Next
private integer array Prev
private integer TrigMax=0
endglobals
private function GetMem takes nothing returns integer
if NextMemMaxPlusOne==1 then
set TrigMax=TrigMax+1
return TrigMax
endif
set NextMemMaxPlusOne=NextMemMaxPlusOne-1
return NextMem[NextMemMaxPlusOne]
endfunction
private function FreeMem takes integer i returns nothing
set Trig[i]=null
set NextMem[NextMemMaxPlusOne]=i
set NextMemMaxPlusOne=NextMemMaxPlusOne+1
endfunction
// Linked list functionality
// NOTE: This means "Next" must be loaded BEFORE executing the trigger, which could delete the current link.
private function Link takes integer pointer, integer new returns nothing
set Prev[new]=0
set Next[new]=PointerTarget[pointer]
set Prev[PointerTarget[pointer]]=new
set PointerTarget[pointer]=new
endfunction
private function Unlink takes integer pointer, integer rem returns nothing
if Prev[rem]==0 then
set PointerTarget[pointer]=Next[rem]
set Prev[Next[rem]]=0
endif
set Next[Prev[rem]]=Next[rem]
set Prev[Next[rem]]=Prev[rem]
endfunction
//////////////////////
// GTrigger General //
////////////////////////////////////////////////////////////////////////////
// Only contains the UnregisterTriggeringEvent action for public use.
globals
boolean UnregisterLastEvent=false
endglobals
public function UnregisterTriggeringEvent takes nothing returns nothing
set UnregisterLastEvent=true
endfunction
/////////////////////////////////////
// GTrigger Ability Implementation //
////////////////////////////////////////////////////////////////////////////
// The nasty textmacro implementation of special "All Players" events.
//! textmacro SetupSpecialAllPlayersEvent takes NAME, EVENT, GETSPECIAL
globals
private trigger $NAME$Trigger=CreateTrigger()
// Extendable arrays
private integer array $NAME$AbilityIdA
private integer array $NAME$ListPointerA
private integer array $NAME$AbilityIdB
private integer array $NAME$ListPointerB
private integer array $NAME$AbilityIdC
private integer array $NAME$ListPointerC
private integer array $NAME$AbilityIdD
private integer array $NAME$ListPointerD
private integer array $NAME$AbilityIdE
private integer array $NAME$ListPointerE
endglobals
globals//locals
private integer GetOrCreateListPointer$NAME$AbilHashed
endglobals
private function GetOrCreate$NAME$ListPointer takes integer abil returns integer
set GetOrCreateListPointer$NAME$AbilHashed=abil-(abil/8191)*8191
if $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
set $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]=abil
set $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
return $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]
endif
if $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
set $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]=abil
set $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
return $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]
endif
if $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
set $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]=abil
set $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
return $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]
endif
if $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
set $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]=abil
set $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
return $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]
endif
if $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
set $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]=abil
set $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
return $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]
endif
call BJDebugMsg("GTrigger Error: Ran out of storage locations for pointers on object "+GetObjectName(abil)+"!")
set PointerTarget[0]=0
return 0
endfunction
globals//locals
private integer GetListPointer$NAME$AbilHashed
endglobals
private function Get$NAME$ListPointer takes integer abil returns integer
set GetListPointer$NAME$AbilHashed=abil-(abil/8191)*8191
if $NAME$AbilityIdA[GetListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerA[GetListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdA[GetListPointer$NAME$AbilHashed]<1 then // Empty
set PointerTarget[0]=0 // Make sure.
return 0
endif
if $NAME$AbilityIdB[GetListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerB[GetListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdB[GetListPointer$NAME$AbilHashed]<1 then // Empty
set PointerTarget[0]=0 // Make sure.
return 0
endif
if $NAME$AbilityIdC[GetListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerC[GetListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdC[GetListPointer$NAME$AbilHashed]<1 then // Empty
set PointerTarget[0]=0 // Make sure.
return 0
endif
if $NAME$AbilityIdD[GetListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerD[GetListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdD[GetListPointer$NAME$AbilHashed]<1 then // Empty
set PointerTarget[0]=0 // Make sure.
return 0
endif
if $NAME$AbilityIdE[GetListPointer$NAME$AbilHashed]==abil then // Correct
return $NAME$ListPointerE[GetListPointer$NAME$AbilHashed]
elseif $NAME$AbilityIdE[GetListPointer$NAME$AbilHashed]<1 then // Empty
set PointerTarget[0]=0 // Make sure.
return 0
endif
call BJDebugMsg("GTrigger Error: Ran out of storage locations for pointers at ability "+GetObjectName(abil)+"!")
set PointerTarget[0]=0
return 0
endfunction
globals//locals
private integer Register$NAME$Mem
endglobals
public function Register$NAME$Event takes trigger t, integer abil returns trigger
set Register$NAME$Mem=GetMem()
set Trig[Register$NAME$Mem]=t
call Link(GetOrCreate$NAME$ListPointer(abil),Register$NAME$Mem)
return t
endfunction
globals//locals
private integer Unregister$NAME$Pointer
private integer Unregister$NAME$Mem
endglobals
public function Unregister$NAME$Event takes trigger t, integer abil returns trigger
set Unregister$NAME$Pointer=Get$NAME$ListPointer(abil)
set Unregister$NAME$Mem=PointerTarget[Unregister$NAME$Pointer]
loop
exitwhen Trig[Unregister$NAME$Mem]==t
if Unregister$NAME$Mem==0 then
return t // Not found.
endif
set Unregister$NAME$Mem=Next[Unregister$NAME$Mem]
endloop
call Unlink(Unregister$NAME$Pointer,Unregister$NAME$Mem)
call FreeMem(Unregister$NAME$Mem)
return t
endfunction
private function Trigger$NAME$Event takes nothing returns boolean
local integer Trigger$NAME$Pointer=Get$NAME$ListPointer($GETSPECIAL$)
local integer Trigger$NAME$Mem=PointerTarget[Trigger$NAME$Pointer]
local integer Trigger$NAME$NextMem
set UnregisterLastEvent=false
loop
exitwhen Trigger$NAME$Mem<1
set Trigger$NAME$NextMem=Next[Trigger$NAME$Mem]
if TriggerEvaluate(Trig[Trigger$NAME$Mem]) then
call TriggerExecute(Trig[Trigger$NAME$Mem])
endif
if UnregisterLastEvent then
set UnregisterLastEvent=false
call Unlink(Trigger$NAME$Pointer,Trigger$NAME$Mem)
call FreeMem(Trigger$NAME$Mem)
endif
set Trigger$NAME$Mem=Trigger$NAME$NextMem
endloop
return false
endfunction
private function Init$NAME$ takes nothing returns nothing
local integer i=bj_MAX_PLAYER_SLOTS
call TriggerAddCondition($NAME$Trigger,Condition(function Trigger$NAME$Event))
loop
set i=i-1
call TriggerRegisterPlayerUnitEvent($NAME$Trigger,Player(i),EVENT_PLAYER_$EVENT$,null)
exitwhen i==0
endloop
endfunction
//! endtextmacro
//! runtextmacro SetupSpecialAllPlayersEvent("StartsEffect", "UNIT_SPELL_EFFECT", "GetSpellAbilityId()")
//! runtextmacro SetupSpecialAllPlayersEvent("BeginsChanelling", "UNIT_SPELL_CHANNEL", "GetSpellAbilityId()")
//! runtextmacro SetupSpecialAllPlayersEvent("BeginsCasting", "UNIT_SPELL_CAST", "GetSpellAbilityId()")
//! runtextmacro SetupSpecialAllPlayersEvent("StopsCasting", "UNIT_SPELL_ENDCAST", "GetSpellAbilityId()")
//! runtextmacro SetupSpecialAllPlayersEvent("FinishesCasting", "UNIT_SPELL_FINISH", "GetSpellAbilityId()")
//! runtextmacro SetupSpecialAllPlayersEvent("TargetOrder", "UNIT_ISSUED_TARGET_ORDER", "GetIssuedOrderId()")
//! runtextmacro SetupSpecialAllPlayersEvent("PointOrder", "UNIT_ISSUED_POINT_ORDER", "GetIssuedOrderId()")
//! runtextmacro SetupSpecialAllPlayersEvent("NoTargetOrder", "UNIT_ISSUED_ORDER", "GetIssuedOrderId()")
//! runtextmacro SetupSpecialAllPlayersEvent("ItemUsed", "UNIT_USE_ITEM", "GetItemTypeId(GetManipulatedItem())")
//! runtextmacro SetupSpecialAllPlayersEvent("ItemAcquired", "UNIT_PICKUP_ITEM", "GetItemTypeId(GetManipulatedItem())")
//! runtextmacro SetupSpecialAllPlayersEvent("ItemDropped", "UNIT_DROP_ITEM", "GetItemTypeId(GetManipulatedItem())")
//! runtextmacro SetupSpecialAllPlayersEvent("UnitDies", "UNIT_DEATH", "GetUnitTypeId(GetTriggerUnit())")
//! runtextmacro SetupSpecialAllPlayersEvent("LearnsAbility", "HERO_SKILL", "GetLearnedSkill()")
// Note to self: Remember to update the Init function.
/////////////////////////////////////////
// GTrigger All Players Implementation //
////////////////////////////////////////////////////////////////////////////
// The textmacro implementation of other "All Players" events.
//! textmacro SetupAllPlayersEvent takes NAME, EVENT
globals
private trigger $NAME$Trigger=CreateTrigger()
private integer $NAME$ListPointer=0
endglobals
globals//locals
private integer Register$NAME$Mem
endglobals
public function Register$NAME$Event takes trigger t returns trigger
set Register$NAME$Mem=GetMem()
set Trig[Register$NAME$Mem]=t
call Link($NAME$ListPointer,Register$NAME$Mem)
return t
endfunction
globals//locals
private integer Unregister$NAME$Pointer
private integer Unregister$NAME$Mem
endglobals
public function Unregister$NAME$Event takes trigger t returns trigger
set Unregister$NAME$Mem=PointerTarget[$NAME$ListPointer]
loop
exitwhen Trig[Unregister$NAME$Mem]==t
if Unregister$NAME$Mem==0 then
return t // Not found.
endif
set Unregister$NAME$Mem=Next[Unregister$NAME$Mem]
endloop
call Unlink($NAME$ListPointer,Unregister$NAME$Mem)
call FreeMem(Unregister$NAME$Mem)
return t
endfunction
private function Trigger$NAME$Event takes nothing returns boolean
local integer Trigger$NAME$Mem=PointerTarget[$NAME$ListPointer]
local integer Trigger$NAME$NextMem
set UnregisterLastEvent=false
loop
exitwhen Trigger$NAME$Mem<1
set Trigger$NAME$NextMem=Next[Trigger$NAME$Mem]
if TriggerEvaluate(Trig[Trigger$NAME$Mem]) then
call TriggerExecute(Trig[Trigger$NAME$Mem])
endif
if UnregisterLastEvent then
set UnregisterLastEvent=false
call Unlink($NAME$ListPointer,Trigger$NAME$Mem)
call FreeMem(Trigger$NAME$Mem)
endif
set Trigger$NAME$Mem=Trigger$NAME$NextMem
endloop
return false
endfunction
private function Init$NAME$ takes nothing returns nothing
local integer i=bj_MAX_PLAYER_SLOTS
call TriggerAddCondition($NAME$Trigger,Condition(function Trigger$NAME$Event))
loop
set i=i-1
call TriggerRegisterPlayerUnitEvent($NAME$Trigger,Player(i),EVENT_PLAYER_UNIT_$EVENT$,null)
exitwhen i==0
endloop
// Initialise the pointer.
set $NAME$ListPointer=GetPointer()
endfunction
//! endtextmacro
// Old: //! runtextmacro SetupAllPlayersEvent("AnyUnitDies", "DEATH")
private function Init takes nothing returns nothing
// Ability events
call InitStartsEffect()
call InitBeginsChanelling()
call InitBeginsCasting()
call InitStopsCasting()
call InitFinishesCasting()
call InitLearnsAbility()
// Order events
call InitTargetOrder()
call InitPointOrder()
call InitNoTargetOrder()
// Item events
call InitItemUsed()
call InitItemAcquired()
call InitItemDropped()
// Unit events
call InitUnitDies()
endfunction
//////////////
// Wrappers //
////////////////////////////////////////////////////////////////////////////
// Wraps it up, for those who really want this interface.
// General
public function RemoveTriggeringAction takes nothing returns nothing
call UnregisterTriggeringEvent()
call DestroyTrigger(GetTriggeringTrigger())
endfunction
// Special All Player Events
//! textmacro AddSpecialAllPlayersWrapper takes EVENT
public function Add$EVENT$Action takes code func, integer special returns nothing
call TriggerAddCondition(Register$EVENT$Event(CreateTrigger(),special),Condition(func))
endfunction
//! endtextmacro
//! runtextmacro AddSpecialAllPlayersWrapper("StartsEffect")
//! runtextmacro AddSpecialAllPlayersWrapper("BeginsChanelling")
//! runtextmacro AddSpecialAllPlayersWrapper("BeginsCasting")
//! runtextmacro AddSpecialAllPlayersWrapper("StopsCasting")
//! runtextmacro AddSpecialAllPlayersWrapper("FinishesCasting")
//! runtextmacro AddSpecialAllPlayersWrapper("TargetOrder")
//! runtextmacro AddSpecialAllPlayersWrapper("PointOrder")
//! runtextmacro AddSpecialAllPlayersWrapper("NoTargetOrder")
//! runtextmacro AddSpecialAllPlayersWrapper("ItemUsed")
//! runtextmacro AddSpecialAllPlayersWrapper("ItemAcquired")
//! runtextmacro AddSpecialAllPlayersWrapper("ItemDropped")
//! runtextmacro AddSpecialAllPlayersWrapper("UnitDies")
//! runtextmacro AddSpecialAllPlayersWrapper("LearnsAbility")
// Note to self: Remember to update the Init function.
// All Player Events
//! textmacro AddAllPlayersWrapper takes EVENT
public function Add$EVENT$Action takes code func returns nothing
call TriggerAddCondition(Register$EVENT$Event(CreateTrigger()),Condition(func))
endfunction
//! endtextmacro
// Old: //! runtextmacro AddAllPlayersWrapper("AnyUnitDies")
endlibrary
//TESH.scrollpos=36
//TESH.alwaysfold=0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ KT ~~ Key Timers 2 ~~ By Jesus4Lyf ~~ Version 1.7.2 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// What is Key Timers?
// - Key Timers attaches structs to timers, or more to the point timed
// effects.
// - You can specify different periods.
// - Key Timers only uses one timer with one trigger per low period
// to keep things efficient, especially within the looping.
// - Key Timers alternatively uses one trigger per instance for all higher
// periods to allow accurate expirations in a stable and efficient fashion.
//
// =Pros=
// - Easy to use.
// - Fastest attachment loading system (storing in parallel arrays).
// - Fastest execution system for low periods (all functions on one trigger).
// - Allows multiple periods to be used.
// - No H2I. Backwards compatability through patch 1.23 and 1.24.
//
// =Cons=
// - The code passed into KT2 must call KT_GetData exactly once.
// - Periods must be a multiple of 0.00125 seconds. Not 0.007, for example.
//
// Functions:
// - KT_Add(userFunc, struct, period)
// - KT_GetData returns the struct
//
// - userFunc is to be a user function that takes nothing and returns boolean.
// It will be executed by the system every period until it returns true.
//
// - KT_GetData is to be used inside func, it will return the struct passed to
// to the Add function. It must be called exactly once within the func.
//
// Details:
// - KT2 treats low periods and high periods differently, optimizing each
// with appropriate speed and accuracy, although this effect is invisible
// to you, the mapper.
//
// - While func returns false the timer will continue to call it each period.
// Once func returns true the instance will be detached from system.
//
// Thanks:
// - Daxtreme: For encouraging me to return to Key Timers 2, rather than
// leave it to rot. His interest in the system restored it, and helped
// it to become what it is now. :)
//
// - Captain Griffen: For his work on Rapid Timers, demonstrating that it
// is possible to attach all functions to one trigger, and that it is
// indeed faster.
//
// - Cohadar: Told me to make Key Timers without a textmacro.
// Thanks to him for helping me with the original Key Timers system too.
// Also, I'd like to thank him for his work on Timer Ticker (TT) which
// demonstrated how to use triggers/conditions in this sort of system,
// which has been used in Key Timers 2.
//
// How to import:
// - Create a trigger named KT.
// - Convert it to custom text and replace the whole trigger text with this.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library KT initializer Init
///////////////
// Constants //
////////////////////////////////////////////////////////////////////////////
// That bit that users may play with if they know what they're doing.
// Not touching these at all is recommended.
globals
// Period Threshold is the point at which Key Timers 2 will switch from
// using the single timer per period mechanism to using TAZO, which is
// better for higher periods due to the first tick being accurate.
private constant real PERIODTHRESHOLD=0.3 // MUST be below 10.24 seconds.
// Tazo's number of precached instances. You can go over this during
// your map at runtime, but it will probably do some small background
// processing. Precaching just speeds things up a bit.
private constant integer TAZO_PRECACHE=64
// Tazo uses the low period part of Key Timers 2 to construct triggers
// over time when precached ones run out. Here you can set the period used.
private constant real TAZO_CONSTRUCT_PERIOD=0.03125
endglobals
//////////////////////////
// Previous KT2 Globals //
////////////////////////////////////////////////////////////////////////////
// These needed to be moved here for TAZO to hook GetData.
globals
private timer array KeyTimer
private trigger array TimerTrigger
private integer array KeyTimerListPointer
private integer array KeyTimerListEndPointer
private triggercondition array TriggerCond
private boolexpr array Boolexpr
private integer array Data
private integer array Next
private integer array Prev
private integer TrigMax=0
private integer array NextMem
private integer NextMemMaxPlusOne=1
private integer array ToAddMem
private triggercondition array ToRemove
private boolexpr array ToDestroy
private boolean array IsAdd
private integer AddRemoveMax=0
// Locals
private integer t_id=-1
private integer t_mem
private integer t_lastmem
private integer a_id
private integer a_mem
// Code Chunks
private conditionfunc RemoveInstanceCond
endglobals
//////////////////
// Previous KT2 //
////////////////////////////////////////////////////////////////////////////
// The KT2 implementation
private function KeyTimerLoop takes nothing returns nothing
set t_id=R2I(TimerGetTimeout(GetExpiredTimer())*800)
set t_mem=KeyTimerListEndPointer[t_id]
call TriggerEvaluate(TimerTrigger[t_id])
set t_mem=0
loop
exitwhen t_mem==AddRemoveMax
set t_mem=t_mem+1
if IsAdd[t_mem] then
set TriggerCond[ToAddMem[t_mem]]=TriggerAddCondition(TimerTrigger[t_id],Boolexpr[ToAddMem[t_mem]])
else
call TriggerRemoveCondition(TimerTrigger[t_id],ToRemove[t_mem])
call DestroyBoolExpr(ToDestroy[t_mem])
endif
endloop
set AddRemoveMax=0
set t_id=-1
endfunction
private function RemoveInstance takes nothing returns boolean
// Will only fire if code returns true.
set AddRemoveMax=AddRemoveMax+1
set IsAdd[AddRemoveMax]=false
set ToRemove[AddRemoveMax]=TriggerCond[t_lastmem]
set ToDestroy[AddRemoveMax]=Boolexpr[t_lastmem]
if Next[t_lastmem]==0 then
set KeyTimerListEndPointer[t_id]=Prev[t_lastmem]
endif
set Prev[Next[t_lastmem]]=Prev[t_lastmem]
if Prev[t_lastmem]==0 then
set KeyTimerListPointer[t_id]=Next[t_lastmem]
if KeyTimerListPointer[t_id]<1 then
call PauseTimer(KeyTimer[t_id])
endif
else
set Next[Prev[t_lastmem]]=Next[t_lastmem]
endif
set NextMem[NextMemMaxPlusOne]=t_lastmem
set NextMemMaxPlusOne=NextMemMaxPlusOne+1
return false
endfunction
private function KTadd takes code func, integer data, real period returns nothing
set a_id=R2I(period*800)
if KeyTimer[a_id]==null then
set KeyTimer[a_id]=CreateTimer()
set TimerTrigger[a_id]=CreateTrigger()
endif
if NextMemMaxPlusOne==1 then
set TrigMax=TrigMax+1
set a_mem=TrigMax
else
set NextMemMaxPlusOne=NextMemMaxPlusOne-1
set a_mem=NextMem[NextMemMaxPlusOne]
endif
set Boolexpr[a_mem]=And(Condition(func),RemoveInstanceCond)
if t_id==a_id then
set AddRemoveMax=AddRemoveMax+1
set IsAdd[AddRemoveMax]=true
set ToAddMem[AddRemoveMax]=a_mem
else
if KeyTimerListPointer[a_id]<1 then
call TimerStart(KeyTimer[a_id],a_id/800.0,true,function KeyTimerLoop)
set KeyTimerListEndPointer[a_id]=a_mem
endif
set TriggerCond[a_mem]=TriggerAddCondition(TimerTrigger[a_id],Boolexpr[a_mem])
endif
set Data[a_mem]=data
set Prev[a_mem]=0
set Next[a_mem]=KeyTimerListPointer[a_id]
set Prev[KeyTimerListPointer[a_id]]=a_mem
set KeyTimerListPointer[a_id]=a_mem
endfunction
public function GetData takes nothing returns integer // Gets hooked by TAZO.
set t_lastmem=t_mem
set t_mem=Prev[t_mem]
return Data[t_lastmem]
endfunction
private function KTinit takes nothing returns nothing
set RemoveInstanceCond=Condition(function RemoveInstance)
endfunction
//////////
// TAZO //
////////////////////////////////////////////////////////////////////////////
// KT2 implementation for higher periods (low frequency).
globals
private constant integer TAZO_DATAMEM=8190 // Added for KT2 hook. Don't change.
endglobals
globals
private conditionfunc TAZO_LoadDataCond
private conditionfunc TAZO_RemoveInstanceCond
private timer array TAZO_TrigTimer
private integer array TAZO_Data
private boolexpr array TAZO_Boolexpr
private trigger array TAZO_AvailableTrig
private integer TAZO_Max=0
private integer TAZO_ConstructNext=0
private trigger array TAZO_ConstructTrig
private integer array TAZO_ConstructCount
endglobals
globals//locals
private integer TAZO_ConKey
endglobals
private function TAZO_Constructer takes nothing returns boolean
set TAZO_ConKey=GetData()
call TriggerExecute(TAZO_ConstructTrig[TAZO_ConKey])
set TAZO_ConstructCount[TAZO_ConKey]=TAZO_ConstructCount[TAZO_ConKey]-1
if TAZO_ConstructCount[TAZO_ConKey]==0 then
set TAZO_Max=TAZO_Max+1
set TAZO_AvailableTrig[TAZO_Max]=TAZO_ConstructTrig[TAZO_ConKey]
set TAZO_TrigTimer[TAZO_ConKey]=CreateTimer()
call TriggerRegisterTimerExpireEvent(TAZO_AvailableTrig[TAZO_Max],TAZO_TrigTimer[TAZO_ConKey])
return true
endif
return false
endfunction
globals//locals
private trigger TAZO_DeadTrig
private integer TAZO_DeadCount
endglobals
private function TAZO_Recycle takes nothing returns boolean
set TAZO_DeadTrig=GetTriggeringTrigger()
set TAZO_DeadCount=GetTriggerExecCount(TAZO_DeadTrig)
call TriggerClearConditions(TAZO_DeadTrig)
call DestroyBoolExpr(TAZO_Boolexpr[TAZO_DeadCount])
call PauseTimer(TAZO_TrigTimer[TAZO_DeadCount])
set TAZO_Max=TAZO_Max+1
set TAZO_AvailableTrig[TAZO_Max]=TAZO_DeadTrig
return false
endfunction
private function TAZO_LoadData takes nothing returns boolean
// KT2 Data Hook
set t_mem=TAZO_DATAMEM
set Data[TAZO_DATAMEM]=TAZO_Data[GetTriggerExecCount(GetTriggeringTrigger())]
// End KT2 Data Hook
return false
endfunction
private function InitTrigExecCount takes trigger t, integer d returns nothing
if d>128 then
call InitTrigExecCount.execute(t,d-128)
set d=128
endif
loop
exitwhen d==0
set d=d-1
call TriggerExecute(t)
endloop
endfunction
globals//locals
private integer TAZO_AddKey
private trigger TAZO_AddTrigger
endglobals
public function TAZOadd takes code func, integer data, real period returns nothing
if TAZO_Max==0 then
// Failsafe.
set TAZO_ConstructNext=TAZO_ConstructNext+1
set TAZO_AddTrigger=CreateTrigger()
set TAZO_AddKey=TAZO_ConstructNext
call InitTrigExecCount.execute(TAZO_AddTrigger,TAZO_AddKey)
set TAZO_TrigTimer[TAZO_AddKey]=CreateTimer()
call TriggerRegisterTimerExpireEvent(TAZO_AddTrigger,TAZO_TrigTimer[TAZO_AddKey])
else
set TAZO_AddTrigger=TAZO_AvailableTrig[TAZO_Max]
set TAZO_AddKey=GetTriggerExecCount(TAZO_AddTrigger)
set TAZO_Max=TAZO_Max-1
endif
set TAZO_Data[TAZO_AddKey]=data
set TAZO_Boolexpr[TAZO_AddKey]=And(Condition(func),TAZO_RemoveInstanceCond)
call TriggerAddCondition(TAZO_AddTrigger,TAZO_LoadDataCond)
call TriggerAddCondition(TAZO_AddTrigger,TAZO_Boolexpr[TAZO_AddKey])
call TimerStart(TAZO_TrigTimer[TAZO_AddKey],period,true,null)
if TAZO_Max<10 then
set TAZO_ConstructNext=TAZO_ConstructNext+1
set TAZO_ConstructTrig[TAZO_ConstructNext]=CreateTrigger()
set TAZO_ConstructCount[TAZO_ConstructNext]=TAZO_ConstructNext
call KTadd(function TAZO_Constructer,TAZO_ConstructNext,TAZO_CONSTRUCT_PERIOD)
endif
endfunction
private function TAZOinit takes nothing returns nothing
set TAZO_LoadDataCond=Condition(function TAZO_LoadData)
set TAZO_RemoveInstanceCond=Condition(function TAZO_Recycle)
// Allow for GetData
set Next[TAZO_DATAMEM]=TAZO_DATAMEM
set Prev[TAZO_DATAMEM]=TAZO_DATAMEM
// End allow for GetData
loop
exitwhen TAZO_Max==TAZO_PRECACHE
set TAZO_ConstructNext=TAZO_ConstructNext+1 // The index.
set TAZO_Max=TAZO_Max+1 // Will be the same in the initialiser as ConstructNext.
set TAZO_AvailableTrig[TAZO_Max]=CreateTrigger()
call InitTrigExecCount.execute(TAZO_AvailableTrig[TAZO_Max],TAZO_ConstructNext)
set TAZO_TrigTimer[TAZO_ConstructNext]=CreateTimer()
call TriggerRegisterTimerExpireEvent(TAZO_AvailableTrig[TAZO_Max],TAZO_TrigTimer[TAZO_ConstructNext])
endloop
endfunction
///////////////
// Interface //
////////////////////////////////////////////////////////////////////////////
// Stitches it all together neatly.
public function Add takes code func, integer data, real period returns nothing
if period<PERIODTHRESHOLD then
call KTadd(func,data,period)
else
call TAZOadd(func,data,period)
endif
endfunction
private function Init takes nothing returns nothing
call KTinit()
call TAZOinit()
endfunction
endlibrary
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// End of Key Timers 2
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library 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
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=3
//TESH.alwaysfold=0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~ Event ~~ By Jesus4Lyf ~~ Version 1.03 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// What is Event?
// - Event simulates Warcraft III events. They can be created,
// registered for, fired and also destroyed.
// - Event, therefore, can also be used like a trigger "group".
// - This was created when there was an influx of event style systems
// emerging that could really benefit from a standardised custom
// events snippet. Many users were trying to achieve the same thing
// and making the same kind of errors. This snippet aims to solve that.
//
// Functions:
// - Event.create() --> Creates a new Event.
// - .chainDestroy() --> Destroys an Event.
// DO NOT use .destroy().
// - .fire() --> Fires all triggers which have been
// registered on this Event.
// - .register(trigger) --> Registers another trigger on this Event.
//
// Details:
// - Event is extremely efficient and lightweight.
// - It is safe to use with dynamic triggers.
// - Internally, it is just a singularly linked list. Very simple.
//
// How to import:
// - Create a trigger named Event.
// - Convert it to custom text and replace the whole trigger text with this.
//
// Thanks:
// - Builder Bob for the trigger destroy detection method.
// - Azlier for inspiring this by ripping off my dodgier code.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library Event
///////////////
// EventRegs //
////////////////////////////////////////////////////////////////////////////
// For reading this far, you can learn one thing more.
// Unlike normal Warcraft III events, you can attach to Event registries.
//
// Event Registries are registrations of one trigger on one event.
// These cannot be created or destroyed, just attached to.
//
// It is VERY efficient for loading and saving data.
//
// Functions:
// - set eventReg.data = someStruct --> Store data.
// - eventReg.data --> Retreive data.
// - Event.getTriggeringEventReg() --> Get the triggering EventReg.
//
struct EventReg
integer data
method clear takes nothing returns nothing
set this.data=0
endmethod
endstruct
struct Event
private trigger trig
private Event next
static method create takes nothing returns Event
local Event this=Event.allocate()
set this.next=0
return this
endmethod
private static Event current
static method getTriggeringEventReg takes nothing returns EventReg
return .current
endmethod
private static trigger t
method fire takes nothing returns nothing
local Event curr
// this = last.
loop
set curr=this.next
exitwhen curr==0
set .t=curr.trig
if IsTriggerEnabled(.t) then
set .current=curr
if TriggerEvaluate(.t) then
call TriggerExecute(.t)
endif
set this=curr
else
call EnableTrigger(.t) // Was trigger destroyed?
if IsTriggerEnabled(.t) then
call DisableTrigger(.t)
set this=curr
else // If trigger destroyed...
set .current.trig=null
set this.next=curr.next
call curr.destroy()
endif
endif
endloop
endmethod
method register takes trigger t returns EventReg
local Event new=Event.allocate()
set new.next=this.next
set new.trig=t
set this.next=new
call EventReg(new).clear()
return new
endmethod
method chainDestroy takes nothing returns nothing
loop
call this.destroy()
set this=this.next
exitwhen this==0
set this.trig=null
endloop
endmethod
endstruct
/////////////////////////////////////////////////////
// Demonstration Functions & Alternative Interface //
////////////////////////////////////////////////////////////////////////////
// What this would look like in normal WC3 style JASS (should all inline).
//
function CreateEvent takes nothing returns Event
return Event.create()
endfunction
function DestroyEvent takes Event whichEvent returns nothing
call whichEvent.chainDestroy()
endfunction
function FireEvent takes Event whichEvent returns nothing
call whichEvent.fire()
endfunction
function TriggerRegisterEvent takes trigger whichTrigger, Event whichEvent returns EventReg
return whichEvent.register(whichTrigger)
endfunction
// And for EventRegs...
function SetEventRegData takes EventReg whichEventReg, integer data returns nothing
set whichEventReg.data=data
endfunction
function GetEventRegData takes EventReg whichEventReg returns integer
return whichEventReg.data
endfunction
function GetTriggeringEventReg takes nothing returns integer
return Event.getTriggeringEventReg()
endfunction
endlibrary
//TESH.scrollpos=215
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
globals
// High enough so the unit is no longer visible, low enough so the
// game doesn't crash...
//
// I think you need 0.0 or soemthing negative prior to patch 1.22
//
private constant real EXTRA = 250.0
endglobals
//=========================================================================================
globals
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns nothing
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local region r=CreateRegion()
local rect rc
set minx=GetCameraBoundMinX() - EXTRA
set miny=GetCameraBoundMinY() - EXTRA
set maxx=GetCameraBoundMaxX() + EXTRA
set maxy=GetCameraBoundMaxY() + EXTRA
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddAction(t, function dis)
//this is not necessary but I'll do it anyway:
set t=null
set r=null
set rc=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
[quote][b]CHANGELOG:[/b]
v1.04 - 01/07/2010
[list]
[*]Removed dependency on Damage. Added configurable [ljass]attacktype[/ljass], [ljass]damagetype[/ljass], and [ljass]weapontype[/ljass].
[*]Made one barrier per instance blocked in the create method as well.
[*]Added a possible death animation if a unit is killed by this ability.
[*]SOUND_EVERY is now in seconds.
[*]Blade Barrier will now track it's target's height.
[*]The number of blades created can now be adjusted by a configurable function which takes a level.
[*]Changed [ljass]IsUnitType( unit, UNIT_TYPE_DEAD )[/ljass] calls to [ljass]UnitAlive[/ljass].
[*]DamageData now ends if the damage dealt kills the target rather than on the next timer iteration.
[*]BladeUnitData now longer checks for a null effect or nulls the unit.
[*]Cleaned up the BladeBarrier struct a bit.
[*]Added colouring global configs. for the Blade Barrier units.
[*]Create method no longer takes a player, but takes a integer level now.
[*]Inlined the INLINED_VOLUME.
[*]Added a new function: [ljass]public function Create takes unit source, unit target, integer level returns boolean[/ljass]
[*]No longer using GroupUtils for recylcing.
[/list]
v1.03 - 26/06/2010
[list]
[*]It actually was not MUI. Fixed this.
[*]Privatalized a shitload of members. I don't know why.
[*]When a unit in the damage group left the radius, they couldn't get back in, fixed this.
[/list]
v1.02 - 03/06/2010
[list]
[*]Code optimisation
[*]Added a configurable to have the original death animation
[*]Fixed the configurables
[*]Create method takes a player, and a unit - sourceUnit and whichPlayer
[*]Fixed the target Blade Barrier - still no support for one instance per target.
[*]Removed the target from the 'damage whirl' when they die.
[/list]
v1.01 - 16/05/2010
[list]
[*]Code improvement
[*]Allowed option for instant removal of blades or fade out
[*]Allowed option for one barrier per unit
[*]Changed creation method of struct to take a unit, to allow in making a 'target' Blade Barrier (Dinowc)
[/list]
v1.00 - 13/05/2010
[list]
[*]Initial release
[/list][/quote]
//TESH.scrollpos=0
//TESH.alwaysfold=0
CHANGELOG:
v1.04 - 01/07/2010
*Removed dependency on Damage. Added configurable attacktype, damagetype, and weapontype.
*Made one barrier per instance blocked in the create method as well.
*Added a possible death animation if a unit is killed by this ability.
*SOUND_EVERY is now in seconds.
*Blade Barrier will now track it's target's height.
*The number of blades created can now be adjusted by a configurable function which takes a level.
*Changed IsUnitType( unit, UNIT_TYPE_DEAD ) calls to UnitAlive
*DamageData now ends if the damage dealt kills the target rather than on the next timer iteration.
*BladeUnitData now longer checks for a null effect or nulls the unit.
*Cleaned up the BladeBarrier struct a bit.
*Added colouring global configs. for the Blade Barrier units.
*Create method no longer takes a player, but takes a integer level now.
*Inlined the INLINED_VOLUME.
*Added a new function: public function Create takes unit source, unit target, integer level returns boolean
*No longer using GroupUtils for recycling.
v1.03 - 26/06/2010
*It actually was not MUI. Fixed this.
*Privatalized a shitload of members. I don't know why.
*When a unit in the damage group left the radius, they couldn't get back in, fixed this.
v1.02 - 03/06/2010
*Code optimisation
*Added a configurable to have the original death animation
*Fixed the configurables
*Create method takes a player, and a unit - sourceUnit and whichPlayer
*Fixed the target Blade Barrier - still no support for one instance per target.
*Removed the target from the 'damage whirl' when they die.
v1.01 - 16/05/2010
*Code improvement
*Allowed option for instant removal of blades or fade out
*Allowed option for one barrier per unit
*Changed creation method of struct to take a unit, to allow in making a 'target' Blade Barrier (Dinowc)
v1.00 - 13/05/2010
*Initial release
//TESH.scrollpos=247
//TESH.alwaysfold=0
library BladeBarrier initializer onInit requires GT, KT, AIDS, SimError, Table
//+----------------------------------------------------------------------------------+
//| v1.04a - 30/06/2010 Saturday
//| Blade Barrier by BlackRose
//|
//+----------------------------------------------------------------------------------+
//|
//| Description: Creates a barrier of blades around the caster unit, damaging enemy
//| units that get in the way.
//|
//| Requires: - JassHelper [latest - http://www.wc3c.net/showthread.php?t=88142]
//| - GTrigger by Jesus4Lyf
//| - KeyTimer2 by Jesus4Lyf
//| - AIDS by Jesus4Lyf
//| - SimError by Vexorian
//| - Table by Vexorian
//|
//| Usage: call BladeBarrier_Create( unit source, unit target, integer level )
//|
//| How to import:
//| -------------
//| 1) Copy this trigger, and all requirements listed above into your map.
//| (You can copy the folder)
//| 2) Go to AIDS system, and follow it's implementation steps.
//| 3) Copy any object with Blade Barrier in it.
//| 4) Import the custom pitchModel into your map.
//| 5) Update all rawcodes.
//|
//+----------------------------------------------------------------------------------+
//|
//| - All credit to Tossrock, creator or Pudge Wars Advanced for the spell concept.
//|
//+----------------------------------------------------------------------------------+
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
//+ CONFIGURABLES SECTION
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
globals
private constant integer ABILITY_ID = 'A004' // "Blade Barrier" ability ID. This causes the response.
private constant integer DUMMY_ID = 'h001' // Rawcode of the required dummy unit. Must used the PitchDummy.MDX model.
private constant real TIMER_PERIOD = 0.03 // Movement of Blades done every TIMER_PERIOD.
private constant real DAMAGE_PERIOD = 0.10 // Units are damaged every DAMAGE_PERIOD.
private constant boolean ALLOW_MULTI_INSTANCE = false // If enabled, more than one Blade Barrier's can be made per unit.
private constant string ERROR_MSG = "You can only have one Blade Barrier at once."
//=====================================================
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_CHAOS
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype WEAPON_TYPE = null
private constant real COLLISION_SIZE = 32 // Collision size of units... well Pudge.
private constant real BLADE_SPINSPEED = 8.00
private constant real BLADE_ANGLESPEED = 0.12
//=====================================================
// Where damage effects are made.
private constant boolean DO_DMG_SFX = true
private constant string DMG_SFX = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
private constant string DMG_LOC = "chest"
// When a unit is killed by Blade Barrier, create these effects.
private constant boolean DO_DEATH_SFX = false
private constant string DEATH_SFX = ""
private constant string DEATH_LOC = ""
//=====================================================
// Sounds:
// --------
// They are played every x ticks of the TIMER_PERIOD, which is 0.075 seconds. One of the sounds below is chosen to play.
// Note: Some sounds cannot be played rapidly.
private constant real SOUND_EVERY = 0.09 // It has to be a multiple of TIMER_PERIOD.
private constant integer SOUND_VOLUME = 50 // The volume of the created sounds. Max is 127.
private constant string SOUND_ONE = "Sound\\Units\\Combat\\MetalLightSliceMetal1.wav"
private constant string SOUND_TWO = "Sound\\Units\\Combat\\MetalLightSliceMetal2.wav"
private constant string SOUND_THREE = "Sound\\Units\\Combat\\MetalLightSliceMetal3.wav"
private constant string SOUND_PARAM = "MetalLightSliceMetal"
private constant string SOUND_EAX = "CombatSoundsEAX"
private constant integer SOUND_DUR_1 = 376
private constant integer SOUND_DUR_2 = 480
private constant integer SOUND_DUR_3 = 461
//=====================================================
// Models:
// -------
// What shall your Blade Barrier look like?
private constant boolean FADE = true // Or else its death animation will be played.
private constant real RANGE_OFFSET = 20 // Blade will be created at RADIUS (function) + GetRandomReal( -20, 20 )
// The unit is not created with this colour for effiency.
// You must make sure these match the Object Editor's values unless you want an odd result.
private constant integer RED = 255
private constant integer GREEN = 255
private constant integer BLUE = 255
private constant real HEIGHT = 60.00
private constant string MODEL_ONE = "Abilities\\Weapons\\BloodElfSpellThiefMISSILE\\BloodElfSpellThiefMISSILE.mdl"
private constant string MODEL_TWO = "Abilities\\Weapons\\BloodElfSpellThiefMISSILE\\BloodElfSpellThiefMISSILE.mdl"
private constant string MODEL_THREE = "Abilities\\Weapons\\BloodElfSpellThiefMISSILE\\BloodElfSpellThiefMISSILE.mdl"
// Should the death animation be that of the original Pudge Wars?
private constant boolean ORIGINAL_DEATH_ANIM = false
endglobals
//+------------------------+
//| Don't touch below. |
//+------------------------+
private keyword BladeBarrier
globals
private constant integer INLINED_VOLUME = R2I(SOUND_VOLUME * 127.00 * 0.01)
private BladeBarrier tempData = 0
endglobals
//===========================================================\\
// Unit filter:
// - Organic
// - Living
// - Enemy
private function IsTargetValid takes unit whichUnit returns boolean
return IsUnitEnemy( whichUnit, tempData.owner ) and /* Enemy
*/ IsUnitType( whichUnit, UNIT_TYPE_STRUCTURE ) == false and /* Structure
*/ IsUnitType( whichUnit, UNIT_TYPE_DEAD ) == false and /* Alive
Don't touch this one.
*/ IsUnitInGroup( whichUnit, tempData.damageGroup ) == false
endfunction
//===========================================================\\
// Damage per second.
private function DPS takes integer level returns real
return level * 250.
endfunction
//===========================================================\\
// Blade Barrier duration.
private function DURATION takes integer level returns real
return 20.00
endfunction
//===========================================================\\
// Area of the spell.
private function RADIUS takes integer level returns real
return 200.00
endfunction
//===========================================================\\
// How many Blade effects are created?
private function BLADES takes integer level returns integer
return level * 20
endfunction
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
//+ END CONFIGURABLES
//+ BLADE BARRIER STARTS HERE.
//+-----------------------------------------------------------------------------------------------------------------------------------+\\
native UnitAlive takes unit id returns boolean
static if not ALLOW_MULTI_INSTANCE then // Do not create the group if its true. Waste of variable.
globals
private group ACTIVE_GROUP = CreateGroup() // Required group if you only want one barrier per unit.
endglobals
endif
//----------------------------------------------------
// BladeUnitData: Handles the blade units.
//----------------------------------------------------
private struct BladeUnitData extends array
//! runtextmacro AIDS()
unit blade
effect fxpath
integer pitch
real pitchDir
real rotationDir
real angle
real angleDir
real radius
static method AIDS_filter takes unit u returns boolean
return GetUnitTypeId( u ) == DUMMY_ID
endmethod
method cleanup takes nothing returns nothing
call DestroyEffect( this.fxpath )
static if FADE then
call RemoveUnit( this.blade )
else
call KillUnit( this.blade )
endif
endmethod
endstruct
//------------------------------------------------------------------------------------------
// DAMAGE SECTION
//------------------------------------------------------------------------------------------
private struct DamageData
BladeBarrier originData // Used to keep track of the parent data.
unit target = null // Assigned target.
real dps = 0.00 // Damage per Second.
real radius_45 = 0.00
real radius_80 = 0.00
private static method periodic takes nothing returns boolean
local thistype this = KT_GetData()
local real tx = GetUnitX( this.target )
local real ty = GetUnitY( this.target )
local real tempX = this.originData.cx - tx
local real tempY = this.originData.cy - ty
if this.originData.sec >= this.originData.dur or /* Expired
*/ ( tempX*tempX+tempY*tempY ) > this.radius_45 or /* Exit barrier radius
*/ not UnitAlive( this.originData.caster ) or /* Dead caster
*/ not UnitAlive( this.target ) then // dead target
call GroupRemoveUnit( this.originData.damageGroup, this.target )
call this.destroy()
return true
endif
if ( tempX*tempX+tempY*tempY ) > this.radius_80 then
static if DO_DMG_SFX then
call DestroyEffect( AddSpecialEffectTarget( DMG_SFX, this.target, DMG_LOC ) )
endif
call UnitDamageTarget( this.originData.source, this.target, this.dps, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
if not UnitAlive( this.target ) then
static if DO_DEATH_SFX then
call call DestroyEffect( AddSpecialEffectTarget( DEATH_SFX, this.target, DEATH_SFX_LOC ) )
endif
call this.destroy()
return true
endif
endif
return false
endmethod
static method create takes unit u, BladeBarrier i returns thistype
local thistype this = thistype.allocate()
set this.originData = i
set this.target = u
set this.dps = DPS( i.level ) * DAMAGE_PERIOD // Level is stored, incase you leveled up the skill while the blade is active.
set this.radius_45 = (i.radius+45)*(i.radius+45)
set this.radius_80 = (i.radius-80)*(i.radius-80)
call GroupAddUnit( i.damageGroup, u )
call KT_Add( function thistype.periodic, this, DAMAGE_PERIOD )
return this
endmethod
endstruct
private struct BladeBarrier
boolean early = false // Was the spell cancelled early?
unit source = null
player owner = null
unit caster = null
integer level = 0
real cx = 0.00
real cy = 0.00
real cz = 0.00
real radius = 0.00
real ticks = 0.00
integer alpha = 255
real sec = 0.00
real dur = 0.00
group bladeGroup = null
group damageGroup = null
trigger t = null
static HandleTable data = 0
// Creates yet another blade to add into the barrier.
private method AddBlade takes nothing returns nothing
local unit blade = CreateUnit( this.owner, DUMMY_ID, this.cx, this.cy, 0 )
local BladeUnitData bdata = BladeUnitData[blade]
local integer n = GetRandomInt( 0, 2 )
// Add a bit of variety into the blades, aye?
set bdata.rotationDir = GetRandomReal( 0.40, 1.60 )
set bdata.pitchDir = GetRandomReal( 0.40, 1.60 )
set bdata.angleDir = GetRandomReal( 0.40, 1.60 )
if GetRandomInt( 0, 1 ) == 0 then
set bdata.rotationDir = bdata.rotationDir * -1
set bdata.angleDir = bdata.angleDir * -1
else
set bdata.pitchDir = bdata.pitchDir * -1
endif
set bdata.blade = blade
set bdata.pitch = GetRandomInt( 0 ,180 )
set bdata.radius = this.radius + GetRandomReal( -RANGE_OFFSET, RANGE_OFFSET )
set bdata.angle = GetRandomReal( -bj_PI, bj_PI )
if n == 0 then
set bdata.fxpath = AddSpecialEffectTarget( MODEL_ONE, blade, "origin" )
elseif n == 1 then
set bdata.fxpath = AddSpecialEffectTarget( MODEL_TWO, blade, "origin" )
elseif n == 2 then
set bdata.fxpath = AddSpecialEffectTarget( MODEL_THREE, blade, "origin" )
endif
call GroupAddUnit( this.bladeGroup, blade )
set blade = null
endmethod
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private static method rotateBlade takes nothing returns nothing
local unit bladeUnit = GetEnumUnit()
local BladeUnitData bdata = BladeUnitData[bladeUnit]
local integer newPitch = ModuloInteger( bdata.pitch + R2I( bdata.pitchDir * BLADE_SPINSPEED ), 180 )
local real newFacing = GetUnitFacing( bladeUnit ) + bdata.rotationDir * BLADE_SPINSPEED
local real x = tempData.cx + bdata.radius * Cos( bdata.angle )
local real y = tempData.cy + bdata.radius * Sin( bdata.angle )
call SetUnitAnimationByIndex( bladeUnit, newPitch )
call SetUnitFacing( bladeUnit, newFacing )
call SetUnitX( bladeUnit, x )
call SetUnitY( bladeUnit, y )
set bdata.angle = bdata.angle + bdata.angleDir * BLADE_ANGLESPEED
set bladeUnit = null
endmethod
private method createBladeSound takes nothing returns nothing
local integer r = GetRandomInt( 0, 2 )
local sound s = null
if r == 0 then
set s = CreateSound( SOUND_ONE, false, true, true, 10, 10, SOUND_EAX )
call SetSoundParamsFromLabel( s, SOUND_PARAM )
call SetSoundDuration( s, SOUND_DUR_1 )
elseif r == 1 then
set s = CreateSound( SOUND_TWO, false, true, true, 10, 10, SOUND_EAX )
call SetSoundParamsFromLabel( s, SOUND_PARAM )
call SetSoundDuration( s, SOUND_DUR_2 )
elseif r == 2 then
set s = CreateSound( SOUND_THREE, false, true, true, 10, 10, SOUND_EAX )
call SetSoundParamsFromLabel( s, SOUND_PARAM )
call SetSoundDuration( s, SOUND_DUR_3 )
endif
call SetSoundVolume( s, INLINED_VOLUME )
call SetSoundPosition( s, this.cx, this.cy, GetUnitFlyHeight( this.caster ) )
call StartSound( s )
call KillSoundWhenDone( s )
set this.ticks = 0.00
set s = null
endmethod
private static method adjustHeight takes nothing returns nothing
call SetUnitFlyHeight( GetEnumUnit(), tempData.cz + HEIGHT, 0.00 )
endmethod
private static method periodic takes nothing returns boolean
local thistype this = KT_GetData()
local real z = GetUnitFlyHeight( this.caster )
set this.cx = GetUnitX( this.caster )
set this.cy = GetUnitY( this.caster )
set tempData = this
call ForGroup( this.bladeGroup, function thistype.rotateBlade )
if z != this.cz then
set this.cz = z
call ForGroup( this.bladeGroup, function thistype.adjustHeight )
endif
set this.ticks = this.ticks + TIMER_PERIOD
if ModuloReal( this.ticks, SOUND_EVERY ) == 0.00 then
call this.createBladeSound()
endif
set this.sec = this.sec + TIMER_PERIOD
if this.sec >= this.dur or not UnitAlive( this.caster ) then
static if not ALLOW_MULTI_INSTANCE then
call GroupRemoveUnit( ACTIVE_GROUP, this.caster )
endif
call thistype.data.flush( this.t )
call DestroyTrigger( this.t )
static if ORIGINAL_DEATH_ANIM then
set this.sec = 15.00
endif
set this.ticks = 0
static if FADE then
call KT_Add( function thistype.release, this, TIMER_PERIOD )
else
call this.destroy()
endif
return true
endif
return false
endmethod
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private static method GroupDestroy takes nothing returns nothing
call BladeUnitData[GetEnumUnit()].cleanup()
endmethod
private static method deathBlade takes nothing returns nothing
local unit u = GetEnumUnit()
local BladeUnitData bdata = BladeUnitData[u]
call SetUnitAnimationByIndex( u, ModuloInteger( bdata.pitch + R2I( bdata.pitchDir * BLADE_SPINSPEED ), 180 ) )
call SetUnitFacing( u, GetUnitFacing( u ) + bdata.rotationDir * BLADE_SPINSPEED )
call SetUnitX( u, tempData.cx + bdata.radius * Cos( bdata.angle ) )
call SetUnitY( u, tempData.cy + bdata.radius * Sin( bdata.angle ) )
set bdata.radius = bdata.radius - 18 / ( tempData.sec - 13 )
call SetUnitVertexColor( u, RED, GREEN, BLUE, tempData.alpha )
endmethod
private static method release takes nothing returns boolean
local thistype this = KT_GetData()
set tempData = this
call ForGroup( this.bladeGroup, function thistype.deathBlade )
set this.ticks = this.ticks + 1
set this.sec = this.sec + TIMER_PERIOD
set this.alpha = R2I( 255 - this.ticks * 3 )
if this.alpha <= 0.00 then
call this.destroy()
return true
endif
return false
endmethod
private static method damageCond takes nothing returns boolean
local unit u = GetTriggerUnit()
if IsTargetValid( u ) then
call DamageData.create( u, thistype( thistype.data[GetTriggeringTrigger()] ) )
endif
set u = null
return false
endmethod
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private method onDestroy takes nothing returns nothing
call ForGroup( this.bladeGroup, function thistype.GroupDestroy )
endmethod
static method create takes unit sourceUnit, unit whichUnit, integer level returns thistype
local thistype this = thistype.allocate()
local integer i = 0
// sourceUnit --> Who cast the spell. They will be responsible for damage.
// whichUnit --> Who is the centre of the barrier.
// whichPlayer --> Who owns the Blade Barrier.
static if not ALLOW_MULTI_INSTANCE then
if IsUnitInGroup( whichUnit, ACTIVE_GROUP ) then
call SimError( GetOwningPlayer( sourceUnit ), ERROR_MSG )
set this.early = true
call this.deallocate()
return 0
endif
endif
if this.bladeGroup == null then
set this.bladeGroup = CreateGroup()
else
call GroupClear( this.bladeGroup )
endif
if this.damageGroup == null then
set this.damageGroup = CreateGroup()
else
call GroupClear( this.damageGroup )
endif
set this.source = sourceUnit
set this.caster = whichUnit
set this.cx = GetUnitX( whichUnit )
set this.cy = GetUnitY( whichUnit )
set this.cz = GetUnitFlyHeight( whichUnit )
set this.owner = GetOwningPlayer( sourceUnit )
set this.level = level
set this.dur = DURATION( this.level )
set this.radius = RADIUS ( this.level )
loop
exitwhen i == BLADES( this.level )
call this.AddBlade()
set i = i + 1
endloop
set tempData = this
set this.t = CreateTrigger()
set thistype.data[this.t] = this
call TriggerRegisterUnitInRange( this.t, this.caster, this.radius + ( 45 - COLLISION_SIZE ), null )
call TriggerAddCondition( this.t, function thistype.damageCond )
static if not ALLOW_MULTI_INSTANCE then
call GroupAddUnit( ACTIVE_GROUP, this.caster )
endif
call KT_Add( function thistype.periodic, this, TIMER_PERIOD )
return this
endmethod
endstruct
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Event responses:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private function onCast takes nothing returns boolean
local unit u = GetTriggerUnit()
if IsUnitInGroup( u, ACTIVE_GROUP ) then
call PauseUnit( u, true )
call IssueImmediateOrder( u, "stop" )
call PauseUnit( u, false )
call SimError( GetTriggerPlayer(), ERROR_MSG )
endif
set u = null
return false
endfunction
// onEffect: response to the no target ability, it directly utilizes the struct.
private function onEffect takes nothing returns boolean
local unit u = GetTriggerUnit()
call BladeBarrier.create( u, u, GetUnitAbilityLevel( u, ABILITY_ID ) )
set u = null
return false
endfunction
// BladeBarrier_Create: uses a boolean for determining whether a instance was
// successful or not, eg: powerups.
public function Create takes unit source, unit target, integer level returns boolean
local BladeBarrier barrier = BladeBarrier.create( source, target, level )
if barrier == 0 then
return false
endif
return true
endfunction
private function onInit takes nothing returns nothing
call GT_AddStartsEffectAction( function onEffect, ABILITY_ID )
static if not ALLOW_MULTI_INSTANCE then
call GT_AddBeginsChanellingAction( function onCast, ABILITY_ID )
endif
set BladeBarrier.data = Table.create()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Powerup_Conditions takes nothing returns boolean
return GetItemTypeId(GetManipulatedItem()) == 'I000'
endfunction
function Trig_Powerup_Actions takes nothing returns nothing
local unit hero = GetManipulatingUnit()
local item powerup = GetManipulatedItem()
local real x = GetWidgetX( powerup )
local real y = GetWidgetY( powerup )
call RemoveItem( powerup )
// Create a Blade Barrier upon the unit that picked up
// the powerup with a level of 2.
if BladeBarrier_Create( hero, hero, 2 ) == false then
call CreateItem( 'I000', x, y )
endif
set hero = null
set powerup = null
endfunction
//===========================================================================
function InitTrig_Powerup takes nothing returns nothing
set gg_trg_Powerup = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Powerup, EVENT_PLAYER_UNIT_PICKUP_ITEM )
call TriggerAddCondition( gg_trg_Powerup, Condition( function Trig_Powerup_Conditions ) )
call TriggerAddAction( gg_trg_Powerup, function Trig_Powerup_Actions )
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
library BladeBarrierTarget initializer onInit requires BladeBarrier, GT
globals
private constant integer ABILITY_ID = 'A000' // Blade Barrier (Target)
endglobals
private function onCast takes nothing returns boolean
// Level 1 barrier made.
call BladeBarrier_Create( GetTriggerUnit(), GetSpellTargetUnit(), 1 )
return false
endfunction
//===========================================================================
private function onInit takes nothing returns nothing
call GT_AddBeginsCastingAction( function onCast, ABILITY_ID )
endfunction
endlibrary