forked from Mirrors/openclonk
1595 lines
57 KiB
C++
1595 lines
57 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2008-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
// player listbox used in lobby and game over dlg
|
|
|
|
#include "C4Include.h"
|
|
#include "gui/C4PlayerInfoListBox.h"
|
|
|
|
#include "control/C4GameControl.h"
|
|
#include "control/C4PlayerInfo.h"
|
|
#include "control/C4RoundResults.h"
|
|
#include "control/C4Teams.h"
|
|
#include "graphics/C4Draw.h"
|
|
#include "graphics/C4GraphicsResource.h"
|
|
#include "gui/C4FileSelDlg.h"
|
|
#include "gui/C4GameLobby.h"
|
|
#include "gui/C4MouseControl.h"
|
|
#include "network/C4Network2.h"
|
|
#include "network/C4Network2Dialogs.h"
|
|
|
|
DWORD GenerateRandomPlayerColor(int32_t iTry); // in C4PlayerInfoConflicts.cpp
|
|
|
|
// ----------- ListItem --------------------------------------------------------------------------------
|
|
|
|
// helper
|
|
C4GameLobby::MainDlg *C4PlayerInfoListBox::ListItem::GetLobby() const
|
|
{
|
|
return ::Network.GetLobby();
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::ListItem::CanLocalChooseTeams(int32_t idPlayer) const
|
|
{
|
|
// whether the local client can change any teams
|
|
// only if teams are available
|
|
if (!Game.Teams.IsMultiTeams()) return false;
|
|
// only if global change allowed
|
|
C4GameLobby::MainDlg *pLobby = GetLobby();
|
|
if (!pLobby) return false;
|
|
if (pLobby->IsCountdown()) return false;
|
|
// only for unjoined players
|
|
if (idPlayer)
|
|
{
|
|
C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idPlayer);
|
|
if (pInfo && pInfo->HasJoined()) return false;
|
|
}
|
|
// finally, only if team settings permit
|
|
return Game.Teams.CanLocalChooseTeam(idPlayer);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ListItem::DrawElement(C4TargetFacet &cgo)
|
|
{
|
|
if (dwBackground) pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y, cgo.TargetX+rcBounds.x+rcBounds.Wdt-1, cgo.TargetY+rcBounds.y+rcBounds.Hgt-1, dwBackground);
|
|
typedef C4GUI::Window BaseClass;
|
|
BaseClass::DrawElement(cgo);
|
|
}
|
|
|
|
// ----------- PlayerListItem -----------------------------------------------------------------------
|
|
|
|
C4PlayerInfoListBox::PlayerListItem::PlayerListItem(C4PlayerInfoListBox *pForListBox, int32_t idClient,
|
|
int32_t idPlayer, bool fSavegamePlayer, C4GUI::Element *pInsertBeforeElement)
|
|
: ListItem(pForListBox), pScoreLabel(nullptr), pTimeLabel(nullptr), pExtraLabel(nullptr),
|
|
pRankIcon(nullptr), pTeamCombo(nullptr), pTeamPic(nullptr), fIconSet(false), fJoinedInfoSet(false),
|
|
dwJoinClr(0), dwPlrClr(0), idClient(idClient), idPlayer(idPlayer), fFreeSavegamePlayer(fSavegamePlayer)
|
|
{
|
|
bool fIsEvaluation = pForListBox->IsEvaluation(), fIsLobby = pForListBox->IsLobby();
|
|
C4PlayerInfo *pInfo = GetPlayerInfo(); assert(pInfo);
|
|
uint32_t dwTextColor = pForListBox->GetTextColor();
|
|
CStdFont *pCustomFont = pForListBox->GetCustomFont();
|
|
uint32_t dwPlayerColor;
|
|
if (fIsEvaluation)
|
|
dwPlayerColor = dwTextColor;
|
|
else
|
|
dwPlayerColor = pInfo->GetLobbyColor() | C4GUI_MessageFontAlpha;
|
|
// league account name? Overwrite the shown name
|
|
StdStrBuf sPlayerName(pInfo->GetLobbyName());
|
|
// calc height
|
|
int32_t iHeight = ::GraphicsResource.TextFont.GetLineHeight() + C4GUI::ComboBox::GetDefaultHeight() + 3 * IconLabelSpacing;
|
|
// create subcomponents
|
|
pIcon = new C4GUI::Icon(C4Rect(0, 0, iHeight, iHeight), C4GUI::Ico_UnknownPlayer);
|
|
if (Game.Parameters.isLeague())
|
|
pRankIcon = new C4GUI::Icon(C4Rect(0, 0, C4GUI::ComboBox::GetDefaultHeight(), C4GUI::ComboBox::GetDefaultHeight()), C4GUI::Ico_None);
|
|
if (Game.Teams.IsMultiTeams() && !(fIsEvaluation && pList->IsTeamFilter()))
|
|
{
|
|
// will be moved when the item is added to the list, and the position moved
|
|
pTeamCombo = new C4GUI::ComboBox(C4Rect(0,0,10,10));
|
|
pTeamCombo->SetComboCB(new C4GUI::ComboBox_FillCallback<PlayerListItem>(this, &PlayerListItem::OnTeamComboFill, &PlayerListItem::OnTeamComboSelChange));
|
|
pTeamCombo->SetSimple(true);
|
|
if (pCustomFont)
|
|
{
|
|
pTeamCombo->SetFont(pCustomFont);
|
|
pTeamCombo->SetColors(dwTextColor, C4GUI_StandardBGColor, 0);
|
|
}
|
|
UpdateTeam();
|
|
}
|
|
// Evaluation
|
|
if (fIsEvaluation)
|
|
{
|
|
// Team image if known and if not placed on top of box anyway
|
|
if (!pList->IsTeamFilter())
|
|
{
|
|
C4Team *pTeam = Game.Teams.GetTeamByID(pInfo->GetTeam());
|
|
if (pTeam && pTeam->GetIconSpec() && *pTeam->GetIconSpec())
|
|
{
|
|
pTeamPic = new C4GUI::Picture(C4Rect(iHeight + IconLabelSpacing, 0, iHeight, iHeight), true);
|
|
Game.DrawTextSpecImage(pTeamPic->GetMFacet(), pTeam->GetIconSpec(), nullptr, pTeam->GetColor());
|
|
pTeamPic->SetDrawColor(pTeam->GetColor());
|
|
}
|
|
}
|
|
// Total playing time (not in team filter because then the second line is taken by the score label)
|
|
if (!pList->IsTeamFilter())
|
|
{
|
|
StdStrBuf sTimeLabelText;
|
|
C4RoundResultsPlayer *pRoundResultsPlr = Game.RoundResults.GetPlayers().GetByID(idPlayer);
|
|
uint32_t iTimeTotal = pRoundResultsPlr ? pRoundResultsPlr->GetTotalPlayingTime() : 0 /* unknown - should not happen */;
|
|
sTimeLabelText.Format(LoadResStr("IDS_CTL_TOTALPLAYINGTIME"), iTimeTotal/3600, (iTimeTotal/60)%60, iTimeTotal%60);
|
|
pTimeLabel = new C4GUI::Label(sTimeLabelText.getData(), 0, 0, ARight, dwTextColor, pForListBox->GetCustomFont(), false, true);
|
|
}
|
|
// Extra info set by script
|
|
C4RoundResultsPlayer *pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(idPlayer);;
|
|
if (pEvaluationPlayer)
|
|
{
|
|
const char *szCustomEval = pEvaluationPlayer->GetCustomEvaluationStrings();
|
|
if (szCustomEval && *szCustomEval)
|
|
{
|
|
pExtraLabel = new C4GUI::Label(szCustomEval, 0,0, ARight); // positioned later
|
|
iHeight += ::GraphicsResource.TextFont.GetLineHeight();
|
|
}
|
|
}
|
|
}
|
|
pNameLabel = new C4GUI::Label(sPlayerName.getData(), (iHeight + IconLabelSpacing) * (1+!!pTeamPic), IconLabelSpacing, ALeft, dwPlayerColor, pCustomFont, !fIsEvaluation, true);
|
|
// calc own bounds - list box needs height only; width and pos will be moved by list
|
|
SetBounds(C4Rect(0,0,10,iHeight));
|
|
// add components
|
|
AddElement(pIcon); AddElement(pNameLabel);
|
|
if (pTeamPic) AddElement(pTeamPic);
|
|
if (pTimeLabel) AddElement(pTimeLabel);
|
|
if (pTeamCombo) AddElement(pTeamCombo);
|
|
if (pRankIcon) AddElement(pRankIcon);
|
|
if (pExtraLabel) AddElement(pExtraLabel);
|
|
// add to listbox (will get resized horizontally and moved)
|
|
pForListBox->InsertElement(this, pInsertBeforeElement, PlayerListBoxIndent);
|
|
// league score update
|
|
UpdateScoreLabel(pInfo);
|
|
// set ID
|
|
if (fFreeSavegamePlayer)
|
|
idListItemID.idType = ListItem::ID::PLI_SAVEGAMEPLR;
|
|
else
|
|
idListItemID.idType = ListItem::ID::PLI_PLAYER;
|
|
idListItemID.id = idPlayer;
|
|
UpdateIcon(pInfo, GetJoinedInfo());
|
|
// context menu for list item
|
|
if (fIsLobby) SetContextHandler(new C4GUI::CBContextHandler<PlayerListItem>(this, &PlayerListItem::OnContext));
|
|
// update collapsed/not collapsed
|
|
fShownCollapsed = false;
|
|
UpdateCollapsed();
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::UpdateOwnPos()
|
|
{
|
|
// parent for client rect
|
|
typedef C4GUI::Window ParentClass;
|
|
ParentClass::UpdateOwnPos();
|
|
C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing);
|
|
// subtract icon(s)
|
|
caBounds.GetFromLeft(pIcon->GetBounds().Wdt);
|
|
if (pTeamPic) caBounds.GetFromLeft(pTeamPic->GetBounds().Wdt - IconLabelSpacing);
|
|
C4Rect rcExtraDataRect;
|
|
// extra data label area
|
|
if (pExtraLabel) rcExtraDataRect = caBounds.GetFromBottom(::GraphicsResource.TextFont.GetLineHeight());
|
|
// second line (team+rank)
|
|
C4GUI::ComponentAligner caTeamArea(caBounds.GetFromBottom(C4GUI::ComboBox::GetDefaultHeight()), 0,0);
|
|
C4Rect rcRankIcon;
|
|
if (pList->IsEvaluation())
|
|
{
|
|
if (pRankIcon)
|
|
{
|
|
rcRankIcon = caBounds.GetFromRight(caBounds.GetInnerHeight());
|
|
if (pExtraLabel) rcExtraDataRect.Wdt = caBounds.GetInnerWidth(); // In evaluation view, rank icon has its own coloumn
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pRankIcon) rcRankIcon = caTeamArea.GetFromRight(caTeamArea.GetInnerHeight());
|
|
}
|
|
C4Rect rcTeam = caTeamArea.GetAll();
|
|
// item to positions: team combo box
|
|
if (pTeamCombo)
|
|
{
|
|
pTeamCombo->SetBounds(rcTeam);
|
|
}
|
|
// rank icon
|
|
if (pRankIcon)
|
|
{
|
|
pRankIcon->SetBounds(rcRankIcon);
|
|
}
|
|
// time label
|
|
if (pTimeLabel)
|
|
{
|
|
C4Rect rcUpperBounds = caBounds.GetAll();
|
|
pTimeLabel->SetBounds(rcTeam);
|
|
pTimeLabel->SetX0(rcUpperBounds.x + rcUpperBounds.Wdt);
|
|
}
|
|
// extra label
|
|
if (pExtraLabel) pExtraLabel->SetBounds(rcExtraDataRect);
|
|
}
|
|
|
|
int32_t C4PlayerInfoListBox::PlayerListItem::GetListItemTopSpacing()
|
|
{
|
|
int32_t iSpacing = C4GUI_DefaultListSpacing;
|
|
// evaluation: Add some extra spacing between players of different teams
|
|
if (pList->IsEvaluation())
|
|
{
|
|
C4GUI::Element *pPrevItem = GetPrev();
|
|
if (pPrevItem)
|
|
{
|
|
C4PlayerInfoListBox::ListItem *pPrevListItem = static_cast<C4PlayerInfoListBox::ListItem *>(pPrevItem);
|
|
if (pPrevListItem->idListItemID.idType == ListItem::ID::PLI_PLAYER)
|
|
{
|
|
PlayerListItem *pPrevPlayerListItem = static_cast<C4PlayerInfoListBox::PlayerListItem *>(pPrevListItem);
|
|
C4PlayerInfo *pThisInfo = GetPlayerInfo();
|
|
C4PlayerInfo *pPrevInfo = pPrevPlayerListItem->GetPlayerInfo();
|
|
if (pThisInfo && pPrevInfo)
|
|
{
|
|
if (pPrevInfo->GetTeam() != pThisInfo->GetTeam())
|
|
{
|
|
iSpacing += 10;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return iSpacing;
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::UpdateIcon(C4PlayerInfo *pInfo, C4PlayerInfo *pJoinedInfo)
|
|
{
|
|
// check whether icon is known
|
|
bool fResPresent = false;
|
|
C4Network2Res *pRes = nullptr;
|
|
if (pInfo)
|
|
if ((pRes = pInfo->GetRes()))
|
|
fResPresent = pRes->isComplete();
|
|
C4RoundResultsPlayer *pEvaluationPlayer = nullptr;
|
|
if (pList->IsEvaluation()) pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(idPlayer);
|
|
bool fHasIcon = fResPresent || pEvaluationPlayer || (!::Network.isEnabled() && pInfo);
|
|
// check whether joined info is present
|
|
bool fHasJoinedInfo = !!pJoinedInfo;
|
|
DWORD dwJoinedInfoClr = pJoinedInfo ? pJoinedInfo->GetLobbyColor() : 0;
|
|
DWORD dwPlayerClr = pInfo ? pInfo->GetLobbyColor() : 0;
|
|
// already up-to-date?
|
|
if (fHasIcon == fIconSet && fJoinedInfoSet == fHasJoinedInfo && dwJoinedInfoClr == dwJoinClr && dwPlayerClr == dwPlrClr) return;
|
|
// update then
|
|
// redraw player icon
|
|
if (fHasIcon)
|
|
{
|
|
// custom icon?
|
|
if (pEvaluationPlayer && pEvaluationPlayer->GetBigIcon().Surface)
|
|
{
|
|
pIcon->SetFacet(pEvaluationPlayer->GetBigIcon());
|
|
fIconSet = true;
|
|
}
|
|
else
|
|
fIconSet = pInfo->LoadBigIcon(pIcon->GetMFacet());
|
|
if (!fIconSet)
|
|
{
|
|
// no custom icon: create default by player color
|
|
pIcon->GetMFacet().Create(64,64); // the bigicon is bigger than the normal 40x40 icon
|
|
::GraphicsResource.fctPlayerClr.DrawClr(pIcon->GetMFacet(), true, dwPlayerClr);
|
|
}
|
|
fIconSet = true;
|
|
}
|
|
else
|
|
// no player info known - either res not retrieved yet or script player
|
|
pIcon->SetIcon((pInfo && pInfo->GetType() == C4PT_Script) ? C4GUI::Ico_Host : C4GUI::Ico_UnknownPlayer);
|
|
// join
|
|
if (fHasJoinedInfo)
|
|
{
|
|
// make sure we're not drawing on GraphicsResource
|
|
if (!pIcon->EnsureOwnSurface()) return;
|
|
// draw join info
|
|
C4Facet fctDraw = pIcon->GetFacet();
|
|
int32_t iSizeMax = std::max<int32_t>(fctDraw.Wdt, fctDraw.Hgt);
|
|
int32_t iCrewClrHgt = iSizeMax/2;
|
|
fctDraw.Hgt -= iCrewClrHgt; fctDraw.Y += iCrewClrHgt;
|
|
fctDraw.Wdt = iSizeMax/2;
|
|
fctDraw.X = 2;
|
|
// shadow
|
|
DWORD dwPrevMod; bool fPrevMod = pDraw->GetBlitModulation(dwPrevMod);
|
|
pDraw->ActivateBlitModulation(1);
|
|
::GraphicsResource.fctCrewClr.DrawClr(fctDraw, true, dwJoinedInfoClr);
|
|
if (fPrevMod) pDraw->ActivateBlitModulation(dwPrevMod); else pDraw->DeactivateBlitModulation();
|
|
fctDraw.X = 0;
|
|
// gfx
|
|
::GraphicsResource.fctCrewClr.DrawClr(fctDraw, true, dwJoinedInfoClr);
|
|
}
|
|
fJoinedInfoSet = fHasJoinedInfo;
|
|
dwJoinClr = dwJoinedInfoClr;
|
|
dwPlrClr = dwPlayerClr;
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::UpdateTeam()
|
|
{
|
|
if (!pTeamCombo) return; // unassigned for no teams
|
|
const char *szTeamName = ""; bool fReadOnly = true;
|
|
fReadOnly = !CanLocalChooseTeam();
|
|
int32_t idTeam; C4Team *pTeam;
|
|
C4PlayerInfo *pInfo = GetPlayerInfo();
|
|
if (!Game.Teams.CanLocalSeeTeam())
|
|
szTeamName = LoadResStr("IDS_MSG_RNDTEAM");
|
|
else if (pInfo)
|
|
if ((idTeam = pInfo->GetTeam()))
|
|
if ((pTeam = Game.Teams.GetTeamByID(idTeam)))
|
|
szTeamName = pTeam->GetName();
|
|
pTeamCombo->SetText(szTeamName);
|
|
pTeamCombo->SetReadOnly(fReadOnly);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::UpdateScoreLabel(C4PlayerInfo *pInfo)
|
|
{
|
|
assert(pInfo);
|
|
C4RoundResultsPlayer *pRoundResultsPlr = nullptr;
|
|
if (pList->IsEvaluation()) pRoundResultsPlr = Game.RoundResults.GetPlayers().GetByID(idPlayer);
|
|
|
|
if (pInfo->getLeagueScore() || pInfo->IsLeagueProjectedGainValid() || pRoundResultsPlr)
|
|
{
|
|
int32_t iScoreRightPos = ((pRankIcon && pList->IsEvaluation()) ? pRankIcon->GetBounds().x : GetBounds().Wdt) - IconLabelSpacing;
|
|
int32_t iScoreYPos = IconLabelSpacing;
|
|
// if evaluation and team lists, move score label into second line - TODO: some hack only, still needs to be done right
|
|
C4RoundResultsPlayer *pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(pInfo->GetID());;
|
|
bool fPlayerHasEvaluationData=false;
|
|
if (pEvaluationPlayer)
|
|
{
|
|
const char *szCustomEval = pEvaluationPlayer->GetCustomEvaluationStrings();
|
|
if (szCustomEval && *szCustomEval)
|
|
fPlayerHasEvaluationData=true;
|
|
}
|
|
if (pList->IsEvaluation() && pList->IsTeamFilter())
|
|
iScoreYPos = GetBounds().Hgt - (C4GUI::ComboBox::GetDefaultHeight()*(1+(int32_t)fPlayerHasEvaluationData)) - IconLabelSpacing;
|
|
|
|
// score label visible
|
|
if (!pScoreLabel)
|
|
{
|
|
AddElement(pScoreLabel = new C4GUI::Label("", iScoreRightPos, iScoreYPos, ARight, pList->GetTextColor(), pList->GetCustomFont(), false));
|
|
if (pList->IsEvaluation())
|
|
pScoreLabel->SetToolTip(LoadResStr("IDS_DESC_OLDANDNEWSCORE"));
|
|
else
|
|
pScoreLabel->SetToolTip(LoadResStr("IDS_DESC_LEAGUESCOREANDPROJECTEDGA"));
|
|
}
|
|
StdStrBuf sText;
|
|
// Evaluation (GameOver)
|
|
if (pList->IsEvaluation())
|
|
{
|
|
if (pInfo->getLeagueScore() || pInfo->IsLeagueProjectedGainValid() || (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid()))
|
|
{
|
|
if (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid())
|
|
{
|
|
// Show old league score, gain, and new league score
|
|
// Normally, the league server should make sure that [Old score] + [Gain] == [New score]
|
|
int32_t iOldScore = pInfo->getLeagueScore(), iScoreGain = pRoundResultsPlr->GetLeagueScoreGain(), iNewScore = pRoundResultsPlr->GetLeagueScoreNew();
|
|
int32_t iDiscrepancy = iNewScore - (iOldScore + iScoreGain);
|
|
if (!iDiscrepancy)
|
|
{
|
|
sText.Format("{{Ico:League}}<c afafaf>%d (%+d)</c> %d %s", (int)iOldScore, (int)iScoreGain, (int)iNewScore, LoadResStr("IDS_TEXT_SCORE"));
|
|
}
|
|
else
|
|
{
|
|
// If there's a discrepancy, there must have been some kind of admin intervention during the game - display it in red!
|
|
sText.Format("{{Ico:League}}<c afafaf>%d (%+d)</c><c ff0000>(%+d)</c> %d %s", (int)iOldScore, (int)iScoreGain, (int)iDiscrepancy, (int)iNewScore, LoadResStr("IDS_TEXT_SCORE"));
|
|
}
|
|
}
|
|
// Show old league score only
|
|
else
|
|
{
|
|
sText.Format("{{Ico:League}}<c afafaf>(%d)</c> %s", (int)pInfo->getLeagueScore(), LoadResStr("IDS_TEXT_SCORE"));
|
|
}
|
|
}
|
|
else if (pRoundResultsPlr && pRoundResultsPlr->IsScoreNewValid() && !Game.RoundResults.SettlementScoreIsHidden())
|
|
{
|
|
// new score known
|
|
sText.Format("{{Ico:Settlement}}<c afafaf>%d (%+d)</c> %d %s", (int)pRoundResultsPlr->GetScoreOld(), (int)(pRoundResultsPlr->GetScoreNew()-pRoundResultsPlr->GetScoreOld()), (int)pRoundResultsPlr->GetScoreNew(), LoadResStr("IDS_TEXT_SCORE"));
|
|
}
|
|
else if (pRoundResultsPlr && !pRoundResultsPlr->IsScoreNewValid() && !Game.RoundResults.SettlementScoreIsHidden())
|
|
{
|
|
// only old score known (e.g., player disconnected)
|
|
sText.Format("{{Ico:Settlement}}<c afafaf>(%d)</c> %s", (int)pRoundResultsPlr->GetScoreOld(), LoadResStr("IDS_TEXT_SCORE"));
|
|
}
|
|
else
|
|
{
|
|
// nothing known. Shouldn't really happen.
|
|
sText.Ref("");
|
|
}
|
|
}
|
|
// Pre-evaluation (Lobby)
|
|
else
|
|
{
|
|
// Show current league score and projected gain
|
|
// Don't show if team invisible, so random surprise teams don't get spoiled
|
|
if (pInfo->IsLeagueProjectedGainValid() && Game.Teams.IsTeamVisible())
|
|
sText.Format("%d (%+d)", (int)pInfo->getLeagueScore(), (int)pInfo->GetLeagueProjectedGain());
|
|
// Show current league score only
|
|
else
|
|
sText.Format("%d", (int)pInfo->getLeagueScore());
|
|
}
|
|
pScoreLabel->SetX0(iScoreRightPos);
|
|
pScoreLabel->SetText(sText.getData(), false);
|
|
}
|
|
else if (pScoreLabel)
|
|
{
|
|
// score label invisible
|
|
delete pScoreLabel;
|
|
pScoreLabel = nullptr;
|
|
}
|
|
if (pRankIcon)
|
|
{
|
|
int32_t iSym = 0;
|
|
if (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid())
|
|
iSym = pRoundResultsPlr->GetLeagueRankSymbolNew();
|
|
if (!iSym)
|
|
iSym = pInfo->getLeagueRankSymbol();
|
|
if (iSym && !fShownCollapsed)
|
|
{
|
|
C4GUI::Icons eRankIcon = (C4GUI::Icons) (C4GUI::Ico_Rank1 + Clamp<int32_t>(iSym-1, 0, C4GUI::Ico_Rank9-C4GUI::Ico_Rank1));
|
|
pRankIcon->SetVisibility(true);
|
|
pRankIcon->SetIcon(eRankIcon);
|
|
}
|
|
else
|
|
{
|
|
pRankIcon->SetVisibility(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::UpdateCollapsed()
|
|
{
|
|
bool fShouldBeCollapsed = pList->IsPlayerItemCollapsed(this);
|
|
if (fShouldBeCollapsed == fShownCollapsed) return;
|
|
// so update collapsed state
|
|
int32_t iHeight; int32_t iNameLblX0;
|
|
if ((fShownCollapsed = fShouldBeCollapsed))
|
|
{
|
|
// calc height
|
|
iHeight = ::GraphicsResource.TextFont.GetLineHeight() + 2 * IconLabelSpacing;
|
|
// teamcombo not visible if collapsed
|
|
if (pTeamCombo) pTeamCombo->SetVisibility(false);
|
|
}
|
|
else
|
|
{
|
|
// calc height
|
|
iHeight = ::GraphicsResource.TextFont.GetLineHeight() + C4GUI::ComboBox::GetDefaultHeight() + 3 * IconLabelSpacing;
|
|
// teamcombo visible if not collapsed
|
|
if (pTeamCombo) pTeamCombo->SetVisibility(true);
|
|
}
|
|
// update subcomponents
|
|
iNameLblX0 = iHeight + IconLabelSpacing;
|
|
pIcon->GetBounds() = C4Rect(0, 0, iHeight, iHeight);
|
|
pIcon->UpdateOwnPos();
|
|
pNameLabel->SetX0(iNameLblX0);
|
|
// calc own bounds - use icon bounds only, because only the height is used when the item is added
|
|
SetBounds(pIcon->GetBounds());
|
|
// update positions
|
|
pList->UpdateElementPosition(this, PlayerListBoxIndent);
|
|
}
|
|
|
|
|
|
C4GUI::ContextMenu *C4PlayerInfoListBox::PlayerListItem::OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
|
|
{
|
|
C4PlayerInfo *pInfo = GetPlayerInfo();
|
|
assert(pInfo);
|
|
// no context menu for evaluation
|
|
if (!GetLobby()) return nullptr;
|
|
// create context menu
|
|
C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
|
|
// if this is a free player, add an option to take it over
|
|
if (fFreeSavegamePlayer)
|
|
{
|
|
if (pInfo->GetType() != C4PT_Script)
|
|
{
|
|
StdCopyStrBuf strTakeOver(LoadResStr("IDS_MSG_TAKEOVERPLR"));
|
|
pMenu->AddItem(strTakeOver.getData(), LoadResStr("IDS_MSG_TAKEOVERPLR_DESC"), C4GUI::Ico_Player, nullptr,
|
|
new C4GUI::CBContextHandler<PlayerListItem>(this, &PlayerListItem::OnContextTakeOver));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// owned players or host can manipulate players
|
|
if (::Network.isHost() || IsLocalClientPlayer())
|
|
{
|
|
// player removal (except for joined script players)
|
|
if (pInfo->GetType() != C4PT_Script || !pInfo->GetAssociatedSavegamePlayerID())
|
|
{
|
|
StdCopyStrBuf strRemove(LoadResStr("IDS_MSG_REMOVEPLR"));
|
|
pMenu->AddItem(strRemove.getData(), LoadResStr("IDS_MSG_REMOVEPLR_DESC"), C4GUI::Ico_Close,
|
|
new C4GUI::CBMenuHandler<PlayerListItem>(this, &PlayerListItem::OnCtxRemove), nullptr);
|
|
}
|
|
// color was changed: Add option to assign a new color
|
|
C4PlayerInfo *pInfo = GetPlayerInfo();
|
|
assert (pInfo);
|
|
if (pInfo && pInfo->HasAutoGeneratedColor() && (!Game.Teams.IsTeamColors() || !pInfo->GetTeam()))
|
|
{
|
|
StdCopyStrBuf strNewColor(LoadResStr("IDS_MSG_NEWPLRCOLOR"));
|
|
pMenu->AddItem(strNewColor.getData(), LoadResStr("IDS_MSG_NEWPLRCOLOR_DESC"), C4GUI::Ico_Player,
|
|
new C4GUI::CBMenuHandler<PlayerListItem>(this, &PlayerListItem::OnCtxNewColor), nullptr);
|
|
}
|
|
}
|
|
}
|
|
// open it
|
|
return pMenu;
|
|
}
|
|
|
|
C4GUI::ContextMenu *C4PlayerInfoListBox::PlayerListItem::OnContextTakeOver(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
|
|
{
|
|
// create context menu
|
|
C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
|
|
// add options for all own, unassigned players
|
|
C4ClientPlayerInfos *pkInfo = ::Network.Players.GetLocalPlayerInfoPacket();
|
|
if (pkInfo)
|
|
{
|
|
int32_t i=0; C4PlayerInfo *pInfo;
|
|
while ((pInfo = pkInfo->GetPlayerInfo(i++)))
|
|
if (!pInfo->HasJoinIssued())
|
|
if (!pInfo->GetAssociatedSavegamePlayerID())
|
|
{
|
|
pMenu->AddItem(FormatString(LoadResStr("IDS_MSG_USINGPLR"), pInfo->GetName()).getData(),
|
|
LoadResStr("IDS_MSG_USINGPLR_DESC"), C4GUI::Ico_Player,
|
|
new C4GUI::CBMenuHandlerEx<PlayerListItem, int32_t>(this, &PlayerListItem::OnCtxTakeOver, pInfo->GetID()));
|
|
}
|
|
}
|
|
// add option to use a new one... TODO
|
|
// add option to take over from savegame player TODO
|
|
// open it
|
|
return pMenu;
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::OnCtxTakeOver(C4GUI::Element *pListItem, const int32_t &idPlayer)
|
|
{
|
|
// use player idPlayer to take over this one
|
|
// this must be processed as a request by the host
|
|
// some safety first...
|
|
C4ClientPlayerInfos *pLocalInfo = ::Network.Players.GetLocalPlayerInfoPacket();
|
|
if (!fFreeSavegamePlayer || !idPlayer || !pLocalInfo) return;
|
|
C4ClientPlayerInfos LocalInfoRequest(*pLocalInfo);
|
|
C4PlayerInfo *pGrabbingInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
|
|
if (!pGrabbingInfo) return;
|
|
// now adjust info packet
|
|
pGrabbingInfo->SetAssociatedSavegamePlayer(this->idPlayer);
|
|
// and request this update (host processes it directly)
|
|
::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::OnCtxRemove(C4GUI::Element *pListItem)
|
|
{
|
|
// only host or own player
|
|
if (!::Network.isEnabled() || (!::Network.isHost() && !IsLocalClientPlayer())) return;
|
|
// remove the player
|
|
// this must be processed as a request by the host
|
|
// now change it in its own request packet
|
|
C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
|
|
if (!pChangeInfo || !idPlayer) return;
|
|
C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
|
|
if (!LocalInfoRequest.GetPlayerInfoByID(idPlayer)) return;
|
|
LocalInfoRequest.RemoveInfo(idPlayer);
|
|
// and request this update (host processes it directly)
|
|
::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::OnCtxNewColor(C4GUI::Element *pListItem)
|
|
{
|
|
// only host or own player
|
|
if (!::Network.isEnabled() || (!::Network.isHost() && !IsLocalClientPlayer())) return;
|
|
// just send a request to reclaim the original color to the host
|
|
// the host will deny this and decide on a new color
|
|
C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
|
|
if (!pChangeInfo || !idPlayer) return;
|
|
C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
|
|
C4PlayerInfo *pPlrInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
|
|
if (!pPlrInfo) return;
|
|
pPlrInfo->SetColor(pPlrInfo->GetOriginalColor());
|
|
// and request this update (host processes it directly)
|
|
::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::OnTeamComboFill(C4GUI::ComboBox_FillCB *pFiller)
|
|
{
|
|
// add all possible teams
|
|
C4Team *pTeam; int32_t i=0;
|
|
while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
|
|
if (!pTeam->IsFull() || GetPlayerInfo()->GetTeam() == pTeam->GetID())
|
|
pFiller->AddEntry(pTeam->GetName(), pTeam->GetID());
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::PlayerListItem::OnTeamComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection)
|
|
{
|
|
// always return true to mark combo sel as processed, so the GUI won't change the team text
|
|
// get new team id by name
|
|
C4Team *pNewTeam = Game.Teams.GetTeamByID(idNewSelection);
|
|
// some safety first...
|
|
if (!CanLocalChooseTeam() || !pNewTeam) return true;
|
|
C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
|
|
if (!pChangeInfo || !idPlayer) return true;
|
|
// this must be processed as a request by the host
|
|
// now change it in its own request packet
|
|
C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
|
|
C4PlayerInfo *pChangedInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
|
|
if (!pChangedInfo) return true;
|
|
pChangedInfo->SetTeam(pNewTeam->GetID());
|
|
// and request this update (host processes it directly)
|
|
::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
|
|
// next update will change the combo box text
|
|
return true;
|
|
}
|
|
|
|
void C4PlayerInfoListBox::PlayerListItem::Update()
|
|
{
|
|
UpdateCollapsed();
|
|
UpdateIcon(GetPlayerInfo(), GetJoinedInfo());
|
|
UpdateTeam();
|
|
C4PlayerInfo *pNfo = GetPlayerInfo();
|
|
if (pNfo)
|
|
{
|
|
UpdateScoreLabel(pNfo);
|
|
// update name + color
|
|
StdStrBuf sShowName(pNfo->GetLobbyName());
|
|
if (pList->IsEvaluation())
|
|
{
|
|
bool fShowWinners = (pList->GetMode() != PILBM_EvaluationNoWinners);
|
|
bool fHasWon = fShowWinners && pNfo->HasTeamWon();
|
|
// Append "winner" or "loser" to player name
|
|
if (fShowWinners)
|
|
{
|
|
sShowName.Take(FormatString("%s (%s)", sShowName.getData(), LoadResStr(fHasWon ? "IDS_CTL_WON" : "IDS_CTL_LOST")));
|
|
}
|
|
// evaluation: Golden color+background for winners; gray for losers or no winner show
|
|
if (fHasWon)
|
|
{
|
|
pNameLabel->SetColor(C4GUI_WinningTextColor, false);
|
|
dwBackground = C4GUI_WinningBackgroundColor;
|
|
}
|
|
else
|
|
{
|
|
pNameLabel->SetColor(C4GUI_LosingTextColor, false);
|
|
dwBackground = C4GUI_LosingBackgroundColor;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// lobby: Label color by player color
|
|
pNameLabel->SetColor(pNfo->GetLobbyColor());
|
|
}
|
|
pNameLabel->SetText(sShowName.getData(), false);
|
|
}
|
|
}
|
|
|
|
C4PlayerInfo *C4PlayerInfoListBox::PlayerListItem::GetPlayerInfo() const
|
|
{
|
|
return fFreeSavegamePlayer ? Game.RestorePlayerInfos.GetPlayerInfoByID(idPlayer) : Game.PlayerInfos.GetPlayerInfoByID(idPlayer);
|
|
}
|
|
|
|
C4PlayerInfo *C4PlayerInfoListBox::PlayerListItem::GetJoinedInfo() const
|
|
{
|
|
// safety
|
|
C4PlayerInfo *pInfo = GetPlayerInfo();
|
|
if (!pInfo) return nullptr;
|
|
// is it a joined savegame player?
|
|
if (fFreeSavegamePlayer)
|
|
// then this is the joined player
|
|
return pInfo;
|
|
// otherwise, does it have a savegame association?
|
|
int32_t idSavegameInfo;
|
|
if ((idSavegameInfo = pInfo->GetAssociatedSavegamePlayerID()))
|
|
// then return the respective info from savegame recreation list
|
|
return Game.RestorePlayerInfos.GetPlayerInfoByID(idSavegameInfo);
|
|
// not joined
|
|
return nullptr;
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::PlayerListItem::CanLocalChooseTeam() const
|
|
{
|
|
// never on savegame players
|
|
if (fFreeSavegamePlayer || GetJoinedInfo()) return false;
|
|
// only host or own player
|
|
if (!::Network.isHost() && !IsLocalClientPlayer()) return false;
|
|
// finally, only if team settings permit
|
|
return CanLocalChooseTeams(idPlayer);
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::PlayerListItem::IsLocalClientPlayer() const
|
|
{
|
|
// check whether client is local
|
|
// if no client can be found, assume network disconnect and everythign local then
|
|
C4Network2Client *pClient = GetNetClient();
|
|
return !pClient || pClient->isLocal();
|
|
}
|
|
|
|
C4Network2Client *C4PlayerInfoListBox::PlayerListItem::GetNetClient() const
|
|
{
|
|
return ::Network.Clients.GetClientByID(idClient);
|
|
}
|
|
|
|
|
|
// ----------- ClientListItem -----------------------------------------------------------------
|
|
|
|
C4PlayerInfoListBox::ClientListItem::ClientListItem(C4PlayerInfoListBox *pForListBox, const C4ClientCore &rClientInfo, ListItem *pInsertBefore) // ctor
|
|
: ListItem(pForListBox), idClient(rClientInfo.getID()), dwClientClr(0xffffff), tLastSoundTime(0)
|
|
{
|
|
// set current active-flag (not really needed until player info is retrieved)
|
|
fIsShownActive = rClientInfo.isActivated();
|
|
// set ID
|
|
idListItemID.idType = ListItem::ID::PLI_CLIENT;
|
|
idListItemID.id = idClient;
|
|
// get height
|
|
int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
|
|
// create subcomponents
|
|
pStatusIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), GetCurrentStatusIcon());
|
|
pNameLabel = new C4GUI::Label(rClientInfo.getName(), iIconSize + IconLabelSpacing,0, ALeft, dwClientClr | C4GUI_MessageFontAlpha, nullptr, true, false);
|
|
pPingLabel = nullptr;
|
|
C4GUI::CallbackButton<ClientListItem, C4GUI::IconButton> *btnAddPlayer = nullptr;
|
|
if (IsLocalClientPlayer())
|
|
{
|
|
// this computer: add player button
|
|
btnAddPlayer = new C4GUI::CallbackButton<ClientListItem, C4GUI::IconButton>(C4GUI::Ico_AddPlr, C4Rect(0, 0, iIconSize, iIconSize), 'P' /* 2do TODO */, &ClientListItem::OnBtnAddPlr, this);
|
|
}
|
|
// calc own bounds
|
|
C4Rect rcOwnBounds = pNameLabel->GetBounds();
|
|
rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
|
|
rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
|
|
SetBounds(rcOwnBounds);
|
|
// add components
|
|
AddElement(pStatusIcon); AddElement(pNameLabel);
|
|
if (btnAddPlayer) AddElement(btnAddPlayer);
|
|
// tooltip (same for all components for now. separate tooltip for status icon later?)
|
|
SetToolTip(FormatString("Client %s (%s)", rClientInfo.getName(), rClientInfo.getNick()).getData());
|
|
// insert into listbox at correct order
|
|
// (will eventually get resized horizontally and moved)
|
|
pForListBox->InsertElement(this, pInsertBefore);
|
|
// after move: update add player button pos
|
|
if (btnAddPlayer)
|
|
{
|
|
int32_t iHgt = GetClientRect().Hgt;
|
|
btnAddPlayer->GetBounds() = GetToprightCornerRect(iHgt,iHgt,2,0);
|
|
}
|
|
// context menu for list item
|
|
SetContextHandler(new C4GUI::CBContextHandler<ClientListItem>(this, &ClientListItem::OnContext));
|
|
// update (also sets color)
|
|
Update();
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::SetPing(int32_t iToPing)
|
|
{
|
|
// no ping?
|
|
if (iToPing == -1)
|
|
{
|
|
// remove any ping label
|
|
if (pPingLabel) { delete pPingLabel; pPingLabel = nullptr; }
|
|
return;
|
|
}
|
|
// get ping as text
|
|
StdStrBuf ping;
|
|
ping.Format("%d ms", iToPing);
|
|
// create ping label if necessary
|
|
if (!pPingLabel)
|
|
{
|
|
pPingLabel = new C4GUI::Label(ping.getData(), GetBounds().Wdt, 0, ARight, C4GUI_MessageFontClr);
|
|
pPingLabel->SetToolTip(LoadResStr("IDS_DLGTIP_PING"));
|
|
AddElement(pPingLabel);
|
|
}
|
|
else
|
|
// or just set updated text
|
|
pPingLabel->SetText(ping.getData());
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::UpdateInfo()
|
|
{
|
|
// update color (always, because it can change silently)
|
|
SetColor(::Network.Players.GetClientChatColor(idClient, true));
|
|
// update activation status
|
|
fIsShownActive = GetClient() && GetClient()->isActivated();
|
|
// update status icon
|
|
SetStatus(GetCurrentStatusIcon());
|
|
}
|
|
|
|
C4Client *C4PlayerInfoListBox::ClientListItem::GetClient() const
|
|
{
|
|
// search (let's hope it exists)
|
|
return Game.Clients.getClientByID(idClient);
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::ClientListItem::IsLocalClientPlayer() const
|
|
{
|
|
// check whether client is local
|
|
// if no client can be found, something is wrong - assume network disconnect and everything local then
|
|
C4Network2Client *pClient = GetNetClient();
|
|
assert(pClient);
|
|
return !pClient || pClient->isLocal();
|
|
}
|
|
|
|
C4Network2Client *C4PlayerInfoListBox::ClientListItem::GetNetClient() const
|
|
{
|
|
return ::Network.Clients.GetClientByID(idClient);
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::ClientListItem::IsLocal() const
|
|
{
|
|
// it's local if client ID matches local ID
|
|
return idClient == Game.Clients.getLocalID();
|
|
}
|
|
|
|
C4GUI::Icons C4PlayerInfoListBox::ClientListItem::GetCurrentStatusIcon()
|
|
{
|
|
if (GetClient()->IsIgnored()) return C4GUI::Ico_Ignored;
|
|
|
|
// sound icon?
|
|
if (tLastSoundTime)
|
|
{
|
|
time_t dt = time(nullptr) - tLastSoundTime;
|
|
if (dt >= SoundIconShowTime)
|
|
{
|
|
// stop showing sound icon
|
|
tLastSoundTime = 0;
|
|
}
|
|
else
|
|
{
|
|
// time not up yet: show sound icon
|
|
return C4GUI::Ico_Sound;
|
|
}
|
|
}
|
|
// info present?
|
|
C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(idClient);
|
|
if (!pInfoPacket || !GetClient())
|
|
// unknown status
|
|
return C4GUI::Ico_UnknownClient;
|
|
// host?
|
|
if (GetClient()->isHost()) return C4GUI::Ico_Host;
|
|
// active client?
|
|
if (GetClient()->isActivated())
|
|
{
|
|
if (GetClient()->isLobbyReady())
|
|
{
|
|
return C4GUI::Ico_Ready;
|
|
}
|
|
else
|
|
{
|
|
return C4GUI::Ico_Client;
|
|
}
|
|
}
|
|
// observer
|
|
return C4GUI::Ico_ObserverClient;
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::UpdatePing()
|
|
{
|
|
// safety for removed clients
|
|
if (!GetClient()) return;
|
|
// default value indicating no ping
|
|
int32_t iPing = -1;
|
|
C4Network2Client *pClient = GetNetClient();
|
|
C4Network2IOConnection *pConn;
|
|
// must be a remote client
|
|
if (pClient && !pClient->isLocal())
|
|
{
|
|
// must have a connection
|
|
if ((pConn = pClient->getMsgConn()))
|
|
// get ping of that connection
|
|
iPing = pConn->getLag();
|
|
// check data connection if msg conn gave no value
|
|
// what's the meaning of those two connections anyway? o_O
|
|
if (iPing<=0)
|
|
if ((pConn = pClient->getDataConn()))
|
|
iPing = pConn->getLag();
|
|
}
|
|
// set that ping in label
|
|
SetPing(iPing);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::SetSoundIcon()
|
|
{
|
|
// remember time for reset
|
|
tLastSoundTime = time(nullptr);
|
|
// force icon
|
|
SetStatus(GetCurrentStatusIcon());
|
|
}
|
|
|
|
C4GUI::ContextMenu *C4PlayerInfoListBox::ClientListItem::OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
|
|
{
|
|
// safety
|
|
if (!::Network.isEnabled()) return nullptr;
|
|
// get associated client
|
|
C4Client *pClient = GetClient();
|
|
// create context menu
|
|
C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
|
|
// host options
|
|
if (::Network.isHost() && GetNetClient())
|
|
{
|
|
StdCopyStrBuf strKickDesc(LoadResStr("IDS_NET_KICKCLIENT_DESC"));
|
|
pMenu->AddItem(LoadResStr("IDS_NET_KICKCLIENT"), strKickDesc.getData(), C4GUI::Ico_None,
|
|
new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxKick));
|
|
StdCopyStrBuf strActivateDesc(LoadResStr("IDS_NET_ACTIVATECLIENT_DESC"));
|
|
pMenu->AddItem(LoadResStr(pClient->isActivated() ? "IDS_NET_DEACTIVATECLIENT" : "IDS_NET_ACTIVATECLIENT"),
|
|
strActivateDesc.getData(), C4GUI::Ico_None,
|
|
new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxActivate));
|
|
}
|
|
// info
|
|
StdCopyStrBuf strClientInfoDesc(LoadResStr("IDS_NET_CLIENTINFO_DESC"));
|
|
pMenu->AddItem(LoadResStr("IDS_NET_CLIENTINFO"), strClientInfoDesc.getData(), C4GUI::Ico_None,
|
|
new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxInfo));
|
|
//Ignore button
|
|
if(!pClient->isLocal())
|
|
{
|
|
StdCopyStrBuf strNewColor(LoadResStr(pClient->IsIgnored() ? "IDS_NET_CLIENT_UNIGNORE" : "IDS_NET_CLIENT_IGNORE"));
|
|
pMenu->AddItem(strNewColor.getData(), FormatString(LoadResStr("IDS_NET_CLIENT_IGNORE_DESC"), pClient->getName()).getData(),
|
|
C4GUI::Ico_None, new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxIgnore), nullptr);
|
|
}
|
|
|
|
// open it
|
|
return pMenu;
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::OnCtxIgnore(C4GUI::Element *pListItem)
|
|
{
|
|
GetClient()->ToggleIgnore();
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::OnCtxKick(C4GUI::Element *pListItem)
|
|
{
|
|
// host only
|
|
if (!::Network.isEnabled() || !::Network.isHost()) return;
|
|
// add control
|
|
Game.Clients.CtrlRemove(GetClient(), LoadResStr("IDS_MSG_KICKFROMLOBBY"));
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::OnCtxActivate(C4GUI::Element *pListItem)
|
|
{
|
|
// host only
|
|
C4Client *pClient = GetClient();
|
|
if (!::Network.isEnabled() || !::Network.isHost() || !pClient) return;
|
|
// add control
|
|
::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(idClient, CUT_Activate, !pClient->isActivated()), CDT_Sync);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::OnCtxInfo(C4GUI::Element *pListItem)
|
|
{
|
|
// show client info dialog
|
|
::pGUI->ShowRemoveDlg(new C4Network2ClientDlg(idClient));
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ClientListItem::OnBtnAddPlr(C4GUI::Control *btn)
|
|
{
|
|
// show player add dialog
|
|
GetScreen()->ShowRemoveDlg(new C4PlayerSelDlg(new C4FileSel_CBEx<C4GameLobby::MainDlg>(GetLobby(), &C4GameLobby::MainDlg::OnClientAddPlayer, idClient)));
|
|
}
|
|
|
|
|
|
// ----------- TeamListItem ---------------------------------------------
|
|
|
|
C4PlayerInfoListBox::TeamListItem::TeamListItem(C4PlayerInfoListBox *pForListBox, int32_t idTeam, ListItem *pInsertBefore)
|
|
: ListItem(pForListBox), idTeam(idTeam)
|
|
{
|
|
bool fEvaluation = pList->IsEvaluation();
|
|
// get team data
|
|
const char *szTeamName;
|
|
C4Team *pTeam = nullptr;
|
|
if (idTeam == TEAMID_Unknown)
|
|
szTeamName = LoadResStr("IDS_MSG_RNDTEAM");
|
|
else
|
|
{
|
|
pTeam = Game.Teams.GetTeamByID(idTeam); assert(pTeam);
|
|
if (pTeam) szTeamName = pTeam->GetName(); else szTeamName = "INTERNAL TEAM ERROR";
|
|
}
|
|
// set ID
|
|
idListItemID.idType = ListItem::ID::PLI_TEAM;
|
|
idListItemID.id = idTeam;
|
|
// get height
|
|
int32_t iIconSize; CStdFont *pFont;
|
|
if (!fEvaluation)
|
|
{
|
|
pFont = &::GraphicsResource.TextFont;
|
|
iIconSize = pFont->GetLineHeight();
|
|
}
|
|
else
|
|
{
|
|
pFont = &::GraphicsResource.TitleFont;
|
|
iIconSize = C4SymbolSize; // C4PictureSize doesn't fit...
|
|
}
|
|
// create subcomponents
|
|
pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Team);
|
|
pNameLabel = new C4GUI::Label(szTeamName, iIconSize + IconLabelSpacing, (iIconSize - pFont->GetLineHeight())/2, ALeft, pList->GetTextColor(), pFont, false);
|
|
if (fEvaluation && pTeam && pTeam->GetIconSpec() && *pTeam->GetIconSpec())
|
|
{
|
|
C4FacetSurface fctSymbol;
|
|
fctSymbol.Create(C4SymbolSize,C4SymbolSize);
|
|
Game.DrawTextSpecImage(fctSymbol, pTeam->GetIconSpec(), nullptr, pTeam->GetColor());
|
|
pIcon->GetMFacet().GrabFrom(fctSymbol);
|
|
}
|
|
// calc own bounds
|
|
C4Rect rcOwnBounds = pNameLabel->GetBounds();
|
|
rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
|
|
rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
|
|
SetBounds(rcOwnBounds);
|
|
// add components
|
|
AddElement(pIcon); AddElement(pNameLabel);
|
|
// tooltip
|
|
SetToolTip(FormatString(LoadResStr("IDS_DESC_TEAM"), szTeamName).getData());
|
|
// insert into listbox at correct order
|
|
// (will eventually get resized horizontally and moved)
|
|
pForListBox->InsertElement(this, pInsertBefore);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::TeamListItem::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
|
|
{
|
|
// double click on team to enter it with all local players
|
|
if (iButton == C4MC_Button_LeftDouble)
|
|
{
|
|
MoveLocalPlayersIntoTeam();
|
|
}
|
|
else
|
|
ListItem::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
|
|
}
|
|
|
|
void C4PlayerInfoListBox::TeamListItem::UpdateOwnPos()
|
|
{
|
|
// parent for client rect
|
|
typedef C4GUI::Window ParentClass;
|
|
ParentClass::UpdateOwnPos();
|
|
// evaluation: Center team label
|
|
if (pList->IsEvaluation())
|
|
{
|
|
int32_t iTotalWdt = pIcon->GetBounds().Wdt + IconLabelSpacing + ::GraphicsResource.TitleFont.GetTextWidth(pNameLabel->GetText());
|
|
C4GUI::ComponentAligner caAll(GetContainedClientRect(), 0,0);
|
|
C4GUI::ComponentAligner caBounds(caAll.GetCentered(iTotalWdt, caAll.GetInnerHeight()), 0,0);
|
|
pIcon->SetBounds(caBounds.GetFromLeft(pIcon->GetBounds().Wdt, pIcon->GetBounds().Hgt));
|
|
pNameLabel->SetBounds(caBounds.GetCentered(caBounds.GetInnerWidth(), ::GraphicsResource.TitleFont.GetLineHeight()));
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::TeamListItem::MoveLocalPlayersIntoTeam()
|
|
{
|
|
// check if changing teams is allowed
|
|
if (!CanLocalChooseTeams()) return;
|
|
// safety: Clicked team must exist
|
|
if (!Game.Teams.GetTeamByID(idTeam)) return;
|
|
// get local client to change teams of
|
|
bool fAnyChange = false;
|
|
C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(Game.Clients.getLocalID());
|
|
if (!pChangeInfo) return;
|
|
// this must be processed as a request by the host
|
|
// now change it in its own request packet
|
|
C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
|
|
C4PlayerInfo *pInfo; int32_t i=0;
|
|
while ((pInfo = LocalInfoRequest.GetPlayerInfo(i++)))
|
|
if (pInfo->GetTeam() != idTeam)
|
|
if (pInfo->GetType() == C4PT_User)
|
|
{
|
|
pInfo->SetTeam(idTeam);
|
|
fAnyChange = true;
|
|
}
|
|
if (!fAnyChange) return;
|
|
// and request this update (host processes it directly)
|
|
::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
|
|
// next update will move the player labels
|
|
}
|
|
|
|
void C4PlayerInfoListBox::TeamListItem::Update()
|
|
{
|
|
// evaluation: update color by team winning status
|
|
if (pList->IsEvaluation())
|
|
{
|
|
C4Team *pTeam = Game.Teams.GetTeamByID(idTeam);
|
|
if (pTeam && pTeam->HasWon())
|
|
{
|
|
pNameLabel->SetColor(C4GUI_WinningTextColor, false);
|
|
}
|
|
else
|
|
{
|
|
pNameLabel->SetColor(C4GUI_LosingTextColor, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ----------- FreeSavegamePlayersListItem ---------------------------------------------
|
|
|
|
C4PlayerInfoListBox::FreeSavegamePlayersListItem::FreeSavegamePlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
|
|
: ListItem(pForListBox)
|
|
{
|
|
// set ID
|
|
idListItemID.idType = ListItem::ID::PLI_SAVEGAMEPLR;
|
|
idListItemID.id = 0;
|
|
// get height
|
|
int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
|
|
// create subcomponents
|
|
pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_SavegamePlayer);
|
|
pNameLabel = new C4GUI::Label(LoadResStr("IDS_MSG_FREESAVEGAMEPLRS"), iIconSize + IconLabelSpacing,0, ALeft);
|
|
// calc own bounds
|
|
C4Rect rcOwnBounds = pNameLabel->GetBounds();
|
|
rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
|
|
rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
|
|
SetBounds(rcOwnBounds);
|
|
// add components
|
|
AddElement(pIcon); AddElement(pNameLabel);
|
|
// tooltip
|
|
SetToolTip(LoadResStr("IDS_DESC_UNASSOCIATEDSAVEGAMEPLAYE"));
|
|
// insert into listbox at correct order
|
|
// (will eventually get resized horizontally and moved)
|
|
pForListBox->InsertElement(this, pInsertBefore);
|
|
// initial update
|
|
Update();
|
|
}
|
|
|
|
void C4PlayerInfoListBox::FreeSavegamePlayersListItem::Update()
|
|
{
|
|
// 2do: none-label
|
|
}
|
|
|
|
|
|
// ----------- ScriptPlayersListItem ---------------------------------------------
|
|
|
|
C4PlayerInfoListBox::ScriptPlayersListItem::ScriptPlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
|
|
: ListItem(pForListBox)
|
|
{
|
|
// set ID
|
|
idListItemID.idType = ListItem::ID::PLI_SCRIPTPLR;
|
|
idListItemID.id = 0;
|
|
// get height
|
|
int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
|
|
// create subcomponents
|
|
pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Record);
|
|
pNameLabel = new C4GUI::Label(LoadResStr("IDS_CTL_SCRIPTPLAYERS"), iIconSize + IconLabelSpacing,0, ALeft);
|
|
btnAddPlayer = nullptr;
|
|
if (::Control.isCtrlHost())
|
|
{
|
|
btnAddPlayer = new C4GUI::CallbackButton<ScriptPlayersListItem, C4GUI::IconButton>(C4GUI::Ico_AddPlr, C4Rect(0, 0, iIconSize, iIconSize), 'A' /* 2do TODO */, &ScriptPlayersListItem::OnBtnAddPlr, this);
|
|
}
|
|
// calc own bounds
|
|
C4Rect rcOwnBounds = pNameLabel->GetBounds();
|
|
rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
|
|
rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
|
|
SetBounds(rcOwnBounds);
|
|
// add components
|
|
AddElement(pIcon); AddElement(pNameLabel);
|
|
if (btnAddPlayer) AddElement(btnAddPlayer);
|
|
// tooltip
|
|
SetToolTip(LoadResStr("IDS_DESC_PLAYERSCONTROLLEDBYCOMPUT"));
|
|
// insert into listbox at correct order
|
|
// (will eventually get resized horizontally and moved)
|
|
pForListBox->InsertElement(this, pInsertBefore);
|
|
// after move: update add player button pos
|
|
if (btnAddPlayer)
|
|
{
|
|
int32_t iHgt = GetClientRect().Hgt;
|
|
btnAddPlayer->GetBounds() = GetToprightCornerRect(iHgt,iHgt,2,0);
|
|
}
|
|
// initial update
|
|
Update();
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ScriptPlayersListItem::Update()
|
|
{
|
|
// player join button: Visible if there's still some room for script players
|
|
if (btnAddPlayer)
|
|
{
|
|
bool fCanJoinScriptPlayers = (Game.Teams.GetMaxScriptPlayers() - Game.PlayerInfos.GetActiveScriptPlayerCount(true, true) > 0);
|
|
btnAddPlayer->SetVisibility(fCanJoinScriptPlayers);
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::ScriptPlayersListItem::OnBtnAddPlr(C4GUI::Control *btn)
|
|
{
|
|
// safety
|
|
int32_t iCurrScriptPlrCount = Game.PlayerInfos.GetActiveScriptPlayerCount(true, true);
|
|
bool fCanJoinScriptPlayers = (Game.Teams.GetMaxScriptPlayers() - iCurrScriptPlrCount > 0);
|
|
if (!fCanJoinScriptPlayers) return;
|
|
if (!::Control.isCtrlHost()) return;
|
|
// request a script player join
|
|
C4PlayerInfo *pScriptPlrInfo = new C4PlayerInfo();
|
|
pScriptPlrInfo->SetAsScriptPlayer(Game.Teams.GetScriptPlayerName().getData(), GenerateRandomPlayerColor(iCurrScriptPlrCount), 0, C4ID::None);
|
|
C4ClientPlayerInfos JoinPkt(nullptr, true, pScriptPlrInfo);
|
|
// add to queue!
|
|
Game.PlayerInfos.DoPlayerInfoUpdate(&JoinPkt);
|
|
}
|
|
|
|
|
|
// ----------- ReplayPlayersListItem ---------------------------------------------
|
|
|
|
C4PlayerInfoListBox::ReplayPlayersListItem::ReplayPlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
|
|
: ListItem(pForListBox)
|
|
{
|
|
// set ID
|
|
idListItemID.idType = ListItem::ID::PLI_REPLAY;
|
|
idListItemID.id = 0;
|
|
// get height
|
|
int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
|
|
// create subcomponents
|
|
pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Record);
|
|
pNameLabel = new C4GUI::Label(LoadResStr("IDS_MSG_REPLAYPLRS"), iIconSize + IconLabelSpacing,0, ALeft);
|
|
// calc own bounds
|
|
C4Rect rcOwnBounds = pNameLabel->GetBounds();
|
|
rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
|
|
rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
|
|
SetBounds(rcOwnBounds);
|
|
// add components
|
|
AddElement(pIcon); AddElement(pNameLabel);
|
|
// tooltip
|
|
SetToolTip(LoadResStr("IDS_MSG_REPLAYPLRS_DESC"));
|
|
// insert into listbox at correct order
|
|
// (will eventually get resized horizontally and moved)
|
|
pForListBox->InsertElement(this, pInsertBefore);
|
|
}
|
|
|
|
|
|
|
|
// ------------------- C4PlayerInfoListBox ------------------------
|
|
|
|
C4PlayerInfoListBox::C4PlayerInfoListBox(const C4Rect &rcBounds, Mode eMode, int32_t iTeamFilter)
|
|
: C4GUI::ListBox(rcBounds), eMode(eMode), iMaxUncollapsedPlayers(10), fIsCollapsed(false), iTeamFilter(iTeamFilter), dwTextColor(C4GUI_MessageFontClr), pCustomFont(nullptr)
|
|
{
|
|
// update if client listbox selection changes
|
|
SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4PlayerInfoListBox>(this, &C4PlayerInfoListBox::OnPlrListSelChange));
|
|
// initial update
|
|
Update();
|
|
}
|
|
|
|
void C4PlayerInfoListBox::SetClientSoundIcon(int32_t iForClientID)
|
|
{
|
|
// get client element
|
|
ListItem *pItem = GetPlayerListItem(ListItem::ID::PLI_CLIENT, iForClientID);
|
|
if (pItem)
|
|
{
|
|
ClientListItem *pClientItem = static_cast<ClientListItem *>(pItem);
|
|
pClientItem->SetSoundIcon();
|
|
}
|
|
}
|
|
|
|
C4PlayerInfoListBox::ListItem *C4PlayerInfoListBox::GetPlayerListItem(ListItem::ID::IDType eType, int32_t id)
|
|
{
|
|
ListItem::ID idSearch(eType, id);
|
|
// search through listbox
|
|
for (C4GUI::Element *pEItem = GetFirst(); pEItem; pEItem = pEItem->GetNext())
|
|
{
|
|
// only playerlistitems in this box
|
|
ListItem *pItem = static_cast<ListItem *>(pEItem);
|
|
if (pItem->idListItemID == idSearch) return pItem;
|
|
}
|
|
// nothing found
|
|
return nullptr;
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::PlrListItemUpdate(ListItem::ID::IDType eType, int32_t id, class ListItem **pEnsurePos)
|
|
{
|
|
assert(pEnsurePos);
|
|
// search item
|
|
ListItem *pItem = GetPlayerListItem(eType, id);
|
|
if (!pItem) return false;
|
|
// ensure its position is correct
|
|
if (pItem != *pEnsurePos)
|
|
{
|
|
RemoveElement(pItem);
|
|
InsertElement(pItem, *pEnsurePos);
|
|
}
|
|
else
|
|
{
|
|
// pos correct; advance past it
|
|
*pEnsurePos = static_cast<ListItem *>(pItem->GetNext());
|
|
}
|
|
// update item
|
|
pItem->Update();
|
|
// done, success
|
|
return true;
|
|
}
|
|
|
|
// static safety var to prevent recusive updates
|
|
static bool fPlayerListUpdating=false;
|
|
|
|
void C4PlayerInfoListBox::Update()
|
|
{
|
|
if (fPlayerListUpdating) return;
|
|
fPlayerListUpdating = true;
|
|
|
|
// synchronize current list with what it should be
|
|
// call update on all other list items
|
|
ListItem *pCurrInList = static_cast<ListItem *>(GetFirst()); // list item being compared with the searched item
|
|
|
|
// free savegame players first
|
|
UpdateSavegamePlayers(&pCurrInList);
|
|
|
|
// next comes the regular players, sorted either by clients or teams, or special sort for evaluation mode
|
|
switch (eMode)
|
|
{
|
|
case PILBM_LobbyTeamSort:
|
|
// sort by team
|
|
if (Game.Teams.CanLocalSeeTeam())
|
|
UpdatePlayersByTeam(&pCurrInList);
|
|
else
|
|
UpdatePlayersByRandomTeam(&pCurrInList);
|
|
break;
|
|
|
|
case PILBM_LobbyClientSort:
|
|
// sort by client
|
|
// replay players first?
|
|
if (Game.C4S.Head.Replay) UpdateReplayPlayers(&pCurrInList);
|
|
// script controlled players from the main list
|
|
UpdateScriptPlayers(&pCurrInList);
|
|
// regular players
|
|
UpdatePlayersByClient(&pCurrInList);
|
|
break;
|
|
|
|
case PILBM_Evaluation:
|
|
case PILBM_EvaluationNoWinners:
|
|
UpdatePlayersByEvaluation(&pCurrInList, eMode == PILBM_Evaluation);
|
|
break;
|
|
}
|
|
|
|
// finally: remove any remaining list items at the end
|
|
while (pCurrInList)
|
|
{
|
|
ListItem *pDel = pCurrInList;
|
|
pCurrInList = static_cast<ListItem *>(pCurrInList->GetNext());
|
|
delete pDel;
|
|
}
|
|
|
|
// update done
|
|
fPlayerListUpdating = false;
|
|
|
|
// check whether view needs to be collapsed
|
|
if (!fIsCollapsed && IsScrollingActive() && !IsEvaluation())
|
|
{
|
|
// then collapse it, and update window
|
|
iMaxUncollapsedPlayers = Game.PlayerInfos.GetPlayerCount()-1;
|
|
fIsCollapsed = true;
|
|
Update(); // recursive call!
|
|
}
|
|
else if (fIsCollapsed && Game.PlayerInfos.GetPlayerCount() <= iMaxUncollapsedPlayers)
|
|
{
|
|
// player count dropped below collapse-limit: uncollapse
|
|
// note that this may again cause a collapse after that update, if scrolling was still necessary
|
|
// however, it will then not recurse any further, because iMaxUncollapsedPlayers will have been updated
|
|
fIsCollapsed = false;
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdateSavegamePlayers(ListItem **ppCurrInList)
|
|
{
|
|
// add unassociated savegame players (script players excluded)
|
|
if (Game.RestorePlayerInfos.GetActivePlayerCount(true) - Game.RestorePlayerInfos.GetActiveScriptPlayerCount(true, true))
|
|
{
|
|
// caption
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_SAVEGAMEPLR, 0, ppCurrInList))
|
|
new FreeSavegamePlayersListItem(this, *ppCurrInList);
|
|
// the players
|
|
bool fAnyPlayers = false;
|
|
C4PlayerInfo *pInfo; int32_t iInfoID=0;
|
|
while ((pInfo = Game.RestorePlayerInfos.GetNextPlayerInfoByID(iInfoID)))
|
|
{
|
|
iInfoID = pInfo->GetID();
|
|
// skip assigned
|
|
if (Game.PlayerInfos.GetPlayerInfoBySavegameID(iInfoID)) continue;
|
|
// skip script controlled - those are put into the script controlled player list
|
|
if (pInfo->GetType() == C4PT_Script) continue;
|
|
// players are in the list
|
|
fAnyPlayers = true;
|
|
// show them
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_SAVEGAMEPLR, iInfoID, ppCurrInList))
|
|
new PlayerListItem(this, -1, iInfoID, true, *ppCurrInList);
|
|
}
|
|
// 2do: none-label
|
|
(void) fAnyPlayers;
|
|
}
|
|
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdateReplayPlayers(ListItem **ppCurrInList)
|
|
{
|
|
// header
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_REPLAY, 0, ppCurrInList))
|
|
new ReplayPlayersListItem(this, *ppCurrInList);
|
|
// players
|
|
C4PlayerInfo *pInfo; int32_t iInfoID=0;
|
|
while ((pInfo = Game.PlayerInfos.GetNextPlayerInfoByID(iInfoID)))
|
|
{
|
|
if (pInfo->IsInvisible()) continue;
|
|
iInfoID = pInfo->GetID();
|
|
// show them
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, iInfoID, ppCurrInList))
|
|
new PlayerListItem(this, -1, iInfoID, false, *ppCurrInList);
|
|
}
|
|
// 2do: none-label
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdateScriptPlayers(ListItem **ppCurrInList)
|
|
{
|
|
// script controlled players from the main list
|
|
// processing the restore list would be redundant, because all script players should have been taken over by a new script player join automatically
|
|
// also show the label if script players can be joined
|
|
if (Game.Teams.GetMaxScriptPlayers() || Game.PlayerInfos.GetActiveScriptPlayerCount(true, false))
|
|
{
|
|
// header
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_SCRIPTPLR, 0, ppCurrInList))
|
|
new ScriptPlayersListItem(this, *ppCurrInList);
|
|
// players
|
|
C4PlayerInfo *pInfo; int32_t iClientIdx=0; C4ClientPlayerInfos *pInfos;
|
|
while ((pInfos = Game.PlayerInfos.GetIndexedInfo(iClientIdx++)))
|
|
{
|
|
int32_t iInfoIdx=0;
|
|
while ((pInfo = pInfos->GetPlayerInfo(iInfoIdx++)))
|
|
{
|
|
if (pInfo->GetType() != C4PT_Script) continue;
|
|
if (pInfo->IsRemoved()) continue;
|
|
if (pInfo->IsInvisible()) continue;
|
|
// show them
|
|
int32_t iInfoID = pInfo->GetID();
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, iInfoID, ppCurrInList))
|
|
new PlayerListItem(this, pInfos->GetClientID(), iInfoID, false, *ppCurrInList);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdatePlayersByTeam(ListItem **ppCurrInList)
|
|
{
|
|
// sort by team
|
|
C4Team *pTeam; int32_t i=0;
|
|
while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
|
|
{
|
|
// no empty teams that are not used
|
|
if (Game.Teams.IsAutoGenerateTeams() && !pTeam->GetPlayerCount()) continue;
|
|
// the team label
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, pTeam->GetID(), ppCurrInList))
|
|
new TeamListItem(this, pTeam->GetID(), *ppCurrInList);
|
|
// players for this team
|
|
int32_t idPlr, j=0; int32_t idClient; C4Client *pClient; C4PlayerInfo *pPlrInfo;
|
|
while ((idPlr = pTeam->GetIndexedPlayer(j++)))
|
|
if ((pPlrInfo = Game.PlayerInfos.GetPlayerInfoByID(idPlr, &idClient)))
|
|
if (!pPlrInfo->IsInvisible())
|
|
if ((pClient=Game.Clients.getClientByID(idClient)) && pClient->isActivated())
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, idPlr, ppCurrInList))
|
|
new PlayerListItem(this, idClient, idPlr, false, *ppCurrInList);
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdatePlayersByRandomTeam(ListItem **ppCurrInList)
|
|
{
|
|
// team sort but teams set to random and invisible: Show all players within one "Random Team"-label
|
|
bool fTeamLabelPut = false;
|
|
C4Client *pClient = nullptr;
|
|
while ((pClient = Game.Clients.getClient(pClient)))
|
|
{
|
|
// player infos for this client - not for deactivated, and never in replays
|
|
if (Game.C4S.Head.Replay || !pClient->isActivated()) continue;
|
|
C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(pClient->getID());
|
|
if (pInfoPacket)
|
|
{
|
|
C4PlayerInfo *pPlrInfo; int32_t i=0;
|
|
while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
|
|
{
|
|
if (pPlrInfo->IsInvisible()) continue;
|
|
if (!fTeamLabelPut)
|
|
{
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, TEAMID_Unknown, ppCurrInList))
|
|
new TeamListItem(this, TEAMID_Unknown, *ppCurrInList);
|
|
fTeamLabelPut = true;
|
|
}
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
|
|
new PlayerListItem(this, pClient->getID(), pPlrInfo->GetID(), false, *ppCurrInList);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdatePlayersByClient(ListItem **ppCurrInList)
|
|
{
|
|
// regular players
|
|
C4Client *pClient = nullptr;
|
|
while ((pClient = Game.Clients.getClient(pClient)))
|
|
{
|
|
// the client label
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_CLIENT, pClient->getID(), ppCurrInList))
|
|
new ClientListItem(this, pClient->getCore(), *ppCurrInList);
|
|
// player infos for this client - not for observers, and never in replays
|
|
// could also check for activated here. However, non-observers will usually be activated later and thus be using their players
|
|
if (Game.C4S.Head.Replay || pClient->isObserver()) continue;
|
|
C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(pClient->getID());
|
|
if (pInfoPacket)
|
|
{
|
|
C4PlayerInfo *pPlrInfo; int32_t i=0;
|
|
while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
|
|
{
|
|
if (pPlrInfo->GetType() == C4PT_Script) continue;
|
|
if (pPlrInfo->IsRemoved()) continue;
|
|
if (pPlrInfo->IsInvisible()) continue;
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
|
|
new PlayerListItem(this, pClient->getID(), pPlrInfo->GetID(), false, *ppCurrInList);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdatePlayersByEvaluation(ListItem **ppCurrInList, bool fShowWinners)
|
|
{
|
|
// if a team filter is provided, add team label first
|
|
if (iTeamFilter)
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, iTeamFilter, ppCurrInList))
|
|
new TeamListItem(this, iTeamFilter, *ppCurrInList);
|
|
// Add by teams: In show-winner-mode winning teams first
|
|
// Otherwise, just add all
|
|
AddMode pShowWinnersAddModes[] = { AM_Winners, AM_Losers };
|
|
AddMode pHideWinnersAddModes[] = { AM_All };
|
|
AddMode *pAddModes; int32_t iAddModeCount;
|
|
if (fShowWinners)
|
|
{
|
|
pAddModes = pShowWinnersAddModes; iAddModeCount = 2;
|
|
}
|
|
else
|
|
{
|
|
pAddModes = pHideWinnersAddModes; iAddModeCount = 1;
|
|
}
|
|
for (int32_t iAddMode = 0; iAddMode < iAddModeCount; ++iAddMode)
|
|
{
|
|
AddMode eAddMode = pAddModes[iAddMode];
|
|
if (iTeamFilter)
|
|
{
|
|
// Team filter mode: Add only players of specified team
|
|
UpdatePlayersByEvaluation(ppCurrInList, Game.Teams.GetTeamByID(iTeamFilter), eAddMode);
|
|
}
|
|
else
|
|
{
|
|
// Normal mode: Add all teams of winning status
|
|
C4Team *pTeam; int32_t i=0;
|
|
while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
|
|
{
|
|
UpdatePlayersByEvaluation(ppCurrInList, pTeam, eAddMode);
|
|
}
|
|
// Add teamless players of winning status
|
|
UpdatePlayersByEvaluation(ppCurrInList, nullptr, eAddMode);
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::UpdatePlayersByEvaluation(ListItem **ppCurrInList, C4Team *pTeam, C4PlayerInfoListBox::AddMode eWinMode)
|
|
{
|
|
// check winning status of team first
|
|
if (pTeam && eWinMode != AM_All) if (pTeam->HasWon() != (eWinMode == AM_Winners)) return;
|
|
// now add all matching players
|
|
int32_t iTeamID = pTeam ? pTeam->GetID() : 0;
|
|
C4ClientPlayerInfos *pInfoPacket; int32_t iClient=0;
|
|
while ((pInfoPacket = Game.PlayerInfos.GetIndexedInfo(iClient++)))
|
|
{
|
|
C4PlayerInfo *pPlrInfo; int32_t i=0;
|
|
while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
|
|
{
|
|
if (!pPlrInfo->HasJoined()) continue;
|
|
if (pPlrInfo->GetTeam() != iTeamID) continue;
|
|
if (pPlrInfo->IsInvisible()) continue;
|
|
if (!pTeam && eWinMode != AM_All && pPlrInfo->HasWon() != (eWinMode == AM_Winners)) continue;
|
|
if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
|
|
new PlayerListItem(this, pInfoPacket->GetClientID(), pPlrInfo->GetID(), false, *ppCurrInList);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool C4PlayerInfoListBox::IsPlayerItemCollapsed(PlayerListItem *pItem)
|
|
{
|
|
// never if view is not collapsed
|
|
if (!fIsCollapsed) return false;
|
|
// collapsed if not selected
|
|
return GetSelectedItem() != pItem;
|
|
}
|
|
|
|
void C4PlayerInfoListBox::SetMode(Mode eNewMode)
|
|
{
|
|
if (eMode != eNewMode)
|
|
{
|
|
eMode = eNewMode;
|
|
Update();
|
|
}
|
|
}
|
|
|
|
void C4PlayerInfoListBox::SetCustomFont(CStdFont *pNewFont, uint32_t dwTextColor)
|
|
{
|
|
pCustomFont = pNewFont;
|
|
this->dwTextColor = dwTextColor;
|
|
// update done later by caller anyway
|
|
}
|