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

[vJASS] Any leaks in this dialog system?

Status
Not open for further replies.
Level 15
Joined
Aug 7, 2013
Messages
1,337
Hi,

I was curious if there were any leaks in this dialog system I wrote.

I'm a bit nervous because I am handling a lot of shuffling of units between different data structures, and it's possible my logic actually causes me to lose handles to these "units," in which case I'd have leaks. I'm also dealing with removing units completely, but not sure if this is done right either.

It's a little long, but if there any leaks I would appreciate help in fixing them.

Note that the struct "Farmer" is only created once, so the globals don't get overwritten. The design may seem stupid, but I'll get to fixing it once I know my logic/leak fixing is correct.

JASS:
library FarmerStruct requires NPCStruct

globals
    private dialog intro
    private button takeBttn //the player wishes to take a monster from the farm
    private button dropoffBttn //the player wishes to drop a monster off at the farm
    private button swapBttn //the player wishes to swap a monster from the party with one from the farm
    private button releaseBttn //the player wishes to get rid of a monster at the farm
    private button quitBttn //option to leave the dialog
    private string introMessage = "|cffff8000Monster Keeper|r:\n |cfff0f000Picking up or dropping off?|r"
endglobals

//a player effectively gets rid of one his monsters at the farm
private function releaseMon takes nothing returns boolean
    local button b = GetClickedButton()
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    local integer i = 0 //counter
    local MonsterGroup farm
    local Monster target
    set farm = playerDatum[pid].farm
    loop
        exitwhen i == MAX_FARM_SIZE
        if b == farm.bttns[i] then
            set target = farm.monsters[i]
            set target.toRelease = true
            call DialogSetMessage(playerDatum[pid].release, "Release your " + target.toString() + "?\nOnce you do, it's gone forever")
            call DialogDisplay(p, playerDatum[pid].release, true) //the release is handled by PlayerData itself   
        endif
        set i = i + 1
    endloop
    set b = null
    set p = null
    //call target.destroy()
    //call party.destroy()
    //call farm.destroy()
    call DestroyTrigger(playerDatum[pid].npcTrig)
    return false
endfunction

//swap a monster from the party and the farm
private function swapMon2 takes nothing returns boolean
    local button b = GetClickedButton()
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    local integer i = 0 //counter
    local integer j = 0 //a second loop counter for going through the player's party
    local MonsterGroup party
    local MonsterGroup farm
    local Monster temp
    set party = playerDatum[pid].party
    set farm = playerDatum[pid].farm
    loop
        exitwhen i == MAX_FARM_SIZE
        if b == farm.bttns[i] then
            loop
                exitwhen j == MAX_PARTY_SIZE
                if party.monsters[j].toSwap == true then
                    set party.monsters[j].toSwap = false
                    set temp = party.monsters[j]
                    set party.monsters[j] = farm.monsters[i]
                    set farm.monsters[i] = temp
                    call SetUnitOwner(party.monsters[j].u, p, true)
                    call SetUnitOwner(farm.monsters[i].u, BOT_ALLY, true)
                    call DisplayTimedTextToPlayer(p, 0, 0, DSPLY_TXT_DUR, "|cffff8000Monster Keeper|r: Swapped out " + farm.monsters[i].toString() + " for " + party.monsters[j].toString())
                endif
                set j = j + 1
            endloop
        endif
        set i = i + 1
    endloop
    loop
        exitwhen i == 0
        if party.monsters[i].toSwap == true then
            set party.monsters[i].toSwap = false
        endif
        set i = i + 1
    endloop
    set b = null
    set p = null
    //call target.destroy()
    //call party.destroy()
    //call farm.destroy()
    call DestroyTrigger(playerDatum[pid].npcTrig)
    return false
endfunction


//swap a monster from the party and the farm
private function swapMon takes nothing returns boolean
    local button b = GetClickedButton()
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    local integer i = 0 //counter
    local trigger t //a second trigger
    local trigger d //a reference to the old NPC trig
    local MonsterGroup party
    local Monster target
    set party = playerDatum[pid].party
    set d = playerDatum[pid].npcTrig
    loop
        exitwhen i == MAX_PARTY_SIZE
        if b == party.bttns[i] then
            set target = party.monsters[i]
            set target.toSwap = true //set the monster's swap flag to true, we're swapping it!
            call playerDatum[pid].farm.displayGroup(true, "Which monster will be taking?")
            set t = CreateTrigger()
            call TriggerRegisterDialogEvent(t, playerDatum[pid].farm.mDialog)
            call TriggerAddCondition(t, Condition(function swapMon2))
            set playerDatum[pid].npcTrig = t
        endif
        set i = i + 1
    endloop
    set b = null
    set p = null
    set t = null
    //call target.destroy()
    //call party.destroy()
    //call farm.destroy()
    call DestroyTrigger(d)
    set d = null
    return false
endfunction


private function takeMon takes nothing returns boolean
    local button b = GetClickedButton()
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    local integer i = 0 //counter
    local MonsterGroup party
    local MonsterGroup farm
    local Monster target
    set party = playerDatum[pid].party
    set farm = playerDatum[pid].farm
    loop
        exitwhen i == MAX_FARM_SIZE
        if b == farm.bttns[i] then
            set target = farm.monsters[i]
            if farm.moveMonster(party, target) then
                call DisplayTimedTextToPlayer(p, 0, 0, DSPLY_TXT_DUR, "|cffff8000Monster Keeper|r: Your " + target.toString() + " is happy to join you.  It really missed you!")
                call SetUnitOwner(target.u, p, true) //get back the player's monster
            else //somehow we failed when we shouldn't have
                call DisplayTimedTextToPlayer(p, 0, 0, DSPLY_TXT_DUR, "Error: Could not move " + target.toString() + " when we should be able to.")
            endif   
        endif
        set i = i + 1
    endloop
    set b = null
    set p = null
    //call target.destroy()
    //call party.destroy()
    //call farm.destroy()
    call DestroyTrigger(playerDatum[pid].npcTrig)
    return false
endfunction

private function dropoffMon takes nothing returns boolean
    local button b = GetClickedButton()
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    local integer i = 0 //counter
    local MonsterGroup party
    local MonsterGroup farm
    local Monster target
    set party = playerDatum[pid].party
    set farm = playerDatum[pid].farm
    loop
        exitwhen i == MAX_PARTY_SIZE
        if b == party.bttns[i] then
            set target = party.monsters[i]
            if party.moveMonster(farm, target) then
                call DisplayTimedTextToPlayer(p, 0, 0, DSPLY_TXT_DUR, "|cffff8000Monster Keeper|r: Your " + target.toString() + " is now at the farm.")
                call SetUnitOwner(target.u, BOT_ALLY, true) //change the ownership for now
            else //somehow we failed when we shouldn't have
                call DisplayTimedTextToPlayer(p, 0, 0, DSPLY_TXT_DUR, "Error: Could not move " + target.toString() + " when we should be able to.")
            endif   
        endif
        set i = i + 1
    endloop
    set b = null
    set p = null
    //call target.destroy()
    //call party.destroy()
    //call farm.destroy()
    call DestroyTrigger(playerDatum[pid].npcTrig)
    return false
endfunction

private function introMain takes nothing returns boolean
    local button b = GetClickedButton()
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    local MonsterGroup party
    local MonsterGroup farm
    local trigger t
    set party = playerDatum[pid].party
    set farm = playerDatum[pid].farm
    if b == quitBttn then
        call DialogClear(intro)
    elseif b == takeBttn then //first check to see if the player has enough party room
        if party.size == MAX_PARTY_SIZE then //party is full, end the dialog
            call DisplayTimedTextToPlayer(Player(pid), 0, 0, DSPLY_TXT_DUR, "|cffff8000Monster Keeper|r: Your party is already full with " + I2S(party.size) + " monsters in it.  Drop off a monster first.")
        else //the player's party isn't full, so we need to list his farm monsters
            call farm.displayGroup(true, "Which monster will you be taking?")
            set t = CreateTrigger()
            call TriggerRegisterDialogEvent(t, farm.mDialog)
            call TriggerAddCondition(t, Condition(function takeMon))
            set playerDatum[pid].npcTrig = t
        endif
    elseif b == dropoffBttn then
        if party.size == MIN_PARTY_SIZE then //party cannot go below one monsters
            call DisplayTimedTextToPlayer(Player(pid), 0, 0, DSPLY_TXT_DUR, "|cffff8000Monster Keeper|r: You can't go around without any monsters!  Take another monster first.")
        elseif farm.size == MAX_FARM_SIZE then //the farm is full, player cannot store more monsters there
            call DisplayTimedTextToPlayer(Player(pid), 0, 0, DSPLY_TXT_DUR, "|cffff8000Monster Keeper|r: The farm is full with " + I2S(farm.size) + " monsters.  Switch monsters or release one.")
        else //the player's party has at least 2 monsters, so we need to list his party monsters
            call party.displayGroup(true, "Which monster will you be dropping off?")
            set t = CreateTrigger()
            call TriggerRegisterDialogEvent(t, party.mDialog)
            call TriggerAddCondition(t, Condition(function dropoffMon))
            set playerDatum[pid].npcTrig = t
        endif
    elseif b == swapBttn then
        if farm.size == 0 then //the farm is empty, no swapping allowed!
            call DisplayTimedTextToPlayer(Player(pid), 0, 0, DSPLY_TXT_DUR, "|cffff8000Monster Keeper|r: You can't swap monsters--the farm is empty!")
        endif
        //else we begin the swapping dialogs...
        call party.displayGroup(true, "Which monster will you swap from your party?")
        set t = CreateTrigger()
        call TriggerRegisterDialogEvent(t, party.mDialog)
        call TriggerAddCondition(t, Condition(function swapMon))
        set playerDatum[pid].npcTrig = t
    else //b == releaseBttn
        call farm.displayGroup(true, "Which monster will be released?\nOnce it's released into the wild,\n it's gone forever.")
        set t = CreateTrigger()
        call TriggerRegisterDialogEvent(t, farm.mDialog)
        call TriggerAddCondition(t, Condition(function releaseMon))
        set playerDatum[pid].npcTrig = t
    endif
    set b = null
    set t = null
    //call party.destroy()
    //call farm.destroy()
    return false
endfunction

struct Farmer extends NPC
    static method create takes integer unitType returns thistype
        local thistype this = thistype.allocate(unitType)
        local trigger t = CreateTrigger()
        set intro = DialogCreate()
        set takeBttn = DialogAddButton(intro, "Pick up a monster", 1)
        set dropoffBttn = DialogAddButton(intro, "Drop off a monster", 2)
        set swapBttn = DialogAddButton(intro, "Swap monsters", 3)
        set releaseBttn = DialogAddButton(intro, "Release a monster", 4)
        set quitBttn = DialogAddButton(intro, quitMessage, 5)
        call DialogSetMessage(intro, introMessage)
        call TriggerRegisterDialogEvent(t, intro)
        call TriggerAddCondition(t, Condition(function introMain))
        set t = null
        return this
    endmethod

    //creates the triggers necessary to run the priest's actions
    method interact takes integer pid returns nothing
        call DialogDisplay(Player(pid), intro, true)
    endmethod
endstruct
    
endlibrary

Here's the other bit of companion code for releaseMon :

JASS:
library PlayerDataStruct requires MonsterGroupStruct

globals
endglobals

private function releaseMain takes nothing returns boolean
    local button b = GetClickedButton()
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    local integer i = 0 //search the farm for the to be released monster
    local Monster target
    local MonsterGroup farm
    if b == playerDatum[pid].releaseYes then
        set farm = playerDatum[pid].farm
        loop
            exitwhen i == MAX_FARM_SIZE
                if farm.monsters[i].toRelease == true then //remove it
                    set target = farm.monsters[i]
                    call DisplayTimedTextToPlayer(p, 0, 0, 10, "Farmer: Say goodbye to your " + target.toString() + "; it's been released back into the wild.")
                    call farm.removeMonster(target)
                    call RemoveUnit(target.u)
                    call target.destroy()
                endif
            set i = i + 1
        endloop
    endif
    //else b == releaseNo
    loop
        exitwhen i == MAX_FARM_SIZE
        if farm.monsters[i].toRelease == true then //set the flag back to false, since we aren't releasing it yet
            set farm.monsters[i].toRelease = false
        endif
        set i = i + 1
    endloop
    set b = null
    set p = null
    call target.destroy()
    call farm.destroy()
    return false
endfunction

struct PlayerData
    integer pid = 0 //the player's unique id
    MonsterGroup party //the player's party monsters
    MonsterGroup farm //the player's farm monsters
    dialog release //make sure the player wants to remove this monster
    button releaseYes //the player does want to remove this monster
    button releaseNo //the player doesn't want to release this monster
    trigger npcTrig //the current npc trig, destroyed everytime a convo finishes
    unit u //the monster master handle

static method create takes integer pid returns thistype
    local thistype this = thistype.allocate()
    local trigger t = CreateTrigger()
    set this.u = CreateUnit(Player(pid), 'H013', CITY_CENTER_X, CITY_CENTER_Y, 0)
    set this.pid = pid
    set this.party = MonsterGroup.create(PARTY, pid) //initialize the party
    set this.farm = MonsterGroup.create(FARM, pid) //initialze the farm
    set this.release = DialogCreate()
    set this.releaseYes = DialogAddButton(release, "Yes, release this monster.", 1)
    set this.releaseNo = DialogAddButton(release, "No, don't release it!", 2)
    call TriggerRegisterDialogEvent(t, release)
    call TriggerAddCondition(t, Condition(function releaseMain))
    set t = null
    return this
endmethod

endstruct
endlibrary
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
Really now?

JASS:
library leakTest initializer onInit
    globals
        private timer t = CreateTimer()
    endglobals

    private function leaks takes nothing returns nothing
        local player p1 = Player(GetRandomInt(0, 15))
        local player p2 = Player(1)
    endfunction
    
    private function pauseResume takes nothing returns nothing
        if ModuloInteger(GetTriggerExecCount(GetTriggeringTrigger()) , 2) == 1 then
            call BJDebugMsg("started")
            call TimerStart(t, 0.001, true, function leaks)
        else
            call BJDebugMsg("paused")
            call PauseTimer(t)
        endif
    endfunction

    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t, function pauseResume)
    endfunction
endlibrary
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Really now?

JASS:
library leakTest initializer onInit
    globals
        private timer t = CreateTimer()
    endglobals

    private function leaks takes nothing returns nothing
        local player p1 = Player(GetRandomInt(0, 15))
        local player p2 = Player(1)
    endfunction
    
    private function pauseResume takes nothing returns nothing
        if ModuloInteger(GetTriggerExecCount(GetTriggeringTrigger()) , 2) == 1 then
            call BJDebugMsg("started")
            call TimerStart(t, 0.001, true, function leaks)
        else
            call BJDebugMsg("paused")
            call PauseTimer(t)
        endif
    endfunction

    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t, function pauseResume)
    endfunction
endlibrary

Seems to not leak. I must've tried it on a map that had something running on it. Sorry about that and plus rep. I will change my above post to not confuse other people.
 
Status
Not open for further replies.
Top