• 🏆 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!

[Snippet] DestructableRevival

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Improved version of my TreeRevival.
Doesn't support not pre-placed destructables.

"Tree" suggests that destructables are filtered, thus IsDestructableTree function is required. All credits go to PitzerMike for his IsDestructableTree and Bribe for it's updated version.
Finaly, we got IsDestructableTree on hiveworkshop updated by BPower.
Script takes care for those who are lazy enough not to CnP this small library though.

Important: It is suggested to increase QUANTITY constant in TimerUtils(Ex) since there might be numerous trees/destructables being restored within short period of time. My test map shows such example.

JASS:
/*****************************************************************************
*       ___  ___  ____
*      |   \| _ \/ __/                 Destructable Revival System
*      | \\ |   /\__ \                    by Bannar
*      |___/|_|\_\___/ v3.0.0.0
*
*    Revives dead destructables.
*
*    Special thanks to Bribe.
*
******************************************************************************
*
*    Requirements:
*
*       Alloc - choose whatever you like
*          e.g.: by Sevion hiveworkshop.com/threads/snippet-alloc.192348/
*
*       ListT by Bannar
*          hiveworkshop.com/threads/containers-list-t.249011/
*
******************************************************************************
*
*    Interface DestructableRevivalFilter:
*
*       static method shouldRevive takes destructable whichDest returns boolean
*          Determinates whether destructable should be revived.
*
*       module DestructableRevivalFilterModule
*          Declares body for new filter type.
*
*    Interface DestructableRevivalCondition:
*
*       static method shouldExtend takes destructable whichDest returns real
*          Determinates whether destructable revival should be extended.
*          Returning value greater than 0 indicates that revival delay has to be extended by said amount.
*
*       module DestructableRevivalConditionModule
*          Declares body for new condition type.
*
*
*    Predicate implementation example:
*
*        | struct MyRevivalFilter extends array
*        |     static method shouldRevive takes destructable whichDest returns boolean
*        |         return IsDestructableTree(whichDest)
*        |     endmethod
*        |
*        |     implement DestructableRevivalFilterModule
*        | endstruct
*
******************************************************************************
*
*    Configurables:
*
*       struct DestructableRevival:   static boolean enabled
*
*    Functions:
*
*       function SetDestructableRevivalDelay takes integer destructableType, real delay returns nothing
*          Sets revival delay for specified destrutable type.
*
*       function GetDestructableRevivalDelay takes integer destructableType returns real
*          Retrieves revival delay for specified destructable type. Defaults to 60.0 seconds.
*
*       function EnableDestructableRevivalAnimation takes integer destructableType, boolean enable returns nothing
*          Enables or disables revival animation for specified destructable type.
*
*       function IsDestructableRevivalAnimation takes integer destructableType returns boolean
*          Whether revival animation is enabled for specified destructable type. Defaults to true.
*
*       function AddDestructableRevivalFilter takes DestructableRevivalPredicate filter returns nothing
*          Adds new filter for destructable to be revived.
*
*       function RemoveDestructableRevivalFilter takes DestructableRevivalPredicate filter returns nothing
*          Removes specified filter from predicate list.
*
*       function AddDestructableRevivalCondition takes DestructableRevivalPredicate condition returns nothing
*          Adds new condition for destructable to be revived. Invoked when revival delay elapses.
*
*       function RemoveDestructableRevivalCondition takes DestructableRevivalPredicate condition returns nothing
*          Removes specified condition from predicate list.
*
*       function RegisterDestructableRevival takes destructable whichDest returns nothing
*          Registers specified destructable to the system.
*          Can be used to register revival for destructables that were created after map initialization.
*
*****************************************************************************/
library DestructableRevival requires Alloc, ListT

globals
    private IntegerList filters = 0
    private IntegerList conditions = 0
    private IntegerList ongoing = 0
    private Table table = 0
    private timer looper = CreateTimer()
    private trigger trig = CreateTrigger()
    private trigger array triggers
    private destructable argDest = null
    private real retReal = 0.0
endglobals

function SetDestructableRevivalDelay takes integer destructableType, real delay returns nothing
    set table.real[destructableType] = delay
endfunction

function GetDestructableRevivalDelay takes integer destructableType returns real
    if table.real.has(destructableType) then
        return table.real[destructableType]
    endif
    return 60.0
endfunction

function EnableDestructableRevivalAnimation takes integer destructableType, boolean enable returns nothing
    set table.boolean[-destructableType] = enable
endfunction

function IsDestructableRevivalAnimation takes integer destructableType returns boolean
    if table.boolean.has(-destructableType) then
        return table.boolean[-destructableType]
    endif
    return true
endfunction

function AddDestructableRevivalFilter takes DestructableRevivalPredicate filter returns nothing
    if filter != 0 then
        call filters.push(filter)
    endif
endfunction

function RemoveDestructableRevivalFilter takes DestructableRevivalPredicate filter returns nothing
    if filter != 0 then
        call filters.erase(filters.find(filter))
    endif
endfunction

function AddDestructableRevivalCondition takes DestructableRevivalPredicate condition returns nothing
    if condition != 0 then
        call conditions.push(condition)
    endif
endfunction

function RemoveDestructableRevivalCondition takes DestructableRevivalPredicate condition returns nothing
    if condition != 0 then
        call conditions.erase(conditions.find(condition))
    endif
endfunction

function RegisterDestructableRevival takes destructable whichDest returns nothing
    call TriggerRegisterDeathEvent(trig, whichDest)
endfunction

struct DestructableRevivalPredicate extends array
    implement Alloc

    static method shouldRevive takes destructable whichDest returns boolean
        return true
    endmethod

    static method shouldExtend takes destructable whichDest returns real
        return 0.0
    endmethod

    static method create takes nothing returns thistype
        local thistype this = allocate()
        set triggers[this] = CreateTrigger()
        return this
    endmethod

    method destroy takes nothing returns nothing
        call DestroyTrigger(triggers[this])
        set triggers[this] = null
        call deallocate()
    endmethod
endstruct

//! textmacro DESTRUCTABLE_REVIVAL_PREDICATE
    private delegate DestructableRevivalPredicate predicate

    static method create takes nothing returns thistype
        local thistype this = DestructableRevivalPredicate.create()
        set predicate = this
        call TriggerAddCondition(triggers[this], Condition(function thistype.onInvoke))
        return this
    endmethod

    method destroy takes nothing returns nothing
        call predicate.destroy()
    endmethod
//! endtextmacro

module DestructableRevivalFilterModule
    private static method onInvoke takes nothing returns boolean
        return thistype.shouldRevive(argDest)
    endmethod
    //! runtextmacro DESTRUCTABLE_REVIVAL_PREDICATE()
endmodule

module DestructableRevivalConditionModule
    private static method onInvoke takes nothing returns boolean
        set retReal = thistype.shouldExtend(argDest)
        return retReal > 0.0
    endmethod
    //! runtextmacro DESTRUCTABLE_REVIVAL_PREDICATE()
endmodule

private struct PeriodicData extends array
    destructable dest
    real remaining

    implement Alloc

    method destroy takes nothing returns nothing
        call table.remove(GetHandleId(dest))
        set dest = null

        call ongoing.erase(ongoing.find(this))
        if ongoing.empty() then
            call PauseTimer(looper)
        endif

        call deallocate()
    endmethod

    static method create takes destructable d returns thistype
        local thistype this = allocate()

        call ongoing.push(this)
        set this.dest = d
        set table[GetHandleId(d)] = this

        return this
    endmethod
endstruct

private function OnCallback takes nothing returns nothing
    local IntegerListItem iter = ongoing.first
    local IntegerListItem iterConditions
    local PeriodicData data
    local DestructableRevivalPredicate condition
    local boolean flag

    loop
        exitwhen iter == 0
        set data = iter.data
        if GetDestructableTypeId(data.dest) == 0 or GetDestructableLife(data.dest) > .405 then
            call data.destroy()
        else
            set data.remaining = data.remaining - 0.031250000

            if data.remaining <= 0 then
                set retReal = 0
                set argDest = data.dest
                set iterConditions = conditions.first

                loop
                    exitwhen iterConditions == 0
                    set condition = iterConditions.data
                    if not TriggerEvaluate(triggers[condition]) then
                        exitwhen true
                    endif
                    set iterConditions = iterConditions.next
                endloop

                if retReal > 0 then
                    set data.remaining = retReal
                else
                    set flag = IsDestructableRevivalAnimation(GetDestructableTypeId(data.dest))
                    call DestructableRestoreLife(data.dest, GetDestructableMaxLife(data.dest), flag)
                    call data.destroy()
                endif
            endif
        endif
        set iter = iter.next
    endloop
endfunction

private function OnDeath takes nothing returns boolean
    local IntegerListItem iter = filters.first
    local DestructableRevivalPredicate filter
    local PeriodicData data

    if not DestructableRevival.enabled then
        return false
    endif

    set argDest = GetTriggerDestructable()
    loop
        exitwhen iter == 0
        set filter = iter.data
        if not TriggerEvaluate(triggers[filter]) then
            return false
        endif
        set iter = iter.next
    endloop

    if not table.has(GetHandleId(argDest)) then
        if ongoing.empty() then
            call TimerStart(looper, 0.031250000, true, function OnCallback)
        endif
        set data = PeriodicData.create(argDest)
    else
        set data = table[GetHandleId(argDest)]
    endif
    set data.remaining = GetDestructableRevivalDelay(GetDestructableTypeId(argDest))

    return false
endfunction

private function RegisterEnumDestructableRevival takes nothing returns nothing
    call RegisterDestructableRevival(GetEnumDestructable())
endfunction

private module DestructableRevivalInit
    private static method onInit takes nothing returns nothing
        set filters = IntegerList.create()
        set conditions = IntegerList.create()
        set ongoing = IntegerList.create()
        set table = Table.create()

        call EnumDestructablesInRect(bj_mapInitialPlayableArea, null, function RegisterEnumDestructableRevival)
        call TriggerAddCondition(trig, Condition(function OnDeath))
    endmethod
endmodule

struct DestructableRevival extends array
    static boolean enabled = true

    implement DestructableRevivalInit
endstruct

endlibrary
Demo code:
JASS:
scope DestructableRevivalDemo initializer Init

struct MyRevivalFilter extends array
    static method shouldRevive takes destructable whichDest returns boolean
        return IsDestructableTree(whichDest)
    endmethod

    implement DestructableRevivalFilterModule
endstruct

struct MyRevivalCondition extends array
    static method shouldExtend takes destructable whichDest returns real
        if GetClosestUnitInRange(GetDestructableX(whichDest), GetDestructableY(whichDest), 200, null) != null then
            return 4.0
        endif
        return 0.0
    endmethod

    implement DestructableRevivalConditionModule
endstruct

private function Init takes nothing returns nothing
    call AddDestructableRevivalFilter(MyRevivalFilter.create())
    call AddDestructableRevivalCondition(MyRevivalCondition.create())
endfunction

endscope
 

Attachments

  • DestructableRevival.w3x
    53.2 KB · Views: 274
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
One thing : GetExpiredTimer() cache please?
and also,how about using Table? you are just using the hashtable for the boolean. and is it okay for you to retrieve whether a boolean is saved in the hashtable or not? because that's how HaveSavedBoolean works.
You have to understand that you are not always forced to create local as reference to given handle. If number of function calls retireving given handle is <=2 its okey to leave it as it is.

objcectids are numbers far greater that 8191 so using integer array seems bad idea. Although, I could use method: [objectid - <some big fixed number here>] but I'm unsure whether its safe method or not when talkin about destids. Can add Table support, sure.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Almia - now read carefully documentation once again.
It says "TimerUtilsEx" in requirements, am I right?

TimerUtilsEx has Table as optional already, so there is no point in listing it in my snippet. Yes I know Mag uses additional constant for table usage - which in big maps needs to be true anyways.

Edit: Added important note about Mag's timer lib in OP ;>

Edit: Changed owner of dummy from Player(0) to Player(15) in IDTModule.
 
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Updated. Struct is now called TreeRevival instead of DestRevival.
Table is now listed as optional, added some extra space within code. Got rid of IDTtext_macro - instead it's just static if.

I noticed that there might be some issues whether folk uses Mag's newer utils or Vex's one. Whatever man, your call if you use one of these or something else. Script is run by textmacro and user determines which stuff he/she wants to be used. One exception though: given library must provide appropriate API, similar to interface granted by commonly used timerutils.

Unfortunately jass doesn't allow running macro within other macro instance, whatmore I'm not allowed to pass any scalar as argument (only straight string is an option) thus instead of globals block there is a single line at the top of lib that determines timer_library name.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
TimerUtilsEx has 100% backwards compability with TimerUtils, it doesn't have to be specified, hence you can remove the textmacro.
There is no other library sharing their API.

Remove your signature.
callback is one word.

This one private constant boolean ENABLED = true should be static boolean enabled = true. --> set TreeRevival.enabled = ?
Making it constant is kinda uhm useless. Same issue with show_animation.
You could cache GetExpiredTimer()
You should null this.tree
Afaik the proper order for stop is 851973.
static if's are cool. I would use library_once. I guess it doesn't matter which one you prefer.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
851973 is "Stunned" order while 851972 stands for "Stop" order.
Textmacro was added only for flexibility, if it's unnecessary I'll remove it.

Regarding constants - might be a good idea, most revivals used only constants mostly because they support only pre-placed dests.
And lol, I already had "No signature flag ticked".

Updated.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
How to detect created destructables? This is the same issue as with items. I'll post update today. I'd changed it into DestRevival with "tree-only option" available.

Edit: Updated to 2.2.3.2. Renamed to DestructableRevival. No longer includes optional IsDestructableTree function. Table requirement has been removed.
Instead of En(Dis)ableTreeTypeRevive and similar stuff for animation case and revive delay time, I've decided to use configurable functions placed at the top. Additional configurable: TREES_ONLY.
Improved documentation, added extra space. Will change test map soon ;>

Request: change thread's name to DestructableRevival. Btw: you prefer DestRevival or DestructableRevival?
 
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
You should be storing your handles to reduce function calls when possible.

For example:GetTriggerDestructable,GetEnumDestructable,GetExpiredTimer&GetDestructableTypeId ect..

Other than that it looks good.
At most:
- GetExpiredTimer() - 2 times
- GetTriggerDestructable() - 2 times
- GetEnumDestructable() - 2/1 times (implementation dependant)
- GetDestructableTypeId() - 1 times

Do you really think I should have stored timer and destructable in GetExpiredTimer() and GetTriggerDestructable() respectively?

In other two cases, it is not adviced.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Rewrite of DestructableRevival (vJass) snippet.

Now makes use of ListT. TimerUtils and IsDestructableTree requirements have been removed.
Instead of using one timer per destructable awaiting revival, now uses single global timer.
Improved system behavior by validating destructables enumerated in periodic handler. If destructable is invalidated for any reason, system properly deallocates all resources assigned to it.

Implemented DestructableRevivalCondition and DestructableRevivalFilter predicates. Those allow one to filter which destructables should be accounted for by the system or postpone revival if certain conditions are met e.g. a unit stands on top of a dead tree.

Jass-like api in form of:
- FilterTypeId
- GetDelayForType
- IsAnimationForType
has been removed.

Expanded function API with following:
- function SetDestructableRevivalDelay, for setting revival delay for dest type
- function GetDestructableRevivalDelay, for retrieving dest type revival delay
- function EnableDestructableRevivalAnimation, enable or disable revival animation
- function IsDestructableRevivalAnimation, whether revival animation is enabled for type
- function AddDestructableRevivalFilter, appends new destructable filter
- function RemoveDestructableRevivalFilter, removes existing destructable filter
- function AddDestructableRevivalCondition, appends new revival condition
- function RemoveDestructableRevivalCondition, removes existing revival condition

Updated documentation.
Validated to be Wurst-friendly.
No public api compatibility has been broken during this rewrite.

Added demo code to the opening post.
If one wants to compare the diff with previous version, please checkout this commit.
 
Top