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

UI: Save&Load Frames

Introduction

Currently none of the versions of warcraft 3 that have access to the UI-Frame natives do probably Save&Load the usage of Frames/TOC (this was written when 1.32.8 /1.32.9 PTR were the current Versions).

This broken Save&Load stops Frames from being recreaterd/modified when the Game is Loaded (but they still hold their handleIds). It is even worser as it sounds, when one uses a variable reference of such a now broken Frame then the game can crash.

As mapper one could fix it without Blizzard fixing the Save&Load for Frames.​

How to fix it

The idea is simple. We just let the old broken Frames leak (can't do much about it anyway) and repeat every frame creation/modification after a 0s timer run in a trigger with a Game was Loaded Event. With that all references should be updated and the custom UI does continue to work. Also be aware that you also have to restore any counters being used in the Frame creation and that you have to repeat the TOC-Loading. This works cleaner and better when your Frame creation/modification function doesn't do anything else then TOC and Frames.

One could write a ReLoader with that Game is Loaded Event that runs all the UI-Frame Init Functions.
In Lua one would have array of functions. But in vjass a trigger to which you add all the functions that have to be repeated after loading the game.​

Code Example

The crash code Example, it just creates a button at the center of the screen at 0s and makes it visible once a second. After the game was Saved & Loaded. The game will crash as soon the game tries to make the frame visible.
Lua:
do
    local frame
    local real = MarkGameStarted
    function MarkGameStarted()
    real()
        frame = BlzCreateFrame("ScriptDialogButton",  BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), 0, 0)       
        BlzFrameSetAbsPoint(frame, FRAMEPOINT_CENTER, 0.4, 0.3)
        TimerStart(CreateTimer(), 1, true, function()
            BlzFrameSetVisible(frame, true)
        end)
    end
end
This are a Lua and vjass example of the above described "reloading".

Lua

vjass


First the reloader:
Lua:
-- in 1.31 and upto 1.32.9 PTR (when I wrote this). Frames are not correctly saved and loaded, breaking the game.
-- This runs all functions added to it with a 0s delay after the game was loaded.
do
    local data = {}
    local real = MarkGameStarted
    local timer
   
    function FrameLoaderAdd(func)
        table.insert(data, func)
    end
    function MarkGameStarted()
        real()
        local trigger = CreateTrigger()
        timer = CreateTimer()
        TriggerRegisterGameEvent(trigger, EVENT_GAME_LOADED)
        TriggerAddAction(trigger, function()
            TimerStart(timer, 0, false, function()
                for _,v in ipairs(data) do v() end
            end)
           
        end)
    end
end
Second the example: This takes the first Hero Button and moves it to the center of the screen.
Lua:
do
    local real = MarkGameStarted
    local function Init()
        BlzEnableUIAutoPosition(false)
        local frame = BlzGetOriginFrame(ORIGIN_FRAME_HERO_BUTTON, 0)
        BlzFrameClearAllPoints(frame)
        BlzFrameSetAbsPoint(frame, FRAMEPOINT_TOPLEFT, 0.4, 0.3)       
    end
    function MarkGameStarted()
        real()
        Init()
        if FrameLoaderAdd then FrameLoaderAdd(Init) end
    end
end

JASS:
library FrameLoader initializer init_function
// in 1.31 and upto 1.32.9 PTR (when I wrote this). Frames are not correctly saved and loaded, breaking the game.
// This library runs all functions added to it with a 0s delay after the game was loaded.
// function FrameLoaderAdd takes code func returns nothing
    // func runs when the game is loaded.
    globals
        private trigger eventTrigger = CreateTrigger()
        private trigger actionTrigger = CreateTrigger()
        private timer t = CreateTimer()
    endglobals
    function FrameLoaderAdd takes code func returns nothing
        call TriggerAddAction(actionTrigger, func)
    endfunction
    private function timerAction takes nothing returns nothing
        call TriggerExecute(actionTrigger)
    endfunction
    private function eventAction takes nothing returns nothing
        call TimerStart(t, 0, false, function timerAction)
    endfunction
    private function init_function takes nothing returns nothing
        call TriggerRegisterGameEvent(eventTrigger, EVENT_GAME_LOADED)
        call TriggerAddAction(eventTrigger, function eventAction)       
    endfunction
endlibrary
Second the example: This takes the first Hero Button and moves it to the center of the screen.
JASS:
library Example initializer init_function requires FrameLoader
    private function At0s takes nothing returns nothing
        local framehandle frame = BlzGetOriginFrame(ORIGIN_FRAME_HERO_BUTTON, 0)
        call BlzEnableUIAutoPosition(false)
        call BlzFrameClearAllPoints(frame)
        call BlzFrameSetAbsPoint(frame, FRAMEPOINT_TOPLEFT, 0.4, 0.3)   
    endfunction
    private function init_function takes nothing returns nothing
        call FrameLoaderAdd(function At0s)
        call TimerStart(CreateTimer(), 0, false, function At0s)
    endfunction
endlibrary
 

Attachments

  • Crash.w3x
    12 KB · Views: 123
  • Lua.w3x
    12.7 KB · Views: 119
  • vjass.w3x
    13.5 KB · Views: 91

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
Couldn't let me approve your tutorials before adding a new one could you?

No edits needed in terms of structure which is nice.

Will read through this later today.

edit: so I suspect this might be too simple.
Wurst:
import ClosureTimers
import ClosureEvents

init
    EventListener.add(EVENT_GAME_LOADED) ->
        doAfter(0) ->
            myUiInitFunction()

This is basically it right? admittedly wurst's standard library makes this insanely simple but I consider the following:

A user will fall into one of two groups
  • The user has knowledge of some text based modding language and is using that to build their own UI from scratcch
  • The user has downloaded a pre-built UI
The first group does not need a tutorial to achieve this as they already know basic coding, they just need the information "create frames after a 0 second delay on load"
The second group should rely on the prebuild supporting this to begin with.
 
Last edited:
Can existing custom frames (bugged through save and load) be safely destroyed, or would it still crash the game? This is in the context of recreating the frames afterwards.
Even BlzDestroyFrame crahses the game when used onto a variable that points to such a broken frame after loading (happens in 1.31 and 1.32.8).
Tring to reach that frame over BlzGetFrameByName won't give you the broken Frame, hence no crash but also no Destroyed Frame.

This is basically it right? admittedly wurst's standard library makes this insanely simple but I consider the following:

A user will fall into one of two groups
  • The user has knowledge of some text based modding language and is using that to build their own UI from scratcch
  • The user has downloaded a pre-built UI
The first group does not need a tutorial to achieve this as they already know basic coding, they just need the information "create frames after a 0 second delay on load"
The second group should rely on the prebuild supporting this to begin with.
You got this, Total true. The solution is quite simple.
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
Right.

So while I technically think this is too simple for a tutorial, given that the target audience probably already knows jass; (more detailed reasoning in my previous post)
I think this is mandatory information on the subject which is currently not common knowledge and needs to be spread somehow.

So for pure usefulness, I'll approve it, although I will try to keep this one at the back of my head in case blizzard fixes this in the future.
 
Last edited:
Top