1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] Buff System - Turn Based Game

Discussion in 'Triggers & Scripts' started by Sephalo, Sep 9, 2015.

  1. Sephalo

    Sephalo

    Joined:
    Oct 12, 2007
    Messages:
    1,791
    Resources:
    0
    Resources:
    0
    Hey there,

    I'm currently a bit stuck on a matter for my map.
    I'm making a turn based game, so durations of buffs and events don't pend on time, but on the amount of turns that pass by.

    Whenever I use the 'end turn' ability, an integer called 'TURN' will be set to 'TURN + 1'. Simple as that.

    Now the problem is that I want to have a good buff system in the map where dispelling is possible. I looked around and found a great system made by Rising_Dusk (on wc3c), but this system is (obviously) using 'time' to check when a buff expires.

    I'm currently thinking of editing this system, or remaking a new one, so that I can set the 'time to expire' as an integer, which means if that value is 3, it will take 3 TURNS to go by until the buff expires.

    Now I just don't know what to do here, since I'm not a very great system coder. A thing that is on my mind is 'how can I easily add -1 to every buff's turns left and see if the buff should be expiring or not'.

    The current buff system, obviously, uses timers to track the elapsed time of a buff. But I need some kind of tracking to whenever a player uses the 'end turn' ability, and whenever that happens it has to modify the buff durations by reducing it by 1. If then its at 0 it should run the specific buff removal function, which means that sometimes it also has to remove more than just the buff ID, since some buffs add +2 agility (per example) so removing extra abilities with BonusMods is also required.

    Is there anyone here that has an idea on how to make this doable? Or even better, would someone like to give it a go for me? :D


    Here's the system I'm currently using (using real time instead of integer turns)
    Code (vJASS):
    library IntuitiveBuffSystem initializer Init requires Stack, TimerUtils, AutoIndex, AbilityPreload
    //******************************************************************************
    //* BY: Rising_Dusk
    //*     (Intuitive) Buff System 1.05
    //*
    //* This library is a simple and easy to understand implementation of custom
    //* buff handling in JASS. I call it the Intuitive Buff System because anyone
    //* should be able to pick it up and use it right away. It uses a callback
    //* driven style where the user specifies which callbacks run either
    //* periodically over a buff's duration or when the buff is removed. This covers
    //* all possible types of buffs that one might want to create.
    //*
    //******************************************************************************
    //*
    //* In order to make a buff work, one must first create a custom aura ability in
    //* the Object Editor and a custom buff for that aura. The raw ids for these
    //* objects need to be supplied to the IBS via the DefineBuffType function.
    //*
    //*     function DefineBuffType takes integer AbilId, integer BuffId, ...
    //*     ... real period, boolean isPeriodic, boolean isPositive, callback onAdd, ...
    //*     ... callback onPeriodic, callback onEnd returns integer
    //*
    //* The arguments for this function are as follows:
    //*     [integer]  AbilId               Owner of the buff being applied
    //*     [integer]  BuffId               Target for the buff to be applied to
    //*     [real]     period               Type of the buff from DefineBuffType
    //*     [boolean]  isPeriodic           Whether the buff is periodic or not
    //*     [boolean]  isPositive           Whether the buff is positive or not
    //*     [callback] onAdd                Callback to run when a buff is added
    //*     [callback] onPeriodic           Callback to run every buff iteration
    //*     [callback] onEnd                Callback to run when a buff ends
    //*
    //* NOTE: All callbacks must follow the below structural convention
    //*     function MyCallback takes nothing returns nothing
    //*
    //* The callback functions may be private, public, or whatever. When using them
    //* in the DefineBuffType function call, simply put the function's name for the
    //* respective argument (no function keyword). If you do not want any function
    //* to be called for one of the callbacks, simply use 0 for that argument.
    //*
    //* Once the bufftype is registered with the system, you are free to use it
    //* whenever you want to. It returns an integer, which you should set to a
    //* global integer in a scope initializer or some initialization function. To
    //* apply a buff to a unit, simple call the UnitAddBuff function. All of these
    //* values are effectively constant for any buff created of the given bufftype.
    //*
    //* The period argument tells the system the interval for running the
    //* onPeriodic callback function. The period and onPeriodic arguments only
    //* matter if the isPeriodic argument is true. The onEnd argument runs when a
    //* buff is removed either manually, by it expiring, or by the unit it is on
    //* dying. The onAdd argument runs when a buff is either applied or refreshed.
    //* Please do not destroy the dbuff struct inside your callback functions; that
    //* will cause doublefrees. The system automatically cleans them up.
    //*
    //* Inside the callbacks, you get one event response:
    //*     function GetEventBuff takes nothing returns dbuff
    //*
    //* Usage:
    //*     local dbuff db = GetEventBuff()
    //*
    //* Within the above struct, the following information can be retrieved (or
    //* modified, but I recommend not changing most of the parameters). At any given
    //* time during the buff's existence, these values can also be retrieved with a
    //* call to GetUnitBuff, GetRandomBuff, or GetRandomBuffSigned on the unit.
    //*
    //*     [unit]    .source               Owning unit of the buff
    //*     [unit]    .target               Unit the buff is applied to
    //*     [real]    .duration             Total duration for buff expiration
    //*     [real]    .timeElapsed()        Total elapsed time of the buff
    //*     [real]    .timeRemaining()      Remaining time before buff expiration
    //*               .setDuration(NewDur)  Sets the current buff duration to NewDur
    //*               .addDuration(ModDur)  Adds ModDur to the current buff duration
    //*     [integer] .btype.period         Periodicity for the buff
    //*     [integer] .level                Level of the buff used on the unit
    //*     [integer] .data                 An integer the user can store data to
    //*     [boolean] .isPeriodic           Whether the buff is periodic or not
    //*     [boolean] .isExpired            Whether the buff expired naturally or not
    //*                                     Can only return true in the 'OnEnd' callback
    //*     [boolean] .isRemoved            Whether the buff was purged or removed by death or not
    //*                                     Can only return true in the 'OnEnd' callback
    //*     [boolean] .isRefreshed          Whether the buff was refreshed or not
    //*                                     Can only return true in the 'OnAdd' callback
    //*
    //******************************************************************************
    //*
    //*     function UnitAddBuff takes unit source, unit target, integer btype, ...
    //*     ... real dur, integer lvl returns dbuff
    //*
    //* The arguments for this function are as follows:
    //*     [unit]    source                The 'owner' of the buff being applied
    //*     [unit]    target                The target for the buff to be applied to
    //*     [integer] btype                 The type of the buff from DefineBuffType
    //*     [real]    dur                   The total duration of the buff
    //*     [integer] lvl                   The 'level' of the buff
    //*
    //* Constants usable in this function call:
    //*     [real]    BUFF_PERMANENT        This is the value you should use for the
    //*                                     duration argument if you want a buff
    //*                                     that never ends.
    //*
    //* Additional options of note:
    //*     set mybuff.data = SomeInteger   This allows you to attach an integer to
    //*                                     a buff that you can get in the event
    //*                                     callbacks.
    //*
    //* The lvl argument is there for reapplication of buff purposes. A buff being
    //* applied of a higher level than the old buff will always overwrite the old
    //* one, it doesn't matter if duration is higher or not. This way, if you want a
    //* buff to get shorter per level, the system will support that.
    //*
    //******************************************************************************
    //*
    //* Available system functions:
    //*
    //*    > function GetUnitBuff takes unit target, integer whichType returns dbuff
    //*
    //* This function allows the user to get a specific buff on a unit at any point
    //* if the user knows the bufftype they want. Will return 0 if the buff does not
    //* exist.
    //*
    //*    > function GetRandomBuff takes unit target returns dbuff
    //*
    //* This function allows the user to get a random buff on a unit at any point.
    //* Getting a random buff is most useful for stealing or moving of arbitrary
    //* buffs on a unit. Will return 0 if the unit has no buffs.
    //*
    //*    > function GetRandomBuffSigned takes unit target, boolean isPos returns dbuff
    //*
    //* This function, like GetRandomBuff, returns a random buff on a unit given a
    //* 'sign'. The isPos argument lets you specify to either get a random positive
    //* buff if true or a random negative buff if false. Will return 0 if the unit
    //* has no buffs of the desired alignment.
    //*
    //*    > function GetLastAppliedBuff takes unit target returns dbuff
    //*
    //* This function returns the most recently applied buff on a unit. Will return
    //* 0 if the unit has no buffs.
    //*
    //*    > function GetFirstAppliedBuff takes unit target returns dbuff
    //*
    //* This function returns the first buff in a user's bufflist that is still
    //* active. Will return 0 if the unit has no buffs.
    //*
    //*    > function UnitHasBuff takes unit target, integer whichType returns boolean
    //*
    //* This function will return true if the given unit has the specified buff.
    //*
    //*    > function UnitHasAnyBuff takes unit target returns boolean
    //*
    //* This function will return true if the given unit has any buff managed by
    //* the system.
    //*
    //*    > function UnitRemoveBuff takes unit target, integer whichType returns boolean
    //*
    //* This function removes a buff of a specified type from a given unit. Will
    //* return false if the buff did not exist to be removed.
    //*
    //*    > function UnitRemoveAllBuffs takes unit target returns nothing
    //*
    //* This function removes all buffs of any type from a given unit.
    //*
    //******************************************************************************
    //*
    //* Finally, there is the inherent limit that you can only have 8192 buffs and
    //* 8192 bufftypes at once. If you ever get this high, your map will have worse
    //* problems than this system working improperly.
    //*
    //* I hope you find this library as useful as I do. Thanks for reading the
    //* documentation. If you have any questions for me about the IBS, how it works,
    //* or if you have suggestions for it, please contact me at WC3C by private
    //* message. This system is only authorized for release at Warcraft 3 Campaigns.
    //*
    //* Enjoy!
    //*

    globals
        //Constant for use with infinite duration buffs
        constant real    BUFF_PERMANENT     = 0x800000
    endglobals

    private keyword TimeOut
    private keyword TempBuff
    private function interface callback takes nothing returns nothing

    globals
        private hashtable     ht    = InitHashtable() //Hashtable for all data attachment
        private integer array lists
    endglobals

    private struct bufftype
        //All readonly so the user doesn't mess with them
        integer  abilid
        integer  buffid
        real     period
        boolean  isPeriodic
        boolean  isPositive
       
        //All of the callback behavior for the bufftype
        callback onAdd
        callback onPeriodic
        callback onEnd
        static method create takes integer abilid, integer buffid, real period, boolean isPeriodic, boolean isPositive, callback onAdd, callback onPeriodic, callback onEnd returns bufftype
            local bufftype bt = bufftype.allocate()
            set bt.abilid     = abilid
            set bt.buffid     = buffid
            set bt.period     = period
            set bt.isPeriodic = isPeriodic
            set bt.onAdd      = onAdd
            set bt.onPeriodic = onPeriodic
            set bt.onEnd      = onEnd
            return bt
        endmethod
    endstruct

    private struct bufflist
        Stack sta
        unit  target
       
        static method create takes unit target returns bufflist
            local bufflist bl = bufflist.allocate()
            set bl.target     = target
            set bl.sta        = Stack.create()
            //Attach it to the unit
            set lists[GetUnitId(target)] = integer(bl)
            return bl
        endmethod
        private method onDestroy takes nothing returns nothing
            call .sta.destroy()               //Destroy the stack
            set lists[GetUnitId(.target)] = 0 //Remove array value
        endmethod
    endstruct

    struct dbuff
        unit     source
        unit     target
        real     elapsed     = 0.
        real     duration
        integer  level
        integer  data        = 0
        bufftype btype
        timer    tmr
       
        //Behavior variables inside the struct
        boolean  isPeriodic
        boolean  isExpired   = false
        boolean  isRemoved   = false
        boolean  isRefreshed = false
       
        //Handy methods that save the user math they don't want to do
        method timeElapsed takes nothing returns real
            if .btype.isPeriodic then
                return .elapsed+TimerGetElapsed(.tmr)
            else
                return TimerGetElapsed(.tmr)
            endif
        endmethod
        method timeRemaining takes nothing returns real
            if .btype.isPeriodic then
                return .duration-.elapsed-TimerGetElapsed(.tmr)
            else
                return TimerGetRemaining(.tmr)
            endif
        endmethod
        method setDuration takes real newDur returns nothing
            local real oldDur = .duration
            if .timeElapsed() >= newDur then
                //This is equivalent to force removing the buff, so kill it
                set .isExpired   = false
                set .isRemoved   = true
                set .isRefreshed = false
                call .destroy() //Timer gets recycled in here!
            else
                if .isPeriodic then
                    if .timeElapsed() + .btype.period > newDur then
                        //Update timeout to cover last segment of buff duration
                        call TimerStart(.tmr, newDur-.timeElapsed(), false, TimeOut)
                        //Since it won't clear a full period, we don't let it run onPeriodic
                        set .isPeriodic = false
                    endif
                else
                    //Run for however long you have to so total duration is the new duration
                    call TimerStart(.tmr, newDur-.timeElapsed()  , false, TimeOut)
                endif
                set .duration = newDur
            endif
        endmethod
        method addDuration takes real modDur returns nothing
            //Just calls the setDuration method
            call .setDuration(.duration+modDur)
        endmethod
        //Some callback running methods
        method runAdd takes nothing returns nothing
            set .isExpired   = false
            set .isRemoved   = false
            set .isRefreshed = false
            call .btype.onAdd.execute()
        endmethod
        method runRefreshed takes nothing returns nothing
            set .isRefreshed = true
            set .isExpired   = false
            set .isRemoved   = false
            call .btype.onAdd.execute()
        endmethod
        method runPeriodic takes nothing returns nothing
            set .isExpired   = false
            set .isRemoved   = false
            set .isRefreshed = false
            call .btype.onPeriodic.execute()
        endmethod
        method runExpired takes nothing returns nothing
            //This destroys the dbuff
            set .isExpired   = true
            set .isRemoved   = false
            set .isRefreshed = false
            call .destroy() //Timer gets recycled in here!
        endmethod
        method runRemoved takes nothing returns nothing
            //This destroys the dbuff
            set .isExpired   = false
            set .isRemoved   = true
            set .isRefreshed = false
            call .destroy() //Timer gets recycled in here!
        endmethod
        static method create takes unit source, unit target, bufftype btype, real dur, integer lvl returns dbuff
            local dbuff   db  = dbuff.allocate()
            local integer id  = GetUnitId(target)
            set db.source     = source
            set db.target     = target
            set db.duration   = dur
            set db.btype      = btype
            set db.isPeriodic = btype.isPeriodic
            set db.level      = lvl
            set db.tmr        = NewTimer()
            //Hook the dbuff to the timer
            call SetTimerData(db.tmr, integer(db))
            //Add ability to the target of the buff
            call UnitAddAbility(target, db.btype.abilid)
            call UnitMakeAbilityPermanent(target, true, db.btype.abilid)
            //Load to table for future referencing
            call SaveInteger(ht, btype, id, integer(db))
            //Push the buff into the bufflist stack on the target
            call bufflist(lists[id]).sta.add(integer(db))
            return db
        endmethod
        private method onDestroy takes nothing returns nothing
            local integer id = GetUnitId(.target)
            //Clear the table value so the system knows it's done
            //Done before the onRemove call to prevent potential double frees with death trigger
            call RemoveSavedInteger(ht, .btype, id)
           
            //Remove the buff from the bufflist stack on the target
            call bufflist(lists[id]).sta.remove(integer(this))
           
            //Set up and run the onRemove callback
            set TempBuff = this
            call .btype.onEnd.execute()
           
            //Clear stuff inside the struct and on the unit
            call UnitRemoveAbility(.target, .btype.abilid)
            call UnitRemoveAbility(.target, .btype.buffid)
            call ReleaseTimer(.tmr)
        endmethod
    endstruct

    globals
        private code           TimeOut       = null //Code callback for buff timeouts
        private integer        BuffTypeCount = 0    //Counter for how many buff types exist
        private bufftype array BuffTypes            //Array for all defined buff types
        private dbuff          TempBuff      = 0    //Temp buff for callback data
    endglobals

    private function BuffTimeout takes nothing returns nothing
        local timer t  = GetExpiredTimer()
        local dbuff db = dbuff(GetTimerData(t))
       
        //Different behavior for periodic buffs than one-timers
        if db.isPeriodic then
            //Run the onPeriod callback no matter what
            set TempBuff = db
            call db.runPeriodic()
            //Check if this iteration kills the buff
            set db.elapsed = db.elapsed + db.btype.period
            if db.elapsed >= db.duration and not db.isRemoved then
                //Kill the buff completely if it hasn't been cleared elsewhere
                call db.runExpired()
            elseif db.elapsed + db.btype.period > db.duration and not db.isRemoved then
                //Update timeout to cover last segment of buff duration
                call TimerStart(db.tmr, db.duration-db.elapsed, false, function BuffTimeout)
                //Since it won't clear a full period, we don't let it run onPeriodic
                set db.isPeriodic = false
            elseif TimerGetElapsed(db.tmr) < db.btype.period then
                //Update period of timer to normal value
                call TimerStart(db.tmr, db.btype.period, true, function BuffTimeout)
            endif
        elseif not db.isRemoved then
            //Kill the buff completely, set it to expired naturally
            //Shouldn't run if it was force removed by the on-death trigger
            call db.runExpired()
        endif
       
        set t = null
    endfunction

    //******************************************************************************

    function GetEventBuff takes nothing returns dbuff
        return TempBuff
    endfunction

    //******************************************************************************

    function UnitHasBuff takes unit target, integer whichType returns boolean
        return HaveSavedInteger(ht, whichType, GetUnitId(target))
    endfunction

    function UnitHasAnyBuff takes unit target returns boolean
        return bufflist(lists[GetUnitId(target)]).sta.size > 0
    endfunction

    function UnitRemoveBuff takes unit target, integer whichType returns boolean
        local dbuff db = 0
        if HaveSavedInteger(ht, whichType, GetUnitId(target)) then
            set db = dbuff(LoadInteger(ht, whichType, GetUnitId(target)))
            //Buff exists, clear it
            call db.runRemoved()
            return true
        endif
        return false
    endfunction

    private function RemoveAllBuffsEnum takes integer value returns nothing
        call dbuff(value).runRemoved()
    endfunction
    function UnitRemoveAllBuffs takes unit target returns nothing
        call bufflist(lists[GetUnitId(target)]).sta.enum(RemoveAllBuffsEnum, true)
    endfunction

    function GetUnitBuff takes unit target, integer whichType returns dbuff
        return dbuff(LoadInteger(ht, whichType, GetUnitId(target)))
    endfunction

    function GetRandomBuff takes unit target returns dbuff
        local bufflist bl = bufflist(lists[GetUnitId(target)])
        if bl.sta.size > 0 then
            return dbuff(bl.sta.random)
        endif
        return 0 //No buff to return
    endfunction

    function GetRandomBuffSigned takes unit target, boolean isPos returns dbuff
        local bufflist bl = bufflist(lists[GetUnitId(target)])
        local Stack    s1 = 0
        local Stack    s2 = 0
        local integer  v  = 0
       
        //Only do this stuff if the unit has any buffs at all
        if bl.sta.size > 0 then
            //Build the needed stacks
            set s1 = bl.sta.copy()
            set s2 = Stack.create()
            //Loop through and build a new stack of buffs of desired type
            loop
                exitwhen s1.size == 0
                set v = s1.first
                if dbuff(v).btype.isPositive == isPos then
                    //Found one, add to #2 stack
                    call s2.add(v)
                endif
                call s1.remove(v)
            endloop
            if s2.size > 0 then
                //Get random member of generated stack
                set v = s2.random
            else
                set v = 0
            endif
            //Destroy the generated stacks
            call s1.destroy()
            call s2.destroy()
            //Return our buff or 0
            if v > 0 then
                return dbuff(v) //Return our buff
            endif
        endif
        return 0 //No buff to return
    endfunction

    function GetLastAppliedBuff takes unit target returns dbuff
        local bufflist bl = bufflist(lists[GetUnitId(target)])
        if bl.sta.size > 0 then
            return dbuff(bl.sta.first)
        endif
        return 0 //No buff to return
    endfunction

    function GetFirstAppliedBuff takes unit target returns dbuff
        local bufflist bl = bufflist(lists[GetUnitId(target)])
        local Stack    s  = 0
        local integer  v  = 0
       
        //Only do this stuff if the unit has any buffs at all
        if bl.sta.size > 0 then
            //Build the stack we need
            set s = bl.sta.copy()
            //Loop through until you find the bottom of the stack
            loop
                exitwhen s.size == 0
                set v = s.first
                call s.remove(v)
            endloop
            return dbuff(v)
        endif
        return 0 //No buff to return
    endfunction

    function UnitAddBuff takes unit source, unit target, integer btype, real dur, integer lvl returns dbuff
        local timer    t  = null
        local integer  id = GetUnitId(target)
        local bufftype bt = bufftype(btype)
        local integer  d  = 0
        local dbuff    db = 0
       
        //Standard debugging procedure
        if source == null or target == null or btype < 0 or lvl < 0 or dur <= 0. then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Invalid buff creation parameters")
            return 0
        endif
       
        //Find if this previous instance exists in the Table
        if HaveSavedInteger(ht, btype, id) then
            //Exists, use its data
            set db = dbuff(LoadInteger(ht, btype, id))
            if (lvl == db.level and db.timeRemaining() < dur) or lvl > db.level then
                //Update all applicable values to the newly supplied ones if new instance overwrites
                //Elapsed goes to 0 because it's as if a new buff has been cast, run timer again
                set db.source   = source
                set db.target   = target
                set db.elapsed  = 0.
                set db.duration = dur
                set db.level    = lvl
                if not db.isPeriodic and db.btype.isPeriodic then
                    //If it was on the last segment of a periodic timer, reset periodicity
                    set db.isPeriodic = true
                endif
                //Run the onAdd callback
                set TempBuff = db
                //It becomes a refreshed buff now
                call db.runRefreshed()
                if db.isPeriodic then
                    call TimerStart(db.tmr, db.btype.period-TimerGetElapsed(db.tmr), true , function BuffTimeout)
                else
                    call TimerStart(db.tmr, dur                                    , false, function BuffTimeout)
                endif
            endif
        else
            //Doesn't exist, create it
            set db = dbuff.create(source, target, bt, dur, lvl)
            //Run the onAdd callback
            set TempBuff = db
            call db.runAdd()
            if db.isPeriodic then
                call TimerStart(db.tmr, db.btype.period, true , function BuffTimeout)
            else
                call TimerStart(db.tmr, dur            , false, function BuffTimeout)
            endif
        endif
        return db
    endfunction

    function DefineBuffType takes integer AbilId, integer BuffId, real period, boolean isPeriodic, boolean isPositive, callback onAdd, callback onPeriodic, callback onEnd returns integer
        local bufftype bt = bufftype.create(AbilId, BuffId, period, isPeriodic, isPositive, onAdd, onPeriodic, onEnd)
        //Preload abilities to prevent first-buff lag
        call AbilityPreload(AbilId)
        set BuffTypeCount = BuffTypeCount + 1
        set BuffTypes[BuffTypeCount] = bt
        return integer(bt)
    endfunction

    //******************************************************************************

    private function DeathActions takes nothing returns nothing
        call UnitRemoveAllBuffs(GetDyingUnit())
    endfunction

    //******************************************************************************

    private function UnitEntersMap takes unit u returns nothing
        call bufflist.create(u)
    endfunction
    private function UnitLeavesMap takes unit u returns nothing
        if lists[GetUnitId(u)] != 0 then
            call bufflist(lists[GetUnitId(u)]).destroy()
        endif
    endfunction

    private function Init takes nothing returns nothing
        local trigger trg = CreateTrigger()
       
        //Trigger for removing buffs on death
        call TriggerAddAction(trg, function DeathActions)
        call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_DEATH)
       
        //Indexing callback to create bufflists for indexed units
        call OnUnitIndexed(UnitEntersMap)
       
        //Removal callback to remove bufflists for indexed units
        call OnUnitDeindexed(UnitLeavesMap)
       
        //Initialize the callback code var
        set TimeOut = function BuffTimeout
    endfunction
    endlibrary
     
  2. DEE-BOO

    DEE-BOO

    Joined:
    Apr 4, 2010
    Messages:
    505
    Resources:
    1
    Icons:
    1
    Resources:
    1
    I made something in GUI if you want to take a look at it, let me know.
     
  3. Sephalo

    Sephalo

    Joined:
    Oct 12, 2007
    Messages:
    1,791
    Resources:
    0
    Resources:
    0
    Sure. Where can I see it?
     
  4. DEE-BOO

    DEE-BOO

    Joined:
    Apr 4, 2010
    Messages:
    505
    Resources:
    1
    Icons:
    1
    Resources:
    1
    Here. EDIT: I don't think it's good enough for the caliber and polished design of your map.
     

    Attached Files:

  5. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,641
    Resources:
    3
    Spells:
    3
    Resources:
    3
    I can rewrite my Buff system to use integers that can be simply incremented/decremented whenever you like.

    It is JASS though, so if you are a GUI user then I cant help you.
     
  6. Sephalo

    Sephalo

    Joined:
    Oct 12, 2007
    Messages:
    1,791
    Resources:
    0
    Resources:
    0
    Thanks guys.
    @DEE-BOO: I think the system is not complex enough. There are a few things I need to have that are present in the current system I use.

    @Wietlol: That would be great! What does your system support right now? I need to be able to customize buffs to classify them as positive/negative and dispellable/undispellable. I currently use an aura ability based on the tornado aura + a buff for each 'buff'. So adding the buffs is done by this aura ability.
     
  7. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,641
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Well... I actually use the exact same method as you do...
    I dont know exactly how you do it but I do it like this:

    Code (vJASS):
    library Buff uses TimerUtils, LLBuff //, Unit Indexer
       
        //! textmacro CREATE_BUFF_ABILITY takes RAWCODE, NAME, ICON, DESCRIPTION
            //! external ObjectMerger w3a Aasl A$RAWCODE$ abuf 1 B$RAWCODE$ Slo1 1 0 aare 1 0 atar 1 "Self,Invulnerable,Vulnerable"
            //! external ObjectMerger w3h Basl B$RAWCODE$ ftip "$NAME$" fube "$DESCRIPTION$" fart "$ICON$"
        //! endtextmacro
        /*function GetTriggerBuff takes nothing returns integer
            return buff_CurrentIndex
        endfunction*/

       
        globals
           
            integer         STACKING_TYPE_PARALLEL                          = 0
            integer         STACKING_TYPE_CUSTOM                            = 1
           
            integer         buff_NewBuffType                                = 0
            integer         buff_NewIndex                                   = 0
            integer         buff_TriggeredBuff                              = 0
           
            real            buff_Event_Applied                              = 0
            unit            buff_EventResponse_Target                       = null
            unit            buff_EventResponse_Source                       = null
            integer         buff_EventResponse_Bufftype                     = 0
            real            buff_EventResponse_Duration                     = 0.
           
            boolean array   buff_IsOccupied
            unit array      buff_Target
            unit array      buff_Source
            integer array   buff_BuffType
            timer array     buff_Timer
            real array      buff_Duration
           
            real array      bufftype_Interval
            integer array   bufftype_Ability
            integer array   bufftype_Buff
            integer array   bufftype_StackingType
            trigger array   bufftype_TriggerApply
            trigger array   bufftype_TriggerStack
            trigger array   bufftype_TriggerInterval
            trigger array   bufftype_TriggerRemove
            trigger array   bufftype_TriggerExpire
            trigger array   bufftype_TriggerDispel
            //User defined:
            //
            //
            //
           
        endglobals
       
        function Buff_CreateBuffType takes nothing returns integer
            set buff_NewBuffType = buff_NewBuffType +1
           
            set bufftype_TriggerApply[buff_NewBuffType] = CreateTrigger()
            set bufftype_TriggerStack[buff_NewBuffType] = CreateTrigger()
            set bufftype_TriggerInterval[buff_NewBuffType] = CreateTrigger()
            set bufftype_TriggerRemove[buff_NewBuffType] = CreateTrigger()
            set bufftype_TriggerExpire[buff_NewBuffType] = CreateTrigger()
            set bufftype_TriggerDispel[buff_NewBuffType] = CreateTrigger()
           
            return buff_NewBuffType
        endfunction
       
        function BuffType_SetBuff takes integer bufftype, integer buffAbility, integer buffBuff returns nothing
            set bufftype_Ability[bufftype] = buffAbility
            set bufftype_Buff[bufftype] = buffBuff
        endfunction
       
        //Get the id of the first buff on the given unit of the given bufftype.
        function Buff_GetBuffIndex takes unit target, integer buffType returns integer
            local integer index = LL_Buff_FirstIndex[GetUnitUserData(target)]
            loop
                exitwhen index == 0
                if buff_BuffType[index] == buffType then
                    return index
                endif
                set index = LL_Buff_NextIndex[index]
            endloop
            return 0
        endfunction

        //Count all buffs on the given unit of the specified bufftype (only returns >1 for Parralel buffs ofcourse).
        function Buff_CountBuffsOfType takes unit target, integer buffType returns integer
            local integer index = LL_Buff_FirstIndex[GetUnitUserData(target)]
            local integer count = 0
            loop
                exitwhen index == 0
                if buff_BuffType[index] == buffType then
                    set count = count +1
                endif
                set index = LL_Buff_NextIndex[index]
            endloop
            return count
        endfunction
       
        //Remove the buff from the unit.
        function Buff_Destroy takes integer id returns nothing
            set buff_TriggeredBuff = id
            call TriggerEvaluate(bufftype_TriggerRemove[buff_BuffType[id]])
           
            if bufftype_Ability[buff_BuffType[id]] != 0 then
                call UnitRemoveAbility(buff_Target[id], bufftype_Ability[buff_BuffType[id]])
                call UnitRemoveAbility(buff_Target[id], bufftype_Buff[buff_BuffType[id]])
            endif
           
            call ReleaseTimer(buff_Timer[id])
            call Buff_RemoveIndex(id)
           
            set buff_IsOccupied[id] = false
            set buff_Target[id] = null
            set buff_Source[id] = null
            set buff_BuffType[id] = 0
            set buff_Timer[id] = null
            set buff_Duration[id] = 0.
        endfunction
       
        //Dispel the buff.
        function Buff_Dispell takes integer id returns nothing
            set buff_TriggeredBuff = id
            call TriggerEvaluate(bufftype_TriggerDispel[buff_BuffType[id]])
           
            call Buff_Destroy(id)
        endfunction
       
        //System functions.
        function Buff_Expire takes nothing returns nothing
            local integer id = GetTimerData(GetExpiredTimer())
           
            set buff_TriggeredBuff = id
            call TriggerEvaluate(bufftype_TriggerExpire[buff_BuffType[id]])
            call Buff_Destroy(id)
        endfunction
        function Buff_Interval takes nothing returns nothing
            local integer id = GetTimerData(GetExpiredTimer())
           
            set buff_TriggeredBuff = id
            call TriggerEvaluate(bufftype_TriggerInterval[buff_BuffType[id]])
           
            set buff_Duration[id] = buff_Duration[id] - bufftype_Interval[buff_BuffType[id]]
            if buff_Duration[id] > bufftype_Interval[buff_BuffType[id]] then
                call TimerStart(buff_Timer[id], buff_Duration[id], false, function Buff_Expire)
            endif
        endfunction
       
        //System function.
        function Buff_Create takes unit source, unit target, integer buffType, real duration returns integer
            local integer id = Buff_CreateIndex(GetUnitUserData(target))
            set buff_Source[id] = source
            set buff_Target[id] = target
            set buff_BuffType[id] = buffType
            set buff_Timer[id] = NewTimerEx(id)
            set buff_Duration[id] = duration
           
            if bufftype_Interval[buffType] > 0. then
                call TimerStart(buff_Timer[id], bufftype_Interval[buffType], true, function Buff_Interval)
            else
                call TimerStart(buff_Timer[id], duration, false, function Buff_Expire)
            endif
           
            if bufftype_Ability[buffType] != 0 then
                call UnitAddAbility(buff_Target[id], bufftype_Ability[buffType])
                call UnitMakeAbilityPermanent(buff_Target[id], true, bufftype_Ability[buffType])
            endif
           
            set buff_TriggeredBuff = id
            call TriggerEvaluate(bufftype_TriggerApply[buffType])
           
            //Events still used to block certain classified buffs or change duration/strength.
            set buff_Event_Applied = 1
            set buff_Event_Applied = 0
           
            return id
        endfunction
       
        //Apply a buff on a unit.
        function Buff_Apply takes unit source, unit target, integer buffType, real duration returns integer
            local integer id = Buff_GetBuffIndex(target, buffType)
           
            if id > 0 or bufftype_StackingType[buffType] == STACKING_TYPE_PARALLEL then
                return Buff_Create(source, target, buffType, duration)
               
            elseif bufftype_StackingType[buffType] == STACKING_TYPE_CUSTOM then
                set buff_TriggeredBuff = id
                set buff_EventResponse_Target = target
                set buff_EventResponse_Source = source
                set buff_EventResponse_Bufftype = buffType
                set buff_EventResponse_Duration = duration
                if TriggerEvaluate(bufftype_TriggerStack[buffType]) then
                    return Buff_Create(source, target, buffType, duration)
                endif
            endif
           
            return 0
        endfunction
       
        //Loop through all buffs on a given unit.
        function ForBuff takes unit target, trigger callback returns nothing
            local integer i = LL_Buff_FirstIndex[GetUnitUserData(target)]
            local integer nextIndex
            loop
                exitwhen i == 0
                set nextIndex = LL_Buff_NextIndex[i]
                set buff_TriggeredBuff = i
                call TriggerEvaluate(callback)
                set i = nextIndex
            endloop
        endfunction
       
    endlibrary
    //! runtextmacro CREATE_LINKED_LIST("Buff", "false", "8191")

     


    (This might not be 100% working cause I fiddled with it just now :D)

    Anyway here is a testmap of the system and the required libraries.
    I also attached a second map where the integer interval is implemented.
    All you have to do is
    call ApplyDurationInterval(whichUnit)
    for each unit every time when your round ends.
    I decided not to do it for all buffs at once because you might only want it to do for the units of the player that just finished his round or something else.
    Anyway, you can pick all units on the map and do this.
    It wont matter if that unit does not have any buff at all.
    The example however is not really working on this version any more but it is good to watch how the buff is made (Object Merger).

    You can add classifications like Positive, Negative, Magical, Physical, Level, Dispellable, Undispellable, etc yourself.
    Just make boolean/integer variables of type array for each of the classifications.
    You can set those to true or >0 to change them to whatever you want.

    Dispelling buffs on a unit is done using
    ForBuff()
    where you can
    call Buff_Dispell(id)
    for each buff where Undispellable is false.
    I guess you figured that one out already though :D

    The buffs display a red name by default, however if you know how to add colored text in the object editor, you can make them green or whatever color you want anyway.
    In the rage example, you could try this for example:
    //! runtextmacro CREATE_BUFF_ABILITY("rag", "|cff00ff00Rage|r", "ReplaceableTextures\CommandButtons\BTNAncestralSpirit.blp", "This unit is affected by Rage.")


    Anyway, if there are any questions, just ask.
    (TimerUtils can be removed from the second map ofc.)
     

    Attached Files:

  8. Sephalo

    Sephalo

    Joined:
    Oct 12, 2007
    Messages:
    1,791
    Resources:
    0
    Resources:
    0
    Wow, that's a very good looking system Wietlol. I've been looking at it for a while and I'm impressed by how you work. There are just a few things I have to get used to when I would use it, and I (since I'm not an experienced coder) have to work and study it a bit to understand how I can do specific things.

    Let's say I create a buff that will give a unit +2 agility (added with BonusMod system) and will increase his level of 'Devotion Aura' by 1. (Just a random example).
    What would be a good way to store this stuff then, for when the buff will be removed. I suppose I need an 'onAdd' function and 'onRemove' function that will run when a unit gets the buff or when it will be removed.

    Or hmm..
    Would you maybe like to take a look at my map and just give your opinion on what you think would be smart to add and what not?
     
  9. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,641
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Well, those things are pretty easy.

    The system is provided with a few triggers that are automatically executed when a certain thing happens.

    These are those triggers:
    Code (vJASS):

            trigger array   bufftype_TriggerApply
            trigger array   bufftype_TriggerStack
            trigger array   bufftype_TriggerRemove
            trigger array   bufftype_TriggerExpire
            trigger array   bufftype_TriggerDispel


    These triggers are unique for each bufftype.
    You add an action to such triggers using
    call TriggerAddCondition(t, Filter(function f))

    t should be "bufftype_TriggerApply[id]" or "bufftype_TriggerRemove[id]" or any other one depending on what you need
    f should be the function name that you want to run when that trigger is ran.
    Do note that conditions are used because they are faster but they require the function to return a boolean.

    To sum things up:
    Code (vJASS):
    globals
       
        integer     BuffType_DevotionAuraUpgrade
        integer     DevotionAuraUpgrade_Ability = 'Abuf'
        integer     DevotionAuraUpgrade_Buff = 'Bbuf'
       
    endglobals

    //! runtextmacro CREATE_BUFF_ABILITY("buf", "|cff00ff00Devotion Aura Upgrade|r", "ReplaceableTextures\CommandButtons\BTNDevotion.blp", "This unit's Devotion Aura is more effective.")
    //  call Buff_Apply(source, target, BuffType_DevotionAuraUpgrade, 5)

    function DevotionAuraUpgrade_OnApply takes nothing returns boolean
       
        //Increase level of Devotion Aura for buff_Target[buff_TriggeredBuff]
        call IncUnitAbilityLevel(buff_Target[buff_TriggeredBuff], 'AHad')
       
        //Add 2 agility to buff_Target[buff_TriggeredBuff]
        call DoSomething()
       
        return false
    endfunction
    function DevotionAuraUpgrade_OnRemove takes nothing returns boolean
       
        //Increase level of Devotion Aura for buff_Target[buff_TriggeredBuff]
        call DecUnitAbilityLevel(buff_Target[buff_TriggeredBuff], 'AHad')
       
        //Remove 2 agility to buff_Target[buff_TriggeredBuff]
        call DoSomething()
       
        return false
    endfunction

    //===========================================================================
    function InitTrig_DevotionAuraUpgrade takes nothing returns nothing
        set BuffType_DevotionAuraUpgrade = Buff_CreateBuffType()
       
        set bufftype_Classification_IsPositive[BuffType_DevotionAuraUpgrade] = true
        set bufftype_Classification_IsDispellable[BuffType_DevotionAuraUpgrade] = true
       
        //If you remove this, it will not show a buff but the effect will remain.
        call BuffType_SetBuff(BuffType_DevotionAuraUpgrade, DevotionAuraUpgrade_Ability, DevotionAuraUpgrade_Buff)
       
        //Register buff actions:
        call TriggerAddCondition(bufftype_TriggerApply[BuffType_DevotionAuraUpgrade], Filter(function DevotionAuraUpgrade_OnApply))
        call TriggerAddCondition(bufftype_TriggerRemove[BuffType_DevotionAuraUpgrade], Filter(function DevotionAuraUpgrade_OnRemove))
    endfunction

     


    You call
    call Buff_Apply(source, target, BuffType_DevotionAuraUpgrade, 5)
    to add the devotion aura upgrade to <target> for <5> rounds giving <source> the credits (just because you might need it for some reason).

    Be aware that this system has no in-built stacking options.
    By default, buffs will stack parralel so a unit can have multiple buffs of one type.
    So a unit can have multiple Devotion Aura Upgrade buffs at once causing his agility and devotion aura to become more powerfull each time.
    To avoid this, you can create your own stacking algorithm by adding a condition to the trigger "bufftype_TriggerStack[]".
     
  10. Sephalo

    Sephalo

    Joined:
    Oct 12, 2007
    Messages:
    1,791
    Resources:
    0
    Resources:
    0
    Ah man, amazing Wietlol.
    I'm going to try to work with your system and delve into it to try to understand all. I'll make an example spell myself to see if I get the hang of it. Can I send you my example once I'm done, just to check if I get everything right? :)
    You seriously made my day!
     
  11. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    You can use Muzzel's http://www.hiveworkshop.com/forums/jass-resources-412/system-buffhandler-242121/ system and just add a "counter" as a custom integer parameter to your buffs.

    Then, just cycle through all buffs every round and decrease the counter variable by 1 and if it reaches 0, destroy the buff.

    Muzzel's system is vJass, though. It requires a bit of extra programming knowledge to use it (basicly, your have to extend all your custom buffs from the basic struct like
    struct MyNewbuff extends Buff
    , but it's a lot cleaner than Wietlol's. I recommend looking into it.

    Here's a tutorial on struct extension you might want to read:
    http://www.hiveworkshop.com/forums/...83/vjass-meet-vjass-extending-structs-267719/
     
  12. Sephalo

    Sephalo

    Joined:
    Oct 12, 2007
    Messages:
    1,791
    Resources:
    0
    Resources:
    0
    Well, Wietlol's system works fine for me now.
    I suppose everything could always be done cleaner/faster/better, but at some point you just gotta go with what you've got. I think it's fine for my needs. :)
    But thanks anyway!
     
  13. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Remember that cleaner code usually also means code is less prone to bugs. But yeah, if it works for you, no worries.
     
  14. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,641
    Resources:
    3
    Spells:
    3
    Resources:
    3
    My system is based on Muzzel's.
    He even helped me make it.

    It is practically the same, except that mine doesnt use structs.
     
  15. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    I never said it's bad, didn't I?
     
  16. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,192
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    I haven't really read the thread yet, but my suggestion is that instead of decreasing the "turn" value for each buff, you start off by setting an expiration turn for the buff, which is [current turn] + [buff duration]. Then, you can just compare if [expiration turn] == [current turn], and remove it if so. Here's a simple example on how i would create this system:

    Code (vJASS):

    library BuffSystem requires ListModule
      //link to ListModule: [url]http://www.wc3c.net/showthread.php?t=105486[/url]

      globals
        private hashtable Hash = InitHashtable()
      endglobals

      //I am not sure if you need to save buff count (for stacking buffs), but i added the functionality anyway.

      function GetBuffCount takes unit whichunit, integer whichbuff returns integer
        if HaveSavedInteger(Hash, GetHandleId(whichunit), whichbuff) then
          return LoadInteger(Hash, GetHandleId(whichunit), whichbuff)
        endif
        return 0
      endfunction

      function SetBuffCount takes unit whichunit, integer whichbuff, integer count returns nothing
        if count < 0 then
          call SaveInteger(Hash, GetHandleId(whichunit), whichbuff, 0)
        else
          call SaveInteger(Hash, GetHandleId(whichunit), whichbuff, count)
        endif
      endfunction

      function UnitHasBuff takes unit whichunit, integer whichbuff returns boolean
        //You might think this function is redundant, but i figured it might be handy.
        return GetBuffCount(whichunit, whichbuff) > 0
      endfunction

      struct Buff

        implement List //I use ListModule by Grim001 for this.

        integer expires_at
        integer buff_id
        boolean stacking = false
        unit target

        static method create takes unit whichunit, integer whichbuff, integer duration, boolean stacking returns thistype
          local thistype this = thistype.allocate()
          local integer buffcount = GetBuffCount(whichunit, buffid)+1

          call .listAdd() //Add the struct instance to the list.

          set .target   = whichunit
          set .buff_id  = whichbuff
          set .expires_at   = TURN + duration
          set .stacking = stacking

          call UnitAddAbility(whichunit, whichbuff)
          if stacking then
              call SetUnitAbilityLevel(whichunit, whichbuff, buffcount)
          endif
          call SetBuffCount(whichunit, whichbuff, buffcount)

          return this
        endmethod

        static method unitRemoveAll takes unit whichunit returns nothing
          //Clears all buffs from a unit. Remember to do this when a unit dies.
          local thistype this = .first
          local thistype temp

          loop
              exitwhen this == 0
              set temp = .next

              if .target == whichunit then
              call .destroy()
              endif

              set this == temp
          endloop
        endmethod

        method onDestroy takes nothing returns nothing
          local integer count

          call .listRemove() //Remove the buff from the list.

          if .target != null then
              set count = GetBuffCount(.target, .buff_id)

              if count > 0 then
                  call SetBuffCount(.target, .buff_id, count-1)
              endif

              if .stacking then
                  call SetUnitAbilityLevel(.target, count-1)
              else
                  call UnitRemoveAbility(.target, .buff_id)
              endif
          endif
        endmethod

        static method update takes nothing returns nothing
          //Run this method at the start of every turn.
          local thistype this = .first
          local thistype temp

          loop
              exitwhen this == 0
              set temp = .next

              if .expires_at = TURN then
                  call .destroy()
              endif

              //Here, it is also easy to add different buff events that would fire at the start of the turn.
              //Let me know if you need this.

              set this = temp
          endloop
        endmethod

      endstruct
    endlibrary
     


    Let's say for instance that you want to create a non-stacking buff of type 'A000' and lasting 3 turns for the triggering unit:

    Code (vJASS):

    call Buff.create(GetTriggerUnit(), 'A000', 3, false)
     


    Simple as that. Every turn, you update the buffs using:

    Code (vJASS):

    call Buff.update()
     


    You can also remove all buffs from a unit using:
    Code (vJASS):

    call Buff.removeAll(GetTriggerUnit())
     


    I haven't tried the script yet, and for the moment it is pretty basic. For instance, i haven't added a function for removing a specific buff from a unit, (right now they only expire) but this is very simple. If you are interested, i can modify it to whatever needs you have.

    EDIT: Wow, the browser really fucked up my indentation. I hope it is still readable, might fix it later.
     
    Last edited: Sep 11, 2015
  17. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,641
    Resources:
    3
    Spells:
    3
    Resources:
    3
    @Zwiebelchen
    I never said that you said it's bad, didn't I?

    @Fingolfin
    That method is really nice indeed and I use it for many things with actual gametime but for this case, it doesnt really matter that much.
    You have to check every round if the buff expires anyway.
     
  18. Sephalo

    Sephalo

    Joined:
    Oct 12, 2007
    Messages:
    1,791
    Resources:
    0
    Resources:
    0
    So far the system worked fine for me. I didn't encounter problems (yet).
    I also just didn't start on making a dispel function yet, but I'll get into that later.

    I just changed one small thing:
    If the duration of a buff is set to 0, it will be permanent. If it becomes 0 after going from 1 to 0 (done by the end turn stuff) it will just be removed.
    Works fine so far. :)
     
  19. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,192
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    Well, with the solution Sephalo first suggested, you'd need to both reduce the expiration variable of each buff every turn, aswell as compare it to zero. Now you just need to do the comparision, so at least it is a bit more elegant.

    Are you talking about my script now? In that case, glad that it works. If you want to make a method for removing specific buffs, the principle is the same as when removing all buffs, except you add an extra check for ".buff_id == whichbuff". Here are some other things that can be done to improve the system:

    • It could be nice to create a separate struct for the buff type, containing stuff like duration, ability id, whether it stacks, etc. That way, you could save buff type structs in global variables on initialization, and use them to simplify the input of the Buff.create method. Might be necessary if you want to add a lot of extra data to the buff. If you want, i can write you an example of this.
    • If you want to add a sort of event-like response to each turn, or for when the buff expires, you could use function interfaces (you can read about these in the jasshelper manual). Basically, you are able to save functions in member variables of the struct, and call them, provided that the function has the same parameters/returns as specified in the interface. If you create different functions for different buff types, you can save them as data and call them on the target whenever you wish.