• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Creating units with frames results in different unit handles

Status
Not open for further replies.
So... For the past 7 days I have tried to resolve the desyncs in my map and I think I have found the cause. When using the frame natives to create units there's a good chance that they are created with different handles.

For context:
I have a character select "screen" similar to that of WoW, and when the player selects a new class they want to create I remove and add the unit displayed in front of the player.

The difference in handles lead me to believe that the frame natives were async and I proceeded to create the units inside a synced event. And guess what? They are still created with different handles.
I then asked the Hive discord if I had to sync the button click and they told me the frames aren't async. So I changed it back and then created a timer and a boolean check for the button click so people can't spam the buttons, but still, the units are created with different handles.

Here's a demonstration (sry, I accidentally launched PlanetSide 2 a couple of times):

The heroes are loaded for each player (keep in mind I'm on the same PC so it loads the same hero for every client, if that confuses anyone), and the handles are the same on each client, so that's all good. But watch what happens when I swap between the different classes. At the end when I print the handles for every player you can see player 1 is out of sync with the others, and I'm just confused as hell.

Here's the code (Lua):
JavaScript:
function SelectClassFuncAlliance()
    local player = GetConvertedPlayerId(GetTriggerPlayer())
    -- Expand this loop when new classes gets added
    for i = 1, 3 do
        if (BlzGetTriggerFrame() == classFrame[i] and selectClassClickCheck[player] == false) then
            selectClassClickCheck[player] = true
            selectedCharName[player] = heroname[i]
            selectedCharClass[player] = class[i]
            CharSelectClick()
            if (GetLocalPlayer() == GetTriggerPlayer()) then
                BlzFrameSetEnable(classFrame[1], false)
                BlzFrameSetEnable(classFrame[2], false)
                BlzFrameSetEnable(classFrame[3], false)
                BlzFrameSetEnable(classFrame[11], false)
                BlzFrameSetEnable(classFrame[12], false)
                BlzFrameSetEnable(classFrame[13], false)
                BlzFrameSetEnable(cancelCharacter, false)
                BlzFrameSetEnable(acceptCharacter, false)
            end
            break
        end
    end
end

function SelectClassFuncHorde()
    local player = GetConvertedPlayerId(GetTriggerPlayer())
    -- Expand this loop when new classes gets added
    for i = 11, 13 do
        if (BlzGetTriggerFrame() == classFrame[i] and selectClassClickCheck[player] == false) then
            selectClassClickCheck[player] = true
            selectedCharName[player] = heroname[i]
            selectedCharClass[player] = class[i]
            CharSelectClick()
            if (GetLocalPlayer() == GetTriggerPlayer()) then
                BlzFrameSetEnable(classFrame[1], false)
                BlzFrameSetEnable(classFrame[2], false)
                BlzFrameSetEnable(classFrame[3], false)
                BlzFrameSetEnable(classFrame[11], false)
                BlzFrameSetEnable(classFrame[12], false)
                BlzFrameSetEnable(classFrame[13], false)
                BlzFrameSetEnable(cancelCharacter, false)
                BlzFrameSetEnable(acceptCharacter, false)
            end
            break
        end
    end
end

function CharSelectClick()
    local convertedPlayerID = GetConvertedPlayerId(GetTriggerPlayer())
    local class = selectedCharClass[convertedPlayerID]

    if (charSelectDisplayUnit[convertedPlayerID] ~= nil) then
        RemoveUnit(charSelectDisplayUnit[convertedPlayerID])
    end

    local location =
        PolarProjectionBJ(
        GetRectCenter(udg_CharacterSelectRegion[convertedPlayerID]),
        256,
        252 + convertedPlayerID * 18
    )
    CreateUnitAtLocSaveLast(Player(27), class, location, 252 + convertedPlayerID * 18)
    print(bj_lastCreatedUnit)
    charSelectDisplayUnit[convertedPlayerID] = bj_lastCreatedUnit

    StartTimerBJ(udg_SelectCharTimer[convertedPlayerID], false, 2)
end

function CharSelectTimerExpire()
    local player = Player(27)
    local convertedPlayerID

    for i = 1, bj_MAX_PLAYER_SLOTS do
        if(GetExpiredTimer() == udg_SelectCharTimer[i]) then
            print(i)
            player = Player(i-1)
            convertedPlayerID = GetConvertedPlayerId(player)
            selectClassClickCheck[convertedPlayerID] = false
            if (GetLocalPlayer() == player) then
                BlzFrameSetEnable(classFrame[1], true)
                BlzFrameSetEnable(classFrame[2], true)
                BlzFrameSetEnable(classFrame[3], true)
                BlzFrameSetEnable(classFrame[11], true)
                BlzFrameSetEnable(classFrame[12], true)
                BlzFrameSetEnable(classFrame[13], true)
                BlzFrameSetEnable(cancelCharacter, true)
                BlzFrameSetEnable(acceptCharacter, true)
            end
            break
        end
    end
end
 
Level 21
Joined
May 16, 2012
Messages
644
"when the player selects a new class they want to create I remove and add the unit displayed in front of the player."

As you said here, by removing the unit and creating another you are effectively handling a completly different unit. If you want the original unit pre-placed in the map, you shouldnt do that. What you can do is move the units in and out of screen, this way, they remain the same. Handles are reference to objects, in your case, a specific unit, if you remove/create then, their handles will be different.
 
So what you're saying is I should create every type of hero for each player that I can move back and forth? The game supports 20 players and I currently have 6 different heroes. That means if I host a game with 20 players I need 120 dummy heroes placed somewhere on the map, not to mention all the heroes the players load into the map. I mean, is warcraft 3 not capable of removing a unit and use the same variable to store the next without the handle problems? That's so laughable.
 
Level 21
Joined
May 16, 2012
Messages
644
So what you're saying is I should create every type of hero for each player that I can move back and forth? The game supports 20 players and I currently have 6 different heroes. That means if I host a game with 20 players I need 120 dummy heroes placed somewhere on the map, not to mention all the heroes the players load into the map. I mean, is warcraft 3 not capable of removing a unit and use the same variable to store the next without the handle problems? That's so laughable.

I'm not saying what you should do, just what's wrong. If in your map can only be one hero/class type per player, what you could do, is store each different pre-placed hero in a variable and switch the way you're doing right now, and when a player chooses that class/hero you use the variable you stored that hero/class type to actually do what you want. Just an idea, there's probably more ways of doing that.
 
You don't print HandleIds. If you use print directly onto an unit then you get some Lua pointer.
In warcraft 3 GetHandleId(unit) matters not the Lua pointer string.
Check, if GetHandleId(unit) is the same. If not you got a problem.
I'm doing this when printing:
JavaScript:
function PrintHandles()
    for p = 1, bj_MAX_PLAYER_SLOTS do
        for i = 1, 3 do
            BJDebugMsg("Player " .. p .. ": " .. GetHandleIdBJ(charPlayerSlots[i]))
        end
    end
end

Is that a Lua pointer or the actual unit handle? 'charPlayerSlots' is the variable that stores the unit.
 
Oh right, it's probably important to mention where that's used:
JavaScript:
function AcceptSelectedChar()
    local convertedPlayerID = GetConvertedPlayerId(GetTriggerPlayer())
    for i = 1, 3 do
        if (charPlayerSlots[convertedPlayerID][i] == 0) then
            charPlayerSlots[convertedPlayerID][i] = charSelectDisplayUnit[convertedPlayerID]
            charPlayerSlotsName[convertedPlayerID][i] = selectedCharName[convertedPlayerID]
            CheckMaxCharacters(convertedPlayerID)
            selectedCharNum[convertedPlayerID] = i
            NewHero(i, charSelectDisplayUnit[convertedPlayerID]) -- save function
            if (GetLocalPlayer() == GetTriggerPlayer()) then
                BlzFrameSetText(
                    charFrame[i],
                    selectedCharName[convertedPlayerID] ..
                        "\nLevel: " .. GetUnitLevel(charSelectDisplayUnit[convertedPlayerID])
                )
                BlzFrameSetEnable(acceptCharacter, false)
                BlzFrameSetEnable(enterWorld, true)
                BlzFrameSetEnable(charFrame[i], true)
                BlzFrameSetEnable(deleteChar, true)
                ShowCharSelection()
                HideCharCreation()
            end
            break
        end
    end
end

I'll have to check up on the player 1 stuff, because I'm not sure.

EDIT: the events are created like this:
JavaScript:
-- Alliance classes frames
    for i = 1, 3 do
        classFrame[i] = BlzCreateFrame("ScoreScreenBottomButtonTemplate", creationMenu, 0, 0)
        BlzFrameSetSize(classFrame[i], 0.05, 0.05)
        BlzFrameSetAbsPoint(classFrame[i], FRAMEPOINT_CENTER, 0.05, 0.55 - frameYPOS)
        imgFrame[i] = BlzGetFrameByName("ScoreScreenButtonBackdrop", 0)
        BlzFrameSetTexture(imgFrame[i], classFrameIcon[i], 0, true)

        frameYPOS = frameYPOS + 0.05

        -- BUTTON CALLBACKS --
        local selectClassTrig = CreateTrigger()
        BlzTriggerRegisterFrameEvent(selectClassTrig, classFrame[i], FRAMEEVENT_CONTROL_CLICK)
        TriggerAddAction(selectClassTrig, SelectClassFuncAlliance)
    end

    frameYPOS = 0.05 -- reset button height

    -- Horde classes frames
    for i = 11, 13 do
        classFrame[i] = BlzCreateFrame("ScoreScreenBottomButtonTemplate", creationMenu, 0, 0)
        BlzFrameSetSize(classFrame[i], 0.05, 0.05)
        BlzFrameSetAbsPoint(classFrame[i], FRAMEPOINT_CENTER, 0.15, 0.55 - frameYPOS)
        imgFrame[i] = BlzGetFrameByName("ScoreScreenButtonBackdrop", 0)
        BlzFrameSetTexture(imgFrame[i], classFrameIcon[i], 0, true)

        frameYPOS = frameYPOS + 0.05

        -- BUTTON CALLBACKS --
        local selectClassTrig = CreateTrigger()
        BlzTriggerRegisterFrameEvent(selectClassTrig, classFrame[i], FRAMEEVENT_CONTROL_CLICK)
        TriggerAddAction(selectClassTrig, SelectClassFuncHorde)
    end

EDIT 2: Just tested and the difference in handles are not exclusive to player 1, but it seems it happens more often for that player.
 
Last edited:
Status
Not open for further replies.
Top