• 🏆 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] [Needs work] GameStatus {Replay/Online/SinglePlayer detection}

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
JASS:
library GameStatus initializer Ini /*
    
    GameStatus 3.1 by Bribe, special thanks to Troll-Brain
    
    Check if a game is online, a replay or is a single-player game.
    
    function GetGameStatus
        takes nothing
            returns integer
        
        Get the status of the game. This function cannot be called until the
        OnGameStatusFound function has run (so don't use this check during an
        initialization or 0-second timer, wait for this function).
        
        It returns one of the three constants:
            
            GAME_STATUS_OFFLINE
            GAME_STATUS_ONLINE
            GAME_STATUS_REPLAY
    
    function OnGameStatusFound
        takes code func
            returns nothing
    
        Executes the function when the game's status has been detected.
        
        Example use:
        
            OnGameStatusFound(function OnLoad)
        
        You are not able to use TriggerSleepAction from this function.
*/
    globals
        //-------------------------------------------------------------------
        // A game cache will be used to determine if the game is an online
        // multiplayer game.
        //
        private constant string CACHE_PATH = "GAME_STATUS"
        
        //-------------------------------------------------------------------
        constant integer GAME_STATUS_OFFLINE = 0
        constant integer GAME_STATUS_ONLINE  = 1
        constant integer GAME_STATUS_REPLAY  = 2
        
        //-------------------------------------------------------------------
        // If ReloadGameCachesFromDisk returns true, cheats can be used (it's
        // an offline game).
        //
        private integer status = IntegerTertiaryOp(ReloadGameCachesFromDisk(), GAME_STATUS_OFFLINE, GAME_STATUS_REPLAY)
        private integer n = -1
        private trigger t = CreateTrigger()
        private triggeraction array funcs
    endglobals
    
    //=======================================================================
    function GetGameStatus takes nothing returns integer
        return status
    endfunction
    
    //=======================================================================
    function OnGameStatusFound takes code func returns nothing
        set n = n + 1
        set funcs[n] = TriggerAddAction(t, func)
    endfunction
    
    private function Execute takes nothing returns nothing
        call TriggerExecute(t)
        loop
            exitwhen n < 0
            call TriggerRemoveAction(t, funcs[n])
            set funcs[n] = null
            set n = n - 1
        endloop
        call DestroyTrigger(t)
        set t = null
    endfunction
    
    //=======================================================================
    /* private */ function GameStatus___failSafePrivateFunction takes nothing returns nothing
        call TriggerSleepAction(0)
        call Execute()
    endfunction
    
    //=======================================================================
    private function Ini takes nothing returns nothing
        local boolean b = false
        local integer i = 12
        local gamecache g
        local string s = ""
        if bj_isSinglePlayer then
            //Execute a failsafe function because a replay of an offline
            //single-player game will crash the thread if it uses a
            //TriggerSyncReady/TriggerSleepAction which wasn't originally
            //in the game.
            call ExecuteFunc("GameStatus___failSafePrivateFunction")
            if status != GAME_STATUS_OFFLINE then
                call TriggerSyncReady()
                //If the thread didn't crash, the game is an online single
                //player game or is a replay of one. Better just say "online"
                //to be safe because no one has found a way to detect it that
                //can't be abused.
                set status = GAME_STATUS_ONLINE
            endif
        else
            //Flush the cache just in case it didn't get to that point
            //last time.
            call FlushGameCache(InitGameCache(CACHE_PATH))
            set g = InitGameCache(CACHE_PATH)
            loop
                set i = i - 1
                set s = I2S(i)
                if GetLocalPlayer() == Player(i) then
                    //Broadcast the boolean to all players.
                    call StoreBoolean(g, "", s, true)
                    call SyncStoredBoolean(g, "", s)
                endif
                exitwhen i == 0
            endloop
            call TriggerSyncReady()
            loop
                //A replay will show only 1 player has the boolean.
                if GetStoredBoolean(g, "", I2S(i)) then
                    if b then
                        set status = GAME_STATUS_ONLINE
                        exitwhen true
                    endif
                    set b = true
                endif
                set i = i + 1
                exitwhen i == 12
            endloop
            call FlushGameCache(g)
            set g = null
            call Execute()
        endif
    endfunction
    
endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I tried this on LAN and it showed "replay" instead of "online". I then tried it using the selection event and the selection event did have the delay. So it seems only the event is delayed, but not IsUnitSelected. Therefore, I will update this shortly.

Edit: Ok, final test because I'd really like to get this to work without a timer. Here's hoping the trigger will evaluate considering recursion and not after a 0-second timer.

If it still doesn't work, then I need the timer, but then you won't know if it's a replay until after the initialization phase.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Tested and it works the same. Minimizing doesn't change the event at all. Also tested plenty of consecutive times. Always returns "online" even when I use LAN, always returns "single player" for regular single player games and well replays has anyone even looked into it? Replays don't work any more in 1.26 or what? If I wasn't banned from the battle.net forums I would have made a post about it a long time ago there.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
from blizzard.j :

JASS:
    // Init singleplayer check
    set bj_isSinglePlayer = false
    set userControlledPlayers = 0
    set index = 0
    loop
        exitwhen index >= bj_MAX_PLAYERS
        if (GetPlayerController(Player(index)) == MAP_CONTROL_USER and GetPlayerSlotState(Player(index)) == PLAYER_SLOT_STATE_PLAYING) then
            set userControlledPlayers = userControlledPlayers + 1
        endif
        set index = index + 1
    endloop
    set bj_isSinglePlayer = (userControlledPlayers == 1)

So it only checks if there is no more than one human player, no more, no less.
Definitely not if the game is online (lan/battle.net) or not.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Update to 2.0 to detect single player replays. I haven't found out how to detect online games vs replays of online games though, because replays stupidly have a selection delay and many other things which you'd think only an online game would have...

Does anyone know what the "igamestate" natives do? Maybe there is an "igamestate" that lets you detect if the game is online??
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
I'm quite sure it won't work as intented.
It's true than in a replay GetLocalPlayer returns the first playing player, but that's not too easy, i've already tested this way before.
It's trickier, it seems to affect things which don't have any effect in the result of the game, like displaying texttags.
But for example, if you have an action which selects an unit with GetLocalPlayer, all unit player events will still fire in a replay.

Plus, i would use game cache to sync data instead of a dummy unit.
It's really lame to use a dummy unit just for that.
I mean using units just for data is way too much overhead.

This could help (eventually i can test with you but i can't host).

For GAME_STATE_DIVINE_INTERVENTION, maybe it's when a player cheats with a code like whosyourdaddy or the one to win the game.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
I must not have be clear enough ...

GetLocalPlayer "bugs" in a replay, but not in a way that it could change how the game occurs (how triggers runs and data included).
In other words, in a replay or not, the game cache values will be the same.
Now it's not the same thing for pure local stuff which doesn't have any effect, like displaying texttags for a local player.
 
I have a really stupid idea that can't fail (Unless the player doesn't speak english lol)

JASS:
library GameStatus

    globals
        private boolean replay = true
        private boolean online = false
    endglobals

    function IsGameOnline takes nothing returns boolean
        return online
    endfunction

    function IsGameReplay takes nothing returns boolean
        return replay
    endfunction

    private struct Inits extends array
        private static method check takes nothing returns boolean
            set replay = false
            set online = true
        endmethod

        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()

            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 10, "/Bro/, is this game online? Say \"yes\". This is currently counted as a replay. Problem?")
            call TriggerRegisterPlayerChatEvent(t, Player(0), "yes", true)
            call TriggerAddCondition(t, Condition(function thistype.check))

            set t = null
        endmethod
    endstruct

endlibrary

:>
 
Top