• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] JCast (advanced casting-system)

Status
Not open for further replies.
Level 5
Joined
Jun 2, 2004
Messages
19
Hi everybody,

I've been deeply involved in wc3 mapping community (war3/wow models importer pluggin for 3dsMAX) and a very active system maker for years.

Long time ago, I started an RPG project Nights of Kalimdor for which I made several advanced systems (spell, buffs, physic, equipment, item sets...)
I've made some videos that are available here. They are quite rough but give a good preview of their use.
Because of the SC2 beta release and the wc3's programmed death, I decided to share these systems among the developers community before they fall into oblivion :)

I'm gonna start with my cast system, so, all I wish before releasing it as a resource is feedback to improve the system, and maybe projects that use it ! ;)


Overview (goal: break wc3 spells' limits and get a very reactive spells system)

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 increase or decrease 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.


Main features

- New timed-cast system (wc3 already implements this functionality but the hero is stuck on place wich is very unfriendly and supress all gameplay-reactivity, moreover the cast/channeling duration may be changed dynamically ingame)
- Many spell events : OnFail, OnBegin, OnCast, OnCastEnd, OnCastEffect, OnChannel, OnChannelTick, OnChannelEnd, OnEnd... (GetCaster() always returns the same value whatever the event)
- Highly configurable (ShowCastBar, ShowChannelBar, ReverseChannelBar, ResumeAttack, CanBeInterrupted...)
- Enable to create complex buffs or over-time spell's effects (ie. irregular ticks sequence).
- Custom casting check (through overloading of the CanCast method)
- Pushback system (the cast duration may increase if the units takes damage when casting, similar to WoW's pushback effect)
- Interruption system (a single function call can cancel any cast/channeling)
- Optional castbar (a customizable castbar can be updated on cast/channeling)
- Target watch (the caster can keep trace of it's previous target, thus it is now possible to create melee-targetless spells that don't interrupt attack)
- Consise and maintanable code (like the buffs system, a spell is defined by a single struct with overriden methods, no trigger have to be created)


Detailed features
The spell definition structure allows the following functions overriding:
JASS:
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

It also provides built-in functions to retrieves spell's informations:
JASS:
method GetLevel takes nothing returns integer
method GetCaster takes nothing returns unit
method GetManaCost takes nothing returns real

method GetAttackTarget takes nothing returns unit

method GetTargetX takes nothing returns real
method GetTargetY takes nothing returns real
method GetTargetUnit takes nothing returns unit
method GetTargetItem takes nothing returns item
method GetTargetDestructable takes nothing returns destructable

method IsCast takes nothing returns boolean
static constant method GetUpdateRate takes nothing returns real


Use example
JASS:
library AbilityLifeDrain uses AbilityCore
// This spell is a remake of the Warcraft III's life drain.


private struct spell extends IJCast
    
//Ability declaration
    private static constant integer AbilityId = 'AoLD'
    
    
//The channeling time lasts 10 seconds.
    method GetChannelTime takes nothing returns real
        return 10.
    endmethod
    
//Channeling tick event occurs each second.
    method GetChannelTickTime takes nothing returns real
        return 1.
    endmethod
    
//The spell can be cast only if the caster sees the target, if it's in given range and if it is still alive!
//Note: During channeling, CanCast method is also called each time a tick occurs (after the OnChannelTick call), then
//      the channeling could be interrupted if the target gets out of range, or die...
    method CanCast takes nothing returns boolean
        return IsUnitInSight(GetTargetUnit(), GetCaster()) and IsUnitInRange(GetTargetUnit(), GetCaster(), 600.) and (GetUnitState(GetTargetUnit(),UNIT_STATE_LIFE) > 0.)
    endmethod
    
//We need some specific code for this spell
    private effect effectCaster
    private effect effectTarget
    private lightning ray
    private sound snd
    private real casterX = 0.
    private real casterY = 0.
    private real casterZ = 0.
    private real targetX = 0.
    private real targetY = 0.
    private real targetZ = 0.
    private real progressT = 0.
    
    private method rayUpdate takes nothing returns nothing
        //Link the ray from caster's hands...
            set casterX = GetUnitX(GetCaster()) + 75.*Cos( GetUnitFacing(GetCaster())*bj_DEGTORAD )
            set casterY = GetUnitY(GetCaster()) + 75.*Sin( GetUnitFacing(GetCaster())*bj_DEGTORAD )
        //...to target's feet
            set targetX = GetUnitX(GetTargetUnit())
            set targetY = GetUnitY(GetTargetUnit())
            call MoveLightningEx( ray, true, casterX, casterY, casterZ, targetX, targetY, targetZ )
        //and make the caster face its target
            call SetUnitFacing( GetCaster(), Atan2(targetY-casterY,targetX-casterX)*bj_RADTODEG )
    endmethod
    
    
//Channeling progress is tracked to update the channelbar and the ray
    method OnChannelUpdate takes real progress returns nothing
        //Update the channel bar
            call Demo_ChannelbarUpdate(GetCaster(), progress)
        //Update the ray (only every 0.05s)
            set progressT = progressT + GetUpdateRate()
            if (progressT >= 0.05)then
                call rayUpdate()
                set progressT = progressT - 0.05
            endif
    endmethod
    
    
//Channeling starts, just create some special effects and initialize the ray
    method OnChannelStart takes nothing returns nothing
        //Add special effects
            set effectCaster = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Drain\\DrainCaster.mdl", GetCaster(), "origin")
            set effectTarget = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Drain\\DrainTarget.mdl", GetTargetUnit(), "chest")
        //Add lighning effect
            set casterZ = 50.
            set targetZ = 50.
            set ray = AddLightningEx("DRAL", true, casterX, casterY, casterZ, targetX, targetY, targetZ)
            call .rayUpdate()
    endmethod
    
//When a tick occurs, this code is executed !
    method OnChannelTick takes nothing returns nothing
    local real amount = 20. //+ I2R(GetHeroInt(GetCaster(),true)) //use hero's int as dmg bonus
        call SetUnitState(GetCaster(), UNIT_STATE_LIFE, GetUnitState(GetCaster(), UNIT_STATE_LIFE) + amount)
        call UnitDamageTarget(GetCaster(), GetTargetUnit(), amount, false/*attack*/, true/*ranged*/, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNIVERSAL/*ignore armor*/, WEAPON_TYPE_WHOKNOWS)
    endmethod
    
    
//The spell is over, that's time to reset the channelbar and destroy created effects.
    method OnChannelEnd takes nothing returns nothing
        call Demo_ChannelbarReset(GetCaster())
        call DestroyLightning(ray)
        call DestroyEffect(effectCaster)
        call DestroyEffect(effectTarget)
    endmethod
    
    
//When hit by an attack, channeling duration is reduced by 25% (max. 2 times per cast)
    method ApplyChannelPushback takes real progress, real channelTime, real pushCount returns real
        if (pushCount <= 2) then
            return progress + channelTime * 0.25
        else
            return progress
        endif
    endmethod
    
//Implement JCastModule at the end of the spell code
    implement JCastModule
    
endstruct

//===========================================================================
endlibrary


System code
JASS:
//***************************************************************************************************
//*  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, [url]http://www.wc3c.net/showthread.php?t=101246[/url] )
//*   TimerUtils (by Vexorian, [url]http://www.wc3c.net/showthread.php?t=101322[/url] )
//*
//*  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
 

Attachments

  • JCast_demo_v02.w3x
    108.4 KB · Views: 150
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
Yea I'm not sure why you're posting this here, you should post it in a submission section so that it can be reviewed by a moderator and approved. At first glance it seems like a neat idea, and a good implementation interface as well. I can't actually test it as you did not upload a test-map (and I'm lazy).

This library requires Table and TimerUtils but I have no seen any credit to Vexorian (the author of those libraries) given.

Other than that, your naming schemes are a little weird it is hard to follow exactly what controls what (not a huge issue, could just be me).

It also provides built-in functions to retrieves spell's informations:

Why not just make those values variables? It would be much quicker than having to use a method call each time you want to reference those values (or it would save variable declaration, either way it would be more efficient).
 
Level 5
Joined
Jun 2, 2004
Messages
19
Yea I'm not sure why you're posting this here, you should post it in a submission section so that it can be reviewed by a moderator and approved. At first glance it seems like a neat idea, and a good implementation interface as well. I can't actually test it as you did not upload a test-map (and I'm lazy).
I'm not releasing it as a resource because it might need some improvements, but if i'm mistaken let's move the post into the right section ;)

This library requires Table and TimerUtils but I have no seen any credit to Vexorian (the author of those libraries) given.
That's true, in fact the lib header isn't final, I'll fix this issue in the next release (/w link to wc3c's posts)

Other than that, your naming schemes are a little weird it is hard to follow exactly what controls what (not a huge issue, could just be me).
I guess this will be the eternal question of programing =)


Why not just make those values variables? It would be much quicker than having to use a method call each time you want to reference those values (or it would save variable declaration, either way it would be more efficient).
Theses functions are inlined (although I believe that the order of module's implementation matters), besides it gives more consistency among all retrievable datas and provides flexibility.


Concerning the demo map I'll be releasing one soon, but actually the only test map avalaible is the NoK's triggers development map that requires a lot of custom resources :)
 
Last edited:
Level 5
Joined
Jun 2, 2004
Messages
19
As promised, a little demo map (first post updated)

Changes:
- Castbar can now be specified inside the struct itself, instead of in the six setting functions (it gives the possibility to have different castbars for each spells)
- Pushback effect is no longer defined through setting constants, two new overridable methods (ApplyCastPushback and ApplyChannelPushback) are now available to modify the progress value.
- Added test map and system documentation
 
Ok nice map but why does it use the exp as castbar?

Because that is the most creative bar so far. ;P The only problem is if you gain experience while you are casting, but it is an example anyway. The system allows you to use whichever sort of cast-thing you want (eg: texttags, multiboards, images, etc.) so it isn't limited to only experience bars. =)
 
Level 6
Joined
Feb 12, 2008
Messages
207
Holy crap! I really envy your talent. Take that as a compliment. And btw, pretty nice custom stuff in the videos! L0v3D those physics...
I loved this system, as I was having bad time with the abilities in wc3. The main problems are when I want to make more than 1 spell based from the same ability, even if I change the string order, both spells are cast. Could that be overcome with this?
All the stuff from this system seems pretty easy to handle.
Just a little thing: I was using the remote bomb skill and it looks like I used it twice and the bomb bugged, never exploded and I was with the bomb like 2 minutes and it didnt wear off. (couldnt cast another one and was with the effect on the paladin).

I saw up there in your first post that you wanted feedback with the improvements. I had an idea when I saw the Hammer of Justice ability: Could you make something similar, but instead of being instant, making it on next melee attack? Similar to a bunch of skills from the warriors in WoW. That would be great.

My project will be using this system, you can count on it. VERY handy!
Although I will have to work hard, I just started with the JASS programming about 2 days ago when my map really needed it (GUI looks very inefficient and laggy).

EDIT: Oh.. I will look at the code by myself later when I get back home, but if you could help me on how to make a casting bar using TextTags would be really appreciated.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
I loved this system, as I was having bad time with the abilities in wc3. The main problems are when I want to make more than 1 spell based from the same ability, even if I change the string order, both spells are cast. Could that be overcome with this?

The majority of the abilities in Warcraft III have hard-coded ability order strings/IDs. Two abilities that do not follow this rule are Spellbook and Channel. Other than that you're always going to have to use different abilities.
 
Level 6
Joined
Feb 12, 2008
Messages
207
Well, thats good to know, but as I wanted to do every spell based on channel making them all coded, I had no success using it. It wasnt working as I intended to and had to go with basic abilities for my current project.
 
Level 5
Joined
Jun 2, 2004
Messages
19
Holy crap! I really envy your talent. Take that as a compliment. And btw, pretty nice custom stuff in the videos! L0v3D those physics...
I'll release my physic engine too, but I'm working on a new version that merge physic and missile systems so we need some patience =)

I loved this system, as I was having bad time with the abilities in wc3. The main problems are when I want to make more than 1 spell based from the same ability, even if I change the string order, both spells are cast. Could that be overcome with this?
As Berb said, the solution relies on the Channel ability (there are many complete guides on internet on how to use the Channel ability).
In my examples I'm using for spells with cast/channel-time a channel ability with a very high Follow through time, and let JCast handles the real cast duration.

Just a little thing: I was using the remote bomb skill and it looks like I used it twice and the bomb bugged, never exploded and I was with the bomb like 2 minutes and it didnt wear off. (couldnt cast another one and was with the effect on the paladin).
Crap, I already get this bug twice but I didn't explained it and never it happened again.
I'll investigate, because this is a very bad issue.

I saw up there in your first post that you wanted feedback with the improvements. I had an idea when I saw the Hammer of Justice ability: Could you make something similar, but instead of being instant, making it on next melee attack? Similar to a bunch of skills from the warriors in WoW. That would be great.
You can already do this by adding a buff (or passive spell) to the unit when the instant spell is cast, and remove just after the next attack :)


EDIT: Oh.. I will look at the code by myself later when I get back home, but if you could help me on how to make a casting bar using TextTags would be really appreciated.
If I'm not wrong, there are several textag-bar systems in the wild. Try to search here or on wc3c.net :)
 
Level 6
Joined
Feb 12, 2008
Messages
207
Well, first of all, sorry for some of my lazy questions about "please, do all my work, google for me" and thanks for the tips.

You can already do this by adding a buff (or passive spell) to the unit when the instant spell is cast, and remove just after the next attack :)
Nice idea. I think I'll use some hidden spellbooks for that. Thanks a lot, my Warrior class will be changed a lot with this system.

In my examples I'm using for spells with cast/channel-time a channel ability with a very high Follow through time, and let JCast handles the real cast duration.
Ok so that means I can use most (if not all) of my map's skills based off from Blizz's 'Channel' ability? I think I will be able now to get to work my bugged abilities :D

PS: Nice map you were making, it's a shame that you had to abandon the project if I'm not wrong. Pretty nice development stuff you have in there.
 
Level 5
Joined
Jun 2, 2004
Messages
19
Ok so that means I can use most (if not all) of my map's skills based off from Blizz's 'Channel' ability? I think I will be able now to get to work my bugged abilities :D
That's what I do in NoK.

PS: Nice map you were making, it's a shame that you had to abandon the project if I'm not wrong. Pretty nice development stuff you have in there.
I'm not really abandoning it... it's a too long standing project to be trashed so fast, so I restarted it's development this week end ;)
'Gonna release an alpha version soon !
 
Level 6
Joined
Feb 12, 2008
Messages
207
Ok but the thing is that I have to make the spell full triggered using channel, is there any way to do a WindWalk ability for example? I mean, it could be possible but maybe in the case of the WW the base ability would be far easy to be used. I dont know if this is a question or me wanting to answer it but thats what I think I'll do. Just use 'ANcl' (Channel) on abilities that I think would need it.
 
Level 5
Joined
Jun 2, 2004
Messages
19
Ok but the thing is that I have to make the spell full triggered using channel, is there any way to do a WindWalk ability for example? I mean, it could be possible but maybe in the case of the WW the base ability would be far easy to be used. I dont know if this is a question or me wanting to answer it but thats what I think I'll do. Just use 'ANcl' (Channel) on abilities that I think would need it.
Making a triggered WW ability would be easy to do, but the question is: do you need special features that JCast allows for this ability ?
I guess not too :)

Besides, this system doesn't support order-based custom abilities (like triggered spells based on footman's Defense), so you might need to use regular triggers occasionally.
 
Level 5
Joined
Jun 2, 2004
Messages
19
New version!

Changelogs:
- Fixed a bug making uninterruptable spells to be interrupted under certain conditions.
- Renamed OnBegin method (to OnPreCast)
- Added a new event method OnBegin (see documentation for more details)
- Fixed double-free with channeled spells

This one should be the final version, let me know if you want some changes before I submit it as resource :)

(first post updated)
 
Last edited:
Status
Not open for further replies.
Top