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

Level 22
Joined
Feb 6, 2014
Messages
2,466
JASS:
library SystemConsole /*
                            SystemConsole v1.01
                                  by Flux
           
            SystemConsole is a text-console simulation for Warcraft 3.
            It main purpose is to display information to players in a
            console format thus it logs all messages displayed so 
            players can view it again anytime. It can hold up to 8192 
            line messages in its buffer.
            It can also be used as a debugging tool through its 
            SystemTest feature.
           
   
    */ requires /*
      (nothing)
     
    */ optional Table /*
     If not found, it will create a hashtable. Hashtables are limited to
     255 per map.
   
   
    Includes:
        * SystemMsg 
            - The interface for displaying game messages.
               
            API:
                - SystemMsg.create(string msg)
                    Display message.
               
                - SystemMsg.createEx(string title, string msg)
                    Display titled message.
               
                - SystemMsg.createIf(boolean condition, string msg)
                    Display message if condition is true.
               
                - SystemMsg.createIfEx(boolean condition, string title, string msg)
                    Display titled message if condition is true.
                   
                - set SystemMsg.show = true/false
                    Controls the visibility of System Messages.
                   
                       
        * SystemTest
            - Test a function or a group of functions if it 
              will execute without fail. Enclosed the function(s)
              to be tested between SystemTest.start(msg) and 
              SystemTest.end().
               
            API:
                - SystemTest.start(string msg)
                    Starting block of SystemTest
               
                - SystemTest.startEx(string title, string msg)
                    Titled starting block of SystemTest
                   
                - SystemTest.end()
                    End block of SystemTest
       
    */
   
    globals
        //The Player who can see the System messages. Use GetLocalPlayer() to make 
        //it visible to all.
        private constant player HOST = Player(0)
       
        //Title color for titled messages
        private constant string TITLE_COLOR = "|cffffcc00"
       
        //Duration of system messages
        private constant real DURATION = 10000
       
        //The time allotted for a SystemTest to finish before it is considered a failure
        private constant real DELAY = 0.25
       
        //Maximum number of lines saved in memory
        public constant integer BUFFER = 256
    endglobals
   
    struct SystemMsg
       
        string value
       
        readonly thistype next
        readonly thistype prev
       
        private static boolean privShow = true
        readonly static integer count = 0
       
        static integer lineCount = 0
        static integer cursor = 16
       
        static method refresh takes nothing returns nothing
            local thistype this = thistype(0).next
            local integer i = 0
            local integer j = thistype.count - cursor
            if thistype.privShow then
                if HOST == GetLocalPlayer() then
                    call ClearTextMessages()
                endif
                loop
                    exitwhen this == 0
                    if i >= j and i < j + 16 then
                        call DisplayTimedTextToPlayer(HOST, 0, 0, DURATION, this.value)
                    endif
                    set i = i + 1
                    set this = this.next
                endloop
            endif
        endmethod
       
        method destroy takes nothing returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set thistype.count = thistype.count - 1
            call this.deallocate()
        endmethod
       
        static method create takes string s returns thistype
            local thistype this = thistype.allocate()
            set thistype.lineCount = thistype.lineCount + 1
            set this.value = "|cff999999#" + I2S(thistype.lineCount) + "|r: " + s
            set this.next = 0
            set this.prev = thistype(0).prev
            set this.next.prev = this
            set this.prev.next = this
            if thistype.count == BUFFER then
                call thistype(0).next.destroy()
            else
                set thistype.count = thistype.count + 1
            endif
            if thistype.privShow and thistype.cursor <= 16 then
                call DisplayTimedTextToPlayer(HOST, 0, 0, DURATION, this.value)
            endif
            if thistype.cursor > 16 then
                set thistype.cursor = thistype.cursor + 1
            endif
            return this
        endmethod
       
        static method createEx takes string title, string s returns thistype
            return thistype.create(TITLE_COLOR + "[" + title + "]|r " + s)
        endmethod
       
        static method createIf takes boolean b, string s returns thistype
            if b then
                return thistype.create(s)
            endif
            return 0
        endmethod
       
        static method createIfEx takes boolean b, string title, string s returns thistype
            if b then
                return thistype.createEx(title, s)
            endif
            return 0
        endmethod
       
        static method operator show takes nothing returns boolean
            return thistype.privShow
        endmethod
       
        static method operator show= takes boolean b returns nothing
            set thistype.privShow = b
            if b then
                call thistype.refresh()
            else
                if HOST == GetLocalPlayer() then
                    call ClearTextMessages()
                endif
            endif
        endmethod
       
    endstruct
   
    struct SystemTest
   
        private boolean checker
        private timer t
        private SystemMsg msg
       
        private thistype next

        static if LIBRARY_Table then
            private static Table tb
        else
            private static hashtable hash = InitHashtable()
        endif
       
        method destroy takes nothing returns nothing
            if this.checker then
                set this.msg.value = this.msg.value + " [ |cff00ff00OK|r ]"
            else
                set this.msg.value = this.msg.value + " [ |cffff0000Failed|r ]"
            endif
            call SystemMsg.refresh()
            static if LIBRARY_Table then
                call thistype.tb.remove(GetHandleId(this.t))
            else
                call RemoveSavedInteger(thistype.hash, GetHandleId(this.t), 0)
            endif
            call PauseTimer(this.t)
            call DestroyTimer(this.t)
            set this.t = null
            call this.deallocate()
        endmethod
       
        private static method expires takes nothing returns nothing
            static if LIBRARY_Table then
                call thistype(thistype.tb[GetHandleId(GetExpiredTimer())]).destroy()
            else
                call thistype(LoadInteger(thistype.hash, GetHandleId(GetExpiredTimer()), 0)).destroy()
            endif
        endmethod
       
        static method create takes string s returns thistype
            local thistype this = thistype.allocate()
            set this.msg = SystemMsg.create(s)
            set this.checker = false
            set this.t = CreateTimer()
            static if LIBRARY_Table then
                set thistype.tb[GetHandleId(t)] = this
            else
                call SaveInteger(thistype.hash, GetHandleId(t), 0, this)
            endif
            call TimerStart(this.t, DELAY, false, function thistype.expires)
            return this
        endmethod
       
        static method start takes string s returns nothing
            local thistype this = thistype.create(s)
            //Push
            set this.next = thistype(0).next
            set thistype(0).next = this
        endmethod
       
        static method startEx takes string title, string s returns nothing
            call thistype.create(TITLE_COLOR + "[" + title + "]|r " + s)
        endmethod
       
        static method end takes nothing returns nothing
            local thistype this = thistype(0).next
            debug if this == 0 then
            debug call SystemMsg.create("Missing SystemTest.start(<message>)")
            debug return
            debug endif
            //Pop
            set thistype(0).next = this.next
            set this.checker = true
            call this.destroy()
        endmethod
       
        static if LIBRARY_Table then
            private static method onInit takes nothing returns nothing
                set thistype.tb = Table.create()
            endmethod
        endif
       
    endstruct
   
endlibrary


JASS:
library SystemConsoleCommands requires SystemConsole
 
    /*
                    SystemConsoleCommands v1.00
                              by Flux
         
            Contains various console commands to control visibility
            or to scroll the console messages.
         
            COMMANDS:
                ">show"   - Shows/unhide the console
                ">hide"   - Hides the console
                ">down #" - Scroll the console cursor downward by # lines
                ">up #"   - Scroll the console cursor upward by # lines
                ">clear"  - Clears the console
    */
 
    globals
        private constant string CMD_PREFIX = ">"
    endglobals
 
    struct ConsoleCommand extends array
         
        private static method show takes nothing returns boolean
            if GetLocalPlayer() == GetTriggerPlayer() then
                set SystemMsg.show = true
            endif
            return false
        endmethod
     
        private static method hide takes nothing returns boolean
            if GetLocalPlayer() == GetTriggerPlayer() then
                set SystemMsg.show = false
            endif
            return false
        endmethod
     
        private static method clear takes nothing returns boolean
            local SystemMsg this = SystemMsg(0).next
            loop
                exitwhen this == 0
                call this.destroy()
                set this = this.next
            endloop
            set SystemMsg.lineCount = 0
            call SystemMsg.refresh()
            return false
        endmethod
     
        private static method up takes nothing returns boolean
            local string s = GetEventPlayerChatString()
            local integer length = StringLength(s)
            local integer d = 1
            local integer temp
            if length > 4 then
                set d = S2I(SubString(s, 4, length))
                if d < 1 then
                    set d = 0
                endif
            endif
            if GetLocalPlayer() == GetTriggerPlayer() then
                set temp = SystemMsg.cursor + d
                if temp < SystemMsg.count + 16 then
                    set SystemMsg.cursor = temp
                else
                    set SystemMsg.cursor = SystemMsg.count + 15
                endif
            endif
            call SystemMsg.refresh()
            return false
        endmethod
     
        private static method down takes nothing returns boolean
            local string s = GetEventPlayerChatString()
            local integer length = StringLength(s)
            local integer d = 1
            if length > 6 then
                set d = S2I(SubString(s, 6, length))
                if d < 1 then
                    set d = 0
                endif
            endif
            if GetLocalPlayer() == GetTriggerPlayer() then
                if SystemMsg.cursor - d > 16 then
                    set SystemMsg.cursor = SystemMsg.cursor - d
                else
                    set SystemMsg.cursor = 16
                endif
            endif
            call SystemMsg.refresh()
            return false
        endmethod
     
        //! textmacro CONSOLE_CMD_TRIGGER_REGISTER takes WORD, MATCH
            set t = CreateTrigger()
            call TriggerRegisterPlayerChatEvent(t, p, CMD_PREFIX + "$WORD$", $MATCH$)
            call TriggerAddCondition(t, function thistype.$WORD$)
        //! endtextmacro
     
        static method register takes player p returns nothing
            local trigger t
            //! runtextmacro CONSOLE_CMD_TRIGGER_REGISTER("show", "true")
            //! runtextmacro CONSOLE_CMD_TRIGGER_REGISTER("hide", "true")
            //! runtextmacro CONSOLE_CMD_TRIGGER_REGISTER("clear", "true")
            //! runtextmacro CONSOLE_CMD_TRIGGER_REGISTER("up", "false")
            //! runtextmacro CONSOLE_CMD_TRIGGER_REGISTER("down", "false")
            set t = null
        endmethod
     
        private static method onInit takes nothing returns nothing
            call thistype.register(GetLocalPlayer())
        endmethod
     
    endstruct

     
endlibrary


Basic Example:

JASS:
            call SystemMsg.create("Hello Cruel World!")



JASS:
            local unit u
            call SystemTest.start("Killing Undefined unit:")
            call KillUnit(u)    //unit u does not have a value
            call SystemTest.end()
            call SystemMsg.create("This won't run because the function prematurely ends")
Will display:
Killing Undefined unit: [ Failed ]



I highly advised checking out the attached map because it has more demos

Changelog:
v1.00 - [18 September 2016]
- Initial Release

v1.01 - [19 September 2016]
- Included SystemConsoleCommands
- Improved Demos
- Fixed ClearTextMessage affecting all players.
 

Attachments

  • SystemConsole v1.01.w3x
    25.6 KB · Views: 99
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
It can be used for as an interactive way to display information
to players or to coders as a debugging tool.
It is meant replaces BJDebugMsg or DisplayTextToPlayer.

For debugging it seems kind of clunky because the Display*Player natives can only display
a window of 16 rows and that's hardly enough. Also the scrolling with the arrow keys
felt bad because it moved a single row at a time and needed repeated pressing for each row
(also the camera moves when the arrow keys are pressed which is a bit annoying) and if
there are say ~100 rows it would take a while to scroll all the way down/up.

Right now it seems to me that the message "Log (F12)" is still better/faster to scroll messages
(because it has that scroll handle of course) but it only has a buffer window of ~127.

So in my opinion for logs with lines < 127 one should stick to the message log and for longer logs
one should probably write to a file (using the Preload natives) unless SystemConsole gets
much much better at scrolling =).

PS: the ClearTextMessages needs to be in a GetLocalPlayer block otherwise it clears for all players.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I intentionally put the scrolling as part of the demo because it is up to the user how they will implement the scrolling. Using the arrow key is just an example.

Also I agree that Message Log is easier to navigate, but that only works on Single Player. What if you're testing something through Multiplayer Emulation?
So right now, the main concern is implement an easy way to scroll.

Good find on the Clear Message though.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I doubted the usefulnes of defining start- and endpoint in the check function versus to print his own message just when the function finishes.
The console system is maybe something cool, but if you don't want to have it then I will graveyard it soon.
Well it's easier to see "<Message>: [Failed]" than checking if a message appears or not if you have tons of messages appearing.
I remember I reported the main post some time ago to Graveyard this because I don't think it's that useful. It was fun to made it though.
 
Top