- Joined
- Nov 18, 2012
- Messages
- 311
src= System - JCast (advanced casting-system) | The Helper
JASS:
//TESH.scrollpos=583
//TESH.alwaysfold=0
//***************************************************************************************************
//* JCast
//* -------
//* Author: profet (profetiser_AT_hotmail.com)
//*
//* Description:
//*
//* This system provides an easy way to create complex custom spells.
//*
//* JCast is a surrogate to the whole warcraft3's casting system : it replaces the functioning of
//* the cast itself and allows a lot of new behaviours like variable casttime (a buff or an item
//* could increases or decreases the cast/channeling time).
//* It also encapsulates the whole spell inside a struct, that enables us to easily attach data to
//* each spell instance (effects, numerical values...), solving by the way a lot of programing problems.
//*
//*
//* Usage:
//*
//* The creation of a new spell is a very simple task:
//* 1. Create a new structure that extends the IJCast interface.
//* 2. Define the static constant AbilityId, needed for spell's registration.
//* 3. Implement JCastModule (at the end of the struct, see FocusedHealing example for details).
//* 4. Override the methods required to fit your needs.
//*
//* Overridable methods are numerous:
/*
method CanCast takes nothing returns boolean defaults true
//Returns FALSE to deny the cast.
method CanBeInterrupted takes nothing returns boolean defaults true
//Returns FALSE to allow the caster to move during the cast (see RemoteBomb example for details).
method ResumeAttack takes nothing returns boolean defaults false
//Returns TRUE to restore caster's attack order when the spell is cast.
method RefundManaOnCancel takes nothing returns boolean defaults true
//Returns FALSE to make the mana consumed if the spell is canceled.
method GetCastTime takes nothing returns real defaults 0.
//Returns the duration of the cast (time before the spell's effects)
method GetChannelTime takes nothing returns real defaults 0.
//Returns the duration of the channeling (duration of spell's effects, used in combination with GetChannelTickTime)
method GetChannelTickTime takes nothing returns real defaults 0.
//Returns the period of channeling tick's (see LifeDrain example for details).
method ApplyCastPushback takes real progress, real castTime, real pushCount returns real
//Returns the new casting progress value, modified by the pushback effect.
//Note: You can return a changeless 'progress' value to ignore this effect.
method ApplyChannelPushback takes real progress, real channelTime, real pushCount returns real
//Returns the new channeling progress value, modified by the pushback effect.
//Note: You can return a changeless 'progress' value to ignore this effect.
static method OnInit takes nothing returns nothing
//Called just after struct's initialization.
method OnBegin takes nothing returns nothing
//Called at spell's initialization start.
method OnFail takes nothing returns nothing
//Called when the spell fails (CanCast returned FALSE).
method OnPreCast takes nothing returns nothing
//Called when the spell starts (after manacost calculation).
method OnCastStart takes nothing returns nothing
//(only if cast duration > 0) Called when the cast starts.
method OnCastUpdate takes real progress returns nothing
//(only if cast duration > 0) Called at each cast update (ref. TIMER_THRESHOLD constant),
//use this only if you need to update things all along the cast.
method OnCastEnd takes nothing returns nothing
//(only if cast duration > 0) Called when the cast ends (after the duration returned by GetCastTime).
method OnEffect takes nothing returns nothing
//Called just after OnCastEnd if spell conditions are still valid (CanCast is reevaluated when the cast ends)
method OnChannelStart takes nothing returns nothing
//(only if channel duration > 0) Called when the channeling starts.
method OnChannelUpdate takes real progress returns nothing
//(only if channel duration > 0) Called at each channeling update (ref. TIMER_THRESHOLD
//constant), use this only if you need to update things all along the channeling.
method OnChannelTick takes nothing returns nothing
//(only if channel duration > 0) Called when a channeling tick is reached (used to
//execute periodical code during channeling)
method OnChannelEnd takes nothing returns nothing
//(only if channel duration > 0) Called when the channeling ends.
method OnEnd takes nothing returns nothing
//Called whenever the spell ends, even on failure.
*/
//*
//*
//* Requirements:
//* Table (by Vexorian, http://www.wc3c.net/showthread.php?t=101246 )
//* TimerUtils (by Vexorian, http://www.wc3c.net/showthread.php?t=101322 )
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//***************************************************************************************************
library JCast initializer init uses Table, TimerUtils, optional JDebug
//===========================================================================
// SETTINGS
// ˉˉˉˉˉˉˉˉ
// This section exposes all the system's configuration variables and functions.
// Feel free to modify them to suit your needs !
//===========================================================================
globals
//Period of the spell's update timer, keeping it as low as possible increases precision but lowers system's performances.
//Note that update period also affects casting/channeling bar and OnCastUpdate/OnChannelUpdate update rate.
private constant real TIMER_THRESHOLD = 0.01
endglobals
//===========================================================================
//* LIBRARY'S CORE
//* ˉˉˉˉˉˉˉˉˉˉˉˉˉˉ
//* Do NOT modify without a really good reason !!
//===========================================================================
//***********************************************************
//* LAST TARGET SYSTEM
//* Allow to retrieve the last attacked unit if interrupted by a cast
//*
// The target is stored whenever the unit issue an "attack" or "smart" order.
// When the unit issue a point order, stored target is instantly cleared.
// When the unit issue an order with no target, if issue order is not "stop" nor "holdposition"
// the target is flagged as "previous target" and will be cleared if an other no-target order is issued.
globals
private HandleTable TARGET_TABLE
private integer tempOrder
endglobals
private struct sTargetManager
private unit m_unit
private unit m_target = null
private boolean m_current = false
method Reset takes nothing returns nothing
//-> Point order or "Stop"
if (.m_target != null) then
set .m_target = null
//set .m_current = false
endif
endmethod
method Set takes unit target returns nothing
//-> "Attack" or "Smart" order
if (target != null) then
set .m_target = target
set .m_current = true
endif
endmethod
method Unset takes nothing returns nothing
//-> order with no target
if (.m_target != null) then
if (.m_current) then
set .m_current = false
else
set .m_target = null
set .m_current = false
endif
endif
endmethod
method getTarget takes nothing returns unit
if (.m_target != null) and (GetWidgetLife(.m_target) > .405) then
return .m_target
endif
return null
endmethod
static method GetTarget takes unit un returns unit
if TARGET_TABLE.exists(un) then
return thistype(TARGET_TABLE[un]).getTarget()
endif
return null
endmethod
static method create takes unit un returns thistype
local thistype this = thistype.allocate()
set this.m_unit = un
return this
endmethod
endstruct
//smart: 851971
//stop: 851972
//attack: 851983
//move: 851986
//patrol: 851990
//holdposition: 851993
private function TargetOrder_Actions takes nothing returns nothing
if TARGET_TABLE.exists(GetOrderedUnit()) then
//If the unit issued an "Attack" order (or "Smart" order on hostile target)
if (GetIssuedOrderId() == 851983 /*attack*/) or ((GetIssuedOrderId() == 851971 /*smart*/) and IsUnitEnemy(GetOrderTargetUnit(), GetOwningPlayer(GetOrderedUnit()))) then
call sTargetManager(TARGET_TABLE[GetOrderedUnit()]).Set( GetOrderTargetUnit() )
endif
endif
endfunction
private function VoidOrder_Actions takes nothing returns nothing
if TARGET_TABLE.exists(GetOrderedUnit()) then
if (GetIssuedOrderId() == 851972 /*stop*/) or (GetIssuedOrderId() == 851993 /*holdposition*/) then
call sTargetManager(TARGET_TABLE[GetOrderedUnit()]).Reset()
else
call sTargetManager(TARGET_TABLE[GetOrderedUnit()]).Unset()
endif
endif
endfunction
private function PointOrder_Actions takes nothing returns nothing
if TARGET_TABLE.exists(GetOrderedUnit()) then
call sTargetManager(TARGET_TABLE[GetOrderedUnit()]).Reset()
endif
endfunction
private function initLastTarget takes nothing returns nothing
local integer i = 0
local trigger t
set TARGET_TABLE = HandleTable.create()
//Issue a target order
set t = CreateTrigger()
call TriggerAddAction(t, function TargetOrder_Actions)
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
set i = i +1
exitwhen (i == bj_MAX_PLAYER_SLOTS)
endloop
set i = 0
//Issue a point order
set t = CreateTrigger()
call TriggerAddAction(t, function PointOrder_Actions)
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
set i = i +1
exitwhen (i == bj_MAX_PLAYER_SLOTS)
endloop
set i = 0
//Issue an order with no target
set t = CreateTrigger()
call TriggerAddAction(t, function VoidOrder_Actions)
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
set i = i +1
exitwhen (i == bj_MAX_PLAYER_SLOTS)
endloop
endfunction
//***********************************************************
//* CASTING SYSTEM
globals
private HandleTable UNIT_TABLE
private trigger STOP_TRIGGER
endglobals
interface IJCast
//Optional methods
method CanCast takes nothing returns boolean defaults true
method CanBeInterrupted takes nothing returns boolean defaults true
method GetCastTime takes nothing returns real defaults 0.
method GetChannelTime takes nothing returns real defaults 0.
method GetChannelTickTime takes nothing returns real defaults 0.
method ResumeAttack takes nothing returns boolean defaults false
method RefundManaOnCancel takes nothing returns boolean defaults true
method OnBegin takes nothing returns nothing defaults nothing
method OnFail takes nothing returns nothing defaults nothing
method OnPreCast takes nothing returns nothing defaults nothing
method OnCastStart takes nothing returns nothing defaults nothing
method OnCastEnd takes nothing returns nothing defaults nothing
method OnEffect takes nothing returns nothing defaults nothing
method OnChannelStart takes nothing returns nothing defaults nothing
method OnChannelTick takes nothing returns nothing defaults nothing
method OnChannelEnd takes nothing returns nothing defaults nothing
method OnEnd takes nothing returns nothing defaults nothing
//Automatically implemented by JCastModule
method destroy takes nothing returns nothing
method interrupt takes trigger t returns nothing
method pushback takes nothing returns nothing
endinterface
module JCastModule
//----------------------------------------------------------------------
// Private Members
//States
private static constant integer STATE_INIT = 0
private static constant integer STATE_FAIL = 1 //only if the cast fails
private static constant integer STATE_CAST = 2 //skipped if no cast time
private static constant integer STATE_EFFECT = 3
private static constant integer STATE_CHANNEL = 4 //skipped if no channel time
private static constant integer STATE_INTERRUPT = 5
private static constant integer STATE_END = 6
private static method getStateName takes integer state returns string
if (state == STATE_INIT) then
return "INIT"
elseif (state == STATE_FAIL) then
return "FAIL"
elseif (state == STATE_CAST) then
return "CAST"
elseif (state == STATE_EFFECT) then
return "EFFECT"
elseif (state == STATE_CHANNEL) then
return "CHANNEL"
elseif (state == STATE_INTERRUPT) then
return "INTERRUPT"
elseif (state == STATE_END) then
return "END"
endif
return ""
endmethod
//Core properties
private timer m_timer = null
private integer m_state = STATE_INIT
private boolean m_isRegistered = false
private boolean m_isCast = false
private real m_progress = 0.
private real m_tickProgress = 0.
private integer m_pushCount = 0
private real m_manaCostTemp //used to calculate spell's manacost
private real m_manaCost = 0.
//Properties loaded from custom struct's definition
private boolean m_manaRefundOnCancel
private unit m_attackTarget
private boolean m_resumeAttack
private real m_castTime
private real m_channelTime
private real m_channelTickTime
//Spell's event response properties
private unit m_caster
private integer m_level
private real m_targetX
private real m_targetY
private unit m_targetUnit
private destructable m_targetDest
private item m_targetItem
//----------------------------------------------------------------------
// Constructor and Destructor
//Constructor is defined as private to prevent wild allocation of this struct
private static method create takes nothing returns thistype
return 0
endmethod
//Simple destructor :)
private method destroy takes nothing returns nothing
//If the cast is not complete, give the mana back to the caster
if (not .m_isCast) and (.m_manaCost > 0) and .m_manaRefundOnCancel then
call SetUnitState(.m_caster, UNIT_STATE_MANA, GetUnitState(.m_caster,UNIT_STATE_MANA) + .m_manaCost)
endif
//Clear instance
if (.m_timer != null) then
call ReleaseTimer( .m_timer )
endif
call this.deallocate()
endmethod
//----------------------------------------------------------------------
// Private Helpers
private method setState takes integer newState returns nothing
static if LIBRARY_JDebug then
call JDebug_LibMsg("JCast("+I2S(this)+")", "State changed from "+I2S(.m_state)+":"+.getStateName(.m_state)+" to "+I2S(newState)+":"+.getStateName(newState))
endif
set .m_state = newState
endmethod
private method getState takes nothing returns integer
return .m_state
endmethod
method pushback takes nothing returns nothing
static if thistype.ApplyCastPushback.exists then
if (getState() == STATE_CAST) then
set .m_pushCount = .m_pushCount + 1
set .m_progress = ApplyCastPushback(.m_progress, .m_castTime, .m_pushCount)
return
endif
else
//No casting pushback!
endif
static if thistype.ApplyChannelPushback.exists then
if (getState() == STATE_CHANNEL) then
set .m_pushCount = .m_pushCount + 1
set .m_progress = ApplyChannelPushback(.m_progress, .m_channelTime, .m_pushCount)
endif
else
//No channeling pushback!
endif
endmethod
//----------------------------------------------------------------------
// Public Helpers
method GetLevel takes nothing returns integer
return .m_level
endmethod
method GetCaster takes nothing returns unit
return .m_caster
endmethod
method GetAttackTarget takes nothing returns unit
return .m_attackTarget
endmethod
method GetTargetX takes nothing returns real
return .m_targetX
endmethod
method GetTargetY takes nothing returns real
return .m_targetY
endmethod
method GetTargetUnit takes nothing returns unit
return .m_targetUnit
endmethod
method GetTargetItem takes nothing returns item
return .m_targetItem
endmethod
method GetTargetDestructable takes nothing returns destructable
return .m_targetDest
endmethod
method GetManaCost takes nothing returns real
return .m_manaCost
endmethod
method IsCast takes nothing returns boolean
return .m_isCast
endmethod
static constant method GetUpdateRate takes nothing returns real
return TIMER_THRESHOLD
endmethod
//----------------------------------------------------------------------
// STATE : End
private method stopAttackContinuation takes nothing returns nothing
call IssueTargetOrderById(m_caster, 851983 /*attack*/, .m_attackTarget)
call destroy()
endmethod
private static method stopAttackContinuation_Callback takes nothing returns nothing
call thistype(GetTimerData(GetExpiredTimer())).stopAttackContinuation()
endmethod
private method doEnd takes nothing returns nothing
//Execute custom event
if (getState() != STATE_FAIL) then
call OnEnd()
endif
//Change state
call setState(STATE_END)
//If the spell is registered, we have to stop the spell
//(prevent the unit from keeping channeling if the spell's based on Channel that has a follow through time)
//If the spell uses attack continuation, order the caster to attack with a tiny delay (else 851973 order won't stop the cast)
if .m_resumeAttack then
call TimerStart(m_timer, 0.00, false, function thistype.stopAttackContinuation_Callback)
return
endif
//else destroy the spell instance
call destroy()
endmethod
private method doFail takes nothing returns nothing
//Stop the WC3 spell
call IssueImmediateOrderById(m_caster, 851973 /*stunned*/)
//Change state from STATE_INIT
call setState(STATE_FAIL)
//Execute custom event
call OnFail()
//Stop the spell before mana and cooldown are consumed
if (not .m_isRegistered) then
call doEnd()
endif
endmethod
//----------------------------------------------------------------------
// STATE : Channel
private method cleanChannel takes nothing returns nothing
call PauseTimer(m_timer)
//if .m_showChannelBar then
// call channelBarStop(m_caster)
//endif
call OnChannelEnd()
endmethod
private method channelTerminate takes nothing returns nothing
//Clear channeling data
call cleanChannel()
//Jump to STOP state
if .m_isRegistered then
call IssueImmediateOrderById(m_caster, 851973 /*stunned*/)
else
call doEnd()
endif
endmethod
private method channelUpdate takes nothing returns nothing
//If custom spell's conditions are not met, end the channeling
if (not CanCast()) then
call channelTerminate()
return
endif
//Update channeling and tick progress
set .m_progress = .m_progress + TIMER_THRESHOLD
set .m_tickProgress = .m_tickProgress + TIMER_THRESHOLD
//Execute custom update method
static if thistype.OnChannelUpdate.exists then
call OnChannelUpdate(.m_progress / .m_channelTime)
else
//No update method call (OnChannelUpdate() method is not found)
endif
//If we reached a tick, execute tick event
if (m_channelTickTime > 0.) and (m_tickProgress >= .m_channelTickTime) then
set .m_tickProgress = .m_tickProgress - .m_channelTickTime
//Execute custom event
call OnChannelTick()
//Check again spell's conditions
if (not CanCast()) then
call channelTerminate()
return
endif
//Update channel tick period
set .m_channelTickTime = .GetChannelTickTime()
endif
//If channeling is complete, terminate.
if (m_progress >= .m_channelTime) then
call channelTerminate()
endif
endmethod
private static method channelUpdate_Callback takes nothing returns nothing
call thistype(GetTimerData(GetExpiredTimer())).channelUpdate()
endmethod
private method doChannel takes nothing returns nothing
//Change state from STATE_EFFECT
call setState(STATE_CHANNEL)
//Execute custom event and start channeling
call OnChannelStart()
call TimerStart(m_timer, TIMER_THRESHOLD, true, function thistype.channelUpdate_Callback)
endmethod
//----------------------------------------------------------------------
// STATE : Effect
private method doEffect takes nothing returns nothing
//Change state from STATE_START or STATE_CAST
call setState(STATE_EFFECT)
//Mana is now irreparably lost
set .m_isCast = true
//If the spell do have a channel time, start channeling
if (m_channelTime > 0.) then
call OnEffect()
call doChannel()
//The cast is complete, then execute custom event and stop the spell
else
if .m_isRegistered then
call IssueImmediateOrderById(m_caster, 851973 /*stunned*/) //stop WC3 spell casting
call OnEffect()
else
call OnEffect()
call doEnd()
endif
endif
endmethod
//----------------------------------------------------------------------
// STATE : Cast
private method cleanCast takes nothing returns nothing
call PauseTimer(m_timer)
set .m_progress = 0.
set .m_pushCount = 0
call OnCastEnd()
endmethod
private method castTerminate takes nothing returns nothing
//Clear casting data
call cleanCast()
//If custom spell's conditions are met, go to the EFFECT state or stop the spell
if CanCast() then
call doEffect()
else
call doFail()
endif
endmethod
private method castUpdate takes nothing returns nothing
//Update precast progress
set .m_progress = .m_progress + TIMER_THRESHOLD
//Execute custom update method
static if thistype.OnCastUpdate.exists then
call OnCastUpdate(.m_progress / .m_castTime)
else
//No update method call (OnCastUpdate() method is not found)
endif
//If progress is complete, terminate.
if (m_progress >= .m_castTime) then
call castTerminate()
endif
endmethod
private static method castUpdate_Callback takes nothing returns nothing
call thistype(GetTimerData(GetExpiredTimer())).castUpdate()
endmethod
private method doCast takes nothing returns nothing
//Change state from STATE_INIT
call setState(STATE_CAST)
//Execute custom event and start casting
call OnCastStart()
call TimerStart(m_timer, TIMER_THRESHOLD, true, function thistype.castUpdate_Callback)
endmethod
//----------------------------------------------------------------------
// Initialization
//
// This system automatically handles Warcraft3's casting events.
// To do that, a trigger is created at struct initialization.
//
// Each time the spell is cast by a unit, an instance of this structure
// is created and proceeded.
//
//----------------------------------------------------------------------
private method initTerminate takes nothing returns nothing
//Execute custom event
call OnPreCast()
//If the spell do have a cast time, start casting
if (m_castTime > 0.) then
call doCast()
//Else skip the CAST state and jump to the EFFECT state
else
call doEffect()
endif
endmethod
private method initAttackContinuation takes nothing returns nothing
set .m_resumeAttack = false
call IssueTargetOrderById(m_caster, 851983 /*attack*/, .m_attackTarget)
call initTerminate()
endmethod
private static method initAttackContinuation_Callback takes nothing returns nothing
call thistype(GetTimerData(GetExpiredTimer())).initAttackContinuation()
endmethod
private method initFull takes nothing returns nothing
//Calculate spell's manacost
set .m_manaCost = .m_manaCostTemp - GetUnitState(m_caster, UNIT_STATE_MANA)
//If the spell is not registered, the caster should not be involved in the cast then stop his current order
//(prevent the unit from keeping channeling if the spell's based on Channel that has a follow through time)
if (not .m_isRegistered) then
call IssueImmediateOrderById(m_caster, 851973 /*stunned*/)
//If the spell uses attack continuation, order the caster to attack with a tiny delay (else 851973 order won't stop the cast)
if .m_resumeAttack then
call TimerStart(m_timer, 0.00, false, function thistype.initAttackContinuation_Callback)
return
endif
endif
//Go to the casting phase
call initTerminate()
endmethod
private static method initFull_Callback takes nothing returns nothing
call thistype(GetTimerData(GetExpiredTimer())).initFull()
endmethod
private static method init_Conditions takes nothing returns boolean
return (GetSpellAbilityId() == thistype.AbilityId)
endmethod
private static method init_Actions takes nothing returns nothing
local thistype this = thistype.allocate()
//Initialization of core data (availaible in struct's methods through the use of getters)
set this.m_manaCost = 0.
set this.m_caster = GetSpellAbilityUnit()
set this.m_level = GetUnitAbilityLevel(this.m_caster, GetSpellAbilityId())
set this.m_targetX = GetSpellTargetX()
set this.m_targetY = GetSpellTargetY()
set this.m_targetUnit = GetSpellTargetUnit()
set this.m_targetDest = GetSpellTargetDestructable()
set this.m_targetItem = GetSpellTargetItem()
set this.m_attackTarget = sTargetManager.GetTarget(this.m_caster)
set this.m_timer = NewTimer()
call SetTimerData(this.m_timer, this)
call this.OnBegin()
set this.m_resumeAttack = this.ResumeAttack() and (this.m_attackTarget != null)
//If custom spell's conditions are met, start the cast
if this.CanCast() then
//Gather more required informations about the spell
set this.m_manaCostTemp = GetUnitState(this.m_caster, UNIT_STATE_MANA)
set this.m_manaRefundOnCancel = this.RefundManaOnCancel()
set this.m_castTime = this.GetCastTime()
set this.m_channelTime = this.GetChannelTime()
set this.m_channelTickTime = this.GetChannelTickTime()
//If the spell is interruptable and has a casting or channeling time, register this instance
if this.CanBeInterrupted() and ((this.m_castTime > 0.) or (this.m_channelTime > 0.)) then
set this.m_isRegistered = true
set UNIT_TABLE[this.m_caster] = integer(this)
endif
//We are at EVENT_PLAYER_UNIT_SPELL_ENDCAST stage and neither cooldown nor manacost was applied yet,
//then wait for a very short period to calculate the spell's manacost
call TimerStart(this.m_timer, 0.00, false, function thistype.initFull_Callback)
//Else abort the cast
else
call this.doFail()
endif
endmethod
//----------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------
//Note: I needed a public (since there is no protected keyword in vJass) method to be call by system's STOP_TRIGGER.
//However, allowing users to call this method from inside the struct (although it may seem interesting) would be
//contrary to the way I designed the system ; spell's control should be done through CanCast() method only.
method interrupt takes trigger t returns nothing
call PauseTimer(m_timer)
//Check if the call is legitimate
if (t != STOP_TRIGGER) then
static if LIBRARY_JDebug then
call JDebug_LibError( "JCast", "Trying to terminate a cast via interrupt() is not allowed, use CanCast() method instead" )
endif
return
endif
//Unregister the caster
call UNIT_TABLE.flush(.m_caster)
//Interrupt the spell
if (getState() == STATE_CAST) then
call cleanCast()
elseif (getState() == STATE_CHANNEL) then
call cleanChannel()
else
return // do nothing if we are in STATE_STOP
endif
//Change state and stop the spell
call setState(STATE_INTERRUPT)
call doEnd()
endmethod
//----------------------------------------------------------------------
//
//
//
//----------------------------------------------------------------------
private static method onInit takes nothing returns nothing
local trigger t
//Check if the specified ability exists or already registered
if (thistype.AbilityId <= 0) or (GetObjectName(thistype.AbilityId) == "Default string") then
return
endif
//Create a trigger to detect the cast of this spell
set t = CreateTrigger()
call TriggerAddAction(t, function thistype.init_Actions)
call TriggerAddCondition(t, Condition(function thistype.init_Conditions) )
call TriggerRegisterPlayerUnitEvent(t, Player(0), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(1), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(2), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(3), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(4), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(5), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(6), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(7), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(8), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(9), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(10), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t, Player(11), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
//Note: EVENT_PLAYER_UNIT_SPELL_CHANNEL : fired whenever a spell is cast, this is the first spell's event
// EVENT_PLAYER_UNIT_SPELL_EFFECT : fired just before mana is removed and cooldown started (if the cast was not canceled during war3's casting time)
// EVENT_PLAYER_UNIT_SPELL_FINISH : fired only when War3's cast is complete, interrupted spells won't fire this event
// EVENT_PLAYER_UNIT_SPELL_ENDCAST : fired whenever a spell is stopped, even if it was canceled
//Execute custom initialization event
static if thistype.OnInit.exists then
call thistype.OnInit()
else
//No init method call (OnInit() method is not found)
endif
endmethod
endmodule
//***********************************************************
//* LIBRARY INITIALIZER
//* A global trigger is used to catch SPELL_ENDCAST event, and stop current
//* cast spell instance.
//*
private function endCast_Actions takes nothing returns nothing
if UNIT_TABLE.exists(GetSpellAbilityUnit()) then
call IJCast(UNIT_TABLE[GetSpellAbilityUnit()]).interrupt(GetTriggeringTrigger()) //Destroy current cast instance
endif
endfunction
private function init takes nothing returns nothing
set STOP_TRIGGER = CreateTrigger()
call TriggerAddAction(STOP_TRIGGER, function endCast_Actions )
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(0), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(1), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(2), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(3), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(4), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(5), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(6), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(7), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(8), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(9), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(10), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
call TriggerRegisterPlayerUnitEvent(STOP_TRIGGER, Player(11), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
set UNIT_TABLE = HandleTable.create()
call initLastTarget()
endfunction
//===========================================================================
//* PUBLIC FUNCTIONS
//===========================================================================
function JCast_IsCasting takes unit un returns boolean
return ((un != null) and UNIT_TABLE.exists(un))
endfunction
function JCast_Pushback takes unit target returns nothing
if (target != null) and UNIT_TABLE.exists(target) then
call IJCast(UNIT_TABLE[target]).pushback()
endif
endfunction
function JCast_Interrupt takes unit target returns nothing
if (target != null) and UNIT_TABLE.exists(target) then
call IssueImmediateOrderById(target, 851972 /*stop*/)
endif
endfunction
function JCast_RegisterTargetWatch takes unit watchedUnit returns nothing
if (watchedUnit == null) or TARGET_TABLE.exists(watchedUnit) then
return
endif
set TARGET_TABLE[watchedUnit] = integer(sTargetManager.create(watchedUnit))
endfunction
endlibrary