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

Resources That Have Yet To Be Coded

Status
Not open for further replies.
Level 4
Joined
Jul 4, 2012
Messages
78
Nes, i like your ideas and i went for coding Periodic Effects Output but i discovered a lot of problems about it:

1) the tick time of all buffs/effects are not the same, for example a buff ticking every 0.25 secs and another with 1.33 secs can't be summed up with 1 timer...

2) the iterating and gathering of current active effects on a unit is a real problem... though i have a list of solutions for it but i'm not certain about their efficiency...

3) even if we run 1 timer per each buff/effect, although the performance won't be a problem since timers have timeouts of about 0.25 to 2 secs so even 8000 instances won't cost a lot, then their summation will be a problem... a solution for this will be something like an average amount in texttag but users want instant info....

4) if we use 1 timer and do some magic to calculate damage outputs and their coverage and other things and finally sum their effect, one more issues will persist : the cast time of all spell are not all at the first of a second, once the timer ticks, an effect has 0.1 second left to do its damage while the other has 0.4 seconds to do its heal one has 0.9 seconds to be expired while the other has 0.02 second to expire...

there are were other issues which i forgot them :)
 
Level 4
Joined
Jul 4, 2012
Messages
78
lol, again the 'figure it yourself' stuff :) sorry i don't have time for figuring things myself while i'm in in exams season !!!!!! just point me to it and you will have it... just like da old filtering system :D

edit:

look, i went this far in writing the code when problems shown them selves!!!! XDDD this is the result of huge pressure of exams :)

JASS:
    library PeriodicEffectsAlpha
    
    
        globals
        
            private constant integer MAX_INSTANCES = 8191
        
        
        endglobals
        
        
        struct PEA [MAX_INSTANCES]
        
        
        
        
        endstruct
    
    
    endlibrary

EDIT:

there are solutions for all of above, i know, i solved them in my SR Amplify lib, except the issue #4, you can't solve it until messing/altering game play, you don't wanna do that, you want? :) ( imean some
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Each effect has its own timer, as usual. Let effects run themselves.

When an effect wants to display text, don't let it create its own text tag.

Create several channels of different colors and priorities (well, allow the user to create them)

You will have a single timer. Whenever that timer expires, go through the channels and display the numbers inside of them and the colors in 1 text tag (make use of new line)

When an effect adds a number to the timer, that number is simply added

Allow a user to configure channel colors based on the sign of the number

positive
negative

allow a user to configure whether the +/- is shown or not

do not display the channel in the texttag if it's 0


for example, the top health channel can be
positive: +green
negative: -green

mana can be
positive: +blue
negative: -blue


then a resource can take advantage of these channels =). Of course, you will not define any channels yourself!


TextTagPeriodicChannel life = TextTagPeriodicChannel.create(negativeColor, positiveColor, showSign) //constant, no setters/getters


Another resource can then use the life channel


You should probably use the following resources

StaticQueue
TextTag
Alloc


TextTag is just.. awesome : P, lol. You should always use it when working with TextTags. It does a lot of stuff.
 
Level 4
Joined
Jul 4, 2012
Messages
78
in my search for buff/periodic effects, i found a couple of good things (BuffGenerator, Timed Effects, Periodic Effects, etc), so what feature(s) my not yet coded snippet/system will have in addition to those? just texttag?

2) two of the resources you said were graveyard-ed (TextTag and StaticQueue) :) but since they are your resources that won't be a problem, the only issue was that i wasn't successful in finding StaticQueue since in its thread the only thing that was written was : "gone" !!!! :) can you put a link to it?

3) thanks a lot for your hints, you're the best as always :))))
 
Last edited:
Level 4
Joined
Jul 4, 2012
Messages
78
OK, I got needed resources and tutorials .... so:

~RESERVED~

will begin writing as soon as i understand the full functions of modules and structs and other things, [ i got 6 God knows how many lines tutorials from THW and 1 form TH].... i'm deadly serious right now on making this. thank you again Nes, i promise to you that this not yet coded System will PAWN, mark my words. :)

the only thing i need right now is Nestharus' Unit Indexer's latest version, can someone put a link to it or paste it somewhere that i can catch it? ty all.
 
Level 4
Joined
Jul 4, 2012
Messages
78
Nes, here it is, check it out:

JASS:
    library PeriodicEffectsAlpha requires Alloc
    
    
        globals
        
            constant integer PE_TYPE_DAMAGE              = 1
            constant integer PE_TYPE_HEALTH              = 2
            constant integer PE_TYPE_MANA                = 3
            constant integer PE_TYPE_XP                  = 4       // only for heroes.

            constant real    PE_TEXTTAG_TIME_SCALE       = 1.000
            constant real    PE_TEXTTAG_UPDATE_TICK      = 0.25
            constant real    PE_TEXTTAG_FONT_SIZE        = 10.000 *(0.023 / 10.000)
            constant integer PE_TEXTTAG_RULE_HIDE_ALL    = 1
            constant integer PE_TEXTTAG_RULE_SHOW_CHOSEN = 2
            constant integer PE_TEXTTAG_RULE_SHOW_ALL    = 3
            
            constant real    PE_INDEXER_CHECK_TICK       = 5.000
        
        endglobals
        
        module PE_Settings
        
            private static method onInit takes nothing returns nothing
                call thistype.createChannel(1,"|c00FF0000","",true) // damage can't be negative.
                call thistype.createChannel(2,"|c0000FF00","|c0000FF00",true) // for health.
                call thistype.createChannel(3,"|c000000FF","|c000000FF",true) // for mana.
                call thistype.createChannel(4,"|c00FEBA0E","|c00FEBA0E",true) // for experience.
                set texttagRule = PE_TEXTTAG_RULE_SHOW_ALL
            endmethod
        
        endmodule
        
        function PE_TEXTTAG_RULE_SHOW_CHOSEN_FILTER takes unit u returns boolean

            if not IsUnitType(u,UNIT_TYPE_HERO) then
                return false
            endif

            return true
        endfunction

        module PE_SimpleIndexer
    
                    static integer    instanceCount = 0
            private static thistype   recycleStack = 0
            private        thistype   recycleStackNext
            private static unit array indexedUnits

            private static method fetch takes nothing returns thistype
                local thistype this
                if (recycleStack == 0) then
                    set instanceCount = instanceCount + 1
                    set this = instanceCount
                else
                    set this = recycleStack
                    set recycleStack = recycleStack.recycleStackNext
                endif
                return this
            endmethod

            private method release takes nothing returns nothing
                set recycleStackNext = recycleStack
                set recycleStack = this
            endmethod
        
            static method doIndex takes unit u returns integer
                local integer index
                if getIndex(u)==0 or getIndex(u)==null then
                    set index = fetch()
                    call SaveInteger(thistype.table,GetHandleId(u),0,index)
                    set indexedUnits[index] = u
                    if texttagRule==PE_TEXTTAG_RULE_SHOW_CHOSEN then
                        if PE_TEXTTAG_RULE_SHOW_CHOSEN_FILTER(u) then
                            set isActive[index] = true
                        else
                            set isActive[index] = false
                        endif
                    elseif texttagRule==PE_TEXTTAG_RULE_SHOW_ALL then
                        set isActive[index] = true
                    else
                        set isActive[index] = false
                    endif
                    set damageChannels[index] = PE_TYPE_DAMAGE
                    set healthChannels[index] = PE_TYPE_HEALTH
                    set manaChannels[index] = PE_TYPE_MANA
                    set xpChannels[index] = PE_TYPE_XP
                    return index
                else
                    return getIndex(u)
                endif
            endmethod
            
            static method deIndex takes unit u returns nothing
                local integer index = LoadInteger(thistype.table,GetHandleId(u),0)
                call thistype(index).release()
                call FlushChildHashtable(thistype.table,GetHandleId(u))
                set indexedUnits[index] = null
                set isActive[index] = false
                call DestroyTextTag(texttags[index])
                //call BJDebugMsg("deindexing")
            endmethod
            
            static method getIndex takes unit u returns integer
                return LoadInteger(thistype.table,GetHandleId(u),0)
            endmethod
            
            static method getIndexedUnit takes integer i returns unit
                return indexedUnits[i]
            endmethod
            
            private static method doCheck takes nothing returns nothing
                local thistype this
                local integer counter = 1
                local group temporaryGroup = CreateGroup()
                loop
                    set this = thistype(counter)
                    exitwhen .whatType==0
                        if .targets!=null then
                            call addGroupToGroup(.targets,temporaryGroup,false,false)
                        endif
                        set counter = counter + 1
                endloop
                set counter = 1
                loop
                    exitwhen counter>instanceCount
                        if IsUnitInGroup(indexedUnits[counter],temporaryGroup)==false then
                            call deIndex(indexedUnits[counter])
                        endif
                        set counter = counter + 1
                endloop
                set counter = 0
                set this = 0
                call DestroyGroup(temporaryGroup)
            endmethod
            
            private static method onInit takes nothing returns nothing
                local timer t = CreateTimer()
                call TimerStart(t,PE_INDEXER_CHECK_TICK,true,function thistype.doCheck)
            endmethod

        endmodule
        
        struct PEA extends array
        
        //  -------------------- Main --------------------
        
            private static hashtable     table
            
                           unit          sourceUnit // only needed when using damage effect for defining the damager.
            
                           real          timeout
                           real          amount
            
                           integer       totalTicks
            private        integer       currentTicks
                           integer       maxTargets
            private        integer       whatType
            
            private        boolean       haveModel
            private        boolean       haveTrigger
            private        boolean       isPaused
            private        boolean       destroyOnEnd
            
            private        string        modelPath
            private        string        modelAttachmentPoint
            
                           damagetype    damageType
                           attacktype    attackType
            
            private        timer         effectTimer
            
            private        group         targets
            
            private        trigger       sideTrigger
            
            private static code          mainCode
            
        //  -------------------- Channels --------------------
            
            private static integer array damageChannels
            private static integer array healthChannels
            private static integer array manaChannels
            private static integer array xpChannels
            
            private static string  array positiveColors
            private static string  array negativeColors
            
            private static boolean array showSigns

        //  -------------------- Texttag --------------------
        
                    static integer       texttagRule
                                
            private static boolean array isActive
            
            private static timer         texttagTimer
            
            private static texttag array texttags

            private static code          texttagCode
            
        //  -------------------- Script begins --------------------
            
            implement Alloc
            
            implement PE_SimpleIndexer
            
                static method getDamageStack takes unit target returns real
                    return LoadReal(table,GetHandleId(target),PE_TYPE_DAMAGE)
                endmethod
                
                static method getHealthStack takes unit target returns real
                    return LoadReal(table,GetHandleId(target),PE_TYPE_HEALTH)
                endmethod
                
                static method getManaStack takes unit target returns real
                    return LoadReal(table,GetHandleId(target),PE_TYPE_MANA)
                endmethod
                
                static method getXPStack takes unit target returns real
                    return LoadReal(table,GetHandleId(target),PE_TYPE_XP)
                endmethod
            
                static method changeChannelsForUnit takes unit desiredUnit, integer damageID, integer healthID, integer manaID, integer xpID returns nothing
                    local integer index = getIndex(desiredUnit)
                    set damageChannels[index] = damageID
                    set healthChannels[index] = healthID
                    set manaChannels[index] = manaID
                    set xpChannels[index] = xpID
                    set index = 0
                endmethod
                
                static method changeChannelsForGroup takes group desiredGroup, integer damageID, integer healthID, integer manaID, integer xpID returns nothing
                    local group tempGroup = CreateGroup()
                    local unit firstUnit
                    call addGroupToGroup(desiredGroup,tempGroup,false,false)
                    loop
                        set firstUnit = FirstOfGroup(tempGroup)
                        exitwhen firstUnit==null
                            call changeChannelsForUnit(firstUnit,damageID,healthID,manaID,xpID)
                            call GroupRemoveUnit(tempGroup,firstUnit)
                    endloop
                endmethod
            
                static method createChannel takes integer desiredID, string positiveColor, string negativeColor, boolean showSign returns nothing
                    set positiveColors[desiredID] = positiveColor
                    set negativeColors[desiredID] = negativeColor
                    set showSigns[desiredID] = showSign
                endmethod
            
                method useDefaults takes nothing returns nothing
                    set .timeout = 0.000
                    set .amount = 0.000
                    set .totalTicks = 0
                    set .currentTicks = 0
                    set .maxTargets = 100000
                    set .haveModel = false
                    set .haveTrigger = false
                    set .isPaused = true
                    set .destroyOnEnd = true
                    set .damageType = DAMAGE_TYPE_NORMAL
                    set .attackType = ATTACK_TYPE_NORMAL
                endmethod
        
                static method createEffect takes integer typeInteger returns thistype
                    local thistype this
                    debug if typeInteger!= PE_TYPE_DAMAGE and typeInteger!= PE_TYPE_HEALTH and typeInteger!= PE_TYPE_MANA and typeInteger!= PE_TYPE_XP then
                        debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"|cffff0000Periodic Effects: Given effect type does not exist!")
                    debug endif
                    set this = thistype.allocate()
                    set this.targets = CreateGroup()
                    set this.whatType = typeInteger
                    call this.useDefaults()
                    return this
                endmethod
                
                method setModel takes string path, string attachmentPoint returns nothing
                    set .haveModel = true
                    set .modelPath = path
                    set .modelAttachmentPoint = attachmentPoint
                endmethod
                
                method setTrigger takes trigger userTrigger returns nothing
                    set .haveTrigger = true
                    set .sideTrigger = userTrigger
                endmethod
                
                private static method addGroupToGroup takes group who, group target, boolean isSubstract, boolean doIndexing returns nothing
                    local group tempGroup = CreateGroup()
                    local unit firstUnit
                    loop
                        set firstUnit = FirstOfGroup(who)
                        exitwhen firstUnit==null
                            call GroupAddUnit(tempGroup, firstUnit)
                            call GroupRemoveUnit(who, firstUnit)
                    endloop
                    loop
                        set firstUnit = FirstOfGroup(tempGroup)
                        exitwhen firstUnit==null
                            if isSubstract then
                                call GroupRemoveUnit(target,firstUnit)
                            else
                                call GroupAddUnit(target, firstUnit)
                                if doIndexing then
                                    call thistype.doIndex(firstUnit)
                                endif
                            endif
                            call GroupAddUnit(who, firstUnit)
                            call GroupRemoveUnit(tempGroup, firstUnit)
                    endloop
                    call DestroyGroup(tempGroup)
                    set firstUnit = null
                endmethod
                
                method addUnitToTargets takes unit who returns nothing
                    call GroupAddUnit(.targets, who)
                    call thistype.doIndex(who)
                endmethod
                
                method addGroupToTargets takes group who returns nothing
                    call addGroupToGroup(who,.targets,false,true)
                endmethod
                
                method removeUnitFromTargets takes unit who returns nothing
                    call GroupRemoveUnit(.targets, who)
                endmethod

                method removeGroupFromTargets takes group who returns nothing
                    call addGroupToGroup(who,.targets,true,true)
                endmethod
                
                private method processTimer takes nothing returns nothing
                    set .effectTimer = CreateTimer()
                    call SaveInteger(table,GetHandleId(.effectTimer),0,this)
                    call TimerStart(.effectTimer, .timeout, true, mainCode)
                endmethod
                
                method startEffect takes nothing returns nothing
                    set .isPaused = false
                    call .processTimer()
                endmethod
                
                method pauseEffect takes nothing returns nothing
                    set .isPaused = true
                    call PauseTimer(.effectTimer)
                endmethod
                
                method resumeEffect takes nothing returns nothing
                    set .isPaused = false
                    call ResumeTimer(.effectTimer)
                endmethod
                
                method restartEffect takes nothing returns nothing
                    set .isPaused = false
                    set .currentTicks = 0
                    call PauseTimer(.effectTimer)
                    call DestroyTimer(.effectTimer)
                    call .processTimer()
                endmethod
                
                method destroyEffect takes nothing returns nothing
                    call .pauseEffect()
                    call FlushChildHashtable(table,GetHandleId(.effectTimer))
                    call DestroyTimer(.effectTimer)
                    //call BJDebugMsg("calling deallocate3")
                    call .useDefaults()
                    set .sideTrigger = null
                    call .deallocate()
                endmethod
                
                method duplicateEffect takes nothing returns thistype
                    local thistype newEffect = thistype.createEffect(this.whatType)
                    set newEffect.amount = this.amount
                    set newEffect.timeout = this.timeout
                    set newEffect.totalTicks = this.totalTicks
                    set newEffect.maxTargets = this.maxTargets
                    set newEffect.haveModel = this.haveModel
                    if newEffect.haveModel then
                        set newEffect.modelPath = this.modelPath
                        set newEffect.modelAttachmentPoint = this.modelAttachmentPoint
                    endif
                    set newEffect.haveTrigger = this.haveTrigger
                    if newEffect.haveTrigger then
                        set newEffect.sideTrigger = this.sideTrigger
                    endif
                    if newEffect.whatType==1 then
                        set newEffect.damageType = this.damageType
                        set newEffect.attackType = this.attackType
                    endif
                    return newEffect
                endmethod
                
                method applyToUnit takes unit who returns thistype
                    local thistype freshEffect = .duplicateEffect()
                    call freshEffect.addUnitToTargets(who)
                    call freshEffect.startEffect()
                    return freshEffect
                endmethod
                
                method applyToGroup takes group who returns thistype
                    local thistype freshEffect = .duplicateEffect()
                    call freshEffect.addGroupToTargets(who)
                    call freshEffect.startEffect()
                    return freshEffect
                endmethod
                
                private static method texttagEngine takes nothing returns nothing
                    local thistype this
                    local integer counter = 1
                    local integer index
                    local group holderGroup = CreateGroup()
                    local group finalGroup = CreateGroup()
                    local unit firstUnit
                    local string tempString = ""
                    local real array damageStack
                    local real array healthStack
                    local real array manaStack
                    local real array xpStack
                    if texttagRule!=PE_TEXTTAG_RULE_HIDE_ALL then
                        loop
                            set this = thistype(counter)
                            exitwhen this.whatType==0
                                call GroupClear(holderGroup)
                                call addGroupToGroup(this.targets,holderGroup,false,false)
                                loop
                                    set firstUnit = FirstOfGroup(holderGroup)
                                    exitwhen firstUnit==null
                                        set index = getIndex(firstUnit)
                                        if isActive[index] then
                                            if not IsUnitInGroup(firstUnit,finalGroup) then
                                                call GroupAddUnit(finalGroup,firstUnit)
                                            endif
                                            if not .isPaused then
                                                if .whatType==PE_TYPE_DAMAGE then
                                                    if .amount>0 then
                                                        set damageStack[index] = damageStack[index] + .amount*(PE_TEXTTAG_TIME_SCALE/.timeout)
                                                    endif
                                                elseif .whatType==PE_TYPE_HEALTH then
                                                    set healthStack[index] = healthStack[index] + .amount*(PE_TEXTTAG_TIME_SCALE/.timeout)
                                                elseif .whatType==PE_TYPE_MANA then
                                                    set manaStack[index] = manaStack[index] + .amount*(PE_TEXTTAG_TIME_SCALE/.timeout)
                                                elseif .whatType==PE_TYPE_XP then
                                                    set xpStack[index] = xpStack[index] + R2I(.amount*(PE_TEXTTAG_TIME_SCALE/.timeout))
                                                endif
                                            elseif .isPaused and .currentTicks==.totalTicks then
                                                call DestroyGroup(.targets)
                                            endif
                                            call GroupRemoveUnit(holderGroup,firstUnit)
                                        endif
                                endloop
                                set counter = counter + 1
                        endloop
                    endif
                    set index = 0
                    loop
                        set firstUnit = FirstOfGroup(finalGroup)
                        exitwhen firstUnit==null
                            set index = getIndex(firstUnit)
                            set tempString = ""
                            //call BJDebugMsg("doing texttag for "+ GetUnitName(firstUnit) + " ("+I2S(index)+")")
                            if damageStack[index]!=0 then
                                set tempString = positiveColors[damageChannels[index]]
                                if showSigns[damageChannels[index]] then
                                    set tempString = tempString + "!"
                                endif
                                set tempString = tempString + R2SW(damageStack[index],5,1) + "|n|r"
                                //call BJDebugMsg("tempString is: " + tempString)
                            else
                                //call BJDebugMsg("damage stack was 0!")
                            endif
                            if healthStack[index]!=0 then
                                if healthStack[index]>0 then
                                    set tempString = tempString + positiveColors[healthChannels[index]]
                                    if showSigns[healthChannels[index]] then
                                        set tempString = tempString + "+ "
                                    endif
                                else
                                    set tempString = tempString + negativeColors[healthChannels[index]]
                                    if showSigns[healthChannels[index]] then
                                        set tempString = tempString + "- "
                                    endif
                                endif
                                set tempString = tempString + R2SW(healthStack[index],7,1) + "|n|r"
                            endif
                            if manaStack[index]!=0 then
                                if manaStack[index]>0 then
                                    set tempString = tempString + positiveColors[manaChannels[index]]
                                    if showSigns[manaChannels[index]] then
                                        set tempString = tempString + "+ "
                                    endif
                                else
                                    set tempString = tempString + negativeColors[manaChannels[index]]
                                    if showSigns[manaChannels[index]] then
                                        set tempString = tempString + "- "
                                    endif
                                endif
                                set tempString = tempString + R2SW(manaStack[index],7,1) + "|n|r"
                            endif
                            if xpStack[index]!=0 then
                                if xpStack[index]>0 then
                                    set tempString = tempString + positiveColors[xpChannels[index]]
                                    if showSigns[xpChannels[index]] then
                                        set tempString = tempString + "+ "
                                    endif
                                else
                                    set tempString = tempString + negativeColors[manaChannels[index]]
                                    if showSigns[xpChannels[index]] then
                                        set tempString = tempString + "- "
                                    endif
                                endif
                                set tempString = tempString + R2SW(xpStack[index],7,1) + "|n|r"
                            endif
                            //call BJDebugMsg(I2S(StringLength(tempString)))
                            if StringLength(tempString)<1 then
                                call DestroyTextTag(texttags[index])
                                set isActive[index] = false
                                //call BJDebugMsg("Destroying texttag")
                            endif
                            if texttags[index]==null and isActive[index]==true then
                                set texttags[index] = CreateTextTag()
                                //call BJDebugMsg("Creating Texttag for index: "+I2S(index))
                            endif
                            if isActive[index]==true then
                                call SetTextTagPos(texttags[index],GetUnitX(firstUnit),GetUnitY(firstUnit),100)
                                call SetTextTagText(texttags[index],tempString,PE_TEXTTAG_FONT_SIZE)
                                call SetTextTagColor(texttags[index],255,255,255,255)
                            endif
                            call SaveReal(table,GetHandleId(firstUnit),PE_TYPE_DAMAGE,damageStack[index])
                            call SaveReal(table,GetHandleId(firstUnit),PE_TYPE_HEALTH,healthStack[index])
                            call SaveReal(table,GetHandleId(firstUnit),PE_TYPE_MANA,manaStack[index])
                            call SaveReal(table,GetHandleId(firstUnit),PE_TYPE_XP,xpStack[index])
                            call GroupRemoveUnit(finalGroup,firstUnit)
                    endloop
                    set counter = 0
                    set index = 0
                    set this = 0
                    set firstUnit = null
                    set tempString = null
                    call DestroyGroup(holderGroup)
                    call DestroyGroup(finalGroup)
                    //call BJDebugMsg("Ending texttag engine")
                    loop
                        exitwhen counter>thistype.instanceCount
                            set damageStack[counter] = 0.000
                            set healthStack[counter] = 0.000
                            set manaStack[counter] = 0.000
                            set xpStack[counter] = 0.000
                            set counter = counter + 1
                    endloop
                    set counter = 0
                endmethod
                
                private static method mainEngine takes nothing returns nothing
                    local thistype this = LoadInteger(table,GetHandleId(GetExpiredTimer()),0)
                    local integer counter = 0
                    local group tempGroup = CreateGroup()
                    local unit firstUnit
                    if .isPaused then
                        set this = 0
                        call DestroyGroup(tempGroup)
                        return
                    endif
                    set .currentTicks = .currentTicks + 1
                    call addGroupToGroup(.targets,tempGroup,false,false)
                    if .whatType==PE_TYPE_DAMAGE then
                        loop
                            set firstUnit = FirstOfGroup(tempGroup)
                            exitwhen firstUnit==null or counter>.maxTargets
                                if GetWidgetLife(firstUnit)>0.405 then
                                    call UnitDamageTarget(.sourceUnit,firstUnit,.amount,true,false,.attackType,.damageType,WEAPON_TYPE_WHOKNOWS)
                                    if .haveModel then
                                        call DestroyEffect(AddSpecialEffectTarget(.modelPath,firstUnit,.modelAttachmentPoint))
                                    endif
                                    set counter = counter + 1
                                endif
                                call GroupRemoveUnit(tempGroup,firstUnit)
                        endloop
                    elseif .whatType==PE_TYPE_HEALTH then
                        loop
                            set firstUnit = FirstOfGroup(tempGroup)
                            exitwhen firstUnit==null or counter>.maxTargets
                                if GetWidgetLife(firstUnit)>0.405 then
                                    call SetWidgetLife(firstUnit,GetWidgetLife(firstUnit)+.amount)
                                    if .haveModel then
                                        call DestroyEffect(AddSpecialEffectTarget(.modelPath,firstUnit,.modelAttachmentPoint))
                                    endif
                                    set counter = counter + 1
                                endif
                                call GroupRemoveUnit(tempGroup,firstUnit)
                        endloop
                    elseif .whatType==PE_TYPE_MANA then
                        loop
                            set firstUnit = FirstOfGroup(tempGroup)
                            exitwhen firstUnit==null or counter>.maxTargets
                            if GetWidgetLife(firstUnit)>0.405 then
                                call SetUnitState(firstUnit,UNIT_STATE_MANA,GetUnitState(firstUnit,UNIT_STATE_MANA)+.amount)
                                if .haveModel then
                                        call DestroyEffect(AddSpecialEffectTarget(.modelPath,firstUnit,.modelAttachmentPoint))
                                    endif
                                set counter = counter + 1
                            endif
                            call GroupRemoveUnit(tempGroup,firstUnit)
                        endloop
                    elseif .whatType==PE_TYPE_XP then
                        loop
                            set firstUnit = FirstOfGroup(tempGroup)
                            exitwhen firstUnit==null or counter>.maxTargets
                                if IsUnitType(firstUnit,UNIT_TYPE_HERO) and GetWidgetLife(firstUnit)>0.405 then
                                    call AddHeroXP(firstUnit,R2I(.amount),false)
                                    if .haveModel then
                                        call DestroyEffect(AddSpecialEffectTarget(.modelPath,firstUnit,.modelAttachmentPoint))
                                    endif
                                    set counter = counter + 1
                                endif
                                call GroupRemoveUnit(tempGroup,firstUnit)
                        endloop
                    endif
                    if .haveTrigger then
                        call TriggerExecute(.sideTrigger)
                    endif
                    //end
                    if .currentTicks==.totalTicks then
                        if .destroyOnEnd then
                            call .destroyEffect()
                        else
                            call .pauseEffect()
                        endif
                    endif
                endmethod
                
                private static method onInit takes nothing returns nothing
                    set table = InitHashtable()
                    set mainCode = (function thistype.mainEngine)
                    set thistype.texttagCode = (function thistype.texttagEngine)
                    set thistype.texttagTimer = CreateTimer()
                    call TimerStart(thistype.texttagTimer,PE_TEXTTAG_UPDATE_TICK,true,thistype.texttagCode)
                endmethod
                
                implement PE_Settings
        
        endstruct
    
    
    endlibrary
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
In regard to ItemIndexer - don't say you haven't known about TriggerRegisterDeathEvent as a item-deindex method because you have. You could just forgot. Just to remind: AII. This is almost 3 years old. There were multiple scripts before, yet finding the "founder" is not the matter.

Now, last time I was looking at item indexing Nes brought "monster drop" issue.
And as it usually happens in wc3, we got a problem. Items spawned in such way are undetectable via native or events. However, there is an option.

Game doesn't give you an option to retrieve such items, yet it doesn't allow you to modify it's spawn point either. What's the conclusion? Registering unit-death event allows us to scan area nearby such unit and filter items. Issue? May be inaccurate.

But before you click "replay" let's look on description below:
- we can't modify item's position -> item should drop at unit's x, y
- how to handle situation when multiple items spawn from a single mob?
- why may it be inaccurate? Unit is a flyer; area around unit is not to be walked on - items will be spawned in remote locations

<I haven't found if in-game item pool for given monster has a limit, I gave up after inserting 150 or so, if someone got some additional info, would be nice to share>

From my research I've found that items dropped by monsters are spawned in quadratic-style: 4 items -> 2x2 square, 100 items -> 10x10 square. Now, to determinate area we could create a rect where given side is NxM long (where N is number of items per side (square root of total number of items) and M determinates space occupied by single item). After some tests M was determinated: integer of value 32. You can quite easily test this yourself. This does not include items with changed size (yet the bigger the item is, the easier detection of such becomes, whatmore even if it's smaller, we gonna take M=32 as constant anyway, thus we could detect even more items within area).
The M=32 taken from multiple tests seems to be proven by WidgetDropItem native:
JASS:
function WidgetDropItem takes widget inWidget, integer inItemID returns item
    local real x
    local real y
    local real radius = 32
    local real widgetX
    local real widgetY

    if (inItemID == -1) then
        return null
    endif

    set widgetX = GetWidgetX(inWidget)
    set widgetY = GetWidgetY(inWidget)

    set x = GetRandomReal(widgetX - radius, widgetX + radius)
    set y = GetRandomReal(widgetY - radius, widgetY + radius)

    return CreateItem(inItemID, x, y)
endfunction

Now, take a look at picture below:
2m2wfmu.png

Our future algorithm could at first check item within small region closest to dying unit (red square), then, if "not-indexed" items were found, iterate through greater area excluding previously checked regions. Those 4 additional regions present such search pattern: if "new item" has been found within red rect, let's search regions nearby until no "new items" has been noticed.
If, for example user determines (via constant) highest possible drop amount we cound iterate through rects, with first one having size of 64x64 (4 items). Scanning within smaller rects is pointless.

Finally, why it's inaccurate?
Unit could be a flyer, whatmore if one would drop 1337 items within not pathable area you get spaghetti of areas instead of quadratic model. After considering this two simple facts, (which are probably not the only possible outcomes) searching for items dropped by monsters is pointless in system such as ItemIndexer - without accurancy it's useless resource.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Level 19
Joined
Mar 18, 2012
Messages
1,716
Do we need something like the "Stun" library, but useable for all kind of buffs you wish to apply to a unit?
Or would it be better to submitt a snippet, which covers those two:
1. Apply any timed buff ( stun, faerie fire, cripple, ...
2. Dumy casting system ( including channeling spells )

1 requires 2 anyways, so it's a good synergy.

If yes, then I need a good library name ( SpellCaster, Spell, BuffEx, DummyCast, ... )
--> Names not availbale: DummyCaster, Buff ( should be a real buff system )
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
For buffs, the best way I have figured out is having a single extendable library that provides methods to create buffs and add effects to them.
Buffs themselves should do nothing. What a buff does should entirely be defined by a list of buff effects. Everything from displaying an SFX to stacking rules would be done with effects. Heck, you could even have effects that dummy cast object editor abilities on units.
Such an approach provides the most versatility and extendability. (Yes, I have coded one)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Can you link your buff library?

Does it handle stacked buffs and overwriting buffs that already exist if the level is higher? Is there an option for the lower buff to return when the new buff fades, like with auras?

Also, how are the buff effects run? Periodic vs Instant. On buff, on debuff, etc.

AuraStruct accomplishes a lot of this, but it's made specifically for auras, heh.

Tx.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Can you link your buff library?

Does it handle stacked buffs and overwriting buffs that already exist if the level is higher? Is there an option for the lower buff to return when the new buff fades, like with auras?

Also, how are the buff effects run? Periodic vs Instant. On buff, on debuff, etc.

I've not made a public version of my buff library, so I can't really link it as a resource. I'll attach a map of mine that uses it.

The system itself handles only a few things. Each unit has a doubly linked list containing its buffs. Each buff has a similar list containing its effects. Both lists also have a counter for easier iteration.
The base system also handles unique ID generation for buffs and effects.
Each buff effect can have a variety of methods:
Apply - When the parent buff is applied to a unit.
Clear - When parent buff is removed from unit
Construct - When effect is added to buff.
Destruct - When effect is removed from buff.
The type off a buff is set during its creation and has no other interaction with the core system. It is in no way related to what effects the buff has, except what the user implements.
Buffs can be created and effects added to them without the buff applying to any unit. They can also be removed from units without destroying them(think of spellsteal to understand why).
Effects have a type and all their methods are attached to the type, not the instance.

What exactly a buff does is defined by what methods it's added upon creation. Multiple effects of the same type can be added to the same buff.
Current methods:
EffectSfx - Creates a special effect on target unit.
EffectSpeed - Modifies the speed of target unit.
EffectTimeout - Destroys the buff a certain time after it has been applied to a unit. Transferring the buff resets the timer.
EffectUnique - Removes any buffs of the same type when this effect is applied to a unit.
EffectHoT - Heals target unit over time.
EffectDoT - Damages target unit over time.
EffectDoR - Deals damage to parent unit when the buff is removed from it for any reason.
EffectRoD - Destroys the buff when the target takes damage.
EffectStun - Stuns the target of the buff until the buff wears off. Handles stacking properly as defined by how they work in melee.
EffectMoT - Restores mana on the unit over time.

Creating new effects is fairly easy as the complexities of applying effects to buffs and units are abstracted away. This keeps the core system safe from feature creep and lets users easily define how exactly they want to use the system.

Currently the system is in heavy development, far from being finished(although usable) and the code may be messy. It's still superior(for my use case) to any other buff system that I've encountered including muzzel's, because:
1. It allows defining the exact effects of a buff separate from its type. This makes it possible to have buffs that have effects based on external conditions, such as the attributes of the casting unit or other abilities present.
2. It can be made to include the functionality of nearly all object editor spell buffs without taking excessive time for any single one.
3. It's easy to implement and extend.
 

Attachments

  • Brute Wars 2.06.w3x
    689.6 KB · Views: 76
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657

Resources that have yet to be coded

Item Indexer
______________________________
No periodic loop

What is wrong with this one?

EDIT:
JASS:
globals
    
    item array      udg_ItemIndex_Items
    integer         udg_ItemIndex_NextIndex             = 0
    
endglobals

function CreateItemIndex takes item whichItem returns nothing
    
    loop
        set udg_ItemIndex_NextIndex = udg_ItemIndex_NextIndex +1
        
        exitwhen udg_ItemIndex_Items[udg_ItemIndex_NextIndex] == null
        
        if udg_ItemIndex_NextIndex >= 8191 then
            set udg_ItemIndex_NextIndex = 0
        endif
    endloop
    
    set udg_ItemIndex_Items[udg_ItemIndex_NextIndex] = whichItem
    call SetItemUserData(whichItem, udg_ItemIndex_NextIndex)
    
endfunction

function RemoveItemIndex takes item whichItem returns nothing
    set udg_II_AntiLoop = true
    set udg_ItemIndex_Items[GetItemUserData(whichItem)] = null
    call SetItemUserData(whichItem, 0)
    set udg_II_AntiLoop = false
endfunction


function SecureItemIndex takes item whichItem returns integer
    if udg_II_AntiLoop then
        return 0
    endif
    
    set udg_II_AntiLoop = true
    if GetItemUserData(whichItem) == 0 then
        call ItemIndex_CreateIndex(whichItem)
        return udg_ItemIndex_NextIndex
    endif
    set udg_II_AntiLoop = false
    return 0
endfunction
hook GetItemUserData SecureItemIndex

JASS:
globals
    
    item array      udg_ItemIndex_Items
    integer         udg_ItemIndex_NextIndex             = 0
    
endglobals

function CreateItemIndex takes item whichItem returns nothing
    
    loop
        set udg_ItemIndex_NextIndex = udg_ItemIndex_NextIndex +1
        
        exitwhen udg_ItemIndex_Items[udg_ItemIndex_NextIndex] == null
        
        if udg_ItemIndex_NextIndex >= 8191 then
            set udg_ItemIndex_NextIndex = 0
        endif
    endloop
    
    set udg_ItemIndex_Items[udg_ItemIndex_NextIndex] = whichItem
    call SetItemUserData(whichItem, udg_ItemIndex_NextIndex)
    
endfunction

function RemoveItemIndex takes item whichItem returns nothing
    set udg_ItemIndex_Items[GetItemUserData(whichItem)] = null
    call SetItemUserData(whichItem, 0)
endfunction

function GetItemIndex takes item whichItem returns integer
    local integer id = GetItemUserData(whichItem)
    
    if id == 0 then
        call ItemIndex_CreateIndex(whichItem)
        return udg_ItemIndex_NextIndex
    endif
    
    return id
endfunction

Still gotta do de-indexing.
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I was looking for Xonok's, not Muzzel's ; ).


I can't even read Muzzel's API at a glance, so I haven't/won't look at his : P. I did read some of the code though and it doesn't seem to do the stacking etc, which is the most complex part of buffs =).
Wow, dat arrogance... But just to get the facts straight: Yes, BuffHandler allows stacking. It's literally the default setting for any buff created.

It can actually be used to do all the timed WC3 hardcoded buffs aswell. You just have a dummy caster apply it with a 0 duration in onApply and remove it in onRemove if number of buffs of same type <= 1.

I used Muzzel's buff to replicate all the WC3 internal buffs in a coded fashion with full functionality (stealing buffs, dispelling buffs, etc.).

So, no, I don't think a buff system belongs to the list of "resources that have yet to be coded". Because that's pretty much solved already.

The system itself handles only a few things. Each unit has a doubly linked list containing its buffs. Each buff has a similar list containing its effects. Both lists also have a counter for easier iteration.
The base system also handles unique ID generation for buffs and effects.
Each buff effect can have a variety of methods:
Apply - When the parent buff is applied to a unit.
Clear - When parent buff is removed from unit
Construct - When effect is added to buff.
Destruct - When effect is removed from buff.
The type off a buff is set during its creation and has no other interaction with the core system. It is in no way related to what effects the buff has, except what the user implements.
Buffs can be created and effects added to them without the buff applying to any unit. They can also be removed from units without destroying them(think of spellsteal to understand why).
Effects have a type and all their methods are attached to the type, not the instance.
BuffHandler can do all this just fine.

What exactly a buff does is defined by what methods it's added upon creation. Multiple effects of the same type can be added to the same buff.
Current methods:
EffectSfx - Creates a special effect on target unit.
EffectSpeed - Modifies the speed of target unit.
EffectTimeout - Destroys the buff a certain time after it has been applied to a unit. Transferring the buff resets the timer.
EffectUnique - Removes any buffs of the same type when this effect is applied to a unit.
EffectHoT - Heals target unit over time.
EffectDoT - Damages target unit over time.
EffectDoR - Deals damage to parent unit when the buff is removed from it for any reason.
EffectRoD - Destroys the buff when the target takes damage.
EffectStun - Stuns the target of the buff until the buff wears off. Handles stacking properly as defined by how they work in melee.
EffectMoT - Restores mana on the unit over time.

Creating new effects is fairly easy as the complexities of applying effects to buffs and units are abstracted away. This keeps the core system safe from feature creep and lets users easily define how exactly they want to use the system.
I don't think all this should actually be part of the buff system itself. The buff system should be the framework, not providing any gameplay effects directly. For this, you should write an extension that builds on your buff system.
Hardcoding such stuff is just a bad practice, because people might want to use different libraries to apply f.ex. stuns or modify the speed of units. This is something a bonus mod should do, not a buff struct. It's what you have methods like onApply, periodic or onDamage for.

Currently the system is in heavy development, far from being finished(although usable) and the code may be messy. It's still superior(for my use case) to any other buff system that I've encountered including muzzel's, because:
1. It allows defining the exact effects of a buff separate from its type. This makes it possible to have buffs that have effects based on external conditions, such as the attributes of the casting unit or other abilities present.
2. It can be made to include the functionality of nearly all object editor spell buffs without taking excessive time for any single one.
3. It's easy to implement and extend.
1. Muzzel's buff can do all this without any hardcoded functionality like in your solution; just add a custom struct member for the casting unit and pass it to the struct on buff creation. Done. BuffHandler allows complete freedom about what parameters you want on each buff and what not, as it makes use of extends to create a buff struct from the base struct.

2. This is basicly a one-liner in Muzzel's buff aswell:

onApply: move dummy to buff owner, cast hardcoded buff with 0 duration.
onRemove: if count number of buffs of thistype <= 1 then remove buff.
I don't see any reason why this should be hardcoded.

3. I don't get how it can be any easier than
struct BuffXYZ extends Buff

I like the effort you want to put into that Xonok, but I think you should take another look into BuffHandler just to confirm if your work can actually improve on that. After testing BuffHandler thoroughly, it literally leaves nothing to desire except for some convenience wrappers (that I posted in the thread).
But then your library can also just provide these wrappers instead of repeating all the work that has been done before.

I highly suggest you actually build your extension module on BuffHandler, simply because it provides all the tools to create your extension and we don't want another community split on systems used. It's bad enough that we have 3 unit indexers.
And from your description about your buff system, the internal structure is actually pretty similar, so there wouldn't even be a speed advantage.
 
Last edited:
Level 21
Joined
Mar 27, 2012
Messages
3,232
You didn't seem to realize that the listed effects are separate from the core system.

1. It can technically not do what my system can, because the effects of a buff are defined by type as opposed to being defined on runtime like I've done.
2. It requires AIDS and TimerUtils, which I don't use or need.
3. Some other stuff that I don't have time to explain atm.

Also, I started development before muzzel's system became public and prefer to write my systems myself whenever possible. I've been designing my system for ages. In any case, I will not use a system that uses structs. It's a huge no-no for me to use a system that obfuscates code without any performance gain.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Bad documentation is bad documentation, lol. If you can't tell the API at a glance, then the documentation sucks ; P.
Somehow I could tell how it works with just the blink of an eye.
After working with it for quite some time (from a mappers point of view... I see how that can confuse people at times, that are use to code for the sake of it), I found the API to be extremely intuitive.

Just because the API isn't your taste doesn't mean it's bad.

You didn't seem to realize that the listed effects are separate from the core system.

1. It can technically not do what my system can, because the effects of a buff are defined by type as opposed to being defined on runtime like I've done.
2. It requires AIDS and TimerUtils, which I don't use or need.
3. Some other stuff that I don't have time to explain atm.
1. Yeah but let's be honest here... why would you ever need that? It makes sense that buff effects are defined by the buff type. If you want different buff groups, you can always create a middleman-buff and extend your sub-buffs from that. So basicly BuffVenom extends BuffDoT extends Buff.

2. This is legit. Then again, changing or removing dependencies is a matter of minutes

Also, I started development before muzzel's system became public and prefer to write my systems myself whenever possible. I've been designing my system for ages. In any case, I will not use a system that uses structs. It's a huge no-no for me to use a system that obfuscates code without any performance gain.
... I prefer readability over miniscule performance gains any day. But I see how people can have a different oppinion on this. Fair enough.

But then, this is all your personal oppinion. It's not like the system has any obvious flaws that requires a re-code or re-submit of a buff system.
But never mind; we'll see when your system is done. At least your system has the advantage of the maker still being around here. ;)
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
1. Yeah but let's be honest here... why would you ever need that? It makes sense that buff effects are defined by the buff type. If you want different buff groups, you can always create a middleman-buff and extend your sub-buffs from that. So basicly BuffVenom extends BuffDoT extends Buff.
2. This is legit. Then again, changing or removing dependencies is a matter of minutes

... I prefer readability over miniscule performance gains any day. But I see how people can have a different oppinion on this. Fair enough.

But then, this is all your personal oppinion. It's not like the system has any obvious flaws that requires a re-code or re-submit of a buff system.
But never mind; we'll see when your system is done. At least your system has the advantage of the maker still being around here. ;)

1. I didn't consider this at first either. Eventually I realized that it's necessary at least for my purposes though. Having this choice doesn't really hurt either, as you can still have a wrapper that provides exactly that - buff type definitions. I might even provide it as one of the useful libraries. That way the best of both worlds can be achieved.
About the example you provided, my system uses effects for this. The thing is, extending things like that doesn't allow freely allocating buff effects. I find it better to be able to create buffs by listing their effects(which I effectively do, despite not having effects defined by buff types).
2. I prefer not to have to touch system code at all. If I take the effort of getting to know someone else's code, I might as well code the thing myself, removing the reason of using other people's code in the first place.

Well, if it actually gave any readability... The useful thing about structs is having the ability to define types, but the way structs are typically used, they outweigh that benefit with having to learn additional syntax to do what is effectively the same. This obfuscates code and makes the learning curve steeper for no apparent gain. I consider it better to use obfuscating as little as possible, especially when they give no performance or understanding benefit.

I thought muzzel is still here. Is he not?
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
1. I didn't consider this at first either. Eventually I realized that it's necessary at least for my purposes though. Having this choice doesn't really hurt either, as you can still have a wrapper that provides exactly that - buff type definitions. I might even provide it as one of the useful libraries. That way the best of both worlds can be achieved.
About the example you provided, my system uses effects for this. The thing is, extending things like that doesn't allow freely allocating buff effects. I find it better to be able to create buffs by listing their effects(which I effectively do, despite not having effects defined by buff types).
2. I prefer not to have to touch system code at all. If I take the effort of getting to know someone else's code, I might as well code the thing myself, removing the reason of using other people's code in the first place.
I guess it really depends on how you actually use buffs in your map.
I personally use it in a way that I create 1 new Buffstruct extending buff for any buff (= different icon and tooltip) I want to have in my map.
If the buff is basicly a mutual exclusive type, I create a middleman buff; so, for example:
SongOfX extends Song extends Buff

I've found this to be a very intuitive solution. Every new child layer basicly adds exclusive parameters specific to the type of buff. Songs add a duration parameter. SongofX adds a strength bonus parameter.
If I had another song called SongOfY that adds intelligence instead of strength, I'd just inherit from the middleman:
SongOfY extends Song extends Buff
It still inherits the duration parameter from Song and all the members of the general Buff struct, but it doesn't have the strength parameter of SongOfX.

I never felt the need to extend any custom buffs from "functionality types", like DoT or HoT or anything like that - because when I took a look at all the abilities I have, I've found them to be too different in design for that to actually be worth the effort. Basicly, as soon as you switch over to a registry of effects, rather than having custom code callbacks for every buff, you also artifically limit your freedom in buff design. What if I want a DoT that instead of damaging heals a very specific type of unit (and only that)? What if I want a DoT to have a chance of randomly "exploding"? I'd need to define a custom effect for anything that goes out of the ordinary.

An effects approach like you are suggesting is great if you have lots, and I mean really lots of buff spells in your game that are very similar to each other. It's basicly the Personification of the "Don't repeat yourself"-principle.
But when we really think about that in terms of game design, then you will ultimately have to ask one question: "If I have dozens of DoT type spells, how are they actually different from each other?"

This is actually a matter of good game design. There is no reason to have redundant abilities, other than for eye candy effects. It just convolutes your game with too much extra information to manage.
I always followed the philosophy that a fireball is a fireball. I could create dozens of other abilities that effectively do the same (dealing damage in an area), having cute names like iceball or doomsday bowl of ultimate doom, but in the end, even with the changed visuals and name, they'd still be a fireball at heart.
And at this point, why even bother adding them? What's the value in having "more of the same"?

When I internalized this principle, I realized that the "don't repeat yourself"-principle doesn't really apply to buffs. Because if you come into a situation in which you would repeat yourself, then you already did something wrong from a game design perspective.

I hope you could follow my reasoning here.

Well, if it actually gave any readability... The useful thing about structs is having the ability to define types, but the way structs are typically used, they outweigh that benefit with having to learn additional syntax to do what is effectively the same. This obfuscates code and makes the learning curve steeper for no apparent gain. I consider it better to use obfuscating as little as possible, especially when they give no performance or understanding benefit.
Depends. If you come from an OOP background, structs are intuitive in design and improve readability by adding a clear inheritance structure.

You know that any car has a steering wheel. You can access that steering wheel when you have access to the car. If you have a steering wheel, on the other hand, it doesn't neccesarily mean that you sit inside a car. For example, you could also be sitting in a boat. This logic is very useful in programming, as you can freely add layers of abstraction to your code, depending on what you want to do.
The struct syntax is designed to have a visual representation of this principle.
With every "Dot" applied to a struct, you basicly go one step further into detail, starting from the largest layer of abstraction:
car.controls.breaks.push()
With good naming conventions, this can go a long way to improve readability.

Go a step further and you will see something like this:
milkyway.solarsystem.earth.america.town.car.controls.breaks.push()
I think you get the idea. It's great for managing huge amounts of data with a high level of abstraction.

I thought muzzel is still here. Is he not?
I haven't seen him active for months, tbh.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
I guess it really depends on how you actually use buffs in your map.
I personally use it in a way that I create 1 new Buffstruct extending buff for any buff (= different icon and tooltip) I want to have in my map.
If the buff is basicly a mutual exclusive type, I create a middleman buff; so, for example:
SongOfX extends Song extends Buff

I've found this to be a very intuitive solution. Every new child layer basicly adds exclusive parameters specific to the type of buff. Songs add a duration parameter. SongofX adds a strength bonus parameter.
If I had another song called SongOfY that adds intelligence instead of strength, I'd just inherit from the middleman:
SongOfY extends Song extends Buff
It still inherits the duration parameter from Song and all the members of the general Buff struct, but it doesn't have the strength parameter of SongOfX.

I never felt the need to extend any custom buffs from "functionality types", like DoT or HoT or anything like that - because when I took a look at all the abilities I have, I've found them to be too different in design for that to actually be worth the effort. Basicly, as soon as you switch over to a registry of effects, rather than having custom code callbacks for every buff, you also artifically limit your freedom in buff design. What if I want a DoT that instead of damaging heals a very specific type of unit (and only that)? What if I want a DoT to have a chance of randomly "exploding"? I'd need to define a custom effect for anything that goes out of the ordinary.

An effects approach like you are suggesting is great if you have lots, and I mean really lots of buff spells in your game that are very similar to each other. It's basicly the Personification of the "Don't repeat yourself"-principle.
But when we really think about that in terms of game design, then you will ultimately have to ask one question: "If I have dozens of DoT type spells, how are they actually different from each other?"

This is actually a matter of good game design. There is no reason to have redundant abilities, other than for eye candy effects. It just convolutes your game with too much extra information to manage.
I always followed the philosophy that a fireball is a fireball. I could create dozens of other abilities that effectively do the same (dealing damage in an area), having cute names like iceball or doomsday bowl of ultimate doom, but in the end, even with the changed visuals and name, they'd still be a fireball at heart.
And at this point, why even bother adding them? What's the value in having "more of the same"?

When I internalized this principle, I realized that the "don't repeat yourself"-principle doesn't really apply to buffs. Because if you come into a situation in which you would repeat yourself, then you already did something wrong from a game design perspective.

I hope you could follow my reasoning here.


Depends. If you come from an OOP background, structs are intuitive in design and improve readability by adding a clear inheritance structure.

You know that any car has a steering wheel. You can access that steering wheel when you have access to the car. If you have a steering wheel, on the other hand, it doesn't neccesarily mean that you sit inside a car. For example, you could also be sitting in a boat. This logic is very useful in programming, as you can freely add layers of abstraction to your code, depending on what you want to do.
The struct syntax is designed to have a visual representation of this principle.
With every "Dot" applied to a struct, you basicly go one step further into detail, starting from the largest layer of abstraction:
car.controls.breaks.push()
With good naming conventions, this can go a long way to improve readability.

Go a step further and you will see something like this:
milkyway.solarsystem.earth.america.town.car.controls.breaks.push()
I think you get the idea. It's great for managing huge amounts of data with a high level of abstraction.


I haven't seen him active for months, tbh.

Well, creating middleman buffs is possible with my system as well. The difference in this case is that I have a layer of abstraction that muzzel's doesn't appear to have and thus, I can modularize things more. No matter which system you use, you still need to define the functionality of buffs at least once, so there is no gain in that aspect for muzzel's system. I'll have to think about the inheriting thing though. I'm not sure how exactly I would do it with my system, since the approach is different. Technically the functionality should allow it though, since my system does the same and more compared to muzzel's.

One thing that can't be done very well when you define buffs by type is basing their effects on stats. For instance, how would you make a HoT effect if you can't input any parameters except the default ones(target, maybe caster too)? It would require the core system to input more and more parameters just to keep pace with all the things that a user might do. The HoT effect in this case can be done if you input 2 parameters, but I'm sure there can be cases where you'd need more.
Nonetheless, my system still doesn't prevent the user from making buffs that can be applied purely based on type. It just doesn't force that design decision.

For anything out of the ordinary, you pretty much still need to define a custom effect in muzzel's system as well.
Note that it's possible in my system to have effects that do things with other effects(making your heals explode, for instance). The effects are placed in a very specific order(newer ones last) and are iterated in the same order when applying methods.
Effects define all mechanics in my system, except the absolute basic ones likes indexing. Consider how many spells have a duration mechanic? Wouldn't it be better to abstract away those things?

About the design part, consider that spells are not always defined by a single mechanic. There can be many ways to deal damage over time. I personally have 3 heal over time spells at the moment with very distinct use-cases:
1. Long but slow heal.
2. Short and mediocre heal.
3. Short and fast heal. Disappears upon being attacked.

I can imagine several other kinds of heals that use the same core mechanic:
*A heal that has a massive tick at the end. This tick won't run if the buff is removed early. (long timeout)
*A heal that explodes upon taking damage

I agree that it's pointless to have many identical spells. However, it's not really true that you'd have to make every spell do different things. You can also make them do things differently. There's a fine balance to walk between those and I am absolutely sure you are aware of this on some level. Consider that in your map, mages and squires both have a DoT effect. Yet, they both have a purpose and they're not the exact same spell for several reasons.
In a map like yours, spells alone don't mean much. They mean something when used as part of a kit. In this case you have 2 functionally similar spells, but they're on different classes, allowing you to further define the identity of classes. Consider that pretty much everything you think of when thinking of buffs is actually what the buffs do and how. Surely there is some overlap, probably quite a lot(durations, classification, base mechanics).

When and if I make my system public, I'll provide a list of commonly used buff mechanics and rules, both of them in the form of effects. Those should be considered examples if you are willing to learn how to create new ones.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Oh, you can base your buffs on stats; it's actually quite easy to do, as this basicly stems directly from how the struct syntax works (hence why structs are a good thing in general):

While the basic buff struct defined by Muzzel's system only has one parameter - the unit it is applied to - you basicly define all optional members on struct extension.

JASS:
struct BuffPoison extends Buff
   unit caster
   real dmg

   method getName takes nothing returns string
        return "Poison"
   endmethod
   method getRaw takes nothing returns integer
        return 'A000' //basicly the tornado ability used for the buff icon
   endmethod

   method periodic takes nothing returns nothing
       call UnitDamageTarget(this.caster, this.getUnit(), this.dmg)
   endmethod

   method onApply takes nothing returns nothing
       call this.startPeriodic(3)
       call this.setDuration(15)
   endmethod
endstruct

Then, in your spell trigger, you just do this:

JASS:
function Spell takes nothing returns nothing
   //this function gets called by a spellcast trigger, obviously
   local BuffPoison b = BuffPoison.create()
   call b.apply(GetSpellTargetUnit())
   set b.caster = GetTriggerUnit()
   set b.dmg = GetHeroInt(GetTriggerUnit())
endfunction

There are no optional members that you have "just in case" to be prepared for other spells, as every spell basicly has it's own unique set of struct members.

Actually an effect-based approach can be replicated easily with just calling external functions on periodic, onApply, onRemove or whenever you want.

If you want reactivity to damage, you can just add a custom method for it:

JASS:
method onDamage takes unit source returns nothing
    //whatever it should do
endmethod

Then, in your damage trigger, you just cycle through all buffs on the unit and check for Buff.onDamage.exists and if so, run it.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
What if I want a buff that slows 30% for 6 seconds and deals 100 damage per second for the duration?

EDIT:
I think it would be something like:
set i = CreateBuff(6, ..., ..., ...)
call BuffAddSlow(i, 0.3)
call BuffAddDoT(i, 100)

(Not in the stuff provided here but this would be some way to achieve such things without using buff types but having everything configurable during runtime.)
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
What if I want a buff that slows 30% for 6 seconds and deals 100 damage per second for the duration?
Just take the code above, adjust the tornado aura ability to slow by 30% (or use SetUnitMoveSpeed, but I don't recommend that because it's a crappy native). Change the duration value to 6 instead of 15 and set dmg to 100.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
That approach doesn't provide full functionality though. Consider spell steal.
Why have a buff system if it can't be used to reasonably provide everything that the inferior(default) system does?

EDIT: Example of the buff that Wietlol mentioned. Note that the verbosity is due to how I prefer it and it doesn't have to be like that. Every line here that starts with EffectSfx_ handles effects and thus, is not part of the core system.
JASS:
public function Apply takes unit target,unit caster  returns nothing
    local integer buffid = Buff_GrantBuff(target,'BPoi')
    local integer effectid = Buff_CreateEffect(EffectSfx)
    call EffectSfx_SetSfxPath(effectid,"insert\model\here.mdl")
    call EffectSfx_SetAttachPoint(effectid,"chest")
    call Buff_BuffAddEffect(buffid,effectid)
    set effectid = Buff_CreateEffect(EffectTimeout)
    call EffectTimeout_Set(effectid,10)
    call Buff_BuffAddEffect(buffid,effectid)
    set effectid = Buff_CreateEffect(EffectDoT)
    call EffectDoT_SetTimeout(effectid,0.1)
    call EffectDoT_SetDPS(effectid,100)
    call EffectDoT_SetSource(effectid,caster)
    call EffectDoT_SetAbility(effectid,Lethal_Poison)
    call EffectDoT_SetAttackType(effectid,ATTACK_TYPE_NORMAL)
    call EffectDoT_SetDamageType(effectid,DAMAGE_TYPE_MAGIC)
    call Buff_BuffAddEffect(buffid,effectid)
    set effectid = Buff_CreateEffect(EffectSpeed)
    call EffectSpeed_SetSpeeds(effectid,0,-500)
    call Buff_BuffAddEffect(buffid,effectid)
    //set effectid = Buff_CreateEffect(EffectUnique)
    //call Buff_BuffAddEffect(buffid,effectid)
endfunction
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Just take the code above, adjust the tornado aura ability to slow by 30% (or use SetUnitMoveSpeed, but I don't recommend that because it's a crappy native). Change the duration value to 6 instead of 15 and set dmg to 100.

I hope that you understand that that is not really what I meant.

What I mean is that I can add several different effects on it.
For example, I want the buff to also increase armor, health and damage by 10%.
Also, I want that the unit gain a heal and mana regain over time.
If that is not enough, I want the duration be equal to 6 + 10% INT.

What I mean is that you have to CODE the effects.
Lets for example take a custom stat system and I use triggers to change movement speed.
In that case, my tornado can do what he wants but he never slows any units.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
That approach doesn't provide full functionality though. Consider spell steal.
Why have a buff system if it can't be used to reasonably provide everything that the inferior(default) system does?
What? Of course it provides spell steal:

b.apply(unit)

It's literally a one-liner.
When using this during the buff's lifetime, it will automaticly fire onRemove on the previous unit and onApply for the new unit and take the buff icon with it. It will not reset any durations (unless the user wants that).

I hope that you understand that that is not really what I meant.

What I mean is that I can add several different effects on it.
For example, I want the buff to also increase armor, health and damage by 10%.
Also, I want that the unit gain a heal and mana regain over time.
If that is not enough, I want the duration be equal to 6 + 10% INT.

What I mean is that you have to CODE the effects.
Lets for example take a custom stat system and I use triggers to change movement speed.
In that case, my tornado can do what he wants but he never slows any units.
All this should be done via a Bonus mod, not via a buff system. You then just apply these bonuses onApply and remove them onRemove. It's as simple as that. What numbers you use there is completely up to you.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Updated my previous post with an example.
EDIT: Example of the buff that Wietlol mentioned. Note that the verbosity is due to how I prefer it and it doesn't have to be like that. Every line here that starts with EffectSfx_ handles effects and thus, is not part of the core system.
I replicated the exact same behaviour in BuffHandler just for the lulz:
JASS:
struct BuffPoison extends Buff
	effect sfx
	unit caster
         
         
	method getName takes nothing returns string
		return "Poison"
	endmethod
	method getRaw takes nothing returns integer
		return 'BPoi'
	endmethod
	method getPolicy takes nothing returns integer
		return BUFF_POLICY_LAST
	endmethod

	method periodic takes nothing returns nothing
		call UnitDamageTarget(this.caster, this.getUnit(), this.dmg, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
	endmethod
         
	method onApply takes nothing returns nothing
		set this.sfx = AddSpecialEffectTarget("insert\\model\\here.mdl", this.getUnit(), "chest")
		call SetUnitMoveSpeed(this.getUnit(), GetUnitDefaultMoveSpeed(this.getUnit())-500) //better use a bonus mod here to apply the slow effect
	endmethod
         
	method onRemove takes nothing returns nothing
		call DestroyEffect(this.sfx)
		set this.sfx = null
		set this.caster = null
		call SetUnitMoveSpeed(this.getUnit(), GetUnitDefaultMoveSpeed(this.getUnit()))
	endmethod

	static method add takes unit caster returns Buff
		local thistype b = thistype.create()
		call b.setDuration(10)
		call b.startPeriodic(0.1)
		set b.dmg = 10
		set b.caster = caster
	endmethod
endstruct

EDIT: Word messed up indendation.

EDIT2: I didn't know what that "Lethal_Poison" thing was supposed to do, though. I guess this is applying a WC3 object editor ability via a dummy caster? In that case, just have a dummy using that ability in the onApply method.
 
Last edited:
Level 21
Joined
Mar 27, 2012
Messages
3,232
The Lethal_Poison thing was an integer variable. I use those sort of like enums, that is, they are numbers given readable meaning.
Typically I use them for allocated values(Effect types, for instance).

Well, it appears that the systems are functionally quite similar. Same approach to mechanics(kind of), different approach to style and API.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Also, I didn't say that the API was bad. I said that the documentation was bad. The API listing isn't in a list of bullets, rather it's in a huge nonsensical paragraph that you'd have to fully read. You can't glance over it. Any documentation that can't be glanced over is automatically fail. Muzzel's documentation can't be glanced over, thus it fails, thus it's bad.

; )


I'm not a huge fan of this documentation, but I can easily glance over it, thus it is infinitely better than muzzel's, lol.

JASS:
//  API:
//      ------------------------------------------------------------------------------------------------
//          Basics
//      ------------------------------------------------------------------------------------------------
//      has(int x)              --> Check if value is in stack
//      index(int value)        --> Get the index of value
//      add(int x)              --> Add a new value to the stack
//      clear()                 --> Remove all values from the stack
//      count                   --> Return how many elements are in stack
//      delete(int x)           --> Delete a value from the stack
//      full                    --> Boolean whether Stack is full or not
//      empty                   --> Boolean whether Stack is empty or not
//
//      ------------------------------------------------------------------------------------------------
//          Utilities
//      ------------------------------------------------------------------------------------------------
//      each(code x)            --> Execute this function for each value in stack
//      max[= x]                --> Get/Set maximum amount of elements in stack
//      merge(Stack x)          --> Merge other stack into current stack
//      split(Stack x, int y)   --> Split this stack with elements after y into the new stack
//      switch(int x, int y)    --> Switch indexes for value x and y
//      sort(bool asc)          --> Sort Stack ascend or descending
//
//      ------------------------------------------------------------------------------------------------
//          Iteration
//      ------------------------------------------------------------------------------------------------
//      cursor                  --> Get current cursor index location
//      direction               --> Get boolean, whether iterating is forward (true) or backward (false)
//      hasNext()               --> Boolean, whether cur is at end or not
//      hasPrev()               --> Boolean, whether cur is at start or not
//      getNext()               --> Increases cur by 1 and returns the current element
//      getPrev()               --> Decreases cur by 1 and returns the current element
//      reset()                 --> Sets cur to -1 (start)
//      end()                   --> Sets cur to count
//
//      ------------------------------------------------------------------------------------------------
//          Getters
//      ------------------------------------------------------------------------------------------------
//      get(int x)              --> Get value on index x
//      getFirst()              --> Returns first element of stack
//      getLast()               --> Returns the last element of stack
//      random()                --> Return any random value of the stack
//
//      ------------------------------------------------------------------------------------------------
//          Each Static Members
//      ------------------------------------------------------------------------------------------------
//      thistype.eachStack      --> Current instance of stack
//      thistype.eachValue      --> Current value
//      thistype.eachIndex      --> Current index

Compare. Apologies for posting the entire thing. I don't know where his API listing starts.

JASS:
//*******************************************
//*        .___.
//*       /     \
//*      | O _ O |
//*      /  \_/  \ 
//*    .' /     \ `.
//*   / _|       |_ \
//*  (_/ |       | \_)
//*      \       /
//*     __\_>-<_/__
//*     ~;/     \;~
//*
//*  muzzels Spellbox presents:
//*
//*  BuffHandler
//*  Version 1.1
//*
//*******************************************
//*  Documentation
//*
//*
//*  1. Basics
//*  =========
//*  1.1 Defining bufftypes
//*  Each bufftype basically exists of one struct that holds all the variable
//*  members and methods your buff needs. The struct has to extend "Buff" which
//*  allows you to access most of the functionality of this system and registers
//*  your buff for the automated bufflists of your units.
//*  For a cleaner code it is recommended that you use a new library for every
//*  bufftype you define.
//*  
//*  struct MyBuff extends Buff
//*    // ...
//*  endstruct
//*  
//*  1.2 Applying buffs to units
//*  To bring a buff on a unit you first have to create a new instance by calling:
//*  
//*  local MyBuff b = MyBuff.create()
//*  
//*  Then you can apply the newly created buff to a unit:
//*  call b.apply(someUnit)
//*  
//*  To destroy a buff simply call the destructor method:
//*  
//*  call b.destroy()
//*  
//*  1.3 Buff events
//*  A very important thing to remember is that you may not define the normal
//*  constructor and destructor methods for your bufftype, namely the "create"
//*  and "onDestroy" methods. They are used by the system and writing your own
//*  will throw an error.
//*  Instead you may optionally implement the following methods:
//*  
//*  method onCreate takes nothing returns nothing
//*  endmethod
//*  // Will be run immediately after the buff is created.
//*  // Use this as a replacement for the "create" method.
//*  
//*  method onApply takes nothing returns nothing
//*  endmethod
//*  // Will be run immediately after the buff is applied to a unit.
//*  
//*  method onRemove takes nothing returns nothing
//*  endmethod
//*  // Will be run right before the buff is removed from the unit.
//*  // (e.g. applied to another unit or destroyed)
//*  
//*  method preDestroy takes nothing returns nothing
//*  endmethod
//*  // Will be run right before the buff is destroyed.
//*  // Use this as a replacement for the "onDestroy" method.
//*  
//*  To access the unit the buff is currently applied to you may use the
//*  method ".getUnit()".
//*  If the buff is created but not yet applied it will return "null".
//*  
//*  1.4 Eyecandy
//*  Defining a bufficon involves some object data creation.
//*  First you need to create a new ability based off "Slow-Aura (Tornad)"
//*  ('Aasl') with a buff based on "Tornado (Slow-Aura)" ('Basl'). Change the
//*  name, tooltip and icon of the buff to whatever you want.
//*  The name of the buff is by default displayed in red. To change it to green
//*  add the following colortags: "|cff00ff00BuffName|r".
//*  
//*  To apply that bufficon to your buff just add the following method to your
//*  struct which returns the raw id of your newly created ability:
//*  
//*  method getRaw takes nothing returns integer
//*     return 'A000'
//*  endmethod
//*  
//*  In addition to "getRaw" to remove the delay between the removal of the
//*  buff and its icon you can optionally define the function:
//*  
//*  method getBuffRaw takes nothing returns integer
//*     return 'B000'
//*  endmethod
//*  
//*  You may also define an optional name for your buff. Note that this name
//*  is not required and also not displayed anywhere, but some of the debugging
//*  functions make use of it and its a good convention to define names for all
//*  your buffs. To define a name for your buff just add the following method
//*  to your struct which returns the desired name:
//*  
//*  method getName takes nothing returns string
//*     return "MyBuff"
//*  endmethod
//*  
//*  2. Advanced Features
//*  ====================
//*  2.1 Unapplying and Reapplying
//*  Once applied buffs are not bound to a specific unit but can be Reapplied
//*  (moved) to another unit which may even be "null" (the buff is then in a
//*  not applied state, but does not get deleted). Do do this simply call the
//*  apply method again:
//*  
//*  call b.apply(someOtherUnit) // reapplying the buff to someOtherUnit
//*  call b.apply(null) // unapplying the buff
//*  
//*  When reapplying a buff to another unit the methods "onRemove" and
//*  "onApply" will, if implemented, be called.
//*  Note that in "onRemove" the method "getUnit()" will refer to the old unit
//*  while in "onApply" it will already return the new unit.
//*  
//*  2.2 Buffs with Duration
//*  To easily create a buff with limited duration you can make use of the
//*  following method that starts a timer and destroys the buff after the
//*  specified timeout:
//*  
//*  method setDuration takes real duration returns nothing
//*  
//*  You can either call this function inside of "onCreate" or inside of
//*  "onApply". This will have two different effects:
//*  When called in "onCreate" the timer will continue when the buff is being
//*  reapplied to another unit. When called in "onApply" the timer will
//*  restart when reapplied.
//*  
//*  2.3 Periodic Buffs
//*  To easily create buffs that perform a certain action periodically you
//*  can define the following method:
//*  
//*  method periodic takes nothing returns nothing
//*    // Do something
//*  endmethod
//*  
//*  You can start and stop the periodical loop with the following methods:
//*  method startPeriodic takes real timeout returns nothing
//*  method stopPeriodic takes nothing returns nothing
//*  
//*  3. BuffLists
//*  ============
//*  3.1 Accessing BuffLists
//*  When applied to a unit buffs get stored in the units bufflist. Any units
//*  bufflist can be accessed using the following function:
//*  
//*  function getUnitBuffList takes unit u returns BuffList
//*  
//*  To access the bufflist a buff is currently applied you can use the
//*  following method of the buffs struct:
//*  
//*  method getParentBufflist takes nothing returns BuffList
//*  
//*  Note that this is faster than using "getUnitBuffList(this.getUnit())"
//*  since it does not require the unit indexing system.
//*  
//*  3.2 BuffType-Ids
//*  Something that might be useful when working with BuffLists are the
//*  BuffType-Ids. BuffType-Ids are automatically assigned to each bufftype
//*  and provide a simple way to compare the type of different buffs.
//*  
//*  To get a buffs BuffType-Id call the function:
//*  
//*  method getBuffTypeId takes nothing returns integer
//*  
//*  For technical reasons there is currently no static method that returns
//*  the BuffType-Id, but this might be added in future versions.
//*  
//*  3.3 BuffList API
//*  To get the unit a bufflist is applied to you can use the following method:
//*  
//*  method getUnit takes nothing returns unit
//*  
//*  To iterate through a bufflist object you can use its iterator:
//*  
//*  method iterator takes nothing returns BuffListIterator
//*  
//*  The BuffListIterator then offers the following methods:
//*  
//*  method next takes nothing returns Buff
//*  // Returns the next Buff in the BuffList.
//*  // You should only use this function if ".hasNext()" returned "true".
//*  
//*  method hasNext takes nothing returns boolean
//*  // Returns "true" if the next ".next()" call will return a valid Buff.
//*  // Returns "false" if there are no more buffs in the BuffList.
//*  
//*  method hasNoNext takes nothing returns boolean
//*  // Negated version of "hasNext" for easier use with "endloop".
//*  
//*  Note that the iterators are exclusively designed for use immediately after
//*  creation. Iterators will not work correctly after the BuffList is altered.
//*  After using the iterator dont forget to destroy it by calling its
//*  ".destroy()" method.
//*  
//*  Lets make a short example of a buff that, when applied to a unit, destroys
//*  all other buffs from that unit except itself and other buffs of this type:
//*  
//*  struct MyNewBuff
//*     method onApply takes nothing returns nothing
//*         local BuffListIterator iter = this.getParentBufflist().iterator()
//*         local Buff b
//*         loop
//*             exitwhen iter.hasNoNext()
//*             set b = iter.next()
//*             if (b.getBuffTypeId() != this.getBuffTypeId()) then
//*                 call b.destroy()
//*             endif
//*         endloop
//*         call iter.destroy()
//*     endmethod
//*  endstruct
//*  
//*  To count the amount of buffs of a specified type you can use the following
//*  To count the amount of buffs of a certain type you can use the following 
//*   of a BuffList object:
//*  
//*  method countBuffsOfType takes Buff buff returns integer
//*  
//*  method countBuffsOfTypeId takes integer buffTypeId returns integer
//*  // Use this if you only have the BuffType-Id to specify the buff type.
//*  
//*  4. Stacking Policies
//*  ====================
//*  There are different possibilities to define how multiple buffs of the same 
//*   behave when applied to the same unit.
//*  4.1 Premade policies
//*  For most situations selecting the right one of the premade stacking
//*  policies will suffice. Currently there are:
//*  
//*  BUFF_POLICY_STACKING (default)
//*  // An unlimited amout of buffs of this type can coexist on all units.
//*  
//*  BUFF_POLICY_LAST
//*  // Buffs of this type are limited to one instance per target unit.
//*  // The last applied buf will destroy existing ones.
//*  
//*  To define the policy for a bufftype you can just add the following method
//*  to your buff struct which returns the the policy type:
//*  
//*  method getPolicy takes nothing returns integer
//*     return BUFF_POLICY_LAST
//*  endmethod
//*  
//*  Default is BUFF_POLICY_STACKING.
//*  
//*  There might be more premade policies in future versions.
//*  
//*  4.2 Customize Stacking Behaviour
//*  If you require a stacking behaviour that the premade policies cannot provide
//*  you can customize it by defining the following function:
//*  
//*  method checkApply takes nothing returns boolean
//*     return true // if you simply return true this is equal to BUFF_POLICY_STACKING
//*  endmethod
//*  
//*  If defined this function runs before the buff is applied to unit and before
//*  "onApply" fires.
//*  In the method body you can check the bufflist for other instances of this
//*  type, remove them, modify them etc.
//*  If this function returns true the buff will get applied.
//*  If this function returns false the buff will not get applied but als not
//*  deleted. If you want it deleted you may call ".delete()" right before
//*  the return statement.
//*  
//*  5. Custom Members
//*  =================
//*  A quick way to add custom members to all buff types is the CustomMembers
//*  module. Its defined in the top of this system and is automatically implemented
//*  by all buff structs you define. Its method "initCustomMembers" can be used to
//*  initialize default values to the custom members.
//*
//*  Changelog
//*  =========
//*  Version 1.1:
//*  - Making use of "UnitMakeAbilityPermanent"
//*  - Added the optional possibility to remove the buff directly, avoiding the small
//*    delay between the removal of the buff and the removal of the buff icon
//*  Version 1.0:
//*  - Initial Release
//*
//*  Credits
//*  =======
//*  - Menag
//*
//*******************************************
//*
//* Add custom members here:
    module CustomMembers
        
        // Initialize default values to the custom members here:
        method initCustomMembers takes nothing returns nothing
        endmethod
    endmodule
//*
//*
//*******************************************


Here is some professional documentation. Notice how the API is cleanly listed?

http://www.antlr.org/api/Java/org/antlr/v4/runtime/tree/ParseTree.html

Here is documentation from Purge. Purge is the documentation king, and some of my documentation style came from his, and vice versa ;). It, like the professional javadoc stuff, has a clean API listing. It is highly readable and can be scanned very easily.

JASS:
/*
*****************************************************************************
*
*  FUNCTIONS
*
*      CreateTrack( string modelPath, real x, real y, real z, real facing ) returns Track
*        - Creates a trackable of modelPath at coordinates ( x, y, z ) with
*        - "facing" in degrees. Returns the trackable instance.
*
*      CreateTrackForPlayer( string modelPath, real x, real y, real z, real facing, player who ) returns Track
*        - Same as function above, but creates it for one player.
*
*      RegisterAnyClickEvent( code c ) returns nothing 
*      RegisterAnyHoverEvent( code c ) returns nothing 
*        - Fires "c" when any trackable is clicked or hovered.
*
*      RegisterClickEvent( Track obj, code c ) returns nothing
*      RegisterHoverEvent( Track obj, code c ) returns nothing
*        - Fires "c" when the trackable of "obj" is clicked or hovered respectively.
*      RegisterInteractEvent( Track obj, code c ) returns nothing
*        - Fires "c" when the trackable of "obj" is clicked or hovered.
*        - Use GetTriggerEventId() == EVENT_GAME_TRACKABLE_TRACK
*          to differentiate between the two event occurrences. This
*          method is more efficient on handles than the two above.
*
*      EnableTrackInstance( Track obj, boolean flag ) returns nothing
*        - A disabled Track instance will not fire its events.
*        - Track instances are enabled by default.
*      IsTrackInstanceEnabled( Track obj ) returns boolean
*        - Returns whether an instance is enabled.
*
*  EVENT RESPONSES
*
*      GetTriggerTrackInstance() returns Track
*        - Returns the Track instance that had a player interaction.
*      GetTriggerTrackable() returns trackable
*        - Returns the trackable object that had a player interaction.
*      GetTriggerTrackablePlayer() returns player
*        - Returns the player that interacted with the trackable object.
*
*****************************************************************************
*
*   struct Track
*
*        static Track instance
*           - The triggering instance of the event.
*        static trackable object
*           - The triggering trackable object of the event.
*        static player tracker
*           - The player who interacted with the trackable object.
*
*        readonly real x
*        readonly real y
*        readonly real z
*        readonly real facing
*        readonly string model
*           - Instance properties.
*           - Warning: the invisible platform has a default z-value
*             of 2.94794. So if you input 0 into the system, it'll
*             end up ~3 units above the ground. For an alternative model,
*             see: [url]http://www.hiveworkshop.com/forums/2661555-post54.html[/url]
*
*        method operator enabled= takes boolean flag returns nothing
*        method operator enabled takes nothing returns boolean
*        
*        static method create takes string modelPath, real x, real y, real z, real facing returns Track
*        static method createForPlayer takes string modelPath, real x, real y, real z, real facing, player p returns Track
*
*        static method registerAnyClick takes code c returns nothing
*        static method registerAnyHover takes code c returns nothing
*
*        method registerClick takes code c returns nothing
*        method registerHover takes code c returns nothing
*        method registerInteract takes code c returns nothing
*
*            - All equivalent to their function counterparts.
*
*****************************************************************************
*    
*    Credits
*       - Azlier: Trackable2 (inspiration)
*       - Arhowk: bugfix
*       - Dalvengyr: bugfix from a typo.
*       - Uberplayer: bugfix; info on the invisible platform Z issue.
*
****************************************************************************/



Muzzel's documentation just looks like a giant wall of text to me. From a glance, I can't find a single method, operator, or field inside of it. I also can't find a single struct. I'm not sure how it's organized, and I'm not going to sit down and read it all to find out. I can almost immediately infer the structure of the system from the other documentation styles and piece everything together in Purge's. Muzzel's? Chaos : P.


It's not that I don't like his API. The documentation sucks, lol.


Here is the OpenGL reference card

https://www.opengl.org/sdk/docs/reference_card/opengl45-reference-card.pdf

The OpenGL reference page

https://www.opengl.org/sdk/docs/man4/

The Unity3D Script Reference

http://docs.unity3d.com/ScriptReference/AccelerationEvent.html

What do all of these things have in common? They aren't huge walls of text, lol. You can glance at any of them easily and quickly look up details for either specific methods or for class descriptions and their included methods. You can also infer how to do things from that. Beyond these references, there are also tutorials that link to these references.


Documentation can easily include examples or explanations, you just need to first Title the dern thing and then explain it. From what I saw in my brief glance, Muzzel did this backwards. That, and the lack of spacing, resulted in documentation that can't be scanned, which means that the documentation is automatically bad : P.


PowerPoint presentations use tabs and spacing with subject titles and stuff. Professionally documented code does this. Microsoft Word lists do this. There is a reason why everyone does this. It's readable. Muzzel doesn't do this. It's unreadable. It's bad ; p.


I have provided many examples and hope that I have clearly demonstrated why muzzel's documentation is not good. I don't really care either way. I'm just not going to read his resource until I can read the documentation easily, lol.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Personally I would campaign for providing a small demo code, along with a Jass submission.
Just basic API, so users get a quick guidance about how-to-use a resource.

For libraries where demo code doesn't make any sense ( Table, ... etc ) a small
demo like documention below the actual code ( like in Table ) turns out useful.

Exceptions may be snippets, which are understandable at a glance. ( AutoFly, LocZ, ...)

I think it's useful, because ultimately you want resources with short learning curve.

@Buff: Documentation is a text wall, penguin is cool and API is good.
Actually I like the system very much. For myself, I never code with stub methods though.
 
Level 11
Joined
Dec 3, 2011
Messages
366
What is wrong with this one?

EDIT:
JASS:
globals
    
    item array      udg_ItemIndex_Items
    integer         udg_ItemIndex_NextIndex             = 0
    
endglobals

function CreateItemIndex takes item whichItem returns nothing
    
    loop
        set udg_ItemIndex_NextIndex = udg_ItemIndex_NextIndex +1
        
        exitwhen udg_ItemIndex_Items[udg_ItemIndex_NextIndex] == null
        
        if udg_ItemIndex_NextIndex >= 8191 then
            set udg_ItemIndex_NextIndex = 0
        endif
    endloop
    
    set udg_ItemIndex_Items[udg_ItemIndex_NextIndex] = whichItem
    call SetItemUserData(whichItem, udg_ItemIndex_NextIndex)
    
endfunction

function RemoveItemIndex takes item whichItem returns nothing
    set udg_II_AntiLoop = true
    set udg_ItemIndex_Items[GetItemUserData(whichItem)] = null
    call SetItemUserData(whichItem, 0)
    set udg_II_AntiLoop = false
endfunction


function SecureItemIndex takes item whichItem returns integer
    if udg_II_AntiLoop then
        return 0
    endif
    
    set udg_II_AntiLoop = true
    if GetItemUserData(whichItem) == 0 then
        call ItemIndex_CreateIndex(whichItem)
        return udg_ItemIndex_NextIndex
    endif
    set udg_II_AntiLoop = false
    return 0
endfunction
hook GetItemUserData SecureItemIndex

JASS:
globals
    
    item array      udg_ItemIndex_Items
    integer         udg_ItemIndex_NextIndex             = 0
    
endglobals

function CreateItemIndex takes item whichItem returns nothing
    
    loop
        set udg_ItemIndex_NextIndex = udg_ItemIndex_NextIndex +1
        
        exitwhen udg_ItemIndex_Items[udg_ItemIndex_NextIndex] == null
        
        if udg_ItemIndex_NextIndex >= 8191 then
            set udg_ItemIndex_NextIndex = 0
        endif
    endloop
    
    set udg_ItemIndex_Items[udg_ItemIndex_NextIndex] = whichItem
    call SetItemUserData(whichItem, udg_ItemIndex_NextIndex)
    
endfunction

function RemoveItemIndex takes item whichItem returns nothing
    set udg_ItemIndex_Items[GetItemUserData(whichItem)] = null
    call SetItemUserData(whichItem, 0)
endfunction

function GetItemIndex takes item whichItem returns integer
    local integer id = GetItemUserData(whichItem)
    
    if id == 0 then
        call ItemIndex_CreateIndex(whichItem)
        return udg_ItemIndex_NextIndex
    endif
    
    return id
endfunction

Still gotta do de-indexing.
We still can't check deindex event. No timer hmmmm how can we deindex Item @@ Use your func instead, hmmm I dont think that's a good way
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Personally I would campaign for providing a small demo code, along with a Jass submission.
Just basic API, so users get a quick guidance about how-to-use a resource.

For libraries where demo code doesn't make any sense ( Table, ... etc ) a small
demo like documention below the actual code ( like in Table ) turns out useful.

Exceptions may be snippets, which are understandable at a glance. ( AutoFly, LocZ, ...)

I think it's useful, because ultimately you want resources with short learning curve.

@Buff: Documentation is a text wall, penguin is cool and API is good.
Actually I like the system very much. For myself, I never code with stub methods though.
Yeah, I'm all for "unified" documentation and demo rules...

... then again, with the low amount of JASS submissions we have on a monthly basis, I think it doesn't really matter. This would have been cool 10 years ago when the userbase was still big. Nowadays, with only a handful of people still reading the JASS section, nobody cares - especially since the users browsing the JASS section are able to read and understand the code anyway.

The divide between JASSers and non-JASSers is as big as never before. We have those very few elitists that still submit and use JASS resources. And then we have the mass of GUIers that won't even bother checking the JASS section for existing scripts.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Name: Item, CustomItem, InventoryItem, ....

Idea:
Creating custom items for full screen inventory systems

Reason:
  • Object tooltips can't be changed dynamically ingame.
  • Inventory systems directly based on object data require 1:x ( item / object editor fields )
  • Current systems don't offer a wide range of items, with relative short setup time.
    Do you have time to design 2000 items via object editor? :p
Current problems with Custom fullscreen inventories:
-->Most polpular pulbic released resource
  • Still one setup represents 1 item ( no random attribute rolls )
  • Lots of object data required, long setup time.
  • Not too flexible
  • ( side note: Overall system could be coded better )

Goal:


  • Short, easy setup results in tousands of possible items

Approach:

Now we need ideas for optimal data structure and required data information, aswell
as format of the final output. My vision would be the following:

1. struct BonusType
--> setup all type of bonuses and how to determine their values.
  • BonusType Bonus --> uses BonusMod, rolls integer GetRandomInt(min, max)
  • BonusType Ability --> uses Abilities, added as ability
  • BonusType ItemPower --> uses ItemPower, rolls real GetRandomReal(min, max)
BonusType also need a prefix and suffix for later explained translation process

2a. struct ItemType
i.e. Sword, Axe, Shield, Amor, Ring, .... --> global ItemType SWORD = CreateItemType()

2b. Setup an ItemType possible rolls
First, static "roll always" instance. i.e Swords always roll a damage range.
Move on to the next roll instance, which is dynamic. Add every bonus an ItemType can roll.
Example ( sword ):
  • Rolls Crit ( BonusType ItemPower )
  • Rolls Strength ( BonusType Bonus )
  • Rolls damage range again
( Side note: Maybe support a copy function to create ItemTypes based
on parent a ItemType with the option to remove/add certain rolls ( faster setup )
i.e bow extends sword --> remove roll strenght, add agility roll.
--> Results in a copy of Sword without strength roll, but now has agility roll. )


3. Setup ItemSubType
--> Now this is intersting. We define a distinct item i.e Sword: Iron Sword
--> Iron Sword can roll everything defined in Sword, so we have to setup a range.
  • Rolls minimum damage from 3-7
  • Rolls maximum damage from 12 - 22
  • Roll Strenght from 10 - 20
  • Rolls Crit from 5 - 15%
Also an amount of roll instances.
--> Rolls all "always" bonuses --> roll x dynamic bonuses --> rolls again x dynamic bonuses.

Here you have to decided if the same bonus can be rolled twice or is excluded after beeing rolled for that roll instance.
i.e. Iron Sword rolls always damage, 2 random bonuses in the first roll instance] and 3 attributes in the second roll instance.
i.e. In roll instance 1
-- rolls agility 5
-- Now can it roll agility again in roll instance 1 or is agility excluded? ( remember in the example it rolls 2 bonuses for roll instance 1)
--
-- Can roll agility again in roll instance 2 --> sums up total agility to a higher value.


The result is an item which can have x possible outcomes.
Compare to object editor setup 1 item vs x items.

4a. Now the heavy identification part
struct CustomItem
  • static method identify takes item whichItem returns CustomItem
  • Create a linked list for the CustomItem instance
  • Roll "always" bonuses first
  • Roll dynamic rolls second, check if bonus has already rolled, if so sum up both values instead
    of adding a new node to the list.
  • Roll eventually x further roll instances.
So far:
Dynamic CustomItem i having a linked list which covers all rolled bonuses.

4b.) Translation
method translate takes nothing returns nothing has to run only once! Outcome is stored into a string member.
Translate the linked list into a string ( item description )
  • Start off with the name ( prefix, suffix? somehow .. i.e Mighty Iron Sword of Doom)
  • The bonuses ( prefix + bonus + suffix. Note that the bonus value
    has to be converted for instance 0.1 Crit --> i.e 10.
    --> Example Critical Hit Chance. Prefix = tab + Increased Crit Change by, Bonus convert to percent, suffix = %
    --> Output: Increased Critical Hit Chance by 10 %
  • next line /n
    .
  • Eventually unique text in the end ( description, unique bonus explanation, restriction, required level, ....)

Finally:
Once translated the performance is really good.
Tooltip output via texttag in CustomInventory system. Complexity O(1) --> CustomItem(i).descriptiom
Bonus applied by iterating over the linked list. --> CustomItem(i).list.first

Conclusion:
Tousands of items --> higher random factor --> interesting.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I would say no to ItemType, BonusType, and ItemSubType.


An Item is a composite of elements. These elements may latch into onAttack, onDamage, provide stats, effects, and so on. You need much more flexibility if you want a generic system ; ).


Provide several useful item and inventory events first

Item events (only exist if used)

onEquip
onUnequip
onPickup
onDrop
onDamaged
onPeriodic


Inventory events too, like swap.

Now beyond this, we start to get into a highly flexible combat system. Firstly, if we are to do auto attacks, then auto attacks come from weapons, and the cooldown of those weapons determine whether or not you get an attack. Thus, the weapons initiate the attack given that the unit is in combat. If the unit goes into auto combat like an RTS, then it is if the unit has a valid target for the weapon. All active weapons may fire at once ; ).

When a weapon fires, it allocates an attack and the stats on the weapon register to the attack. When the attack resolves, the stuff unleashes its payload. The calculations are done during registration. There are more calculations, like dodge and stuff, which are done during the release of the payload.

Any actual item can latch on to an attack. Furthermore, items can latch on to attacks from specific weapons. You can also set up phases of combat. Let the weapon populate the attack and then let items modify it ; ). For example, if the attack has fire damage, a piece of armor might increase that fire damage by 4%.



The thing is, this would be a full Combat System that should be separate from the items, but should use the items. How then would the items support any sort of event at any given point? A combination of parallel arrays and triggers.


Eventually, we have to come into a set of possible stats an item can have, no? Not really. Every stat from an item can be displayed dynamically and can be run dynamically. Let's say that we have an Inventory system. Every item should have several events associated with that inventory system.

onHover (view?)
onRender (icon?)

We cab then extend these things to create base items with common properties. For example, every item could render pretty much the same way ; ). A few might have some special things. We can also render their stats the same way for the most part with onHover, but they can populate whatever information they want to populate there. This is where we start getting into more map-specific things built on still general views.


Every item would need both a physical representation in the game world (a unit?) and a data representation. For example, if units are used, then when the unit is clicked, you can see the item's stats. When your hero is ordered to the unit and gets close enough, the unit is recycled. Targetable dummies can be used. By displaying the information for the item on click, you no longer have to generate lots of objects. You just get the models and icons you want and you're set. You can use the onHover event to display the details ^_-. Alternatively, you can do a loot system and view the items on the corpse of a unit. The items would still need a physical representation though for when a player drops an item.
 
Status
Not open for further replies.
Top