- Joined
- Mar 29, 2016
- Messages
- 688
The motivation behind this library is to simplify spell cloning.
But what is spell cloning, you ask? Well the concept is explained in the rationale portion of this tutorial:
Efficient Spell Cloning
The method discussed in that tutorial however is outdated and it has its certain limitations. This library
utilizes a better method and it also provides sufficient abstractions so that the spell developer can easily
carry on his work without having to understand how the whole method works. Just a couple of module
and function usage and following a certain template then he can proceed to the usual spellmaking process.
SpellCloner Library
Core Spell Module
(Should contain all spell mechanics)
Note that this is also just an example, you can change/remove the unnecessary variables that this template might contain.
Configuration Module
(Local configurations: you can divide these into multiple libraries if preferable)
But what is spell cloning, you ask? Well the concept is explained in the rationale portion of this tutorial:
Efficient Spell Cloning
The method discussed in that tutorial however is outdated and it has its certain limitations. This library
utilizes a better method and it also provides sufficient abstractions so that the spell developer can easily
carry on his work without having to understand how the whole method works. Just a couple of module
and function usage and following a certain template then he can proceed to the usual spellmaking process.
SpellCloner Library
JASS:
library SpellCloner /* v2.2.0 https://www.hiveworkshop.com/threads/324157/
*/uses /*
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/Table /* https://www.hiveworkshop.com/threads/188084/
*///! novjass
/*
CREDITS:
- AGD (Author)
- JAKEZINC (Feedbacks and suggestions, which helped bring the system into its current form)
*/
|=====|
| API |
|=====|
readonly static SpellCloner.configuredInstance/*
- Use this variable inside the configuration function to refer to the spell
instance being configured
*/module SpellClonerHeader/*
- Implement this module at the top of your spell struct
*/static method hasActivationAbility takes integer abilId returns boolean/*
*/static method hasConfiguration takes integer configStructId returns boolean/*
- Only call this from inside a spell event handler (generic or specific)
- Could be useful for especially in generic handlers to see if the casted
ability activates a certain type of spell, as cloned spell can have many
activation abilities
*/method initSpellConfiguration takes integer abilId returns integer/*
- Call this method at the top of you onSpellStart() method to initialize the
correct local configuration of your spell instance based on the activation
ability id
- Returns the struct type id of the struct containing the configuration
*/method loadSpellConfiguration takes integer configStructId returns nothing/*
- Call this method with the value returned by initSpellConfiguration() as the
parameter
- Like initSpellConfiguration(), loads the correct local configuration of the
spell, but based on the typeid of the configuration struct
*/module SpellClonerFooter/*
- Implement this module at the bottom of your spell struct, below your SpellEvent implementation
*/static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype/*
- Creates a new local configuration instance for the spell (Return value is obsolete)
*/module SpellCloner/*
- A supplement to using both SpellClonerHeader and SpellClonerFotter. Implement this
module at the bottom of your spell struct, no need to implement the SpellEvent module
*/interface method onClonedSpellStart takes nothing returns thistype/*
*/interface method onClonedSpellPeriodic takes nothing returns boolean/*
*/interface method onClonedSpellEnd takes nothing returns nothing/*
- Supplement to the onSpellStart(), onSpellPeriodic(), and onSpellEnd() methods from
SpellEvent module
- All these interface methods follow the same rules as their SpellEvent interface methods
counterpart
- You no longer need to call this.initSpellConfiguration(ABIL_ID) on onClonedSpellStart()
to run your configuration function, this is already done internally by the system.
*/static method hasActivationAbility takes integer abilId returns boolean/*
*/static method hasConfiguration takes integer configStructId returns boolean/*
- Already defined above (see SpellClonerHeader module)
*/static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype/*
- Creates a new local configuration instance for the spell (Return value is obsolete)
*///! endnovjass
globals
private trigger evaluator = CreateTrigger()
private integer array eventIndex
private integer configuredSpellInstance = 0
private TableArray table
private TableArray configStructNode
endglobals
private module Init
private static method onInit takes nothing returns nothing
set table = TableArray[JASS_MAX_ARRAY_SIZE]
set configStructNode = TableArray[JASS_MAX_ARRAY_SIZE]
set eventIndex[EVENT_SPELL_CAST] = 1
set eventIndex[EVENT_SPELL_CHANNEL] = 2
set eventIndex[EVENT_SPELL_EFFECT] = 3
set eventIndex[EVENT_SPELL_ENDCAST] = 4
set eventIndex[EVENT_SPELL_FINISH] = 5
endmethod
endmodule
struct SpellCloner extends array
static method operator configuredInstance takes nothing returns thistype
return configuredSpellInstance
endmethod
implement Init
endstruct
private struct SpellConfigList extends array
thistype current
readonly thistype prev
readonly thistype next
readonly integer structId
readonly boolexpr configExpr
private static thistype node = 0
method evaluateExpr takes integer spellInstance returns nothing
local integer prevInstance = configuredSpellInstance
set configuredSpellInstance = spellInstance
call TriggerAddCondition(evaluator, this.configExpr)
call TriggerEvaluate(evaluator)
call TriggerClearConditions(evaluator)
set configuredSpellInstance = prevInstance
endmethod
method insert takes integer id, boolexpr expr returns thistype
local thistype next = this.next
set node = node + 1
set node.structId = id
set node.configExpr = expr
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
return node
endmethod
static method create takes nothing returns thistype
set node = node + 1
set node.prev = node
set node.next = node
set node.current = node
return node
endmethod
endstruct
public function HasActivationAbility takes integer spellStructId, integer abilId returns boolean
if GetEventSpellEventType() == 0 then
return table[spellStructId*5 + 1].has(-abilId)
endif
return table[spellStructId*5 + eventIndex[GetEventSpellEventType()]].has(abilId)
endfunction
public function HasConfiguration takes integer spellStructId, integer configStructId returns boolean
if GetEventSpellEventType() == 0 then
return configStructNode[spellStructId*5 + 1].has(-configStructId)
endif
return configStructNode[spellStructId*5 + eventIndex[GetEventSpellEventType()]].has(configStructId)
endfunction
public function InitSpellConfiguration takes integer spellStructId, integer spellInstance, integer abilId returns integer
local integer configStructId
local SpellConfigList configList
if GetEventSpellEventType() == 0 then
set configList = table[spellStructId*5 + 1][-abilId]
else
set configList = table[spellStructId*5 + eventIndex[GetEventSpellEventType()]][abilId]
endif
set configList.current = configList.current.next
set configStructId = configList.current.structId
call configList.current.evaluateExpr(spellInstance)
if configList.current.next == configList then
set configList.current = configList
endif
return configStructId
endfunction
public function LoadSpellConfiguration takes integer spellStructId, integer spellInstance, integer configStructId returns nothing
if GetEventSpellEventType() == 0 then
call SpellConfigList(configStructNode[spellStructId*5 + 1][-configStructId]).evaluateExpr(spellInstance)
else
call SpellConfigList(configStructNode[spellStructId*5 + eventIndex[GetEventSpellEventType()]][configStructId]).evaluateExpr(spellInstance)
endif
endfunction
public function CloneSpell takes integer spellStructId, integer configStructId, integer abilId, integer eventType, code configFunc returns nothing
local SpellConfigList configList
local integer eventId = 0x10
local integer key
if eventType == 0 then
set key = spellStructId*5 + 1
if configStructNode[key][-configStructId] == 0 then
set configList = table[key][-abilId]
if configList == 0 then
set configList = SpellConfigList.create()
set table[key][-abilId] = configList
endif
set configStructNode[key][-configStructId] = configList.prev.insert(configStructId, Filter(configFunc))
endif
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
set key = spellStructId*5 + eventIndex[eventId]
if configStructNode[key][configStructId] == 0 then
set configList = table[key][abilId]
if configList == 0 then
set configList = SpellConfigList.create()
set table[key][abilId] = configList
endif
set configStructNode[key][configStructId] = configList.prev.insert(configStructId, Filter(configFunc))
endif
endif
set eventId = eventId/2
endloop
endif
endfunction
private module SpellClonerCommonHeader
static constant integer SPELL_ABILITY_ID = 0
static constant integer SPELL_EVENT_TYPE = 0
static method hasActivationAbility takes integer abilId returns boolean
return HasActivationAbility(thistype.typeid, abilId)
endmethod
static method hasConfiguration takes integer configStructId returns boolean
return HasConfiguration(thistype.typeid, configStructId)
endmethod
endmodule
module SpellClonerHeader
implement SpellClonerCommonHeader
method initSpellConfiguration takes integer abilId returns integer
return InitSpellConfiguration(thistype.typeid, this, abilId)
endmethod
method loadSpellConfiguration takes integer configStructId returns nothing
call LoadSpellConfiguration(thistype.typeid, this, configStructId)
endmethod
endmodule
module SpellClonerFooter
static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype
call CloneSpell(thistype.typeid, configStructId, abilId, spellEventType, configurationFunc)
call registerSpellEvent(abilId, spellEventType)
return 0
endmethod
endmodule
module SpellCloner
implement SpellClonerCommonHeader
method onSpellStart takes nothing returns thistype
local integer configStructId = InitSpellConfiguration(thistype.typeid, this, GetEventSpellAbilityId())
local thistype node = this.onClonedSpellStart()
if node > 0 then
if node != this then
call SpellConfigList(configStructNode[thistype.typeid][configStructId]).evaluateExpr(node)
endif
return node
endif
return 0
endmethod
method onSpellPeriodic takes nothing returns boolean
return this.onClonedSpellPeriodic()
endmethod
method onSpellEnd takes nothing returns nothing
call this.onClonedSpellEnd()
endmethod
implement SpellEvent
implement SpellClonerFooter
endmodule
endlibrary
Core Spell Module
(Should contain all spell mechanics)
Note that this is also just an example, you can change/remove the unnecessary variables that this template might contain.
JASS:
library <SpellName> /*
*/uses /*
*/SpellEvent /*
*/SpellCloner /*
*/
/*****************************************************************
* GLOBAL SPELL CONFIGURATION *
*****************************************************************/
private module GlobalSpellConfiguration
static constant real SPELL_PERIOD = 1.00/32.00
static constant real SFX_DEATH_TIME = 1.50
...
endmodule
private function TargetFilter takes unit target, unit caster returns boolean
return UnitAlive(target)
endfunction
...
/*****************************************************************
* END OF GLOBAL SPELL CONFIGURATION *
*****************************************************************/
/*========================= SPELL CODE =========================*/
native UnitAlive takes unit u returns boolean
public struct <SpellName> extends array
implement GlobalSpellConfiguration
implement SpellClonerHeader
string staticSfxModel
string staticSfxAttachPoint
real spellDuration
...
private method onSpellStart takes nothing returns thistype
call this.initSpellConfiguration(GetEventSpellAbilityId())
if <InvalidCastCondition> then
return 0
endif
...
return this
endmethod
private method onSpellPeriodic takes nothing returns boolean
...
set this.spellDuration = this.spellDuration - SPELL_PERIOD
return this.spellDuration > 0.00
endmethod
private method onSpellEnd takes nothing returns nothing
...
endmethod
implement SpellEvent
implement SpellClonerFooter
endstruct
/*
* This is the module that you will implement into the struct that you wish to
* contain the local spell configurations
*/
module <SpellName>Configuration
private static method configHandler takes nothing returns nothing
local <SpellName> node = SpellCloner.configuredInstance
set node.staticSfxModel = STATIC_SFX_MODEL
set node.staticSfxAttachPoint = STATIC_SFX_ATTACHPOINT
...
set node.spellDuration = spellDuration(currentSpellInstance.level)
...
endmethod
private static method onInit takes nothing returns nothing
call <SpellName>.create(thistype.typeid, SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.configHandler)
endmethod
endmodule
endlibrary
Configuration Module
(Local configurations: you can divide these into multiple libraries if preferable)
JASS:
library <SpellName>ConfigurationModule uses <SpellName> SpellEvent
public struct Configuration1 extends array
static constant integer SPELL_ABILITY_ID = 'A000'
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
static constant string STATIC_SFX_MODEL = ""
static constant string STATIC_SFX_ATTACHPOINT = "origin"
...
static method spellDuration takes integer level returns real
return 10.00 + 0.00*level
endmethod
...
implement <SpellName>Configuration
endstruct
public struct Configuration2 extends array
/*
* Activation ability ids can even be similar, in which case a single cast would
* activate two (or more) 'spells'
*/
static constant integer SPELL_ABILITY_ID = 'A001'
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
static constant string STATIC_SFX_MODEL = ""
static constant string STATIC_SFX_ATTACHPOINT = "origin"
...
static method spellDuration takes integer level returns real
return 5.00 + 1.00*level
endmethod
...
implement <SpellName>Configuration
endstruct
...
endlibrary
v2.2.0
- Added features for checking if a cloneable-spell struct has a certain configuration (check can be made by an ability ID or by a configuration struct type ID)
v2.0.0
- API changes
- Added 'SpellCloner' module
- Made Table a mandatory dependency
v1.1.0
- Improved the design
v1.0.0
- Initial release
- Added features for checking if a cloneable-spell struct has a certain configuration (check can be made by an ability ID or by a configuration struct type ID)
v2.0.0
- API changes
- Added 'SpellCloner' module
- Made Table a mandatory dependency
v1.1.0
- Improved the design
v1.0.0
- Initial release
Last edited: