From e4310342bb803ceb4b4650a28adaabafc881d7c8 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 21 Jan 2018 22:17:06 +1100 Subject: [PATCH] Port player list widget to C. --- ClassicalSharp/2D/Widgets/PlayerListWidget.cs | 4 +- .../Rendering/Env/WeatherRenderer.cs | 2 +- src/Client/Entity.c | 6 +- src/Client/Entity.h | 4 + src/Client/String.c | 13 + src/Client/String.h | 1 + src/Client/Widgets.c | 381 ++++++++++++++++++ src/Client/Widgets.h | 6 +- 8 files changed, 411 insertions(+), 6 deletions(-) diff --git a/ClassicalSharp/2D/Widgets/PlayerListWidget.cs b/ClassicalSharp/2D/Widgets/PlayerListWidget.cs index 4603dbd8b..8103324df 100644 --- a/ClassicalSharp/2D/Widgets/PlayerListWidget.cs +++ b/ClassicalSharp/2D/Widgets/PlayerListWidget.cs @@ -41,7 +41,7 @@ namespace ClassicalSharp.Gui.Widgets { SortAndReposition(); overview = TextWidget.Create(game, "Connected players:", font) - .SetLocation(Anchor.Centre, Anchor.LeftOrTop, 0, 0); + .SetLocation(Anchor.Centre, Anchor.LeftOrTop, 0, 0); game.EntityEvents.TabListEntryAdded += TabEntryAdded; game.EntityEvents.TabListEntryRemoved += TabEntryRemoved; game.EntityEvents.TabListEntryChanged += TabEntryChanged; @@ -273,7 +273,7 @@ namespace ClassicalSharp.Gui.Widgets { void SortEntries() { if (namesCount == 0) return; if (classic) { - Array.Sort(IDs, textures, 0, namesCount, grpComparer); + Array.Sort(IDs, textures, 0, namesCount, comparer); return; } diff --git a/ClassicalSharp/Rendering/Env/WeatherRenderer.cs b/ClassicalSharp/Rendering/Env/WeatherRenderer.cs index c4066f6c8..ddff7ed11 100644 --- a/ClassicalSharp/Rendering/Env/WeatherRenderer.cs +++ b/ClassicalSharp/Rendering/Env/WeatherRenderer.cs @@ -86,7 +86,7 @@ namespace ClassicalSharp.Renderers { v.X = x2; v.Z = z2; v.U = 1; vertices[vCount++] = v; v.Y = y1; v.V = v1; vertices[vCount++] = v; - v.Z = z1; vertices[vCount++] = v; + v.Z = z1; vertices[vCount++] = v; v.Y = y2; v.V = v2; vertices[vCount++] = v; v.X = x1; v.Z = z2; v.U = 0; vertices[vCount++] = v; v.Y = y1; v.V = v1; vertices[vCount++] = v; diff --git a/src/Client/Entity.c b/src/Client/Entity.c index 29fdf031c..c6a4889fb 100644 --- a/src/Client/Entity.c +++ b/src/Client/Entity.c @@ -394,8 +394,12 @@ void Entities_DrawShadows(void) { Gfx_SetTexturing(false); } +bool TabList_Valid(EntityID id) { + return TabList_PlayerNames[id] > 0 || TabList_ListNames[id] > 0 || TabList_GroupNames[id] > 0; +} + bool TabList_Remove(EntityID id) { - if (TabList_PlayerNames[id] == 0 && TabList_ListNames[id] == 0 && TabList_GroupNames[id] == 0) return false; + if (!TabList_Valid(id)) return false; StringsBuffer_Remove(&TabList_Buffer, TabList_PlayerNames[id]); TabList_PlayerNames[id] = 0; StringsBuffer_Remove(&TabList_Buffer, TabList_ListNames[id]); TabList_ListNames[id] = 0; diff --git a/src/Client/Entity.h b/src/Client/Entity.h index 34fe1c25f..ad7327beb 100644 --- a/src/Client/Entity.h +++ b/src/Client/Entity.h @@ -123,8 +123,12 @@ UInt32 TabList_PlayerNames[TABLIST_MAX_NAMES]; UInt32 TabList_ListNames[TABLIST_MAX_NAMES]; UInt32 TabList_GroupNames[TABLIST_MAX_NAMES]; UInt8 TabList_GroupRanks[TABLIST_MAX_NAMES]; +bool TabList_Valid(EntityID id); bool TabList_Remove(EntityID id); void TabList_Set(EntityID id, STRING_PURE String* player, STRING_PURE String* list, STRING_PURE String* group, UInt8 rank); IGameComponent TabList_MakeComponent(void); +#define TabList_UNSAFE_GetPlayer(id) StringsBuffer_UNSAFE_Get(&TabList_Buffer, TabList_PlayerNames[id]); +#define TabList_UNSAFE_GetList(id) StringsBuffer_UNSAFE_Get(&TabList_Buffer, TabList_ListNames[id]); +#define TabList_UNSAFE_GetGroup(id) StringsBuffer_UNSAFE_Get(&TabList_Buffer, TabList_GroupNames[id]); #endif \ No newline at end of file diff --git a/src/Client/String.c b/src/Client/String.c index 88807396e..51f539ec6 100644 --- a/src/Client/String.c +++ b/src/Client/String.c @@ -246,6 +246,19 @@ Int32 String_IndexOfString(STRING_PURE String* str, STRING_PURE String* sub) { return -1; } +Int32 String_Compare(STRING_PURE String* a, STRING_PURE String* b) { + Int32 minLen = min(a->length, b->length); + Int32 i; + for (i = 0; i < minLen; i++) { + if (a->buffer[i] == b->buffer[i]) continue; + return a->buffer[i] < b->buffer[i] ? 1 : -1; + } + + /* all chars are equal here - same string, or a substring */ + if (a->length == b->length) return 0; + return a->length < b->length ? 1 : -1; +} + #define Convert_ControlCharsCount 32 UInt16 Convert_ControlChars[Convert_ControlCharsCount] = { diff --git a/src/Client/String.h b/src/Client/String.h index 39ab3ab3b..9bf4e9e72 100644 --- a/src/Client/String.h +++ b/src/Client/String.h @@ -70,6 +70,7 @@ void String_DeleteAt(STRING_TRANSIENT String* str, Int32 offset); Int32 String_IndexOfString(STRING_PURE String* str, STRING_PURE String* sub); #define String_ContainsString(str, sub) (String_IndexOfString(str, sub) >= 0) #define String_StartsWith(str, sub) (String_IndexOfString(str, sub) == 0) +Int32 String_Compare(STRING_PURE String* a, STRING_PURE String* b); UInt16 Convert_CP437ToUnicode(UInt8 c); UInt8 Convert_UnicodeToCP437(UInt16 c); diff --git a/src/Client/Widgets.c b/src/Client/Widgets.c index bc5bfd657..d5b5b715f 100644 --- a/src/Client/Widgets.c +++ b/src/Client/Widgets.c @@ -13,6 +13,7 @@ #include "Platform.h" #include "WordWrap.h" #include "ServerConnection.h" +#include "Event.h" void Widget_SetLocation(Widget* widget, Anchor horAnchor, Anchor verAnchor, Int32 xOffset, Int32 yOffset) { widget->HorAnchor = horAnchor; widget->VerAnchor = verAnchor; @@ -1533,3 +1534,383 @@ void InputWidget_Create(InputWidget* widget, FontDesc* font, STRING_REF String* widget->PrefixWidth = (UInt16)size.Width; widget->Base.Width = size.Width; widget->PrefixHeight = (UInt16)size.Height; widget->Base.Height = size.Height; } + + +#define GROUP_NAME_ID UInt16_MaxValue +#define LIST_COLUMN_PADDING 5 +#define LIST_BOUNDS_SIZE 10 +#define LIST_NAMES_PER_COLUMN 16 +PackedCol list_topCol = PACKEDCOL_CONST(0, 0, 0, 180); +PackedCol list_bottomCol = PACKEDCOL_CONST(50, 50, 50, 205); + +Texture PlayerListWidget_DrawName(PlayerListWidget* widget, STRING_PURE String* name) { + UInt8 tmpBuffer[String_BufferSize(STRING_SIZE)]; + String tmp; + if (Game_PureClassic) { + tmp = String_InitAndClearArray(tmpBuffer); + String_AppendColorless(&tmp, name); + } else { + tmp = *name; + } + + DrawTextArgs args; DrawTextArgs_Make(&args, &tmp, &widget->Font, !widget->Classic); + Texture tex = Drawer2D_MakeTextTexture(&args, 0, 0); + Drawer2D_ReducePadding_Tex(&tex, widget->Font.Size, 3); + return tex; +} + + +Int32 PlayerListWidget_HighlightedName(PlayerListWidget* widget, Int32 mouseX, Int32 mouseY) { + Int32 i; + for (i = 0; i < widget->NamesCount; i++) { + if (!Texture_IsValid(&widget->Textures[i]) || widget->IDs[i] == GROUP_NAME_ID) continue; + + Texture t = widget->Textures[i]; + if (Gui_Contains(t.X, t.Y, t.Width, t.Height, mouseX, mouseY)) return i; + } + return -1; +} + +void PlayerListWidget_GetNameUnder(PlayerListWidget* widget, Int32 mouseX, Int32 mouseY, STRING_TRANSIENT String* name) { + String_Clear(name); + Int32 i = PlayerListWidget_HighlightedName(widget, mouseX, mouseY); + if (i == -1) return; + + String player = TabList_UNSAFE_GetPlayer(widget->IDs[i]); + String_AppendString(name, &player); +} + +void PlayerListWidget_UpdateTableDimensions(PlayerListWidget* widget) { + Int32 width = widget->XMax - widget->XMin, height = widget->YHeight; + widget->Base.X = (widget->XMin ) - LIST_BOUNDS_SIZE; + widget->Base.Y = (Game_Height / 2 - height / 2) - LIST_BOUNDS_SIZE; + widget->Base.Width = width + LIST_BOUNDS_SIZE * 2; + widget->Base.Height = height + LIST_BOUNDS_SIZE * 2; +} + +Int32 PlayerListWidget_GetColumnWidth(PlayerListWidget* widget, Int32 column) { + Int32 i = column * LIST_NAMES_PER_COLUMN; + Int32 maxWidth = 0; + Int32 maxIndex = min(widget->NamesCount, i + LIST_NAMES_PER_COLUMN); + + for (; i < maxIndex; i++) { + maxWidth = max(maxWidth, widget->Textures[i].Width); + } + return maxWidth + LIST_COLUMN_PADDING + widget->ElementOffset; +} + +Int32 PlayerListWidget_GetColumnHeight(PlayerListWidget* widget, Int32 column) { + Int32 i = column * LIST_NAMES_PER_COLUMN; + Int32 total = 0; + Int32 maxIndex = min(widget->NamesCount, i + LIST_NAMES_PER_COLUMN); + + for (; i < maxIndex; i++) { + total += widget->Textures[i].Height + 1; + } + return total; +} + +void PlayerListWidget_SetColumnPos(PlayerListWidget* widget, Int32 column, Int32 x, Int32 y) { + Int32 i = column * LIST_NAMES_PER_COLUMN; + Int32 maxIndex = min(widget->NamesCount, i + LIST_NAMES_PER_COLUMN); + + for (; i < maxIndex; i++) { + Texture tex = widget->Textures[i]; + tex.X = (Int16)x; tex.Y = (Int16)(y - 10); + + y += tex.Height + 1; + /* offset player names a bit, compared to group name */ + if (!widget->Classic && widget->IDs[i] != GROUP_NAME_ID) { + tex.X += (Int16)widget->ElementOffset; + } + widget->Textures[i] = tex; + } +} + +void PlayerListWidget_RepositionColumns(PlayerListWidget* widget) { + Int32 width = 0, centreX = Game_Width / 2; + widget->YHeight = 0; + + Int32 col, columns = Math_CeilDiv(widget->NamesCount, LIST_NAMES_PER_COLUMN); + for (col = 0; col < columns; col++) { + width += PlayerListWidget_GetColumnWidth(widget, col); + Int32 colHeight = PlayerListWidget_GetColumnHeight(widget, col); + widget->YHeight = nax(colHeight, widget->YHeight); + } + + if (width < 480) width = 480; + widget->XMin = centreX - width / 2; + widget->XMax = centreX + width / 2; + + Int32 x = widget->XMin, y = Game_Height / 2 - widget->YHeight / 2; + for (col = 0; col < columns; col++) { + PlayerListWidget_SetColumnPos(widget, col, x, y); + x += PlayerListWidget_GetColumnWidth(widget, col); + } +} + +void PlayerListWidget_RecalcYOffset(PlayerListWidget* widget) { + Int32 yPosition = Game_Height / 4 - widget->Base.Height / 2; + widget->Base.YOffset = -max(0, yPosition); +} + +void PlayerListWidget_Reposition(Widget* elem) { + Int32 oldX = elem->X, oldY = elem->Y; + Widget_DoReposition(elem); + PlayerListWidget* widget = (PlayerListWidget*)elem; + + Int32 i; + for (i = 0; i < widget->NamesCount; i++) { + widget->Textures[i].X += elem->X - oldX; + widget->Textures[i].Y += elem->Y - oldY; + } +} + +void PlayerListWidget_AddName(PlayerListWidget* widget, EntityID id, Int32 index) { + /* insert at end of list */ + if (index == -1) { index = widget->NamesCount; widget->NamesCount++; } + + String name = TabList_UNSAFE_GetList(id); + widget->IDs[index] = id; + widget->Textures[index] = PlayerListWidget_DrawName(widget, &name); +} + +void PlayerListWidget_DeleteAt(PlayerListWidget* widget, Int32 i) { + Texture tex = widget->Textures[i]; + Gfx_DeleteTexture(&tex.ID); + + for (; i < widget->NamesCount - 1; i++) { + widget->IDs[i] = widget->IDs[i + 1]; + widget->Textures[i] = widget->Textures[i + 1]; + } + + widget->IDs[widget->NamesCount] = 0; + widget->Textures[widget->NamesCount] = Texture_MakeInvalid(); + widget->NamesCount--; +} + +void PlayerListWidget_DeleteGroup(PlayerListWidget* widget, Int32* i) { + PlayerListWidget_DeleteAt(widget, *i); + (*i)--; +} + +void PlayerListWidget_AddGroup(PlayerListWidget* widget, UInt16 id, Int32* index) { + Int32 i; + for (i = Array_NumElements(widget->IDs) - 1; i > (*index); i--) { + widget->IDs[i] = widget->IDs[i - 1]; + widget->Textures[i] = widget->Textures[i - 1]; + } + + String group = TabList_UNSAFE_GetGroup(id); + widget->IDs[*index] = GROUP_NAME_ID; + widget->Textures[*index] = PlayerListWidget_DrawName(widget, &group); + + (*index)++; + widget->NamesCount++; +} + +Int32 PlayerListWidget_GetGroupCount(PlayerListWidget* widget, UInt16 id, Int32 idx) { + String group = TabList_UNSAFE_GetGroup(id); + Int32 count = 0; + + while (idx < widget->NamesCount) { + String curGroup = TabList_UNSAFE_GetGroup(widget->IDs[idx]); + if (!String_CaselessEquals(&group, &curGroup)) return count; + idx++; count++; + } + return count; +} + +Int32 PlayerListWidget_PlayerCompare(UInt16 x, UInt16 y) { + UInt8 xRank = TabList_GroupRanks[x]; + UInt8 yRank = TabList_GroupRanks[y]; + if (xRank != yRank) return (xRank < yRank ? 1 : -1); + + UInt8 xNameBuffer[String_BufferSize(STRING_SIZE)]; + String xName = String_InitAndClearArray(xNameBuffer); + String xNameRaw = TabList_UNSAFE_GetList(x); + String_AppendColorless(&xName, &xNameRaw); + + UInt8 yNameBuffer[String_BufferSize(STRING_SIZE)]; + String yName = String_InitAndClearArray(yNameBuffer); + String yNameRaw = TabList_UNSAFE_GetList(y); + String_AppendColorless(&yName, &yNameRaw); + + return String_Compare(&xName, &yName); +} + +Int32 PlayerListWidget_GroupCompare(UInt16 x, UInt16 y) { + UInt8 xGroupBuffer[String_BufferSize(STRING_SIZE)]; + String xGroup = String_InitAndClearArray(xGroupBuffer); + String xGroupRaw = TabList_UNSAFE_GetGroup(x); + String_AppendColorless(&xGroup, &xGroupRaw); + + UInt8 yGroupBuffer[String_BufferSize(STRING_SIZE)]; + String yGroup = String_InitAndClearArray(yGroupBuffer); + String yGroupRaw = TabList_UNSAFE_GetGroup(y); + String_AppendColorless(&yGroup, &yGroupRaw); + + return String_Compare(&xGroup, &yGroup); +} + +PlayerListWidget* List_SortObj; +Int32 (*List_SortCompare)(UInt16 x, UInt16 y); +void PlayerListWidget_QuickSort(Int32 left, Int32 right) { + Texture* values = List_SortObj->Textures; Texture value; + UInt16* keys = List_SortObj->IDs; UInt16 key; + while (left < right) { + Int32 i = left, j = right; + UInt16 pivot = keys[(i + j) / 2]; + + /* partition the list */ + while (i <= j) { + while (List_SortCompare(pivot, keys[i]) > 0) i++; + while (List_SortCompare(pivot, keys[j]) < 0) j--; + QuickSort_Swap_KV_Maybe(); + } + /* recurse into the smaller subset */ + QuickSort_Recurse(PlayerListWidget_QuickSort) + } +} + +void PlayerListWidget_SortEntries(PlayerListWidget* widget) { + if (widget->NamesCount == 0) return; + List_SortObj = widget; + if (widget->Classic) { + List_SortCompare = PlayerListWidget_PlayerCompare; + PlayerListWidget_QuickSort(0, widget->NamesCount - 1); + return; + } + + /* Sort the list into groups */ + Int32 i; + for (i = 0; i < widget->NamesCount; i++) { + if (widget->IDs[i] != GROUP_NAME_ID) continue; + PlayerListWidget_DeleteGroup(widget, &i); + } + List_SortCompare = PlayerListWidget_GroupCompare; + PlayerListWidget_QuickSort(0, widget->NamesCount - 1); + + /* Sort the entries in each group */ + i = 0; + List_SortCompare = PlayerListWidget_PlayerCompare; + while (i < widget->NamesCount) { + UInt16 id = widget->IDs[i]; + PlayerListWidget_AddGroup(widget, id, &i); + Int32 count = PlayerListWidget_GetGroupCount(widget, id, i); + PlayerListWidget_QuickSort(i, i + (count - 1)); + i += count; + } +} + +void PlayerListWidget_SortAndReposition(PlayerListWidget* widget) { + PlayerListWidget_SortEntries(widget); + PlayerListWidget_RepositionColumns(widget); + PlayerListWidget_UpdateTableDimensions(widget); + PlayerListWidget_RecalcYOffset(widget); + PlayerListWidget_Reposition(widget); +} + +void PlayerListWidget_TabEntryAdded(void* obj, EntityID id) { + PlayerListWidget* widget = (PlayerListWidget*)obj; + PlayerListWidget_AddName(widget, id, -1); + PlayerListWidget_SortAndReposition(widget); +} + +void PlayerListWidget_TabEntryChanged(void* obj, EntityID id) { + PlayerListWidget* widget = (PlayerListWidget*)obj; + Int32 i; + for (i = 0; i < widget->NamesCount; i++) { + if (widget->IDs[i] != id) continue; + + Texture tex = widget->Textures[i]; + Gfx_DeleteTexture(&tex.ID); + PlayerListWidget_AddName(widget, id, i); + PlayerListWidget_SortAndReposition(widget); + return; + } +} + +void PlayerListWidget_TabEntryRemoved(void* obj, EntityID id) { + PlayerListWidget* widget = (PlayerListWidget*)obj; + Int32 i; + for (i = 0; i < widget->NamesCount; i++) { + if (widget->IDs[i] != id) continue; + PlayerListWidget_DeleteAt(widget, i); + PlayerListWidget_SortAndReposition(widget); + return; + } +} + +void PlayerListWidget_Init(GuiElement* elem) { + PlayerListWidget* widget = (PlayerListWidget*)elem; + Int32 id; + for (id = 0; id < TABLIST_MAX_NAMES; id++) { + if (!TabList_Valid((EntityID)id)) continue; + PlayerListWidget_AddName(widget, (EntityID)id, -1); + } + PlayerListWidget_SortAndReposition(widget); + + String msg = String_FromConst("Connected players:"); + TextWidget_Create(&widget->Overview, &msg, &widget->Font); + Widget_SetLocation(&widget->Overview.Base, ANCHOR_CENTRE, ANCHOR_LEFT_OR_TOP, 0, 0); + + Event_RegisterEntityID(&TabListEvents_Added, widget, PlayerListWidget_TabEntryAdded); + Event_RegisterEntityID(&TabListEvents_Changed, widget, PlayerListWidget_TabEntryChanged); + Event_RegisterEntityID(&TabListEvents_Removed, widget, PlayerListWidget_TabEntryRemoved); +} + +void PlayerListWidget_Render(GuiElement* elem, Real64 delta) { + PlayerListWidget* widget = (PlayerListWidget*)elem; + Widget* elemW = &widget->Base; + TextWidget* overview = &widget->Overview; + + Gfx_SetTexturing(false); + Int32 offset = overview->Base.Height + 10; + Int32 height = max(300, elemW->Height + overview->Base.Height); + GfxCommon_Draw2DGradient(elemW->X, elemW->Y - offset, elemW->Width, elemW->Height, list_topCol, list_bottomCol); + + Gfx_SetTexturing(true); + overview->Base.YOffset = elemW->Y - offset + 5; + overview->Base.Reposition(&overview->Base); + overview->Base.Base.Render(&overview->Base.Base, delta); + + Int32 i, highlightedI = PlayerListWidget_HighlightedName(widget, Mouse_X, Mouse_Y); + for (i = 0; i < widget->NamesCount; i++) { + if (!Texture_IsValid(&widget->Textures[i])) continue; + + Texture tex = widget->Textures[i]; + if (i == highlightedI) tex.X += 4; + Texture_Render(&tex); + } +} + +void PlayerListWidget_Free(GuiElement* elem) { + PlayerListWidget* widget = (PlayerListWidget*)elem; + Int32 i; + for (i = 0; i < widget->NamesCount; i++) { + Gfx_DeleteTexture(&widget->Textures[i].ID); + } + + elem = &widget->Overview.Base.Base; + elem->Free(elem); + + Event_UnregisterEntityID(&TabListEvents_Added, widget, PlayerListWidget_TabEntryAdded); + Event_UnregisterEntityID(&TabListEvents_Changed, widget, PlayerListWidget_TabEntryChanged); + Event_UnregisterEntityID(&TabListEvents_Removed, widget, PlayerListWidget_TabEntryRemoved); +} + +void PlayerListWidget_Create(PlayerListWidget* widget, FontDesc* font, bool classic) { + Widget_Init(&widget->Base); + widget->NamesCount = 0; + + widget->Base.Base.Init = PlayerListWidget_Init; + widget->Base.Base.Free = PlayerListWidget_Free; + widget->Base.Reposition = PlayerListWidget_Reposition; + + widget->Base.HorAnchor = ANCHOR_CENTRE; + widget->Base.VerAnchor = ANCHOR_CENTRE; + widget->Font = *font; + widget->Classic = classic; + widget->ElementOffset = classic ? 0 : 10; +} \ No newline at end of file diff --git a/src/Client/Widgets.h b/src/Client/Widgets.h index 07a7fd019..163faed15 100644 --- a/src/Client/Widgets.h +++ b/src/Client/Widgets.h @@ -200,10 +200,12 @@ typedef struct PlayerListWidget_ { UInt16 NamesCount, ElementOffset; Int32 XMin, XMax, YHeight; bool Classic; + TextWidget Overview; UInt16 IDs[TABLIST_MAX_NAMES * 2]; Texture Textures[TABLIST_MAX_NAMES * 2]; } PlayerListWidget; + void PlayerListWidget_Create(PlayerListWidget* widget, FontDesc* font, bool classic); -String* PlayerListWidget_GetNameUnder(Int32 mouseX, Int32 mouseY); -void PlayerListWidget_RecalcYOffset(void); +void PlayerListWidget_GetNameUnder(PlayerListWidget* widget, Int32 mouseX, Int32 mouseY, STRING_TRANSIENT String* name); +void PlayerListWidget_RecalcYOffset(PlayerListWidget* widget); #endif \ No newline at end of file