From ac4d7085c5a7e6bd59c6c17a81468c576cd37738 Mon Sep 17 00:00:00 2001 From: Corantin H Date: Tue, 21 May 2019 12:49:19 +0200 Subject: [PATCH] Switched to a proper new recorder instead of using the auto one --- .../menus_settings.cpp | 2267 ++++++++++ .../client.cpp | 3961 +++++++++++++++++ .../91ae225c878a52a1e196841713774bc/menus.h | 424 ++ .../ab485498acbe1781456755bfe57a34/menus.cpp | 1975 ++++++++ .../da59d8b44e17c6aea1909b3c822a50/client.h | 425 ++ src/engine/client.h | 3 +- src/engine/client/client.cpp | 32 +- src/engine/client/client.h | 1 - src/game/client/components/menus.cpp | 18 - src/game/client/components/menus.h | 1 - src/game/client/components/menus_settings.cpp | 9 +- src/game/client/components/scoreboard.cpp | 9 +- 12 files changed, 9074 insertions(+), 51 deletions(-) create mode 100644 build/enc_temp_folder/2ba8eeccdeea0a2c44ef2c29c837b9/menus_settings.cpp create mode 100644 build/enc_temp_folder/7442489b57efc3296db690fba0f863d0/client.cpp create mode 100644 build/enc_temp_folder/91ae225c878a52a1e196841713774bc/menus.h create mode 100644 build/enc_temp_folder/ab485498acbe1781456755bfe57a34/menus.cpp create mode 100644 build/enc_temp_folder/da59d8b44e17c6aea1909b3c822a50/client.h diff --git a/build/enc_temp_folder/2ba8eeccdeea0a2c44ef2c29c837b9/menus_settings.cpp b/build/enc_temp_folder/2ba8eeccdeea0a2c44ef2c29c837b9/menus_settings.cpp new file mode 100644 index 000000000..bf497187b --- /dev/null +++ b/build/enc_temp_folder/2ba8eeccdeea0a2c44ef2c29c837b9/menus_settings.cpp @@ -0,0 +1,2267 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#include "SDL.h" // SDL_VIDEO_DRIVER_X11 + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "binds.h" +#include "camera.h" +#include "countryflags.h" +#include "menus.h" +#include "skins.h" + +CMenusKeyBinder CMenus::m_Binder; + +CMenusKeyBinder::CMenusKeyBinder() +{ + m_TakeKey = false; + m_GotKey = false; + m_Modifier = 0; +} + +bool CMenusKeyBinder::OnInput(IInput::CEvent Event) +{ + if(m_TakeKey) + { + int TriggeringEvent = (Event.m_Key == KEY_MOUSE_1) ? IInput::FLAG_PRESS : IInput::FLAG_RELEASE; + if(Event.m_Flags&TriggeringEvent) + { + m_Key = Event; + m_GotKey = true; + m_TakeKey = false; + + int Mask = CBinds::GetModifierMask(Input()); + m_Modifier = 0; + while(!(Mask&1)) + { + Mask >>= 1; + m_Modifier++; + } + if(CBinds::ModifierMatchesKey(m_Modifier, Event.m_Key)) + m_Modifier = 0; + } + return true; + } + + return false; +} + +void CMenus::RenderSettingsGeneral(CUIRect MainView) +{ + char aBuf[128]; + CUIRect Label, Button, Left, Right, Game, Client, AutoReconnect; + MainView.HSplitTop(180.0f, &Game, &Client); + Client.HSplitTop(160.0f, &Client, &AutoReconnect); + + // game + { + // headline + Game.HSplitTop(30.0f, &Label, &Game); + UI()->DoLabelScaled(&Label, Localize("Game"), 20.0f, -1); + Game.Margin(5.0f, &Game); + Game.VSplitMid(&Left, &Right); + Left.VSplitRight(5.0f, &Left, 0); + Right.VMargin(5.0f, &Right); + + // dynamic camera + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClDyncam, Localize("Dynamic Camera"), g_Config.m_ClDyncam, &Button)) + g_Config.m_ClDyncam ^= 1; + + // weapon pickup + Left.HSplitTop(5.0f, 0, &Left); + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClAutoswitchWeapons, Localize("Switch weapon on pickup"), g_Config.m_ClAutoswitchWeapons, &Button)) + g_Config.m_ClAutoswitchWeapons ^= 1; + + // weapon out of ammo autoswitch + Left.HSplitTop(5.0f, 0, &Left); + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClAutoswitchWeaponsOutOfAmmo, Localize("Switch weapon when out of ammo"), g_Config.m_ClAutoswitchWeaponsOutOfAmmo, &Button)) + g_Config.m_ClAutoswitchWeaponsOutOfAmmo ^= 1; + + // weapon reset on death + Left.HSplitTop(5.0f, 0, &Left); + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClResetWantedWeaponOnDeath, Localize("Reset wanted weapon on death"), g_Config.m_ClResetWantedWeaponOnDeath, &Button)) + g_Config.m_ClResetWantedWeaponOnDeath ^= 1; + + // chat messages + Right.HSplitTop(5.0f, 0, &Right); + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClShowChatFriends, Localize("Show only chat messages from friends"), g_Config.m_ClShowChatFriends, &Button)) + g_Config.m_ClShowChatFriends ^= 1; + + // name plates + Right.HSplitTop(5.0f, 0, &Right); + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClNameplates, Localize("Show name plates"), g_Config.m_ClNameplates, &Button)) + g_Config.m_ClNameplates ^= 1; + + if(g_Config.m_ClNameplates) + { + Right.HSplitTop(2.5f, 0, &Right); + Right.HSplitTop(20.0f, &Label, &Right); + Right.HSplitTop(20.0f, &Button, &Right); + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Name plates size"), g_Config.m_ClNameplatesSize); + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Button.HMargin(2.0f, &Button); + g_Config.m_ClNameplatesSize = (int)(DoScrollbarH(&g_Config.m_ClNameplatesSize, &Button, g_Config.m_ClNameplatesSize/100.0f)*100.0f+0.1f); + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClNameplatesTeamcolors, Localize("Use team colors for name plates"), g_Config.m_ClNameplatesTeamcolors, &Button)) + g_Config.m_ClNameplatesTeamcolors ^= 1; + + Right.HSplitTop(5.0f, 0, &Right); + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClNameplatesClan, Localize("Show clan above name plates"), g_Config.m_ClNameplatesClan, &Button)) + g_Config.m_ClNameplatesClan ^= 1; + } + + if(g_Config.m_ClNameplatesClan) + { + Right.HSplitTop(2.5f, 0, &Right); + Right.HSplitTop(20.0f, &Label, &Right); + Right.HSplitTop(20.0f, &Button, &Right); + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Clan plates size"), g_Config.m_ClNameplatesClanSize); + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Button.HMargin(2.0f, &Button); + g_Config.m_ClNameplatesClanSize = (int)(DoScrollbarH(&g_Config.m_ClNameplatesClanSize, &Button, g_Config.m_ClNameplatesClanSize/100.0f)*100.0f+0.1f); + } + } + + // client + { + // headline + Client.HSplitTop(30.0f, &Label, &Client); + UI()->DoLabelScaled(&Label, Localize("Client"), 20.0f, -1); + Client.Margin(5.0f, &Client); + Client.VSplitMid(&Left, &Right); + Left.VSplitRight(5.0f, &Left, 0); + Right.VMargin(5.0f, &Right); + + // auto demo settings + { + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClAutoDemoRecord, Localize("Automatically record demos"), g_Config.m_ClAutoDemoRecord, &Button)) + g_Config.m_ClAutoDemoRecord ^= 1; + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClAutoScreenshot, Localize("Automatically take game over screenshot"), g_Config.m_ClAutoScreenshot, &Button)) + g_Config.m_ClAutoScreenshot ^= 1; + + Left.HSplitTop(10.0f, 0, &Left); + Left.HSplitTop(20.0f, &Label, &Left); + Button.VSplitRight(20.0f, &Button, 0); + char aBuf[64]; + if(g_Config.m_ClAutoDemoMax) + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Max demos"), g_Config.m_ClAutoDemoMax); + else + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Max demos"), "∞"); + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Left.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClAutoDemoMax = static_cast(DoScrollbarH(&g_Config.m_ClAutoDemoMax, &Button, g_Config.m_ClAutoDemoMax/1000.0f)*1000.0f+0.1f); + + Right.HSplitTop(10.0f, 0, &Right); + Right.HSplitTop(20.0f, &Label, &Right); + Button.VSplitRight(20.0f, &Button, 0); + if(g_Config.m_ClAutoScreenshotMax) + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Max Screenshots"), g_Config.m_ClAutoScreenshotMax); + else + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Max Screenshots"), "∞"); + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Right.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClAutoScreenshotMax = static_cast(DoScrollbarH(&g_Config.m_ClAutoScreenshotMax, &Button, g_Config.m_ClAutoScreenshotMax/1000.0f)*1000.0f+0.1f); + } + + Left.HSplitTop(20.0f, 0, &Left); + Left.HSplitTop(20.0f, &Label, &Left); + Button.VSplitRight(20.0f, &Button, 0); + char aBuf[64]; + if(g_Config.m_ClRefreshRate) + str_format(aBuf, sizeof(aBuf), "%s: %i Hz", Localize("Refresh Rate"), g_Config.m_ClRefreshRate); + else + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Refresh Rate"), "∞"); + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Left.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClRefreshRate= static_cast(DoScrollbarH(&g_Config.m_ClRefreshRate, &Button, g_Config.m_ClRefreshRate/10000.0f)*10000.0f+0.1f); + +#if defined(CONF_FAMILY_WINDOWS) + Left.HSplitTop(20.0f, 0, &Left); + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowConsole, Localize("Show console window"), g_Config.m_ClShowConsole, &Button)) + g_Config.m_ClShowConsole ^= 1; +#endif + + // auto statboard screenshot + { + Right.HSplitTop(20.0f, 0, &Right); // + Right.HSplitTop(20.0f, 0, &Right); // Make some distance so it looks more natural + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClAutoStatboardScreenshot, + Localize("Automatically take statboard screenshot"), + g_Config.m_ClAutoStatboardScreenshot, &Button)) + { + g_Config.m_ClAutoStatboardScreenshot ^= 1; + } + + Right.HSplitTop(10.0f, 0, &Right); + Right.HSplitTop(20.0f, &Label, &Right); + Button.VSplitRight(20.0f, &Button, 0); + if(g_Config.m_ClAutoStatboardScreenshotMax) + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Max Screenshots"), g_Config.m_ClAutoStatboardScreenshotMax); + else + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Max Screenshots"), "∞"); + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Right.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClAutoStatboardScreenshotMax = + static_cast(DoScrollbarH(&g_Config.m_ClAutoStatboardScreenshotMax, + &Button, + g_Config.m_ClAutoStatboardScreenshotMax/1000.0f)*1000.0f+0.1f); + } + + // auto statboard csv + { + Right.HSplitTop(20.0f, 0, &Right); // + Right.HSplitTop(20.0f, 0, &Right); // Make some distance so it looks more natural + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClAutoCSV, + Localize("Automatically create statboard csv"), + g_Config.m_ClAutoCSV, &Button)) + { + g_Config.m_ClAutoCSV ^= 1; + } + + Right.HSplitTop(10.0f, 0, &Right); + Right.HSplitTop(20.0f, &Label, &Right); + Button.VSplitRight(20.0f, &Button, 0); + if(g_Config.m_ClAutoCSVMax) + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Max CSVs"), g_Config.m_ClAutoCSVMax); + else + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Max CSVs"), "∞"); + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Right.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClAutoCSVMax = + static_cast(DoScrollbarH(&g_Config.m_ClAutoCSVMax, + &Button, + g_Config.m_ClAutoCSVMax / 1000.0f)*1000.0f + 0.1f); + } + } +} + +void CMenus::RenderSettingsPlayer(CUIRect MainView) +{ + CUIRect Button, Label, Dummy; + MainView.HSplitTop(10.0f, 0, &MainView); + + char *Name = g_Config.m_PlayerName; + char *Clan = g_Config.m_PlayerClan; + int *Country = &g_Config.m_PlayerCountry; + + if(m_Dummy) + { + Name = g_Config.m_ClDummyName; + Clan = g_Config.m_ClDummyClan; + Country = &g_Config.m_ClDummyCountry; + } + + // player name + MainView.HSplitTop(20.0f, &Button, &MainView); + Button.VSplitLeft(80.0f, &Label, &Button); + Button.VSplitLeft(200.0f, &Button, &Dummy); + Button.VSplitLeft(150.0f, &Button, 0); + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name")); + UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); + static float s_OffsetName = 0.0f; + if(DoEditBox(Name, &Button, Name, sizeof(g_Config.m_PlayerName), 14.0f, &s_OffsetName)) + { + if(m_Dummy) + m_NeedSendDummyinfo = true; + else + m_NeedSendinfo = true; + } + + if(DoButton_CheckBox(&m_Dummy, Localize("Dummy settings"), m_Dummy, &Dummy)) + { + m_Dummy ^= 1; + } + + // player clan + MainView.HSplitTop(5.0f, 0, &MainView); + MainView.HSplitTop(20.0f, &Button, &MainView); + Button.VSplitLeft(80.0f, &Label, &Button); + Button.VSplitLeft(150.0f, &Button, 0); + str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan")); + UI()->DoLabelScaled(&Label, aBuf, 14.0, -1); + static float s_OffsetClan = 0.0f; + if(DoEditBox(Clan, &Button, Clan, sizeof(g_Config.m_PlayerClan), 14.0f, &s_OffsetClan)) + { + if(m_Dummy) + m_NeedSendDummyinfo = true; + else + m_NeedSendinfo = true; + } + + static bool s_ListBoxUsed = false; + if(UI()->ActiveItem() == Clan || UI()->ActiveItem() == Name) + s_ListBoxUsed = false; + + // country flag selector + MainView.HSplitTop(20.0f, 0, &MainView); + static float s_ScrollValue = 0.0f; + int OldSelected = -1; + UiDoListboxStart(&s_ScrollValue, &MainView, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue); + + for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i) + { + const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i); + if(pEntry->m_CountryCode == *Country) + OldSelected = i; + + CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i, s_ListBoxUsed); + if(Item.m_Visible) + { + CUIRect Label; + Item.m_Rect.Margin(5.0f, &Item.m_Rect); + Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); + float OldWidth = Item.m_Rect.w; + Item.m_Rect.w = Item.m_Rect.h*2; + Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f; + ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f); + m_pClient->m_pCountryFlags->Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h); + if(pEntry->m_Texture != -1) + UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0); + } + } + + bool Clicked = false; + const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0, &Clicked); + if(Clicked) + s_ListBoxUsed = true; + + if(OldSelected != NewSelected) + { + *Country = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode; + if(m_Dummy) + m_NeedSendDummyinfo = true; + else + m_NeedSendinfo = true; + } +} + +void CMenus::RenderSettingsTee(CUIRect MainView) +{ + CUIRect Button, Label, Button2, Dummy, DummyLabel, SkinList, QuickSearch, QuickSearchClearButton, SkinPrefix, SkinPrefixLabel; + + static float s_ClSkinPrefix = 0.0f; + + static bool s_InitSkinlist = true; + MainView.HSplitTop(10.0f, 0, &MainView); + + char *Skin = g_Config.m_ClPlayerSkin; + int *UseCustomColor = &g_Config.m_ClPlayerUseCustomColor; + int *ColorBody = &g_Config.m_ClPlayerColorBody; + int *ColorFeet = &g_Config.m_ClPlayerColorFeet; + + if(m_Dummy) + { + Skin = g_Config.m_ClDummySkin; + UseCustomColor = &g_Config.m_ClDummyUseCustomColor; + ColorBody = &g_Config.m_ClDummyColorBody; + ColorFeet = &g_Config.m_ClDummyColorFeet; + } + + // skin info + const CSkins::CSkin *pOwnSkin = m_pClient->m_pSkins->Get(m_pClient->m_pSkins->Find(Skin)); + CTeeRenderInfo OwnSkinInfo; + if(*UseCustomColor) + { + OwnSkinInfo.m_Texture = pOwnSkin->m_ColorTexture; + OwnSkinInfo.m_ColorBody = color_cast(ColorHSLA(*ColorBody).Lighten()); + OwnSkinInfo.m_ColorFeet = color_cast(ColorHSLA(*ColorFeet).Lighten()); + } + else + { + OwnSkinInfo.m_Texture = pOwnSkin->m_OrgTexture; + OwnSkinInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f); + OwnSkinInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f); + } + OwnSkinInfo.m_Size = 50.0f*UI()->Scale(); + + MainView.HSplitTop(20.0f, &Label, &MainView); + Label.VSplitLeft(280.0f, &Label, &Dummy); + Label.VSplitLeft(230.0f, &Label, 0); + Dummy.VSplitLeft(170.0f, &Dummy, &SkinPrefix); + SkinPrefix.VSplitLeft(120.0f, &SkinPrefix, 0); + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "%s:", Localize("Your skin")); + UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); + + Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); + + if(DoButton_CheckBox(&m_Dummy, Localize("Dummy settings"), m_Dummy, &DummyLabel)) + { + m_Dummy ^= 1; + } + + Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); + + if(DoButton_CheckBox(&g_Config.m_ClVanillaSkinsOnly, Localize("Vanilla skins only"), g_Config.m_ClVanillaSkinsOnly, &DummyLabel)) + { + g_Config.m_ClVanillaSkinsOnly ^= 1; + s_InitSkinlist = true; + } + + Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); + + if (DoButton_CheckBox(&g_Config.m_ClFatSkins, Localize("Fat skins (DDFat)"), g_Config.m_ClFatSkins, &DummyLabel)) + { + g_Config.m_ClFatSkins ^= 1; + } + + SkinPrefix.HSplitTop(20.0f, &SkinPrefixLabel, &SkinPrefix); + UI()->DoLabelScaled(&SkinPrefixLabel, Localize("Skin prefix"), 14.0f, -1); + + SkinPrefix.HSplitTop(20.0f, &SkinPrefixLabel, &SkinPrefix); + { + static int s_ClearButton = 0; + DoClearableEditBox(g_Config.m_ClSkinPrefix, &s_ClearButton, &SkinPrefixLabel, g_Config.m_ClSkinPrefix, sizeof(g_Config.m_ClSkinPrefix), 14.0f, &s_ClSkinPrefix); + } + + SkinPrefix.HSplitTop(2.0f, 0, &SkinPrefix); + { + static const char *s_aSkinPrefixes[] = {"kitty", "coala", "santa"}; + for(unsigned i = 0; i < sizeof(s_aSkinPrefixes) / sizeof(s_aSkinPrefixes[0]); i++) + { + const char *pPrefix = s_aSkinPrefixes[i]; + CUIRect Button; + SkinPrefix.HSplitTop(20.0f, &Button, &SkinPrefix); + Button.HMargin(2.0f, &Button); + if(DoButton_Menu(&s_aSkinPrefixes[i], pPrefix, 0, &Button)) + { + str_copy(g_Config.m_ClSkinPrefix, pPrefix, sizeof(g_Config.m_ClSkinPrefix)); + } + } + } + + Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy); + + MainView.HSplitTop(50.0f, &Label, &MainView); + Label.VSplitLeft(230.0f, &Label, 0); + RenderTools()->DrawUIRect(&Label, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 10.0f); + RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, 0, vec2(1, 0), vec2(Label.x+30.0f, Label.y+28.0f)); + Label.VSplitLeft(70.0f, 0, &Label); + UI()->DoLabelScaled(&Label, Skin, 14.0f, -1, 150.0f); + + // custom colour selector + MainView.HSplitTop(20.0f, 0, &MainView); + MainView.HSplitTop(20.0f, &Button, &MainView); + Button.VSplitMid(&Button, &Button2); + if(DoButton_CheckBox(&ColorBody, Localize("Custom colors"), *UseCustomColor, &Button)) + { + *UseCustomColor = *UseCustomColor?0:1; + if(m_Dummy) + m_NeedSendDummyinfo = true; + else + m_NeedSendinfo = true; + } + + MainView.HSplitTop(5.0f, 0, &MainView); + MainView.HSplitTop(82.5f, &Label, &MainView); + if(*UseCustomColor) + { + CUIRect aRects[2]; + Label.VSplitMid(&aRects[0], &aRects[1]); + aRects[0].VSplitRight(10.0f, &aRects[0], 0); + aRects[1].VSplitLeft(10.0f, 0, &aRects[1]); + + int *paColors[2]; + paColors[0] = ColorBody; + paColors[1] = ColorFeet; + + const char *paParts[] = { + Localize("Body"), + Localize("Feet")}; + const char *paLabels[] = { + Localize("Hue"), + Localize("Sat."), + Localize("Lht.")}; + static int s_aColorSlider[2][3] = {{0}}; + + for(int i = 0; i < 2; i++) + { + aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); + UI()->DoLabelScaled(&Label, paParts[i], 14.0f, -1); + aRects[i].VSplitLeft(20.0f, 0, &aRects[i]); + aRects[i].HSplitTop(2.5f, 0, &aRects[i]); + + int PrevColor = *paColors[i]; + int Color = 0; + for(int s = 0; s < 3; s++) + { + aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); + Label.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + + float k = ((PrevColor>>((2-s)*8))&0xff) / 255.0f; + k = DoScrollbarH(&s_aColorSlider[i][s], &Button, k); + Color <<= 8; + Color += clamp((int)(k*255), 0, 255); + UI()->DoLabelScaled(&Label, paLabels[s], 14.0f, -1); + } + + if(PrevColor != Color) + { + if(m_Dummy) + m_NeedSendDummyinfo = true; + else + m_NeedSendinfo = true; + } + + *paColors[i] = Color; + } + } + + // skin selector + MainView.HSplitTop(20.0f, 0, &MainView); + MainView.HSplitTop(230.0f, &SkinList, &MainView); + static sorted_array s_paSkinList; + static float s_ScrollValue = 0.0f; + if(s_InitSkinlist) + { + s_paSkinList.clear(); + for(int i = 0; i < m_pClient->m_pSkins->Num(); ++i) + { + const CSkins::CSkin *s = m_pClient->m_pSkins->Get(i); + + // filter quick search + if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_find_nocase(s->m_aName, g_Config.m_ClSkinFilterString)) + continue; + + // no special skins + if((s->m_aName[0] == 'x' && s->m_aName[1] == '_')) + continue; + + // vanilla skins only + if (g_Config.m_ClVanillaSkinsOnly && !s->m_IsVanilla) + continue; + + s_paSkinList.add(s); + } + s_InitSkinlist = false; + } + + int OldSelected = -1; + UiDoListboxStart(&s_InitSkinlist, &SkinList, 50.0f, Localize("Skins"), "", s_paSkinList.size(), 4, OldSelected, s_ScrollValue); + for(int i = 0; i < s_paSkinList.size(); ++i) + { + const CSkins::CSkin *s = s_paSkinList[i]; + if(s == 0) + continue; + + if(str_comp(s->m_aName, Skin) == 0) + OldSelected = i; + + CListboxItem Item = UiDoListboxNextItem(&s_paSkinList[i], OldSelected == i); + char aBuf[128]; + if(Item.m_Visible) + { + CTeeRenderInfo Info = OwnSkinInfo; + Info.m_Texture = *UseCustomColor ? s->m_ColorTexture : s->m_OrgTexture; + + Item.m_Rect.HSplitTop(5.0f, 0, &Item.m_Rect); // some margin from the top + RenderTools()->RenderTee(CAnimState::GetIdle(), &Info, 0, vec2(1.0f, 0.0f), vec2(Item.m_Rect.x+30, Item.m_Rect.y+Item.m_Rect.h/2)); + + Item.m_Rect.VSplitLeft(60.0f, 0, &Item.m_Rect); + str_format(aBuf, sizeof(aBuf), "%s", s->m_aName); + RenderTools()->UI()->DoLabelScaled(&Item.m_Rect, aBuf, 12.0f, -1,Item.m_Rect.w); + if(g_Config.m_Debug) + { + ColorRGBA BloodColor = *UseCustomColor ? color_cast(ColorHSLA(*ColorBody)) : s->m_BloodColor; + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(BloodColor.r, BloodColor.g, BloodColor.b, 1.0f); + IGraphics::CQuadItem QuadItem(Item.m_Rect.x, Item.m_Rect.y, 12.0f, 12.0f); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + } + } + } + + const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); + if(OldSelected != NewSelected) + { + mem_copy(Skin, s_paSkinList[NewSelected]->m_aName, sizeof(g_Config.m_ClPlayerSkin)); + if(m_Dummy) + m_NeedSendDummyinfo = true; + else + m_NeedSendinfo = true; + } + + // render quick search + { + MainView.HSplitBottom(ms_ButtonHeight, &MainView, &QuickSearch); + QuickSearch.VSplitLeft(240.0f, &QuickSearch, 0); + QuickSearch.HSplitTop(5.0f, 0, &QuickSearch); + const char *pSearchLabel = "\xEE\xA2\xB6"; + TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); + UI()->DoLabelScaled(&QuickSearch, pSearchLabel, 14.0f, -1); + float wSearch = TextRender()->TextWidth(0, 14.0f, pSearchLabel, -1); + TextRender()->SetRenderFlags(0); + TextRender()->SetCurFont(NULL); + QuickSearch.VSplitLeft(wSearch, 0, &QuickSearch); + QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); + QuickSearch.VSplitLeft(QuickSearch.w-15.0f, &QuickSearch, &QuickSearchClearButton); + static int s_ClearButton = 0; + static float Offset = 0.0f; + if(Input()->KeyPress(KEY_F) && (Input()->KeyIsPressed(KEY_LCTRL) || Input()->KeyIsPressed(KEY_RCTRL))) + UI()->SetActiveItem(&g_Config.m_ClSkinFilterString); + if(DoClearableEditBox(&g_Config.m_ClSkinFilterString, &s_ClearButton, &QuickSearch, g_Config.m_ClSkinFilterString, sizeof(g_Config.m_ClSkinFilterString), 14.0f, &Offset, false, CUI::CORNER_ALL, Localize("Search"))) + s_InitSkinlist = true; + } +} + + +typedef void (*pfnAssignFuncCallback)(CConfiguration *pConfig, int Value); + +typedef struct +{ + CLocConstString m_Name; + const char *m_pCommand; + int m_KeyId; + int m_Modifier; +} CKeyInfo; + +static CKeyInfo gs_aKeys[] = +{ + { "Move left", "+left", 0, 0 }, // Localize - these strings are localized within CLocConstString + { "Move right", "+right", 0, 0 }, + { "Jump", "+jump", 0, 0 }, + { "Fire", "+fire", 0, 0 }, + { "Hook", "+hook", 0, 0 }, + { "Hook collisions", "+showhookcoll", 0, 0 }, + { "Pause", "say /pause", 0, 0 }, + { "Kill", "kill", 0, 0 }, + { "Zoom in", "zoom+", 0, 0 }, + { "Zoom out", "zoom-", 0, 0 }, + { "Default zoom", "zoom", 0, 0 }, + { "Show others", "say /showothers", 0, 0 }, + { "Show all", "say /showall", 0, 0 }, + { "Toggle dyncam", "toggle cl_dyncam 0 1", 0, 0 }, + { "Toggle dummy", "toggle cl_dummy 0 1", 0, 0 }, + { "Toggle ghost", "toggle cl_race_show_ghost 0 1", 0, 0 }, + { "Dummy copy", "toggle cl_dummy_copy_moves 0 1", 0, 0 }, + { "Hammerfly dummy", "toggle cl_dummy_hammer 0 1", 0, 0 }, + + { "Hammer", "+weapon1", 0, 0 }, + { "Pistol", "+weapon2", 0, 0 }, + { "Shotgun", "+weapon3", 0, 0 }, + { "Grenade", "+weapon4", 0, 0 }, + { "Rifle", "+weapon5", 0, 0 }, + { "Next weapon", "+nextweapon", 0, 0 }, + { "Prev. weapon", "+prevweapon", 0, 0 }, + + { "Vote yes", "vote yes", 0, 0 }, + { "Vote no", "vote no", 0, 0 }, + + { "Chat", "+show_chat; chat all", 0, 0 }, + { "Team chat", "+show_chat; chat team", 0, 0 }, + { "Converse", "+show_chat; chat all /c ", 0, 0 }, + { "Show chat", "+show_chat", 0, 0 }, + + { "Emoticon", "+emote", 0, 0 }, + { "Spectator mode", "+spectate", 0, 0 }, + { "Spectate next", "spectate_next", 0, 0 }, + { "Spectate previous", "spectate_previous", 0, 0 }, + { "Console", "toggle_local_console", 0, 0 }, + { "Remote console", "toggle_remote_console", 0, 0 }, + { "Screenshot", "screenshot", 0, 0 }, + { "Scoreboard", "+scoreboard", 0, 0 }, + { "Statboard", "+statboard", 0, 0 }, + { "Lock team", "say /lock", 0, 0 }, + { "Show entities", "toggle cl_overlay_entities 0 100", 0, 0 }, + { "Show HUD", "toggle cl_showhud 0 1", 0, 0 }, +}; + +/* This is for scripts/update_localization.py to work, don't remove! + Localize("Move left");Localize("Move right");Localize("Jump");Localize("Fire");Localize("Hook");Localize("Hammer"); + Localize("Pistol");Localize("Shotgun");Localize("Grenade");Localize("Rifle");Localize("Next weapon");Localize("Prev. weapon"); + Localize("Vote yes");Localize("Vote no");Localize("Chat");Localize("Team chat");Localize("Show chat");Localize("Emoticon"); + Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console");Localize("Remote console");Localize("Screenshot");Localize("Scoreboard");Localize("Respawn"); +*/ + +const int g_KeyCount = sizeof(gs_aKeys) / sizeof(CKeyInfo); + +void CMenus::UiDoGetButtons(int Start, int Stop, CUIRect View, CUIRect ScopeView) +{ + for(int i = Start; i < Stop; i++) + { + CKeyInfo &Key = gs_aKeys[i]; + CUIRect Button, Label; + View.HSplitTop(20.0f, &Button, &View); + Button.VSplitLeft(135.0f, &Label, &Button); + + if(Button.y >= ScopeView.y && Button.y + Button.h <= ScopeView.y + ScopeView.h) + { + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "%s:", (const char *)Key.m_Name); + + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + int OldId = Key.m_KeyId, OldModifier = Key.m_Modifier, NewModifier; + int NewId = DoKeyReader((void *)&gs_aKeys[i].m_Name, &Button, OldId, OldModifier, &NewModifier); + if(NewId != OldId || NewModifier != OldModifier) + { + if(OldId != 0 || NewId == 0) + m_pClient->m_pBinds->Bind(OldId, "", false, OldModifier); + if(NewId != 0) + m_pClient->m_pBinds->Bind(NewId, gs_aKeys[i].m_pCommand, false, NewModifier); + } + } + + View.HSplitTop(2.0f, 0, &View); + } +} + +void CMenus::RenderSettingsControls(CUIRect MainView) +{ + char aBuf[128]; + + // this is kinda slow, but whatever + for(int i = 0; i < g_KeyCount; i++) + gs_aKeys[i].m_KeyId = gs_aKeys[i].m_Modifier = 0; + + for(int Mod = 0; Mod < CBinds::MODIFIER_COUNT; Mod++) + { + for(int KeyId = 0; KeyId < KEY_LAST; KeyId++) + { + const char *pBind = m_pClient->m_pBinds->Get(KeyId, Mod); + if(!pBind[0]) + continue; + + for(int i = 0; i < g_KeyCount; i++) + if(str_comp(pBind, gs_aKeys[i].m_pCommand) == 0) + { + gs_aKeys[i].m_KeyId = KeyId; + gs_aKeys[i].m_Modifier = Mod; + break; + } + } + } + + // controls in a scrollable listbox + static int s_ControlsList = 0; + static int s_SelectedControl = -1; + static float s_ScrollValue = 0; + int OldSelected = s_SelectedControl; + UiDoListboxStart(&s_ControlsList , &MainView, 475.0f, Localize("Controls"), "", 1, 1, s_SelectedControl, s_ScrollValue); + + CUIRect MovementSettings, WeaponSettings, VotingSettings, ChatSettings, MiscSettings, ResetButton; + CListboxItem Item = UiDoListboxNextItem(&OldSelected, false, false); + Item.m_Rect.HSplitTop(10.0f, 0, &Item.m_Rect); + Item.m_Rect.VSplitMid(&MovementSettings, &VotingSettings); + + // movement settings + { + MovementSettings.VMargin(5.0f, &MovementSettings); + MovementSettings.HSplitTop(515.0f, &MovementSettings, &WeaponSettings); + RenderTools()->DrawUIRect(&MovementSettings, ColorRGBA(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); + MovementSettings.VMargin(10.0f, &MovementSettings); + + TextRender()->Text(0, MovementSettings.x, MovementSettings.y + (14.0f + 5.0f + 10.0f - 14.0f*UI()->Scale()) / 2.f, 14.0f*UI()->Scale(), Localize("Movement"), -1); + + MovementSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &MovementSettings); + + { + CUIRect Button, Label; + MovementSettings.HSplitTop(20.0f, &Button, &MovementSettings); + Button.VSplitLeft(160.0f, &Label, &Button); + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Mouse sens."), g_Config.m_InpMousesens); + UI()->DoLabel(&Label, aBuf, 14.0f*UI()->Scale(), -1); + Button.HMargin(2.0f, &Button); + int NewValue = (int)(DoScrollbarH(&g_Config.m_InpMousesens, &Button, (minimum(g_Config.m_InpMousesens, 500)-1)/500.0f)*500.0f)+1; + if(g_Config.m_InpMousesens < 500 || NewValue < 500) + g_Config.m_InpMousesens = minimum(NewValue, 500); + MovementSettings.HSplitTop(20.0f, 0, &MovementSettings); + } + + { + CUIRect Button, Label; + MovementSettings.HSplitTop(20.0f, &Button, &MovementSettings); + Button.VSplitLeft(160.0f, &Label, &Button); + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("UI mouse s."), g_Config.m_UiMousesens); + UI()->DoLabel(&Label, aBuf, 14.0f*UI()->Scale(), -1); + Button.HMargin(2.0f, &Button); + int NewValue = (int)(DoScrollbarH(&g_Config.m_UiMousesens, &Button, (minimum(g_Config.m_UiMousesens, 500)-1)/500.0f)*500.0f)+1; + if(g_Config.m_UiMousesens < 500 || NewValue < 500) + g_Config.m_UiMousesens = minimum(NewValue, 500); + MovementSettings.HSplitTop(20.0f, 0, &MovementSettings); + } + + UiDoGetButtons(0, 18, MovementSettings, MainView); + + } + + // weapon settings + { + WeaponSettings.HSplitTop(10.0f, 0, &WeaponSettings); + WeaponSettings.HSplitTop(190.0f, &WeaponSettings, &ResetButton); + RenderTools()->DrawUIRect(&WeaponSettings, ColorRGBA(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); + WeaponSettings.VMargin(10.0f, &WeaponSettings); + + TextRender()->Text(0, WeaponSettings.x, WeaponSettings.y + (14.0f + 5.0f + 10.0f - 14.0f*UI()->Scale()) / 2.f, 14.0f*UI()->Scale(), Localize("Weapon"), -1); + + WeaponSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &WeaponSettings); + UiDoGetButtons(18, 25, WeaponSettings, MainView); + } + + // defaults + { + ResetButton.HSplitTop(10.0f, 0, &ResetButton); + ResetButton.HSplitTop(40.0f, &ResetButton, 0); + RenderTools()->DrawUIRect(&ResetButton, ColorRGBA(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); + ResetButton.HMargin(10.0f, &ResetButton); + ResetButton.VMargin(30.0f, &ResetButton); + ResetButton.HSplitTop(20.0f, &ResetButton, 0); + static int s_DefaultButton = 0; + if(DoButton_Menu((void*)&s_DefaultButton, Localize("Reset to defaults"), 0, &ResetButton)) + m_pClient->m_pBinds->SetDefaults(); + } + + // voting settings + { + VotingSettings.VMargin(5.0f, &VotingSettings); + VotingSettings.HSplitTop(80.0f, &VotingSettings, &ChatSettings); + RenderTools()->DrawUIRect(&VotingSettings, ColorRGBA(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); + VotingSettings.VMargin(10.0f, &VotingSettings); + + TextRender()->Text(0, VotingSettings.x, VotingSettings.y + (14.0f + 5.0f + 10.0f - 14.0f*UI()->Scale()) / 2.f, 14.0f*UI()->Scale(), Localize("Voting"), -1); + + VotingSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &VotingSettings); + UiDoGetButtons(25, 27, VotingSettings, MainView); + } + + // chat settings + { + ChatSettings.HSplitTop(10.0f, 0, &ChatSettings); + ChatSettings.HSplitTop(125.0f, &ChatSettings, &MiscSettings); + RenderTools()->DrawUIRect(&ChatSettings, ColorRGBA(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); + ChatSettings.VMargin(10.0f, &ChatSettings); + + TextRender()->Text(0, ChatSettings.x, ChatSettings.y + (14.0f + 5.0f + 10.0f - 14.0f*UI()->Scale()) / 2.f, 14.0f*UI()->Scale(), Localize("Chat"), -1); + + ChatSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &ChatSettings); + UiDoGetButtons(27, 31, ChatSettings, MainView); + } + + // misc settings + { + MiscSettings.HSplitTop(10.0f, 0, &MiscSettings); + MiscSettings.HSplitTop(300.0f, &MiscSettings, 0); + RenderTools()->DrawUIRect(&MiscSettings, ColorRGBA(1,1,1,0.25f), CUI::CORNER_ALL, 10.0f); + MiscSettings.VMargin(10.0f, &MiscSettings); + + TextRender()->Text(0, MiscSettings.x, MiscSettings.y + (14.0f + 5.0f + 10.0f - 14.0f*UI()->Scale()) / 2.f, 14.0f*UI()->Scale(), Localize("Miscellaneous"), -1); + + MiscSettings.HSplitTop(14.0f+5.0f+10.0f, 0, &MiscSettings); + UiDoGetButtons(31, 43, MiscSettings, MainView); + } + + UiDoListboxEnd(&s_ScrollValue, 0); +} + +void CMenus::RenderSettingsGraphics(CUIRect MainView) +{ + CUIRect Button, Label; + char aBuf[128]; + bool CheckSettings = false; + + static const int MAX_RESOLUTIONS = 256; + static CVideoMode s_aModes[MAX_RESOLUTIONS]; + static int s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen); + static int s_GfxScreenWidth = g_Config.m_GfxScreenWidth; + static int s_GfxScreenHeight = g_Config.m_GfxScreenHeight; + static int s_GfxColorDepth = g_Config.m_GfxColorDepth; + static int s_GfxVsync = g_Config.m_GfxVsync; + static int s_GfxFsaaSamples = g_Config.m_GfxFsaaSamples; + static int s_GfxTextureQuality = g_Config.m_GfxTextureQuality; + static int s_GfxTextureCompression = g_Config.m_GfxTextureCompression; + static int s_GfxOpenGLVersion = g_Config.m_GfxOpenGL3; + static int s_GfxEnableTextureUnitOptimization = g_Config.m_GfxEnableTextureUnitOptimization; + static int s_GfxUsePreinitBuffer = g_Config.m_GfxUsePreinitBuffer; + + CUIRect ModeList; + MainView.VSplitLeft(300.0f, &MainView, &ModeList); + + // draw allmodes switch + ModeList.HSplitTop(20, &Button, &ModeList); + if(DoButton_CheckBox(&g_Config.m_GfxDisplayAllModes, Localize("Show only supported"), g_Config.m_GfxDisplayAllModes^1, &Button)) + { + g_Config.m_GfxDisplayAllModes ^= 1; + s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen); + } + + // display mode list + static float s_ScrollValue = 0; + int OldSelected = -1; + int G = gcd(s_GfxScreenWidth, s_GfxScreenHeight); + str_format(aBuf, sizeof(aBuf), "%s: %dx%d %d bit (%d:%d)", Localize("Current"), s_GfxScreenWidth, s_GfxScreenHeight, s_GfxColorDepth, s_GfxScreenWidth/G, s_GfxScreenHeight/G); + UiDoListboxStart(&s_NumNodes , &ModeList, 24.0f, Localize("Display Modes"), aBuf, s_NumNodes, 1, OldSelected, s_ScrollValue); + + for(int i = 0; i < s_NumNodes; ++i) + { + const int Depth = s_aModes[i].m_Red+s_aModes[i].m_Green+s_aModes[i].m_Blue > 16 ? 24 : 16; + if(g_Config.m_GfxColorDepth == Depth && + g_Config.m_GfxScreenWidth == s_aModes[i].m_Width && + g_Config.m_GfxScreenHeight == s_aModes[i].m_Height) + { + OldSelected = i; + } + + CListboxItem Item = UiDoListboxNextItem(&s_aModes[i], OldSelected == i); + if(Item.m_Visible) + { + int G = gcd(s_aModes[i].m_Width, s_aModes[i].m_Height); + str_format(aBuf, sizeof(aBuf), " %dx%d %d bit (%d:%d)", s_aModes[i].m_Width, s_aModes[i].m_Height, Depth, s_aModes[i].m_Width/G, s_aModes[i].m_Height/G); + UI()->DoLabelScaled(&Item.m_Rect, aBuf, 16.0f, -1); + } + } + + const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); + if(OldSelected != NewSelected) + { + const int Depth = s_aModes[NewSelected].m_Red+s_aModes[NewSelected].m_Green+s_aModes[NewSelected].m_Blue > 16 ? 24 : 16; + g_Config.m_GfxColorDepth = Depth; + g_Config.m_GfxScreenWidth = s_aModes[NewSelected].m_Width; + g_Config.m_GfxScreenHeight = s_aModes[NewSelected].m_Height; +#if defined(SDL_VIDEO_DRIVER_X11) + Graphics()->Resize(g_Config.m_GfxScreenWidth, g_Config.m_GfxScreenHeight); +#else + CheckSettings = true; +#endif + } + + // switches + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxBorderless, Localize("Borderless window"), g_Config.m_GfxBorderless, &Button)) + { + Client()->ToggleWindowBordered(); + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxFullscreen, Localize("Fullscreen"), g_Config.m_GfxFullscreen, &Button)) + { + Client()->ToggleFullscreen(); + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + str_format(aBuf, sizeof(aBuf), "%s (%s)", Localize("V-Sync"), Localize("may cause delay")); + if(DoButton_CheckBox(&g_Config.m_GfxVsync, aBuf, g_Config.m_GfxVsync, &Button)) + { + Client()->ToggleWindowVSync(); + } + + if(Graphics()->GetNumScreens() > 1) + { + int NumScreens = Graphics()->GetNumScreens(); + MainView.HSplitTop(20.0f, &Button, &MainView); + int Screen_MouseButton = DoButton_CheckBox_Number(&g_Config.m_GfxScreen, Localize("Screen"), g_Config.m_GfxScreen, &Button); + if(Screen_MouseButton == 1) //inc + { + Client()->SwitchWindowScreen((g_Config.m_GfxScreen+1)%NumScreens); + s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen); + } + else if(Screen_MouseButton == 2) //dec + { + Client()->SwitchWindowScreen((g_Config.m_GfxScreen-1+NumScreens)%NumScreens); + s_NumNodes = Graphics()->GetVideoModes(s_aModes, MAX_RESOLUTIONS, g_Config.m_GfxScreen); + } + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + str_format(aBuf, sizeof(aBuf), "%s (%s)", Localize("FSAA samples"), Localize("may cause delay")); + int GfxFsaaSamples_MouseButton = DoButton_CheckBox_Number(&g_Config.m_GfxFsaaSamples, aBuf, g_Config.m_GfxFsaaSamples, &Button); + if(GfxFsaaSamples_MouseButton == 1) //inc + { + g_Config.m_GfxFsaaSamples = (g_Config.m_GfxFsaaSamples+1)%17; + CheckSettings = true; + } + else if(GfxFsaaSamples_MouseButton == 2) //dec + { + g_Config.m_GfxFsaaSamples = (g_Config.m_GfxFsaaSamples-1 +17)%17; + CheckSettings = true; + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxTextureQuality, Localize("Quality Textures"), g_Config.m_GfxTextureQuality, &Button)) + { + g_Config.m_GfxTextureQuality ^= 1; + CheckSettings = true; + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxTextureCompression, Localize("Texture Compression"), g_Config.m_GfxTextureCompression, &Button)) + { + g_Config.m_GfxTextureCompression ^= 1; + CheckSettings = true; + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxHighDetail, Localize("High Detail"), g_Config.m_GfxHighDetail, &Button)) + g_Config.m_GfxHighDetail ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxOpenGL3, Localize("Use OpenGL 3.3 (experimental)"), g_Config.m_GfxOpenGL3, &Button)) + { + CheckSettings = true; + g_Config.m_GfxOpenGL3 ^= 1; + } + + if(g_Config.m_GfxOpenGL3) + { + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxUsePreinitBuffer, Localize("Preinit VBO (iGPUs only)"), g_Config.m_GfxUsePreinitBuffer, &Button)) + { + CheckSettings = true; + g_Config.m_GfxUsePreinitBuffer ^= 1; + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_GfxEnableTextureUnitOptimization, Localize("Multiple texture units (disable for MacOS)"), g_Config.m_GfxEnableTextureUnitOptimization, &Button)) + { + CheckSettings = true; + g_Config.m_GfxEnableTextureUnitOptimization ^= 1; + } + } + + // check if the new settings require a restart + if(CheckSettings) + { + if(s_GfxScreenWidth == g_Config.m_GfxScreenWidth && + s_GfxScreenHeight == g_Config.m_GfxScreenHeight && + s_GfxColorDepth == g_Config.m_GfxColorDepth && + s_GfxVsync == g_Config.m_GfxVsync && + s_GfxFsaaSamples == g_Config.m_GfxFsaaSamples && + s_GfxTextureQuality == g_Config.m_GfxTextureQuality && + s_GfxTextureCompression == g_Config.m_GfxTextureCompression && + s_GfxOpenGLVersion == g_Config.m_GfxOpenGL3 && + s_GfxUsePreinitBuffer == g_Config.m_GfxUsePreinitBuffer && + s_GfxEnableTextureUnitOptimization == g_Config.m_GfxEnableTextureUnitOptimization) + m_NeedRestartGraphics = false; + else + m_NeedRestartGraphics = true; + } + + MainView.HSplitTop(20.0f, &Label, &MainView); + Label.VSplitLeft(130.0f, &Label, &Button); + if(g_Config.m_GfxRefreshRate) + str_format(aBuf, sizeof(aBuf), "%s: %i Hz", Localize("Refresh Rate"), g_Config.m_GfxRefreshRate); + else + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Refresh Rate"), "∞"); + UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); + Button.HMargin(2.0f, &Button); + int NewRefreshRate = static_cast(DoScrollbarH(&g_Config.m_GfxRefreshRate, &Button, (minimum(g_Config.m_GfxRefreshRate, 1000))/1000.0f)*1000.0f+0.1f); + if(g_Config.m_GfxRefreshRate <= 1000 || NewRefreshRate < 1000) + g_Config.m_GfxRefreshRate = NewRefreshRate; + + CUIRect Text; + MainView.HSplitTop(20.0f, 0, &MainView); + MainView.HSplitTop(20.0f, &Text, &MainView); + //text.VSplitLeft(15.0f, 0, &text); + UI()->DoLabelScaled(&Text, Localize("UI Color"), 14.0f, -1); + + ColorHSLA UIColor = ColorHSLA(g_Config.m_UiColor, true); + const char *paLabels[] = { + Localize("Hue"), + Localize("Sat."), + Localize("Lht."), + Localize("Alpha")}; + float *pColorSlider[4] = {&UIColor.h, &UIColor.s, &UIColor.l, &UIColor.a}; + for(int s = 0; s < 4; s++) + { + CUIRect Text; + MainView.HSplitTop(19.0f, &Button, &MainView); + Button.VMargin(15.0f, &Button); + Button.VSplitLeft(100.0f, &Text, &Button); + //Button.VSplitRight(5.0f, &Button, 0); + Button.HSplitTop(4.0f, 0, &Button); + + float *k = pColorSlider[s]; + *k = DoScrollbarH(k, &Button, *k); + UI()->DoLabelScaled(&Text, paLabels[s], 15.0f, -1); + } + g_Config.m_UiColor = UIColor.Pack(); +} + +void CMenus::RenderSettingsSound(CUIRect MainView) +{ + CUIRect Button; + MainView.VSplitMid(&MainView, 0); + static int s_SndEnable = g_Config.m_SndEnable; + static int s_SndRate = g_Config.m_SndRate; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndEnable, Localize("Use sounds"), g_Config.m_SndEnable, &Button)) + { + g_Config.m_SndEnable ^= 1; + if(g_Config.m_SndEnable) + { + if(g_Config.m_SndMusic && Client()->State() == IClient::STATE_OFFLINE) + m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f); + } + else + m_pClient->m_pSounds->Stop(SOUND_MENU); + m_NeedRestartSound = g_Config.m_SndEnable && (!s_SndEnable || s_SndRate != g_Config.m_SndRate); + } + + if(!g_Config.m_SndEnable) + return; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndMusic, Localize("Play background music"), g_Config.m_SndMusic, &Button)) + { + g_Config.m_SndMusic ^= 1; + if(Client()->State() == IClient::STATE_OFFLINE) + { + if(g_Config.m_SndMusic) + m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f); + else + m_pClient->m_pSounds->Stop(SOUND_MENU); + } + } + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndNonactiveMute, Localize("Mute when not active"), g_Config.m_SndNonactiveMute, &Button)) + g_Config.m_SndNonactiveMute ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndGame, Localize("Enable game sounds"), g_Config.m_SndGame, &Button)) + g_Config.m_SndGame ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndGun, Localize("Enable gun sound"), g_Config.m_SndGun, &Button)) + g_Config.m_SndGun ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndLongPain, Localize("Enable long pain sound (used when shooting in freeze)"), g_Config.m_SndLongPain, &Button)) + g_Config.m_SndLongPain ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndServerMessage, Localize("Enable server message sound"), g_Config.m_SndServerMessage, &Button)) + g_Config.m_SndServerMessage ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndChat, Localize("Enable regular chat sound"), g_Config.m_SndChat, &Button)) + g_Config.m_SndChat ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndTeamChat, Localize("Enable team chat sound"), g_Config.m_SndTeamChat, &Button)) + g_Config.m_SndTeamChat ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_SndHighlight, Localize("Enable highlighted chat sound"), g_Config.m_SndHighlight, &Button)) + g_Config.m_SndHighlight ^= 1; + + MainView.HSplitTop(20.0f, &Button, &MainView); + if(DoButton_CheckBox(&g_Config.m_ClThreadsoundloading, Localize("Threaded sound loading"), g_Config.m_ClThreadsoundloading, &Button)) + g_Config.m_ClThreadsoundloading ^= 1; + + // sample rate box + { + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "%d", g_Config.m_SndRate); + MainView.HSplitTop(20.0f, &Button, &MainView); + UI()->DoLabelScaled(&Button, Localize("Sample rate"), 14.0f, -1); + Button.VSplitLeft(190.0f, 0, &Button); + static float Offset = 0.0f; + DoEditBox(&g_Config.m_SndRate, &Button, aBuf, sizeof(aBuf), 14.0f, &Offset); + g_Config.m_SndRate = maximum(1, str_toint(aBuf)); + m_NeedRestartSound = !s_SndEnable || s_SndRate != g_Config.m_SndRate; + } + + // volume slider + { + CUIRect Button, Label; + MainView.HSplitTop(5.0f, &Button, &MainView); + MainView.HSplitTop(20.0f, &Button, &MainView); + Button.VSplitLeft(190.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sound volume"), 14.0f, -1); + g_Config.m_SndVolume = (int)(DoScrollbarH(&g_Config.m_SndVolume, &Button, g_Config.m_SndVolume/100.0f)*100.0f); + } + + // volume slider map sounds + { + CUIRect Button, Label; + MainView.HSplitTop(5.0f, &Button, &MainView); + MainView.HSplitTop(20.0f, &Button, &MainView); + Button.VSplitLeft(190.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Map sound volume"), 14.0f, -1); + g_Config.m_SndMapSoundVolume = (int)(DoScrollbarH(&g_Config.m_SndMapSoundVolume, &Button, g_Config.m_SndMapSoundVolume/100.0f)*100.0f); + } +} + +class CLanguage +{ +public: + CLanguage() {} + CLanguage(const char *n, const char *f, int Code) : m_Name(n), m_FileName(f), m_CountryCode(Code) {} + + string m_Name; + string m_FileName; + int m_CountryCode; + + bool operator<(const CLanguage &Other) { return m_Name < Other.m_Name; } +}; + +void LoadLanguageIndexfile(IStorage *pStorage, IConsole *pConsole, sorted_array *pLanguages) +{ + IOHANDLE File = pStorage->OpenFile("languages/index.txt", IOFLAG_READ, IStorage::TYPE_ALL); + if(!File) + { + pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "couldn't open index file"); + return; + } + + char aOrigin[128]; + char aReplacement[128]; + CLineReader LineReader; + LineReader.Init(File); + char *pLine; + while((pLine = LineReader.Get())) + { + if(!str_length(pLine) || pLine[0] == '#') // skip empty lines and comments + continue; + + str_copy(aOrigin, pLine, sizeof(aOrigin)); + + pLine = LineReader.Get(); + if(!pLine) + { + pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file"); + break; + } + + if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ') + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin); + pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", aBuf); + (void)LineReader.Get(); + continue; + } + str_copy(aReplacement, pLine+3, sizeof(aReplacement)); + + pLine = LineReader.Get(); + if(!pLine) + { + pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", "unexpected end of index file"); + break; + } + + if(pLine[0] != '=' || pLine[1] != '=' || pLine[2] != ' ') + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "malform replacement for index '%s'", aOrigin); + pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "localization", aBuf); + continue; + } + + char aFileName[128]; + str_format(aFileName, sizeof(aFileName), "languages/%s.txt", aOrigin); + pLanguages->add(CLanguage(aReplacement, aFileName, str_toint(pLine+3))); + } + io_close(File); +} + +void CMenus::RenderLanguageSelection(CUIRect MainView) +{ + static int s_LanguageList = 0; + static int s_SelectedLanguage = 0; + static sorted_array s_Languages; + static float s_ScrollValue = 0; + + if(s_Languages.size() == 0) + { + s_Languages.add(CLanguage("English", "", 826)); + LoadLanguageIndexfile(Storage(), Console(), &s_Languages); + for(int i = 0; i < s_Languages.size(); i++) + if(str_comp(s_Languages[i].m_FileName, g_Config.m_ClLanguagefile) == 0) + { + s_SelectedLanguage = i; + break; + } + } + + int OldSelected = s_SelectedLanguage; + + UiDoListboxStart(&s_LanguageList , &MainView, 24.0f, Localize("Language"), "", s_Languages.size(), 1, s_SelectedLanguage, s_ScrollValue); + + for(sorted_array::range r = s_Languages.all(); !r.empty(); r.pop_front()) + { + CListboxItem Item = UiDoListboxNextItem(&r.front()); + + if(Item.m_Visible) + { + CUIRect Rect; + Item.m_Rect.VSplitLeft(Item.m_Rect.h*2.0f, &Rect, &Item.m_Rect); + Rect.VMargin(6.0f, &Rect); + Rect.HMargin(3.0f, &Rect); + ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f); + m_pClient->m_pCountryFlags->Render(r.front().m_CountryCode, &Color, Rect.x, Rect.y, Rect.w, Rect.h); + Item.m_Rect.HSplitTop(2.0f, 0, &Item.m_Rect); + UI()->DoLabelScaled(&Item.m_Rect, r.front().m_Name, 16.0f, -1); + } + } + + s_SelectedLanguage = UiDoListboxEnd(&s_ScrollValue, 0); + + if(OldSelected != s_SelectedLanguage) + { + str_copy(g_Config.m_ClLanguagefile, s_Languages[s_SelectedLanguage].m_FileName, sizeof(g_Config.m_ClLanguagefile)); + g_Localization.Load(s_Languages[s_SelectedLanguage].m_FileName, Storage(), Console()); + Client()->LoadFont(); + } +} + +void CMenus::RenderSettings(CUIRect MainView) +{ + static int s_SettingsPage = 0; + + // render background + CUIRect Temp, TabBar, RestartWarning; + MainView.VSplitRight(120.0f, &MainView, &TabBar); + RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f); + MainView.Margin(10.0f, &MainView); + MainView.HSplitBottom(15.0f, &MainView, &RestartWarning); + TabBar.HSplitTop(50.0f, &Temp, &TabBar); + RenderTools()->DrawUIRect(&Temp, ms_ColorTabbarActive, CUI::CORNER_BR, 10.0f); + + MainView.HSplitTop(10.0f, 0, &MainView); + + CUIRect Button; + + const char *aTabs[] = { + Localize("Language"), + Localize("General"), + Localize("Player"), + ("Tee"), + Localize("HUD"), + Localize("Controls"), + Localize("Graphics"), + Localize("Sound"), + Localize("DDNet") + }; + + int NumTabs = (int)(sizeof(aTabs)/sizeof(*aTabs)); + + for(int i = 0; i < NumTabs; i++) + { + TabBar.HSplitTop(10, &Button, &TabBar); + TabBar.HSplitTop(26, &Button, &TabBar); + if(DoButton_MenuTab(aTabs[i], aTabs[i], s_SettingsPage == i, &Button, CUI::CORNER_R)) + s_SettingsPage = i; + } + + MainView.Margin(10.0f, &MainView); + + if(s_SettingsPage == 0) + RenderLanguageSelection(MainView); + else if(s_SettingsPage == 1) + RenderSettingsGeneral(MainView); + else if(s_SettingsPage == 2) + RenderSettingsPlayer(MainView); + else if(s_SettingsPage == 3) + RenderSettingsTee(MainView); + else if(s_SettingsPage == 4) + RenderSettingsHUD(MainView); + else if(s_SettingsPage == 5) + RenderSettingsControls(MainView); + else if(s_SettingsPage == 6) + RenderSettingsGraphics(MainView); + else if(s_SettingsPage == 7) + RenderSettingsSound(MainView); + else if(s_SettingsPage == 8) + RenderSettingsDDNet(MainView); + + if(m_NeedRestartUpdate) + { + TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); + UI()->DoLabelScaled(&RestartWarning, Localize("DDNet Client needs to be restarted to complete update!"), 14.0f, -1); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + } + else if(m_NeedRestartSkins || m_NeedRestartGraphics || m_NeedRestartSound || m_NeedRestartDDNet) + UI()->DoLabelScaled(&RestartWarning, Localize("You must restart the game for all settings to take effect."), 14.0f, -1); +} + +void CMenus::RenderSettingsHUD(CUIRect MainView) +{ + static int pIDP1 = 0, pIDP2 = 0; + static int Page = 1; + CUIRect Left, Right, HUD, Messages, Button, Label, Weapon, Laser, Page1Tab, Page2Tab, Enable, Heart; + + MainView.HSplitTop(150.0f, &HUD, &MainView); + + HUD.HSplitTop(30.0f, &Label, &HUD); + float tw = TextRender()->TextWidth(0, 20.0f, Localize("HUD"), -1); + Label.VSplitLeft(tw + 10.0f, &Label, &Page1Tab); + Page1Tab.VSplitLeft(60.0f, &Page1Tab, 0); + Page1Tab.VSplitLeft(30.0f, &Page1Tab, &Page2Tab); + + UI()->DoLabelScaled(&Label, Localize("HUD"), 20.0f, -1); + if(DoButton_MenuTab((void *)&pIDP1, "1", Page == 1, &Page1Tab, 5)) + Page = 1; + if(DoButton_MenuTab((void *)&pIDP2, "2", Page == 2, &Page2Tab, 10)) + Page = 2; + + HUD.Margin(5.0f, &HUD); + HUD.VSplitMid(&Left, &Right); + Left.VSplitRight(5.0f, &Left, 0); + Right.VMargin(5.0f, &Right); + + if(Page == 1) + { + // show hud1 + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowhud, Localize("Show ingame HUD"), g_Config.m_ClShowhud, &Button)) + g_Config.m_ClShowhud ^= 1; + + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClDDRaceScoreBoard, Localize("Use DDRace Scoreboard"), g_Config.m_ClDDRaceScoreBoard, &Button)) + { + g_Config.m_ClDDRaceScoreBoard ^= 1; + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowIDs, Localize("Show client IDs in Scoreboard"), g_Config.m_ClShowIDs, &Button)) + { + g_Config.m_ClShowIDs ^= 1; + } + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClShowhudScore, Localize("Show score"), g_Config.m_ClShowhudScore, &Button)) + { + g_Config.m_ClShowhudScore ^= 1; + } + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClShowhudHealthAmmo, Localize("Show health + ammo"), g_Config.m_ClShowhudHealthAmmo, &Button)) + { + g_Config.m_ClShowhudHealthAmmo ^= 1; + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowChat, Localize("Show chat"), g_Config.m_ClShowChat, &Button)) + { + g_Config.m_ClShowChat ^= 1; + } + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClChatTeamColors, Localize("Show names in chat in team colors"), g_Config.m_ClChatTeamColors, &Button)) + { + g_Config.m_ClChatTeamColors ^= 1; + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowKillMessages, Localize("Show kill messages"), g_Config.m_ClShowKillMessages, &Button)) + { + g_Config.m_ClShowKillMessages ^= 1; + } + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClShowVotesAfterVoting, Localize("Show votes window after voting"), g_Config.m_ClShowVotesAfterVoting, &Button)) + { + g_Config.m_ClShowVotesAfterVoting ^= 1; + } + MainView.HSplitTop(170.0f, &Messages, &MainView); + Messages.HSplitTop(30.0f, &Label, &Messages); + Label.VSplitMid(&Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Messages"), 20.0f, -1); + Messages.Margin(5.0f, &Messages); + Messages.VSplitMid(&Left, &Right); + Left.VSplitRight(5.0f, &Left, 0); + Right.VMargin(5.0f, &Right); + { + char aBuf[64]; + Left.HSplitTop(20.0f, &Label, &Left); + Label.VSplitRight(50.0f, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("System message"), 16.0f, -1); + { + static int s_DefaultButton = 0; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)) + { + ColorHSLA HSL = color_cast(ColorRGBA(1.0f, 1.0f, 0.5f)); + g_Config.m_ClMessageSystemColor = HSL.Pack() & 0xFFFFFF; + } + } + + static ColorHSLA SMColor; + SMColor = g_Config.m_ClMessageSystemColor; + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); + SMColor.h = DoScrollbarH(&SMColor.h, &Button, SMColor.h); + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); + SMColor.s = DoScrollbarH(&SMColor.s, &Button, SMColor.s); + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); + SMColor.l = DoScrollbarH(&SMColor.l, &Button, SMColor.l); + + g_Config.m_ClMessageSystemColor = SMColor.Pack() & 0xFFFFFF; + + Left.HSplitTop(10.0f, &Label, &Left); + + ColorRGBA rgb = color_cast(SMColor); + TextRender()->TextColor(rgb); + + + char name[16]; + str_copy(name, g_Config.m_PlayerName, sizeof(name)); + str_format(aBuf, sizeof(aBuf), "*** '%s' entered and joined the spectators", name); + while(TextRender()->TextWidth(0, 12.0f, aBuf, -1) > Label.w) + { + name[str_length(name) - 1] = 0; + str_format(aBuf, sizeof(aBuf), "*** '%s' entered and joined the spectators", name); + } + UI()->DoLabelScaled(&Label, aBuf, 12.0f, -1); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + Left.HSplitTop(20.0f, 0, &Left); + } + { + char aBuf[64]; + Right.HSplitTop(20.0f, &Label, &Right); + Label.VSplitRight(50.0f, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Highlighted message"), 16.0f, -1); + { + static int s_DefaultButton = 0; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)) + { + ColorHSLA HSL = color_cast(ColorRGBA(1.0f, 0.5f, 0.5f)); + g_Config.m_ClMessageHighlightColor = HSL.Pack() & 0xFFFFFF; + } + } + + static ColorHSLA HMColor; + HMColor = g_Config.m_ClMessageHighlightColor; + + Right.HSplitTop(20.0f, &Button, &Right); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); + HMColor.h = DoScrollbarH(&HMColor.h, &Button, HMColor.h); + + Right.HSplitTop(20.0f, &Button, &Right); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); + HMColor.s = DoScrollbarH(&HMColor.s, &Button, HMColor.s); + + Right.HSplitTop(20.0f, &Button, &Right); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); + HMColor.l = DoScrollbarH(&HMColor.l, &Button, HMColor.l); + + g_Config.m_ClMessageHighlightColor = HMColor.Pack() & 0xFFFFFF; + Right.HSplitTop(10.0f, &Label, &Right); + + TextRender()->TextColor(0.75f, 0.5f, 0.75f, 1.0f); + float tw = TextRender()->TextWidth(0, 12.0f, Localize("Spectator"), -1); + Label.VSplitLeft(tw, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Spectator"), 12.0f, -1); + + ColorRGBA rgb = color_cast(HMColor); + TextRender()->TextColor(rgb); + char name[16]; + str_copy(name, g_Config.m_PlayerName, sizeof(name)); + str_format(aBuf, sizeof(aBuf), ": %s: %s", name, Localize ("Look out!")); + while(TextRender()->TextWidth(0, 12.0f, aBuf, -1) > Button.w) + { + name[str_length(name) - 1] = 0; + str_format(aBuf, sizeof(aBuf), ": %s: %s", name, Localize("Look out!")); + } + UI()->DoLabelScaled(&Button, aBuf, 12.0f, -1); + + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + Right.HSplitTop(20.0f, 0, &Right); + } + { + char aBuf[64]; + Left.HSplitTop(20.0f, &Label, &Left); + Label.VSplitRight(50.0f, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Team message"), 16.0f, -1); + { + static int s_DefaultButton = 0; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)) + { + ColorHSLA HSL = color_cast(ColorRGBA(0.65f, 1.0f, 0.65f)); + g_Config.m_ClMessageTeamColor = HSL.Pack() & 0xFFFFFF; + } + } + + static ColorHSLA TMColor; + TMColor = g_Config.m_ClMessageTeamColor; + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); + TMColor.h = DoScrollbarH(&TMColor.h, &Button, TMColor.h); + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); + TMColor.s = DoScrollbarH(&TMColor.s, &Button, TMColor.s); + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); + TMColor.l = DoScrollbarH(&TMColor.l, &Button, TMColor.l); + + g_Config.m_ClMessageTeamColor = TMColor.Pack() & 0xFFFFFF; + Left.HSplitTop(10.0f, &Label, &Left); + + ColorRGBA rgbn = CalculateNameColor(TMColor); + TextRender()->TextColor(rgbn); + float tw = TextRender()->TextWidth(0, 12.0f, Localize("Player"), -1); + Label.VSplitLeft(tw, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Player"), 12.0f, -1); + + ColorRGBA rgb = color_cast(TMColor); + TextRender()->TextColor(rgb); + str_format(aBuf, sizeof(aBuf), ": %s!", Localize("We will win")); + UI()->DoLabelScaled(&Button, aBuf, 12.0f, -1); + + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + Left.HSplitTop(20.0f, 0, &Left); + } + { + char aBuf[64]; + Right.HSplitTop(20.0f, &Label, &Right); + Label.VSplitRight(50.0f, &Label, &Button); + float twh = TextRender()->TextWidth(0, 16.0f, Localize("Friend message"), -1) ; + Label.VSplitLeft(twh + 5.0f, &Label, &Enable); + Enable.VSplitLeft(20.0f, &Enable, 0); + UI()->DoLabelScaled(&Label, Localize("Friend message"), 16.0f, -1); + { + static int s_DefaultButton = 0; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)) + g_Config.m_ClMessageFriendColor = ColorHSLA(0, 1, 145/255.0f).Pack() & 0xFFFFFF; + } + + if(DoButton_CheckBox(&g_Config.m_ClMessageFriend, "", g_Config.m_ClMessageFriend, &Enable)) + { + g_Config.m_ClMessageFriend ^= 1; + } + + static ColorHSLA FMColor; + FMColor = g_Config.m_ClMessageFriendColor; + + Right.HSplitTop(20.0f, &Button, &Right); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); + FMColor.h = DoScrollbarH(&FMColor.h, &Button, FMColor.h); + + Right.HSplitTop(20.0f, &Button, &Right); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); + FMColor.s = DoScrollbarH(&FMColor.s, &Button, FMColor.s); + + Right.HSplitTop(20.0f, &Button, &Right); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); + FMColor.l = DoScrollbarH(&FMColor.l, &Button, FMColor.l); + + g_Config.m_ClMessageFriendColor = FMColor.Pack() & 0xFFFFFF; + Right.HSplitTop(10.0f, &Label, &Right); + + ColorRGBA rgbf = color_cast(FMColor); + TextRender()->TextColor(rgbf); + float hw = TextRender()->TextWidth(0, 12.0f, "♥ ", -1); + Label.VSplitLeft(hw, &Heart, &Label); + UI()->DoLabelScaled(&Heart, "♥ ", 12.0f, -1); + + TextRender()->TextColor(0.8f, 0.8f, 0.8f, 1.0f); + float tw = TextRender()->TextWidth(0, 12.0f, Localize("Friend"), -1); + Label.VSplitLeft(tw, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Friend"), 12.0f, -1); + + ColorRGBA rgb = color_cast(ColorHSLA(g_Config.m_ClMessageColor)); + TextRender()->TextColor(rgb); + str_format(aBuf, sizeof(aBuf), ": %s", Localize("Hi o/")); + UI()->DoLabelScaled(&Button, aBuf, 12.0f, -1); + + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + Right.HSplitTop(20.0f, 0, &Right); + } + { + char aBuf[64]; + Left.HSplitTop(20.0f, &Label, &Left); + Label.VSplitRight(50.0f, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Normal message"), 16.0f, -1); + { + static int s_DefaultButton = 0; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)) + { + ColorHSLA HSL = color_cast(ColorRGBA(1.0f, 1.0f, 1.0f)); + g_Config.m_ClMessageColor = HSL.Pack() & 0xFFFFFF; + } + } + + static ColorHSLA MColor; + MColor = g_Config.m_ClMessageColor; + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Hue"), 14.0f, -1); + MColor.h = DoScrollbarH(&MColor.h, &Button, MColor.h); + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sat."), 14.0f, -1); + MColor.s = DoScrollbarH(&MColor.s, &Button, MColor.s); + + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Lht."), 14.0f, -1); + MColor.l = DoScrollbarH(&MColor.l, &Button, MColor.l); + + g_Config.m_ClMessageColor = MColor.Pack() & 0xFFFFFF; + Left.HSplitTop(10.0f, &Label, &Left); + + TextRender()->TextColor(0.8f, 0.8f, 0.8f, 1.0f); + float tw = TextRender()->TextWidth(0, 12.0f, Localize("Player"), -1); + Label.VSplitLeft(tw, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Player"), 12.0f, -1); + + ColorRGBA rgb = color_cast(MColor); + TextRender()->TextColor(rgb); + str_format(aBuf, sizeof(aBuf), ": %s :D", Localize("Hello and welcome")); + UI()->DoLabelScaled(&Button, aBuf, 12.0f, -1); + + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + } + } + else if(Page == 2) + { + Left.HSplitTop(220.0f, &Laser, &Left); + //RenderTools()->DrawUIRect(&Laser, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_ALL, 5.0f); + //Laser.Margin(10.0f, &Laser); + Laser.HSplitTop(30.0f, &Label, &Laser); + Label.VSplitLeft(TextRender()->TextWidth(0, 20.0f, Localize("Laser"), -1) + 5.0f, &Label, &Weapon); + UI()->DoLabelScaled(&Label, Localize("Laser"), 20.0f, -1); + + Laser.HSplitTop(20.0f, &Label, &Laser); + Label.VSplitLeft(5.0f, 0, &Label); + Label.VSplitRight(50.0f, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Inner color"), 16.0f, -1); + { + static int s_DefaultButton = 0; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)) + { + ColorHSLA HSL = color_cast(ColorRGBA(0.5f, 0.5f, 1.0f)); + g_Config.m_ClLaserInnerColor = HSL.Pack() & 0xFFFFFF; + } + } + + ColorHSLA LIColor(g_Config.m_ClLaserInnerColor); + + Laser.HSplitTop(20.0f, &Button, &Laser); + Button.VSplitLeft(20.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Hue"), 12.0f, -1); + LIColor.h = DoScrollbarH(&LIColor.h, &Button, LIColor.h); + + Laser.HSplitTop(20.0f, &Button, &Laser); + Button.VSplitLeft(20.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sat."), 12.0f, -1); + LIColor.s = DoScrollbarH(&LIColor.s, &Button, LIColor.s); + + Laser.HSplitTop(20.0f, &Button, &Laser); + Button.VSplitLeft(20.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Lht."), 12.0f, -1); + LIColor.l = DoScrollbarH(&LIColor.l, &Button, LIColor.l); + + g_Config.m_ClLaserInnerColor = LIColor.Pack() & 0xFFFFFF; + Laser.HSplitTop(10.0f, 0, &Laser); + + Laser.HSplitTop(20.0f, &Label, &Laser); + Label.VSplitLeft(5.0f, 0, &Label); + Label.VSplitRight(50.0f, &Label, &Button); + UI()->DoLabelScaled(&Label, Localize("Outline color"), 16.0f, -1); + { + static int s_DefaultButton = 0; + if(DoButton_Menu(&s_DefaultButton, Localize("Reset"), 0, &Button)) + { + ColorHSLA HSL = color_cast(ColorRGBA(0.075f, 0.075f, 0.25f)); + g_Config.m_ClLaserOutlineColor = HSL.Pack() & 0xFFFFFF; + } + } + + ColorRGBA LOColor(g_Config.m_ClLaserOutlineColor); + + Laser.HSplitTop(20.0f, &Button, &Laser); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Hue"), 12.0f, -1); + LOColor.h = DoScrollbarH(&LOColor.h, &Button, LOColor.h); + + Laser.HSplitTop(20.0f, &Button, &Laser); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Sat."), 12.0f, -1); + LOColor.s = DoScrollbarH(&LOColor.s, &Button, LOColor.s); + + Laser.HSplitTop(20.0f, &Button, &Laser); + Button.VSplitLeft(15.0f, 0, &Button); + Button.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Lht."), 12.0f, -1); + LOColor.l = DoScrollbarH(&LOColor.l, &Button, LOColor.l); + + g_Config.m_ClLaserOutlineColor = LOColor.Pack() & 0xFFFFFF; + //Laser.HSplitTop(8.0f, &Weapon, &Laser); + Weapon.VSplitLeft(30.0f, 0, &Weapon); + + ColorRGBA RGB; + vec2 From = vec2(Weapon.x, Weapon.y + Weapon.h / 2.0f); + vec2 Pos = vec2(Weapon.x + Weapon.w - 10.0f, Weapon.y + Weapon.h / 2.0f); + + vec2 Out, Border; + + Graphics()->BlendNormal(); + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + + // do outline + RGB = color_cast(ColorHSLA(g_Config.m_ClLaserOutlineColor)); + ColorRGBA OuterColor(RGB.r, RGB.g, RGB.b, 1.0f); + Graphics()->SetColor(RGB.r, RGB.g, RGB.b, 1.0f); // outline + Out = vec2(0.0f, -1.0f) * (3.15f); + + IGraphics::CFreeformItem Freeform( + From.x - Out.x, From.y - Out.y, + From.x + Out.x, From.y + Out.y, + Pos.x - Out.x, Pos.y - Out.y, + Pos.x + Out.x, Pos.y + Out.y); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + + // do inner + RGB = color_cast(ColorHSLA(g_Config.m_ClLaserInnerColor)); + ColorRGBA InnerColor(RGB.r, RGB.g, RGB.b, 1.0f); + Out = vec2(0.0f, -1.0f) * (2.25f); + Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f); // center + + Freeform = IGraphics::CFreeformItem( + From.x - Out.x, From.y - Out.y, + From.x + Out.x, From.y + Out.y, + Pos.x - Out.x, Pos.y - Out.y, + Pos.x + Out.x, Pos.y + Out.y); + Graphics()->QuadsDrawFreeform(&Freeform, 1); + + Graphics()->QuadsEnd(); + + // render head + { + Graphics()->BlendNormal(); + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_PARTICLES].m_Id); + Graphics()->QuadsBegin(); + + int Sprites[] = { SPRITE_PART_SPLAT01, SPRITE_PART_SPLAT02, SPRITE_PART_SPLAT03 }; + RenderTools()->SelectSprite(Sprites[time_get() % 3]); + Graphics()->QuadsSetRotation(time_get()); + Graphics()->SetColor(OuterColor.r, OuterColor.g, OuterColor.b, 1.0f); + IGraphics::CQuadItem QuadItem(Pos.x, Pos.y, 24, 24); + Graphics()->QuadsDraw(&QuadItem, 1); + Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f); + QuadItem = IGraphics::CQuadItem(Pos.x, Pos.y, 20, 20); + Graphics()->QuadsDraw(&QuadItem, 1); + Graphics()->QuadsEnd(); + } + // draw laser weapon + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GAME].m_Id); + Graphics()->QuadsBegin(); + + RenderTools()->SelectSprite(SPRITE_WEAPON_RIFLE_BODY); + RenderTools()->DrawSprite(Weapon.x, Weapon.y + Weapon.h / 2.0f, 60.0f); + + Graphics()->QuadsEnd(); + } + /* + Left.VSplitLeft(20.0f, 0, &Left); + Left.HSplitTop(20.0f, &Label, &Left); + Button.VSplitRight(20.0f, &Button, 0); + char aBuf[64]; + if(g_Config.m_ClReconnectTimeout == 1) + { + str_format(aBuf, sizeof(aBuf), "%s %i %s", Localize("Wait before try for"), g_Config.m_ClReconnectTimeout, Localize("second")); + } + else + { + str_format(aBuf, sizeof(aBuf), "%s %i %s", Localize("Wait before try for"), g_Config.m_ClReconnectTimeout, Localize("seconds")); + } + UI()->DoLabelScaled(&Label, aBuf, 13.0f, -1); + Left.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClReconnectTimeout = static_cast(DoScrollbarH(&g_Config.m_ClReconnectTimeout, &Button, g_Config.m_ClReconnectTimeout / 120.0f) * 120.0f); + if(g_Config.m_ClReconnectTimeout < 5) + g_Config.m_ClReconnectTimeout = 5;*/ +} + +void CMenus::RenderSettingsDDNet(CUIRect MainView) +{ + CUIRect Button, Left, Right, LeftLeft, Demo, Gameplay, Miscellaneous, Label, Background; + + bool CheckSettings = false; + static int s_InpMouseOld = g_Config.m_InpMouseOld; + + MainView.HSplitTop(100.0f, &Demo , &MainView); + + Demo.HSplitTop(30.0f, &Label, &Demo); + UI()->DoLabelScaled(&Label, Localize("Demo"), 20.0f, -1); + Demo.Margin(5.0f, &Demo); + Demo.VSplitMid(&Left, &Right); + Left.VSplitRight(5.0f, &Left, 0); + Right.VMargin(5.0f, &Right); + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClAutoRaceRecord, Localize("Save the best demo of each race"), g_Config.m_ClAutoRaceRecord, &Button)) + { + g_Config.m_ClAutoRaceRecord ^= 1; + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClRaceReplays, Localize("Enable replays"), g_Config.m_ClRaceReplays, &Button)) + { + if(g_Config.m_ClAutoDemoRecord && !g_Config.m_ClRaceReplays) + { + m_Popup = POPUP_REPLAY_ENABLING_ERROR; + } + else + { + g_Config.m_ClRaceReplays ^= 1; + } + } + + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClRaceGhost, Localize("Ghost"), g_Config.m_ClRaceGhost, &Button)) + { + g_Config.m_ClRaceGhost ^= 1; + } + + if(g_Config.m_ClRaceGhost) + { + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClRaceShowGhost, Localize("Show ghost"), g_Config.m_ClRaceShowGhost, &Button)) + { + g_Config.m_ClRaceShowGhost ^= 1; + } + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClRaceSaveGhost, Localize("Save ghost"), g_Config.m_ClRaceSaveGhost, &Button)) + { + g_Config.m_ClRaceSaveGhost ^= 1; + } + } + + MainView.HSplitTop(290.0f, &Gameplay , &MainView); + + Gameplay.HSplitTop(30.0f, &Label, &Gameplay); + UI()->DoLabelScaled(&Label, Localize("Gameplay"), 20.0f, -1); + Gameplay.Margin(5.0f, &Gameplay); + Gameplay.VSplitMid(&Left, &Right); + Left.VSplitRight(5.0f, &Left, 0); + Right.VMargin(5.0f, &Right); + + { + CUIRect Button, Label; + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitLeft(120.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Overlay entities"), 14.0f, -1); + g_Config.m_ClOverlayEntities = (int)(DoScrollbarH(&g_Config.m_ClOverlayEntities, &Button, g_Config.m_ClOverlayEntities/100.0f)*100.0f); + } + + { + CUIRect Button, Label; + Left.HSplitTop(20.0f, &Button, &Left); + Button.VSplitMid(&LeftLeft, &Button); + + Button.VSplitLeft(50.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + UI()->DoLabelScaled(&Label, Localize("Alpha"), 14.0f, -1); + g_Config.m_ClShowOthersAlpha = (int)(DoScrollbarH(&g_Config.m_ClShowOthersAlpha, &Button, g_Config.m_ClShowOthersAlpha /100.0f)*100.0f); + + if(DoButton_CheckBox(&g_Config.m_ClShowOthers, Localize("Show others"), g_Config.m_ClShowOthers, &LeftLeft)) + { + g_Config.m_ClShowOthers ^= 1; + } + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowQuads, Localize("Show quads"), g_Config.m_ClShowQuads, &Button)) + { + g_Config.m_ClShowQuads ^= 1; + } + + Right.HSplitTop(20.0f, &Label, &Right); + Label.VSplitLeft(130.0f, &Label, &Button); + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("Default zoom"), g_Config.m_ClDefaultZoom); + UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); + //Right.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClDefaultZoom= static_cast(DoScrollbarH(&g_Config.m_ClDefaultZoom, &Button, g_Config.m_ClDefaultZoom/20.0f)*20.0f+0.1f); + + Right.HSplitTop(20.0f, &Label, &Right); + Label.VSplitLeft(130.0f, &Label, &Button); + str_format(aBuf, sizeof(aBuf), "%s: %i", Localize("AntiPing limit"), g_Config.m_ClAntiPingLimit); + UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); + //Right.HSplitTop(20.0f, &Button, 0); + Button.HMargin(2.0f, &Button); + g_Config.m_ClAntiPingLimit= static_cast(DoScrollbarH(&g_Config.m_ClAntiPingLimit, &Button, g_Config.m_ClAntiPingLimit/200.0f)*200.0f+0.1f); + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClAntiPing, Localize("AntiPing"), g_Config.m_ClAntiPing, &Button)) + { + g_Config.m_ClAntiPing ^= 1; + } + + if(g_Config.m_ClAntiPing) + { + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClAntiPingPlayers, Localize("AntiPing: predict other players"), g_Config.m_ClAntiPingPlayers, &Button)) + { + g_Config.m_ClAntiPingPlayers ^= 1; + } + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClAntiPingWeapons, Localize("AntiPing: predict weapons"), g_Config.m_ClAntiPingWeapons, &Button)) + { + g_Config.m_ClAntiPingWeapons ^= 1; + } + + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_CheckBox(&g_Config.m_ClAntiPingGrenade, Localize("AntiPing: predict grenade paths"), g_Config.m_ClAntiPingGrenade, &Button)) + { + g_Config.m_ClAntiPingGrenade ^= 1; + } + } + else + { + Right.HSplitTop(60.0f, 0, &Right); + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowHookCollOther, Localize("Show other players' hook collision lines"), g_Config.m_ClShowHookCollOther, &Button)) + { + g_Config.m_ClShowHookCollOther ^= 1; + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClShowDirection, Localize("Show other players' key presses"), g_Config.m_ClShowDirection, &Button)) + { + g_Config.m_ClShowDirection ^= 1; + } + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_InpMouseOld, Localize("Old mouse mode"), g_Config.m_InpMouseOld, &Button)) + { + g_Config.m_InpMouseOld ^= 1; + CheckSettings = true; + } + + if(CheckSettings) + { + if(s_InpMouseOld == g_Config.m_InpMouseOld) + m_NeedRestartDDNet = false; + else + m_NeedRestartDDNet = true; + } + + CUIRect aRects[2]; + Left.HSplitTop(5.0f, &Button, &Left); + Right.HSplitTop(5.0f, &Button, &Right); + aRects[0] = Left; + aRects[1] = Right; + aRects[0].VSplitRight(10.0f, &aRects[0], 0); + aRects[1].VSplitLeft(10.0f, 0, &aRects[1]); + + ColorHSLA Bg = ColorHSLA(g_Config.m_ClBackgroundColor), BgE = ColorHSLA(g_Config.m_ClBackgroundEntitiesColor); + float *pColorSlider[2][3] = {{&Bg.h, &Bg.s, &Bg.l}, {&BgE.h, &BgE.s, &BgE.l}}; + + const char *paParts[] = { + Localize("Background (regular)"), + Localize("Background (entities)")}; + const char *paLabels[] = { + Localize("Hue"), + Localize("Sat."), + Localize("Lht.")}; + + for(int i = 0; i < 2; i++) + { + aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); + UI()->DoLabelScaled(&Label, paParts[i], 14.0f, -1); + aRects[i].VSplitLeft(20.0f, 0, &aRects[i]); + aRects[i].HSplitTop(2.5f, 0, &aRects[i]); + + for(int s = 0; s < 3; s++) + { + aRects[i].HSplitTop(20.0f, &Label, &aRects[i]); + Label.VSplitLeft(100.0f, &Label, &Button); + Button.HMargin(2.0f, &Button); + + float *k = pColorSlider[i][s]; + *k = DoScrollbarH(k, &Button, *k); + UI()->DoLabelScaled(&Label, paLabels[s], 15.0f, -1); + } + } + g_Config.m_ClBackgroundColor = Bg.Pack(); + g_Config.m_ClBackgroundEntitiesColor = BgE.Pack(); + + { + static float s_Map = 0.0f; + aRects[1].HSplitTop(25.0f, &Background, &aRects[1]); + Background.HSplitTop(20.0f, &Background, 0); + Background.VSplitLeft(100.0f, &Label, &Left); + UI()->DoLabelScaled(&Label, Localize("Map"), 14.0f, -1); + DoEditBox(g_Config.m_ClBackgroundEntities, &Left, g_Config.m_ClBackgroundEntities, sizeof(g_Config.m_ClBackgroundEntities), 14.0f, &s_Map); + + aRects[1].HSplitTop(20.0f, &Button, 0); + if(DoButton_CheckBox(&g_Config.m_ClBackgroundShowTilesLayers, Localize("Show tiles layers from BG map"), g_Config.m_ClBackgroundShowTilesLayers, &Button)) + { + g_Config.m_ClBackgroundShowTilesLayers ^= 1; + } + } + + MainView.HSplitTop(30.0f, &Label, &Miscellaneous); + UI()->DoLabelScaled(&Label, Localize("Miscellaneous"), 20.0f, -1); + Miscellaneous.VMargin(5.0f, &Miscellaneous); + Miscellaneous.VSplitMid(&Left, &Right); + Left.VSplitRight(5.0f, &Left, 0); + Right.VMargin(5.0f, &Right); + + Left.HSplitTop(20.0f, &Button, &Left); + if(DoButton_CheckBox(&g_Config.m_ClHttpMapDownload, Localize("Try fast HTTP map download first"), g_Config.m_ClHttpMapDownload, &Button)) + { + g_Config.m_ClHttpMapDownload ^= 1; + } + + // Updater +#if defined(CONF_AUTOUPDATE) + { + Left.HSplitTop(20.0f, &Label, &Left); + bool NeedUpdate = str_comp(Client()->LatestVersion(), "0"); + char aBuf[256]; + int State = Updater()->GetCurrentState(); + + // Update Button + if(NeedUpdate && State <= IUpdater::CLEAN) + { + str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is available:"), Client()->LatestVersion()); + Label.VSplitLeft(TextRender()->TextWidth(0, 14.0f, aBuf, -1) + 10.0f, &Label, &Button); + Button.VSplitLeft(100.0f, &Button, 0); + static int s_ButtonUpdate = 0; + if(DoButton_Menu(&s_ButtonUpdate, Localize("Update now"), 0, &Button)) + Updater()->InitiateUpdate(); + } + else if(State >= IUpdater::GETTING_MANIFEST && State < IUpdater::NEED_RESTART) + str_format(aBuf, sizeof(aBuf), Localize("Updating...")); + else if(State == IUpdater::NEED_RESTART) + { + str_format(aBuf, sizeof(aBuf), Localize("DDNet Client updated!")); + m_NeedRestartUpdate = true; + } + else + { + str_format(aBuf, sizeof(aBuf), Localize("No updates available")); + Label.VSplitLeft(TextRender()->TextWidth(0, 14.0f, aBuf, -1) + 10.0f, &Label, &Button); + Button.VSplitLeft(100.0f, &Button, 0); + static int s_ButtonUpdate = 0; + if(DoButton_Menu(&s_ButtonUpdate, Localize("Check now"), 0, &Button)) + { + Client()->RequestDDNetInfo(); + } + } + UI()->DoLabelScaled(&Label, aBuf, 14.0f, -1); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); + } +#endif + + { + static int s_ButtonTimeout = 0; + Right.HSplitTop(20.0f, &Button, &Right); + if(DoButton_Menu(&s_ButtonTimeout, Localize("New random timeout code"), 0, &Button)) + { + Client()->GenerateTimeoutSeed(); + } + } +} diff --git a/build/enc_temp_folder/7442489b57efc3296db690fba0f863d0/client.cpp b/build/enc_temp_folder/7442489b57efc3296db690fba0f863d0/client.cpp new file mode 100644 index 000000000..8128e5a6c --- /dev/null +++ b/build/enc_temp_folder/7442489b57efc3296db690fba0f863d0/client.cpp @@ -0,0 +1,3961 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ + +#define _WIN32_WINNT 0x0501 + +#include + +#include // qsort +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#if defined(CONF_FAMILY_WINDOWS) + #define WIN32_LEAN_AND_MEAN + #include +#endif + +#include "friends.h" +#include "serverbrowser.h" +#include "updater.h" +#include "client.h" + +#include + +#include "SDL.h" +#ifdef main +#undef main +#endif + +void CGraph::Init(float Min, float Max) +{ + m_Min = Min; + m_Max = Max; + m_Index = 0; +} + +void CGraph::ScaleMax() +{ + int i = 0; + m_Max = 0; + for(i = 0; i < MAX_VALUES; i++) + { + if(m_aValues[i] > m_Max) + m_Max = m_aValues[i]; + } +} + +void CGraph::ScaleMin() +{ + int i = 0; + m_Min = m_Max; + for(i = 0; i < MAX_VALUES; i++) + { + if(m_aValues[i] < m_Min) + m_Min = m_aValues[i]; + } +} + +void CGraph::Add(float v, float r, float g, float b) +{ + m_Index = (m_Index+1)&(MAX_VALUES-1); + m_aValues[m_Index] = v; + m_aColors[m_Index][0] = r; + m_aColors[m_Index][1] = g; + m_aColors[m_Index][2] = b; +} + +void CGraph::Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription) +{ + //m_pGraphics->BlendNormal(); + + + pGraphics->TextureSet(-1); + + pGraphics->QuadsBegin(); + pGraphics->SetColor(0, 0, 0, 0.75f); + IGraphics::CQuadItem QuadItem(x, y, w, h); + pGraphics->QuadsDrawTL(&QuadItem, 1); + pGraphics->QuadsEnd(); + + pGraphics->LinesBegin(); + pGraphics->SetColor(0.95f, 0.95f, 0.95f, 1.00f); + IGraphics::CLineItem LineItem(x, y+h/2, x+w, y+h/2); + pGraphics->LinesDraw(&LineItem, 1); + pGraphics->SetColor(0.5f, 0.5f, 0.5f, 0.75f); + IGraphics::CLineItem Array[2] = { + IGraphics::CLineItem(x, y+(h*3)/4, x+w, y+(h*3)/4), + IGraphics::CLineItem(x, y+h/4, x+w, y+h/4)}; + pGraphics->LinesDraw(Array, 2); + for(int i = 1; i < MAX_VALUES; i++) + { + float a0 = (i-1)/(float)MAX_VALUES; + float a1 = i/(float)MAX_VALUES; + int i0 = (m_Index+i-1)&(MAX_VALUES-1); + int i1 = (m_Index+i)&(MAX_VALUES-1); + + float v0 = (m_aValues[i0]-m_Min) / (m_Max-m_Min); + float v1 = (m_aValues[i1]-m_Min) / (m_Max-m_Min); + + IGraphics::CColorVertex Array[2] = { + IGraphics::CColorVertex(0, m_aColors[i0][0], m_aColors[i0][1], m_aColors[i0][2], 0.75f), + IGraphics::CColorVertex(1, m_aColors[i1][0], m_aColors[i1][1], m_aColors[i1][2], 0.75f)}; + pGraphics->SetColorVertex(Array, 2); + IGraphics::CLineItem LineItem(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h); + pGraphics->LinesDraw(&LineItem, 1); + + } + pGraphics->LinesEnd(); + + pGraphics->TextureSet(Font); + pGraphics->QuadsBegin(); + pGraphics->QuadsText(x+2, y+h-16, 16, pDescription); + + char aBuf[32]; + str_format(aBuf, sizeof(aBuf), "%.2f", m_Max); + pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, aBuf); + + str_format(aBuf, sizeof(aBuf), "%.2f", m_Min); + pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, aBuf); + pGraphics->QuadsEnd(); +} + + +void CSmoothTime::Init(int64 Target) +{ + m_Snap = time_get(); + m_Current = Target; + m_Target = Target; + m_aAdjustSpeed[0] = 0.3f; + m_aAdjustSpeed[1] = 0.3f; + m_Graph.Init(0.0f, 0.5f); +} + +void CSmoothTime::SetAdjustSpeed(int Direction, float Value) +{ + m_aAdjustSpeed[Direction] = Value; +} + +int64 CSmoothTime::Get(int64 Now) +{ + int64 c = m_Current + (Now - m_Snap); + int64 t = m_Target + (Now - m_Snap); + + // it's faster to adjust upward instead of downward + // we might need to adjust these abit + + float AdjustSpeed = m_aAdjustSpeed[0]; + if(t > c) + AdjustSpeed = m_aAdjustSpeed[1]; + + float a = ((Now-m_Snap)/(float)time_freq()) * AdjustSpeed; + if(a > 1.0f) + a = 1.0f; + + int64 r = c + (int64)((t-c)*a); + + m_Graph.Add(a+0.5f,1,1,1); + + return r; +} + +void CSmoothTime::UpdateInt(int64 Target) +{ + int64 Now = time_get(); + m_Current = Get(Now); + m_Snap = Now; + m_Target = Target; +} + +void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection) +{ + int UpdateTimer = 1; + + if(TimeLeft < 0) + { + int IsSpike = 0; + if(TimeLeft < -50) + { + IsSpike = 1; + + m_SpikeCounter += 5; + if(m_SpikeCounter > 50) + m_SpikeCounter = 50; + } + + if(IsSpike && m_SpikeCounter < 15) + { + // ignore this ping spike + UpdateTimer = 0; + pGraph->Add(TimeLeft, 1,1,0); + } + else + { + pGraph->Add(TimeLeft, 1,0,0); + if(m_aAdjustSpeed[AdjustDirection] < 30.0f) + m_aAdjustSpeed[AdjustDirection] *= 2.0f; + } + } + else + { + if(m_SpikeCounter) + m_SpikeCounter--; + + pGraph->Add(TimeLeft, 0,1,0); + + m_aAdjustSpeed[AdjustDirection] *= 0.95f; + if(m_aAdjustSpeed[AdjustDirection] < 2.0f) + m_aAdjustSpeed[AdjustDirection] = 2.0f; + } + + if(UpdateTimer) + UpdateInt(Target); +} + + +CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta) +{ + for (int i = 0; i < RECORDER_MAX; i++) { + m_DemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta); + } + + m_pEditor = 0; + m_pInput = 0; + m_pGraphics = 0; + m_pSound = 0; + m_pGameClient = 0; + m_pMap = 0; + m_pConsole = 0; + + m_RenderFrameTime = 0.0001f; + m_RenderFrameTimeLow = 1.0f; + m_RenderFrameTimeHigh = 0.0f; + m_RenderFrames = 0; + m_LastRenderTime = time_get(); + + m_GameTickSpeed = SERVER_TICK_SPEED; + + m_SnapCrcErrors = 0; + m_AutoScreenshotRecycle = false; + m_AutoStatScreenshotRecycle = false; + m_AutoCSVRecycle = false; + m_EditorActive = false; + + m_AckGameTick[0] = -1; + m_AckGameTick[1] = -1; + m_CurrentRecvTick[0] = 0; + m_CurrentRecvTick[1] = 0; + m_RconAuthed[0] = 0; + m_RconAuthed[1] = 0; + m_RconPassword[0] = '\0'; + m_Password[0] = '\0'; + + // version-checking + m_aVersionStr[0] = '0'; + m_aVersionStr[1] = '\0'; + + // pinging + m_PingStartTime = 0; + + m_aCurrentMap[0] = 0; + + m_aCmdConnect[0] = 0; + + // map download + m_aMapdownloadFilename[0] = 0; + m_aMapdownloadName[0] = 0; + m_pMapdownloadTask = NULL; + m_MapdownloadFile = 0; + m_MapdownloadChunk = 0; + m_MapdownloadSha256Present = false; + m_MapdownloadSha256 = SHA256_ZEROED; + m_MapdownloadCrc = 0; + m_MapdownloadAmount = -1; + m_MapdownloadTotalsize = -1; + + m_MapDetailsPresent = false; + m_aMapDetailsName[0] = 0; + m_MapDetailsSha256 = SHA256_ZEROED; + m_MapDetailsCrc = 0; + + m_pDDNetInfoTask = NULL; + m_aNews[0] = '\0'; + + m_CurrentServerInfoRequestTime = -1; + + m_CurrentInput[0] = 0; + m_CurrentInput[1] = 0; + m_LastDummy = 0; + m_LastDummy2 = 0; + + mem_zero(&m_aInputs, sizeof(m_aInputs)); + + m_State = IClient::STATE_OFFLINE; + m_aServerAddressStr[0] = 0; + + mem_zero(m_aSnapshots, sizeof(m_aSnapshots)); + m_SnapshotStorage[0].Init(); + m_SnapshotStorage[1].Init(); + m_ReceivedSnapshots[0] = 0; + m_ReceivedSnapshots[1] = 0; + m_SnapshotParts[0] = 0; + m_SnapshotParts[1] = 0; + + m_VersionInfo.m_State = CVersionInfo::STATE_INIT; + + if(g_Config.m_ClDummy == 0) + m_LastDummyConnectTime = 0; + + m_ReconnectTime = 0; + + m_GenerateTimeoutSeed = true; + + m_FrameTimeAvg = 0.0001f; +} + +// ----- send functions ----- +int CClient::SendMsg(CMsgPacker *pMsg, int Flags) +{ + return SendMsgEx(pMsg, Flags, false); +} + +int CClient::SendMsgEx(CMsgPacker *pMsg, int Flags, bool System) +{ + CNetChunk Packet; + + if(State() == IClient::STATE_OFFLINE) + return 0; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = 0; + Packet.m_pData = pMsg->Data(); + Packet.m_DataSize = pMsg->Size(); + + // HACK: modify the message id in the packet and store the system flag + if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1) + { + dbg_break(); + } + + *((unsigned char*)Packet.m_pData) <<= 1; + if(System) + *((unsigned char*)Packet.m_pData) |= 1; + + if(Flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(Flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + if(Flags&MSGFLAG_RECORD) + { + for(int i = 0; i < RECORDER_MAX; i++) + if(m_DemoRecorder[i].IsRecording()) + m_DemoRecorder[i].RecordMessage(Packet.m_pData, Packet.m_DataSize); + } + + if(!(Flags&MSGFLAG_NOSEND)) + { + m_NetClient[g_Config.m_ClDummy].Send(&Packet); + } + + return 0; +} + +void CClient::SendInfo() +{ + CMsgPacker Msg(NETMSG_INFO); + Msg.AddString(GameClient()->NetVersion(), 128); + Msg.AddString(m_Password, 128); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); +} + + +void CClient::SendEnterGame() +{ + CMsgPacker Msg(NETMSG_ENTERGAME); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); +} + +void CClient::SendReady() +{ + CMsgPacker Msg(NETMSG_READY); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); +} + +void CClient::SendMapRequest() +{ + if(m_MapdownloadFile) + io_close(m_MapdownloadFile); + m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE); + CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA); + Msg.AddInt(m_MapdownloadChunk); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); +} + +void CClient::RconAuth(const char *pName, const char *pPassword) +{ + if(RconAuthed()) + return; + + if(pPassword != m_RconPassword) + str_copy(m_RconPassword, pPassword, sizeof(m_RconPassword)); + + CMsgPacker Msg(NETMSG_RCON_AUTH); + Msg.AddString(pName, 32); + Msg.AddString(pPassword, 32); + Msg.AddInt(1); + SendMsgEx(&Msg, MSGFLAG_VITAL); +} + +void CClient::Rcon(const char *pCmd) +{ + CMsgPacker Msg(NETMSG_RCON_CMD); + Msg.AddString(pCmd, 256); + SendMsgEx(&Msg, MSGFLAG_VITAL); +} + +bool CClient::ConnectionProblems() +{ + return m_NetClient[g_Config.m_ClDummy].GotProblems() != 0; +} + +void CClient::DirectInput(int *pInput, int Size) +{ + int i; + CMsgPacker Msg(NETMSG_INPUT); + Msg.AddInt(m_AckGameTick[g_Config.m_ClDummy]); + Msg.AddInt(m_PredTick[g_Config.m_ClDummy]); + Msg.AddInt(Size); + + for(i = 0; i < Size/4; i++) + Msg.AddInt(pInput[i]); + + SendMsgEx(&Msg, 0); +} + +void CClient::SendInput() +{ + int64 Now = time_get(); + + if(m_PredTick[g_Config.m_ClDummy] <= 0) + return; + + if(m_LastDummy != (bool)g_Config.m_ClDummy) + { + m_LastDummy = g_Config.m_ClDummy; + GameClient()->OnDummySwap(); + } + + bool Force = false; + // fetch input + for(int Dummy = 0; Dummy < 2; Dummy++) + { + if(!m_DummyConnected && Dummy != 0) + { + break; + } + int i = g_Config.m_ClDummy ^ Dummy; + int Size = GameClient()->OnSnapInput(m_aInputs[i][m_CurrentInput[i]].m_aData, Dummy, Force); + + if(Size) + { + // pack input + CMsgPacker Msg(NETMSG_INPUT); + Msg.AddInt(m_AckGameTick[i]); + Msg.AddInt(m_PredTick[i]); + Msg.AddInt(Size); + + m_aInputs[i][m_CurrentInput[i]].m_Tick = m_PredTick[i]; + m_aInputs[i][m_CurrentInput[i]].m_PredictedTime = m_PredictedTime.Get(Now); + m_aInputs[i][m_CurrentInput[i]].m_Time = Now; + + // pack it + for(int k = 0; k < Size/4; k++) + Msg.AddInt(m_aInputs[i][m_CurrentInput[i]].m_aData[k]); + + m_CurrentInput[i]++; + m_CurrentInput[i] %= 200; + + SendMsgExY(&Msg, MSGFLAG_FLUSH, true, i); + // ugly workaround for dummy. we need to send input with dummy to prevent + // prediction time resets. but if we do it too often, then it's + // impossible to use grenade with frozen dummy that gets hammered... + if(g_Config.m_ClDummyCopyMoves || m_CurrentInput[i] % 2) + Force = true; + } + } +} + +const char *CClient::LatestVersion() +{ + return m_aVersionStr; +} + +// TODO: OPT: do this a lot smarter! +int *CClient::GetInput(int Tick) +{ + int Best = -1; + for(int i = 0; i < 200; i++) + { + if(m_aInputs[g_Config.m_ClDummy][i].m_Tick <= Tick && (Best == -1 || m_aInputs[g_Config.m_ClDummy][Best].m_Tick < m_aInputs[g_Config.m_ClDummy][i].m_Tick)) + Best = i; + } + + if(Best != -1) + return (int *)m_aInputs[g_Config.m_ClDummy][Best].m_aData; + return 0; +} + +int *CClient::GetDirectInput(int Tick) +{ + for(int i = 0; i < 200; i++) + if(m_aInputs[g_Config.m_ClDummy][i].m_Tick == Tick) + return (int *)m_aInputs[g_Config.m_ClDummy][i].m_aData; + return 0; +} + +// ------ state handling ----- +void CClient::SetState(int s) +{ + if(m_State == IClient::STATE_QUITING) + return; + + int Old = m_State; + if(g_Config.m_Debug) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "state change. last=%d current=%d", m_State, s); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); + } + m_State = s; + if(Old != s) + { + GameClient()->OnStateChange(m_State, Old); + + if(s == IClient::STATE_OFFLINE && m_ReconnectTime == 0) + { + if(g_Config.m_ClReconnectFull > 0 && (str_find_nocase(ErrorString(), "full") || str_find_nocase(ErrorString(), "reserved"))) + m_ReconnectTime = time_get() + time_freq() * g_Config.m_ClReconnectFull; + else if(g_Config.m_ClReconnectTimeout > 0 && (str_find_nocase(ErrorString(), "Timeout") || str_find_nocase(ErrorString(), "Too weak connection"))) + m_ReconnectTime = time_get() + time_freq() * g_Config.m_ClReconnectTimeout; + } + } +} + +// called when the map is loaded and we should init for a new round +void CClient::OnEnterGame() +{ + // reset input + int i; + for(i = 0; i < 200; i++) + { + m_aInputs[0][i].m_Tick = -1; + m_aInputs[1][i].m_Tick = -1; + } + m_CurrentInput[0] = 0; + m_CurrentInput[1] = 0; + + // reset snapshots + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = 0; + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = 0; + m_SnapshotStorage[g_Config.m_ClDummy].PurgeAll(); + m_ReceivedSnapshots[g_Config.m_ClDummy] = 0; + m_SnapshotParts[g_Config.m_ClDummy] = 0; + m_PredTick[g_Config.m_ClDummy] = 0; + m_CurrentRecvTick[g_Config.m_ClDummy] = 0; + m_CurGameTick[g_Config.m_ClDummy] = 0; + m_PrevGameTick[g_Config.m_ClDummy] = 0; + + if(g_Config.m_ClDummy == 0) + m_LastDummyConnectTime = 0; + + GameClient()->OnEnterGame(); +} + +void CClient::EnterGame() +{ + if(State() == IClient::STATE_DEMOPLAYBACK) + return; + + // now we will wait for two snapshots + // to finish the connection + SendEnterGame(); + OnEnterGame(); + + ServerInfoRequest(); // fresh one for timeout protection + m_aTimeoutCodeSent[0] = false; + m_aTimeoutCodeSent[1] = false; +} + +void GenerateTimeoutCode(char *pBuffer, unsigned Size, char *pSeed, const NETADDR &Addr, bool Dummy) +{ + MD5_CTX Md5; + md5_init(&Md5); + const char *pDummy = Dummy ? "dummy" : "normal"; + md5_update(&Md5, (unsigned char *)pDummy, str_length(pDummy) + 1); + md5_update(&Md5, (unsigned char *)pSeed, str_length(pSeed) + 1); + md5_update(&Md5, (unsigned char *)&Addr, sizeof(Addr)); + MD5_DIGEST Digest = md5_finish(&Md5); + + unsigned short Random[8]; + mem_copy(Random, Digest.data, sizeof(Random)); + generate_password(pBuffer, Size, Random, 8); +} + +void CClient::GenerateTimeoutSeed() +{ + secure_random_password(g_Config.m_ClTimeoutSeed, sizeof(g_Config.m_ClTimeoutSeed), 16); +} + +void CClient::GenerateTimeoutCodes() +{ + if(g_Config.m_ClTimeoutSeed[0]) + { + for(int i = 0; i < 2; i++) + { + GenerateTimeoutCode(m_aTimeoutCodes[i], sizeof(m_aTimeoutCodes[i]), g_Config.m_ClTimeoutSeed, m_ServerAddress, i); + + char aBuf[64]; + str_format(aBuf, sizeof(aBuf), "timeout code '%s' (%s)", m_aTimeoutCodes[i], i == 0 ? "normal" : "dummy"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + } + } + else + { + str_copy(m_aTimeoutCodes[0], g_Config.m_ClTimeoutCode, sizeof(m_aTimeoutCodes[0])); + str_copy(m_aTimeoutCodes[1], g_Config.m_ClDummyTimeoutCode, sizeof(m_aTimeoutCodes[1])); + } +} + +void CClient::Connect(const char *pAddress, const char *pPassword) +{ + char aBuf[512]; + int Port = 8303; + + replayCounter = 0; + + Disconnect(); + + str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr)); + + str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + + ServerInfoRequest(); + if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient[0].NetType()) != 0) + { + char aBufMsg[256]; + str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg); + net_host_lookup("localhost", &m_ServerAddress, m_NetClient[0].NetType()); + } + + if(m_SendPassword) + { + str_copy(m_Password, g_Config.m_Password, sizeof(m_Password)); + m_SendPassword = false; + } + else if(!pPassword) + m_Password[0] = 0; + else + str_copy(m_Password, pPassword, sizeof(m_Password)); + + // Deregister Rcon commands from last connected server, might not have called + // DisconnectWithReason if the server was shut down + m_RconAuthed[0] = 0; + m_UseTempRconCommands = 0; + m_pConsole->DeregisterTempAll(); + if(m_ServerAddress.port == 0) + m_ServerAddress.port = Port; + m_NetClient[0].Connect(&m_ServerAddress); + SetState(IClient::STATE_CONNECTING); + + for(int i = 0; i < RECORDER_MAX; i++) + if(m_DemoRecorder[i].IsRecording()) + DemoRecorder_Stop(i); + + m_InputtimeMarginGraph.Init(-150.0f, 150.0f); + m_GametimeMarginGraph.Init(-150.0f, 150.0f); + + GenerateTimeoutCodes(); + + GameClient()->OnTimeScore(1, false); +} + +void CClient::DisconnectWithReason(const char *pReason) +{ + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason?pReason:"unknown"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + + // stop demo playback and recorder + m_DemoPlayer.Stop(); + for(int i = 0; i < RECORDER_MAX; i++) + DemoRecorder_Stop(i); + + // + m_RconAuthed[0] = 0; + m_UseTempRconCommands = 0; + m_pConsole->DeregisterTempAll(); + m_NetClient[0].Disconnect(pReason); + SetState(IClient::STATE_OFFLINE); + m_pMap->Unload(); + + // disable all downloads + m_MapdownloadChunk = 0; + if(m_pMapdownloadTask) + m_pMapdownloadTask->Abort(); + if(m_MapdownloadFile) + io_close(m_MapdownloadFile); + m_MapdownloadFile = 0; + m_MapdownloadSha256Present = false; + m_MapdownloadSha256 = SHA256_ZEROED; + m_MapdownloadCrc = 0; + m_MapdownloadTotalsize = -1; + m_MapdownloadAmount = 0; + m_MapDetailsPresent = false; + + // clear the current server info + mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); + mem_zero(&m_ServerAddress, sizeof(m_ServerAddress)); + + // clear snapshots + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = 0; + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = 0; + m_ReceivedSnapshots[g_Config.m_ClDummy] = 0; +} + +void CClient::Disconnect() +{ + if(m_DummyConnected) + DummyDisconnect(0); + if(m_State != IClient::STATE_OFFLINE) + DisconnectWithReason(0); + + // make sure to remove replay tmp demo + if(g_Config.m_ClRaceReplays) + { + Storage()->RemoveFile((&m_DemoRecorder[RECORDER_REPLAYS])->GetCurrentFilename(), IStorage::TYPE_SAVE); + } +} + +bool CClient::DummyConnected() +{ + return m_DummyConnected; +} + +bool CClient::DummyConnecting() +{ + return !m_DummyConnected && m_LastDummyConnectTime > 0 && m_LastDummyConnectTime + GameTickSpeed() * 5 > GameTick(); +} + +void CClient::DummyConnect() +{ + if(m_LastDummyConnectTime > 0 && m_LastDummyConnectTime + GameTickSpeed() * 5 > GameTick()) + return; + + if(m_NetClient[0].State() != NET_CONNSTATE_ONLINE && m_NetClient[0].State() != NET_CONNSTATE_PENDING) + return; + + if(m_DummyConnected) + return; + + m_LastDummyConnectTime = GameTick(); + + m_RconAuthed[1] = 0; + + m_DummySendConnInfo = true; + + g_Config.m_ClDummyCopyMoves = 0; + g_Config.m_ClDummyHammer = 0; + + //connecting to the server + m_NetClient[1].Connect(&m_ServerAddress); + + GameClient()->OnTimeScore(1, true); +} + +void CClient::DummyDisconnect(const char *pReason) +{ + if(!m_DummyConnected) + return; + + m_NetClient[1].Disconnect(pReason); + g_Config.m_ClDummy = 0; + m_RconAuthed[1] = 0; + m_DummyConnected = false; + GameClient()->OnDummyDisconnect(); +} + +int CClient::GetCurrentRaceTime() +{ + if(GameClient()->GetLastRaceTick() < 0) + return 0; + return (GameTick() - GameClient()->GetLastRaceTick()) / 50; +} + +int CClient::SendMsgExY(CMsgPacker *pMsg, int Flags, bool System, int NetClient) +{ + CNetChunk Packet; + + mem_zero(&Packet, sizeof(CNetChunk)); + + Packet.m_ClientID = 0; + Packet.m_pData = pMsg->Data(); + Packet.m_DataSize = pMsg->Size(); + + // HACK: modify the message id in the packet and store the system flag + if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1) + { + dbg_break(); + } + + *((unsigned char*)Packet.m_pData) <<= 1; + if(System) + *((unsigned char*)Packet.m_pData) |= 1; + + if(Flags&MSGFLAG_VITAL) + Packet.m_Flags |= NETSENDFLAG_VITAL; + if(Flags&MSGFLAG_FLUSH) + Packet.m_Flags |= NETSENDFLAG_FLUSH; + + m_NetClient[NetClient].Send(&Packet); + return 0; +} + +void CClient::GetServerInfo(CServerInfo *pServerInfo) +{ + mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); + + if(m_DemoPlayer.IsPlaying() && g_Config.m_ClDemoAssumeRace) + str_copy(pServerInfo->m_aGameType, "DDraceNetwork", 14); +} + +void CClient::ServerInfoRequest() +{ + mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo)); + m_CurrentServerInfoRequestTime = 0; +} + +int CClient::LoadData() +{ + m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE); + return 1; +} + +// --- + +void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem) +{ + CSnapshotItem *i; + dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); + i = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index); + pItem->m_DataSize = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemSize(Index); + pItem->m_Type = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemType(Index); + pItem->m_ID = i->ID(); + return (void *)i->Data(); +} + +void CClient::SnapInvalidateItem(int SnapID, int Index) +{ + CSnapshotItem *i; + dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); + i = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(Index); + if(i) + { + if((char *)i < (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap || (char *)i > (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap + m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_SnapSize) + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem"); + if((char *)i >= (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap && (char *)i < (char *)m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap + m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_SnapSize) + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem"); + i->m_TypeAndID = -1; + } +} + +void *CClient::SnapFindItem(int SnapID, int Type, int ID) +{ + // TODO: linear search. should be fixed. + int i; + + if(!m_aSnapshots[g_Config.m_ClDummy][SnapID]) + return 0x0; + + for(i = 0; i < m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap->NumItems(); i++) + { + CSnapshotItem *pItem = m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItem(i); + if(m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pAltSnap->GetItemType(i) == Type && pItem->ID() == ID) + return (void *)pItem->Data(); + } + return 0x0; +} + +int CClient::SnapNumItems(int SnapID) +{ + dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID"); + if(!m_aSnapshots[g_Config.m_ClDummy][SnapID]) + return 0; + return m_aSnapshots[g_Config.m_ClDummy][SnapID]->m_pSnap->NumItems(); +} + +void CClient::SnapSetStaticsize(int ItemType, int Size) +{ + m_SnapshotDelta.SetStaticsize(ItemType, Size); +} + + +void CClient::DebugRender() +{ + static NETSTATS Prev, Current; + static int64 LastSnap = 0; + static float FrameTimeAvg = 0; + char aBuffer[512]; + + if(!g_Config.m_Debug) + return; + + //m_pGraphics->BlendNormal(); + Graphics()->TextureSet(m_DebugFont); + Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight()); + Graphics()->QuadsBegin(); + + if(time_get()-LastSnap > time_freq()) + { + LastSnap = time_get(); + Prev = Current; + net_stats(&Current); + } + + /* + eth = 14 + ip = 20 + udp = 8 + total = 42 + */ + FrameTimeAvg = FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f; + str_format(aBuffer, sizeof(aBuffer), "ticks: %8d %8d gfxmem: %dk fps: %3d", + m_CurGameTick[g_Config.m_ClDummy], m_PredTick[g_Config.m_ClDummy], + Graphics()->MemoryUsage()/1024, + (int)(1.0f/FrameTimeAvg + 0.5f)); + Graphics()->QuadsText(2, 2, 16, aBuffer); + + + { + int SendPackets = (Current.sent_packets-Prev.sent_packets); + int SendBytes = (Current.sent_bytes-Prev.sent_bytes); + int SendTotal = SendBytes + SendPackets*42; + int RecvPackets = (Current.recv_packets-Prev.recv_packets); + int RecvBytes = (Current.recv_bytes-Prev.recv_bytes); + int RecvTotal = RecvBytes + RecvPackets*42; + + if(!SendPackets) SendPackets++; + if(!RecvPackets) RecvPackets++; + str_format(aBuffer, sizeof(aBuffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d", + SendPackets, SendBytes, SendPackets*42, SendTotal, (SendTotal*8)/1024, SendBytes/SendPackets, + RecvPackets, RecvBytes, RecvPackets*42, RecvTotal, (RecvTotal*8)/1024, RecvBytes/RecvPackets); + Graphics()->QuadsText(2, 14, 16, aBuffer); + } + + // render rates + { + int y = 0; + int i; + for(i = 0; i < 256; i++) + { + if(m_SnapshotDelta.GetDataRate(i)) + { + str_format(aBuffer, sizeof(aBuffer), "%4d %20s: %8d %8d %8d", i, GameClient()->GetItemName(i), m_SnapshotDelta.GetDataRate(i)/8, m_SnapshotDelta.GetDataUpdates(i), + (m_SnapshotDelta.GetDataRate(i)/m_SnapshotDelta.GetDataUpdates(i))/8); + Graphics()->QuadsText(2, 100+y*12, 16, aBuffer); + y++; + } + } + } + + str_format(aBuffer, sizeof(aBuffer), "pred: %d ms", GetPredictionTime()); + Graphics()->QuadsText(2, 70, 16, aBuffer); + Graphics()->QuadsEnd(); + + // render graphs + if(g_Config.m_DbgGraphs) + { + //Graphics()->MapScreen(0,0,400.0f,300.0f); + float w = Graphics()->ScreenWidth()/4.0f; + float h = Graphics()->ScreenHeight()/6.0f; + float sp = Graphics()->ScreenWidth()/100.0f; + float x = Graphics()->ScreenWidth()-w-sp; + + m_FpsGraph.ScaleMax(); + m_FpsGraph.ScaleMin(); + m_FpsGraph.Render(Graphics(), m_DebugFont, x, sp*5, w, h, "FPS"); + m_InputtimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp, w, h, "Prediction Margin"); + m_GametimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin"); + } +} + +void CClient::Restart() +{ + char aBuf[512]; + shell_execute(Storage()->GetBinaryPath(PLAT_CLIENT_EXEC, aBuf, sizeof aBuf)); + Quit(); +} + +void CClient::Quit() +{ + SetState(IClient::STATE_QUITING); +} + +const char *CClient::ErrorString() +{ + return m_NetClient[0].ErrorString(); +} + +void CClient::Render() +{ + if(g_Config.m_ClOverlayEntities) + { + ColorRGBA bg = color_cast(ColorHSLA(g_Config.m_ClBackgroundEntitiesColor)); + Graphics()->Clear(bg.r, bg.g, bg.b); + } + else + { + ColorRGBA bg = color_cast(ColorHSLA(g_Config.m_ClBackgroundColor)); + Graphics()->Clear(bg.r, bg.g, bg.b); + } + + GameClient()->OnRender(); + DebugRender(); + + if(State() == IClient::STATE_ONLINE && g_Config.m_ClAntiPingLimit) + { + int64 Now = time_get(); + g_Config.m_ClAntiPing = (m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq() > g_Config.m_ClAntiPingLimit; + } +} + +const char *CClient::LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc) +{ + static char s_aErrorMsg[128]; + + SetState(IClient::STATE_LOADING); + + if(!m_pMap->Load(pFilename)) + { + str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "map '%s' not found", pFilename); + return s_aErrorMsg; + } + + if(pWantedSha256 && m_pMap->Sha256() != *pWantedSha256) + { + char aWanted[SHA256_MAXSTRSIZE]; + char aGot[SHA256_MAXSTRSIZE]; + sha256_str(*pWantedSha256, aWanted, sizeof(aWanted)); + sha256_str(m_pMap->Sha256(), aGot, sizeof(aWanted)); + str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "map differs from the server. %s != %s", aGot, aWanted); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", s_aErrorMsg); + m_pMap->Unload(); + return s_aErrorMsg; + } + + // get the crc of the map + if(m_pMap->Crc() != WantedCrc) + { + str_format(s_aErrorMsg, sizeof(s_aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", s_aErrorMsg); + m_pMap->Unload(); + return s_aErrorMsg; + } + + // stop demo recording if we loaded a new map + for(int i = 0; i < RECORDER_MAX; i++) + DemoRecorder_Stop(i); + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "loaded map '%s'", pFilename); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + m_ReceivedSnapshots[g_Config.m_ClDummy] = 0; + + str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap)); + str_copy(m_aCurrentMapPath, pFilename, sizeof(m_aCurrentMapPath)); + + return 0; +} + +const char *CClient::LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc) +{ + const char *pError = 0; + char aBuf[512]; + char aWanted[256]; + char aWantedSha256[SHA256_MAXSTRSIZE]; + + aWanted[0] = 0; + + if(pWantedSha256) + { + sha256_str(*pWantedSha256, aWantedSha256, sizeof(aWantedSha256)); + str_format(aWanted, sizeof(aWanted), " sha256=%s", aWantedSha256); + } + + str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted%s crc=%08x", pMapName, aWanted, WantedCrc); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf); + SetState(IClient::STATE_LOADING); + + // try the normal maps folder + str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName); + pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc); + if(!pError) + return pError; + + // try the downloaded maps + if(pWantedSha256) + { + str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x_%s.map", pMapName, WantedCrc, aWantedSha256); + pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc); + if(!pError) + return pError; + } + + // try the downloaded maps folder without appending the sha256 + str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc); + pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc); + if(!pError) + return pError; + + // search for the map within subfolders + char aFilename[128]; + str_format(aFilename, sizeof(aFilename), "%s.map", pMapName); + if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf))) + pError = LoadMap(pMapName, aBuf, pWantedSha256, WantedCrc); + + return pError; +} + +int CClient::PlayerScoreNameComp(const void *a, const void *b) +{ + CServerInfo::CClient *p0 = (CServerInfo::CClient *)a; + CServerInfo::CClient *p1 = (CServerInfo::CClient *)b; + if(p0->m_Player && !p1->m_Player) + return -1; + if(!p0->m_Player && p1->m_Player) + return 1; + if(p0->m_Score > p1->m_Score) + return -1; + if(p0->m_Score < p1->m_Score) + return 1; + return str_comp_nocase(p0->m_aName, p1->m_aName); +} + +void CClient::ProcessConnlessPacket(CNetChunk *pPacket) +{ + //server count from master server + if(pPacket->m_DataSize == (int)sizeof(SERVERBROWSE_COUNT) + 2 && mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) + { + unsigned char *pP = (unsigned char*) pPacket->m_pData; + pP += sizeof(SERVERBROWSE_COUNT); + int ServerCount = ((*pP)<<8) | *(pP+1); + int ServerID = -1; + for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) + { + if(!m_pMasterServer->IsValid(i)) + continue; + NETADDR tmp = m_pMasterServer->GetAddr(i); + if(net_addr_comp(&pPacket->m_Address, &tmp) == 0) + { + ServerID = i; + break; + } + } + if(ServerCount > -1 && ServerID != -1) + { + m_pMasterServer->SetCount(ServerID, ServerCount); + if(g_Config.m_Debug) + dbg_msg("mastercount", "server %d got %d servers", ServerID, ServerCount); + } + } + // server list from master server + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && + mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) + { + // check for valid master server address + bool Valid = false; + for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; ++i) + { + if(m_pMasterServer->IsValid(i)) + { + NETADDR Addr = m_pMasterServer->GetAddr(i); + if(net_addr_comp(&pPacket->m_Address, &Addr) == 0) + { + Valid = true; + break; + } + } + } + if(!Valid) + return; + + int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); + int Num = Size/sizeof(CMastersrvAddr); + CMastersrvAddr *pAddrs = (CMastersrvAddr *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); + for(int i = 0; i < Num; i++) + { + NETADDR Addr; + + static unsigned char s_IPV4Mapping[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF }; + + // copy address + if(!mem_comp(s_IPV4Mapping, pAddrs[i].m_aIp, sizeof(s_IPV4Mapping))) + { + mem_zero(&Addr, sizeof(Addr)); + Addr.type = NETTYPE_IPV4; + Addr.ip[0] = pAddrs[i].m_aIp[12]; + Addr.ip[1] = pAddrs[i].m_aIp[13]; + Addr.ip[2] = pAddrs[i].m_aIp[14]; + Addr.ip[3] = pAddrs[i].m_aIp[15]; + } + else + { + Addr.type = NETTYPE_IPV6; + mem_copy(Addr.ip, pAddrs[i].m_aIp, sizeof(Addr.ip)); + } + Addr.port = (pAddrs[i].m_aPort[0]<<8) | pAddrs[i].m_aPort[1]; + + m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0); + } + } + + // server info + if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO)) + { + int Type = -1; + if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) + Type = SERVERINFO_VANILLA; + else if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO_64_LEGACY, sizeof(SERVERBROWSE_INFO_64_LEGACY)) == 0) + Type = SERVERINFO_64_LEGACY; + else if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO_EXTENDED, sizeof(SERVERBROWSE_INFO_EXTENDED)) == 0) + Type = SERVERINFO_EXTENDED; + else if(mem_comp(pPacket->m_pData, SERVERBROWSE_INFO_EXTENDED_MORE, sizeof(SERVERBROWSE_INFO_EXTENDED_MORE)) == 0) + Type = SERVERINFO_EXTENDED_MORE; + + if(Type != -1) + { + void *pData = (unsigned char *)pPacket->m_pData + sizeof(SERVERBROWSE_INFO); + int DataSize = pPacket->m_DataSize - sizeof(SERVERBROWSE_INFO); + ProcessServerInfo(Type, &pPacket->m_Address, pData, DataSize); + } + } +} + +static int SavedServerInfoType(int Type) +{ + if(Type == SERVERINFO_EXTENDED_MORE) + return SERVERINFO_EXTENDED; + + return Type; +} + +void CClient::ProcessServerInfo(int RawType, NETADDR *pFrom, const void *pData, int DataSize) +{ + CServerBrowser::CServerEntry *pEntry = m_ServerBrowser.Find(*pFrom); + + CServerInfo Info = {0}; + int SavedType = SavedServerInfoType(RawType); + if((SavedType == SERVERINFO_64_LEGACY || SavedType == SERVERINFO_EXTENDED) + && pEntry && pEntry->m_GotInfo && SavedType == pEntry->m_Info.m_Type) + { + Info = pEntry->m_Info; + } + + Info.m_Type = SavedType; + + net_addr_str(pFrom, Info.m_aAddress, sizeof(Info.m_aAddress), true); + + CUnpacker Up; + Up.Reset(pData, DataSize); + + #define GET_STRING(array) str_copy(array, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(array)) + #define GET_INT(integer) (integer) = str_toint(Up.GetString()) + + int Offset = 0; // Only used for SavedType == SERVERINFO_64_LEGACY + int Token; + int PacketNo = 0; // Only used if SavedType == SERVERINFO_EXTENDED + + GET_INT(Token); + if(RawType != SERVERINFO_EXTENDED_MORE) + { + GET_STRING(Info.m_aVersion); + GET_STRING(Info.m_aName); + GET_STRING(Info.m_aMap); + + if(SavedType == SERVERINFO_EXTENDED) + { + GET_INT(Info.m_MapCrc); + GET_INT(Info.m_MapSize); + } + + GET_STRING(Info.m_aGameType); + GET_INT(Info.m_Flags); + GET_INT(Info.m_NumPlayers); + GET_INT(Info.m_MaxPlayers); + GET_INT(Info.m_NumClients); + GET_INT(Info.m_MaxClients); + if(Info.m_aMap[0]) + Info.m_HasRank = m_ServerBrowser.HasRank(Info.m_aMap); + + // don't add invalid info to the server browser list + if(Info.m_NumClients < 0 || Info.m_MaxClients < 0 || + Info.m_NumPlayers < 0 || Info.m_MaxPlayers < 0 || + Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers > Info.m_MaxClients) + { + return; + } + + switch(SavedType) + { + case SERVERINFO_VANILLA: + if(Info.m_MaxPlayers > VANILLA_MAX_CLIENTS || + Info.m_MaxClients > VANILLA_MAX_CLIENTS) + { + return; + } + break; + case SERVERINFO_64_LEGACY: + if(Info.m_MaxPlayers > MAX_CLIENTS || + Info.m_MaxClients > MAX_CLIENTS) + { + return; + } + break; + case SERVERINFO_EXTENDED: + if(Info.m_NumPlayers > Info.m_NumClients) + return; + break; + default: + dbg_assert(false, "unknown serverinfo type"); + } + + if(SavedType == SERVERINFO_64_LEGACY) + Offset = Up.GetInt(); + + // Check for valid offset. + if(Offset < 0) + return; + + if(SavedType == SERVERINFO_EXTENDED) + PacketNo = 0; + } + else + { + GET_INT(PacketNo); + // 0 needs to be excluded because that's reserved for the main packet. + if(PacketNo <= 0 || PacketNo >= 64) + return; + } + + bool DuplicatedPacket = false; + if(SavedType == SERVERINFO_EXTENDED) + { + Up.GetString(); // extra info, reserved + + uint64 Flag = (uint64)1 << PacketNo; + DuplicatedPacket = Info.m_ReceivedPackets&Flag; + Info.m_ReceivedPackets |= Flag; + } + + bool IgnoreError = false; + for(int i = Offset; i < MAX_CLIENTS && Info.m_NumReceivedClients < MAX_CLIENTS && !Up.Error(); i++) + { + CServerInfo::CClient *pClient = &Info.m_aClients[Info.m_NumReceivedClients]; + GET_STRING(pClient->m_aName); + if(Up.Error()) + { + // Packet end, no problem unless it happens during one + // player info, so ignore the error. + IgnoreError = true; + break; + } + GET_STRING(pClient->m_aClan); + GET_INT(pClient->m_Country); + GET_INT(pClient->m_Score); + GET_INT(pClient->m_Player); + if(SavedType == SERVERINFO_EXTENDED) + { + Up.GetString(); // extra info, reserved + } + if(!Up.Error()) + { + if(SavedType == SERVERINFO_64_LEGACY) + { + uint64 Flag = (uint64)1 << i; + if(!(Info.m_ReceivedPackets&Flag)) + { + Info.m_ReceivedPackets |= Flag; + Info.m_NumReceivedClients++; + } + } + else + { + Info.m_NumReceivedClients++; + } + } + } + + if(!Up.Error() || IgnoreError) + { + qsort(Info.m_aClients, Info.m_NumReceivedClients, sizeof(*Info.m_aClients), PlayerScoreNameComp); + + if(!DuplicatedPacket && (!pEntry || !pEntry->m_GotInfo || SavedType >= pEntry->m_Info.m_Type)) + { + m_ServerBrowser.Set(*pFrom, IServerBrowser::SET_TOKEN, Token, &Info); + pEntry = m_ServerBrowser.Find(*pFrom); + + if(SavedType == SERVERINFO_VANILLA && Is64Player(&Info) && pEntry) + { + pEntry->m_Request64Legacy = true; + // Force a quick update. + m_ServerBrowser.RequestImpl64(pEntry->m_Addr, pEntry); + } + } + + // Player info is irrelevant for the client (while connected), + // it gets its info from elsewhere. + // + // SERVERINFO_EXTENDED_MORE doesn't carry any server + // information, so just skip it. + if(net_addr_comp(&m_ServerAddress, pFrom) == 0 && RawType != SERVERINFO_EXTENDED_MORE) + { + // Only accept server info that has a type that is + // newer or equal to something the server already sent + // us. + if(SavedType >= m_CurrentServerInfo.m_Type) + { + mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo)); + m_CurrentServerInfo.m_NetAddr = m_ServerAddress; + m_CurrentServerInfoRequestTime = -1; + } + } + } + + #undef GET_STRING + #undef GET_INT +} + +static void FormatMapDownloadFilename(const char *pName, SHA256_DIGEST *pSha256, int Crc, bool Temp, char *pBuffer, int BufferSize) +{ + char aSha256[SHA256_MAXSTRSIZE + 1]; + aSha256[0] = 0; + if(pSha256) + { + aSha256[0] = '_'; + sha256_str(*pSha256, aSha256 + 1, sizeof(aSha256) - 1); + } + + str_format(pBuffer, BufferSize, "%s_%08x%s.map%s", + pName, + Crc, + aSha256, + Temp ? ".tmp" : ""); +} + +void CClient::ProcessServerPacket(CNetChunk *pPacket) +{ + CUnpacker Unpacker; + Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); + CMsgPacker Packer(NETMSG_EX); + + // unpack msgid and system flag + int Msg; + bool Sys; + CUuid Uuid; + + int Result = UnpackMessageID(&Msg, &Sys, &Uuid, &Unpacker, &Packer); + if(Result == UNPACKMESSAGE_ERROR) + { + return; + } + else if(Result == UNPACKMESSAGE_ANSWER) + { + SendMsgEx(&Packer, MSGFLAG_VITAL, true); + } + + if(Sys) + { + // system message + if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_DETAILS) + { + const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES); + SHA256_DIGEST *pMapSha256 = (SHA256_DIGEST *)Unpacker.GetRaw(sizeof(*pMapSha256)); + int MapCrc = Unpacker.GetInt(); + + if(Unpacker.Error()) + { + return; + } + + m_MapDetailsPresent = true; + str_copy(m_aMapDetailsName, pMap, sizeof(m_aMapDetailsName)); + m_MapDetailsSha256 = *pMapSha256; + m_MapDetailsCrc = MapCrc; + } + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_CHANGE) + { + bool MapDetailsWerePresent = m_MapDetailsPresent; + m_MapDetailsPresent = false; + + const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES); + int MapCrc = Unpacker.GetInt(); + int MapSize = Unpacker.GetInt(); + const char *pError = 0; + + if(Unpacker.Error()) + return; + + if(m_DummyConnected) + DummyDisconnect(0); + + for(int i = 0; pMap[i]; i++) // protect the player from nasty map names + { + if(pMap[i] == '/' || pMap[i] == '\\') + pError = "strange character in map name"; + } + + if(MapSize < 0) + pError = "invalid map size"; + + if(pError) + DisconnectWithReason(pError); + else + { + SHA256_DIGEST *pMapSha256 = 0; + if(MapDetailsWerePresent && str_comp(m_aMapDetailsName, pMap) == 0 && m_MapDetailsCrc == MapCrc) + { + pMapSha256 = &m_MapDetailsSha256; + } + pError = LoadMapSearch(pMap, pMapSha256, MapCrc); + + if(!pError) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); + SendReady(); + } + else + { + char aFilename[256]; + FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, false, aFilename, sizeof(aFilename)); + + char aTempFilename[256]; + FormatMapDownloadFilename(pMap, pMapSha256, MapCrc, true, aTempFilename, sizeof(aTempFilename)); + + str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s", aTempFilename); + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilename); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf); + + m_MapdownloadChunk = 0; + str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName)); + + m_MapdownloadSha256Present = (bool)pMapSha256; + m_MapdownloadSha256 = pMapSha256 ? *pMapSha256 : SHA256_ZEROED; + m_MapdownloadCrc = MapCrc; + m_MapdownloadTotalsize = MapSize; + m_MapdownloadAmount = 0; + + ResetMapDownload(); + + if(pMapSha256 && g_Config.m_ClHttpMapDownload) + { + char aUrl[256]; + char aEscaped[256]; + EscapeUrl(aEscaped, sizeof(aEscaped), aFilename); + str_format(aUrl, sizeof(aUrl), "%s/%s", g_Config.m_ClDDNetMapDownloadUrl, aEscaped); + + m_pMapdownloadTask = std::make_shared(Storage(), aUrl, m_aMapdownloadFilename, IStorage::TYPE_SAVE, true); + Engine()->AddJob(m_pMapdownloadTask); + } + else + SendMapRequest(); + } + } + } + else if(Msg == NETMSG_MAP_DATA) + { + int Last = Unpacker.GetInt(); + int MapCRC = Unpacker.GetInt(); + int Chunk = Unpacker.GetInt(); + int Size = Unpacker.GetInt(); + const unsigned char *pData = Unpacker.GetRaw(Size); + + // check for errors + if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk || !m_MapdownloadFile) + return; + + io_write(m_MapdownloadFile, pData, Size); + + m_MapdownloadAmount += Size; + + if(Last) + { + if(m_MapdownloadFile) + { + io_close(m_MapdownloadFile); + m_MapdownloadFile = 0; + } + FinishMapDownload(); + } + else + { + // request new chunk + m_MapdownloadChunk++; + + CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA); + Msg.AddInt(m_MapdownloadChunk); + SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); + + if(g_Config.m_Debug) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf); + } + } + } + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_CON_READY) + { + GameClient()->OnConnected(); + } + else if(Msg == NETMSG_PING) + { + CMsgPacker Msg(NETMSG_PING_REPLY); + SendMsgEx(&Msg, 0); + } + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_ADD) + { + const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); + const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC); + const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC); + if(Unpacker.Error() == 0) + m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp); + } + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_REM) + { + const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); + if(Unpacker.Error() == 0) + m_pConsole->DeregisterTemp(pName); + } + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_STATUS) + { + int Result = Unpacker.GetInt(); + if(Unpacker.Error() == 0) + m_RconAuthed[g_Config.m_ClDummy] = Result; + int Old = m_UseTempRconCommands; + m_UseTempRconCommands = Unpacker.GetInt(); + if(Unpacker.Error() != 0) + m_UseTempRconCommands = 0; + if(Old != 0 && m_UseTempRconCommands == 0) + m_pConsole->DeregisterTempAll(); + } + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_LINE) + { + const char *pLine = Unpacker.GetString(); + if(Unpacker.Error() == 0) + GameClient()->OnRconLine(pLine); + } + else if(Msg == NETMSG_PING_REPLY) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq()); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client/network", aBuf); + } + else if(Msg == NETMSG_INPUTTIMING) + { + int InputPredTick = Unpacker.GetInt(); + int TimeLeft = Unpacker.GetInt(); + int64 Now = time_get(); + + // adjust our prediction time + int64 Target = 0; + for(int k = 0; k < 200; k++) + { + if(m_aInputs[g_Config.m_ClDummy][k].m_Tick == InputPredTick) + { + Target = m_aInputs[g_Config.m_ClDummy][k].m_PredictedTime + (Now - m_aInputs[g_Config.m_ClDummy][k].m_Time); + Target = Target - (int64)(((TimeLeft-PREDICTION_MARGIN)/1000.0f)*time_freq()); + break; + } + } + + if(Target) + m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1); + } + else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY) + { + int NumParts = 1; + int Part = 0; + int GameTick = Unpacker.GetInt(); + int DeltaTick = GameTick-Unpacker.GetInt(); + int PartSize = 0; + int Crc = 0; + int CompleteSize = 0; + const char *pData = 0; + + // only allow packets from the server we actually want + if(net_addr_comp(&pPacket->m_Address, &m_ServerAddress)) + return; + + // we are not allowed to process snapshot yet + if(State() < IClient::STATE_LOADING) + return; + + if(Msg == NETMSG_SNAP) + { + NumParts = Unpacker.GetInt(); + Part = Unpacker.GetInt(); + } + + if(Msg != NETMSG_SNAPEMPTY) + { + Crc = Unpacker.GetInt(); + PartSize = Unpacker.GetInt(); + } + + pData = (const char *)Unpacker.GetRaw(PartSize); + + if(Unpacker.Error() || NumParts < 1 || Part < 0 || PartSize < 0) + return; + + if(GameTick >= m_CurrentRecvTick[g_Config.m_ClDummy]) + { + if(GameTick != m_CurrentRecvTick[g_Config.m_ClDummy]) + { + m_SnapshotParts[g_Config.m_ClDummy] = 0; + m_CurrentRecvTick[g_Config.m_ClDummy] = GameTick; + } + + mem_copy((char*)m_aSnapshotIncomingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, clamp(PartSize, 0, (int)sizeof(m_aSnapshotIncomingData) - Part*MAX_SNAPSHOT_PACKSIZE)); + m_SnapshotParts[g_Config.m_ClDummy] |= 1<= 0) + { + int DeltashotSize = m_SnapshotStorage[g_Config.m_ClDummy].Get(DeltaTick, 0, &pDeltaShot, 0); + + if(DeltashotSize < 0) + { + // couldn't find the delta snapshots that the server used + // to compress this snapshot. force the server to resync + if(g_Config.m_Debug) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); + } + + // ack snapshot + // TODO: combine this with the input message + m_AckGameTick[g_Config.m_ClDummy] = -1; + return; + } + } + + // decompress snapshot + pDeltaData = m_SnapshotDelta.EmptyDelta(); + DeltaSize = sizeof(int)*3; + + if(CompleteSize) + { + int IntSize = CVariableInt::Decompress(m_aSnapshotIncomingData, CompleteSize, aTmpBuffer2, sizeof(aTmpBuffer2)); + + if(IntSize < 0) // failure during decompression, bail + return; + + pDeltaData = aTmpBuffer2; + DeltaSize = IntSize; + } + + // unpack delta + SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize); + if(SnapSize < 0) + { + dbg_msg("client", "delta unpack failed!=%d", SnapSize); + return; + } + + if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc) + { + if(g_Config.m_Debug) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", + m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); + } + + m_SnapCrcErrors++; + if(m_SnapCrcErrors > 10) + { + // to many errors, send reset + m_AckGameTick[g_Config.m_ClDummy] = -1; + SendInput(); + m_SnapCrcErrors = 0; + } + return; + } + else + { + if(m_SnapCrcErrors) + m_SnapCrcErrors--; + } + + // purge old snapshots + PurgeTick = DeltaTick; + if(m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick < PurgeTick) + PurgeTick = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick; + if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick < PurgeTick) + PurgeTick = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick; + m_SnapshotStorage[g_Config.m_ClDummy].PurgeUntil(PurgeTick); + + // add new + m_SnapshotStorage[g_Config.m_ClDummy].Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1); + + // for antiping: if the projectile netobjects from the server contains extra data, this is removed and the original content restored before recording demo + unsigned char aExtraInfoRemoved[CSnapshot::MAX_SIZE]; + mem_copy(aExtraInfoRemoved, pTmpBuffer3, SnapSize); + CServerInfo Info; + GetServerInfo(&Info); + if(IsDDNet(&Info)) + SnapshotRemoveExtraInfo(aExtraInfoRemoved); + + // add snapshot to demo + for(int i = 0; i < RECORDER_MAX; i++) + { + if(m_DemoRecorder[i].IsRecording()) + { + // write snapshot + m_DemoRecorder[i].RecordSnapshot(GameTick, aExtraInfoRemoved, SnapSize); + } + } + + // apply snapshot, cycle pointers + m_ReceivedSnapshots[g_Config.m_ClDummy]++; + + m_CurrentRecvTick[g_Config.m_ClDummy] = GameTick; + + // we got two snapshots until we see us self as connected + if(m_ReceivedSnapshots[g_Config.m_ClDummy] == 2) + { + // start at 200ms and work from there + m_PredictedTime.Init(GameTick*time_freq()/50); + m_PredictedTime.SetAdjustSpeed(1, 1000.0f); + m_GameTime[g_Config.m_ClDummy].Init((GameTick-1)*time_freq()/50); + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_SnapshotStorage[g_Config.m_ClDummy].m_pFirst; + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = m_SnapshotStorage[g_Config.m_ClDummy].m_pLast; + m_LocalStartTime = time_get(); + SetState(IClient::STATE_ONLINE); + DemoRecorder_HandleAutoStart(); + } + + // adjust game time + if(m_ReceivedSnapshots[g_Config.m_ClDummy] > 2) + { + int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get()); + int64 TickStart = GameTick*time_freq()/50; + int64 TimeLeft = (TickStart-Now)*1000 / time_freq(); + m_GameTime[g_Config.m_ClDummy].Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0); + } + + if(m_ReceivedSnapshots[g_Config.m_ClDummy] > 50 && !m_aTimeoutCodeSent[g_Config.m_ClDummy]) + { + if(IsDDNet(&m_CurrentServerInfo)) + { + m_aTimeoutCodeSent[g_Config.m_ClDummy] = true; + CNetMsg_Cl_Say Msg; + Msg.m_Team = 0; + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "/timeout %s", m_aTimeoutCodes[g_Config.m_ClDummy]); + Msg.m_pMessage = aBuf; + CMsgPacker Packer(Msg.MsgID()); + Msg.Pack(&Packer); + SendMsgExY(&Packer, MSGFLAG_VITAL, false, g_Config.m_ClDummy); + } + } + + // ack snapshot + m_AckGameTick[g_Config.m_ClDummy] = GameTick; + } + } + } + else if(Msg == NETMSG_RCONTYPE) + { + bool UsernameReq = Unpacker.GetInt() & 1; + GameClient()->OnRconType(UsernameReq); + } + else if(Msg == NETMSG_TIME_SCORE) + { + int NewTimeScore = Unpacker.GetInt(); + if (Unpacker.Error()) + return; + GameClient()->OnTimeScore(NewTimeScore, g_Config.m_ClDummy); + } + } + else + { + if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 || Msg == NETMSGTYPE_SV_EXTRAPROJECTILE) + { + // game message + for(int i = 0; i < RECORDER_MAX; i++) + if(m_DemoRecorder[i].IsRecording()) + m_DemoRecorder[i].RecordMessage(pPacket->m_pData, pPacket->m_DataSize); + + GameClient()->OnMessage(Msg, &Unpacker); + } + } +} + +void CClient::ProcessServerPacketDummy(CNetChunk *pPacket) +{ + CUnpacker Unpacker; + Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); + CMsgPacker Packer(NETMSG_EX); + + // unpack msgid and system flag + int Msg; + bool Sys; + CUuid Uuid; + + int Result = UnpackMessageID(&Msg, &Sys, &Uuid, &Unpacker, &Packer); + if(Result == UNPACKMESSAGE_ERROR) + { + return; + } + else if(Result == UNPACKMESSAGE_ANSWER) + { + SendMsgEx(&Packer, MSGFLAG_VITAL, true); + } + + if(Sys) + { + if(Msg == NETMSG_CON_READY) + { + m_DummyConnected = true; + g_Config.m_ClDummy = 1; + Rcon("crashmeplx"); + if(m_RconAuthed[0]) + RconAuth("", m_RconPassword); + } + else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY) + { + int NumParts = 1; + int Part = 0; + int GameTick = Unpacker.GetInt(); + int DeltaTick = GameTick-Unpacker.GetInt(); + int PartSize = 0; + int Crc = 0; + int CompleteSize = 0; + const char *pData = 0; + + // only allow packets from the server we actually want + if(net_addr_comp(&pPacket->m_Address, &m_ServerAddress)) + return; + + // we are not allowed to process snapshot yet + if(State() < IClient::STATE_LOADING) + return; + + if(Msg == NETMSG_SNAP) + { + NumParts = Unpacker.GetInt(); + Part = Unpacker.GetInt(); + } + + if(Msg != NETMSG_SNAPEMPTY) + { + Crc = Unpacker.GetInt(); + PartSize = Unpacker.GetInt(); + } + + pData = (const char *)Unpacker.GetRaw(PartSize); + + if(Unpacker.Error() || NumParts < 1 || Part < 0 || PartSize < 0) + return; + + if(GameTick >= m_CurrentRecvTick[!g_Config.m_ClDummy]) + { + if(GameTick != m_CurrentRecvTick[!g_Config.m_ClDummy]) + { + m_SnapshotParts[!g_Config.m_ClDummy] = 0; + m_CurrentRecvTick[!g_Config.m_ClDummy] = GameTick; + } + + mem_copy((char*)m_aSnapshotIncomingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, clamp(PartSize, 0, (int)sizeof(m_aSnapshotIncomingData) - Part*MAX_SNAPSHOT_PACKSIZE)); + m_SnapshotParts[!g_Config.m_ClDummy] |= 1<= 0) + { + int DeltashotSize = m_SnapshotStorage[!g_Config.m_ClDummy].Get(DeltaTick, 0, &pDeltaShot, 0); + + if(DeltashotSize < 0) + { + // couldn't find the delta snapshots that the server used + // to compress this snapshot. force the server to resync + if(g_Config.m_Debug) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot"); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); + } + + // ack snapshot + // TODO: combine this with the input message + m_AckGameTick[!g_Config.m_ClDummy] = -1; + return; + } + } + + // decompress snapshot + pDeltaData = m_SnapshotDelta.EmptyDelta(); + DeltaSize = sizeof(int)*3; + + if(CompleteSize) + { + int IntSize = CVariableInt::Decompress(m_aSnapshotIncomingData, CompleteSize, aTmpBuffer2, sizeof(aTmpBuffer2)); + + if(IntSize < 0) // failure during decompression, bail + return; + + pDeltaData = aTmpBuffer2; + DeltaSize = IntSize; + } + + // unpack delta + SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize); + if(SnapSize < 0) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "delta unpack failed!"); + return; + } + + if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc) + { + if(g_Config.m_Debug) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d", + m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf); + } + + m_SnapCrcErrors++; + if(m_SnapCrcErrors > 10) + { + // to many errors, send reset + m_AckGameTick[!g_Config.m_ClDummy] = -1; + SendInput(); + m_SnapCrcErrors = 0; + } + return; + } + else + { + if(m_SnapCrcErrors) + m_SnapCrcErrors--; + } + + // purge old snapshots + PurgeTick = DeltaTick; + if(m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] && m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick < PurgeTick) + PurgeTick = m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick; + if(m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick < PurgeTick) + PurgeTick = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick; + m_SnapshotStorage[!g_Config.m_ClDummy].PurgeUntil(PurgeTick); + + // add new + m_SnapshotStorage[!g_Config.m_ClDummy].Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1); + + // apply snapshot, cycle pointers + m_ReceivedSnapshots[!g_Config.m_ClDummy]++; + + m_CurrentRecvTick[!g_Config.m_ClDummy] = GameTick; + + // we got two snapshots until we see us self as connected + if(m_ReceivedSnapshots[!g_Config.m_ClDummy] == 2) + { + // start at 200ms and work from there + //m_PredictedTime[!g_Config.m_ClDummy].Init(GameTick*time_freq()/50); + //m_PredictedTime[!g_Config.m_ClDummy].SetAdjustSpeed(1, 1000.0f); + m_GameTime[!g_Config.m_ClDummy].Init((GameTick-1)*time_freq()/50); + m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] = m_SnapshotStorage[!g_Config.m_ClDummy].m_pFirst; + m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] = m_SnapshotStorage[!g_Config.m_ClDummy].m_pLast; + m_LocalStartTime = time_get(); + SetState(IClient::STATE_ONLINE); + } + + // adjust game time + if(m_ReceivedSnapshots[!g_Config.m_ClDummy] > 2) + { + int64 Now = m_GameTime[!g_Config.m_ClDummy].Get(time_get()); + int64 TickStart = GameTick*time_freq()/50; + int64 TimeLeft = (TickStart-Now)*1000 / time_freq(); + m_GameTime[!g_Config.m_ClDummy].Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0); + } + + // ack snapshot + m_AckGameTick[!g_Config.m_ClDummy] = GameTick; + } + } + } + else if(Msg == NETMSG_TIME_SCORE) + { + int NewTimeScore = Unpacker.GetInt(); + if (Unpacker.Error()) + return; + GameClient()->OnTimeScore(NewTimeScore, true); + } + } + else + { + GameClient()->OnMessage(Msg, &Unpacker, 1); + } +} + +void CClient::ResetMapDownload() +{ + if(m_pMapdownloadTask) + { + m_pMapdownloadTask->Abort(); + m_pMapdownloadTask = NULL; + } + m_MapdownloadFile = 0; + m_MapdownloadAmount = 0; +} + +void CClient::FinishMapDownload() +{ + const char *pError; + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map"); + + int Prev = m_MapdownloadTotalsize; + m_MapdownloadTotalsize = -1; + SHA256_DIGEST *pSha256 = m_MapdownloadSha256Present ? &m_MapdownloadSha256 : 0; + + char aTmp[256]; + char aMapFileTemp[256]; + char aMapFile[256]; + FormatMapDownloadFilename(m_aMapdownloadName, pSha256, m_MapdownloadCrc, true, aTmp, sizeof(aTmp)); + str_format(aMapFileTemp, sizeof(aMapFileTemp), "downloadedmaps/%s", aTmp); + FormatMapDownloadFilename(m_aMapdownloadName, pSha256, m_MapdownloadCrc, false, aTmp, sizeof(aTmp)); + str_format(aMapFile, sizeof(aMapFileTemp), "downloadedmaps/%s", aTmp); + + Storage()->RenameFile(aMapFileTemp, aMapFile, IStorage::TYPE_SAVE); + + // load map + pError = LoadMap(m_aMapdownloadName, aMapFile, pSha256, m_MapdownloadCrc); + if(!pError) + { + ResetMapDownload(); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); + SendReady(); + } + else if(m_pMapdownloadTask) // fallback + { + ResetMapDownload(); + m_MapdownloadTotalsize = Prev; + SendMapRequest(); + } + else + { + if(m_MapdownloadFile) + { + io_close(m_MapdownloadFile); + m_MapdownloadFile = 0; + } + ResetMapDownload(); + DisconnectWithReason(pError); + } +} + +void CClient::ResetDDNetInfo() +{ + if(m_pDDNetInfoTask) + { + m_pDDNetInfoTask->Abort(); + m_pDDNetInfoTask = NULL; + } +} + +void CClient::FinishDDNetInfo() +{ + ResetDDNetInfo(); + m_pStorage->RenameFile(DDNET_INFO_TMP, DDNET_INFO, IStorage::TYPE_SAVE); + LoadDDNetInfo(); +} + +typedef std::tuple Version; +static const Version InvalidVersion = std::make_tuple(-1, -1, -1); + +Version ToVersion(char *pStr) +{ + int version[3] = {0, 0, 0}; + const char *p = strtok(pStr, "."); + + for(int i = 0; i < 3 && p; ++i) + { + if(!str_isallnum(p)) + return InvalidVersion; + + version[i] = str_toint(p); + p = strtok(NULL, "."); + } + + if(p) + return InvalidVersion; + + return std::make_tuple(version[0], version[1], version[2]); +} + +void CClient::LoadDDNetInfo() +{ + const json_value *pDDNetInfo = m_ServerBrowser.LoadDDNetInfo(); + + if(!pDDNetInfo) + return; + + const json_value *pVersion = json_object_get(pDDNetInfo, "version"); + if(pVersion->type == json_string) + { + char aNewVersionStr[64]; + str_copy(aNewVersionStr, json_string_get(pVersion), sizeof(aNewVersionStr)); + char aCurVersionStr[64]; + str_copy(aCurVersionStr, GAME_RELEASE_VERSION, sizeof(aCurVersionStr)); + if(ToVersion(aNewVersionStr) > ToVersion(aCurVersionStr)) + { + str_copy(m_aVersionStr, json_string_get(pVersion), sizeof(m_aVersionStr)); + } + else + { + m_aVersionStr[0] = '0'; + m_aVersionStr[1] = '\0'; + } + } + + const json_value *pNews = json_object_get(pDDNetInfo, "news"); + if(pNews->type == json_string) + { + const char *pNewsString = json_string_get(pNews); + + if(m_aNews[0] && str_comp(m_aNews, pNewsString)) + g_Config.m_UiPage = CMenus::PAGE_NEWS; + + str_copy(m_aNews, pNewsString, sizeof(m_aNews)); + } +} + +void CClient::PumpNetwork() +{ + for(int i=0; i<3; i++) + { + m_NetClient[i].Update(); + } + + if(State() != IClient::STATE_DEMOPLAYBACK) + { + // check for errors + if(State() != IClient::STATE_OFFLINE && State() != IClient::STATE_QUITING && m_NetClient[0].State() == NETSTATE_OFFLINE) + { + SetState(IClient::STATE_OFFLINE); + Disconnect(); + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "offline error='%s'", m_NetClient[0].ErrorString()); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + } + + // + if(State() == IClient::STATE_CONNECTING && m_NetClient[0].State() == NETSTATE_ONLINE) + { + // we switched to online + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info"); + SetState(IClient::STATE_LOADING); + SendInfo(); + } + } + + // process packets + CNetChunk Packet; + for(int i=0; i < 3; i++) + { + while(m_NetClient[i].Recv(&Packet)) + { + if(Packet.m_ClientID == -1 || i > 1) + { + ProcessConnlessPacket(&Packet); + } + else if(i > 0 && i < 2) + { + if(g_Config.m_ClDummy) + ProcessServerPacket(&Packet); //self + else + ProcessServerPacketDummy(&Packet); //multiclient + } + else + { + if(g_Config.m_ClDummy) + ProcessServerPacketDummy(&Packet); //multiclient + else + ProcessServerPacket(&Packet); //self + } + } + } +} + +void CClient::OnDemoPlayerSnapshot(void *pData, int Size) +{ + // update ticks, they could have changed + const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); + CSnapshotStorage::CHolder *pTemp; + m_CurGameTick[g_Config.m_ClDummy] = pInfo->m_Info.m_CurrentTick; + m_PrevGameTick[g_Config.m_ClDummy] = pInfo->m_PreviousTick; + + // handle snapshots + pTemp = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pTemp; + + mem_copy(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pSnap, pData, Size); + mem_copy(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pAltSnap, pData, Size); + + GameClient()->OnNewSnapshot(); +} + +void CClient::OnDemoPlayerMessage(void *pData, int Size) +{ + CUnpacker Unpacker; + Unpacker.Reset(pData, Size); + + // unpack msgid and system flag + int Msg = Unpacker.GetInt(); + int Sys = Msg&1; + Msg >>= 1; + + if(Unpacker.Error()) + return; + + if(!Sys) + GameClient()->OnMessage(Msg, &Unpacker); +} +/* +const IDemoPlayer::CInfo *client_demoplayer_getinfo() +{ + static DEMOPLAYBACK_INFO ret; + const DEMOREC_PLAYBACKINFO *info = m_DemoPlayer.Info(); + ret.first_tick = info->first_tick; + ret.last_tick = info->last_tick; + ret.current_tick = info->current_tick; + ret.paused = info->paused; + ret.speed = info->speed; + return &ret; +}*/ + +/* +void DemoPlayer()->SetPos(float percent) +{ + demorec_playback_set(percent); +} + +void DemoPlayer()->SetSpeed(float speed) +{ + demorec_playback_setspeed(speed); +} + +void DemoPlayer()->SetPause(int paused) +{ + if(paused) + demorec_playback_pause(); + else + demorec_playback_unpause(); +}*/ + +void CClient::Update() +{ + if(State() == IClient::STATE_DEMOPLAYBACK) + { + m_DemoPlayer.Update(); + if(m_DemoPlayer.IsPlaying()) + { + // update timers + const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); + m_CurGameTick[g_Config.m_ClDummy] = pInfo->m_Info.m_CurrentTick; + m_PrevGameTick[g_Config.m_ClDummy] = pInfo->m_PreviousTick; + m_GameIntraTick[g_Config.m_ClDummy] = pInfo->m_IntraTick; + m_GameTickTime[g_Config.m_ClDummy] = pInfo->m_TickTime; + } + else + { + // disconnect on error + Disconnect(); + } + } + else if(State() == IClient::STATE_ONLINE && m_ReceivedSnapshots[g_Config.m_ClDummy] >= 3) + { + if(m_ReceivedSnapshots[!g_Config.m_ClDummy] >= 3) + { + // switch dummy snapshot + int64 Now = m_GameTime[!g_Config.m_ClDummy].Get(time_get()); + while(1) + { + CSnapshotStorage::CHolder *pCur = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]; + int64 TickStart = (pCur->m_Tick)*time_freq()/50; + + if(TickStart < Now) + { + CSnapshotStorage::CHolder *pNext = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_pNext; + if(pNext) + { + m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]; + m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT] = pNext; + + // set ticks + m_CurGameTick[!g_Config.m_ClDummy] = m_aSnapshots[!g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick; + m_PrevGameTick[!g_Config.m_ClDummy] = m_aSnapshots[!g_Config.m_ClDummy][SNAP_PREV]->m_Tick; + } + else + break; + } + else + break; + } + } + + // switch snapshot + int Repredict = 0; + int64 Freq = time_freq(); + int64 Now = m_GameTime[g_Config.m_ClDummy].Get(time_get()); + int64 PredNow = m_PredictedTime.Get(time_get()); + + while(1) + { + CSnapshotStorage::CHolder *pCur = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; + int64 TickStart = (pCur->m_Tick)*time_freq()/50; + + if(TickStart < Now) + { + CSnapshotStorage::CHolder *pNext = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pNext; + if(pNext) + { + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = pNext; + + // set ticks + m_CurGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick; + m_PrevGameTick[g_Config.m_ClDummy] = m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick; + + if(m_LastDummy2 == (bool)g_Config.m_ClDummy && m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]) + { + GameClient()->OnNewSnapshot(); + Repredict = 1; + } + } + else + break; + } + else + break; + } + + if(m_LastDummy2 != (bool)g_Config.m_ClDummy) + { + m_LastDummy2 = g_Config.m_ClDummy; + } + + if(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] && m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]) + { + int64 CurtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick)*time_freq()/50; + int64 PrevtickStart = (m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick)*time_freq()/50; + int PrevPredTick = (int)(PredNow*50/time_freq()); + int NewPredTick = PrevPredTick+1; + + m_GameIntraTick[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart); + m_GameTickTime[g_Config.m_ClDummy] = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED); + + CurtickStart = NewPredTick*time_freq()/50; + PrevtickStart = PrevPredTick*time_freq()/50; + m_PredIntraTick[g_Config.m_ClDummy] = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart); + + if(NewPredTick < m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick+SERVER_TICK_SPEED) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!"); + m_PredictedTime.Init(m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick*time_freq()/50); + } + + if(NewPredTick > m_PredTick[g_Config.m_ClDummy]) + { + m_PredTick[g_Config.m_ClDummy] = NewPredTick; + Repredict = 1; + + // send input + SendInput(); + } + } + + // only do sane predictions + if(Repredict) + { + if(m_PredTick[g_Config.m_ClDummy] > m_CurGameTick[g_Config.m_ClDummy] && m_PredTick[g_Config.m_ClDummy] < m_CurGameTick[g_Config.m_ClDummy]+50) + GameClient()->OnPredict(); + } + + // fetch server info if we don't have it + if(State() >= IClient::STATE_LOADING && + m_CurrentServerInfoRequestTime >= 0 && + time_get() > m_CurrentServerInfoRequestTime) + { + m_ServerBrowser.RequestCurrentServer(m_ServerAddress); + m_CurrentServerInfoRequestTime = time_get()+time_freq()*2; + } + } + + // STRESS TEST: join the server again +#ifdef CONF_DEBUG + if(g_Config.m_DbgStress) + { + static int64 ActionTaken = 0; + int64 Now = time_get(); + if(State() == IClient::STATE_OFFLINE) + { + if(Now > ActionTaken+time_freq()*2) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "reconnecting!"); + Connect(g_Config.m_DbgStressServer); + ActionTaken = Now; + } + } + else + { + if(Now > ActionTaken+time_freq()*(10+g_Config.m_DbgStress)) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "disconnecting!"); + Disconnect(); + ActionTaken = Now; + } + } + } +#endif + + // pump the network + PumpNetwork(); + + if(m_pMapdownloadTask) + { + if(m_pMapdownloadTask->State() == HTTP_DONE) + FinishMapDownload(); + else if(m_pMapdownloadTask->State() == HTTP_ERROR) + { + dbg_msg("webdl", "http failed, falling back to gameserver"); + ResetMapDownload(); + SendMapRequest(); + } + else if(m_pMapdownloadTask->State() == HTTP_ABORTED) + { + m_pMapdownloadTask = NULL; + } + } + + if(m_pDDNetInfoTask) + { + if(m_pDDNetInfoTask->State() == HTTP_DONE) + FinishDDNetInfo(); + else if(m_pDDNetInfoTask->State() == HTTP_ERROR) + { + dbg_msg("ddnet-info", "download failed"); + ResetDDNetInfo(); + } + else if(m_pDDNetInfoTask->State() == HTTP_ABORTED) + { + m_pDDNetInfoTask = NULL; + } + } + + // update the maser server registry + MasterServer()->Update(); + + // update the server browser + m_ServerBrowser.Update(m_ResortServerBrowser); + m_ResortServerBrowser = false; + + // update gameclient + if(!m_EditorActive) + GameClient()->OnUpdate(); + + if(m_ReconnectTime > 0 && time_get() > m_ReconnectTime) + { + Connect(m_aServerAddressStr); + m_ReconnectTime = 0; + } +} + +void CClient::RegisterInterfaces() +{ + Kernel()->RegisterInterface(static_cast(&m_DemoRecorder[RECORDER_MANUAL]), false); + Kernel()->RegisterInterface(static_cast(&m_DemoPlayer), false); + Kernel()->RegisterInterface(static_cast(&m_GhostRecorder), false); + Kernel()->RegisterInterface(static_cast(&m_GhostLoader), false); + Kernel()->RegisterInterface(static_cast(&m_ServerBrowser), false); +#if defined(CONF_AUTOUPDATE) + Kernel()->RegisterInterface(static_cast(&m_Updater), false); +#endif + Kernel()->RegisterInterface(static_cast(&m_Friends), false); + Kernel()->ReregisterInterface(static_cast(&m_Foes)); +} + +void CClient::InitInterfaces() +{ + // fetch interfaces + m_pEngine = Kernel()->RequestInterface(); + m_pEditor = Kernel()->RequestInterface(); + //m_pGraphics = Kernel()->RequestInterface(); + m_pSound = Kernel()->RequestInterface(); + m_pGameClient = Kernel()->RequestInterface(); + m_pInput = Kernel()->RequestInterface(); + m_pMap = Kernel()->RequestInterface(); + m_pMasterServer = Kernel()->RequestInterface(); +#if defined(CONF_AUTOUPDATE) + m_pUpdater = Kernel()->RequestInterface(); +#endif + m_pStorage = Kernel()->RequestInterface(); + + m_DemoEditor.Init(m_pGameClient->NetVersion(), &m_SnapshotDelta, m_pConsole, m_pStorage); + + m_ServerBrowser.SetBaseInfo(&m_NetClient[2], m_pGameClient->NetVersion()); + + HttpInit(m_pStorage); + +#if defined(CONF_AUTOUPDATE) + m_Updater.Init(); +#endif + + m_Friends.Init(); + m_Foes.Init(true); + + m_GhostRecorder.Init(); + m_GhostLoader.Init(); +} + +void CClient::Run() +{ + m_LocalStartTime = time_get(); + + if(m_GenerateTimeoutSeed) + { + GenerateTimeoutSeed(); + } + + unsigned int Seed; + secure_random_fill(&Seed, sizeof(Seed)); + srand(Seed); + + if(g_Config.m_Debug) + { + g_UuidManager.DebugDump(); + } + + // init SDL + { + if(SDL_Init(0) < 0) + { + dbg_msg("client", "unable to init SDL base: %s", SDL_GetError()); + return; + } + + atexit(SDL_Quit); // ignore_convention + } + + // init graphics + { + m_pGraphics = CreateEngineGraphicsThreaded(); + + bool RegisterFail = false; + RegisterFail = RegisterFail || !Kernel()->RegisterInterface(m_pGraphics); // IEngineGraphics + RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast(m_pGraphics), false); + + if(RegisterFail || m_pGraphics->Init() != 0) + { + dbg_msg("client", "couldn't init graphics"); + return; + } + } + + // init sound, allowed to fail + m_SoundInitFailed = Sound()->Init() != 0; + + // open socket + { + NETADDR BindAddr; + if(g_Config.m_Bindaddr[0] && net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) == 0) + { + // got bindaddr + BindAddr.type = NETTYPE_ALL; + } + else + { + mem_zero(&BindAddr, sizeof(BindAddr)); + BindAddr.type = NETTYPE_ALL; + } + for(int i = 0; i < 3; i++) + { + do + { + BindAddr.port = (secure_rand() % 64511) + 1024; + } + while(!m_NetClient[i].Open(BindAddr, 0)); + } + } + + // init font rendering + Kernel()->RequestInterface()->Init(); + + // init the input + Input()->Init(); + + // start refreshing addresses while we load + MasterServer()->RefreshAddresses(m_NetClient[0].NetType()); + + // init the editor + m_pEditor->Init(); + + // load and save a map to fix it + /*if(m_pEditor->Load(arg, IStorage::TYPE_ALL)) + m_pEditor->Save(arg); + return;*/ + + // load data + if(!LoadData()) + return; + + GameClient()->OnInit(); + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion()); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf); + + // connect to the server if wanted + /* + if(config.cl_connect[0] != 0) + Connect(config.cl_connect); + config.cl_connect[0] = 0; + */ + + // + m_FpsGraph.Init(0.0f, 200.0f); + + // never start with the editor + g_Config.m_ClEditor = 0; + + // process pending commands + m_pConsole->StoreCommands(false); + +#if defined(CONF_FAMILY_UNIX) + m_Fifo.Init(m_pConsole, g_Config.m_ClInputFifo, CFGFLAG_CLIENT); +#endif + + // loads the existing ddnet info file if it exists + LoadDDNetInfo(); + // but still request the new one from server + if(g_Config.m_ClShowWelcome) + g_Config.m_ClShowWelcome = 0; + else + RequestDDNetInfo(); + + bool LastD = false; + bool LastQ = false; + bool LastE = false; + bool LastG = false; + + int64 LastTime = time_get_microseconds(); + int64 LastRenderTime = time_get(); + + while(1) + { + set_new_tick(); + + // handle pending connects + if(m_aCmdConnect[0]) + { + str_copy(g_Config.m_UiServerAddress, m_aCmdConnect, sizeof(g_Config.m_UiServerAddress)); + Connect(m_aCmdConnect); + m_aCmdConnect[0] = 0; + } + + // handle pending demo play + if(m_aCmdPlayDemo[0]) + { + const char *pError = DemoPlayer_Play(m_aCmdPlayDemo, IStorage::TYPE_ABSOLUTE); + if(pError) + dbg_msg("demo_player", "playing passed demo file '%s' failed: %s", m_aCmdPlayDemo, pError); + m_aCmdPlayDemo[0] = 0; + } + + // progress on dummy connect if security token handshake skipped/passed + if(m_DummySendConnInfo && !m_NetClient[1].SecurityTokenUnknown()) + { + m_DummySendConnInfo = false; + + // send client info + CMsgPacker MsgInfo(NETMSG_INFO); + MsgInfo.AddString(GameClient()->NetVersion(), 128); + MsgInfo.AddString(m_Password, 128); + SendMsgExY(&MsgInfo, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1); + + // update netclient + m_NetClient[1].Update(); + + // send ready + CMsgPacker MsgReady(NETMSG_READY); + SendMsgExY(&MsgReady, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1); + + // startinfo + GameClient()->SendDummyInfo(true); + + // send enter game an finish the connection + CMsgPacker MsgEnter(NETMSG_ENTERGAME); + SendMsgExY(&MsgEnter, MSGFLAG_VITAL|MSGFLAG_FLUSH, true, 1); + } + + // update input + if(Input()->Update()) + break; // SDL_QUIT +#if defined(CONF_AUTOUPDATE) + Updater()->Update(); +#endif + + // update sound + Sound()->Update(); + + // panic quit button + if(CtrlShiftKey(KEY_Q, LastQ)) + { + Quit(); + break; + } + + if(CtrlShiftKey(KEY_D, LastD)) + g_Config.m_Debug ^= 1; + + if(CtrlShiftKey(KEY_G, LastG)) + g_Config.m_DbgGraphs ^= 1; + + if(CtrlShiftKey(KEY_E, LastE)) + { + g_Config.m_ClEditor = g_Config.m_ClEditor^1; + Input()->MouseModeRelative(); + Input()->SetIMEState(true); + } + + // render + { + if(g_Config.m_ClEditor) + { + if(!m_EditorActive) + { + Input()->MouseModeRelative(); + GameClient()->OnActivateEditor(); + m_pEditor->ResetMentions(); + m_EditorActive = true; + } + } + else if(m_EditorActive) + m_EditorActive = false; + + Update(); + int64 Now = time_get(); + + if((g_Config.m_GfxBackgroundRender || m_pGraphics->WindowOpen()) + && (!g_Config.m_GfxAsyncRenderOld || m_pGraphics->IsIdle()) + && (!g_Config.m_GfxRefreshRate || (time_freq() / (int64)g_Config.m_GfxRefreshRate) <= Now - LastRenderTime)) + { + m_RenderFrames++; + + // update frametime + m_RenderFrameTime = (Now - m_LastRenderTime) / (float)time_freq(); + if(m_RenderFrameTime < m_RenderFrameTimeLow) + m_RenderFrameTimeLow = m_RenderFrameTime; + if(m_RenderFrameTime > m_RenderFrameTimeHigh) + m_RenderFrameTimeHigh = m_RenderFrameTime; + m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1); + + m_FrameTimeAvg = m_FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f; + + // keep the overflow time - it's used to make sure the gfx refreshrate is reached + int64 AdditionalTime = g_Config.m_GfxRefreshRate ? ((Now - LastRenderTime) - (time_freq() / (int64)g_Config.m_GfxRefreshRate)) : 0; + // if the value is over a second time loose, reset the additional time (drop the frames, that are lost already) + if(AdditionalTime > time_freq()) + AdditionalTime = time_freq(); + LastRenderTime = Now - AdditionalTime; + m_LastRenderTime = Now; + +#ifdef CONF_DEBUG + if(g_Config.m_DbgStress) + { + if((m_RenderFrames%10) == 0) + { + if(!m_EditorActive) + Render(); + else + { + m_pEditor->UpdateAndRender(); + DebugRender(); + } + m_pGraphics->Swap(); + } + } + else +#endif + { + if(!m_EditorActive) + Render(); + else + { + m_pEditor->UpdateAndRender(); + DebugRender(); + } + m_pGraphics->Swap(); + } + + Input()->NextFrame(); + } + + if(Input()->VideoRestartNeeded()) + { + m_pGraphics->Init(); + LoadData(); + GameClient()->OnInit(); + } + } + + AutoScreenshot_Cleanup(); + AutoStatScreenshot_Cleanup(); + AutoCSV_Cleanup(); + + // check conditions + if(State() == IClient::STATE_QUITING) + break; + +#if defined(CONF_FAMILY_UNIX) + m_Fifo.Update(); +#endif + + // beNice + int64 Now = time_get_microseconds(); + int64 SleepTimeInMicroSeconds = 0; + bool Slept = false; + if( +#ifdef CONF_DEBUG + g_Config.m_DbgStress || +#endif + (g_Config.m_ClRefreshRateInactive && !m_pGraphics->WindowActive())) + { + SleepTimeInMicroSeconds = ((int64)1000000 / (int64)g_Config.m_ClRefreshRateInactive) - (Now - LastTime); + if(SleepTimeInMicroSeconds / (int64)1000 > (int64)0) + thread_sleep(SleepTimeInMicroSeconds); + Slept = true; + } + else if(g_Config.m_ClRefreshRate) + { + SleepTimeInMicroSeconds = ((int64)1000000 / (int64)g_Config.m_ClRefreshRate) - (Now - LastTime); + if(SleepTimeInMicroSeconds > (int64)0) + net_socket_read_wait(m_NetClient[0].m_Socket, SleepTimeInMicroSeconds); + Slept = true; + } + if(Slept) + { + // if the diff gets too small it shouldn't get even smaller (drop the updates, that could not be handled) + if(SleepTimeInMicroSeconds < (int64)-1000000) + SleepTimeInMicroSeconds = (int64)-1000000; + // don't go higher than the game ticks speed, because the network is waking up the client with the server's snapshots anyway + else if(SleepTimeInMicroSeconds > (int64)1000000 / m_GameTickSpeed) + SleepTimeInMicroSeconds = (int64)1000000 / m_GameTickSpeed; + // the time diff between the time that was used actually used and the time the thread should sleep/wait + // will be calculated in the sleep time of the next update tick by faking the time it should have slept/wait. + // so two cases (and the case it slept exactly the time it should): + // - the thread slept/waited too long, then it adjust the time to sleep/wait less in the next update tick + // - the thread slept/waited too less, then it adjust the time to sleep/wait more in the next update tick + LastTime = Now + SleepTimeInMicroSeconds; + } + else + LastTime = Now; + + if(g_Config.m_DbgHitch) + { + thread_sleep(g_Config.m_DbgHitch*1000); + g_Config.m_DbgHitch = 0; + } + + // update local time + m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq(); + } + +#if defined(CONF_FAMILY_UNIX) + m_Fifo.Shutdown(); +#endif + + GameClient()->OnShutdown(); + Disconnect(); + + delete m_pEditor; + m_pGraphics->Shutdown(); + + // shutdown SDL + { + SDL_Quit(); + } +} + +bool CClient::CtrlShiftKey(int Key, bool &Last) +{ + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyIsPressed(KEY_LSHIFT) && !Last && Input()->KeyIsPressed(Key)) + { + Last = true; + return true; + } + else if(Last && !Input()->KeyIsPressed(Key)) + Last = false; + + return false; +} + +void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + str_copy(pSelf->m_aCmdConnect, pResult->GetString(0), sizeof(pSelf->m_aCmdConnect)); +} + +void CClient::Con_Disconnect(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->Disconnect(); +} + +void CClient::Con_DummyConnect(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->DummyConnect(); +} + +void CClient::Con_DummyDisconnect(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->DummyDisconnect(0); +} + +void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->Quit(); +} + +void CClient::Con_Minimize(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->Graphics()->Minimize(); +} + +void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + + CMsgPacker Msg(NETMSG_PING); + pSelf->SendMsgEx(&Msg, 0); + pSelf->m_PingStartTime = time_get(); +} + +void CClient::AutoScreenshot_Start() +{ + if(g_Config.m_ClAutoScreenshot) + { + Graphics()->TakeScreenshot("auto/autoscreen"); + m_AutoScreenshotRecycle = true; + } +} + +void CClient::AutoStatScreenshot_Start() +{ + if(g_Config.m_ClAutoStatboardScreenshot) + { + Graphics()->TakeScreenshot("auto/stats/autoscreen"); + m_AutoStatScreenshotRecycle = true; + } +} + +void CClient::AutoScreenshot_Cleanup() +{ + if(m_AutoScreenshotRecycle) + { + if(g_Config.m_ClAutoScreenshotMax) + { + // clean up auto taken screens + CFileCollection AutoScreens; + AutoScreens.Init(Storage(), "screenshots/auto", "autoscreen", ".png", g_Config.m_ClAutoScreenshotMax); + } + m_AutoScreenshotRecycle = false; + } +} + +void CClient::AutoStatScreenshot_Cleanup() +{ + if(m_AutoStatScreenshotRecycle) + { + if(g_Config.m_ClAutoStatboardScreenshotMax) + { + // clean up auto taken screens + CFileCollection AutoScreens; + AutoScreens.Init(Storage(), "screenshots/auto/stats", "autoscreen", ".png", g_Config.m_ClAutoStatboardScreenshotMax); + } + m_AutoStatScreenshotRecycle = false; + } +} + +void CClient::AutoCSV_Start() +{ + if(g_Config.m_ClAutoCSV) + m_AutoCSVRecycle = true; +} + +void CClient::AutoCSV_Cleanup() +{ + if(m_AutoCSVRecycle) + { + if(g_Config.m_ClAutoCSVMax) + { + // clean up auto csvs + CFileCollection AutoRecord; + AutoRecord.Init(Storage(), "record/csv", "autorecord", ".csv", g_Config.m_ClAutoCSVMax); + } + m_AutoCSVRecycle = false; + } +} + +void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->Graphics()->TakeScreenshot(0); +} + +void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->Rcon(pResult->GetString(0)); +} + +void CClient::Con_RconAuth(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->RconAuth("", pResult->GetString(0)); +} + +void CClient::Con_RconLogin(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->RconAuth(pResult->GetString(0), pResult->GetString(1)); +} + +void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + NETADDR Addr; + if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0) + pSelf->m_ServerBrowser.AddFavorite(Addr); +} + +void CClient::Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + NETADDR Addr; + if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0) + pSelf->m_ServerBrowser.RemoveFavorite(Addr); +} + +void CClient::DemoSliceBegin() +{ + const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); + g_Config.m_ClDemoSliceBegin = pInfo->m_Info.m_CurrentTick; +} + +void CClient::DemoSliceEnd() +{ + const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info(); + g_Config.m_ClDemoSliceEnd = pInfo->m_Info.m_CurrentTick; +} + +void CClient::Con_DemoSliceBegin(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->DemoSliceBegin(); +} + +void CClient::Con_DemoSliceEnd(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->DemoSliceEnd(); +} + +void CClient::Con_SaveReplay(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->SaveReplay(); +} + +void CClient::SaveReplay() +{ + if (!g_Config.m_ClRaceReplays) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", "Feature is disabled. Please enabled it via the configuration"); + } else { + if(!DemoRecorder(RECORDER_REPLAYS)->IsRecording()) + { + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", "Error: demorecorder isn't recording. Try to rejoin to fix that."); + } + else + { + //DemoRecorder_HandleAutoStart(); + //DemoRecorder_Stop(RECORDER_REPLAYS); + replayCounter++; + + char aFilename[256]; + + char aDate[64]; + str_timestamp(aDate, sizeof(aDate)); + + str_format(aFilename, sizeof(aFilename), "demos/replays/%s_%s (replay).demo", m_aCurrentMap, aDate); + char *pSrc = (&m_DemoRecorder[RECORDER_REPLAYS])->GetCurrentFilename(); + + + // Slice the demo to get only the last 30 seconds + m_DemoEditor.Slice(pSrc, aFilename, GameTick() - g_Config.m_ClReplayLength * GameTickSpeed(), GameTick(), NULL, 0); + + Storage()->RemoveFile(pSrc, IStorage::TYPE_SAVE); + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "Successfully saved the replay to %s !", aFilename); + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", aBuf); + + //DemoRecorder_HandleAutoStart(); + } + } +} + +void CClient::DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser) +{ + if(m_DemoPlayer.IsPlaying()) + { + const char *pDemoFileName = m_DemoPlayer.GetDemoFileName(); + m_DemoEditor.Slice(pDemoFileName, pDstPath, g_Config.m_ClDemoSliceBegin, g_Config.m_ClDemoSliceEnd, pfnFilter, pUser); + } +} + +const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType) +{ + int Crc; + const char *pError; + Disconnect(); + m_NetClient[0].ResetErrorString(); + + // try to start playback + m_DemoPlayer.SetListener(this); + + if(m_DemoPlayer.Load(Storage(), m_pConsole, pFilename, StorageType)) + return "error loading demo"; + + // load map + Crc = (m_DemoPlayer.Info()->m_Header.m_aMapCrc[0]<<24)| + (m_DemoPlayer.Info()->m_Header.m_aMapCrc[1]<<16)| + (m_DemoPlayer.Info()->m_Header.m_aMapCrc[2]<<8)| + (m_DemoPlayer.Info()->m_Header.m_aMapCrc[3]); + pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, 0, Crc); + if(pError) + { + DisconnectWithReason(pError); + return pError; + } + + GameClient()->OnConnected(); + + // setup buffers + mem_zero(m_aDemorecSnapshotData, sizeof(m_aDemorecSnapshotData)); + + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT] = &m_aDemorecSnapshotHolders[SNAP_CURRENT]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV] = &m_aDemorecSnapshotHolders[SNAP_PREV]; + + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][0]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][1]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_SnapSize = 0; + m_aSnapshots[g_Config.m_ClDummy][SNAP_CURRENT]->m_Tick = -1; + + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][0]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][1]; + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_SnapSize = 0; + m_aSnapshots[g_Config.m_ClDummy][SNAP_PREV]->m_Tick = -1; + + // enter demo playback state + SetState(IClient::STATE_DEMOPLAYBACK); + + m_DemoPlayer.Play(); + GameClient()->OnEnterGame(); + + return 0; +} + +void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL); +} + +void CClient::Con_DemoPlay(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + if(pSelf->m_DemoPlayer.IsPlaying()) + { + if(pSelf->m_DemoPlayer.BaseInfo()->m_Paused) + { + pSelf->m_DemoPlayer.Unpause(); + } + else + { + pSelf->m_DemoPlayer.Pause(); + } + } +} + +void CClient::Con_DemoSpeed(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->m_DemoPlayer.SetSpeed(pResult->GetFloat(0)); +} + +void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder) +{ + if(State() != IClient::STATE_ONLINE) + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); + else + { + char aFilename[128]; + if(WithTimestamp) + { + char aDate[20]; + str_timestamp(aDate, sizeof(aDate)); + str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", pFilename, aDate); + } + else + str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename); + m_DemoRecorder[Recorder].Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Sha256(), m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File()); + } +} + +void CClient::DemoRecorder_HandleAutoStart() +{ + if(g_Config.m_ClAutoDemoRecord) + { + DemoRecorder_Stop(RECORDER_AUTO); + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "auto/%s", m_aCurrentMap); + DemoRecorder_Start(aBuf, true, RECORDER_AUTO); + if(g_Config.m_ClAutoDemoMax) + { + // clean up auto recorded demos + CFileCollection AutoDemos; + AutoDemos.Init(Storage(), "demos/auto", "" /* empty for wild card */, ".demo", g_Config.m_ClAutoDemoMax); + } + } + if(g_Config.m_ClRaceReplays) + { + DemoRecorder_Stop(RECORDER_REPLAYS); + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "replays/replay-%s", m_aCurrentMap); + DemoRecorder_Start(aBuf, true, RECORDER_REPLAYS); + } + + //TODO +} + +void CClient::DemoRecorder_Stop(int Recorder) +{ + m_DemoRecorder[Recorder].Stop(); +} + +void CClient::DemoRecorder_AddDemoMarker(int Recorder) +{ + m_DemoRecorder[Recorder].AddDemoMarker(); +} + +class IDemoRecorder *CClient::DemoRecorder(int Recorder) +{ + return &m_DemoRecorder[Recorder]; +} + +void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + if(pResult->NumArguments()) + pSelf->DemoRecorder_Start(pResult->GetString(0), false, RECORDER_MANUAL); + else + pSelf->DemoRecorder_Start(pSelf->m_aCurrentMap, true, RECORDER_MANUAL); +} + +void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->DemoRecorder_Stop(RECORDER_MANUAL); +} + +void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pSelf->DemoRecorder_AddDemoMarker(RECORDER_MANUAL); + pSelf->DemoRecorder_AddDemoMarker(RECORDER_RACE); + pSelf->DemoRecorder_AddDemoMarker(RECORDER_AUTO); + pSelf->DemoRecorder_AddDemoMarker(RECORDER_REPLAYS); +} + +void CClient::ServerBrowserUpdate() +{ + m_ResortServerBrowser = true; +} + +void CClient::ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + ((CClient *)pUserData)->ServerBrowserUpdate(); +} + +void CClient::SwitchWindowScreen(int Index) +{ + // Todo SDL: remove this when fixed (changing screen when in fullscreen is bugged) + if(g_Config.m_GfxFullscreen) + { + ToggleFullscreen(); + if(Graphics()->SetWindowScreen(Index)) + g_Config.m_GfxScreen = Index; + ToggleFullscreen(); + } + else + { + if(Graphics()->SetWindowScreen(Index)) + g_Config.m_GfxScreen = Index; + } +} + +void CClient::ConchainWindowScreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CClient *pSelf = (CClient *)pUserData; + if(pSelf->Graphics() && pResult->NumArguments()) + { + if(g_Config.m_GfxScreen != pResult->GetInteger(0)) + pSelf->SwitchWindowScreen(pResult->GetInteger(0)); + } + else + pfnCallback(pResult, pCallbackUserData); +} + +void CClient::ToggleFullscreen() +{ + if(Graphics()->Fullscreen(g_Config.m_GfxFullscreen^1)) + g_Config.m_GfxFullscreen ^= 1; +} + +void CClient::ConchainFullscreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CClient *pSelf = (CClient *)pUserData; + if(pSelf->Graphics() && pResult->NumArguments()) + { + if(g_Config.m_GfxFullscreen != pResult->GetInteger(0)) + pSelf->ToggleFullscreen(); + } + else + pfnCallback(pResult, pCallbackUserData); +} + +void CClient::ToggleWindowBordered() +{ + g_Config.m_GfxBorderless ^= 1; + Graphics()->SetWindowBordered(!g_Config.m_GfxBorderless); +} + +void CClient::ConchainWindowBordered(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CClient *pSelf = (CClient *)pUserData; + if(pSelf->Graphics() && pResult->NumArguments()) + { + if(!g_Config.m_GfxFullscreen && (g_Config.m_GfxBorderless != pResult->GetInteger(0))) + pSelf->ToggleWindowBordered(); + } + else + pfnCallback(pResult, pCallbackUserData); +} + +void CClient::ToggleWindowVSync() +{ + if(Graphics()->SetVSync(g_Config.m_GfxVsync^1)) + g_Config.m_GfxVsync ^= 1; +} + +void CClient::LoadFont() +{ + static CFont *pDefaultFont = 0; + char aFilename[512]; + const char *pFontFile = "fonts/DejaVuSansCJKName.ttf"; + if(str_find(g_Config.m_ClLanguagefile, "chinese") != NULL || str_find(g_Config.m_ClLanguagefile, "japanese") != NULL || + str_find(g_Config.m_ClLanguagefile, "korean") != NULL) + pFontFile = "fonts/DejavuWenQuanYiMicroHei.ttf"; + IOHANDLE File = Storage()->OpenFile(pFontFile, IOFLAG_READ, IStorage::TYPE_ALL, aFilename, sizeof(aFilename)); + if(File) + { + io_close(File); + IEngineTextRender *pTextRender = Kernel()->RequestInterface(); + pDefaultFont = pTextRender->GetFont(aFilename); + if(pDefaultFont == NULL) + pDefaultFont = pTextRender->LoadFont(aFilename); + Kernel()->RequestInterface()->SetDefaultFont(pDefaultFont); + } + if(!pDefaultFont) + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load font. filename='%s'", pFontFile); +} + +void CClient::ConchainWindowVSync(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CClient *pSelf = (CClient *)pUserData; + if(pSelf->Graphics() && pResult->NumArguments()) + { + if(g_Config.m_GfxVsync != pResult->GetInteger(0)) + pSelf->ToggleWindowVSync(); + } + else + pfnCallback(pResult, pCallbackUserData); +} + +void CClient::ConchainTimeoutSeed(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments()) + pSelf->m_GenerateTimeoutSeed = false; +} + +void CClient::ConchainPassword(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) +{ + CClient *pSelf = (CClient *)pUserData; + pfnCallback(pResult, pCallbackUserData); + if(pResult->NumArguments() && pSelf->m_LocalStartTime) //won't set m_SendPassword before game has started + pSelf->m_SendPassword = true; +} + +void CClient::RegisterCommands() +{ + m_pConsole = Kernel()->RequestInterface(); + // register server dummy commands for tab completion + m_pConsole->Register("kick", "i[id] ?r[reason]", CFGFLAG_SERVER, 0, 0, "Kick player with specified id for any reason"); + m_pConsole->Register("ban", "s[ip|id] ?i[minutes] r[reason]", CFGFLAG_SERVER, 0, 0, "Ban player with ip/id for x minutes for any reason"); + m_pConsole->Register("unban", "s[ip]", CFGFLAG_SERVER, 0, 0, "Unban ip"); + m_pConsole->Register("bans", "", CFGFLAG_SERVER, 0, 0, "Show banlist"); + m_pConsole->Register("status", "", CFGFLAG_SERVER, 0, 0, "List players"); + m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, 0, 0, "Shut down"); + m_pConsole->Register("record", "s[file]", CFGFLAG_SERVER, 0, 0, "Record to a file"); + m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, 0, 0, "Stop recording"); + m_pConsole->Register("reload", "", CFGFLAG_SERVER, 0, 0, "Reload the map"); + + m_pConsole->Register("dummy_connect", "", CFGFLAG_CLIENT, Con_DummyConnect, this, "connect dummy"); + m_pConsole->Register("dummy_disconnect", "", CFGFLAG_CLIENT, Con_DummyDisconnect, this, "disconnect dummy"); + + m_pConsole->Register("quit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds"); + m_pConsole->Register("exit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds"); + m_pConsole->Register("minimize", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Minimize, this, "Minimize Teeworlds"); + m_pConsole->Register("connect", "s[host|ip]", CFGFLAG_CLIENT, Con_Connect, this, "Connect to the specified host/ip"); + m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server"); + m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server"); + m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot"); + m_pConsole->Register("rcon", "r[rcon-command]", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon"); + m_pConsole->Register("rcon_auth", "s[password]", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon"); + m_pConsole->Register("rcon_login", "s[username] r[password]", CFGFLAG_CLIENT, Con_RconLogin, this, "Authenticate to rcon with a username"); + m_pConsole->Register("play", "r[file]", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Play, this, "Play the file specified"); + m_pConsole->Register("record", "?s[file]", CFGFLAG_CLIENT, Con_Record, this, "Record to the file"); + m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording"); + m_pConsole->Register("add_demomarker", "", CFGFLAG_CLIENT, Con_AddDemoMarker, this, "Add demo timeline marker"); + m_pConsole->Register("add_favorite", "s[host|ip]", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite"); + m_pConsole->Register("remove_favorite", "s[host|ip]", CFGFLAG_CLIENT, Con_RemoveFavorite, this, "Remove a server from favorites"); + m_pConsole->Register("demo_slice_start", "", CFGFLAG_CLIENT, Con_DemoSliceBegin, this, ""); + m_pConsole->Register("demo_slice_end", "", CFGFLAG_CLIENT, Con_DemoSliceEnd, this, ""); + m_pConsole->Register("demo_play", "", CFGFLAG_CLIENT, Con_DemoPlay, this, "Play demo"); + m_pConsole->Register("demo_speed", "i[speed]", CFGFLAG_CLIENT, Con_DemoSpeed, this, "Set demo speed"); + + m_pConsole->Register("save_replay", "", CFGFLAG_CLIENT, Con_SaveReplay, this, "Save a replay of 30 seconds"); + + m_pConsole->Chain("cl_timeout_seed", ConchainTimeoutSeed, this); + + m_pConsole->Chain("password", ConchainPassword, this); + + // used for server browser update + m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this); + m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this); + m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this); + + m_pConsole->Chain("gfx_screen", ConchainWindowScreen, this); + m_pConsole->Chain("gfx_fullscreen", ConchainFullscreen, this); + m_pConsole->Chain("gfx_borderless", ConchainWindowBordered, this); + m_pConsole->Chain("gfx_vsync", ConchainWindowVSync, this); + + // DDRace + + + #define CONSOLE_COMMAND(name, params, flags, callback, userdata, help) m_pConsole->Register(name, params, flags, 0, 0, help); + #include +} + +static CClient *CreateClient() +{ + CClient *pClient = static_cast(malloc(sizeof(*pClient))); + mem_zero(pClient, sizeof(CClient)); + return new(pClient) CClient; +} + +void CClient::HandleConnectLink(const char *pLink) +{ + str_copy(m_aCmdConnect, pLink + sizeof(CONNECTLINK) - 1, sizeof(m_aCmdConnect)); +} + +void CClient::HandleDemoPath(const char *pPath) +{ + str_copy(m_aCmdPlayDemo, pPath, sizeof(m_aCmdPlayDemo)); +} + +/* + Server Time + Client Mirror Time + Client Predicted Time + + Snapshot Latency + Downstream latency + + Prediction Latency + Upstream latency +*/ + +#if defined(CONF_PLATFORM_MACOSX) +extern "C" int SDL_main(int argc, char **argv_) // ignore_convention +{ + const char **argv = const_cast(argv_); +#else +int main(int argc, const char **argv) // ignore_convention +{ +#endif + bool Silent = false; + bool RandInitFailed = false; + + for(int i = 1; i < argc; i++) // ignore_convention + { + if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention + { + Silent = true; +#if defined(CONF_FAMILY_WINDOWS) + FreeConsole(); +#endif + break; + } + } + + if(secure_random_init() != 0) + { + RandInitFailed = true; + } + + CClient *pClient = CreateClient(); + IKernel *pKernel = IKernel::Create(); + pKernel->RegisterInterface(pClient, false); + pClient->RegisterInterfaces(); + + // create the components + IEngine *pEngine = CreateEngine("DDNet", Silent, 1); + IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT); + IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_CLIENT, argc, argv); // ignore_convention + IConfig *pConfig = CreateConfig(); + IEngineSound *pEngineSound = CreateEngineSound(); + IEngineInput *pEngineInput = CreateEngineInput(); + IEngineTextRender *pEngineTextRender = CreateEngineTextRender(); + IEngineMap *pEngineMap = CreateEngineMap(); + IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); + + if(RandInitFailed) + { + dbg_msg("secure", "could not initialize secure RNG"); + return -1; + } + + { + bool RegisterFail = false; + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig); + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineSound); // IEngineSound + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineSound), false); + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineInput); // IEngineInput + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineInput), false); + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineTextRender); // IEngineTextRender + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineTextRender), false); + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMap); // IEngineMap + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap), false); + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngineMasterServer); // IEngineMasterServer + RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer), false); + + RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor(), false); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient(), false); + RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage); + + if(RegisterFail) + { + delete pKernel; + pClient->~CClient(); + free(pClient); + return -1; + } + } + + pEngine->Init(); + pConfig->Init(); + pEngineMasterServer->Init(); + pEngineMasterServer->Load(); + + // register all console commands + pClient->RegisterCommands(); + + pKernel->RequestInterface()->OnConsoleInit(); + + // init client's interfaces + pClient->InitInterfaces(); + + // execute config file + IOHANDLE File = pStorage->OpenFile(CONFIG_FILE, IOFLAG_READ, IStorage::TYPE_ALL); + if(File) + { + io_close(File); + pConsole->ExecuteFile(CONFIG_FILE); + } + else // fallback + { + pConsole->ExecuteFile("settings.cfg"); + } + + // execute autoexec file + File = pStorage->OpenFile(AUTOEXEC_CLIENT_FILE, IOFLAG_READ, IStorage::TYPE_ALL); + if(File) + { + io_close(File); + pConsole->ExecuteFile(AUTOEXEC_CLIENT_FILE); + } + else // fallback + { + pConsole->ExecuteFile(AUTOEXEC_FILE); + } + + if(g_Config.m_ClConfigVersion < 1) + { + if(g_Config.m_ClAntiPing == 0) + { + g_Config.m_ClAntiPingPlayers = 1; + g_Config.m_ClAntiPingGrenade = 1; + g_Config.m_ClAntiPingWeapons = 1; + } + } + g_Config.m_ClConfigVersion = 1; + + // parse the command line arguments + if(argc == 2 && str_startswith(argv[1], CONNECTLINK)) + pClient->HandleConnectLink(argv[1]); + else if(argc == 2 && str_endswith(argv[1], ".demo")) + pClient->HandleDemoPath(argv[1]); + else if(argc > 1) // ignore_convention + pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention + + pClient->Engine()->InitLogfile(); + +#if defined(CONF_FAMILY_WINDOWS) + if(!g_Config.m_ClShowConsole) + FreeConsole(); +#endif + + // run the client + dbg_msg("client", "starting..."); + pClient->Run(); + + // write down the config and quit + pConfig->Save(); + + delete pKernel; + pClient->~CClient(); + free(pClient); + + return 0; +} + +// DDRace + +const char *CClient::GetCurrentMap() +{ + return m_aCurrentMap; +} + +const char *CClient::GetCurrentMapPath() +{ + return m_aCurrentMapPath; +} + +unsigned CClient::GetMapCrc() +{ + return m_pMap->Crc(); +} + +void CClient::RaceRecord_Start(const char *pFilename) +{ + if(State() != IClient::STATE_ONLINE) + m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); + else + m_DemoRecorder[RECORDER_RACE].Start(Storage(), m_pConsole, pFilename, GameClient()->NetVersion(), m_aCurrentMap, m_pMap->Sha256(), m_pMap->Crc(), "client", m_pMap->MapSize(), 0, m_pMap->File()); +} + +void CClient::RaceRecord_Stop() +{ + if(m_DemoRecorder[RECORDER_RACE].IsRecording()) + m_DemoRecorder[RECORDER_RACE].Stop(); +} + +bool CClient::RaceRecord_IsRecording() +{ + return m_DemoRecorder[RECORDER_RACE].IsRecording(); +} + + +void CClient::RequestDDNetInfo() +{ + char aUrl[256]; + static bool s_IsWinXP = os_is_winxp_or_lower(); + if(s_IsWinXP) + str_copy(aUrl, "http://info.ddnet.tw/info", sizeof(aUrl)); + else + str_copy(aUrl, "https://info.ddnet.tw/info", sizeof(aUrl)); + + if(g_Config.m_BrIndicateFinished) + { + char aEscaped[128]; + EscapeUrl(aEscaped, sizeof(aEscaped), g_Config.m_PlayerName); + str_append(aUrl, "?name=", sizeof(aUrl)); + str_append(aUrl, aEscaped, sizeof(aUrl)); + } + + m_pDDNetInfoTask = std::make_shared(Storage(), aUrl, DDNET_INFO_TMP, IStorage::TYPE_SAVE, true); + Engine()->AddJob(m_pDDNetInfoTask); +} + +int CClient::GetPredictionTime() +{ + int64 Now = time_get(); + return (int)((m_PredictedTime.Get(Now)-m_GameTime[g_Config.m_ClDummy].Get(Now))*1000/(float)time_freq()); +} + +void CClient::GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount) +{ + int64 GameTime = m_GameTime[g_Config.m_ClDummy].Get(time_get()); + int64 PredTime = m_PredictedTime.Get(time_get()); + int64 SmoothTime = clamp(GameTime + (int64)(MixAmount * (PredTime - GameTime)), GameTime, PredTime); + + *pSmoothTick = (int)(SmoothTime*50/time_freq())+1; + *pSmoothIntraTick = (SmoothTime - (*pSmoothTick-1)*time_freq()/50) / (float)(time_freq()/50); +} diff --git a/build/enc_temp_folder/91ae225c878a52a1e196841713774bc/menus.h b/build/enc_temp_folder/91ae225c878a52a1e196841713774bc/menus.h new file mode 100644 index 000000000..7efe0ed66 --- /dev/null +++ b/build/enc_temp_folder/91ae225c878a52a1e196841713774bc/menus.h @@ -0,0 +1,424 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef GAME_CLIENT_COMPONENTS_MENUS_H +#define GAME_CLIENT_COMPONENTS_MENUS_H + +#include +#include + +#include +#include + +#include +#include +#include + + +// compnent to fetch keypresses, override all other input +class CMenusKeyBinder : public CComponent +{ +public: + bool m_TakeKey; + bool m_GotKey; + IInput::CEvent m_Key; + int m_Modifier; + CMenusKeyBinder(); + virtual bool OnInput(IInput::CEvent Event); +}; + +class CMenus : public CComponent +{ + static ColorRGBA ms_GuiColor; + static ColorRGBA ms_ColorTabbarInactiveOutgame; + static ColorRGBA ms_ColorTabbarActiveOutgame; + static ColorRGBA ms_ColorTabbarInactiveIngame; + static ColorRGBA ms_ColorTabbarActiveIngame; + static ColorRGBA ms_ColorTabbarInactive; + static ColorRGBA ms_ColorTabbarActive; + + float ButtonColorMul(const void *pID); + + + int DoButton_DemoPlayer(const void *pID, const char *pText, int Checked, const CUIRect *pRect); + int DoButton_Sprite(const void *pID, int ImageID, int SpriteID, int Checked, const CUIRect *pRect, int Corners); + int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active); + int DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect); + int DoButton_MenuTab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Corners); + + int DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect); + int DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect); + int DoButton_CheckBox_Number(const void *pID, const char *pText, int Checked, const CUIRect *pRect); + + /*static void ui_draw_menu_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); + static void ui_draw_keyselect_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); + static void ui_draw_menu_tab_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); + static void ui_draw_settings_tab_button(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); + */ + + int DoButton_Icon(int ImageId, int SpriteId, const CUIRect *pRect); + int DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect); + + //static void ui_draw_browse_icon(int what, const CUIRect *r); + //static void ui_draw_grid_header(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); + + /*static void ui_draw_checkbox_common(const void *id, const char *text, const char *boxtext, const CUIRect *r, const void *extra); + static void ui_draw_checkbox(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); + static void ui_draw_checkbox_number(const void *id, const char *text, int checked, const CUIRect *r, const void *extra); + */ + int DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden=false, int Corners=CUI::CORNER_ALL, const char *pEmptyText = ""); + int DoClearableEditBox(void *pID, void *pClearID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden=false, int Corners=CUI::CORNER_ALL, const char *pEmptyText = ""); + //static int ui_do_edit_box(void *id, const CUIRect *rect, char *str, unsigned str_size, float font_size, bool hidden=false); + + float DoScrollbarV(const void *pID, const CUIRect *pRect, float Current); + float DoScrollbarH(const void *pID, const CUIRect *pRect, float Current); + void DoButton_KeySelect(const void *pID, const char *pText, int Checked, const CUIRect *pRect); + int DoKeyReader(void *pID, const CUIRect *pRect, int Key, int Modifier, int *NewModifier); + + //static int ui_do_key_reader(void *id, const CUIRect *rect, int key); + void UiDoGetButtons(int Start, int Stop, CUIRect View, CUIRect ScopeView); + + struct CListboxItem + { + int m_Visible; + int m_Selected; + CUIRect m_Rect; + CUIRect m_HitRect; + }; + + void UiDoListboxStart(const void *pID, const CUIRect *pRect, float RowHeight, const char *pTitle, const char *pBottomText, int NumItems, + int ItemsPerRow, int SelectedIndex, float ScrollValue); + CListboxItem UiDoListboxNextItem(const void *pID, bool Selected = false, bool KeyEvents = true); + CListboxItem UiDoListboxNextRow(); + int UiDoListboxEnd(float *pScrollValue, bool *pItemActivated, bool *pListBoxActive = 0); + + //static void demolist_listdir_callback(const char *name, int is_dir, void *user); + //static void demolist_list_callback(const CUIRect *rect, int index, void *user); + + int m_GamePage; + int m_Popup; + int m_ActivePage; + bool m_MenuActive; + bool m_UseMouseButtons; + vec2 m_MousePos; + bool m_MouseSlow; + + int64 m_LastInput; + + // loading + int m_LoadCurrent; + int m_LoadTotal; + + // + char m_aMessageTopic[512]; + char m_aMessageBody[512]; + char m_aMessageButton[512]; + + void PopupMessage(const char *pTopic, const char *pBody, const char *pButton); + + // TODO: this is a bit ugly but.. well.. yeah + enum { MAX_INPUTEVENTS = 32 }; + static IInput::CEvent m_aInputEvents[MAX_INPUTEVENTS]; + static int m_NumInputEvents; + + // some settings + static float ms_ButtonHeight; + static float ms_ListheaderHeight; + static float ms_ListitemAdditionalHeight; + static float ms_FontmodHeight; + + // for settings + bool m_NeedRestartSkins; + bool m_NeedRestartGraphics; + bool m_NeedRestartSound; + bool m_NeedRestartUpdate; + bool m_NeedRestartDDNet; + bool m_NeedSendinfo; + bool m_NeedSendDummyinfo; + int m_SettingPlayerPage; + + // + bool m_EscapePressed; + bool m_EnterPressed; + bool m_DeletePressed; + + // for map download popup + int64 m_DownloadLastCheckTime; + int m_DownloadLastCheckSize; + float m_DownloadSpeed; + + // for call vote + int m_CallvoteSelectedOption; + int m_CallvoteSelectedPlayer; + char m_aCallvoteReason[VOTE_REASON_LENGTH]; + char m_aFilterString[25]; + + // demo + enum + { + SORT_DEMONAME=0, + SORT_MARKERS, + SORT_LENGTH, + SORT_DATE, + }; + + struct CDemoItem + { + char m_aFilename[128]; + char m_aName[128]; + bool m_IsDir; + int m_StorageType; + time_t m_Date; + + bool m_InfosLoaded; + bool m_Valid; + CDemoHeader m_Info; + CTimelineMarkers m_TimelineMarkers; + + int NumMarkers() const + { + return ((m_TimelineMarkers.m_aNumTimelineMarkers[0]<<24)&0xFF000000) | ((m_TimelineMarkers.m_aNumTimelineMarkers[1]<<16)&0xFF0000) | + ((m_TimelineMarkers.m_aNumTimelineMarkers[2]<<8)&0xFF00) | (m_TimelineMarkers.m_aNumTimelineMarkers[3]&0xFF); + } + + int Length() const + { + return ((m_Info.m_aLength[0]<<24)&0xFF000000) | ((m_Info.m_aLength[1]<<16)&0xFF0000) | + ((m_Info.m_aLength[2]<<8)&0xFF00) | (m_Info.m_aLength[3]&0xFF); + } + + bool operator<(const CDemoItem &Other) const + { + if(!str_comp(m_aFilename, "..")) + return true; + if(!str_comp(Other.m_aFilename, "..")) + return false; + if(m_IsDir && !Other.m_IsDir) + return true; + if(!m_IsDir && Other.m_IsDir) + return false; + + const CDemoItem &Left = g_Config.m_BrDemoSortOrder ? Other : *this; + const CDemoItem &Right = g_Config.m_BrDemoSortOrder ? *this : Other; + + if(g_Config.m_BrDemoSort == SORT_DEMONAME) + return str_comp_nocase(Left.m_aFilename, Right.m_aFilename) < 0; + if(g_Config.m_BrDemoSort == SORT_DATE) + return Left.m_Date < Right.m_Date; + + if(!Other.m_InfosLoaded) + return m_InfosLoaded; + if(!m_InfosLoaded) + return !Other.m_InfosLoaded; + + if(g_Config.m_BrDemoSort == SORT_MARKERS) + return Left.NumMarkers() < Right.NumMarkers(); + if(g_Config.m_BrDemoSort == SORT_LENGTH) + return Left.Length() < Right.Length(); + + // Unknown sort + return true; + } + }; + + //sorted_array m_lDemos; + char m_aCurrentDemoFolder[256]; + char m_aCurrentDemoFile[64]; + int m_DemolistSelectedIndex; + bool m_DemolistSelectedIsDir; + int m_DemolistStorageType; + + void DemolistOnUpdate(bool Reset); + //void DemolistPopulate(); + static int DemolistFetchCallback(const char *pName, time_t Date, int IsDir, int StorageType, void *pUser); + + // friends + struct CFriendItem + { + const CFriendInfo *m_pFriendInfo; + int m_NumFound; + + bool operator<(const CFriendItem &Other) + { + if(m_NumFound && !Other.m_NumFound) + return true; + else if(!m_NumFound && Other.m_NumFound) + return false; + else + { + int Result = str_comp(m_pFriendInfo->m_aName, Other.m_pFriendInfo->m_aName); + if(Result) + return Result < 0; + else + return str_comp(m_pFriendInfo->m_aClan, Other.m_pFriendInfo->m_aClan) < 0; + } + } + }; + + sorted_array m_lFriends; + int m_FriendlistSelectedIndex; + + void FriendlistOnUpdate(); + + // found in menus.cpp + int Render(); + //void render_background(); + //void render_loading(float percent); + int RenderMenubar(CUIRect r); + void RenderNews(CUIRect MainView); + + // found in menus_demo.cpp + static bool DemoFilterChat(const void *pData, int Size, void *pUser); + bool FetchHeader(CDemoItem &Item); + void FetchAllHeaders(); + void RenderDemoPlayer(CUIRect MainView); + void RenderDemoList(CUIRect MainView); + + // found in menus_ingame.cpp + void RenderGame(CUIRect MainView); + void RenderPlayers(CUIRect MainView); + void RenderServerInfo(CUIRect MainView); + void RenderServerControl(CUIRect MainView); + bool RenderServerControlKick(CUIRect MainView, bool FilterSpectators); + bool RenderServerControlServer(CUIRect MainView); + + // found in menus_browser.cpp + int m_SelectedIndex; + int m_DoubleClickIndex; + int m_ScrollOffset; + void RenderServerbrowserServerList(CUIRect View); + void RenderServerbrowserServerDetail(CUIRect View); + void RenderServerbrowserFilters(CUIRect View); + void RenderServerbrowserFriends(CUIRect View); + void RenderServerbrowser(CUIRect MainView); + static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + // found in menus_settings.cpp + void RenderLanguageSelection(CUIRect MainView); + void RenderSettingsGeneral(CUIRect MainView); + void RenderSettingsPlayer(CUIRect MainView); + void RenderSettingsDummyPlayer(CUIRect MainView); + void RenderSettingsTee(CUIRect MainView); + void RenderSettingsControls(CUIRect MainView); + void RenderSettingsGraphics(CUIRect MainView); + void RenderSettingsSound(CUIRect MainView); + void RenderSettings(CUIRect MainView); + + void SetActive(bool Active); +public: + void RenderBackground(); + + void UseMouseButtons(bool Use) { m_UseMouseButtons = Use; } + + static CMenusKeyBinder m_Binder; + + CMenus(); + + void RenderLoading(); + void RenderUpdating(const char *pCaption, int current=0, int total=0); + + bool IsActive() const { return m_MenuActive; } + + virtual void OnInit(); + + virtual void OnStateChange(int NewState, int OldState); + virtual void OnReset(); + virtual void OnRender(); + virtual bool OnInput(IInput::CEvent Event); + virtual bool OnMouseMove(float x, float y); + + enum + { + PAGE_NEWS=1, + PAGE_GAME, + PAGE_PLAYERS, + PAGE_SERVER_INFO, + PAGE_CALLVOTE, + PAGE_INTERNET, + PAGE_LAN, + PAGE_FAVORITES, + PAGE_DDNET, + PAGE_KOG, + PAGE_DEMOS, + PAGE_SETTINGS, + PAGE_SYSTEM, + PAGE_NETWORK, + PAGE_GHOST + }; + + // DDRace + int DoButton_CheckBox_DontCare(const void *pID, const char *pText, int Checked, const CUIRect *pRect); + sorted_array m_lDemos; + void DemolistPopulate(); + bool m_Dummy; + + const char *GetCurrentDemoFolder() const { return m_aCurrentDemoFolder; } + + // Ghost + struct CGhostItem + { + char m_aFilename[256]; + char m_aPlayer[MAX_NAME_LENGTH]; + + int m_Time; + int m_Slot; + bool m_Own; + + CGhostItem() : m_Slot(-1), m_Own(false) { m_aFilename[0] = 0; } + + bool operator<(const CGhostItem &Other) { return m_Time < Other.m_Time; } + + bool Active() const { return m_Slot != -1; } + bool HasFile() const { return m_aFilename[0]; } + }; + + sorted_array m_lGhosts; + + void GhostlistPopulate(); + CGhostItem *GetOwnGhost(); + void UpdateOwnGhost(CGhostItem Item); + void DeleteGhostItem(int Index); + + void setPopup(int Popup) { m_Popup = Popup; } + + int m_DemoPlayerState; + char m_aDemoPlayerPopupHint[256]; + + enum + { + POPUP_NONE=0, + POPUP_FIRST_LAUNCH, + POPUP_CONNECTING, + POPUP_MESSAGE, + POPUP_DISCONNECTED, + POPUP_PURE, + POPUP_LANGUAGE, + POPUP_COUNTRY, + POPUP_DELETE_DEMO, + POPUP_RENAME_DEMO, + POPUP_REMOVE_FRIEND, + POPUP_SOUNDERROR, + POPUP_PASSWORD, + POPUP_QUIT, + POPUP_DISCONNECT, + POPUP_DISCONNECT_DUMMY, + POPUP_REPLAY_ENABLING_ERROR, + + // demo player states + DEMOPLAYER_NONE=0, + DEMOPLAYER_SLICE_SAVE, + }; + +private: + + static int GhostlistFetchCallback(const char *pName, int IsDir, int StorageType, void *pUser); + + // found in menus_ingame.cpp + void RenderInGameNetwork(CUIRect MainView); + void RenderGhost(CUIRect MainView); + + // found in menus_settings.cpp + void RenderSettingsDDNet(CUIRect MainView); + void RenderSettingsHUD(CUIRect MainView); +}; +#endif diff --git a/build/enc_temp_folder/ab485498acbe1781456755bfe57a34/menus.cpp b/build/enc_temp_folder/ab485498acbe1781456755bfe57a34/menus.cpp new file mode 100644 index 000000000..c67cc7ec3 --- /dev/null +++ b/build/enc_temp_folder/ab485498acbe1781456755bfe57a34/menus.cpp @@ -0,0 +1,1975 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "countryflags.h" +#include "menus.h" +#include "skins.h" +#include "controls.h" + +ColorRGBA CMenus::ms_GuiColor; +ColorRGBA CMenus::ms_ColorTabbarInactiveOutgame; +ColorRGBA CMenus::ms_ColorTabbarActiveOutgame; +ColorRGBA CMenus::ms_ColorTabbarInactive; +ColorRGBA CMenus::ms_ColorTabbarActive = ColorRGBA(0,0,0,0.5f); +ColorRGBA CMenus::ms_ColorTabbarInactiveIngame; +ColorRGBA CMenus::ms_ColorTabbarActiveIngame; + +float CMenus::ms_ButtonHeight = 25.0f; +float CMenus::ms_ListheaderHeight = 17.0f; +float CMenus::ms_FontmodHeight = 0.8f; + +IInput::CEvent CMenus::m_aInputEvents[MAX_INPUTEVENTS]; +int CMenus::m_NumInputEvents; + + +CMenus::CMenus() +{ + m_Popup = POPUP_NONE; + m_ActivePage = PAGE_INTERNET; + m_GamePage = PAGE_GAME; + + m_NeedRestartGraphics = false; + m_NeedRestartSound = false; + m_NeedSendinfo = false; + m_NeedSendDummyinfo = false; + m_MenuActive = true; + m_UseMouseButtons = true; + m_MouseSlow = false; + + m_EscapePressed = false; + m_EnterPressed = false; + m_DeletePressed = false; + m_NumInputEvents = 0; + + m_LastInput = time_get(); + + str_copy(m_aCurrentDemoFolder, "demos", sizeof(m_aCurrentDemoFolder)); + m_aCallvoteReason[0] = 0; + + m_FriendlistSelectedIndex = -1; + m_DoubleClickIndex = -1; + + m_DemoPlayerState = DEMOPLAYER_NONE; + m_Dummy = false; +} + +float CMenus::ButtonColorMul(const void *pID) +{ + if(UI()->ActiveItem() == pID) + return 0.5f; + else if(UI()->HotItem() == pID) + return 1.5f; + return 1; +} + +int CMenus::DoButton_Icon(int ImageId, int SpriteId, const CUIRect *pRect) +{ + int x = pRect->x; + int y = pRect->y; + int w = pRect->w; + int h = pRect->h; + + // Square and center + if(w > h) + { + x += (w-h) / 2; + w = h; + } + else if(h > w) + { + y += (h-w) / 2; + h = w; + } + + Graphics()->TextureSet(g_pData->m_aImages[ImageId].m_Id); + + Graphics()->QuadsBegin(); + RenderTools()->SelectSprite(SpriteId); + IGraphics::CQuadItem QuadItem(x, y, w, h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + + return 0; +} + +int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active) +{ + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIBUTTONS].m_Id); + Graphics()->QuadsBegin(); + if(!Active) + Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f); + RenderTools()->SelectSprite(Checked?SPRITE_GUIBUTTON_ON:SPRITE_GUIBUTTON_OFF); + IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + if(UI()->HotItem() == pID && Active) + { + RenderTools()->SelectSprite(SPRITE_GUIBUTTON_HOVER); + IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h); + Graphics()->QuadsDrawTL(&QuadItem, 1); + } + Graphics()->QuadsEnd(); + + return Active ? UI()->DoButtonLogic(pID, "", Checked, pRect) : 0; +} + +int CMenus::DoButton_Menu(const void *pID, const char *pText, int Checked, const CUIRect *pRect) +{ + RenderTools()->DrawUIRect(pRect, ColorRGBA(1,1,1,0.5f * ButtonColorMul(pID)), CUI::CORNER_ALL, 5.0f); + CUIRect Temp; + pRect->HMargin(pRect->h>=20.0f?2.0f:1.0f, &Temp); + UI()->DoLabel(&Temp, pText, Temp.h*ms_FontmodHeight, 0); + return UI()->DoButtonLogic(pID, pText, Checked, pRect); +} + +void CMenus::DoButton_KeySelect(const void *pID, const char *pText, int Checked, const CUIRect *pRect) +{ + RenderTools()->DrawUIRect(pRect, ColorRGBA(1,1,1,0.5f * ButtonColorMul(pID)), CUI::CORNER_ALL, 5.0f); + CUIRect Temp; + pRect->HMargin(1.0f, &Temp); + UI()->DoLabel(&Temp, pText, Temp.h*ms_FontmodHeight, 0); +} + +int CMenus::DoButton_MenuTab(const void *pID, const char *pText, int Checked, const CUIRect *pRect, int Corners) +{ + if(Checked) + RenderTools()->DrawUIRect(pRect, ms_ColorTabbarActive, Corners, 10.0f); + else + RenderTools()->DrawUIRect(pRect, ms_ColorTabbarInactive, Corners, 10.0f); + CUIRect Temp; + pRect->HMargin(2.0f, &Temp); + UI()->DoLabel(&Temp, pText, Temp.h*ms_FontmodHeight, 0); + + return UI()->DoButtonLogic(pID, pText, Checked, pRect); +} + +int CMenus::DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect) +{ + if(Checked) + RenderTools()->DrawUIRect(pRect, ColorRGBA(1,1,1,0.5f), CUI::CORNER_T, 5.0f); + CUIRect t; + pRect->VSplitLeft(5.0f, 0, &t); + UI()->DoLabel(&t, pText, pRect->h*ms_FontmodHeight, -1); + return UI()->DoButtonLogic(pID, pText, Checked, pRect); +} + +int CMenus::DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect) +{ + CUIRect c = *pRect; + CUIRect t = *pRect; + c.w = c.h; + t.x += c.w; + t.w -= c.w; + t.VSplitLeft(5.0f, 0, &t); + + c.Margin(2.0f, &c); + RenderTools()->DrawUIRect(&c, ColorRGBA(1,1,1,0.25f * ButtonColorMul(pID)), CUI::CORNER_ALL, 3.0f); + + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT); + bool CheckAble = *pBoxText == 'X'; + if(CheckAble) + { + TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + UI()->DoLabel(&c, "\xEE\x97\x8D", c.h*ms_FontmodHeight, 0); + TextRender()->SetCurFont(NULL); + } + else + UI()->DoLabel(&c, pBoxText, c.h*ms_FontmodHeight, 0); + TextRender()->SetRenderFlags(0); + UI()->DoLabel(&t, pText, c.h*ms_FontmodHeight, -1); + + return UI()->DoButtonLogic(pID, pText, 0, pRect); +} + +int CMenus::DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect) +{ + return DoButton_CheckBox_Common(pID, pText, Checked?"X":"", pRect); +} + + +int CMenus::DoButton_CheckBox_Number(const void *pID, const char *pText, int Checked, const CUIRect *pRect) +{ + char aBuf[16]; + str_format(aBuf, sizeof(aBuf), "%d", Checked); + return DoButton_CheckBox_Common(pID, pText, aBuf, pRect); +} + +int CMenus::DoEditBox(void *pID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners, const char *pEmptyText) +{ + int Inside = UI()->MouseInside(pRect); + bool ReturnValue = false; + bool UpdateOffset = false; + static int s_AtIndex = 0; + static bool s_DoScroll = false; + static float s_ScrollStart = 0.0f; + + FontSize *= UI()->Scale(); + + if(UI()->LastActiveItem() == pID) + { + int Len = str_length(pStr); + if(Len == 0) + s_AtIndex = 0; + + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_V)) + { + const char *Text = Input()->GetClipboardText(); + if(Text) + { + int Offset = str_length(pStr); + int CharsLeft = StrSize - Offset - 1; + for(int i = 0; i < str_length(Text) && i < CharsLeft; i++) + { + if(Text[i] == '\n') + pStr[i + Offset] = ' '; + else + pStr[i + Offset] = Text[i]; + } + s_AtIndex = str_length(pStr); + } + } + + if(Input()->KeyIsPressed(KEY_LCTRL) && Input()->KeyPress(KEY_C)) + { + Input()->SetClipboardText(pStr); + } + + if(Inside && UI()->MouseButton(0)) + { + s_DoScroll = true; + s_ScrollStart = UI()->MouseX(); + int MxRel = (int)(UI()->MouseX() - pRect->x); + + for(int i = 1; i <= Len; i++) + { + if(TextRender()->TextWidth(0, FontSize, pStr, i) - *Offset > MxRel) + { + s_AtIndex = i - 1; + break; + } + + if(i == Len) + s_AtIndex = Len; + } + } + else if(!UI()->MouseButton(0)) + s_DoScroll = false; + else if(s_DoScroll) + { + // do scrolling + if(UI()->MouseX() < pRect->x && s_ScrollStart-UI()->MouseX() > 10.0f) + { + s_AtIndex = maximum(0, s_AtIndex-1); + s_ScrollStart = UI()->MouseX(); + UpdateOffset = true; + } + else if(UI()->MouseX() > pRect->x+pRect->w && UI()->MouseX()-s_ScrollStart > 10.0f) + { + s_AtIndex = minimum(Len, s_AtIndex+1); + s_ScrollStart = UI()->MouseX(); + UpdateOffset = true; + } + } + + for(int i = 0; i < m_NumInputEvents; i++) + { + Len = str_length(pStr); + int NumChars = Len; + ReturnValue |= CLineInput::Manipulate(m_aInputEvents[i], pStr, StrSize, StrSize, &Len, &s_AtIndex, &NumChars); + } + } + + bool JustGotActive = false; + + if(UI()->ActiveItem() == pID) + { + if(!UI()->MouseButton(0)) + { + s_AtIndex = minimum(s_AtIndex, str_length(pStr)); + s_DoScroll = false; + UI()->SetActiveItem(0); + } + } + else if(UI()->HotItem() == pID) + { + if(UI()->MouseButton(0)) + { + if(UI()->LastActiveItem() != pID) + JustGotActive = true; + UI()->SetActiveItem(pID); + } + } + + if(Inside) + { + UI()->SetHotItem(pID); + } + + CUIRect Textbox = *pRect; + RenderTools()->DrawUIRect(&Textbox, ColorRGBA(1, 1, 1, 0.5f), Corners, 3.0f); + Textbox.VMargin(2.0f, &Textbox); + Textbox.HMargin(2.0f, &Textbox); + + const char *pDisplayStr = pStr; + char aStars[128]; + + if(pDisplayStr[0] == '\0') + { + pDisplayStr = pEmptyText; + TextRender()->TextColor(1, 1, 1, 0.75f); + } + + if(Hidden) + { + unsigned s = str_length(pDisplayStr); + if(s >= sizeof(aStars)) + s = sizeof(aStars)-1; + for(unsigned int i = 0; i < s; ++i) + aStars[i] = '*'; + aStars[s] = 0; + pDisplayStr = aStars; + } + + char aInputing[32] = {0}; + if(UI()->HotItem() == pID && Input()->GetIMEState()) + { + str_copy(aInputing, pStr, sizeof(aInputing)); + const char *Text = Input()->GetIMECandidate(); + if(str_length(Text)) + { + int NewTextLen = str_length(Text); + int CharsLeft = StrSize - str_length(aInputing) - 1; + int FillCharLen = minimum(NewTextLen, CharsLeft); + //Push Char Backward + for(int i = str_length(aInputing); i >= s_AtIndex ; i--) + aInputing[i+FillCharLen] = aInputing[i]; + for(int i = 0; i < FillCharLen; i++) + { + if(Text[i] == '\n') + aInputing[s_AtIndex + i] = ' '; + else + aInputing[s_AtIndex + i] = Text[i]; + } + //s_AtIndex = s_AtIndex+FillCharLen; + pDisplayStr = aInputing; + } + } + + // check if the text has to be moved + if(UI()->LastActiveItem() == pID && !JustGotActive && (UpdateOffset || m_NumInputEvents)) + { + float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); + if(w-*Offset > Textbox.w) + { + // move to the left + float wt = TextRender()->TextWidth(0, FontSize, pDisplayStr, -1); + do + { + *Offset += minimum(wt-*Offset-Textbox.w, Textbox.w/3); + } + while(w-*Offset > Textbox.w); + } + else if(w-*Offset < 0.0f) + { + // move to the right + do + { + *Offset = maximum(0.0f, *Offset-Textbox.w/3); + } + while(w-*Offset < 0.0f); + } + } + UI()->ClipEnable(pRect); + Textbox.x -= *Offset; + + UI()->DoLabel(&Textbox, pDisplayStr, FontSize, -1); + + TextRender()->TextColor(1, 1, 1, 1); + + // render the cursor + if(UI()->LastActiveItem() == pID && !JustGotActive) + { + if(str_length(aInputing)) + { + float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex + Input()->GetEditingCursor()); + Textbox = *pRect; + Textbox.VSplitLeft(2.0f, 0, &Textbox); + Textbox.x += (w-*Offset-TextRender()->TextWidth(0, FontSize, "|", -1)/2); + + UI()->DoLabel(&Textbox, "|", FontSize, -1); + } + float w = TextRender()->TextWidth(0, FontSize, pDisplayStr, s_AtIndex); + Textbox = *pRect; + Textbox.VSplitLeft(2.0f, 0, &Textbox); + Textbox.x += (w-*Offset-TextRender()->TextWidth(0, FontSize, "|", -1)/2); + + if((2*time_get()/time_freq()) % 2) // make it blink + UI()->DoLabel(&Textbox, "|", FontSize, -1); + } + UI()->ClipDisable(); + + return ReturnValue; +} + +int CMenus::DoClearableEditBox(void *pID, void *pClearID, const CUIRect *pRect, char *pStr, unsigned StrSize, float FontSize, float *Offset, bool Hidden, int Corners, const char *pEmptyText) +{ + bool ReturnValue = false; + CUIRect EditBox; + CUIRect ClearButton; + pRect->VSplitRight(15.0f, &EditBox, &ClearButton); + if(DoEditBox(pID, &EditBox, pStr, StrSize, FontSize, Offset, Hidden, Corners&~CUI::CORNER_R, pEmptyText)) + { + ReturnValue = true; + } + + RenderTools()->DrawUIRect(&ClearButton, ColorRGBA(1, 1, 1, 0.33f * ButtonColorMul(pID)), Corners&~CUI::CORNER_L, 3.0f); + UI()->DoLabel(&ClearButton, "×", ClearButton.h * ms_FontmodHeight, 0); + if(UI()->DoButtonLogic(pClearID, "×", 0, &ClearButton)) + { + pStr[0] = 0; + UI()->SetActiveItem(pID); + ReturnValue = true; + } + return ReturnValue; +} + +float CMenus::DoScrollbarV(const void *pID, const CUIRect *pRect, float Current) +{ + CUIRect Handle; + static float OffsetY; + pRect->HSplitTop(33, &Handle, 0); + + Handle.y += (pRect->h-Handle.h)*Current; + + // logic + float ReturnValue = Current; + int Inside = UI()->MouseInside(&Handle); + + if(UI()->ActiveItem() == pID) + { + if(!UI()->MouseButton(0)) + UI()->SetActiveItem(0); + + if(Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT)) + m_MouseSlow = true; + + float Min = pRect->y; + float Max = pRect->h-Handle.h; + float Cur = UI()->MouseY()-OffsetY; + ReturnValue = (Cur-Min)/Max; + if(ReturnValue < 0.0f) ReturnValue = 0.0f; + if(ReturnValue > 1.0f) ReturnValue = 1.0f; + } + else if(UI()->HotItem() == pID) + { + if(UI()->MouseButton(0)) + { + UI()->SetActiveItem(pID); + OffsetY = UI()->MouseY()-Handle.y; + } + } + + if(Inside) + UI()->SetHotItem(pID); + + // render + CUIRect Rail; + pRect->VMargin(5.0f, &Rail); + RenderTools()->DrawUIRect(&Rail, ColorRGBA(1,1,1,0.25f), 0, 0.0f); + + CUIRect Slider = Handle; + Slider.w = Rail.x-Slider.x; + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1,1,1,0.25f), CUI::CORNER_L, 2.5f); + Slider.x = Rail.x+Rail.w; + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1,1,1,0.25f), CUI::CORNER_R, 2.5f); + + Slider = Handle; + Slider.Margin(5.0f, &Slider); + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1,1,1,0.25f*ButtonColorMul(pID)), CUI::CORNER_ALL, 2.5f); + + return ReturnValue; +} + + + +float CMenus::DoScrollbarH(const void *pID, const CUIRect *pRect, float Current) +{ + CUIRect Handle; + static float OffsetX; + pRect->VSplitLeft(33, &Handle, 0); + + Handle.x += (pRect->w-Handle.w)*Current; + + // logic + float ReturnValue = Current; + int Inside = UI()->MouseInside(&Handle); + + if(UI()->ActiveItem() == pID) + { + if(!UI()->MouseButton(0)) + UI()->SetActiveItem(0); + + if(Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT)) + m_MouseSlow = true; + + float Min = pRect->x; + float Max = pRect->w-Handle.w; + float Cur = UI()->MouseX()-OffsetX; + ReturnValue = (Cur-Min)/Max; + if(ReturnValue < 0.0f) ReturnValue = 0.0f; + if(ReturnValue > 1.0f) ReturnValue = 1.0f; + } + else if(UI()->HotItem() == pID) + { + if(UI()->MouseButton(0)) + { + UI()->SetActiveItem(pID); + OffsetX = UI()->MouseX()-Handle.x; + } + } + + if(Inside) + UI()->SetHotItem(pID); + + // render + CUIRect Rail; + pRect->HMargin(5.0f, &Rail); + RenderTools()->DrawUIRect(&Rail, ColorRGBA(1,1,1,0.25f), 0, 0.0f); + + CUIRect Slider = Handle; + Slider.h = Rail.y-Slider.y; + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1,1,1,0.25f), CUI::CORNER_T, 2.5f); + Slider.y = Rail.y+Rail.h; + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1,1,1,0.25f), CUI::CORNER_B, 2.5f); + + Slider = Handle; + Slider.Margin(5.0f, &Slider); + RenderTools()->DrawUIRect(&Slider, ColorRGBA(1,1,1,0.25f * ButtonColorMul(pID)), CUI::CORNER_ALL, 2.5f); + + return ReturnValue; +} + +int CMenus::DoKeyReader(void *pID, const CUIRect *pRect, int Key, int Modifier, int *NewModifier) +{ + // process + static void *pGrabbedID = 0; + static bool MouseReleased = true; + static int ButtonUsed = 0; + int Inside = UI()->MouseInside(pRect); + int NewKey = Key; + *NewModifier = Modifier; + + if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && pGrabbedID == pID) + MouseReleased = true; + + if(UI()->ActiveItem() == pID) + { + if(m_Binder.m_GotKey) + { + // abort with escape key + if(m_Binder.m_Key.m_Key != KEY_ESCAPE) + { + NewKey = m_Binder.m_Key.m_Key; + *NewModifier = m_Binder.m_Modifier; + } + m_Binder.m_GotKey = false; + UI()->SetActiveItem(0); + MouseReleased = false; + pGrabbedID = pID; + } + + if(ButtonUsed == 1 && !UI()->MouseButton(1)) + { + if(Inside) + NewKey = 0; + UI()->SetActiveItem(0); + } + } + else if(UI()->HotItem() == pID) + { + if(MouseReleased) + { + if(UI()->MouseButton(0)) + { + m_Binder.m_TakeKey = true; + m_Binder.m_GotKey = false; + UI()->SetActiveItem(pID); + ButtonUsed = 0; + } + + if(UI()->MouseButton(1)) + { + UI()->SetActiveItem(pID); + ButtonUsed = 1; + } + } + } + + if(Inside) + UI()->SetHotItem(pID); + + // draw + if(UI()->ActiveItem() == pID && ButtonUsed == 0) + DoButton_KeySelect(pID, "???", 0, pRect); + else + { + if(Key) + { + char aBuf[64]; + if(*NewModifier) + str_format(aBuf, sizeof(aBuf), "%s+%s", CBinds::GetModifierName(*NewModifier), Input()->KeyName(Key)); + else + str_format(aBuf, sizeof(aBuf), "%s", Input()->KeyName(Key)); + + DoButton_KeySelect(pID, aBuf, 0, pRect); + } + else + DoButton_KeySelect(pID, "", 0, pRect); + } + return NewKey; +} + + +int CMenus::RenderMenubar(CUIRect r) +{ + CUIRect Box = r; + CUIRect Button; + + m_ActivePage = g_Config.m_UiPage; + int NewPage = -1; + + if(Client()->State() != IClient::STATE_OFFLINE) + m_ActivePage = m_GamePage; + + if(Client()->State() == IClient::STATE_OFFLINE) + { + // offline menus + Box.VSplitLeft(90.0f, &Button, &Box); + static int s_NewsButton=0; + if(DoButton_MenuTab(&s_NewsButton, Localize("News"), m_ActivePage==PAGE_NEWS, &Button, CUI::CORNER_T)) + { + NewPage = PAGE_NEWS; + m_DoubleClickIndex = -1; + } + Box.VSplitLeft(10.0f, 0, &Box); + + Box.VSplitLeft(100.0f, &Button, &Box); + static int s_InternetButton=0; + if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), m_ActivePage==PAGE_INTERNET, &Button, CUI::CORNER_TL)) + { + if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) + ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); + NewPage = PAGE_INTERNET; + m_DoubleClickIndex = -1; + } + + Box.VSplitLeft(60.0f, &Button, &Box); + static int s_LanButton=0; + if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), m_ActivePage==PAGE_LAN, &Button, 0)) + { + if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN) + ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); + NewPage = PAGE_LAN; + m_DoubleClickIndex = -1; + } + + Box.VSplitLeft(100.0f, &Button, &Box); + static int s_FavoritesButton=0; + if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), m_ActivePage==PAGE_FAVORITES, &Button, 0)) + { + if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) + ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); + NewPage = PAGE_FAVORITES; + m_DoubleClickIndex = -1; + } + + Box.VSplitLeft(80.0f, &Button, &Box); + static int s_DDNetButton=0; + if(DoButton_MenuTab(&s_DDNetButton, "DDNet", m_ActivePage==PAGE_DDNET, &Button, 0)) + { + if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_DDNET) + { + Client()->RequestDDNetInfo(); + ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); + } + NewPage = PAGE_DDNET; + m_DoubleClickIndex = -1; + } + + Box.VSplitLeft(60.0f, &Button, &Box); + static int s_KoGButton=0; + if(DoButton_MenuTab(&s_KoGButton, "KoG", m_ActivePage==PAGE_KOG, &Button, CUI::CORNER_TR)) + { + if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_KOG) + { + Client()->RequestDDNetInfo(); + ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG); + } + NewPage = PAGE_KOG; + m_DoubleClickIndex = -1; + } + + Box.VSplitLeft(10.0f, 0, &Box); + Box.VSplitLeft(100.0f, &Button, &Box); + static int s_DemosButton=0; + if(DoButton_MenuTab(&s_DemosButton, Localize("Demos"), m_ActivePage==PAGE_DEMOS, &Button, CUI::CORNER_T)) + { + DemolistPopulate(); + NewPage = PAGE_DEMOS; + m_DoubleClickIndex = -1; + } + } + else + { + // online menus + Box.VSplitLeft(90.0f, &Button, &Box); + static int s_GameButton=0; + if(DoButton_MenuTab(&s_GameButton, Localize("Game"), m_ActivePage==PAGE_GAME, &Button, CUI::CORNER_TL)) + NewPage = PAGE_GAME; + + Box.VSplitLeft(90.0f, &Button, &Box); + static int s_PlayersButton=0; + if(DoButton_MenuTab(&s_PlayersButton, Localize("Players"), m_ActivePage==PAGE_PLAYERS, &Button, 0)) + NewPage = PAGE_PLAYERS; + + Box.VSplitLeft(130.0f, &Button, &Box); + static int s_ServerInfoButton=0; + if(DoButton_MenuTab(&s_ServerInfoButton, Localize("Server info"), m_ActivePage==PAGE_SERVER_INFO, &Button, 0)) + NewPage = PAGE_SERVER_INFO; + + Box.VSplitLeft(90.0f, &Button, &Box); + static int s_NetworkButton=0; + if(DoButton_MenuTab(&s_NetworkButton, Localize("Browser"), m_ActivePage==PAGE_NETWORK, &Button, 0)) + NewPage = PAGE_NETWORK; + + { + CServerInfo Info; + Client()->GetServerInfo(&Info); + static int s_GhostButton=0; + if(IsRace(&Info) || IsDDNet(&Info)) + { + Box.VSplitLeft(70.0f, &Button, &Box); + if(DoButton_MenuTab(&s_GhostButton, Localize("Ghost"), m_ActivePage==PAGE_GHOST, &Button, 0)) + NewPage = PAGE_GHOST; + } + } + + Box.VSplitLeft(100.0f, &Button, &Box); + Box.VSplitLeft(4.0f, 0, &Box); + static int s_CallVoteButton=0; + if(DoButton_MenuTab(&s_CallVoteButton, Localize("Call vote"), m_ActivePage==PAGE_CALLVOTE, &Button, CUI::CORNER_TR)) + NewPage = PAGE_CALLVOTE; + } + + TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); + + Box.VSplitRight(33.0f, &Box, &Button); + static int s_QuitButton=0; + if(DoButton_MenuTab(&s_QuitButton, "\xEE\x97\x8D", 0, &Button, CUI::CORNER_T)) + { + if(m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0)) + { + m_Popup = POPUP_QUIT; + } + else + { + Client()->Quit(); + } + } + + Box.VSplitRight(10.0f, &Box, &Button); + Box.VSplitRight(33.0f, &Box, &Button); + static int s_SettingsButton=0; + + if(DoButton_MenuTab(&s_SettingsButton, "\xEE\xA2\xB8", m_ActivePage==PAGE_SETTINGS, &Button, CUI::CORNER_T)) + NewPage = PAGE_SETTINGS; + + Box.VSplitRight(10.0f, &Box, &Button); + Box.VSplitRight(33.0f, &Box, &Button); + static int s_EditorButton=0; + if(DoButton_MenuTab(&s_EditorButton, "\xEE\x8F\x89", 0, &Button, CUI::CORNER_T)) + { + g_Config.m_ClEditor = 1; + } + + TextRender()->SetRenderFlags(0); + TextRender()->SetCurFont(NULL); + + if(NewPage != -1) + { + if(Client()->State() == IClient::STATE_OFFLINE) + g_Config.m_UiPage = NewPage; + else + m_GamePage = NewPage; + } + + return 0; +} + +void CMenus::RenderLoading() +{ + // TODO: not supported right now due to separate render thread + + static int64 LastLoadRender = 0; + float Percent = m_LoadCurrent++/(float)m_LoadTotal; + + // make sure that we don't render for each little thing we load + // because that will slow down loading if we have vsync + if(time_get()-LastLoadRender < time_freq()/60) + return; + + LastLoadRender = time_get(); + + // need up date this here to get correct + ms_GuiColor = color_cast(ColorHSLA(g_Config.m_UiColor, true)); + + CUIRect Screen = *UI()->Screen(); + Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); + + RenderBackground(); + + float w = 700; + float h = 200; + float x = Screen.w/2-w/2; + float y = Screen.h/2-h/2; + + Graphics()->BlendNormal(); + + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0,0,0,0.50f); + RenderTools()->DrawRoundRect(x, y, w, h, 40.0f); + Graphics()->QuadsEnd(); + + + const char *pCaption = Localize("Loading DDNet Client"); + + CUIRect r; + r.x = x; + r.y = y+20; + r.w = w; + r.h = h - 130; + UI()->DoLabel(&r, pCaption, 48.0f, 0, -1); + + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1,1,1,0.75f); + RenderTools()->DrawRoundRect(x+40, y+h-75, (w-80)*Percent, 25, 5.0f); + Graphics()->QuadsEnd(); + + Graphics()->Swap(); +} + +void CMenus::RenderNews(CUIRect MainView) +{ + RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_B, 10.0f); + + MainView.HSplitTop(15.0f, 0, &MainView); + MainView.VSplitLeft(15.0f, 0, &MainView); + + CUIRect Label; + + const char *pStr = Client()->m_aNews; + char aLine[256]; + while((pStr = str_next_token(pStr, "\n", aLine, sizeof(aLine)))) + { + const int Len = str_length(aLine); + if(Len > 0 && aLine[0] == '|' && aLine[Len-1] == '|') + { + MainView.HSplitTop(30.0f, &Label, &MainView); + aLine[Len-1] = '\0'; + UI()->DoLabelScaled(&Label, aLine + 1, 20.0f, -1); + } + else + { + MainView.HSplitTop(20.0f, &Label, &MainView); + UI()->DoLabelScaled(&Label, aLine, 15.f, -1, MainView.w-30.0f); + } + } +} + +void CMenus::OnInit() +{ + if(g_Config.m_ClShowWelcome) + m_Popup = POPUP_LANGUAGE; + + Console()->Chain("add_favorite", ConchainServerbrowserUpdate, this); + Console()->Chain("remove_favorite", ConchainServerbrowserUpdate, this); + Console()->Chain("add_friend", ConchainFriendlistUpdate, this); + Console()->Chain("remove_friend", ConchainFriendlistUpdate, this); + + // setup load amount + m_LoadCurrent = 0; + m_LoadTotal = g_pData->m_NumImages; + if(!g_Config.m_ClThreadsoundloading) + m_LoadTotal += g_pData->m_NumSounds; +} + +void CMenus::PopupMessage(const char *pTopic, const char *pBody, const char *pButton) +{ + // reset active item + UI()->SetActiveItem(0); + + str_copy(m_aMessageTopic, pTopic, sizeof(m_aMessageTopic)); + str_copy(m_aMessageBody, pBody, sizeof(m_aMessageBody)); + str_copy(m_aMessageButton, pButton, sizeof(m_aMessageButton)); + m_Popup = POPUP_MESSAGE; +} + + +int CMenus::Render() +{ + CUIRect Screen = *UI()->Screen(); + Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); + + m_MouseSlow = false; + + static int s_Frame = 0; + if(s_Frame == 0) + { + s_Frame++; + } + else if(s_Frame == 1) + { + m_pClient->m_pSounds->Enqueue(CSounds::CHN_MUSIC, SOUND_MENU); + s_Frame++; + m_DoubleClickIndex = -1; + + if(g_Config.m_UiPage == PAGE_INTERNET) + ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); + else if(g_Config.m_UiPage == PAGE_LAN) + ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); + else if(g_Config.m_UiPage == PAGE_FAVORITES) + ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); + else if(g_Config.m_UiPage == PAGE_DDNET) + ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET); + else if(g_Config.m_UiPage == PAGE_KOG) + ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG); + } + + if(Client()->State() == IClient::STATE_ONLINE) + { + ms_ColorTabbarInactive = ms_ColorTabbarInactiveIngame; + ms_ColorTabbarActive = ms_ColorTabbarActiveIngame; + } + else + { + RenderBackground(); + ms_ColorTabbarInactive = ms_ColorTabbarInactiveOutgame; + ms_ColorTabbarActive = ms_ColorTabbarActiveOutgame; + } + + CUIRect TabBar; + CUIRect MainView; + + // some margin around the screen + Screen.Margin(10.0f, &Screen); + + static bool s_SoundCheck = false; + if(!s_SoundCheck && m_Popup == POPUP_NONE) + { + if(Client()->SoundInitFailed()) + m_Popup = POPUP_SOUNDERROR; + s_SoundCheck = true; + } + + if(m_Popup == POPUP_NONE) + { + Screen.HSplitTop(24.0f, &TabBar, &MainView); + + // render news + if(g_Config.m_UiPage < PAGE_NEWS || g_Config.m_UiPage > PAGE_SETTINGS || (Client()->State() == IClient::STATE_OFFLINE && g_Config.m_UiPage >= PAGE_GAME && g_Config.m_UiPage <= PAGE_CALLVOTE)) + { + ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); + g_Config.m_UiPage = PAGE_INTERNET; + m_DoubleClickIndex = -1; + } + + // render current page + if(Client()->State() != IClient::STATE_OFFLINE) + { + if(m_GamePage == PAGE_GAME) + RenderGame(MainView); + else if(m_GamePage == PAGE_PLAYERS) + RenderPlayers(MainView); + else if(m_GamePage == PAGE_SERVER_INFO) + RenderServerInfo(MainView); + else if(m_GamePage == PAGE_NETWORK) + RenderInGameNetwork(MainView); + else if(m_GamePage == PAGE_GHOST) + RenderGhost(MainView); + else if(m_GamePage == PAGE_CALLVOTE) + RenderServerControl(MainView); + else if(m_GamePage == PAGE_SETTINGS) + RenderSettings(MainView); + } + else if(g_Config.m_UiPage == PAGE_NEWS) + RenderNews(MainView); + else if(g_Config.m_UiPage == PAGE_INTERNET) + RenderServerbrowser(MainView); + else if(g_Config.m_UiPage == PAGE_LAN) + RenderServerbrowser(MainView); + else if(g_Config.m_UiPage == PAGE_DEMOS) + RenderDemoList(MainView); + else if(g_Config.m_UiPage == PAGE_FAVORITES) + RenderServerbrowser(MainView); + else if(g_Config.m_UiPage == PAGE_DDNET) + RenderServerbrowser(MainView); + else if(g_Config.m_UiPage == PAGE_KOG) + RenderServerbrowser(MainView); + else if(g_Config.m_UiPage == PAGE_SETTINGS) + RenderSettings(MainView); + + // do tab bar + RenderMenubar(TabBar); + } + else + { + // make sure that other windows doesn't do anything funnay! + //UI()->SetHotItem(0); + //UI()->SetActiveItem(0); + char aBuf[128]; + const char *pTitle = ""; + const char *pExtraText = ""; + const char *pButtonText = ""; + int ExtraAlign = 0; + + if(m_Popup == POPUP_MESSAGE) + { + pTitle = m_aMessageTopic; + pExtraText = m_aMessageBody; + pButtonText = m_aMessageButton; + } + else if(m_Popup == POPUP_CONNECTING) + { + pTitle = Localize("Connecting to"); + pExtraText = g_Config.m_UiServerAddress; // TODO: query the client about the address + pButtonText = Localize("Abort"); + if(Client()->MapDownloadTotalsize() > 0) + { + str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Downloading map"), Client()->MapDownloadName()); + pTitle = aBuf; + pExtraText = ""; + } + } + else if(m_Popup == POPUP_DISCONNECTED) + { + pTitle = Localize("Disconnected"); + pExtraText = Client()->ErrorString(); + pButtonText = Localize("Ok"); + if(Client()->m_ReconnectTime > 0) + { + str_format(aBuf, sizeof(aBuf), Localize("\n\nReconnect in %d sec"), (int)((Client()->m_ReconnectTime - time_get()) / time_freq())); + pTitle = Client()->ErrorString(); + pExtraText = aBuf; + pButtonText = Localize("Abort"); + } + ExtraAlign = 0; + } + else if(m_Popup == POPUP_PURE) + { + pTitle = Localize("Disconnected"); + pExtraText = Localize("The server is running a non-standard tuning on a pure game type."); + pButtonText = Localize("Ok"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_DELETE_DEMO) + { + pTitle = Localize("Delete demo"); + pExtraText = Localize("Are you sure that you want to delete the demo?"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_RENAME_DEMO) + { + pTitle = Localize("Rename demo"); + pExtraText = ""; + ExtraAlign = -1; + } + else if(m_Popup == POPUP_REMOVE_FRIEND) + { + pTitle = Localize("Remove friend"); + pExtraText = Localize("Are you sure that you want to remove the player from your friends list?"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_SOUNDERROR) + { + pTitle = Localize("Sound error"); + pExtraText = Localize("The audio device couldn't be initialised."); + pButtonText = Localize("Ok"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_PASSWORD) + { + pTitle = Localize("Password incorrect"); + pExtraText = ""; + pButtonText = Localize("Try again"); + } + else if(m_Popup == POPUP_QUIT) + { + pTitle = Localize("Quit"); + pExtraText = Localize("Are you sure that you want to quit?"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_DISCONNECT) + { + pTitle = Localize("Disconnect"); + pExtraText = Localize("Are you sure that you want to disconnect?"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_DISCONNECT_DUMMY) + { + pTitle = Localize("Disconnect Dummy"); + pExtraText = Localize("Are you sure that you want to disconnect your dummy?"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_FIRST_LAUNCH) + { + pTitle = Localize("Welcome to DDNet"); + pExtraText = Localize("As this is the first time you launch the game, please enter your nick name below. It's recommended that you check the settings to adjust them to your liking before joining a server."); + pButtonText = Localize("Ok"); + ExtraAlign = -1; + } + else if(m_Popup == POPUP_REPLAY_ENABLING_ERROR) + { + pTitle = Localize("Error enabling replays"); + pExtraText = Localize("As the replay system uses the demo recorder system, no other record options should be used along with replays. Please disable any other option if you want to enable replays."); + ExtraAlign = -1; + } + + CUIRect Box, Part; + Box = Screen; + Box.VMargin(150.0f/UI()->Scale(), &Box); + Box.HMargin(150.0f/UI()->Scale(), &Box); + + // render the box + RenderTools()->DrawUIRect(&Box, ColorRGBA(0,0,0,0.5f), CUI::CORNER_ALL, 15.0f); + + Box.HSplitTop(20.f/UI()->Scale(), &Part, &Box); + Box.HSplitTop(24.f/UI()->Scale(), &Part, &Box); + Part.VMargin(20.f/UI()->Scale(), &Part); + if(TextRender()->TextWidth(0, 24.f, pTitle, -1) > Part.w) + UI()->DoLabelScaled(&Part, pTitle, 24.f, -1, (int)Part.w); + else + UI()->DoLabelScaled(&Part, pTitle, 24.f, 0); + Box.HSplitTop(20.f/UI()->Scale(), &Part, &Box); + Box.HSplitTop(24.f/UI()->Scale(), &Part, &Box); + Part.VMargin(20.f/UI()->Scale(), &Part); + + if(ExtraAlign == -1) + UI()->DoLabelScaled(&Part, pExtraText, 20.f, -1, (int)Part.w); + else + { + if(TextRender()->TextWidth(0, 20.f, pExtraText, -1) > Part.w) + UI()->DoLabelScaled(&Part, pExtraText, 20.f, -1, (int)Part.w); + else + UI()->DoLabelScaled(&Part, pExtraText, 20.f, 0, -1); + } + + if(m_Popup == POPUP_QUIT) + { + CUIRect Yes, No; + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + + // additional info + Box.VMargin(20.f/UI()->Scale(), &Box); + if(m_pClient->Editor()->HasUnsavedData()) + { + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), "%s\n%s", Localize("There's an unsaved map in the editor, you might want to save it before you quit the game."), Localize("Quit anyway?")); + UI()->DoLabelScaled(&Box, aBuf, 20.f, -1, Part.w-20.0f); + } + + // buttons + Part.VMargin(80.0f, &Part); + Part.VSplitMid(&No, &Yes); + Yes.VMargin(20.0f, &Yes); + No.VMargin(20.0f, &No); + + static int s_ButtonAbort = 0; + if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) + m_Popup = POPUP_NONE; + + static int s_ButtonTryAgain = 0; + if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) + Client()->Quit(); + } + else if(m_Popup == POPUP_DISCONNECT) + { + CUIRect Yes, No; + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + + // buttons + Part.VMargin(80.0f, &Part); + Part.VSplitMid(&No, &Yes); + Yes.VMargin(20.0f, &Yes); + No.VMargin(20.0f, &No); + + static int s_ButtonAbort = 0; + if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) + m_Popup = POPUP_NONE; + + static int s_ButtonTryAgain = 0; + if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) + Client()->Disconnect(); + } + else if(m_Popup == POPUP_DISCONNECT_DUMMY) + { + CUIRect Yes, No; + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + + // buttons + Part.VMargin(80.0f, &Part); + Part.VSplitMid(&No, &Yes); + Yes.VMargin(20.0f, &Yes); + No.VMargin(20.0f, &No); + + static int s_ButtonAbort = 0; + if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) + m_Popup = POPUP_NONE; + + static int s_ButtonTryAgain = 0; + if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) + { + Client()->DummyDisconnect(0); + m_Popup = POPUP_NONE; + } + } + else if (m_Popup == POPUP_REPLAY_ENABLING_ERROR) + { + CUIRect Ok; + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + + Part.VMargin(40.0f, &Ok); + + static int s_ButtonOk = 0; + if (DoButton_Menu(&s_ButtonOk, Localize("Got it"), 0, &Ok) || m_EscapePressed) + m_Popup = POPUP_NONE; + } + else if(m_Popup == POPUP_PASSWORD) + { + CUIRect Label, TextBox, TryAgain, Abort; + + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Part.VMargin(80.0f, &Part); + + Part.VSplitMid(&Abort, &TryAgain); + + TryAgain.VMargin(20.0f, &TryAgain); + Abort.VMargin(20.0f, &Abort); + + static int s_ButtonAbort = 0; + if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || m_EscapePressed) + m_Popup = POPUP_NONE; + + static int s_ButtonTryAgain = 0; + if(DoButton_Menu(&s_ButtonTryAgain, Localize("Try again"), 0, &TryAgain) || m_EnterPressed) + { + Client()->Connect(g_Config.m_UiServerAddress, g_Config.m_Password); + } + + Box.HSplitBottom(60.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + + Part.VSplitLeft(60.0f, 0, &Label); + Label.VSplitLeft(100.0f, 0, &TextBox); + TextBox.VSplitLeft(20.0f, 0, &TextBox); + TextBox.VSplitRight(60.0f, &TextBox, 0); + UI()->DoLabel(&Label, Localize("Password"), 18.0f, -1); + static float Offset = 0.0f; + DoEditBox(&g_Config.m_Password, &TextBox, g_Config.m_Password, sizeof(g_Config.m_Password), 12.0f, &Offset, true); + } + else if(m_Popup == POPUP_CONNECTING) + { + Box = Screen; + Box.VMargin(150.0f, &Box); + Box.HMargin(150.0f, &Box); + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Part.VMargin(120.0f, &Part); + + static int s_Button = 0; + if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || m_EscapePressed || m_EnterPressed) + { + Client()->Disconnect(); + m_Popup = POPUP_NONE; + } + + if(Client()->MapDownloadTotalsize() > 0) + { + int64 Now = time_get(); + if(Now-m_DownloadLastCheckTime >= time_freq()) + { + if(m_DownloadLastCheckSize > Client()->MapDownloadAmount()) + { + // map downloaded restarted + m_DownloadLastCheckSize = 0; + } + + // update download speed + float Diff = (Client()->MapDownloadAmount()-m_DownloadLastCheckSize)/((int)((Now-m_DownloadLastCheckTime)/time_freq())); + float StartDiff = m_DownloadLastCheckSize-0.0f; + if(StartDiff+Diff > 0.0f) + m_DownloadSpeed = (Diff/(StartDiff+Diff))*(Diff/1.0f) + (StartDiff/(Diff+StartDiff))*m_DownloadSpeed; + else + m_DownloadSpeed = 0.0f; + m_DownloadLastCheckTime = Now; + m_DownloadLastCheckSize = Client()->MapDownloadAmount(); + } + + Box.HSplitTop(64.f, 0, &Box); + Box.HSplitTop(24.f, &Part, &Box); + str_format(aBuf, sizeof(aBuf), "%d/%d KiB (%.1f KiB/s)", Client()->MapDownloadAmount()/1024, Client()->MapDownloadTotalsize()/1024, m_DownloadSpeed/1024.0f); + UI()->DoLabel(&Part, aBuf, 20.f, 0, -1); + + // time left + int TimeLeft = maximum(1, m_DownloadSpeed > 0.0f ? static_cast((Client()->MapDownloadTotalsize()-Client()->MapDownloadAmount())/m_DownloadSpeed) : 1); + if(TimeLeft >= 60) + { + TimeLeft /= 60; + str_format(aBuf, sizeof(aBuf), TimeLeft == 1 ? Localize("%i minute left") : Localize("%i minutes left"), TimeLeft); + } + else + { + str_format(aBuf, sizeof(aBuf), TimeLeft == 1 ? Localize("%i second left") : Localize("%i seconds left"), TimeLeft); + } + Box.HSplitTop(20.f, 0, &Box); + Box.HSplitTop(24.f, &Part, &Box); + UI()->DoLabel(&Part, aBuf, 20.f, 0, -1); + + // progress bar + Box.HSplitTop(20.f, 0, &Box); + Box.HSplitTop(24.f, &Part, &Box); + Part.VMargin(40.0f, &Part); + RenderTools()->DrawUIRect(&Part, ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 5.0f); + Part.w = maximum(10.0f, (Part.w*Client()->MapDownloadAmount())/Client()->MapDownloadTotalsize()); + RenderTools()->DrawUIRect(&Part, ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), CUI::CORNER_ALL, 5.0f); + } + } + else if(m_Popup == POPUP_LANGUAGE) + { + Box = Screen; + Box.VMargin(150.0f, &Box); + Box.HMargin(150.0f, &Box); + Box.HSplitTop(20.f, &Part, &Box); + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Box.HSplitBottom(20.f, &Box, 0); + Box.VMargin(20.0f, &Box); + RenderLanguageSelection(Box); + Part.VMargin(120.0f, &Part); + + static int s_Button = 0; + if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EscapePressed || m_EnterPressed) + m_Popup = POPUP_FIRST_LAUNCH; + } + else if(m_Popup == POPUP_COUNTRY) + { + Box = Screen; + Box.VMargin(150.0f, &Box); + Box.HMargin(150.0f, &Box); + Box.HSplitTop(20.f, &Part, &Box); + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Box.HSplitBottom(20.f, &Box, 0); + Box.VMargin(20.0f, &Box); + + static int ActSelection = -2; + if(ActSelection == -2) + ActSelection = g_Config.m_BrFilterCountryIndex; + static float s_ScrollValue = 0.0f; + int OldSelected = -1; + UiDoListboxStart(&s_ScrollValue, &Box, 50.0f, Localize("Country"), "", m_pClient->m_pCountryFlags->Num(), 6, OldSelected, s_ScrollValue); + + for(int i = 0; i < m_pClient->m_pCountryFlags->Num(); ++i) + { + const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_pCountryFlags->GetByIndex(i); + if(pEntry->m_CountryCode == ActSelection) + OldSelected = i; + + CListboxItem Item = UiDoListboxNextItem(&pEntry->m_CountryCode, OldSelected == i); + if(Item.m_Visible) + { + CUIRect Label; + Item.m_Rect.Margin(5.0f, &Item.m_Rect); + Item.m_Rect.HSplitBottom(10.0f, &Item.m_Rect, &Label); + float OldWidth = Item.m_Rect.w; + Item.m_Rect.w = Item.m_Rect.h*2; + Item.m_Rect.x += (OldWidth-Item.m_Rect.w)/ 2.0f; + ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f); + m_pClient->m_pCountryFlags->Render(pEntry->m_CountryCode, &Color, Item.m_Rect.x, Item.m_Rect.y, Item.m_Rect.w, Item.m_Rect.h); + UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, 0); + } + } + + const int NewSelected = UiDoListboxEnd(&s_ScrollValue, 0); + if(OldSelected != NewSelected) + ActSelection = m_pClient->m_pCountryFlags->GetByIndex(NewSelected)->m_CountryCode; + + Part.VMargin(120.0f, &Part); + + static int s_Button = 0; + if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Part) || m_EnterPressed) + { + g_Config.m_BrFilterCountryIndex = ActSelection; + Client()->ServerBrowserUpdate(); + m_Popup = POPUP_NONE; + } + + if(m_EscapePressed) + { + ActSelection = g_Config.m_BrFilterCountryIndex; + m_Popup = POPUP_NONE; + } + } + else if(m_Popup == POPUP_DELETE_DEMO) + { + CUIRect Yes, No; + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Part.VMargin(80.0f, &Part); + + Part.VSplitMid(&No, &Yes); + + Yes.VMargin(20.0f, &Yes); + No.VMargin(20.0f, &No); + + static int s_ButtonAbort = 0; + if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) + m_Popup = POPUP_NONE; + + static int s_ButtonTryAgain = 0; + if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) + { + m_Popup = POPUP_NONE; + // delete demo + if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) + { + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_lDemos[m_DemolistSelectedIndex].m_aFilename); + if(Storage()->RemoveFile(aBuf, m_lDemos[m_DemolistSelectedIndex].m_StorageType)) + { + DemolistPopulate(); + DemolistOnUpdate(false); + } + else + PopupMessage(Localize("Error"), Localize("Unable to delete the demo"), Localize("Ok")); + } + } + } + else if(m_Popup == POPUP_RENAME_DEMO) + { + CUIRect Label, TextBox, Ok, Abort; + + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Part.VMargin(80.0f, &Part); + + Part.VSplitMid(&Abort, &Ok); + + Ok.VMargin(20.0f, &Ok); + Abort.VMargin(20.0f, &Abort); + + static int s_ButtonAbort = 0; + if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || m_EscapePressed) + m_Popup = POPUP_NONE; + + static int s_ButtonOk = 0; + if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || m_EnterPressed) + { + m_Popup = POPUP_NONE; + // rename demo + if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) + { + char aBufOld[512]; + str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_lDemos[m_DemolistSelectedIndex].m_aFilename); + int Length = str_length(m_aCurrentDemoFile); + char aBufNew[512]; + if(Length <= 4 || m_aCurrentDemoFile[Length-5] != '.' || str_comp_nocase(m_aCurrentDemoFile+Length-4, "demo")) + str_format(aBufNew, sizeof(aBufNew), "%s/%s.demo", m_aCurrentDemoFolder, m_aCurrentDemoFile); + else + str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_aCurrentDemoFile); + if(Storage()->RenameFile(aBufOld, aBufNew, m_lDemos[m_DemolistSelectedIndex].m_StorageType)) + { + DemolistPopulate(); + DemolistOnUpdate(false); + } + else + PopupMessage(Localize("Error"), Localize("Unable to rename the demo"), Localize("Ok")); + } + } + + Box.HSplitBottom(60.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + + Part.VSplitLeft(60.0f, 0, &Label); + Label.VSplitLeft(120.0f, 0, &TextBox); + TextBox.VSplitLeft(20.0f, 0, &TextBox); + TextBox.VSplitRight(60.0f, &TextBox, 0); + UI()->DoLabel(&Label, Localize("New name:"), 18.0f, -1); + static float Offset = 0.0f; + DoEditBox(&Offset, &TextBox, m_aCurrentDemoFile, sizeof(m_aCurrentDemoFile), 12.0f, &Offset); + } + else if(m_Popup == POPUP_REMOVE_FRIEND) + { + CUIRect Yes, No; + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Part.VMargin(80.0f, &Part); + + Part.VSplitMid(&No, &Yes); + + Yes.VMargin(20.0f, &Yes); + No.VMargin(20.0f, &No); + + static int s_ButtonAbort = 0; + if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || m_EscapePressed) + m_Popup = POPUP_NONE; + + static int s_ButtonTryAgain = 0; + if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || m_EnterPressed) + { + m_Popup = POPUP_NONE; + // remove friend + if(m_FriendlistSelectedIndex >= 0) + { + m_pClient->Friends()->RemoveFriend(m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName, + m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aClan); + FriendlistOnUpdate(); + Client()->ServerBrowserUpdate(); + } + } + } + else if(m_Popup == POPUP_FIRST_LAUNCH) + { + CUIRect Label, TextBox; + + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Part.VMargin(80.0f, &Part); + + static int s_EnterButton = 0; + if(DoButton_Menu(&s_EnterButton, Localize("Enter"), 0, &Part) || m_EnterPressed) + { + Client()->RequestDDNetInfo(); + m_Popup = POPUP_NONE; + } + + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + + Part.VSplitLeft(60.0f, 0, &Part); + if(DoButton_CheckBox(&g_Config.m_BrIndicateFinished, Localize("Show DDNet map finishes in server browser\n(transmits your player name to info.ddnet.tw)"), g_Config.m_BrIndicateFinished, &Part)) + g_Config.m_BrIndicateFinished ^= 1; + + Box.HSplitBottom(24.f, &Box, &Part); + + Part.VSplitLeft(60.0f, 0, &Label); + Label.VSplitLeft(100.0f, 0, &TextBox); + TextBox.VSplitLeft(20.0f, 0, &TextBox); + TextBox.VSplitRight(60.0f, &TextBox, 0); + UI()->DoLabel(&Label, Localize("Nickname"), 18.0f, -1); + static float Offset = 0.0f; + DoEditBox(&g_Config.m_PlayerName, &TextBox, g_Config.m_PlayerName, sizeof(g_Config.m_PlayerName), 12.0f, &Offset); + } + else + { + Box.HSplitBottom(20.f, &Box, &Part); + Box.HSplitBottom(24.f, &Box, &Part); + Part.VMargin(120.0f, &Part); + + static int s_Button = 0; + if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || m_EscapePressed || m_EnterPressed) + { + if(m_Popup == POPUP_DISCONNECTED && Client()->m_ReconnectTime > 0) + Client()->m_ReconnectTime = 0; + m_Popup = POPUP_NONE; + } + } + + if(m_Popup == POPUP_NONE) + UI()->SetActiveItem(0); + } + return 0; +} + + +void CMenus::SetActive(bool Active) +{ + if(Active != m_MenuActive) + Input()->SetIMEState(Active); + m_MenuActive = Active; + if(!m_MenuActive) + { + if(m_NeedSendinfo) + { + m_pClient->SendInfo(false); + m_NeedSendinfo = false; + } + + if(m_NeedSendDummyinfo) + { + m_pClient->SendDummyInfo(false); + m_NeedSendDummyinfo = false; + } + + if(Client()->State() == IClient::STATE_ONLINE) + { + m_pClient->OnRelease(); + } + } + else if(Client()->State() == IClient::STATE_DEMOPLAYBACK) + { + m_pClient->OnRelease(); + } +} + +void CMenus::OnReset() +{ +} + +bool CMenus::OnMouseMove(float x, float y) +{ + m_LastInput = time_get(); + + if(!m_MenuActive) + return false; + + UI()->ConvertMouseMove(&x, &y); + if(m_MouseSlow) + { + m_MousePos.x += x * 0.05f; + m_MousePos.y += y * 0.05f; + } + else + { + m_MousePos.x += x; + m_MousePos.y += y; + } + m_MousePos.x = clamp(m_MousePos.x, 0.f, (float)Graphics()->ScreenWidth()); + m_MousePos.y = clamp(m_MousePos.y, 0.f, (float)Graphics()->ScreenHeight()); + + return true; +} + +bool CMenus::OnInput(IInput::CEvent e) +{ + m_LastInput = time_get(); + + // special handle esc and enter for popup purposes + if(e.m_Flags&IInput::FLAG_PRESS) + { + if(e.m_Key == KEY_ESCAPE) + { + m_EscapePressed = true; + if(m_Popup == POPUP_NONE) + SetActive(!IsActive()); + return true; + } + } + + if(IsActive()) + { + if(e.m_Flags&IInput::FLAG_PRESS) + { + // special for popups + if(e.m_Key == KEY_RETURN || e.m_Key == KEY_KP_ENTER) + m_EnterPressed = true; + else if(e.m_Key == KEY_DELETE) + m_DeletePressed = true; + } + + if(m_NumInputEvents < MAX_INPUTEVENTS) + m_aInputEvents[m_NumInputEvents++] = e; + return true; + } + return false; +} + +void CMenus::OnStateChange(int NewState, int OldState) +{ + // reset active item + UI()->SetActiveItem(0); + + if(NewState == IClient::STATE_OFFLINE) + { + if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITING) + m_pClient->m_pSounds->Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f); + m_Popup = POPUP_NONE; + if(Client()->ErrorString() && Client()->ErrorString()[0] != 0) + { + if(str_find(Client()->ErrorString(), "password")) + { + m_Popup = POPUP_PASSWORD; + UI()->SetHotItem(&g_Config.m_Password); + UI()->SetActiveItem(&g_Config.m_Password); + } + else + m_Popup = POPUP_DISCONNECTED; + } + } + else if(NewState == IClient::STATE_LOADING) + { + m_Popup = POPUP_CONNECTING; + m_DownloadLastCheckTime = time_get(); + m_DownloadLastCheckSize = 0; + m_DownloadSpeed = 0.0f; + } + else if(NewState == IClient::STATE_CONNECTING) + m_Popup = POPUP_CONNECTING; + else if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK) + { + m_Popup = POPUP_NONE; + SetActive(false); + } +} + +extern "C" void font_debug_render(); + +void CMenus::OnRender() +{ + if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) + SetActive(true); + + if(Client()->State() == IClient::STATE_DEMOPLAYBACK) + { + CUIRect Screen = *UI()->Screen(); + Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); + RenderDemoPlayer(Screen); + } + + if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == m_pClient->SERVERMODE_PUREMOD) + { + Client()->Disconnect(); + SetActive(true); + m_Popup = POPUP_PURE; + } + + if(!IsActive()) + { + m_EscapePressed = false; + m_EnterPressed = false; + m_DeletePressed = false; + m_NumInputEvents = 0; + return; + } + + // update colors + ms_GuiColor = color_cast(ColorHSLA(g_Config.m_UiColor, true)); + + ms_ColorTabbarInactiveOutgame = ColorRGBA(0,0,0,0.25f); + ms_ColorTabbarActiveOutgame = ColorRGBA(0,0,0,0.5f); + + float ColorIngameScaleI = 0.5f; + float ColorIngameAcaleA = 0.2f; + ms_ColorTabbarInactiveIngame = ColorRGBA( + ms_GuiColor.r*ColorIngameScaleI, + ms_GuiColor.g*ColorIngameScaleI, + ms_GuiColor.b*ColorIngameScaleI, + ms_GuiColor.a*0.8f); + + ms_ColorTabbarActiveIngame = ColorRGBA( + ms_GuiColor.r*ColorIngameAcaleA, + ms_GuiColor.g*ColorIngameAcaleA, + ms_GuiColor.b*ColorIngameAcaleA, + ms_GuiColor.a); + + // update the ui + CUIRect *pScreen = UI()->Screen(); + float mx = (m_MousePos.x/(float)Graphics()->ScreenWidth())*pScreen->w; + float my = (m_MousePos.y/(float)Graphics()->ScreenHeight())*pScreen->h; + + int Buttons = 0; + if(m_UseMouseButtons) + { + if(Input()->KeyIsPressed(KEY_MOUSE_1)) Buttons |= 1; + if(Input()->KeyIsPressed(KEY_MOUSE_2)) Buttons |= 2; + if(Input()->KeyIsPressed(KEY_MOUSE_3)) Buttons |= 4; + } + + UI()->Update(mx,my,mx*3.0f,my*3.0f,Buttons); + + // render + if(Client()->State() != IClient::STATE_DEMOPLAYBACK) + Render(); + + // render cursor + Graphics()->TextureSet(g_pData->m_aImages[IMAGE_CURSOR].m_Id); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1,1,1,1); + IGraphics::CQuadItem QuadItem(mx, my, 24, 24); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + + // render debug information + if(g_Config.m_Debug) + { + CUIRect Screen = *UI()->Screen(); + Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); + + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "%p %p %p", UI()->HotItem(), UI()->ActiveItem(), UI()->LastActiveItem()); + CTextCursor Cursor; + TextRender()->SetCursor(&Cursor, 10, 10, 10, TEXTFLAG_RENDER); + TextRender()->TextEx(&Cursor, aBuf, -1); + } + + m_EscapePressed = false; + m_EnterPressed = false; + m_DeletePressed = false; + m_NumInputEvents = 0; +} + +static int gs_TextureBlob = -1; + +void CMenus::RenderBackground() +{ + if(gs_TextureBlob == -1) + gs_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0); + + + float sw = 300*Graphics()->ScreenAspect(); + float sh = 300; + Graphics()->MapScreen(0, 0, sw, sh); + + // render background color + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + ColorRGBA Bottom(ms_GuiColor.r, ms_GuiColor.g, ms_GuiColor.b, 1.0f); + ColorRGBA Top(ms_GuiColor.r, ms_GuiColor.g, ms_GuiColor.b, 1.0f); + IGraphics::CColorVertex Array[4] = { + IGraphics::CColorVertex(0, Top.r, Top.g, Top.b, Top.a), + IGraphics::CColorVertex(1, Top.r, Top.g, Top.b, Top.a), + IGraphics::CColorVertex(2, Bottom.r, Bottom.g, Bottom.b, Bottom.a), + IGraphics::CColorVertex(3, Bottom.r, Bottom.g, Bottom.b, Bottom.a)}; + Graphics()->SetColorVertex(Array, 4); + IGraphics::CQuadItem QuadItem(0, 0, sw, sh); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + + // render the tiles + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + float Size = 15.0f; + float OffsetTime = fmod(Client()->LocalTime()*0.15f, 2.0f); + for(int y = -2; y < (int)(sw/Size); y++) + for(int x = -2; x < (int)(sh/Size); x++) + { + Graphics()->SetColor(0,0,0,0.045f); + IGraphics::CQuadItem QuadItem((x-OffsetTime)*Size*2+(y&1)*Size, (y+OffsetTime)*Size, Size, Size); + Graphics()->QuadsDrawTL(&QuadItem, 1); + } + Graphics()->QuadsEnd(); + + // render border fade + Graphics()->TextureSet(gs_TextureBlob); + Graphics()->QuadsBegin(); + Graphics()->SetColor(1,1,1,1); + QuadItem = IGraphics::CQuadItem(-100, -100, sw+200, sh+200); + Graphics()->QuadsDrawTL(&QuadItem, 1); + Graphics()->QuadsEnd(); + + // restore screen + {CUIRect Screen = *UI()->Screen(); + Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h);} +} + +int CMenus::DoButton_CheckBox_DontCare(const void *pID, const char *pText, int Checked, const CUIRect *pRect) +{ + switch(Checked) + { + case 0: + return DoButton_CheckBox_Common(pID, pText, "", pRect); + case 1: + return DoButton_CheckBox_Common(pID, pText, "X", pRect); + case 2: + return DoButton_CheckBox_Common(pID, pText, "O", pRect); + default: + return DoButton_CheckBox_Common(pID, pText, "", pRect); + } +} + +void CMenus::RenderUpdating(const char *pCaption, int current, int total) +{ + // make sure that we don't render for each little thing we load + // because that will slow down loading if we have vsync + static int64 LastLoadRender = 0; + if(time_get()-LastLoadRender < time_freq()/60) + return; + LastLoadRender = time_get(); + + // need up date this here to get correct + ms_GuiColor = color_cast(ColorHSLA(g_Config.m_UiColor, true)); + + CUIRect Screen = *UI()->Screen(); + Graphics()->MapScreen(Screen.x, Screen.y, Screen.w, Screen.h); + + Graphics()->BlendNormal(); + RenderBackground(); + + float w = 700; + float h = 200; + float x = Screen.w/2-w/2; + float y = Screen.h/2-h/2; + + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0,0,0,0.50f); + RenderTools()->DrawRoundRect(0, y, Screen.w, h, 0.0f); + Graphics()->QuadsEnd(); + + CUIRect r; + r.x = x; + r.y = y+20; + r.w = w; + r.h = h; + UI()->DoLabel(&r, Localize(pCaption), 32.0f, 0, -1); + + if(total>0) + { + float Percent = current/(float)total; + Graphics()->TextureSet(-1); + Graphics()->QuadsBegin(); + Graphics()->SetColor(0.15f,0.15f,0.15f,0.75f); + RenderTools()->DrawRoundRect(x+40, y+h-75, w-80, 30, 5.0f); + Graphics()->SetColor(1,1,1,0.75f); + RenderTools()->DrawRoundRect(x+45, y+h-70, (w-85)*Percent, 20, 5.0f); + Graphics()->QuadsEnd(); + } + + Graphics()->Swap(); +} diff --git a/build/enc_temp_folder/da59d8b44e17c6aea1909b3c822a50/client.h b/build/enc_temp_folder/da59d8b44e17c6aea1909b3c822a50/client.h new file mode 100644 index 000000000..fc46fd541 --- /dev/null +++ b/build/enc_temp_folder/da59d8b44e17c6aea1909b3c822a50/client.h @@ -0,0 +1,425 @@ +/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ +/* If you are missing that file, acquire a complete release at teeworlds.com. */ +#ifndef ENGINE_CLIENT_CLIENT_H +#define ENGINE_CLIENT_CLIENT_H + +#include + +#include +#include + +#define CONNECTLINK "ddnet:" + +class CGraph +{ +public: + enum + { + // restrictions: Must be power of two + MAX_VALUES=128, + }; + + float m_Min, m_Max; + float m_aValues[MAX_VALUES]; + float m_aColors[MAX_VALUES][3]; + int m_Index; + + void Init(float Min, float Max); + + void ScaleMax(); + void ScaleMin(); + + void Add(float v, float r, float g, float b); + void Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription); +}; + + +class CSmoothTime +{ + int64 m_Snap; + int64 m_Current; + int64 m_Target; + + CGraph m_Graph; + + int m_SpikeCounter; + + float m_aAdjustSpeed[2]; // 0 = down, 1 = up +public: + void Init(int64 Target); + void SetAdjustSpeed(int Direction, float Value); + + int64 Get(int64 Now); + + void UpdateInt(int64 Target); + void Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection); +}; + + +class CClient : public IClient, public CDemoPlayer::IListener +{ + // needed interfaces + IEngine *m_pEngine; + IEditor *m_pEditor; + IEngineInput *m_pInput; + IEngineGraphics *m_pGraphics; + IEngineSound *m_pSound; + IGameClient *m_pGameClient; + IEngineMap *m_pMap; + IConsole *m_pConsole; + IStorage *m_pStorage; + IUpdater *m_pUpdater; + IEngineMasterServer *m_pMasterServer; + + enum + { + NUM_SNAPSHOT_TYPES=2, + PREDICTION_MARGIN=1000/50/2, // magic network prediction value + }; + + class CNetClient m_NetClient[3]; + class CDemoPlayer m_DemoPlayer; + class CDemoRecorder m_DemoRecorder[RECORDER_MAX]; + class CDemoEditor m_DemoEditor; + class CGhostRecorder m_GhostRecorder; + class CGhostLoader m_GhostLoader; + class CServerBrowser m_ServerBrowser; + class CUpdater m_Updater; + class CFriends m_Friends; + class CFriends m_Foes; + + char m_aServerAddressStr[256]; + + unsigned m_SnapshotParts[2]; + int64 m_LocalStartTime; + + int m_DebugFont; + int replayCounter = 0; + + int64 m_LastRenderTime; + float m_RenderFrameTimeLow; + float m_RenderFrameTimeHigh; + int m_RenderFrames; + + NETADDR m_ServerAddress; + int m_SnapCrcErrors; + bool m_AutoScreenshotRecycle; + bool m_AutoStatScreenshotRecycle; + bool m_AutoCSVRecycle; + bool m_EditorActive; + bool m_SoundInitFailed; + bool m_ResortServerBrowser; + + int m_AckGameTick[2]; + int m_CurrentRecvTick[2]; + int m_RconAuthed[2]; + char m_RconPassword[32]; + int m_UseTempRconCommands; + char m_Password[32]; + bool m_SendPassword; + + // version-checking + char m_aVersionStr[10]; + + // pinging + int64 m_PingStartTime; + + char m_aCurrentMap[MAX_PATH_LENGTH]; + char m_aCurrentMapPath[MAX_PATH_LENGTH]; + + char m_aTimeoutCodes[2][32]; + bool m_aTimeoutCodeSent[2]; + bool m_GenerateTimeoutSeed; + + // + char m_aCmdConnect[256]; + char m_aCmdPlayDemo[MAX_PATH_LENGTH]; + + // map download + std::shared_ptr m_pMapdownloadTask; + char m_aMapdownloadFilename[256]; + char m_aMapdownloadName[256]; + IOHANDLE m_MapdownloadFile; + int m_MapdownloadChunk; + int m_MapdownloadCrc; + int m_MapdownloadAmount; + int m_MapdownloadTotalsize; + bool m_MapdownloadSha256Present; + SHA256_DIGEST m_MapdownloadSha256; + + bool m_MapDetailsPresent; + char m_aMapDetailsName[256]; + int m_MapDetailsCrc; + SHA256_DIGEST m_MapDetailsSha256; + + std::shared_ptr m_pDDNetInfoTask; + + // time + CSmoothTime m_GameTime[2]; + CSmoothTime m_PredictedTime; + + // input + struct // TODO: handle input better + { + int m_aData[MAX_INPUT_SIZE]; // the input data + int m_Tick; // the tick that the input is for + int64 m_PredictedTime; // prediction latency when we sent this input + int64 m_Time; + } m_aInputs[2][200]; + + int m_CurrentInput[2]; + bool m_LastDummy; + bool m_LastDummy2; + bool m_DummySendConnInfo; + + // graphs + CGraph m_InputtimeMarginGraph; + CGraph m_GametimeMarginGraph; + CGraph m_FpsGraph; + + // the game snapshots are modifiable by the game + class CSnapshotStorage m_SnapshotStorage[2]; + CSnapshotStorage::CHolder *m_aSnapshots[2][NUM_SNAPSHOT_TYPES]; + + int m_ReceivedSnapshots[2]; + char m_aSnapshotIncomingData[CSnapshot::MAX_SIZE]; + + class CSnapshotStorage::CHolder m_aDemorecSnapshotHolders[NUM_SNAPSHOT_TYPES]; + char *m_aDemorecSnapshotData[NUM_SNAPSHOT_TYPES][2][CSnapshot::MAX_SIZE]; + + class CSnapshotDelta m_SnapshotDelta; + + // + class CServerInfo m_CurrentServerInfo; + int64 m_CurrentServerInfoRequestTime; // >= 0 should request, == -1 got info + + // version info + struct CVersionInfo + { + enum + { + STATE_INIT=0, + STATE_START, + STATE_READY, + }; + + int m_State; + class CHostLookup m_VersionServeraddr; + } m_VersionInfo; + + volatile int m_GfxState; + static void GraphicsThreadProxy(void *pThis) { ((CClient*)pThis)->GraphicsThread(); } + void GraphicsThread(); + +#if defined(CONF_FAMILY_UNIX) + CFifo m_Fifo; +#endif + +public: + IEngine *Engine() { return m_pEngine; } + IEngineGraphics *Graphics() { return m_pGraphics; } + IEngineInput *Input() { return m_pInput; } + IEngineSound *Sound() { return m_pSound; } + IGameClient *GameClient() { return m_pGameClient; } + IEngineMasterServer *MasterServer() { return m_pMasterServer; } + IStorage *Storage() { return m_pStorage; } + IUpdater *Updater() { return m_pUpdater; } + + CClient(); + + // ----- send functions ----- + virtual int SendMsg(CMsgPacker *pMsg, int Flags); + virtual int SendMsgExY(CMsgPacker *pMsg, int Flags, bool System=true, int NetClient=1); + + int SendMsgEx(CMsgPacker *pMsg, int Flags, bool System=true); + void SendInfo(); + void SendEnterGame(); + void SendReady(); + void SendMapRequest(); + + virtual bool RconAuthed() { return m_RconAuthed[g_Config.m_ClDummy] != 0; } + virtual bool UseTempRconCommands() { return m_UseTempRconCommands != 0; } + void RconAuth(const char *pName, const char *pPassword); + virtual void Rcon(const char *pCmd); + + virtual bool ConnectionProblems(); + + virtual bool SoundInitFailed() { return m_SoundInitFailed; } + + virtual int GetDebugFont() { return m_DebugFont; } + + void DirectInput(int *pInput, int Size); + void SendInput(); + + // TODO: OPT: do this a lot smarter! + virtual int *GetInput(int Tick); + virtual int *GetDirectInput(int Tick); + + const char *LatestVersion(); + + // ------ state handling ----- + void SetState(int s); + + // called when the map is loaded and we should init for a new round + void OnEnterGame(); + virtual void EnterGame(); + + virtual void Connect(const char *pAddress, const char *pPassword = NULL); + void DisconnectWithReason(const char *pReason); + virtual void Disconnect(); + + virtual void DummyDisconnect(const char *pReason); + virtual void DummyConnect(); + virtual bool DummyConnected(); + virtual bool DummyConnecting(); + int m_DummyConnected; + int m_LastDummyConnectTime; + + virtual void GetServerInfo(CServerInfo *pServerInfo); + void ServerInfoRequest(); + + int LoadData(); + + // --- + + int GetPredictionTime(); + void *SnapGetItem(int SnapID, int Index, CSnapItem *pItem); + void SnapInvalidateItem(int SnapID, int Index); + void *SnapFindItem(int SnapID, int Type, int ID); + int SnapNumItems(int SnapID); + void SnapSetStaticsize(int ItemType, int Size); + + void Render(); + void DebugRender(); + + virtual void Restart(); + virtual void Quit(); + + virtual const char *ErrorString(); + + const char *LoadMap(const char *pName, const char *pFilename, SHA256_DIGEST *pWantedSha256, unsigned WantedCrc); + const char *LoadMapSearch(const char *pMapName, SHA256_DIGEST *pWantedSha256, int WantedCrc); + + static int PlayerScoreNameComp(const void *a, const void *b); + + void ProcessConnlessPacket(CNetChunk *pPacket); + void ProcessServerInfo(int Type, NETADDR *pFrom, const void *pData, int DataSize); + void ProcessServerPacket(CNetChunk *pPacket); + void ProcessServerPacketDummy(CNetChunk *pPacket); + + void ResetMapDownload(); + void FinishMapDownload(); + + void RequestDDNetInfo(); + void ResetDDNetInfo(); + void FinishDDNetInfo(); + void LoadDDNetInfo(); + + virtual const char *MapDownloadName() { return m_aMapdownloadName; } + virtual int MapDownloadAmount() { return !m_pMapdownloadTask ? m_MapdownloadAmount : (int)m_pMapdownloadTask->Current(); } + virtual int MapDownloadTotalsize() { return !m_pMapdownloadTask ? m_MapdownloadTotalsize : (int)m_pMapdownloadTask->Size(); } + + void PumpNetwork(); + + virtual void OnDemoPlayerSnapshot(void *pData, int Size); + virtual void OnDemoPlayerMessage(void *pData, int Size); + + void Update(); + + void RegisterInterfaces(); + void InitInterfaces(); + + void Run(); + + bool CtrlShiftKey(int Key, bool &Last); + + static void Con_Connect(IConsole::IResult *pResult, void *pUserData); + static void Con_Disconnect(IConsole::IResult *pResult, void *pUserData); + + static void Con_DummyConnect(IConsole::IResult *pResult, void *pUserData); + static void Con_DummyDisconnect(IConsole::IResult *pResult, void *pUserData); + + static void Con_Quit(IConsole::IResult *pResult, void *pUserData); + static void Con_DemoPlay(IConsole::IResult *pResult, void *pUserData); + static void Con_DemoSpeed(IConsole::IResult *pResult, void *pUserData); + static void Con_Minimize(IConsole::IResult *pResult, void *pUserData); + static void Con_Ping(IConsole::IResult *pResult, void *pUserData); + static void Con_Screenshot(IConsole::IResult *pResult, void *pUserData); + static void Con_Rcon(IConsole::IResult *pResult, void *pUserData); + static void Con_RconAuth(IConsole::IResult *pResult, void *pUserData); + static void Con_RconLogin(IConsole::IResult *pResult, void *pUserData); + static void Con_AddFavorite(IConsole::IResult *pResult, void *pUserData); + static void Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData); + static void Con_Play(IConsole::IResult *pResult, void *pUserData); + static void Con_Record(IConsole::IResult *pResult, void *pUserData); + static void Con_StopRecord(IConsole::IResult *pResult, void *pUserData); + static void Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData); + static void ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainFullscreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainWindowBordered(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainWindowScreen(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainWindowVSync(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainTimeoutSeed(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + static void ConchainPassword(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + + static void Con_DemoSlice(IConsole::IResult *pResult, void *pUserData); + static void Con_DemoSliceBegin(IConsole::IResult *pResult, void *pUserData); + static void Con_DemoSliceEnd(IConsole::IResult *pResult, void *pUserData); + static void Con_SaveReplay(IConsole::IResult *pResult, void *pUserData); + + void RegisterCommands(); + + const char *DemoPlayer_Play(const char *pFilename, int StorageType); + void DemoRecorder_Start(const char *pFilename, bool WithTimestamp, int Recorder); + void DemoRecorder_HandleAutoStart(); + void DemoRecorder_Stop(int Recorder); + void DemoRecorder_AddDemoMarker(int Recorder); + class IDemoRecorder *DemoRecorder(int Recorder); + + void AutoScreenshot_Start(); + void AutoStatScreenshot_Start(); + void AutoScreenshot_Cleanup(); + void AutoStatScreenshot_Cleanup(); + + void AutoCSV_Start(); + void AutoCSV_Cleanup(); + + void ServerBrowserUpdate(); + + void HandleConnectLink(const char *pLink); + void HandleDemoPath(const char *pPath); + + // gfx + void SwitchWindowScreen(int Index); + void ToggleFullscreen(); + void ToggleWindowBordered(); + void ToggleWindowVSync(); + void LoadFont(); + + // DDRace + + void GenerateTimeoutSeed(); + void GenerateTimeoutCodes(); + + virtual int GetCurrentRaceTime(); + + const char *GetCurrentMap(); + const char *GetCurrentMapPath(); + unsigned GetMapCrc(); + + void RaceRecord_Start(const char *pFilename); + void RaceRecord_Stop(); + bool RaceRecord_IsRecording(); + + virtual void DemoSliceBegin(); + virtual void DemoSliceEnd(); + virtual void DemoSlice(const char *pDstPath, CLIENTFUNC_FILTER pfnFilter, void *pUser); + virtual void SaveReplay(); + + bool EditorHasUnsavedData() { return m_pEditor->HasUnsavedData(); } + + virtual IFriends* Foes() {return &m_Foes; } + + void GetSmoothTick(int *pSmoothTick, float *pSmoothIntraTick, float MixAmount); +}; +#endif diff --git a/src/engine/client.h b/src/engine/client.h index 1f77f8774..e6a35039b 100644 --- a/src/engine/client.h +++ b/src/engine/client.h @@ -13,7 +13,8 @@ enum RECORDER_MANUAL=0, RECORDER_AUTO=1, RECORDER_RACE=2, - RECORDER_MAX=3, + RECORDER_REPLAYS=3, + RECORDER_MAX=4, }; typedef bool (*CLIENTFUNC_FILTER)(const void *pData, int DataSize, void *pUser); diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 1b5a511c5..d06a2a6bd 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -263,9 +263,9 @@ void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustD CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta) { - m_DemoRecorder[0] = CDemoRecorder(&m_SnapshotDelta); - m_DemoRecorder[1] = CDemoRecorder(&m_SnapshotDelta); - m_DemoRecorder[2] = CDemoRecorder(&m_SnapshotDelta); + for (int i = 0; i < RECORDER_MAX; i++) { + m_DemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta); + } m_pEditor = 0; m_pInput = 0; @@ -681,8 +681,6 @@ void CClient::Connect(const char *pAddress, const char *pPassword) char aBuf[512]; int Port = 8303; - replayCounter = 0; - Disconnect(); str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr)); @@ -784,7 +782,7 @@ void CClient::Disconnect() // make sure to remove replay tmp demo if(g_Config.m_ClRaceReplays) { - Storage()->RemoveFile((&m_DemoRecorder[RECORDER_AUTO])->GetCurrentFilename(), IStorage::TYPE_SAVE); + Storage()->RemoveFile((&m_DemoRecorder[RECORDER_REPLAYS])->GetCurrentFilename(), IStorage::TYPE_SAVE); } } @@ -3295,35 +3293,26 @@ void CClient::SaveReplay() { m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", "Feature is disabled. Please enabled it via the configuration"); } else { - if(!DemoRecorder(RECORDER_AUTO)->IsRecording()) + if(!DemoRecorder(RECORDER_REPLAYS)->IsRecording()) { m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", "Error: demorecorder isn't recording. Try to rejoin to fix that."); } else { - //DemoRecorder_HandleAutoStart(); - DemoRecorder_Stop(RECORDER_AUTO); - replayCounter++; - char aFilename[256]; char aDate[64]; str_timestamp(aDate, sizeof(aDate)); str_format(aFilename, sizeof(aFilename), "demos/replays/%s_%s (replay).demo", m_aCurrentMap, aDate); - char *pSrc = (&m_DemoRecorder[RECORDER_AUTO])->GetCurrentFilename(); + char *pSrc = (&m_DemoRecorder[RECORDER_REPLAYS])->GetCurrentFilename(); - - // Slice the demo to get only the last 30 seconds + // Slice the demo to get only the last cl_replay_length seconds m_DemoEditor.Slice(pSrc, aFilename, GameTick() - g_Config.m_ClReplayLength * GameTickSpeed(), GameTick(), NULL, 0); - Storage()->RemoveFile(pSrc, IStorage::TYPE_SAVE); - char aBuf[256]; str_format(aBuf, sizeof(aBuf), "Successfully saved the replay to %s !", aFilename); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "replay", aBuf); - - DemoRecorder_HandleAutoStart(); } } } @@ -3451,12 +3440,12 @@ void CClient::DemoRecorder_HandleAutoStart() AutoDemos.Init(Storage(), "demos/auto", "" /* empty for wild card */, ".demo", g_Config.m_ClAutoDemoMax); } } - else if(g_Config.m_ClRaceReplays) + if(g_Config.m_ClRaceReplays) { - DemoRecorder_Stop(RECORDER_AUTO); + DemoRecorder_Stop(RECORDER_REPLAYS); char aBuf[512]; str_format(aBuf, sizeof(aBuf), "replays/replay-%s", m_aCurrentMap); - DemoRecorder_Start(aBuf, true, RECORDER_AUTO); + DemoRecorder_Start(aBuf, true, RECORDER_REPLAYS); } //TODO @@ -3498,6 +3487,7 @@ void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData) pSelf->DemoRecorder_AddDemoMarker(RECORDER_MANUAL); pSelf->DemoRecorder_AddDemoMarker(RECORDER_RACE); pSelf->DemoRecorder_AddDemoMarker(RECORDER_AUTO); + pSelf->DemoRecorder_AddDemoMarker(RECORDER_REPLAYS); } void CClient::ServerBrowserUpdate() diff --git a/src/engine/client/client.h b/src/engine/client/client.h index fc46fd541..2fced01a1 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -94,7 +94,6 @@ class CClient : public IClient, public CDemoPlayer::IListener int64 m_LocalStartTime; int m_DebugFont; - int replayCounter = 0; int64 m_LastRenderTime; float m_RenderFrameTimeLow; diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index c67cc7ec3..061fc0ba5 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -1159,12 +1159,6 @@ int CMenus::Render() pButtonText = Localize("Ok"); ExtraAlign = -1; } - else if(m_Popup == POPUP_REPLAY_ENABLING_ERROR) - { - pTitle = Localize("Error enabling replays"); - pExtraText = Localize("As the replay system uses the demo recorder system, no other record options should be used along with replays. Please disable any other option if you want to enable replays."); - ExtraAlign = -1; - } CUIRect Box, Part; Box = Screen; @@ -1267,18 +1261,6 @@ int CMenus::Render() m_Popup = POPUP_NONE; } } - else if (m_Popup == POPUP_REPLAY_ENABLING_ERROR) - { - CUIRect Ok; - Box.HSplitBottom(20.f, &Box, &Part); - Box.HSplitBottom(24.f, &Box, &Part); - - Part.VMargin(40.0f, &Ok); - - static int s_ButtonOk = 0; - if (DoButton_Menu(&s_ButtonOk, Localize("Got it"), 0, &Ok) || m_EscapePressed) - m_Popup = POPUP_NONE; - } else if(m_Popup == POPUP_PASSWORD) { CUIRect Label, TextBox, TryAgain, Abort; diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 7efe0ed66..1109c5cbf 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -402,7 +402,6 @@ public: POPUP_QUIT, POPUP_DISCONNECT, POPUP_DISCONNECT_DUMMY, - POPUP_REPLAY_ENABLING_ERROR, // demo player states DEMOPLAYER_NONE=0, diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index bf497187b..b8eab91da 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -2000,14 +2000,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView) Left.HSplitTop(20.0f, &Button, &Left); if(DoButton_CheckBox(&g_Config.m_ClRaceReplays, Localize("Enable replays"), g_Config.m_ClRaceReplays, &Button)) { - if(g_Config.m_ClAutoDemoRecord && !g_Config.m_ClRaceReplays) - { - m_Popup = POPUP_REPLAY_ENABLING_ERROR; - } - else - { - g_Config.m_ClRaceReplays ^= 1; - } + g_Config.m_ClRaceReplays ^= 1; } diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index e7f28ae31..aad37a0f2 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -525,7 +525,8 @@ void CScoreboard::RenderRecordingNotification(float x) { if(!m_pClient->DemoRecorder(RECORDER_MANUAL)->IsRecording() && !m_pClient->DemoRecorder(RECORDER_AUTO)->IsRecording() && - !m_pClient->DemoRecorder(RECORDER_RACE)->IsRecording()) + !m_pClient->DemoRecorder(RECORDER_RACE)->IsRecording() && + !m_pClient->DemoRecorder(RECORDER_REPLAYS)->IsRecording()) { return; } @@ -553,6 +554,12 @@ void CScoreboard::RenderRecordingNotification(float x) str_format(aBuf2, sizeof(aBuf2), Localize("Auto %3d:%02d "), Seconds/60, Seconds%60); str_append(aBuf, aBuf2, sizeof(aBuf)); } + if (m_pClient->DemoRecorder(RECORDER_REPLAYS)->IsRecording()) + { + Seconds = m_pClient->DemoRecorder(RECORDER_REPLAYS)->Length(); + str_format(aBuf2, sizeof(aBuf2), Localize("Replay %3d:%02d "), Seconds / 60, Seconds % 60); + str_append(aBuf, aBuf2, sizeof(aBuf)); + } float w = TextRender()->TextWidth(0, 20.0f, aBuf, -1);