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