1 /************************************************************************************
2
3 AstroMenace
4 Hardcore 3D space scroll-shooter with spaceship upgrade possibilities.
5 Copyright (c) 2006-2019 Mikhail Kurinnoi, Viewizard
6
7
8 AstroMenace is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 AstroMenace is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with AstroMenace. If not, see <https://www.gnu.org/licenses/>.
20
21
22 Website: https://viewizard.com/
23 Project: https://github.com/viewizard/astromenace
24 E-mail: viewizard@viewizard.com
25
26 *************************************************************************************/
27
28 // FIXME ostringstream is not so fast, move all string initialization into setup,
29 // all ostringstream-related code should be called only one time in init
30
31 // TODO translate comments
32
33 // NOTE in future, use make_unique() to make unique_ptr-s (since C++14)
34
35 #include "../core/core.h"
36 #include "../enum.h"
37 #include "../config/config.h"
38 #include "../assets/audio.h"
39 #include "../assets/texture.h"
40 #include "../ui/cursor.h"
41 #include "../command.h"
42 #include "../game.h" // FIXME "game.h" should be replaced by individual headers
43 #include <sstream>
44 #include <iomanip>
45
46 // NOTE switch to nested namespace definition (namespace A::B::C { ... }) (since C++17)
47 namespace viewizard {
48 namespace astromenace {
49
50 namespace {
51
52 struct cMission {
53 std::string Title{};
54 std::string Descr{};
55 sRGBCOLOR TitleColor{sRGBCOLOR{eRGBCOLOR::orange}};
56 sRGBCOLOR DescrColor{sRGBCOLOR{eRGBCOLOR::white}};
57 std::string Icon{};
58 std::string File{};
59 };
60
61 std::vector<cMission> MissionList{};
62
63 } // unnamed namespace
64
65
66
67 int SoundOnMissionID = -1;
68
69 int CurrentMission = -1;
70 // всего доступно миссий
71 int AllMission;
72 // начало и конец отображаемых миссий
73 int StartMission = 0;
74 int EndMission = 4;
75
76 bool SliderUnderMouseControl = false;
77
78
79
80 // получаем имя файла миссии
GetCurrentMissionFileName()81 std::string GetCurrentMissionFileName()
82 {
83 if ((CurrentMission >= 0) &&
84 (MissionList.size() > static_cast<unsigned>(CurrentMission)))
85 return MissionList[CurrentMission].File;
86
87 return std::string{};
88 }
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 //------------------------------------------------------------------------------------
104 // инициализация данных списка миссий
105 //------------------------------------------------------------------------------------
MissionListInit()106 void MissionListInit()
107 {
108 if (!MissionList.empty())
109 return;
110
111 std::string ScriptName{"script/list.xml"};
112
113 // по скрипту, смотрим что загружать + считаем сколько позиций
114 std::unique_ptr<cXMLDocument> xmlDoc{new cXMLDocument{ScriptName}};
115
116 // проверяем корневой элемент
117 if (!xmlDoc->GetRootEntry() || ("AstroMenaceMissionList" != xmlDoc->GetRootEntry()->Name)) {
118 std::cerr << __func__ << "(): " << "Can't find AstroMenaceMissionList element in the: " << ScriptName << "\n";
119 return;
120 }
121
122 for (const auto &xmlEntry : xmlDoc->GetRootEntry()->ChildrenList) {
123 MissionList.emplace_back();
124
125 // берем каждую миссию и смотрим настройки
126 if (xmlEntry.Name == "Mission") {
127 for (const auto &TMission : xmlEntry.ChildrenList) {
128 // тайтл миссии
129 if (TMission.Name == "Title") {
130 int tmpColor{0};
131 if (xmlDoc->iGetEntryAttribute(TMission, "color", tmpColor)) {
132 switch (tmpColor) {
133 case 1:
134 MissionList.back().TitleColor = sRGBCOLOR{eRGBCOLOR::yellow};
135 break;
136 case 2:
137 MissionList.back().TitleColor = sRGBCOLOR{eRGBCOLOR::red};
138 break;
139 case 3:
140 MissionList.back().TitleColor = sRGBCOLOR{eRGBCOLOR::green};
141 break;
142 case 4:
143 MissionList.back().TitleColor = sRGBCOLOR{eRGBCOLOR::orange};
144 break;
145 case 5: // grey
146 MissionList.back().TitleColor = sRGBCOLOR{0.5f, 0.5f, 0.5f};
147 break;
148 case 6: // dark orange
149 MissionList.back().TitleColor = sRGBCOLOR{1.0f, 0.3f, 0.0f};
150 break;
151 default:
152 MissionList.back().TitleColor = sRGBCOLOR{eRGBCOLOR::white};
153 break;
154 }
155 }
156 MissionList.back().Title = TMission.Content;
157 } else if (TMission.Name == "Descr") {
158 int tmpColor{0};
159 if (xmlDoc->iGetEntryAttribute(TMission, "color", tmpColor)) {
160 switch (tmpColor) {
161 case 1:
162 MissionList.back().DescrColor = sRGBCOLOR{eRGBCOLOR::yellow};
163 break;
164 case 2:
165 MissionList.back().DescrColor = sRGBCOLOR{eRGBCOLOR::red};
166 break;
167 case 3:
168 MissionList.back().DescrColor = sRGBCOLOR{eRGBCOLOR::green};
169 break;
170 case 4:
171 MissionList.back().DescrColor = sRGBCOLOR{eRGBCOLOR::orange};
172 break;
173 case 5: // grey
174 MissionList.back().DescrColor = sRGBCOLOR{0.5f, 0.5f, 0.5f};
175 break;
176 case 6: // dark orange
177 MissionList.back().DescrColor = sRGBCOLOR{1.0f, 0.3f, 0.0f};
178 break;
179 default:
180 MissionList.back().DescrColor = sRGBCOLOR{eRGBCOLOR::white};
181 break;
182 }
183 }
184 MissionList.back().Descr = TMission.Content;
185 } else if (TMission.Name == "Icon") {
186 MissionList.back().Icon = TMission.Content;
187 } else if (TMission.Name == "File") {
188 MissionList.back().File = TMission.Content;
189 }
190 }
191 }
192 }
193
194 AllMission = MissionList.size();
195 }
196
197
198
199
200
201
202
203
204
205
206
207
208 //------------------------------------------------------------------------------------
209 // выбор миссии
210 //------------------------------------------------------------------------------------
MissionMenu()211 void MissionMenu()
212 {
213
214 // проверка ограничения
215 if (GameConfig().Profile[CurrentProfile].OpenLevelNum > AllMission-1)
216 ChangeGameConfig().Profile[CurrentProfile].OpenLevelNum = AllMission-1;
217
218
219
220 sRECT SrcRect, DstRect;
221 SrcRect(2, 2, 863-2, 484-2);
222 DstRect(GameConfig().InternalWidth/2-427, 175-15, GameConfig().InternalWidth/2-427+863-4, 175-15+484-4);
223 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset("menu/panel800_444_back.tga"), true, 0.9f * MenuContentTransp);
224
225
226
227 int X1 = GameConfig().InternalWidth/2 - 372;
228 int Y1 = 270;
229
230
231
232
233 // выводим текущий профиль пилота
234 std::ostringstream tmpStream;
235 tmpStream << vw_GetText("Pilot Profile") << ": ";
236 int Size = vw_TextWidth(tmpStream.str());
237 vw_DrawText(X1, 208+12, 0, 0, 1.0f, sRGBCOLOR{eRGBCOLOR::green}, MenuContentTransp, tmpStream.str());
238
239 if ((Size + vw_TextWidth(GameConfig().Profile[CurrentProfile].Name)) > 500) {
240 vw_DrawText(X1+Size, 208+12, 0, 500-Size, 1.0f, sRGBCOLOR{eRGBCOLOR::white}, MenuContentTransp, GameConfig().Profile[CurrentProfile].Name);
241 vw_DrawText(X1+510, 208+12, 0, 0, 1.0f, sRGBCOLOR{eRGBCOLOR::white}, MenuContentTransp, "...");
242 } else
243 vw_DrawText(X1+Size, 208+12, 0, 0, 1.0f, sRGBCOLOR{eRGBCOLOR::white}, MenuContentTransp, GameConfig().Profile[CurrentProfile].Name);
244
245 if (DrawButton200_2(X1+616-72, 212, vw_GetTextUTF32("Change Profile"), MenuContentTransp, false)) {
246 cCommand::GetInstance().Set(eCommand::SWITCH_TO_PROFILE);
247 }
248
249
250
251
252 // подложка для вывода описания миссий
253 SrcRect(0,0,2,2);
254 DstRect(X1-2,Y1-2,X1+2+710,Y1+2+320);
255 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset("menu/blackpoint.tga"), true, 0.2f*MenuContentTransp);
256 DstRect(X1,Y1,X1+710,Y1+320);
257 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset("menu/blackpoint.tga"), true, 0.5f*MenuContentTransp);
258
259
260
261
262 // подсвечиваем выбранный уровень
263 if (CurrentMission != -1)
264 if (StartMission<=CurrentMission && CurrentMission<=EndMission) {
265 int ShowLine = CurrentMission;
266 if (ShowLine>=StartMission) ShowLine -= StartMission;
267
268 SrcRect(0,0,2,2);
269 DstRect(X1+1,Y1 + 64*ShowLine+1,X1+709,Y1 + 64*ShowLine+63);
270 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset("menu/whitepoint.tga"), true, 0.1f*MenuContentTransp);
271 }
272
273
274
275
276 // выводим миссии текущего листа
277 int TMPSoundOnMissionID = -1;
278 for (int i=StartMission; i<=EndMission; i++)
279 if (AllMission > i) {
280 // если не можем выбирать...
281 if (i > GameConfig().Profile[CurrentProfile].OpenLevelNum) {
282 SrcRect(0,0,64,64);
283 DstRect(X1+2,Y1+2,X1+62,Y1+62);
284
285 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset(MissionList[i].Icon), true, 0.3f*MenuContentTransp);
286 vw_DrawTextUTF32(X1+20+64, Y1+9, -610, 0, 1.0f, MissionList[i].TitleColor, 0.3f*MenuContentTransp, vw_GetTextUTF32(MissionList[i].Title));
287 vw_DrawTextUTF32(X1+20+64, Y1+33, -610, 0, 1.0f, MissionList[i].DescrColor, 0.3f*MenuContentTransp, vw_GetTextUTF32(MissionList[i].Descr));
288 }
289
290
291 DstRect(X1, Y1+1, X1+710, Y1+64);
292 if (i <= GameConfig().Profile[CurrentProfile].OpenLevelNum) {
293 // работаем с клавиатурой
294 if ((MenuContentTransp >= 0.99f) && !isDialogBoxDrawing()) CurrentActiveMenuElement++;
295 bool InFocusByKeyboard = false;
296 if (CurrentKeyboardSelectMenuElement > 0) {
297 if (CurrentKeyboardSelectMenuElement == CurrentActiveMenuElement) {
298 InFocusByKeyboard = true;
299 }
300 }
301
302
303 if ((vw_MouseOverRect(DstRect) || InFocusByKeyboard) && !isDialogBoxDrawing()) {
304 TMPSoundOnMissionID = i;
305 SetCursorStatus(eCursorStatus::ActionAllowed);
306 // если только встали - нужно звуком это показать
307 if (SoundOnMissionID != i) {
308 SoundOnMissionID = i;
309 // если задействуем клавиатуру - неиграем тут звук
310 if (CurrentKeyboardSelectMenuElement == 0)
311 PlayMenuSFX(eMenuSFX::OverLine, 1.0f);
312 }
313
314 // если стоим над ним...
315 SrcRect(0,0,64,64);
316 DstRect(X1,Y1,X1+64,Y1+64);
317 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset(MissionList[i].Icon), true, MenuContentTransp);
318 vw_DrawTextUTF32(X1+20+64, Y1+9, -610, 0, 1.0f, MissionList[i].TitleColor, MenuContentTransp, vw_GetTextUTF32(MissionList[i].Title));
319 vw_DrawTextUTF32(X1+20+64, Y1+33, -610, 0, 1.0f, MissionList[i].DescrColor, MenuContentTransp, vw_GetTextUTF32(MissionList[i].Descr));
320
321
322 if (CurrentMission != i) {
323 SrcRect(0,0,2,2);
324 DstRect(X1+64,Y1+1,X1+709,Y1+63);
325 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset("menu/whitepoint.tga"), true, 0.1f*MenuContentTransp);
326 }
327 if (vw_GetMouseLeftClick(true) || (InFocusByKeyboard && (vw_GetKeyStatus(SDLK_KP_ENTER) || vw_GetKeyStatus(SDLK_RETURN)))) {
328
329 CurrentMission = i;
330 ChangeGameConfig().Profile[CurrentProfile].LastMission = CurrentMission;
331 PlayMenuSFX(eMenuSFX::SelectLine, 1.0f);
332 if (InFocusByKeyboard) {
333 vw_SetKeyStatus(SDLK_KP_ENTER, false);
334 vw_SetKeyStatus(SDLK_RETURN, false);
335 }
336 }
337
338 if (vw_GetMouseLeftDoubleClick(true)) {
339 CurrentMission = i;
340 ChangeGameConfig().Profile[CurrentProfile].LastMission = CurrentMission;
341 // если уже играли в эту миссию
342 if (GameConfig().Profile[CurrentProfile].MissionReplayCount[CurrentMission] > 0) {
343 if (GameConfig().NeedShowHint[5]) {
344 SetCurrentDialogBox(eDialogBox::StartMissionSecondTime);
345 } else {
346 cCommand::GetInstance().Set(eCommand::SWITCH_TO_WORKSHOP);
347 CurrentWorkshop = 3;
348 WorkshopCreate();
349 }
350 } else {
351 cCommand::GetInstance().Set(eCommand::SWITCH_TO_WORKSHOP);
352 CurrentWorkshop = 3;
353 WorkshopCreate();
354 }
355 }
356 } else {
357 // если не стоим над ним, но можем выбирать
358 SrcRect(0,0,64,64);
359 DstRect(X1+2,Y1+2,X1+62,Y1+62);
360 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset(MissionList[i].Icon), true, 0.8f*MenuContentTransp);
361 vw_DrawTextUTF32(X1+20+64, Y1+9, -610, 0, 1.0f, MissionList[i].TitleColor, 0.8f*MenuContentTransp, vw_GetTextUTF32(MissionList[i].Title));
362 vw_DrawTextUTF32(X1+20+64, Y1+33, -610, 0, 1.0f, MissionList[i].DescrColor, 0.8f*MenuContentTransp, vw_GetTextUTF32(MissionList[i].Descr));
363 }
364 }
365
366 Y1 += 64;
367 }
368 // если не стоим над профилями - нужно сбросить флаг
369 if (TMPSoundOnMissionID == -1)
370 SoundOnMissionID = -1;
371
372
373
374
375
376 Y1 = 270;
377 // стрелки перемещения списка
378 if (DrawListUpButton(X1+718, Y1, MenuContentTransp, !(StartMission > 0))) {
379 StartMission--;
380 EndMission--;
381 }
382
383 if (DrawListDownButton(X1+718,Y1+320-32, MenuContentTransp, !(StartMission < AllMission-5))) {
384 StartMission++;
385 EndMission++;
386 }
387 // проверяем колесико мышки, если курсор находится над активной частью
388 DstRect(X1,Y1,X1+750,Y1+320);
389 if (vw_MouseOverRect(DstRect)) {
390 if (vw_GetWheelStatus() != 0 && !isDialogBoxDrawing()) {
391 StartMission += vw_GetWheelStatus();
392 EndMission += vw_GetWheelStatus();
393
394 if (StartMission < 0) {
395 StartMission = 0;
396 EndMission = 4;
397 }
398 if (EndMission > AllMission-1) {
399 EndMission = AllMission-1;
400 StartMission = EndMission-4;
401 }
402
403 vw_ResetWheelStatus();
404 }
405 } else if (vw_GetWheelStatus() != 0) {
406 vw_ResetWheelStatus();
407 }
408
409 // выводим отображение положени в списке на полоске со стрелками
410 SrcRect(0,0,32,32);
411 DstRect(X1+750-32+4,Y1+32+((320.0f-64)/AllMission)*StartMission,X1+750-4,Y1+32+((320.0f-64)/AllMission)*(EndMission+1));
412 vw_Draw2D(DstRect, SrcRect, GetPreloadedTextureAsset("menu/whitepoint.tga"), true, 0.3f*MenuContentTransp);
413
414 // обработка перетягивания ползунка отображения позиции списка
415 // если стоим на ползунком и нажали кнопку мышки - "захватываем"
416 if (!SliderUnderMouseControl && vw_MouseOverRect(DstRect) && vw_GetMouseLeftClick(false) && !isDialogBoxDrawing()) {
417 SliderUnderMouseControl = true;
418 PlayMenuSFX(eMenuSFX::Click, 1.0f);
419 }
420 // если ползунок был захвачен, но уже не над секцией где его можно перетягивать или отпустили мышку - отпускаем
421 sRECT DstRect2;
422 DstRect2(X1+750-32+4,Y1+32,X1+750-4,Y1+32+(320.0f-64));
423 if ((SliderUnderMouseControl && (!vw_MouseOverRect(DstRect2) || !vw_GetMouseLeftClick(false))) || isDialogBoxDrawing()) {
424 SliderUnderMouseControl = false;
425 }
426 // просто кликнули на зону перетягивания, не на ползунок
427 if (!vw_MouseOverRect(DstRect) && vw_MouseOverRect(DstRect2) && vw_GetMouseLeftClick(false) && !isDialogBoxDrawing()) {
428 SliderUnderMouseControl = true;
429 PlayMenuSFX(eMenuSFX::Click, 1.0f);
430 vw_SetMouseLeftClick(false);
431 }
432 // отображаем курсором, что можно кликать на полосе прокрутки
433 if (vw_MouseOverRect(DstRect2))
434 SetCursorStatus(eCursorStatus::ActionAllowed);
435 // корректируем его положение ползунка согласно положению мышки
436 if (SliderUnderMouseControl) {
437 int MouseX, MouseY;
438 vw_GetMousePos(MouseX, MouseY);
439 int SliderNewPosition = (MouseY - Y1-32)/((320.0f-64)/AllMission);
440
441 StartMission = 0;
442 EndMission = 4;
443 if (SliderNewPosition > 2) {
444 StartMission = SliderNewPosition-2;
445 EndMission = SliderNewPosition+2;
446
447 if (SliderNewPosition >= AllMission-2) {
448 StartMission = AllMission-5;
449 EndMission = AllMission-1;
450 }
451 }
452 }
453
454
455
456
457
458
459 int X = GameConfig().InternalWidth / 2 - 284;
460 int Y = 165 + 100 * 5;
461 if (DrawButton256(X,Y, vw_GetTextUTF32("MAIN MENU"), MenuContentTransp, Button10Transp, LastButton10UpdateTime))
462 cCommand::GetInstance().Set(eCommand::SWITCH_TO_MAIN_MENU);
463
464 X = GameConfig().InternalWidth / 2 + 28;
465 if (DrawButton256(X,Y, vw_GetTextUTF32("NEXT"), MenuContentTransp, Button11Transp, LastButton11UpdateTime, !(CurrentMission >= 0))) {
466 // если уже играли в эту миссию
467 if (GameConfig().Profile[CurrentProfile].MissionReplayCount[CurrentMission] > 0) {
468 if (GameConfig().NeedShowHint[5]) {
469 SetCurrentDialogBox(eDialogBox::StartMissionSecondTime);
470 } else {
471 cCommand::GetInstance().Set(eCommand::SWITCH_TO_WORKSHOP);
472 CurrentWorkshop = 3;
473 WorkshopCreate();
474 }
475 } else {
476 cCommand::GetInstance().Set(eCommand::SWITCH_TO_WORKSHOP);
477 CurrentWorkshop = 3;
478 WorkshopCreate();
479 }
480 }
481
482
483
484
485 }
486
487 } // astromenace namespace
488 } // viewizard namespace
489