ddnet/src/game/client/components/menu_background.cpp
Robert Müller 6633e9af1d Add button to reload entities background, improve DDNet menu layout
Add a button to manually reload the entities background map instead of automatically reloading it when it's changed. As the background map was only reloaded every 10 seconds, sometimes changing the background map quickly had no effect.

Improve the layout of the DDNet settings menu. Align labels and UI elements and reduce unused empty space.
2023-08-19 17:44:58 +02:00

419 lines
12 KiB
C++

#include <base/system.h>
#include <algorithm>
#include <engine/graphics.h>
#include <engine/map.h>
#include <engine/shared/config.h>
#include <game/client/components/camera.h>
#include <game/client/components/mapimages.h>
#include <game/client/components/maplayers.h>
#include <game/layers.h>
#include <game/mapitems.h>
#include "menu_background.h"
#include <chrono>
using namespace std::chrono_literals;
std::array<vec2, CMenuBackground::NUM_POS> GenerateMenuBackgroundPositions()
{
std::array<vec2, CMenuBackground::NUM_POS> Positions;
Positions[CMenuBackground::POS_START] = vec2(500.0f, 500.0f);
Positions[CMenuBackground::POS_BROWSER_INTERNET] = vec2(1000.0f, 1000.0f);
Positions[CMenuBackground::POS_BROWSER_LAN] = vec2(1100.0f, 1000.0f);
Positions[CMenuBackground::POS_DEMOS] = vec2(900.0f, 100.0f);
Positions[CMenuBackground::POS_NEWS] = vec2(500.0f, 750.0f);
Positions[CMenuBackground::POS_BROWSER_FAVORITES] = vec2(1250.0f, 500.0f);
Positions[CMenuBackground::POS_SETTINGS_LANGUAGE] = vec2(500.0f, 1200.0f);
Positions[CMenuBackground::POS_SETTINGS_GENERAL] = vec2(500.0f, 1000.0f);
Positions[CMenuBackground::POS_SETTINGS_PLAYER] = vec2(600.0f, 1000.0f);
Positions[CMenuBackground::POS_SETTINGS_TEE] = vec2(700.0f, 1000.0f);
Positions[CMenuBackground::POS_SETTINGS_APPEARANCE] = vec2(200.0f, 1000.0f);
Positions[CMenuBackground::POS_SETTINGS_CONTROLS] = vec2(800.0f, 1000.0f);
Positions[CMenuBackground::POS_SETTINGS_GRAPHICS] = vec2(900.0f, 1000.0f);
Positions[CMenuBackground::POS_SETTINGS_SOUND] = vec2(1000.0f, 1000.0f);
Positions[CMenuBackground::POS_SETTINGS_DDNET] = vec2(1200.0f, 200.0f);
Positions[CMenuBackground::POS_SETTINGS_ASSETS] = vec2(500.0f, 500.0f);
for(int i = 0; i < CMenuBackground::POS_BROWSER_CUSTOM_NUM; ++i)
Positions[CMenuBackground::POS_BROWSER_CUSTOM0 + i] = vec2(500.0f + (75.0f * (float)i), 650.0f - (75.0f * (float)i));
for(int i = 0; i < CMenuBackground::POS_SETTINGS_RESERVED_NUM; ++i)
Positions[CMenuBackground::POS_SETTINGS_RESERVED0 + i] = vec2(0, 0);
for(int i = 0; i < CMenuBackground::POS_RESERVED_NUM; ++i)
Positions[CMenuBackground::POS_RESERVED0 + i] = vec2(0, 0);
return Positions;
}
CMenuBackground::CMenuBackground() :
CBackground(CMapLayers::TYPE_FULL_DESIGN, false)
{
m_ChangedPosition = false;
ResetPositions();
m_CurrentPosition = -1;
m_MoveTime = 0.0f;
m_IsInit = false;
}
CBackgroundEngineMap *CMenuBackground::CreateBGMap()
{
return new CMenuMap;
}
void CMenuBackground::OnInit()
{
m_pBackgroundMap = CreateBGMap();
m_pMap = m_pBackgroundMap;
m_IsInit = true;
m_pImages->m_pClient = GameClient();
Kernel()->RegisterInterface<CMenuMap>((CMenuMap *)m_pBackgroundMap);
if(g_Config.m_ClMenuMap[0] != '\0')
LoadMenuBackground();
m_Camera.m_pClient = GameClient();
m_Camera.m_ZoomSet = false;
m_Camera.m_ZoomSmoothingTarget = 0;
}
void CMenuBackground::ResetPositions()
{
m_aPositions = GenerateMenuBackgroundPositions();
}
int CMenuBackground::ThemeScan(const char *pName, int IsDir, int DirType, void *pUser)
{
CMenuBackground *pSelf = (CMenuBackground *)pUser;
const char *pSuffix = str_endswith(pName, ".map");
if(IsDir || !pSuffix)
return 0;
char aFullName[128];
char aThemeName[128];
str_truncate(aFullName, sizeof(aFullName), pName, pSuffix - pName);
bool IsDay = false;
bool IsNight = false;
if((pSuffix = str_endswith(aFullName, "_day")))
{
str_truncate(aThemeName, sizeof(aThemeName), pName, pSuffix - aFullName);
IsDay = true;
}
else if((pSuffix = str_endswith(aFullName, "_night")))
{
str_truncate(aThemeName, sizeof(aThemeName), pName, pSuffix - aFullName);
IsNight = true;
}
else
str_copy(aThemeName, aFullName);
if(str_comp(aThemeName, "none") == 0 || str_comp(aThemeName, "auto") == 0 || str_comp(aThemeName, "rand") == 0) // "none", "auto" and "rand" reserved, disallowed for maps
return 0;
// try to edit an existing theme
for(auto &Theme : pSelf->m_vThemes)
{
if(str_comp(Theme.m_Name.c_str(), aThemeName) == 0)
{
if(IsDay)
Theme.m_HasDay = true;
if(IsNight)
Theme.m_HasNight = true;
return 0;
}
}
// make new theme
CTheme Theme(aThemeName, IsDay, IsNight);
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "added theme %s from themes/%s", aThemeName, pName);
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
pSelf->m_vThemes.push_back(Theme);
auto TimeNow = time_get_nanoseconds();
if(TimeNow - pSelf->m_ThemeScanStartTime >= std::chrono::nanoseconds(1s) / 60)
{
pSelf->Client()->UpdateAndSwap();
pSelf->m_ThemeScanStartTime = TimeNow;
}
return 0;
}
int CMenuBackground::ThemeIconScan(const char *pName, int IsDir, int DirType, void *pUser)
{
CMenuBackground *pSelf = (CMenuBackground *)pUser;
const char *pSuffix = str_endswith(pName, ".png");
if(IsDir || !pSuffix)
return 0;
auto TimeNow = time_get_nanoseconds();
if(TimeNow - pSelf->m_ThemeScanStartTime >= std::chrono::nanoseconds(1s) / 60)
{
pSelf->Client()->UpdateAndSwap();
pSelf->m_ThemeScanStartTime = TimeNow;
}
char aThemeName[128];
str_truncate(aThemeName, sizeof(aThemeName), pName, pSuffix - pName);
// save icon for an existing theme
for(CTheme &Theme : pSelf->m_vThemes) // bit slow but whatever
{
if(str_comp(Theme.m_Name.c_str(), aThemeName) == 0 || (Theme.m_Name.empty() && str_comp(aThemeName, "none") == 0))
{
char aBuf[IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "themes/%s", pName);
CImageInfo Info;
if(!pSelf->Graphics()->LoadPNG(&Info, aBuf, DirType))
{
str_format(aBuf, sizeof(aBuf), "failed to load theme icon from %s", pName);
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
return 0;
}
str_format(aBuf, sizeof(aBuf), "loaded theme icon %s", pName);
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
Theme.m_IconTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0);
pSelf->Graphics()->FreePNG(&Info);
return 0;
}
}
return 0; // no existing theme
}
void CMenuBackground::LoadMenuBackground(bool HasDayHint, bool HasNightHint)
{
if(!m_IsInit)
return;
if(m_Loaded && m_pMap == m_pBackgroundMap)
m_pMap->Unload();
m_Loaded = false;
m_pMap = m_pBackgroundMap;
m_pLayers = m_pBackgroundLayers;
m_pImages = m_pBackgroundImages;
ResetPositions();
str_copy(m_aMapName, g_Config.m_ClMenuMap);
if(g_Config.m_ClMenuMap[0] != '\0')
{
const char *pMenuMap = g_Config.m_ClMenuMap;
if(str_comp(pMenuMap, "auto") == 0)
{
switch(time_season())
{
case SEASON_SPRING:
pMenuMap = "heavens";
break;
case SEASON_SUMMER:
pMenuMap = "jungle";
break;
case SEASON_AUTUMN:
pMenuMap = "autumn";
break;
case SEASON_WINTER:
pMenuMap = "winter";
break;
case SEASON_NEWYEAR:
pMenuMap = "newyear";
break;
}
}
else if(str_comp(pMenuMap, "rand") == 0)
{
// make sure to load themes
const std::vector<CTheme> &vThemesRef = GetThemes();
if(vThemesRef.size() > PREDEFINED_THEMES_COUNT)
{
int RandomThemeIndex = rand() % (vThemesRef.size() - PREDEFINED_THEMES_COUNT);
if(RandomThemeIndex + PREDEFINED_THEMES_COUNT < (int)vThemesRef.size())
pMenuMap = vThemesRef[RandomThemeIndex + PREDEFINED_THEMES_COUNT].m_Name.c_str();
}
}
char aBuf[128];
const int HourOfTheDay = time_houroftheday();
const bool IsDaytime = HourOfTheDay >= 6 && HourOfTheDay < 18;
if(!m_Loaded && ((HasDayHint && IsDaytime) || (HasNightHint && !IsDaytime)))
{
str_format(aBuf, sizeof(aBuf), "themes/%s_%s.map", pMenuMap, IsDaytime ? "day" : "night");
if(m_pMap->Load(aBuf))
{
m_Loaded = true;
}
}
if(!m_Loaded)
{
str_format(aBuf, sizeof(aBuf), "themes/%s.map", pMenuMap);
if(m_pMap->Load(aBuf))
{
m_Loaded = true;
}
}
if(!m_Loaded && ((HasDayHint && !IsDaytime) || (HasNightHint && IsDaytime)))
{
str_format(aBuf, sizeof(aBuf), "themes/%s_%s.map", pMenuMap, IsDaytime ? "night" : "day");
if(m_pMap->Load(aBuf))
{
m_Loaded = true;
}
}
if(m_Loaded)
{
m_pLayers->InitBackground(m_pMap);
CMapLayers::OnMapLoad();
m_pImages->LoadBackground(m_pLayers, m_pMap);
// look for custom positions
CMapItemLayerTilemap *pTLayer = m_pLayers->GameLayer();
if(pTLayer)
{
int DataIndex = pTLayer->m_Data;
unsigned int Size = m_pLayers->Map()->GetDataSize(DataIndex);
void *pTiles = m_pLayers->Map()->GetData(DataIndex);
unsigned int TileSize = sizeof(CTile);
if(Size >= pTLayer->m_Width * pTLayer->m_Height * TileSize)
{
for(int y = 0; y < pTLayer->m_Height; ++y)
{
for(int x = 0; x < pTLayer->m_Width; ++x)
{
unsigned char Index = ((CTile *)pTiles)[y * pTLayer->m_Width + x].m_Index;
if(Index >= TILE_TIME_CHECKPOINT_FIRST && Index <= TILE_TIME_CHECKPOINT_LAST)
{
int ArrayIndex = clamp<int>((Index - TILE_TIME_CHECKPOINT_FIRST), 0, NUM_POS);
m_aPositions[ArrayIndex] = vec2(x * 32.0f + 16.0f, y * 32.0f + 16.0f);
}
x += ((CTile *)pTiles)[y * pTLayer->m_Width + x].m_Skip;
}
}
}
}
}
}
}
void CMenuBackground::OnMapLoad()
{
}
void CMenuBackground::OnRender()
{
}
bool CMenuBackground::Render()
{
if(!m_Loaded)
return false;
if(Client()->State() >= IClient::STATE_ONLINE)
return false;
m_Camera.m_Zoom = 0.7f;
static vec2 Dir = vec2(1.0f, 0.0f);
float DistToCenter = distance(m_Camera.m_Center, m_RotationCenter);
if(!m_ChangedPosition && absolute(DistToCenter - (float)g_Config.m_ClRotationRadius) <= 0.5f)
{
// do little rotation
float RotPerTick = 360.0f / (float)g_Config.m_ClRotationSpeed * clamp(Client()->RenderFrameTime(), 0.0f, 0.1f);
Dir = rotate(Dir, RotPerTick);
m_Camera.m_Center = m_RotationCenter + Dir * (float)g_Config.m_ClRotationRadius;
}
else
{
// positions for the animation
vec2 DirToCenter;
if(DistToCenter > 0.5f)
DirToCenter = normalize(m_AnimationStartPos - m_RotationCenter);
else
DirToCenter = vec2(1, 0);
vec2 TargetPos = m_RotationCenter + DirToCenter * (float)g_Config.m_ClRotationRadius;
float Distance = distance(m_AnimationStartPos, TargetPos);
if(Distance > 0.001f)
Dir = normalize(m_AnimationStartPos - TargetPos);
else
Dir = vec2(1, 0);
// move time
m_MoveTime += clamp(Client()->RenderFrameTime(), 0.0f, 0.1f) * g_Config.m_ClCameraSpeed / 10.0f;
float XVal = 1 - m_MoveTime;
XVal = std::pow(XVal, 7.0f);
m_Camera.m_Center = TargetPos + Dir * (XVal * Distance);
if(m_CurrentPosition < 0)
{
m_AnimationStartPos = m_Camera.m_Center;
m_MoveTime = 0.0f;
}
m_ChangedPosition = false;
}
CMapLayers::OnRender();
m_CurrentPosition = -1;
return true;
}
CCamera *CMenuBackground::GetCurCamera()
{
return &m_Camera;
}
void CMenuBackground::ChangePosition(int PositionNumber)
{
if(PositionNumber != m_CurrentPosition)
{
if(PositionNumber >= POS_START && PositionNumber < NUM_POS)
{
m_CurrentPosition = PositionNumber;
}
else
{
m_CurrentPosition = POS_START;
}
m_ChangedPosition = true;
}
m_AnimationStartPos = m_Camera.m_Center;
m_RotationCenter = m_aPositions[m_CurrentPosition];
m_MoveTime = 0.0f;
}
std::vector<CTheme> &CMenuBackground::GetThemes()
{
if(m_vThemes.empty()) // not loaded yet
{
// when adding more here, make sure to change the value of PREDEFINED_THEMES_COUNT too
m_vThemes.emplace_back("", true, true); // no theme
m_vThemes.emplace_back("auto", true, true); // auto theme
m_vThemes.emplace_back("rand", true, true); // random theme
m_ThemeScanStartTime = time_get_nanoseconds();
Storage()->ListDirectory(IStorage::TYPE_ALL, "themes", ThemeScan, (CMenuBackground *)this);
Storage()->ListDirectory(IStorage::TYPE_ALL, "themes", ThemeIconScan, (CMenuBackground *)this);
std::sort(m_vThemes.begin() + PREDEFINED_THEMES_COUNT, m_vThemes.end());
}
return m_vThemes;
}