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

[System] Dialog

JASS:
library Dialog requires Table
// Dialog System by The_Witcher
//
//  Some simple wrappers for easier dialog handling
//
//  Feel free to use!
//
//      requires Table by Bribe: [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
//
    globals
        private constant integer MAX_BUTTONS = 12 //Max amount of buttons per dialog
    endglobals
//  New Things:
//
//      function GetTriggerButton takes nothing returns DialogButton
//
//
//      struct DialogButton
//          operator [] takes button b returns dialogButton    (useful to get the data of the clicked button)
//          string text        [read-only]                     (the text on the button)
//          integer hotkey     [read-only]                     (the hotkey for the button)
//          integer index      [read-only]                     (the button's position in the dialog, first is 0)
//          integer data                                       (user data for you)
//
//          method create takes Dialog dlg, string title, integer hotkey returns DialogButton
//
//          method registerClick takes trigger t returns nothing
//
//
//      struct Dialog
//          operator [] takes integer i returns dialogButton
//          string text                                                                (the dialog's title, set this to another value to change the dialogs title aswell)
//          integer data                                                               (user data for you)
//
//          method create takes string title returns Dialog                            (creates a new dialog with the given title)
//          method addButton takes string text, integer hotkey returns dialogButton    (adds a new button and returns it)
//          method clear takes nothing returns nothing                                 (clears the dialog of all buttons)
//
//          method display takes player toPlayer, boolean flag returns nothing
//          method displayToAll takes boolean flag returns nothing
//
//          method register takes code func returns nothing                            (fires the given function whenever a button for this dialog is clicked)
//
//
//==============================================================================
//========================= System Code ========================================
//==============================================================================

    private module InitM
        private static method onInit takes nothing returns nothing
            set .buttons = Table.create()
        endmethod
    endmodule

    struct DialogButton
        private static Table buttons
        private button b
        readonly string text
        readonly integer hotkey
        readonly integer index
        integer data
        
        static method operator [] takes button b returns DialogButton
            return .buttons[GetHandleId(b)]
        endmethod
    
        method registerClick takes trigger t returns nothing
            call TriggerRegisterDialogButtonEvent(t, .b)
        endmethod
    
        static method create takes Dialog dlg, string title, integer hotkey returns DialogButton
            local DialogButton this = DialogButton.allocate()
            set .b = DialogAddButton(dlg.d, title, hotkey)
            set .hotkey = hotkey
            set .text = title
            set .index = dlg.count
            set .buttons[GetHandleId(.b)] = this
            call dlg.add(this)
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            call .buttons.remove(GetHandleId(.b))
        endmethod
        
        implement InitM
    endstruct

    struct Dialog extends array
        private static integer instanceCount = 0
        private static Dialog recycle = 0
        private Dialog recycleNext
        private static DialogButton array buttons
        readonly dialog d
        readonly integer count
        private string s
        private trigger t
        integer data

        method operator [] takes integer i returns DialogButton
            return .buttons[this * MAX_BUTTONS + i]
        endmethod
    
        method operator text takes nothing returns string
            return .s
        endmethod
        method operator text= takes string s returns nothing
            set .s = s
            call DialogSetMessage(.d, s)
        endmethod
    
        method clear takes nothing returns nothing
            loop
                exitwhen .count == 0
                set .count = .count - 1
                call .buttons[this * MAX_BUTTONS + .count].destroy()
            endloop
            call DialogClear(.d)
        endmethod
    
        method display takes player toPlayer, boolean flag returns nothing
            call DialogDisplay(toPlayer, .d, flag)
        endmethod
        method displayToAll takes boolean flag returns nothing
            call DialogDisplay(GetLocalPlayer(), .d, flag)
        endmethod
    
        method register takes code func returns nothing
            call TriggerAddAction(.t, func)
        endmethod
        
        method add takes DialogButton b returns nothing
            if .count < MAX_BUTTONS then
                set .buttons[this * MAX_BUTTONS + .count] = b
                set .count = .count + 1
            else
                debug call BJDebugMsg("DIALOG ERROR: More than 12 buttons were added to a dialog.")
            endif
        endmethod
    
        method addButton takes string text returns DialogButton
            return .addHotkeyButton( text, 0)
        endmethod
        
        method addHotkeyButton takes string text, integer hotkey returns DialogButton
            return DialogButton.create(this, text, hotkey)
        endmethod
    
        static method create takes string title returns Dialog
            local Dialog this
            if (.recycle == 0) then
                set .instanceCount = .instanceCount + 1
                set this = .instanceCount
            else
                set this = .recycle
                set .recycle = .recycle.recycleNext
            endif
            set .d = DialogCreate()
            set .count = 0
            set .s = title
            call DialogSetMessage(.d, title)
            set .t = CreateTrigger()
            call TriggerRegisterDialogEvent(.t, .d)
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            call .clear()
            call DestroyTrigger(.t)
            call DialogDestroy(.d)
            set .recycleNext = .recycle
            set .recycle = this
        endmethod
    endstruct
    
    function GetTriggerButton takes nothing returns DialogButton
        return DialogButton[GetClickedButton()]
    endfunction

endlibrary


//Code indented using The_Witcher's Script Language Aligner
//Download the newest version and report bugs at [url]www.hiveworkshop.com[/url]
 
Last edited:
Nice.

Bro-tips:
  • dialogButton -> DialogButton
  • GetTriggerButton() > DialogButton[GetClickedButton()]
  • Rename onDestroy to destroy and move it to the top of the struct to remove unnecessary trigger evaluations.

Fixed all these things :D thank you!

Pretty neat... :)

Please add a method to the dialog struct such that we can add our own buttons that we created using the DialogButton struct...

Sorry but this won't make sense, because a button is always bound to a dialog!
Nevertheless I improved the DialogButton struct for you, so create is now different and instantly adds the created button to the dialog:
JASS:
create takes Dialog dlg, string title, integer hotkey returns DialogButton
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
Fixed all these things :D thank you!
[&]

You sure "fixed" it. Just do whatever people tell you.
You replacement of onDestroy with destroy just destroyed the the recycle ability of your struct.

Here have an example.
JASS:
library Yoink initializer init
    private struct foo
        method destroy takes nothing returns nothing
            call BJDebugMsg("foo destroy")
        endmethod
        
        static method create takes nothing returns thistype
            local thistype this = allocate()
            call BJDebugMsg("foo new: "+ I2S(integer(this)))
            return this
        endmethod
    endstruct
    
    private struct bar
        method onDestroy takes nothing returns nothing
            call BJDebugMsg("bar destroy")
        endmethod
    
        static method create takes nothing returns thistype
            local thistype this = allocate()
            call BJDebugMsg("bar bew: "+ I2S(integer(this)))
            return this
        endmethod
    endstruct

    private function init takes nothing returns nothing
        call TriggerSleepAction(0)
        
        call foo.create().destroy()
        call bar.create().destroy()
        
        call foo.create().destroy()
        call bar.create().destroy()
        
        call foo.create().destroy()
        call bar.create().destroy()
        
        call foo.create().destroy()
        call bar.create().destroy()
    endfunction
endlibrary

Now guess what this will print.

foo new: 1
foo destroy
bar new: 1
bar destroy
foo new: 2
foo destroy
bar new: 1
bar destroy
foo new: 3
foo destroy
bar new: 1
bar destroy
foo new: 4
foo destroy
bar new: 1
bar destroy

Just for the record: the Dialog create/destroy methods:
JASS:
struct Dialog
        method destroy takes nothing returns nothing
            call .clear()
            call DestroyTrigger(.t)
            call DialogDestroy(.d)
        endmethod
// [...]        
        static method create takes string title returns Dialog
            local Dialog this = Dialog.allocate()
            set .d = DialogCreate()
            set .count = 0
            set .s = title
            call DialogSetMessage(.d, title)
            set .t = CreateTrigger()
            call TriggerRegisterDialogEvent(.t, .d)
            return this
        endmethod
endstruct
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
its because you didn't call .deallocate on the destroy method...

since it overwrites the default destroy method you should have a .deallocate call there, just as the create method has a .allocate call...

That would fuck up other mechanisms.
Take this example:
JASS:
library Nah initializer init
 private struct super1
        method destroy takes nothing returns nothing
            call BJDebugMsg("super1 destroy")
            call deallocate()
        endmethod
    endstruct
    
    private struct sub1 extends super1
        method destroy takes nothing returns nothing
            call BJDebugMsg("sub1 destroy")
            // the following don't change anything being printed
            // see below for reason
            // in fact, they get compiled to the same code
            // and one of them is necessary for recycling
            // call this.deallocate()
            // call super.destroy()

        endmethod
    endstruct
    
    private struct super2
        method onDestroy takes nothing returns nothing
            call BJDebugMsg("super2 destroy")
        endmethod
    endstruct
    
    private struct sub2 extends super2
        method onDestroy takes nothing returns nothing
            call BJDebugMsg("sub2 destroy")
        endmethod
    endstruct

    private function init takes nothing returns nothing
        local super1 x
        local super2 y

        call TriggerSleepAction(0)

        set x = sub1.create()
        // this calls super1.destroy, which does -of course- not know about other instances.
        // the mechanism for that is, you say it, the onDestroy method
        call x.destroy()

        set y = sub2.create()
        call y.destroy()
    endfunction
endlibrary

Guess again what this will print.
super1 destroy
sub2 destroy
super2 destroy
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
if it only happens when you extend the struct, I guess that is fine... just make a note that says, never extend this struct...

That's a dumb and arbitrary restriction with no real gain.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
yeah, I don't see the use of extending the Dialog struct...

I have some imagination left.

I think its an issue of efficiency versus functionality... Efficiency coz of the less evaluations due to not using onDestroy and functionality in terms of extension and maybe recycling...
And you know, killing type safety. Not following conventions leading to strange behavior (either make extending a compile time error or allow extending).

It's not a struct that you'd want to extend anyway.

Polymorphism in vJass is not a good coding practice when you look at how it compiles.

Don't tell what structs i extend.
It's the cleanest way to add functionality to my dialogs.
Want player-colored buttons: just extend and change the text field.
Want more than 12 buttons via pages: fucking extend and change the addButton method.

And holy fuck, why should i care how my dialogs (!!) are compiled?!
They neither add megs of jasscode nor have any performance penalty.
And if they do, i'll fix it, as soon and not before i've got problems.
"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil"

I'm pretty sure Dialogs don't fall into the 3%.


I, as a programmer, want to have nice code to work with instead of nice compiled code.
In a perfect world we would have both, but we aren't so i choose the former.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
Just for the record, my map went down by 600KB when I stopped extending structs.

That pretty much falls under premature optimization.
If, and only if, these 600kB stop your map from working it's perfectly fine to optimize it.
 
Well, honestly, you made some pretty good points here.
But come on, who would need to extend a Dialog struct? :/

I know you're being imaginative and thinking of all these scenarios in which you can actually do something pretty cool with it, but.. well, I don't know what to say.

Maybe he can include a textmacro and use delegates so you can have the results obtained from extending without actually extending ^.^
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
Well, honestly, you made some pretty good points here.
But come on, who would need to extend a Dialog struct? :/

I know you're being imaginative and thinking of all these scenarios in which you can actually do something pretty cool with it, but.. well, I don't know what to say.

Maybe he can include a textmacro and use delegates so you can have the results obtained from extending without actually extending ^.^

I already gave examples why you would extend this in particular.
But it's not only about a Dialog but about easy extendability in general.
Why restrict it without any good reason? And if you do, do it via extends array, so that i atleast get a compile-time error.

Textmacro + delegate is no real option.
Textmacros are for repetitive structures of code.
Delegates are cool, but too restricted in vJass. They are good for reusing but since they don't fulfill interface requirements you can't really use them in your polymorph code without giving up some of the good things you get from delegates. Modules work better in that regard under vJass (that is, reusing code which fulfills interface rules).

But both have there uses. They aren't exclusive.
 
Last edited:
Come on guys :D
I will go back to the onDestroy method to keep the structure simple!
There won't be spammings of dialog destroys i think,
So this should be a solution everybody can live with^^

@Bribe: what does jass precendence mean? Sorry i am from germany^^
and i didn't change any configuration for jasshelper... I simply updated to cohadars mod...
 
I was wondering why struct methods doesn't seem to follow precedence... I mean they still work no matter if the method was below or above and JH doesn't return an error...

If it is below JH will usually either create an entire copy of the function(s) or it will create a trigger and evaluate it. (along with a few functions and variables) So precedence is still important if you care about the text compiled.
 
- The DialogButton should use a module initializer because in it's current state, the system will break if a DialogButton is managed by an external system using a module initializer :/

- The destroy method of the dialog struct should clean the DialogButtons properly. (Add a destroy method to your DialogButton struct)

Updated. But the destroy method DOES clean the dialog buttons properly!
 
Top