[System] Mode Manager

This system allows to you to create game commands and modes.
You can assign a function callback for whenever this command/mode is used.

For a player to use a certain command/mode, you must authorize him.
This also allows you to give a certain player the ability to select a game mode.

You can also lock a player rendering him unable to use anything.
This makes the system faster, because then it won't have to go
through the loop for nothing.

Each mode has a boolean 'flag'.
This boolean can be used any way you desire.
For example, you could use it to indicate an activated mode.

This system allows several mode strings to be accepted in the same string.

JASS:
/*********************************************************
*
*   Mode Manager
*   v6.0.0.1
*   By Magtheridon96
*
*   - Allows you to create game modes and commands.
*   - Max Modes/Commands: 682 (<9000)
*       * If you require a higher mode cap, contact me.
*
*   Requires:
*   ---------
*
*       - StringIndexer by Magtheridon96
*           - hiveworkshop.com/forums/jass-resources-412/snippet-stringindexer-208187/
*
*   API:
*   ----
*
*       - struct GameMode extends array
*           - static boolean enabled
*               - Enables/Disables System.
*
*           - method operator flag takes nothing returns boolean
*           - method operator flag= takes boolean b returns nothing
*               - For users to store a boolean in an instance of GameMode
*
*           - method authorize takes integer id returns nothing
*           - method unauthorize takes integer id returns nothing
*           - method isPlayerAuthorized takes integer id returns boolean
*               - Used to manage Player Authorization for certain modes/commands.
*
*           - static method lockPlayer takes integer id returns nothing
*           - static method unlockPlayer takes integer id returns nothing
*           - static method isPlayerLocked takes integer id returns boolean
*               - Used to manage Player Authentication. If a player is authorized 
*               for a few modes and you lock him, he will be rendered unable to use 
*               anything. When you unlock him, he will be able to use all of the 
*               modes you authorized him for.
*
*           - static method create takes string cd, boolean m, boolexpr b returns thistype
*               - Creates a new mode/command.
*
*       - function CreateGameMode takes string modeString, code func returns GameMode
*       - function CreateGameCommand takes string commandString, code func returns GameMode
*
*       - function AuthorizePlayerForGameModeById takes GameMode m, integer i returns nothing
*       - function AuthorizePlayerForGameMode takes GameMode m, player p returns nothing
*
*       - function UnauthorizePlayerForGameModeById takes GameMode m, integer i returns nothing
*       - function UnauthorizePlayerForGameMode takes GameMode m, player p returns nothing
*
*       - function IsPlayerAuthorizedForGameModeById takes GameMode m, integer i returns boolean
*       - function IsPlayerAuthorizedForGameMode takes GameMode m, player p returns boolean
*
*       - function LockPlayerFromGameModesById takes integer i returns nothing
*       - function LockPlayerFromGameModes takes player p returns nothing
*
*       - function UnlockPlayerFromGameModesById takes integer i returns nothing
*       - function UnlockPlayerFromGameModes takes player p returns nothing
*
*       - function IsPlayerLockedFromGameModesById takes integer i returns boolean
*       - function IsPlayerLockedFromGameModes takes player p returns boolean
*
*       - function SetGameModeData takes GameMode m, boolean b returns nothing
*       - function GetGameModeData takes GameMode m returns boolean
*
*       - function EnableGameModes takes nothing returns nothing
*       - function DisableGameModes takes nothing returns nothing
*       - function GameModesEnabled takes nothing return boolean
*
*********************************************************/
library ModeManager requires StringIndexer
    
    globals
        // **** Configuration ****
        private constant string  MODE_CHAR = "-"               // This character will be used to indicate a command/mode.
        private constant integer MODE_CHAR_LENGTH = 1          // This is the length of the MODE_CHARACTER
        private constant integer MODE_LENGTH = 2               // This is the length of each command/mode string
        // **** End Configuration ****
    endglobals
    
    struct GameMode extends array
        private static trigger TRIGGER = CreateTrigger()
        private static boolean e = true
        
        private static boolexpr array func
        private static boolean array mode
        
        private static boolean array authorized
        private static boolean array locked
        
        private static boolean array data
        private static boolean array allocated
        
        static method enable takes nothing returns nothing
            set e = true
        endmethod
        
        static method disable takes nothing returns nothing
            set e = false
        endmethod
        
        static method operator enabled takes nothing returns boolean
            return e
        endmethod
        
        method operator flag takes nothing returns boolean
            return data[this]
        endmethod
        
        method operator flag= takes boolean b returns nothing
            set data[this] = b
        endmethod
        
        method authorize takes integer id returns nothing
            debug if 0 > id or 15 < id then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Attempted to authorize an invalid player!")
                debug return
            debug endif
            
            set authorized[this * 12 + id] = true
        endmethod
        
        method unauthorize takes integer id returns nothing
            debug if 0 > id or 15 < id then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Attempted to unauthorize an invalid player!")
                debug return
            debug endif
            
            set authorized[this * 12 + id] = false
        endmethod
        
        method isPlayerAuthorized takes integer id returns boolean
            debug if 0 > id or 15 < id then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Attempted to evaluate an invalid player!")
                debug return false
            debug endif
            
            return authorized[this * 12 + id]
        endmethod
        
        static method lockPlayer takes integer id returns nothing
            debug if 0 > id or 15 < id then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Attempted to lock a null player.")
                debug return
            debug endif
            
            set locked[id] = true
        endmethod
        
        static method unlockPlayer takes integer id returns nothing
            debug if 0 > id or 15 < id then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Attempted to unlock a null player.")
                debug return
            debug endif
            
            set locked[id] = false
        endmethod
        
        static method isPlayerLocked takes integer id returns boolean
            debug if 0 > id or 15 < id then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Attempted to evaluate a null player!")
                debug return false
            debug endif
            
            return locked[id]
        endmethod
        
        static method create takes string text, boolean isMode, code c returns thistype
            local thistype this = IndexString(text)
            
            debug if allocated[this] then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Warning: Attempted to overwrite mode data for an existing mode!")
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Registeration Terminated!")
                debug return 0
            debug endif
            
            debug if MODE_LENGTH != StringLength(text) then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Failed to register " + text + "!")
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Invalid String Length!")
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Registeration Terminated!")
                debug return 0
            debug endif
            
            debug if c == null then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Invalid boolexpr!")
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Registeration Terminated!")
                debug return 0
            debug endif
            
            // Data storage
            set mode[this] = isMode
            set func[this] = Filter(c)
            
            // Safety
            set allocated[this] = true
            
            return this
        endmethod
        
        private static method filterSpace takes string s returns string
            local string new = ""
            local string tmp = ""
            local integer index = 0
            local integer length = StringLength(s)
            
            loop
                exitwhen index >= length
                set tmp = SubString(s, index, index + 1)
                
                if " " != tmp then
                    set new = new + tmp
                endif
                
                set index = index + 1
            endloop
            
            return new
        endmethod
        
        private static method run takes nothing returns boolean
            local thistype this
            local integer index = MODE_CHAR_LENGTH
            local integer id = GetPlayerId(GetTriggerPlayer())
            local integer length
            local string str
            
            // If you disabled the system, it won't run
            if e and not locked[id] then
            
                call TriggerClearConditions(TRIGGER)
                
                // Remove all spaces from chat string
                set str = filterSpace(StringCase(GetEventPlayerChatString(), false))
                set length = StringLength(str)
                
                // Loop through all the substrings in the chat string that have a length of MODE_LENGTH
                // We will start after the MODE_CHAR
                loop
                    exitwhen index >= length
                    
                    // Get current instance through pointer Table
                    set this = GetStringId(SubString(str, index, index + MODE_LENGTH))
                    
                    // Check if the instance is not null and 
                    // the player is authorized
                    if authorized[this * 12 + id] and allocated[this] then
                        
                        // Check if it's a mode string
                        if mode[this] then
                            // Add function to trigger
                            call TriggerAddCondition(TRIGGER, func[this])
                        // Check if it's a command string and it's the first one encountered
                        elseif not mode[this] and index == MODE_CHAR_LENGTH then
                            // Add function to trigger
                            call TriggerAddCondition(TRIGGER, func[this])
                            exitwhen true
                        endif
                        
                    endif
                    
                    // Increase index to get next string
                    set index = index + MODE_LENGTH
                endloop
                
                return TriggerEvaluate(TRIGGER)
            endif
            
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local integer i = 11
            local player p
            
            debug if MODE_CHAR_LENGTH != StringLength(MODE_CHAR) then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: System Terminated!")
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Invalid integer (MODE_CHAR_LENGTH)")
                debug return
            debug endif
            
            debug if 0 >= MODE_LENGTH then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: System Terminated!")
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[Mode Manager]Error: Invalid integer (MODE_LENGTH)")
                debug return
            debug endif
            
            // Loop through active players
            loop
                set p = Player(i)
                // If the player is available and happens to be a user (Non-computer)
                if GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(p) == MAP_CONTROL_USER then
                    // Register the event for this player
                    call TriggerRegisterPlayerChatEvent(t, p, MODE_CHAR, false)
                endif
                
                exitwhen i == 0
                set i = i - 1
            endloop
            
            call TriggerAddCondition(t, Condition(function thistype.run))
            
            set t = null
            set p = null
        endmethod
    endstruct
    
    function CreateGameMode takes string modeString, code func returns GameMode
        return GameMode.create(modeString, true, func)
    endfunction
    
    function CreateGameCommand takes string commandString, boolean executeOnce, code func returns GameMode
        return GameMode.create(commandString, false, func)
    endfunction
    
    function AuthorizePlayerForGameModeById takes GameMode m, integer i returns nothing
        call m.authorize(i)
    endfunction
    
    function AuthorizePlayerForGameMode takes GameMode m, player p returns nothing
        call m.authorize(GetPlayerId(p))
    endfunction
    
    function UnauthorizePlayerForGameModeById takes GameMode m, integer i returns nothing
        call m.unauthorize(i)
    endfunction
    
    function UnauthorizePlayerForGameMode takes GameMode m, player p returns nothing
        call m.unauthorize(GetPlayerId(p))
    endfunction
    
    function IsPlayerAuthorizedForGameModeById takes GameMode m, integer i returns boolean
        return m.isPlayerAuthorized(i)
    endfunction
    
    function IsPlayerAuthorizedForGameMode takes GameMode m, player p returns boolean
        return m.isPlayerAuthorized(GetPlayerId(p))
    endfunction
    
    function LockPlayerFromGameModesById takes integer i returns nothing
        call GameMode.lockPlayer(i)
    endfunction
    
    function LockPlayerFromGameModes takes player p returns nothing
        call GameMode.lockPlayer(GetPlayerId(p))
    endfunction
    
    function UnlockPlayerFromGameModesById takes integer i returns nothing
        call GameMode.unlockPlayer(i)
    endfunction
    
    function UnlockPlayerFromGameModes takes player p returns nothing
        call GameMode.unlockPlayer(GetPlayerId(p))
    endfunction
    
    function IsPlayerLockedFromGameModesById takes integer i returns boolean
        return GameMode.isPlayerLocked(i)
    endfunction
    
    function IsPlayerLockedFromGameModes takes player p returns boolean
        return GameMode.isPlayerLocked(GetPlayerId(p))
    endfunction
    
    function SetGameModeData takes GameMode m, boolean b returns nothing
        set m.flag = b
    endfunction
    
    function GetGameModeData takes GameMode m returns boolean
        return m.flag
    endfunction
    
    function EnableGameModes takes nothing returns nothing
        call GameMode.enable()
    endfunction
    
    function DisableGameModes takes nothing returns nothing
        call GameMode.disable()
    endfunction
    
    function GameModesEnabled takes nothing returns boolean
        return GameMode.enabled
    endfunction
endlibrary


Here's the Demo:
JASS:
struct Demo extends array
    private static GameMode Allpick
    private static GameMode Allrandom
    private static GameMode Onlymid
    private static GameMode Fastrespawn
    private static GameMode Movespeed
    private static GameMode Myenemies
    
    static method print takes string s returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, s)
    endmethod
    
    // Good examples on how to stop mode collisions and avoiding
    // functions running more than once.
    static method ap takes nothing returns nothing
        if not Allpick.flag and not Allrandom.flag then
            call print("MODE Allpick")
            set Allpick.flag = true
        endif
    endmethod
    
    static method ar takes nothing returns nothing
        if not Allpick.flag and not Allrandom.flag then
            call print("MODE Allrandom")
            set Allrandom.flag = true
        endif
    endmethod
    
    static method ms takes nothing returns nothing
        call print("COMMAND Movespeed")
    endmethod
    
    static method om takes nothing returns nothing
        if not Onlymid.flag then
            call print("MODE Onlymid")
            set Onlymid.flag = true
        endif
    endmethod
    
    static method ma takes nothing returns nothing
        call print("COMMAND Myenemies")
    endmethod
    
    static method fr takes nothing returns nothing
        if not Fastrespawn.flag then
            call print("MODE Fastrespawn")
            set Fastrespawn.flag = true
        endif
    endmethod
    
    private static method reset takes nothing returns boolean
        set Allpick.flag = false
        set Allrandom.flag = false
        set Onlymid.flag = false
        set Fastrespawn.flag = false
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()

        set Allpick = GameMode.create("ap", true, function thistype.ap)
        set Onlymid = GameMode.create("om", true, function thistype.om)
        set Allrandom = GameMode.create("ar", true, function thistype.ar)
        set Fastrespawn = GameMode.create("fr", true, function thistype.fr)
        
        set Movespeed = GameMode.create("ms", false, function thistype.ms)
        set Myenemies = GameMode.create("ma", false, function thistype.ma)
        
        call Allpick.authorize(0)
        call Allrandom.authorize(0)
        call Onlymid.authorize(0)
        call Fastrespawn.authorize(0)
        
        call Movespeed.authorize(0)
        call Myenemies.authorize(0)

        call print("[Mode Manager]Available Mode strings: ap, ar, om, fr")
        call print("[Mode Manager]Available Command strings: ms, ma")
        call print("[Mode Manager]To activate a mode, type \"-\" followed by a queue of mode strings")
        call print("[Mode Manager]To run a command, type \"-\" followed by a single command string")
        call print("[Mode Manager]You cannot activate the same mode twice. To reset a mode, type \"-reset\"")

        call TriggerRegisterPlayerChatEvent(t, Player(0), "-reset", true)
        call TriggerAddCondition(t, Condition(function thistype.reset))

        set t = null
    endmethod
endstruct

Contructive critisism will be accepted and is highly appreciated = )
Please give credits if used.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,307
You do know that your demonstration won't even run
public struct Mode {

Publics on structs or functions or w/e is a no no.

This should be readonly-
player dataPlayer = null;


And this is rather limited >.>, but I guess it's ok for simple modes. Also, keep in mind that a lot of games allow all of the modes to be sent in in one string.

this is bad too
boolean auth[12];

Change that into a table. The vjass arrays like that are going to be slower and generate a lot of crap.


Should be able to turn commands on/off for different players as well as turn on/off specific commands and should be able to run multiple commands in a string

-ap fs nh ma we po ls hh zl

etc etc

And yea.... string commands are pretty old school.
 
Also, keep in mind that a lot of games allow all of the modes to be sent in in one string.

This system does that >.>

It would run...

In Zinc, everything is private by default.
Making it public would allow vJASS libraries to access it (I tested it for 2 hours)

Also, why should I make it require Table ^_-

Should be able to turn commands on/off for different players as well as turn on/off specific commands and should be able to run multiple commands in a string

-ap fs nh ma we po ls hh zl

It does that :p
The only thing it doesn't do is unauthenticate a player :p
I'll add that ..

And yea.... string commands are pretty old school.

Yeah! :ogre_datass: xD


edit
updated.


This should be readonly-
player dataPlayer = null;

The readonly keyword cannot be used for variables outside structs :p
 
Level 38
Joined
Sep 26, 2009
Messages
8,464
Nes, something tells me you've never even written in Zinc :p

Public in vJass adds the prefix, but public in Zinc simply turns it into a non-private entity.

Of course, Zinc still is bad for many other reasons. But as long as a library can be written around its bugs, Zinc resources are fine.
 
Encapsulation is important.

You've got a point there :p
I'll just put it in then :ain:

edit
Updated
I changed 2 function names :p (Authenticate -> Authorize and Deauthenticate -> Unauthorize) because those names were stupid.
Also, I changed the player variable. It's now readonly.

Nes, something tells me you've never even written in Zinc :p

What a shame :p
Zinc is awesome. You should try it. :p

edit
So Bribe, do you think this system has potential? :)
 
Also, GetTriggerPlayer() does work within trigger evaluations >.>.
It does? O.O
I'll update this as soon as I can confirm your statement.

Should just let people pass in a conditionfunc

A conditionfunc is then stored into a variable, but then what? :p
I can't execute it.

type modeFunc extends function(); is most likely my best solution according to Daeod, DSG, and LeP.
 
Level 38
Joined
Sep 26, 2009
Messages
8,464
You can evaluate raw conditions without trouble.

You can execute raw code without trouble.

This is thanks to DioD's method outlined on TheHelper.net.

LuckyParser will be using these techniques instead of stuff like
ExecuteFunc or triggers. This way, you use way way fewer
handles.

Here's an excerpt from the Common.luck file I have:

JASS:
struct boolexpr
    static constant force temp = CreateForce()
    nothing evaluate(): ForceEnumPlayersCounted(temp, this, 1)

struct code
    static constant force temp = CreateForce()
    oninit: ForceAddPlayer(temp, Player(15))
    
    nothing execute(): ForForce(temp, this)
 

LeP

LeP

Level 10
Joined
Feb 13, 2008
Messages
498
You can evaluate raw conditions without trouble.

You can execute raw code without trouble.

This is thanks to DioD's method outlined on TheHelper.net.

LuckyParser will be using these techniques instead of stuff like
ExecuteFunc or triggers. This way, you use way way fewer
handles.

Here's an excerpt from the Common.luck file I have:

JASS:
struct boolexpr
    static constant force temp = CreateForce()
    nothing evaluate(): ForceEnumPlayersCounted(temp, this, 1)

struct code
    static constant force temp = CreateForce()
    oninit: ForceAddPlayer(temp, Player(15))
    
    nothing execute(): ForForce(temp, this)

This is kinda the wrong thread, but you started it... :p

So, where do i pass my arguments with the method mentioned above?
I thought we left the phase where we had to use globals all the way.

The vJass-approach is actually a lot more cleaner. You operate on basic types where vJass operates on real code (not the jass-type).
This gives us some benefits: the compiler can handle our calls which gives us the possibility to use arguments (this could actually use your method...) and we get more freedom.
I can use the same code and if want to switch from .execute() to .evaluate() i just change the methods. If i used code and boolexpr i have to change all types and all the assignments. And i maybe even break some other scripts depending on this.
 
Level 38
Joined
Sep 26, 2009
Messages
8,464
You misunderstand or maybe this was a discussion for another time, another place. What I intend for compiled code is for things like .execute, .evaluate to compile to that approach instead of assigning a trigger for each thing. If you look at the output war3map.j file, you will notice that he uses a lot of ExecuteFunc to run each thread which causes some string leakage which could easily be avoided using this method behind-the-scenes.

I have also included "codeblocks" which allows users to create similar stuff like that on their own.

So instead of .execute compiling to two useless "generated methods" plus a trigger, it just uses one method with the one, global force doing all the work. Of course you can't use waits in this case but waits are horrible crap anyway.
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
Your script won't even compile.

The readonly keyword doesn't exist in Zinc. Use an operator instead. Using operators is a better practice anyways, and that is exactly what Zinc "forces" you to do.
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
Operators isn't a better practice since that creates functions.

Yeah, okay, and everything that creates a bit of extra code equates bad practice? Man, I'm amused.

You ignore the fact that (if you're using operators to create readonly members) the function will be inlined by JassHelper and removed by the map optimizer.
 
Updated to Mode Manager 3.0.0.0!

Changelog:
- New API (Making it a requirement to go to 3.0.0.0)
- Fixed a huge bug
- Added debug messages everywhere :p
- Fixed Mode/Command collisions.


There are still some potential bugs that allow a player to ruin a game, but that can be easily fixed (via the 'flag' boolean).
For example, a player could ruin the game if he enters a mode string like -apar (All Pick and All Random).
If you want to fix that, this is how:

Your functions should look like this:

JASS:
function Ap takes nothing returns boolean
    if not AR.flag then
        set AP.flag = true
        // your code here
    endif
    return false
endfunction

function Ar takes nothing returns boolean
    if not AP.flag then
        set AR.flag = true
        // your code here
    endif
    return false
endfunction

I told you the 'flag' is useful ;)

edit
Updated to 3.0.1.0
- You may now completely lock players to render them unable to use any commands/modes.
 
Last edited:
Level 9
Joined
May 27, 2009
Messages
494
i can't let it work :/ doesn't init anyways

here's my init code
JASS:
    private function ND takes nothing returns boolean
        set udg_EnablePvP_On = false
        set PvP_ModeAllowed = false
        call BJDebugMsg("Init ND")
        return false
    endfunction
    private function Init takes nothing returns nothing
        set nd = GameMode.create("nd",true, Condition(function ND))
        call nd.authorize(GetPlayerId(PLAYERS_HOST))
        set nd.enabled = true
    endfunction
 
Level 38
Joined
Sep 26, 2009
Messages
8,464
This does not need to be initialized in a module, think about it ;)

Just initialize enabled to true from the globals block. You can use
a library init or a normal struct init here without fuglying up the
syntax.

Instead of using the .auth table you could use a 2D array. At worst
we'll be limited to several hundred different mode instances? Yeah,
since that won't happen you could just use a 2D array, such as
".auth[index * 12 + this] = true", for example.
 
Top