- Joined
- Jun 23, 2007
- Messages
- 4,066
Introduction
For the longest time map developers have wanted the ability to find out who the host is. Maps would generally mark Player 1 (Red) as the host, but that has it's flaws. You either need to create a new team specifically for the host, or just hope that the players know that Player 1 (Red) picks the game settings, or whatever functionality is host-specific.
In the past developers would try to measure latency with the game cache natives and whoever had the lowest latency was marked as the host. That method was inaccurate, but it also no longer works as of patch 1.30. Blizzard's servers are the real hosts now, not the player, so latency is much more variable. The method used in this script doesn't rely on latency.
Explanation
Patch 1.31 introduced natives to manipulate the game's frame data (UI). It turns out some of these natives also work inside of the game lobby. I figured this could be used in some way to detect the host. Unfortunately you cannot get a frame's text from inside the lobby, you can only set it. This means we can't simply get the text of the "Game Creator" frame to detect the host like I initially planned. However, I noticed a frame that existed for everyone but the host.
Knowing this I could inject some code into the map's config function and use that to detect the host. Although, the problem with injecting code into the config function that every time you make an update to your map's settings, you also have to update the config function. I wanted a solution where you can simply copy a script, use it, and forget about it. Then, I realized that code from within globals blocks are executed in the lobby as well. That's when I came up with
There are a couple issues with the above code though.
As for fixing the desync, we just need to make the handle counter the same for everyone, so simply calling
All that's left is syncing the value to the rest of the players so they know who the host is. This system handles all of it automatically.
Script
For the longest time map developers have wanted the ability to find out who the host is. Maps would generally mark Player 1 (Red) as the host, but that has it's flaws. You either need to create a new team specifically for the host, or just hope that the players know that Player 1 (Red) picks the game settings, or whatever functionality is host-specific.
In the past developers would try to measure latency with the game cache natives and whoever had the lowest latency was marked as the host. That method was inaccurate, but it also no longer works as of patch 1.30. Blizzard's servers are the real hosts now, not the player, so latency is much more variable. The method used in this script doesn't rely on latency.
Explanation
Patch 1.31 introduced natives to manipulate the game's frame data (UI). It turns out some of these natives also work inside of the game lobby. I figured this could be used in some way to detect the host. Unfortunately you cannot get a frame's text from inside the lobby, you can only set it. This means we can't simply get the text of the "Game Creator" frame to detect the host like I initially planned. However, I noticed a frame that existed for everyone but the host.
BlzGetFrameByName("NameMenu", 1)
returns null only for the host. I'm not entirely sure why, but I suspect the frame doesn't exist for the host until after the map's data has been parsed. When joining a lobby the frame must exist before the map data is parsed.Knowing this I could inject some code into the map's config function and use that to detect the host. Although, the problem with injecting code into the config function that every time you make an update to your map's settings, you also have to update the config function. I wanted a solution where you can simply copy a script, use it, and forget about it. Then, I realized that code from within globals blocks are executed in the lobby as well. That's when I came up with
JASS:
globals
boolean LocalHostFlag = (BlzGetFrameByName("NameMenu", 1) == null)
endglobals
- The map desyncs because we are creating a frame handle for everyone except for the host.
- It runs twice. Once at the lobby (config) and once at the loading screen. During the loading screen it is null for everyone.
hashtable
in the lobby and not to overwrite the global during the loading screen if the value was found in the hashtable.As for fixing the desync, we just need to make the handle counter the same for everyone, so simply calling
Location(0,0)
for the host would accomplish that. So, the code now becomes
JASS:
globals
boolean LocalHostFlag = not HaveSavedString(HostDetectHT, 0, 0) and (BlzGetFrameByName("NameMenu", 1) == null) and Location(0, 0) != null and SaveStr(HostDetectHT, 0, 0, "1")
endglobals
Script
JASS:
library HostDetection initializer Init
/***************************************************************
*
* v1.0.0, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Detect which player hosted the current match.
* _________________________________________________________________________
* 1. Requirements
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - Patch 1.31 or higher.
* - JassHelper (vJASS)
* _________________________________________________________________________
* 2. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map and save it.
* _________________________________________________________________________
* 3. API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* function GetHost takes nothing returns boolean
* function IsHostDetected nothing returnrs boolean
* function IsLocalPlayerHost nothing returnrs boolean
*
* function OnHostDetect takes code callback returns triggeraction
* function RemoveHostDetect takes triggeraction action returns nothing
*
***************************************************************/
globals
private hashtable HostDetectHT = InitHashtable()
private boolean LocalHostFlag = not HaveSavedString(HostDetectHT, 0, 0) and (BlzGetFrameByName("NameMenu", 1) == null) and Location(0, 0) != null and SaveStr(HostDetectHT, 0, 0, "1")
private trigger SyncTrig = CreateTrigger()
private trigger EventTrig = CreateTrigger()
private player HostPlayer = null
endglobals
function GetHost takes nothing returns player
return HostPlayer
endfunction
function IsHostDetected takes nothing returns boolean
return HostPlayer != null
endfunction
function IsLocalPlayerHost takes nothing returns boolean // async
return LocalHostFlag
endfunction
function OnHostDetect takes code callback returns triggeraction
return TriggerAddAction(EventTrig, callback)
endfunction
function RemoveHostDetect takes triggeraction action returns nothing
call TriggerRemoveAction(EventTrig, action)
endfunction
private function OnHostSync takes nothing returns nothing
set HostPlayer = GetTriggerPlayer()
call TriggerExecute(EventTrig)
call DisableTrigger(GetTriggeringTrigger())
endfunction
private function Init takes nothing returns nothing
local integer i = 0
loop
exitwhen i > bj_MAX_PLAYERS
call BlzTriggerRegisterPlayerSyncEvent(SyncTrig, Player(i), "hostdetect", false)
set i = i + 1
endloop
call TriggerAddAction(SyncTrig, function OnHostSync)
if (IsLocalPlayerHost()) then
call BlzSendSyncData("hostdetect", "1")
endif
endfunction
endlibrary
Last edited: