mirror of
https://github.com/odamex/odamex
synced 2026-08-19 02:23:09 -04:00
1594 lines
39 KiB
C++
1594 lines
39 KiB
C++
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id$
|
|
//
|
|
// Copyright (C) 2006-2026 by The Odamex Team.
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// DESCRIPTION:
|
|
// User interface
|
|
//
|
|
// AUTHORS:
|
|
// Michael Wood (mwoodj at huntsvegas dot org)
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <memory>
|
|
|
|
#include <agar/core.h>
|
|
#include <agar/gui.h>
|
|
|
|
#include "agol_main.h"
|
|
#include "agol_settings.h"
|
|
#include "agol_solo.h"
|
|
#include "agol_about.h"
|
|
#include "agol_manual.h"
|
|
#include "game_command.h"
|
|
#include "gui_config.h"
|
|
#include "typedefs.h"
|
|
#include "icons.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace agOdalaunch {
|
|
|
|
// Static Resources
|
|
AG_SurfacePtr AGOL_MainWindow::BulletRed;
|
|
AG_SurfacePtr AGOL_MainWindow::BulletBlue;
|
|
AG_SurfacePtr AGOL_MainWindow::SpectatorIcon;
|
|
AG_SurfacePtr AGOL_MainWindow::PadlockIcon;
|
|
AG_SurfacePtr AGOL_MainWindow::NullSurface;
|
|
|
|
AGOL_MainWindow::AGOL_MainWindow(int width, int height) :
|
|
SettingsDialog(NULL), CloseSettingsHandler(NULL),
|
|
SoloGameDialog(NULL), CloseSoloGameHandler(NULL),
|
|
AboutDialog(NULL), CloseAboutHandler(NULL),
|
|
ManualDialog(NULL), CloseManualHandler(NULL),
|
|
QServer(NULL), WindowExited(false)
|
|
{
|
|
// Create the Agar window. If we are using a single-window display driver (sdlfb, sdlgl)
|
|
// make the window plain (no window decorations). No flags for multi-window drivers (glx, wgl)
|
|
MainWindow = AG_WindowNewNamedS(agDriverSw ? AG_WINDOW_PLAIN : 0, "MainWindow");
|
|
AG_WindowSetGeometryAligned(MainWindow, AG_WINDOW_MC, width, height);
|
|
AG_WindowSetCaptionS(MainWindow, "The Odamex Launcher");
|
|
|
|
// Load interface resources
|
|
LoadResources();
|
|
|
|
// Create the components of the main window
|
|
MainMenu = CreateMainMenu(MainWindow);
|
|
MainButtonBox = CreateMainButtonBox(MainWindow);
|
|
MainListPane = CreateMainListPane(MainWindow);
|
|
BottomListPane = CreateBottomListPane(MainListPane->div[1]);
|
|
ServerList = CreateServerList(MainListPane->div[0]);
|
|
PlayerList = CreatePlayerList(BottomListPane->div[0]);
|
|
ServInfoList = CreateServInfoList(BottomListPane->div[1]);
|
|
MainStatusbar = CreateMainStatusbar(MainWindow);
|
|
|
|
// If this is a single-window display driver (sdlgl, sdlfb) maximize the main window
|
|
// within the single window space.
|
|
if(agDriverSw)
|
|
AG_WindowMaximize(MainWindow);
|
|
|
|
// Add the show event
|
|
AG_AddEvent(MainWindow, "window-shown", EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnShow));
|
|
|
|
// Set the window close action
|
|
AG_WindowSetCloseAction(MainWindow, AG_WINDOW_DETACH);
|
|
AG_AddEvent(MainWindow, "window-close", EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::ExitWindow));
|
|
|
|
// Add the save widget states event
|
|
AG_SetEvent(MainWindow, "save-wstates", EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::SaveWidgetStates));
|
|
|
|
// set up the master server information
|
|
MServer.AddMaster("master1.odamex.net:15000");
|
|
MServer.AddMaster("voxelsoft.com:15000");
|
|
|
|
// Don't poll the server list by default
|
|
StopServerListPoll();
|
|
|
|
// If query master on start is configured post the event.
|
|
GuiConfig::Read("MasterOnStart", StartupQuery);
|
|
if(StartupQuery)
|
|
AG_PostEvent(MainWindow, MainButtonBox->mlist, "button-pushed", NULL);
|
|
|
|
// Show the window
|
|
AG_WindowShow(MainWindow);
|
|
}
|
|
|
|
AGOL_MainWindow::~AGOL_MainWindow()
|
|
{
|
|
// If the window exit action has not been performed by this time
|
|
// then the driver being used is a single-window driver that does
|
|
// not trigger the exit action when the window is closed (due to
|
|
// the fact that the close button is actually for the driver window
|
|
// itself.) In that case it should be safe to save the widget states
|
|
// because the window is actually still valid. In all other cases
|
|
// the contents would be invalid and this would cause a crash!
|
|
if(!WindowExited)
|
|
ExitWindow(NULL);
|
|
|
|
delete[] QServer;
|
|
|
|
delete MainStatusbar;
|
|
delete MainButtonBox;
|
|
}
|
|
|
|
void AGOL_MainWindow::LoadResources()
|
|
{
|
|
AG_DataSource *pngdata;
|
|
|
|
// Red Bullet
|
|
if(!BulletRed.get())
|
|
{
|
|
if((pngdata = AG_OpenConstCore(bullet_red15x15, sizeof(bullet_red15x15))) != NULL)
|
|
{
|
|
BulletRed = AG_SurfacePtr(AG_ReadSurfaceFromPNG(pngdata));
|
|
AG_CloseDataSource(pngdata);
|
|
}
|
|
}
|
|
|
|
// Blue Bullet
|
|
if(!BulletBlue.get())
|
|
{
|
|
if((pngdata = AG_OpenConstCore(bullet_blue15x15, sizeof(bullet_blue15x15))) != NULL)
|
|
{
|
|
BulletBlue = AG_SurfacePtr(AG_ReadSurfaceFromPNG(pngdata));
|
|
AG_CloseDataSource(pngdata);
|
|
}
|
|
}
|
|
|
|
// Spectator Icon
|
|
if(!SpectatorIcon.get())
|
|
{
|
|
if((pngdata = AG_OpenConstCore(spectator, sizeof(spectator))) != NULL)
|
|
{
|
|
SpectatorIcon = AG_SurfacePtr(AG_ReadSurfaceFromPNG(pngdata));
|
|
AG_CloseDataSource(pngdata);
|
|
}
|
|
}
|
|
|
|
// Padlock Icon
|
|
if(!PadlockIcon.get())
|
|
{
|
|
if((pngdata = AG_OpenConstCore(locked_server, sizeof(locked_server))) != NULL)
|
|
{
|
|
PadlockIcon = AG_SurfacePtr(AG_ReadSurfaceFromPNG(pngdata));
|
|
AG_CloseDataSource(pngdata);
|
|
}
|
|
}
|
|
|
|
if(!NullSurface.get())
|
|
{
|
|
NullSurface = AG_SurfacePtr(AG_SurfaceEmpty());
|
|
}
|
|
}
|
|
|
|
AG_Menu *AGOL_MainWindow::CreateMainMenu(void *parent)
|
|
{
|
|
AG_Menu *menu;
|
|
AG_MenuItem *m;
|
|
|
|
// Menu bar
|
|
menu = AG_MenuNew(parent, AG_MENU_HFILL);
|
|
|
|
// File menu
|
|
m = AG_MenuNode(menu->root, "File", NULL);
|
|
AG_MenuAction(m, "Settings", agIconGear.s, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnOpenSettingsDialog));
|
|
AG_MenuSeparator(m);
|
|
AG_MenuActionKb(m, "Exit", agIconClose.s, AG_KEY_Q, AG_KEYMOD_CTRL,
|
|
EventReceiver, "%p", RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnExit));
|
|
|
|
// Action menu
|
|
m = AG_MenuNode(menu->root, "Action", NULL);
|
|
AG_MenuAction(m, "Launch", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnLaunch));
|
|
AG_MenuAction(m, "Run Offline", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnOfflineLaunch));
|
|
AG_MenuSeparator(m);
|
|
AG_MenuAction(m, "Refresh Selected", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnRefreshSelected));
|
|
AG_MenuAction(m, "Refresh All", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnRefreshAll));
|
|
AG_MenuAction(m, "Get Master List", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnGetMasterList));
|
|
|
|
// Tools Menu
|
|
m = AG_MenuNode(menu->root, "Tools", NULL);
|
|
m = AG_MenuNode(m, "OdaGet", NULL);
|
|
AG_MenuDisable(m);
|
|
AG_MenuAction(m, "Get WAD", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnGetWAD));
|
|
AG_MenuAction(m, "Configure", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnOdaGetConfig));
|
|
AG_MenuEnable(m);
|
|
|
|
// Advanced menu
|
|
m = AG_MenuNode(menu->root, "Advanced", NULL);
|
|
AG_MenuAction(m, "Manual Connect", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnManualConnect));
|
|
AG_MenuDisable(m);
|
|
AG_MenuAction(m, "Custom Servers", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnCustomServer));
|
|
AG_MenuEnable(m);
|
|
|
|
// Help menu
|
|
m = AG_MenuNode(menu->root, "Help", NULL);
|
|
AG_MenuAction(m, "Report Bug", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnReportBug));
|
|
AG_MenuSeparator(m);
|
|
AG_MenuAction(m, "About", NULL, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnAbout));
|
|
|
|
return menu;
|
|
}
|
|
|
|
ODA_ButtonBox *AGOL_MainWindow::CreateMainButtonBox(void *parent)
|
|
{
|
|
ODA_ButtonBox *bbox;
|
|
|
|
bbox = new ODA_ButtonBox;
|
|
|
|
bbox->buttonbox = AG_BoxNewHoriz(parent, AG_BOX_HFILL);
|
|
|
|
bbox->launch = CreateButton(bbox->buttonbox, "Launch", odalaunchico,
|
|
sizeof(odalaunchico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnLaunch);
|
|
bbox->qlaunch = CreateButton(bbox->buttonbox, "Quick Launch", odaqlaunchico,
|
|
sizeof(odaqlaunchico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnOfflineLaunch);
|
|
|
|
AG_SeparatorNewVert(bbox->buttonbox);
|
|
|
|
bbox->refresh = CreateButton(bbox->buttonbox, "Refresh Selected", btnrefreshico,
|
|
sizeof(btnrefreshico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnRefreshSelected);
|
|
bbox->refreshall = CreateButton(bbox->buttonbox, "Refresh All", btnrefreshallico,
|
|
sizeof(btnrefreshico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnRefreshAll);
|
|
bbox->mlist = CreateButton(bbox->buttonbox, "Query Master", btnlistico,
|
|
sizeof(btnlistico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnGetMasterList);
|
|
|
|
AG_SeparatorNewVert(bbox->buttonbox);
|
|
|
|
bbox->settings = CreateButton(bbox->buttonbox, "Settings", btnsettingsico,
|
|
sizeof(btnsettingsico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnOpenSettingsDialog);
|
|
bbox->about = CreateButton(bbox->buttonbox, "About", btnhelpico,
|
|
sizeof(btnhelpico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnAbout);
|
|
|
|
AG_SeparatorNewVert(bbox->buttonbox);
|
|
|
|
bbox->exit = CreateButton(bbox->buttonbox, "Exit", btnexitico,
|
|
sizeof(btnexitico), (EVENT_FUNC_PTR)&AGOL_MainWindow::OnExit);
|
|
|
|
return bbox;
|
|
}
|
|
|
|
AG_Pane *AGOL_MainWindow::CreateMainListPane(void *parent)
|
|
{
|
|
AG_Pane *pane;
|
|
|
|
pane = AG_PaneNewVert(parent, AG_PANE_EXPAND);
|
|
|
|
AG_PaneResizeAction(pane, AG_PANE_EXPAND_DIV1);
|
|
AG_PaneMoveDividerPct(pane, 60);
|
|
AG_BoxSetPadding(pane->div[0], 1);
|
|
AG_BoxSetPadding(pane->div[1], 1);
|
|
|
|
return pane;
|
|
}
|
|
|
|
AG_Pane *AGOL_MainWindow::CreateBottomListPane(void *parent)
|
|
{
|
|
AG_Pane *pane;
|
|
|
|
pane = AG_PaneNewHoriz(parent, AG_PANE_EXPAND);
|
|
|
|
AG_PaneResizeAction(pane, AG_PANE_EXPAND_DIV1);
|
|
AG_BoxSetPadding(pane->div[0], 1);
|
|
AG_BoxSetPadding(pane->div[1], 1);
|
|
|
|
return pane;
|
|
}
|
|
|
|
AG_Table *AGOL_MainWindow::CreateServerList(void *parent)
|
|
{
|
|
AG_Table *list;
|
|
ostringstream colSzSpec[8];
|
|
int colW[8] = { 200, 33, 48, 100, 47, 105, 72, 125 };
|
|
|
|
list = AG_TableNewPolled(parent, AG_TABLE_EXPAND, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::UpdateServerList));
|
|
|
|
AG_TableSetRowDblClickFn(list, EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnLaunch));
|
|
|
|
AG_SetEvent(list, "row-selected", EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnServerListRowSelected));
|
|
|
|
// Configure each columns size spec
|
|
for(int i = 0; i < 8; i++)
|
|
{
|
|
ostringstream colOption;
|
|
int width;
|
|
|
|
colOption << "SrvListColW_" << i;
|
|
|
|
// Check for a previously stored column size
|
|
if(!GuiConfig::Read(colOption.str(), width) && width > 0)
|
|
colW[i] = width;
|
|
|
|
colSzSpec[i] << colW[i] << "px";
|
|
}
|
|
|
|
AG_TableAddCol(list, "", "18px", &AGOL_MainWindow::CellCompare);
|
|
AG_TableAddCol(list, "Server Name", colSzSpec[0].str().c_str(), NULL);
|
|
AG_TableAddCol(list, "Ping", colSzSpec[1].str().c_str(), NULL);
|
|
AG_TableAddCol(list, "Players", colSzSpec[2].str().c_str(), &AGOL_MainWindow::CellCompare);
|
|
AG_TableAddCol(list, "WADs", colSzSpec[3].str().c_str(), NULL);
|
|
AG_TableAddCol(list, "Map", colSzSpec[4].str().c_str(), NULL);
|
|
AG_TableAddCol(list, "Type", colSzSpec[5].str().c_str(), NULL);
|
|
AG_TableAddCol(list, "Game IWAD", colSzSpec[6].str().c_str(), NULL);
|
|
AG_TableAddCol(list, "Address : Port", colSzSpec[7].str().c_str(), NULL);
|
|
|
|
// Add an update event that we can schedule without enabling polling
|
|
AG_SetEvent(list, "update-items", EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::UpdateServerList));
|
|
|
|
return list;
|
|
}
|
|
|
|
AG_Table *AGOL_MainWindow::CreatePlayerList(void *parent)
|
|
{
|
|
AG_Table *list;
|
|
|
|
list = AG_TableNew(parent, AG_TABLE_EXPAND);
|
|
|
|
AG_WidgetSetFocusable(list, 0);
|
|
|
|
AG_TableAddCol(list, "", "18px", &AGOL_MainWindow::CellCompare);
|
|
AG_TableAddCol(list, "Player Name", "150px", NULL);
|
|
AG_TableAddCol(list, "Ping", "< Ping >", NULL);
|
|
AG_TableAddCol(list, "Time", "< Time >", NULL);
|
|
AG_TableAddCol(list, "Frags", "< Frags >", NULL);
|
|
AG_TableAddCol(list, "Kills", "< Kills >", NULL);
|
|
AG_TableAddCol(list, "Deaths", "< Deaths >", NULL);
|
|
AG_TableAddCol(list, "", "18px", &AGOL_MainWindow::CellCompare);
|
|
|
|
return list;
|
|
}
|
|
|
|
AG_Table *AGOL_MainWindow::CreateServInfoList(void *parent)
|
|
{
|
|
AG_Table *list;
|
|
|
|
list = AG_TableNew(parent, AG_TABLE_EXPAND);
|
|
|
|
AG_TableSetColumnAction(list, AG_TABLE_COL_SELECT);
|
|
AG_WidgetSetFocusable(list, 0);
|
|
|
|
AG_TableAddCol(list, "Server Details", "-", NULL);
|
|
|
|
return list;
|
|
}
|
|
|
|
ODA_Statusbar *AGOL_MainWindow::CreateMainStatusbar(void *parent)
|
|
{
|
|
ODA_Statusbar *statusbar;
|
|
|
|
statusbar = new ODA_Statusbar;
|
|
|
|
statusbar->statbox = AG_BoxNewHoriz(parent, AG_BOX_HOMOGENOUS | AG_BOX_HFILL);
|
|
|
|
statusbar->tooltip = AG_LabelNew(statusbar->statbox, AG_LABEL_EXPAND, "Welcome to Odamex");
|
|
|
|
statusbar->mping = AG_LabelNew(statusbar->statbox, AG_LABEL_EXPAND, "Master Ping: 0");
|
|
|
|
statusbar->queried.completed = statusbar->queried.total = 0;
|
|
AG_MutexInit(&statusbar->queried.mutex);
|
|
statusbar->queried.label = AG_LabelNewPolledMT(statusbar->statbox, AG_LABEL_EXPAND, &statusbar->queried.mutex,
|
|
"Queried Servers %i of %i", &statusbar->queried.completed, &statusbar->queried.total);
|
|
|
|
statusbar->players.numplayers = 0;
|
|
AG_MutexInit(&statusbar->players.mutex);
|
|
statusbar->players.label = AG_LabelNewPolledMT(statusbar->statbox, AG_LABEL_EXPAND, &statusbar->players.mutex,
|
|
"Total Players: %i", &statusbar->players.numplayers);
|
|
|
|
return statusbar;
|
|
}
|
|
|
|
|
|
//*********************************//
|
|
// Interface Interaction Functions //
|
|
//*********************************//
|
|
void AGOL_MainWindow::UpdateStatusbarTooltip(const char *tip)
|
|
{
|
|
if(tip)
|
|
{
|
|
AG_LabelTextS(MainStatusbar->tooltip, tip);
|
|
}
|
|
}
|
|
|
|
void AGOL_MainWindow::ClearStatusbarTooltip()
|
|
{
|
|
AG_LabelTextS(MainStatusbar->tooltip, "");
|
|
}
|
|
|
|
void AGOL_MainWindow::UpdateStatusbarMasterPing(uint64_t ping)
|
|
{
|
|
AG_LabelText(MainStatusbar->mping, "Master Ping: %lu", ping);
|
|
}
|
|
|
|
AG_Button *AGOL_MainWindow::CreateButton(void *parent, const char *label,
|
|
const unsigned char *icon, int iconsize,
|
|
EVENT_FUNC_PTR handler)
|
|
{
|
|
AG_Button *button;
|
|
AG_DataSource *icondata;
|
|
|
|
button = AG_ButtonNewFn(parent, 0, label, EventReceiver, "%p", RegisterEventHandler(handler));
|
|
|
|
AG_SetEvent(button, "button-mouseoverlap", EventReceiver, "%p",
|
|
RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnMouseOverWidget));
|
|
|
|
|
|
if((icondata = AG_OpenConstCore(icon, iconsize)) != NULL)
|
|
{
|
|
AG_ButtonSurface(button, AG_ReadSurfaceFromPNG(icondata));
|
|
AG_CloseDataSource(icondata);
|
|
}
|
|
else
|
|
{
|
|
cerr << "Failed to load icon: " << AG_GetError() << endl;
|
|
}
|
|
|
|
return button;
|
|
}
|
|
|
|
int AGOL_MainWindow::GetSelectedServerListRow()
|
|
{
|
|
// Loop until a selected row is found
|
|
for(int row = 0; row < ServerList->m; row++)
|
|
{
|
|
if(AG_TableRowSelected(ServerList, row))
|
|
return row;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int AGOL_MainWindow::GetSelectedServerArrayIndex()
|
|
{
|
|
int row;
|
|
size_t serverCount;
|
|
string cellAddr;
|
|
|
|
// Get the selected list row index
|
|
row = GetSelectedServerListRow();
|
|
|
|
// No selection
|
|
if(row < 0)
|
|
return -1;
|
|
|
|
cellAddr = GetAddrFromServerListRow(row);
|
|
|
|
if(!cellAddr.size())
|
|
return -1;
|
|
|
|
// Get the number of servers
|
|
MServer.GetLock();
|
|
serverCount = MServer.GetServerCount();
|
|
MServer.Unlock();
|
|
|
|
// No servers
|
|
if(serverCount == 0)
|
|
return -1;
|
|
|
|
// Loop until the selected server array is found
|
|
for(size_t i = 0; i < serverCount; i++)
|
|
{
|
|
string srvAddr;
|
|
|
|
QServer[i].GetLock();
|
|
srvAddr = QServer[i].GetAddress();
|
|
QServer[i].Unlock();
|
|
|
|
if(srvAddr == cellAddr)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
string AGOL_MainWindow::GetAddrFromServerListRow(int row)
|
|
{
|
|
AG_TableCell *cell = NULL;
|
|
|
|
cell = AG_TableGetCell(ServerList, row, ServerList->n-1);
|
|
|
|
// Row has no address field data
|
|
if(!cell || !cell->data.s)
|
|
return "";
|
|
|
|
return string(cell->data.s);
|
|
}
|
|
|
|
int AGOL_MainWindow::GetServerListRowFromAddr(const string &address)
|
|
{
|
|
// Loop until the server address is found
|
|
for(int row = 0; row < ServerList->m; row++)
|
|
{
|
|
string cellAddr;
|
|
|
|
cellAddr = GetAddrFromServerListRow(row);
|
|
|
|
if(!cellAddr.size())
|
|
continue;
|
|
|
|
if(address == cellAddr)
|
|
return row;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int AGOL_MainWindow::GetServerArrayIndexFromListRow(int row)
|
|
{
|
|
size_t serverCount;
|
|
string cellAddr;
|
|
|
|
// No selection
|
|
if(row < 0)
|
|
return -1;
|
|
|
|
cellAddr = GetAddrFromServerListRow(row);
|
|
|
|
if(!cellAddr.size())
|
|
return -1;
|
|
|
|
// Get the number of servers
|
|
MServer.GetLock();
|
|
serverCount = MServer.GetServerCount();
|
|
MServer.Unlock();
|
|
|
|
// No servers
|
|
if(serverCount == 0)
|
|
return -1;
|
|
|
|
// Loop until the selected server array is found
|
|
for(size_t i = 0; i < serverCount; i++)
|
|
{
|
|
string srvAddr;
|
|
|
|
QServer[i].GetLock();
|
|
srvAddr = QServer[i].GetAddress();
|
|
QServer[i].Unlock();
|
|
|
|
if(srvAddr == cellAddr)
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void AGOL_MainWindow::ClearList(AG_Table *table)
|
|
{
|
|
if(!table)
|
|
return;
|
|
|
|
AG_TableBegin(table);
|
|
AG_TableEnd(table);
|
|
}
|
|
|
|
void AGOL_MainWindow::UpdatePlayerList(int serverNdx)
|
|
{
|
|
// Always start with an empty list
|
|
ClearList(PlayerList);
|
|
|
|
// No server selected
|
|
if(serverNdx < 0)
|
|
return;
|
|
|
|
QServer[serverNdx].GetLock();
|
|
|
|
if(QServer[serverNdx].Info.Players.size())
|
|
{
|
|
for(size_t i = 0; i < QServer[serverNdx].Info.Players.size(); i++)
|
|
{
|
|
AG_Surface *(*teamFn)(void*,int,int) = NullSurfFn;
|
|
AG_Surface *(*specFn)(void*,int,int) = NullSurfFn;
|
|
string name = " ";
|
|
|
|
if(QServer[serverNdx].Info.Players[i].Name.size())
|
|
name = QServer[serverNdx].Info.Players[i].Name;
|
|
|
|
// Team pixmap
|
|
if(QServer[serverNdx].Info.GameType == GT_TeamDeathmatch ||
|
|
QServer[serverNdx].Info.GameType == GT_CaptureTheFlag)
|
|
{
|
|
switch(QServer[serverNdx].Info.Players[i].Team)
|
|
{
|
|
case 0:
|
|
teamFn = BulletRedSurfFn;
|
|
break;
|
|
case 1:
|
|
teamFn = BulletBlueSurfFn;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Spectator pixmap
|
|
if(QServer[serverNdx].Info.Players[i].Spectator)
|
|
{
|
|
specFn = SpectatorIconSurfFn;
|
|
}
|
|
|
|
AG_TableAddRow(PlayerList, "%[FS]:%s:%u:%u:%i:%u:%u:%[FS]",
|
|
specFn, name.c_str(),
|
|
QServer[serverNdx].Info.Players[i].Ping,
|
|
QServer[serverNdx].Info.Players[i].Time,
|
|
QServer[serverNdx].Info.Players[i].Frags,
|
|
QServer[serverNdx].Info.Players[i].Kills,
|
|
QServer[serverNdx].Info.Players[i].Deaths,
|
|
teamFn);
|
|
}
|
|
}
|
|
|
|
QServer[serverNdx].Unlock();
|
|
}
|
|
|
|
void AGOL_MainWindow::UpdateServInfoList(int serverNdx)
|
|
{
|
|
// Always start with an empty list
|
|
ClearList(ServInfoList);
|
|
|
|
// No server selected
|
|
if(serverNdx < 0)
|
|
return;
|
|
|
|
QServer[serverNdx].GetLock();
|
|
|
|
if(QServer[serverNdx].Info.Cvars.size())
|
|
{
|
|
ostringstream rowStream;
|
|
|
|
// Version
|
|
rowStream << "Version " << (int)QServer[serverNdx].Info.VersionMajor << "." <<
|
|
(int)QServer[serverNdx].Info.VersionMinor << "." <<
|
|
(int)QServer[serverNdx].Info.VersionPatch << "-r" <<
|
|
QServer[serverNdx].Info.VersionRevision;
|
|
AG_TableAddRow(ServInfoList, "%s", rowStream.str().c_str());
|
|
rowStream.str("");
|
|
|
|
// Query Protocol Version
|
|
rowStream << "QP Version " << QServer[serverNdx].Info.VersionProtocol;
|
|
AG_TableAddRow(ServInfoList, "%s", rowStream.str().c_str());
|
|
rowStream.str("");
|
|
|
|
AG_TableAddRow(ServInfoList, "%s", "");
|
|
|
|
// Status
|
|
AG_TableAddRow(ServInfoList, "%s", "Game Status");
|
|
|
|
rowStream << "Time Left " << QServer[serverNdx].Info.TimeLeft;
|
|
AG_TableAddRow(ServInfoList, "%s", rowStream.str().c_str());
|
|
rowStream.str("");
|
|
|
|
if(QServer[serverNdx].Info.GameType == GT_TeamDeathmatch ||
|
|
QServer[serverNdx].Info.GameType == GT_CaptureTheFlag)
|
|
{
|
|
rowStream << "Score Limit " << QServer[serverNdx].Info.ScoreLimit;
|
|
AG_TableAddRow(ServInfoList, "%s", rowStream.str().c_str());
|
|
rowStream.str("");
|
|
}
|
|
|
|
AG_TableAddRow(ServInfoList, "%s", "");
|
|
|
|
// Patch (BEX/DEH) files
|
|
AG_TableAddRow(ServInfoList, "%s", "BEX/DEH Files");
|
|
|
|
if(QServer[serverNdx].Info.Patches.size() <= 0)
|
|
AG_TableAddRow(ServInfoList, "%s", "None");
|
|
else
|
|
{
|
|
size_t patchCnt = QServer[serverNdx].Info.Patches.size();
|
|
size_t i = 0;
|
|
|
|
while(i < patchCnt)
|
|
{
|
|
string patch = QServer[serverNdx].Info.Patches[i];
|
|
|
|
++i;
|
|
|
|
if(i < patchCnt)
|
|
patch += " " + QServer[serverNdx].Info.Patches[i];
|
|
|
|
++i;
|
|
|
|
AG_TableAddRow(ServInfoList, "%s", patch.c_str());
|
|
}
|
|
}
|
|
|
|
AG_TableAddRow(ServInfoList, "%s", "");
|
|
|
|
// Sort cvars ascending
|
|
sort(QServer[serverNdx].Info.Cvars.begin(), QServer[serverNdx].Info.Cvars.end(), AGOL_MainWindow::CvarCompare);
|
|
|
|
// Cvars that are enabled
|
|
AG_TableAddRow(ServInfoList, "%s", "Cvars Enabled");
|
|
|
|
std::vector<Cvar_t>::iterator cvarIter = QServer[serverNdx].Info.Cvars.begin();
|
|
std::vector<Cvar_t>::iterator cvarEndIter = QServer[serverNdx].Info.Cvars.end();
|
|
|
|
while(cvarIter != cvarEndIter)
|
|
{
|
|
if(cvarIter->Type == CVARTYPE_BOOL)
|
|
{
|
|
AG_TableAddRow(ServInfoList, "%s", cvarIter->Name.c_str());
|
|
}
|
|
++cvarIter;
|
|
}
|
|
AG_TableAddRow(ServInfoList, "%s", "");
|
|
|
|
// Gameplay variables (Cvars, others)
|
|
AG_TableAddRow(ServInfoList, "%s", "Gameplay Variables");
|
|
|
|
cvarIter = QServer[serverNdx].Info.Cvars.begin();
|
|
while(cvarIter != cvarEndIter)
|
|
{
|
|
rowStream << cvarIter->Name << " ";
|
|
|
|
switch(cvarIter->Type)
|
|
{
|
|
case CVARTYPE_BYTE:
|
|
{
|
|
rowStream << (int)cvarIter->i8;
|
|
}
|
|
break;
|
|
|
|
case CVARTYPE_WORD:
|
|
{
|
|
rowStream << cvarIter->i16;
|
|
}
|
|
break;
|
|
|
|
case CVARTYPE_INT:
|
|
{
|
|
rowStream << cvarIter->i32;
|
|
}
|
|
break;
|
|
|
|
case CVARTYPE_FLOAT:
|
|
case CVARTYPE_STRING:
|
|
{
|
|
rowStream << cvarIter->Value;
|
|
}
|
|
break;
|
|
|
|
case CVARTYPE_NONE:
|
|
case CVARTYPE_MAX:
|
|
default:
|
|
rowStream.str("");
|
|
break;
|
|
}
|
|
|
|
if(!rowStream.str().empty())
|
|
{
|
|
AG_TableAddRow(ServInfoList, "%s", rowStream.str().c_str());
|
|
rowStream.str("");
|
|
}
|
|
++cvarIter;
|
|
}
|
|
|
|
QServer[serverNdx].Unlock();
|
|
|
|
AutoSizeTableColumn(ServInfoList, 0);
|
|
}
|
|
}
|
|
|
|
void AGOL_MainWindow::UpdateQueriedLabelTotal(int total)
|
|
{
|
|
if(total < 0)
|
|
return;
|
|
|
|
AG_MutexLock(&MainStatusbar->queried.mutex);
|
|
|
|
MainStatusbar->queried.total = total;
|
|
|
|
AG_MutexUnlock(&MainStatusbar->queried.mutex);
|
|
}
|
|
|
|
void AGOL_MainWindow::UpdateQueriedLabelCompleted(int completed)
|
|
{
|
|
if(completed < 0)
|
|
return;
|
|
|
|
AG_MutexLock(&MainStatusbar->queried.mutex);
|
|
|
|
MainStatusbar->queried.completed = completed;
|
|
|
|
AG_MutexUnlock(&MainStatusbar->queried.mutex);
|
|
}
|
|
|
|
void AGOL_MainWindow::ResetTotalPlayerCount()
|
|
{
|
|
AG_MutexLock(&MainStatusbar->players.mutex);
|
|
|
|
MainStatusbar->players.numplayers = 0;
|
|
|
|
AG_MutexUnlock(&MainStatusbar->players.mutex);
|
|
}
|
|
|
|
void AGOL_MainWindow::AddToPlayerTotal(int num)
|
|
{
|
|
if(num <= 0)
|
|
return;
|
|
|
|
AG_MutexLock(&MainStatusbar->players.mutex);
|
|
|
|
MainStatusbar->players.numplayers += num;
|
|
|
|
AG_MutexUnlock(&MainStatusbar->players.mutex);
|
|
}
|
|
|
|
void AGOL_MainWindow::SetServerListRowCellFlags(int row)
|
|
{
|
|
// Disable cell comparison on all but the last column (address:port)
|
|
// for the selection restoration cell comparison.
|
|
for(int i = 0; i < ServerList->n - 1; i++)
|
|
{
|
|
AG_TableCell *cell = NULL;
|
|
|
|
cell = AG_TableGetCell(ServerList, row, i);
|
|
cell->flags |= AG_TABLE_CELL_NOCOMPARE;
|
|
}
|
|
}
|
|
|
|
void AGOL_MainWindow::StartServerListPoll()
|
|
{
|
|
AG_TableSetPollInterval(ServerList, 250);
|
|
}
|
|
|
|
void AGOL_MainWindow::StopServerListPoll()
|
|
{
|
|
AG_TableSetPollInterval(ServerList, 0);
|
|
}
|
|
|
|
// This begs for a custom table widget
|
|
void AGOL_MainWindow::AutoSizeTableColumn(AG_Table *table, int col)
|
|
{
|
|
int maxpx = 0;
|
|
|
|
if(table->n <= col)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for(int row = 0; row < table->m; ++row)
|
|
{
|
|
AG_TableCell *cell = AG_TableGetCell(table, row, col);
|
|
|
|
switch(cell->type)
|
|
{
|
|
case AG_CELL_STRING:
|
|
{
|
|
if(cell && cell->data.s)
|
|
{
|
|
int txtsize;
|
|
|
|
AG_TextSize(cell->data.s, &txtsize, NULL);
|
|
|
|
if(txtsize > maxpx)
|
|
{
|
|
maxpx = txtsize;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
AG_ObjectLock(table);
|
|
|
|
if(maxpx > table->wColDefault)
|
|
{
|
|
table->cols[0].w = maxpx + 2;
|
|
}
|
|
else
|
|
{
|
|
table->cols[0].w = table->wColDefault;
|
|
}
|
|
|
|
AG_ObjectUnlock(table);
|
|
AG_WidgetUpdate(table);
|
|
}
|
|
|
|
//*************************//
|
|
// Event Handler Functions //
|
|
//*************************//
|
|
void AGOL_MainWindow::OnOpenSettingsDialog(AG_Event *event)
|
|
{
|
|
if(SettingsDialog)
|
|
return;
|
|
|
|
SettingsDialog = new AGOL_Settings();
|
|
|
|
CloseSettingsHandler = RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnCloseSettingsDialog);
|
|
|
|
SettingsDialog->SetWindowCloseEvent(CloseSettingsHandler);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnCloseSettingsDialog(AG_Event *event)
|
|
{
|
|
DeleteEventHandler(CloseSettingsHandler);
|
|
|
|
delete SettingsDialog;
|
|
SettingsDialog = NULL;
|
|
|
|
// Update the server list in case the "show blocked servers" option changed.
|
|
AG_SchedEvent(MainWindow, ServerList, 0, "update-items", NULL);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnAbout(AG_Event *event)
|
|
{
|
|
if(AboutDialog)
|
|
return;
|
|
|
|
AboutDialog = new AGOL_About();
|
|
|
|
CloseAboutHandler = RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnCloseAboutDialog);
|
|
|
|
AboutDialog->SetWindowCloseEvent(CloseAboutHandler);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnCloseAboutDialog(AG_Event *event)
|
|
{
|
|
DeleteEventHandler(CloseAboutHandler);
|
|
|
|
delete AboutDialog;
|
|
AboutDialog = NULL;
|
|
}
|
|
|
|
void AGOL_MainWindow::OnOfflineLaunch(AG_Event *event)
|
|
{
|
|
if(SoloGameDialog)
|
|
return;
|
|
|
|
SoloGameDialog = new AGOL_Solo();
|
|
|
|
CloseSoloGameHandler = RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnCloseSoloGameDialog);
|
|
|
|
SoloGameDialog->SetWindowCloseEvent(CloseSoloGameHandler);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnCloseSoloGameDialog(AG_Event *event)
|
|
{
|
|
DeleteEventHandler(CloseSoloGameHandler);
|
|
|
|
delete SoloGameDialog;
|
|
SoloGameDialog = NULL;
|
|
}
|
|
|
|
void AGOL_MainWindow::OnManualConnect(AG_Event *event)
|
|
{
|
|
if(ManualDialog)
|
|
return;
|
|
|
|
ManualDialog = new AGOL_Manual();
|
|
|
|
CloseManualHandler = RegisterEventHandler((EVENT_FUNC_PTR)&AGOL_MainWindow::OnCloseManualDialog);
|
|
|
|
ManualDialog->SetWindowCloseEvent(CloseManualHandler);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnCloseManualDialog(AG_Event *event)
|
|
{
|
|
DeleteEventHandler(CloseManualHandler);
|
|
|
|
delete ManualDialog;
|
|
ManualDialog = NULL;
|
|
}
|
|
|
|
void AGOL_MainWindow::OnShow(AG_Event *event)
|
|
{
|
|
AG_TableSetDefaultColWidth(ServInfoList, ServInfoList->cols[0].w);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnExit(AG_Event *event)
|
|
{
|
|
ExitWindow(NULL);
|
|
|
|
// Exit the event loop
|
|
AG_QuitGUI();
|
|
}
|
|
|
|
void AGOL_MainWindow::OnLaunch(AG_Event *event)
|
|
{
|
|
GameCommand cmd;
|
|
string sAddr;
|
|
string waddirs;
|
|
string extraParams;
|
|
int ndx = GetSelectedServerArrayIndex();
|
|
|
|
// No selection
|
|
if(ndx < 0)
|
|
return;
|
|
|
|
if(GuiConfig::Read("WadDirs", waddirs))
|
|
{
|
|
char cwd[AG_PATHNAME_MAX];
|
|
|
|
if(!AG_GetCWD(cwd, AG_PATHNAME_MAX))
|
|
cmd.AddParameter("-waddir", cwd);
|
|
}
|
|
else
|
|
cmd.AddParameter("-waddir", waddirs);
|
|
|
|
if(!GuiConfig::Read("ExtraParams", extraParams))
|
|
cmd.AddParameter(extraParams);
|
|
|
|
|
|
QServer[ndx].GetLock();
|
|
sAddr = QServer[ndx].GetAddress();
|
|
QServer[ndx].Unlock();
|
|
|
|
if(!sAddr.size())
|
|
return;
|
|
|
|
cmd.AddParameter("-connect", sAddr);
|
|
|
|
cmd.Launch();
|
|
}
|
|
|
|
void AGOL_MainWindow::OnRefreshSelected(AG_Event *event)
|
|
{
|
|
int ndx = GetSelectedServerArrayIndex();
|
|
|
|
// No selection
|
|
if(ndx < 0)
|
|
return;
|
|
|
|
QuerySingleServer(&QServer[ndx]);
|
|
|
|
AG_SchedEvent(MainWindow, ServerList, 0, "update-items", NULL);
|
|
UpdatePlayerList(ndx);
|
|
UpdateServInfoList(ndx);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnRefreshAll(AG_Event *event)
|
|
{
|
|
// Kick off a thread to query all the servers
|
|
if(!MasterThread.IsRunning())
|
|
MasterThread.Create(this, (THREAD_FUNC_PTR)&AGOL_MainWindow::QueryAllServers, NULL);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnGetMasterList(AG_Event *event)
|
|
{
|
|
// Kick off a thread to get the master list and query all the servers
|
|
if(!MasterThread.IsRunning())
|
|
MasterThread.Create(this, (THREAD_FUNC_PTR)&AGOL_MainWindow::GetMasterList, NULL);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnGetWAD(AG_Event *event)
|
|
{
|
|
cout << "Get WAD: Stub" << endl;
|
|
}
|
|
|
|
void AGOL_MainWindow::OnOdaGetConfig(AG_Event *event)
|
|
{
|
|
cout << "OdaGet Config: Stub" << endl;
|
|
}
|
|
|
|
void AGOL_MainWindow::OnCustomServer(AG_Event *event)
|
|
{
|
|
cout << "Custom Server: Stub" << endl;
|
|
}
|
|
|
|
void AGOL_MainWindow::OnReportBug(AG_Event *event)
|
|
{
|
|
AG_TextMsgS(AG_MSG_INFO, "Please report any bugs at: http://odamex.net/bugs/\n\nThank You!");
|
|
}
|
|
|
|
void AGOL_MainWindow::OnMouseOverWidget(AG_Event *event)
|
|
{
|
|
static AG_Widget *last = NULL;
|
|
AG_Widget *widget = static_cast<AG_Widget *>(AG_SELF());
|
|
int overlap = AG_INT(2); // Passed by '<widget>-mouseoverlap'
|
|
|
|
if(overlap)
|
|
{
|
|
// The mouse over event will be issued for all mouse movement over a widget.
|
|
// If the status bar is already set to the desired label leave it alone.
|
|
if(last && (last == widget))
|
|
return;
|
|
|
|
// Any widget can use this mouse over function. We just need to cast the generic widget pointer.
|
|
if((AG_Button*)widget == MainButtonBox->launch)
|
|
UpdateStatusbarTooltip("Connect to selected server");
|
|
else if((AG_Button*)widget == MainButtonBox->qlaunch)
|
|
UpdateStatusbarTooltip("Start Solo Game");
|
|
else if((AG_Button*)widget == MainButtonBox->refresh)
|
|
UpdateStatusbarTooltip("Refresh selected server");
|
|
else if((AG_Button*)widget == MainButtonBox->refreshall)
|
|
UpdateStatusbarTooltip("Refresh all servers");
|
|
else if((AG_Button*)widget == MainButtonBox->mlist)
|
|
UpdateStatusbarTooltip("Get new server list");
|
|
else if((AG_Button*)widget == MainButtonBox->settings)
|
|
UpdateStatusbarTooltip("Configure the launcher");
|
|
else if((AG_Button*)widget == MainButtonBox->about)
|
|
UpdateStatusbarTooltip("About the launcher");
|
|
else if((AG_Button*)widget == MainButtonBox->exit)
|
|
UpdateStatusbarTooltip("Exit the launcher");
|
|
else
|
|
ClearStatusbarTooltip();
|
|
|
|
// Record that we have set the statusbar for this widget mouse over.
|
|
last = widget;
|
|
}
|
|
else
|
|
{
|
|
ClearStatusbarTooltip();
|
|
last = NULL;
|
|
}
|
|
}
|
|
|
|
void AGOL_MainWindow::UpdateServerList(AG_Event *event)
|
|
{
|
|
size_t serverCount;
|
|
bool showBlocked;
|
|
|
|
// If we can't immediately get a lock on the
|
|
// master server don't update the list this tick.
|
|
if(MServer.TryLock())
|
|
return;
|
|
|
|
serverCount = MServer.GetServerCount();
|
|
|
|
MServer.Unlock();
|
|
|
|
// Saves table selection and clears the rows
|
|
AG_TableBegin(ServerList);
|
|
|
|
// Reset the total player count statusbar
|
|
ResetTotalPlayerCount();
|
|
|
|
if(serverCount == 0)
|
|
{
|
|
AG_TableEnd(ServerList);
|
|
return;
|
|
}
|
|
|
|
GuiConfig::Read("ShowBlockedServers", showBlocked);
|
|
|
|
for(size_t i = 0; i < serverCount; ++i)
|
|
{
|
|
AG_Surface *(*padlockFn)(void*,int,int) = NullSurfFn;
|
|
ostringstream plyrCnt;
|
|
string name;
|
|
string iwad;
|
|
string sAddr;
|
|
string map;
|
|
string pwads;
|
|
string gametype;
|
|
size_t wadCnt = 0;
|
|
int row;
|
|
|
|
// If we can't immediately get a lock on this server
|
|
// move on to the next one.
|
|
if(QServer[i].TryLock())
|
|
continue;
|
|
|
|
sAddr = QServer[i].GetAddress();
|
|
|
|
if(!QServer[i].GetPing() || !QServer[i].Info.MaxClients)
|
|
{
|
|
QServer[i].Unlock();
|
|
|
|
if(showBlocked)
|
|
{
|
|
// Display just the address for unqueried or unreachable servers
|
|
row = AG_TableAddRow(ServerList, "%[FS]::::::::%s", padlockFn, sAddr.c_str());
|
|
|
|
// Set the cell flags
|
|
SetServerListRowCellFlags(row);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Name column
|
|
if(QServer[i].Info.Name.size())
|
|
name = QServer[i].Info.Name;
|
|
|
|
wadCnt = QServer[i].Info.Wads.size();
|
|
|
|
// IWAD column
|
|
if(wadCnt >= 2)
|
|
iwad = QServer[i].Info.Wads[1].Name.substr(0, QServer[i].Info.Wads[1].Name.find('.'));
|
|
|
|
// PWADs column
|
|
if(wadCnt < 3)
|
|
pwads = " "; // Required to satisfy the add row format string
|
|
else
|
|
for(size_t j = 2; j < wadCnt; j++)
|
|
pwads += QServer[i].Info.Wads[j].Name.substr(0, QServer[i].Info.Wads[j].Name.find('.')) + " ";
|
|
|
|
// Player Count column
|
|
plyrCnt << QServer[i].Info.Players.size() << "/" << static_cast<int>(QServer[i].Info.MaxClients) << '\0';
|
|
|
|
// Map column
|
|
if(QServer[i].Info.CurrentMap.size())
|
|
{
|
|
map.resize(QServer[i].Info.CurrentMap.size());
|
|
transform(QServer[i].Info.CurrentMap.begin(), QServer[i].Info.CurrentMap.end(), map.begin(), ::toupper);
|
|
}
|
|
|
|
// Game Type
|
|
switch(QServer[i].Info.GameType)
|
|
{
|
|
case GT_Cooperative:
|
|
{
|
|
// Detect a single-player server
|
|
if(QServer[i].Info.MaxPlayers > 1)
|
|
gametype = "Cooperative";
|
|
else
|
|
gametype = "Single Player";
|
|
|
|
break;
|
|
}
|
|
case GT_Deathmatch:
|
|
gametype = "Deathmatch";
|
|
break;
|
|
case GT_TeamDeathmatch:
|
|
gametype = "Team Deathmatch";
|
|
break;
|
|
case GT_CaptureTheFlag:
|
|
gametype = "Capture The Flag";
|
|
break;
|
|
default:
|
|
gametype = "Unknown";
|
|
}
|
|
|
|
if(QServer[i].Info.PasswordHash.size())
|
|
{
|
|
padlockFn = PadlockIconSurfFn;
|
|
}
|
|
|
|
row = AG_TableAddRow(ServerList, "%[FS]:%s:%u:%s:%s:%s:%s:%s:%s", padlockFn, name.c_str(),
|
|
static_cast<int>(QServer[i].GetPing()),
|
|
plyrCnt.str().c_str(), pwads.c_str(),
|
|
map.c_str(), gametype.c_str(),
|
|
iwad.c_str(), sAddr.c_str());
|
|
|
|
|
|
// Set the cell flags
|
|
SetServerListRowCellFlags(row);
|
|
|
|
// Add to the player total statusbar
|
|
if(QServer[i].Info.Players.size())
|
|
AddToPlayerTotal(QServer[i].Info.Players.size());
|
|
|
|
QServer[i].Unlock();
|
|
}
|
|
|
|
// Restore row selection
|
|
AG_TableEnd(ServerList);
|
|
}
|
|
|
|
void AGOL_MainWindow::OnServerListRowSelected(AG_Event *event)
|
|
{
|
|
int row = AG_INT(2);
|
|
int ndx = GetServerArrayIndexFromListRow(row);
|
|
|
|
UpdatePlayerList(ndx);
|
|
UpdateServInfoList(ndx);
|
|
}
|
|
|
|
void AGOL_MainWindow::SaveWidgetStates(AG_Event *event)
|
|
{
|
|
// Save window dimensions
|
|
GuiConfig::Write("MainWindow-Width", MainWindow->r.w);
|
|
GuiConfig::Write("MainWindow-Height", MainWindow->r.h);
|
|
|
|
// Save server list column sizes
|
|
for(int i = 1; i < ServerList->n; i++)
|
|
{
|
|
ostringstream colOption;
|
|
|
|
colOption << "SrvListColW_" << i - 1;
|
|
GuiConfig::Write(colOption.str(), ServerList->cols[i].w);
|
|
}
|
|
}
|
|
|
|
void AGOL_MainWindow::ExitWindow(AG_Event *event)
|
|
{
|
|
std::cout << "Exit window event! Calling SaveWidgetStates!" << std::endl;
|
|
SaveWidgetStates(NULL);
|
|
|
|
WindowExited = true;
|
|
}
|
|
|
|
//*****************//
|
|
// Query Functions //
|
|
//*****************//
|
|
void *AGOL_MainWindow::GetMasterList(void *arg)
|
|
{
|
|
odalpapi::BufferedSocket socket;
|
|
string address = "";
|
|
size_t serverCount = 0;
|
|
uint16_t port = 0;
|
|
unsigned int masterTimeout;
|
|
|
|
if(GuiConfig::Read("MasterTimeout", masterTimeout) || masterTimeout == 0)
|
|
masterTimeout = 500;
|
|
|
|
// Get the lock
|
|
MServer.GetLock();
|
|
|
|
MServer.SetSocket(&socket);
|
|
|
|
// Get a list of servers
|
|
MServer.QueryMasters(masterTimeout, 1, 2); // TODO: Make broadcast and retry configurable
|
|
|
|
serverCount = MServer.GetServerCount();
|
|
|
|
// If there are no servers something went wrong
|
|
// (either with the query or with the Odamex project ;P)
|
|
if(serverCount == 0)
|
|
{
|
|
MServer.Unlock();
|
|
return NULL;
|
|
}
|
|
|
|
// Update the statusbar with the master ping
|
|
UpdateStatusbarMasterPing(MServer.GetPing());
|
|
|
|
MServer.Unlock();
|
|
|
|
// Allocate the array of server classes
|
|
delete[] QServer;
|
|
QServer = new Server[serverCount];
|
|
|
|
UpdateQueriedLabelTotal(serverCount);
|
|
|
|
// Set the address for each server
|
|
for(size_t i = 0; i < serverCount; i++)
|
|
{
|
|
MServer.GetLock();
|
|
MServer.GetServerAddress(i, address, port);
|
|
MServer.Unlock();
|
|
|
|
QServer[i].GetLock();
|
|
QServer[i].SetAddress(address, port);
|
|
QServer[i].Unlock();
|
|
}
|
|
|
|
// Query all the servers
|
|
QueryAllServers(NULL);
|
|
|
|
StartupQuery = false;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int AGOL_MainWindow::QuerySingleServer(Server *server)
|
|
{
|
|
odalpapi::BufferedSocket socket;
|
|
unsigned int serverTimeout;
|
|
int ret;
|
|
|
|
if(GuiConfig::Read("ServerTimeout", serverTimeout) || serverTimeout == 0)
|
|
serverTimeout = 500;
|
|
|
|
server->GetLock();
|
|
server->SetSocket(&socket);
|
|
ret = server->Query(serverTimeout);
|
|
server->Unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void *AGOL_MainWindow::QueryAllServers(void *arg)
|
|
{
|
|
size_t count = 0;
|
|
size_t serverCount = 0;
|
|
size_t serversQueried = 0;
|
|
int selectedNdx;
|
|
|
|
MServer.GetLock();
|
|
|
|
serverCount = MServer.GetServerCount();
|
|
|
|
MServer.Unlock();
|
|
|
|
// There are no servers to query
|
|
if(serverCount == 0)
|
|
return NULL;
|
|
|
|
StartServerListPoll();
|
|
|
|
ClearList(PlayerList);
|
|
ClearList(ServInfoList);
|
|
UpdateQueriedLabelCompleted(0);
|
|
|
|
while(count < serverCount)
|
|
{
|
|
for(size_t i = 0; i < NUM_THREADS; i++)
|
|
{
|
|
if((!QServerThread.empty()) && ((QServerThread.size() - 1) >= i))
|
|
{
|
|
// If a thread is no longer running delete it
|
|
if(QServerThread[i]->IsRunning())
|
|
continue;
|
|
else
|
|
{
|
|
QServerThread[i]->Join(NULL);
|
|
delete QServerThread[i];
|
|
QServerThread.erase(QServerThread.begin() + i);
|
|
count++;
|
|
}
|
|
}
|
|
if(serversQueried < serverCount)
|
|
{
|
|
// Push a new thread onto the thread vector
|
|
QServerThread.push_back(new ODA_Thread());
|
|
|
|
// Start the thread for a server query
|
|
QServerThread.back()->Create(this, (THREAD_FUNC_PTR)&AGOL_MainWindow::QueryServerThrEntry,
|
|
&QServer[serversQueried]);
|
|
|
|
// Incremement the number of requested server queries
|
|
serversQueried++;
|
|
}
|
|
}
|
|
UpdateQueriedLabelCompleted(static_cast<int>(count));
|
|
}
|
|
|
|
// Stop the server list automatic polling
|
|
StopServerListPoll();
|
|
|
|
// Issue a final update in case polling disabled before the final servers could be updated
|
|
AG_SchedEvent(MainWindow, ServerList, 0, "update-items", NULL);
|
|
|
|
selectedNdx = GetSelectedServerArrayIndex();
|
|
|
|
UpdatePlayerList(selectedNdx);
|
|
UpdateServInfoList(selectedNdx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void *AGOL_MainWindow::QueryServerThrEntry(void *arg)
|
|
{
|
|
if(arg)
|
|
QuerySingleServer((Server*)arg);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool AGOL_MainWindow::CvarCompare(const Cvar_t &a, const Cvar_t &b)
|
|
{
|
|
return a.Name < b.Name;
|
|
}
|
|
|
|
int AGOL_MainWindow::CellCompare(const void *p1, const void *p2)
|
|
{
|
|
AG_TableCell *c1 = (AG_TableCell*)p1;
|
|
AG_TableCell *c2 = (AG_TableCell*)p2;
|
|
|
|
// Make sure the cells are the same type
|
|
if (c1->type != c2->type || strcmp(c1->fmt, c2->fmt) != 0)
|
|
{
|
|
// See if one of the cells is null
|
|
if (c1->type == AG_CELL_NULL || c2->type == AG_CELL_NULL)
|
|
{
|
|
// Sort out the null (unset) cells
|
|
return (c1->type == AG_CELL_NULL ? 1 : -1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
switch(c1->type)
|
|
{
|
|
case AG_CELL_STRING:
|
|
{
|
|
uint32_t c1_plyrtot, c1_plyrmax;
|
|
uint32_t c2_plyrtot, c2_plyrmax;
|
|
char c;
|
|
|
|
istringstream is1(c1->data.s);
|
|
istringstream is2(c2->data.s);
|
|
|
|
// Parse the player total strings ("tot/max")
|
|
is1 >> c1_plyrtot >> c >> c1_plyrmax;
|
|
is2 >> c2_plyrtot >> c >> c2_plyrmax;
|
|
|
|
// Compare player max if player total is equal
|
|
if(c1_plyrtot == c2_plyrtot)
|
|
return c1_plyrmax - c2_plyrmax;
|
|
|
|
return c1_plyrtot - c2_plyrtot;
|
|
}
|
|
case AG_CELL_FN_SU_NODUP:
|
|
{
|
|
int compval[2] = {-1, -1};
|
|
|
|
if(!c1->fnSu)
|
|
return -1;
|
|
|
|
if(!c2->fnSu)
|
|
return 1;
|
|
|
|
if(c1->fnSu == BulletBlueSurfFn ||
|
|
c1->fnSu == SpectatorIconSurfFn ||
|
|
c1->fnSu == PadlockIconSurfFn)
|
|
{
|
|
compval[0] = 0;
|
|
}
|
|
else if(c1->fnSu == BulletRedSurfFn)
|
|
{
|
|
compval[0] = 1;
|
|
}
|
|
|
|
if(c2->fnSu == BulletBlueSurfFn ||
|
|
c2->fnSu == SpectatorIconSurfFn ||
|
|
c2->fnSu == PadlockIconSurfFn)
|
|
{
|
|
compval[1] = 0;
|
|
}
|
|
else if(c2->fnSu == BulletRedSurfFn)
|
|
{
|
|
compval[1] = 1;
|
|
}
|
|
|
|
return compval[0] - compval[1];
|
|
}
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
AG_Surface *AGOL_MainWindow::BulletRedSurfFn(void *tbl, int x, int y)
|
|
{
|
|
return BulletRed.get();
|
|
}
|
|
|
|
AG_Surface *AGOL_MainWindow::BulletBlueSurfFn(void *tbl, int x, int y)
|
|
{
|
|
return BulletBlue.get();
|
|
}
|
|
|
|
AG_Surface *AGOL_MainWindow::SpectatorIconSurfFn(void *tbl, int x, int y)
|
|
{
|
|
return SpectatorIcon.get();
|
|
}
|
|
|
|
AG_Surface *AGOL_MainWindow::PadlockIconSurfFn(void *tbl, int x, int y)
|
|
{
|
|
return PadlockIcon.get();
|
|
}
|
|
|
|
AG_Surface *AGOL_MainWindow::NullSurfFn(void *tbl, int x, int y)
|
|
{
|
|
return NullSurface.get();
|
|
}
|
|
|
|
} // namespace
|