1 /*
2  * This file is part of the Colobot: Gold Edition source code
3  * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam
4  * http://epsitec.ch; http://colobot.info; http://github.com/colobot
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://gnu.org/licenses
18  */
19 
20 #include "level/robotmain.h"
21 
22 #include "CBot/CBot.h"
23 
24 #include "app/app.h"
25 #include "app/input.h"
26 #include "app/pausemanager.h"
27 
28 #include "common/config_file.h"
29 #include "common/event.h"
30 #include "common/logger.h"
31 #include "common/make_unique.h"
32 #include "common/restext.h"
33 #include "common/settings.h"
34 #include "common/stringutils.h"
35 
36 #include "common/resources/inputstream.h"
37 #include "common/resources/outputstream.h"
38 #include "common/resources/resourcemanager.h"
39 
40 #include "graphics/engine/camera.h"
41 #include "graphics/engine/cloud.h"
42 #include "graphics/engine/engine.h"
43 #include "graphics/engine/lightman.h"
44 #include "graphics/engine/lightning.h"
45 #include "graphics/engine/oldmodelmanager.h"
46 #include "graphics/engine/particle.h"
47 #include "graphics/engine/planet.h"
48 #include "graphics/engine/pyro_manager.h"
49 #include "graphics/engine/terrain.h"
50 #include "graphics/engine/text.h"
51 #include "graphics/engine/water.h"
52 
53 #include "graphics/model/model_manager.h"
54 
55 #include "level/mainmovie.h"
56 #include "level/player_profile.h"
57 #include "level/scene_conditions.h"
58 #include "level/scoreboard.h"
59 
60 #include "level/parser/parser.h"
61 
62 #include "math/const.h"
63 #include "math/func.h"
64 #include "math/geometry.h"
65 
66 #include "object/object.h"
67 #include "object/object_create_exception.h"
68 #include "object/object_manager.h"
69 
70 #include "object/auto/auto.h"
71 
72 #include "object/motion/motion.h"
73 #include "object/motion/motionhuman.h"
74 #include "object/motion/motiontoto.h"
75 
76 #include "object/subclass/exchange_post.h"
77 
78 #include "object/task/task.h"
79 #include "object/task/taskbuild.h"
80 #include "object/task/taskmanip.h"
81 
82 #include "physics/physics.h"
83 
84 #include "script/cbottoken.h"
85 #include "script/script.h"
86 #include "script/scriptfunc.h"
87 
88 #include "sound/sound.h"
89 
90 #include "ui/debug_menu.h"
91 #include "ui/displayinfo.h"
92 #include "ui/displaytext.h"
93 #include "ui/maindialog.h"
94 #include "ui/mainmap.h"
95 #include "ui/mainshort.h"
96 #include "ui/mainui.h"
97 
98 #include "ui/controls/button.h"
99 #include "ui/controls/edit.h"
100 #include "ui/controls/group.h"
101 #include "ui/controls/interface.h"
102 #include "ui/controls/label.h"
103 #include "ui/controls/map.h"
104 #include "ui/controls/shortcut.h"
105 #include "ui/controls/slider.h"
106 #include "ui/controls/window.h"
107 
108 #include "ui/screen/screen_loading.h"
109 
110 #include <algorithm>
111 #include <iomanip>
112 #include <stdexcept>
113 #include <ctime>
114 
115 #include <boost/lexical_cast.hpp>
116 
117 
118 // Global variables.
119 
120 const float UNIT = 4.0f;    // default for g_unit
121 float   g_unit;             // conversion factor
122 
123 // Min/max values for the game speed.
124 const float MIN_SPEED = 1/8.0f;
125 const float MAX_SPEED = 256.0f;
126 
127 // Reference colors used when recoloring textures, see ChangeColor()
128 const Gfx::Color COLOR_REF_BOT   = Gfx::Color( 10.0f/256.0f, 166.0f/256.0f, 254.0f/256.0f);  // blue
129 const Gfx::Color COLOR_REF_ALIEN = Gfx::Color(135.0f/256.0f, 170.0f/256.0f,  13.0f/256.0f);  // green
130 const Gfx::Color COLOR_REF_GREEN = Gfx::Color(135.0f/256.0f, 170.0f/256.0f,  13.0f/256.0f);  // green
131 const Gfx::Color COLOR_REF_WATER = Gfx::Color( 25.0f/256.0f, 255.0f/256.0f, 240.0f/256.0f);  // cyan
132 
133 //! Constructor of robot application
CRobotMain()134 CRobotMain::CRobotMain()
135 {
136     m_app        = CApplication::GetInstancePointer();
137 
138     m_eventQueue = m_app->GetEventQueue();
139     m_sound      = m_app->GetSound();
140 
141     m_engine     = Gfx::CEngine::GetInstancePointer();
142     m_oldModelManager = m_engine->GetModelManager();
143     m_lightMan   = m_engine->GetLightManager();
144     m_particle   = m_engine->GetParticle();
145     m_water      = m_engine->GetWater();
146     m_cloud      = m_engine->GetCloud();
147     m_lightning  = m_engine->GetLightning();
148     m_planet     = m_engine->GetPlanet();
149     m_input      = CInput::GetInstancePointer();
150 
151     m_modelManager = MakeUnique<Gfx::CModelManager>();
152     m_settings    = MakeUnique<CSettings>();
153     m_pause       = MakeUnique<CPauseManager>();
154     m_interface   = MakeUnique<Ui::CInterface>();
155     m_terrain     = MakeUnique<Gfx::CTerrain>();
156     m_camera      = MakeUnique<Gfx::CCamera>();
157     m_displayText = MakeUnique<Ui::CDisplayText>();
158     m_movie       = MakeUnique<CMainMovie>();
159     m_ui          = MakeUnique<Ui::CMainUserInterface>();
160     m_short       = MakeUnique<Ui::CMainShort>();
161     m_map         = MakeUnique<Ui::CMainMap>();
162 
163     m_objMan = MakeUnique<CObjectManager>(
164         m_engine,
165         m_terrain.get(),
166         m_oldModelManager,
167         m_modelManager.get(),
168         m_particle);
169 
170     m_debugMenu   = MakeUnique<Ui::CDebugMenu>(this, m_engine, m_objMan.get(), m_sound);
171 
172     m_time = 0.0f;
173     m_gameTime = 0.0f;
174     m_gameTimeAbsolute = 0.0f;
175 
176     m_levelCategory = LevelCategory::Exercises;
177     m_levelChap = 0;
178     m_levelRank = 0;
179     m_sceneReadPath = "";
180 
181     m_missionTimerEnabled = false;
182     m_missionTimerStarted = false;
183     m_missionTimer = 0.0f;
184 
185     m_phase       = PHASE_PLAYER_SELECT;
186     m_visitLast   = EVENT_NULL;
187     m_visitObject = nullptr;
188     m_visitArrow  = nullptr;
189     m_audioTrack  = "";
190     m_audioRepeat = true;
191     m_satcomTrack  = "";
192     m_satcomRepeat = true;
193     m_editorTrack  = "";
194     m_editorRepeat = true;
195     m_selectObject = nullptr;
196     m_infoUsed     = 0;
197 
198     m_controller   = nullptr;
199     m_missionType  = MISSION_NORMAL;
200     m_immediatSatCom = false;
201     m_beginSatCom  = false;
202     m_lockedSatCom = false;
203     m_movieLock    = false;
204     m_satComLock   = false;
205     m_editLock     = false;
206     m_editFull     = false;
207     m_hilite       = false;
208     m_cheatSelectInsect = false;
209     m_cheatShowSoluce   = false;
210 
211     m_codeBattleInit = false;
212     m_codeBattleStarted = false;
213 
214     m_teamNames.clear();
215 
216     #if DEV_BUILD
217     m_cheatAllMission      = true; // for development
218     #else
219     m_cheatAllMission      = false;
220     #endif
221 
222     m_cheatRadar   = false;
223     m_fixScene     = false;
224     m_cheatTrainerPilot = false;
225     m_friendAim    = false;
226     m_resetCreate  = false;
227     m_shortCut     = true;
228 
229     m_commandHistoryIndex = -1;
230 
231     m_movieInfoIndex = -1;
232 
233     m_tooltipPos = Math::Point(0.0f, 0.0f);
234     m_tooltipName.clear();
235     m_tooltipTime = 0.0f;
236 
237     m_winTerminate   = false;
238 
239     m_globalMagnifyDamage = 1.0f;
240 
241     m_autosave = true;
242     m_autosaveInterval = 5;
243     m_autosaveSlots = 3;
244     m_autosaveLast = 0.0f;
245 
246     m_shotSaving = 0;
247 
248     m_build = 0;
249     m_researchDone.clear();  // no research done
250     m_researchDone[0] = 0;
251     m_researchEnable = 0;
252     g_unit = UNIT;
253 
254     for (int i = 0; i < MAXSHOWLIMIT; i++)
255     {
256         m_showLimit[i].used = false;
257         m_showLimit[i].total = 0;
258         m_showLimit[i].link = nullptr;
259     }
260 
261     m_debugCrashSpheres = false;
262 
263     m_engine->SetTerrain(m_terrain.get());
264 
265     m_app->SetMouseMode(MOUSE_ENGINE);
266 
267     m_movie->Flush();
268 
269     FlushDisplayInfo();
270 
271     InitEye();
272 
273     m_engine->SetTracePrecision(1.0f);
274 
275     m_settings->LoadSettings();
276     m_settings->SaveSettings();
277     m_settings->SaveResolutionSettings(m_app->GetVideoConfig());
278 
279     SelectPlayer(CPlayerProfile::GetLastName());
280 
281     CScriptFunctions::Init();
282 }
283 
284 //! Destructor of robot application
~CRobotMain()285 CRobotMain::~CRobotMain()
286 {
287 }
288 
GetCamera()289 Gfx::CCamera* CRobotMain::GetCamera()
290 {
291     return m_camera.get();
292 }
293 
GetTerrain()294 Gfx::CTerrain* CRobotMain::GetTerrain()
295 {
296     return m_terrain.get();
297 }
298 
GetInterface()299 Ui::CInterface* CRobotMain::GetInterface()
300 {
301     return m_interface.get();
302 }
303 
GetDisplayText()304 Ui::CDisplayText* CRobotMain::GetDisplayText()
305 {
306     return m_displayText.get();
307 }
308 
GetPauseManager()309 CPauseManager* CRobotMain::GetPauseManager()
310 {
311     return m_pause.get();
312 }
313 
PhaseToString(Phase phase)314 std::string PhaseToString(Phase phase)
315 {
316     if (phase == PHASE_WELCOME1) return "PHASE_WELCOME1";
317     if (phase == PHASE_WELCOME2) return "PHASE_WELCOME2";
318     if (phase == PHASE_WELCOME3) return "PHASE_WELCOME3";
319     if (phase == PHASE_PLAYER_SELECT) return "PHASE_PLAYER_SELECT";
320     if (phase == PHASE_APPERANCE) return "PHASE_APPERANCE";
321     if (phase == PHASE_MAIN_MENU) return "PHASE_MAIN_MENU";
322     if (phase == PHASE_LEVEL_LIST) return "PHASE_LEVEL_LIST";
323     if (phase == PHASE_MOD_LIST) return "PHASE_MOD_LIST";
324     if (phase == PHASE_SIMUL) return "PHASE_SIMUL";
325     if (phase == PHASE_SETUPd) return "PHASE_SETUPd";
326     if (phase == PHASE_SETUPg) return "PHASE_SETUPg";
327     if (phase == PHASE_SETUPp) return "PHASE_SETUPp";
328     if (phase == PHASE_SETUPc) return "PHASE_SETUPc";
329     if (phase == PHASE_SETUPs) return "PHASE_SETUPs";
330     if (phase == PHASE_SETUPds) return "PHASE_SETUPds";
331     if (phase == PHASE_SETUPgs) return "PHASE_SETUPgs";
332     if (phase == PHASE_SETUPps) return "PHASE_SETUPps";
333     if (phase == PHASE_SETUPcs) return "PHASE_SETUPcs";
334     if (phase == PHASE_SETUPss) return "PHASE_SETUPss";
335     if (phase == PHASE_WRITEs) return "PHASE_WRITEs";
336     if (phase == PHASE_READ) return "PHASE_READ";
337     if (phase == PHASE_READs) return "PHASE_READs";
338     if (phase == PHASE_WIN) return "PHASE_WIN";
339     if (phase == PHASE_LOST) return "PHASE_LOST";
340     if (phase == PHASE_QUIT_SCREEN) return "PHASE_QUIT_SCREEN";
341     if (phase == PHASE_SATCOM) return "PHASE_SATCOM";
342     return "(unknown)";
343 }
344 
IsInSimulationConfigPhase(Phase phase)345 bool IsInSimulationConfigPhase(Phase phase)
346 {
347     return (phase >= PHASE_SETUPds && phase <= PHASE_SETUPss) || phase == PHASE_READs || phase == PHASE_WRITEs;
348 }
349 
IsPhaseWithWorld(Phase phase)350 bool IsPhaseWithWorld(Phase phase)
351 {
352     if (phase == PHASE_SIMUL    ) return true;
353     if (phase == PHASE_WIN      ) return true;
354     if (phase == PHASE_LOST     ) return true;
355     if (phase == PHASE_APPERANCE) return true;
356     if (IsInSimulationConfigPhase(phase)) return true;
357     return false;
358 }
359 
IsMainMenuPhase(Phase phase)360 bool IsMainMenuPhase(Phase phase)
361 {
362     if (phase == PHASE_APPERANCE) return true;
363     return !IsPhaseWithWorld(phase);
364 }
365 
366 //! Changes phase
ChangePhase(Phase phase)367 void CRobotMain::ChangePhase(Phase phase)
368 {
369     bool resetWorld = false;
370     if ((IsPhaseWithWorld(m_phase) || IsPhaseWithWorld(phase)) && !IsInSimulationConfigPhase(m_phase) && !IsInSimulationConfigPhase(phase))
371     {
372         if (IsPhaseWithWorld(m_phase) && !IsPhaseWithWorld(phase) && m_exitAfterMission)
373         {
374             GetLogger()->Info("Mission finished in single mission mode, exiting\n");
375             m_eventQueue->AddEvent(Event(EVENT_QUIT));
376             return;
377         }
378 
379         GetLogger()->Info("Reseting world on phase change...\n");
380         resetWorld = true;
381     }
382 
383     if (resetWorld)
384     {
385         m_missionTimerEnabled = m_missionTimerStarted = false;
386         m_missionTimer = 0.0f;
387 
388         if (m_phase == PHASE_SIMUL)  // ends a simulation?
389         {
390             SaveAllScript();
391             m_sound->StopMusic(0.0f);
392             m_camera->SetControllingObject(nullptr);
393 
394             if (m_gameTime > 10.0f)  // did you play at least 10 seconds?
395             {
396                 m_playerProfile->IncrementLevelTryCount(m_levelCategory, m_levelChap, m_levelRank);
397             }
398 
399             if (m_userPause != nullptr)
400             {
401                 m_pause->DeactivatePause(m_userPause);
402                 m_userPause = nullptr;
403             }
404         }
405 
406         if (phase == PHASE_WIN)  // wins a simulation?
407         {
408             m_playerProfile->SetLevelPassed(m_levelCategory, m_levelChap, m_levelRank, true);
409             m_ui->NextMission();  // passes to the next mission
410         }
411 
412         DeleteAllObjects();  // removes all the current 3D Scene
413     }
414 
415     m_phase = phase;
416 
417     if (m_phase != PHASE_SIMUL)
418     {
419         Ui::CWindow* pw = static_cast<Ui::CWindow*>(m_interface->SearchControl(EVENT_WINDOW6));
420         if ( pw != nullptr )  pw->ClearState(Ui::STATE_VISIBLE | Ui::STATE_ENABLE);
421     }
422 
423     if (resetWorld)
424     {
425         m_winDelay     = 0.0f;
426         m_lostDelay    = 0.0f;
427         m_beginSatCom = false;
428         m_movieLock   = false;
429         m_satComLock  = false;
430         m_editLock    = false;
431         m_resetCreate = false;
432         m_infoObject  = nullptr;
433 
434         m_pause->FlushPause();
435         m_freePhotoPause = nullptr;
436         m_userPause = nullptr;
437         m_focusPause = nullptr;
438         FlushDisplayInfo();
439         m_engine->SetRankView(0);
440         m_terrain->FlushRelief();
441         m_engine->DeleteAllObjects();
442         m_oldModelManager->DeleteAllModelCopies();
443         m_engine->SetWaterAddColor(Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f));
444         m_engine->SetBackground("");
445         m_engine->SetBackForce(false);
446         m_engine->SetForegroundName("");
447         m_engine->SetOverColor();
448         m_engine->DeleteGroundMark(0);
449         SetSpeed(1.0f);
450         m_terrain->SetWind(Math::Vector(0.0f, 0.0f, 0.0f));
451         m_terrain->FlushBuildingLevel();
452         m_terrain->FlushFlyingLimit();
453         m_lightMan->FlushLights();
454         m_particle->FlushParticle();
455         m_water->Flush();
456         m_cloud->Flush();
457         m_lightning->Flush();
458         m_planet->Flush();
459         m_interface->Flush();
460         m_newScriptName.clear();
461         m_sound->SetListener(Math::Vector(0.0f, 0.0f, 0.0f), Math::Vector(0.0f, 0.0f, 1.0f));
462         m_sound->StopAll();
463         m_camera->SetType(Gfx::CAM_TYPE_NULL);
464         m_movie->Flush();
465         m_movieInfoIndex = -1;
466         m_shortCut = true;
467 
468         m_viewpoints.clear();
469     }
470     ClearInterface();
471 
472     Math::Point dim, pos;
473 
474     // Creates and hide the command console.
475     dim.x = 200.0f/640.0f;
476     dim.y =  18.0f/480.0f;
477     pos.x =  20.0f/640.0f;
478     pos.y = 100.0f/480.0f;
479     Ui::CEdit* pe = m_interface->CreateEdit(pos, dim, 0, EVENT_CMD);
480     pe->ClearState(Ui::STATE_VISIBLE);
481     pe->SetMaxChar(100);
482     m_cmdEdit = false;  // hidden for now
483 
484     // Creates the speedometer.
485     dim.x =  30.0f/640.0f;
486     dim.y =  20.0f/480.0f;
487     pos.x =   4.0f/640.0f;
488     pos.y = 426.0f/480.0f;
489 
490     // Creates the save indicator
491     Ui::CButton* pb = m_interface->CreateButton(pos, dim, 0, EVENT_SPEED);
492     pb->SetState(Ui::STATE_SIMPLY);
493     pb->ClearState(Ui::STATE_VISIBLE);
494 
495     if (m_phase == PHASE_PLAYER_SELECT)
496     {
497         if (CResourceManager::DirectoryExists("crashsave"))
498         {
499             GetLogger()->Info("Pre-crash save found!\n");
500             m_ui->GetDialog()->StartQuestion(
501                 "Your game seems to have crashed. Do you want to restore pre-crash state?", false, false, false,
502                 [&]()
503                 {
504                     GetLogger()->Info("Trying to restore pre-crash state...\n");
505                     assert(m_playerProfile != nullptr);
506                     m_playerProfile->LoadScene("../../crashsave");
507                     CResourceManager::RemoveDirectory("crashsave");
508                 },
509                 [&]()
510                 {
511                     GetLogger()->Info("Not restoring pre-crash state\n");
512                     CResourceManager::RemoveDirectory("crashsave");
513                 }
514             );
515         }
516     }
517 
518     m_ui->ChangePhase(m_phase);
519     if (m_phase == PHASE_SATCOM)
520     {
521         m_interface->DeleteControl(EVENT_WINDOW5);
522         StartDisplayInfo(InjectLevelPathsForCurrentLevel("cbot.txt", "help/%lng%"), 0);
523     }
524 
525     if (!resetWorld) return;
526 
527     dim.x = 32.0f/640.0f;
528     dim.y = 32.0f/480.0f;
529     float ox = 3.0f/640.0f;
530     float oy = 3.0f/480.0f;
531     float sx = (32.0f+2.0f)/640.0f;
532     float sy = (32.0f+2.0f)/480.0f;
533 
534     if (m_phase != PHASE_APPERANCE)
535     {
536         m_engine->SetDrawWorld(true);
537         m_engine->SetDrawFront(false);
538         m_fixScene = false;
539     }
540 
541     if (m_phase == PHASE_SIMUL)
542     {
543         bool loading = !m_sceneReadPath.empty();
544 
545         m_ui->ShowLoadingScreen(true);
546         m_ui->GetLoadingScreen()->SetProgress(0.0f, RT_LOADING_INIT);
547 
548         m_map->CreateMap();
549         m_map->ShowMap(false);
550 
551         try
552         {
553             CreateScene(m_ui->GetSceneSoluce(), false, false);  // interactive scene
554             if (m_mapImage)
555                 m_map->SetFixImage(m_mapFilename);
556 
557             m_app->ResetTimeAfterLoading();
558 
559             m_sound->StopMusic(0.0f);
560             if (m_base == nullptr || loading) StartMusic();
561 
562             if (m_immediatSatCom && !loading  &&
563                 m_infoFilename[SATCOM_HUSTON][0] != 0)
564                 StartDisplayInfo(SATCOM_HUSTON, false);  // shows the instructions
565         }
566         catch (const std::runtime_error& e)
567         {
568             LevelLoadingError("An error occurred while trying to load a level", e);
569         }
570     }
571 
572     if (m_phase == PHASE_WIN)
573     {
574         m_sound->StopAll();
575         if (m_endingWin.empty())
576         {
577             ChangePhase(PHASE_LEVEL_LIST);
578         }
579         else
580         {
581             m_winTerminate = (m_endingWin.substr(m_endingWin.find_last_of("/")+1) == "win904.txt");
582             m_levelFile = m_endingWin;
583             try
584             {
585                 CreateScene(false, true, false);  // sets scene
586 
587                 pos.x = ox+sx*1;  pos.y = oy+sy*1;
588                 Math::Point ddim;
589                 ddim.x = dim.x*2;  ddim.y = dim.y*2;
590                 m_interface->CreateButton(pos, ddim, 16, EVENT_BUTTON_OK);
591 
592                 if (m_winTerminate)
593                 {
594                     pos.x = ox+sx*3;  pos.y = oy+sy*0.2f;
595                     ddim.x = dim.x*15;  ddim.y = dim.y*3.0f;
596                     pe = m_interface->CreateEdit(pos, ddim, 0, EVENT_EDIT0);
597                     pe->SetGenericMode(true);
598                     pe->SetFontType(Gfx::FONT_COMMON);
599                     pe->SetEditCap(false);
600                     pe->SetHighlightCap(false);
601                     pe->ReadText(std::string("help/") + m_app->GetLanguageChar() + std::string("/win.txt"));
602                 }
603                 else
604                 {
605                     m_displayText->DisplayError(INFO_WIN, Math::Vector(0.0f,0.0f,0.0f), 15.0f, 60.0f, 1000.0f);
606                 }
607                 StartMusic();
608             }
609             catch (const std::runtime_error& e)
610             {
611                 LevelLoadingError("An error occurred while trying to load win scene", e);
612             }
613         }
614     }
615 
616     if (m_phase == PHASE_LOST)
617     {
618         m_sound->StopAll();
619         if (m_endingLost.empty())
620         {
621             ChangePhase(PHASE_LEVEL_LIST);
622         }
623         else
624         {
625             m_winTerminate = false;
626             m_levelFile = m_endingLost;
627             try
628             {
629                 CreateScene(false, true, false);  // sets scene
630 
631                 pos.x = ox+sx*1;  pos.y = oy+sy*1;
632                 Math::Point ddim;
633                 ddim.x = dim.x*2;  ddim.y = dim.y*2;
634                 m_interface->CreateButton(pos, ddim, 16, EVENT_BUTTON_OK);
635                 m_displayText->DisplayError(INFO_LOST, Math::Vector(0.0f,0.0f,0.0f), 15.0f, 60.0f, 1000.0f);
636 
637                 StartMusic();
638             }
639             catch (const std::runtime_error& e)
640             {
641                 LevelLoadingError("An error occurred while trying to load lost scene", e);
642             }
643         }
644     }
645 
646     m_engine->LoadAllTextures();
647 }
648 
GetPhase()649 Phase CRobotMain::GetPhase()
650 {
651     return m_phase;
652 }
653 
654 //! Processes an event
ProcessEvent(Event & event)655 bool CRobotMain::ProcessEvent(Event &event)
656 {
657     if (!m_ui->EventProcess(event)) return false;
658     if (m_phase == PHASE_SIMUL)
659     {
660         if (!m_editFull)
661             m_camera->EventProcess(event);
662     }
663     if (!m_debugMenu->EventProcess(event)) return false;
664 
665     if (event.type == EVENT_FRAME)
666     {
667         if (!m_movie->EventProcess(event))  // end of the movie?
668         {
669             MainMovieType type = m_movie->GetStopType();
670             if (type == MM_SATCOMopen)
671             {
672                 m_pause->DeactivatePause(m_satcomMoviePause);
673                 m_satcomMoviePause = nullptr;
674                 SelectObject(m_infoObject, false);  // hands over the command buttons
675                 m_map->ShowMap(m_mapShow);
676                 m_displayText->HideText(false);
677                 int i = m_movieInfoIndex;
678                 StartDisplayInfo(m_movieInfoIndex, false);
679                 m_movieInfoIndex = i;
680             }
681         }
682 
683         m_displayText->EventProcess(event);
684 
685         if (m_displayInfo != nullptr)  // current edition?
686             m_displayInfo->EventProcess(event);
687 
688         UpdateInfoText();
689 
690         return EventFrame(event);
691     }
692 
693     if (event.type == EVENT_RELOAD_TEXTURES)
694     {
695         if (IsPhaseWithWorld(m_phase))
696         {
697             ChangeColor();
698             UpdateMap();
699         }
700         m_engine->LoadAllTextures();
701     }
702 
703     if (event.type == EVENT_RESOLUTION_CHANGED)
704     {
705         // Recreate the interface (needed if the aspect ratio changes)
706         // TODO: This can sometimes cause unwanted side effects, like hidden windows reappearing. To be fixed during CEGUI refactoring.
707         m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE));
708         CreateShortcuts();
709     }
710 
711     if (event.type == EVENT_FOCUS_LOST)
712     {
713         GetLogger()->Trace("Window unfocused\n");
714         if (m_settings->GetFocusLostPause())
715         {
716             m_focusPause = m_pause->ActivatePause(PAUSE_ENGINE);
717         }
718 
719         if (m_settings->GetFocusLostMute())
720         {
721             m_sound->SetAudioVolume(0);
722             m_sound->SetMusicVolume(0);
723         }
724         return false;
725     }
726 
727     if (event.type == EVENT_FOCUS_GAINED)
728     {
729         GetLogger()->Trace("Window focused\n");
730         if (m_focusPause != nullptr)
731         {
732             m_pause->DeactivatePause(m_focusPause);
733             m_focusPause = nullptr;
734         }
735 
736         if (m_settings->GetFocusLostMute())
737         {
738             int volume;
739             // Set music volume
740             if (GetConfigFile().GetIntProperty("Setup", "MusicVolume", volume))
741             {
742                 m_sound->SetMusicVolume(volume);
743             }
744             else
745             {
746                 m_sound->SetMusicVolume(MAXVOLUME*3/4);
747             }
748             // Set audio volume
749             if (GetConfigFile().GetIntProperty("Setup", "AudioVolume", volume))
750             {
751                 m_sound->SetAudioVolume(volume);
752             }
753             else
754             {
755                 m_sound->SetAudioVolume(MAXVOLUME);
756             }
757         }
758 
759         return false;
760     }
761 
762     if (event.type == EVENT_WRITE_SCENE_FINISHED)
763     {
764         IOWriteSceneFinished();
765         return false;
766     }
767 
768     if (event.type == EVENT_UPDINTERFACE)
769     {
770         if (m_missionType == MISSION_CODE_BATTLE && !m_codeBattleStarted)
771         {
772             CreateCodeBattleInterface();
773         }
774     }
775 
776     if (event.type == EVENT_CODE_BATTLE_START)
777     {
778         m_pause->DeactivatePause(m_userPause);
779         m_userPause = nullptr;
780     }
781 
782     if (event.type == EVENT_CODE_BATTLE_SPECTATOR)
783     {
784         SetCodeBattleSpectatorMode(!m_codeBattleSpectator);
785     }
786 
787     if (event.type >= EVENT_VIEWPOINT0 && event.type <= EVENT_VIEWPOINT9)
788     {
789         m_camera->SetType(Gfx::CAM_TYPE_SCRIPT);
790         m_camera->SetSmooth(Gfx::CAM_SMOOTH_HARD);
791         m_camera->SetScriptCameraAnimate(m_viewpoints[event.type - EVENT_VIEWPOINT0].eye, m_viewpoints[event.type - EVENT_VIEWPOINT0].look);
792     }
793 
794     // Management of the console.
795     if (event.type == EVENT_KEY_DOWN)
796     {
797         auto data = event.GetData<KeyEventData>();
798 
799         if (data->slot == INPUT_SLOT_CMDLINE)
800         {
801             if (m_phase != PHASE_PLAYER_SELECT &&
802                 !m_movie->IsExist()   &&
803                 !m_movieLock && !m_editLock && !m_cmdEdit)
804             {
805                 Ui::CEdit* pe = static_cast<Ui::CEdit*>(m_interface->SearchControl(EVENT_CMD));
806                 if (pe == nullptr) return false;
807                 pe->SetState(Ui::STATE_VISIBLE);
808                 m_interface->SetFocus(pe);
809                 if (m_phase == PHASE_SIMUL) m_cmdEditPause = m_pause->ActivatePause(PAUSE_ENGINE);
810                 m_cmdEdit = true;
811                 m_commandHistoryIndex = -1; // no element selected in command history
812             }
813             return false;
814         }
815 
816         if (IsPhaseWithWorld(m_phase))
817         {
818             if (data->key == KEY(F10))
819             {
820                 m_debugMenu->ToggleInterface();
821                 return false;
822             }
823         }
824     }
825 
826     // Browse forward command history with UP key
827     if (event.type == EVENT_KEY_DOWN &&
828         event.GetData<KeyEventData>()->key == KEY(UP) && m_cmdEdit)
829     {
830         Ui::CEdit* pe = static_cast<Ui::CEdit*>(m_interface->SearchControl(EVENT_CMD));
831         if (pe == nullptr) return false;
832         std::string cmd = GetNextFromCommandHistory();
833         if (!cmd.empty()) pe->SetText(cmd);
834         return false;
835     }
836 
837     // Browse backward command history with DOWN key
838     if (event.type == EVENT_KEY_DOWN &&
839         event.GetData<KeyEventData>()->key == KEY(DOWN) && m_cmdEdit)
840     {
841         Ui::CEdit* pe = static_cast<Ui::CEdit*>(m_interface->SearchControl(EVENT_CMD));
842         if (pe == nullptr) return false;
843         std::string cmd = GetPreviousFromCommandHistory();
844         if (!cmd.empty()) pe->SetText(cmd);
845         return false;
846     }
847 
848     if (event.type == EVENT_KEY_DOWN &&
849         event.GetData<KeyEventData>()->key == KEY(RETURN) && m_cmdEdit)
850     {
851         std::string cmd;
852         Ui::CEdit* pe = static_cast<Ui::CEdit*>(m_interface->SearchControl(EVENT_CMD));
853         if (pe == nullptr) return false;
854         cmd = pe->GetText(50);
855         pe->SetText("");
856         pe->ClearState(Ui::STATE_VISIBLE);
857         m_interface->SetFocus(nullptr);
858         if (m_phase == PHASE_SIMUL)
859         {
860             m_pause->DeactivatePause(m_cmdEditPause);
861             m_cmdEditPause = nullptr;
862         }
863         ExecuteCmd(cmd);
864         PushToCommandHistory(cmd);
865         m_cmdEdit = false;
866         return false;
867     }
868 
869     if (event.type == EVENT_KEY_DOWN && m_cmdEdit)
870         return false; // cheat console active, so ignore keys
871 
872     // Management of the speed change.
873     if (event.type == EVENT_SPEED)
874         SetSpeed(1.0f);
875 
876     if (!m_displayText->EventProcess(event))
877         return false;
878 
879     if (event.type == EVENT_MOUSE_MOVE)
880     {
881         HiliteObject(event.mousePos);
882     }
883 
884     if (m_displayInfo != nullptr)  // current info?
885     {
886         m_displayInfo->EventProcess(event);
887 
888         if (event.type == EVENT_KEY_DOWN)
889         {
890             auto data = event.GetData<KeyEventData>();
891 
892             if (data->slot == INPUT_SLOT_HELP ||
893                 data->slot == INPUT_SLOT_PROG ||
894                 data->key == KEY(ESCAPE))
895             {
896                 StopDisplayInfo();
897             }
898         }
899 
900         if (event.type == EVENT_OBJECT_INFOOK)
901             StopDisplayInfo();
902 
903         if (m_displayInfo == nullptr && m_phase == PHASE_SATCOM)
904             ChangePhase(PHASE_MAIN_MENU);
905 
906         return false;
907     }
908 
909     CObject* obj;
910 
911     // Simulation phase of the game
912     if (m_phase == PHASE_SIMUL)
913     {
914         switch (event.type)
915         {
916             case EVENT_KEY_DOWN:
917             {
918                 auto data = event.GetData<KeyEventData>();
919 
920                 HiliteClear();
921                 if (m_editLock)  // current edition?
922                 {
923                     if (data->slot == INPUT_SLOT_HELP)
924                     {
925                         StartDisplayInfo(SATCOM_HUSTON, false);
926                         return false;
927                     }
928                     if (data->slot == INPUT_SLOT_PROG)
929                     {
930                         StartDisplayInfo(SATCOM_PROG, false);
931                         return false;
932                     }
933                     break;
934                 }
935                 if (m_movieLock)  // current movie?
936                 {
937                     if (data->slot == INPUT_SLOT_QUIT ||
938                         data->key == KEY(ESCAPE))
939                     {
940                         AbortMovie();
941                     }
942                     return false;
943                 }
944                 if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT)
945                 {
946                     if (data->slot == INPUT_SLOT_VISIT)
947                     {
948                         StartDisplayVisit(EVENT_NULL);
949                     }
950                     if (data->slot == INPUT_SLOT_QUIT ||
951                         data->key == KEY(ESCAPE))
952                     {
953                         StopDisplayVisit();
954                     }
955                     return false;
956                 }
957                 if (data->slot == INPUT_SLOT_QUIT)
958                 {
959                     if (m_movie->IsExist())
960                         StartDisplayInfo(SATCOM_HUSTON, false);
961                     else if (m_winDelay > 0.0f)
962                         ChangePhase(PHASE_WIN);
963                     else if (m_lostDelay > 0.0f)
964                         ChangePhase(PHASE_LOST);
965                     else if (!m_cmdEdit)
966                         m_ui->GetDialog()->StartPauseMenu();  // do you want to leave?
967                 }
968                 if (data->slot == INPUT_SLOT_PAUSE)
969                 {
970                     if (m_userPause == nullptr)
971                     {
972                         if (!m_pause->IsPauseType(PAUSE_ENGINE))
973                         {
974                             m_userPause = m_pause->ActivatePause(PAUSE_ENGINE);
975                         }
976                     }
977                     else
978                     {
979                         m_pause->DeactivatePause(m_userPause);
980                         m_userPause = nullptr;
981                     }
982                 }
983                 if (data->slot == INPUT_SLOT_CAMERA)
984                 {
985                     ChangeCamera();
986                 }
987                 if (data->slot == INPUT_SLOT_DESEL)
988                 {
989                     if (m_shortCut)
990                         DeselectObject();
991                 }
992                 if (data->slot == INPUT_SLOT_HUMAN)
993                 {
994                     SelectObject(SearchHuman());
995                 }
996                 if (data->slot == INPUT_SLOT_NEXT && ((event.kmodState & KEY_MOD(CTRL)) != 0))
997                 {
998                     m_short->SelectShortcut(EVENT_OBJECT_SHORTCUT_MODE); // switch bots <-> buildings
999                     return false;
1000                 }
1001                 if (data->slot == INPUT_SLOT_NEXT)
1002                 {
1003                     if (m_shortCut)
1004                         m_short->SelectNext();
1005                 }
1006                 if (data->slot == INPUT_SLOT_HELP)
1007                 {
1008                     StartDisplayInfo(SATCOM_HUSTON, true);
1009                 }
1010                 if (data->slot == INPUT_SLOT_PROG)
1011                 {
1012                     StartDisplayInfo(SATCOM_PROG, true);
1013                 }
1014                 if (data->slot == INPUT_SLOT_VISIT)
1015                 {
1016                     StartDisplayVisit(EVENT_NULL);
1017                 }
1018                 if (data->slot == INPUT_SLOT_SPEED_DEC)
1019                 {
1020                     SetSpeed(GetSpeed()*0.5f);
1021                 }
1022                 if (data->slot == INPUT_SLOT_SPEED_RESET)
1023                 {
1024                     SetSpeed(1.0f);
1025                 }
1026                 if (data->slot == INPUT_SLOT_SPEED_INC)
1027                 {
1028                     SetSpeed(GetSpeed()*2.0f);
1029                 }
1030                 if (data->slot == INPUT_SLOT_QUICKSAVE)
1031                 {
1032                     QuickSave();
1033                 }
1034                 if (data->slot == INPUT_SLOT_QUICKLOAD)
1035                 {
1036                     QuickLoad();
1037                 }
1038                 if (data->key == KEY(c) && ((event.kmodState & KEY_MOD(CTRL)) != 0) && m_engine->GetShowStats())
1039                 {
1040                     CObject* obj = GetSelect();
1041                     if (obj != nullptr)
1042                     {
1043                         CLevelParserLine line("CreateObject");
1044                         line.AddParam("type", MakeUnique<CLevelParserParam>(obj->GetType()));
1045 
1046                         Math::Vector pos = obj->GetPosition()/g_unit;
1047                         pos.y = 0.0f;
1048                         line.AddParam("pos", MakeUnique<CLevelParserParam>(pos));
1049 
1050                         float dir = Math::NormAngle(obj->GetRotationY()) / Math::PI;
1051                         line.AddParam("dir", MakeUnique<CLevelParserParam>(dir));
1052 
1053                         std::stringstream ss;
1054                         ss << line;
1055                         SDL_SetClipboardText(ss.str().c_str());
1056                     }
1057                 }
1058                 break;
1059             }
1060 
1061             case EVENT_MOUSE_BUTTON_DOWN:
1062             {
1063                 if (event.GetData<MouseButtonEventData>()->button != MOUSE_BUTTON_LEFT) // only left mouse button
1064                     break;
1065 
1066                 obj = DetectObject(event.mousePos);
1067                 if (!m_shortCut) obj = nullptr;
1068                 if (obj != nullptr && obj->GetType() == OBJECT_TOTO)
1069                 {
1070                     if (m_displayInfo != nullptr)  // current info?
1071                     {
1072                         StopDisplayInfo();
1073                     }
1074                     else
1075                     {
1076                         if (!m_editLock)
1077                             StartDisplayInfo(SATCOM_HUSTON, true);
1078                     }
1079                 }
1080                 else
1081                 {
1082                     SelectObject(obj);
1083                 }
1084                 break;
1085             }
1086 
1087             case EVENT_OBJECT_LIMIT:
1088                 StartShowLimit();
1089                 break;
1090 
1091             case EVENT_OBJECT_DESELECT:
1092                 if (m_shortCut)
1093                     DeselectObject();
1094                 break;
1095 
1096             case EVENT_OBJECT_HELP:
1097                 HelpObject();
1098                 break;
1099 
1100             case EVENT_OBJECT_CAMERA:
1101                 ChangeCamera();
1102                 break;
1103 
1104             case EVENT_OBJECT_DELETE:
1105                 m_ui->GetDialog()->StartQuestion(
1106                     RT_DIALOG_DELOBJ, true, false, false,
1107                     [&]()
1108                     {
1109                         DestroySelectedObject();
1110                     }
1111                 );
1112                 break;
1113 
1114             case EVENT_OBJECT_BHELP:
1115                 StartDisplayInfo(SATCOM_HUSTON, true);
1116                 break;
1117 
1118             case EVENT_OBJECT_SOLUCE:
1119                 StartDisplayInfo(SATCOM_SOLUCE, true);
1120                 break;
1121 
1122             case EVENT_OBJECT_MAPZOOM:
1123                 m_map->ZoomMap();
1124                 break;
1125 
1126             case EVENT_DT_VISIT0:
1127             case EVENT_DT_VISIT1:
1128             case EVENT_DT_VISIT2:
1129             case EVENT_DT_VISIT3:
1130             case EVENT_DT_VISIT4:
1131                 StartDisplayVisit(event.type);
1132                 break;
1133 
1134             case EVENT_DT_END:
1135                 StopDisplayVisit();
1136                 break;
1137 
1138             case EVENT_OBJECT_MOVIELOCK:
1139                 AbortMovie();
1140                 break;
1141 
1142             case EVENT_WIN:
1143                 m_missionTimerEnabled = m_missionTimerStarted = false;
1144                 ChangePhase(PHASE_WIN);
1145                 break;
1146 
1147             case EVENT_LOST:
1148                 m_missionTimerEnabled = m_missionTimerStarted = false;
1149                 ChangePhase(PHASE_LOST);
1150                 break;
1151 
1152             default:
1153                 break;
1154         }
1155 
1156         if (event.type >= EVENT_OBJECT_SHORTCUT_MODE && event.type <= EVENT_OBJECT_SHORTCUT_MAX)
1157         {
1158             m_short->SelectShortcut(event.type);
1159         }
1160 
1161         EventObject(event);
1162         return false;
1163     }
1164 
1165     if (m_phase == PHASE_APPERANCE)
1166         EventObject(event);
1167 
1168     if (m_phase == PHASE_WIN  ||
1169         m_phase == PHASE_LOST)
1170     {
1171         EventObject(event);
1172 
1173         switch (event.type)
1174         {
1175             case EVENT_KEY_DOWN:
1176             {
1177                 auto data = event.GetData<KeyEventData>();
1178 
1179                 if (data->key == KEY(ESCAPE) ||
1180                     data->key == KEY(RETURN))
1181                 {
1182                     if (m_winTerminate)
1183                         ChangePhase(PHASE_MAIN_MENU);
1184                     else
1185                         ChangePhase(PHASE_LEVEL_LIST);
1186                 }
1187                 break;
1188             }
1189 
1190             case EVENT_BUTTON_OK:
1191                 if (m_winTerminate)
1192                     ChangePhase(PHASE_MAIN_MENU);
1193                 else
1194                     ChangePhase(PHASE_LEVEL_LIST);
1195 
1196                 break;
1197 
1198             default:
1199                 break;
1200         }
1201     }
1202 
1203     return true;
1204 }
1205 
1206 
1207 
1208 //! Executes a command
ExecuteCmd(const std::string & cmd)1209 void CRobotMain::ExecuteCmd(const std::string& cmd)
1210 {
1211     if (cmd.empty()) return;
1212 
1213     if (m_phase == PHASE_SIMUL)
1214     {
1215         if (cmd == "winmission")
1216             m_eventQueue->AddEvent(Event(EVENT_WIN));
1217 
1218         if (cmd == "lostmission")
1219             m_eventQueue->AddEvent(Event(EVENT_LOST));
1220 
1221         if (cmd == "trainerpilot")
1222         {
1223             m_cheatTrainerPilot = !m_cheatTrainerPilot;
1224             return;
1225         }
1226 
1227         if (cmd == "fly")
1228         {
1229             m_researchDone[0] |= RESEARCH_FLY;
1230 
1231             m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE));
1232             return;
1233         }
1234 
1235         if (cmd == "allresearch")
1236         {
1237             m_researchDone[0] = -1;  // all research are done
1238 
1239             m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE));
1240             return;
1241         }
1242 
1243         if (cmd == "allbuildings")
1244         {
1245             m_build = -1;  // all buildings are available
1246 
1247             m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE));
1248             return;
1249         }
1250 
1251         if (cmd == "all")
1252         {
1253             m_researchDone[0] = -1;  // all research are done
1254             m_build = -1;  // all buildings are available
1255 
1256             m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE));
1257             return;
1258         }
1259 
1260         if (cmd == "nolimit")
1261         {
1262             m_terrain->SetFlyingMaxHeight(280.0f);
1263             return;
1264         }
1265 
1266         if (cmd == "controller")
1267         {
1268             if (m_controller == nullptr)
1269             {
1270                 GetLogger()->Error("No LevelController on the map to select\n");
1271                 return;
1272             }
1273 
1274             // Don't use SelectObject because it checks if the object is selectable
1275             if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT)
1276                 StopDisplayVisit();
1277 
1278             CObject* prev = DeselectAll();
1279             if (prev != nullptr && prev != m_controller)
1280                 PushToSelectionHistory(prev);
1281 
1282             SelectOneObject(m_controller, true);
1283             m_short->UpdateShortcuts();
1284             return;
1285         }
1286 
1287         if (cmd == "photo1")
1288         {
1289             if (m_freePhotoPause == nullptr)
1290             {
1291                 m_camera->SetType(Gfx::CAM_TYPE_FREE);
1292                 m_freePhotoPause = m_pause->ActivatePause(PAUSE_ENGINE|PAUSE_PHOTO|PAUSE_OBJECT_UPDATES);
1293             }
1294             else
1295             {
1296                 m_camera->SetType(Gfx::CAM_TYPE_BACK);
1297                 m_pause->DeactivatePause(m_freePhotoPause);
1298                 m_freePhotoPause = nullptr;
1299             }
1300             return;
1301         }
1302 
1303         if (cmd == "photo2")
1304         {
1305             if (m_freePhotoPause == nullptr)
1306             {
1307                 m_camera->SetType(Gfx::CAM_TYPE_FREE);
1308                 DeselectAll();  // removes the control buttons
1309                 m_freePhotoPause = m_pause->ActivatePause(PAUSE_ENGINE|PAUSE_PHOTO|PAUSE_OBJECT_UPDATES);
1310                 m_map->ShowMap(false);
1311                 m_displayText->HideText(true);
1312             }
1313             else
1314             {
1315                 m_camera->SetType(Gfx::CAM_TYPE_BACK);
1316                 m_pause->DeactivatePause(m_freePhotoPause);
1317                 m_freePhotoPause = nullptr;
1318                 m_map->ShowMap(m_mapShow);
1319                 m_displayText->HideText(false);
1320             }
1321             return;
1322         }
1323 
1324         int camtype;
1325         if (sscanf(cmd.c_str(), "camtype %d", &camtype) > 0)
1326         {
1327             m_camera->SetType(static_cast<Gfx::CameraType>(camtype));
1328             return;
1329         }
1330 
1331         float camspeed;
1332         if (sscanf(cmd.c_str(), "camspeed %f", &camspeed) > 0)
1333         {
1334             m_camera->SetCameraSpeed(camspeed);
1335             return;
1336         }
1337 
1338         if (cmd == "freecam")
1339         {
1340             m_camera->SetType(Gfx::CAM_TYPE_FREE);
1341             return;
1342         }
1343 
1344         if (cmd == "noclip")
1345         {
1346             CObject* object = GetSelect();
1347             if (object != nullptr)
1348                 object->SetCollisions(false);
1349             return;
1350         }
1351 
1352         if (cmd == "clip")
1353         {
1354             CObject* object = GetSelect();
1355             if (object != nullptr)
1356                 object->SetCollisions(true);
1357             return;
1358         }
1359 
1360         if (cmd == "addhusky")
1361         {
1362             CObject* object = GetSelect();
1363             if (object != nullptr && object->Implements(ObjectInterfaceType::Shielded))
1364                 dynamic_cast<CShieldedObject&>(*object).SetMagnifyDamage(dynamic_cast<CShieldedObject&>(*object).GetMagnifyDamage()*0.1f);
1365             return;
1366         }
1367 
1368         if (cmd == "addfreezer")
1369         {
1370             CObject* object = GetSelect();
1371             if (object != nullptr && object->Implements(ObjectInterfaceType::JetFlying))
1372                 dynamic_cast<CJetFlyingObject&>(*object).SetRange(dynamic_cast<CJetFlyingObject&>(*object).GetRange()*10.0f);
1373             return;
1374         }
1375 
1376         if (cmd == "\155\157\157")
1377         {
1378             // VGhpcyBpcyBlYXN0ZXItZWdnIGFuZCBzbyBpdCBzaG91bGQgYmUgb2JmdXNjYXRlZCEgRG8gbm90
1379             // IGNsZWFuLXVwIHRoaXMgY29kZSEK
1380             GetLogger()->Info(" _________________________\n");
1381             GetLogger()->Info("< \x50\x6F\x6C\x73\x6B\x69 \x50\x6F\x72\x74\x61\x6C C\x6F\x6C\x6F\x62\x6F\x74\x61! \x3E\n");
1382             GetLogger()->Info(" -------------------------\n");
1383             GetLogger()->Info("        \x5C\x20\x20\x20\x5E\x5F\x5F\x5E\n");
1384             GetLogger()->Info("        \x20\x5C\x20\x20\x28\x6F\x6F\x29\x5C\x5F\x5F\x5F\x5F\x5F\x5F\x5F\n");
1385             GetLogger()->Info("            \x28\x5F\x5F\x29\x5C   \x20\x20\x20\x20\x29\x5C\x2F\x5C\n");
1386             GetLogger()->Info("            \x20\x20\x20\x20\x7C|\x2D\x2D\x2D\x2D\x77\x20\x7C\n");
1387             GetLogger()->Info("          \x20\x20    \x7C\x7C\x20\x20\x20\x20 ||\n");
1388         }
1389 
1390         if (cmd == "fullpower")
1391         {
1392             CObject* object = GetSelect();
1393             if (object != nullptr)
1394             {
1395                 if (object->Implements(ObjectInterfaceType::Powered))
1396                 {
1397                     CObject* power = dynamic_cast<CPoweredObject&>(*object).GetPower();
1398                     if (power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer))
1399                         dynamic_cast<CPowerContainerObject&>(*power).SetEnergyLevel(1.0f);
1400                 }
1401 
1402                 if (object->Implements(ObjectInterfaceType::Shielded))
1403                     dynamic_cast<CShieldedObject&>(*object).SetShield(1.0f);
1404 
1405                 if (object->Implements(ObjectInterfaceType::JetFlying))
1406                     dynamic_cast<CJetFlyingObject&>(*object).SetReactorRange(1.0f);
1407             }
1408             return;
1409         }
1410 
1411         if (cmd == "fullenergy")
1412         {
1413             CObject* object = GetSelect();
1414 
1415             if (object != nullptr)
1416             {
1417                 if (object->Implements(ObjectInterfaceType::Powered))
1418                 {
1419                     CObject* power = dynamic_cast<CPoweredObject&>(*object).GetPower();
1420                     if (power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer))
1421                         dynamic_cast<CPowerContainerObject&>(*power).SetEnergyLevel(1.0f);
1422                 }
1423             }
1424             return;
1425         }
1426 
1427         if (cmd == "fullshield")
1428         {
1429             CObject* object = GetSelect();
1430             if (object != nullptr && object->Implements(ObjectInterfaceType::Shielded))
1431                 dynamic_cast<CShieldedObject&>(*object).SetShield(1.0f);
1432             return;
1433         }
1434 
1435         if (cmd == "fullrange")
1436         {
1437             CObject* object = GetSelect();
1438             if (object != nullptr)
1439             {
1440                 if (object->Implements(ObjectInterfaceType::JetFlying))
1441                     dynamic_cast<CJetFlyingObject&>(*object).SetReactorRange(1.0f);
1442             }
1443             return;
1444         }
1445     }
1446 
1447     if (cmd == "debugmode")
1448     {
1449         if (m_app->IsDebugModeActive(DEBUG_ALL))
1450         {
1451             m_app->SetDebugModeActive(DEBUG_ALL, false);
1452         }
1453         else
1454         {
1455             m_app->SetDebugModeActive(DEBUG_ALL, true);
1456         }
1457         return;
1458     }
1459 
1460     if (cmd == "showstat")
1461     {
1462         m_engine->SetShowStats(!m_engine->GetShowStats());
1463         return;
1464     }
1465 
1466     if (cmd == "invui")
1467     {
1468         m_engine->SetRenderInterface(!m_engine->GetRenderInterface());
1469         return;
1470     }
1471 
1472     if (cmd == "selectinsect")
1473     {
1474         m_cheatSelectInsect = !m_cheatSelectInsect;
1475         return;
1476     }
1477 
1478     if (cmd == "showsoluce")
1479     {
1480         m_cheatShowSoluce = !m_cheatShowSoluce;
1481         m_ui->ShowSoluceUpdate();
1482         return;
1483     }
1484 
1485     if (cmd == "allmission")
1486     {
1487         m_cheatAllMission = !m_cheatAllMission;
1488         m_ui->AllMissionUpdate();
1489         return;
1490     }
1491 
1492     if (cmd == "invradar")
1493     {
1494         m_cheatRadar = !m_cheatRadar;
1495         return;
1496     }
1497 
1498     float speed;
1499     if (sscanf(cmd.c_str(), "speed %f", &speed) > 0)
1500     {
1501         SetSpeed(speed);
1502         UpdateSpeedLabel();
1503         return;
1504     }
1505 
1506     if (m_phase == PHASE_SIMUL)
1507         m_displayText->DisplayError(ERR_CMD, Math::Vector(0.0f,0.0f,0.0f));
1508 }
1509 
1510 
1511 
1512 //! Returns the type of current movie
GetMainMovie()1513 MainMovieType CRobotMain::GetMainMovie()
1514 {
1515     return m_movie->GetType();
1516 }
1517 
1518 
1519 //! Clears the display of instructions
FlushDisplayInfo()1520 void CRobotMain::FlushDisplayInfo()
1521 {
1522     for (int i = 0; i < SATCOM_MAX; i++)
1523     {
1524         m_infoFilename[i][0] = 0;
1525     }
1526     strcpy(m_infoFilename[SATCOM_OBJECT], "objects.txt");
1527 }
1528 
1529 //! Beginning of the displaying of instructions.
1530 //! index: SATCOM_*
StartDisplayInfo(int index,bool movie)1531 void CRobotMain::StartDisplayInfo(int index, bool movie)
1532 {
1533     if (m_cmdEdit || m_satComLock || m_lockedSatCom) return;
1534 
1535     CObject* obj = GetSelect();
1536     bool human = obj != nullptr && obj->GetType() == OBJECT_HUMAN;
1537 
1538     if (!m_editLock && movie && !m_movie->IsExist() && human)
1539     {
1540         assert(obj->Implements(ObjectInterfaceType::Movable));
1541         if (dynamic_cast<CMovableObject&>(*obj).GetMotion()->GetAction() == -1)
1542         {
1543             m_movieInfoIndex = index;
1544             m_movie->Start(MM_SATCOMopen, 2.5f);
1545             m_satcomMoviePause = m_pause->ActivatePause(PAUSE_ENGINE|PAUSE_HIDE_SHORTCUTS);
1546             m_infoObject = DeselectAll();  // removes the control buttons
1547             m_displayText->HideText(true);
1548             return;
1549         }
1550     }
1551 
1552     if (m_movie->IsExist())
1553     {
1554         m_movie->Stop();
1555         m_pause->DeactivatePause(m_satcomMoviePause);
1556         m_satcomMoviePause = nullptr;
1557         SelectObject(m_infoObject, false);  // hands over the command buttons
1558         m_displayText->HideText(false);
1559     }
1560 
1561     StartDisplayInfo(m_infoFilename[index], index);
1562 }
1563 
1564 //! Beginning of the displaying of instructions
StartDisplayInfo(const std::string & filename,int index)1565 void CRobotMain::StartDisplayInfo(const std::string& filename, int index)
1566 {
1567     if (m_cmdEdit) return;
1568 
1569     m_movieInfoIndex = -1;
1570     ClearInterface();  // removes setting evidence and tooltip
1571 
1572     if (!m_editLock)
1573     {
1574         m_infoObject = DeselectAll();  // removes the control buttons
1575         m_displayText->HideText(true);
1576         m_sound->MuteAll(true);
1577     }
1578 
1579     bool soluce = m_ui->GetSceneSoluce();
1580 
1581     m_displayInfo = MakeUnique<Ui::CDisplayInfo>();
1582     m_displayInfo->StartDisplayInfo(filename, index, soluce);
1583     m_displayInfo->SetPosition(0);
1584 }
1585 
1586 //! End of displaying of instructions
StopDisplayInfo()1587 void CRobotMain::StopDisplayInfo()
1588 {
1589     if (m_cmdEdit) return;
1590 
1591     if (m_movieInfoIndex != -1)  // film to read the SatCom?
1592         m_movie->Start(MM_SATCOMclose, 2.0f);
1593 
1594     m_displayInfo->StopDisplayInfo();
1595 
1596     m_displayInfo.reset();
1597 
1598     if (!m_editLock)
1599     {
1600         SelectObject(m_infoObject, false);  // gives the command buttons
1601         m_displayText->HideText(false);
1602 
1603         m_sound->MuteAll(false);
1604     }
1605 
1606     if (m_infoUsed == 0)
1607         m_displayText->ClearText();  // removes message "see SatCom ..."
1608     m_infoUsed ++;
1609 }
1610 
1611 //! Returns the name of the text display
GetDisplayInfoName(int index)1612 char* CRobotMain::GetDisplayInfoName(int index)
1613 {
1614     return m_infoFilename[index];
1615 }
1616 
1617 
1618 //! Beginning of a dialogue during the game
StartSuspend()1619 void CRobotMain::StartSuspend()
1620 {
1621     if (m_suspend != nullptr) return; // already suspended
1622     if (!IsPhaseWithWorld(m_phase)) return;
1623     GetLogger()->Info("Start suspend\n");
1624 
1625     m_sound->MuteAll(true);
1626     ClearInterface();
1627     m_suspend = m_pause->ActivatePause(PAUSE_ENGINE | PAUSE_HIDE_SHORTCUTS | PAUSE_MUTE_SOUND | PAUSE_CAMERA);
1628     m_engine->SetOverFront(false);  // over flat behind
1629     CreateShortcuts();
1630 
1631     m_map->ShowMap(false);
1632     m_infoObject = DeselectAll();  // removes the control buttons
1633     m_displayText->HideText(true);
1634 
1635     m_engine->EnablePauseBlur();
1636 }
1637 
1638 //! End of dialogue during the game
StopSuspend()1639 void CRobotMain::StopSuspend()
1640 {
1641     if (m_suspend == nullptr) return; // not suspended
1642     GetLogger()->Info("Stop suspend\n");
1643 
1644     m_sound->MuteAll(false);
1645     ClearInterface();
1646     m_pause->DeactivatePause(m_suspend);
1647     m_suspend = nullptr;
1648     m_engine->SetOverFront(true);  // over flat front
1649     CreateShortcuts();
1650 
1651     if (m_infoObject != nullptr)
1652         SelectObject(m_infoObject, false);  // gives the command buttons
1653     m_map->ShowMap(m_mapShow);
1654     m_displayText->HideText(false);
1655 
1656     m_engine->DisablePauseBlur();
1657 }
1658 
1659 
1660 //! Returns the absolute time of the game
GetGameTime()1661 float CRobotMain::GetGameTime()
1662 {
1663     return m_gameTime;
1664 }
1665 
1666 
1667 //! Start of the visit instead of an error
StartDisplayVisit(EventType event)1668 void CRobotMain::StartDisplayVisit(EventType event)
1669 {
1670     if (m_editLock) return;
1671 
1672     if (m_visitPause)
1673     {
1674         m_pause->DeactivatePause(m_visitPause);
1675         m_visitPause = nullptr;
1676     }
1677 
1678     Ui::CWindow* pw = static_cast<Ui::CWindow*>(m_interface->SearchControl(EVENT_WINDOW2));
1679     if (pw == nullptr) return;
1680 
1681     if (event == EVENT_NULL)  // visit by keyboard shortcut?
1682     {
1683         int i;
1684         if (m_visitLast != EVENT_NULL)  // already a current visit?
1685             i = m_visitLast-EVENT_DT_VISIT0;
1686         else
1687             i = Ui::MAXDTLINE;
1688 
1689         // Seeks the last.
1690         for (int j = 0; j < Ui::MAXDTLINE; j++)
1691         {
1692             i --;
1693             if (i < 0) i = Ui::MAXDTLINE-1;
1694 
1695             Ui::CButton* button = static_cast<Ui::CButton*>(pw->SearchControl(static_cast<EventType>(EVENT_DT_VISIT0+i)));
1696             if (button == nullptr || !button->TestState(Ui::STATE_ENABLE)) continue;
1697 
1698             Ui::CGroup* group = static_cast<Ui::CGroup*>(pw->SearchControl(static_cast<EventType>(EVENT_DT_GROUP0+i)));
1699             if (group != nullptr)
1700             {
1701                 event = static_cast<EventType>(EVENT_DT_VISIT0+i);
1702                 break;
1703             }
1704         }
1705     }
1706     if (event == EVENT_NULL)
1707     {
1708         m_sound->Play(SOUND_TZOING);  // nothing to do!
1709         return;
1710     }
1711 
1712     m_visitLast = event;
1713 
1714     ClearInterface();  // removes setting evidence and tooltip
1715 
1716     if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT)  // already a current visit?
1717     {
1718         m_camera->StopVisit();
1719         m_displayText->ClearVisit();
1720     }
1721     else
1722     {
1723         m_visitObject = DeselectAll();  // removes the control buttons
1724     }
1725 
1726     // Creates the "continue" button.
1727     if (m_interface->SearchControl(EVENT_DT_END) == nullptr)
1728     {
1729         Math::Point pos, dim;
1730         pos.x = 10.0f/640.0f;
1731         pos.y = 10.0f/480.0f;
1732         dim.x = 50.0f/640.0f;
1733         dim.y = 50.0f/480.0f;
1734         m_interface->CreateButton(pos, dim, 16, EVENT_DT_END);
1735     }
1736 
1737     // Creates the arrow to show the place.
1738     if (m_visitArrow != nullptr)
1739     {
1740         CObjectManager::GetInstancePointer()->DeleteObject(m_visitArrow);
1741         m_visitArrow = nullptr;
1742     }
1743 
1744     ObjectCreateParams params;
1745     params.pos = m_displayText->GetVisitGoal(event);
1746     params.type = OBJECT_SHOW;
1747     params.height = 10.0f;
1748     m_visitArrow = m_objMan->CreateObject(params);
1749 
1750     m_visitPos = m_visitArrow->GetPosition();
1751     m_visitPosArrow = m_visitPos;
1752     m_visitPosArrow.y += m_displayText->GetVisitHeight(event);
1753     m_visitArrow->SetPosition(m_visitPosArrow);
1754 
1755     m_visitTime = 0.0;
1756     m_visitParticle = 0.0f;
1757 
1758     m_particle->DeleteParticle(Gfx::PARTISHOW);
1759 
1760     m_camera->StartVisit(m_displayText->GetVisitGoal(event),
1761                          m_displayText->GetVisitDist(event));
1762     m_displayText->SetVisit(event);
1763     m_visitPause = m_pause->ActivatePause(PAUSE_ENGINE);
1764 }
1765 
1766 //! Move the arrow to visit
FrameVisit(float rTime)1767 void CRobotMain::FrameVisit(float rTime)
1768 {
1769     if (m_visitArrow == nullptr) return;
1770 
1771     // Moves the arrow.
1772     m_visitTime += rTime;
1773 
1774     Math::Vector pos = m_visitPosArrow;
1775     pos.y += 1.5f+sinf(m_visitTime*4.0f)*4.0f;
1776     m_visitArrow->SetPosition(pos);
1777     m_visitArrow->SetRotationY(m_visitTime*2.0f);
1778 
1779     // Manages the particles "arrows".
1780     m_visitParticle -= rTime;
1781     if (m_visitParticle <= 0.0f)
1782     {
1783         m_visitParticle = 1.5f;
1784 
1785         pos = m_visitPos;
1786         float level = m_terrain->GetFloorLevel(pos)+2.0f;
1787         if (pos.y < level) pos.y = level;  // not below the ground
1788         Math::Vector speed(0.0f, 0.0f, 0.0f);
1789         Math::Point dim;
1790         dim.x = 30.0f;
1791         dim.y = dim.x;
1792         m_particle->CreateParticle(pos, speed, dim, Gfx::PARTISHOW, 2.0f);
1793     }
1794 }
1795 
1796 //! End of the visit instead of an error
StopDisplayVisit()1797 void CRobotMain::StopDisplayVisit()
1798 {
1799     m_visitLast = EVENT_NULL;
1800 
1801     // Removes the button.
1802     m_interface->DeleteControl(EVENT_DT_END);
1803 
1804     // Removes the arrow.
1805     if (m_visitArrow != nullptr)
1806     {
1807         CObjectManager::GetInstancePointer()->DeleteObject(m_visitArrow);
1808         m_visitArrow = nullptr;
1809     }
1810 
1811     // Removes particles "arrows".
1812     m_particle->DeleteParticle(Gfx::PARTISHOW);
1813 
1814     m_camera->StopVisit();
1815     m_displayText->ClearVisit();
1816     m_pause->DeactivatePause(m_visitPause);
1817     m_visitPause = nullptr;
1818     if (m_visitObject != nullptr)
1819     {
1820         SelectObject(m_visitObject, false);  // gives the command buttons
1821         m_visitObject = nullptr;
1822     }
1823 }
1824 
1825 
1826 
UpdateShortcuts()1827 void CRobotMain::UpdateShortcuts()
1828 {
1829     m_short->UpdateShortcuts();
1830 }
1831 
GetSelectObject()1832 CObject* CRobotMain::GetSelectObject()
1833 {
1834     if (m_selectObject != nullptr) return m_selectObject;
1835     return SearchHuman();
1836 }
1837 
DeselectAll()1838 CObject* CRobotMain::DeselectAll()
1839 {
1840     CObject* prev = nullptr;
1841     for (CObject* obj : m_objMan->GetAllObjects())
1842     {
1843         if (!obj->Implements(ObjectInterfaceType::Controllable)) continue;
1844         auto controllableObj = dynamic_cast<CControllableObject*>(obj);
1845         if (controllableObj->GetSelect()) prev = obj;
1846         controllableObj->SetSelect(false);
1847     }
1848     return prev;
1849 }
1850 
1851 //! Selects an object, without attending to deselect the rest
SelectOneObject(CObject * obj,bool displayError)1852 void CRobotMain::SelectOneObject(CObject* obj, bool displayError)
1853 {
1854     assert(obj->Implements(ObjectInterfaceType::Controllable));
1855     dynamic_cast<CControllableObject&>(*obj).SetSelect(true, displayError);
1856     m_camera->SetControllingObject(obj);
1857 
1858     ObjectType type = obj->GetType();
1859     if ( type == OBJECT_HUMAN    ||
1860          type == OBJECT_MOBILEfa ||
1861          type == OBJECT_MOBILEta ||
1862          type == OBJECT_MOBILEwa ||
1863          type == OBJECT_MOBILEia ||
1864          type == OBJECT_MOBILEfb ||
1865          type == OBJECT_MOBILEtb ||
1866          type == OBJECT_MOBILEwb ||
1867          type == OBJECT_MOBILEib ||
1868          type == OBJECT_MOBILEfc ||
1869          type == OBJECT_MOBILEtc ||
1870          type == OBJECT_MOBILEwc ||
1871          type == OBJECT_MOBILEic ||
1872          type == OBJECT_MOBILEfi ||
1873          type == OBJECT_MOBILEti ||
1874          type == OBJECT_MOBILEwi ||
1875          type == OBJECT_MOBILEii ||
1876          type == OBJECT_MOBILEfs ||
1877          type == OBJECT_MOBILEts ||
1878          type == OBJECT_MOBILEws ||
1879          type == OBJECT_MOBILEis ||
1880          type == OBJECT_MOBILErt ||
1881          type == OBJECT_MOBILErc ||
1882          type == OBJECT_MOBILErr ||
1883          type == OBJECT_MOBILErs ||
1884          type == OBJECT_MOBILEsa ||
1885          type == OBJECT_MOBILEft ||
1886          type == OBJECT_MOBILEtt ||
1887          type == OBJECT_MOBILEwt ||
1888          type == OBJECT_MOBILEit ||
1889          type == OBJECT_MOBILErp ||
1890          type == OBJECT_MOBILEst ||
1891          type == OBJECT_MOBILEdr ||
1892          type == OBJECT_APOLLO2  )
1893     {
1894         m_camera->SetType(dynamic_cast<CControllableObject&>(*obj).GetCameraType());
1895     }
1896     else
1897     {
1898         m_camera->SetType(Gfx::CAM_TYPE_BACK);
1899     }
1900 }
1901 
SelectObject(CObject * obj,bool displayError)1902 bool CRobotMain::SelectObject(CObject* obj, bool displayError)
1903 {
1904     if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT)
1905         StopDisplayVisit();
1906 
1907     if (m_movieLock || m_editLock) return false;
1908     if (m_movie->IsExist()) return false;
1909     if (obj != nullptr &&
1910         (!obj->Implements(ObjectInterfaceType::Controllable) || !(dynamic_cast<CControllableObject&>(*obj).GetSelectable() || m_cheatSelectInsect))) return false;
1911 
1912     if (m_missionType == MISSION_CODE_BATTLE && m_codeBattleStarted && m_codeBattleSpectator)
1913     {
1914         DeselectAll();
1915 
1916         // During code battles, only change camera
1917         m_camera->SetControllingObject(obj);
1918         if (obj != nullptr)
1919         {
1920             m_camera->SetType(Gfx::CAM_TYPE_PLANE);
1921         }
1922         else
1923         {
1924             m_camera->SetType(Gfx::CAM_TYPE_FREE);
1925         }
1926     }
1927     else
1928     {
1929         if (obj == nullptr) return false;
1930         CObject* prev = DeselectAll();
1931 
1932         if (prev != nullptr && prev != obj)
1933            PushToSelectionHistory(prev);
1934 
1935         SelectOneObject(obj, displayError);
1936     }
1937     m_short->UpdateShortcuts();
1938     return true;
1939 }
1940 
DeselectObject()1941 bool CRobotMain::DeselectObject()
1942 {
1943     DeselectAll();
1944 
1945     CObject* obj = PopFromSelectionHistory();
1946     if (obj == nullptr)
1947         obj = SearchHuman();
1948 
1949     if (obj != nullptr)
1950         SelectOneObject(obj);
1951     else
1952         m_camera->SetType(Gfx::CAM_TYPE_FREE);
1953 
1954     m_short->UpdateShortcuts();
1955     return true;
1956 }
1957 
1958 //! Quickly removes all objects
DeleteAllObjects()1959 void CRobotMain::DeleteAllObjects()
1960 {
1961     m_engine->GetPyroManager()->DeleteAll();
1962 
1963     // Removes the arrow.
1964     if (m_visitArrow != nullptr)
1965     {
1966         CObjectManager::GetInstancePointer()->DeleteObject(m_visitArrow);
1967         m_visitArrow = nullptr;
1968     }
1969 
1970     for (int i = 0; i < MAXSHOWLIMIT; i++)
1971         FlushShowLimit(i);
1972 
1973     m_objMan->DeleteAllObjects();
1974 }
1975 
SearchHuman()1976 CObject* CRobotMain::SearchHuman()
1977 {
1978     return m_objMan->FindNearest(nullptr, OBJECT_HUMAN);
1979 }
1980 
GetSelect()1981 CObject* CRobotMain::GetSelect()
1982 {
1983     for (CObject* obj : m_objMan->GetAllObjects())
1984     {
1985         if (!obj->Implements(ObjectInterfaceType::Controllable)) continue;
1986         if (dynamic_cast<CControllableObject&>(*obj).GetSelect())
1987             return obj;
1988     }
1989     return nullptr;
1990 }
1991 
1992 //! Detects the object aimed by the mouse
DetectObject(Math::Point pos)1993 CObject* CRobotMain::DetectObject(Math::Point pos)
1994 {
1995     Math::Vector p;
1996     int objRank = m_engine->DetectObject(pos, p);
1997 
1998     for (CObject* obj : m_objMan->GetAllObjects())
1999     {
2000         if (!obj->GetDetectable()) continue;
2001 
2002         CObject* transporter = nullptr;
2003         if (obj->Implements(ObjectInterfaceType::Transportable))
2004             transporter = dynamic_cast<CTransportableObject&>(*obj).GetTransporter();
2005 
2006         if (transporter != nullptr && !transporter->GetDetectable()) continue;
2007         if (obj->GetProxyActivate()) continue;
2008 
2009         CObject* target = obj;
2010         if (obj->Implements(ObjectInterfaceType::PowerContainer) && obj->Implements(ObjectInterfaceType::Transportable))
2011         {
2012             target = dynamic_cast<CTransportableObject&>(*obj).GetTransporter();  // battery connected
2013             if (target == nullptr)
2014             {
2015                 target = obj; // standalone battery
2016             }
2017             else
2018             {
2019                 if (!target->Implements(ObjectInterfaceType::Powered) || dynamic_cast<CPoweredObject&>(*target).GetPower() != obj)
2020                 {
2021                     // transported, but not in the power slot
2022                     target = obj;
2023                 }
2024             }
2025         }
2026 
2027         if (!obj->Implements(ObjectInterfaceType::Old)) continue;
2028         for (int j = 0; j < OBJECTMAXPART; j++)
2029         {
2030             int rank = obj->GetObjectRank(j);
2031             if (rank == -1) continue;
2032             if (rank != objRank) continue;
2033             return target;
2034         }
2035     }
2036     return nullptr;
2037 }
2038 
2039 
2040 //! Deletes the selected object
DestroySelectedObject()2041 bool CRobotMain::DestroySelectedObject()
2042 {
2043     CObject* obj = GetSelect();
2044     if (obj == nullptr) return false;
2045     assert(obj->Implements(ObjectInterfaceType::Controllable));
2046 
2047     m_engine->GetPyroManager()->Create(Gfx::PT_FRAGT, obj);
2048 
2049     dynamic_cast<CControllableObject&>(*obj).SetSelect(false);  // deselects the object
2050     m_camera->SetType(Gfx::CAM_TYPE_EXPLO);
2051     DeselectAll();
2052     RemoveFromSelectionHistory(obj);
2053 
2054     return true;
2055 }
2056 
2057 
2058 //! Removes setting evidence of the object with the mouse hovers over
HiliteClear()2059 void CRobotMain::HiliteClear()
2060 {
2061     ClearTooltip();
2062     m_tooltipName.clear();  // really removes the tooltip
2063 
2064     if (!m_hilite) return;
2065 
2066     int rank = -1;
2067     m_engine->SetHighlightRank(&rank);  // nothing more selected
2068 
2069     for (CObject* obj : m_objMan->GetAllObjects())
2070     {
2071         if (!obj->Implements(ObjectInterfaceType::Controllable)) continue;
2072         dynamic_cast<CControllableObject&>(*obj).SetHighlight(false);
2073     }
2074     m_map->SetHighlight(nullptr);
2075     m_short->SetHighlight(nullptr);
2076 
2077     m_hilite = false;
2078 }
2079 
2080 //! Highlights the object with the mouse hovers over
HiliteObject(Math::Point pos)2081 void CRobotMain::HiliteObject(Math::Point pos)
2082 {
2083     if (m_fixScene && m_phase != PHASE_APPERANCE) return;
2084     if (m_movieLock) return;
2085     if (m_movie->IsExist()) return;
2086     if (m_app->GetMouseMode() == MOUSE_NONE) return;
2087 
2088     ClearInterface();  // removes setting evidence and tooltip
2089 
2090     CObject* obj = m_short->DetectShort(pos);
2091 
2092     std::string interfaceTooltipName;
2093     if (m_settings->GetTooltips() && m_interface->GetTooltip(pos, interfaceTooltipName))
2094     {
2095         m_tooltipPos = pos;
2096         m_tooltipName = interfaceTooltipName;
2097         m_tooltipTime = 0.0f;
2098         if (obj == nullptr) return;
2099     }
2100 
2101     if (m_suspend != nullptr) return;
2102 
2103     if (obj == nullptr)
2104     {
2105         bool inMap = false;
2106         obj = m_map->DetectMap(pos, inMap);
2107         if (obj == nullptr)
2108         {
2109             if (inMap)  return;
2110 
2111             obj = DetectObject(pos);
2112 
2113             if ((m_camera->GetType() == Gfx::CAM_TYPE_ONBOARD) &&
2114                 (m_camera->GetControllingObject() == obj))
2115                 return;
2116         }
2117     }
2118 
2119     if (obj != nullptr)
2120     {
2121         if (m_settings->GetTooltips())
2122         {
2123             std::string objectTooltipName = obj->GetTooltipText();
2124             if (!objectTooltipName.empty())
2125             {
2126                 m_tooltipPos = pos;
2127                 m_tooltipName = objectTooltipName;
2128                 m_tooltipTime = 0.0f;
2129             }
2130         }
2131 
2132         if (obj->Implements(ObjectInterfaceType::Controllable) && (dynamic_cast<CControllableObject&>(*obj).GetSelectable() || m_cheatSelectInsect))
2133         {
2134             if (dynamic_cast<CControllableObject&>(*obj).GetSelectable())
2135             {
2136                 // Don't highlight objects that would not be selectable without selectinsect
2137                 dynamic_cast<CControllableObject&>(*obj).SetHighlight(true);
2138             }
2139             m_map->SetHighlight(obj);
2140             m_short->SetHighlight(obj);
2141             m_hilite = true;
2142         }
2143     }
2144 }
2145 
2146 //! Highlights the object with the mouse hovers over
HiliteFrame(float rTime)2147 void CRobotMain::HiliteFrame(float rTime)
2148 {
2149     if (m_fixScene && m_phase != PHASE_APPERANCE) return;
2150     if (m_movieLock) return;
2151     if (m_movie->IsExist()) return;
2152 
2153     m_tooltipTime += rTime;
2154 
2155     ClearTooltip();
2156 
2157     if (m_tooltipTime >= 0.2f && !m_tooltipName.empty())
2158     {
2159         CreateTooltip(m_tooltipPos, m_tooltipName);
2160     }
2161 }
2162 
2163 //! Creates a tooltip
CreateTooltip(Math::Point pos,const std::string & text)2164 void CRobotMain::CreateTooltip(Math::Point pos, const std::string& text)
2165 {
2166     Math::Point corner;
2167     corner.x = pos.x+0.022f;
2168     corner.y = pos.y-0.052f;
2169 
2170     Math::Point start, end;
2171 
2172     m_engine->GetText()->SizeText(text, Gfx::FONT_COMMON, Gfx::FONT_SIZE_SMALL,
2173                                   corner, Gfx::TEXT_ALIGN_LEFT,
2174                                   start, end);
2175 
2176     start.x -= 0.010f;
2177     start.y -= 0.006f;
2178     end.x   += 0.010f;
2179     end.y   += 0.008f;  // small'ish margin
2180 
2181     pos.x = start.x;
2182     pos.y = start.y;
2183 
2184     Math::Point dim;
2185     dim.x = end.x-start.x;
2186     dim.y = end.y-start.y;
2187 
2188     Math::Point offset;
2189     offset.x = 0.0f;
2190     offset.y = 0.0f;
2191     if (pos.x+dim.x > 1.0f) offset.x = 1.0f-(pos.x+dim.x);
2192     if (pos.y       < 0.0f) offset.y = -pos.y;
2193 
2194     corner.x += offset.x;
2195     corner.y += offset.y;
2196     pos.x += offset.x;
2197     pos.y += offset.y;
2198 
2199     m_interface->CreateWindows(pos, dim, 1, EVENT_TOOLTIP);
2200 
2201     Ui::CWindow* pw = static_cast<Ui::CWindow*>(m_interface->SearchControl(EVENT_TOOLTIP));
2202     if (pw != nullptr)
2203     {
2204         pw->SetState(Ui::STATE_SHADOW);
2205         pw->SetTrashEvent(false);
2206 
2207         pos.y -= m_engine->GetText()->GetHeight(Gfx::FONT_COMMON, Gfx::FONT_SIZE_SMALL) / 2.0f;
2208         pw->CreateLabel(pos, dim, -1, EVENT_LABEL2, text);
2209     }
2210 }
2211 
2212 //! Clears the previous tooltip
ClearTooltip()2213 void CRobotMain::ClearTooltip()
2214 {
2215     m_interface->DeleteControl(EVENT_TOOLTIP);
2216 }
2217 
2218 
2219 //! Displays help for an object
HelpObject()2220 void CRobotMain::HelpObject()
2221 {
2222     CObject* obj = GetSelect();
2223     if (obj == nullptr) return;
2224 
2225     std::string filename = GetHelpFilename(obj->GetType());
2226     if (filename.empty()) return;
2227 
2228     StartDisplayInfo(filename, -1);
2229 }
2230 
2231 
2232 //! Change the mode of the camera
ChangeCamera()2233 void CRobotMain::ChangeCamera()
2234 {
2235     CObject* obj = GetSelect();
2236     if (obj == nullptr) return;
2237     assert(obj->Implements(ObjectInterfaceType::Controllable));
2238     auto controllableObj = dynamic_cast<CControllableObject*>(obj);
2239 
2240     if (controllableObj->GetCameraLock()) return;
2241 
2242     ObjectType oType = obj->GetType();
2243     Gfx::CameraType type = controllableObj->GetCameraType();
2244 
2245     if ( oType != OBJECT_HUMAN &&
2246          oType != OBJECT_TECH &&
2247          oType != OBJECT_MOBILEfa &&
2248          oType != OBJECT_MOBILEta &&
2249          oType != OBJECT_MOBILEwa &&
2250          oType != OBJECT_MOBILEia &&
2251          oType != OBJECT_MOBILEfb &&
2252          oType != OBJECT_MOBILEtb &&
2253          oType != OBJECT_MOBILEwb &&
2254          oType != OBJECT_MOBILEib &&
2255          oType != OBJECT_MOBILEfc &&
2256          oType != OBJECT_MOBILEtc &&
2257          oType != OBJECT_MOBILEwc &&
2258          oType != OBJECT_MOBILEic &&
2259          oType != OBJECT_MOBILEfi &&
2260          oType != OBJECT_MOBILEti &&
2261          oType != OBJECT_MOBILEwi &&
2262          oType != OBJECT_MOBILEii &&
2263          oType != OBJECT_MOBILEfs &&
2264          oType != OBJECT_MOBILEts &&
2265          oType != OBJECT_MOBILEws &&
2266          oType != OBJECT_MOBILEis &&
2267          oType != OBJECT_MOBILErt &&
2268          oType != OBJECT_MOBILErc &&
2269          oType != OBJECT_MOBILErr &&
2270          oType != OBJECT_MOBILErs &&
2271          oType != OBJECT_MOBILEsa &&
2272          oType != OBJECT_MOBILEtg &&
2273          oType != OBJECT_MOBILEft &&
2274          oType != OBJECT_MOBILEtt &&
2275          oType != OBJECT_MOBILEwt &&
2276          oType != OBJECT_MOBILEit &&
2277          oType != OBJECT_MOBILErp &&
2278          oType != OBJECT_MOBILEst &&
2279          oType != OBJECT_MOBILEdr &&
2280          oType != OBJECT_APOLLO2  )  return;
2281 
2282     if (oType == OBJECT_MOBILEdr)  // designer?
2283     {
2284              if (type == Gfx::CAM_TYPE_PLANE  )  type = Gfx::CAM_TYPE_BACK;
2285         else if (type == Gfx::CAM_TYPE_BACK   )  type = Gfx::CAM_TYPE_PLANE;
2286     }
2287     else if (controllableObj->GetTrainer())  // trainer?
2288     {
2289              if (type == Gfx::CAM_TYPE_ONBOARD)  type = Gfx::CAM_TYPE_FIX;
2290         else if (type == Gfx::CAM_TYPE_FIX    )  type = Gfx::CAM_TYPE_PLANE;
2291         else if (type == Gfx::CAM_TYPE_PLANE  )  type = Gfx::CAM_TYPE_BACK;
2292         else if (type == Gfx::CAM_TYPE_BACK   )  type = Gfx::CAM_TYPE_ONBOARD;
2293     }
2294     else
2295     {
2296              if (type == Gfx::CAM_TYPE_ONBOARD)  type = Gfx::CAM_TYPE_BACK;
2297         else if (type == Gfx::CAM_TYPE_BACK   )  type = Gfx::CAM_TYPE_ONBOARD;
2298     }
2299 
2300     controllableObj->SetCameraType(type);
2301     m_camera->SetType(type);
2302 }
2303 
2304 
2305 //! Cancels the current movie
AbortMovie()2306 void CRobotMain::AbortMovie()
2307 {
2308     for (CObject* obj : m_objMan->GetAllObjects())
2309     {
2310         if (obj->Implements(ObjectInterfaceType::Old))
2311         {
2312             CAuto* automat = obj->GetAuto();
2313             if (automat != nullptr)
2314                 automat->Abort();
2315         }
2316     }
2317 }
2318 
2319 
TimeFormat(float time)2320 static std::string TimeFormat(float time)
2321 {
2322     int minutes = static_cast<int>(floor(time/60));
2323     double time2 = fmod(time, 60);
2324     double seconds;
2325     double fraction = modf(time2, &seconds)*100;
2326     std::ostringstream sstream;
2327     sstream << std::setfill('0') << std::setw(2) << minutes << ":" << std::setfill('0') << std::setw(2) << floor(seconds) << "." << std::setfill('0') << std::setw(2) << floor(fraction);
2328     return sstream.str();
2329 }
2330 
2331 //! Updates the text information
UpdateInfoText()2332 void CRobotMain::UpdateInfoText()
2333 {
2334     if (m_phase == PHASE_SIMUL)
2335     {
2336         CObject* obj = GetSelect();
2337         if (obj != nullptr)
2338         {
2339             Math::Vector pos = obj->GetPosition();
2340             m_engine->SetStatisticPos(pos / g_unit);
2341         }
2342     }
2343     m_engine->SetTimerDisplay(m_missionTimerEnabled && m_missionTimerStarted ? TimeFormat(m_missionTimer) : "");
2344 }
2345 
2346 
2347 //! Initializes the view
InitEye()2348 void CRobotMain::InitEye()
2349 {
2350     if (m_phase == PHASE_SIMUL)
2351         m_camera->Init(Math::Vector( 0.0f, 10.0f, 0.0f),
2352                        Math::Vector(10.0f,  5.0f, 0.0f), 0.0f);
2353 }
2354 
2355 //! Advances the entire scene
EventFrame(const Event & event)2356 bool CRobotMain::EventFrame(const Event &event)
2357 {
2358     m_time += event.rTime;
2359 
2360     m_water->EventProcess(event);
2361     m_cloud->EventProcess(event);
2362     m_lightning->EventProcess(event);
2363     m_planet->EventProcess(event);
2364 
2365     UpdateDebugCrashSpheres();
2366 
2367     Ui::CMap* pm = nullptr;
2368     Ui::CWindow* pw = static_cast<Ui::CWindow*>(m_interface->SearchControl(EVENT_WINDOW1));
2369     if (pw == nullptr)
2370     {
2371         pm = nullptr;
2372     }
2373     else
2374     {
2375         pm = static_cast<Ui::CMap*>(pw->SearchControl(EVENT_OBJECT_MAP));
2376         if (pm != nullptr) pm->FlushObject();
2377     }
2378 
2379     CObject* toto = nullptr;
2380     if (!m_pause->IsPauseType(PAUSE_OBJECT_UPDATES))
2381     {
2382         // Advances all the robots, but not toto.
2383         for (CObject* obj : m_objMan->GetAllObjects())
2384         {
2385             if (pm != nullptr)
2386                 pm->UpdateObject(obj);
2387 
2388             if (IsObjectBeingTransported(obj))
2389                 continue;
2390 
2391             if (obj->GetType() == OBJECT_TOTO)
2392                 toto = obj;
2393             else if (obj->Implements(ObjectInterfaceType::Interactive))
2394                 dynamic_cast<CInteractiveObject&>(*obj).EventProcess(event);
2395 
2396             if ( obj->GetProxyActivate() )  // active if it is near?
2397             {
2398                 Math::Vector eye = m_engine->GetLookatPt();
2399                 float dist = Math::Distance(eye, obj->GetPosition());
2400                 if ( dist < obj->GetProxyDistance() )
2401                 {
2402                     obj->SetProxyActivate(false);
2403                     CreateShortcuts();
2404                     m_sound->Play(SOUND_FINDING);
2405                     m_engine->GetPyroManager()->Create(Gfx::PT_FINDING, obj, 0.0f);
2406                     DisplayError(INFO_FINDING, obj);
2407                 }
2408             }
2409         }
2410         // Advances all objects transported by robots.
2411         for (CObject* obj : m_objMan->GetAllObjects())
2412         {
2413             if (! IsObjectBeingTransported(obj))
2414                 continue;
2415 
2416             if (obj->Implements(ObjectInterfaceType::Interactive))
2417                 dynamic_cast<CInteractiveObject&>(*obj).EventProcess(event);
2418         }
2419 
2420         m_engine->GetPyroManager()->EventProcess(event);
2421     }
2422 
2423     // The camera follows the object, because its position
2424     // may depend on the selected object (Gfx::CAM_TYPE_ONBOARD or Gfx::CAM_TYPE_BACK).
2425     if (m_phase == PHASE_SIMUL && !m_editFull)
2426     {
2427         m_camera->EventProcess(event);
2428 
2429         if (m_engine->GetFog())
2430             m_camera->SetOverBaseColor(m_particle->GetFogColor(m_engine->GetEyePt()));
2431     }
2432     if (m_phase == PHASE_APPERANCE ||
2433         m_phase == PHASE_WIN   ||
2434         m_phase == PHASE_LOST)
2435     {
2436         m_camera->EventProcess(event);
2437     }
2438 
2439     // Advances toto following the camera, because its position depends on the camera.
2440     if (toto != nullptr)
2441         dynamic_cast<CInteractiveObject&>(*toto).EventProcess(event);
2442 
2443     // NOTE: m_movieLock is set only after the first update of CAutoBase finishes
2444 
2445     if (m_phase == PHASE_SIMUL)
2446     {
2447         if (!m_immediatSatCom && !m_beginSatCom && !m_movieLock)
2448         {
2449             m_displayText->DisplayError(INFO_BEGINSATCOM, Math::Vector(0.0f, 0.0f, 0.0f));
2450             m_beginSatCom = true;  // message appears
2451         }
2452 
2453         if (!m_pause->IsPauseType(PAUSE_ENGINE) && !m_movieLock)
2454         {
2455             m_gameTime += event.rTime;
2456             m_gameTimeAbsolute += m_app->GetRealRelTime() / 1e9f;
2457 
2458             if (m_missionTimerStarted)
2459                 m_missionTimer += event.rTime;
2460 
2461             if (m_autosave && m_gameTimeAbsolute >= m_autosaveLast + (m_autosaveInterval * 60))
2462             {
2463                 if (m_levelCategory == LevelCategory::Missions ||
2464                     m_levelCategory == LevelCategory::FreeGame ||
2465                     m_levelCategory == LevelCategory::GamePlus ||
2466                     m_levelCategory == LevelCategory::CustomLevels)
2467                 {
2468                     if (!IOIsBusy() && m_missionType != MISSION_CODE_BATTLE)
2469                     {
2470                         m_autosaveLast = m_gameTimeAbsolute;
2471                         Autosave();
2472                     }
2473                 }
2474             }
2475         }
2476     }
2477 
2478     HiliteFrame(event.rTime);
2479 
2480     // Moves the film indicator.
2481     if (m_movieLock && !m_editLock)  // movie in progress?
2482     {
2483         Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_MOVIELOCK);
2484         if (pc != nullptr)
2485         {
2486             Math::Point pos, dim;
2487 
2488             dim.x = 32.0f/640.0f;
2489             dim.y = 32.0f/480.0f;
2490             pos.x = 20.0f/640.0f;
2491             pos.y = (480.0f-24.0f)/480.0f;
2492 
2493             float zoom = 1.0f+sinf(m_time*6.0f)*0.1f;  // 0.9 .. 1.1
2494             dim.x *= zoom;
2495             dim.y *= zoom;
2496             pos.x -= dim.x/2.0f;
2497             pos.y -= dim.y/2.0f;
2498 
2499             pc->SetPos(pos);
2500             pc->SetDim(dim);
2501         }
2502     }
2503 
2504     // Moves edition indicator.
2505     if (m_editLock || m_pause->IsPauseType(PAUSE_ENGINE))  // edition in progress?
2506     {
2507         Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_EDITLOCK);
2508         if (pc != nullptr)
2509         {
2510             Math::Point pos, dim;
2511 
2512             if (m_editFull || m_editLock)
2513             {
2514                 dim.x = 10.0f/640.0f;
2515                 dim.y = 10.0f/480.0f;
2516                 pos.x = -20.0f/640.0f;
2517                 pos.y = -20.0f/480.0f;  // invisible!
2518             }
2519             else
2520             {
2521                 dim.x = 32.0f/640.0f;
2522                 dim.y = 32.0f/480.0f;
2523                 pos.x = (640.0f-24.0f)/640.0f;
2524                 pos.y = (480.0f-24.0f)/480.0f;
2525 
2526                 float zoom = 1.0f+sinf(m_time*6.0f)*0.1f;  // 0.9 .. 1.1
2527                 dim.x *= zoom;
2528                 dim.y *= zoom;
2529                 pos.x -= dim.x/2.0f;
2530                 pos.y -= dim.y/2.0f;
2531             }
2532             pc->SetPos(pos);
2533             pc->SetDim(dim);
2534         }
2535     }
2536 
2537     Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_SAVING);
2538     if (pc != nullptr)
2539     {
2540         Math::Point pos, dim;
2541 
2542         if (m_shotSaving <= 0)
2543         {
2544             dim.x = 10.0f/640.0f;
2545             dim.y = 10.0f/480.0f;
2546             pos.x = -20.0f/640.0f;
2547             pos.y = -20.0f/480.0f;  // invisible!
2548         }
2549         else
2550         {
2551             dim.x = 32.0f/640.0f;
2552             dim.y = 32.0f/480.0f;
2553             pos.x = (640.0f-24.0f)/640.0f;
2554             pos.y = (480.0f-24.0f)/480.0f;
2555 
2556             float zoom = 1.0f+sinf(m_time*6.0f)*0.1f;  // 0.9 .. 1.1
2557             dim.x *= zoom;
2558             dim.y *= zoom;
2559             pos.x -= dim.x/2.0f;
2560             pos.y -= dim.y/2.0f;
2561         }
2562         pc->SetPos(pos);
2563         pc->SetDim(dim);
2564     }
2565 
2566     // Will move the arrow to visit.
2567     if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT)
2568         FrameVisit(event.rTime);
2569 
2570     // Moves the boundaries.
2571     FrameShowLimit(event.rTime);
2572 
2573     if (m_phase == PHASE_SIMUL)
2574     {
2575         if (!m_editLock && !m_engine->GetPause())
2576         {
2577             CheckEndMission(true);
2578             UpdateAudio(true);
2579             if (m_scoreboard)
2580                 m_scoreboard->UpdateObjectCount();
2581         }
2582 
2583         if (m_winDelay > 0.0f && !m_editLock)
2584         {
2585             m_winDelay -= event.rTime;
2586             if (m_winDelay <= 0.0f)
2587             {
2588                 if (m_movieLock)
2589                     m_winDelay = 1.0f;
2590                 else
2591                     m_eventQueue->AddEvent(Event(EVENT_WIN));
2592             }
2593         }
2594 
2595         if (m_lostDelay > 0.0f && !m_editLock)
2596         {
2597             m_lostDelay -= event.rTime;
2598             if (m_lostDelay <= 0.0f)
2599             {
2600                 if (m_movieLock)
2601                     m_lostDelay = 1.0f;
2602                 else
2603                     m_eventQueue->AddEvent(Event(EVENT_LOST));
2604             }
2605         }
2606 
2607         if (GetMissionType() == MISSION_CODE_BATTLE)
2608         {
2609             if (!m_codeBattleInit)
2610             {
2611                 // NOTE: It's important to do this AFTER the first update event finished processing
2612                 //       because otherwise all robot parts are misplaced
2613                 m_userPause = m_pause->ActivatePause(PAUSE_ENGINE);
2614                 m_codeBattleInit = true; // Will start on resume
2615             }
2616 
2617             if (!m_codeBattleStarted && m_userPause == nullptr)
2618             {
2619                 m_codeBattleStarted = true;
2620                 ApplyCodeBattleInterface();
2621                 CreateCodeBattleInterface();
2622 
2623                 SetCodeBattleSpectatorMode(true);
2624 
2625                 m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE));
2626             }
2627 
2628             UpdateCodeBattleInterface();
2629         }
2630     }
2631 
2632     return true;
2633 }
2634 
ShowSaveIndicator(bool show)2635 void CRobotMain::ShowSaveIndicator(bool show)
2636 {
2637     Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_SAVING);
2638     if (pc != nullptr)
2639     {
2640         Math::Point pos, dim;
2641 
2642         if (!show)
2643         {
2644             dim.x = 10.0f/640.0f;
2645             dim.y = 10.0f/480.0f;
2646             pos.x = -20.0f/640.0f;
2647             pos.y = -20.0f/480.0f;  // invisible!
2648         }
2649         else
2650         {
2651             dim.x = 32.0f/640.0f;
2652             dim.y = 32.0f/480.0f;
2653             pos.x = (640.0f-24.0f)/640.0f;
2654             pos.y = (480.0f-24.0f)/480.0f;
2655 
2656             pos.x -= dim.x/2.0f;
2657             pos.y -= dim.y/2.0f;
2658         }
2659         pc->SetPos(pos);
2660         pc->SetDim(dim);
2661     }
2662 }
2663 
2664 //! Makes the event for all robots
EventObject(const Event & event)2665 bool CRobotMain::EventObject(const Event &event)
2666 {
2667     if (m_pause->IsPauseType(PAUSE_OBJECT_UPDATES)) return true;
2668 
2669     m_resetCreate = false;
2670 
2671     for (CObject* obj : m_objMan->GetAllObjects())
2672     {
2673         if (obj->Implements(ObjectInterfaceType::Interactive))
2674         {
2675             dynamic_cast<CInteractiveObject&>(*obj).EventProcess(event);
2676         }
2677     }
2678 
2679     if (m_resetCreate)
2680         ResetCreate();
2681 
2682     return true;
2683 }
2684 
2685 
2686 
ScenePerso()2687 void CRobotMain::ScenePerso()
2688 {
2689     DeleteAllObjects();  // removes all the current 3D Scene
2690     m_terrain->FlushRelief();
2691     m_engine->DeleteAllObjects();
2692     m_oldModelManager->DeleteAllModelCopies();
2693     m_terrain->FlushBuildingLevel();
2694     m_terrain->FlushFlyingLimit();
2695     m_lightMan->FlushLights();
2696     m_particle->FlushParticle();
2697 
2698     m_levelFile = "levels/other/perso.txt";
2699     try
2700     {
2701         CreateScene(false, true, false);  // sets scene
2702     }
2703     catch (const std::runtime_error& e)
2704     {
2705         LevelLoadingError("An error occurred while trying to load apperance scene", e, PHASE_PLAYER_SELECT);
2706     }
2707 
2708     m_engine->SetDrawWorld(false);  // does not draw anything on the interface
2709     m_engine->SetDrawFront(true);  // draws on the human interface
2710     CObject* obj = SearchHuman();
2711     if (obj != nullptr)
2712     {
2713         obj->SetDrawFront(true);  // draws the interface
2714 
2715         assert(obj->Implements(ObjectInterfaceType::Movable));
2716         CMotionHuman* mh = static_cast<CMotionHuman*>(dynamic_cast<CMovableObject&>(*obj).GetMotion());
2717         mh->StartDisplayPerso();
2718     }
2719 }
2720 
2721 //! Creates the whole scene
CreateScene(bool soluce,bool fixScene,bool resetObject)2722 void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject)
2723 {
2724     m_fixScene = fixScene;
2725 
2726     m_base = nullptr;
2727 
2728     if (!resetObject)
2729     {
2730         m_build = 0;
2731         m_researchDone.clear();  // no research done
2732         m_researchDone[0] = 0;
2733         m_researchEnable = 0;
2734 
2735         g_unit = UNIT;
2736 
2737         FlushDisplayInfo();
2738         m_terrain->FlushMaterials();
2739         m_audioTrack = "";
2740         m_audioRepeat = true;
2741         m_satcomTrack  = "";
2742         m_satcomRepeat = true;
2743         m_editorTrack  = "";
2744         m_editorRepeat = true;
2745         m_displayText->SetDelay(1.0f);
2746         m_displayText->SetEnable(true);
2747         m_immediatSatCom = false;
2748         m_lockedSatCom = false;
2749         m_endingWin = "";
2750         m_endingLost = "";
2751         m_audioChange.clear();
2752         m_endTake.clear();
2753         m_endTakeImmediat = false;
2754         m_endTakeResearch = 0;
2755         m_endTakeTimeout = -1.0f;
2756         m_endTakeTeamImmediateWin = false;
2757         m_endTakeWinDelay = 2.0f;
2758         m_endTakeLostDelay = 2.0f;
2759         m_teamFinished.clear();
2760         m_scoreboard.reset();
2761         m_globalMagnifyDamage = 1.0f;
2762         m_obligatoryTokens.clear();
2763         m_mapShow = true;
2764         m_mapImage = false;
2765         m_mapFilename[0] = 0;
2766 
2767         m_controller = nullptr;
2768 
2769         m_colorNewBot.clear();
2770         m_colorNewBot[0] = COLOR_REF_BOT;
2771         m_colorNewAlien = COLOR_REF_ALIEN;
2772         m_colorNewGreen = COLOR_REF_GREEN;
2773         m_colorNewWater = COLOR_REF_WATER;
2774 
2775         m_engine->SetAmbientColor(Gfx::Color(0.5f, 0.5f, 0.5f, 0.5f), 0);
2776         m_engine->SetAmbientColor(Gfx::Color(0.5f, 0.5f, 0.5f, 0.5f), 1);
2777         m_engine->SetFogColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f), 0);
2778         m_engine->SetFogColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f), 1);
2779         m_engine->SetDeepView(1000.0f, 0);
2780         m_engine->SetDeepView(1000.0f, 1);
2781         m_engine->SetFogStart(0.75f, 0);
2782         m_engine->SetFogStart(0.75f, 1);
2783         m_engine->SetSecondTexture("");
2784         m_engine->SetForegroundName("");
2785 
2786         GetResource(RES_TEXT, RT_SCRIPT_NEW, m_scriptName);
2787         m_scriptFile = "";
2788 
2789         m_missionType   = MISSION_NORMAL;
2790         m_codeBattleInit = false;
2791         m_codeBattleStarted = false;
2792 
2793         m_teamNames.clear();
2794 
2795         m_missionResult = ERR_MISSION_NOTERM;
2796         m_missionResultFromScript = false;
2797     }
2798 
2799     // NOTE: Reset timer always, even when only resetting object positions
2800     m_missionTimerEnabled = false;
2801     m_missionTimerStarted = false;
2802     m_missionTimer = 0.0f;
2803 
2804     std::string backgroundPath = "";
2805     Gfx::Color backgroundUp = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f);
2806     Gfx::Color backgroundDown = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f);
2807     Gfx::Color backgroundCloudUp = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f);
2808     Gfx::Color backgroundCloudDown = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f);
2809     bool backgroundFull = false;
2810 
2811     auto LoadingWarning = [&](const std::string& message)
2812     {
2813         GetLogger()->Warn("%s\n", message.c_str());
2814         m_ui->GetDialog()->StartInformation("Level loading warning", "This level contains problems. It may stop working in future versions of the game.", message);
2815     };
2816 
2817     try
2818     {
2819         m_ui->GetLoadingScreen()->SetProgress(0.05f, RT_LOADING_PROCESSING);
2820         GetLogger()->Info("Loading level: %s\n", m_levelFile.c_str());
2821         CLevelParser levelParser(m_levelFile);
2822         levelParser.SetLevelPaths(m_levelCategory, m_levelChap, m_levelRank);
2823         levelParser.Load();
2824         int numObjects = levelParser.CountLines("CreateObject");
2825         m_ui->GetLoadingScreen()->SetProgress(0.1f, RT_LOADING_LEVEL_SETTINGS);
2826 
2827         int rankObj = 0;
2828         CObject* sel = nullptr;
2829 
2830         for (auto& line : levelParser.GetLines())
2831         {
2832             if (line->GetCommand() == "Title" && !resetObject)
2833             {
2834                 //strcpy(m_title, line->GetParam("text")->AsString().c_str());
2835                 continue;
2836             }
2837 
2838             if (line->GetCommand() == "Resume" && !resetObject)
2839             {
2840                 //strcpy(m_resume, line->GetParam("text")->AsString().c_str());
2841                 continue;
2842             }
2843 
2844             if (line->GetCommand() == "ScriptName" && !resetObject)
2845             {
2846                 m_scriptName = line->GetParam("text")->AsString();
2847                 continue;
2848             }
2849 
2850             if (line->GetCommand() == "ScriptFile" && !resetObject)
2851             {
2852                 m_scriptFile = line->GetParam("name")->AsString();
2853                 continue;
2854             }
2855 
2856             if (line->GetCommand() == "Instructions" && !resetObject)
2857             {
2858                 strcpy(m_infoFilename[SATCOM_HUSTON], line->GetParam("name")->AsPath("help/%lng%").c_str());
2859 
2860                 m_immediatSatCom = line->GetParam("immediat")->AsBool(false);
2861                 m_beginSatCom = m_lockedSatCom = line->GetParam("lock")->AsBool(false);
2862                 if (m_app->GetSceneTestMode()) m_immediatSatCom = false;
2863                 continue;
2864             }
2865 
2866             if (line->GetCommand() == "Satellite" && !resetObject)
2867             {
2868                 strcpy(m_infoFilename[SATCOM_SAT], line->GetParam("name")->AsPath("help/%lng%").c_str());
2869                 continue;
2870             }
2871 
2872             if (line->GetCommand() == "Loading" && !resetObject)
2873             {
2874                 strcpy(m_infoFilename[SATCOM_LOADING], line->GetParam("name")->AsPath("help/%lng%").c_str());
2875                 continue;
2876             }
2877 
2878             if (line->GetCommand() == "HelpFile" && !resetObject)
2879             {
2880                 strcpy(m_infoFilename[SATCOM_PROG], line->GetParam("name")->AsPath("help/%lng%").c_str());
2881                 continue;
2882             }
2883             if (line->GetCommand() == "SoluceFile" && !resetObject)
2884             {
2885                 strcpy(m_infoFilename[SATCOM_SOLUCE], line->GetParam("name")->AsPath("help/%lng%").c_str());
2886                 continue;
2887             }
2888 
2889             if (line->GetCommand() == "EndingFile" && !resetObject)
2890             {
2891                 auto Process = [&](const std::string& type) -> std::string
2892                 {
2893                     if (line->GetParam(type)->IsDefined())
2894                     {
2895                         try
2896                         {
2897                             int rank = boost::lexical_cast<int>(line->GetParam(type)->GetValue());
2898                             if (rank >= 0)
2899                             {
2900                                 // TODO: Fix default levels and add a future removal warning
2901                                 GetLogger()->Warn("This level is using deprecated way of defining %1$s scene. Please change the %1$s= parameter in EndingFile from %2$d to \"levels/other/%1$s%2$03d.txt\".\n", type.c_str(), rank);
2902                                 std::stringstream ss;
2903                                 ss << "levels/other/" << type << std::setfill('0') << std::setw(3) << rank << ".txt";
2904                                 return ss.str();
2905                             }
2906                             else
2907                             {
2908                                 // TODO: Fix default levels and add a future removal warning
2909                                 GetLogger()->Warn("This level is using deprecated way of defining %1$s scene. Please remove the %1$s= parameter in EndingFile.\n", type.c_str());
2910                                 return "";
2911                             }
2912 
2913                         }
2914                         catch (boost::bad_lexical_cast &e)
2915                         {
2916                             return line->GetParam(type)->AsPath("levels");
2917                         }
2918                     }
2919                     return "";
2920                 };
2921                 m_endingWin = Process("win");
2922                 m_endingLost = Process("lost");
2923                 continue;
2924             }
2925 
2926             if (line->GetCommand() == "MessageDelay" && !resetObject)
2927             {
2928                 m_displayText->SetDelay(line->GetParam("factor")->AsFloat());
2929                 continue;
2930             }
2931 
2932             if (line->GetCommand() == "MissionTimer")
2933             {
2934                 m_missionTimerEnabled = line->GetParam("enabled")->AsBool();
2935                 if (!line->GetParam("program")->AsBool(false))
2936                 {
2937                     m_missionTimerStarted = true;
2938                 }
2939                 continue;
2940             }
2941 
2942             if (line->GetCommand() == "TeamName")
2943             {
2944                 int team = line->GetParam("team")->AsInt();
2945                 std::string name = line->GetParam("name")->AsString();
2946                 m_teamNames[team] = name;
2947                 continue;
2948             }
2949 
2950             if (line->GetCommand() == "CacheAudio" && !resetObject)
2951             {
2952                 std::string filename = line->GetParam("filename")->AsPath("music");
2953                 m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, filename);
2954                 m_sound->CacheMusic(filename);
2955                 continue;
2956             }
2957 
2958             if (line->GetCommand() == "AudioChange" && !resetObject)
2959             {
2960                 auto audioChange = MakeUnique<CAudioChangeCondition>();
2961                 audioChange->Read(line.get());
2962                 m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, audioChange->music);
2963                 m_sound->CacheMusic(audioChange->music);
2964                 m_audioChange.push_back(std::move(audioChange));
2965 
2966                 if (!line->GetParam("pos")->IsDefined() || !line->GetParam("dist")->IsDefined())
2967                 {
2968                     LoadingWarning("The defaults for pos= and dist= are going to change, specify them explicitly. See issue #759 (https://git.io/vVBzH)");
2969                 }
2970                 continue;
2971             }
2972 
2973             if (line->GetCommand() == "Audio" && !resetObject)
2974             {
2975                 if (line->GetParam("track")->IsDefined())
2976                 {
2977                     if (line->GetParam("filename")->IsDefined())
2978                         throw CLevelParserException("You can't use track and filename at the same time");
2979 
2980                     GetLogger()->Warn("Using track= is deprecated. Please replace this with filename=\n");
2981                     int trackid = line->GetParam("track")->AsInt();
2982                     if (trackid != 0)
2983                     {
2984                         std::stringstream filenameStr;
2985                         filenameStr << "music/music" << std::setfill('0') << std::setw(3) << trackid << ".ogg";
2986                         m_audioTrack = filenameStr.str();
2987                     }
2988                     else
2989                     {
2990                         m_audioTrack = "";
2991                     }
2992                 }
2993                 else
2994                 {
2995                     if (line->GetParam("filename")->IsDefined())
2996                     {
2997                         m_audioTrack = line->GetParam("filename")->AsPath("music");
2998                     }
2999                     else
3000                     {
3001                         m_audioTrack = "";
3002                     }
3003                 }
3004                 if (!m_audioTrack.empty())
3005                 {
3006                     m_audioRepeat = line->GetParam("repeat")->AsBool(true);
3007                 }
3008 
3009                 if (line->GetParam("satcom")->IsDefined())
3010                 {
3011                     m_satcomTrack = line->GetParam("satcom")->AsPath("music");
3012                     m_satcomRepeat = line->GetParam("satcomRepeat")->AsBool(true);
3013                 }
3014                 else
3015                 {
3016                     m_satcomTrack = "";
3017                 }
3018 
3019                 if (line->GetParam("editor")->IsDefined())
3020                 {
3021                     m_editorTrack = line->GetParam("editor")->AsPath("music");
3022                     m_editorRepeat = line->GetParam("editorRepeat")->AsBool(true);
3023                 }
3024                 else
3025                 {
3026                     m_editorTrack = "";
3027                 }
3028 
3029                 if (!m_audioTrack.empty())
3030                 {
3031                     m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, m_audioTrack);
3032                     m_sound->CacheMusic(m_audioTrack);
3033                 }
3034                 if (!m_satcomTrack.empty())
3035                 {
3036                     m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, m_satcomTrack);
3037                     m_sound->CacheMusic(m_satcomTrack);
3038                 }
3039                 if (!m_editorTrack.empty())
3040                 {
3041                     m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, m_editorTrack);
3042                     m_sound->CacheMusic(m_editorTrack);
3043                 }
3044                 continue;
3045             }
3046 
3047             if (line->GetCommand() == "AmbientColor" && !resetObject)
3048             {
3049                 m_engine->SetAmbientColor(line->GetParam("air")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 0);
3050                 m_engine->SetAmbientColor(line->GetParam("water")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 1);
3051                 continue;
3052             }
3053 
3054             if (line->GetCommand() == "FogColor" && !resetObject)
3055             {
3056                 m_engine->SetFogColor(line->GetParam("air")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 0);
3057                 m_engine->SetFogColor(line->GetParam("water")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 1);
3058                 continue;
3059             }
3060 
3061             if (line->GetCommand() == "VehicleColor" && !resetObject)
3062             {
3063                 m_colorNewBot[line->GetParam("team")->AsInt(0)] = line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f));
3064                 continue;
3065             }
3066 
3067             if (line->GetCommand() == "InsectColor" && !resetObject)
3068             {
3069                 m_colorNewAlien = line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f));
3070                 continue;
3071             }
3072 
3073             if (line->GetCommand() == "GreeneryColor" && !resetObject)
3074             {
3075                 m_colorNewGreen = line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f));
3076                 continue;
3077             }
3078 
3079             if (line->GetCommand() == "DeepView" && !resetObject)
3080             {
3081                 m_engine->SetDeepView(line->GetParam("air")->AsFloat(500.0f)*g_unit, 0, false);
3082                 m_engine->SetDeepView(line->GetParam("water")->AsFloat(100.0f)*g_unit, 1, false);
3083                 continue;
3084             }
3085 
3086             if (line->GetCommand() == "FogStart" && !resetObject)
3087             {
3088                 m_engine->SetFogStart(line->GetParam("air")->AsFloat(0.5f), 0);
3089                 m_engine->SetFogStart(line->GetParam("water")->AsFloat(0.5f), 1);
3090                 continue;
3091             }
3092 
3093             if (line->GetCommand() == "SecondTexture" && !resetObject)
3094             {
3095                 if (line->GetParam("rank")->IsDefined())
3096                 {
3097                     char tex[20] = { 0 };
3098                     sprintf(tex, "dirty%.2d.png", line->GetParam("rank")->AsInt());
3099                     m_engine->SetSecondTexture(tex);
3100                 }
3101                 else
3102                 {
3103                     m_engine->SetSecondTexture("../" + line->GetParam("texture")->AsPath("textures"));
3104                 }
3105                 continue;
3106             }
3107 
3108             if (line->GetCommand() == "Background" && !resetObject)
3109             {
3110                 if (line->GetParam("image")->IsDefined())
3111                     backgroundPath = line->GetParam("image")->AsPath("textures");
3112                 backgroundUp = line->GetParam("up")->AsColor(backgroundUp);
3113                 backgroundDown = line->GetParam("down")->AsColor(backgroundDown);
3114                 backgroundCloudUp = line->GetParam("cloudUp")->AsColor(backgroundCloudUp);
3115                 backgroundCloudDown = line->GetParam("cloudDown")->AsColor(backgroundCloudDown);
3116                 backgroundFull = line->GetParam("full")->AsBool(backgroundFull);
3117                 continue;
3118             }
3119 
3120             if (line->GetCommand() == "Planet" && !resetObject)
3121             {
3122                 Math::Vector    ppos, uv1, uv2;
3123 
3124                 ppos  = line->GetParam("pos")->AsPoint();
3125                 uv1   = line->GetParam("uv1")->AsPoint();
3126                 uv2   = line->GetParam("uv2")->AsPoint();
3127                 m_planet->Create(line->GetParam("mode")->AsPlanetType(),
3128                                 Math::Point(ppos.x, ppos.z),
3129                                 line->GetParam("dim")->AsFloat(0.2f),
3130                                 line->GetParam("speed")->AsFloat(0.0f),
3131                                 line->GetParam("dir")->AsFloat(0.0f),
3132                                 line->GetParam("image")->AsPath("textures"),
3133                                 Math::Point(uv1.x, uv1.z),
3134                                 Math::Point(uv2.x, uv2.z),
3135                                 line->GetParam("image")->AsPath("textures").find("planet") != std::string::npos // TODO: add transparent op or modify textures
3136                 );
3137                 continue;
3138             }
3139 
3140             if (line->GetCommand() == "ForegroundName" && !resetObject)
3141             {
3142                 m_engine->SetForegroundName(line->GetParam("image")->AsPath("textures"));
3143                 continue;
3144             }
3145 
3146             if (line->GetCommand() == "Level" && !resetObject)
3147             {
3148                 g_unit = line->GetParam("unitScale")->AsFloat(4.0f);
3149                 m_engine->SetTracePrecision(line->GetParam("traceQuality")->AsFloat(1.0f));
3150                 m_shortCut = line->GetParam("shortcut")->AsBool(true);
3151 
3152                 m_missionType = line->GetParam("type")->AsMissionType(MISSION_NORMAL);
3153                 m_globalMagnifyDamage = line->GetParam("magnifyDamage")->AsFloat(1.0f);
3154                 m_globalNuclearCapacity = line->GetParam("nuclearCapacity")->AsFloat(10.0f);
3155                 m_globalCellCapacity = line->GetParam("cellCapacity")->AsFloat(1.0f);
3156 
3157                 continue;
3158             }
3159 
3160             if (line->GetCommand() == "TerrainGenerate" && !resetObject)
3161             {
3162                 m_ui->GetLoadingScreen()->SetProgress(0.2f, RT_LOADING_TERRAIN);
3163                 m_terrain->Generate(line->GetParam("mosaic")->AsInt(20),
3164                                     line->GetParam("brick")->AsInt(3),
3165                                     line->GetParam("size")->AsFloat(20.0f),
3166                                     line->GetParam("vision")->AsFloat(500.0f)*g_unit,
3167                                     line->GetParam("depth")->AsInt(2),
3168                                     line->GetParam("hard")->AsFloat(0.5f));
3169                 continue;
3170             }
3171 
3172             if (line->GetCommand() == "TerrainWind" && !resetObject)
3173             {
3174                 m_terrain->SetWind(line->GetParam("speed")->AsPoint());
3175                 continue;
3176             }
3177 
3178             if (line->GetCommand() == "TerrainRelief" && !resetObject)
3179             {
3180                 m_ui->GetLoadingScreen()->SetProgress(0.2f+(1.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_RELIEF);
3181                 m_terrain->LoadRelief(
3182                     line->GetParam("image")->AsPath("textures"),
3183                     line->GetParam("factor")->AsFloat(1.0f),
3184                     line->GetParam("border")->AsBool(true));
3185                 continue;
3186             }
3187 
3188             if (line->GetCommand() == "TerrainRandomRelief" && !resetObject)
3189             {
3190                 m_ui->GetLoadingScreen()->SetProgress(0.2f+(1.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_RELIEF);
3191                 m_terrain->RandomizeRelief();
3192                 continue;
3193             }
3194 
3195             if (line->GetCommand() == "TerrainResource" && !resetObject)
3196             {
3197                 m_ui->GetLoadingScreen()->SetProgress(0.2f+(2.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_RES);
3198                 m_terrain->LoadResources(line->GetParam("image")->AsPath("textures"));
3199                 continue;
3200             }
3201 
3202             if (line->GetCommand() == "TerrainWater" && !resetObject)
3203             {
3204                 Math::Vector pos;
3205                 pos.x = line->GetParam("moveX")->AsFloat(0.0f);
3206                 pos.y = line->GetParam("moveY")->AsFloat(0.0f);
3207                 pos.z = pos.x;
3208                 m_water->Create(line->GetParam("air")->AsWaterType(Gfx::WATER_TT),
3209                                 line->GetParam("water")->AsWaterType(Gfx::WATER_TT),
3210                                 line->GetParam("image")->AsPath("textures"),
3211                                 line->GetParam("diffuse")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)),
3212                                 line->GetParam("ambient")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)),
3213                                 line->GetParam("level")->AsFloat(100.0f)*g_unit,
3214                                 line->GetParam("glint")->AsFloat(1.0f),
3215                                 pos);
3216                 m_colorNewWater = line->GetParam("color")->AsColor(COLOR_REF_WATER);
3217                 m_colorShiftWater = line->GetParam("brightness")->AsFloat(0.0f);
3218                 continue;
3219             }
3220 
3221             if (line->GetCommand() == "TerrainLava" && !resetObject)
3222             {
3223                 m_water->SetLava(line->GetParam("mode")->AsBool());
3224                 continue;
3225             }
3226 
3227             if (line->GetCommand() == "TerrainCloud" && !resetObject)
3228             {
3229                 std::string path = "";
3230                 if (line->GetParam("image")->IsDefined())
3231                     path = line->GetParam("image")->AsPath("textures");
3232                 m_cloud->Create(path,
3233                                 line->GetParam("diffuse")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)),
3234                                 line->GetParam("ambient")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)),
3235                                 line->GetParam("level")->AsFloat(500.0f)*g_unit);
3236                 continue;
3237             }
3238 
3239             if (line->GetCommand() == "TerrainBlitz" && !resetObject)
3240             {
3241                 m_lightning->Create(line->GetParam("sleep")->AsFloat(0.0f),
3242                                     line->GetParam("delay")->AsFloat(3.0f),
3243                                     line->GetParam("magnetic")->AsFloat(50.0f)*g_unit);
3244                 continue;
3245             }
3246 
3247             if (line->GetCommand() == "TerrainInitTextures" && !resetObject)
3248             {
3249                 m_ui->GetLoadingScreen()->SetProgress(0.2f+(3.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_TEX);
3250                 std::string name = "../" + line->GetParam("image")->AsPath("textures");
3251                 if (name.find(".") == std::string::npos)
3252                     name += ".png";
3253                 unsigned int dx = line->GetParam("dx")->AsInt(1);
3254                 unsigned int dy = line->GetParam("dy")->AsInt(1);
3255 
3256                 int tt[100]; //TODO: I have no idea how TerrainInitTextures works, but maybe we shuld remove the limit to 100?
3257                 if (dx*dy > 100)
3258                     throw CLevelParserException("In TerrainInitTextures: dx*dy must be <100");
3259                 if (line->GetParam("table")->IsDefined())
3260                 {
3261                     auto& table = line->GetParam("table")->AsArray();
3262 
3263                     if (table.size() > dx*dy)
3264                         throw CLevelParserException("In TerrainInitTextures: table size must be dx*dy");
3265 
3266                     for (unsigned int i = 0; i < dx*dy; i++)
3267                     {
3268                         if (i >= table.size())
3269                         {
3270                             tt[i] = 0;
3271                         }
3272                         else
3273                         {
3274                             tt[i] = table[i]->AsInt();
3275                         }
3276                     }
3277                 }
3278                 else
3279                 {
3280                     for (unsigned int i = 0; i < dx*dy; i++)
3281                     {
3282                         tt[i] = 0;
3283                     }
3284                 }
3285 
3286                 m_terrain->InitTextures(name.c_str(), tt, dx, dy);
3287                 continue;
3288             }
3289 
3290             if (line->GetCommand() == "TerrainInit" && !resetObject)
3291             {
3292                 m_terrain->InitMaterials(line->GetParam("id")->AsInt(1));
3293                 continue;
3294             }
3295 
3296             if (line->GetCommand() == "TerrainMaterial" && !resetObject)
3297             {
3298                 std::string name = line->GetParam("image")->AsPath("textures");
3299                 if (name.find(".") == std::string::npos)
3300                     name += ".png";
3301                 name = "../" + name;
3302 
3303                 m_terrain->AddMaterial(line->GetParam("id")->AsInt(0),
3304                                     name.c_str(),
3305                                     Math::Point(line->GetParam("u")->AsFloat(),
3306                                                 line->GetParam("v")->AsFloat()),
3307                                     line->GetParam("up")->AsInt(),
3308                                     line->GetParam("right")->AsInt(),
3309                                     line->GetParam("down")->AsInt(),
3310                                     line->GetParam("left")->AsInt(),
3311                                     line->GetParam("hard")->AsFloat(0.5f));
3312                 continue;
3313             }
3314 
3315             if (line->GetCommand() == "TerrainLevel" && !resetObject)
3316             {
3317                 m_ui->GetLoadingScreen()->SetProgress(0.2f+(3.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_TEX);
3318                 int id[50]; //TODO: I have no idea how TerrainLevel works, but maybe we should remove the limit to 50?
3319                 if (line->GetParam("id")->IsDefined())
3320                 {
3321                     auto& idArray = line->GetParam("id")->AsArray();
3322 
3323                     if (idArray.size() > 50)
3324                         throw CLevelParserException("In TerrainLevel: id array size must be < 50");
3325 
3326                     unsigned int i = 0;
3327                     while (i < 50)
3328                     {
3329                         id[i] = idArray[i]->AsInt();
3330                         i++;
3331                         if (i >= idArray.size()) break;
3332                     }
3333                     id[i] = 0;
3334                 }
3335 
3336                 m_terrain->GenerateMaterials(id,
3337                                             line->GetParam("min")->AsFloat(0.0f)*g_unit,
3338                                             line->GetParam("max")->AsFloat(100.0f)*g_unit,
3339                                             line->GetParam("slope")->AsFloat(5.0f),
3340                                             line->GetParam("freq")->AsFloat(100.0f),
3341                                             line->GetParam("center")->AsPoint(Math::Vector(0.0f, 0.0f, 0.0f))*g_unit,
3342                                             line->GetParam("radius")->AsFloat(0.0f)*g_unit);
3343                 continue;
3344             }
3345 
3346             if (line->GetCommand() == "TerrainCreate" && !resetObject)
3347             {
3348                 m_ui->GetLoadingScreen()->SetProgress(0.2f+(4.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_GEN);
3349                 m_terrain->CreateObjects();
3350                 continue;
3351             }
3352 
3353             if (line->GetCommand() == "BeginObject")
3354             {
3355                 InitEye();
3356                 SetMovieLock(false);
3357 
3358                 if (!resetObject)
3359                     ChangeColor();  // changes the colors of texture
3360 
3361                 if (!m_sceneReadPath.empty())  // loading file ?
3362                 {
3363                     m_ui->GetLoadingScreen()->SetProgress(0.25f, RT_LOADING_OBJECTS_SAVED);
3364                     sel = IOReadScene(m_sceneReadPath + "/data.sav", m_sceneReadPath + "/cbot.run");
3365                 }
3366                 else
3367                 {
3368                     m_ui->GetLoadingScreen()->SetProgress(0.25f, RT_LOADING_OBJECTS);
3369                 }
3370 
3371                 continue;
3372             }
3373 
3374             if (line->GetCommand() == "LevelController" && m_sceneReadPath.empty())
3375             {
3376                 if (m_controller != nullptr)
3377                 {
3378                     throw CLevelParserException("There can be only one LevelController in the level");
3379                 }
3380 
3381                 m_controller = m_objMan->CreateObject(Math::Vector(0.0f, 0.0f, 0.0f), 0.0f, OBJECT_CONTROLLER);
3382                 assert(m_controller->Implements(ObjectInterfaceType::Programmable));
3383                 assert(m_controller->Implements(ObjectInterfaceType::ProgramStorage));
3384 
3385                 assert(m_controller->Implements(ObjectInterfaceType::Old));
3386                 dynamic_cast<COldObject&>(*m_controller).SetCheckToken(false);
3387 
3388                 if (line->GetParam("script")->IsDefined())
3389                 {
3390                     CProgramStorageObject* programStorage = dynamic_cast<CProgramStorageObject*>(m_controller);
3391                     Program* program = programStorage->AddProgram();
3392                     programStorage->ReadProgram(program, line->GetParam("script")->AsPath("ai"));
3393                     program->readOnly = true;
3394                     dynamic_cast<CProgrammableObject&>(*m_controller).RunProgram(program);
3395                 }
3396                 continue;
3397             }
3398 
3399             if (line->GetCommand() == "CreateObject" && m_sceneReadPath.empty())
3400             {
3401                 ObjectCreateParams params = CObject::ReadCreateParams(line.get());
3402 
3403                 float objectProgress = static_cast<float>(rankObj) / static_cast<float>(numObjects);
3404                 std::string details = StrUtils::ToString<int>(rankObj+1)+" / "+StrUtils::ToString<int>(numObjects);
3405                 #if DEV_BUILD
3406                 // Object categories may spoil the level a bit, so hide them in release builds
3407                 details += ": "+CLevelParserParam::FromObjectType(params.type);
3408                 #endif
3409                 m_ui->GetLoadingScreen()->SetProgress(0.25f+objectProgress*0.75f, RT_LOADING_OBJECTS, details);
3410 
3411                 try
3412                 {
3413                     CObject* obj = m_objMan->CreateObject(params);
3414                     obj->Read(line.get());
3415 
3416                     if (m_fixScene && obj->GetType() == OBJECT_HUMAN)
3417                     {
3418                         assert(obj->Implements(ObjectInterfaceType::Movable));
3419                         CMotion* motion = dynamic_cast<CMovableObject&>(*obj).GetMotion();
3420                         if (m_phase == PHASE_WIN ) motion->SetAction(MHS_WIN,  0.4f);
3421                         if (m_phase == PHASE_LOST) motion->SetAction(MHS_LOST, 0.5f);
3422                     }
3423 
3424                     if (obj->Implements(ObjectInterfaceType::Controllable) && line->GetParam("select")->AsBool(false))
3425                         sel = obj;
3426 
3427                     if (obj->GetType() == OBJECT_BASE)
3428                         m_base = obj;
3429 
3430                     if (obj->Implements(ObjectInterfaceType::ProgramStorage))
3431                     {
3432                         CProgramStorageObject* programStorage = dynamic_cast<CProgramStorageObject*>(obj);
3433 
3434                         if (obj->Implements(ObjectInterfaceType::Controllable) && dynamic_cast<CControllableObject&>(*obj).GetSelectable() && obj->GetType() != OBJECT_HUMAN)
3435                         {
3436                             programStorage->SetProgramStorageIndex(rankObj);
3437                         }
3438 
3439                         char categoryChar = GetLevelCategoryDir(m_levelCategory)[0];
3440                         programStorage->LoadAllProgramsForLevel(
3441                             line.get(),
3442                             m_playerProfile->GetSaveFile(StrUtils::Format("%c%.3d%.3d", categoryChar, m_levelChap, m_levelRank)),
3443                             soluce
3444                         );
3445                     }
3446                 }
3447                 catch (const CObjectCreateException& e)
3448                 {
3449                     GetLogger()->Error("Error loading level object: %s\n", e.what());
3450                     throw;
3451                 }
3452 
3453                 rankObj ++;
3454                 continue;
3455             }
3456 
3457             if (line->GetCommand() == "CreateFog" && !resetObject)
3458             {
3459                 Gfx::ParticleType type = static_cast<Gfx::ParticleType>(Gfx::PARTIFOG0+(line->GetParam("type")->AsInt()));
3460                 Math::Vector pos = line->GetParam("pos")->AsPoint()*g_unit;
3461                 float height = line->GetParam("height")->AsFloat(1.0f)*g_unit;
3462                 float ddim = line->GetParam("dim")->AsFloat(50.0f)*g_unit;
3463                 float delay = line->GetParam("delay")->AsFloat(2.0f);
3464                 m_terrain->AdjustToFloor(pos);
3465                 pos.y += height;
3466                 Math::Point dim;
3467                 dim.x = ddim;
3468                 dim.y = dim.x;
3469                 m_particle->CreateParticle(pos, Math::Vector(0.0f, 0.0f, 0.0f), dim, type, delay, 0.0f, 0.0f);
3470                 continue;
3471             }
3472 
3473             if (line->GetCommand() == "CreateLight" && !resetObject)
3474             {
3475                 Gfx::EngineObjectType  type;
3476 
3477                 int lightRank = CreateLight(line->GetParam("dir")->AsPoint(),
3478                                             line->GetParam("color")->AsColor(Gfx::Color(0.5f, 0.5f, 0.5f, 1.0f)));
3479 
3480                 type = line->GetParam("type")->AsTerrainType(Gfx::ENG_OBJTYPE_NULL);
3481 
3482                 if (type == Gfx::ENG_OBJTYPE_TERRAIN)
3483                 {
3484                     m_lightMan->SetLightPriority(lightRank, Gfx::LIGHT_PRI_HIGHEST);
3485                     m_lightMan->SetLightIncludeType(lightRank, Gfx::ENG_OBJTYPE_TERRAIN);
3486                 }
3487 
3488                 if (type == Gfx::ENG_OBJTYPE_QUARTZ)
3489                     m_lightMan->SetLightIncludeType(lightRank, Gfx::ENG_OBJTYPE_QUARTZ);
3490 
3491                 if (type == Gfx::ENG_OBJTYPE_METAL)
3492                     m_lightMan->SetLightIncludeType(lightRank, Gfx::ENG_OBJTYPE_METAL);
3493 
3494                 if (type == Gfx::ENG_OBJTYPE_FIX)
3495                     m_lightMan->SetLightExcludeType(lightRank, Gfx::ENG_OBJTYPE_TERRAIN);
3496 
3497                 continue;
3498             }
3499             if (line->GetCommand() == "CreateSpot" && !resetObject)
3500             {
3501                 Gfx::EngineObjectType  type;
3502 
3503                 int rankLight = CreateSpot(line->GetParam("pos")->AsPoint()*g_unit,
3504                                         line->GetParam("color")->AsColor(Gfx::Color(0.5f, 0.5f, 0.5f, 1.0f)));
3505 
3506                 type = line->GetParam("type")->AsTerrainType(Gfx::ENG_OBJTYPE_NULL);
3507                 if (type == Gfx::ENG_OBJTYPE_TERRAIN)
3508                     m_lightMan->SetLightIncludeType(rankLight, Gfx::ENG_OBJTYPE_TERRAIN);
3509 
3510                 if (type == Gfx::ENG_OBJTYPE_QUARTZ)
3511                     m_lightMan->SetLightIncludeType(rankLight, Gfx::ENG_OBJTYPE_QUARTZ);
3512 
3513                 if (type == Gfx::ENG_OBJTYPE_METAL)
3514                     m_lightMan->SetLightIncludeType(rankLight, Gfx::ENG_OBJTYPE_METAL);
3515 
3516                 if (type == Gfx::ENG_OBJTYPE_FIX)
3517                     m_lightMan->SetLightExcludeType(rankLight, Gfx::ENG_OBJTYPE_TERRAIN);
3518 
3519                 continue;
3520             }
3521 
3522             if (line->GetCommand() == "GroundSpot" && !resetObject)
3523             {
3524                 int rank = m_engine->CreateGroundSpot();
3525                 if (rank != -1)
3526                 {
3527                     m_engine->SetObjectGroundSpotPos(rank, line->GetParam("pos")->AsPoint(Math::Vector(0.0f, 0.0f, 0.0f))*g_unit);
3528                     m_engine->SetObjectGroundSpotRadius(rank, line->GetParam("radius")->AsFloat(10.0f)*g_unit);
3529                     m_engine->SetObjectGroundSpotColor(rank, line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)));
3530                     m_engine->SetObjectGroundSpotSmooth(rank, line->GetParam("smooth")->AsFloat(1.0f));
3531                     m_engine->SetObjectGroundSpotMinMax(rank, line->GetParam("min")->AsFloat(0.0f)*g_unit,
3532                                                         line->GetParam("max")->AsFloat(0.0f)*g_unit);
3533                 }
3534                 continue;
3535             }
3536 
3537             if (line->GetCommand() == "WaterColor" && !resetObject)
3538             {
3539                 m_engine->SetWaterAddColor(line->GetParam("color")->AsColor());
3540                 continue;
3541             }
3542 
3543             if (line->GetCommand() == "MapColor" && !resetObject)
3544             {
3545                 m_map->FloorColorMap(line->GetParam("floor")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)),
3546                                     line->GetParam("water")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)));
3547                 m_mapShow = line->GetParam("show")->AsBool(true);
3548                 m_map->SetToy(line->GetParam("toyIcon")->AsBool(false));
3549                 m_mapImage = line->GetParam("image")->AsBool(false);
3550                 if (m_mapImage)
3551                 {
3552                     Math::Vector offset;
3553                     strcpy(m_mapFilename, line->GetParam("filename")->AsPath("textures").c_str());
3554                     offset = line->GetParam("offset")->AsPoint(Math::Vector(0.0f, 0.0f, 0.0f));
3555                     m_map->SetFixParam(line->GetParam("zoom")->AsFloat(1.0f),
3556                                     offset.x, offset.z,
3557                                     line->GetParam("angle")->AsFloat(0.0f)*Math::PI/180.0f,
3558                                     line->GetParam("mode")->AsInt(0),
3559                                     line->GetParam("debug")->AsBool(false));
3560                 }
3561                 continue;
3562             }
3563 
3564             if (line->GetCommand() == "MapZoom" && !resetObject)
3565             {
3566                 m_map->ZoomMap(line->GetParam("factor")->AsFloat(2.0f));
3567                 m_map->MapEnable(line->GetParam("enable")->AsBool(true));
3568                 continue;
3569             }
3570 
3571             if (line->GetCommand() == "MaxFlyingHeight" && !resetObject)
3572             {
3573                 m_terrain->SetFlyingMaxHeight(line->GetParam("max")->AsFloat(280.0f)*g_unit);
3574                 continue;
3575             }
3576 
3577             if (line->GetCommand() == "AddFlyingHeight" && !resetObject)
3578             {
3579                 m_terrain->AddFlyingLimit(line->GetParam("center")->AsPoint()*g_unit,
3580                                         line->GetParam("extRadius")->AsFloat(20.0f)*g_unit,
3581                                         line->GetParam("intRadius")->AsFloat(10.0f)*g_unit,
3582                                         line->GetParam("maxHeight")->AsFloat(200.0f));
3583                 continue;
3584             }
3585 
3586             if (line->GetCommand() == "Camera")
3587             {
3588                 m_camera->Init(line->GetParam("eye")->AsPoint(Math::Vector(0.0f, 0.0f, 0.0f))*g_unit,
3589                             line->GetParam("lookat")->AsPoint(Math::Vector(0.0f, 0.0f, 0.0f))*g_unit,
3590                             resetObject ? 0.0f : line->GetParam("delay")->AsFloat(0.0f));
3591 
3592                 if (line->GetParam("fadeIn")->AsBool(false))
3593                     m_camera->StartOver(Gfx::CAM_OVER_EFFECT_FADEIN_WHITE, Math::Vector(0.0f, 0.0f, 0.0f), 1.0f);
3594                 continue;
3595             }
3596 
3597             //! Note: This feature may be changed in next releases,
3598             //! Places new viewpoint, which can be selected later in (currently only in Code Battle) UI.
3599             //! Usage: View eye=x; y; z lookat=x; y; z
3600 
3601             if (line->GetCommand() == "View")
3602             {
3603                 if(m_viewpoints.size() == 10)
3604                 {
3605                     GetLogger()->Warn("Reached limit of 10 viewpoints, next ones will be ommited.\n");
3606                     continue;
3607                 }
3608                 Viewpoint tmp;
3609                 tmp.eye = line->GetParam("eye")->AsPoint()*g_unit;
3610                 tmp.look = line->GetParam("lookat")->AsPoint()*g_unit;
3611                 tmp.button = line->GetParam("button")->AsInt(13); // 13 is the camera button
3612                 m_viewpoints.push_back(tmp);
3613                 continue;
3614             }
3615 
3616             if (line->GetCommand() == "EndMissionTake" && !resetObject)
3617             {
3618                 auto endTake = MakeUnique<CSceneEndCondition>();
3619                 endTake->Read(line.get());
3620                 if (endTake->immediat)
3621                     m_endTakeImmediat = true;
3622                 m_endTake.push_back(std::move(endTake));
3623 
3624                 if (!line->GetParam("pos")->IsDefined() || !line->GetParam("dist")->IsDefined())
3625                 {
3626                     LoadingWarning("The defaults for pos= and dist= are going to change, specify them explicitly. See issue #759 (https://git.io/vVBzH)");
3627                 }
3628                 continue;
3629             }
3630             if (line->GetCommand() == "EndMissionTeams" && !resetObject)
3631             {
3632                 m_endTakeTeamImmediateWin = line->GetParam("immediateWin")->AsBool(false); // false = finishing removes the team that finished, true = finishing for one team ends the whole game
3633                 continue;
3634             }
3635             if (line->GetCommand() == "EndMissionDelay" && !resetObject)
3636             {
3637                 m_endTakeWinDelay  = line->GetParam("win")->AsFloat(2.0f);
3638                 m_endTakeLostDelay = line->GetParam("lost")->AsFloat(2.0f);
3639                 continue;
3640             }
3641             if (line->GetCommand() == "EndMissionResearch" && !resetObject) // This is not used in any original Colobot levels, but we'll keep it for userlevel creators
3642             {
3643                 m_endTakeResearch |= line->GetParam("type")->AsResearchFlag();
3644                 continue;
3645             }
3646             if (line->GetCommand() == "EndMissionTimeout" && !resetObject)
3647             {
3648                 m_endTakeTimeout = line->GetParam("time")->AsFloat();
3649                 continue;
3650             }
3651 
3652             if (line->GetCommand() == "Scoreboard" && !resetObject)
3653             {
3654                 if (line->GetParam("enable")->AsBool(false))
3655                 {
3656                     // Create the scoreboard
3657                     m_scoreboard = MakeUnique<CScoreboard>();
3658                     m_scoreboard->SetSortType(line->GetParam("sort")->AsSortType(CScoreboard::SortType::SORT_ID));
3659                 }
3660                 continue;
3661             }
3662 
3663             if (line->GetCommand() == "ScoreboardKillRule" && !resetObject)
3664             {
3665                 if (!m_scoreboard)
3666                     throw CLevelParserException("ScoreboardKillRule encountered but scoreboard is not enabled");
3667                 auto rule = MakeUnique<CScoreboard::CScoreboardKillRule>();
3668                 rule->Read(line.get());
3669                 m_scoreboard->AddKillRule(std::move(rule));
3670                 continue;
3671             }
3672             if (line->GetCommand() == "ScoreboardObjectRule" && !resetObject)
3673             {
3674                 if (!m_scoreboard)
3675                     throw CLevelParserException("ScoreboardObjectRule encountered but scoreboard is not enabled");
3676                 auto rule = MakeUnique<CScoreboard::CScoreboardObjectRule>();
3677                 rule->Read(line.get());
3678                 m_scoreboard->AddObjectRule(std::move(rule));
3679                 continue;
3680             }
3681             if (line->GetCommand() == "ScoreboardEndTakeRule" && !resetObject)
3682             {
3683                 if (!m_scoreboard)
3684                     throw CLevelParserException("ScoreboardEndTakeRule encountered but scoreboard is not enabled");
3685                 auto rule = MakeUnique<CScoreboard::CScoreboardEndTakeRule>();
3686                 rule->Read(line.get());
3687                 m_scoreboard->AddEndTakeRule(std::move(rule));
3688                 continue;
3689             }
3690 
3691             if (line->GetCommand() == "ObligatoryToken" && !resetObject)
3692             {
3693                 std::string token = line->GetParam("text")->AsString();
3694                 if (!line->GetParam("min")->IsDefined() && !line->GetParam("max")->IsDefined())
3695                     GetLogger()->Warn("ObligatoryToken without specifying min/max is provided only for backwards compatibility - instead, do this: ObligatoryToken text=\"%s\" min=1\n", token.c_str());
3696                 if (m_obligatoryTokens.count(token))
3697                     throw CLevelParserException("Incorrect ObligatoryToken specification - you cannot define a token twice");
3698 
3699                 m_obligatoryTokens[token].min = line->GetParam("min")->AsInt(line->GetParam("max")->IsDefined() ? -1 : 1); // BACKWARDS COMPATIBILITY: if neither min or max are defined, default to min=1
3700                 m_obligatoryTokens[token].max = line->GetParam("max")->AsInt(-1);
3701                 if (m_obligatoryTokens[token].min >= 0 && m_obligatoryTokens[token].max >= 0 && m_obligatoryTokens[token].min > m_obligatoryTokens[token].max)
3702                 {
3703                     throw CLevelParserException("Incorrect ObligatoryToken specification - min cannot be greater than max");
3704                 }
3705                 continue;
3706             }
3707 
3708             if (line->GetCommand() == "ProhibitedToken" && !resetObject) // NOTE: Kept only for backwards compatibility
3709             {
3710                 std::string token = line->GetParam("text")->AsString();
3711                 GetLogger()->Warn("ProhibitedToken is only provided for backwards compatibility - instead, do this: ObligatoryToken text=\"%s\" max=0\n", token.c_str());
3712                 if (m_obligatoryTokens.count(token))
3713                     throw CLevelParserException("Incorrect ObligatoryToken specification - you cannot define a token twice");
3714 
3715                 m_obligatoryTokens[token].min = -1;
3716                 m_obligatoryTokens[token].max = 0;
3717                 continue;
3718             }
3719 
3720             if (line->GetCommand() == "EnableBuild" && !resetObject)
3721             {
3722                 m_build |= line->GetParam("type")->AsBuildFlag();
3723                 continue;
3724             }
3725 
3726             if (line->GetCommand() == "EnableResearch" && !resetObject)
3727             {
3728                 m_researchEnable |= line->GetParam("type")->AsResearchFlag();
3729                 continue;
3730             }
3731 
3732             if (line->GetCommand() == "DoneResearch" && m_sceneReadPath.empty() && !resetObject) // not loading file?
3733             {
3734                 m_researchDone[0] |= line->GetParam("type")->AsResearchFlag();
3735                 continue;
3736             }
3737 
3738             if (line->GetCommand() == "NewScript" && !resetObject)
3739             {
3740                 m_newScriptName.push_back(NewScriptName(line->GetParam("type")->AsObjectType(OBJECT_NULL), line->GetParam("name")->AsString("")));
3741                 continue;
3742             }
3743 
3744             if (!m_sceneReadPath.empty()) continue; // ignore errors when loading saved game (TODO: don't report ones that are just not loaded when loading saved game)
3745             if (resetObject) continue; // ignore when reseting just objects (TODO: see above)
3746 
3747             throw CLevelParserException("Unknown command: '" + line->GetCommand() + "' in " + line->GetLevelFilename() + ":" + boost::lexical_cast<std::string>(line->GetLineNumber()));
3748         }
3749 
3750         // Do this here to prevent the first frame from taking a long time to render
3751         m_engine->UpdateGroundSpotTextures();
3752 
3753         m_ui->GetLoadingScreen()->SetProgress(1.0f, RT_LOADING_FINISHED);
3754         if (m_ui->GetLoadingScreen()->IsVisible())
3755         {
3756             // Force render of the "Loading finished" screen because it looks weird when the progress bar disappears in the middle
3757             m_app->Render();
3758         }
3759 
3760         if (!resetObject)
3761         {
3762             m_engine->SetBackground(backgroundPath,
3763                                     backgroundUp,
3764                                     backgroundDown,
3765                                     backgroundCloudUp,
3766                                     backgroundCloudDown,
3767                                     backgroundFull);
3768         }
3769 
3770         if (m_levelCategory == LevelCategory::Missions && !resetObject)  // mission?
3771         {
3772             m_playerProfile->SetFreeGameResearchUnlock(m_playerProfile->GetFreeGameResearchUnlock() | m_researchDone[0]);
3773             m_playerProfile->SetFreeGameBuildUnlock(m_playerProfile->GetFreeGameBuildUnlock() | m_build);
3774         }
3775 
3776         if (m_levelCategory == LevelCategory::FreeGame && !resetObject)  // free play?
3777         {
3778             m_researchDone[0] = m_playerProfile->GetFreeGameResearchUnlock();
3779 
3780             m_build = m_playerProfile->GetFreeGameBuildUnlock();
3781             m_build &= ~BUILD_RESEARCH;
3782             m_build &= ~BUILD_LABO;
3783             m_build |= BUILD_FACTORY;
3784             m_build |= BUILD_GFLAT;
3785             m_build |= BUILD_FLAG;
3786         }
3787 
3788         if (m_levelCategory == LevelCategory::GamePlus && !m_ui->GetPlusResearch() && !resetObject)  // new game plus?
3789         {
3790             m_researchDone[0] |= m_playerProfile->GetFreeGameResearchUnlock();
3791             m_build |= m_playerProfile->GetFreeGameBuildUnlock();
3792         }
3793 
3794         if (!resetObject)
3795         {
3796             m_short->SetMode(false);  // vehicles?
3797         }
3798 
3799         m_map->ShowMap(m_mapShow);
3800         m_map->UpdateMap();
3801         // TODO: m_engine->TimeInit(); ??
3802         m_input->ResetKeyStates();
3803         m_time = 0.0f;
3804         if (m_sceneReadPath.empty()) m_gameTime = 0.0f;
3805         m_gameTimeAbsolute = 0.0f;
3806         m_autosaveLast = 0.0f;
3807         m_infoUsed = 0;
3808 
3809         m_selectObject = sel;
3810 
3811         if (m_base == nullptr &&  // no main base?
3812             !m_fixScene)    // interractive scene?
3813         {
3814             CObject* obj = sel;
3815             if (sel == nullptr)
3816                 obj = SearchHuman();
3817 
3818             if (obj != nullptr)
3819             {
3820                 assert(obj->Implements(ObjectInterfaceType::Controllable));
3821                 SelectObject(obj);
3822                 m_camera->SetControllingObject(obj);
3823                 m_camera->SetType(dynamic_cast<CControllableObject&>(*obj).GetCameraType());
3824             }
3825         }
3826 
3827         if (m_fixScene)
3828             m_camera->SetType(Gfx::CAM_TYPE_SCRIPT);
3829 
3830         if (!m_sceneReadPath.empty() && sel != nullptr)  // loading file?
3831         {
3832             Math::Vector pos = sel->GetPosition();
3833             m_camera->Init(pos, pos, 0.0f);
3834 
3835             SelectObject(sel);
3836             m_camera->SetControllingObject(sel);
3837 
3838             m_beginSatCom = true;  // message already displayed
3839         }
3840     }
3841     catch (...)
3842     {
3843         m_sceneReadPath = "";
3844         throw;
3845     }
3846     m_sceneReadPath = "";
3847 
3848     if (m_app->GetSceneTestMode())
3849         m_eventQueue->AddEvent(Event(EVENT_QUIT));
3850 
3851     m_ui->ShowLoadingScreen(false);
3852     if (m_missionType == MISSION_CODE_BATTLE)
3853     {
3854         CreateCodeBattleInterface();
3855     }
3856     CreateShortcuts();
3857 }
3858 
LevelLoadingError(const std::string & error,const std::runtime_error & exception,Phase exitPhase)3859 void CRobotMain::LevelLoadingError(const std::string& error, const std::runtime_error& exception, Phase exitPhase)
3860 {
3861     m_ui->ShowLoadingScreen(false);
3862 
3863     GetLogger()->Error("%s\n", error.c_str());
3864     GetLogger()->Error("%s\n", exception.what());
3865     ChangePhase(exitPhase);
3866     m_ui->GetDialog()->StartInformation("Loading error", error, exception.what(), true, false);
3867 }
3868 
3869 //! Creates a directional light
CreateLight(Math::Vector direction,Gfx::Color color)3870 int CRobotMain::CreateLight(Math::Vector direction, Gfx::Color color)
3871 {
3872     if (direction.x == 0.0f &&
3873         direction.y == 0.0f &&
3874         direction.z == 0.0f)
3875     {
3876         direction.y = -1.0f;
3877     }
3878 
3879     Gfx::Light light;
3880     light.type = Gfx::LIGHT_DIRECTIONAL;
3881     light.diffuse = color;
3882     light.ambient = color * 0.1f;
3883     light.direction  = direction;
3884     int obj = m_lightMan->CreateLight(Gfx::LIGHT_PRI_HIGH);
3885     m_lightMan->SetLight(obj, light);
3886 
3887     return obj;
3888 }
3889 
3890 //! Creates a light spot
CreateSpot(Math::Vector pos,Gfx::Color color)3891 int CRobotMain::CreateSpot(Math::Vector pos, Gfx::Color color)
3892 {
3893     if (!m_engine->GetLightMode()) return -1;
3894 
3895     pos.y += m_terrain->GetFloorLevel(pos);
3896 
3897     Gfx::Light light;
3898     light.type          = Gfx::LIGHT_SPOT;
3899     light.diffuse       = color;
3900     light.ambient       = color * 0.1f;
3901     light.position      = pos;
3902     light.direction     = Math::Vector(0.0f, -1.0f, 0.0f);
3903     light.spotIntensity = 1.0f;
3904     light.spotAngle     = 90.0f*Math::PI/180.0f;
3905     light.attenuation0  = 2.0f;
3906     light.attenuation1  = 0.0f;
3907     light.attenuation2  = 0.0f;
3908     int obj = m_lightMan->CreateLight(Gfx::LIGHT_PRI_HIGH);
3909     m_lightMan->SetLight(obj, light);
3910 
3911     return obj;
3912 }
3913 
3914 
3915 //! Change the colors and textures
ChangeColor()3916 void CRobotMain::ChangeColor()
3917 {
3918     if (m_phase != PHASE_SIMUL    &&
3919         m_phase != PHASE_SETUPds  &&
3920         m_phase != PHASE_SETUPgs  &&
3921         m_phase != PHASE_SETUPps  &&
3922         m_phase != PHASE_SETUPcs  &&
3923         m_phase != PHASE_SETUPss  &&
3924         m_phase != PHASE_MOD_LIST &&
3925         m_phase != PHASE_WIN      &&
3926         m_phase != PHASE_LOST     &&
3927         m_phase != PHASE_APPERANCE ) return;
3928 
3929     // Player texture
3930 
3931     Math::Point ts = Math::Point(0.0f, 0.0f);
3932     Math::Point ti = Math::Point(1.0f, 1.0f);  // the entire image
3933 
3934     Gfx::Color colorRef1, colorNew1, colorRef2, colorNew2;
3935 
3936     colorRef1.a = 0.0f;
3937     colorRef2.a = 0.0f;
3938 
3939     colorRef1.r = 206.0f/256.0f;
3940     colorRef1.g = 206.0f/256.0f;
3941     colorRef1.b = 204.0f/256.0f;  // ~white
3942     colorNew1 = m_playerProfile->GetApperance().colorCombi;
3943     colorRef2.r = 255.0f/256.0f;
3944     colorRef2.g = 132.0f/256.0f;
3945     colorRef2.b =   1.0f/256.0f;  // orange
3946     colorNew2 = m_playerProfile->GetApperance().colorBand;
3947 
3948     Math::Point exclu[6];
3949     exclu[0] = Math::Point(192.0f/256.0f,   0.0f/256.0f);
3950     exclu[1] = Math::Point(256.0f/256.0f,  64.0f/256.0f);  // crystals + cylinders
3951     exclu[2] = Math::Point(208.0f/256.0f, 224.0f/256.0f);
3952     exclu[3] = Math::Point(256.0f/256.0f, 256.0f/256.0f);  // SatCom screen
3953     exclu[4] = Math::Point(0.0f, 0.0f);
3954     exclu[5] = Math::Point(0.0f, 0.0f);  // terminator
3955     m_engine->ChangeTextureColor("textures/objects/human.png", colorRef1, colorNew1, colorRef2, colorNew2, 0.30f, 0.01f, ts, ti, exclu);
3956 
3957     float tolerance;
3958 
3959     int face = GetGamerFace();
3960     if (face == 0)  // normal?
3961     {
3962         colorRef1.r =  90.0f/256.0f;
3963         colorRef1.g =  95.0f/256.0f;
3964         colorRef1.b =  85.0f/256.0f;  // black
3965         tolerance = 0.15f;
3966     }
3967     if (face == 1)  // bald?
3968     {
3969         colorRef1.r =  74.0f/256.0f;
3970         colorRef1.g =  58.0f/256.0f;
3971         colorRef1.b =  46.0f/256.0f;  // brown
3972         tolerance = 0.20f;
3973     }
3974     if (face == 2)  // carlos?
3975     {
3976         colorRef1.r =  70.0f/256.0f;
3977         colorRef1.g =  40.0f/256.0f;
3978         colorRef1.b =   8.0f/256.0f;  // brown
3979         tolerance = 0.30f;
3980     }
3981     if (face == 3)  // blonde?
3982     {
3983         colorRef1.r =  74.0f/256.0f;
3984         colorRef1.g =  16.0f/256.0f;
3985         colorRef1.b =   0.0f/256.0f;  // yellow
3986         tolerance = 0.20f;
3987     }
3988     colorNew1 = m_playerProfile->GetApperance().colorHair;
3989     colorRef2.r = 0.0f;
3990     colorRef2.g = 0.0f;
3991     colorRef2.b = 0.0f;
3992     colorNew2.r = 0.0f;
3993     colorNew2.g = 0.0f;
3994     colorNew2.b = 0.0f;
3995 
3996     char name[100];
3997     sprintf(name, "textures/objects/face%.2d.png", face+1);
3998     exclu[0] = Math::Point(105.0f/256.0f, 47.0f/166.0f);
3999     exclu[1] = Math::Point(153.0f/256.0f, 79.0f/166.0f);  // blue canister
4000     exclu[2] = Math::Point(0.0f, 0.0f);
4001     exclu[3] = Math::Point(0.0f, 0.0f);  // terminator
4002     m_engine->ChangeTextureColor(name, colorRef1, colorNew1, colorRef2, colorNew2, tolerance, 0.00f, ts, ti, exclu);
4003 
4004     colorRef2.r = 0.0f;
4005     colorRef2.g = 0.0f;
4006     colorRef2.b = 0.0f;
4007     colorNew2.r = 0.0f;
4008     colorNew2.g = 0.0f;
4009     colorNew2.b = 0.0f;
4010 
4011     // VehicleColor
4012 
4013     for (auto it : m_colorNewBot)
4014     {
4015         int team = it.first;
4016         Gfx::Color newColor = it.second;
4017         std::string teamStr = StrUtils::ToString<int>(team);
4018         if(team == 0) teamStr = "";
4019 
4020         m_engine->ChangeTextureColor("textures/objects/base1.png"+teamStr,   "textures/objects/base1.png",   COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4021         m_engine->ChangeTextureColor("textures/objects/convert.png"+teamStr, "textures/objects/convert.png", COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4022         m_engine->ChangeTextureColor("textures/objects/derrick.png"+teamStr, "textures/objects/derrick.png", COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4023         m_engine->ChangeTextureColor("textures/objects/factory.png"+teamStr, "textures/objects/factory.png", COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4024         m_engine->ChangeTextureColor("textures/objects/lemt.png"+teamStr,    "textures/objects/lemt.png",    COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4025         m_engine->ChangeTextureColor("textures/objects/roller.png"+teamStr,  "textures/objects/roller.png",  COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4026         m_engine->ChangeTextureColor("textures/objects/search.png"+teamStr,  "textures/objects/search.png",  COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4027         m_engine->ChangeTextureColor("textures/objects/rollert.png"+teamStr, "textures/objects/rollert.png", COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, nullptr, 0, true);
4028 
4029         exclu[0] = Math::Point(  0.0f/256.0f, 160.0f/256.0f);
4030         exclu[1] = Math::Point(256.0f/256.0f, 256.0f/256.0f);  // pencils
4031         exclu[2] = Math::Point(0.0f, 0.0f);
4032         exclu[3] = Math::Point(0.0f, 0.0f);  // terminator
4033         m_engine->ChangeTextureColor("textures/objects/drawer.png"+teamStr, "textures/objects/drawer.png",  COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, exclu, 0, true);
4034 
4035         exclu[0] = Math::Point(237.0f/256.0f, 176.0f/256.0f);
4036         exclu[1] = Math::Point(256.0f/256.0f, 220.0f/256.0f);  // blue canister
4037         exclu[2] = Math::Point(106.0f/256.0f, 150.0f/256.0f);
4038         exclu[3] = Math::Point(130.0f/256.0f, 214.0f/256.0f);  // safe location
4039         exclu[4] = Math::Point(0.0f, 0.0f);
4040         exclu[5] = Math::Point(0.0f, 0.0f);  // terminator
4041         m_engine->ChangeTextureColor("textures/objects/subm.png"+teamStr,   "textures/objects/subm.png",    COLOR_REF_BOT, newColor, colorRef2, colorNew2, 0.10f, -1.0f, ts, ti, exclu, 0, true);
4042     }
4043 
4044     // AlienColor
4045 
4046     exclu[0] = Math::Point(128.0f/256.0f, 160.0f/256.0f);
4047     exclu[1] = Math::Point(256.0f/256.0f, 256.0f/256.0f);  // SatCom
4048     exclu[2] = Math::Point(0.0f, 0.0f);
4049     exclu[3] = Math::Point(0.0f, 0.0f);  // terminator
4050     m_engine->ChangeTextureColor("textures/objects/ant.png",     COLOR_REF_ALIEN, m_colorNewAlien, colorRef2, colorNew2, 0.50f, -1.0f, ts, ti, exclu);
4051     m_engine->ChangeTextureColor("textures/objects/mother.png",  COLOR_REF_ALIEN, m_colorNewAlien, colorRef2, colorNew2, 0.50f, -1.0f, ts, ti);
4052 
4053     // GreeneryColor
4054     m_engine->ChangeTextureColor("textures/objects/plant.png",   COLOR_REF_GREEN, m_colorNewGreen, colorRef2, colorNew2, 0.50f, -1.0f, ts, ti);
4055 
4056     // water color
4057 
4058     // PARTIPLOUF0 and PARTIDROP :
4059     ts = Math::Point(0.500f, 0.500f);
4060     ti = Math::Point(0.875f, 0.750f);
4061     m_engine->ChangeTextureColor("textures/effect00.png", COLOR_REF_WATER, m_colorNewWater, colorRef2, colorNew2, 0.20f, -1.0f, ts, ti, nullptr, m_colorShiftWater, true);
4062 
4063     // PARTIFLIC :
4064     ts = Math::Point(0.00f, 0.75f);
4065     ti = Math::Point(0.25f, 1.00f);
4066     m_engine->ChangeTextureColor("textures/effect02.png", COLOR_REF_WATER, m_colorNewWater, colorRef2, colorNew2, 0.20f, -1.0f, ts, ti, nullptr, m_colorShiftWater, true);
4067 }
4068 
4069 //! Calculates the distance to the nearest object
4070 namespace
4071 {
SearchNearestObject(CObjectManager * objMan,Math::Vector center,CObject * exclu)4072 float SearchNearestObject(CObjectManager* objMan, Math::Vector center, CObject* exclu)
4073 {
4074     float min = 100000.0f;
4075     for (CObject* obj : objMan->GetAllObjects())
4076     {
4077         if (!obj->GetDetectable()) continue;  // inactive?
4078         if (IsObjectBeingTransported(obj)) continue;
4079 
4080         if (obj == exclu) continue;
4081 
4082         ObjectType type = obj->GetType();
4083 
4084         if (type == OBJECT_BASE)
4085         {
4086             Math::Vector oPos = obj->GetPosition();
4087             if (oPos.x != center.x ||
4088                 oPos.z != center.z)
4089             {
4090                 float dist = Math::Distance(center, oPos) - 80.0f;
4091                 if (dist < 0.0f) dist = 0.0f;
4092                 min = Math::Min(min, dist);
4093                 continue;
4094             }
4095         }
4096 
4097         if (type == OBJECT_STATION ||
4098             type == OBJECT_REPAIR ||
4099             type == OBJECT_DESTROYER)
4100         {
4101             Math::Vector oPos = obj->GetPosition();
4102             float dist = Math::Distance(center, oPos) - 8.0f;
4103             if (dist < 0.0f) dist = 0.0f;
4104             min = Math::Min(min, dist);
4105         }
4106 
4107         for (const auto &crashSphere : obj->GetAllCrashSpheres())
4108         {
4109             Math::Vector oPos = crashSphere.sphere.pos;
4110             float oRadius = crashSphere.sphere.radius;
4111 
4112             float dist = Math::Distance(center, oPos) - oRadius;
4113             if (dist < 0.0f) dist = 0.0f;
4114             min = Math::Min(min, dist);
4115         }
4116     }
4117     return min;
4118 }
4119 }
4120 
4121 //! Calculates a free space
FreeSpace(Math::Vector & center,float minRadius,float maxRadius,float space,CObject * exclu)4122 bool CRobotMain::FreeSpace(Math::Vector &center, float minRadius, float maxRadius,
4123                            float space, CObject *exclu)
4124 {
4125     if (minRadius < maxRadius)  // from internal to external?
4126     {
4127         for (float radius = minRadius; radius <= maxRadius; radius += space)
4128         {
4129             float ia = space/radius;
4130             for (float angle = 0.0f; angle < Math::PI*2.0f; angle += ia)
4131             {
4132                 Math::Point p;
4133                 p.x = center.x+radius;
4134                 p.y = center.z;
4135                 p = Math::RotatePoint(Math::Point(center.x, center.z), angle, p);
4136                 Math::Vector pos;
4137                 pos.x = p.x;
4138                 pos.z = p.y;
4139                 pos.y = 0.0f;
4140                 m_terrain->AdjustToFloor(pos, true);
4141                 float dist = SearchNearestObject(m_objMan.get(), pos, exclu);
4142                 if (dist >= space)
4143                 {
4144                     float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f);
4145                     if (flat >= dist/2.0f)
4146                     {
4147                         center = pos;
4148                         return true;
4149                     }
4150                 }
4151             }
4152         }
4153     }
4154     else    // from external to internal?
4155     {
4156         for (float radius=maxRadius; radius >= minRadius; radius -= space)
4157         {
4158             float ia = space/radius;
4159             for (float angle=0.0f ; angle<Math::PI*2.0f ; angle+=ia )
4160             {
4161                 Math::Point p;
4162                 p.x = center.x+radius;
4163                 p.y = center.z;
4164                 p = Math::RotatePoint(Math::Point(center.x, center.z), angle, p);
4165                 Math::Vector pos;
4166                 pos.x = p.x;
4167                 pos.z = p.y;
4168                 pos.y = 0.0f;
4169                 m_terrain->AdjustToFloor(pos, true);
4170                 float dist = SearchNearestObject(m_objMan.get(), pos, exclu);
4171                 if (dist >= space)
4172                 {
4173                     float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f);
4174                     if (flat >= dist/2.0f)
4175                     {
4176                         center = pos;
4177                         return true;
4178                     }
4179                 }
4180             }
4181         }
4182     }
4183     return false;
4184 }
4185 
4186 //! Calculates a flat free space
FlatFreeSpace(Math::Vector & center,float minFlat,float minRadius,float maxRadius,float space,CObject * exclu)4187 bool CRobotMain::FlatFreeSpace(Math::Vector &center, float minFlat, float minRadius, float maxRadius,
4188                            float space, CObject *exclu)
4189 {
4190     if (minRadius < maxRadius)  // from internal to external?
4191     {
4192         for (float radius = minRadius; radius <= maxRadius; radius += space)
4193         {
4194             float ia = space/radius;
4195             for (float angle = 0.0f; angle < Math::PI*2.0f; angle += ia)
4196             {
4197                 Math::Point p;
4198                 p.x = center.x+radius;
4199                 p.y = center.z;
4200                 p = Math::RotatePoint(Math::Point(center.x, center.z), angle, p);
4201                 Math::Vector pos;
4202                 pos.x = p.x;
4203                 pos.z = p.y;
4204                 pos.y = 0.0f;
4205                 m_terrain->AdjustToFloor(pos, true);
4206                 float dist = SearchNearestObject(m_objMan.get(), pos, exclu);
4207                 if (dist >= space)
4208                 {
4209                     float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f);
4210                     if (flat >= dist/2.0f)
4211                     {
4212                         flat = m_terrain->GetFlatZoneRadius(pos, minFlat);
4213                         if(flat >= minFlat)
4214                         {
4215                             center = pos;
4216                             return true;
4217                         }
4218                     }
4219                 }
4220             }
4221         }
4222     }
4223     else    // from external to internal?
4224     {
4225         for (float radius=maxRadius; radius >= minRadius; radius -= space)
4226         {
4227             float ia = space/radius;
4228             for (float angle=0.0f ; angle<Math::PI*2.0f ; angle+=ia )
4229             {
4230                 Math::Point p;
4231                 p.x = center.x+radius;
4232                 p.y = center.z;
4233                 p = Math::RotatePoint(Math::Point(center.x, center.z), angle, p);
4234                 Math::Vector pos;
4235                 pos.x = p.x;
4236                 pos.z = p.y;
4237                 pos.y = 0.0f;
4238                 m_terrain->AdjustToFloor(pos, true);
4239                 float dist = SearchNearestObject(m_objMan.get(), pos, exclu);
4240                 if (dist >= space)
4241                 {
4242                     float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f);
4243                     if (flat >= dist/2.0f)
4244                     {
4245                         flat = m_terrain->GetFlatZoneRadius(pos, minFlat);
4246                         if(flat >= minFlat)
4247                         {
4248                             center = pos;
4249                             return true;
4250                         }
4251                     }
4252                 }
4253             }
4254         }
4255     }
4256     return false;
4257 }
4258 
4259 //! Calculates the maximum radius of a free space
GetFlatZoneRadius(Math::Vector center,float maxRadius,CObject * exclu)4260 float CRobotMain::GetFlatZoneRadius(Math::Vector center, float maxRadius,
4261                                     CObject *exclu)
4262 {
4263     float dist = SearchNearestObject(m_objMan.get(), center, exclu);
4264     if (dist == 0.0f) return 0.0f;
4265     if (dist < maxRadius)
4266         maxRadius = dist;
4267 
4268     return m_terrain->GetFlatZoneRadius(center, maxRadius);
4269 }
4270 
4271 
4272 //! Hides buildable area when a cube of metal is taken up
HideDropZone(CObject * metal)4273 void CRobotMain::HideDropZone(CObject* metal)
4274 {
4275     if (m_showLimit[1].used         &&
4276         m_showLimit[1].link == metal)
4277     {
4278         FlushShowLimit(1);
4279     }
4280 
4281     if (m_showLimit[2].used         &&
4282         m_showLimit[2].link == metal)
4283     {
4284         FlushShowLimit(2);
4285     }
4286 }
4287 
4288 //! Shows the buildable area when a cube of metal is deposited
ShowDropZone(CObject * metal,CObject * transporter)4289 void CRobotMain::ShowDropZone(CObject* metal, CObject* transporter)
4290 {
4291     if (metal == nullptr) return;
4292 
4293     Math::Vector center = metal->GetPosition();
4294 
4295     // Calculates the maximum radius possible depending on other items.
4296     float oMax = 30.0f;  // radius to build the biggest building
4297     float tMax;
4298     for (CObject* obj : m_objMan->GetAllObjects())
4299     {
4300         if (!obj->GetDetectable()) continue;  // inactive?
4301         if (IsObjectBeingTransported(obj)) continue;
4302 
4303         if (obj == metal) continue;
4304         if (obj == transporter) continue;
4305 
4306         Math::Vector oPos;
4307 
4308         ObjectType type = obj->GetType();
4309         if (type == OBJECT_BASE)
4310         {
4311             oPos = obj->GetPosition();
4312             float dist = Math::Distance(center, oPos)-80.0f;
4313             oMax = Math::Min(oMax, dist);
4314         }
4315         else
4316         {
4317             for (const auto& crashSphere : obj->GetAllCrashSpheres())
4318             {
4319                 float dist = Math::Distance(center, crashSphere.sphere.pos)-crashSphere.sphere.radius;
4320                 oMax = Math::Min(oMax, dist);
4321             }
4322         }
4323 
4324         if ( type == OBJECT_DERRICK  ||
4325              type == OBJECT_FACTORY  ||
4326              type == OBJECT_STATION  ||
4327              type == OBJECT_CONVERT  ||
4328              type == OBJECT_REPAIR   ||
4329              type == OBJECT_DESTROYER||
4330              type == OBJECT_TOWER    ||
4331              type == OBJECT_RESEARCH ||
4332              type == OBJECT_RADAR    ||
4333              type == OBJECT_ENERGY   ||
4334              type == OBJECT_LABO     ||
4335              type == OBJECT_NUCLEAR  ||
4336              type == OBJECT_START    ||
4337              type == OBJECT_END      ||
4338              type == OBJECT_INFO     ||
4339              type == OBJECT_PARA     ||
4340              type == OBJECT_SAFE     ||
4341              type == OBJECT_HUSTON   )  // building?
4342         {
4343             for (const auto& crashSphere : obj->GetAllCrashSpheres())
4344             {
4345                 float dist = Math::Distance(center, crashSphere.sphere.pos)-crashSphere.sphere.radius-BUILDMARGIN;
4346                 oMax = Math::Min(oMax, dist);
4347             }
4348         }
4349     }
4350 
4351     // Calculates the maximum possible radius depending on terrain.
4352     if (oMax >= 2.0f)
4353         tMax = m_terrain->GetFlatZoneRadius(center, 30.0f);
4354     else
4355         tMax = 0.0f;
4356 
4357     float radius = Math::Min(oMax, tMax);
4358     if (radius >= 2.0f)
4359         SetShowLimit(1, Gfx::PARTILIMIT2, metal, center, radius, 10.0f);
4360 }
4361 
4362 //! Erases the boundaries shown
FlushShowLimit(int i)4363 void CRobotMain::FlushShowLimit(int i)
4364 {
4365     for (int j = 0; j < m_showLimit[i].total; j++)
4366     {
4367         if (m_showLimit[i].parti[j] == 0) continue;
4368 
4369         m_particle->DeleteParticle(m_showLimit[i].parti[j]);
4370         m_showLimit[i].parti[j] = 0;
4371     }
4372 
4373     m_showLimit[i].total = 0;
4374     m_showLimit[i].link = nullptr;
4375     m_showLimit[i].used = false;
4376 }
4377 
4378 //! Specifies the boundaries to show
SetShowLimit(int i,Gfx::ParticleType parti,CObject * obj,Math::Vector pos,float radius,float duration)4379 void CRobotMain::SetShowLimit(int i, Gfx::ParticleType parti, CObject *obj,
4380                               Math::Vector pos, float radius, float duration)
4381 {
4382     FlushShowLimit(i);  // erases the current boundaries
4383 
4384     if (radius <= 0.0f) return;
4385 
4386     Math::Point dim;
4387     float dist;
4388     if (radius <= 50.0f)
4389     {
4390         dim = Math::Point(0.3f, 0.3f);
4391         dist = 2.5f;
4392     }
4393     else
4394     {
4395         dim = Math::Point(1.5f, 1.5f);
4396         dist = 10.0f;
4397     }
4398 
4399     m_showLimit[i].used = true;
4400     m_showLimit[i].link = obj;
4401     m_showLimit[i].pos = pos;
4402     m_showLimit[i].radius = radius;
4403     m_showLimit[i].duration = duration;
4404     m_showLimit[i].total = static_cast<int>((radius*2.0f*Math::PI)/dist);
4405     if (m_showLimit[i].total > MAXSHOWPARTI) m_showLimit[i].total = MAXSHOWPARTI;
4406     m_showLimit[i].time = 0.0f;
4407 
4408     for (int j = 0; j < m_showLimit[i].total; j++)
4409     {
4410         m_showLimit[i].parti[j] = m_particle->CreateParticle(pos, Math::Vector(0.0f, 0.0f, 0.0f), dim, parti, duration);
4411     }
4412 }
4413 
4414 //! Mount the boundaries of the selected object
StartShowLimit()4415 void CRobotMain::StartShowLimit()
4416 {
4417     CObject* obj = GetSelect();
4418     if (obj == nullptr) return;
4419     if (!obj->Implements(ObjectInterfaceType::Ranged)) return;
4420     float range = dynamic_cast<CRangedObject&>(*obj).GetShowLimitRadius();
4421     if (range == 0.0f) return;
4422     SetShowLimit(0, Gfx::PARTILIMIT1, obj, obj->GetPosition(), range);
4423 }
4424 
4425 //! Advances the boundaries shown
FrameShowLimit(float rTime)4426 void CRobotMain::FrameShowLimit(float rTime)
4427 {
4428     if (m_engine->GetPause()) return;
4429 
4430     for (int i = 0; i < MAXSHOWLIMIT; i++)
4431     {
4432         if (!m_showLimit[i].used) continue;
4433 
4434         m_showLimit[i].time += rTime;
4435 
4436         if (m_showLimit[i].time >= m_showLimit[i].duration)
4437         {
4438             FlushShowLimit(i);
4439             continue;
4440         }
4441 
4442         float factor;
4443         if (m_showLimit[i].time < 1.0f)
4444             factor = m_showLimit[i].time;
4445         else if (m_showLimit[i].time > m_showLimit[i].duration-1.0f)
4446             factor = m_showLimit[i].duration-m_showLimit[i].time;
4447         else
4448             factor = 1.0f;
4449 
4450         float speed = 0.4f-m_showLimit[i].radius*0.001f;
4451         if (speed < 0.1f) speed = 0.1f;
4452         float angle = m_showLimit[i].time*speed;
4453 
4454         if (m_showLimit[i].link != nullptr)
4455         {
4456             m_showLimit[i].pos = m_showLimit[i].link->GetPosition();
4457         }
4458 
4459         for (int j = 0; j < m_showLimit[i].total; j++)
4460         {
4461             if (m_showLimit[i].parti[j] == 0) continue;
4462 
4463             Math::Point center;
4464             center.x = m_showLimit[i].pos.x;
4465             center.y = m_showLimit[i].pos.z;
4466             Math::Point rotate;
4467             rotate.x = center.x+m_showLimit[i].radius*factor;
4468             rotate.y = center.y;
4469             rotate = Math::RotatePoint(center, angle, rotate);
4470 
4471             Math::Vector pos;
4472             pos.x = rotate.x;
4473             pos.z = rotate.y;
4474             pos.y = 0.0f;
4475             m_terrain->AdjustToFloor(pos, true);
4476             if (m_showLimit[i].radius <= 50.0f) pos.y += 0.5f;
4477             else                                pos.y += 2.0f;
4478             m_particle->SetPosition(m_showLimit[i].parti[j], pos);
4479 
4480             angle += (2.0f*Math::PI)/m_showLimit[i].total;
4481         }
4482     }
4483 }
4484 
4485 //! Saves all programs of all the robots
SaveAllScript()4486 void CRobotMain::SaveAllScript()
4487 {
4488     for (CObject* obj : m_objMan->GetAllObjects())
4489     {
4490         SaveOneScript(obj);
4491     }
4492 }
4493 
4494 //! Saves all programs of the robot.
SaveOneScript(CObject * obj)4495 void CRobotMain::SaveOneScript(CObject *obj)
4496 {
4497     if (! obj->Implements(ObjectInterfaceType::ProgramStorage)) return;
4498 
4499     CProgramStorageObject* programStorage = dynamic_cast<CProgramStorageObject*>(obj);
4500 
4501     char categoryChar = GetLevelCategoryDir(m_levelCategory)[0];
4502     programStorage->SaveAllUserPrograms(m_playerProfile->GetSaveFile(StrUtils::Format("%c%.3d%.3d", categoryChar, m_levelChap, m_levelRank)));
4503 }
4504 
4505 //! Saves the stack of the program in execution of a robot
SaveFileStack(CObject * obj,std::ostream & ostr)4506 bool CRobotMain::SaveFileStack(CObject *obj, std::ostream &ostr)
4507 {
4508     if (! obj->Implements(ObjectInterfaceType::Programmable)) return true;
4509 
4510     CProgrammableObject* programmable = dynamic_cast<CProgrammableObject*>(obj);
4511 
4512     ObjectType type = obj->GetType();
4513     if (type == OBJECT_HUMAN) return true;
4514 
4515     long status = 1;
4516     std::stringstream sstr("");
4517 
4518     if (!programmable->WriteStack(sstr))
4519     {
4520         GetLogger()->Error("WriteStack failed at object id = %i\n", obj->GetID());
4521         status = 100; // marked bad
4522     }
4523 
4524     if (!CBot::WriteLong(ostr, status)) return false;
4525     if (!CBot::WriteStream(ostr, sstr)) return false;
4526 
4527     return true;
4528 }
4529 
4530 //! Resumes the execution stack of the program in a robot
ReadFileStack(CObject * obj,std::istream & istr)4531 bool CRobotMain::ReadFileStack(CObject *obj, std::istream &istr)
4532 {
4533     if (! obj->Implements(ObjectInterfaceType::Programmable)) return true;
4534 
4535     CProgrammableObject* programmable = dynamic_cast<CProgrammableObject*>(obj);
4536 
4537     ObjectType type = obj->GetType();
4538     if (type == OBJECT_HUMAN) return true;
4539 
4540     long status;
4541     if (!CBot::ReadLong(istr, status)) return false;
4542 
4543     if (status == 100) // was marked bad ?
4544     {
4545         if (!CBot::ReadLong(istr, status)) return false;
4546         if (!istr.seekg(status, istr.cur)) return false;
4547         return true; // next program
4548     }
4549 
4550     if (status == 1)
4551     {
4552         std::stringstream sstr("");
4553         if (!CBot::ReadStream(istr, sstr)) return false;
4554 
4555         if (!programmable->ReadStack(sstr))
4556         {
4557             GetLogger()->Error("ReadStack failed at object id = %i\n", obj->GetID());
4558         }
4559         return true; // next program
4560     }
4561 
4562     return false; // error: status == ??
4563 }
4564 
GetNewScriptNames(ObjectType type)4565 std::vector<std::string> CRobotMain::GetNewScriptNames(ObjectType type)
4566 {
4567     std::vector<std::string> names;
4568     for (const auto& newScript : m_newScriptName)
4569     {
4570         if (newScript.type == type        ||
4571             newScript.type == OBJECT_NULL  )
4572         {
4573             names.push_back(newScript.name);
4574         }
4575     }
4576 
4577     return names;
4578 }
4579 
4580 
4581 //! Seeks if an object occupies in a spot, to prevent a backup of the game
IOIsBusy()4582 bool CRobotMain::IOIsBusy()
4583 {
4584     if (CScriptFunctions::CheckOpenFiles()) return true;
4585 
4586     for (CObject* obj : m_objMan->GetAllObjects())
4587     {
4588         if (! obj->Implements(ObjectInterfaceType::TaskExecutor)) continue;
4589 
4590         if (obj->Implements(ObjectInterfaceType::Programmable) && dynamic_cast<CProgrammableObject&>(*obj).IsProgram()) continue; // TODO: I'm not sure if this is correct but this is how it worked earlier
4591         if (dynamic_cast<CTaskExecutorObject&>(*obj).IsForegroundTask()) return true;
4592     }
4593     return false;
4594 }
4595 
4596 //! Writes an object into the backup file
IOWriteObject(CLevelParserLine * line,CObject * obj,const std::string & programDir,int objRank)4597 void CRobotMain::IOWriteObject(CLevelParserLine* line, CObject* obj, const std::string& programDir, int objRank)
4598 {
4599     line->AddParam("type", MakeUnique<CLevelParserParam>(obj->GetType()));
4600     line->AddParam("id", MakeUnique<CLevelParserParam>(obj->GetID()));
4601     line->AddParam("pos", MakeUnique<CLevelParserParam>(obj->GetPosition()/g_unit));
4602     line->AddParam("angle", MakeUnique<CLevelParserParam>(obj->GetRotation() * Math::RAD_TO_DEG));
4603     line->AddParam("zoom", MakeUnique<CLevelParserParam>(obj->GetScale()));
4604 
4605     if (obj->Implements(ObjectInterfaceType::Old))
4606     {
4607         line->AddParam("option", MakeUnique<CLevelParserParam>(obj->GetOption()));
4608     }
4609 
4610     if (obj->Implements(ObjectInterfaceType::Controllable))
4611     {
4612         auto controllableObj = dynamic_cast<CControllableObject*>(obj);
4613         line->AddParam("trainer", MakeUnique<CLevelParserParam>(controllableObj->GetTrainer()));
4614         if (controllableObj->GetSelect())
4615             line->AddParam("select", MakeUnique<CLevelParserParam>(true));
4616     }
4617 
4618     obj->Write(line);
4619 
4620     if (obj->GetType() == OBJECT_BASE)
4621         line->AddParam("run", MakeUnique<CLevelParserParam>(3));  // stops and open (PARAM_FIXSCENE)
4622 
4623 
4624     if (obj->Implements(ObjectInterfaceType::ProgramStorage))
4625     {
4626         CProgramStorageObject* programStorage = dynamic_cast<CProgramStorageObject*>(obj);
4627         if (programStorage->GetProgramStorageIndex() >= 0)
4628         {
4629             programStorage->SaveAllProgramsForSavedScene(line, programDir);
4630         }
4631         else
4632         {
4633             // Probably an object created after the scene started, not loaded from level file
4634             // This means it doesn't normally store programs so it doesn't have program storage id assigned
4635             programStorage->SetProgramStorageIndex(999-objRank); // Set something that won't collide with normal programs
4636             programStorage->SaveAllProgramsForSavedScene(line, programDir);
4637             programStorage->SetProgramStorageIndex(-1); // Disable again
4638         }
4639 
4640         if (obj->Implements(ObjectInterfaceType::Programmable))
4641         {
4642             int run = dynamic_cast<CProgramStorageObject&>(*obj).GetProgramIndex(dynamic_cast<CProgrammableObject&>(*obj).GetCurrentProgram());
4643             if (run != -1)
4644             {
4645                 line->AddParam("run", MakeUnique<CLevelParserParam>(run+1));
4646             }
4647         }
4648     }
4649 }
4650 
4651 //! Saves the current game
IOWriteScene(std::string filename,std::string filecbot,std::string filescreenshot,const std::string & info,bool emergencySave)4652 bool CRobotMain::IOWriteScene(std::string filename, std::string filecbot, std::string filescreenshot, const std::string& info, bool emergencySave)
4653 {
4654     if (!emergencySave)
4655     {
4656         // Render the indicator to show that we are working
4657         ShowSaveIndicator(true);
4658         m_app->Render(); // update
4659     }
4660 
4661     std::string dirname = filename.substr(0, filename.find_last_of("/"));
4662 
4663     CLevelParser levelParser(filename);
4664     CLevelParserLineUPtr line;
4665 
4666     line = MakeUnique<CLevelParserLine>("Title");
4667     line->AddParam("text", MakeUnique<CLevelParserParam>(std::string(info)));
4668     levelParser.AddLine(std::move(line));
4669 
4670 
4671     //TODO: Do we need that? It's not used anyway
4672     line = MakeUnique<CLevelParserLine>("Version");
4673     line->AddParam("maj", MakeUnique<CLevelParserParam>(0));
4674     line->AddParam("min", MakeUnique<CLevelParserParam>(1));
4675     levelParser.AddLine(std::move(line));
4676 
4677 
4678     line = MakeUnique<CLevelParserLine>("Created");
4679     line->AddParam("date", MakeUnique<CLevelParserParam>(static_cast<int>(time(nullptr))));
4680     levelParser.AddLine(std::move(line));
4681 
4682     line = MakeUnique<CLevelParserLine>("Mission");
4683     line->AddParam("base", MakeUnique<CLevelParserParam>(GetLevelCategoryDir(m_levelCategory)));
4684     if (m_levelCategory == LevelCategory::CustomLevels)
4685         line->AddParam("dir", MakeUnique<CLevelParserParam>(GetCustomLevelDir()));
4686     else
4687         line->AddParam("chap", MakeUnique<CLevelParserParam>(m_levelChap));
4688     line->AddParam("rank", MakeUnique<CLevelParserParam>(m_levelRank));
4689     line->AddParam("gametime", MakeUnique<CLevelParserParam>(GetGameTime()));
4690     levelParser.AddLine(std::move(line));
4691 
4692     line = MakeUnique<CLevelParserLine>("Map");
4693     line->AddParam("zoom", MakeUnique<CLevelParserParam>(m_map->GetZoomMap()));
4694     levelParser.AddLine(std::move(line));
4695 
4696     line = MakeUnique<CLevelParserLine>("DoneResearch");
4697     line->AddParam("bits", MakeUnique<CLevelParserParam>(static_cast<int>(m_researchDone[0])));
4698     levelParser.AddLine(std::move(line));
4699 
4700     float sleep, delay, magnetic, progress;
4701     if (m_lightning->GetStatus(sleep, delay, magnetic, progress))
4702     {
4703         line = MakeUnique<CLevelParserLine>("BlitzMode");
4704         line->AddParam("sleep", MakeUnique<CLevelParserParam>(sleep));
4705         line->AddParam("delay", MakeUnique<CLevelParserParam>(delay));
4706         line->AddParam("magnetic", MakeUnique<CLevelParserParam>(magnetic/g_unit));
4707         line->AddParam("progress", MakeUnique<CLevelParserParam>(progress));
4708         levelParser.AddLine(std::move(line));
4709     }
4710 
4711 
4712     int objRank = 0;
4713     for (CObject* obj : m_objMan->GetAllObjects())
4714     {
4715         if (obj->GetType() == OBJECT_TOTO) continue;
4716         if (IsObjectBeingTransported(obj)) continue;
4717         if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast<CDestroyableObject&>(*obj).IsDying()) continue;
4718 
4719         if (obj->Implements(ObjectInterfaceType::Carrier))
4720         {
4721             CObject* cargo = dynamic_cast<CCarrierObject&>(*obj).GetCargo();
4722             if (cargo != nullptr)  // object transported?
4723             {
4724                 line = MakeUnique<CLevelParserLine>("CreateFret");
4725                 IOWriteObject(line.get(), cargo, dirname, objRank++);
4726                 levelParser.AddLine(std::move(line));
4727             }
4728         }
4729 
4730         if (obj->Implements(ObjectInterfaceType::Powered))
4731         {
4732             CObject* power = dynamic_cast<CPoweredObject&>(*obj).GetPower();
4733             if (power != nullptr) // battery transported?
4734             {
4735                 line = MakeUnique<CLevelParserLine>("CreatePower");
4736                 IOWriteObject(line.get(), power, dirname, objRank++);
4737                 levelParser.AddLine(std::move(line));
4738             }
4739         }
4740 
4741 
4742         line = MakeUnique<CLevelParserLine>("CreateObject");
4743         IOWriteObject(line.get(), obj, dirname, objRank++);
4744         levelParser.AddLine(std::move(line));
4745     }
4746     try
4747     {
4748         levelParser.Save();
4749     }
4750     catch (CLevelParserException& e)
4751     {
4752         GetLogger()->Error("Failed to save level state - %s\n", e.what()); // TODO add visual error to notify user that save failed
4753         return false;
4754     }
4755 
4756     // Writes the file of stacks of execution.
4757     COutputStream ostr(filecbot);
4758     if (!ostr.is_open()) return false;
4759 
4760     bool bError = false;
4761     long version = 1;
4762     CBot::WriteLong(ostr, version);                 // version of COLOBOT
4763     version = CBot::CBotProgram::GetVersion();
4764     CBot::WriteLong(ostr, version);                 // version of CBOT
4765     CBot::WriteWord(ostr, 0); // TODO
4766 
4767     for (CObject* obj : m_objMan->GetAllObjects())
4768     {
4769         if (obj->GetType() == OBJECT_TOTO) continue;
4770         if (IsObjectBeingTransported(obj)) continue;
4771         if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast<CDestroyableObject&>(*obj).IsDying()) continue;
4772 
4773         if (!SaveFileStack(obj, ostr))
4774         {
4775             GetLogger()->Error("SaveFileStack failed at object id = %i\n", obj->GetID());
4776             bError = true;
4777             break;
4778         }
4779     }
4780 
4781     if (!bError && !CBot::CBotClass::SaveStaticState(ostr))
4782     {
4783         GetLogger()->Error("CBotClass save static state failed\n");
4784     }
4785 
4786     ostr.close();
4787 
4788     if (!emergencySave)
4789     {
4790         ShowSaveIndicator(false); // force hide for screenshot
4791         MouseMode oldMouseMode = m_app->GetMouseMode();
4792         m_app->SetMouseMode(MOUSE_NONE); // disable the mouse
4793         m_displayText->HideText(true); // hide
4794         m_engine->SetScreenshotMode(true);
4795 
4796         m_engine->Render(); // update (but don't show, we're not swapping buffers here!)
4797         m_engine->WriteScreenShot(filescreenshot);
4798         m_shotSaving++;
4799 
4800         m_engine->SetScreenshotMode(false);
4801         m_displayText->HideText(false);
4802         m_app->SetMouseMode(oldMouseMode);
4803 
4804         m_app->ResetTimeAfterLoading();
4805     }
4806     return true;
4807 }
4808 
4809 //! Notifies the user that scene write is finished
IOWriteSceneFinished()4810 void CRobotMain::IOWriteSceneFinished()
4811 {
4812     m_displayText->DisplayError(INFO_WRITEOK, Math::Vector(0.0f,0.0f,0.0f));
4813     m_shotSaving--;
4814 }
4815 
4816 //! Resumes the game
IOReadObject(CLevelParserLine * line,const std::string & programDir,const std::string & objCounterText,float objectProgress,int objRank)4817 CObject* CRobotMain::IOReadObject(CLevelParserLine *line, const std::string& programDir, const std::string& objCounterText, float objectProgress, int objRank)
4818 {
4819     ObjectCreateParams params = CObject::ReadCreateParams(line);
4820     params.power = -1.0f;
4821     params.id = line->GetParam("id")->AsInt();
4822 
4823     std::string details = objCounterText;
4824     #if DEV_BUILD
4825     // Object categories may spoil the level a bit, so hide them in release builds
4826     details += ": "+CLevelParserParam::FromObjectType(params.type);
4827     #endif
4828     m_ui->GetLoadingScreen()->SetProgress(0.25f+objectProgress*0.7f, RT_LOADING_OBJECTS_SAVED, details);
4829 
4830     CObject* obj = m_objMan->CreateObject(params);
4831 
4832     if (obj->Implements(ObjectInterfaceType::Old))
4833     {
4834         COldObject* oldObj = dynamic_cast<COldObject*>(obj);
4835         oldObj->SetPosition(line->GetParam("pos")->AsPoint() * g_unit);
4836         oldObj->SetRotation(line->GetParam("angle")->AsPoint() * Math::DEG_TO_RAD);
4837     }
4838 
4839     if (obj->GetType() == OBJECT_BASE) m_base = obj;
4840 
4841     obj->Read(line);
4842 
4843     int run = line->GetParam("run")->AsInt(-1);
4844     if (run != -1)
4845     {
4846         CAuto* automat = obj->GetAuto();
4847         if (automat != nullptr)
4848             automat->Start(run);  // starts the film
4849     }
4850 
4851     if (obj->Implements(ObjectInterfaceType::ProgramStorage))
4852     {
4853         CProgramStorageObject* programStorage = dynamic_cast<CProgramStorageObject*>(obj);
4854         if (!line->GetParam("programStorageIndex")->IsDefined()) // Backwards compatibility
4855             programStorage->SetProgramStorageIndex(objRank);
4856         programStorage->LoadAllProgramsForSavedScene(line, programDir);
4857     }
4858 
4859     return obj;
4860 }
4861 
4862 //! Resumes some part of the game
IOReadScene(std::string filename,std::string filecbot)4863 CObject* CRobotMain::IOReadScene(std::string filename, std::string filecbot)
4864 {
4865     std::string dirname = filename.substr(0, filename.find_last_of("/"));
4866 
4867     CLevelParser levelParser(filename);
4868     levelParser.SetLevelPaths(m_levelCategory, m_levelChap, m_levelRank);
4869     levelParser.Load();
4870     int numObjects = levelParser.CountLines("CreateObject") + levelParser.CountLines("CreatePower") + levelParser.CountLines("CreateFret");
4871 
4872     m_base = nullptr;
4873 
4874     CObject* cargo   = nullptr;
4875     CObject* power  = nullptr;
4876     CObject* sel    = nullptr;
4877     int objRank = 0;
4878     int objCounter = 0;
4879     for (auto& line : levelParser.GetLines())
4880     {
4881         if (line->GetCommand() == "Mission")
4882             m_gameTime = line->GetParam("gametime")->AsFloat(0.0f);
4883 
4884         if (line->GetCommand() == "Map")
4885             m_map->ZoomMap(line->GetParam("zoom")->AsFloat());
4886 
4887         if (line->GetCommand() == "DoneResearch")
4888             m_researchDone[0] = line->GetParam("bits")->AsInt();
4889 
4890         if (line->GetCommand() == "BlitzMode")
4891         {
4892             float sleep = line->GetParam("sleep")->AsFloat();
4893             float delay = line->GetParam("delay")->AsFloat();
4894             float magnetic = line->GetParam("magnetic")->AsFloat()*g_unit;
4895             float progress = line->GetParam("progress")->AsFloat();
4896             m_lightning->SetStatus(sleep, delay, magnetic, progress);
4897         }
4898 
4899         if (line->GetCommand() == "CreateFret")
4900         {
4901             cargo = IOReadObject(line.get(), dirname, StrUtils::ToString<int>(objCounter+1)+" / "+StrUtils::ToString<int>(numObjects), static_cast<float>(objCounter) / static_cast<float>(numObjects));
4902             objCounter++;
4903         }
4904 
4905         if (line->GetCommand() == "CreatePower")
4906         {
4907             power = IOReadObject(line.get(), dirname, StrUtils::ToString<int>(objCounter+1)+" / "+StrUtils::ToString<int>(numObjects), static_cast<float>(objCounter) / static_cast<float>(numObjects));
4908             objCounter++;
4909         }
4910 
4911         if (line->GetCommand() == "CreateObject")
4912         {
4913             CObject* obj = IOReadObject(line.get(), dirname, StrUtils::ToString<int>(objCounter+1)+" / "+StrUtils::ToString<int>(numObjects), static_cast<float>(objCounter) / static_cast<float>(numObjects), objRank++);
4914 
4915             if (line->GetParam("select")->AsBool(false))
4916                 sel = obj;
4917 
4918             if (cargo != nullptr)
4919             {
4920                 assert(obj->Implements(ObjectInterfaceType::Carrier)); // TODO: exception?
4921                 assert(obj->Implements(ObjectInterfaceType::Old));
4922                 dynamic_cast<CCarrierObject&>(*obj).SetCargo(cargo);
4923                 auto task = MakeUnique<CTaskManip>(dynamic_cast<COldObject*>(obj));
4924                 task->Start(TMO_AUTO, TMA_GRAB);  // holds the object!
4925             }
4926 
4927             if (power != nullptr)
4928             {
4929                 assert(obj->Implements(ObjectInterfaceType::Powered));
4930                 dynamic_cast<CPoweredObject&>(*obj).SetPower(power);
4931                 assert(power->Implements(ObjectInterfaceType::Transportable));
4932                 dynamic_cast<CTransportableObject&>(*power).SetTransporter(obj);
4933             }
4934             cargo = nullptr;
4935             power = nullptr;
4936 
4937             objCounter++;
4938         }
4939     }
4940 
4941     m_ui->GetLoadingScreen()->SetProgress(0.95f, RT_LOADING_CBOT_SAVE);
4942 
4943     // Reads the file of stacks of execution.
4944     CInputStream istr(filecbot);
4945 
4946     if (istr.is_open())
4947     {
4948         bool bError = false;
4949         long version = 0;
4950         CBot::ReadLong(istr, version);             // version of COLOBOT
4951         if (version == 1)
4952         {
4953             CBot::ReadLong(istr, version);         // version of CBOT
4954             if (version == CBot::CBotProgram::GetVersion())
4955             {
4956                 unsigned short flag;
4957                 CBot::ReadWord(istr, flag); // TODO
4958                 bError = (flag != 0);
4959 
4960                 if (!bError) for (CObject* obj : m_objMan->GetAllObjects())
4961                 {
4962                     if (obj->GetType() == OBJECT_TOTO) continue;
4963                     if (IsObjectBeingTransported(obj)) continue;
4964                     if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast<CDestroyableObject&>(*obj).IsDying()) continue;
4965 
4966                     if (!ReadFileStack(obj, istr))
4967                     {
4968                         GetLogger()->Error("ReadFileStack failed at object id = %i\n", obj->GetID());
4969                         bError = true;
4970                         break;
4971                     }
4972                 }
4973 
4974                 if (!bError && !CBot::CBotClass::RestoreStaticState(istr))
4975                 {
4976                     GetLogger()->Error("CBotClass restore static state failed\n");
4977                     bError = true;
4978                 }
4979             }
4980             else
4981                 GetLogger()->Error("cbot.run file is wrong version: %i\n", version);
4982         }
4983 
4984         if (bError) GetLogger()->Error("Restoring CBOT state failed at stream position: %li\n", istr.tellg());
4985         istr.close();
4986     }
4987 
4988     m_ui->GetLoadingScreen()->SetProgress(1.0f, RT_LOADING_FINISHED);
4989 
4990     return sel;
4991 }
4992 
4993 
4994 //! Changes current player
SelectPlayer(std::string playerName)4995 void CRobotMain::SelectPlayer(std::string playerName)
4996 {
4997     assert(!playerName.empty());
4998 
4999     m_playerProfile = MakeUnique<CPlayerProfile>(playerName);
5000     SetGlobalGamerName(playerName);
5001 }
5002 
GetPlayerProfile()5003 CPlayerProfile* CRobotMain::GetPlayerProfile()
5004 {
5005     return m_playerProfile.get();
5006 }
5007 
5008 
5009 //! Resets all objects to their original position
ResetObject()5010 void CRobotMain::ResetObject()
5011 {
5012     // schedule reset during next frame
5013     m_resetCreate = true;
5014 }
5015 
5016 //! Resets all objects to their original position
ResetCreate()5017 void CRobotMain::ResetCreate()
5018 {
5019     SaveAllScript();
5020 
5021     // Removes all bullets in progress.
5022     m_particle->DeleteParticle(Gfx::PARTIGUN1);
5023     m_particle->DeleteParticle(Gfx::PARTIGUN2);
5024     m_particle->DeleteParticle(Gfx::PARTIGUN3);
5025     m_particle->DeleteParticle(Gfx::PARTIGUN4);
5026 
5027     DeselectAll();  // removes the control buttons
5028     DeleteAllObjects();  // removes all the current 3D Scene
5029 
5030     m_particle->FlushParticle();
5031     m_terrain->FlushBuildingLevel();
5032 
5033     m_camera->SetType(Gfx::CAM_TYPE_NULL);
5034 
5035     try
5036     {
5037         CreateScene(m_ui->GetSceneSoluce(), false, true);
5038 
5039         for (CObject* obj : m_objMan->GetAllObjects())
5040         {
5041             if (obj->GetAnimateOnReset())
5042             {
5043                 m_engine->GetPyroManager()->Create(Gfx::PT_RESET, obj);
5044             }
5045         }
5046     }
5047     catch (const std::runtime_error& e)
5048     {
5049         LevelLoadingError("An error occurred while trying to reset scene", e);
5050     }
5051 }
5052 
5053 //! Updates the audiotracks
UpdateAudio(bool frame)5054 void CRobotMain::UpdateAudio(bool frame)
5055 {
5056     for(std::unique_ptr<CAudioChangeCondition>& audioChange : m_audioChange)
5057     {
5058         if (audioChange->changed) continue;
5059 
5060         if (audioChange->Check())
5061         {
5062             GetLogger()->Info("Changing music to \"%s\"\n", audioChange->music.c_str());
5063             m_sound->PlayMusic(audioChange->music, audioChange->repeat);
5064             audioChange->changed = true;
5065         }
5066     }
5067 }
5068 
5069 //! Set mission result from LevelController script
SetMissionResultFromScript(Error result,float delay)5070 void CRobotMain::SetMissionResultFromScript(Error result, float delay)
5071 {
5072     m_endTakeWinDelay = delay;
5073     m_endTakeLostDelay = delay;
5074     m_missionResult = result;
5075     m_missionResultFromScript = true;
5076 }
5077 
ProcessEndMissionTakeForGroup(std::vector<CSceneEndCondition * > & endTakes)5078 Error CRobotMain::ProcessEndMissionTakeForGroup(std::vector<CSceneEndCondition*>& endTakes)
5079 {
5080     Error finalResult = ERR_OK;
5081     bool hasWinningConditions = false;
5082     for (CSceneEndCondition* endTake : endTakes)
5083     {
5084         Error result = endTake->GetMissionResult();
5085         if (endTake->lost < 0)
5086             hasWinningConditions = true;
5087 
5088         if (result == ERR_OK && endTake->immediat)
5089         {
5090             hasWinningConditions = true;
5091             finalResult = result;
5092             break;
5093         }
5094 
5095         if (result != ERR_OK)
5096         {
5097             finalResult = result;
5098             break;
5099         }
5100     }
5101     if (finalResult == ERR_OK && !hasWinningConditions) finalResult = ERR_MISSION_NOTERM; // Never end mission without ending conditions
5102     return finalResult;
5103 }
5104 
5105 //! Process EndMissionTake commands, result is stored in m_missionResult
5106 //! If return value is different than ERR_MISSION_NOTERM, assume the mission is finished and pass on the result
ProcessEndMissionTake()5107 Error CRobotMain::ProcessEndMissionTake()
5108 {
5109     bool timeout = false;
5110     if (m_missionResult != INFO_LOST && m_missionResult != INFO_LOSTq)
5111     {
5112         if (m_endTakeTimeout >= 0.0f)
5113         {
5114             // Use the mission timer if available, or global mission time otherwise
5115             // Useful for exercises where the time starts when you start the program, not the mission itself
5116             float currentTime = m_missionTimerEnabled ? m_missionTimer : m_gameTime;
5117             if (currentTime > m_endTakeTimeout)
5118             {
5119                 m_missionResult = INFO_LOST;
5120                 timeout = true;
5121             }
5122         }
5123     }
5124 
5125     // Sort end conditions by teams
5126     std::map<int, std::vector<CSceneEndCondition*>> teamsEndTake;
5127     for (std::unique_ptr<CSceneEndCondition>& endTake : m_endTake)
5128         teamsEndTake[endTake->winTeam].push_back(endTake.get());
5129 
5130     // This is just a smart way to check if we have any map values other than 0 defined
5131     bool usesTeamConditions = teamsEndTake.size() > teamsEndTake.count(0);
5132 
5133     if (!usesTeamConditions)
5134     {
5135         if (!timeout)
5136             m_missionResult = ProcessEndMissionTakeForGroup(teamsEndTake[0]);
5137 
5138         if (m_missionResult != INFO_LOST && m_missionResult != INFO_LOSTq)
5139         {
5140             if (m_endTakeResearch != 0)
5141             {
5142                 if (m_endTakeResearch != (m_endTakeResearch&m_researchDone[0]))
5143                 {
5144                     m_missionResult = ERR_MISSION_NOTERM;
5145                 }
5146             }
5147         }
5148     }
5149     else
5150     {
5151         assert(m_endTakeResearch == 0); // TODO: Add support for per-team EndTakeResearch
5152 
5153         // Special handling for teams
5154         m_missionResult = ERR_MISSION_NOTERM;
5155 
5156         if (GetAllActiveTeams().empty() || timeout)
5157         {
5158             GetLogger()->Info("All teams died, mission ended\n");
5159             if (m_scoreboard)
5160             {
5161                 std::string title, text, details_line;
5162                 GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS, title);
5163                 if (m_missionTimerEnabled && m_missionTimerStarted)
5164                 {
5165                     GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS_TIME, text);
5166                     text = StrUtils::Format(text.c_str(), TimeFormat(m_missionTimer).c_str());
5167                 }
5168                 else
5169                 {
5170                     GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS_TEXT, text);
5171                 }
5172                 GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS_LINE, details_line);
5173                 std::string details = "";
5174                 for (std::pair<int, CScoreboard::Score> team : m_scoreboard->GetSortedScores())
5175                 {
5176                     if (!details.empty())
5177                         details += ", ";
5178                     details += StrUtils::Format(details_line.c_str(), GetTeamName(team.first).c_str(), team.second.points);
5179                 }
5180                 m_ui->GetDialog()->StartInformation(
5181                     title,
5182                     text,
5183                     details,
5184                     false, true,
5185                     [&]()
5186                     {
5187                         ChangePhase(PHASE_WIN);
5188                     }
5189                 );
5190                 m_endTakeWinDelay = 0.0f;
5191                 m_missionResult = ERR_OK;
5192             }
5193             else
5194             {
5195                 m_missionResult = INFO_LOST;
5196             }
5197         }
5198         else
5199         {
5200             for (auto it : teamsEndTake)
5201             {
5202                 int team = it.first;
5203                 if (team == 0) continue;
5204                 if (m_teamFinished[team]) continue;
5205 
5206                 Error result = ProcessEndMissionTakeForGroup(it.second);
5207                 if (result == INFO_LOST || result == INFO_LOSTq)
5208                 {
5209                     GetLogger()->Info("Team %d lost\n", team);
5210                     std::string text;
5211                     GetResource(RES_ERR, INFO_TEAM_DEAD, text);
5212                     text = StrUtils::Format(text.c_str(), GetTeamName(team).c_str());
5213                     m_displayText->DisplayText(text.c_str(), Math::Vector(0.0f,0.0f,0.0f), 15.0f, 60.0f, 10.0f, Ui::TT_ERROR);
5214 
5215                     m_displayText->SetEnable(false); // To prevent "bot destroyed" messages
5216                     m_objMan->DestroyTeam(team);
5217                     m_displayText->SetEnable(true);
5218 
5219                     m_teamFinished[team] = true;
5220                 }
5221                 else if (result == ERR_OK)
5222                 {
5223                     /*if (m_winDelay == 0.0f)
5224                     {
5225                         GetLogger()->Info("Team %d won\n", team);
5226 
5227                         m_displayText->DisplayText(("<<< Team "+boost::lexical_cast<std::string>(team)+" won the game >>>").c_str(), Math::Vector(0.0f,0.0f,0.0f));
5228                         if (m_missionTimerEnabled && m_missionTimerStarted)
5229                         {
5230                             GetLogger()->Info("Mission time: %s\n", TimeFormat(m_missionTimer).c_str());
5231                             m_displayText->DisplayText(("Time: " + TimeFormat(m_missionTimer)).c_str(), Math::Vector(0.0f,0.0f,0.0f));
5232                         }
5233                         m_missionTimerEnabled = m_missionTimerStarted = false;
5234                         m_winDelay  = m_endTakeWinDelay;  // wins in two seconds
5235                         m_lostDelay = 0.0f;
5236                         m_displayText->SetEnable(false);
5237                     }
5238                     m_missionResult = ERR_OK;
5239                     return ERR_OK;*/
5240                     GetLogger()->Info("Team %d finished\n", team);
5241                     std::string text;
5242                     GetResource(RES_ERR, INFO_TEAM_FINISH, text);
5243                     text = StrUtils::Format(text.c_str(), GetTeamName(team).c_str());
5244                     m_displayText->DisplayText(text.c_str(), Math::Vector(0.0f,0.0f,0.0f));
5245                     if (m_scoreboard)
5246                         m_scoreboard->ProcessEndTake(team);
5247                     m_objMan->DestroyTeam(team, DestructionType::Win);
5248                     m_teamFinished[team] = true;
5249                     if (m_endTakeTeamImmediateWin)
5250                     {
5251                         // All other teams fail
5252                         for(int other_team : GetAllActiveTeams())
5253                         {
5254                             m_displayText->SetEnable(false); // To prevent "bot destroyed" messages
5255                             m_objMan->DestroyTeam(other_team);
5256                             m_displayText->SetEnable(true);
5257 
5258                             m_teamFinished[other_team] = true;
5259                         }
5260                     }
5261                 }
5262             }
5263         }
5264     }
5265 
5266     return ERR_MISSION_NOTERM;
5267 }
5268 
5269 //! Checks if the mission is over
CheckEndMission(bool frame)5270 Error CRobotMain::CheckEndMission(bool frame)
5271 {
5272     // Process EndMissionTake, unless we are using LevelController script for processing ending conditions
5273     if (!m_missionResultFromScript)
5274     {
5275         Error result = ProcessEndMissionTake();
5276         if (result != ERR_MISSION_NOTERM) return result;
5277     }
5278     // Take action depending on m_missionResult
5279 
5280     if (m_missionResult == INFO_LOSTq)
5281     {
5282         if (m_lostDelay == 0.0f)
5283         {
5284             m_lostDelay = 0.1f;  // lost immediately
5285             m_winDelay  = 0.0f;
5286         }
5287         m_missionTimerEnabled = m_missionTimerStarted = false;
5288         m_displayText->SetEnable(false);
5289         return INFO_LOSTq;
5290     }
5291 
5292     if (m_missionResult == INFO_LOST)
5293     {
5294         if (m_lostDelay == 0.0f)
5295         {
5296             m_displayText->DisplayError(INFO_LOST, Math::Vector(0.0f,0.0f,0.0f));
5297             m_lostDelay = m_endTakeLostDelay;  // lost in 6 seconds
5298             m_winDelay  = 0.0f;
5299         }
5300         m_missionTimerEnabled = m_missionTimerStarted = false;
5301         m_displayText->SetEnable(false);
5302         return INFO_LOST;
5303     }
5304 
5305     if (m_missionResult == ERR_OK)
5306     {
5307         if (m_endTakeWinDelay == -1.0f && m_winDelay == 0.0f)
5308         {
5309             m_winDelay  = 1.0f;  // wins in one second
5310             m_lostDelay = 0.0f;
5311             m_missionTimerEnabled = m_missionTimerStarted = false;
5312             m_displayText->SetEnable(false);
5313             return ERR_OK;  // mission ended
5314         }
5315 
5316         if (frame)
5317         {
5318             if (m_base != nullptr && !m_endTakeImmediat)
5319             {
5320                 assert(m_base->Implements(ObjectInterfaceType::Controllable));
5321                 if(dynamic_cast<CControllableObject&>(*m_base).GetSelectable())
5322                     return ERR_MISSION_NOTERM;
5323             }
5324         }
5325 
5326         if (m_winDelay == 0.0f)
5327         {
5328             m_displayText->DisplayError(INFO_WIN, Math::Vector(0.0f,0.0f,0.0f));
5329             if (m_missionTimerEnabled && m_missionTimerStarted)
5330             {
5331                 GetLogger()->Info("Mission time: %s\n", TimeFormat(m_missionTimer).c_str());
5332                 m_displayText->DisplayText(("Time: " + TimeFormat(m_missionTimer)).c_str(), Math::Vector(0.0f,0.0f,0.0f));
5333             }
5334             m_missionTimerEnabled = m_missionTimerStarted = false;
5335             m_winDelay  = m_endTakeWinDelay;  // wins in two seconds
5336             m_lostDelay = 0.0f;
5337         }
5338         m_displayText->SetEnable(false);
5339         return ERR_OK;  // mission ended
5340     }
5341     else
5342     {
5343         m_displayText->SetEnable(true);
5344         return ERR_MISSION_NOTERM;
5345     }
5346 }
5347 
5348 
5349 //! Returns the list instructions required in CBot program in level
GetObligatoryTokenList()5350 const std::map<std::string, MinMax>& CRobotMain::GetObligatoryTokenList()
5351 {
5352     return m_obligatoryTokens;
5353 }
5354 
5355 //! Indicates whether it is possible to control a driving robot
GetTrainerPilot()5356 bool CRobotMain::GetTrainerPilot()
5357 {
5358     return m_cheatTrainerPilot;
5359 }
5360 
GetPlusTrainer()5361 bool CRobotMain::GetPlusTrainer()
5362 {
5363     return m_ui->GetPlusTrainer();
5364 }
5365 
GetPlusExplorer()5366 bool CRobotMain::GetPlusExplorer()
5367 {
5368     return m_ui->GetPlusExplorer();
5369 }
5370 
5371 //! Indicates whether the scene is fixed, without interaction
GetFixScene()5372 bool CRobotMain::GetFixScene()
5373 {
5374     return m_fixScene;
5375 }
5376 
5377 
GetScriptName()5378 const std::string& CRobotMain::GetScriptName()
5379 {
5380     return m_scriptName;
5381 }
5382 
GetScriptFile()5383 const std::string& CRobotMain::GetScriptFile()
5384 {
5385     return m_scriptFile;
5386 }
5387 
5388 
GetShowSoluce()5389 bool CRobotMain::GetShowSoluce()
5390 {
5391     return m_cheatShowSoluce;
5392 }
5393 
GetSceneSoluce()5394 bool CRobotMain::GetSceneSoluce()
5395 {
5396     if (m_infoFilename[SATCOM_SOLUCE][0] == 0) return false;
5397     return m_ui->GetSceneSoluce();
5398 }
5399 
GetShowAll()5400 bool CRobotMain::GetShowAll()
5401 {
5402     return m_cheatAllMission;
5403 }
5404 
GetRadar()5405 bool CRobotMain::GetRadar()
5406 {
5407     if (m_cheatRadar)
5408         return true;
5409 
5410     for (CObject* obj : m_objMan->GetAllObjects())
5411     {
5412         ObjectType type = obj->GetType();
5413         if (type == OBJECT_RADAR && !obj->GetLock())
5414             return true;
5415     }
5416     return false;
5417 }
5418 
GetMissionType()5419 MissionType CRobotMain::GetMissionType()
5420 {
5421     return m_missionType;
5422 }
5423 
5424 
5425 //! Returns the representation to use for the player
GetGamerFace()5426 int CRobotMain::GetGamerFace()
5427 {
5428     return m_playerProfile->GetApperance().face;
5429 }
5430 
5431 //! Returns the representation to use for the player
GetGamerGlasses()5432 int CRobotMain::GetGamerGlasses()
5433 {
5434     return m_playerProfile->GetApperance().glasses;
5435 }
5436 
5437 //! Returns the mode with just the head
GetGamerOnlyHead()5438 bool CRobotMain::GetGamerOnlyHead()
5439 {
5440     return m_ui->GetGamerOnlyHead();
5441 }
5442 
5443 //! Returns the angle of presentation
GetPersoAngle()5444 float CRobotMain::GetPersoAngle()
5445 {
5446     return m_ui->GetPersoAngle();
5447 }
5448 
SetLevel(LevelCategory cat,int chap,int rank)5449 void CRobotMain::SetLevel(LevelCategory cat, int chap, int rank)
5450 {
5451     GetLogger()->Debug("Change level to %s %d %d\n", GetLevelCategoryDir(cat).c_str(), chap, rank);
5452     m_levelCategory = cat;
5453     m_levelChap = chap;
5454     m_levelRank = rank;
5455     m_levelFile = CLevelParser::BuildScenePath(m_levelCategory, m_levelChap, m_levelRank);
5456 }
5457 
GetLevelCategory()5458 LevelCategory CRobotMain::GetLevelCategory()
5459 {
5460     return m_levelCategory;
5461 }
5462 
GetLevelChap()5463 int CRobotMain::GetLevelChap()
5464 {
5465     return m_levelChap;
5466 }
5467 
GetLevelRank()5468 int CRobotMain::GetLevelRank()
5469 {
5470     return m_levelRank;
5471 }
5472 
5473 //! Returns folder name of the scene that user selected to play.
GetCustomLevelDir()5474 std::string CRobotMain::GetCustomLevelDir()
5475 {
5476     assert(m_levelCategory == LevelCategory::CustomLevels);
5477     return m_ui->GetCustomLevelName(m_levelChap);
5478 }
5479 
SetReadScene(std::string path)5480 void CRobotMain::SetReadScene(std::string path)
5481 {
5482     m_sceneReadPath = path;
5483 }
5484 
UpdateChapterPassed()5485 void CRobotMain::UpdateChapterPassed()
5486 {
5487     return m_ui->UpdateChapterPassed();
5488 }
5489 
5490 //! Changes game speed
SetSpeed(float speed)5491 void CRobotMain::SetSpeed(float speed)
5492 {
5493     speed = Math::Clamp(speed, MIN_SPEED, MAX_SPEED);
5494 
5495     m_app->SetSimulationSpeed(speed);
5496     UpdateSpeedLabel();
5497 }
5498 
GetSpeed()5499 float CRobotMain::GetSpeed()
5500 {
5501     return m_app->GetSimulationSpeed();
5502 }
5503 
UpdateSpeedLabel()5504 void CRobotMain::UpdateSpeedLabel()
5505 {
5506     Ui::CButton* pb = static_cast<Ui::CButton*>(m_interface->SearchControl(EVENT_SPEED));
5507     float speed = m_app->GetSimulationSpeed();
5508 
5509     if (pb != nullptr)
5510     {
5511         if (speed == 1.0f)
5512         {
5513             pb->ClearState(Ui::STATE_VISIBLE);
5514         }
5515         else
5516         {
5517             char text[10];
5518             sprintf(text, "x%.1f", speed);
5519             pb->SetName(text);
5520             pb->SetState(Ui::STATE_VISIBLE);
5521         }
5522     }
5523 
5524 }
5525 
5526 
CreateShortcuts()5527 bool CRobotMain::CreateShortcuts()
5528 {
5529     if (m_phase != PHASE_SIMUL) return false;
5530     if (m_ui->GetLoadingScreen()->IsVisible()) return false;
5531     if (!m_shortCut) return false;
5532     return m_short->CreateShortcuts();
5533 }
5534 
5535 //! Updates the map
UpdateMap()5536 void CRobotMain::UpdateMap()
5537 {
5538     m_map->UpdateMap();
5539 }
5540 
5541 //! Indicates whether the mini-map is visible
GetShowMap()5542 bool CRobotMain::GetShowMap()
5543 {
5544     return m_mapShow;
5545 }
5546 
5547 
5548 //! Management of the lock mode for movies
SetMovieLock(bool lock)5549 void CRobotMain::SetMovieLock(bool lock)
5550 {
5551     m_movieLock = lock;
5552 
5553     CreateShortcuts();
5554     m_map->ShowMap(!m_movieLock && m_mapShow && !m_ui->GetLoadingScreen()->IsVisible());
5555     if (m_movieLock) HiliteClear();
5556 }
5557 
GetMovieLock()5558 bool CRobotMain::GetMovieLock()
5559 {
5560     return m_movieLock;
5561 }
5562 
GetInfoLock()5563 bool CRobotMain::GetInfoLock()
5564 {
5565     return m_displayInfo != nullptr;  // info in progress?
5566 }
5567 
5568 //! Management of the blocking of the call of SatCom
SetSatComLock(bool lock)5569 void CRobotMain::SetSatComLock(bool lock)
5570 {
5571     m_satComLock = lock;
5572 }
5573 
GetSatComLock()5574 bool CRobotMain::GetSatComLock()
5575 {
5576     return m_satComLock;
5577 }
5578 
5579 //! Management of the lock mode for the edition
SetEditLock(bool lock,bool edit)5580 void CRobotMain::SetEditLock(bool lock, bool edit)
5581 {
5582     m_editLock = lock;
5583 
5584     CreateShortcuts();
5585 
5586     // Do not remove the card if it contains a still image.
5587     if (!lock || !m_map->GetFixImage())
5588         m_map->ShowMap(!m_editLock && m_mapShow);
5589 
5590     m_displayText->HideText(lock);
5591     m_input->ResetKeyStates();
5592 
5593     if (m_editLock)
5594         HiliteClear();
5595     else
5596         m_editFull = false;
5597 }
5598 
GetEditLock()5599 bool CRobotMain::GetEditLock()
5600 {
5601     return m_editLock;
5602 }
5603 
5604 //! Management of the fullscreen mode during editing
SetEditFull(bool full)5605 void CRobotMain::SetEditFull(bool full)
5606 {
5607     m_editFull = full;
5608 }
5609 
GetEditFull()5610 bool CRobotMain::GetEditFull()
5611 {
5612     return m_editFull;
5613 }
5614 
5615 
5616 //! Indicates whether mouse is on an friend object, on which we should not shoot
SetFriendAim(bool friendAim)5617 void CRobotMain::SetFriendAim(bool friendAim)
5618 {
5619     m_friendAim = friendAim;
5620 }
5621 
GetFriendAim()5622 bool CRobotMain::GetFriendAim()
5623 {
5624     return m_friendAim;
5625 }
5626 
5627 
5628 //! Starts music with a mission
StartMusic()5629 void CRobotMain::StartMusic()
5630 {
5631     GetLogger()->Debug("Starting music...\n");
5632     if (m_audioTrack != "")
5633     {
5634         m_sound->PlayMusic(m_audioTrack, m_audioRepeat, 0.0f);
5635     }
5636 }
5637 
UpdatePause(PauseType pause)5638 void CRobotMain::UpdatePause(PauseType pause)
5639 {
5640     m_engine->SetPause(pause & PAUSE_ENGINE);
5641     m_camera->SetFreeze(pause & PAUSE_CAMERA);
5642     m_sound->MuteAll(pause & PAUSE_MUTE_SOUND);
5643     CreateShortcuts();
5644     if (pause != PAUSE_NONE) HiliteClear();
5645 }
5646 
UpdatePauseMusic(PauseMusic music)5647 void CRobotMain::UpdatePauseMusic(PauseMusic music)
5648 {
5649     switch (music)
5650     {
5651         case PAUSE_MUSIC_NONE:
5652             m_sound->StopPauseMusic();
5653             break;
5654 
5655         case PAUSE_MUSIC_EDITOR:
5656             if (m_editorTrack != "")
5657                 m_sound->PlayPauseMusic(m_editorTrack, m_editorRepeat);
5658             break;
5659 
5660         case PAUSE_MUSIC_SATCOM:
5661             if (m_satcomTrack != "")
5662                 m_sound->PlayPauseMusic(m_satcomTrack, m_satcomRepeat);
5663             break;
5664     }
5665 }
5666 
5667 //! Removes hilite and tooltip
ClearInterface()5668 void CRobotMain::ClearInterface()
5669 {
5670     HiliteClear();  // removes setting evidence
5671     m_tooltipName.clear();  // really removes the tooltip
5672 }
5673 
DisplayError(Error err,CObject * pObj,float time)5674 void CRobotMain::DisplayError(Error err, CObject* pObj, float time)
5675 {
5676     m_displayText->DisplayError(err, pObj, time);
5677 }
5678 
DisplayError(Error err,Math::Vector goal,float height,float dist,float time)5679 void CRobotMain::DisplayError(Error err, Math::Vector goal, float height, float dist, float time)
5680 {
5681     m_displayText->DisplayError(err, goal, height, dist, time);
5682 }
5683 
UpdateCustomLevelList()5684 void CRobotMain::UpdateCustomLevelList()
5685 {
5686     m_ui->UpdateCustomLevelList();
5687 }
5688 
GetCustomLevelName(int id)5689 std::string CRobotMain::GetCustomLevelName(int id)
5690 {
5691     return m_ui->GetCustomLevelName(id);
5692 }
5693 
GetCustomLevelList()5694 const std::vector<std::string>& CRobotMain::GetCustomLevelList()
5695 {
5696     return m_ui->GetCustomLevelList();
5697 }
5698 
IsLoading()5699 bool CRobotMain::IsLoading()
5700 {
5701     return m_ui->GetLoadingScreen()->IsVisible();
5702 }
5703 
StartMissionTimer()5704 void CRobotMain::StartMissionTimer()
5705 {
5706     if (m_missionTimerEnabled && !m_missionTimerStarted)
5707     {
5708         GetLogger()->Info("Starting mission timer...\n");
5709         m_missionTimerStarted = true;
5710     }
5711 }
5712 
SetAutosave(bool enable)5713 void CRobotMain::SetAutosave(bool enable)
5714 {
5715     if (m_autosave == enable) return;
5716 
5717     m_autosave = enable;
5718     m_autosaveLast = m_gameTimeAbsolute;
5719 }
5720 
GetAutosave()5721 bool CRobotMain::GetAutosave()
5722 {
5723     return m_autosave;
5724 }
5725 
SetAutosaveInterval(int interval)5726 void CRobotMain::SetAutosaveInterval(int interval)
5727 {
5728     if (m_autosaveInterval == interval) return;
5729 
5730     m_autosaveInterval = interval;
5731     m_autosaveLast = m_gameTimeAbsolute;
5732 }
5733 
GetAutosaveInterval()5734 int CRobotMain::GetAutosaveInterval()
5735 {
5736     return m_autosaveInterval;
5737 }
5738 
SetAutosaveSlots(int slots)5739 void CRobotMain::SetAutosaveSlots(int slots)
5740 {
5741     if (m_autosaveSlots == slots) return;
5742 
5743     m_autosaveSlots = slots;
5744 }
5745 
GetAutosaveSlots()5746 int CRobotMain::GetAutosaveSlots()
5747 {
5748     return m_autosaveSlots;
5749 }
5750 
5751 // Remove oldest saves with autosave prefix
AutosaveRotate()5752 void CRobotMain::AutosaveRotate()
5753 {
5754     if (m_playerProfile == nullptr)
5755         return;
5756 
5757     GetLogger()->Debug("Rotate autosaves...\n");
5758     auto saveDirs = CResourceManager::ListDirectories(m_playerProfile->GetSaveDir());
5759     const std::string autosavePrefix = "autosave";
5760     std::vector<std::string> autosaves;
5761     std::copy_if(saveDirs.begin(), saveDirs.end(), std::back_inserter(autosaves), [&](const std::string &save)
5762     {
5763         return save.substr(0, autosavePrefix.length()) == autosavePrefix;
5764     });
5765 
5766     std::sort(autosaves.begin(), autosaves.end(), std::less<std::string>());
5767     for (int i = 0; i < static_cast<int>(autosaves.size()) - m_autosaveSlots + 1; i++)
5768     {
5769         CResourceManager::RemoveDirectory(m_playerProfile->GetSaveDir() + "/" + autosaves[i]);
5770     }
5771 }
5772 
Autosave()5773 void CRobotMain::Autosave()
5774 {
5775     AutosaveRotate();
5776     GetLogger()->Info("Autosave!\n");
5777 
5778     char timestr[100];
5779     char infostr[100];
5780     time_t now = time(nullptr);
5781     strftime(timestr, 99, "%y%m%d%H%M%S", localtime(&now));
5782     strftime(infostr, 99, "%y.%m.%d %H:%M", localtime(&now));
5783     std::string info = std::string("[AUTOSAVE] ") + infostr;
5784     std::string dir = m_playerProfile->GetSaveFile(std::string("autosave") + timestr);
5785 
5786     m_playerProfile->SaveScene(dir, info);
5787 }
5788 
QuickSave()5789 void CRobotMain::QuickSave()
5790 {
5791     GetLogger()->Info("Quicksave!\n");
5792 
5793     char infostr[100];
5794     time_t now = time(nullptr);
5795     strftime(infostr, 99, "%y.%m.%d %H:%M", localtime(&now));
5796     std::string info = std::string("[QUICKSAVE]") + infostr;
5797     std::string dir = m_playerProfile->GetSaveFile(std::string("quicksave"));
5798 
5799     m_playerProfile->SaveScene(dir, info);
5800 }
5801 
QuickLoad()5802 void CRobotMain::QuickLoad()
5803 {
5804     std::string dir = m_playerProfile->GetSaveFile(std::string("quicksave"));
5805     if(!CResourceManager::Exists(dir))
5806     {
5807         m_displayText->DisplayError(ERR_NO_QUICK_SLOT, Math::Vector(0.0f,0.0f,0.0f), 15.0f, 60.0f, 1000.0f);
5808         GetLogger()->Debug("Quicksave slot not found\n");
5809         return;
5810     }
5811     m_playerProfile->LoadScene(dir);
5812 }
5813 
SetExitAfterMission(bool exit)5814 void CRobotMain::SetExitAfterMission(bool exit)
5815 {
5816     m_exitAfterMission = exit;
5817 }
5818 
CanPlayerInteract()5819 bool CRobotMain::CanPlayerInteract()
5820 {
5821     if(GetMissionType() == MISSION_CODE_BATTLE)
5822     {
5823         return !m_codeBattleStarted;
5824     }
5825     return true;
5826 }
5827 
5828 const std::string NO_TEAM_NAME = "Team";
GetTeamName(int id)5829 const std::string& CRobotMain::GetTeamName(int id)
5830 {
5831     if(m_teamNames.count(id) == 0) return NO_TEAM_NAME;
5832     return m_teamNames[id];
5833 }
5834 
IsTeamColorDefined(int id)5835 bool CRobotMain::IsTeamColorDefined(int id)
5836 {
5837     if(id == 0) return false; // Always use default for team 0
5838 
5839     return m_colorNewBot.find(id) != m_colorNewBot.end();
5840 }
5841 
5842 
GetEnableBuild()5843 int CRobotMain::GetEnableBuild()
5844 {
5845     return m_build;
5846 }
5847 
SetEnableBuild(int enableBuild)5848 void CRobotMain::SetEnableBuild(int enableBuild)
5849 {
5850     m_build = enableBuild;
5851 }
5852 
GetEnableResearch()5853 int CRobotMain::GetEnableResearch()
5854 {
5855     return m_researchEnable;
5856 }
5857 
SetEnableResearch(int enableResearch)5858 void CRobotMain::SetEnableResearch(int enableResearch)
5859 {
5860     m_researchEnable = enableResearch;
5861 }
5862 
GetDoneResearch(int team)5863 int CRobotMain::GetDoneResearch(int team)
5864 {
5865     return m_researchDone[team];
5866 }
5867 
SetDoneResearch(int doneResearch,int team)5868 void CRobotMain::SetDoneResearch(int doneResearch, int team)
5869 {
5870     m_researchDone[team] = doneResearch;
5871 }
5872 
5873 
IsBuildingEnabled(BuildType type)5874 bool CRobotMain::IsBuildingEnabled(BuildType type)
5875 {
5876     return (m_build & type) != 0;
5877 }
5878 
IsBuildingEnabled(ObjectType type)5879 bool CRobotMain::IsBuildingEnabled(ObjectType type)
5880 {
5881     if(type == OBJECT_DERRICK) return IsBuildingEnabled(BUILD_DERRICK);
5882     if(type == OBJECT_FACTORY) return IsBuildingEnabled(BUILD_FACTORY);
5883     if(type == OBJECT_STATION) return IsBuildingEnabled(BUILD_STATION);
5884     if(type == OBJECT_CONVERT) return IsBuildingEnabled(BUILD_CONVERT);
5885     if(type == OBJECT_REPAIR) return IsBuildingEnabled(BUILD_REPAIR);
5886     if(type == OBJECT_TOWER) return IsBuildingEnabled(BUILD_TOWER);
5887     if(type == OBJECT_RESEARCH) return IsBuildingEnabled(BUILD_RESEARCH);
5888     if(type == OBJECT_RADAR) return IsBuildingEnabled(BUILD_RADAR);
5889     if(type == OBJECT_ENERGY) return IsBuildingEnabled(BUILD_ENERGY);
5890     if(type == OBJECT_LABO) return IsBuildingEnabled(BUILD_LABO);
5891     if(type == OBJECT_NUCLEAR) return IsBuildingEnabled(BUILD_NUCLEAR);
5892     if(type == OBJECT_INFO) return IsBuildingEnabled(BUILD_INFO);
5893     if(type == OBJECT_PARA) return IsBuildingEnabled(BUILD_PARA);
5894     if(type == OBJECT_SAFE) return IsBuildingEnabled(BUILD_SAFE);
5895     if(type == OBJECT_DESTROYER) return IsBuildingEnabled(BUILD_DESTROYER);
5896 
5897     return false;
5898 }
5899 
IsResearchEnabled(ResearchType type)5900 bool CRobotMain::IsResearchEnabled(ResearchType type)
5901 {
5902     return (m_researchEnable & type) != 0;
5903 }
5904 
IsResearchDone(ResearchType type,int team)5905 bool CRobotMain::IsResearchDone(ResearchType type, int team)
5906 {
5907     if(team != 0 && m_researchDone.count(team) == 0)
5908     {
5909         // Initialize with defaults
5910         m_researchDone[team] = m_researchDone[0];
5911     }
5912 
5913     return (m_researchDone[team] & type) != 0;
5914 }
5915 
MarkResearchDone(ResearchType type,int team)5916 void CRobotMain::MarkResearchDone(ResearchType type, int team)
5917 {
5918     if(team != 0 && m_researchDone.count(team) == 0)
5919     {
5920         // Initialize with defaults
5921         m_researchDone[team] = m_researchDone[0];
5922     }
5923 
5924     m_researchDone[team] |= type;
5925 
5926     if(team == 0)
5927     {
5928         m_playerProfile->SetFreeGameResearchUnlock(m_playerProfile->GetFreeGameResearchUnlock() | m_researchDone[0]);
5929     }
5930 }
5931 
CanBuildError(ObjectType type,int team)5932 Error CRobotMain::CanBuildError(ObjectType type, int team)
5933 {
5934     if(!IsBuildingEnabled(type)) return ERR_BUILD_DISABLED;
5935 
5936     if(type == OBJECT_TOWER   && !IsResearchDone(RESEARCH_TOWER,  team)) return ERR_BUILD_RESEARCH;
5937     if(type == OBJECT_NUCLEAR && !IsResearchDone(RESEARCH_ATOMIC, team)) return ERR_BUILD_RESEARCH;
5938 
5939     return ERR_OK;
5940 }
5941 
CanFactoryError(ObjectType type,int team)5942 Error CRobotMain::CanFactoryError(ObjectType type, int team)
5943 {
5944     ToolType tool = GetToolFromObject(type);
5945     DriveType drive = GetDriveFromObject(type);
5946 
5947     if (tool == ToolType::Sniffer        && !IsResearchDone(RESEARCH_SNIFFER,  team)) return ERR_BUILD_RESEARCH;
5948     if (tool == ToolType::Shooter        && !IsResearchDone(RESEARCH_CANON,    team)) return ERR_BUILD_RESEARCH;
5949     if (tool == ToolType::OrganicShooter && !IsResearchDone(RESEARCH_iGUN,     team)) return ERR_BUILD_RESEARCH;
5950     if (tool == ToolType::Builder        && !IsResearchDone(RESEARCH_BUILDER,  team)) return ERR_BUILD_RESEARCH;
5951 
5952     if (drive == DriveType::Tracked      && !IsResearchDone(RESEARCH_TANK,     team)) return ERR_BUILD_RESEARCH;
5953     if (drive == DriveType::Winged       && !IsResearchDone(RESEARCH_FLY,      team)) return ERR_BUILD_RESEARCH;
5954     if (drive == DriveType::Legged       && !IsResearchDone(RESEARCH_iPAW,     team)) return ERR_BUILD_RESEARCH;
5955     if (drive == DriveType::Heavy        && !IsResearchDone(RESEARCH_TANK,     team)) return ERR_BUILD_RESEARCH;
5956 
5957     if (type == OBJECT_MOBILErt          && !IsResearchDone(RESEARCH_THUMP,    team)) return ERR_BUILD_RESEARCH;
5958     if (type == OBJECT_MOBILErc          && !IsResearchDone(RESEARCH_PHAZER,   team)) return ERR_BUILD_RESEARCH;
5959     if (type == OBJECT_MOBILErr          && !IsResearchDone(RESEARCH_RECYCLER, team)) return ERR_BUILD_RESEARCH;
5960     if (type == OBJECT_MOBILErs          && !IsResearchDone(RESEARCH_SHIELD,   team)) return ERR_BUILD_RESEARCH;
5961     if (type == OBJECT_MOBILEsa          && !IsResearchDone(RESEARCH_SUBM,     team)) return ERR_BUILD_DISABLED; // Can be only researched manually in Scene file
5962     if (type == OBJECT_MOBILEst          && !IsResearchDone(RESEARCH_SUBM,     team)) return ERR_BUILD_DISABLED;
5963     if (type == OBJECT_MOBILEtg          && !IsResearchDone(RESEARCH_TARGET,   team)) return ERR_BUILD_RESEARCH;
5964 
5965     if (tool == ToolType::Other && drive == DriveType::Other && type != OBJECT_MOBILEtg)   return ERR_WRONG_OBJ;
5966 
5967     return ERR_OK;
5968 }
5969 
PushToSelectionHistory(CObject * obj)5970 void CRobotMain::PushToSelectionHistory(CObject* obj)
5971 {
5972     if (!m_selectionHistory.empty() && m_selectionHistory.back() == obj)
5973         return; // already in history
5974 
5975     m_selectionHistory.push_back(obj);
5976 
5977     if (m_selectionHistory.size() > 50) // to avoid infinite growth
5978         m_selectionHistory.pop_front();
5979 }
5980 
PopFromSelectionHistory()5981 CObject* CRobotMain::PopFromSelectionHistory()
5982 {
5983     if (m_selectionHistory.empty())
5984         return nullptr;
5985 
5986     CObject* obj = m_selectionHistory.back();
5987     m_selectionHistory.pop_back();
5988     return obj;
5989 }
5990 
RemoveFromSelectionHistory(CObject * object)5991 void CRobotMain::RemoveFromSelectionHistory(CObject* object)
5992 {
5993     auto it = std::remove_if(m_selectionHistory.begin(), m_selectionHistory.end(),
5994                              [object](const CObject* obj) { return obj == object; });
5995     m_selectionHistory.erase(it, m_selectionHistory.end());
5996 }
5997 
GetGlobalMagnifyDamage()5998 float CRobotMain::GetGlobalMagnifyDamage()
5999 {
6000     return m_globalMagnifyDamage;
6001 }
6002 
GetGlobalNuclearCapacity()6003 float CRobotMain::GetGlobalNuclearCapacity()
6004 {
6005     return m_globalNuclearCapacity;
6006 }
6007 
GetGlobalCellCapacity()6008 float CRobotMain::GetGlobalCellCapacity()
6009 {
6010     return m_globalCellCapacity;
6011 }
6012 
6013 // Beginning of the effect when the instruction "detect" is used.
6014 
StartDetectEffect(COldObject * object,CObject * target)6015 void CRobotMain::StartDetectEffect(COldObject* object, CObject* target)
6016 {
6017     Math::Matrix*   mat;
6018     Math::Vector    pos, goal;
6019     Math::Point     dim;
6020 
6021     mat = object->GetWorldMatrix(0);
6022     pos = Math::Transform(*mat, Math::Vector(2.0f, 3.0f, 0.0f));
6023 
6024     if ( target == nullptr )
6025     {
6026         goal = Math::Transform(*mat, Math::Vector(50.0f, 3.0f, 0.0f));
6027     }
6028     else
6029     {
6030         goal = target->GetPosition();
6031         goal.y += 3.0f;
6032         goal = Math::SegmentPoint(pos, goal, Math::Distance(pos, goal)-3.0f);
6033     }
6034 
6035     dim.x = 3.0f;
6036     dim.y = dim.x;
6037     m_particle->CreateRay(pos, goal, Gfx::PARTIRAY2, dim, 0.2f);
6038 
6039     if ( target != nullptr )
6040     {
6041         goal = target->GetPosition();
6042         goal.y += 3.0f;
6043         goal = Math::SegmentPoint(pos, goal, Math::Distance(pos, goal)-1.0f);
6044         dim.x = 6.0f;
6045         dim.y = dim.x;
6046         m_particle->CreateParticle(goal, Math::Vector(0.0f, 0.0f, 0.0f), dim,
6047                                      target != nullptr ? Gfx::PARTIGLINT : Gfx::PARTIGLINTr, 0.5f);
6048     }
6049 
6050     m_sound->Play(target != nullptr ? SOUND_BUILD : SOUND_RECOVER);
6051 }
6052 
CreateCodeBattleInterface()6053 void CRobotMain::CreateCodeBattleInterface()
6054 {
6055     if (m_phase == PHASE_SIMUL)
6056     {
6057         Math::Point pos, ddim;
6058         float offset = (ceil(m_viewpoints.size() / 2.0f) * 50);
6059 
6060         int numTeams = m_scoreboard ? GetAllTeams().size() : 0;
6061         assert(numTeams < EVENT_SCOREBOARD_MAX-EVENT_SCOREBOARD+1);
6062         float textHeight = m_engine->GetText()->GetHeight(Gfx::FONT_COMMON, Gfx::FONT_SIZE_SMALL);
6063 
6064         //window
6065         ddim.x = 100.0f/640.0f;
6066         ddim.y = (100.0f+offset)/480.0f + numTeams * textHeight;
6067         pos.x = 540.0f/640.0f;
6068         pos.y = 100.0f/480.0f;
6069         Ui::CWindow* pw = m_interface->CreateWindows(pos, ddim, 3, EVENT_WINDOW6);
6070 
6071         //label text
6072         ddim.x = 100.0f/640.0f;
6073         ddim.y = 16.0f/480.0f;
6074         pos.x = 540.0f/640.0f;
6075         pos.y = (178.0f+offset)/480.0f + numTeams * textHeight;
6076         std::string text;
6077         GetResource(RES_EVENT, EVENT_LABEL_CODE_BATTLE, text);
6078         pw->CreateLabel(pos, ddim, 0, EVENT_LABEL_CODE_BATTLE, text);
6079 
6080         //viewpoint selection section
6081         ddim.x = 40.0f/640.0f;
6082         ddim.y = 50.0f/640.0f;
6083         for(unsigned int i = 0; i < m_viewpoints.size(); i++)
6084         {
6085             //create button
6086             pos.x = (550.0f+40.0f*(i%2))/640.0f;
6087             pos.y = (130.0f+offset)/480.0f + numTeams * textHeight - 45.0f*(i/2)/480.0f;
6088             pw->CreateButton(pos, ddim, m_viewpoints[i].button, EventType(EVENT_VIEWPOINT0 + i));
6089         }
6090 
6091         //start/camera button
6092         float titleBarSize = (11.0f/64.0f); // this is from the texture
6093         ddim.x = 80.0f/640.0f;
6094         ddim.y = ((1-titleBarSize)*100.0f-20.0f)/480.0f;
6095         pos.x = 550.0f/640.0f;
6096         pos.y = 110.0f/480.0f;
6097         if (!m_codeBattleStarted)
6098         {
6099             pw->CreateButton(pos, ddim, 21, EVENT_CODE_BATTLE_START);
6100         }
6101         else
6102         {
6103             pw->CreateButton(pos, ddim, 13, EVENT_CODE_BATTLE_SPECTATOR);
6104         }
6105 
6106         if (!m_scoreboard) return;
6107         pos.y += ddim.y;
6108         ddim.y = textHeight;
6109         int i = 0;
6110         auto teams = GetAllTeams();
6111         for (auto it = teams.rbegin(); it != teams.rend(); ++it)
6112         {
6113             int team = *it;
6114             Ui::CControl* pl;
6115             ddim.x = 55.0f/640.0f;
6116             pl = m_codeBattleStarted
6117                  ? static_cast<Ui::CControl*>(pw->CreateLabel(pos, ddim, 0, static_cast<EventType>(EVENT_SCOREBOARD+2*(numTeams-i-1)+0), "XXXXX"))
6118                  : static_cast<Ui::CControl*>(pw->CreateEdit( pos, ddim, 0, static_cast<EventType>(EVENT_SCOREBOARD+2*(numTeams-i-1)+0)));
6119             pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT);
6120             pl->SetFontSize(m_codeBattleStarted ? Gfx::FONT_SIZE_SMALL : Gfx::FONT_SIZE_SMALL*0.75f);
6121             m_codeBattleStarted ? pl->SetName(GetTeamName(team)) : static_cast<Ui::CEdit*>(pl)->SetText(GetTeamName(team));
6122             pos.x += 57.5f/640.0f;
6123             ddim.x = 22.5f/640.0f;
6124             pl = m_codeBattleStarted
6125                  ? static_cast<Ui::CControl*>(pw->CreateLabel(pos, ddim, 0, static_cast<EventType>(EVENT_SCOREBOARD+2*(numTeams-i-1)+1), "???"))
6126                  : static_cast<Ui::CControl*>(pw->CreateEdit( pos, ddim, 0, static_cast<EventType>(EVENT_SCOREBOARD+2*(numTeams-i-1)+1)));
6127             pl->SetTextAlign(Gfx::TEXT_ALIGN_RIGHT);
6128             pl->SetFontSize(m_codeBattleStarted ? Gfx::FONT_SIZE_SMALL : Gfx::FONT_SIZE_SMALL*0.75f);
6129             m_codeBattleStarted ? pl->SetName(StrUtils::ToString<int>(m_scoreboard->GetScore(team).points)) : static_cast<Ui::CEdit*>(pl)->SetText(StrUtils::ToString<int>(m_scoreboard->GetScore(team).points));
6130             pos.x -= 57.5f/640.0f;
6131             pos.y += ddim.y;
6132             i++;
6133         }
6134     }
6135 }
6136 
ApplyCodeBattleInterface()6137 void CRobotMain::ApplyCodeBattleInterface()
6138 {
6139     assert(GetMissionType() == MISSION_CODE_BATTLE);
6140     if (!m_scoreboard) return;
6141 
6142     Ui::CWindow* pw = static_cast<Ui::CWindow*>(m_interface->SearchControl(EVENT_WINDOW6));
6143     assert(pw != nullptr);
6144 
6145     int i = 0;
6146     for (int team : GetAllTeams())
6147     {
6148         Ui::CEdit* pl;
6149 
6150         pl = static_cast<Ui::CEdit*>(pw->SearchControl(static_cast<EventType>(EVENT_SCOREBOARD+2*i+0)));
6151         assert(pl != nullptr);
6152         m_teamNames[team] = pl->GetText(pl->GetTextLength());
6153 
6154         pl = static_cast<Ui::CEdit*>(pw->SearchControl(static_cast<EventType>(EVENT_SCOREBOARD+2*i+1)));
6155         assert(pl != nullptr);
6156         m_scoreboard->SetScore(team, StrUtils::FromString<int>(pl->GetText(pl->GetTextLength())));
6157 
6158         i++;
6159     }
6160 }
6161 
UpdateCodeBattleInterface()6162 void CRobotMain::UpdateCodeBattleInterface()
6163 {
6164     assert(GetMissionType() == MISSION_CODE_BATTLE);
6165     if (!m_scoreboard) return;
6166 
6167     Ui::CWindow* pw = static_cast<Ui::CWindow*>(m_interface->SearchControl(EVENT_WINDOW6));
6168     assert(pw != nullptr);
6169 
6170     int i = 0;
6171     for (std::pair<int, CScoreboard::Score> team : m_scoreboard->GetSortedScores())
6172     {
6173         Ui::CControl* pl;
6174 
6175         pl = pw->SearchControl(static_cast<EventType>(EVENT_SCOREBOARD+2*i+0));
6176         assert(pl != nullptr);
6177         pl->SetName(GetTeamName(team.first));
6178 
6179         pl = pw->SearchControl(static_cast<EventType>(EVENT_SCOREBOARD+2*i+1));
6180         assert(pl != nullptr);
6181         pl->SetName(StrUtils::ToString<int>(team.second.points));
6182 
6183         i++;
6184     }
6185 }
6186 
DestroyCodeBattleInterface()6187 void CRobotMain::DestroyCodeBattleInterface()
6188 {
6189     m_viewpoints.clear();
6190     m_interface->DeleteControl(EVENT_WINDOW6);
6191 }
6192 
SetCodeBattleSpectatorMode(bool mode)6193 void CRobotMain::SetCodeBattleSpectatorMode(bool mode)
6194 {
6195     // Deselect object, but keep camera attached to it
6196     CObject* obj = DeselectAll();
6197     if (m_codeBattleSpectator)
6198         obj = m_camera->GetControllingObject();
6199 
6200     m_codeBattleSpectator = mode;
6201     SelectObject(obj, false); // this uses code battle selection mode already
6202 }
6203 
UpdateDebugCrashSpheres()6204 void CRobotMain::UpdateDebugCrashSpheres()
6205 {
6206     if (m_debugCrashSpheres)
6207     {
6208         for (CObject* obj : m_objMan->GetAllObjects())
6209         {
6210             for (const auto& crashSphere : obj->GetAllCrashSpheres())
6211             {
6212                 m_engine->RenderDebugSphere(crashSphere.sphere, Math::Matrix{}, Gfx::Color{0.0f, 0.0f, 1.0f, 1.0f});
6213             }
6214         }
6215     }
6216 }
6217 
SetDebugCrashSpheres(bool draw)6218 void CRobotMain::SetDebugCrashSpheres(bool draw)
6219 {
6220     m_debugCrashSpheres = draw;
6221 }
6222 
GetDebugCrashSpheres()6223 bool CRobotMain::GetDebugCrashSpheres()
6224 {
6225     return m_debugCrashSpheres;
6226 }
6227 
PushToCommandHistory(std::string cmd)6228 void CRobotMain::PushToCommandHistory(std::string cmd)
6229 {
6230     if (!m_commandHistory.empty() && m_commandHistory.front() == cmd) // already in history
6231         return;
6232 
6233     m_commandHistory.push_front(cmd);
6234 
6235     if (m_commandHistory.size() > 50) // to avoid infinite growth
6236         m_commandHistory.pop_back();
6237 }
6238 
GetNextFromCommandHistory()6239 std::string CRobotMain::GetNextFromCommandHistory()
6240 {
6241     if (m_commandHistory.empty() || static_cast<int>(m_commandHistory.size()) <= m_commandHistoryIndex + 1) // no next element
6242         return "";
6243     return m_commandHistory[++m_commandHistoryIndex];
6244 }
6245 
GetPreviousFromCommandHistory()6246 std::string CRobotMain::GetPreviousFromCommandHistory()
6247 {
6248     if (m_commandHistory.empty() || m_commandHistoryIndex < 1) // first or none element selected
6249         return "";
6250     return m_commandHistory[--m_commandHistoryIndex];
6251 }
6252 
GetScoreboard()6253 CScoreboard* CRobotMain::GetScoreboard()
6254 {
6255     return m_scoreboard.get();
6256 }
6257 
GetAllTeams()6258 std::set<int> CRobotMain::GetAllTeams()
6259 {
6260     std::set<int> teams = GetAllActiveTeams();
6261     for(auto& it : m_teamFinished)
6262     {
6263         teams.insert(it.first);
6264     }
6265     return teams;
6266 }
6267 
GetAllActiveTeams()6268 std::set<int> CRobotMain::GetAllActiveTeams()
6269 {
6270     std::set<int> teams;
6271     for (CObject* obj : m_objMan->GetAllObjects())
6272     {
6273         int team = obj->GetTeam();
6274         if (team == 0) continue;
6275         teams.insert(team);
6276     }
6277     return teams;
6278 }
6279