feat: added bots column on server browser

This commit is contained in:
Jari van der Kaap 2023-04-10 15:15:12 +02:00
parent 586067e6ec
commit 4a5b6d43d0
5 changed files with 397 additions and 8 deletions

View File

@ -0,0 +1,371 @@
if Engine.GetCurrentMap() ~= "core_frontend" then
return
end
function IsServerBrowserEnabled()
return true
end
DataSources.LobbyServer = {
prepare = function ( controller, list, filter )
list.numElementsInList = list.vCount
list.controller = controller
list.serverBrowserRootModel = Engine.CreateModel( Engine.GetGlobalModel(), "serverBrowser" )
local serverListCountModel = Engine.GetModel( list.serverBrowserRootModel, "serverListCount" )
if serverListCountModel then
list.serverCount = Engine.GetModelValue( serverListCountModel )
else
list.serverCount = 0
end
list.servers = {}
local serversModel = Engine.CreateModel( list.serverBrowserRootModel, "servers" )
for i = 1, list.numElementsInList, 1 do
list.servers[i] = {}
list.servers[i].root = Engine.CreateModel( serversModel, "server_" .. i )
list.servers[i].model = Engine.CreateModel( list.servers[i].root, "model" )
end
list.updateModels = function ( controller, list, offset )
local serverInfo = Engine.SteamServerBrowser_GetServerInfo( offset )
if serverInfo then
local SetModelValue = function ( model, key, value )
local model = Engine.CreateModel( model, key )
if model then
Engine.SetModelValue( model, value )
end
end
local elementIndex = offset % list.numElementsInList + 1
local serverModel = list.servers[elementIndex].model
SetModelValue( serverModel, "serverIndex", serverInfo.serverIndex )
SetModelValue( serverModel, "connectAddr", serverInfo.connectAddr )
SetModelValue( serverModel, "ping", serverInfo.ping )
SetModelValue( serverModel, "modName", serverInfo.modName )
SetModelValue( serverModel, "mapName", serverInfo.map )
SetModelValue( serverModel, "desc", serverInfo.desc )
-- Changed the client count to be the actual player count
local clientCount = serverInfo.playerCount - serverInfo.botCount
SetModelValue( serverModel, "clientCount", clientCount )
SetModelValue( serverModel, "maxClients", serverInfo.maxPlayers )
SetModelValue( serverModel, "passwordProtected", serverInfo.password )
SetModelValue( serverModel, "secure", serverInfo.secure )
SetModelValue( serverModel, "name", serverInfo.name )
SetModelValue( serverModel, "gameType", serverInfo.gametype )
SetModelValue( serverModel, "dedicated", serverInfo.dedicated )
SetModelValue( serverModel, "ranked", serverInfo.ranked )
SetModelValue( serverModel, "hardcore", serverInfo.hardcore )
-- Added the bot count
SetModelValue( serverModel, "botCount", serverInfo.botCount )
return serverModel
else
return nil
end
end
if list.serverListUpdateSubscription then
list:removeSubscription( list.serverListUpdateSubscription )
end
local serverListUpdateModel = Engine.CreateModel( list.serverBrowserRootModel, "serverListCount" )
list.serverListUpdateSubscription = list:subscribeToModel( serverListUpdateModel, function ( model )
list:updateDataSource( false, false )
end, false )
if list.serverListSortTypeSubscription then
list:removeSubscription( list.serverListSortTypeSubscription )
end
local serverListSortTypeModel = Engine.CreateModel( list.serverBrowserRootModel, "serverListSortType" )
list.serverListSortTypeSubscription = list:subscribeToModel( serverListSortTypeModel, function ( model )
list:updateDataSource( false, false )
end, false )
end,
getCount = function ( list )
return list.serverCount
end,
getItem = function ( controller, list, index )
local offset = index - 1
return list.updateModels( controller, list, offset )
end,
cleanup = function ( list )
if list.serverBrowserRootModel then
Engine.UnsubscribeAndFreeModel( list.serverBrowserRootModel )
list.serverBrowserRootModel = nil
end
end
}
CoD.ServerBrowserRowInternal.new = function ( menu, controller )
local self = LUI.UIHorizontalList.new( {
left = 0,
top = 0,
right = 0,
bottom = 0,
leftAnchor = true,
topAnchor = true,
rightAnchor = true,
bottomAnchor = true,
spacing = 2
} )
self:setAlignment( LUI.Alignment.Left )
if PreLoadFunc then
PreLoadFunc( self, controller )
end
self:setUseStencil( false )
self:setClass( CoD.ServerBrowserRowInternal )
self.id = "ServerBrowserRowInternal"
self.soundSet = "default"
self:setLeftRight( true, false, 0, 700 )
self:setTopBottom( true, false, 0, 22 )
self:makeFocusable()
self.onlyChildrenFocusable = true
self.anyChildUsesUpdateState = true
local passwordFlag = CoD.ServerBrowserFlag.new( menu, controller )
passwordFlag:setLeftRight( true, false, 0, 28 )
passwordFlag:setTopBottom( true, true, 0, 0 )
passwordFlag.icon:setImage( RegisterImage( "uie_t7_icon_serverbrowser_protected" ) )
passwordFlag:linkToElementModel( self, nil, false, function ( model )
passwordFlag:setModel( model, controller )
end )
passwordFlag:mergeStateConditions( {
{
stateName = "FlagOn",
condition = function ( menu, element, event )
return IsSelfModelValueTrue( element, controller, "passwordProtected" )
end
}
} )
passwordFlag:linkToElementModel( passwordFlag, "passwordProtected", true, function ( model )
menu:updateElementState( passwordFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue( model ),
modelName = "passwordProtected"
} )
end )
self:addElement( passwordFlag )
self.passwordFlag = passwordFlag
local dedicatedFlag = CoD.ServerBrowserFlag.new( menu, controller )
dedicatedFlag:setLeftRight( true, false, 30, 58 )
dedicatedFlag:setTopBottom( true, true, 0, 0 )
dedicatedFlag.icon:setImage( RegisterImage( "uie_t7_icon_serverbrowser_dedicated" ) )
dedicatedFlag:linkToElementModel( self, nil, false, function ( model )
dedicatedFlag:setModel( model, controller )
end )
dedicatedFlag:mergeStateConditions( {
{
stateName = "FlagOn",
condition = function ( menu, element, event )
return IsSelfModelValueTrue( element, controller, "dedicated" )
end
}
} )
dedicatedFlag:linkToElementModel( dedicatedFlag, "dedicated", true, function ( model )
menu:updateElementState( dedicatedFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue( model ),
modelName = "dedicated"
} )
end )
self:addElement( dedicatedFlag )
self.dedicatedFlag = dedicatedFlag
local rankedFlag = CoD.ServerBrowserFlag.new( menu, controller )
rankedFlag:setLeftRight( true, false, 60, 88 )
rankedFlag:setTopBottom( true, true, 0, 0 )
rankedFlag.icon:setImage( RegisterImage( "uie_t7_icon_serverbrowser_ranked" ) )
rankedFlag:linkToElementModel( self, nil, false, function ( model )
rankedFlag:setModel( model, controller )
end )
rankedFlag:mergeStateConditions( {
{
stateName = "FlagOn",
condition = function ( menu, element, event )
return IsSelfModelValueTrue( element, controller, "ranked" )
end
}
} )
rankedFlag:linkToElementModel( rankedFlag, "ranked", true, function ( model )
menu:updateElementState( rankedFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue( model ),
modelName = "ranked"
} )
end )
self:addElement( rankedFlag )
self.rankedFlag = rankedFlag
local name = CoD.horizontalScrollingTextBox_18pt.new( menu, controller )
name:setLeftRight( true, false, 90, 330 )
name:setTopBottom( true, false, 2, 20 )
name.textBox:setTTF( "fonts/default.ttf" )
name.textBox:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_LEFT )
name:linkToElementModel( self, "name", true, function ( model )
local _name = Engine.GetModelValue( model )
if _name then
name.textBox:setText( Engine.Localize( _name ) )
end
end )
self:addElement( name )
self.name = name
local spacer = LUI.UIFrame.new( menu, controller, 0, 0, false )
spacer:setLeftRight( true, false, 332, 339 )
spacer:setTopBottom( true, false, 0, 22 )
spacer:setAlpha( 0 )
self:addElement( spacer )
self.spacer = spacer
local map = CoD.horizontalScrollingTextBox_18pt.new( menu, controller )
map:setLeftRight( true, false, 341, 446 )
map:setTopBottom( true, false, 2, 20 )
map.textBox:setTTF( "fonts/default.ttf" )
map.textBox:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_LEFT )
map:linkToElementModel( self, "mapName", true, function ( model )
local mapName = Engine.GetModelValue( model )
if mapName then
map.textBox:setText( MapNameToLocalizedMapName( mapName ) )
end
end )
self:addElement( map )
self.map = map
local hardcoreFlag = CoD.ServerBrowserFlag.new( menu, controller )
hardcoreFlag:setLeftRight( true, false, 448, 470 )
hardcoreFlag:setTopBottom( true, true, 0, 0 )
hardcoreFlag.icon:setImage( RegisterImage( "uie_t7_icon_serverbrowser_skull" ) )
hardcoreFlag:linkToElementModel( self, nil, false, function ( model )
hardcoreFlag:setModel( model, controller )
end )
hardcoreFlag:mergeStateConditions( {
{
stateName = "FlagOn",
condition = function ( menu, element, event )
return IsSelfModelValueTrue( element, controller, "hardcore" )
end
}
} )
hardcoreFlag:linkToElementModel( hardcoreFlag, "hardcore", true, function ( model )
menu:updateElementState( hardcoreFlag, {
name = "model_validation",
menu = menu,
modelValue = Engine.GetModelValue( model ),
modelName = "hardcore"
} )
end )
self:addElement( hardcoreFlag )
self.hardcoreFlag = hardcoreFlag
local gametype = LUI.UIText.new()
gametype:setLeftRight( true, false, 472, 576 )
gametype:setTopBottom( true, false, 2, 20 )
gametype:setTTF( "fonts/RefrigeratorDeluxe-Regular.ttf" )
gametype:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_LEFT )
gametype:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_TOP )
gametype:linkToElementModel( self, "gameType", true, function ( model )
local gameType = Engine.GetModelValue( model )
if gameType then
gametype:setText( Engine.Localize( GetGameTypeDisplayString( gameType ) ) )
end
end )
self:addElement( gametype )
self.gametype = gametype
local playerCount = LUI.UIText.new()
playerCount:setLeftRight( true, false, 593, 613 )
playerCount:setTopBottom( true, false, 2, 20 )
playerCount:setTTF( "fonts/RefrigeratorDeluxe-Regular.ttf" )
playerCount:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_RIGHT )
playerCount:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_TOP )
playerCount:linkToElementModel( self, "clientCount", true, function ( model )
local clientCount = Engine.GetModelValue( model )
if clientCount then
playerCount:setText( Engine.Localize( clientCount ) )
end
end )
self:addElement( playerCount )
self.playerCount = playerCount
local slash = LUI.UIText.new()
slash:setLeftRight( true, false, 615, 624 )
slash:setTopBottom( true, false, 2, 20 )
slash:setText( Engine.Localize( "/" ) )
slash:setTTF( "fonts/RefrigeratorDeluxe-Regular.ttf" )
slash:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_LEFT )
slash:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_TOP )
self:addElement( slash )
self.slash = slash
local maxPlayers = LUI.UIText.new()
maxPlayers:setLeftRight( true, false, 626, 645 )
maxPlayers:setTopBottom( true, false, 2, 20 )
maxPlayers:setTTF( "fonts/RefrigeratorDeluxe-Regular.ttf" )
maxPlayers:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_LEFT )
maxPlayers:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_TOP )
maxPlayers:linkToElementModel( self, "maxClients", true, function ( model )
local maxClients = Engine.GetModelValue( model )
if maxClients then
maxPlayers:setText( Engine.Localize( maxClients ) )
end
end )
self:addElement( maxPlayers )
self.maxPlayers = maxPlayers
local botCount = LUI.UIText.new()
botCount:setLeftRight( true, false, 637, 659 )
botCount:setTopBottom( true, false, 2, 20 )
botCount:setTTF( "fonts/RefrigeratorDeluxe-Regular.ttf" )
botCount:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_LEFT )
botCount:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_TOP )
botCount:linkToElementModel( self, "botCount", true, function ( model )
local _botCount = Engine.GetModelValue( model )
if _botCount then
botCount:setText( "[".. Engine.Localize( _botCount ) .."]" )
end
end )
self:addElement( botCount )
self.botCount = botCount
local ping = LUI.UIText.new()
ping:setLeftRight( true, false, 661, 699.37 )
ping:setTopBottom( true, false, 2, 20 )
ping:setTTF( "fonts/RefrigeratorDeluxe-Regular.ttf" )
ping:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_CENTER )
ping:setAlignment( Enum.LUIAlignment.LUI_ALIGNMENT_TOP )
ping:linkToElementModel( self, "ping", true, function ( model )
local _ping = Engine.GetModelValue( model )
if _ping then
ping:setText( Engine.Localize( _ping ) )
end
end )
self:addElement( ping )
self.ping = ping
spacer.id = "spacer"
self:registerEventHandler( "gain_focus", function ( self, event )
if self.m_focusable and self.spacer:processEvent( event ) then
return true
else
return LUI.UIElement.gainFocus( self, event )
end
end )
LUI.OverrideFunction_CallOriginalSecond( self, "close", function ( element )
element.passwordFlag:close()
element.dedicatedFlag:close()
element.rankedFlag:close()
element.name:close()
element.map:close()
element.hardcoreFlag:close()
element.gametype:close()
element.playerCount:close()
element.maxPlayers:close()
element.ping:close()
end )
if PostLoadFunc then
PostLoadFunc( self, controller, menu )
end
return self
end

View File

@ -1,3 +0,0 @@
function IsServerBrowserEnabled()
return true
end

View File

@ -6,6 +6,7 @@
#include <utils/string.hpp>
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
#include "network.hpp"
#include "scheduler.hpp"
@ -14,6 +15,8 @@ namespace server_list
{
namespace
{
utils::hook::detour lua_gameitem_to_table_hook;
struct state
{
game::netadr_t address{};
@ -72,6 +75,17 @@ namespace server_list
}
callback(true, result);
}
void lua_gameitem_to_table_stub(game::hks::lua_State* state, __int64 gameItem, int index)
{
lua_gameitem_to_table_hook.invoke(state, gameItem, index);
if (state)
{
auto botCount = atoi(game::Info_ValueForKey(gameItem + 276, "bots"));
game::Lua_SetTableInt("botCount", botCount, state);
}
}
}
@ -131,7 +145,9 @@ namespace server_list
s.callback(false, {});
s.callback = {};
});
}, scheduler::async, 200ms);
}, scheduler::async, 200ms);
lua_gameitem_to_table_hook.create(0x141F1FD10_g, lua_gameitem_to_table_stub);
}
void pre_destroy() override

View File

@ -72,7 +72,10 @@ namespace game
WEAK symbol<void()> DB_ReleaseXAssets{0x1414247C0};
// Live
WEAK symbol<bool(uint64_t, int*, bool)> Live_GetConnectivityInformation{0x141E0C380};
WEAK symbol<bool(uint64_t, int*, bool)> Live_GetConnectivityInformation{0x141E0C380};
// Info
WEAK symbol<const char* (__int64, const char* key)> Info_ValueForKey{ 0x1422E87B0 };
// MSG
WEAK symbol<uint8_t(msg_t* msg)> MSG_ReadByte{0x142155450, 0x14050D1B0};
@ -141,7 +144,8 @@ namespace game
WEAK symbol<const char*(int)> UI_CoD_GetRootNameForController{0x141F28940, 0x0};
WEAK symbol<void(hks::lua_State*, const char*)> Lua_CoD_LoadLuaFile{0x141F11A20, 0x0};
WEAK symbol<void(int localClientNum)> CG_LUIHUDRestart{0x140F7E970};
WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0};
WEAK symbol<void(int localClientNum)> CL_CheckKeepDrawingConnectScreen{0x1413CCAE0};
WEAK symbol<void(const char* key, int value, hks::lua_State* luaVM)> Lua_SetTableInt{ 0x141F066E0 };
// Scr
WEAK symbol<void(scriptInstance_t inst, int value)> Scr_AddInt{0x1412E9870, 0x14016F160};

View File

@ -55,10 +55,11 @@ namespace steam
const auto mode = game::eModes(std::atoi(playmode.data()));
const auto* tags = ::utils::string::va(
R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\%d)",
R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\%d\bots\%d\)",
info.get("gametype").data(),
info.get("dedicated") == "1" ? "true" : "false",
mode == game::MODE_ZOMBIES ? "true" : "false", server.m_nPlayers);
mode == game::MODE_ZOMBIES ? "true" : "false",
server.m_nPlayers, atoi(info.get("bots").data()));
::utils::string::copy(server.m_szGameTags, tags);
server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16);