1 | #include <base/system.h> |
2 | |
3 | #include <algorithm> |
4 | |
5 | #include <engine/graphics.h> |
6 | #include <engine/map.h> |
7 | #include <engine/shared/config.h> |
8 | |
9 | #include <game/client/components/camera.h> |
10 | #include <game/client/components/mapimages.h> |
11 | #include <game/client/components/maplayers.h> |
12 | #include <game/client/gameclient.h> |
13 | |
14 | #include <game/layers.h> |
15 | #include <game/localization.h> |
16 | #include <game/mapitems.h> |
17 | |
18 | #include "menu_background.h" |
19 | |
20 | #include <chrono> |
21 | |
22 | using namespace std::chrono_literals; |
23 | |
24 | std::array<vec2, CMenuBackground::NUM_POS> () |
25 | { |
26 | std::array<vec2, CMenuBackground::NUM_POS> Positions; |
27 | |
28 | Positions[CMenuBackground::POS_START] = vec2(500.0f, 500.0f); |
29 | Positions[CMenuBackground::POS_BROWSER_INTERNET] = vec2(1000.0f, 1000.0f); |
30 | Positions[CMenuBackground::POS_BROWSER_LAN] = vec2(1100.0f, 1000.0f); |
31 | Positions[CMenuBackground::POS_DEMOS] = vec2(900.0f, 100.0f); |
32 | Positions[CMenuBackground::POS_NEWS] = vec2(500.0f, 750.0f); |
33 | Positions[CMenuBackground::POS_BROWSER_FAVORITES] = vec2(1250.0f, 500.0f); |
34 | Positions[CMenuBackground::POS_SETTINGS_LANGUAGE] = vec2(500.0f, 1200.0f); |
35 | Positions[CMenuBackground::POS_SETTINGS_GENERAL] = vec2(500.0f, 1000.0f); |
36 | Positions[CMenuBackground::POS_SETTINGS_PLAYER] = vec2(600.0f, 1000.0f); |
37 | Positions[CMenuBackground::POS_SETTINGS_TEE] = vec2(700.0f, 1000.0f); |
38 | Positions[CMenuBackground::POS_SETTINGS_APPEARANCE] = vec2(200.0f, 1000.0f); |
39 | Positions[CMenuBackground::POS_SETTINGS_CONTROLS] = vec2(800.0f, 1000.0f); |
40 | Positions[CMenuBackground::POS_SETTINGS_GRAPHICS] = vec2(900.0f, 1000.0f); |
41 | Positions[CMenuBackground::POS_SETTINGS_SOUND] = vec2(1000.0f, 1000.0f); |
42 | Positions[CMenuBackground::POS_SETTINGS_DDNET] = vec2(1200.0f, 200.0f); |
43 | Positions[CMenuBackground::POS_SETTINGS_ASSETS] = vec2(500.0f, 500.0f); |
44 | for(int i = 0; i < CMenuBackground::POS_BROWSER_CUSTOM_NUM; ++i) |
45 | Positions[CMenuBackground::POS_BROWSER_CUSTOM0 + i] = vec2(500.0f + (75.0f * (float)i), 650.0f - (75.0f * (float)i)); |
46 | for(int i = 0; i < CMenuBackground::POS_SETTINGS_RESERVED_NUM; ++i) |
47 | Positions[CMenuBackground::POS_SETTINGS_RESERVED0 + i] = vec2(0, 0); |
48 | for(int i = 0; i < CMenuBackground::POS_RESERVED_NUM; ++i) |
49 | Positions[CMenuBackground::POS_RESERVED0 + i] = vec2(0, 0); |
50 | |
51 | return Positions; |
52 | } |
53 | |
54 | CMenuBackground::() : |
55 | CBackground(CMapLayers::TYPE_FULL_DESIGN, false) |
56 | { |
57 | m_RotationCenter = vec2(0.0f, 0.0f); |
58 | m_AnimationStartPos = vec2(0.0f, 0.0f); |
59 | m_Camera.m_Center = vec2(0.0f, 0.0f); |
60 | m_Camera.m_PrevCenter = vec2(0.0f, 0.0f); // unused in this class |
61 | m_ChangedPosition = false; |
62 | |
63 | ResetPositions(); |
64 | |
65 | m_CurrentPosition = -1; |
66 | m_MoveTime = 0.0f; |
67 | |
68 | m_IsInit = false; |
69 | } |
70 | |
71 | CBackgroundEngineMap *CMenuBackground::() |
72 | { |
73 | return new CMenuMap; |
74 | } |
75 | |
76 | void CMenuBackground::() |
77 | { |
78 | m_pBackgroundMap = CreateBGMap(); |
79 | m_pMap = m_pBackgroundMap; |
80 | |
81 | m_IsInit = true; |
82 | |
83 | m_pImages->m_pClient = GameClient(); |
84 | Kernel()->RegisterInterface<CMenuMap>(pInterface: (CMenuMap *)m_pBackgroundMap); |
85 | if(g_Config.m_ClMenuMap[0] != '\0') |
86 | LoadMenuBackground(); |
87 | |
88 | m_Camera.m_pClient = GameClient(); |
89 | m_Camera.m_ZoomSet = false; |
90 | m_Camera.m_ZoomSmoothingTarget = 0; |
91 | } |
92 | |
93 | void CMenuBackground::() |
94 | { |
95 | m_aPositions = GenerateMenuBackgroundPositions(); |
96 | } |
97 | |
98 | void CMenuBackground::(CTheme &Theme) |
99 | { |
100 | char aIconPath[IO_MAX_PATH_LENGTH]; |
101 | str_format(buffer: aIconPath, buffer_size: sizeof(aIconPath), format: "themes/%s.png" , Theme.m_Name.empty() ? "none" : Theme.m_Name.c_str()); |
102 | Theme.m_IconTexture = Graphics()->LoadTexture(pFilename: aIconPath, StorageType: IStorage::TYPE_ALL); |
103 | |
104 | char aBuf[32 + IO_MAX_PATH_LENGTH]; |
105 | if(Theme.m_IconTexture.IsNullTexture()) |
106 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to load theme icon '%s'" , aIconPath); |
107 | else |
108 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "loaded theme icon '%s'" , aIconPath); |
109 | Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "menuthemes" , pStr: aBuf); |
110 | } |
111 | |
112 | int CMenuBackground::(const char *pName, int IsDir, int DirType, void *pUser) |
113 | { |
114 | CMenuBackground *pSelf = (CMenuBackground *)pUser; |
115 | const char *pSuffix = str_endswith(str: pName, suffix: ".map" ); |
116 | if(IsDir || !pSuffix) |
117 | return 0; |
118 | char aFullName[128]; |
119 | char aThemeName[128]; |
120 | str_truncate(dst: aFullName, dst_size: sizeof(aFullName), src: pName, truncation_len: pSuffix - pName); |
121 | |
122 | bool IsDay = false; |
123 | bool IsNight = false; |
124 | if((pSuffix = str_endswith(str: aFullName, suffix: "_day" ))) |
125 | { |
126 | str_truncate(dst: aThemeName, dst_size: sizeof(aThemeName), src: pName, truncation_len: pSuffix - aFullName); |
127 | IsDay = true; |
128 | } |
129 | else if((pSuffix = str_endswith(str: aFullName, suffix: "_night" ))) |
130 | { |
131 | str_truncate(dst: aThemeName, dst_size: sizeof(aThemeName), src: pName, truncation_len: pSuffix - aFullName); |
132 | IsNight = true; |
133 | } |
134 | else |
135 | str_copy(dst&: aThemeName, src: aFullName); |
136 | |
137 | if(str_comp(a: aThemeName, b: "none" ) == 0 || str_comp(a: aThemeName, b: "auto" ) == 0 || str_comp(a: aThemeName, b: "rand" ) == 0) // "none", "auto" and "rand" reserved, disallowed for maps |
138 | return 0; |
139 | |
140 | // try to edit an existing theme |
141 | for(auto &Theme : pSelf->m_vThemes) |
142 | { |
143 | if(str_comp(a: Theme.m_Name.c_str(), b: aThemeName) == 0) |
144 | { |
145 | if(IsDay) |
146 | Theme.m_HasDay = true; |
147 | if(IsNight) |
148 | Theme.m_HasNight = true; |
149 | return 0; |
150 | } |
151 | } |
152 | |
153 | // make new theme |
154 | char aBuf[512]; |
155 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "added theme '%s' from 'themes/%s'" , aThemeName, pName); |
156 | pSelf->Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "menuthemes" , pStr: aBuf); |
157 | pSelf->m_vThemes.emplace_back(args&: aThemeName, args&: IsDay, args&: IsNight); |
158 | pSelf->LoadThemeIcon(Theme&: pSelf->m_vThemes.back()); |
159 | |
160 | if(time_get_nanoseconds() - pSelf->m_ThemeScanStartTime > 500ms) |
161 | { |
162 | pSelf->GameClient()->m_Menus.RenderLoading(pCaption: Localize(pStr: "Loading menu themes" ), pContent: "" , IncreaseCounter: 0, RenderLoadingBar: false); |
163 | } |
164 | return 0; |
165 | } |
166 | |
167 | void CMenuBackground::(bool HasDayHint, bool HasNightHint) |
168 | { |
169 | if(!m_IsInit) |
170 | return; |
171 | |
172 | if(m_Loaded && m_pMap == m_pBackgroundMap) |
173 | m_pMap->Unload(); |
174 | |
175 | m_Loaded = false; |
176 | m_pMap = m_pBackgroundMap; |
177 | m_pLayers = m_pBackgroundLayers; |
178 | m_pImages = m_pBackgroundImages; |
179 | |
180 | ResetPositions(); |
181 | |
182 | str_copy(dst&: m_aMapName, src: g_Config.m_ClMenuMap); |
183 | |
184 | if(g_Config.m_ClMenuMap[0] != '\0') |
185 | { |
186 | const char * = g_Config.m_ClMenuMap; |
187 | if(str_comp(a: pMenuMap, b: "auto" ) == 0) |
188 | { |
189 | switch(time_season()) |
190 | { |
191 | case SEASON_SPRING: |
192 | case SEASON_EASTER: |
193 | pMenuMap = "heavens" ; |
194 | break; |
195 | case SEASON_SUMMER: |
196 | pMenuMap = "jungle" ; |
197 | break; |
198 | case SEASON_AUTUMN: |
199 | case SEASON_HALLOWEEN: |
200 | pMenuMap = "autumn" ; |
201 | break; |
202 | case SEASON_WINTER: |
203 | case SEASON_XMAS: |
204 | pMenuMap = "winter" ; |
205 | break; |
206 | case SEASON_NEWYEAR: |
207 | pMenuMap = "newyear" ; |
208 | break; |
209 | } |
210 | } |
211 | else if(str_comp(a: pMenuMap, b: "rand" ) == 0) |
212 | { |
213 | // make sure to load themes |
214 | const std::vector<CTheme> &vThemesRef = GetThemes(); |
215 | if(vThemesRef.size() > PREDEFINED_THEMES_COUNT) |
216 | { |
217 | int RandomThemeIndex = rand() % (vThemesRef.size() - PREDEFINED_THEMES_COUNT); |
218 | if(RandomThemeIndex + PREDEFINED_THEMES_COUNT < (int)vThemesRef.size()) |
219 | pMenuMap = vThemesRef[RandomThemeIndex + PREDEFINED_THEMES_COUNT].m_Name.c_str(); |
220 | } |
221 | } |
222 | |
223 | char aBuf[128]; |
224 | |
225 | const int HourOfTheDay = time_houroftheday(); |
226 | const bool IsDaytime = HourOfTheDay >= 6 && HourOfTheDay < 18; |
227 | |
228 | if(!m_Loaded && ((HasDayHint && IsDaytime) || (HasNightHint && !IsDaytime))) |
229 | { |
230 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "themes/%s_%s.map" , pMenuMap, IsDaytime ? "day" : "night" ); |
231 | if(m_pMap->Load(pMapName: aBuf)) |
232 | { |
233 | m_Loaded = true; |
234 | } |
235 | } |
236 | |
237 | if(!m_Loaded) |
238 | { |
239 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "themes/%s.map" , pMenuMap); |
240 | if(m_pMap->Load(pMapName: aBuf)) |
241 | { |
242 | m_Loaded = true; |
243 | } |
244 | } |
245 | |
246 | if(!m_Loaded && ((HasDayHint && !IsDaytime) || (HasNightHint && IsDaytime))) |
247 | { |
248 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "themes/%s_%s.map" , pMenuMap, IsDaytime ? "night" : "day" ); |
249 | if(m_pMap->Load(pMapName: aBuf)) |
250 | { |
251 | m_Loaded = true; |
252 | } |
253 | } |
254 | |
255 | if(m_Loaded) |
256 | { |
257 | m_pLayers->InitBackground(pMap: m_pMap); |
258 | |
259 | CMapLayers::OnMapLoad(); |
260 | m_pImages->LoadBackground(pLayers: m_pLayers, pMap: m_pMap); |
261 | |
262 | // look for custom positions |
263 | CMapItemLayerTilemap *pTLayer = m_pLayers->GameLayer(); |
264 | if(pTLayer) |
265 | { |
266 | int DataIndex = pTLayer->m_Data; |
267 | unsigned int Size = m_pLayers->Map()->GetDataSize(Index: DataIndex); |
268 | void *pTiles = m_pLayers->Map()->GetData(Index: DataIndex); |
269 | unsigned int TileSize = sizeof(CTile); |
270 | |
271 | if(Size >= pTLayer->m_Width * pTLayer->m_Height * TileSize) |
272 | { |
273 | for(int y = 0; y < pTLayer->m_Height; ++y) |
274 | { |
275 | for(int x = 0; x < pTLayer->m_Width; ++x) |
276 | { |
277 | unsigned char Index = ((CTile *)pTiles)[y * pTLayer->m_Width + x].m_Index; |
278 | if(Index >= TILE_TIME_CHECKPOINT_FIRST && Index <= TILE_TIME_CHECKPOINT_LAST) |
279 | { |
280 | int ArrayIndex = clamp<int>(val: (Index - TILE_TIME_CHECKPOINT_FIRST), lo: 0, hi: NUM_POS); |
281 | m_aPositions[ArrayIndex] = vec2(x * 32.0f + 16.0f, y * 32.0f + 16.0f); |
282 | } |
283 | |
284 | x += ((CTile *)pTiles)[y * pTLayer->m_Width + x].m_Skip; |
285 | } |
286 | } |
287 | } |
288 | } |
289 | } |
290 | } |
291 | } |
292 | |
293 | void CMenuBackground::() |
294 | { |
295 | } |
296 | |
297 | void CMenuBackground::() |
298 | { |
299 | } |
300 | |
301 | bool CMenuBackground::() |
302 | { |
303 | if(!m_Loaded) |
304 | return false; |
305 | |
306 | if(Client()->State() == IClient::STATE_ONLINE || Client()->State() == IClient::STATE_DEMOPLAYBACK) |
307 | return false; |
308 | |
309 | m_Camera.m_Zoom = 0.7f; |
310 | static vec2 Dir = vec2(1.0f, 0.0f); |
311 | |
312 | float DistToCenter = distance(a: m_Camera.m_Center, b: m_RotationCenter); |
313 | if(!m_ChangedPosition && absolute(a: DistToCenter - (float)g_Config.m_ClRotationRadius) <= 0.5f) |
314 | { |
315 | // do little rotation |
316 | float RotPerTick = 360.0f / (float)g_Config.m_ClRotationSpeed * clamp(val: Client()->RenderFrameTime(), lo: 0.0f, hi: 0.1f); |
317 | Dir = rotate(a: Dir, angle: RotPerTick); |
318 | m_Camera.m_Center = m_RotationCenter + Dir * (float)g_Config.m_ClRotationRadius; |
319 | } |
320 | else |
321 | { |
322 | // positions for the animation |
323 | vec2 DirToCenter; |
324 | if(DistToCenter > 0.5f) |
325 | DirToCenter = normalize(v: m_AnimationStartPos - m_RotationCenter); |
326 | else |
327 | DirToCenter = vec2(1, 0); |
328 | vec2 TargetPos = m_RotationCenter + DirToCenter * (float)g_Config.m_ClRotationRadius; |
329 | float Distance = distance(a: m_AnimationStartPos, b: TargetPos); |
330 | if(Distance > 0.001f) |
331 | Dir = normalize(v: m_AnimationStartPos - TargetPos); |
332 | else |
333 | Dir = vec2(1, 0); |
334 | |
335 | // move time |
336 | m_MoveTime += clamp(val: Client()->RenderFrameTime(), lo: 0.0f, hi: 0.1f) * g_Config.m_ClCameraSpeed / 10.0f; |
337 | float XVal = 1 - m_MoveTime; |
338 | XVal = std::pow(x: XVal, y: 7.0f); |
339 | |
340 | m_Camera.m_Center = TargetPos + Dir * (XVal * Distance); |
341 | if(m_CurrentPosition < 0) |
342 | { |
343 | m_AnimationStartPos = m_Camera.m_Center; |
344 | m_MoveTime = 0.0f; |
345 | } |
346 | |
347 | m_ChangedPosition = false; |
348 | } |
349 | |
350 | CMapLayers::OnRender(); |
351 | |
352 | m_CurrentPosition = -1; |
353 | |
354 | return true; |
355 | } |
356 | |
357 | CCamera *CMenuBackground::() |
358 | { |
359 | return &m_Camera; |
360 | } |
361 | |
362 | void CMenuBackground::(int PositionNumber) |
363 | { |
364 | if(PositionNumber != m_CurrentPosition) |
365 | { |
366 | if(PositionNumber >= POS_START && PositionNumber < NUM_POS) |
367 | { |
368 | m_CurrentPosition = PositionNumber; |
369 | } |
370 | else |
371 | { |
372 | m_CurrentPosition = POS_START; |
373 | } |
374 | |
375 | m_ChangedPosition = true; |
376 | } |
377 | m_AnimationStartPos = m_Camera.m_Center; |
378 | m_RotationCenter = m_aPositions[m_CurrentPosition]; |
379 | m_MoveTime = 0.0f; |
380 | } |
381 | |
382 | std::vector<CTheme> &CMenuBackground::() |
383 | { |
384 | if(m_vThemes.empty()) // not loaded yet |
385 | { |
386 | // when adding more here, make sure to change the value of PREDEFINED_THEMES_COUNT too |
387 | m_vThemes.emplace_back(args: "" , args: true, args: true); // no theme |
388 | LoadThemeIcon(Theme&: m_vThemes.back()); |
389 | |
390 | m_vThemes.emplace_back(args: "auto" , args: true, args: true); // auto theme |
391 | LoadThemeIcon(Theme&: m_vThemes.back()); |
392 | |
393 | m_vThemes.emplace_back(args: "rand" , args: true, args: true); // random theme |
394 | LoadThemeIcon(Theme&: m_vThemes.back()); |
395 | |
396 | m_ThemeScanStartTime = time_get_nanoseconds(); |
397 | Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: "themes" , pfnCallback: ThemeScan, pUser: this); |
398 | |
399 | std::sort(first: m_vThemes.begin() + PREDEFINED_THEMES_COUNT, last: m_vThemes.end()); |
400 | } |
401 | return m_vThemes; |
402 | } |
403 | |