wc3campaigns
WC3C Homepage - www.wc3c.netUser Control Panel (Requires Log-In)Engage in discussions with other users and join contests in the WC3C forums!Read one of our many tutorials, ranging in difficulty from beginner to advanced!Show off your artistic talents in the WC3C Gallery!Download quality models, textures, spells (vJASS/JASS), systems, and scripts!Download maps that have passed through our rigorous approval process!

Go Back   Wc3C.net > Tutorials > JASS/AI scripts tutorials
User Name
Password
Register Rules Get Hosted! Chat Pastebin FAQ and Rules Members List Calendar



Reply
 
Thread Tools Search this Thread
Old 06-15-2008, 10:08 PM   #1
moyack
Evil Emoticon
 
moyack's Avatar


Respected User
Project Leader: PoC
 
Join Date: Jan 2006
Posts: 3,279

Submissions (17)

moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)

AI Tournament #2 - 2nd PlaceHero Contest - Second place

Send a message via MSN to moyack
Idea How to develop spells with effects over time

How to Develop Spells with Effects over Time

By moyack - 2008


Introduction.

Ok, the purpose of this tutorial is to give a general idea about how to make spells with effects over time, with a focus in the usage of some of the new features of vJASS and as addition I will treat with some aspect related to spell stackability and optimization according to the situations. In order to follow it you MUST have some experience in JASS and hopefully vJASS.



Basic Concepts.

What are scopes and libraries?? well, this is not something easy to explain but you can understand it by seeing the examples of the JassHelper manual. Libraries - Scopes

What's a struct?? A struct is a way to "pack" several variables and functions, so they can be called as one object. This is the concept that we're going to work here more deeply.

With the new improvements made by Vexorian, structs can be set in several ways, but right now I'll start with something very easy, showing little by little more features that a struct can do.

Because an example is the best way to see how this technique works, I'll do it with a spell which deals damage over time to a unit. You can extrapolate this to other situations.



Starting the Spell Development.

Ok, we know how to start. Let's create a custom ability (in this case base it on slow), set the fields that you consider more appropriate, create a custom buff for that spell and assign it to that ability in the buff field. After this, create a new trigger, in GUI set the event to "Unit - Generic Unit Event" and then select "A unit Starts the effect of an Ability", in Conditions select an "Ability Comparison" select "ability being cast equal to <your custom ability name>". Then Select in the Edit menu "Convert to custom script" and the funny thing will start.

Ok, we get this:
Collapse JASS:
function Trig_Rabid_Bite_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A000' ) ) then
        return false
    endif
    return true
endfunction

function Trig_Rabid_Bite_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Trig_Rabid_Bite_Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Trig_Rabid_Bite_Actions )
endfunction
And we'll convert into this:
Collapse JASS:
scope RabidBite

globals
    private constant integer SpellID = 'A000' //Spell Rawcode.
endglobals

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction

endscope

As we can see, we've added the scope, we have shorten the functions names and we've started to apply good programming practice by setting the constant variables which will allow to a spell user customize the code (mandatory if you want to comply with the JESP standard).

With this we have part of the spell skeleton, now we need to define what information must be managed by the spell. In this specific case the ability needs to damage an enemy, so we'll need to store the unit who cast the spell and the target, therefore this preliminary info will be part of our spell struct.

Collapse Struct Version 1.0 Beta:
private struct Data
    unit caster
    unit target
endstruct

Things to notice: Why private? because we don't want that other functions but the ones of the trigger can access to that struct, if you need that other functions can call that struct, then you should make it with a more appropriate name and remove the private keyword. You've noticed that I've used the name data, in this case there's no problem because this struct only has sence in this scope, so we can set them with short or very stardard names and JassHelper will do the ugly job of differentiation for us :P

Now the spell skeleton has grown and it could look in this way:

Collapse JASS:
scope RabidBite

globals
    private constant integer SpellID = 'A000' //Spell Rawcode.
endglobals

private struct Data
    unit caster
    unit target
endstruct

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
    local Data D = Data.create()
    set D.caster = GetTriggerUnit()
    set D.target = GetSpellTargetUnit()
    
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction

endscope

Let's check the Actions function. We are calling a kind of function called Data.create() those special functions will be called from now on methods, because they're functions which only have sense inside the struct, and only can be called or used by making reference to the struct of which they belong. Now we're setting the variable components of this struct after it's created. Until now it's ok, but we can do the things better, in fact we can make this in only one line by defining a custom create method. So let's do it:

Collapse Struct V 1.01 RC1:
private struct Data
    unit caster
    unit target

    static method create takes unit c, unit t returns Data
        local Data D = Data.allocate() // this method is private, therefore it ONLY has sense and only can be used inside the struct.
        set D.caster = c
        set D.target = t
        return D
    endmethod
endstruct

With this new struct, we can reduce the number of lines in the Action function to simply one line. There are other advantages of doing this, one important is to make it easy the debug process because you know in which methods you set the variables, where you destroy them, etc.

Static keyword??? what the hell is that??

Probably you noticed the static keyword, this word is used when we need to make a method or a component global (or independent) of the structs created. If we assign the static keyword to a component of the struct, it will behave exactly as a global variable, the difference will be in the way it can be called. In the case of methods, a static method does not depends of the struct variable, it's like a normal function.

Collapse JASS:
globals
    private group G
endglobals

function F takes unit u returns nothing
    call GroupAddunit(G, u)
endfunction

function Bla takes nothing returns nothing
    set G = CreateGroup()
    call F(GetTriggerUnit())
endfunction
Collapse JASS:
struct Test
    static group G

    static method F takes unit u returns nothing
        call GroupAddunit(Test.G, u)
    endmethod
endstruct

function Bla takes nothing returns nothing
    set Test.G = CreateGroup()
    call Test.F(GetTriggerUnit())
endfunction

Both codes are equivalent. So the first question to ask is: what is more convenient? My answer is: it depends. If you need that your functions could get access to any private method or component of the struct, then the static methods and component are the way to go. I personally use this notation so I can separate the variables which can be modified by the user with the ones that shouldn't be touched. This notation is so powerful that if we want we can make totally this spell inside one struct, converting all the functions into static methods and all the globals into static components.



Collapse JASS:
scope RabidBite

globals
    private constant integer SpellID = 'A000' //Spell Rawcode.
endglobals

private struct Data
    unit caster
    unit target

    static method create takes unit c, unit t returns Data // This method now will carry the responsibility of setting the variable components of the struct
        local Data D = Data.allocate() 
        set D.caster = c
        set D.target = t
        return D
    endmethod
endstruct

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
    local Data D = Data.create(GetTriggerUnit(), GetSpellTargetUnit()) // we do all our creating process in only one line :)
    
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction

endscope

With this the only pending thing to do is to do something with this stuff and add the timed effect. But before everything, let's remember what is the problem now: we need to create a periodic timer which will execute a function periodically, and this function must be able to get the information properly if the spell is casted by several units (AKA ensure the MUI of this spell). In order to achieve this, there are 2 ways that we'll discuss in detail.



Approach N° 1: Using timers and storage system to pass the struct data.

This procedure implies the usage of a timer recycler like TimerUtils and a storage system, or simply a storage system with timer recycler included. For this example I'll do this spell dependent of TimerUtils now that this system allows us recycle and attach data to timers. Note: This procedure can be adapted perfectly to other storage systems like ABC, HAIL, HSAS, Cool Coll.

Ok, let's start adding functionality to this baby. First let's add to the Actions function some stuff:

Collapse JASS:
scope RabidBite

globals
    private constant integer SpellID = 'A000' //Spell Rawcode.
    private constant real    dt      = 0.1 //timer period
endglobals

private struct Data
    unit caster
    unit target

    static method create takes unit c, unit t returns Data
        local Data D = Data.allocate() 
        set D.caster = c
        set D.target = t
        return D
    endmethod
endstruct

private function Loop takes nothing returns nothing
    //Our periodic stuff
endfunction

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
    local Data D = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
    local timer t = NewTimer() //Creates a new timer...
    call SetTimerData(t, integer(D)) //Attach the data to the timer
    call TimerStart(t, dt, true, function Loop) // Start the created timer so it can periodically run the Loop function
    set t = null // Set the local timer variable to null, we don't need ti more in this function
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction

endscope

Now let's put some work to the Loop function. We need that the effect in the target unit keeps on it until the buff vanishes or get removed by external sources (dispelling spells for instance), so the Looping function basically will do a check if the buff is on the unit, if so, it will deal the damage to that unit. To do that, them we need to add more variables to this spell, like the buff rawcode and the damage per second. Check the highlighted text in the next code:

Collapse JASS:
scope RabidBite

globals
    private constant integer SpellID = 'A000' //Spell Rawcode.
    private constant integer BuffID = 'B000' //Buff Rawcode
    private constant real    dt      = 0.1 //timer period
endglobals

private constant function Damage takes integer level returns real
    return 15. + 7. * (level - 1) //Damage proportional to the spell level so it complies with the JESP standard
endfunction

private struct Data
    unit caster
    unit target

    static method create takes unit c, unit t returns Data
        local Data D = Data.allocate() 
        set D.caster = c
        set D.target = t
        return D
    endmethod
endstruct

private function Loop takes nothing returns nothing
    local timer t = GetExpiredTimer() // Gets the timer...
    local Data D = Data(GetTimerData(t)) // Gets the struct attached to the timer...
    local real Dam = Damage(GetUnitAbilityLevel(D.caster, SpellID)) // Gets the damage according to the level of the spell...
    if GetUnitAbilityLevel(D.target, BuffID) > 0 then // Checks if the buff is on the target unit...
        //If so, it will deal damage to the unit...
        call UnitDamageTarget(D.caster, D.target, Dam * dt, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
    else // There's no buff on the unit, so....
        call D.destroy() // Recycle the struct for a later use...
        call ReleaseTimer(t) // Release the timer, pausing it, and making it avaliable for a later use with other struct...
    endif
    set t = null
endfunction

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
    local Data D = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
    local timer t = NewTimer()
    call SetTimerData(t, integer(D))
    call TimerStart(t, dt, true, function Loop)
    set t = null
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction

endscope

As you can see, I'm doing like a template, I'm putting the configuration stuff in the first lines, including the constant functions, then the struct and then the looping function and finally the trigger functions. Other thing to notice is that the Damage is actually Damage per second, and therefore all the damage must be multiplied by the period of the timer in order to get an accurate value.

With this changes, we have now this spell working. But (there's always a but... :P) what would happen if this spell uses a projectile (not instant, like one based on Acid bomb or Storm bolt)?? well, it simply won't start because it's non instant and the EVENT_PLAYER_UNIT_SPELL_EFFECT starts before the buff is set on the target unit, so the only if that will activate will be the one that destroys the recently created struct (buahhh!!! snif!!!). So in order to fix that, and ensure that those kinds of spells work with this situation we need to make some adjustments to our struct. Please check the highlighted code to see the new stuff.

Collapse JASS:
scope RabidBite

globals
    private constant integer SpellID = 'A000' //Spell Rawcode.
    private constant integer BuffID = 'B000' //Buff Rawcode
    private constant real    dt      = 0.1 //timer period
endglobals

private constant function Damage takes integer level returns real
    return 15. + 7. * (level - 1)
endfunction

private struct Data
    unit caster
    unit target
    boolean hasbuff = false //used to check if the buff is on the target unit...

    static method create takes unit c, unit t returns Data
        local Data D = Data.allocate() 
        set D.caster = c
        set D.target = t
        return D
    endmethod
    
    method onDestroy takes nothing returns nothing
        set .hasbuff = false // this custom method will set the hasbuff variable to false, so it can start properly when the spell is casted again...
    endmethod
endstruct

private function Loop takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Data D = Data(GetTimerData(t))
    local real Dam = Damage(GetUnitAbilityLevel(D.caster, SpellID))
    // This conditional detects if the buff is on the target unit...
    if not D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
        set D.hasbuff = true
    endif
    // If the buff is on the target unit, then do the effect
    if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
        call UnitDamageTarget(D.caster, D.target, Dam * dt, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
    endif
    // If the buff is not present anymore, then stop the spell
    if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) < 1 then
        call D.destroy()
        call ReleaseTimer(t)
    endif
    set t = null
endfunction

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
    local Data D = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
    local timer t = NewTimer()
    call SetTimerData(t, integer(D))
    call TimerStart(t, dt, true, function Loop)
    set t = null
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction

endscope

Wow!!! now this is becoming more complex :). Things to notice:
  • In the struct you see that the hasbuff component is set to false, that's used when we need to set a value to the new struct created. This set is done when we call the allocate() private method and therefore is advisable to use it only with non handle variable types (booleans, reals, integers, strings). With handles, I suggest to set and manage them with create and destroy methods in order to control their respective creation and destruction.
  • Other thing is that we added to our struct a custom onDestroy method, this method will be executed when we call D.destroy() (This function can't have any arguments and it will generate syntax error if you set arguments to it).
  • The Loop function now does 3 verifications: Checks if the buff is present on the target unit, Checks if the hasbuff flag and the buff are present so it can do the effect and lastly it checks if the buff is gone, stopping the spell effect. With this this template can manage instant and not instant buff spells.


Stackable and not stackable effects.

Yay!!! our spell is working wonderfully.... hmmmm... actually not, there's one "problem" more to solve. What would happen if this spell is casted and 1 second later other unit cast this spell in the same unit?? well, the unit will be damaged by 2 and the worst thing is the buff duration has been extended, in other words: a stacked spell.

Sometimes the stackability is desirable and sometimes it doesn't but in the desirable situation we should balance this effect at least by detecting when the first cast should end, so one solution is to give to the spell the ability to detect if the duration has been reached, independently of the buff presence in the target unit. Here's the modification of this spell so it stacks but takes into account the duration of the spell but not necessarily the buff duration. (Note: There are other ways to balance a stackable spell, according of the effect type, this example is one way that works with the example)

Expand Rabid Bite stackable with duration control:

Now let's analyze the possibility of making this spell not stackable. In order to do this, the spell should deal the same damage over time and it should be able to detect if the target unit has the buff, and if it's the case, then update the respective struct with the new caster in order to ensure in case of the death of the unit, the bounty and/or credits for death get assigned properly to the last caster.

Here I'm going to use the usage of some static elements in order to allow the reader to check how they can be used. Let's see how the code should look:

Expand Rabid Bite with non stacktable effect:

Things to notice:
  • The Duration function and the counter parameter in the struct have been removed now that this spell is not stackable anymore.
  • We've added 2 static components: group IsBitten and integer index, as we said before, they behave as global variables, dependent of the Data struct. The purpose of the group variable is to store all the units affected by the spell and the integer stores the struct array size.
  • We have a new method: onInit. Every method created with this name is executed at map initialization. In this case we added it in order to set up the IsBitten group variable.
  • We added a custom method: SetCaster. This one will search through all the active structs and if it finds the respective target unit, then it will update the caster which deals the passive damage.
  • The Actions function has been changed so the spell can determine if it has to create or updated an existing struct.

this keyword??? integer as a function?? what's happening??

The keyword this is used ONLY in non static methods to make reference to the struct that it's applying it. This cannot be used in static methods because it has no sense in them, so don't try it!!

Notation: set this.caster = u set .caster = u
Both notations are perfectly equivalents.

the integer(Struct) function is used to return the index of the struct. It's possible to get this value directly but it's advisable to use this notation in order to ensure compatibility with later versions of JassHelper.

local integer i = integer(Struct) = local integer i = Struct



Very well, now our spell is stable and can work in the way we needed. Now let's see the second approach.



Approach N° 2: using one single timer for all the units casting the spell.

I personally love this approach, because it allows you to reduce (for not saying avoid) the usage of storage systems. This approach is based in the following precept: If the time is the same for all the units, then one timer should be able to review and control all the units affected by one spell and not one timer per spell casted as we did before.

So the first step has been defined: We can't start a new timer every time we cast the spell, instead, we need to start one timer at map init and putting it to run a code periodically so it can check units affected.

Let's do the modifications based on the non stackable version of Rabid Bite:

Collapse JASS:
scope RabidBite

globals
    private constant integer SpellID = 'A000' //Spell Rawcode.
    private constant integer BuffID = 'B000' //Buff Rawcode
    private constant real    dt      = 0.1 //timer period
endglobals

private constant function Damage takes integer level returns real
    return 10. + 7. * (level - 1)
endfunction

private struct Data
    static group IsBitten
    static integer index = 0

    unit caster
    unit target
    boolean hasbuff = false
    
    private static method onInit takes nothing returns nothing
        set Data.IsBitten = CreateGroup()
    endmethod

    static method create takes unit c, unit t returns Data
        local Data D = Data.allocate() 
        set D.caster = c
        set D.target = t
        call GroupAddUnit(Data.IsBitten, t)
        if integer(D) > Data.index then
            set Data.index = integer(D)
        endif
        return D
    endmethod
    
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit(Data.IsBitten, .target)
        set .hasbuff = false
        if integer(this) == Data.index then
            set Data.index = Data.index - 1
        endif
    endmethod
    
    static method SetCaster takes unit caster, unit target returns nothing
        local integer i = 0
        local Data D
        loop
            exitwhen i > Data.index
            set D = Data(i)
            if D.target == target and D.hasbuff then
                set D.caster = caster
                return
            endif
            set i = i + 1
        endloop
    endmethod
endstruct

private function Loop takes nothing returns nothing
    local integer i = 0 //Used to make the loop through all the struct array
    local Data D 
    local real Dam
    loop // Looping through the struct array...
        exitwhen i > Data.index
        set D = Data(i)
        if not D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
            set D.hasbuff = true
        endif
        if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
            set Dam = Damage(GetUnitAbilityLevel(D.caster, SpellID))
            call UnitDamageTarget(D.caster, D.target, Dam * dt, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
        endif
        if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) < 1 then
            call D.destroy()
        endif
        set i = i + 1
    endloop
endfunction

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
    // The Actions function just determines if it has to create or update an active struct...
    if not IsUnitInGroup(GetSpellTargetUnit(), Data.IsBitten) then
        call Data.create(GetTriggerUnit(), GetSpellTargetUnit())
    else
        call Data.SetCaster(GetTriggerUnit(), GetSpellTargetUnit())
    endif
endfunction

//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
    call TimerStart(CreateTimer(), dt, true, function Loop) //Start the timer at map init...
endfunction

endscope

As you can see, too few changes were required to get this improvement. In this case we removed the usage of CSSafety and HandleVars, which is in my opinion a big improvement. Other thing to notice is that the timer never stops, if there's no units, the loop won't run so this function practically won't put any stress in the game.


Stackacle spell using the second approach

Now... if we want to make it stackable, we just have to remove the SetCaster method, add the duration function, do the modifications in the conditionals in the Loop function and modify the Actions function to get the desired effect. Check the highlighted text and appreciate how the code changed:

Expand Rabid Bite stackable, made with the Approach N° 2:



When is better one approach than other??

Unconsciously, this example has evolved from the approach 1 to the approach 2, but it's important to point out that it doesn't mean that one approach is worst than the other, actually it depends in how is used and with which frequency.

For example, if we have an AoS sytle map, which is generally hero based, the first approach is more convenient because you don't know if the hero with the custom spells will be summoned and therefore the chances that this spell can be casted are less. In the other hand, with spells that can be casted by several hundreds of units (like in custom melee games of footies) the second approach is more convenient, because instead of having several timers controlling a spell (one timer per unit, and imagine a footies game with full house and the footmen casting those custom spells), this will be a considerable memory eater. With one timer checking all the units, we can optimize it pretty fine.




Final Words.

Well, what we did in this tutorial was creating a spell, modify it according to the required circumstances and at the end we ended doing something very interesting: a template, a very nice template. That's something good, because it allows us develop several kind of spells with small modifications of one pattern. The template is basically in this way:

Collapse Jass spell template for effect over time:
Scope My spell
// Customization section
globals
    //Constant variables...
endglobals

< Constant functions... >

// End customization section

private struct Data
     // Struct components
endstruct

< Spell functions required for the looping function>

< Looping function >

< Triggers functions >

endscope

Well, I think this is all. I hope this tutorial helps you to improve your the spell development. Any questions, typos, mistakes or suggestions about how to make this tutorial better can be post here. Happy spell making :)

==========================================================================

Addition: Looping throught units.

As a part of development of spells controlled with a single timer, we need to iterate through an array of data. this can be done in several way, ones are less efficients than others. If we have units as a part of the data struct, we can use them to develop a very safe way of iteration using the ForGroup command. An example can help us right now.

Collapse Spell that uses second approach and uses units as reference...:
// ================================================================= \\
// Custom Immolation modifier spell, so it targets destructables too \\
// Request by Abriko, by moyack. 2008.                               \\
// ================================================================= \\
// Requires Table to work...                                         \\
// ================================================================= \\
scope Immolation2 initializer init

// Configuration Part...
globals
    private constant integer SpellID = 'AEi2' //Spell based on Immolation
    private constant integer BuffID = 'BEim' //Immolation buff, please base it on the immolation buff.
    private constant real dt = 1.
endglobals

private constant function DamageRate takes integer level returns real
    return 10. + 5. * (level - 1)
endfunction

private constant function AOE takes integer level returns real
    return 160.
endfunction
// End configuration Part...

private struct data
    static HandleTable T
    static group G
    static rect R
    static unit U

    unit c
    boolean flag = false
    
    static method Start takes unit c returns nothing
        local data D = data.allocate()
        set D.c = c
        call GroupAddUnit(data.G, c)
        set data.T[c] = integer(D)
    endmethod
    
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit(data.G, .c)
        call data.T.flush(.c)
    endmethod
endstruct

private function GetLivingDestructables takes nothing returns boolean
    return GetDestructableLife(GetFilterDestructable()) > 0.405
endfunction 

private function BurnDestructables takes nothing returns nothing
    local destructable d = GetEnumDestructable()
    call DestroyEffect(AddSpecialEffect(GetAbilityEffectById(BuffID, EFFECT_TYPE_SPECIAL, 0), GetWidgetX(d), GetWidgetY(d)))
    call UnitDamageTarget(data.U, d, DamageRate(GetUnitAbilityLevel(data.U, SpellID)) * dt, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
    set d = null
endfunction

private function CheckStatus takes nothing returns nothing
    local unit u = GetEnumUnit()
    local data D = data( data.T[u] )
    if not D.flag and GetUnitAbilityLevel(u, BuffID) > 0 then
        set D.flag = true
    endif
    if D.flag and GetUnitAbilityLevel(u, BuffID) > 0 then
        call SetRect(data.R, GetUnitX(u) - AOE(GetUnitAbilityLevel(u, SpellID)), GetUnitY(u) - AOE(GetUnitAbilityLevel(u, SpellID)), GetUnitX(u) + AOE(GetUnitAbilityLevel(u, SpellID)), GetUnitY(u) + AOE(GetUnitAbilityLevel(u, SpellID))) 
        set data.U = u
        call EnumDestructablesInRect(data.R, Condition(function GetLivingDestructables), function BurnDestructables)
    endif
    if D.flag and GetUnitAbilityLevel(u, BuffID) < 1 then
        call D.destroy()
    endif
    set u = null
endfunction

private function Loop takes nothing returns nothing
    call ForGroup(data.G, function CheckStatus)
endfunction

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction

private function Actions takes nothing returns nothing
    if not IsUnitInGroup(GetTriggerUnit(), data.G) then
        call data.Start(GetTriggerUnit())
    endif
endfunction

//===========================================================================
private function init takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Conditions ) )
    call TriggerAddAction( t, function Actions )
    set t = null
    set data.T = HandleTable.create()
    set data.G = CreateGroup()
    set data.R = Rect(0,0,1,1)
    call TimerStart(CreateTimer(), dt, true, function Loop)
endfunction

endscope

What I'm doing in this spell:
  • I use a group to store all the units that cast the spell. Those units will be my reference or index key.
  • I use a HandleTable to store the struct data ID related to the unit.
  • To iterate though all the active struct data, I call the ForGroup command and in the enumerating function I retrieve the data struct related to the unit with the command data.

The advantages of this iterating process:
  • You can have total control of the structs.
  • Therefore, you'll have control over all the struct in a safe way.
  • Data search is practically O(1) thanks to the usage of table (gamecache search property)

Disadvantage:
  • Only can be used with handles which support grouper handles (units > groups, player > force)

This procedure is very useful in spells because almost in all the cases (for not saying all the cases) there's a unit involved in the spell process, and that unit can serve us as a index for the spell itself.

Last edited by moyack : 12-19-2008 at 12:26 PM.
moyack is offline   Reply With Quote
Sponsored Links - Login to hide this ad!
Old 06-16-2008, 09:47 AM   #2
Pyrogasm
Lackadaisically Absent.
 
Pyrogasm's Avatar


Respected User
 
Join Date: Sep 2006
Posts: 4,523

Submissions (9)

Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)Pyrogasm is a splendid one to behold (638)

Hero Contest - Fourth place

Send a message via ICQ to Pyrogasm Send a message via AIM to Pyrogasm Send a message via MSN to Pyrogasm Send a message via Yahoo to Pyrogasm
Default

I'd say that's darn near masterful, Moyack! Dunno what else to say... nothing glares out at me as being horribly wrong or anything.
__________________
Quote:
Originally posted by Rising_Dusk
Your spells are mostly ignored because they are not very cool so we aren't very excited to review/approve them, but you are incredibly persistent and won't give us an excuse to graveyard it. That is generally what results in a resource being ignored for a long time.

The Spell Request Thread — Done for, unless someone else wants to revive it...
It lasted a damn long time.

Please; Ask for Help Appropriately














Quote:
Originally posted by Kyrbi0
Huh. Almost makes me wish I had a girlfriend, to take advantage of today (wait, no, that's not what I meant... I mean, take advantage of the fact that it is international women's day... gah, never mind).
Quote:
Originally posted by Pyrogasm
Rome may not have been built in a day, but the Romans sure as hell didn't say "look at this great city we built guys!" when they had nothing more than a bit of stone and some cottages.
Pyrogasm is offline   Reply With Quote
Old 06-16-2008, 07:43 PM   #3
Rising_Dusk
Obscurity, the Art


Projects Director
Project Leader: OD
 
Join Date: Feb 2006
Posts: 9,729

Submissions (27)

Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)Rising_Dusk has a reputation beyond repute (1192)

Hero Contest #3 - 1st PlaceApproved Map: Desert of ExileApproved Map: Advent of the ZenithHero Contest #2 - 1st PlaceHero Contest - Third place>

Send a message via AIM to Rising_Dusk Send a message via MSN to Rising_Dusk
Default

Bravado, sir, I didn't read the whole thing in detail but it looks well-made and pretty awesome. The only thing I can think being a problem is the colored text, since some themes don't render it properly. You should just make it italicized or bold instead and skip that problem entirely.

Otherwise, great stuff. +sex
__________________
Rising_Dusk is offline   Reply With Quote
Old 06-16-2008, 08:41 PM   #4
moyack
Evil Emoticon
 
moyack's Avatar


Respected User
Project Leader: PoC
 
Join Date: Jan 2006
Posts: 3,279

Submissions (17)

moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)

AI Tournament #2 - 2nd PlaceHero Contest - Second place

Send a message via MSN to moyack
Default

Quote:
Originally Posted by Rising_Dusk
Bravado, sir, I didn't read the whole thing in detail but it looks well-made and pretty awesome. The only thing I can think being a problem is the colored text, since some themes don't render it properly. You should just make it italicized or bold instead and skip that problem entirely.

Otherwise, great stuff. +sex
Colored text fixed... Thanks for your comments :)

I'm waiting for Vex answer because I just knew that CSSafety had been updated. When I get this info, I'll update it.
moyack is offline   Reply With Quote
Old 06-17-2008, 06:41 AM   #5
chobibo
100% Genuine Retard!
 
chobibo's Avatar
 
Join Date: Jan 2007
Posts: 797

chobibo has a spectacular aura about (101)chobibo has a spectacular aura about (101)chobibo has a spectacular aura about (101)chobibo has a spectacular aura about (101)

Send a message via Yahoo to chobibo
Default

Very informative, it would prove useful for those people having trouble understanding how to use structs and its methods. However, IMHO, you should consider editing the onInit method in this script:
Collapse JASS:
private struct Data
    static group IsBitten
    static integer index = 0

    unit caster
    unit target
    boolean hasbuff = false
    
    private static method onInit takes nothing returns nothing
        set Data.IsBitten = CreateGroup()
    endmethod
some handle-based data types can be initialized on the global declaration; timers and groups are examples of those data types that can be initialized upon declaration.
Collapse JASS:
static group IsBitten = CreateGroup()
Collapse JASS:
private struct Data
 static unit DummyCaster
 method onInit takes nothing returns nothing
  set Data.DummyCaster=CreateUnit(player, unitid, x, y, angle)
 endmethod
Saves some line of code and a bit of bytes. But if you find my suggestion irrelevant to the stated subject matter, you may just ignore it.

+REP for making this guide, I myself learned things from it. Thank you for the effort!
__________________
Where's mah sig?

Last edited by chobibo : 06-17-2008 at 06:46 AM.
chobibo is offline   Reply With Quote
Old 06-18-2008, 12:41 AM   #6
moyack
Evil Emoticon
 
moyack's Avatar


Respected User
Project Leader: PoC
 
Join Date: Jan 2006
Posts: 3,279

Submissions (17)

moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)

AI Tournament #2 - 2nd PlaceHero Contest - Second place

Send a message via MSN to moyack
Default

Quote:
Originally Posted by chobibo
Very informative, it would prove useful for those people having trouble understanding how to use structs and its methods. However, IMHO, you should consider editing the onInit method in this script:
Collapse JASS:
private struct Data
    static group IsBitten
    static integer index = 0

    unit caster
    unit target
    boolean hasbuff = false
    
    private static method onInit takes nothing returns nothing
        set Data.IsBitten = CreateGroup()
    endmethod
some handle-based data types can be initialized on the global declaration; timers and groups are examples of those data types that can be initialized upon declaration.
Collapse JASS:
static group IsBitten = CreateGroup()
Collapse JASS:
private struct Data
 static unit DummyCaster
 method onInit takes nothing returns nothing
  set Data.DummyCaster=CreateUnit(player, unitid, x, y, angle)
 endmethod
Saves some line of code and a bit of bytes. But if you find my suggestion irrelevant to the stated subject matter, you may just ignore it.

+REP for making this guide, I myself learned things from it. Thank you for the effort!
Thanks for your comments :)

Well, I did the initialization using the onInit method in order to give a presentation of this kind of method and its usability for other situations. You're right about that we can initilize those type of handles at global level, but I remember taht Vex mentioned to me that we should avoid this, to be honest I'm not sure why, but he suggested me with my Spear of Longinus spell that I should avoid that kind of initialization (it seems it could generate issues if you optimize the map if I'm not wrong)

EDIT: That was the post where he mentioned that

Last edited by moyack : 06-18-2008 at 12:43 AM.
moyack is offline   Reply With Quote
Old 06-18-2008, 10:59 AM   #7
chobibo
100% Genuine Retard!
 
chobibo's Avatar
 
Join Date: Jan 2007
Posts: 797

chobibo has a spectacular aura about (101)chobibo has a spectacular aura about (101)chobibo has a spectacular aura about (101)chobibo has a spectacular aura about (101)

Send a message via Yahoo to chobibo
Default

Oh that's why, thanks for the link!
__________________
Where's mah sig?
chobibo is offline   Reply With Quote
Old 07-01-2008, 10:12 PM   #8
Flame_Phoenix
retired coder | real ilfe
 
Flame_Phoenix's Avatar
 
Join Date: Mar 2007
Posts: 2,208

Submissions (10)

Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)

Send a message via MSN to Flame_Phoenix
Default

Hey, Great tutorial, however I have some questions.
Anitarf says that we should always have an "onDestroy" method. Why doesn't your spell has one in the version without the boolean ?
Also, I never got the point, why do you pass the caster and target as arguments ??

I am not an adept of Katanas ... using bugs is bad, and I2H (or H2I, I am no sure) is said to be very evil and causes bugs (such as handle corruption which I experienced and is bad). Also a great friend of mine, you may know, PurplePoot says Katanas system is really not that good neither needed.
Using cache is also slow, in resume, why using it ?

Quote:

Yay!!! our spell is working wonderfully.... hmmmm... actually not, there's one problem more to solve. What would happen if this spell is casted and 1 second later other unit cast this spell in the same unit?? well, the unit will be damaged by 2 and the worst thing is the buff duration has been extended, in other words: an abusable spell.

Sometimes this is desirable and sometimes it doesn't but in the desirable situation we should balance this effect at least by detecting when the first cast should end, so one solution is to give to the spell the ability to detect if the duration has been reached, independently of the buff presence in the target unit. Here's the modification of this spell so it stacks but takes into account the duration of the spell but not the buff duration.

So, the spell will stack right ? This means, the unit will take 1 damage, or 2 damage depending on the numbers of spells cast on it?

Quote:
I personally love this approach, because it allows you to reduce (for not saying avoid) the usage of storage systems. This approach is based in the following precept: If the time is the same for all the units, then one timer should be able to review and control all the units affected by one spell and not one timer per spell casted as we did before.

So the first step has been defined: We can't start a new timer every time we cast the spell, instead, we need to start one timer at map init and putting it to run a code periodically so it can check units affected.
Well, if I understand right, Imagine I cast the spell out of sync with the timer. I will have to wait for the timer to realise that my unit got affected right ?

I was planning to do a tutorial like this one ... but I see you had my idea first .. great job xD
__________________
Check out my tutorials at:
1-Creating a Hero Tavern
2-Complete Icon Tutorial - ALL about Icons
3-Making a spell in vJass - Practice Session 1
Check out all my current spells at here
Finally, check my project:
Castle vs Castle Flame Edition

Last edited by Flame_Phoenix : 07-01-2008 at 10:14 PM.
Flame_Phoenix is offline   Reply With Quote
Old 07-02-2008, 12:39 AM   #9
moyack
Evil Emoticon
 
moyack's Avatar


Respected User
Project Leader: PoC
 
Join Date: Jan 2006
Posts: 3,279

Submissions (17)

moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)

AI Tournament #2 - 2nd PlaceHero Contest - Second place

Send a message via MSN to moyack
Default

Prepare for the uber quote!!!!



Quote:
Originally Posted by Flame_Phoenix
Hey, Great tutorial, however I have some questions.
<3<3<3<3<3<3 I'm glas you like it... and I'm more than glad that this tutorial generates questions on the reader...
Quote:
Anitarf says that we should always have an "onDestroy" method. Why doesn't your spell has one in the version without the boolean ?
AS you could notice, I use a spell and changed its effect according to the requirements... so in some cases the onDestroy method is not necessary to define. Now the question is: when it's neccesary to define the onDestroy method and when not?? when you need to clean, remove or destroy handles. For example, if you have a triggered AOE spell, you'll probably need to pass the target location to the looping function, therefore you will need to add to the struct a create method which stores the location in one of the struct parameters, and because we don't want to leak locations, we will need a custom onDestroy method that will remove the location and then nullify. Probably in the case of your spells Anitarf has been emphatic in that, because it's needed, but there could be other situations where the onDestroy methods are not needed.



Quote:
Also, I never got the point, why do you pass the caster and target as arguments ??
Because the purpose of the struct is to store the caster and the target, and the best way to make it clear is to make them as parameters. Put in the shoes of a new user who wants to adapt the spell to other circunstance different from casting a spell, then this user in order to achieve a good adaptation, he has to get into the struct. It's more easier to left those variables as parameters of the create function, so the user can understand and adapt better this spell in different conditions.

Quote:
I am not an adept of Katanas ... using bugs is bad, and I2H (or H2I, I am no sure) is said to be very evil and causes bugs (such as handle corruption which I experienced and is bad). Also a great friend of mine, you may know, PurplePoot says Katanas system is really not that good neither needed.
Using cache is also slow, in resume, why using it ?
If you notice, I'm not forcing anybody to use them, the procedures exposed in the tutorial are perfectly compatible with other systems. Besides, if you reread the tutorial, I expose how to make this spell without timer recyclers and without storage systems (second approach in the tutorial).


Quote:
So, the spell will stack right ? This means, the unit will take 1 damage, or 2 damage depending on the numbers of spells cast on it?
In the first attempts yes, it will stack, but then I show how to remove this stackability, if it's not desired. This is important because its very common to see in maps spells which don't take this factor into account and make the games very abuseable.


Quote:
Well, if I understand right, Imagine I cast the spell out of sync with the timer. I will have to wait for the timer to realise that my unit got affected right ?
As you could see, I use short periods (0.1 or less)Believe that this is something that we won't notice in game (or you can detect a desync of 0.1 seconds?? :P)

You can use delays of several seconds, but in a very limited range of cases. But if you need that the spell starts the effect with a high precision, then the Approach number 1 is the answer, because it will use a timer per spell casted.

Quote:
I was planning to do a tutorial like this one ... but I see you had my idea first .. great job xD
Well, this has been something that I've always wanted to do, and at the end I got the time and the inspiration to get this tutorial done. I hope it helps you, and if you have more questions, go ahead.

EDIT: I did some grammatical fixes, if there's still something unclear please let me know.

Last edited by moyack : 07-02-2008 at 03:15 AM.
moyack is offline   Reply With Quote
Old 07-02-2008, 09:51 AM   #10
Flame_Phoenix
retired coder | real ilfe
 
Flame_Phoenix's Avatar
 
Join Date: Mar 2007
Posts: 2,208

Submissions (10)

Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)

Send a message via MSN to Flame_Phoenix
Default

Hey, thx for your fats reply.
Also, another thing, here you use CSSafety. Is there any other recycling system is is CSSafety the best recycling system there is actually ?
Where can I download the most recent version of CSSafety ?
__________________
Check out my tutorials at:
1-Creating a Hero Tavern
2-Complete Icon Tutorial - ALL about Icons
3-Making a spell in vJass - Practice Session 1
Check out all my current spells at here
Finally, check my project:
Castle vs Castle Flame Edition
Flame_Phoenix is offline   Reply With Quote
Old 07-02-2008, 01:10 PM   #11
moyack
Evil Emoticon
 
moyack's Avatar


Respected User
Project Leader: PoC
 
Join Date: Jan 2006
Posts: 3,279

Submissions (17)

moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)

AI Tournament #2 - 2nd PlaceHero Contest - Second place

Send a message via MSN to moyack
Default

Quote:
Originally Posted by Flame_Phoenix
Hey, thx for your fats reply.
Also, another thing, here you use CSSafety. Is there any other recycling system is is CSSafety the best recycling system there is actually ?
Where can I download the most recent version of CSSafety ?
As far as I remember there's no other timer recycler, well cohadar did a modification to the CSSafety allowing to recycle timers and groups, you can find it in one of his system posted here.

The most recent version of CSSafety can be found on the caster system made by Vexorian.
moyack is offline   Reply With Quote
Old 07-02-2008, 02:07 PM   #12
Flame_Phoenix
retired coder | real ilfe
 
Flame_Phoenix's Avatar
 
Join Date: Mar 2007
Posts: 2,208

Submissions (10)

Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)

Send a message via MSN to Flame_Phoenix
Default

Quote:
The most recent version of CSSafety can be found on the caster system made by Vexorian.
Damn.... this version you are talking about has high coupling ... it will also require CSData to work. In order to use one little thing, I'll have to import lots of stuff i don't need =(
__________________
Check out my tutorials at:
1-Creating a Hero Tavern
2-Complete Icon Tutorial - ALL about Icons
3-Making a spell in vJass - Practice Session 1
Check out all my current spells at here
Finally, check my project:
Castle vs Castle Flame Edition
Flame_Phoenix is offline   Reply With Quote
Old 07-02-2008, 02:56 PM   #13
moyack
Evil Emoticon
 
moyack's Avatar


Respected User
Project Leader: PoC
 
Join Date: Jan 2006
Posts: 3,279

Submissions (17)

moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)

AI Tournament #2 - 2nd PlaceHero Contest - Second place

Send a message via MSN to moyack
Default

Quote:
Originally Posted by Flame_Phoenix
Damn.... this version you are talking about has high coupling ... it will also require CSData to work. In order to use one little thing, I'll have to import lots of stuff i don't need =(
No, actually not!!! CSSafety is independent of CSData and other systems and therefore it can be used standalone.
moyack is offline   Reply With Quote
Old 07-02-2008, 07:59 PM   #14
Flame_Phoenix
retired coder | real ilfe
 
Flame_Phoenix's Avatar
 
Join Date: Mar 2007
Posts: 2,208

Submissions (10)

Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)Flame_Phoenix has a spectacular aura about (90)

Send a message via MSN to Flame_Phoenix
Default

Quote:
No, actually not!!! CSSafety is independent of CSData and other systems and therefore it can be used standalone.
MMmm, so how do I change this code, to avoid using unnecessary group recycling and, mainly CSData ??
Also, is gorup recycling as good and does it has as many advantages as timer recycling ?

Collapse JASS:
library CSSafety requires CSData
//******************************************************************************************
//*
//* CSSafety 15.2
//* ŻŻŻŻŻŻŻŻ
//*  CSSafety was originally just a timer stack, take your map functions and replace calls
//* to CreateTimer with NewTimer and calls to DestroyTimer to ReleaseTimer and you can feel
//* at ease if your map's spells abuse dynamic triggers a lot since it prevents handle stack
//* corruptions caused by crazy reference counters, basically, wc3 will go crazy if you
//* detroy timers, so it is better to just recycle them.
//*
//*  Eventually, we also added a group stack, the justification is different though, there
//* are just memory leak related issues when using DestroyGroup on groups that were handled
//* by certain natives, thus it is good to recycle them. Replace CreateGroup with NewGroup
//* and DestroyGroup with ReleaseGroup.
//*
//******************************************************************************************

//==========================================================================================
globals
    private timer array tT
    private integer tN = 0
    private group array gT
    private integer gN = 0
    private constant integer HELD=0x28829022
    //use a totally random number here, the more improbable someone uses it, the better.
endglobals

//==========================================================================================
function NewTimer takes nothing returns timer
    if (tN==0) then
        set tT[0]=CreateTimer()
    else
        set tN=tN-1
    endif
    call SetCSData(tT[tN],0)
 return tT[tN]
endfunction

//==========================================================================================
function ReleaseTimer takes timer t returns nothing
    if(t==null) then
        debug call BJDebugMsg("Warning: attempt to release a null timer")
        return
    endif
    if (tN==8191) then
        debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

        //stack is full, the map already has much more troubles than the chance of bug
        call DestroyTimer(t)
    else
        call PauseTimer(t)
        if(GetCSData(t)==HELD) then
            debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
            return
        endif
        call SetCSData(t,HELD)
        set tT[tN]=t
        set tN=tN+1
    endif    
endfunction

//==========================================================================================
function NewGroup takes nothing returns group
    if (gN==0) then
        set gT[0]=CreateGroup()
    else
        set gN=gN-1
    endif
    call SetCSData(gT[gN],0)
 return gT[gN]
endfunction

//==========================================================================================
function ReleaseGroup takes group g returns nothing
    if(g==null) then
        debug call BJDebugMsg("Warning: attempt to release a null group")
        return
    endif
    if (gN==8191) then
        debug call BJDebugMsg("Warning: group stack is full, destroying group!!")

        //stack is full, the map already has much more troubles than the chance of bug
        call DestroyGroup(g)
    else
        call GroupClear(g)
        if(GetCSData(g)==HELD) then
            debug call BJDebugMsg("Warning: ReleaseGroup: Double free!")
            return
        endif
        call SetCSData(g,HELD)
        set gT[gN]=g
        set gN=gN+1
    endif    
endfunction

endlibrary
__________________
Check out my tutorials at:
1-Creating a Hero Tavern
2-Complete Icon Tutorial - ALL about Icons
3-Making a spell in vJass - Practice Session 1
Check out all my current spells at here
Finally, check my project:
Castle vs Castle Flame Edition
Flame_Phoenix is offline   Reply With Quote
Old 07-02-2008, 08:35 PM   #15
moyack
Evil Emoticon
 
moyack's Avatar


Respected User
Project Leader: PoC
 
Join Date: Jan 2006
Posts: 3,279

Submissions (17)

moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)moyack is a splendid one to behold (666)

AI Tournament #2 - 2nd PlaceHero Contest - Second place

Send a message via MSN to moyack
Default

My mistake: the new version of CSSafety is not independent of CSData anymore...

I think this version will work perfectly for you: http://www.wc3campaigns.net/pastebin...5d68521ab3cde7

Last edited by moyack : 07-02-2008 at 08:35 PM.
moyack is offline   Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off


All times are GMT. The time now is 02:15 AM.


Affiliates
The Hubb The JASS Vault Clan WEnW Campaign Creations Clan CBS GamesModding Flixreel Videos

Powered by vBulletin (Copyright ©2000 - 2018, Jelsoft Enterprises Ltd).
Hosted by www.OICcam.com
IT Support and Services provided by Executive IT Services