Scriptified scoreboard drawing

This commit is contained in:
Ricardo Luís Vaz Silva 2025-05-19 02:52:17 -03:00
commit 706d1b6978
16 changed files with 617 additions and 415 deletions

View file

@ -1840,6 +1840,51 @@ DEFINE_ACTION_FUNCTION(_Screen, DrawLineFrame)
return 0;
}
DEFINE_ACTION_FUNCTION(_Screen, GetTextureWidth)
{
PARAM_PROLOGUE;
PARAM_INT(textureId);
PARAM_BOOL(animated);
FGameTexture *tex = textureId >= 1 ? TexMan.GetGameTexture(FSetTextureID(textureId), animated) : nullptr;
ACTION_RETURN_FLOAT(tex ? tex->GetDisplayWidth() : -1);
}
DEFINE_ACTION_FUNCTION(_Screen, GetTextureHeight)
{
PARAM_PROLOGUE;
PARAM_INT(textureId);
PARAM_BOOL(animated);
FGameTexture *tex = textureId >= 1 ? TexMan.GetGameTexture(FSetTextureID(textureId), animated) : nullptr;
ACTION_RETURN_FLOAT(tex ? tex->GetDisplayHeight() : -1);
}
DEFINE_ACTION_FUNCTION(_Screen, GetTextureLeftOffset)
{
PARAM_PROLOGUE;
PARAM_INT(textureId);
PARAM_BOOL(animated);
FGameTexture *tex = textureId >= 1 ? TexMan.GetGameTexture(FSetTextureID(textureId), animated) : nullptr;
ACTION_RETURN_FLOAT(tex ? tex->GetDisplayLeftOffset() : -1);
}
DEFINE_ACTION_FUNCTION(_Screen, GetTextureTopOffset)
{
PARAM_PROLOGUE;
PARAM_INT(textureId);
PARAM_BOOL(animated);
FGameTexture *tex = textureId >= 1 ? TexMan.GetGameTexture(FSetTextureID(textureId), animated) : nullptr;
ACTION_RETURN_FLOAT(tex ? tex->GetDisplayTopOffset() : -1);
}
DEFINE_ACTION_FUNCTION(FCanvas, DrawLineFrame)
{
PARAM_SELF_PROLOGUE(FCanvas);

View file

@ -564,6 +564,52 @@ DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Substitute, StringSubst)
return 0;
}
static int StringCompare(FString *self, const FString &other)
{
return self->Compare(other);
}
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Compare, StringCompare)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
PARAM_STRING(other);
ACTION_RETURN_INT(StringCompare(self, other));
}
static int StringCompareNoCase(FString *self, const FString &other)
{
return self->CompareNoCase(other);
}
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CompareNoCase, StringCompareNoCase)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
PARAM_STRING(other);
ACTION_RETURN_INT(StringCompareNoCase(self, other));
}
static int StringIsEmpty(FString *self)
{
return self->IsEmpty();
}
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, IsEmpty, StringIsEmpty)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
ACTION_RETURN_INT(StringIsEmpty(self));
}
static int StringIsNotEmpty(FString *self)
{
return self->IsNotEmpty();
}
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, IsNotEmpty, StringIsNotEmpty)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
ACTION_RETURN_INT(StringIsNotEmpty(self));
}
static void StringStripRight(FString* self, const FString& junk)
{
if (junk.IsNotEmpty()) self->StripRight(junk);

View file

@ -45,6 +45,7 @@
#include "c_buttons.h"
#include "d_buttons.h"
#include "v_draw.h"
#include "r_utility.h"
enum
{
@ -241,6 +242,7 @@ void CT_PasteChat(const char *clip)
void CT_Drawer (void)
{
auto &vp = r_viewpoint;
auto drawer = twod;
FFont *displayfont = NewConsoleFont;
@ -254,7 +256,7 @@ void CT_Drawer (void)
{
// todo: check for summary screen
}
if (!skipit) HU_DrawScores (&players[consoleplayer]);
if (!skipit) HU_DrawScores (consoleplayer, vp.TicFrac);
}
if (chatmodeon)
{

View file

@ -232,6 +232,12 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, GetDisplayColor)
ACTION_RETURN_INT(c);
}
DEFINE_ACTION_FUNCTION(_PlayerInfo, GetAverageLatency)
{
PARAM_SELF_STRUCT_PROLOGUE(player_t);
ACTION_RETURN_INT(ClientStates[self - players].AverageLatency);
}
// Find out which teams are present. If there is only one,
// then another team should be chosen at random.
//

View file

@ -67,6 +67,7 @@
static void HU_DoDrawScores (player_t *, player_t *[MAXPLAYERS]);
static void HU_DrawTimeRemaining (int y);
static void HU_DrawPlayer(player_t *, bool, int, int, int, int, int, int, int, int, int);
static void HU_DrawColorBar(int x, int y, int height, int playernum);
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
@ -132,8 +133,6 @@ void HU_SortPlayers
*/
bool SB_ForceActive = false;
static FFont *displayFont;
static int FontScale;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
@ -146,345 +145,12 @@ static int FontScale;
//
//==========================================================================
void HU_DrawScores(player_t* player)
void HU_DrawScores(int player, double ticFrac)
{
displayFont = NewSmallFont;
FontScale = max<int>(screen->GetHeight() / 400, 1);
int numPlayers = 0;
for (int i = 0; i < MAXPLAYERS; ++i)
numPlayers += playeringame[i];
if (numPlayers > 8)
FontScale = static_cast<int>(ceil(FontScale * 0.75));
if (deathmatch)
IFVIRTUALPTR(StatusBar, DBaseStatusBar, Scoreboard_DrawScores)
{
if (teamplay)
{
if (!sb_teamdeathmatch_enable)
return;
}
else if (!sb_deathmatch_enable)
{
return;
}
}
else if (!multiplayer || !sb_cooperative_enable)
{
return;
}
player_t* sortedPlayers[MAXPLAYERS];
if (player->camera && player->camera->player)
player = player->camera->player;
sortedPlayers[MAXPLAYERS - 1] = player;
for (int i = 0, j = 0; j < MAXPLAYERS - 1; ++i, ++j)
{
if (&players[i] == player)
++i;
sortedPlayers[j] = &players[i];
}
if (teamplay && deathmatch)
qsort(sortedPlayers, MAXPLAYERS, sizeof(player_t*), compareteams);
else
qsort(sortedPlayers, MAXPLAYERS, sizeof(player_t*), comparepoints);
HU_DoDrawScores(player, sortedPlayers);
}
//==========================================================================
//
// HU_GetPlayerWidths
//
// Returns the widest player name and class icon.
//
//==========================================================================
void HU_GetPlayerWidths(int& maxNameWidth, int& maxScoreWidth, int& maxIconHeight)
{
constexpr char NameLabel[] = "Name";
displayFont = NewSmallFont;
maxNameWidth = displayFont->StringWidth(NameLabel);
maxScoreWidth = 0;
maxIconHeight = 0;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (!playeringame[i])
continue;
int width = displayFont->StringWidth(players[i].userinfo.GetName(16));
if (width > maxNameWidth)
maxNameWidth = width;
auto icon = FSetTextureID(players[i].mo->IntVar(NAME_ScoreIcon));
if (icon.isValid())
{
auto pic = TexMan.GetGameTexture(icon);
width = int(pic->GetDisplayWidth() - pic->GetDisplayLeftOffset() + 2.5);
if (width > maxScoreWidth)
maxScoreWidth = width;
// The icon's top offset does not count toward its height, because
// zdoom.pk3's standard Hexen class icons are designed that way.
int height = int(pic->GetDisplayHeight() - pic->GetDisplayTopOffset() + 0.5);
if (height > maxIconHeight)
maxIconHeight = height;
}
}
}
//==========================================================================
//
// HU_DoDrawScores
//
//==========================================================================
static void HU_DrawFontScaled(double x, double y, int color, const char *text)
{
DrawText(twod, displayFont, color, x / FontScale, y / FontScale, text, DTA_VirtualWidth, twod->GetWidth() / FontScale, DTA_VirtualHeight, twod->GetHeight() / FontScale, TAG_END);
}
static void HU_DoDrawScores(player_t* player, player_t* sortedPlayers[MAXPLAYERS])
{
int color = sb_cooperative_headingcolor;
if (deathmatch)
{
if (teamplay)
color = sb_teamdeathmatch_headingcolor;
else
color = sb_deathmatch_headingcolor;
}
int maxNameWidth, maxScoreWidth, maxIconHeight;
HU_GetPlayerWidths(maxNameWidth, maxScoreWidth, maxIconHeight);
int height = displayFont->GetHeight() * FontScale;
int lineHeight = max<int>(height, maxIconHeight * CleanYfac);
int yPadding = (lineHeight - height + 1) / 2;
int bottom = StatusBar->GetTopOfStatusbar();
int y = max<int>(48 * CleanYfac, (bottom - MAXPLAYERS * (height + CleanYfac + 1)) / 2);
HU_DrawTimeRemaining(bottom - height);
if (teamplay && deathmatch)
{
y -= (BigFont->GetHeight() + 8) * CleanYfac;
for (size_t i = 0u; i < Teams.Size(); ++i)
{
Teams[i].m_iPlayerCount = 0;
Teams[i].m_iScore = 0;
}
int numTeams = 0;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (playeringame[sortedPlayers[i]-players] && FTeam::IsValid(sortedPlayers[i]->userinfo.GetTeam()))
{
if (Teams[sortedPlayers[i]->userinfo.GetTeam()].m_iPlayerCount++ == 0)
++numTeams;
Teams[sortedPlayers[i]->userinfo.GetTeam()].m_iScore += sortedPlayers[i]->fragcount;
}
}
int scoreXWidth = twod->GetWidth() / max<int>(8, numTeams);
int numScores = 0;
for (size_t i = 0u; i < Teams.Size(); ++i)
{
if (Teams[i].m_iPlayerCount)
++numScores;
}
int scoreX = (twod->GetWidth() - scoreXWidth * (numScores - 1)) / 2;
for (size_t i = 0u; i < Teams.Size(); ++i)
{
if (!Teams[i].m_iPlayerCount)
continue;
char score[80];
mysnprintf(score, countof(score), "%d", Teams[i].m_iScore);
DrawText(twod, BigFont, Teams[i].GetTextColor(),
scoreX - BigFont->StringWidth(score)*CleanXfac/2, y, score,
DTA_CleanNoMove, true, TAG_DONE);
scoreX += scoreXWidth;
}
y += (BigFont->GetHeight() + 8) * CleanYfac;
}
const char* text_color = GStrings.GetString("SCORE_COLOR"),
*text_frags = GStrings.GetString(deathmatch ? "SCORE_FRAGS" : "SCORE_KILLS"),
*text_name = GStrings.GetString("SCORE_NAME"),
*text_delay = GStrings.GetString("SCORE_DELAY");
int col2 = (displayFont->StringWidth(text_color) + 16) * FontScale;
int col3 = col2 + (displayFont->StringWidth(text_frags) + 16) * FontScale;
int col4 = col3 + maxScoreWidth * FontScale;
int col5 = col4 + (maxNameWidth + 16) * FontScale;
int x = (twod->GetWidth() >> 1) - (((displayFont->StringWidth(text_delay) * FontScale) + col5) >> 1);
//HU_DrawFontScaled(x, y, color, text_color);
HU_DrawFontScaled(x + col2, y, color, text_frags);
HU_DrawFontScaled(x + col4, y, color, text_name);
HU_DrawFontScaled(x + col5, y, color, text_delay);
y += height + 6 * CleanYfac;
bottom -= height;
for (int i = 0; i < MAXPLAYERS && y <= bottom; ++i)
{
if (!playeringame[sortedPlayers[i] - players])
continue;
HU_DrawPlayer(sortedPlayers[i], player == sortedPlayers[i], x, col2, col3, col4, col5, maxNameWidth, y, yPadding, lineHeight);
y += lineHeight + CleanYfac;
}
}
//==========================================================================
//
// HU_DrawTimeRemaining
//
//==========================================================================
static void HU_DrawTimeRemaining (int y)
{
if (deathmatch && timelimit && gamestate == GS_LEVEL)
{
char str[80];
int timeleft = (int)(timelimit * TICRATE * 60) - primaryLevel->maptime;
int hours, minutes, seconds;
if (timeleft < 0)
timeleft = 0;
hours = timeleft / (TICRATE * 3600);
timeleft -= hours * TICRATE * 3600;
minutes = timeleft / (TICRATE * 60);
timeleft -= minutes * TICRATE * 60;
seconds = timeleft / TICRATE;
if (hours)
mysnprintf (str, countof(str), "Level ends in %d:%02d:%02d", hours, minutes, seconds);
else
mysnprintf (str, countof(str), "Level ends in %d:%02d", minutes, seconds);
HU_DrawFontScaled(twod->GetWidth() / 2 - displayFont->StringWidth(str) / 2 * FontScale, y, CR_GRAY, str);
}
}
//==========================================================================
//
// HU_DrawPlayer
//
//==========================================================================
static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2, int col3, int col4, int col5, int maxnamewidth, int y, int ypadding, int height)
{
int color;
char str[80];
if (highlight)
{
// The teamplay mode uses colors to show teams, so we need some
// other way to do highlighting. And it may as well be used for
// all modes for the sake of consistancy.
Dim(twod, MAKERGB(200,245,255), 0.125f, col1 - 12*FontScale, y - 1, col5 + (maxnamewidth + 24)*FontScale, height + 2);
}
col2 += col1;
col3 += col1;
col4 += col1;
col5 += col1;
color = HU_GetRowColor(player, highlight);
HU_DrawColorBar(col1, y, height, (int)(player - players));
mysnprintf (str, countof(str), "%d", deathmatch ? player->fragcount : player->killcount);
HU_DrawFontScaled(col2, y + ypadding, color, player->playerstate == PST_DEAD && !deathmatch ? "DEAD" : str);
auto icon = FSetTextureID(player->mo->IntVar(NAME_ScoreIcon));
if (icon.isValid())
{
DrawTexture(twod, icon, false, col3, y,
DTA_CleanNoMove, true,
TAG_DONE);
}
HU_DrawFontScaled(col4, y + ypadding, color, player->userinfo.GetName());
mysnprintf(str, countof(str), "%u", ClientStates[player - players].AverageLatency);
HU_DrawFontScaled(col5, y + ypadding, color, str);
const int team = player->userinfo.GetTeam();
if (team != TEAM_NONE && teamplay && Teams[team].GetLogo().IsNotEmpty ())
{
auto pic = TexMan.GetGameTextureByName(Teams[player->userinfo.GetTeam()].GetLogo().GetChars ());
DrawTexture(twod, pic, col1 - (pic->GetDisplayWidth() + 2) * CleanXfac, y,
DTA_CleanNoMove, true, TAG_DONE);
}
}
//==========================================================================
//
// HU_DrawColorBar
//
//==========================================================================
void HU_DrawColorBar(int x, int y, int height, int playernum)
{
float h, s, v, r, g, b;
D_GetPlayerColor (playernum, &h, &s, &v, NULL);
HSVtoRGB (&r, &g, &b, h, s, v);
ClearRect(twod, x, y, x + 24*FontScale, y + height, -1,
MAKEARGB(255,clamp(int(r*255.f),0,255),
clamp(int(g*255.f),0,255),
clamp(int(b*255.f),0,255)));
}
//==========================================================================
//
// HU_GetRowColor
//
//==========================================================================
int HU_GetRowColor(player_t *player, bool highlight)
{
if (teamplay && deathmatch)
{
if (FTeam::IsValid (player->userinfo.GetTeam()))
return Teams[player->userinfo.GetTeam()].GetTextColor();
else
return CR_GREY;
}
else
{
if (!highlight)
{
if (demoplayback && player == &players[consoleplayer])
{
return CR_GOLD;
}
else
{
return deathmatch ? sb_deathmatch_otherplayercolor : sb_cooperative_otherplayercolor;
}
}
else
{
return deathmatch ? sb_deathmatch_yourplayercolor : sb_cooperative_yourplayercolor;
}
VMValue params[] = { (DObject*)StatusBar, player, ticFrac };
VMCall(func, params, countof(params), nullptr, 0);
}
}

View file

@ -42,16 +42,8 @@ void CT_Drawer (void);
// [RH] Draw deathmatch scores
void HU_DrawScores(player_t* me);
void HU_GetPlayerWidths(int& maxNameWidth, int& maxScoreWidth, int& maxIconHeight);
void HU_DrawColorBar(int x, int y, int height, int playernum);
int HU_GetRowColor(player_t *player, bool hightlight);
void HU_DrawScores(int me, double ticFrac);
extern bool SB_ForceActive;
// Sorting routines
int comparepoints(const void *arg1, const void *arg2);
int compareteams(const void *arg1, const void *arg2);
#endif

View file

@ -2369,6 +2369,13 @@ DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, GetUDMFString, ZGetUDMFString)
ACTION_RETURN_STRING(GetUDMFString(self, type, index, key));
}
DEFINE_ACTION_FUNCTION(FLevelLocals, PlayerNum)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
PARAM_POINTER(player, player_t);
ACTION_RETURN_INT(self->PlayerNum(player));
}
DEFINE_ACTION_FUNCTION(FLevelLocals, GetChecksum)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);

View file

@ -1219,69 +1219,6 @@ DObject* WI_Start(wbstartstruct_t *wbstartstruct)
//
//====================================================================
DEFINE_ACTION_FUNCTION(DStatusScreen, GetPlayerWidths)
{
PARAM_PROLOGUE;
int maxnamewidth, maxscorewidth, maxiconheight;
HU_GetPlayerWidths(maxnamewidth, maxscorewidth, maxiconheight);
if (numret > 0) ret[0].SetInt(maxnamewidth);
if (numret > 1) ret[1].SetInt(maxscorewidth);
if (numret > 2) ret[2].SetInt(maxiconheight);
return min(numret, 3);
}
//====================================================================
//
//
//
//====================================================================
DEFINE_ACTION_FUNCTION(DStatusScreen, GetRowColor)
{
PARAM_PROLOGUE;
PARAM_POINTER(p, player_t);
PARAM_BOOL(highlight);
ACTION_RETURN_INT(HU_GetRowColor(p, highlight));
}
//====================================================================
//
//
//
//====================================================================
DEFINE_ACTION_FUNCTION(DStatusScreen, GetSortedPlayers)
{
PARAM_PROLOGUE;
PARAM_POINTER(array, TArray<int>);
PARAM_BOOL(teamplay);
player_t *sortedplayers[MAXPLAYERS];
// Sort all players
for (int i = 0; i < MAXPLAYERS; i++)
{
sortedplayers[i] = &players[i];
}
if (teamplay)
qsort(sortedplayers, MAXPLAYERS, sizeof(player_t *), compareteams);
else
qsort(sortedplayers, MAXPLAYERS, sizeof(player_t *), comparepoints);
array->Resize(MAXPLAYERS);
for (unsigned i = 0; i < MAXPLAYERS; i++)
{
(*array)[i] = int(sortedplayers[i] - players);
}
return 0;
}
//====================================================================
//
//
//
//====================================================================
DEFINE_FIELD_X(WBPlayerStruct, wbplayerstruct_t, skills);
DEFINE_FIELD_X(WBPlayerStruct, wbplayerstruct_t, sitems);
DEFINE_FIELD_X(WBPlayerStruct, wbplayerstruct_t, ssecret);

View file

@ -308,6 +308,7 @@ version "4.15.1"
#include "zscript/ui/statusbar/sbarinfowrapper.zs"
#include "zscript/ui/statusbar/statusbar.zs"
#include "zscript/ui/statusbar/strife_sbar.zs"
#include "zscript/ui/statusbar/scoreboard.zs"
#include "zscript/ui/intermission.zs"

View file

@ -3002,6 +3002,8 @@ struct PlayerInfo native play // self is what internally is known as player_t
native clearscope bool GetClassicFlight() const;
native void SendPitchLimits();
native clearscope bool HasWeaponsInSlot(int slot) const;
native clearscope int GetAverageLatency() const;
// The actual implementation is on PlayerPawn where it can be overridden. Use that directly in the future.
deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(PlayerInfo activator, class<PlayerPawn> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> exitFlash = "TeleportFog")

View file

@ -1,6 +1,8 @@
// for flag changer functions.
const FLAG_NO_CHANGE = -1;
const MAXPLAYERS = 64;
const TEAM_NONE = 255;
const TEAM_MAXIMUM = 16;
enum EStateUseFlags
{

View file

@ -587,6 +587,8 @@ struct LevelLocals native
native clearscope int ActorOnLineSide(Actor mo, Line l) const;
native clearscope int BoxOnLineSide(Vector2 pos, double radius, Line l) const;
native clearscope int PlayerNum(PlayerInfo player) const;
native String GetChecksum() const;
native void ChangeSky(TextureID sky1, TextureID sky2 );

View file

@ -578,6 +578,11 @@ struct Screen native
native static void ClearStencil();
native static void SetTransform(Shape2DTransform transform);
native static void ClearTransform();
native static double GetTextureWidth(TextureID texture, bool animated = false);
native static double GetTextureHeight(TextureID texture, bool animated = false);
native static double GetTextureLeftOffset(TextureID texture, bool animated = false);
native static double GetTextureTopOffset(TextureID texture, bool animated = false);
}
struct Font native
@ -948,6 +953,12 @@ struct StringStruct native unsafe(internal)
native void StripLeft(String junk = "");
native void StripRight(String junk = "");
native void StripLeftRight(String junk = "");
native int Compare(String other) const; // strcmp
native int CompareNoCase(String other) const; // stricmp
native bool IsEmpty() const; // strcmp
native bool IsNotEmpty() const; // stricmp
}
struct Translation version("2.4")

View file

@ -967,7 +967,40 @@ class StatusScreen : ScreenJob abstract version("2.5")
protected virtual void updateStats() {}
protected virtual void drawStats() {}
native static int, int, int GetPlayerWidths();
native static Color GetRowColor(PlayerInfo player, bool highlight);
native static void GetSortedPlayers(in out Array<int> sorted, bool teamplay);
static int, int, int GetPlayerWidths()
{
int maxNameWidth;
int maxScoreWidth;
int maxIconHeight;
StatusBar.Scoreboard_GetPlayerWidths(maxNameWidth, maxScoreWidth, maxIconHeight);
return maxNameWidth, maxScoreWidth, maxIconHeight;
}
static Color GetRowColor(PlayerInfo player, bool highlight)
{
return StatusBar.Scoreboard_GetRowColor(player, highlight);
}
static void GetSortedPlayers(in out Array<int> sorted, bool teamplay)
{
sorted.clear();
for(int i = 0; i < MAXPLAYERS; i++)
{
if(playeringame[i])
{
sorted.Push(i);
}
}
if(teamplay)
{
StatusBar.Scoreboard_SortPlayers(sorted, BaseStatusBar.Scoreboard_CompareByTeams);
}
else
{
StatusBar.Scoreboard_SortPlayers(sorted, BaseStatusBar.Scoreboard_CompareByPoints);
}
}
}

View file

@ -0,0 +1,449 @@
extend class BaseStatusBar
{
Font scoreboardFont;
virtual void InitScoreboard()
{
scoreboardFont = NewSmallFont;
}
static clearscope int Scoreboard_CompareByTeams(int playerA, int playerB)
{
// Compare first by teams, then by frags, then by name.
PlayerInfo p1 = players[playerA];
PlayerInfo p2 = players[playerB];
int diff = p1.GetTeam() - p2.GetTeam();
if(diff == 0)
{
diff = p2.fragcount - p1.fragcount;
if(diff == 0)
{
diff = p1.GetUserName().CompareNoCase(p2.GetUserName());
}
}
return diff;
}
static clearscope int Scoreboard_CompareByPoints(int playerA, int playerB)
{
// Compare first by frags/kills, then by name.
PlayerInfo p1 = players[playerA];
PlayerInfo p2 = players[playerB];
int diff = deathmatch ? (p2.fragcount - p1.fragcount) : (p2.killcount - p1.killcount);
if(diff == 0)
{
diff = p1.GetUserName().CompareNoCase(p2.GetUserName());
}
return diff;
}
static void Scoreboard_SortPlayers(out Array<int> players, Function<clearscope int(int, int)> compareFunc)
{
Array<int> unsorted;
unsorted.Move(players);
players.Push(unsorted[0]);
for(int i = 1; i < unsorted.Size(); i++)
{
bool inserted = false;
for(int j = 0; j < players.Size(); j++)
{
if(compareFunc.Call(players[j], unsorted[i]) > 0)
{
players.Insert(j, unsorted[i]);
inserted = true;
break;
}
}
if(!inserted)
{
players.Push(unsorted[i]);
}
}
}
virtual void Scoreboard_DrawScores(int playerNum, double ticFrac)
{
if(deathmatch)
{
if(teamplay)
{
if(!sb_teamdeathmatch_enable)
return;
}
else if(!sb_deathmatch_enable)
{
return;
}
}
else if(!multiplayer || !sb_cooperative_enable)
{
return;
}
if(!scoreboardFont) InitScoreboard();
PlayerInfo player = players[playernum];
if(player.camera && player.camera.player)
{
player = player.camera.player;
playerNum = Level.PlayerNum(player);
}
Array<int> sortedPlayers;
for(int i = 0; i < MAXPLAYERS; i++)
{
if(playeringame[i])
{
sortedPlayers.Push(i);
}
}
/*
console.printf("SortedPlayers was [");
foreach(p : sortedPlayers)
{
console.printf("%d", p);
}
console.printf("]");
*/
if(teamplay && deathmatch)
{
Scoreboard_SortPlayers(sortedPlayers, Scoreboard_CompareByTeams);
}
else
{
Scoreboard_SortPlayers(sortedPlayers, Scoreboard_CompareByPoints);
}
/*
console.printf("SortedPlayers is now [");
foreach(p : sortedPlayers)
{
console.printf("%d", p);
}
console.printf("]");
*/
int numPlayers = sortedPlayers.size();
int FontScale = max(screen.GetHeight() / 400, 1);
if(numPlayers > 8)
FontScale = int(ceil(FontScale * 0.75));
Scoreboard_DoDrawScores(player, sortedPlayers, FontScale, ticFrac);
}
//==========================================================================
//
// HU_DrawFontScaled
//
//==========================================================================
void Scoreboard_DrawFontScaled(double x, double y, Color color, String text, int FontScale)
{
screen.DrawText(scoreboardFont, color, x / FontScale, y / FontScale, text, DTA_VirtualWidth, screen.GetWidth() / FontScale, DTA_VirtualHeight, screen.GetHeight() / FontScale);
}
//==========================================================================
//
// HU_DoDrawScores
//
//==========================================================================
virtual void Scoreboard_DoDrawScores(PlayerInfo player, Array<int> sortedPlayers, int FontScale, double ticFrac)
{
Color _color = sb_cooperative_headingcolor;
if(deathmatch)
{
if (teamplay)
_color = sb_teamdeathmatch_headingcolor;
else
_color = sb_deathmatch_headingcolor;
}
int maxNameWidth, maxScoreWidth, maxIconHeight;
Scoreboard_GetPlayerWidths(maxNameWidth, maxScoreWidth, maxIconHeight);
int height = scoreboardFont.GetHeight() * FontScale;
int lineHeight = max(height, maxIconHeight * CleanYfac);
int yPadding = (lineHeight - height + 1) / 2;
int bottom = GetTopOfStatusbar();
int y = max(48 * CleanYfac, (bottom - MAXPLAYERS * (height + CleanYfac + 1)) / 2);
Scoreboard_DrawTimeRemaining(bottom - height, FontScale);
Array<int> teamPlayerCounts;
Array<int> teamScores;
teamPlayerCounts.Resize(Teams.Size());
teamScores.Resize(Teams.Size());
if(teamplay && deathmatch)
{
y -= (BigFont.GetHeight() + 8) * CleanYfac;
int numTeams = 0;
for(int i = 0; i < MAXPLAYERS; ++i)
{
PlayerInfo p = players[sortedPlayers[i]];
if (playeringame[sortedPlayers[i]] && Team.IsValid(p.GetTeam()))
{
if (teamPlayerCounts[p.GetTeam()]++ == 0)
++numTeams;
teamScores[p.GetTeam()] += p.fragcount;
}
}
int scoreXWidth = screen.GetWidth() / max(8, numTeams);
int numScores = 0;
for(int i = 0; i < Teams.Size(); ++i)
{
if (teamPlayerCounts[i])
++numScores;
}
int scoreX = (screen.GetWidth() - scoreXWidth * (numScores - 1)) / 2;
for(int i = 0; i < Teams.Size(); ++i)
{
if (!teamPlayerCounts[i])
continue;
String score = String.Format("%d", teamScores[i]);
screen.DrawText(BigFont, Teams[i].GetTextColor(),
scoreX - BigFont.StringWidth(score)*CleanXfac/2, y, score,
DTA_CleanNoMove, true);
scoreX += scoreXWidth;
}
y += (BigFont.GetHeight() + 8) * CleanYfac;
}
String text_color = StringTable.Localize("$SCORE_COLOR"),
text_frags = StringTable.Localize(deathmatch ? "$SCORE_FRAGS" : "$SCORE_KILLS"),
text_name = StringTable.Localize("$SCORE_NAME"),
text_delay = StringTable.Localize("$SCORE_DELAY");
int col2 = (scoreboardFont.StringWidth(text_color) + 16) * FontScale;
int col3 = col2 + (scoreboardFont.StringWidth(text_frags) + 16) * FontScale;
int col4 = col3 + maxScoreWidth * FontScale;
int col5 = col4 + (maxNameWidth + 16) * FontScale;
int x = (screen.GetWidth() >> 1) - (((scoreboardFont.StringWidth(text_delay) * FontScale) + col5) >> 1);
//Scoreboard_DrawFontScaled(x, y, _color, text_color);
Scoreboard_DrawFontScaled(x + col2, y, _color, text_frags, FontScale);
Scoreboard_DrawFontScaled(x + col4, y, _color, text_name, FontScale);
Scoreboard_DrawFontScaled(x + col5, y, _color, text_delay, FontScale);
y += height + 6 * CleanYfac;
bottom -= height;
for(int i = 0; i < sortedPlayers.Size() && y <= bottom; ++i)
{
Scoreboard_DrawPlayer(players[sortedPlayers[i]], Level.PlayerNum(player) == sortedPlayers[i], x, col2, col3, col4, col5, maxNameWidth, y, yPadding, lineHeight, FontScale);
y += lineHeight + CleanYfac;
}
}
//==========================================================================
//
// HU_DrawTimeRemaining
//
//==========================================================================
void Scoreboard_DrawTimeRemaining(int y, int FontScale)
{
if(deathmatch && timelimit && gamestate == GS_LEVEL)
{
int timeleft = int(timelimit * GameTicRate * 60) - Level.maptime;
int hours, minutes, seconds;
if(timeleft < 0) timeleft = 0;
hours = timeleft / (GameTicRate * 3600);
timeleft -= hours * GameTicRate * 3600;
minutes = timeleft / (GameTicRate * 60);
timeleft -= minutes * GameTicRate * 60;
seconds = timeleft / GameTicRate;
String str;
if(hours)
{
str = String.Format("Level ends in %d:%02d:%02d", hours, minutes, seconds);
}
else
{
str = String.Format("Level ends in %d:%02d", minutes, seconds);
}
Scoreboard_DrawFontScaled(screen.GetWidth() / 2 - scoreboardFont.StringWidth(str) / 2 * FontScale, y, Font.CR_GRAY, str, FontScale);
}
}
//==========================================================================
//
// HU_DrawPlayer
//
//==========================================================================
void Scoreboard_DrawPlayer(PlayerInfo player, bool highlight, int col1, int col2, int col3, int col4, int col5, int maxnamewidth, int y, int ypadding, int height, int FontScale)
{
String str;
if(highlight)
{
// The teamplay mode uses colors to show teams, so we need some
// other way to do highlighting. And it may as well be used for
// all modes for the sake of consistancy.
screen.Dim(Color(200,245,255), 0.125f, col1 - 12 * FontScale, y - 1, col5 + (maxnamewidth + 24) * FontScale, height + 2);
}
col2 += col1;
col3 += col1;
col4 += col1;
col5 += col1;
Color _color = Scoreboard_GetRowColor(player, highlight);
Scoreboard_DrawColorBar(col1, y, height, player, FontScale);
str = String.Format("%d", deathmatch ? player.fragcount : player.killcount);
Scoreboard_DrawFontScaled(col2, y + ypadding, _color, player.playerstate == PST_DEAD && !deathmatch ? "DEAD" : str, FontScale);
TextureID icon = player.mo.ScoreIcon;
if(icon.isValid())
{
screen.DrawTexture(icon, false, col3, y, DTA_CleanNoMove, true);
}
Scoreboard_DrawFontScaled(col4, y + ypadding, _color, player.GetUserName(), FontScale);
str = String.Format("%d", player.GetAverageLatency());
Scoreboard_DrawFontScaled(col5, y + ypadding, _color, str, FontScale);
int team = player.GetTeam();
if(team != TEAM_NONE && teamplay && Teams[team].GetLogoName().IsNotEmpty())
{
TextureID pic = Teams[team].GetLogo();
screen.DrawTexture(pic, col1 - (screen.GetTextureWidth(pic) + 2) * CleanXfac, y, DTA_CleanNoMove, true);
}
}
//==========================================================================
//
// HU_DrawColorBar
//
//==========================================================================
static void Scoreboard_DrawColorBar(int x, int y, int height, PlayerInfo player, int FontScale)
{
Screen.Clear(x, y, x + 24 * FontScale, y + height, player.GetDisplayColor());
}
//==========================================================================
//
// HU_GetRowColor
//
//==========================================================================
static Color Scoreboard_GetRowColor(PlayerInfo player, bool highlight)
{
if(teamplay && deathmatch)
{
if(Team.IsValid(player.GetTeam()))
{
Color teamColor = Teams[player.GetTeam()].GetTextColor();
return teamColor;
}
else
{
return Font.CR_GREY;
}
}
else
{
if(!highlight)
{
if(demoplayback && Level.PlayerNum(player) == consoleplayer)
{
return Font.CR_GOLD;
}
else
{
return deathmatch ? sb_deathmatch_otherplayercolor : sb_cooperative_otherplayercolor;
}
}
else
{
return deathmatch ? sb_deathmatch_yourplayercolor : sb_cooperative_yourplayercolor;
}
}
}
//==========================================================================
//
// HU_GetPlayerWidths
//
// Returns the widest player name and class icon.
//
//==========================================================================
void Scoreboard_GetPlayerWidths(int& maxNameWidth, int& maxScoreWidth, int& maxIconHeight)
{
if(!scoreboardFont) InitScoreboard();
maxNameWidth = scoreboardFont.StringWidth("Name");
maxScoreWidth = 0;
maxIconHeight = 0;
for (int i = 0; i < MAXPLAYERS; ++i)
{
if (!playeringame[i])
continue;
int width = scoreboardFont.StringWidth(players[i].GetUserName(16));
if (width > maxNameWidth)
maxNameWidth = width;
TextureID icon = players[i].mo.ScoreIcon;
if (icon.isValid())
{
width = int(screen.GetTextureWidth(icon) - screen.GetTextureLeftOffset(icon) + 2.5);
if (width > maxScoreWidth)
maxScoreWidth = width;
// The icon's top offset does not count toward its height, because
// zdoom.pk3's standard Hexen class icons are designed that way.
int height = int(screen.GetTextureHeight(icon) - screen.GetTextureTopOffset(icon) + 0.5);
if (height > maxIconHeight)
maxIconHeight = height;
}
}
}
}

View file

@ -214,6 +214,7 @@ class BaseStatusBar : StatusBarCore native
virtual void Init()
{
InitScoreboard();
}
virtual void Draw (int state, double TicFrac)