• 🏆 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] Weather

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Struct family I've made some time ago. It was first made in GUI via request few years ago.

This might be usefull for users working with weathereffects. Provides nicely coupled api to ease creation and management of weathereffects.

You can not define 2 instances of WeatherEffect given rect r if both such instances share WeatherType. Of course here, we consider only situation when you have those 2 instances are running at the same time.
You can have multiple WeatherEffects with different types running at the same time.

Instances are associated with thier rects, thus some safety is already included. It is up to user to ensure weathereffect viability on random rect 'r' though. Make sure you understand the weathereffects with all thier flaws before you start using this snippet. You can always use "Help" subforum or refer to following threads: weather & rects, weather & handle id.

JASS:
/*****************************************************************************
*
*    Weather v1.1.0.0
*       by Bannar aka Spinnaker
*
*    Defines Weather struct family, for better weathereffect management.
*
******************************************************************************
*
*    Optional requirements:
*
*       Table by Bribe
*          hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*
*       Alloc - choose whatever you like
*
******************************************************************************
*
*    Function:
*
*       function GetRectWeather takes rect r, WeatherType wt returns WeatherEffect
*          returns WeatherEffect instance (if exists) associated with rect r of type wt
*
******************************************************************************
*
*    struct WeatherType:
*
*       constant type    Rain
*       constant type    Shield
*       constant type    DungeonFog
*       constant type    Snow
*       constant type    Wind
*       constant type    Ray
*
******************************************************************************
*
*    struct WeatherStyle:
*
*       Fields:
*
*        | readonly integer effectId
*        |    static id of weathereffect style
*        |
*        | readonly WeatherType type
*        |    generic type of weathereffect
*
*       constant style    AshenvaleRainHeavy
*       constant style    AshenvaleRainLight
*       constant style    DalaranShield
*       constant style    DungeonBlueFogHeavy
*       constant style    DungeonBlueFogLight
*       constant style    DungeonGreenFogHeavy
*       constant style    DungeonGreenFogLight
*       constant style    DungeonRedFogHeavy
*       constant style    DungeonRedFogLight
*       constant style    DungeonWhiteFogHeavy
*       constant style    DungeonWhiteFogLight
*       constant style    LordaeronRainHeavy
*       constant style    LordaeronRainLight
*       constant style    NorthrendBlizzard
*       constant style    NorthrendSnowHeavy
*       constant style    NorthrendSnowLight
*       constant style    OutlandWindHeavy
*       constant style    OutlandWindLight
*       constant style    RaysOfLight
*       constant style    RaysOfMoonlight
*       constant style    WindHeavy
*
******************************************************************************
*
*    struct WeatherEffect:
*
*       Fields:
*
*        | readonly weathereffect weather
*        |    actual weathereffect handle
*        |
*        | readonly rect where
*        |    area which weathereffect is associated with
*        |
*        | readonly boolean enabled
*        |    indicates whether weathereffect is visible or not
*        |
*        | method operator style takes nothing returns WeatherStyle
*        | method operator style= takes WeatherStyle ws returns nothing
*        |    generic style of weathereffect
*
*       Methods:
*
*        | static method create takes rect r, WeatherStyle ws returns thistype
*        |    default ctor, creates new WeatherEffect given the rect and WeatherStyle
*        |
*        | method destroy takes nothing returns nothing
*        |    default dctor
*        |
*        | method enable takes nothing returns nothing
*        |    enables this weathereffect on associated rect
*        |
*        | method disable takes nothing returns nothing
*        |    disables this weathereffect on associated rect
*
*****************************************************************************/
library WeatherEffect uses optional Table, optional Alloc

struct WeatherType extends array
    readonly static thistype    Rain          = 1
    readonly static thistype    Shield        = 2
    readonly static thistype    DungeonFog    = 3
    readonly static thistype    Snow          = 4
    readonly static thistype    Wind          = 5
    readonly static thistype    Ray           = 6
endstruct

private module WeatherStyleInit
    private static method onInit takes nothing returns nothing
        set AshenvaleRainHeavy      = create('RAhr', WeatherType.Rain)
        set AshenvaleRainLight      = create('RAlr', WeatherType.Rain)
        set DalaranShield           = create('MEds', WeatherType.Shield)
        set DungeonBlueFogHeavy     = create('FDbh', WeatherType.DungeonFog)
        set DungeonBlueFogLight     = create('FDbl', WeatherType.DungeonFog)
        set DungeonGreenFogHeavy    = create('FDgh', WeatherType.DungeonFog)
        set DungeonGreenFogLight    = create('FDgl', WeatherType.DungeonFog)
        set DungeonRedFogHeavy      = create('FDrh', WeatherType.DungeonFog)
        set DungeonRedFogLight      = create('FDrl', WeatherType.DungeonFog)
        set DungeonWhiteFogHeavy    = create('FDwh', WeatherType.DungeonFog)
        set DungeonWhiteFogLight    = create('FDwl', WeatherType.DungeonFog)
        set LordaeronRainHeavy      = create('RLhr', WeatherType.Rain)
        set LordaeronRainLight      = create('RLlr', WeatherType.Rain)
        set NorthrendBlizzard       = create('SNbs', WeatherType.Snow)
        set NorthrendSnowHeavy      = create('SNhs', WeatherType.Snow)
        set NorthrendSnowLight      = create('SNls', WeatherType.Snow)
        set OutlandWindHeavy        = create('WOcw', WeatherType.Wind)
        set OutlandWindLight        = create('WOlw', WeatherType.Wind)
        set RaysOfLight             = create('LRaa', WeatherType.Ray)
        set RaysOfMoonlight         = create('LRma', WeatherType.Ray)
        set WindHeavy               = create('WNcw', WeatherType.Wind)
    endmethod
endmodule

struct WeatherStyle extends array
    private static integer count = 1 // up to 21
    readonly integer effectId
    readonly WeatherType type

    readonly static thistype    AshenvaleRainHeavy
    readonly static thistype    AshenvaleRainLight
    readonly static thistype    DalaranShield
    readonly static thistype    DungeonBlueFogHeavy
    readonly static thistype    DungeonBlueFogLight
    readonly static thistype    DungeonGreenFogHeavy
    readonly static thistype    DungeonGreenFogLight
    readonly static thistype    DungeonRedFogHeavy
    readonly static thistype    DungeonRedFogLight
    readonly static thistype    DungeonWhiteFogHeavy
    readonly static thistype    DungeonWhiteFogLight
    readonly static thistype    LordaeronRainHeavy
    readonly static thistype    LordaeronRainLight
    readonly static thistype    NorthrendBlizzard
    readonly static thistype    NorthrendSnowHeavy
    readonly static thistype    NorthrendSnowLight
    readonly static thistype    OutlandWindHeavy
    readonly static thistype    OutlandWindLight
    readonly static thistype    RaysOfLight
    readonly static thistype    RaysOfMoonlight
    readonly static thistype    WindHeavy

    private static method create takes integer id, WeatherType t returns thistype
        local thistype this = count
        set effectId = id
        set type = t

        set count = count + 1

        return this
    endmethod

    implement WeatherStyleInit
endstruct

static if LIBRARY_Table then
    private module WeatherBaseInit
        private static method onInit takes nothing returns nothing
            set table = TableArray[7]
        endmethod
    endmodule
endif

private struct WeatherBase extends array
    static if LIBRARY_Table then
        static TableArray table
        implement WeatherBaseInit
    else
        static hashtable table = InitHashtable()
    endif
endstruct

function GetRectWeather takes rect r, WeatherType wt returns WeatherEffect
    static if LIBRARY_Table then
        return WeatherBase.table[wt][GetHandleId(r)]
    else
        return LoadInteger(WeatherBase.table, wt, GetHandleId(r))
    endif
endfunction

struct WeatherEffect extends array
    readonly weathereffect weather
    readonly rect where
    readonly boolean enabled
    private WeatherStyle wstyle

    private method init takes WeatherStyle ws returns nothing // assumes all the checks already happened
        if ( wstyle != 0 ) then
            call RemoveWeatherEffect(weather)
        endif

        set weather = AddWeatherEffect(where, ws.effectId)
        call EnableWeatherEffect(weather, enabled)

        if ( ws.type != wstyle.type ) then
            static if LIBRARY_Table then
                call WeatherBase.table[wstyle.type].remove(GetHandleId(where))
                set WeatherBase.table[ws.type][GetHandleId(where)] = this
            else
                call RemoveSavedInteger(WeatherBase.table, wstyle.type, GetHandleId(where))
                call SaveInteger(WeatherBase.table, ws.type, GetHandleId(where), this)
            endif

            set wstyle = ws
        endif
    endmethod

    method operator style takes nothing returns WeatherStyle
        return wstyle
    endmethod

    method operator style= takes WeatherStyle ws returns nothing
        local WeatherEffect we

        if ( ws != wstyle ) then
            set we = GetRectWeather(where, wstyle.type)
            if ( we == 0 or we == this ) then            
                call init(ws) // initializes actual weathereffect
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "WeatherEffect::operator style= failed assigning new style. Area is already connected with WeatherEffect instance of type: " + I2S(ws.type) + ".")
            endif
        endif
    endmethod

    implement optional Alloc

    static if not thistype.allocate.exists then
        private static integer instances = 0
        private thistype recycle

        static method allocate takes nothing returns thistype
            local thistype this = thistype(0).recycle

            if (this == 0) then
                set instances = instances + 1
                set this = instances
            else
                set thistype(0).recycle = this.recycle
            endif

            return this
        endmethod

        method deallocate takes nothing returns nothing
            set this.recycle = thistype(0).recycle
            set thistype(0).recycle = this
        endmethod
    endif

    static method create takes rect r, WeatherStyle ws returns thistype
        local thistype this = GetRectWeather(r, ws.type)

        if ( this == 0 ) then
            set this = allocate()
            set where = r
            set enabled = false
            call init(ws) // initializes actual weathereffect
        debug else
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "WeatherEffect::create failed allocating new instance. Area is already connected with WeatherEffect instance of type: " + I2S(ws.type) + ".")
        endif

        return this
    endmethod

    method destroy takes nothing returns nothing
        static if LIBRARY_Table then
            call WeatherBase.table[wstyle.type].remove(GetHandleId(where))
        else
            call RemoveSavedInteger(WeatherBase.table, wstyle.type, GetHandleId(where))
        endif

        call RemoveWeatherEffect(weather)
        set weather = null
        set where = null
        set wstyle = 0

        call deallocate()
    endmethod

    method enable takes nothing returns nothing
        set enabled = true
        call EnableWeatherEffect(weather, enabled)
    endmethod

    method disable takes nothing returns nothing
        set enabled = false
        call EnableWeatherEffect(weather, enabled)
    endmethod
endstruct

endlibrary
Demo code:
Notice that operator "style=" will change WeatherStyle of WeatherEffect dynamically and set it's visibility accordingly - no need for additional .enable() call.

JASS:
scope WeatherTest initializer onInit

    private function StartWeather takes nothing returns nothing
        local WeatherEffect we = WeatherEffect.create(bj_mapInitialPlayableArea, WeatherStyle.AshenvaleRainHeavy)
        local WeatherEffect we2 = WeatherEffect.create(bj_mapInitialPlayableArea, WeatherStyle.DalaranShield)

        call we.enable()
        call we2.enable()

        if ( we.enabled ) then
            set we.style = WeatherStyle.LordaeronRainLight
        endif

        call DestroyTimer(GetExpiredTimer())
    endfunction

    private function onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 5, false, function StartWeather)
    endfunction

endscope
 
Last edited:
Level 12
Joined
Feb 22, 2010
Messages
1,115
Can we just create a duplication of that rect which we want 2nd effect(same position, size) and add weather effect to that rect?
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
You can have multiple effects with different types on any rect r (up to 6). However, because of how weathereffects are connected to rects (global assignment), you can not "trick" the game multiplying rect that contains some part or even whole another rect which has already weathereffect assigned to it, to apply an additional weathereffect of the same type is that area. This restriction is hardcoded.

Some usefull informations.

This will work:
JASS:
    private function StartWeather takes nothing returns nothing
        local rect r = Rect(-500, -500, 500, 500)
        local rect r2 = Rect(600, 600, 1000, 1000)
        local WeatherEffect we = WeatherEffect.create(r2, WeatherStyle.DungeonRedFogHeavy)
        local WeatherEffect we2 = WeatherEffect.create(r, WeatherStyle.DungeonGreenFogHeavy)

        call we.enable()
        call we2.enable()

        call DestroyTimer(GetExpiredTimer())
    endfunction

And this, unfortunately, will not:
JASS:
    private function StartWeather takes nothing returns nothing
        local rect r = Rect(-500, -500, 500, 500)
        local WeatherEffect we = WeatherEffect.create(bj_mapInitialPlayableArea, WeatherStyle.DungeonRedFogHeavy)
        local WeatherEffect we2 = WeatherEffect.create(r, WeatherStyle.DungeonGreenFogHeavy)

        call we.enable()
        call we2.enable()

        call DestroyTimer(GetExpiredTimer())
    endfunction
 
Last edited:
Awesome. I like it. Only one thing before it gets approved: isn't there that bug where the first weather effect == null? You may want to account for that somehow (in destroy, you compare weather to null). If removing a null weather effect is no issue, I would just consider putting the "RemoveWeatherEffect" directly without the if-check, and let the internal wc3 script handle verifying whether the effect exists or not.

Also, consider adding a module onInit. I know it sucks, sorry.
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
Very nice wrapper. Haven't tested it yet but this is just what I need. I tried making my own wrapper but for some reason the weather just wasn't turning off. Hopefully this will fix it.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Since I really like when resource is "light" on requirements I've implemented this idea even in this wrapper. Hope you won't be angry seeing code getting bigger on paper :) tho actual code size doesn't change.
As of now, this is flexible as most (or even all) of my resources. ^)^

Btw, whats the best method to declare only specific "globals"? Textmacros suck, static ifs + global block sucks and option c) are structs. Only then it works properly enough.

Here, I wanted to "hide" table member, however, in WeatherEffect I'd had to declare it as "readonly" so function GetRectWeather has access to it. To privatize it in some way I implemented:
JASS:
    private struct WeatherBase extends array
        static if LIBRARY_Table then
            static key table
        else
            static hashtable table = InitHashtable()
        endif
    endstruct

Should I just ignore this stuff and add this member directly into WeatherEffect struct as readonly or leave it the way it is?

Thanks in advance.
 
There are three options:
(1) Your method.
(2) Readonly.
(3) Add a static method to get rect weather. That way, you can keep the hashtable private, but that exposes some random method to the user to get rect weather (with struct syntax).

I think your method is fine though. It doesn't expose anything extraneous to the user. Only issue: "static key table" should be "static Table table".
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Bribe's table is made in such way that you can use "keys" since .create() method instanciates Tables from 8192 or so. Whatmore you can increase that value at the top of Table script if you need to.

Second, honestly, I prefer "readonly" in case uber-safety shouldn't be really an issue since person who would use that script, problably already knows how to use it and that protecting user from himself is rather unnecessary.

The method approach is wierd in case static method would basically serve as "readonly-operator". Plus, leaving public methods alone without them being described in lib description (since it's not really part of api) seems wierd.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Minor update.

Predefined WeatherType instances are now defined as readonly thistypes instead of constant integers (just to keep struct consistent with WeatherStyle).
Removed safety "if statements" within enable/disable methods for WeatherEffect struct - reason: new instance has to be created anyway for AddWeatherEffect native to be called. Without that, Enable/Disable WeatherEffect won't do anything anyway.
Predefined instances for WeatherStyle are now instanciated from 1 instead of 0 as they should be. This allows you to check for invalid style in more convenient way (myWeatherStyle != 0).
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Major upgrade, thanks to TheJackal47.

GetRectWeather() function now returns the WeatherEffect instance based on both, rect 'r' and WeatherType 'wt' instead of relaying solely on rect handle. This is the exact same issue you should be familliar with when dealing with Maggy's RegisterPlayerUnitEvent (check out retrieval function).

WeatherType instances are now instanciated from index of value 1, same as WeatherStyle.
Added 'enabled' flag giving user option to check if given WeatherEffect is visible.
Added 'style' operator. Allows for dynamical WeatherStyle changing. Handle-id issue has been taken into consideration to provide bug-free switch. Additionaly, once weathereffect has been re-initialized, its enabled flag will be used in order to re-enable the weathereffect if needed. This relives user from calling .enable() after using 'style' operator.

Documentation and OP have been refreshed to reflect the changes.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Sentence at the top:
Make sure you understand the weathereffects with all thier flaws before you start using this snippet.
Is actually very important. This snippet takes into consideration and deals with all limitations of hard-coded in-game weathereffect handle.

@edo494, you can get glimpse of info from threads I've linked in OP. To answer your question: Blizzard has divided weathereffect into separate types and provided a limitation of weathereffect-type per area. In other words: you can have up to 6 weathereffects per area (512 x 512) granted they all differ in type. Enabling multiple "Rains" within same area with earn you single weathereffect instead.

This snippet links area with WeatherType and then with given WeatherEffect instance enabling for smooth and instant weather manipulation, plus it's equiped with some safety - handles re-applying issue and handleId stuff.
 
Top