From 01d2b3e3bc074b478886cc63ed66157ff1f44ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 26 Jun 2024 22:42:43 +0200 Subject: [PATCH 01/26] Highlight hovered and active color picker scrollbar handle Add visual feedback to color picker scrollbar handle being hovered and active. --- src/game/client/ui.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/game/client/ui.cpp b/src/game/client/ui.cpp index 16830838ce7..4dda005913e 100644 --- a/src/game/client/ui.cpp +++ b/src/game/client/ui.cpp @@ -1381,19 +1381,20 @@ float CUi::DoScrollbarH(const void *pId, const CUIRect *pRect, float Current, co } // render + const ColorRGBA HandleColor = ms_ScrollBarColorFunction.GetColor(CheckActiveItem(pId), HotItem() == pId); if(pColorInner) { CUIRect Slider; Handle.VMargin(-2.0f, &Slider); Slider.HMargin(-3.0f, &Slider); - Slider.Draw(ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), IGraphics::CORNER_ALL, 5.0f); + Slider.Draw(ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f).Multiply(HandleColor), IGraphics::CORNER_ALL, 5.0f); Slider.Margin(2.0f, &Slider); - Slider.Draw(*pColorInner, IGraphics::CORNER_ALL, 3.0f); + Slider.Draw(pColorInner->Multiply(HandleColor), IGraphics::CORNER_ALL, 3.0f); } else { Rail.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, Rail.h / 2.0f); - Handle.Draw(ms_ScrollBarColorFunction.GetColor(CheckActiveItem(pId), HotItem() == pId), IGraphics::CORNER_ALL, Handle.h / 2.0f); + Handle.Draw(HandleColor, IGraphics::CORNER_ALL, Handle.h / 2.0f); } return ReturnValue; From abf8fac568f50227ce189587b67eac4dc38b18bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 26 Jun 2024 23:11:56 +0200 Subject: [PATCH 02/26] Refactor HSLA scrollbar color picker rendering Rename `CMenus::RenderHSLScrollbars` function to `RenderHslaScrollbars`. Pass darkest light value as `float` to the `RenderHslaScrollbars` function to add support for different values, instead of enabling the clamping with a `bool` parameter. Previously, this parameter was always set to `true`, as this type of color picker is only used for skins. Remove unnecessarily complicated code for rendering unclamped lighting scrollbar. The code can be simplified by considering the darkest lighting value as a variable. Let the `RenderHslaScrollbars` function return `true` if the color was changed to simplify the usage. The function previously returned the color but this value was unused. Use `IGraphics::SetColor4` function instead of `IGraphics::SetColorVertex` when possible. Rename variables `Array` to `aColorVertices`. Use `std::size` instead of hard-coding array sizes. --- src/game/client/components/menus.h | 2 +- src/game/client/components/menus_settings.cpp | 289 ++++++------------ 2 files changed, 94 insertions(+), 197 deletions(-) diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 1a16bfa56a8..f16cfec7416 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -791,7 +791,7 @@ class CMenus : public CComponent // found in menus_settings.cpp void RenderSettingsDDNet(CUIRect MainView); void RenderSettingsAppearance(CUIRect MainView); - ColorHSLA RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha = false, bool ClampedLight = false); + bool RenderHslaScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, float DarkestLight); CServerProcess m_ServerProcess; }; diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 9eff2b3af32..af5f40f56e0 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -758,10 +758,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView) { aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); Ui()->DoLabel(&Label, apParts[i], 14.0f, TEXTALIGN_ML); - - const unsigned PrevColor = *apColors[i]; - RenderHSLScrollbars(&aRects[i], apColors[i], false, true); - if(PrevColor != *apColors[i]) + if(RenderHslaScrollbars(&aRects[i], apColors[i], false, ColorHSLA::DARKEST_LGT)) { SetNeedSendInfo(); } @@ -2128,53 +2125,39 @@ void CMenus::RenderSettings(CUIRect MainView) } } -ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, bool ClampedLight) +bool CMenus::RenderHslaScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, float DarkestLight) { + const unsigned PrevPackedColor = *pColor; ColorHSLA Color(*pColor, Alpha); - CUIRect Preview, Button, Label; - char aBuf[32]; - float *apComponent[] = {&Color.h, &Color.s, &Color.l, &Color.a}; + const ColorHSLA OriginalColor = Color; const char *apLabels[] = {Localize("Hue"), Localize("Sat."), Localize("Lht."), Localize("Alpha")}; - - float SizePerEntry = 20.0f; - float MarginPerEntry = 5.0f; - - float OffY = (SizePerEntry + MarginPerEntry) * (3 + (Alpha ? 1 : 0)) - 40.0f; - pRect->VSplitLeft(40.0f, &Preview, pRect); - Preview.HSplitTop(OffY / 2.0f, NULL, &Preview); - Preview.HSplitTop(40.0f, &Preview, NULL); - - Graphics()->TextureClear(); - { - const float SizeBorder = 5.0f; - Graphics()->SetColor(ColorRGBA(0.15f, 0.15f, 0.15f, 1)); - int TmpCont = Graphics()->CreateRectQuadContainer(Preview.x - SizeBorder / 2.0f, Preview.y - SizeBorder / 2.0f, Preview.w + SizeBorder, Preview.h + SizeBorder, 4.0f + SizeBorder / 2.0f, IGraphics::CORNER_ALL); - Graphics()->RenderQuadContainer(TmpCont, -1); - Graphics()->DeleteQuadContainer(TmpCont); - } - ColorHSLA RenderColorHSLA(Color.r, Color.g, Color.b, Color.a); - if(ClampedLight) - RenderColorHSLA = RenderColorHSLA.UnclampLighting(); - Graphics()->SetColor(color_cast(RenderColorHSLA)); - int TmpCont = Graphics()->CreateRectQuadContainer(Preview.x, Preview.y, Preview.w, Preview.h, 4.0f, IGraphics::CORNER_ALL); - Graphics()->RenderQuadContainer(TmpCont, -1); - Graphics()->DeleteQuadContainer(TmpCont); - - auto &&RenderHSLColorsRect = [&](CUIRect *pColorRect) { - Graphics()->TextureClear(); - Graphics()->TrianglesBegin(); - + const float SizePerEntry = 20.0f; + const float MarginPerEntry = 5.0f; + const float PreviewMargin = 2.5f; + const float PreviewHeight = 40.0f + 2 * PreviewMargin; + const float OffY = (SizePerEntry + MarginPerEntry) * (3 + (Alpha ? 1 : 0)) - PreviewHeight; + + CUIRect Preview; + pRect->VSplitLeft(PreviewHeight, &Preview, pRect); + Preview.HSplitTop(OffY / 2.0f, nullptr, &Preview); + Preview.HSplitTop(PreviewHeight, &Preview, nullptr); + + Preview.Draw(ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), IGraphics::CORNER_ALL, 4.0f + PreviewMargin); + Preview.Margin(PreviewMargin, &Preview); + Preview.Draw(color_cast(Color.UnclampLighting(DarkestLight)), IGraphics::CORNER_ALL, 4.0f + PreviewMargin); + + auto &&RenderHueRect = [&](CUIRect *pColorRect) { float CurXOff = pColorRect->x; - float SizeColor = pColorRect->w / 6; + const float SizeColor = pColorRect->w / 6; // red to yellow { - IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex aColorVertices[] = { IGraphics::CColorVertex(0, 1, 0, 0, 1), IGraphics::CColorVertex(1, 1, 1, 0, 1), IGraphics::CColorVertex(2, 1, 0, 0, 1), IGraphics::CColorVertex(3, 1, 1, 0, 1)}; - Graphics()->SetColorVertex(Array, 4); + Graphics()->SetColorVertex(aColorVertices, std::size(aColorVertices)); IGraphics::CFreeformItem Freeform( CurXOff, pColorRect->y, @@ -2187,12 +2170,12 @@ ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool // yellow to green CurXOff += SizeColor; { - IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex aColorVertices[] = { IGraphics::CColorVertex(0, 1, 1, 0, 1), IGraphics::CColorVertex(1, 0, 1, 0, 1), IGraphics::CColorVertex(2, 1, 1, 0, 1), IGraphics::CColorVertex(3, 0, 1, 0, 1)}; - Graphics()->SetColorVertex(Array, 4); + Graphics()->SetColorVertex(aColorVertices, std::size(aColorVertices)); IGraphics::CFreeformItem Freeform( CurXOff, pColorRect->y, @@ -2205,12 +2188,12 @@ ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool CurXOff += SizeColor; // green to turquoise { - IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex aColorVertices[] = { IGraphics::CColorVertex(0, 0, 1, 0, 1), IGraphics::CColorVertex(1, 0, 1, 1, 1), IGraphics::CColorVertex(2, 0, 1, 0, 1), IGraphics::CColorVertex(3, 0, 1, 1, 1)}; - Graphics()->SetColorVertex(Array, 4); + Graphics()->SetColorVertex(aColorVertices, std::size(aColorVertices)); IGraphics::CFreeformItem Freeform( CurXOff, pColorRect->y, @@ -2223,12 +2206,12 @@ ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool CurXOff += SizeColor; // turquoise to blue { - IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex aColorVertices[] = { IGraphics::CColorVertex(0, 0, 1, 1, 1), IGraphics::CColorVertex(1, 0, 0, 1, 1), IGraphics::CColorVertex(2, 0, 1, 1, 1), IGraphics::CColorVertex(3, 0, 0, 1, 1)}; - Graphics()->SetColorVertex(Array, 4); + Graphics()->SetColorVertex(aColorVertices, std::size(aColorVertices)); IGraphics::CFreeformItem Freeform( CurXOff, pColorRect->y, @@ -2241,12 +2224,12 @@ ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool CurXOff += SizeColor; // blue to purple { - IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex aColorVertices[] = { IGraphics::CColorVertex(0, 0, 0, 1, 1), IGraphics::CColorVertex(1, 1, 0, 1, 1), IGraphics::CColorVertex(2, 0, 0, 1, 1), IGraphics::CColorVertex(3, 1, 0, 1, 1)}; - Graphics()->SetColorVertex(Array, 4); + Graphics()->SetColorVertex(aColorVertices, std::size(aColorVertices)); IGraphics::CFreeformItem Freeform( CurXOff, pColorRect->y, @@ -2259,12 +2242,12 @@ ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool CurXOff += SizeColor; // purple to red { - IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex aColorVertices[] = { IGraphics::CColorVertex(0, 1, 0, 1, 1), IGraphics::CColorVertex(1, 1, 0, 0, 1), IGraphics::CColorVertex(2, 1, 0, 1, 1), IGraphics::CColorVertex(3, 1, 0, 0, 1)}; - Graphics()->SetColorVertex(Array, 4); + Graphics()->SetColorVertex(aColorVertices, std::size(aColorVertices)); IGraphics::CFreeformItem Freeform( CurXOff, pColorRect->y, @@ -2273,150 +2256,68 @@ ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool CurXOff + SizeColor, pColorRect->y + pColorRect->h); Graphics()->QuadsDrawFreeform(&Freeform, 1); } - - Graphics()->TrianglesEnd(); }; - auto &&RenderHSLSatRect = [&](CUIRect *pColorRect, ColorRGBA &CurColor) { - Graphics()->TextureClear(); - Graphics()->TrianglesBegin(); - - float CurXOff = pColorRect->x; - float SizeColor = pColorRect->w; - - ColorHSLA RightColor = color_cast(CurColor); + auto &&RenderSaturationRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColor) { ColorHSLA LeftColor = color_cast(CurColor); + ColorHSLA RightColor = color_cast(CurColor); - LeftColor.g = 0; - RightColor.g = 1; - - ColorRGBA RightColorRGBA = color_cast(RightColor); - ColorRGBA LeftColorRGBA = color_cast(LeftColor); + LeftColor.s = 0.0f; + RightColor.s = 1.0f; - // saturation - { - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), - IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), - IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), - IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; - Graphics()->SetColorVertex(Array, 4); + const ColorRGBA LeftColorRGBA = color_cast(LeftColor); + const ColorRGBA RightColorRGBA = color_cast(RightColor); - IGraphics::CFreeformItem Freeform( - CurXOff, pColorRect->y, - CurXOff + SizeColor, pColorRect->y, - CurXOff, pColorRect->y + pColorRect->h, - CurXOff + SizeColor, pColorRect->y + pColorRect->h); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - } + Graphics()->SetColor4(LeftColorRGBA, RightColorRGBA, RightColorRGBA, LeftColorRGBA); - Graphics()->TrianglesEnd(); + IGraphics::CFreeformItem Freeform( + pColorRect->x, pColorRect->y, + pColorRect->x + pColorRect->w, pColorRect->y, + pColorRect->x, pColorRect->y + pColorRect->h, + pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); }; - auto &&RenderHSLLightRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorSat) { - Graphics()->TextureClear(); - Graphics()->TrianglesBegin(); - - float CurXOff = pColorRect->x; - float SizeColor = pColorRect->w / (ClampedLight ? 1.0f : 2.0f); - - ColorHSLA RightColor = color_cast(CurColorSat); - ColorHSLA LeftColor = color_cast(CurColorSat); - - LeftColor.b = ColorHSLA::DARKEST_LGT; - RightColor.b = 1; - - ColorRGBA RightColorRGBA = color_cast(RightColor); - ColorRGBA LeftColorRGBA = color_cast(LeftColor); - - if(!ClampedLight) - CurXOff += SizeColor; - - // light - { - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), - IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), - IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), - IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; - Graphics()->SetColorVertex(Array, 4); - - IGraphics::CFreeformItem Freeform( - CurXOff, pColorRect->y, - CurXOff + SizeColor, pColorRect->y, - CurXOff, pColorRect->y + pColorRect->h, - CurXOff + SizeColor, pColorRect->y + pColorRect->h); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - } + auto &&RenderLightingRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColor) { + ColorHSLA LeftColor = color_cast(CurColor); + ColorHSLA RightColor = color_cast(CurColor); - if(!ClampedLight) - { - CurXOff -= SizeColor; - LeftColor.b = 0; - RightColor.b = ColorHSLA::DARKEST_LGT; + LeftColor.l = DarkestLight; + RightColor.l = 1.0f; - RightColorRGBA = color_cast(RightColor); - LeftColorRGBA = color_cast(LeftColor); + const ColorRGBA LeftColorRGBA = color_cast(LeftColor); + const ColorRGBA RightColorRGBA = color_cast(RightColor); - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), - IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1), - IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, 1), - IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, 1)}; - Graphics()->SetColorVertex(Array, 4); + Graphics()->SetColor4(LeftColorRGBA, RightColorRGBA, RightColorRGBA, LeftColorRGBA); - IGraphics::CFreeformItem Freeform( - CurXOff, pColorRect->y, - CurXOff + SizeColor, pColorRect->y, - CurXOff, pColorRect->y + pColorRect->h, - CurXOff + SizeColor, pColorRect->y + pColorRect->h); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - } - - Graphics()->TrianglesEnd(); + IGraphics::CFreeformItem Freeform( + pColorRect->x, pColorRect->y, + pColorRect->x + pColorRect->w, pColorRect->y, + pColorRect->x, pColorRect->y + pColorRect->h, + pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); }; - auto &&RenderHSLAlphaRect = [&](CUIRect *pColorRect, ColorRGBA &CurColorFull) { - Graphics()->TextureClear(); - Graphics()->TrianglesBegin(); - - float CurXOff = pColorRect->x; - float SizeColor = pColorRect->w; + auto &&RenderAlphaRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColorFull) { + const ColorRGBA LeftColorRGBA = color_cast(color_cast(CurColorFull).WithAlpha(0.0f)); + const ColorRGBA RightColorRGBA = color_cast(color_cast(CurColorFull).WithAlpha(1.0f)); - ColorHSLA RightColor = color_cast(CurColorFull); - ColorHSLA LeftColor = color_cast(CurColorFull); + Graphics()->SetColor4(LeftColorRGBA, RightColorRGBA, RightColorRGBA, LeftColorRGBA); - LeftColor.a = 0; - RightColor.a = 1; - - ColorRGBA RightColorRGBA = color_cast(RightColor); - ColorRGBA LeftColorRGBA = color_cast(LeftColor); - - // alpha - { - IGraphics::CColorVertex Array[4] = { - IGraphics::CColorVertex(0, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a), - IGraphics::CColorVertex(1, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a), - IGraphics::CColorVertex(2, LeftColorRGBA.r, LeftColorRGBA.g, LeftColorRGBA.b, LeftColorRGBA.a), - IGraphics::CColorVertex(3, RightColorRGBA.r, RightColorRGBA.g, RightColorRGBA.b, RightColorRGBA.a)}; - Graphics()->SetColorVertex(Array, 4); - - IGraphics::CFreeformItem Freeform( - CurXOff, pColorRect->y, - CurXOff + SizeColor, pColorRect->y, - CurXOff, pColorRect->y + pColorRect->h, - CurXOff + SizeColor, pColorRect->y + pColorRect->h); - Graphics()->QuadsDrawFreeform(&Freeform, 1); - } - - Graphics()->TrianglesEnd(); + IGraphics::CFreeformItem Freeform( + pColorRect->x, pColorRect->y, + pColorRect->x + pColorRect->w, pColorRect->y, + pColorRect->x, pColorRect->y + pColorRect->h, + pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h); + Graphics()->QuadsDrawFreeform(&Freeform, 1); }; for(int i = 0; i < 3 + Alpha; i++) { + CUIRect Button, Label; pRect->HSplitTop(SizePerEntry, &Button, pRect); - pRect->HSplitTop(MarginPerEntry, NULL, pRect); - Button.VSplitLeft(10.0f, 0, &Button); + pRect->HSplitTop(MarginPerEntry, nullptr, pRect); + Button.VSplitLeft(10.0f, nullptr, &Button); Button.VSplitLeft(100.0f, &Label, &Button); Button.Draw(ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), IGraphics::CORNER_ALL, 1.0f); @@ -2424,47 +2325,43 @@ ColorHSLA CMenus::RenderHSLScrollbars(CUIRect *pRect, unsigned int *pColor, bool CUIRect Rail; Button.Margin(2.0f, &Rail); - str_format(aBuf, sizeof(aBuf), "%s: %03d", apLabels[i], (int)(*apComponent[i] * 255)); + char aBuf[32]; + str_format(aBuf, sizeof(aBuf), "%s: %03d", apLabels[i], round_to_int(Color[i] * 255.0f)); Ui()->DoLabel(&Label, aBuf, 14.0f, TEXTALIGN_ML); - ColorHSLA CurColorPureHSLA(RenderColorHSLA.r, 1, 0.5f, 1); - ColorRGBA CurColorPure = color_cast(CurColorPureHSLA); - ColorRGBA ColorInner(1, 1, 1, 0.25f); - + ColorRGBA HandleColor; + Graphics()->TextureClear(); + Graphics()->TrianglesBegin(); if(i == 0) { - ColorInner = CurColorPure; - RenderHSLColorsRect(&Rail); + RenderHueRect(&Rail); + HandleColor = color_cast(ColorHSLA(Color.h, 1.0f, 0.5f, 1.0f)); } else if(i == 1) { - RenderHSLSatRect(&Rail, CurColorPure); - ColorInner = color_cast(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], CurColorPureHSLA.b, 1)); + RenderSaturationRect(&Rail, color_cast(ColorHSLA(Color.h, 1.0f, 0.5f, 1.0f))); + HandleColor = color_cast(ColorHSLA(Color.h, Color.s, 0.5f, 1.0f)); } else if(i == 2) { - ColorRGBA CurColorSat = color_cast(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], 0.5f, 1)); - RenderHSLLightRect(&Rail, CurColorSat); - float LightVal = *apComponent[2]; - if(ClampedLight) - LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT); - ColorInner = color_cast(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], LightVal, 1)); + RenderLightingRect(&Rail, color_cast(ColorHSLA(Color.h, Color.s, 0.5f, 1.0f))); + HandleColor = color_cast(ColorHSLA(Color.h, Color.s, Color.l, 1.0f).UnclampLighting(DarkestLight)); } else if(i == 3) { - ColorRGBA CurColorFull = color_cast(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], *apComponent[2], 1)); - RenderHSLAlphaRect(&Rail, CurColorFull); - float LightVal = *apComponent[2]; - if(ClampedLight) - LightVal = ColorHSLA::DARKEST_LGT + LightVal * (1.0f - ColorHSLA::DARKEST_LGT); - ColorInner = color_cast(ColorHSLA(CurColorPureHSLA.r, *apComponent[1], LightVal, *apComponent[3])); + RenderAlphaRect(&Rail, color_cast(ColorHSLA(Color.h, Color.s, Color.l, 1.0f).UnclampLighting(DarkestLight))); + HandleColor = color_cast(Color.UnclampLighting(DarkestLight)); } + Graphics()->TrianglesEnd(); - *apComponent[i] = Ui()->DoScrollbarH(&((char *)pColor)[i], &Button, *apComponent[i], &ColorInner); + Color[i] = Ui()->DoScrollbarH(&((char *)pColor)[i], &Button, Color[i], &HandleColor); } - *pColor = Color.Pack(Alpha); - return Color; + if(OriginalColor != Color) + { + *pColor = Color.Pack(Alpha); + } + return PrevPackedColor != *pColor; } enum From ce0e52851cca93b9c98a303d1d5b465e43105702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 21 Aug 2024 17:39:07 +0200 Subject: [PATCH 03/26] Refactor `CSkins7::GetColor` function Use existing color constructor to conditionally unpack the alpha component instead of doing this separately. Make `DARKEST_COLOR_LGT` a `float` constant instead of using an `enum` to simplify the usage. --- src/game/client/components/skins7.cpp | 6 +----- src/game/client/components/skins7.h | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/game/client/components/skins7.cpp b/src/game/client/components/skins7.cpp index 458d5d721f2..fb0f0bdf960 100644 --- a/src/game/client/components/skins7.cpp +++ b/src/game/client/components/skins7.cpp @@ -476,11 +476,7 @@ void CSkins7::RandomizeSkin(int Dummy) ColorRGBA CSkins7::GetColor(int Value, bool UseAlpha) const { - float Dark = DARKEST_COLOR_LGT / 255.0f; - ColorRGBA Color = color_cast(ColorHSLA(Value).UnclampLighting(Dark)); - float Alpha = UseAlpha ? ((Value >> 24) & 0xff) / 255.0f : 1.0f; - Color.a = Alpha; - return Color; + return color_cast(ColorHSLA(Value, UseAlpha).UnclampLighting(DARKEST_COLOR_LGT)); } ColorRGBA CSkins7::GetTeamColor(int UseCustomColors, int PartColor, int Team, int Part) const diff --git a/src/game/client/components/skins7.h b/src/game/client/components/skins7.h index a5b0d9fcb2a..d75dac88fa2 100644 --- a/src/game/client/components/skins7.h +++ b/src/game/client/components/skins7.h @@ -20,14 +20,14 @@ class CSkins7 : public CComponent SKINFLAG_SPECIAL = 1 << 0, SKINFLAG_STANDARD = 1 << 1, - DARKEST_COLOR_LGT = 61, - NUM_COLOR_COMPONENTS = 4, HAT_NUM = 2, HAT_OFFSET_SIDE = 2, }; + static constexpr float DARKEST_COLOR_LGT = 61.0f / 255.0f; + struct CSkinPart { int m_Flags; From 992f235f0550e89a1293161742580ddcf01eecfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sun, 18 Aug 2024 15:12:24 +0200 Subject: [PATCH 04/26] Use HSLA scrollbar color picker for 0.7 tee settings Use the same HSLA scrollbar color picker for the 0.7 tee settings as for the 0.6 tee settings. Use the correct darkest lighting value for 0.7 skin color pickers instead of not clamping the lighting value in the color picker. --- .../client/components/menus_settings7.cpp | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/game/client/components/menus_settings7.cpp b/src/game/client/components/menus_settings7.cpp index d076d9ff723..71025cc9fba 100644 --- a/src/game/client/components/menus_settings7.cpp +++ b/src/game/client/components/menus_settings7.cpp @@ -312,7 +312,7 @@ void CMenus::RenderSettingsTeeBasic7(CUIRect MainView) void CMenus::RenderSettingsTeeCustom7(CUIRect MainView) { - CUIRect Label, Patterns, Button, Left, Right, Picker, Palette; + CUIRect Label, Patterns, Button, Left, Right; // render skin preview background float SpacingH = 2.0f; @@ -344,33 +344,31 @@ void CMenus::RenderSettingsTeeCustom7(CUIRect MainView) MainView.HSplitTop(SpacingH, 0, &MainView); MainView.VSplitMid(&Left, &Right, SpacingW); + Right.Margin(5.0f, &Right); - // part selection RenderSkinPartSelection7(Left); - // use custom color checkbox - Right.HSplitTop(ButtonHeight, &Button, &Right); - Right.HSplitBottom(45.0f, &Picker, &Palette); - static CButtonContainer s_ColorPicker; - DoLine_ColorPicker( - &s_ColorPicker, - 25.0f, // LineSize - 13.0f, // LabelSize - 5.0f, // BottomMargin - &Right, - Localize("Custom colors"), - CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected], - ColorRGBA(1.0f, 1.0f, 0.5f), // DefaultColor - true, // CheckBoxSpacing - CSkins7::ms_apUCCVariables[(int)m_Dummy][m_TeePartSelected], // CheckBoxValue - m_TeePartSelected == protocol7::SKINPART_MARKING); // use alpha - static int s_OldColor = *CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected]; - int NewColor = *CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected]; - if(s_OldColor != NewColor) + CUIRect CustomColorsButton; + Right.HSplitTop(20.0f, &CustomColorsButton, &Right); + + int *pUseCustomColor = CSkins7::ms_apUCCVariables[(int)m_Dummy][m_TeePartSelected]; + if(DoButton_CheckBox(pUseCustomColor, Localize("Custom colors"), *pUseCustomColor, &CustomColorsButton)) { - s_OldColor = NewColor; + *pUseCustomColor = !*pUseCustomColor; SetNeedSendInfo(); } + + if(*pUseCustomColor) + { + CUIRect CustomColors; + Right.HSplitTop(5.0f, nullptr, &Right); + Right.HSplitTop(95.0f, &CustomColors, &Right); + + if(RenderHslaScrollbars(&CustomColors, CSkins7::ms_apColorVariables[(int)m_Dummy][m_TeePartSelected], m_TeePartSelected == protocol7::SKINPART_MARKING, CSkins7::DARKEST_COLOR_LGT)) + { + SetNeedSendInfo(); + } + } } void CMenus::RenderSkinSelection7(CUIRect MainView) From 499ad48ee6c6d21642418b14c7dddedd507b1df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 21 Aug 2024 19:21:50 +0200 Subject: [PATCH 05/26] Revert "Quit if Android back-button is pressed 3 times within 1 second" This reverts commit 91848f0be635ef495b18600abd3a95afe3b7beeb. This was prone to accidental usage. The user interface is now usable with touch input, so this additional method to quickly and cleanly quit the game is not necessary anymore. --- src/engine/client/input.cpp | 26 -------------------------- src/engine/client/input.h | 5 ----- 2 files changed, 31 deletions(-) diff --git a/src/engine/client/input.cpp b/src/engine/client/input.cpp index 0d8b2a34559..f94894ecaed 100644 --- a/src/engine/client/input.cpp +++ b/src/engine/client/input.cpp @@ -712,35 +712,9 @@ int CInput::Update() // handle keys case SDL_KEYDOWN: -#if defined(CONF_PLATFORM_ANDROID) - if(Event.key.keysym.scancode == KEY_AC_BACK && m_BackButtonReleased) - { - if(m_LastBackPress == -1 || (Now - m_LastBackPress) / (float)time_freq() > 1.0f) - { - m_NumBackPresses = 1; - m_LastBackPress = Now; - } - else - { - m_NumBackPresses++; - if(m_NumBackPresses >= 3) - { - // Quit if the Android back-button was pressed 3 times within 1 second - return 1; - } - } - m_BackButtonReleased = false; - } -#endif Scancode = TranslateScancode(Event.key); break; case SDL_KEYUP: -#if defined(CONF_PLATFORM_ANDROID) - if(Event.key.keysym.scancode == KEY_AC_BACK && !m_BackButtonReleased) - { - m_BackButtonReleased = true; - } -#endif Action = IInput::FLAG_RELEASE; Scancode = TranslateScancode(Event.key); break; diff --git a/src/engine/client/input.h b/src/engine/client/input.h index 90b170f2e91..70127785fd4 100644 --- a/src/engine/client/input.h +++ b/src/engine/client/input.h @@ -76,11 +76,6 @@ class CInput : public IEngineInput bool m_InputGrabbed; bool m_MouseFocus; -#if defined(CONF_PLATFORM_ANDROID) - int m_NumBackPresses = 0; - bool m_BackButtonReleased = true; - int64_t m_LastBackPress = -1; -#endif // IME support std::string m_CompositionString; From f796309cc82e8d762d383070eadfb3e2ef945c7f Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Wed, 21 Aug 2024 15:36:05 +0200 Subject: [PATCH 06/26] Fix save/swap with draggers --- src/game/server/ddracecommands.cpp | 2 +- src/game/server/entities/dragger_beam.cpp | 6 ++++ src/game/server/entities/dragger_beam.h | 1 + src/game/server/entity.h | 10 ++++++ src/game/server/gameworld.cpp | 15 +++++++++ src/game/server/gameworld.h | 8 +++++ src/game/server/save.cpp | 37 ++++++++++++----------- src/game/server/save.h | 15 +++++++-- src/game/server/score.cpp | 2 +- 9 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/game/server/ddracecommands.cpp b/src/game/server/ddracecommands.cpp index d595eac8299..83ce130c849 100644 --- a/src/game/server/ddracecommands.cpp +++ b/src/game/server/ddracecommands.cpp @@ -870,7 +870,7 @@ void CGameContext::ConDrySave(IConsole::IResult *pResult, void *pUserData) CSaveTeam SavedTeam; int Team = pSelf->GetDDRaceTeam(pResult->m_ClientId); - int Result = SavedTeam.Save(pSelf, Team, true); + ESaveResult Result = SavedTeam.Save(pSelf, Team, true); if(CSaveTeam::HandleSaveError(Result, pResult->m_ClientId, pSelf)) return; diff --git a/src/game/server/entities/dragger_beam.cpp b/src/game/server/entities/dragger_beam.cpp index f4d19806864..5dace37f676 100644 --- a/src/game/server/entities/dragger_beam.cpp +++ b/src/game/server/entities/dragger_beam.cpp @@ -10,6 +10,7 @@ #include #include +#include CDraggerBeam::CDraggerBeam(CGameWorld *pGameWorld, CDragger *pDragger, vec2 Pos, float Strength, bool IgnoreWalls, int ForClientId, int Layer, int Number) : @@ -140,3 +141,8 @@ void CDraggerBeam::SwapClients(int Client1, int Client2) { m_ForClientId = m_ForClientId == Client1 ? Client2 : m_ForClientId == Client2 ? Client1 : m_ForClientId; } + +ESaveResult CDraggerBeam::BlocksSave(int ClientId) +{ + return m_ForClientId == ClientId ? ESaveResult::DRAGGER_ACTIVE : ESaveResult::SUCCESS; +} diff --git a/src/game/server/entities/dragger_beam.h b/src/game/server/entities/dragger_beam.h index cff7db57fd3..70249d1e847 100644 --- a/src/game/server/entities/dragger_beam.h +++ b/src/game/server/entities/dragger_beam.h @@ -39,6 +39,7 @@ class CDraggerBeam : public CEntity void Tick() override; void Snap(int SnappingClient) override; void SwapClients(int Client1, int Client2) override; + ESaveResult BlocksSave(int ClientId) override; }; #endif // GAME_SERVER_ENTITIES_DRAGGER_BEAM_H diff --git a/src/game/server/entity.h b/src/game/server/entity.h index 03930b25215..b3cf409bc2f 100644 --- a/src/game/server/entity.h +++ b/src/game/server/entity.h @@ -8,6 +8,7 @@ #include #include "gameworld.h" +#include "save.h" class CCollision; class CGameContext; @@ -138,6 +139,15 @@ class CEntity */ virtual void SwapClients(int Client1, int Client2) {} + /* + Function: BlocksSave + Called to check if a team can be saved + + Arguments: + ClientId - Client ID + */ + virtual ESaveResult BlocksSave(int ClientId) { return ESaveResult::SUCCESS; } + /* Function GetOwnerId Returns: diff --git a/src/game/server/gameworld.cpp b/src/game/server/gameworld.cpp index 4e9d90aa49a..5f422908108 100644 --- a/src/game/server/gameworld.cpp +++ b/src/game/server/gameworld.cpp @@ -270,6 +270,21 @@ void CGameWorld::Tick() } } +ESaveResult CGameWorld::BlocksSave(int ClientId) +{ + // check all objects + for(auto *pEnt : m_apFirstEntityTypes) + for(; pEnt;) + { + m_pNextTraverseEntity = pEnt->m_pNextTypeEntity; + ESaveResult Result = pEnt->BlocksSave(ClientId); + if(Result != ESaveResult::SUCCESS) + return Result; + pEnt = m_pNextTraverseEntity; + } + return ESaveResult::SUCCESS; +} + void CGameWorld::SwapClients(int Client1, int Client2) { // update all objects diff --git a/src/game/server/gameworld.h b/src/game/server/gameworld.h index 15f338a2e5f..758f23203af 100644 --- a/src/game/server/gameworld.h +++ b/src/game/server/gameworld.h @@ -5,6 +5,8 @@ #include +#include "save.h" + #include class CEntity; @@ -153,6 +155,12 @@ class CGameWorld */ void SwapClients(int Client1, int Client2); + /* + Function: BlocksSave + Checks if any entity would block /save + */ + ESaveResult BlocksSave(int ClientId); + // DDRace void ReleaseHooked(int ClientId); diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index f4f07a4070a..cec79c946bb 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -490,30 +490,30 @@ CSaveTeam::~CSaveTeam() delete[] m_pSavedTees; } -int CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry) +ESaveResult CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry) { if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && (Team <= 0 || MAX_CLIENTS <= Team)) - return 1; + return ESaveResult::TEAM_FLOCK; IGameController *pController = pGameServer->m_pController; CGameTeams *pTeams = &pController->Teams(); if(pTeams->TeamFlock(Team)) { - return 5; + return ESaveResult::TEAM_0_MODE; } m_MembersCount = pTeams->Count(Team); if(m_MembersCount <= 0) { - return 2; + return ESaveResult::TEAM_NOT_FOUND; } m_TeamState = pTeams->GetTeamState(Team); if(m_TeamState != CGameTeams::TEAMSTATE_STARTED) { - return 4; + return ESaveResult::NOT_STARTED; } m_HighestSwitchNumber = pGameServer->Collision()->m_HighestSwitchNumber; @@ -529,13 +529,16 @@ int CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry) if(pTeams->m_Core.Team(p->GetPlayer()->GetCid()) != Team) continue; if(m_MembersCount == j) - return 3; + return ESaveResult::CHAR_NOT_FOUND; + ESaveResult Result = pGameServer->m_World.BlocksSave(p->GetPlayer()->GetCid()); + if(Result != ESaveResult::SUCCESS) + return Result; m_pSavedTees[j].Save(p); aPlayerCids[j] = p->GetPlayer()->GetCid(); j++; } if(m_MembersCount != j) - return 3; + return ESaveResult::CHAR_NOT_FOUND; if(pGameServer->Collision()->m_HighestSwitchNumber) { @@ -555,32 +558,32 @@ int CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry) { pGameServer->m_World.RemoveEntitiesFromPlayers(aPlayerCids, m_MembersCount); } - return 0; + return ESaveResult::SUCCESS; } -bool CSaveTeam::HandleSaveError(int Result, int ClientId, CGameContext *pGameContext) +bool CSaveTeam::HandleSaveError(ESaveResult Result, int ClientId, CGameContext *pGameContext) { switch(Result) { - case 0: + case ESaveResult::SUCCESS: return false; - case 1: + case ESaveResult::TEAM_FLOCK: pGameContext->SendChatTarget(ClientId, "You have to be in a team (from 1-63)"); break; - case 2: + case ESaveResult::TEAM_NOT_FOUND: pGameContext->SendChatTarget(ClientId, "Could not find your Team"); break; - case 3: + case ESaveResult::CHAR_NOT_FOUND: pGameContext->SendChatTarget(ClientId, "To save all players in your team have to be alive and not in '/spec'"); break; - case 4: + case ESaveResult::NOT_STARTED: pGameContext->SendChatTarget(ClientId, "Your team has not started yet"); break; - case 5: + case ESaveResult::TEAM_0_MODE: pGameContext->SendChatTarget(ClientId, "Team can't be saved while in team 0 mode"); break; - default: // this state should never be reached - pGameContext->SendChatTarget(ClientId, "Unknown error while saving"); + case ESaveResult::DRAGGER_ACTIVE: + pGameContext->SendChatTarget(ClientId, "Team can't be saved while a dragger is active"); break; } return true; diff --git a/src/game/server/save.h b/src/game/server/save.h index 145dbf25c5c..c60a7807217 100644 --- a/src/game/server/save.h +++ b/src/game/server/save.h @@ -19,6 +19,17 @@ enum NUM_RESCUEMODES }; +enum class ESaveResult +{ + SUCCESS, + TEAM_FLOCK, + TEAM_NOT_FOUND, + CHAR_NOT_FOUND, + NOT_STARTED, + TEAM_0_MODE, + DRAGGER_ACTIVE +}; + class CSaveTee { public: @@ -148,13 +159,13 @@ class CSaveTeam int FromString(const char *pString); // returns true if a team can load, otherwise writes a nice error Message in pMessage bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientId, int NumPlayer, char *pMessage, int MessageLen) const; - int Save(CGameContext *pGameServer, int Team, bool Dry = false); + ESaveResult Save(CGameContext *pGameServer, int Team, bool Dry = false); bool Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong); CSaveTee *m_pSavedTees = nullptr; // returns true if an error occurred - static bool HandleSaveError(int Result, int ClientId, CGameContext *pGameContext); + static bool HandleSaveError(ESaveResult Result, int ClientId, CGameContext *pGameContext); private: CCharacter *MatchCharacter(CGameContext *pGameServer, int ClientId, int SaveId, bool KeepCurrentCharacter) const; diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index 60d0efc8c8b..80e5c6c27f5 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -303,7 +303,7 @@ void CScore::SaveTeam(int ClientId, const char *pCode, const char *pServer) auto SaveResult = std::make_shared(ClientId); SaveResult->m_SaveId = RandomUuid(); - int Result = SaveResult->m_SavedTeam.Save(GameServer(), Team); + ESaveResult Result = SaveResult->m_SavedTeam.Save(GameServer(), Team); if(CSaveTeam::HandleSaveError(Result, ClientId, GameServer())) return; pController->Teams().SetSaving(Team, SaveResult); From 804a91ced0d8c2dfd930f0d79d0b6340841dcf2a Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Thu, 22 Aug 2024 19:41:14 +0800 Subject: [PATCH 07/26] Fix sounds being doubled in 0.6 Closed #8785 --- src/game/server/entities/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 001b73fe2b7..9d136bb1313 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -853,7 +853,7 @@ void CCharacter::TickDeferred() // Some sounds are triggered client-side for the acting player (or for all players on Sixup) // so we need to avoid duplicating them - CClientMask TeamMaskExceptSelfAndSixup = Teams()->TeamMask(Team(), -1, CID, CGameContext::FLAG_SIX); + CClientMask TeamMaskExceptSelfAndSixup = Teams()->TeamMask(Team(), CID, CID, CGameContext::FLAG_SIX); // Some are triggered client-side but only on Sixup CClientMask TeamMaskExceptSixup = Teams()->TeamMask(Team(), -1, CID, CGameContext::FLAG_SIX); From a3c8949d4012f2859838c0eb1d5f98f013b0dfa6 Mon Sep 17 00:00:00 2001 From: lolipodass <56706038+lolipodass@users.noreply.github.com> Date: Thu, 22 Aug 2024 20:55:40 +0300 Subject: [PATCH 08/26] Update russian.txt to match wiki MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix names like in https://wiki.ddnet.org/wiki/Dummy/ru and https://wiki.ddnet.org/wiki/Common_Terminology/ru#Тии --- data/languages/russian.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/languages/russian.txt b/data/languages/russian.txt index 611e7015958..ca2f536605e 100644 --- a/data/languages/russian.txt +++ b/data/languages/russian.txt @@ -805,7 +805,7 @@ Toggle dyncam == Смена дин. камеры Toggle dummy -== Смена Tee +== Переключение дамми Toggle ghost == Переключить тень @@ -1835,10 +1835,10 @@ No login required == Без логина Player info change cooldown -== Задержка смены информации об игроке +== Кулдаун смены данных об игроке Tee -== Ти +== Тии Always show chat == Всегда показывать чат From 4d09332b83a57d174a572a0bce426c1adb0c916b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 21 Aug 2024 00:45:04 +0200 Subject: [PATCH 09/26] Add `CUIRect::TopLeft` and `Size` convenience functions --- src/game/client/ui_rect.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/game/client/ui_rect.h b/src/game/client/ui_rect.h index 54d7b178955..f3d38e420ec 100644 --- a/src/game/client/ui_rect.h +++ b/src/game/client/ui_rect.h @@ -136,7 +136,24 @@ class CUIRect void Draw(ColorRGBA Color, int Corners, float Rounding) const; void Draw4(ColorRGBA ColorTopLeft, ColorRGBA ColorTopRight, ColorRGBA ColorBottomLeft, ColorRGBA ColorBottomRight, int Corners, float Rounding) const; - vec2 Center() const { return vec2(x + w / 2.0f, y + h / 2.0f); } + /** + * Returns the top-left position of *this* CUIRect as a vec2. + * + * @return Top-left position as vec2. + */ + vec2 TopLeft() const { return vec2(x, y); } + /** + * Returns the size of *this* CUIRect as a vec2. + * + * @return Size as vec2. + */ + vec2 Size() const { return vec2(w, h); } + /** + * Returns the center position of *this* CUIRect as a vec2. + * + * @return Center position as vec2. + */ + vec2 Center() const { return TopLeft() + Size() / 2.0f; } }; #endif From 56b56ef2b96de6d7428e7e3583f5d0fcaeb5bfad Mon Sep 17 00:00:00 2001 From: ChillerDragon Date: Fri, 23 Aug 2024 19:20:15 +0800 Subject: [PATCH 10/26] Fix save code not being censored in streamer mode Closed #8790 --- src/game/client/components/chat.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 1ca2c8e08c4..e73b94f7307 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -992,7 +992,11 @@ void CChat::OnPrepareLines(float y) const char *pText = Line.m_aText; if(Config()->m_ClStreamerMode && Line.m_ClientId == SERVER_MSG) { - if(str_startswith(Line.m_aText, "Team save in progress. You'll be able to load with '/load") && str_endswith(Line.m_aText, "if it fails")) + if(str_startswith(Line.m_aText, "Team save in progress. You'll be able to load with '/load ") && str_endswith(Line.m_aText, "'")) + { + pText = "Team save in progress. You'll be able to load with '/load ***'"; + } + else if(str_startswith(Line.m_aText, "Team save in progress. You'll be able to load with '/load") && str_endswith(Line.m_aText, "if it fails")) { pText = "Team save in progress. You'll be able to load with '/load ***' if save is successful or with '/load *** *** ***' if it fails"; } From a01e5e7151f5f2ee500523f4dc9d57838264fe46 Mon Sep 17 00:00:00 2001 From: furo Date: Fri, 23 Aug 2024 17:06:59 +0200 Subject: [PATCH 11/26] Improve "Proof borders" description --- src/game/editor/editor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 9eab3909cdc..4f259191437 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -1075,7 +1075,7 @@ void CEditor::DoToolbarLayers(CUIRect ToolBar) // proof button TB_Top.VSplitLeft(40.0f, &Button, &TB_Top); static int s_ProofButton = 0; - if(DoButton_Ex(&s_ProofButton, "Proof", MapView()->ProofMode()->IsEnabled(), &Button, 0, "[ctrl+p] Toggles proof borders. These borders represent what a player maximum can see.", IGraphics::CORNER_L) || + if(DoButton_Ex(&s_ProofButton, "Proof", MapView()->ProofMode()->IsEnabled(), &Button, 0, "[ctrl+p] Toggles proof borders. These borders represent the area that a player can see with default zoom.", IGraphics::CORNER_L) || (m_Dialog == DIALOG_NONE && CLineInput::GetActiveInput() == nullptr && Input()->KeyPress(KEY_P) && ModPressed)) { MapView()->ProofMode()->Toggle(); From b9a794121af6d7b780bf52919766364ca8d0d765 Mon Sep 17 00:00:00 2001 From: furo Date: Fri, 23 Aug 2024 18:40:15 +0200 Subject: [PATCH 12/26] Don't keep `m_DDRaceState` and `m_StartTime` of teammate in team0mode after death --- src/game/server/entities/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 9d136bb1313..2c1f33e6afa 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -2346,7 +2346,7 @@ void CCharacter::DDRaceInit() int Team = Teams()->m_Core.Team(m_Core.m_Id); - if(Teams()->TeamLocked(Team)) + if(Teams()->TeamLocked(Team) && !Teams()->TeamFlock(Team)) { for(int i = 0; i < MAX_CLIENTS; i++) { From f897af8dbb987bd28fb65f3be159f6830e6ba514 Mon Sep 17 00:00:00 2001 From: furo Date: Fri, 23 Aug 2024 20:07:11 +0200 Subject: [PATCH 13/26] Use `CJsonWriter` for creating server info JSON --- src/engine/server.h | 5 +- src/engine/server/server.cpp | 131 +++++++++++++++++--------------- src/game/server/gamecontext.cpp | 75 +++++++----------- src/game/server/gamecontext.h | 2 +- 4 files changed, 101 insertions(+), 112 deletions(-) diff --git a/src/engine/server.h b/src/engine/server.h index 64ec543caee..62f9504b80a 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -12,6 +12,7 @@ #include "kernel.h" #include "message.h" +#include #include #include #include @@ -365,10 +366,10 @@ class IGameServer : public IInterface /** * Used to report custom player info to master servers. * - * @param aBuf Should be the json key values to add, starting with a ',' beforehand, like: ',"skin": "default", "team": 1' + * @param pJsonWriter A pointer to a CJsonStringWriter which the custom data will be added to. * @param i The client id. */ - virtual void OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int Id) = 0; + virtual void OnUpdatePlayerServerInfo(CJsonStringWriter *pJSonWriter, int Id) = 0; }; extern IGameServer *CreateGameServer(); diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index de815e4ac8a..79195389005 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -2315,77 +2316,81 @@ void CServer::UpdateRegisterServerInfo() int MaxPlayers = maximum(m_NetServer.MaxClients() - maximum(g_Config.m_SvSpectatorSlots, g_Config.m_SvReservedSlots), PlayerCount); int MaxClients = maximum(m_NetServer.MaxClients() - g_Config.m_SvReservedSlots, ClientCount); - char aName[256]; - char aGameType[32]; - char aMapName[64]; - char aVersion[64]; char aMapSha256[SHA256_MAXSTRSIZE]; sha256_str(m_aCurrentMapSha256[MAP_TYPE_SIX], aMapSha256, sizeof(aMapSha256)); - char aInfo[16384]; - str_format(aInfo, sizeof(aInfo), - "{" - "\"max_clients\":%d," - "\"max_players\":%d," - "\"passworded\":%s," - "\"game_type\":\"%s\"," - "\"name\":\"%s\"," - "\"map\":{" - "\"name\":\"%s\"," - "\"sha256\":\"%s\"," - "\"size\":%d" - "}," - "\"version\":\"%s\"," - "\"client_score_kind\":\"time\"," - "\"requires_login\":false," - "\"clients\":[", - MaxClients, - MaxPlayers, - JsonBool(g_Config.m_Password[0]), - EscapeJson(aGameType, sizeof(aGameType), GameServer()->GameType()), - EscapeJson(aName, sizeof(aName), g_Config.m_SvName), - EscapeJson(aMapName, sizeof(aMapName), m_aCurrentMap), - aMapSha256, - m_aCurrentMapSize[MAP_TYPE_SIX], - EscapeJson(aVersion, sizeof(aVersion), GameServer()->Version())); - - bool FirstPlayer = true; + CJsonStringWriter JsonWriter; + + JsonWriter.BeginObject(); + JsonWriter.WriteAttribute("max_clients"); + JsonWriter.WriteIntValue(MaxClients); + + JsonWriter.WriteAttribute("max_players"); + JsonWriter.WriteIntValue(MaxPlayers); + + JsonWriter.WriteAttribute("passworded"); + JsonWriter.WriteBoolValue(g_Config.m_Password[0]); + + JsonWriter.WriteAttribute("game_type"); + JsonWriter.WriteStrValue(GameServer()->GameType()); + + JsonWriter.WriteAttribute("name"); + JsonWriter.WriteStrValue(g_Config.m_SvName); + + JsonWriter.WriteAttribute("map"); + JsonWriter.BeginObject(); + JsonWriter.WriteAttribute("name"); + JsonWriter.WriteStrValue(m_aCurrentMap); + JsonWriter.WriteAttribute("sha256"); + JsonWriter.WriteStrValue(aMapSha256); + JsonWriter.WriteAttribute("size"); + JsonWriter.WriteIntValue(m_aCurrentMapSize[MAP_TYPE_SIX]); + JsonWriter.EndObject(); + + JsonWriter.WriteAttribute("version"); + JsonWriter.WriteStrValue(GameServer()->Version()); + + JsonWriter.WriteAttribute("client_score_kind"); + JsonWriter.WriteStrValue("time"); // "points" or "time" + + JsonWriter.WriteAttribute("requires_login"); + JsonWriter.WriteBoolValue(false); + + JsonWriter.WriteAttribute("clients"); + JsonWriter.BeginArray(); + for(int i = 0; i < MAX_CLIENTS; i++) { if(m_aClients[i].IncludedInServerInfo()) { - char aCName[32]; - char aCClan[32]; - - char aExtraPlayerInfo[512]; - GameServer()->OnUpdatePlayerServerInfo(aExtraPlayerInfo, sizeof(aExtraPlayerInfo), i); - - char aClientInfo[1024]; - str_format(aClientInfo, sizeof(aClientInfo), - "%s{" - "\"name\":\"%s\"," - "\"clan\":\"%s\"," - "\"country\":%d," - "\"score\":%d," - "\"is_player\":%s" - "%s" - "}", - !FirstPlayer ? "," : "", - EscapeJson(aCName, sizeof(aCName), ClientName(i)), - EscapeJson(aCClan, sizeof(aCClan), ClientClan(i)), - m_aClients[i].m_Country, - m_aClients[i].m_Score.value_or(-9999), - JsonBool(GameServer()->IsClientPlayer(i)), - aExtraPlayerInfo); - str_append(aInfo, aClientInfo); - FirstPlayer = false; - } - } - - str_append(aInfo, "]}"); - - m_pRegister->OnNewInfo(aInfo); + JsonWriter.BeginObject(); + + JsonWriter.WriteAttribute("name"); + JsonWriter.WriteStrValue(ClientName(i)); + + JsonWriter.WriteAttribute("clan"); + JsonWriter.WriteStrValue(ClientClan(i)); + + JsonWriter.WriteAttribute("country"); + JsonWriter.WriteIntValue(m_aClients[i].m_Country); + + JsonWriter.WriteAttribute("score"); + JsonWriter.WriteIntValue(m_aClients[i].m_Score.value_or(-9999)); + + JsonWriter.WriteAttribute("is_player"); + JsonWriter.WriteBoolValue(GameServer()->IsClientPlayer(i)); + + GameServer()->OnUpdatePlayerServerInfo(&JsonWriter, i); + + JsonWriter.EndObject(); + } + } + + JsonWriter.EndArray(); + JsonWriter.EndObject(); + + m_pRegister->OnNewInfo(JsonWriter.GetOutputString().c_str()); } void CServer::UpdateServerInfo(bool Resend) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 884ef1e9efd..e47f6347533 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -4842,78 +4842,61 @@ bool CGameContext::RateLimitPlayerMapVote(int ClientId) const return false; } -void CGameContext::OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int Id) +void CGameContext::OnUpdatePlayerServerInfo(CJsonStringWriter *pJSonWriter, int Id) { - if(BufSize <= 0) - return; - - aBuf[0] = '\0'; - if(!m_apPlayers[Id]) return; - char aCSkinName[64]; - CTeeInfo &TeeInfo = m_apPlayers[Id]->m_TeeInfos; - char aJsonSkin[400]; - aJsonSkin[0] = '\0'; + pJSonWriter->WriteAttribute("skin"); + pJSonWriter->BeginObject(); + // 0.6 if(!Server()->IsSixup(Id)) { - // 0.6 + pJSonWriter->WriteAttribute("name"); + pJSonWriter->WriteStrValue(TeeInfo.m_aSkinName); + if(TeeInfo.m_UseCustomColor) { - str_format(aJsonSkin, sizeof(aJsonSkin), - "\"name\":\"%s\"," - "\"color_body\":%d," - "\"color_feet\":%d", - EscapeJson(aCSkinName, sizeof(aCSkinName), TeeInfo.m_aSkinName), - TeeInfo.m_ColorBody, - TeeInfo.m_ColorFeet); - } - else - { - str_format(aJsonSkin, sizeof(aJsonSkin), - "\"name\":\"%s\"", - EscapeJson(aCSkinName, sizeof(aCSkinName), TeeInfo.m_aSkinName)); + pJSonWriter->WriteAttribute("color_body"); + pJSonWriter->WriteIntValue(TeeInfo.m_ColorBody); + + pJSonWriter->WriteAttribute("color_feet"); + pJSonWriter->WriteIntValue(TeeInfo.m_ColorFeet); } } + // 0.7 else { const char *apPartNames[protocol7::NUM_SKINPARTS] = {"body", "marking", "decoration", "hands", "feet", "eyes"}; - char aPartBuf[64]; for(int i = 0; i < protocol7::NUM_SKINPARTS; ++i) { - str_format(aPartBuf, sizeof(aPartBuf), - "%s\"%s\":{" - "\"name\":\"%s\"", - i == 0 ? "" : ",", - apPartNames[i], - EscapeJson(aCSkinName, sizeof(aCSkinName), TeeInfo.m_apSkinPartNames[i])); + pJSonWriter->WriteAttribute(apPartNames[i]); + pJSonWriter->BeginObject(); - str_append(aJsonSkin, aPartBuf); + pJSonWriter->WriteAttribute("name"); + pJSonWriter->WriteStrValue(TeeInfo.m_apSkinPartNames[i]); if(TeeInfo.m_aUseCustomColors[i]) { - str_format(aPartBuf, sizeof(aPartBuf), - ",\"color\":%d", - TeeInfo.m_aSkinPartColors[i]); - str_append(aJsonSkin, aPartBuf); + pJSonWriter->WriteAttribute("color"); + pJSonWriter->WriteIntValue(TeeInfo.m_aSkinPartColors[i]); } - str_append(aJsonSkin, "}"); + + pJSonWriter->EndObject(); } } + pJSonWriter->EndObject(); + + pJSonWriter->WriteAttribute("afk"); + pJSonWriter->WriteBoolValue(m_apPlayers[Id]->IsAfk()); + const int Team = m_pController->IsTeamPlay() ? m_apPlayers[Id]->GetTeam() : m_apPlayers[Id]->GetTeam() == TEAM_SPECTATORS ? -1 : GetDDRaceTeam(Id); - str_format(aBuf, BufSize, - ",\"skin\":{" - "%s" - "}," - "\"afk\":%s," - "\"team\":%d", - aJsonSkin, - JsonBool(m_apPlayers[Id]->IsAfk()), - Team); + + pJSonWriter->WriteAttribute("team"); + pJSonWriter->WriteIntValue(Team); } diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 62e235caec8..66db3dc51d3 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -357,7 +357,7 @@ class CGameContext : public IGameServer bool RateLimitPlayerVote(int ClientId); bool RateLimitPlayerMapVote(int ClientId) const; - void OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int Id) override; + void OnUpdatePlayerServerInfo(CJsonStringWriter *pJSonWriter, int Id) override; std::shared_ptr m_SqlRandomMapResult; From 7ee735a2740cda171b1be72955e4715427060a47 Mon Sep 17 00:00:00 2001 From: Pavukoplov Date: Sat, 24 Aug 2024 00:03:29 +0300 Subject: [PATCH 14/26] Update belarusian translation for 18.4 --- data/languages/belarusian.txt | 113 +++++++++++++++++----------------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/data/languages/belarusian.txt b/data/languages/belarusian.txt index e769d9fd788..097e6c9e240 100644 --- a/data/languages/belarusian.txt +++ b/data/languages/belarusian.txt @@ -2,10 +2,11 @@ #originally created by: # arionwt1997 #modified by: -# Chill & PoKeMoN 2023-03-31 16:00:00 -# Chill & PoKeMoN 2023-07-02 00:54:00 +# Chill [TD] & PoKeMoN [TD] 2023-03-31 16:00:00 +# Chill [TD] & PoKeMoN [TD] 2023-07-02 00:54:00 # Chill [TD] & PoKeMoN [TD] 2023-09-22 17:49:00 # Chill [TD] & PoKeMoN [TD] 2023-11-14 12:42:00 +# Chill [TD] & PoKeMoN [TD] 2024-08-23 23:17:00 # ##### /authors ##### @@ -345,7 +346,7 @@ Shotgun == Драбавік Show chat -== Паказаць чат +== Паказваць чат Show friends only == Толькі з сябрамі @@ -381,7 +382,7 @@ Stop record == Стоп запісу Strict gametype filter -== Строгі фільтр рэжым. +== Строгі фільтр рэжымаў Sudden Death == Раптоўная смерць @@ -1714,162 +1715,162 @@ A render command failed. Try to update your GPU drivers. [Graphics error] Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan in settings_ddnet.cfg in the config directory and try again. -== +== Памылка падчас ініцыялізацыі. Паспрабуйце змяніць gfx_backend на OpenGL або Vulcan у settings_dnet.cfg ў тэчцы канфігурацыйных файлаў і паспрабуйце яшчэ раз. [Graphics error] Out of VRAM. Try removing custom assets (skins, entities, etc.), especially those with high resolution. -== +== Недахоп VRAM. Паспрабуйце выдаліць карыстацкія тэкстуры (скіны, энтыты і г.д.), асабліва тыя, якія маюць высокую разрознасць. [Graphics error] Submitting the render commands failed. Try to update your GPU drivers. -== +== Адпраўка каманд рэндэрынгу не ўдалася. Паспрабуйце абнавіць драйверы відэакарты. Failed saving the replay! -== +== Не атрымалася захаваць паўтор! Saving settings to '%s' failed -== +== Захаванне налад у '%s' не атрымалася Error saving settings -== +== Памылка захавання налад Loading demo file from storage -== +== Загрузка файла дэма са сховішча Searching -== +== Пошук Enter Username -== +== Увядзіце імя карыстальніка Enter Password -== +== Увядзіце пароль NOT CONNECTED -== +== НЕ ПАДЛУЧАНА Match %d of %d -== +== Адпаведна %d з %d No results -== +== Няма вынікаў Lines %d - %d (%s) -== +== Радкі %d - %d (%s) Locked -== +== Зафіксаваныя Following -== +== Бягучыя Loading commands… -== +== Загрузка каманд... [Spectating] Following %s -== +== Назіранне за %s Press a key… -== +== Націсніце клавішу... Main menu -== +== Галоўнае меню Are you sure that you want to restart? -== +== Вы ўпэўненыя, што хочаце пачаць нанова? There's an unsaved map in the editor, you might want to save it. -== +== У рэдактары засталася не захаваная карта, магчыма вы хочаце яе захаваць. Continue anyway? -== +== Працягнуць у любым выпадку? %d/%d KiB (%.1f KiB/s) -== +== %d/%d КіБ (%.1f КіБ/с) Example of usage -== +== Прыклад выкарыстання No login required -== +== Уваход не патрабуецца Communities -== +== Суполкі Server filter -== +== Фільтр сервераў Friends -== +== Сябры Loading… -== +== Загрузка... Player info change cooldown -== +== Затрымка абнаўлення інфармацыі пра гульца Tee -== +== Tee Info Messages -== +== Інфа. Паведамленні Show local time always -== +== Заўсёды паказваць мясцовы час Always show chat -== +== Заўсёды паказваць чат Show only chat messages from team members -== +== Паказваць паведамленні чата толькі ад чальцоў каманды Chat font size -== +== Памер шрыфта чата Chat width -== +== Шырыня чата Show friend mark (♥) in name plates -== +== Паказваць адзнаку сябра (♥) у таблічках з імёнамі Show hook strength icon indicator -== +== Паказваць іконку індыкатара сілы крука Show hook strength number indicator -== +== Паказваць нумар індыкатара сілы крука Authed name color in scoreboard -== +== Колер аўтарызаваных гульцоў у табло ачкоў Same clan color in scoreboard -== +== Колер твайго клана ў табло ачкоў Show own player's hook collision line -== +== Паказваць сваю лінію сутыкнення крука Always show own player's hook collision line -== +== Заўсёды паказваць сваю лінію сутыкнення крука Always show other players' hook collision lines -== +== Заўсёды паказваць лініі сутыкнення крука іншых гульцоў Show finish messages -== +== Паказваць паведамленні пра фініш Round %d/%d -== +== Раўнд %d/%d [Spectators] %d others… -== +== %d іншых... [Team and size] %d\n(%d/%d) -== +== %d\n(%d/%d) Team %d (%d/%d) -== +== Каманда %d (%d/%d) https://wiki.ddnet.org/wiki/Mapping -== +== https://wiki.ddnet.org/wiki/Mapping From 93675799e8e8e6499527140b32859d2ea7717fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 24 Aug 2024 00:08:05 +0200 Subject: [PATCH 15/26] Fix inconsistent margin in ingame menu, minor refactoring The additional margin left of the Spectate and Join game buttons is unnecessary. Avoid reusing `s_SpectateButton` variable for different buttons. Use `TEAM_RED` instead of `0` when joining game in non-team server. --- src/game/client/components/menus_ingame.cpp | 29 ++++++++++----------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 0b751848a4a..47e1ea345b1 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -69,7 +69,7 @@ void CMenus::RenderGame(CUIRect MainView) } } - ButtonBar.VSplitRight(5.0f, &ButtonBar, 0); + ButtonBar.VSplitRight(5.0f, &ButtonBar, nullptr); ButtonBar.VSplitRight(170.0f, &ButtonBar, &Button); static CButtonContainer s_DummyButton; @@ -107,9 +107,8 @@ void CMenus::RenderGame(CUIRect MainView) } } - ButtonBar.VSplitRight(5.0f, &ButtonBar, 0); + ButtonBar.VSplitRight(5.0f, &ButtonBar, nullptr); ButtonBar.VSplitRight(140.0f, &ButtonBar, &Button); - static CButtonContainer s_DemoButton; const bool Recording = DemoRecorder(RECORDER_MANUAL)->IsRecording(); if(DoButton_Menu(&s_DemoButton, Recording ? Localize("Stop record") : Localize("Record demo"), 0, &Button)) @@ -130,12 +129,11 @@ void CMenus::RenderGame(CUIRect MainView) if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pGameInfoObj && !Paused && !Spec) { - static CButtonContainer s_SpectateButton; - if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); + ButtonBar.VSplitLeft(5.0f, nullptr, &ButtonBar); + static CButtonContainer s_SpectateButton; if(!Client()->DummyConnecting() && DoButton_Menu(&s_SpectateButton, Localize("Spectate"), 0, &Button)) { if(g_Config.m_ClDummy == 0 || Client()->DummyConnected()) @@ -150,8 +148,8 @@ void CMenus::RenderGame(CUIRect MainView) { if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_RED) { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); + ButtonBar.VSplitLeft(5.0f, nullptr, &ButtonBar); static CButtonContainer s_JoinRedButton; if(!Client()->DummyConnecting() && DoButton_Menu(&s_JoinRedButton, Localize("Join red"), 0, &Button)) { @@ -162,8 +160,8 @@ void CMenus::RenderGame(CUIRect MainView) if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_BLUE) { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); + ButtonBar.VSplitLeft(5.0f, nullptr, &ButtonBar); static CButtonContainer s_JoinBlueButton; if(!Client()->DummyConnecting() && DoButton_Menu(&s_JoinBlueButton, Localize("Join blue"), 0, &Button)) { @@ -174,13 +172,14 @@ void CMenus::RenderGame(CUIRect MainView) } else { - if(m_pClient->m_Snap.m_pLocalInfo->m_Team != 0) + if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_RED) { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(120.0f, &Button, &ButtonBar); - if(!Client()->DummyConnecting() && DoButton_Menu(&s_SpectateButton, Localize("Join game"), 0, &Button)) + ButtonBar.VSplitLeft(5.0f, nullptr, &ButtonBar); + static CButtonContainer s_JoinGameButton; + if(!Client()->DummyConnecting() && DoButton_Menu(&s_JoinGameButton, Localize("Join game"), 0, &Button)) { - m_pClient->SendSwitchTeam(0); + m_pClient->SendSwitchTeam(TEAM_RED); SetActive(false); } } @@ -188,8 +187,8 @@ void CMenus::RenderGame(CUIRect MainView) if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS && (ShowDDRaceButtons || !(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS))) { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); ButtonBar.VSplitLeft(65.0f, &Button, &ButtonBar); + ButtonBar.VSplitLeft(5.0f, nullptr, &ButtonBar); static CButtonContainer s_KillButton; if(DoButton_Menu(&s_KillButton, Localize("Kill"), 0, &Button)) @@ -204,13 +203,13 @@ void CMenus::RenderGame(CUIRect MainView) { if(m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS || Paused || Spec) { - ButtonBar.VSplitLeft(5.0f, 0, &ButtonBar); ButtonBar.VSplitLeft((!Paused && !Spec) ? 65.0f : 120.0f, &Button, &ButtonBar); + ButtonBar.VSplitLeft(5.0f, nullptr, &ButtonBar); static CButtonContainer s_PauseButton; if(DoButton_Menu(&s_PauseButton, (!Paused && !Spec) ? Localize("Pause") : Localize("Join game"), 0, &Button)) { - m_pClient->Console()->ExecuteLine("say /pause"); + Console()->ExecuteLine("say /pause"); SetActive(false); } } From 3bda76ff1fa958bac712b602bde057e5b7aada55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Wed, 21 Aug 2024 20:43:53 +0200 Subject: [PATCH 16/26] Add touch support to emoticon selector and spectator menu Support using emoticon selector and spectator menu with touch inputs. Touching in the emoticon selector activates the selected (eye) emote and closes the emoticon selector. The spectator menu is kept open when using it for more convenience. The emoticon selector and spectator menu can be closed by touching anywhere outside of them. Additionally, the emoticon selector and specator menu can now be closed by pressing the Escape key (i.e. the back button on Android). This made it necessary to change the order of the components for input handling, so the ingame menu will handle the Escape key after the emoticon selector and spectator menu. This also means that the Escape key can now be used to close the selector/menu when they are stuck open, which was previously delayed until ingame menu or console were opened and closed. --- src/game/client/components/emoticon.cpp | 43 +++++++++++++++++++++-- src/game/client/components/emoticon.h | 8 +++++ src/game/client/components/spectator.cpp | 44 +++++++++++++++++++++--- src/game/client/components/spectator.h | 6 ++++ src/game/client/gameclient.cpp | 2 +- 5 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/game/client/components/emoticon.cpp b/src/game/client/components/emoticon.cpp index 9d0d2b91061..dc1fe96b3f4 100644 --- a/src/game/client/components/emoticon.cpp +++ b/src/game/client/components/emoticon.cpp @@ -41,6 +41,7 @@ void CEmoticon::OnReset() m_Active = false; m_SelectedEmote = -1; m_SelectedEyeEmote = -1; + m_TouchPressedOutside = false; } void CEmoticon::OnRelease() @@ -58,6 +59,16 @@ bool CEmoticon::OnCursorMove(float x, float y, IInput::ECursorType CursorType) return true; } +bool CEmoticon::OnInput(const IInput::CEvent &Event) +{ + if(IsActive() && Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE) + { + OnRelease(); + return true; + } + return false; +} + void CEmoticon::OnRender() { if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) @@ -65,6 +76,13 @@ void CEmoticon::OnRender() if(!m_Active) { + if(m_TouchPressedOutside) + { + m_SelectedEmote = -1; + m_SelectedEyeEmote = -1; + m_TouchPressedOutside = false; + } + if(m_WasActive && m_SelectedEmote != -1) Emote(m_SelectedEmote); if(m_WasActive && m_SelectedEyeEmote != -1) @@ -82,6 +100,29 @@ void CEmoticon::OnRender() m_WasActive = true; + const CUIRect Screen = *Ui()->Screen(); + + const bool WasTouchPressed = m_TouchState.m_AnyPressed; + Ui()->UpdateTouchState(m_TouchState); + if(m_TouchState.m_AnyPressed) + { + const vec2 TouchPos = (m_TouchState.m_PrimaryPosition - vec2(0.5f, 0.5f)) * Screen.Size(); + const float TouchCenterDistance = length(TouchPos); + if(TouchCenterDistance <= 170.0f) + { + m_SelectorMouse = TouchPos; + } + else if(TouchCenterDistance > 190.0f) + { + m_TouchPressedOutside = true; + } + } + else if(WasTouchPressed) + { + m_Active = false; + return; + } + if(length(m_SelectorMouse) > 170.0f) m_SelectorMouse = normalize(m_SelectorMouse) * 170.0f; @@ -96,8 +137,6 @@ void CEmoticon::OnRender() else if(length(m_SelectorMouse) > 40.0f) m_SelectedEyeEmote = (int)(SelectedAngle / (2 * pi) * NUM_EMOTES); - CUIRect Screen = *Ui()->Screen(); - Ui()->MapScreen(); Graphics()->BlendNormal(); diff --git a/src/game/client/components/emoticon.h b/src/game/client/components/emoticon.h index c7d068cb1bb..890f1c80efe 100644 --- a/src/game/client/components/emoticon.h +++ b/src/game/client/components/emoticon.h @@ -4,7 +4,9 @@ #define GAME_CLIENT_COMPONENTS_EMOTICON_H #include #include + #include +#include class CEmoticon : public CComponent { @@ -15,6 +17,9 @@ class CEmoticon : public CComponent int m_SelectedEmote; int m_SelectedEyeEmote; + CUi::CTouchState m_TouchState; + bool m_TouchPressedOutside; + static void ConKeyEmoticon(IConsole::IResult *pResult, void *pUserData); static void ConEmote(IConsole::IResult *pResult, void *pUserData); @@ -27,9 +32,12 @@ class CEmoticon : public CComponent virtual void OnRender() override; virtual void OnRelease() override; virtual bool OnCursorMove(float x, float y, IInput::ECursorType CursorType) override; + virtual bool OnInput(const IInput::CEvent &Event) override; void Emote(int Emoticon); void EyeEmote(int EyeEmote); + + bool IsActive() const { return m_Active; } }; #endif diff --git a/src/game/client/components/spectator.cpp b/src/game/client/components/spectator.cpp index 97b9d955b3b..c14a33f91b0 100644 --- a/src/game/client/components/spectator.cpp +++ b/src/game/client/components/spectator.cpp @@ -186,6 +186,16 @@ bool CSpectator::OnCursorMove(float x, float y, IInput::ECursorType CursorType) return true; } +bool CSpectator::OnInput(const IInput::CEvent &Event) +{ + if(IsActive() && Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE) + { + OnRelease(); + return true; + } + return false; +} + void CSpectator::OnRelease() { OnReset(); @@ -258,8 +268,6 @@ void CSpectator::OnRender() float BoxMove = -10.0f; float BoxOffset = 0.0f; - const bool MousePressed = Input()->KeyPress(KEY_MOUSE_1); - for(const auto &pInfo : m_pClient->m_Snap.m_apInfoByDDTeamName) { if(!pInfo || pInfo->m_Team == TEAM_SPECTATORS) @@ -293,14 +301,42 @@ void CSpectator::OnRender() ObjWidth = 600.0f; } + const vec2 ScreenSize = vec2(Width, Height); + const vec2 ScreenCenter = ScreenSize / 2.0f; + CUIRect SpectatorRect = {Width / 2.0f - ObjWidth, Height / 2.0f - 300.0f, ObjWidth * 2.0f, 600.0f}; + CUIRect SpectatorMouseRect; + SpectatorRect.Margin(20.0f, &SpectatorMouseRect); + + const bool WasTouchPressed = m_TouchState.m_AnyPressed; + Ui()->UpdateTouchState(m_TouchState); + if(m_TouchState.m_AnyPressed) + { + const vec2 TouchPos = (m_TouchState.m_PrimaryPosition - vec2(0.5f, 0.5f)) * ScreenSize; + if(SpectatorMouseRect.Inside(ScreenCenter + TouchPos)) + { + m_SelectorMouse = TouchPos; + } + } + else if(WasTouchPressed) + { + const vec2 TouchPos = (m_TouchState.m_PrimaryPosition - vec2(0.5f, 0.5f)) * ScreenSize; + if(!SpectatorRect.Inside(ScreenCenter + TouchPos)) + { + OnRelease(); + return; + } + } + Graphics()->MapScreen(0, 0, Width, Height); - Graphics()->DrawRect(Width / 2.0f - ObjWidth, Height / 2.0f - 300.0f, ObjWidth * 2, 600.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f), IGraphics::CORNER_ALL, 20.0f); + SpectatorRect.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f), IGraphics::CORNER_ALL, 20.0f); // clamp mouse position to selector area m_SelectorMouse.x = clamp(m_SelectorMouse.x, -(ObjWidth - 20.0f), ObjWidth - 20.0f); m_SelectorMouse.y = clamp(m_SelectorMouse.y, -280.0f, 280.0f); + const bool MousePressed = Input()->KeyPress(KEY_MOUSE_1) || m_TouchState.m_PrimaryPressed; + // draw selections if((Client()->State() == IClient::STATE_DEMOPLAYBACK && m_pClient->m_DemoSpecId == SPEC_FREEVIEW) || (Client()->State() != IClient::STATE_DEMOPLAYBACK && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId == SPEC_FREEVIEW)) @@ -543,7 +579,7 @@ void CSpectator::OnRender() } TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); - RenderTools()->RenderCursor(m_SelectorMouse + vec2(Width, Height) / 2, 48.0f); + RenderTools()->RenderCursor(ScreenCenter + m_SelectorMouse, 48.0f); } void CSpectator::OnReset() diff --git a/src/game/client/components/spectator.h b/src/game/client/components/spectator.h index e7fdc2449e6..25dddfb60e4 100644 --- a/src/game/client/components/spectator.h +++ b/src/game/client/components/spectator.h @@ -6,6 +6,7 @@ #include #include +#include class CSpectator : public CComponent { @@ -21,6 +22,8 @@ class CSpectator : public CComponent int m_SelectedSpectatorId; vec2 m_SelectorMouse; + CUi::CTouchState m_TouchState; + float m_MultiViewActivateDelay; bool CanChangeSpectator(); @@ -39,12 +42,15 @@ class CSpectator : public CComponent virtual void OnConsoleInit() override; virtual bool OnCursorMove(float x, float y, IInput::ECursorType CursorType) override; + virtual bool OnInput(const IInput::CEvent &Event) override; virtual void OnRender() override; virtual void OnRelease() override; virtual void OnReset() override; void Spectate(int SpectatorId); void SpectateClosest(); + + bool IsActive() const { return m_Active; } }; #endif diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 636f9e80668..1f450fa0446 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -160,9 +160,9 @@ void CGameClient::OnConsoleInit() &m_GameConsole, &m_Chat, // chat has higher prio, due to that you can quit it by pressing esc &m_Motd, // for pressing esc to remove it - &m_Menus, &m_Spectator, &m_Emoticon, + &m_Menus, &m_Controls, &m_Binds}); From ed9720f1be0f2b612331c663645b0fd1f26b9b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 22 Aug 2024 21:40:10 +0200 Subject: [PATCH 17/26] Minor refactoring of emoticon selector rendering - Extract variable `ScreenCenter`. - Rename variables `i` to `Emote`. - Move variable declarations closer to their usages. --- src/game/client/components/emoticon.cpp | 34 +++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/game/client/components/emoticon.cpp b/src/game/client/components/emoticon.cpp index dc1fe96b3f4..e7c0963d0ce 100644 --- a/src/game/client/components/emoticon.cpp +++ b/src/game/client/components/emoticon.cpp @@ -137,6 +137,8 @@ void CEmoticon::OnRender() else if(length(m_SelectorMouse) > 40.0f) m_SelectedEyeEmote = (int)(SelectedAngle / (2 * pi) * NUM_EMOTES); + const vec2 ScreenCenter = Screen.Center(); + Ui()->MapScreen(); Graphics()->BlendNormal(); @@ -144,26 +146,22 @@ void CEmoticon::OnRender() Graphics()->TextureClear(); Graphics()->QuadsBegin(); Graphics()->SetColor(0, 0, 0, 0.3f); - Graphics()->DrawCircle(Screen.w / 2, Screen.h / 2, 190.0f, 64); + Graphics()->DrawCircle(ScreenCenter.x, ScreenCenter.y, 190.0f, 64); Graphics()->QuadsEnd(); Graphics()->WrapClamp(); - for(int i = 0; i < NUM_EMOTICONS; i++) + for(int Emote = 0; Emote < NUM_EMOTICONS; Emote++) { - float Angle = 2 * pi * i / NUM_EMOTICONS; + float Angle = 2 * pi * Emote / NUM_EMOTICONS; if(Angle > pi) Angle -= 2 * pi; - bool Selected = m_SelectedEmote == i; - - float Size = Selected ? 80.0f : 50.0f; - - Graphics()->TextureSet(GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[i]); + Graphics()->TextureSet(GameClient()->m_EmoticonsSkin.m_aSpriteEmoticons[Emote]); Graphics()->QuadsSetSubset(0, 0, 1, 1); - Graphics()->QuadsBegin(); const vec2 Nudge = direction(Angle) * 150.0f; - IGraphics::CQuadItem QuadItem(Screen.w / 2 + Nudge.x, Screen.h / 2 + Nudge.y, Size, Size); + const float Size = m_SelectedEmote == Emote ? 80.0f : 50.0f; + IGraphics::CQuadItem QuadItem(ScreenCenter.x + Nudge.x, ScreenCenter.y + Nudge.y, Size, Size); Graphics()->QuadsDraw(&QuadItem, 1); Graphics()->QuadsEnd(); } @@ -174,34 +172,32 @@ void CEmoticon::OnRender() Graphics()->TextureClear(); Graphics()->QuadsBegin(); Graphics()->SetColor(1.0, 1.0, 1.0, 0.3f); - Graphics()->DrawCircle(Screen.w / 2, Screen.h / 2, 100.0f, 64); + Graphics()->DrawCircle(ScreenCenter.x, ScreenCenter.y, 100.0f, 64); Graphics()->QuadsEnd(); CTeeRenderInfo TeeInfo = m_pClient->m_aClients[m_pClient->m_aLocalIds[g_Config.m_ClDummy]].m_RenderInfo; - for(int i = 0; i < NUM_EMOTES; i++) + for(int Emote = 0; Emote < NUM_EMOTES; Emote++) { - float Angle = 2 * pi * i / NUM_EMOTES; + float Angle = 2 * pi * Emote / NUM_EMOTES; if(Angle > pi) Angle -= 2 * pi; - const bool Selected = m_SelectedEyeEmote == i; - const vec2 Nudge = direction(Angle) * 70.0f; - TeeInfo.m_Size = Selected ? 64.0f : 48.0f; - RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, i, vec2(-1, 0), vec2(Screen.w / 2 + Nudge.x, Screen.h / 2 + Nudge.y)); + TeeInfo.m_Size = m_SelectedEyeEmote == Emote ? 64.0f : 48.0f; + RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, Emote, vec2(-1, 0), ScreenCenter + Nudge); } Graphics()->TextureClear(); Graphics()->QuadsBegin(); Graphics()->SetColor(0, 0, 0, 0.3f); - Graphics()->DrawCircle(Screen.w / 2, Screen.h / 2, 30.0f, 64); + Graphics()->DrawCircle(ScreenCenter.x, ScreenCenter.y, 30.0f, 64); Graphics()->QuadsEnd(); } else m_SelectedEyeEmote = -1; - RenderTools()->RenderCursor(m_SelectorMouse + vec2(Screen.w, Screen.h) / 2, 24.0f); + RenderTools()->RenderCursor(ScreenCenter + m_SelectorMouse, 24.0f); } void CEmoticon::Emote(int Emoticon) From 9b90588ccc240b60e9e72261f020b8c8c07f48c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 27 Jul 2024 16:40:05 +0200 Subject: [PATCH 18/26] Pin curl version to 8.8.0 for Android to fix library building --- scripts/compile_libs/gen_libs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/compile_libs/gen_libs.sh b/scripts/compile_libs/gen_libs.sh index f9134c03cd1..e9a6436bcc6 100755 --- a/scripts/compile_libs/gen_libs.sh +++ b/scripts/compile_libs/gen_libs.sh @@ -80,7 +80,7 @@ cd compile_libs || exit 1 build_cmake_lib zlib https://github.com/madler/zlib build_cmake_lib png https://github.com/glennrp/libpng -build_cmake_lib curl https://github.com/curl/curl +build_cmake_lib curl https://github.com/curl/curl "curl-8_8_0" build_cmake_lib freetype2 https://gitlab.freedesktop.org/freetype/freetype build_cmake_lib sdl https://github.com/libsdl-org/SDL SDL2 build_cmake_lib ogg https://github.com/xiph/ogg From e46d2375bab79683bcf33e12cc61b9a18628ee0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 27 Jul 2024 16:40:05 +0200 Subject: [PATCH 19/26] Add note to Android README about using stable rust version --- scripts/android/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/android/README.md b/scripts/android/README.md index 561aaea13f3..a34bdc315a9 100644 --- a/scripts/android/README.md +++ b/scripts/android/README.md @@ -4,6 +4,7 @@ Requirements for building for Android - At least 10-15 GiB of free disk space. - First follow the general instructions for setting up https://github.com/ddnet/ddnet for building on Linux. This guide has only been tested on Linux. +- Note: Use a stable version of Rust. Using the nightly version results in linking errors. - Install the Android NDK (version 26) in the same location where Android Studio would unpack it (`~/Android/Sdk/ndk/`): ```shell From 24356bd029c7b14ff33aebfbf4d0b28d66bfdd03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Sat, 24 Aug 2024 13:44:30 +0200 Subject: [PATCH 20/26] Set `debuggable` flag in Gradle debug build So the debug build of the app can be debugged. --- scripts/android/files/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/android/files/build.gradle b/scripts/android/files/build.gradle index 1025f955ed1..31bb06bb6a9 100644 --- a/scripts/android/files/build.gradle +++ b/scripts/android/files/build.gradle @@ -43,6 +43,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { + debuggable true minifyEnabled false shrinkResources false } From 6c0e42987e87deb413a125490e07a096db5ddcd0 Mon Sep 17 00:00:00 2001 From: furo Date: Sat, 24 Aug 2024 22:32:13 +0200 Subject: [PATCH 21/26] Add a popup for picking a map for background entities setting --- src/engine/shared/config_variables.h | 2 +- src/game/client/components/menus.h | 30 +++++ src/game/client/components/menus_settings.cpp | 121 +++++++++++++++++- 3 files changed, 148 insertions(+), 5 deletions(-) diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h index b18acd47b8d..15f10e22a0c 100644 --- a/src/engine/shared/config_variables.h +++ b/src/engine/shared/config_variables.h @@ -599,7 +599,7 @@ MACRO_CONFIG_INT(ClOverlayEntities, cl_overlay_entities, 0, 0, 100, CFGFLAG_CLIE MACRO_CONFIG_INT(ClShowQuads, cl_showquads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show quads (only interesting for mappers, or if your system has extremely bad performance)") MACRO_CONFIG_COL(ClBackgroundColor, cl_background_color, 128, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background color") // 0 0 128 MACRO_CONFIG_COL(ClBackgroundEntitiesColor, cl_background_entities_color, 128, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background (entities) color") // 0 0 128 -MACRO_CONFIG_STR(ClBackgroundEntities, cl_background_entities, 100, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background (entities)") +MACRO_CONFIG_STR(ClBackgroundEntities, cl_background_entities, IO_MAX_PATH_LENGTH, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background (entities)") MACRO_CONFIG_STR(ClRunOnJoin, cl_run_on_join, 100, "", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Command to run when joining a server") // menu background map diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index f16cfec7416..6fae3606eb8 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -607,6 +607,36 @@ class CMenus : public CComponent void RenderSettings(CUIRect MainView); void RenderSettingsCustom(CUIRect MainView); + class CMapListItem + { + public: + char m_aFilename[IO_MAX_PATH_LENGTH]; + bool m_IsDirectory; + }; + class CPopupMapPickerContext + { + public: + std::vector m_vMaps; + char m_aCurrentMapFolder[IO_MAX_PATH_LENGTH] = ""; + static int MapListFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser); + void MapListPopulate(); + CMenus *m_pMenus; + int m_Selection; + }; + + static bool CompareFilenameAscending(const CMapListItem Lhs, const CMapListItem Rhs) + { + if(str_comp(Lhs.m_aFilename, "..") == 0) + return true; + if(str_comp(Rhs.m_aFilename, "..") == 0) + return false; + if(Lhs.m_IsDirectory != Rhs.m_IsDirectory) + return Lhs.m_IsDirectory; + return str_comp_filenames(Lhs.m_aFilename, Rhs.m_aFilename) < 0; + } + + static CUi::EPopupMenuFunctionResult PopupMapPicker(void *pContext, CUIRect View, bool Active); + void SetNeedSendInfo(); void SetActive(bool Active); void UpdateColors(); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index af5f40f56e0..3259f0ff1d3 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -3212,11 +3212,12 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) static CButtonContainer s_ResetId2; DoLine_ColorPicker(&s_ResetId2, 25.0f, 13.0f, 5.0f, &Background, Localize("Entities background color"), &g_Config.m_ClBackgroundEntitiesColor, GreyDefault, false); - CUIRect EditBox; + CUIRect EditBox, ReloadButton; Background.HSplitTop(20.0f, &Label, &Background); Background.HSplitTop(2.0f, nullptr, &Background); Label.VSplitLeft(100.0f, &Label, &EditBox); - EditBox.VSplitRight(100.0f, &EditBox, &Button); + EditBox.VSplitRight(60.0f, &EditBox, &Button); + Button.VSplitMid(&ReloadButton, &Button, 5.0f); EditBox.VSplitRight(5.0f, &EditBox, nullptr); Ui()->DoLabel(&Label, Localize("Map"), 14.0f, TEXTALIGN_ML); @@ -3224,12 +3225,23 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) static CLineInput s_BackgroundEntitiesInput(g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities)); Ui()->DoEditBox(&s_BackgroundEntitiesInput, &EditBox, 14.0f); - static CButtonContainer s_BackgroundEntitiesReloadButton; - if(DoButton_Menu(&s_BackgroundEntitiesReloadButton, Localize("Reload"), 0, &Button)) + static CButtonContainer s_BackgroundEntitiesMapPicker, s_BackgroundEntitiesReload; + + if(DoButton_FontIcon(&s_BackgroundEntitiesReload, FONT_ICON_ARROW_ROTATE_RIGHT, 0, &ReloadButton)) { UpdateBackgroundEntities(); } + if(DoButton_FontIcon(&s_BackgroundEntitiesMapPicker, FONT_ICON_FOLDER, 0, &Button)) + { + static SPopupMenuId s_PopupMapPickerId; + static CPopupMapPickerContext s_PopupMapPickerContext; + s_PopupMapPickerContext.m_pMenus = this; + + s_PopupMapPickerContext.MapListPopulate(); + Ui()->DoPopupMenu(&s_PopupMapPickerId, Ui()->MouseX(), Ui()->MouseY(), 300.0f, 250.0f, &s_PopupMapPickerContext, PopupMapPicker); + } + Background.HSplitTop(20.0f, &Button, &Background); const bool UseCurrentMap = str_comp(g_Config.m_ClBackgroundEntities, CURRENT_MAP) == 0; static int s_UseCurrentMapId = 0; @@ -3320,3 +3332,104 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) } #endif } + +CUi::EPopupMenuFunctionResult CMenus::PopupMapPicker(void *pContext, CUIRect View, bool Active) +{ + CPopupMapPickerContext *pPopupContext = static_cast(pContext); + CMenus *pMenus = pPopupContext->m_pMenus; + + static CListBox s_ListBox; + s_ListBox.SetActive(Active); + s_ListBox.DoStart(20.0f, pPopupContext->m_vMaps.size(), 1, 1, -1, &View, false); + + int MapIndex = 0; + for(auto &Map : pPopupContext->m_vMaps) + { + MapIndex++; + const CListboxItem Item = s_ListBox.DoNextItem(&Map, MapIndex == pPopupContext->m_Selection); + if(!Item.m_Visible) + continue; + + CUIRect Label, Icon; + Item.m_Rect.VSplitLeft(20.0f, &Icon, &Label); + + char aLabelText[IO_MAX_PATH_LENGTH]; + str_copy(aLabelText, Map.m_aFilename); + if(Map.m_IsDirectory) + str_append(aLabelText, "/", sizeof(aLabelText)); + + const char *pIconType; + if(!Map.m_IsDirectory) + { + pIconType = FONT_ICON_MAP; + } + else + { + if(!str_comp(Map.m_aFilename, "..")) + pIconType = FONT_ICON_FOLDER_TREE; + else + pIconType = FONT_ICON_FOLDER; + } + + pMenus->TextRender()->SetFontPreset(EFontPreset::ICON_FONT); + pMenus->TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); + pMenus->Ui()->DoLabel(&Icon, pIconType, 12.0f, TEXTALIGN_ML); + pMenus->TextRender()->SetRenderFlags(0); + pMenus->TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + + pMenus->Ui()->DoLabel(&Label, aLabelText, 10.0f, TEXTALIGN_ML); + } + + const int NewSelected = s_ListBox.DoEnd(); + pPopupContext->m_Selection = NewSelected >= 0 ? NewSelected : -1; + if(s_ListBox.WasItemSelected() || s_ListBox.WasItemActivated()) + { + const CMapListItem SelectedItem = pPopupContext->m_vMaps[pPopupContext->m_Selection]; + + if(SelectedItem.m_IsDirectory) + { + if(!str_comp(SelectedItem.m_aFilename, "..")) + { + fs_parent_dir(pPopupContext->m_aCurrentMapFolder); + } + else + { + str_append(pPopupContext->m_aCurrentMapFolder, "/", sizeof(pPopupContext->m_aCurrentMapFolder)); + str_append(pPopupContext->m_aCurrentMapFolder, SelectedItem.m_aFilename, sizeof(pPopupContext->m_aCurrentMapFolder)); + } + pPopupContext->MapListPopulate(); + } + else + { + str_format(g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities), "%s/%s", pPopupContext->m_aCurrentMapFolder, SelectedItem.m_aFilename); + pMenus->UpdateBackgroundEntities(); + return CUi::POPUP_CLOSE_CURRENT; + } + } + + return CUi::POPUP_KEEP_OPEN; +} + +void CMenus::CPopupMapPickerContext::MapListPopulate() +{ + m_vMaps.clear(); + char aTemp[IO_MAX_PATH_LENGTH]; + str_format(aTemp, sizeof(aTemp), "maps/%s", m_aCurrentMapFolder); + m_pMenus->Storage()->ListDirectoryInfo(IStorage::TYPE_ALL, aTemp, MapListFetchCallback, this); + std::stable_sort(m_vMaps.begin(), m_vMaps.end(), CompareFilenameAscending); +} + +int CMenus::CPopupMapPickerContext::MapListFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser) +{ + CPopupMapPickerContext *pRealUser = (CPopupMapPickerContext *)pUser; + if((!IsDir && !str_endswith(pInfo->m_pName, ".map")) || !str_comp(pInfo->m_pName, ".") || (!str_comp(pInfo->m_pName, "..") && (!str_comp(pRealUser->m_aCurrentMapFolder, "")))) + return 0; + + CMapListItem Item; + str_copy(Item.m_aFilename, pInfo->m_pName); + Item.m_IsDirectory = IsDir; + + pRealUser->m_vMaps.emplace_back(Item); + + return 0; +} From 728bb9777fd64d9f0ff34490ed6b56b39adcbed1 Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 25 Aug 2024 00:40:12 +0200 Subject: [PATCH 22/26] =?UTF-8?q?Validate=20language=20files=20for=20`?= =?UTF-8?q?=E2=80=A6`=20and=20non-matching=20formatters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/style.yml | 2 ++ scripts/languages/twlang.py | 19 ------------ scripts/languages/validate.py | 54 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 19 deletions(-) create mode 100755 scripts/languages/validate.py diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index e0bd0fde714..5b5b811c311 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -40,6 +40,8 @@ jobs: run: scripts/fix_style.py --dry-run - name: Check header guards run: scripts/check_header_guards.py + - name: Validate Languages + run: scripts/languages/validate.py - name: Check languages run: scripts/languages/update_all.py - name: Check dilated images diff --git a/scripts/languages/twlang.py b/scripts/languages/twlang.py index 899e75b35cf..eba1c911610 100644 --- a/scripts/languages/twlang.py +++ b/scripts/languages/twlang.py @@ -7,22 +7,6 @@ def __init__(self, message, filename, line): error = f"File \"{filename}\", line {line+1}: {message}" super().__init__(error) - -# Taken from https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python -cfmt = r'''\ -( # start of capture group 1 -% # literal "%" -(?: # first option -(?:[-+0 #]{0,5}) # optional flags -(?:\d+|\*)? # width -(?:\.(?:\d+|\*))? # precision -(?:h|l|ll|w|I|I32|I64)? # size -[cCdiouxXeEfgGaAnpsSZ] # type -) | # OR -%%) # literal "%%" -''' - - def decode(fileobj, elements_per_key): data = {} current_context = "" @@ -45,10 +29,7 @@ def decode(fileobj, elements_per_key): if len(data[current_key]) >= 1+elements_per_key: raise LanguageDecodeError("Wrong number of elements per key", fileobj.name, index) if current_key: - original = current_key[0] # pylint: disable=unsubscriptable-object translation = line[3:] - if translation and [m.group(1) for m in re.finditer(cfmt, original, flags=re.X)] != [m.group(1) for m in re.finditer(cfmt, translation, flags=re.X)]: - raise LanguageDecodeError("Non-matching formatting string", fileobj.name, index) data[current_key].extend([translation]) else: raise LanguageDecodeError("Element before key given", fileobj.name, index) diff --git a/scripts/languages/validate.py b/scripts/languages/validate.py new file mode 100755 index 00000000000..fe214bdb1b4 --- /dev/null +++ b/scripts/languages/validate.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import os +import sys +import re +import twlang + +os.chdir(os.path.dirname(__file__) + "/../..") + +# Taken from https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python +cfmt = ''' +( # start of capture group 1 +% # literal "%" +(?: # first option +(?:[-+0 #]{0,5}) # optional flags +(?:\\d+|\\*)? # width +(?:\\.(?:\\d+|\\*))? # precision +(?:h|l|ll|w|I|I32|I64)? # size +[cCdiouxXeEfgGaAnpsSZ] # type +) | # OR +%%) # literal "%%" +''' + +total_errors = 0 + +def print_validation_error(error, filename, error_line): + print(f"Invalid: {translated}") + print(f"- {error} in {filename}:{error_line + 1}\n") + global total_errors + total_errors += 1 + +if len(sys.argv) > 1: + languages = sys.argv[1:] +else: + languages = twlang.languages() +local = twlang.localizes() + +for language in languages: + translations = twlang.translations(language) + + for (english, _), (line, translated, _) in translations.items(): + if not translated: + continue + + # Validate c format strings. Strings that move the formatters are not validated. + if re.findall(cfmt, english, flags=re.X) != re.findall(cfmt, translated, flags=re.X) and not "1$" in translated: + print_validation_error("Non-matching formatting", language, line) + + # Check for elipisis + if "…" in english and "..." in translated: + print_validation_error("Usage of ... instead of the … character", language, line) + +if total_errors: + print(f"Found {total_errors} {'error' if total_errors == 1 else 'errors'} ") + sys.exit(1) From 10376fa4ff636aff22dc0a51b990b745535d4ee9 Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 25 Aug 2024 00:40:42 +0200 Subject: [PATCH 23/26] Fix language files that failed validation --- data/languages/arabic.txt | 2 +- data/languages/belarusian.txt | 10 +++++----- data/languages/hungarian.txt | 2 +- data/languages/italian.txt | 2 +- data/languages/korean.txt | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/data/languages/arabic.txt b/data/languages/arabic.txt index 5676c4c9ea6..51ab738ead4 100644 --- a/data/languages/arabic.txt +++ b/data/languages/arabic.txt @@ -1027,7 +1027,7 @@ Existing Player == ﺩﻮﺟﻮﻣ ﺐﻋﻼﻟﺍ Your nickname '%s' is already used (%d points). Do you still want to use it? -== ﻪﻣﺍﺪﺨﺘﺳﺍ ﺪﻳﺮﺗ ﺖﻟﺯﺎﻣ ﻞﻫ '%s' ﻞﻤﻌﺘﺴﻣ ﻚﻤﺳﺍ +== Checking for existing player with your name == ﻚﻤﺳﺎﺑ ﺐﻋﻻ ﺩﻮﺟﻭ ﻦﻣ ﻖﻘﺤﺘﻟﺍ diff --git a/data/languages/belarusian.txt b/data/languages/belarusian.txt index 097e6c9e240..ff2ff0541d8 100644 --- a/data/languages/belarusian.txt +++ b/data/languages/belarusian.txt @@ -1622,7 +1622,7 @@ Go back the specified duration [Demo player duration] %d sec. -== % сек. +== %d сек. Change the skip duration == Змяніць працягласць пропуску @@ -1765,14 +1765,14 @@ Following == Бягучыя Loading commands… -== Загрузка каманд... +== Загрузка каманд… [Spectating] Following %s == Назіранне за %s Press a key… -== Націсніце клавішу... +== Націсніце клавішу… Main menu == Галоўнае меню @@ -1805,7 +1805,7 @@ Friends == Сябры Loading… -== Загрузка... +== Загрузка… Player info change cooldown == Затрымка абнаўлення інфармацыі пра гульца @@ -1863,7 +1863,7 @@ Round %d/%d [Spectators] %d others… -== %d іншых... +== %d іншых… [Team and size] %d\n(%d/%d) diff --git a/data/languages/hungarian.txt b/data/languages/hungarian.txt index a4dc400db84..7f855f55851 100644 --- a/data/languages/hungarian.txt +++ b/data/languages/hungarian.txt @@ -1061,7 +1061,7 @@ Replay == Visszajátszás The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== Ennek a Textúrának szélessége, vagy magassága (%s) nem megfelelően osztható el ezzel a számmal: (%d), ami vizuális hibákhoz vezethet. (Méretek máshogyan fognak kinézni és a teljesítményt is ronthatja.) +== Getting server list from master server == Szerverlista lekérése a fő szerverekről diff --git a/data/languages/italian.txt b/data/languages/italian.txt index 889c3c37396..dc9e290158c 100644 --- a/data/languages/italian.txt +++ b/data/languages/italian.txt @@ -1180,7 +1180,7 @@ No server selected == Nessun server selezionato Online clanmates (%d) -== Compagni di clan online +== Compagni di clan online (%d) Click to select server. Double click to join your friend. == Fare click per selezionare il server. Fai doppio click per unirti al tuo amico. diff --git a/data/languages/korean.txt b/data/languages/korean.txt index 1e5d0235f4d..bfa76bf1984 100644 --- a/data/languages/korean.txt +++ b/data/languages/korean.txt @@ -535,7 +535,7 @@ Replay feature is disabled! == 리플레이 기능을 비활성화했습니다! The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs. -== 텍스처 %s 의 너비 또는 높이를 %d 으로 나눌 수 없습니다. 이는 시각적 오류를 발생시킬 수 있습니다. +== Warning == 주의 From 0b27a47553da63d8b32958d6b01ada55a46d062d Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 25 Aug 2024 19:09:09 +0200 Subject: [PATCH 24/26] Don't allow input in console while it is opening/closing --- src/game/client/components/console.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 30459059063..1c08e615532 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -377,6 +377,10 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) { bool Handled = false; + // Don't allow input while the console is opening/closing + if(m_pGameConsole->m_ConsoleState == CONSOLE_OPENING || m_pGameConsole->m_ConsoleState == CONSOLE_CLOSING) + return Handled; + auto &&SelectNextSearchMatch = [&](int Direction) { if(!m_vSearchMatches.empty()) { From 59dd8735bf21c160a1038d7f3efd8748939d819a Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 25 Aug 2024 19:41:10 +0200 Subject: [PATCH 25/26] Add button to collapse/expand all groups in editor --- src/game/editor/editor.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 4f259191437..1829811e459 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -4297,7 +4297,7 @@ void CEditor::RenderLayers(CUIRect LayersBox) s_ScrollToSelectionNext = true; } - CUIRect AddGroupButton; + CUIRect AddGroupButton, CollapseAllButton; LayersBox.HSplitTop(RowHeight + 1.0f, &AddGroupButton, &LayersBox); if(s_ScrollRegion.AddRect(AddGroupButton)) { @@ -4311,6 +4311,35 @@ void CEditor::RenderLayers(CUIRect LayersBox) } } + LayersBox.HSplitTop(5.0f, nullptr, &LayersBox); + LayersBox.HSplitTop(RowHeight + 1.0f, &CollapseAllButton, &LayersBox); + if(s_ScrollRegion.AddRect(CollapseAllButton)) + { + unsigned long TotalCollapsed = 0; + for(const auto &pGroup : m_Map.m_vpGroups) + { + if(pGroup->m_Collapse) + { + TotalCollapsed++; + } + } + + const char *pActionText = TotalCollapsed == m_Map.m_vpGroups.size() ? "Expand all" : "Collapse all"; + + CollapseAllButton.HSplitTop(RowHeight, &CollapseAllButton, 0); + static int s_CollapseAllButton = 0; + if(DoButton_Editor(&s_CollapseAllButton, pActionText, 0, &CollapseAllButton, IGraphics::CORNER_R, "Expand or collapse all groups")) + { + for(const auto &pGroup : m_Map.m_vpGroups) + { + if(TotalCollapsed == m_Map.m_vpGroups.size()) + pGroup->m_Collapse = false; + else + pGroup->m_Collapse = true; + } + } + } + s_ScrollRegion.End(); if(s_Operation == OP_NONE) From 7628783e95ba88b755a9da3ad97475684d9e8414 Mon Sep 17 00:00:00 2001 From: furo Date: Sun, 25 Aug 2024 20:20:13 +0200 Subject: [PATCH 26/26] Transfer server settings while using append --- src/game/editor/editor.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 4f259191437..8546a5fbdaf 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8881,6 +8881,22 @@ bool CEditor::Append(const char *pFileName, int StorageType, bool IgnoreHistory) } NewMap.m_vpGroups.clear(); + // transfer server settings + for(const auto &pSetting : NewMap.m_vSettings) + { + // Check if setting already exists + bool AlreadyExists = false; + for(const auto &pExistingSetting : m_Map.m_vSettings) + { + if(!str_comp(pExistingSetting.m_aCommand, pSetting.m_aCommand)) + AlreadyExists = true; + } + if(!AlreadyExists) + m_Map.m_vSettings.push_back(pSetting); + } + + NewMap.m_vSettings.clear(); + auto IndexMap = SortImages(); if(!IgnoreHistory)