/* * This file is part of the Colobot: Gold Edition source code * Copyright (C) 2001-2021, Daniel Roux, EPSITEC SA & TerranovaTeam * http://epsitec.ch; http://colobot.info; http://github.com/colobot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://gnu.org/licenses */ #include "level/robotmain.h" #include "CBot/CBot.h" #include "app/app.h" #include "app/input.h" #include "app/pausemanager.h" #include "common/config_file.h" #include "common/event.h" #include "common/logger.h" #include "common/restext.h" #include "common/settings.h" #include "common/stringutils.h" #include "common/resources/inputstream.h" #include "common/resources/outputstream.h" #include "common/resources/resourcemanager.h" #include "graphics/core/material.h" #include "graphics/engine/camera.h" #include "graphics/engine/cloud.h" #include "graphics/engine/engine.h" #include "graphics/engine/lightman.h" #include "graphics/engine/lightning.h" #include "graphics/engine/oldmodelmanager.h" #include "graphics/engine/particle.h" #include "graphics/engine/planet.h" #include "graphics/engine/pyro_manager.h" #include "graphics/engine/terrain.h" #include "graphics/engine/text.h" #include "graphics/engine/water.h" #include "graphics/model/model_manager.h" #include "level/mainmovie.h" #include "level/player_profile.h" #include "level/scene_conditions.h" #include "level/scoreboard.h" #include "level/parser/parser.h" #include "math/const.h" #include "math/func.h" #include "math/geometry.h" #include "object/object.h" #include "object/object_create_exception.h" #include "object/object_manager.h" #include "object/auto/auto.h" #include "object/interface/slotted_object.h" #include "object/motion/motion.h" #include "object/motion/motionhuman.h" #include "object/motion/motiontoto.h" #include "object/subclass/exchange_post.h" #include "object/task/task.h" #include "object/task/taskbuild.h" #include "object/task/taskmanip.h" #include "physics/physics.h" #include "script/cbottoken.h" #include "script/script.h" #include "script/scriptfunc.h" #include "sound/sound.h" #include "ui/debug_menu.h" #include "ui/displayinfo.h" #include "ui/displaytext.h" #include "ui/maindialog.h" #include "ui/mainmap.h" #include "ui/mainshort.h" #include "ui/mainui.h" #include "ui/controls/button.h" #include "ui/controls/edit.h" #include "ui/controls/group.h" #include "ui/controls/interface.h" #include "ui/controls/label.h" #include "ui/controls/map.h" #include "ui/controls/shortcut.h" #include "ui/controls/slider.h" #include "ui/controls/window.h" #include "ui/screen/screen_loading.h" #include #include #include #include #include // Global variables. const float UNIT = 4.0f; // default for g_unit float g_unit; // conversion factor // Min/max values for the game speed. const float MIN_SPEED = 1/8.0f; const float MAX_SPEED = 256.0f; // Reference colors used when recoloring textures, see ChangeColor() const Gfx::Color COLOR_REF_BOT = Gfx::Color( 10.0f/256.0f, 166.0f/256.0f, 254.0f/256.0f); // blue const Gfx::Color COLOR_REF_ALIEN = Gfx::Color(135.0f/256.0f, 170.0f/256.0f, 13.0f/256.0f); // green const Gfx::Color COLOR_REF_GREEN = Gfx::Color(135.0f/256.0f, 170.0f/256.0f, 13.0f/256.0f); // green const Gfx::Color COLOR_REF_WATER = Gfx::Color( 25.0f/256.0f, 255.0f/256.0f, 240.0f/256.0f); // cyan //! Constructor of robot application CRobotMain::CRobotMain() { m_app = CApplication::GetInstancePointer(); m_eventQueue = m_app->GetEventQueue(); m_sound = m_app->GetSound(); m_engine = Gfx::CEngine::GetInstancePointer(); m_oldModelManager = m_engine->GetModelManager(); m_lightMan = m_engine->GetLightManager(); m_particle = m_engine->GetParticle(); m_water = m_engine->GetWater(); m_cloud = m_engine->GetCloud(); m_lightning = m_engine->GetLightning(); m_planet = m_engine->GetPlanet(); m_input = CInput::GetInstancePointer(); m_modelManager = std::make_unique(); m_settings = std::make_unique(); m_pause = std::make_unique(); m_interface = std::make_unique(); m_terrain = std::make_unique(); m_camera = std::make_unique(); m_displayText = std::make_unique(); m_movie = std::make_unique(); m_ui = std::make_unique(); m_short = std::make_unique(); m_map = std::make_unique(); m_objMan = std::make_unique( m_engine, m_terrain.get(), m_oldModelManager, m_modelManager.get(), m_particle); m_debugMenu = std::make_unique(this, m_engine, m_objMan.get(), m_sound); m_time = 0.0f; m_gameTime = 0.0f; m_gameTimeAbsolute = 0.0f; m_levelCategory = LevelCategory::Exercises; m_levelChap = 0; m_levelRank = 0; m_sceneReadPath = ""; m_missionTimerEnabled = false; m_missionTimerStarted = false; m_missionTimer = 0.0f; m_phase = PHASE_PLAYER_SELECT; m_visitLast = EVENT_NULL; m_visitObject = nullptr; m_visitArrow = nullptr; m_audioTrack = ""; m_audioRepeat = true; m_satcomTrack = ""; m_satcomRepeat = true; m_editorTrack = ""; m_editorRepeat = true; m_selectObject = nullptr; m_infoUsed = 0; m_controller = nullptr; m_missionType = MISSION_NORMAL; m_immediatSatCom = false; m_beginSatCom = false; m_lockedSatCom = false; m_movieLock = false; m_satComLock = false; m_editLock = false; m_editFull = false; m_hilite = false; m_cheatSelectInsect = false; m_cheatShowSoluce = false; m_codeBattleInit = false; m_codeBattleStarted = false; m_teamNames.clear(); #if DEV_BUILD m_cheatAllMission = true; // for development #else m_cheatAllMission = false; #endif m_cheatRadar = false; m_fixScene = false; m_cheatTrainerPilot = false; m_friendAim = false; m_resetCreate = false; m_shortCut = true; m_commandHistoryIndex = -1; m_movieInfoIndex = -1; m_tooltipPos = { 0.0f, 0.0f }; m_tooltipName.clear(); m_tooltipTime = 0.0f; m_winTerminate = false; m_globalMagnifyDamage = 1.0f; m_autosave = true; m_autosaveInterval = 5; m_autosaveSlots = 3; m_autosaveLast = 0.0f; m_shotSaving = 0; m_build = 0; m_researchDone.clear(); // no research done m_researchDone[0] = 0; m_researchEnable = 0; g_unit = UNIT; for (int i = 0; i < MAXSHOWLIMIT; i++) { m_showLimit[i].used = false; m_showLimit[i].total = 0; m_showLimit[i].link = nullptr; } m_debugCrashSpheres = false; m_engine->SetTerrain(m_terrain.get()); m_app->SetMouseMode(MOUSE_ENGINE); m_movie->Flush(); FlushDisplayInfo(); InitEye(); m_engine->SetTracePrecision(1.0f); m_settings->LoadSettings(); m_settings->SaveSettings(); m_settings->SaveResolutionSettings(m_app->GetVideoConfig()); SelectPlayer(CPlayerProfile::GetLastName()); CScriptFunctions::Init(); } //! Destructor of robot application CRobotMain::~CRobotMain() { } Gfx::CCamera* CRobotMain::GetCamera() { return m_camera.get(); } Gfx::CTerrain* CRobotMain::GetTerrain() { return m_terrain.get(); } Ui::CInterface* CRobotMain::GetInterface() { return m_interface.get(); } Ui::CDisplayText* CRobotMain::GetDisplayText() { return m_displayText.get(); } CPauseManager* CRobotMain::GetPauseManager() { return m_pause.get(); } std::string PhaseToString(Phase phase) { if (phase == PHASE_WELCOME1) return "PHASE_WELCOME1"; if (phase == PHASE_WELCOME2) return "PHASE_WELCOME2"; if (phase == PHASE_WELCOME3) return "PHASE_WELCOME3"; if (phase == PHASE_PLAYER_SELECT) return "PHASE_PLAYER_SELECT"; if (phase == PHASE_APPEARANCE) return "PHASE_APPEARANCE"; if (phase == PHASE_MAIN_MENU) return "PHASE_MAIN_MENU"; if (phase == PHASE_LEVEL_LIST) return "PHASE_LEVEL_LIST"; if (phase == PHASE_MOD_LIST) return "PHASE_MOD_LIST"; if (phase == PHASE_SIMUL) return "PHASE_SIMUL"; if (phase == PHASE_SETUPd) return "PHASE_SETUPd"; if (phase == PHASE_SETUPg) return "PHASE_SETUPg"; if (phase == PHASE_SETUPp) return "PHASE_SETUPp"; if (phase == PHASE_SETUPc) return "PHASE_SETUPc"; if (phase == PHASE_SETUPs) return "PHASE_SETUPs"; if (phase == PHASE_SETUPds) return "PHASE_SETUPds"; if (phase == PHASE_SETUPgs) return "PHASE_SETUPgs"; if (phase == PHASE_SETUPps) return "PHASE_SETUPps"; if (phase == PHASE_SETUPcs) return "PHASE_SETUPcs"; if (phase == PHASE_SETUPss) return "PHASE_SETUPss"; if (phase == PHASE_WRITEs) return "PHASE_WRITEs"; if (phase == PHASE_READ) return "PHASE_READ"; if (phase == PHASE_READs) return "PHASE_READs"; if (phase == PHASE_WIN) return "PHASE_WIN"; if (phase == PHASE_LOST) return "PHASE_LOST"; if (phase == PHASE_QUIT_SCREEN) return "PHASE_QUIT_SCREEN"; if (phase == PHASE_SATCOM) return "PHASE_SATCOM"; return "(unknown)"; } bool IsInSimulationConfigPhase(Phase phase) { return (phase >= PHASE_SETUPds && phase <= PHASE_SETUPss) || phase == PHASE_READs || phase == PHASE_WRITEs; } bool IsPhaseWithWorld(Phase phase) { if (phase == PHASE_SIMUL ) return true; if (phase == PHASE_WIN ) return true; if (phase == PHASE_LOST ) return true; if (phase == PHASE_APPEARANCE) return true; if (IsInSimulationConfigPhase(phase)) return true; return false; } bool IsMainMenuPhase(Phase phase) { if (phase == PHASE_APPEARANCE) return true; return !IsPhaseWithWorld(phase); } //! Changes phase void CRobotMain::ChangePhase(Phase phase) { bool resetWorld = false; if ((IsPhaseWithWorld(m_phase) || IsPhaseWithWorld(phase)) && !IsInSimulationConfigPhase(m_phase) && !IsInSimulationConfigPhase(phase)) { if (IsPhaseWithWorld(m_phase) && !IsPhaseWithWorld(phase) && m_exitAfterMission) { GetLogger()->Info("Mission finished in single mission mode, exiting\n"); m_eventQueue->AddEvent(Event(EVENT_QUIT)); return; } GetLogger()->Info("Reseting world on phase change...\n"); resetWorld = true; } if (resetWorld) { m_missionTimerEnabled = m_missionTimerStarted = false; m_missionTimer = 0.0f; if (m_phase == PHASE_SIMUL) // ends a simulation? { SaveAllScript(); m_sound->StopMusic(0.0f); m_camera->SetControllingObject(nullptr); if (m_gameTime > 10.0f) // did you play at least 10 seconds? { m_playerProfile->IncrementLevelTryCount(m_levelCategory, m_levelChap, m_levelRank); } if (m_userPause != nullptr) { m_pause->DeactivatePause(m_userPause); m_userPause = nullptr; } } if (phase == PHASE_WIN) // wins a simulation? { m_playerProfile->SetLevelPassed(m_levelCategory, m_levelChap, m_levelRank, true); m_ui->NextMission(); // passes to the next mission } DeleteAllObjects(); // removes all the current 3D Scene } m_phase = phase; if (m_phase != PHASE_SIMUL) { Ui::CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW6)); if ( pw != nullptr ) pw->ClearState(Ui::STATE_VISIBLE | Ui::STATE_ENABLE); } if (resetWorld) { m_winDelay = 0.0f; m_lostDelay = 0.0f; m_beginSatCom = false; m_movieLock = false; m_satComLock = false; m_editLock = false; m_resetCreate = false; m_infoObject = nullptr; m_pause->FlushPause(); m_freePhotoPause = nullptr; m_userPause = nullptr; m_focusPause = nullptr; FlushDisplayInfo(); m_engine->SetRankView(0); m_terrain->FlushRelief(); m_engine->DeleteAllObjects(); m_oldModelManager->DeleteAllModelCopies(); m_engine->SetWaterAddColor(Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f)); m_engine->SetBackground(""); m_engine->SetBackForce(false); m_engine->SetForegroundName(""); m_engine->SetOverColor(Gfx::Color(), Gfx::TransparencyMode::BLACK); m_engine->DeleteGroundMark(0); SetSpeed(1.0f); m_terrain->SetWind(glm::vec3(0.0f, 0.0f, 0.0f)); m_terrain->FlushBuildingLevel(); m_terrain->FlushFlyingLimit(); m_lightMan->FlushLights(); m_particle->FlushParticle(); m_water->Flush(); m_cloud->Flush(); m_lightning->Flush(); m_planet->Flush(); m_interface->Flush(); m_newScriptName.clear(); m_sound->SetListener(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); m_sound->StopAll(); m_camera->SetType(Gfx::CAM_TYPE_NULL); m_movie->Flush(); m_movieInfoIndex = -1; m_shortCut = true; m_viewpoints.clear(); } ClearInterface(); glm::vec2 dim, pos; // Creates and hide the command console. dim.x = 200.0f/640.0f; dim.y = 18.0f/480.0f; pos.x = 20.0f/640.0f; pos.y = 100.0f/480.0f; Ui::CEdit* pe = m_interface->CreateEdit(pos, dim, 0, EVENT_CMD); pe->ClearState(Ui::STATE_VISIBLE); pe->SetMaxChar(100); m_cmdEdit = false; // hidden for now // Creates the speedometer. dim.x = 30.0f/640.0f; dim.y = 20.0f/480.0f; pos.x = 4.0f/640.0f; pos.y = 426.0f/480.0f; // Creates the save indicator Ui::CButton* pb = m_interface->CreateButton(pos, dim, 0, EVENT_SPEED); pb->SetState(Ui::STATE_SIMPLY); pb->ClearState(Ui::STATE_VISIBLE); if (m_phase == PHASE_PLAYER_SELECT) { if (CResourceManager::DirectoryExists("crashsave")) { GetLogger()->Info("Pre-crash save found!\n"); m_ui->GetDialog()->StartQuestion( "Your game seems to have crashed. Do you want to restore pre-crash state?", false, false, false, [&]() { GetLogger()->Info("Trying to restore pre-crash state...\n"); assert(m_playerProfile != nullptr); m_playerProfile->LoadScene("../../crashsave"); CResourceManager::RemoveExistingDirectory("crashsave"); }, [&]() { GetLogger()->Info("Not restoring pre-crash state\n"); CResourceManager::RemoveExistingDirectory("crashsave"); } ); } } m_ui->ChangePhase(m_phase); if (m_phase == PHASE_SATCOM) { m_interface->DeleteControl(EVENT_WINDOW5); StartDisplayInfo(InjectLevelPathsForCurrentLevel("cbot.txt", "help/%lng%"), 0); } if (!resetWorld) return; dim.x = 32.0f/640.0f; dim.y = 32.0f/480.0f; float ox = 3.0f/640.0f; float oy = 3.0f/480.0f; float sx = (32.0f+2.0f)/640.0f; float sy = (32.0f+2.0f)/480.0f; if (m_phase != PHASE_APPEARANCE) { m_engine->SetDrawWorld(true); m_engine->SetDrawFront(false); m_fixScene = false; } if (m_phase == PHASE_SIMUL) { bool loading = !m_sceneReadPath.empty(); m_ui->ShowLoadingScreen(true); m_ui->GetLoadingScreen()->SetProgress(0.0f, RT_LOADING_INIT); m_map->CreateMap(); m_map->ShowMap(false); try { CreateScene(m_ui->GetSceneSoluce(), false, false); // interactive scene if (m_mapImage) m_map->SetFixImage(m_mapFilename); m_app->ResetTimeAfterLoading(); m_sound->StopMusic(0.0f); if (m_base == nullptr || loading) StartMusic(); if (m_immediatSatCom && !loading && m_infoFilename[SATCOM_HUSTON][0] != 0) StartDisplayInfo(SATCOM_HUSTON, false); // shows the instructions } catch (const std::runtime_error& e) { LevelLoadingError("An error occurred while trying to load a level", e); } } if (m_phase == PHASE_WIN) { m_sound->StopAll(); if (m_endingWin.empty()) { ChangePhase(PHASE_LEVEL_LIST); } else { m_winTerminate = (m_endingWin.substr(m_endingWin.find_last_of("/")+1) == "win904.txt"); m_levelFile = m_endingWin; try { CreateScene(false, true, false); // sets scene pos.x = ox+sx*1; pos.y = oy+sy*1; glm::vec2 ddim; ddim.x = dim.x*2; ddim.y = dim.y*2; m_interface->CreateButton(pos, ddim, 16, EVENT_BUTTON_OK); if (m_winTerminate) { pos.x = ox+sx*3; pos.y = oy+sy*0.2f; ddim.x = dim.x*15; ddim.y = dim.y*3.0f; pe = m_interface->CreateEdit(pos, ddim, 0, EVENT_EDIT0); pe->SetGenericMode(true); pe->SetFontType(Gfx::FONT_COMMON); pe->SetEditCap(false); pe->SetHighlightCap(false); pe->ReadText(std::string("help/") + m_app->GetLanguageChar() + std::string("/win.txt")); } else { m_displayText->DisplayError(INFO_WIN, glm::vec3(0.0f,0.0f,0.0f), 15.0f, 60.0f, 1000.0f); } StartMusic(); } catch (const std::runtime_error& e) { LevelLoadingError("An error occurred while trying to load win scene", e); } } } if (m_phase == PHASE_LOST) { m_sound->StopAll(); if (m_endingLost.empty()) { ChangePhase(PHASE_LEVEL_LIST); } else { m_winTerminate = false; m_levelFile = m_endingLost; try { CreateScene(false, true, false); // sets scene pos.x = ox+sx*1; pos.y = oy+sy*1; glm::vec2 ddim; ddim.x = dim.x*2; ddim.y = dim.y*2; m_interface->CreateButton(pos, ddim, 16, EVENT_BUTTON_OK); m_displayText->DisplayError(INFO_LOST, glm::vec3(0.0f,0.0f,0.0f), 15.0f, 60.0f, 1000.0f); StartMusic(); } catch (const std::runtime_error& e) { LevelLoadingError("An error occurred while trying to load lost scene", e); } } } m_engine->LoadAllTextures(); } Phase CRobotMain::GetPhase() { return m_phase; } //! Processes an event bool CRobotMain::ProcessEvent(Event &event) { if (!m_ui->EventProcess(event)) return false; if (m_phase == PHASE_SIMUL) { if (!m_editFull) m_camera->EventProcess(event); } if (!m_debugMenu->EventProcess(event)) return false; if (event.type == EVENT_FRAME) { if (!m_movie->EventProcess(event)) // end of the movie? { MainMovieType type = m_movie->GetStopType(); if (type == MM_SATCOMopen) { m_pause->DeactivatePause(m_satcomMoviePause); m_satcomMoviePause = nullptr; SelectObject(m_infoObject, false); // hands over the command buttons m_map->ShowMap(m_mapShow); m_displayText->HideText(false); int i = m_movieInfoIndex; StartDisplayInfo(m_movieInfoIndex, false); m_movieInfoIndex = i; } } m_displayText->EventProcess(event); if (m_displayInfo != nullptr) // current edition? m_displayInfo->EventProcess(event); UpdateInfoText(); return EventFrame(event); } if (event.type == EVENT_RELOAD_TEXTURES) { if (IsPhaseWithWorld(m_phase)) { ChangeColor(); UpdateMap(); } m_engine->LoadAllTextures(); } if (event.type == EVENT_RESOLUTION_CHANGED) { // Recreate the interface (needed if the aspect ratio changes) // TODO: This can sometimes cause unwanted side effects, like hidden windows reappearing. To be fixed during CEGUI refactoring. m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE)); CreateShortcuts(); } if (event.type == EVENT_FOCUS_LOST) { GetLogger()->Trace("Window unfocused\n"); if (m_settings->GetFocusLostPause()) { m_focusPause = m_pause->ActivatePause(PAUSE_ENGINE); } if (m_settings->GetFocusLostMute()) { m_sound->SetAudioVolume(0); m_sound->SetMusicVolume(0); } return false; } if (event.type == EVENT_FOCUS_GAINED) { GetLogger()->Trace("Window focused\n"); if (m_focusPause != nullptr) { m_pause->DeactivatePause(m_focusPause); m_focusPause = nullptr; } if (m_settings->GetFocusLostMute()) { int volume; // Set music volume if (GetConfigFile().GetIntProperty("Setup", "MusicVolume", volume)) { m_sound->SetMusicVolume(volume); } else { m_sound->SetMusicVolume(MAXVOLUME*3/4); } // Set audio volume if (GetConfigFile().GetIntProperty("Setup", "AudioVolume", volume)) { m_sound->SetAudioVolume(volume); } else { m_sound->SetAudioVolume(MAXVOLUME); } } return false; } if (event.type == EVENT_WRITE_SCENE_FINISHED) { IOWriteSceneFinished(); return false; } if (event.type == EVENT_UPDINTERFACE) { if (m_missionType == MISSION_CODE_BATTLE && !m_codeBattleStarted) { CreateCodeBattleInterface(); } } if (event.type == EVENT_CODE_BATTLE_START) { m_pause->DeactivatePause(m_userPause); m_userPause = nullptr; } if (event.type == EVENT_CODE_BATTLE_SPECTATOR) { SetCodeBattleSpectatorMode(!m_codeBattleSpectator); } if (event.type >= EVENT_VIEWPOINT0 && event.type <= EVENT_VIEWPOINT9) { m_camera->SetType(Gfx::CAM_TYPE_SCRIPT); m_camera->SetSmooth(Gfx::CAM_SMOOTH_HARD); m_camera->SetScriptCameraAnimate(m_viewpoints[event.type - EVENT_VIEWPOINT0].eye, m_viewpoints[event.type - EVENT_VIEWPOINT0].look); } // Management of the console. if (event.type == EVENT_KEY_DOWN) { auto data = event.GetData(); if (data->slot == INPUT_SLOT_CMDLINE) { if (m_phase != PHASE_PLAYER_SELECT && !m_movie->IsExist() && !m_movieLock && !m_editLock && !m_cmdEdit) { Ui::CEdit* pe = static_cast(m_interface->SearchControl(EVENT_CMD)); if (pe == nullptr) return false; pe->SetState(Ui::STATE_VISIBLE); m_interface->SetFocus(pe); if (m_phase == PHASE_SIMUL) m_cmdEditPause = m_pause->ActivatePause(PAUSE_ENGINE); m_cmdEdit = true; m_commandHistoryIndex = -1; // no element selected in command history } return false; } if (IsPhaseWithWorld(m_phase)) { if (data->key == KEY(F10)) { m_debugMenu->ToggleInterface(); return false; } } } // Browse forward command history with UP key if (event.type == EVENT_KEY_DOWN && event.GetData()->key == KEY(UP) && m_cmdEdit) { Ui::CEdit* pe = static_cast(m_interface->SearchControl(EVENT_CMD)); if (pe == nullptr) return false; std::string cmd = GetNextFromCommandHistory(); if (!cmd.empty()) pe->SetText(cmd); return false; } // Browse backward command history with DOWN key if (event.type == EVENT_KEY_DOWN && event.GetData()->key == KEY(DOWN) && m_cmdEdit) { Ui::CEdit* pe = static_cast(m_interface->SearchControl(EVENT_CMD)); if (pe == nullptr) return false; std::string cmd = GetPreviousFromCommandHistory(); if (!cmd.empty()) pe->SetText(cmd); return false; } if (event.type == EVENT_KEY_DOWN && event.GetData()->key == KEY(RETURN) && m_cmdEdit) { std::string cmd; Ui::CEdit* pe = static_cast(m_interface->SearchControl(EVENT_CMD)); if (pe == nullptr) return false; cmd = pe->GetText(50); pe->SetText(""); pe->ClearState(Ui::STATE_VISIBLE); m_interface->SetFocus(nullptr); if (m_phase == PHASE_SIMUL) { m_pause->DeactivatePause(m_cmdEditPause); m_cmdEditPause = nullptr; } ExecuteCmd(cmd); PushToCommandHistory(cmd); m_cmdEdit = false; return false; } if (event.type == EVENT_KEY_DOWN && m_cmdEdit) return false; // cheat console active, so ignore keys // Management of the speed change. if (event.type == EVENT_SPEED) SetSpeed(1.0f); if (!m_displayText->EventProcess(event)) return false; if (event.type == EVENT_MOUSE_MOVE) { HiliteObject(event.mousePos); } if (m_displayInfo != nullptr) // current info? { m_displayInfo->EventProcess(event); if (event.type == EVENT_KEY_DOWN) { auto data = event.GetData(); if (data->slot == INPUT_SLOT_HELP || data->slot == INPUT_SLOT_PROG || data->key == KEY(ESCAPE)) { StopDisplayInfo(); } } if (event.type == EVENT_OBJECT_INFOOK) StopDisplayInfo(); if (m_displayInfo == nullptr && m_phase == PHASE_SATCOM) ChangePhase(PHASE_MAIN_MENU); return false; } CObject* obj; // Simulation phase of the game if (m_phase == PHASE_SIMUL) { switch (event.type) { case EVENT_KEY_DOWN: { auto data = event.GetData(); HiliteClear(); if (m_editLock) // current edition? { if (data->slot == INPUT_SLOT_HELP) { StartDisplayInfo(SATCOM_HUSTON, false); return false; } if (data->slot == INPUT_SLOT_PROG) { StartDisplayInfo(SATCOM_PROG, false); return false; } break; } if (m_movieLock) // current movie? { if (data->slot == INPUT_SLOT_QUIT || data->key == KEY(ESCAPE)) { AbortMovie(); } return false; } if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT) { if (data->slot == INPUT_SLOT_VISIT) { StartDisplayVisit(EVENT_NULL); } if (data->slot == INPUT_SLOT_QUIT || data->key == KEY(ESCAPE)) { StopDisplayVisit(); } return false; } if (data->slot == INPUT_SLOT_QUIT) { if (m_movie->IsExist()) StartDisplayInfo(SATCOM_HUSTON, false); else if (m_winDelay > 0.0f) ChangePhase(PHASE_WIN); else if (m_lostDelay > 0.0f) ChangePhase(PHASE_LOST); else if (!m_cmdEdit) m_ui->GetDialog()->StartPauseMenu(); // do you want to leave? } if (data->slot == INPUT_SLOT_PAUSE) { if (m_userPause == nullptr) { if (!m_pause->IsPauseType(PAUSE_ENGINE)) { m_userPause = m_pause->ActivatePause(PAUSE_ENGINE); } } else { m_pause->DeactivatePause(m_userPause); m_userPause = nullptr; } } if (data->slot == INPUT_SLOT_CAMERA) { ChangeCamera(); } if (data->slot == INPUT_SLOT_DESEL) { if (m_shortCut) DeselectObject(); } if (data->slot == INPUT_SLOT_HUMAN) { SelectObject(SearchHuman()); } if (data->slot == INPUT_SLOT_NEXT && ((event.kmodState & KEY_MOD(CTRL)) != 0)) { m_short->SelectShortcut(EVENT_OBJECT_SHORTCUT_MODE); // switch bots <-> buildings return false; } if (data->slot == INPUT_SLOT_NEXT) { if (m_shortCut) m_short->SelectNext(); } if (data->slot == INPUT_SLOT_HELP) { StartDisplayInfo(SATCOM_HUSTON, true); } if (data->slot == INPUT_SLOT_PROG) { StartDisplayInfo(SATCOM_PROG, true); } if (data->slot == INPUT_SLOT_VISIT) { StartDisplayVisit(EVENT_NULL); } if (data->slot == INPUT_SLOT_SPEED_DEC) { SetSpeed(GetSpeed()*0.5f); } if (data->slot == INPUT_SLOT_SPEED_RESET) { SetSpeed(1.0f); } if (data->slot == INPUT_SLOT_SPEED_INC) { SetSpeed(GetSpeed()*2.0f); } if (data->slot == INPUT_SLOT_QUICKSAVE) { QuickSave(); } if (data->slot == INPUT_SLOT_QUICKLOAD) { QuickLoad(); } if (data->key == KEY(c) && ((event.kmodState & KEY_MOD(CTRL)) != 0) && m_engine->GetShowStats()) { CObject* obj = GetSelect(); if (obj != nullptr) { CLevelParserLine line("CreateObject"); line.AddParam("type", std::make_unique(obj->GetType())); glm::vec3 pos = obj->GetPosition()/g_unit; pos.y = 0.0f; line.AddParam("pos", std::make_unique(pos)); float dir = Math::NormAngle(obj->GetRotationY()) / Math::PI; line.AddParam("dir", std::make_unique(dir)); std::stringstream ss; ss << line; SDL_SetClipboardText(ss.str().c_str()); } } break; } case EVENT_MOUSE_BUTTON_DOWN: { if (event.GetData()->button != MOUSE_BUTTON_LEFT) // only left mouse button break; obj = DetectObject(event.mousePos); if (!m_shortCut) obj = nullptr; if (obj != nullptr && obj->GetType() == OBJECT_TOTO) { if (m_displayInfo != nullptr) // current info? { StopDisplayInfo(); } else { if (!m_editLock) StartDisplayInfo(SATCOM_HUSTON, true); } } else { SelectObject(obj); } break; } case EVENT_OBJECT_LIMIT: StartShowLimit(); break; case EVENT_OBJECT_DESELECT: if (m_shortCut) DeselectObject(); break; case EVENT_OBJECT_HELP: HelpObject(); break; case EVENT_OBJECT_CAMERA: ChangeCamera(); break; case EVENT_OBJECT_DELETE: m_ui->GetDialog()->StartQuestion( RT_DIALOG_DELOBJ, true, false, false, [&]() { DestroySelectedObject(); } ); break; case EVENT_OBJECT_BHELP: StartDisplayInfo(SATCOM_HUSTON, true); break; case EVENT_OBJECT_SOLUCE: StartDisplayInfo(SATCOM_SOLUCE, true); break; case EVENT_OBJECT_MAPZOOM: m_map->ZoomMap(); break; case EVENT_DT_VISIT0: case EVENT_DT_VISIT1: case EVENT_DT_VISIT2: case EVENT_DT_VISIT3: case EVENT_DT_VISIT4: StartDisplayVisit(event.type); break; case EVENT_DT_END: StopDisplayVisit(); break; case EVENT_OBJECT_MOVIELOCK: AbortMovie(); break; case EVENT_WIN: m_missionTimerEnabled = m_missionTimerStarted = false; ChangePhase(PHASE_WIN); break; case EVENT_LOST: m_missionTimerEnabled = m_missionTimerStarted = false; ChangePhase(PHASE_LOST); break; default: break; } if (event.type >= EVENT_OBJECT_SHORTCUT_MODE && event.type <= EVENT_OBJECT_SHORTCUT_MAX) { m_short->SelectShortcut(event.type); } EventObject(event); return false; } if (m_phase == PHASE_APPEARANCE) EventObject(event); if (m_phase == PHASE_WIN || m_phase == PHASE_LOST) { EventObject(event); switch (event.type) { case EVENT_KEY_DOWN: { auto data = event.GetData(); if (data->key == KEY(ESCAPE) || data->key == KEY(RETURN)) { if (m_winTerminate) ChangePhase(PHASE_MAIN_MENU); else ChangePhase(PHASE_LEVEL_LIST); } break; } case EVENT_BUTTON_OK: if (m_winTerminate) ChangePhase(PHASE_MAIN_MENU); else ChangePhase(PHASE_LEVEL_LIST); break; default: break; } } return true; } //! Executes a command void CRobotMain::ExecuteCmd(const std::string& cmd) { if (cmd.empty()) return; if (m_phase == PHASE_SIMUL) { if (cmd == "winmission") m_eventQueue->AddEvent(Event(EVENT_WIN)); if (cmd == "lostmission") m_eventQueue->AddEvent(Event(EVENT_LOST)); if (cmd == "trainerpilot") { m_cheatTrainerPilot = !m_cheatTrainerPilot; return; } if (cmd == "fly") { m_researchDone[0] |= RESEARCH_FLY; m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE)); return; } if (cmd == "allresearch") { m_researchDone[0] = -1; // all research are done m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE)); return; } if (cmd == "allbuildings") { m_build = -1; // all buildings are available m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE)); return; } if (cmd == "all") { m_researchDone[0] = -1; // all research are done m_build = -1; // all buildings are available m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE)); return; } if (cmd == "nolimit") { m_terrain->SetFlyingMaxHeight(280.0f); return; } if (cmd == "controller") { if (m_controller == nullptr) { GetLogger()->Error("No LevelController on the map to select\n"); return; } // Don't use SelectObject because it checks if the object is selectable if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT) StopDisplayVisit(); CObject* prev = DeselectAll(); if (prev != nullptr && prev != m_controller) PushToSelectionHistory(prev); SelectOneObject(m_controller, true); m_short->UpdateShortcuts(); return; } if (cmd == "photo1") { if (m_freePhotoPause == nullptr) { m_camera->SetType(Gfx::CAM_TYPE_FREE); m_freePhotoPause = m_pause->ActivatePause(PAUSE_ENGINE|PAUSE_PHOTO|PAUSE_OBJECT_UPDATES); } else { m_camera->SetType(Gfx::CAM_TYPE_BACK); m_pause->DeactivatePause(m_freePhotoPause); m_freePhotoPause = nullptr; } return; } if (cmd == "photo2") { if (m_freePhotoPause == nullptr) { m_camera->SetType(Gfx::CAM_TYPE_FREE); DeselectAll(); // removes the control buttons m_freePhotoPause = m_pause->ActivatePause(PAUSE_ENGINE|PAUSE_PHOTO|PAUSE_OBJECT_UPDATES); m_map->ShowMap(false); m_displayText->HideText(true); } else { m_camera->SetType(Gfx::CAM_TYPE_BACK); m_pause->DeactivatePause(m_freePhotoPause); m_freePhotoPause = nullptr; m_map->ShowMap(m_mapShow); m_displayText->HideText(false); } return; } int camtype; if (sscanf(cmd.c_str(), "camtype %d", &camtype) > 0) { m_camera->SetType(static_cast(camtype)); return; } float camspeed; if (sscanf(cmd.c_str(), "camspeed %f", &camspeed) > 0) { m_camera->SetCameraSpeed(camspeed); return; } if (cmd == "freecam") { m_camera->SetType(Gfx::CAM_TYPE_FREE); return; } if (cmd == "noclip") { CObject* object = GetSelect(); if (object != nullptr) object->SetCollisions(false); return; } if (cmd == "clip") { CObject* object = GetSelect(); if (object != nullptr) object->SetCollisions(true); return; } if (cmd == "addhusky") { CObject* object = GetSelect(); if (object != nullptr && object->Implements(ObjectInterfaceType::Shielded)) dynamic_cast(*object).SetMagnifyDamage(dynamic_cast(*object).GetMagnifyDamage()*0.1f); return; } if (cmd == "addfreezer") { CObject* object = GetSelect(); if (object != nullptr && object->Implements(ObjectInterfaceType::JetFlying)) dynamic_cast(*object).SetRange(dynamic_cast(*object).GetRange()*10.0f); return; } if (cmd == "\155\157\157") { // VGhpcyBpcyBlYXN0ZXItZWdnIGFuZCBzbyBpdCBzaG91bGQgYmUgb2JmdXNjYXRlZCEgRG8gbm90 // IGNsZWFuLXVwIHRoaXMgY29kZSEK GetLogger()->Info(" _________________________\n"); GetLogger()->Info("< \x50\x6F\x6C\x73\x6B\x69 \x50\x6F\x72\x74\x61\x6C C\x6F\x6C\x6F\x62\x6F\x74\x61! \x3E\n"); GetLogger()->Info(" -------------------------\n"); GetLogger()->Info(" \x5C\x20\x20\x20\x5E\x5F\x5F\x5E\n"); GetLogger()->Info(" \x20\x5C\x20\x20\x28\x6F\x6F\x29\x5C\x5F\x5F\x5F\x5F\x5F\x5F\x5F\n"); GetLogger()->Info(" \x28\x5F\x5F\x29\x5C \x20\x20\x20\x20\x29\x5C\x2F\x5C\n"); GetLogger()->Info(" \x20\x20\x20\x20\x7C|\x2D\x2D\x2D\x2D\x77\x20\x7C\n"); GetLogger()->Info(" \x20\x20 \x7C\x7C\x20\x20\x20\x20 ||\n"); } if (cmd == "fullpower") { CObject* object = GetSelect(); if (object != nullptr) { if (CPowerContainerObject *power = GetObjectPowerCell(object)) power->SetEnergyLevel(1.0f); if (object->Implements(ObjectInterfaceType::Shielded)) dynamic_cast(*object).SetShield(1.0f); if (object->Implements(ObjectInterfaceType::JetFlying)) dynamic_cast(*object).SetReactorRange(1.0f); } return; } if (cmd == "fullenergy") { CObject* object = GetSelect(); if (object != nullptr) { if (CPowerContainerObject *power = GetObjectPowerCell(object)) power->SetEnergyLevel(1.0f); } return; } if (cmd == "fullshield") { CObject* object = GetSelect(); if (object != nullptr && object->Implements(ObjectInterfaceType::Shielded)) dynamic_cast(*object).SetShield(1.0f); return; } if (cmd == "fullrange") { CObject* object = GetSelect(); if (object != nullptr) { if (object->Implements(ObjectInterfaceType::JetFlying)) dynamic_cast(*object).SetReactorRange(1.0f); } return; } } if (cmd == "debugmode") { if (m_app->IsDebugModeActive(DEBUG_ALL)) { m_app->SetDebugModeActive(DEBUG_ALL, false); } else { m_app->SetDebugModeActive(DEBUG_ALL, true); } return; } if (cmd == "showstat") { m_engine->SetShowStats(!m_engine->GetShowStats()); return; } if (cmd == "invui") { m_engine->SetRenderInterface(!m_engine->GetRenderInterface()); return; } if (cmd == "selectinsect") { m_cheatSelectInsect = !m_cheatSelectInsect; return; } if (cmd == "showsoluce") { m_cheatShowSoluce = !m_cheatShowSoluce; m_ui->ShowSoluceUpdate(); return; } if (cmd == "allmission") { m_cheatAllMission = !m_cheatAllMission; m_ui->AllMissionUpdate(); return; } if (cmd == "invradar") { m_cheatRadar = !m_cheatRadar; return; } if (cmd.find("triplanar") != std::string::npos) { std::string command, arg; std::istringstream stream(cmd); stream >> command >> arg; if (command == "triplanar_mode") { if (arg == "1" || arg == "on") { m_engine->SetTriplanarMode(true); return; } if (arg == "0" || arg == "off") { m_engine->SetTriplanarMode(false); return; } } if (command == "triplanar_scale") { float scale = std::stof(arg); if (scale > 0.01f && scale < 1000.0f) { m_engine->SetTriplanarScale(scale); return; } } } float speed; if (sscanf(cmd.c_str(), "speed %f", &speed) > 0) { SetSpeed(speed); UpdateSpeedLabel(); return; } if (m_phase == PHASE_SIMUL) m_displayText->DisplayError(ERR_CMD, glm::vec3(0.0f,0.0f,0.0f)); } //! Returns the type of current movie MainMovieType CRobotMain::GetMainMovie() { return m_movie->GetType(); } //! Clears the display of instructions void CRobotMain::FlushDisplayInfo() { for (int i = 0; i < SATCOM_MAX; i++) { m_infoFilename[i][0] = 0; } strcpy(m_infoFilename[SATCOM_OBJECT], "objects.txt"); } //! Beginning of the displaying of instructions. //! index: SATCOM_* void CRobotMain::StartDisplayInfo(int index, bool movie) { if (m_cmdEdit || m_satComLock || m_lockedSatCom) return; CObject* obj = GetSelect(); bool human = obj != nullptr && obj->GetType() == OBJECT_HUMAN; if (!m_editLock && movie && !m_movie->IsExist() && human) { assert(obj->Implements(ObjectInterfaceType::Movable)); if (dynamic_cast(*obj).GetMotion()->GetAction() == -1) { m_movieInfoIndex = index; m_movie->Start(MM_SATCOMopen, 2.5f); m_satcomMoviePause = m_pause->ActivatePause(PAUSE_ENGINE|PAUSE_HIDE_SHORTCUTS); m_infoObject = DeselectAll(); // removes the control buttons m_displayText->HideText(true); return; } } if (m_movie->IsExist()) { m_movie->Stop(); m_pause->DeactivatePause(m_satcomMoviePause); m_satcomMoviePause = nullptr; SelectObject(m_infoObject, false); // hands over the command buttons m_displayText->HideText(false); } StartDisplayInfo(m_infoFilename[index], index); } //! Beginning of the displaying of instructions void CRobotMain::StartDisplayInfo(const std::string& filename, int index) { if (m_cmdEdit) return; m_movieInfoIndex = -1; ClearInterface(); // removes setting evidence and tooltip if (!m_editLock) { m_infoObject = DeselectAll(); // removes the control buttons m_displayText->HideText(true); m_sound->MuteAll(true); } bool soluce = m_ui->GetSceneSoluce(); m_displayInfo = std::make_unique(); m_displayInfo->StartDisplayInfo(filename, index, soluce); m_displayInfo->SetPosition(0); } //! End of displaying of instructions void CRobotMain::StopDisplayInfo() { if (m_cmdEdit) return; if (m_movieInfoIndex != -1) // film to read the SatCom? m_movie->Start(MM_SATCOMclose, 2.0f); m_displayInfo->StopDisplayInfo(); m_displayInfo.reset(); if (!m_editLock) { SelectObject(m_infoObject, false); // gives the command buttons m_displayText->HideText(false); m_sound->MuteAll(false); } if (m_infoUsed == 0) m_displayText->ClearText(); // removes message "see SatCom ..." m_infoUsed ++; } //! Returns the name of the text display char* CRobotMain::GetDisplayInfoName(int index) { return m_infoFilename[index]; } //! Beginning of a dialogue during the game void CRobotMain::StartSuspend() { if (m_suspend != nullptr) return; // already suspended if (!IsPhaseWithWorld(m_phase)) return; GetLogger()->Info("Start suspend\n"); m_sound->MuteAll(true); ClearInterface(); m_suspend = m_pause->ActivatePause(PAUSE_ENGINE | PAUSE_HIDE_SHORTCUTS | PAUSE_MUTE_SOUND | PAUSE_CAMERA); m_engine->SetOverFront(false); // over flat behind CreateShortcuts(); m_map->ShowMap(false); m_infoObject = DeselectAll(); // removes the control buttons m_displayText->HideText(true); m_engine->EnablePauseBlur(); } //! End of dialogue during the game void CRobotMain::StopSuspend() { if (m_suspend == nullptr) return; // not suspended GetLogger()->Info("Stop suspend\n"); m_sound->MuteAll(false); ClearInterface(); m_pause->DeactivatePause(m_suspend); m_suspend = nullptr; m_engine->SetOverFront(true); // over flat front CreateShortcuts(); if (m_infoObject != nullptr) SelectObject(m_infoObject, false); // gives the command buttons m_map->ShowMap(m_mapShow); m_displayText->HideText(false); m_engine->DisablePauseBlur(); } //! Returns the absolute time of the game float CRobotMain::GetGameTime() { return m_gameTime; } //! Start of the visit instead of an error void CRobotMain::StartDisplayVisit(EventType event) { if (m_editLock) return; if (m_visitPause) { m_pause->DeactivatePause(m_visitPause); m_visitPause = nullptr; } Ui::CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW2)); if (pw == nullptr) return; if (event == EVENT_NULL) // visit by keyboard shortcut? { int i; if (m_visitLast != EVENT_NULL) // already a current visit? i = m_visitLast-EVENT_DT_VISIT0; else i = Ui::MAXDTLINE; // Seeks the last. for (int j = 0; j < Ui::MAXDTLINE; j++) { i --; if (i < 0) i = Ui::MAXDTLINE-1; Ui::CButton* button = static_cast(pw->SearchControl(static_cast(EVENT_DT_VISIT0+i))); if (button == nullptr || !button->TestState(Ui::STATE_ENABLE)) continue; Ui::CGroup* group = static_cast(pw->SearchControl(static_cast(EVENT_DT_GROUP0+i))); if (group != nullptr) { event = static_cast(EVENT_DT_VISIT0+i); break; } } } if (event == EVENT_NULL) { m_sound->Play(SOUND_TZOING); // nothing to do! return; } m_visitLast = event; ClearInterface(); // removes setting evidence and tooltip if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT) // already a current visit? { m_camera->StopVisit(); m_displayText->ClearVisit(); } else { m_visitObject = DeselectAll(); // removes the control buttons } // Creates the "continue" button. if (m_interface->SearchControl(EVENT_DT_END) == nullptr) { glm::vec2 pos, dim; pos.x = 10.0f/640.0f; pos.y = 10.0f/480.0f; dim.x = 50.0f/640.0f; dim.y = 50.0f/480.0f; m_interface->CreateButton(pos, dim, 16, EVENT_DT_END); } // Creates the arrow to show the place. if (m_visitArrow != nullptr) { CObjectManager::GetInstancePointer()->DeleteObject(m_visitArrow); m_visitArrow = nullptr; } ObjectCreateParams params; params.pos = m_displayText->GetVisitGoal(event); params.type = OBJECT_SHOW; params.height = 10.0f; m_visitArrow = m_objMan->CreateObject(params); m_visitPos = m_visitArrow->GetPosition(); m_visitPosArrow = m_visitPos; m_visitPosArrow.y += m_displayText->GetVisitHeight(event); m_visitArrow->SetPosition(m_visitPosArrow); m_visitTime = 0.0; m_visitParticle = 0.0f; m_particle->DeleteParticle(Gfx::PARTISHOW); m_camera->StartVisit(m_displayText->GetVisitGoal(event), m_displayText->GetVisitDist(event)); m_displayText->SetVisit(event); m_visitPause = m_pause->ActivatePause(PAUSE_ENGINE); } //! Move the arrow to visit void CRobotMain::FrameVisit(float rTime) { if (m_visitArrow == nullptr) return; // Moves the arrow. m_visitTime += rTime; glm::vec3 pos = m_visitPosArrow; pos.y += 1.5f+sinf(m_visitTime*4.0f)*4.0f; m_visitArrow->SetPosition(pos); m_visitArrow->SetRotationY(m_visitTime*2.0f); // Manages the particles "arrows". m_visitParticle -= rTime; if (m_visitParticle <= 0.0f) { m_visitParticle = 1.5f; pos = m_visitPos; float level = m_terrain->GetFloorLevel(pos)+2.0f; if (pos.y < level) pos.y = level; // not below the ground glm::vec3 speed(0.0f, 0.0f, 0.0f); glm::vec2 dim; dim.x = 30.0f; dim.y = dim.x; m_particle->CreateParticle(pos, speed, dim, Gfx::PARTISHOW, 2.0f); } } //! End of the visit instead of an error void CRobotMain::StopDisplayVisit() { m_visitLast = EVENT_NULL; // Removes the button. m_interface->DeleteControl(EVENT_DT_END); // Removes the arrow. if (m_visitArrow != nullptr) { CObjectManager::GetInstancePointer()->DeleteObject(m_visitArrow); m_visitArrow = nullptr; } // Removes particles "arrows". m_particle->DeleteParticle(Gfx::PARTISHOW); m_camera->StopVisit(); m_displayText->ClearVisit(); m_pause->DeactivatePause(m_visitPause); m_visitPause = nullptr; if (m_visitObject != nullptr) { SelectObject(m_visitObject, false); // gives the command buttons m_visitObject = nullptr; } } void CRobotMain::UpdateShortcuts() { m_short->UpdateShortcuts(); } CObject* CRobotMain::GetSelectObject() { if (m_selectObject != nullptr) return m_selectObject; return SearchHuman(); } CObject* CRobotMain::DeselectAll() { CObject* prev = nullptr; for (CObject* obj : m_objMan->GetAllObjects()) { if (!obj->Implements(ObjectInterfaceType::Controllable)) continue; auto controllableObj = dynamic_cast(obj); if (controllableObj->GetSelect()) prev = obj; controllableObj->SetSelect(false); } return prev; } //! Selects an object, without attending to deselect the rest void CRobotMain::SelectOneObject(CObject* obj, bool displayError) { assert(obj->Implements(ObjectInterfaceType::Controllable)); dynamic_cast(*obj).SetSelect(true, displayError); m_camera->SetControllingObject(obj); ObjectType type = obj->GetType(); if ( type == OBJECT_HUMAN || type == OBJECT_MOBILEfa || type == OBJECT_MOBILEta || type == OBJECT_MOBILEwa || type == OBJECT_MOBILEia || type == OBJECT_MOBILEfb || type == OBJECT_MOBILEtb || type == OBJECT_MOBILEwb || type == OBJECT_MOBILEib || type == OBJECT_MOBILEfc || type == OBJECT_MOBILEtc || type == OBJECT_MOBILEwc || type == OBJECT_MOBILEic || type == OBJECT_MOBILEfi || type == OBJECT_MOBILEti || type == OBJECT_MOBILEwi || type == OBJECT_MOBILEii || type == OBJECT_MOBILEfs || type == OBJECT_MOBILEts || type == OBJECT_MOBILEws || type == OBJECT_MOBILEis || type == OBJECT_MOBILErt || type == OBJECT_MOBILErc || type == OBJECT_MOBILErr || type == OBJECT_MOBILErs || type == OBJECT_MOBILEsa || type == OBJECT_MOBILEft || type == OBJECT_MOBILEtt || type == OBJECT_MOBILEwt || type == OBJECT_MOBILEit || type == OBJECT_MOBILErp || type == OBJECT_MOBILEst || type == OBJECT_MOBILEdr || type == OBJECT_APOLLO2 ) { m_camera->SetType(dynamic_cast(*obj).GetCameraType()); } else { m_camera->SetType(Gfx::CAM_TYPE_BACK); } } bool CRobotMain::SelectObject(CObject* obj, bool displayError) { if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT) StopDisplayVisit(); if (m_movieLock || m_editLock) return false; if (m_movie->IsExist()) return false; if (obj != nullptr && (!obj->Implements(ObjectInterfaceType::Controllable) || !(dynamic_cast(*obj).GetSelectable() || m_cheatSelectInsect))) return false; if (m_missionType == MISSION_CODE_BATTLE && m_codeBattleStarted && m_codeBattleSpectator) { DeselectAll(); // During code battles, only change camera m_camera->SetControllingObject(obj); if (obj != nullptr) { m_camera->SetType(Gfx::CAM_TYPE_PLANE); } else { m_camera->SetType(Gfx::CAM_TYPE_FREE); } } else { if (obj == nullptr) return false; CObject* prev = DeselectAll(); if (prev != nullptr && prev != obj) PushToSelectionHistory(prev); SelectOneObject(obj, displayError); } m_short->UpdateShortcuts(); return true; } bool CRobotMain::DeselectObject() { DeselectAll(); CObject* obj = PopFromSelectionHistory(); if (obj == nullptr) obj = SearchHuman(); if (obj != nullptr) SelectOneObject(obj); else m_camera->SetType(Gfx::CAM_TYPE_FREE); m_short->UpdateShortcuts(); return true; } //! Quickly removes all objects void CRobotMain::DeleteAllObjects() { m_engine->GetPyroManager()->DeleteAll(); // Removes the arrow. if (m_visitArrow != nullptr) { CObjectManager::GetInstancePointer()->DeleteObject(m_visitArrow); m_visitArrow = nullptr; } for (int i = 0; i < MAXSHOWLIMIT; i++) FlushShowLimit(i); m_objMan->DeleteAllObjects(); } CObject* CRobotMain::SearchHuman() { return m_objMan->FindNearest(nullptr, OBJECT_HUMAN); } CObject* CRobotMain::GetSelect() { for (CObject* obj : m_objMan->GetAllObjects()) { if (!obj->Implements(ObjectInterfaceType::Controllable)) continue; if (dynamic_cast(*obj).GetSelect()) return obj; } return nullptr; } //! Detects the object aimed by the mouse CObject* CRobotMain::DetectObject(const glm::vec2& pos) { glm::vec3 p; int objRank = m_engine->DetectObject(pos, p); for (CObject* obj : m_objMan->GetAllObjects()) { if (!obj->GetDetectable()) continue; CObject* transporter = nullptr; if (obj->Implements(ObjectInterfaceType::Transportable)) transporter = dynamic_cast(*obj).GetTransporter(); if (transporter != nullptr && !transporter->GetDetectable()) continue; if (obj->GetProxyActivate()) continue; CObject* target = obj; // TODO: should this also apply to slots other than power cell slots? if (obj->Implements(ObjectInterfaceType::PowerContainer) && obj->Implements(ObjectInterfaceType::Transportable)) { CObject *transporter = dynamic_cast(*obj).GetTransporter(); // battery connected if (transporter != nullptr && obj == GetObjectInPowerCellSlot(transporter)) target = transporter; } if (!obj->Implements(ObjectInterfaceType::Old)) continue; for (int j = 0; j < OBJECTMAXPART; j++) { int rank = obj->GetObjectRank(j); if (rank == -1) continue; if (rank != objRank) continue; return target; } } return nullptr; } //! Deletes the selected object bool CRobotMain::DestroySelectedObject() { CObject* obj = GetSelect(); if (obj == nullptr) return false; assert(obj->Implements(ObjectInterfaceType::Controllable)); m_engine->GetPyroManager()->Create(Gfx::PT_FRAGT, obj); dynamic_cast(*obj).SetSelect(false); // deselects the object m_camera->SetType(Gfx::CAM_TYPE_EXPLO); DeselectAll(); RemoveFromSelectionHistory(obj); return true; } //! Removes setting evidence of the object with the mouse hovers over void CRobotMain::HiliteClear() { ClearTooltip(); m_tooltipName.clear(); // really removes the tooltip if (!m_hilite) return; int rank = -1; m_engine->SetHighlightRank(&rank); // nothing more selected for (CObject* obj : m_objMan->GetAllObjects()) { if (!obj->Implements(ObjectInterfaceType::Controllable)) continue; dynamic_cast(*obj).SetHighlight(false); } m_map->SetHighlight(nullptr); m_short->SetHighlight(nullptr); m_hilite = false; } //! Highlights the object with the mouse hovers over void CRobotMain::HiliteObject(const glm::vec2& pos) { if (m_fixScene && m_phase != PHASE_APPEARANCE) return; if (m_movieLock) return; if (m_movie->IsExist()) return; if (m_app->GetMouseMode() == MOUSE_NONE) return; ClearInterface(); // removes setting evidence and tooltip CObject* obj = m_short->DetectShort(pos); std::string interfaceTooltipName; if (m_settings->GetTooltips() && m_interface->GetTooltip(pos, interfaceTooltipName)) { m_tooltipPos = pos; m_tooltipName = interfaceTooltipName; m_tooltipTime = 0.0f; if (obj == nullptr) return; } if (m_suspend != nullptr) return; if (obj == nullptr) { bool inMap = false; obj = m_map->DetectMap(pos, inMap); if (obj == nullptr) { if (inMap) return; obj = DetectObject(pos); if ((m_camera->GetType() == Gfx::CAM_TYPE_ONBOARD) && (m_camera->GetControllingObject() == obj)) return; } } if (obj != nullptr) { if (m_settings->GetTooltips()) { std::string objectTooltipName = obj->GetTooltipText(); if (!objectTooltipName.empty()) { m_tooltipPos = pos; m_tooltipName = objectTooltipName; m_tooltipTime = 0.0f; } } if (obj->Implements(ObjectInterfaceType::Controllable) && (dynamic_cast(*obj).GetSelectable() || m_cheatSelectInsect)) { if (dynamic_cast(*obj).GetSelectable()) { // Don't highlight objects that would not be selectable without selectinsect dynamic_cast(*obj).SetHighlight(true); } m_map->SetHighlight(obj); m_short->SetHighlight(obj); m_hilite = true; } } } //! Highlights the object with the mouse hovers over void CRobotMain::HiliteFrame(float rTime) { if (m_fixScene && m_phase != PHASE_APPEARANCE) return; if (m_movieLock) return; if (m_movie->IsExist()) return; m_tooltipTime += rTime; ClearTooltip(); if (m_tooltipTime >= 0.2f && !m_tooltipName.empty()) { CreateTooltip(m_tooltipPos, m_tooltipName); } } //! Creates a tooltip void CRobotMain::CreateTooltip(const glm::vec2& position, const std::string& text) { glm::vec2 pos = position; glm::vec2 corner; corner.x = pos.x+0.022f; corner.y = pos.y-0.052f; glm::vec2 start, end; m_engine->GetText()->SizeText(text, Gfx::FONT_COMMON, Gfx::FONT_SIZE_SMALL, corner, Gfx::TEXT_ALIGN_LEFT, start, end); start.x -= 0.010f; start.y -= 0.006f; end.x += 0.010f; end.y += 0.008f; // small'ish margin pos.x = start.x; pos.y = start.y; glm::vec2 dim; dim.x = end.x-start.x; dim.y = end.y-start.y; glm::vec2 offset; offset.x = 0.0f; offset.y = 0.0f; if (pos.x+dim.x > 1.0f) offset.x = 1.0f-(pos.x+dim.x); if (pos.y < 0.0f) offset.y = -pos.y; corner.x += offset.x; corner.y += offset.y; pos.x += offset.x; pos.y += offset.y; m_interface->CreateWindows(pos, dim, 1, EVENT_TOOLTIP); Ui::CWindow* pw = static_cast(m_interface->SearchControl(EVENT_TOOLTIP)); if (pw != nullptr) { pw->SetState(Ui::STATE_SHADOW); pw->SetTrashEvent(false); pos.y -= m_engine->GetText()->GetHeight(Gfx::FONT_COMMON, Gfx::FONT_SIZE_SMALL) / 2.0f; pw->CreateLabel(pos, dim, -1, EVENT_LABEL2, text); } } //! Clears the previous tooltip void CRobotMain::ClearTooltip() { m_interface->DeleteControl(EVENT_TOOLTIP); } //! Displays help for an object void CRobotMain::HelpObject() { CObject* obj = GetSelect(); if (obj == nullptr) return; std::string filename = GetHelpFilename(obj->GetType()); if (filename.empty()) return; StartDisplayInfo(filename, -1); } //! Change the mode of the camera void CRobotMain::ChangeCamera() { CObject* obj = GetSelect(); if (obj == nullptr) return; assert(obj->Implements(ObjectInterfaceType::Controllable)); auto controllableObj = dynamic_cast(obj); if (controllableObj->GetCameraLock()) return; ObjectType oType = obj->GetType(); Gfx::CameraType type = controllableObj->GetCameraType(); if ( oType != OBJECT_HUMAN && oType != OBJECT_TECH && oType != OBJECT_MOBILEfa && oType != OBJECT_MOBILEta && oType != OBJECT_MOBILEwa && oType != OBJECT_MOBILEia && oType != OBJECT_MOBILEfb && oType != OBJECT_MOBILEtb && oType != OBJECT_MOBILEwb && oType != OBJECT_MOBILEib && oType != OBJECT_MOBILEfc && oType != OBJECT_MOBILEtc && oType != OBJECT_MOBILEwc && oType != OBJECT_MOBILEic && oType != OBJECT_MOBILEfi && oType != OBJECT_MOBILEti && oType != OBJECT_MOBILEwi && oType != OBJECT_MOBILEii && oType != OBJECT_MOBILEfs && oType != OBJECT_MOBILEts && oType != OBJECT_MOBILEws && oType != OBJECT_MOBILEis && oType != OBJECT_MOBILErt && oType != OBJECT_MOBILErc && oType != OBJECT_MOBILErr && oType != OBJECT_MOBILErs && oType != OBJECT_MOBILEsa && oType != OBJECT_MOBILEtg && oType != OBJECT_MOBILEft && oType != OBJECT_MOBILEtt && oType != OBJECT_MOBILEwt && oType != OBJECT_MOBILEit && oType != OBJECT_MOBILErp && oType != OBJECT_MOBILEst && oType != OBJECT_MOBILEdr && oType != OBJECT_APOLLO2 ) return; if (oType == OBJECT_MOBILEdr) // designer? { if (type == Gfx::CAM_TYPE_PLANE ) type = Gfx::CAM_TYPE_BACK; else if (type == Gfx::CAM_TYPE_BACK ) type = Gfx::CAM_TYPE_PLANE; } else if (controllableObj->GetTrainer()) // trainer? { if (type == Gfx::CAM_TYPE_ONBOARD) type = Gfx::CAM_TYPE_FIX; else if (type == Gfx::CAM_TYPE_FIX ) type = Gfx::CAM_TYPE_PLANE; else if (type == Gfx::CAM_TYPE_PLANE ) type = Gfx::CAM_TYPE_BACK; else if (type == Gfx::CAM_TYPE_BACK ) type = Gfx::CAM_TYPE_ONBOARD; } else { if (type == Gfx::CAM_TYPE_ONBOARD) type = Gfx::CAM_TYPE_BACK; else if (type == Gfx::CAM_TYPE_BACK ) type = Gfx::CAM_TYPE_ONBOARD; } controllableObj->SetCameraType(type); m_camera->SetType(type); } //! Cancels the current movie void CRobotMain::AbortMovie() { for (CObject* obj : m_objMan->GetAllObjects()) { if (obj->Implements(ObjectInterfaceType::Old)) { CAuto* automat = obj->GetAuto(); if (automat != nullptr) automat->Abort(); } } } static std::string TimeFormat(float time) { int minutes = static_cast(floor(time/60)); double time2 = fmod(time, 60); double seconds; double fraction = modf(time2, &seconds)*100; std::ostringstream sstream; sstream << std::setfill('0') << std::setw(2) << minutes << ":" << std::setfill('0') << std::setw(2) << floor(seconds) << "." << std::setfill('0') << std::setw(2) << floor(fraction); return sstream.str(); } //! Updates the text information void CRobotMain::UpdateInfoText() { if (m_phase == PHASE_SIMUL) { CObject* obj = GetSelect(); if (obj != nullptr) { glm::vec3 pos = obj->GetPosition(); m_engine->SetStatisticPos(pos / g_unit); } } m_engine->SetTimerDisplay(m_missionTimerEnabled && m_missionTimerStarted ? TimeFormat(m_missionTimer) : ""); } //! Initializes the view void CRobotMain::InitEye() { if (m_phase == PHASE_SIMUL) m_camera->Init(glm::vec3( 0.0f, 10.0f, 0.0f), glm::vec3(10.0f, 5.0f, 0.0f), 0.0f); } //! Advances the entire scene bool CRobotMain::EventFrame(const Event &event) { m_time += event.rTime; m_water->EventProcess(event); m_cloud->EventProcess(event); m_lightning->EventProcess(event); m_planet->EventProcess(event); UpdateDebugCrashSpheres(); Ui::CMap* pm = nullptr; Ui::CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW1)); if (pw == nullptr) { pm = nullptr; } else { pm = static_cast(pw->SearchControl(EVENT_OBJECT_MAP)); if (pm != nullptr) pm->FlushObject(); } CObject* toto = nullptr; if (!m_pause->IsPauseType(PAUSE_OBJECT_UPDATES)) { // Advances all the robots, but not toto. for (CObject* obj : m_objMan->GetAllObjects()) { if (pm != nullptr) pm->UpdateObject(obj); if (IsObjectBeingTransported(obj)) continue; if (obj->GetType() == OBJECT_TOTO) toto = obj; else if (obj->Implements(ObjectInterfaceType::Interactive)) dynamic_cast(*obj).EventProcess(event); if ( obj->GetProxyActivate() ) // active if it is near? { glm::vec3 eye = m_engine->GetLookatPt(); float dist = glm::distance(eye, obj->GetPosition()); if ( dist < obj->GetProxyDistance() ) { obj->SetProxyActivate(false); CreateShortcuts(); m_sound->Play(SOUND_FINDING); m_engine->GetPyroManager()->Create(Gfx::PT_FINDING, obj, 0.0f); DisplayError(INFO_FINDING, obj); } } } // Advances all objects transported by robots. for (CObject* obj : m_objMan->GetAllObjects()) { if (! IsObjectBeingTransported(obj)) continue; if (obj->Implements(ObjectInterfaceType::Interactive)) dynamic_cast(*obj).EventProcess(event); } m_engine->GetPyroManager()->EventProcess(event); } // The camera follows the object, because its position // may depend on the selected object (Gfx::CAM_TYPE_ONBOARD or Gfx::CAM_TYPE_BACK). if (m_phase == PHASE_SIMUL && !m_editFull) { m_camera->EventProcess(event); if (m_engine->GetFog()) m_camera->SetOverBaseColor(m_particle->GetFogColor(m_engine->GetEyePt())); } if (m_phase == PHASE_APPEARANCE || m_phase == PHASE_WIN || m_phase == PHASE_LOST) { m_camera->EventProcess(event); } // Advances toto following the camera, because its position depends on the camera. if (toto != nullptr) dynamic_cast(*toto).EventProcess(event); // NOTE: m_movieLock is set only after the first update of CAutoBase finishes if (m_phase == PHASE_SIMUL) { if (!m_immediatSatCom && !m_beginSatCom && !m_movieLock) { m_displayText->DisplayError(INFO_BEGINSATCOM, glm::vec3(0.0f, 0.0f, 0.0f)); m_beginSatCom = true; // message appears } if (!m_pause->IsPauseType(PAUSE_ENGINE) && !m_movieLock) { m_gameTime += event.rTime; m_gameTimeAbsolute += m_app->GetRealRelTime() / 1e9f; if (m_missionTimerStarted) m_missionTimer += event.rTime; if (m_autosave && m_gameTimeAbsolute >= m_autosaveLast + (m_autosaveInterval * 60)) { if (m_levelCategory == LevelCategory::Missions || m_levelCategory == LevelCategory::FreeGame || m_levelCategory == LevelCategory::GamePlus || m_levelCategory == LevelCategory::CustomLevels) { if (!IOIsBusy() && m_missionType != MISSION_CODE_BATTLE) { m_autosaveLast = m_gameTimeAbsolute; Autosave(); } } } } } HiliteFrame(event.rTime); // Moves the film indicator. if (m_movieLock && !m_editLock) // movie in progress? { Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_MOVIELOCK); if (pc != nullptr) { glm::vec2 pos, dim; dim.x = 32.0f/640.0f; dim.y = 32.0f/480.0f; pos.x = 20.0f/640.0f; pos.y = (480.0f-24.0f)/480.0f; float zoom = 1.0f+sinf(m_time*6.0f)*0.1f; // 0.9 .. 1.1 dim.x *= zoom; dim.y *= zoom; pos.x -= dim.x/2.0f; pos.y -= dim.y/2.0f; pc->SetPos(pos); pc->SetDim(dim); } } // Moves edition indicator. if (m_editLock || m_pause->IsPauseType(PAUSE_ENGINE)) // edition in progress? { Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_EDITLOCK); if (pc != nullptr) { glm::vec2 pos, dim; if (m_editFull || m_editLock) { dim.x = 10.0f/640.0f; dim.y = 10.0f/480.0f; pos.x = -20.0f/640.0f; pos.y = -20.0f/480.0f; // invisible! } else { dim.x = 32.0f/640.0f; dim.y = 32.0f/480.0f; pos.x = (640.0f-24.0f)/640.0f; pos.y = (480.0f-24.0f)/480.0f; float zoom = 1.0f+sinf(m_time*6.0f)*0.1f; // 0.9 .. 1.1 dim.x *= zoom; dim.y *= zoom; pos.x -= dim.x/2.0f; pos.y -= dim.y/2.0f; } pc->SetPos(pos); pc->SetDim(dim); } } Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_SAVING); if (pc != nullptr) { glm::vec2 pos, dim; if (m_shotSaving <= 0) { dim.x = 10.0f/640.0f; dim.y = 10.0f/480.0f; pos.x = -20.0f/640.0f; pos.y = -20.0f/480.0f; // invisible! } else { dim.x = 32.0f/640.0f; dim.y = 32.0f/480.0f; pos.x = (640.0f-24.0f)/640.0f; pos.y = (480.0f-24.0f)/480.0f; float zoom = 1.0f+sinf(m_time*6.0f)*0.1f; // 0.9 .. 1.1 dim.x *= zoom; dim.y *= zoom; pos.x -= dim.x/2.0f; pos.y -= dim.y/2.0f; } pc->SetPos(pos); pc->SetDim(dim); } // Will move the arrow to visit. if (m_camera->GetType() == Gfx::CAM_TYPE_VISIT) FrameVisit(event.rTime); // Moves the boundaries. FrameShowLimit(event.rTime); if (m_phase == PHASE_SIMUL) { if (!m_editLock && !m_engine->GetPause()) { CheckEndMission(true); UpdateAudio(true); if (m_scoreboard) m_scoreboard->UpdateObjectCount(); } if (m_winDelay > 0.0f && !m_editLock) { m_winDelay -= event.rTime; if (m_winDelay <= 0.0f) { if (m_movieLock) m_winDelay = 1.0f; else m_eventQueue->AddEvent(Event(EVENT_WIN)); } } if (m_lostDelay > 0.0f && !m_editLock) { m_lostDelay -= event.rTime; if (m_lostDelay <= 0.0f) { if (m_movieLock) m_lostDelay = 1.0f; else m_eventQueue->AddEvent(Event(EVENT_LOST)); } } if (GetMissionType() == MISSION_CODE_BATTLE) { if (!m_codeBattleInit) { // NOTE: It's important to do this AFTER the first update event finished processing // because otherwise all robot parts are misplaced m_userPause = m_pause->ActivatePause(PAUSE_ENGINE); m_codeBattleInit = true; // Will start on resume } if (!m_codeBattleStarted && m_userPause == nullptr) { m_codeBattleStarted = true; ApplyCodeBattleInterface(); CreateCodeBattleInterface(); SetCodeBattleSpectatorMode(true); m_eventQueue->AddEvent(Event(EVENT_UPDINTERFACE)); } UpdateCodeBattleInterface(); } } return true; } void CRobotMain::ShowSaveIndicator(bool show) { Ui::CControl* pc = m_interface->SearchControl(EVENT_OBJECT_SAVING); if (pc != nullptr) { glm::vec2 pos, dim; if (!show) { dim.x = 10.0f/640.0f; dim.y = 10.0f/480.0f; pos.x = -20.0f/640.0f; pos.y = -20.0f/480.0f; // invisible! } else { dim.x = 32.0f/640.0f; dim.y = 32.0f/480.0f; pos.x = (640.0f-24.0f)/640.0f; pos.y = (480.0f-24.0f)/480.0f; pos.x -= dim.x/2.0f; pos.y -= dim.y/2.0f; } pc->SetPos(pos); pc->SetDim(dim); } } //! Makes the event for all robots bool CRobotMain::EventObject(const Event &event) { if (m_pause->IsPauseType(PAUSE_OBJECT_UPDATES)) return true; m_resetCreate = false; for (CObject* obj : m_objMan->GetAllObjects()) { if (obj->Implements(ObjectInterfaceType::Interactive)) { dynamic_cast(*obj).EventProcess(event); } } if (m_resetCreate) ResetCreate(); return true; } void CRobotMain::ScenePerso() { DeleteAllObjects(); // removes all the current 3D Scene m_terrain->FlushRelief(); m_engine->DeleteAllObjects(); m_oldModelManager->DeleteAllModelCopies(); m_terrain->FlushBuildingLevel(); m_terrain->FlushFlyingLimit(); m_lightMan->FlushLights(); m_particle->FlushParticle(); m_levelFile = "levels/other/perso.txt"; try { CreateScene(false, true, false); // sets scene } catch (const std::runtime_error& e) { LevelLoadingError("An error occurred while trying to load appearance scene", e, PHASE_PLAYER_SELECT); } m_engine->SetDrawWorld(false); // does not draw anything on the interface m_engine->SetDrawFront(true); // draws on the human interface CObject* obj = SearchHuman(); if (obj != nullptr) { obj->SetDrawFront(true); // draws the interface assert(obj->Implements(ObjectInterfaceType::Movable)); CMotionHuman* mh = static_cast(dynamic_cast(*obj).GetMotion()); mh->StartDisplayPerso(); } } //! Creates the whole scene void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) { m_fixScene = fixScene; m_base = nullptr; if (!resetObject) { m_build = 0; m_researchDone.clear(); // no research done m_researchDone[0] = 0; m_researchEnable = 0; g_unit = UNIT; FlushDisplayInfo(); m_terrain->FlushMaterials(); m_audioTrack = ""; m_audioRepeat = true; m_satcomTrack = ""; m_satcomRepeat = true; m_editorTrack = ""; m_editorRepeat = true; m_displayText->SetDelay(1.0f); m_displayText->SetEnable(true); m_immediatSatCom = false; m_lockedSatCom = false; m_endingWin = ""; m_endingLost = ""; m_audioChange.clear(); m_endTake.clear(); m_endTakeImmediat = false; m_endTakeResearch = 0; m_endTakeTimeout = -1.0f; m_endTakeTeamImmediateWin = false; m_endTakeWinDelay = 2.0f; m_endTakeLostDelay = 2.0f; m_teamFinished.clear(); m_scoreboard.reset(); m_globalMagnifyDamage = 1.0f; m_obligatoryTokens.clear(); m_mapShow = true; m_mapImage = false; m_mapFilename[0] = 0; m_controller = nullptr; m_colorNewBot.clear(); m_colorNewBot[0] = COLOR_REF_BOT; m_colorNewAlien = COLOR_REF_ALIEN; m_colorNewGreen = COLOR_REF_GREEN; m_colorNewWater = COLOR_REF_WATER; m_engine->SetAmbientColor(Gfx::Color(0.5f, 0.5f, 0.5f, 0.5f), 0); m_engine->SetAmbientColor(Gfx::Color(0.5f, 0.5f, 0.5f, 0.5f), 1); m_engine->SetFogColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f), 0); m_engine->SetFogColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f), 1); m_engine->SetDeepView(1000.0f, 0); m_engine->SetDeepView(1000.0f, 1); m_engine->SetFogStart(0.75f, 0); m_engine->SetFogStart(0.75f, 1); m_engine->SetSecondTexture(""); m_engine->SetForegroundName(""); GetResource(RES_TEXT, RT_SCRIPT_NEW, m_scriptName); m_scriptFile = ""; m_missionType = MISSION_NORMAL; m_codeBattleInit = false; m_codeBattleStarted = false; m_teamNames.clear(); m_missionResult = ERR_MISSION_NOTERM; m_missionResultFromScript = false; } // NOTE: Reset timer always, even when only resetting object positions m_missionTimerEnabled = false; m_missionTimerStarted = false; m_missionTimer = 0.0f; std::string backgroundPath = ""; Gfx::Color backgroundUp = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f); Gfx::Color backgroundDown = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f); Gfx::Color backgroundCloudUp = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f); Gfx::Color backgroundCloudDown = Gfx::Color(0.0f, 0.0f, 0.0f, 0.0f); bool backgroundFull = false; auto LoadingWarning = [&](const std::string& message) { GetLogger()->Warn("%s\n", message.c_str()); m_ui->GetDialog()->StartInformation("Level loading warning", "This level contains problems. It may stop working in future versions of the game.", message); }; try { m_ui->GetLoadingScreen()->SetProgress(0.05f, RT_LOADING_PROCESSING); GetLogger()->Info("Loading level: %s\n", m_levelFile.c_str()); CLevelParser levelParser(m_levelFile); levelParser.SetLevelPaths(m_levelCategory, m_levelChap, m_levelRank); levelParser.Load(); int numObjects = levelParser.CountLines("CreateObject"); m_ui->GetLoadingScreen()->SetProgress(0.1f, RT_LOADING_LEVEL_SETTINGS); int rankObj = 0; CObject* sel = nullptr; for (auto& line : levelParser.GetLines()) { if (line->GetCommand() == "Title" && !resetObject) { //strcpy(m_title, line->GetParam("text")->AsString().c_str()); continue; } if (line->GetCommand() == "Resume" && !resetObject) { //strcpy(m_resume, line->GetParam("text")->AsString().c_str()); continue; } if (line->GetCommand() == "ScriptName" && !resetObject) { m_scriptName = line->GetParam("text")->AsString(); continue; } if (line->GetCommand() == "ScriptFile" && !resetObject) { m_scriptFile = line->GetParam("name")->AsString(); continue; } if (line->GetCommand() == "Instructions" && !resetObject) { strcpy(m_infoFilename[SATCOM_HUSTON], line->GetParam("name")->AsPath("help/%lng%").c_str()); m_immediatSatCom = line->GetParam("immediat")->AsBool(false); m_beginSatCom = m_lockedSatCom = line->GetParam("lock")->AsBool(false); if (m_app->GetSceneTestMode()) m_immediatSatCom = false; continue; } if (line->GetCommand() == "Satellite" && !resetObject) { strcpy(m_infoFilename[SATCOM_SAT], line->GetParam("name")->AsPath("help/%lng%").c_str()); continue; } if (line->GetCommand() == "Loading" && !resetObject) { strcpy(m_infoFilename[SATCOM_LOADING], line->GetParam("name")->AsPath("help/%lng%").c_str()); continue; } if (line->GetCommand() == "HelpFile" && !resetObject) { strcpy(m_infoFilename[SATCOM_PROG], line->GetParam("name")->AsPath("help/%lng%").c_str()); continue; } if (line->GetCommand() == "SoluceFile" && !resetObject) { strcpy(m_infoFilename[SATCOM_SOLUCE], line->GetParam("name")->AsPath("help/%lng%").c_str()); continue; } if (line->GetCommand() == "EndingFile" && !resetObject) { auto Process = [&](const std::string& type) -> std::string { if (line->GetParam(type)->IsDefined()) { try { int rank = boost::lexical_cast(line->GetParam(type)->GetValue()); if (rank >= 0) { // TODO: Fix default levels and add a future removal warning 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); std::stringstream ss; ss << "levels/other/" << type << std::setfill('0') << std::setw(3) << rank << ".txt"; return ss.str(); } else { // TODO: Fix default levels and add a future removal warning 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()); return ""; } } catch (boost::bad_lexical_cast &e) { return line->GetParam(type)->AsPath("levels"); } } return ""; }; m_endingWin = Process("win"); m_endingLost = Process("lost"); continue; } if (line->GetCommand() == "MessageDelay" && !resetObject) { m_displayText->SetDelay(line->GetParam("factor")->AsFloat()); continue; } if (line->GetCommand() == "MissionTimer") { m_missionTimerEnabled = line->GetParam("enabled")->AsBool(); if (!line->GetParam("program")->AsBool(false)) { m_missionTimerStarted = true; } continue; } if (line->GetCommand() == "TeamName") { int team = line->GetParam("team")->AsInt(); std::string name = line->GetParam("name")->AsString(); m_teamNames[team] = name; continue; } if (line->GetCommand() == "CacheAudio" && !resetObject) { std::string filename = line->GetParam("filename")->AsPath("music"); m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, filename); m_sound->CacheMusic(filename); continue; } if (line->GetCommand() == "AudioChange" && !resetObject) { auto audioChange = std::make_unique(); audioChange->Read(line.get()); m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, audioChange->music); m_sound->CacheMusic(audioChange->music); m_audioChange.push_back(std::move(audioChange)); if (!line->GetParam("pos")->IsDefined() || !line->GetParam("dist")->IsDefined()) { LoadingWarning("The defaults for pos= and dist= are going to change, specify them explicitly. See issue #759 (https://git.io/vVBzH)"); } continue; } if (line->GetCommand() == "Audio" && !resetObject) { if (line->GetParam("track")->IsDefined()) { if (line->GetParam("filename")->IsDefined()) throw CLevelParserException("You can't use track and filename at the same time"); GetLogger()->Warn("Using track= is deprecated. Please replace this with filename=\n"); int trackid = line->GetParam("track")->AsInt(); if (trackid != 0) { std::stringstream filenameStr; filenameStr << "music/music" << std::setfill('0') << std::setw(3) << trackid << ".ogg"; m_audioTrack = filenameStr.str(); } else { m_audioTrack = ""; } } else { if (line->GetParam("filename")->IsDefined()) { m_audioTrack = line->GetParam("filename")->AsPath("music"); } else { m_audioTrack = ""; } } if (!m_audioTrack.empty()) { m_audioRepeat = line->GetParam("repeat")->AsBool(true); } if (line->GetParam("satcom")->IsDefined()) { m_satcomTrack = line->GetParam("satcom")->AsPath("music"); m_satcomRepeat = line->GetParam("satcomRepeat")->AsBool(true); } else { m_satcomTrack = ""; } if (line->GetParam("editor")->IsDefined()) { m_editorTrack = line->GetParam("editor")->AsPath("music"); m_editorRepeat = line->GetParam("editorRepeat")->AsBool(true); } else { m_editorTrack = ""; } if (!m_audioTrack.empty()) { m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, m_audioTrack); m_sound->CacheMusic(m_audioTrack); } if (!m_satcomTrack.empty()) { m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, m_satcomTrack); m_sound->CacheMusic(m_satcomTrack); } if (!m_editorTrack.empty()) { m_ui->GetLoadingScreen()->SetProgress(0.15f, RT_LOADING_MUSIC, m_editorTrack); m_sound->CacheMusic(m_editorTrack); } continue; } if (line->GetCommand() == "AmbientColor" && !resetObject) { m_engine->SetAmbientColor(line->GetParam("air")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 0); m_engine->SetAmbientColor(line->GetParam("water")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 1); continue; } if (line->GetCommand() == "FogColor" && !resetObject) { m_engine->SetFogColor(line->GetParam("air")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 0); m_engine->SetFogColor(line->GetParam("water")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), 1); continue; } if (line->GetCommand() == "VehicleColor" && !resetObject) { m_colorNewBot[line->GetParam("team")->AsInt(0)] = line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)); continue; } if (line->GetCommand() == "InsectColor" && !resetObject) { m_colorNewAlien = line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)); continue; } if (line->GetCommand() == "GreeneryColor" && !resetObject) { m_colorNewGreen = line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)); continue; } if (line->GetCommand() == "DeepView" && !resetObject) { m_engine->SetDeepView(line->GetParam("air")->AsFloat(500.0f)*g_unit, 0, false); m_engine->SetDeepView(line->GetParam("water")->AsFloat(100.0f)*g_unit, 1, false); continue; } if (line->GetCommand() == "FogStart" && !resetObject) { m_engine->SetFogStart(line->GetParam("air")->AsFloat(0.5f), 0); m_engine->SetFogStart(line->GetParam("water")->AsFloat(0.5f), 1); continue; } if (line->GetCommand() == "SecondTexture" && !resetObject) { if (line->GetParam("rank")->IsDefined()) { char tex[20] = { 0 }; sprintf(tex, "dirty%.2d.png", line->GetParam("rank")->AsInt()); m_engine->SetSecondTexture(tex); } else { m_engine->SetSecondTexture("../" + line->GetParam("texture")->AsPath("textures")); } continue; } if (line->GetCommand() == "Background" && !resetObject) { if (line->GetParam("image")->IsDefined()) backgroundPath = line->GetParam("image")->AsPath("textures"); backgroundUp = line->GetParam("up")->AsColor(backgroundUp); backgroundDown = line->GetParam("down")->AsColor(backgroundDown); backgroundCloudUp = line->GetParam("cloudUp")->AsColor(backgroundCloudUp); backgroundCloudDown = line->GetParam("cloudDown")->AsColor(backgroundCloudDown); backgroundFull = line->GetParam("full")->AsBool(backgroundFull); continue; } if (line->GetCommand() == "Planet" && !resetObject) { glm::vec3 ppos, uv1, uv2; ppos = line->GetParam("pos")->AsPoint(); uv1 = line->GetParam("uv1")->AsPoint(); uv2 = line->GetParam("uv2")->AsPoint(); m_planet->Create(line->GetParam("mode")->AsPlanetType(), { ppos.x, ppos.z }, line->GetParam("dim")->AsFloat(0.2f), line->GetParam("speed")->AsFloat(0.0f), line->GetParam("dir")->AsFloat(0.0f), line->GetParam("image")->AsPath("textures"), { uv1.x, uv1.z }, { uv2.x, uv2.z }, line->GetParam("image")->AsPath("textures").find("planet") != std::string::npos // TODO: add transparent op or modify textures ); continue; } if (line->GetCommand() == "ForegroundName" && !resetObject) { m_engine->SetForegroundName(line->GetParam("image")->AsPath("textures")); continue; } if (line->GetCommand() == "Level" && !resetObject) { g_unit = line->GetParam("unitScale")->AsFloat(4.0f); m_engine->SetTracePrecision(line->GetParam("traceQuality")->AsFloat(1.0f)); m_shortCut = line->GetParam("shortcut")->AsBool(true); m_missionType = line->GetParam("type")->AsMissionType(MISSION_NORMAL); m_globalMagnifyDamage = line->GetParam("magnifyDamage")->AsFloat(1.0f); m_globalNuclearCapacity = line->GetParam("nuclearCapacity")->AsFloat(10.0f); m_globalCellCapacity = line->GetParam("cellCapacity")->AsFloat(1.0f); continue; } if (line->GetCommand() == "TerrainGenerate" && !resetObject) { m_ui->GetLoadingScreen()->SetProgress(0.2f, RT_LOADING_TERRAIN); m_terrain->Generate(line->GetParam("mosaic")->AsInt(20), line->GetParam("brick")->AsInt(3), line->GetParam("size")->AsFloat(20.0f), line->GetParam("vision")->AsFloat(500.0f)*g_unit, line->GetParam("depth")->AsInt(2), line->GetParam("hard")->AsFloat(0.5f)); continue; } if (line->GetCommand() == "TerrainWind" && !resetObject) { m_terrain->SetWind(line->GetParam("speed")->AsPoint()); continue; } if (line->GetCommand() == "TerrainRelief" && !resetObject) { m_ui->GetLoadingScreen()->SetProgress(0.2f+(1.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_RELIEF); m_terrain->LoadRelief( line->GetParam("image")->AsPath("textures"), line->GetParam("factor")->AsFloat(1.0f), line->GetParam("border")->AsBool(true)); continue; } if (line->GetCommand() == "TerrainRandomRelief" && !resetObject) { m_ui->GetLoadingScreen()->SetProgress(0.2f+(1.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_RELIEF); m_terrain->RandomizeRelief(); continue; } if (line->GetCommand() == "TerrainResource" && !resetObject) { m_ui->GetLoadingScreen()->SetProgress(0.2f+(2.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_RES); m_terrain->LoadResources(line->GetParam("image")->AsPath("textures")); continue; } if (line->GetCommand() == "TerrainWater" && !resetObject) { glm::vec3 pos; pos.x = line->GetParam("moveX")->AsFloat(0.0f); pos.y = line->GetParam("moveY")->AsFloat(0.0f); pos.z = pos.x; m_water->Create(line->GetParam("air")->AsWaterType(Gfx::WATER_TT), line->GetParam("water")->AsWaterType(Gfx::WATER_TT), line->GetParam("image")->AsPath("textures"), line->GetParam("diffuse")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)), line->GetParam("ambient")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)), line->GetParam("level")->AsFloat(100.0f)*g_unit, line->GetParam("glint")->AsFloat(1.0f), pos); m_colorNewWater = line->GetParam("color")->AsColor(COLOR_REF_WATER); m_colorShiftWater = line->GetParam("brightness")->AsFloat(0.0f); continue; } if (line->GetCommand() == "TerrainLava" && !resetObject) { m_water->SetLava(line->GetParam("mode")->AsBool()); continue; } if (line->GetCommand() == "TerrainCloud" && !resetObject) { std::string path = ""; if (line->GetParam("image")->IsDefined()) path = line->GetParam("image")->AsPath("textures"); m_cloud->Create(path, line->GetParam("diffuse")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)), line->GetParam("ambient")->AsColor(Gfx::Color(1.0f, 1.0f, 1.0f, 1.0f)), line->GetParam("level")->AsFloat(500.0f)*g_unit); continue; } if (line->GetCommand() == "TerrainBlitz" && !resetObject) { m_lightning->Create(line->GetParam("sleep")->AsFloat(0.0f), line->GetParam("delay")->AsFloat(3.0f), line->GetParam("magnetic")->AsFloat(50.0f)*g_unit); continue; } if (line->GetCommand() == "TerrainInitTextures" && !resetObject) { m_ui->GetLoadingScreen()->SetProgress(0.2f+(3.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_TEX); std::string name = "../" + line->GetParam("image")->AsPath("textures"); if (name.find(".") == std::string::npos) name += ".png"; unsigned int dx = line->GetParam("dx")->AsInt(1); unsigned int dy = line->GetParam("dy")->AsInt(1); int tt[100]; //TODO: I have no idea how TerrainInitTextures works, but maybe we shuld remove the limit to 100? if (dx*dy > 100) throw CLevelParserException("In TerrainInitTextures: dx*dy must be <100"); if (line->GetParam("table")->IsDefined()) { auto& table = line->GetParam("table")->AsArray(); if (table.size() > dx*dy) throw CLevelParserException("In TerrainInitTextures: table size must be dx*dy"); for (unsigned int i = 0; i < dx*dy; i++) { if (i >= table.size()) { tt[i] = 0; } else { tt[i] = table[i]->AsInt(); } } } else { for (unsigned int i = 0; i < dx*dy; i++) { tt[i] = 0; } } m_terrain->InitTextures(name.c_str(), tt, dx, dy); continue; } if (line->GetCommand() == "TerrainInit" && !resetObject) { m_terrain->InitMaterials(line->GetParam("id")->AsInt(1)); continue; } if (line->GetCommand() == "TerrainMaterial" && !resetObject) { std::string name = line->GetParam("image")->AsPath("textures"); if (name.find(".") == std::string::npos) name += ".png"; name = "../" + name; m_terrain->AddMaterial(line->GetParam("id")->AsInt(0), name.c_str(), { line->GetParam("u")->AsFloat(), line->GetParam("v")->AsFloat() }, line->GetParam("up")->AsInt(), line->GetParam("right")->AsInt(), line->GetParam("down")->AsInt(), line->GetParam("left")->AsInt(), line->GetParam("hard")->AsFloat(0.5f)); continue; } if (line->GetCommand() == "TerrainLevel" && !resetObject) { m_ui->GetLoadingScreen()->SetProgress(0.2f+(3.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_TEX); int id[50]; //TODO: I have no idea how TerrainLevel works, but maybe we should remove the limit to 50? if (line->GetParam("id")->IsDefined()) { auto& idArray = line->GetParam("id")->AsArray(); if (idArray.size() > 50) throw CLevelParserException("In TerrainLevel: id array size must be < 50"); unsigned int i = 0; while (i < 50) { id[i] = idArray[i]->AsInt(); i++; if (i >= idArray.size()) break; } id[i] = 0; } m_terrain->GenerateMaterials(id, line->GetParam("min")->AsFloat(0.0f)*g_unit, line->GetParam("max")->AsFloat(100.0f)*g_unit, line->GetParam("slope")->AsFloat(5.0f), line->GetParam("freq")->AsFloat(100.0f), line->GetParam("center")->AsPoint(glm::vec3(0.0f, 0.0f, 0.0f))*g_unit, line->GetParam("radius")->AsFloat(0.0f)*g_unit); continue; } if (line->GetCommand() == "TerrainCreate" && !resetObject) { m_ui->GetLoadingScreen()->SetProgress(0.2f+(4.f/5.f)*0.05f, RT_LOADING_TERRAIN, RT_LOADING_TERRAIN_GEN); m_terrain->CreateObjects(); continue; } if (line->GetCommand() == "BeginObject") { InitEye(); SetMovieLock(false); if (!resetObject) ChangeColor(); // changes the colors of texture if (!m_sceneReadPath.empty()) // loading file ? { m_ui->GetLoadingScreen()->SetProgress(0.25f, RT_LOADING_OBJECTS_SAVED); sel = IOReadScene(m_sceneReadPath + "/data.sav", m_sceneReadPath + "/cbot.run"); } else { m_ui->GetLoadingScreen()->SetProgress(0.25f, RT_LOADING_OBJECTS); } continue; } if (line->GetCommand() == "LevelController" && m_sceneReadPath.empty()) { if (m_controller != nullptr) { throw CLevelParserException("There can be only one LevelController in the level"); } m_controller = m_objMan->CreateObject(glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, OBJECT_CONTROLLER); assert(m_controller->Implements(ObjectInterfaceType::Programmable)); assert(m_controller->Implements(ObjectInterfaceType::ProgramStorage)); assert(m_controller->Implements(ObjectInterfaceType::Old)); dynamic_cast(*m_controller).SetCheckToken(false); if (line->GetParam("script")->IsDefined()) { CProgramStorageObject* programStorage = dynamic_cast(m_controller); Program* program = programStorage->AddProgram(); programStorage->ReadProgram(program, line->GetParam("script")->AsPath("ai")); program->readOnly = true; dynamic_cast(*m_controller).RunProgram(program); } continue; } if (line->GetCommand() == "CreateObject" && m_sceneReadPath.empty()) { ObjectCreateParams params = CObject::ReadCreateParams(line.get()); float objectProgress = static_cast(rankObj) / static_cast(numObjects); std::string details = StrUtils::ToString(rankObj+1)+" / "+StrUtils::ToString(numObjects); #if DEV_BUILD // Object categories may spoil the level a bit, so hide them in release builds details += ": "+CLevelParserParam::FromObjectType(params.type); #endif m_ui->GetLoadingScreen()->SetProgress(0.25f+objectProgress*0.75f, RT_LOADING_OBJECTS, details); try { CObject* obj = m_objMan->CreateObject(params); obj->Read(line.get()); if (m_fixScene && obj->GetType() == OBJECT_HUMAN) { assert(obj->Implements(ObjectInterfaceType::Movable)); CMotion* motion = dynamic_cast(*obj).GetMotion(); if (m_phase == PHASE_WIN ) motion->SetAction(MHS_WIN, 0.4f); if (m_phase == PHASE_LOST) motion->SetAction(MHS_LOST, 0.5f); } if (obj->Implements(ObjectInterfaceType::Controllable) && line->GetParam("select")->AsBool(false)) sel = obj; if (obj->GetType() == OBJECT_BASE) m_base = obj; if (obj->Implements(ObjectInterfaceType::ProgramStorage)) { CProgramStorageObject* programStorage = dynamic_cast(obj); if (obj->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(*obj).GetSelectable() && obj->GetType() != OBJECT_HUMAN) { programStorage->SetProgramStorageIndex(rankObj); } char categoryChar = GetLevelCategoryDir(m_levelCategory)[0]; programStorage->LoadAllProgramsForLevel( line.get(), m_playerProfile->GetSaveFile(StrUtils::Format("%c%.3d%.3d", categoryChar, m_levelChap, m_levelRank)), soluce ); } } catch (const CObjectCreateException& e) { GetLogger()->Error("Error loading level object: %s\n", e.what()); throw; } rankObj ++; continue; } if (line->GetCommand() == "CreateFog" && !resetObject) { Gfx::ParticleType type = static_cast(Gfx::PARTIFOG0+(line->GetParam("type")->AsInt())); glm::vec3 pos = line->GetParam("pos")->AsPoint()*g_unit; float height = line->GetParam("height")->AsFloat(1.0f)*g_unit; float ddim = line->GetParam("dim")->AsFloat(50.0f)*g_unit; float delay = line->GetParam("delay")->AsFloat(2.0f); m_terrain->AdjustToFloor(pos); pos.y += height; glm::vec2 dim; dim.x = ddim; dim.y = dim.x; m_particle->CreateParticle(pos, glm::vec3(0.0f, 0.0f, 0.0f), dim, type, delay, 0.0f, 0.0f); continue; } if (line->GetCommand() == "CreateLight" && !resetObject) { Gfx::EngineObjectType type; int lightRank = CreateLight(line->GetParam("dir")->AsPoint(), line->GetParam("color")->AsColor(Gfx::Color(0.5f, 0.5f, 0.5f, 1.0f))); type = line->GetParam("type")->AsTerrainType(Gfx::ENG_OBJTYPE_NULL); if (type == Gfx::ENG_OBJTYPE_TERRAIN) { m_lightMan->SetLightPriority(lightRank, Gfx::LIGHT_PRI_HIGHEST); m_lightMan->SetLightIncludeType(lightRank, Gfx::ENG_OBJTYPE_TERRAIN); } if (type == Gfx::ENG_OBJTYPE_QUARTZ) m_lightMan->SetLightIncludeType(lightRank, Gfx::ENG_OBJTYPE_QUARTZ); if (type == Gfx::ENG_OBJTYPE_METAL) m_lightMan->SetLightIncludeType(lightRank, Gfx::ENG_OBJTYPE_METAL); if (type == Gfx::ENG_OBJTYPE_FIX) m_lightMan->SetLightExcludeType(lightRank, Gfx::ENG_OBJTYPE_TERRAIN); continue; } if (line->GetCommand() == "CreateSpot" && !resetObject) { Gfx::EngineObjectType type; int rankLight = CreateSpot(line->GetParam("pos")->AsPoint()*g_unit, line->GetParam("color")->AsColor(Gfx::Color(0.5f, 0.5f, 0.5f, 1.0f))); type = line->GetParam("type")->AsTerrainType(Gfx::ENG_OBJTYPE_NULL); if (type == Gfx::ENG_OBJTYPE_TERRAIN) m_lightMan->SetLightIncludeType(rankLight, Gfx::ENG_OBJTYPE_TERRAIN); if (type == Gfx::ENG_OBJTYPE_QUARTZ) m_lightMan->SetLightIncludeType(rankLight, Gfx::ENG_OBJTYPE_QUARTZ); if (type == Gfx::ENG_OBJTYPE_METAL) m_lightMan->SetLightIncludeType(rankLight, Gfx::ENG_OBJTYPE_METAL); if (type == Gfx::ENG_OBJTYPE_FIX) m_lightMan->SetLightExcludeType(rankLight, Gfx::ENG_OBJTYPE_TERRAIN); continue; } if (line->GetCommand() == "GroundSpot" && !resetObject) { int rank = m_engine->CreateGroundSpot(); if (rank != -1) { m_engine->SetObjectGroundSpotPos(rank, line->GetParam("pos")->AsPoint(glm::vec3(0.0f, 0.0f, 0.0f))*g_unit); m_engine->SetObjectGroundSpotRadius(rank, line->GetParam("radius")->AsFloat(10.0f)*g_unit); m_engine->SetObjectGroundSpotColor(rank, line->GetParam("color")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f))); m_engine->SetObjectGroundSpotSmooth(rank, line->GetParam("smooth")->AsFloat(1.0f)); m_engine->SetObjectGroundSpotMinMax(rank, line->GetParam("min")->AsFloat(0.0f)*g_unit, line->GetParam("max")->AsFloat(0.0f)*g_unit); } continue; } if (line->GetCommand() == "WaterColor" && !resetObject) { m_engine->SetWaterAddColor(line->GetParam("color")->AsColor()); continue; } if (line->GetCommand() == "MapColor" && !resetObject) { m_map->FloorColorMap(line->GetParam("floor")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f)), line->GetParam("water")->AsColor(Gfx::Color(0.533f, 0.533f, 0.533f, 0.533f))); m_mapShow = line->GetParam("show")->AsBool(true); m_map->SetToy(line->GetParam("toyIcon")->AsBool(false)); m_mapImage = line->GetParam("image")->AsBool(false); if (m_mapImage) { glm::vec3 offset; strcpy(m_mapFilename, line->GetParam("filename")->AsPath("textures").c_str()); offset = line->GetParam("offset")->AsPoint(glm::vec3(0.0f, 0.0f, 0.0f)); m_map->SetFixParam(line->GetParam("zoom")->AsFloat(1.0f), offset.x, offset.z, line->GetParam("angle")->AsFloat(0.0f)*Math::PI/180.0f, line->GetParam("mode")->AsInt(0), line->GetParam("debug")->AsBool(false)); } continue; } if (line->GetCommand() == "MapZoom" && !resetObject) { m_map->ZoomMap(line->GetParam("factor")->AsFloat(2.0f)); m_map->MapEnable(line->GetParam("enable")->AsBool(true)); continue; } if (line->GetCommand() == "MaxFlyingHeight" && !resetObject) { m_terrain->SetFlyingMaxHeight(line->GetParam("max")->AsFloat(280.0f)*g_unit); continue; } if (line->GetCommand() == "AddFlyingHeight" && !resetObject) { m_terrain->AddFlyingLimit(line->GetParam("center")->AsPoint()*g_unit, line->GetParam("extRadius")->AsFloat(20.0f)*g_unit, line->GetParam("intRadius")->AsFloat(10.0f)*g_unit, line->GetParam("maxHeight")->AsFloat(200.0f)); continue; } if (line->GetCommand() == "Camera") { m_camera->Init(line->GetParam("eye")->AsPoint(glm::vec3(0.0f, 0.0f, 0.0f))*g_unit, line->GetParam("lookat")->AsPoint(glm::vec3(0.0f, 0.0f, 0.0f))*g_unit, resetObject ? 0.0f : line->GetParam("delay")->AsFloat(0.0f)); if (line->GetParam("fadeIn")->AsBool(false)) m_camera->StartOver(Gfx::CAM_OVER_EFFECT_FADEIN_WHITE, glm::vec3(0.0f, 0.0f, 0.0f), 1.0f); continue; } //! Note: This feature may be changed in next releases, //! Places new viewpoint, which can be selected later in (currently only in Code Battle) UI. //! Usage: View eye=x; y; z lookat=x; y; z if (line->GetCommand() == "View") { if(m_viewpoints.size() == 10) { GetLogger()->Warn("Reached limit of 10 viewpoints, next ones will be ommited.\n"); continue; } Viewpoint tmp; tmp.eye = line->GetParam("eye")->AsPoint()*g_unit; tmp.look = line->GetParam("lookat")->AsPoint()*g_unit; tmp.button = line->GetParam("button")->AsInt(13); // 13 is the camera button m_viewpoints.push_back(tmp); continue; } if (line->GetCommand() == "EndMissionTake" && !resetObject) { auto endTake = std::make_unique(); endTake->Read(line.get()); if (endTake->immediat) m_endTakeImmediat = true; m_endTake.push_back(std::move(endTake)); if (!line->GetParam("pos")->IsDefined() || !line->GetParam("dist")->IsDefined()) { LoadingWarning("The defaults for pos= and dist= are going to change, specify them explicitly. See issue #759 (https://git.io/vVBzH)"); } continue; } if (line->GetCommand() == "EndMissionTeams" && !resetObject) { m_endTakeTeamImmediateWin = line->GetParam("immediateWin")->AsBool(false); // false = finishing removes the team that finished, true = finishing for one team ends the whole game continue; } if (line->GetCommand() == "EndMissionDelay" && !resetObject) { m_endTakeWinDelay = line->GetParam("win")->AsFloat(2.0f); m_endTakeLostDelay = line->GetParam("lost")->AsFloat(2.0f); continue; } if (line->GetCommand() == "EndMissionResearch" && !resetObject) // This is not used in any original Colobot levels, but we'll keep it for userlevel creators { m_endTakeResearch |= line->GetParam("type")->AsResearchFlag(); continue; } if (line->GetCommand() == "EndMissionTimeout" && !resetObject) { m_endTakeTimeout = line->GetParam("time")->AsFloat(); continue; } if (line->GetCommand() == "Scoreboard" && !resetObject) { if (line->GetParam("enable")->AsBool(false)) { // Create the scoreboard m_scoreboard = std::make_unique(); m_scoreboard->SetSortType(line->GetParam("sort")->AsSortType(CScoreboard::SortType::SORT_ID)); } continue; } if (line->GetCommand() == "ScoreboardKillRule" && !resetObject) { if (!m_scoreboard) throw CLevelParserException("ScoreboardKillRule encountered but scoreboard is not enabled"); auto rule = std::make_unique(); rule->Read(line.get()); m_scoreboard->AddKillRule(std::move(rule)); continue; } if (line->GetCommand() == "ScoreboardObjectRule" && !resetObject) { if (!m_scoreboard) throw CLevelParserException("ScoreboardObjectRule encountered but scoreboard is not enabled"); auto rule = std::make_unique(); rule->Read(line.get()); m_scoreboard->AddObjectRule(std::move(rule)); continue; } if (line->GetCommand() == "ScoreboardEndTakeRule" && !resetObject) { if (!m_scoreboard) throw CLevelParserException("ScoreboardEndTakeRule encountered but scoreboard is not enabled"); auto rule = std::make_unique(); rule->Read(line.get()); m_scoreboard->AddEndTakeRule(std::move(rule)); continue; } if (line->GetCommand() == "ObligatoryToken" && !resetObject) { std::string token = line->GetParam("text")->AsString(); if (!line->GetParam("min")->IsDefined() && !line->GetParam("max")->IsDefined()) 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()); if (m_obligatoryTokens.count(token)) throw CLevelParserException("Incorrect ObligatoryToken specification - you cannot define a token twice"); 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 m_obligatoryTokens[token].max = line->GetParam("max")->AsInt(-1); if (m_obligatoryTokens[token].min >= 0 && m_obligatoryTokens[token].max >= 0 && m_obligatoryTokens[token].min > m_obligatoryTokens[token].max) { throw CLevelParserException("Incorrect ObligatoryToken specification - min cannot be greater than max"); } continue; } if (line->GetCommand() == "ProhibitedToken" && !resetObject) // NOTE: Kept only for backwards compatibility { std::string token = line->GetParam("text")->AsString(); GetLogger()->Warn("ProhibitedToken is only provided for backwards compatibility - instead, do this: ObligatoryToken text=\"%s\" max=0\n", token.c_str()); if (m_obligatoryTokens.count(token)) throw CLevelParserException("Incorrect ObligatoryToken specification - you cannot define a token twice"); m_obligatoryTokens[token].min = -1; m_obligatoryTokens[token].max = 0; continue; } if (line->GetCommand() == "EnableBuild" && !resetObject) { m_build |= line->GetParam("type")->AsBuildFlag(); continue; } if (line->GetCommand() == "EnableResearch" && !resetObject) { m_researchEnable |= line->GetParam("type")->AsResearchFlag(); continue; } if (line->GetCommand() == "DoneResearch" && m_sceneReadPath.empty() && !resetObject) // not loading file? { m_researchDone[0] |= line->GetParam("type")->AsResearchFlag(); continue; } if (line->GetCommand() == "NewScript" && !resetObject) { m_newScriptName.push_back(NewScriptName(line->GetParam("type")->AsObjectType(OBJECT_NULL), line->GetParam("name")->AsString(""))); continue; } 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) if (resetObject) continue; // ignore when reseting just objects (TODO: see above) throw CLevelParserException("Unknown command: '" + line->GetCommand() + "' in " + line->GetLevelFilename() + ":" + boost::lexical_cast(line->GetLineNumber())); } // Do this here to prevent the first frame from taking a long time to render m_engine->UpdateGroundSpotTextures(); m_ui->GetLoadingScreen()->SetProgress(1.0f, RT_LOADING_FINISHED); if (m_ui->GetLoadingScreen()->IsVisible()) { // Force render of the "Loading finished" screen because it looks weird when the progress bar disappears in the middle m_app->Render(); } if (!resetObject) { m_engine->SetBackground(backgroundPath, backgroundUp, backgroundDown, backgroundCloudUp, backgroundCloudDown, backgroundFull); } if (m_levelCategory == LevelCategory::Missions && !resetObject) // mission? { m_playerProfile->SetFreeGameResearchUnlock(m_playerProfile->GetFreeGameResearchUnlock() | m_researchDone[0]); m_playerProfile->SetFreeGameBuildUnlock(m_playerProfile->GetFreeGameBuildUnlock() | m_build); } if (m_levelCategory == LevelCategory::FreeGame && !resetObject) // free play? { m_researchDone[0] = m_playerProfile->GetFreeGameResearchUnlock(); m_build = m_playerProfile->GetFreeGameBuildUnlock(); m_build &= ~BUILD_RESEARCH; m_build &= ~BUILD_LABO; m_build |= BUILD_FACTORY; m_build |= BUILD_GFLAT; m_build |= BUILD_FLAG; } if (m_levelCategory == LevelCategory::GamePlus && !m_ui->GetPlusResearch() && !resetObject) // new game plus? { m_researchDone[0] |= m_playerProfile->GetFreeGameResearchUnlock(); m_build |= m_playerProfile->GetFreeGameBuildUnlock(); } if (!resetObject) { m_short->SetMode(false); // vehicles? } m_map->ShowMap(m_mapShow); m_map->UpdateMap(); // TODO: m_engine->TimeInit(); ?? m_input->ResetKeyStates(); m_time = 0.0f; if (m_sceneReadPath.empty()) m_gameTime = 0.0f; m_gameTimeAbsolute = 0.0f; m_autosaveLast = 0.0f; m_infoUsed = 0; m_selectObject = sel; if (m_base == nullptr && // no main base? !m_fixScene) // interractive scene? { CObject* obj = sel; if (sel == nullptr) obj = SearchHuman(); if (obj != nullptr) { assert(obj->Implements(ObjectInterfaceType::Controllable)); SelectObject(obj); m_camera->SetControllingObject(obj); m_camera->SetType(dynamic_cast(*obj).GetCameraType()); } } if (m_fixScene) m_camera->SetType(Gfx::CAM_TYPE_SCRIPT); if (!m_sceneReadPath.empty() && sel != nullptr) // loading file? { glm::vec3 pos = sel->GetPosition(); m_camera->Init(pos, pos, 0.0f); SelectObject(sel); m_camera->SetControllingObject(sel); m_beginSatCom = true; // message already displayed } } catch (...) { m_sceneReadPath = ""; throw; } m_sceneReadPath = ""; if (m_app->GetSceneTestMode()) m_eventQueue->AddEvent(Event(EVENT_QUIT)); m_ui->ShowLoadingScreen(false); if (m_missionType == MISSION_CODE_BATTLE) { CreateCodeBattleInterface(); } CreateShortcuts(); } void CRobotMain::LevelLoadingError(const std::string& error, const std::runtime_error& exception, Phase exitPhase) { m_ui->ShowLoadingScreen(false); GetLogger()->Error("%s\n", error.c_str()); GetLogger()->Error("%s\n", exception.what()); ChangePhase(exitPhase); m_ui->GetDialog()->StartInformation("Loading error", error, exception.what(), true, false); } //! Creates a directional light int CRobotMain::CreateLight(glm::vec3 direction, Gfx::Color color) { if (direction.x == 0.0f && direction.y == 0.0f && direction.z == 0.0f) { direction.y = -1.0f; } Gfx::Light light; light.type = Gfx::LIGHT_DIRECTIONAL; light.diffuse = color; light.ambient = color * 0.1f; light.direction = direction; int obj = m_lightMan->CreateLight(Gfx::LIGHT_PRI_HIGH); m_lightMan->SetLight(obj, light); return obj; } //! Creates a light spot int CRobotMain::CreateSpot(glm::vec3 pos, Gfx::Color color) { if (!m_engine->GetLightMode()) return -1; pos.y += m_terrain->GetFloorLevel(pos); Gfx::Light light; light.type = Gfx::LIGHT_SPOT; light.diffuse = color; light.ambient = color * 0.1f; light.position = pos; light.direction = glm::vec3(0.0f, -1.0f, 0.0f); light.spotIntensity = 1.0f; light.spotAngle = 90.0f*Math::PI/180.0f; light.attenuation0 = 2.0f; light.attenuation1 = 0.0f; light.attenuation2 = 0.0f; int obj = m_lightMan->CreateLight(Gfx::LIGHT_PRI_HIGH); m_lightMan->SetLight(obj, light); return obj; } //! Change the colors and textures void CRobotMain::ChangeColor() { if (m_phase != PHASE_SIMUL && m_phase != PHASE_SETUPds && m_phase != PHASE_SETUPgs && m_phase != PHASE_SETUPps && m_phase != PHASE_SETUPcs && m_phase != PHASE_SETUPss && m_phase != PHASE_MOD_LIST && m_phase != PHASE_WIN && m_phase != PHASE_LOST && m_phase != PHASE_APPEARANCE ) return; // Player texture glm::vec2 ts = { 0.0f, 0.0f }; glm::vec2 ti = { 1.0f, 1.0f }; // the entire image Gfx::Color colorRef1, colorNew1, colorRef2, colorNew2; colorRef1.a = 0.0f; colorRef2.a = 0.0f; colorRef1.r = 206.0f/256.0f; colorRef1.g = 206.0f/256.0f; colorRef1.b = 204.0f/256.0f; // ~white colorNew1 = m_playerProfile->GetAppearance().colorCombi; colorRef2.r = 255.0f/256.0f; colorRef2.g = 132.0f/256.0f; colorRef2.b = 1.0f/256.0f; // orange colorNew2 = m_playerProfile->GetAppearance().colorBand; glm::vec2 exclu[6]; exclu[0] = { 192.0f / 256.0f, 0.0f / 256.0f }; exclu[1] = { 256.0f / 256.0f, 64.0f / 256.0f }; // crystals + cylinders exclu[2] = { 208.0f / 256.0f, 224.0f / 256.0f }; exclu[3] = { 256.0f / 256.0f, 256.0f / 256.0f }; // SatCom screen exclu[4] = { 0.0f, 0.0f }; exclu[5] = { 0.0f, 0.0f }; // terminator //m_engine->ChangeTextureColor("textures/objects/human.png", colorRef1, colorNew1, colorRef2, colorNew2, 0.30f, 0.01f, ts, ti, exclu); float tolerance; int face = GetGamerFace(); if (face == 0) // normal? { colorRef1.r = 90.0f/256.0f; colorRef1.g = 95.0f/256.0f; colorRef1.b = 85.0f/256.0f; // black tolerance = 0.15f; } if (face == 1) // bald? { colorRef1.r = 74.0f/256.0f; colorRef1.g = 58.0f/256.0f; colorRef1.b = 46.0f/256.0f; // brown tolerance = 0.20f; } if (face == 2) // carlos? { colorRef1.r = 70.0f/256.0f; colorRef1.g = 40.0f/256.0f; colorRef1.b = 8.0f/256.0f; // brown tolerance = 0.30f; } if (face == 3) // blonde? { colorRef1.r = 74.0f/256.0f; colorRef1.g = 16.0f/256.0f; colorRef1.b = 0.0f/256.0f; // yellow tolerance = 0.20f; } colorNew1 = m_playerProfile->GetAppearance().colorHair; colorRef2.r = 0.0f; colorRef2.g = 0.0f; colorRef2.b = 0.0f; colorNew2.r = 0.0f; colorNew2.g = 0.0f; colorNew2.b = 0.0f; char name[100]; sprintf(name, "textures/objects/face%.2d.png", face+1); exclu[0] = { 105.0f / 256.0f, 47.0f / 166.0f }; exclu[1] = { 153.0f / 256.0f, 79.0f / 166.0f }; // blue canister exclu[2] = { 0.0f, 0.0f }; exclu[3] = { 0.0f, 0.0f }; // terminator //m_engine->ChangeTextureColor(name, colorRef1, colorNew1, colorRef2, colorNew2, tolerance, 0.00f, ts, ti, exclu); colorRef2.r = 0.0f; colorRef2.g = 0.0f; colorRef2.b = 0.0f; colorNew2.r = 0.0f; colorNew2.g = 0.0f; colorNew2.b = 0.0f; // VehicleColor for (const auto& it : m_colorNewBot) { int team = it.first; Gfx::Color newColor = it.second; std::string teamStr = StrUtils::ToString(team); if(team == 0) teamStr = ""; m_engine->SetTeamColor(team, newColor); //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); //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); //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); //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); //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); //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); //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); //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); exclu[0] = { 0.0f / 256.0f, 160.0f / 256.0f }; exclu[1] = { 256.0f / 256.0f, 256.0f / 256.0f }; // pencils exclu[2] = { 0.0f, 0.0f }; exclu[3] = { 0.0f, 0.0f }; // terminator //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); exclu[0] = { 237.0f / 256.0f, 176.0f / 256.0f }; exclu[1] = { 256.0f / 256.0f, 220.0f / 256.0f }; // blue canister exclu[2] = { 106.0f / 256.0f, 150.0f / 256.0f }; exclu[3] = { 130.0f / 256.0f, 214.0f / 256.0f }; // safe location exclu[4] = { 0.0f, 0.0f }; exclu[5] = { 0.0f, 0.0f }; // terminator //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); } // AlienColor exclu[0] = { 128.0f / 256.0f, 160.0f / 256.0f }; exclu[1] = { 256.0f / 256.0f, 256.0f / 256.0f }; // SatCom exclu[2] = { 0.0f, 0.0f }; exclu[3] = { 0.0f, 0.0f }; // terminator //m_engine->ChangeTextureColor("textures/objects/ant.png", COLOR_REF_ALIEN, m_colorNewAlien, colorRef2, colorNew2, 0.50f, -1.0f, ts, ti, exclu); //m_engine->ChangeTextureColor("textures/objects/mother.png", COLOR_REF_ALIEN, m_colorNewAlien, colorRef2, colorNew2, 0.50f, -1.0f, ts, ti); // GreeneryColor //m_engine->ChangeTextureColor("textures/objects/plant.png", COLOR_REF_GREEN, m_colorNewGreen, colorRef2, colorNew2, 0.50f, -1.0f, ts, ti); // water color // PARTIPLOUF0 and PARTIDROP : ts = { 0.500f, 0.500f }; ti = { 0.875f, 0.750f }; //m_engine->ChangeTextureColor("textures/effect00.png", COLOR_REF_WATER, m_colorNewWater, colorRef2, colorNew2, 0.20f, -1.0f, ts, ti, nullptr, m_colorShiftWater, true); // PARTIFLIC : ts = { 0.00f, 0.75f }; ti = { 0.25f, 1.00f }; //m_engine->ChangeTextureColor("textures/effect02.png", COLOR_REF_WATER, m_colorNewWater, colorRef2, colorNew2, 0.20f, -1.0f, ts, ti, nullptr, m_colorShiftWater, true); } //! Calculates the distance to the nearest object namespace { float SearchNearestObject(CObjectManager* objMan, glm::vec3 center, CObject* exclu) { float min = 100000.0f; for (CObject* obj : objMan->GetAllObjects()) { if (!obj->GetDetectable()) continue; // inactive? if (IsObjectBeingTransported(obj)) continue; if (obj == exclu) continue; ObjectType type = obj->GetType(); if (type == OBJECT_BASE) { glm::vec3 oPos = obj->GetPosition(); if (oPos.x != center.x || oPos.z != center.z) { float dist = glm::distance(center, oPos) - 80.0f; if (dist < 0.0f) dist = 0.0f; min = Math::Min(min, dist); continue; } } if (type == OBJECT_STATION || type == OBJECT_REPAIR || type == OBJECT_DESTROYER) { glm::vec3 oPos = obj->GetPosition(); float dist = glm::distance(center, oPos) - 8.0f; if (dist < 0.0f) dist = 0.0f; min = Math::Min(min, dist); } for (const auto &crashSphere : obj->GetAllCrashSpheres()) { glm::vec3 oPos = crashSphere.sphere.pos; float oRadius = crashSphere.sphere.radius; float dist = glm::distance(center, oPos) - oRadius; if (dist < 0.0f) dist = 0.0f; min = Math::Min(min, dist); } } return min; } } //! Calculates a free space bool CRobotMain::FreeSpace(glm::vec3 ¢er, float minRadius, float maxRadius, float space, CObject *exclu) { if (minRadius < maxRadius) // from internal to external? { for (float radius = minRadius; radius <= maxRadius; radius += space) { float ia = space/radius; for (float angle = 0.0f; angle < Math::PI*2.0f; angle += ia) { glm::vec2 p; p.x = center.x+radius; p.y = center.z; p = Math::RotatePoint({ center.x, center.z }, angle, p); glm::vec3 pos; pos.x = p.x; pos.z = p.y; pos.y = 0.0f; m_terrain->AdjustToFloor(pos, true); float dist = SearchNearestObject(m_objMan.get(), pos, exclu); if (dist >= space) { float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f); if (flat >= dist/2.0f) { center = pos; return true; } } } } } else // from external to internal? { for (float radius=maxRadius; radius >= minRadius; radius -= space) { float ia = space/radius; for (float angle=0.0f ; angleAdjustToFloor(pos, true); float dist = SearchNearestObject(m_objMan.get(), pos, exclu); if (dist >= space) { float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f); if (flat >= dist/2.0f) { center = pos; return true; } } } } } return false; } //! Calculates a flat free space bool CRobotMain::FlatFreeSpace(glm::vec3 ¢er, float minFlat, float minRadius, float maxRadius, float space, CObject *exclu) { if (minRadius < maxRadius) // from internal to external? { for (float radius = minRadius; radius <= maxRadius; radius += space) { float ia = space/radius; for (float angle = 0.0f; angle < Math::PI*2.0f; angle += ia) { glm::vec2 p; p.x = center.x+radius; p.y = center.z; p = Math::RotatePoint({ center.x, center.z }, angle, p); glm::vec3 pos; pos.x = p.x; pos.z = p.y; pos.y = 0.0f; m_terrain->AdjustToFloor(pos, true); float dist = SearchNearestObject(m_objMan.get(), pos, exclu); if (dist >= space) { float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f); if (flat >= dist/2.0f) { flat = m_terrain->GetFlatZoneRadius(pos, minFlat); if(flat >= minFlat) { center = pos; return true; } } } } } } else // from external to internal? { for (float radius=maxRadius; radius >= minRadius; radius -= space) { float ia = space/radius; for (float angle=0.0f ; angleAdjustToFloor(pos, true); float dist = SearchNearestObject(m_objMan.get(), pos, exclu); if (dist >= space) { float flat = m_terrain->GetFlatZoneRadius(pos, dist/2.0f); if (flat >= dist/2.0f) { flat = m_terrain->GetFlatZoneRadius(pos, minFlat); if(flat >= minFlat) { center = pos; return true; } } } } } } return false; } //! Calculates the maximum radius of a free space float CRobotMain::GetFlatZoneRadius(glm::vec3 center, float maxRadius, CObject *exclu) { float dist = SearchNearestObject(m_objMan.get(), center, exclu); if (dist == 0.0f) return 0.0f; if (dist < maxRadius) maxRadius = dist; return m_terrain->GetFlatZoneRadius(center, maxRadius); } //! Hides buildable area when a cube of metal is taken up void CRobotMain::HideDropZone(CObject* metal) { if (m_showLimit[1].used && m_showLimit[1].link == metal) { FlushShowLimit(1); } if (m_showLimit[2].used && m_showLimit[2].link == metal) { FlushShowLimit(2); } } //! Shows the buildable area when a cube of metal is deposited void CRobotMain::ShowDropZone(CObject* metal, CObject* transporter) { if (metal == nullptr) return; glm::vec3 center = metal->GetPosition(); // Calculates the maximum radius possible depending on other items. float oMax = 30.0f; // radius to build the biggest building float tMax; for (CObject* obj : m_objMan->GetAllObjects()) { if (!obj->GetDetectable()) continue; // inactive? if (IsObjectBeingTransported(obj)) continue; if (obj == metal) continue; if (obj == transporter) continue; glm::vec3 oPos; ObjectType type = obj->GetType(); if (type == OBJECT_BASE) { oPos = obj->GetPosition(); float dist = glm::distance(center, oPos)-80.0f; oMax = Math::Min(oMax, dist); } else { for (const auto& crashSphere : obj->GetAllCrashSpheres()) { float dist = glm::distance(center, crashSphere.sphere.pos)-crashSphere.sphere.radius; oMax = Math::Min(oMax, dist); } } if ( type == OBJECT_DERRICK || type == OBJECT_FACTORY || type == OBJECT_STATION || type == OBJECT_CONVERT || type == OBJECT_REPAIR || type == OBJECT_DESTROYER|| type == OBJECT_TOWER || type == OBJECT_RESEARCH || type == OBJECT_RADAR || type == OBJECT_ENERGY || type == OBJECT_LABO || type == OBJECT_NUCLEAR || type == OBJECT_START || type == OBJECT_END || type == OBJECT_INFO || type == OBJECT_PARA || type == OBJECT_SAFE || type == OBJECT_HUSTON ) // building? { for (const auto& crashSphere : obj->GetAllCrashSpheres()) { float dist = glm::distance(center, crashSphere.sphere.pos)-crashSphere.sphere.radius-BUILDMARGIN; oMax = Math::Min(oMax, dist); } } } // Calculates the maximum possible radius depending on terrain. if (oMax >= 2.0f) tMax = m_terrain->GetFlatZoneRadius(center, 30.0f); else tMax = 0.0f; float radius = Math::Min(oMax, tMax); if (radius >= 2.0f) SetShowLimit(1, Gfx::PARTILIMIT2, metal, center, radius, 10.0f); } //! Erases the boundaries shown void CRobotMain::FlushShowLimit(int i) { for (int j = 0; j < m_showLimit[i].total; j++) { if (m_showLimit[i].parti[j] == 0) continue; m_particle->DeleteParticle(m_showLimit[i].parti[j]); m_showLimit[i].parti[j] = 0; } m_showLimit[i].total = 0; m_showLimit[i].link = nullptr; m_showLimit[i].used = false; } //! Specifies the boundaries to show void CRobotMain::SetShowLimit(int i, Gfx::ParticleType parti, CObject *obj, glm::vec3 pos, float radius, float duration) { FlushShowLimit(i); // erases the current boundaries if (radius <= 0.0f) return; glm::vec2 dim; float dist; if (radius <= 50.0f) { dim = { 0.3f, 0.3f }; dist = 2.5f; } else { dim = { 1.5f, 1.5f }; dist = 10.0f; } m_showLimit[i].used = true; m_showLimit[i].link = obj; m_showLimit[i].pos = pos; m_showLimit[i].radius = radius; m_showLimit[i].duration = duration; m_showLimit[i].total = static_cast((radius*2.0f*Math::PI)/dist); if (m_showLimit[i].total > MAXSHOWPARTI) m_showLimit[i].total = MAXSHOWPARTI; m_showLimit[i].time = 0.0f; for (int j = 0; j < m_showLimit[i].total; j++) { m_showLimit[i].parti[j] = m_particle->CreateParticle(pos, glm::vec3(0.0f, 0.0f, 0.0f), dim, parti, duration); } } //! Mount the boundaries of the selected object void CRobotMain::StartShowLimit() { CObject* obj = GetSelect(); if (obj == nullptr) return; if (!obj->Implements(ObjectInterfaceType::Ranged)) return; float range = dynamic_cast(*obj).GetShowLimitRadius(); if (range == 0.0f) return; SetShowLimit(0, Gfx::PARTILIMIT1, obj, obj->GetPosition(), range); } //! Advances the boundaries shown void CRobotMain::FrameShowLimit(float rTime) { if (m_engine->GetPause()) return; for (int i = 0; i < MAXSHOWLIMIT; i++) { if (!m_showLimit[i].used) continue; m_showLimit[i].time += rTime; if (m_showLimit[i].time >= m_showLimit[i].duration) { FlushShowLimit(i); continue; } float factor; if (m_showLimit[i].time < 1.0f) factor = m_showLimit[i].time; else if (m_showLimit[i].time > m_showLimit[i].duration-1.0f) factor = m_showLimit[i].duration-m_showLimit[i].time; else factor = 1.0f; float speed = 0.4f-m_showLimit[i].radius*0.001f; if (speed < 0.1f) speed = 0.1f; float angle = m_showLimit[i].time*speed; if (m_showLimit[i].link != nullptr) { m_showLimit[i].pos = m_showLimit[i].link->GetPosition(); } for (int j = 0; j < m_showLimit[i].total; j++) { if (m_showLimit[i].parti[j] == 0) continue; glm::vec2 center; center.x = m_showLimit[i].pos.x; center.y = m_showLimit[i].pos.z; glm::vec2 rotate; rotate.x = center.x+m_showLimit[i].radius*factor; rotate.y = center.y; rotate = Math::RotatePoint(center, angle, rotate); glm::vec3 pos; pos.x = rotate.x; pos.z = rotate.y; pos.y = 0.0f; m_terrain->AdjustToFloor(pos, true); if (m_showLimit[i].radius <= 50.0f) pos.y += 0.5f; else pos.y += 2.0f; m_particle->SetPosition(m_showLimit[i].parti[j], pos); angle += (2.0f*Math::PI)/m_showLimit[i].total; } } } //! Saves all programs of all the robots void CRobotMain::SaveAllScript() { for (CObject* obj : m_objMan->GetAllObjects()) { SaveOneScript(obj); } } //! Saves all programs of the robot. void CRobotMain::SaveOneScript(CObject *obj) { if (! obj->Implements(ObjectInterfaceType::ProgramStorage)) return; CProgramStorageObject* programStorage = dynamic_cast(obj); char categoryChar = GetLevelCategoryDir(m_levelCategory)[0]; programStorage->SaveAllUserPrograms(m_playerProfile->GetSaveFile(StrUtils::Format("%c%.3d%.3d", categoryChar, m_levelChap, m_levelRank))); } //! Saves the stack of the program in execution of a robot bool CRobotMain::SaveFileStack(CObject *obj, std::ostream &ostr) { if (! obj->Implements(ObjectInterfaceType::Programmable)) return true; CProgrammableObject* programmable = dynamic_cast(obj); ObjectType type = obj->GetType(); if (type == OBJECT_HUMAN) return true; long status = 1; std::stringstream sstr(""); if (!programmable->WriteStack(sstr)) { GetLogger()->Error("WriteStack failed at object id = %i\n", obj->GetID()); status = 100; // marked bad } if (!CBot::WriteLong(ostr, status)) return false; if (!CBot::WriteStream(ostr, sstr)) return false; return true; } //! Resumes the execution stack of the program in a robot bool CRobotMain::ReadFileStack(CObject *obj, std::istream &istr) { if (! obj->Implements(ObjectInterfaceType::Programmable)) return true; CProgrammableObject* programmable = dynamic_cast(obj); ObjectType type = obj->GetType(); if (type == OBJECT_HUMAN) return true; long status; if (!CBot::ReadLong(istr, status)) return false; if (status == 100) // was marked bad ? { if (!CBot::ReadLong(istr, status)) return false; if (!istr.seekg(status, istr.cur)) return false; return true; // next program } if (status == 1) { std::stringstream sstr(""); if (!CBot::ReadStream(istr, sstr)) return false; if (!programmable->ReadStack(sstr)) { GetLogger()->Error("ReadStack failed at object id = %i\n", obj->GetID()); } return true; // next program } return false; // error: status == ?? } std::vector CRobotMain::GetNewScriptNames(ObjectType type) { std::vector names; for (const auto& newScript : m_newScriptName) { if (newScript.type == type || newScript.type == OBJECT_NULL ) { names.push_back(newScript.name); } } return names; } //! Seeks if an object occupies in a spot, to prevent a backup of the game bool CRobotMain::IOIsBusy() { if (CScriptFunctions::CheckOpenFiles()) return true; for (CObject* obj : m_objMan->GetAllObjects()) { if (! obj->Implements(ObjectInterfaceType::TaskExecutor)) continue; if (obj->Implements(ObjectInterfaceType::Programmable) && dynamic_cast(*obj).IsProgram()) continue; // TODO: I'm not sure if this is correct but this is how it worked earlier if (dynamic_cast(*obj).IsForegroundTask()) return true; } return false; } //! Writes an object into the backup file void CRobotMain::IOWriteObject(CLevelParserLine* line, CObject* obj, const std::string& programDir, int objRank) { line->AddParam("type", std::make_unique(obj->GetType())); line->AddParam("id", std::make_unique(obj->GetID())); line->AddParam("pos", std::make_unique(obj->GetPosition()/g_unit)); line->AddParam("angle", std::make_unique(obj->GetRotation() * Math::RAD_TO_DEG)); line->AddParam("zoom", std::make_unique(obj->GetScale())); if (obj->Implements(ObjectInterfaceType::Old)) { line->AddParam("option", std::make_unique(obj->GetOption())); } if (obj->Implements(ObjectInterfaceType::Controllable)) { auto controllableObj = dynamic_cast(obj); line->AddParam("trainer", std::make_unique(controllableObj->GetTrainer())); if (controllableObj->GetSelect()) line->AddParam("select", std::make_unique(true)); } obj->Write(line); if (obj->GetType() == OBJECT_BASE) line->AddParam("run", std::make_unique(3)); // stops and open (PARAM_FIXSCENE) if (obj->Implements(ObjectInterfaceType::ProgramStorage)) { CProgramStorageObject* programStorage = dynamic_cast(obj); if (programStorage->GetProgramStorageIndex() >= 0) { programStorage->SaveAllProgramsForSavedScene(line, programDir); } else { // Probably an object created after the scene started, not loaded from level file // This means it doesn't normally store programs so it doesn't have program storage id assigned programStorage->SetProgramStorageIndex(999-objRank); // Set something that won't collide with normal programs programStorage->SaveAllProgramsForSavedScene(line, programDir); programStorage->SetProgramStorageIndex(-1); // Disable again } if (obj->Implements(ObjectInterfaceType::Programmable)) { int run = dynamic_cast(*obj).GetProgramIndex(dynamic_cast(*obj).GetCurrentProgram()); if (run != -1) { line->AddParam("run", std::make_unique(run+1)); } } } } //! Saves the current game bool CRobotMain::IOWriteScene(std::string filename, std::string filecbot, std::string filescreenshot, const std::string& info, bool emergencySave) { if (!emergencySave) { // Render the indicator to show that we are working ShowSaveIndicator(true); m_app->Render(); // update } std::string dirname = filename.substr(0, filename.find_last_of("/")); CLevelParser levelParser(filename); CLevelParserLineUPtr line; line = std::make_unique("Title"); line->AddParam("text", std::make_unique(std::string(info))); levelParser.AddLine(std::move(line)); //TODO: Do we need that? It's not used anyway line = std::make_unique("Version"); line->AddParam("maj", std::make_unique(0)); line->AddParam("min", std::make_unique(1)); levelParser.AddLine(std::move(line)); line = std::make_unique("Created"); line->AddParam("date", std::make_unique(static_cast(time(nullptr)))); levelParser.AddLine(std::move(line)); line = std::make_unique("Mission"); line->AddParam("base", std::make_unique(GetLevelCategoryDir(m_levelCategory))); if (m_levelCategory == LevelCategory::CustomLevels) line->AddParam("dir", std::make_unique(GetCustomLevelDir())); else line->AddParam("chap", std::make_unique(m_levelChap)); line->AddParam("rank", std::make_unique(m_levelRank)); line->AddParam("gametime", std::make_unique(GetGameTime())); levelParser.AddLine(std::move(line)); line = std::make_unique("Map"); line->AddParam("zoom", std::make_unique(m_map->GetZoomMap())); levelParser.AddLine(std::move(line)); line = std::make_unique("DoneResearch"); line->AddParam("bits", std::make_unique(static_cast(m_researchDone[0]))); levelParser.AddLine(std::move(line)); float sleep, delay, magnetic, progress; if (m_lightning->GetStatus(sleep, delay, magnetic, progress)) { line = std::make_unique("BlitzMode"); line->AddParam("sleep", std::make_unique(sleep)); line->AddParam("delay", std::make_unique(delay)); line->AddParam("magnetic", std::make_unique(magnetic/g_unit)); line->AddParam("progress", std::make_unique(progress)); levelParser.AddLine(std::move(line)); } int objRank = 0; for (CObject* obj : m_objMan->GetAllObjects()) { if (obj->GetType() == OBJECT_TOTO) continue; if (IsObjectBeingTransported(obj)) continue; if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*obj).IsDying()) continue; if (obj->Implements(ObjectInterfaceType::Slotted)) { CSlottedObject* slotted = dynamic_cast(obj); for (int slot = slotted->GetNumSlots() - 1; slot >= 0; slot--) { if (CObject *sub = slotted->GetSlotContainedObject(slot)) { if (slot == slotted->MapPseudoSlot(CSlottedObject::Pseudoslot::POWER)) line = std::make_unique("CreatePower"); else if (slot == slotted->MapPseudoSlot(CSlottedObject::Pseudoslot::CARRYING)) line = std::make_unique("CreateFret"); else line = std::make_unique("CreateSlotObject"); line->AddParam("slotNum", std::make_unique(slot)); IOWriteObject(line.get(), sub, dirname, objRank++); levelParser.AddLine(std::move(line)); } } } line = std::make_unique("CreateObject"); IOWriteObject(line.get(), obj, dirname, objRank++); levelParser.AddLine(std::move(line)); } try { levelParser.Save(); } catch (CLevelParserException& e) { GetLogger()->Error("Failed to save level state - %s\n", e.what()); // TODO add visual error to notify user that save failed return false; } // Writes the file of stacks of execution. COutputStream ostr(filecbot); if (!ostr.is_open()) return false; bool bError = false; long version = 1; CBot::WriteLong(ostr, version); // version of COLOBOT version = CBot::CBotProgram::GetVersion(); CBot::WriteLong(ostr, version); // version of CBOT CBot::WriteWord(ostr, 0); // TODO for (CObject* obj : m_objMan->GetAllObjects()) { if (obj->GetType() == OBJECT_TOTO) continue; if (IsObjectBeingTransported(obj)) continue; if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*obj).IsDying()) continue; if (!SaveFileStack(obj, ostr)) { GetLogger()->Error("SaveFileStack failed at object id = %i\n", obj->GetID()); bError = true; break; } } if (!bError && !CBot::CBotClass::SaveStaticState(ostr)) { GetLogger()->Error("CBotClass save static state failed\n"); } ostr.close(); if (!emergencySave) { ShowSaveIndicator(false); // force hide for screenshot MouseMode oldMouseMode = m_app->GetMouseMode(); m_app->SetMouseMode(MOUSE_NONE); // disable the mouse m_displayText->HideText(true); // hide m_engine->SetScreenshotMode(true); m_engine->Render(); // update (but don't show, we're not swapping buffers here!) m_engine->WriteScreenShot(filescreenshot); m_shotSaving++; m_engine->SetScreenshotMode(false); m_displayText->HideText(false); m_app->SetMouseMode(oldMouseMode); m_app->ResetTimeAfterLoading(); } return true; } //! Notifies the user that scene write is finished void CRobotMain::IOWriteSceneFinished() { m_displayText->DisplayError(INFO_WRITEOK, glm::vec3(0.0f,0.0f,0.0f)); m_shotSaving--; } //! Resumes the game CObject* CRobotMain::IOReadObject(CLevelParserLine *line, const std::string& programDir, const std::string& objCounterText, float objectProgress, int objRank) { ObjectCreateParams params = CObject::ReadCreateParams(line); params.power = -1.0f; params.id = line->GetParam("id")->AsInt(); std::string details = objCounterText; #if DEV_BUILD // Object categories may spoil the level a bit, so hide them in release builds details += ": "+CLevelParserParam::FromObjectType(params.type); #endif m_ui->GetLoadingScreen()->SetProgress(0.25f+objectProgress*0.7f, RT_LOADING_OBJECTS_SAVED, details); CObject* obj = m_objMan->CreateObject(params); if (obj->Implements(ObjectInterfaceType::Old)) { COldObject* oldObj = dynamic_cast(obj); oldObj->SetPosition(line->GetParam("pos")->AsPoint() * g_unit); oldObj->SetRotation(line->GetParam("angle")->AsPoint() * Math::DEG_TO_RAD); } if (obj->GetType() == OBJECT_BASE) m_base = obj; obj->Read(line); int run = line->GetParam("run")->AsInt(-1); if (run != -1) { CAuto* automat = obj->GetAuto(); if (automat != nullptr) automat->Start(run); // starts the film } if (obj->Implements(ObjectInterfaceType::ProgramStorage)) { CProgramStorageObject* programStorage = dynamic_cast(obj); if (!line->GetParam("programStorageIndex")->IsDefined()) // Backwards compatibility programStorage->SetProgramStorageIndex(objRank); programStorage->LoadAllProgramsForSavedScene(line, programDir); } return obj; } //! Resumes some part of the game CObject* CRobotMain::IOReadScene(std::string filename, std::string filecbot) { std::string dirname = filename.substr(0, filename.find_last_of("/")); CLevelParser levelParser(filename); levelParser.SetLevelPaths(m_levelCategory, m_levelChap, m_levelRank); levelParser.Load(); int numObjects = levelParser.CountLines("CreateObject") + levelParser.CountLines("CreatePower") + levelParser.CountLines("CreateFret") + levelParser.CountLines("CreateSlotObject"); m_base = nullptr; CObject* cargo = nullptr; CObject* power = nullptr; CObject* sel = nullptr; int objRank = 0; int objCounter = 0; std::map slots; for (auto& line : levelParser.GetLines()) { if (line->GetCommand() == "Mission") m_gameTime = line->GetParam("gametime")->AsFloat(0.0f); if (line->GetCommand() == "Map") m_map->ZoomMap(line->GetParam("zoom")->AsFloat()); if (line->GetCommand() == "DoneResearch") m_researchDone[0] = line->GetParam("bits")->AsInt(); if (line->GetCommand() == "BlitzMode") { float sleep = line->GetParam("sleep")->AsFloat(); float delay = line->GetParam("delay")->AsFloat(); float magnetic = line->GetParam("magnetic")->AsFloat()*g_unit; float progress = line->GetParam("progress")->AsFloat(); m_lightning->SetStatus(sleep, delay, magnetic, progress); } if (line->GetCommand() == "CreateFret") { cargo = IOReadObject(line.get(), dirname, StrUtils::ToString(objCounter+1)+" / "+StrUtils::ToString(numObjects), static_cast(objCounter) / static_cast(numObjects)); objCounter++; } if (line->GetCommand() == "CreatePower") { power = IOReadObject(line.get(), dirname, StrUtils::ToString(objCounter+1)+" / "+StrUtils::ToString(numObjects), static_cast(objCounter) / static_cast(numObjects)); objCounter++; } if (line->GetCommand() == "CreateSlotObject") { int slotNum = line->GetParam("slotNum")->AsInt(); CObject *slotObject = IOReadObject(line.get(), dirname, StrUtils::ToString(objCounter+1)+" / "+StrUtils::ToString(numObjects), static_cast(objCounter) / static_cast(numObjects)); objCounter++; assert(slots.find(slotNum) == slots.end()); slots.emplace(slotNum, slotObject); } if (line->GetCommand() == "CreateObject") { CObject* obj = IOReadObject(line.get(), dirname, StrUtils::ToString(objCounter+1)+" / "+StrUtils::ToString(numObjects), static_cast(objCounter) / static_cast(numObjects), objRank++); if (line->GetParam("select")->AsBool(false)) sel = obj; if (obj->Implements(ObjectInterfaceType::Slotted)) { CSlottedObject* asSlotted = dynamic_cast(obj); if (cargo != nullptr) { int slotNum = asSlotted->MapPseudoSlot(CSlottedObject::Pseudoslot::CARRYING); assert(slotNum >= 0); assert(slots.find(slotNum) == slots.end()); asSlotted->SetSlotContainedObject(slotNum, cargo); // TODO: eww! assert(obj->Implements(ObjectInterfaceType::Old)); auto task = std::make_unique(dynamic_cast(obj)); task->Start(TMO_AUTO, TMA_GRAB); // holds the object! } if (power != nullptr) { int slotNum = asSlotted->MapPseudoSlot(CSlottedObject::Pseudoslot::POWER); assert(slotNum >= 0); assert(slots.find(slotNum) == slots.end()); asSlotted->SetSlotContainedObject(slotNum, power); } for (std::pair& slot : slots) { asSlotted->SetSlotContainedObject(slot.first, slot.second); } cargo = nullptr; power = nullptr; slots.clear(); } else { // TODO: exception? assert(slots.empty()); assert(power == nullptr); assert(cargo == nullptr); } if (power != nullptr) { dynamic_cast(*obj).SetSlotContainedObjectReq(CSlottedObject::Pseudoslot::POWER, power); dynamic_cast(*power).SetTransporter(obj); } cargo = nullptr; power = nullptr; objCounter++; } } // all slot objects assigned to parent objects assert(slots.empty()); assert(power == nullptr); assert(cargo == nullptr); m_ui->GetLoadingScreen()->SetProgress(0.95f, RT_LOADING_CBOT_SAVE); // Reads the file of stacks of execution. CInputStream istr(filecbot); if (istr.is_open()) { bool bError = false; long version = 0; CBot::ReadLong(istr, version); // version of COLOBOT if (version == 1) { CBot::ReadLong(istr, version); // version of CBOT if (version == CBot::CBotProgram::GetVersion()) { unsigned short flag; CBot::ReadWord(istr, flag); // TODO bError = (flag != 0); if (!bError) for (CObject* obj : m_objMan->GetAllObjects()) { if (obj->GetType() == OBJECT_TOTO) continue; if (IsObjectBeingTransported(obj)) continue; if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*obj).IsDying()) continue; if (!ReadFileStack(obj, istr)) { GetLogger()->Error("ReadFileStack failed at object id = %i\n", obj->GetID()); bError = true; break; } } if (!bError && !CBot::CBotClass::RestoreStaticState(istr)) { GetLogger()->Error("CBotClass restore static state failed\n"); bError = true; } } else GetLogger()->Error("cbot.run file is wrong version: %i\n", version); } if (bError) GetLogger()->Error("Restoring CBOT state failed at stream position: %li\n", istr.tellg()); istr.close(); } m_ui->GetLoadingScreen()->SetProgress(1.0f, RT_LOADING_FINISHED); return sel; } //! Changes current player void CRobotMain::SelectPlayer(std::string playerName) { assert(!playerName.empty()); m_playerProfile = std::make_unique(playerName); SetGlobalGamerName(playerName); } CPlayerProfile* CRobotMain::GetPlayerProfile() { return m_playerProfile.get(); } //! Resets all objects to their original position void CRobotMain::ResetObject() { // schedule reset during next frame m_resetCreate = true; } //! Resets all objects to their original position void CRobotMain::ResetCreate() { SaveAllScript(); // Removes all bullets in progress. m_particle->DeleteParticle(Gfx::PARTIGUN1); m_particle->DeleteParticle(Gfx::PARTIGUN2); m_particle->DeleteParticle(Gfx::PARTIGUN3); m_particle->DeleteParticle(Gfx::PARTIGUN4); DeselectAll(); // removes the control buttons DeleteAllObjects(); // removes all the current 3D Scene m_particle->FlushParticle(); m_terrain->FlushBuildingLevel(); m_camera->SetType(Gfx::CAM_TYPE_NULL); try { CreateScene(m_ui->GetSceneSoluce(), false, true); for (CObject* obj : m_objMan->GetAllObjects()) { if (obj->GetAnimateOnReset()) { m_engine->GetPyroManager()->Create(Gfx::PT_RESET, obj); } } } catch (const std::runtime_error& e) { LevelLoadingError("An error occurred while trying to reset scene", e); } } //! Updates the audiotracks void CRobotMain::UpdateAudio(bool frame) { for(std::unique_ptr& audioChange : m_audioChange) { if (audioChange->changed) continue; if (audioChange->Check()) { GetLogger()->Info("Changing music to \"%s\"\n", audioChange->music.c_str()); m_sound->PlayMusic(audioChange->music, audioChange->repeat); audioChange->changed = true; } } } //! Set mission result from LevelController script void CRobotMain::SetMissionResultFromScript(Error result, float delay) { m_endTakeWinDelay = delay; m_endTakeLostDelay = delay; m_missionResult = result; m_missionResultFromScript = true; } Error CRobotMain::ProcessEndMissionTakeForGroup(std::vector& endTakes) { Error finalResult = ERR_OK; bool hasWinningConditions = false; for (CSceneEndCondition* endTake : endTakes) { Error result = endTake->GetMissionResult(); if (endTake->lost < 0) hasWinningConditions = true; if (result == ERR_OK && endTake->immediat) { hasWinningConditions = true; finalResult = result; break; } if (result != ERR_OK) { finalResult = result; break; } } if (finalResult == ERR_OK && !hasWinningConditions) finalResult = ERR_MISSION_NOTERM; // Never end mission without ending conditions return finalResult; } //! Process EndMissionTake commands, result is stored in m_missionResult //! If return value is different than ERR_MISSION_NOTERM, assume the mission is finished and pass on the result Error CRobotMain::ProcessEndMissionTake() { bool timeout = false; if (m_missionResult != INFO_LOST && m_missionResult != INFO_LOSTq) { if (m_endTakeTimeout >= 0.0f) { // Use the mission timer if available, or global mission time otherwise // Useful for exercises where the time starts when you start the program, not the mission itself float currentTime = m_missionTimerEnabled ? m_missionTimer : m_gameTime; if (currentTime > m_endTakeTimeout) { m_missionResult = INFO_LOST; timeout = true; } } } // Sort end conditions by teams std::map> teamsEndTake; for (std::unique_ptr& endTake : m_endTake) teamsEndTake[endTake->winTeam].push_back(endTake.get()); // This is just a smart way to check if we have any map values other than 0 defined bool usesTeamConditions = teamsEndTake.size() > teamsEndTake.count(0); if (!usesTeamConditions) { if (!timeout) m_missionResult = ProcessEndMissionTakeForGroup(teamsEndTake[0]); if (m_missionResult != INFO_LOST && m_missionResult != INFO_LOSTq) { if (m_endTakeResearch != 0) { if (m_endTakeResearch != (m_endTakeResearch&m_researchDone[0])) { m_missionResult = ERR_MISSION_NOTERM; } } } } else { assert(m_endTakeResearch == 0); // TODO: Add support for per-team EndTakeResearch // Special handling for teams m_missionResult = ERR_MISSION_NOTERM; if (GetAllActiveTeams().empty() || timeout) { GetLogger()->Info("All teams died, mission ended\n"); if (m_scoreboard) { std::string title, text, details_line; GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS, title); if (m_missionTimerEnabled && m_missionTimerStarted) { GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS_TIME, text); text = StrUtils::Format(text.c_str(), TimeFormat(m_missionTimer).c_str()); } else { GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS_TEXT, text); } GetResource(RES_TEXT, RT_SCOREBOARD_RESULTS_LINE, details_line); std::string details = ""; for (std::pair team : m_scoreboard->GetSortedScores()) { if (!details.empty()) details += ", "; details += StrUtils::Format(details_line.c_str(), GetTeamName(team.first).c_str(), team.second.points); } m_ui->GetDialog()->StartInformation( title, text, details, false, true, [&]() { ChangePhase(PHASE_WIN); } ); m_endTakeWinDelay = 0.0f; m_missionResult = ERR_OK; } else { m_missionResult = INFO_LOST; } } else { for (auto it : teamsEndTake) { int team = it.first; if (team == 0) continue; if (m_teamFinished[team]) continue; Error result = ProcessEndMissionTakeForGroup(it.second); if (result == INFO_LOST || result == INFO_LOSTq) { GetLogger()->Info("Team %d lost\n", team); std::string text; GetResource(RES_ERR, INFO_TEAM_DEAD, text); text = StrUtils::Format(text.c_str(), GetTeamName(team).c_str()); m_displayText->DisplayText(text.c_str(), glm::vec3(0.0f,0.0f,0.0f), 15.0f, 60.0f, 10.0f, Ui::TT_ERROR); m_displayText->SetEnable(false); // To prevent "bot destroyed" messages m_objMan->DestroyTeam(team); m_displayText->SetEnable(true); m_teamFinished[team] = true; } else if (result == ERR_OK) { /*if (m_winDelay == 0.0f) { GetLogger()->Info("Team %d won\n", team); m_displayText->DisplayText(("<<< Team "+boost::lexical_cast(team)+" won the game >>>").c_str(), glm::vec3(0.0f,0.0f,0.0f)); if (m_missionTimerEnabled && m_missionTimerStarted) { GetLogger()->Info("Mission time: %s\n", TimeFormat(m_missionTimer).c_str()); m_displayText->DisplayText(("Time: " + TimeFormat(m_missionTimer)).c_str(), glm::vec3(0.0f,0.0f,0.0f)); } m_missionTimerEnabled = m_missionTimerStarted = false; m_winDelay = m_endTakeWinDelay; // wins in two seconds m_lostDelay = 0.0f; m_displayText->SetEnable(false); } m_missionResult = ERR_OK; return ERR_OK;*/ GetLogger()->Info("Team %d finished\n", team); std::string text; GetResource(RES_ERR, INFO_TEAM_FINISH, text); text = StrUtils::Format(text.c_str(), GetTeamName(team).c_str()); m_displayText->DisplayText(text.c_str(), glm::vec3(0.0f,0.0f,0.0f)); if (m_scoreboard) m_scoreboard->ProcessEndTake(team); m_objMan->DestroyTeam(team, DestructionType::Win); m_teamFinished[team] = true; if (m_endTakeTeamImmediateWin) { // All other teams fail for(int other_team : GetAllActiveTeams()) { m_displayText->SetEnable(false); // To prevent "bot destroyed" messages m_objMan->DestroyTeam(other_team); m_displayText->SetEnable(true); m_teamFinished[other_team] = true; } } } } } } return ERR_MISSION_NOTERM; } //! Checks if the mission is over Error CRobotMain::CheckEndMission(bool frame) { // Process EndMissionTake, unless we are using LevelController script for processing ending conditions if (!m_missionResultFromScript) { Error result = ProcessEndMissionTake(); if (result != ERR_MISSION_NOTERM) return result; } // Take action depending on m_missionResult if (m_missionResult == INFO_LOSTq) { if (m_lostDelay == 0.0f) { m_lostDelay = 0.1f; // lost immediately m_winDelay = 0.0f; } m_missionTimerEnabled = m_missionTimerStarted = false; m_displayText->SetEnable(false); return INFO_LOSTq; } if (m_missionResult == INFO_LOST) { if (m_lostDelay == 0.0f) { m_displayText->DisplayError(INFO_LOST, glm::vec3(0.0f,0.0f,0.0f)); m_lostDelay = m_endTakeLostDelay; // lost in 6 seconds m_winDelay = 0.0f; } m_missionTimerEnabled = m_missionTimerStarted = false; m_displayText->SetEnable(false); return INFO_LOST; } if (m_missionResult == ERR_OK) { if (m_endTakeWinDelay == -1.0f && m_winDelay == 0.0f) { m_winDelay = 1.0f; // wins in one second m_lostDelay = 0.0f; m_missionTimerEnabled = m_missionTimerStarted = false; m_displayText->SetEnable(false); return ERR_OK; // mission ended } if (frame) { if (m_base != nullptr && !m_endTakeImmediat) { assert(m_base->Implements(ObjectInterfaceType::Controllable)); if(dynamic_cast(*m_base).GetSelectable()) return ERR_MISSION_NOTERM; } } if (m_winDelay == 0.0f) { m_displayText->DisplayError(INFO_WIN, glm::vec3(0.0f,0.0f,0.0f)); if (m_missionTimerEnabled && m_missionTimerStarted) { GetLogger()->Info("Mission time: %s\n", TimeFormat(m_missionTimer).c_str()); m_displayText->DisplayText(("Time: " + TimeFormat(m_missionTimer)).c_str(), glm::vec3(0.0f,0.0f,0.0f)); } m_missionTimerEnabled = m_missionTimerStarted = false; m_winDelay = m_endTakeWinDelay; // wins in two seconds m_lostDelay = 0.0f; } m_displayText->SetEnable(false); return ERR_OK; // mission ended } else { m_displayText->SetEnable(true); return ERR_MISSION_NOTERM; } } //! Returns the list instructions required in CBot program in level const std::map& CRobotMain::GetObligatoryTokenList() { return m_obligatoryTokens; } //! Indicates whether it is possible to control a driving robot bool CRobotMain::GetTrainerPilot() { return m_cheatTrainerPilot; } bool CRobotMain::GetPlusTrainer() { return m_ui->GetPlusTrainer(); } bool CRobotMain::GetPlusExplorer() { return m_ui->GetPlusExplorer(); } //! Indicates whether the scene is fixed, without interaction bool CRobotMain::GetFixScene() { return m_fixScene; } const std::string& CRobotMain::GetScriptName() { return m_scriptName; } const std::string& CRobotMain::GetScriptFile() { return m_scriptFile; } bool CRobotMain::GetShowSoluce() { return m_cheatShowSoluce; } bool CRobotMain::GetSceneSoluce() { if (m_infoFilename[SATCOM_SOLUCE][0] == 0) return false; return m_ui->GetSceneSoluce(); } bool CRobotMain::GetShowAll() { return m_cheatAllMission; } bool CRobotMain::GetRadar() { if (m_cheatRadar) return true; for (CObject* obj : m_objMan->GetAllObjects()) { ObjectType type = obj->GetType(); if (type == OBJECT_RADAR && !obj->GetLock()) return true; } return false; } MissionType CRobotMain::GetMissionType() { return m_missionType; } //! Returns the representation to use for the player int CRobotMain::GetGamerFace() { return m_playerProfile->GetAppearance().face; } //! Returns the representation to use for the player int CRobotMain::GetGamerGlasses() { return m_playerProfile->GetAppearance().glasses; } //! Returns the mode with just the head bool CRobotMain::GetGamerOnlyHead() { return m_ui->GetGamerOnlyHead(); } //! Returns the angle of presentation float CRobotMain::GetPersoAngle() { return m_ui->GetPersoAngle(); } void CRobotMain::SetLevel(LevelCategory cat, int chap, int rank) { GetLogger()->Debug("Change level to %s %d %d\n", GetLevelCategoryDir(cat).c_str(), chap, rank); m_levelCategory = cat; m_levelChap = chap; m_levelRank = rank; m_levelFile = CLevelParser::BuildScenePath(m_levelCategory, m_levelChap, m_levelRank); } LevelCategory CRobotMain::GetLevelCategory() { return m_levelCategory; } int CRobotMain::GetLevelChap() { return m_levelChap; } int CRobotMain::GetLevelRank() { return m_levelRank; } //! Returns folder name of the scene that user selected to play. std::string CRobotMain::GetCustomLevelDir() { assert(m_levelCategory == LevelCategory::CustomLevels); return m_ui->GetCustomLevelName(m_levelChap); } void CRobotMain::SetReadScene(std::string path) { m_sceneReadPath = path; } void CRobotMain::UpdateChapterPassed() { return m_ui->UpdateChapterPassed(); } //! Changes game speed void CRobotMain::SetSpeed(float speed) { speed = glm::clamp(speed, MIN_SPEED, MAX_SPEED); m_app->SetSimulationSpeed(speed); UpdateSpeedLabel(); } float CRobotMain::GetSpeed() { return m_app->GetSimulationSpeed(); } void CRobotMain::UpdateSpeedLabel() { Ui::CButton* pb = static_cast(m_interface->SearchControl(EVENT_SPEED)); float speed = m_app->GetSimulationSpeed(); if (pb != nullptr) { if (speed == 1.0f) { pb->ClearState(Ui::STATE_VISIBLE); } else { char text[10]; sprintf(text, "x%.1f", speed); pb->SetName(text); pb->SetState(Ui::STATE_VISIBLE); } } } bool CRobotMain::CreateShortcuts() { if (m_phase != PHASE_SIMUL) return false; if (m_ui->GetLoadingScreen()->IsVisible()) return false; if (!m_shortCut) return false; return m_short->CreateShortcuts(); } //! Updates the map void CRobotMain::UpdateMap() { m_map->UpdateMap(); } //! Indicates whether the mini-map is visible bool CRobotMain::GetShowMap() { return m_mapShow; } //! Management of the lock mode for movies void CRobotMain::SetMovieLock(bool lock) { m_movieLock = lock; CreateShortcuts(); m_map->ShowMap(!m_movieLock && m_mapShow && !m_ui->GetLoadingScreen()->IsVisible()); if (m_movieLock) HiliteClear(); } bool CRobotMain::GetMovieLock() { return m_movieLock; } bool CRobotMain::GetInfoLock() { return m_displayInfo != nullptr; // info in progress? } //! Management of the blocking of the call of SatCom void CRobotMain::SetSatComLock(bool lock) { m_satComLock = lock; } bool CRobotMain::GetSatComLock() { return m_satComLock; } //! Management of the lock mode for the edition void CRobotMain::SetEditLock(bool lock, bool edit) { m_editLock = lock; CreateShortcuts(); // Do not remove the card if it contains a still image. if (!lock || !m_map->GetFixImage()) m_map->ShowMap(!m_editLock && m_mapShow); m_displayText->HideText(lock); m_input->ResetKeyStates(); if (m_editLock) HiliteClear(); else m_editFull = false; } bool CRobotMain::GetEditLock() { return m_editLock; } //! Management of the fullscreen mode during editing void CRobotMain::SetEditFull(bool full) { m_editFull = full; } bool CRobotMain::GetEditFull() { return m_editFull; } //! Indicates whether mouse is on an friend object, on which we should not shoot void CRobotMain::SetFriendAim(bool friendAim) { m_friendAim = friendAim; } bool CRobotMain::GetFriendAim() { return m_friendAim; } //! Starts music with a mission void CRobotMain::StartMusic() { GetLogger()->Debug("Starting music...\n"); if (m_audioTrack != "") { m_sound->PlayMusic(m_audioTrack, m_audioRepeat, 0.0f); } } void CRobotMain::UpdatePause(PauseType pause) { m_engine->SetPause(pause & PAUSE_ENGINE); m_camera->SetFreeze(pause & PAUSE_CAMERA); m_sound->MuteAll(pause & PAUSE_MUTE_SOUND); CreateShortcuts(); if (pause != PAUSE_NONE) HiliteClear(); } void CRobotMain::UpdatePauseMusic(PauseMusic music) { switch (music) { case PAUSE_MUSIC_NONE: m_sound->StopPauseMusic(); break; case PAUSE_MUSIC_EDITOR: if (m_editorTrack != "") m_sound->PlayPauseMusic(m_editorTrack, m_editorRepeat); break; case PAUSE_MUSIC_SATCOM: if (m_satcomTrack != "") m_sound->PlayPauseMusic(m_satcomTrack, m_satcomRepeat); break; } } //! Removes hilite and tooltip void CRobotMain::ClearInterface() { HiliteClear(); // removes setting evidence m_tooltipName.clear(); // really removes the tooltip } void CRobotMain::DisplayError(Error err, CObject* pObj, float time) { m_displayText->DisplayError(err, pObj, time); } void CRobotMain::DisplayError(Error err, glm::vec3 goal, float height, float dist, float time) { m_displayText->DisplayError(err, goal, height, dist, time); } void CRobotMain::UpdateCustomLevelList() { m_ui->UpdateCustomLevelList(); } std::string CRobotMain::GetCustomLevelName(int id) { return m_ui->GetCustomLevelName(id); } const std::vector& CRobotMain::GetCustomLevelList() { return m_ui->GetCustomLevelList(); } bool CRobotMain::IsLoading() { return m_ui->GetLoadingScreen()->IsVisible(); } void CRobotMain::StartMissionTimer() { if (m_missionTimerEnabled && !m_missionTimerStarted) { GetLogger()->Info("Starting mission timer...\n"); m_missionTimerStarted = true; } } void CRobotMain::SetAutosave(bool enable) { if (m_autosave == enable) return; m_autosave = enable; m_autosaveLast = m_gameTimeAbsolute; } bool CRobotMain::GetAutosave() { return m_autosave; } void CRobotMain::SetAutosaveInterval(int interval) { if (m_autosaveInterval == interval) return; m_autosaveInterval = interval; m_autosaveLast = m_gameTimeAbsolute; } int CRobotMain::GetAutosaveInterval() { return m_autosaveInterval; } void CRobotMain::SetAutosaveSlots(int slots) { if (m_autosaveSlots == slots) return; m_autosaveSlots = slots; } int CRobotMain::GetAutosaveSlots() { return m_autosaveSlots; } // Remove oldest saves with autosave prefix void CRobotMain::AutosaveRotate() { if (m_playerProfile == nullptr) return; GetLogger()->Debug("Rotate autosaves...\n"); auto saveDirs = CResourceManager::ListDirectories(m_playerProfile->GetSaveDir()); const std::string autosavePrefix = "autosave"; std::vector autosaves; std::copy_if(saveDirs.begin(), saveDirs.end(), std::back_inserter(autosaves), [&](const std::string &save) { return save.substr(0, autosavePrefix.length()) == autosavePrefix; }); std::sort(autosaves.begin(), autosaves.end(), std::less()); for (int i = 0; i < static_cast(autosaves.size()) - m_autosaveSlots + 1; i++) { CResourceManager::RemoveExistingDirectory(m_playerProfile->GetSaveDir() + "/" + autosaves[i]); } } void CRobotMain::Autosave() { AutosaveRotate(); GetLogger()->Info("Autosave!\n"); char timestr[100]; char infostr[100]; time_t now = time(nullptr); strftime(timestr, 99, "%y%m%d%H%M%S", localtime(&now)); strftime(infostr, 99, "%y.%m.%d %H:%M", localtime(&now)); std::string info = std::string("[AUTOSAVE] ") + infostr; std::string dir = m_playerProfile->GetSaveFile(std::string("autosave") + timestr); m_playerProfile->SaveScene(dir, info); } void CRobotMain::QuickSave() { GetLogger()->Info("Quicksave!\n"); char infostr[100]; time_t now = time(nullptr); strftime(infostr, 99, "%y.%m.%d %H:%M", localtime(&now)); std::string info = std::string("[QUICKSAVE]") + infostr; std::string dir = m_playerProfile->GetSaveFile(std::string("quicksave")); m_playerProfile->SaveScene(dir, info); } void CRobotMain::QuickLoad() { std::string dir = m_playerProfile->GetSaveFile(std::string("quicksave")); if(!CResourceManager::Exists(dir)) { m_displayText->DisplayError(ERR_NO_QUICK_SLOT, glm::vec3(0.0f,0.0f,0.0f), 15.0f, 60.0f, 1000.0f); GetLogger()->Debug("Quicksave slot not found\n"); return; } m_playerProfile->LoadScene(dir); } void CRobotMain::LoadSaveFromDirName(const std::string& gameDir) { std::string dir = m_playerProfile->GetSaveFile(gameDir); if(!CResourceManager::Exists(dir)) { GetLogger()->Error("Save slot not found\n"); return; } m_playerProfile->LoadScene(dir); } void CRobotMain::SetExitAfterMission(bool exit) { m_exitAfterMission = exit; } bool CRobotMain::CanPlayerInteract() { if(GetMissionType() == MISSION_CODE_BATTLE) { return !m_codeBattleStarted; } return true; } const std::string NO_TEAM_NAME = "Team"; const std::string& CRobotMain::GetTeamName(int id) { if(m_teamNames.count(id) == 0) return NO_TEAM_NAME; return m_teamNames[id]; } bool CRobotMain::IsTeamColorDefined(int id) { if(id == 0) return false; // Always use default for team 0 return m_colorNewBot.find(id) != m_colorNewBot.end(); } int CRobotMain::GetEnableBuild() { return m_build; } void CRobotMain::SetEnableBuild(int enableBuild) { m_build = enableBuild; } int CRobotMain::GetEnableResearch() { return m_researchEnable; } void CRobotMain::SetEnableResearch(int enableResearch) { m_researchEnable = enableResearch; } int CRobotMain::GetDoneResearch(int team) { return m_researchDone[team]; } void CRobotMain::SetDoneResearch(int doneResearch, int team) { m_researchDone[team] = doneResearch; } bool CRobotMain::IsBuildingEnabled(BuildType type) { return (m_build & type) != 0; } bool CRobotMain::IsBuildingEnabled(ObjectType type) { if(type == OBJECT_DERRICK) return IsBuildingEnabled(BUILD_DERRICK); if(type == OBJECT_FACTORY) return IsBuildingEnabled(BUILD_FACTORY); if(type == OBJECT_STATION) return IsBuildingEnabled(BUILD_STATION); if(type == OBJECT_CONVERT) return IsBuildingEnabled(BUILD_CONVERT); if(type == OBJECT_REPAIR) return IsBuildingEnabled(BUILD_REPAIR); if(type == OBJECT_TOWER) return IsBuildingEnabled(BUILD_TOWER); if(type == OBJECT_RESEARCH) return IsBuildingEnabled(BUILD_RESEARCH); if(type == OBJECT_RADAR) return IsBuildingEnabled(BUILD_RADAR); if(type == OBJECT_ENERGY) return IsBuildingEnabled(BUILD_ENERGY); if(type == OBJECT_LABO) return IsBuildingEnabled(BUILD_LABO); if(type == OBJECT_NUCLEAR) return IsBuildingEnabled(BUILD_NUCLEAR); if(type == OBJECT_INFO) return IsBuildingEnabled(BUILD_INFO); if(type == OBJECT_PARA) return IsBuildingEnabled(BUILD_PARA); if(type == OBJECT_SAFE) return IsBuildingEnabled(BUILD_SAFE); if(type == OBJECT_DESTROYER) return IsBuildingEnabled(BUILD_DESTROYER); return false; } bool CRobotMain::IsResearchEnabled(ResearchType type) { return (m_researchEnable & type) != 0; } bool CRobotMain::IsResearchDone(ResearchType type, int team) { if(team != 0 && m_researchDone.count(team) == 0) { // Initialize with defaults m_researchDone[team] = m_researchDone[0]; } return (m_researchDone[team] & type) != 0; } void CRobotMain::MarkResearchDone(ResearchType type, int team) { if(team != 0 && m_researchDone.count(team) == 0) { // Initialize with defaults m_researchDone[team] = m_researchDone[0]; } m_researchDone[team] |= type; if(team == 0) { m_playerProfile->SetFreeGameResearchUnlock(m_playerProfile->GetFreeGameResearchUnlock() | m_researchDone[0]); } } Error CRobotMain::CanBuildError(ObjectType type, int team) { if(!IsBuildingEnabled(type)) return ERR_BUILD_DISABLED; if(type == OBJECT_TOWER && !IsResearchDone(RESEARCH_TOWER, team)) return ERR_BUILD_RESEARCH; if(type == OBJECT_NUCLEAR && !IsResearchDone(RESEARCH_ATOMIC, team)) return ERR_BUILD_RESEARCH; return ERR_OK; } Error CRobotMain::CanFactoryError(ObjectType type, int team) { ToolType tool = GetToolFromObject(type); DriveType drive = GetDriveFromObject(type); if (tool == ToolType::Sniffer && !IsResearchDone(RESEARCH_SNIFFER, team)) return ERR_BUILD_RESEARCH; if (tool == ToolType::Shooter && !IsResearchDone(RESEARCH_CANON, team)) return ERR_BUILD_RESEARCH; if (tool == ToolType::OrganicShooter && !IsResearchDone(RESEARCH_iGUN, team)) return ERR_BUILD_RESEARCH; if (tool == ToolType::Builder && !IsResearchDone(RESEARCH_BUILDER, team)) return ERR_BUILD_RESEARCH; if (drive == DriveType::Tracked && !IsResearchDone(RESEARCH_TANK, team)) return ERR_BUILD_RESEARCH; if (drive == DriveType::Winged && !IsResearchDone(RESEARCH_FLY, team)) return ERR_BUILD_RESEARCH; if (drive == DriveType::Legged && !IsResearchDone(RESEARCH_iPAW, team)) return ERR_BUILD_RESEARCH; if (drive == DriveType::Heavy && !IsResearchDone(RESEARCH_TANK, team)) return ERR_BUILD_RESEARCH; if (type == OBJECT_MOBILErt && !IsResearchDone(RESEARCH_THUMP, team)) return ERR_BUILD_RESEARCH; if (type == OBJECT_MOBILErc && !IsResearchDone(RESEARCH_PHAZER, team)) return ERR_BUILD_RESEARCH; if (type == OBJECT_MOBILErr && !IsResearchDone(RESEARCH_RECYCLER, team)) return ERR_BUILD_RESEARCH; if (type == OBJECT_MOBILErs && !IsResearchDone(RESEARCH_SHIELD, team)) return ERR_BUILD_RESEARCH; if (type == OBJECT_MOBILEsa && !IsResearchDone(RESEARCH_SUBM, team)) return ERR_BUILD_DISABLED; // Can be only researched manually in Scene file if (type == OBJECT_MOBILEst && !IsResearchDone(RESEARCH_SUBM, team)) return ERR_BUILD_DISABLED; if (type == OBJECT_MOBILEtg && !IsResearchDone(RESEARCH_TARGET, team)) return ERR_BUILD_RESEARCH; if (tool == ToolType::Other && drive == DriveType::Other && type != OBJECT_MOBILEtg) return ERR_WRONG_OBJ; return ERR_OK; } void CRobotMain::PushToSelectionHistory(CObject* obj) { if (!m_selectionHistory.empty() && m_selectionHistory.back() == obj) return; // already in history m_selectionHistory.push_back(obj); if (m_selectionHistory.size() > 50) // to avoid infinite growth m_selectionHistory.pop_front(); } CObject* CRobotMain::PopFromSelectionHistory() { if (m_selectionHistory.empty()) return nullptr; CObject* obj = m_selectionHistory.back(); m_selectionHistory.pop_back(); return obj; } void CRobotMain::RemoveFromSelectionHistory(CObject* object) { auto it = std::remove_if(m_selectionHistory.begin(), m_selectionHistory.end(), [object](const CObject* obj) { return obj == object; }); m_selectionHistory.erase(it, m_selectionHistory.end()); } float CRobotMain::GetGlobalMagnifyDamage() { return m_globalMagnifyDamage; } float CRobotMain::GetGlobalNuclearCapacity() { return m_globalNuclearCapacity; } float CRobotMain::GetGlobalCellCapacity() { return m_globalCellCapacity; } // Beginning of the effect when the instruction "detect" is used. void CRobotMain::StartDetectEffect(COldObject* object, CObject* target) { glm::vec3 goal; glm::vec2 dim; glm::mat4 mat = object->GetWorldMatrix(0); glm::vec3 pos = Math::Transform(mat, glm::vec3(2.0f, 3.0f, 0.0f)); if ( target == nullptr ) { goal = Math::Transform(mat, glm::vec3(50.0f, 3.0f, 0.0f)); } else { goal = target->GetPosition(); goal.y += 3.0f; goal = Math::SegmentPoint(pos, goal, glm::distance(pos, goal)-3.0f); } dim.x = 3.0f; dim.y = dim.x; m_particle->CreateRay(pos, goal, Gfx::PARTIRAY2, dim, 0.2f); if ( target != nullptr ) { goal = target->GetPosition(); goal.y += 3.0f; goal = Math::SegmentPoint(pos, goal, glm::distance(pos, goal)-1.0f); dim.x = 6.0f; dim.y = dim.x; m_particle->CreateParticle(goal, glm::vec3(0.0f, 0.0f, 0.0f), dim, target != nullptr ? Gfx::PARTIGLINT : Gfx::PARTIGLINTr, 0.5f); } m_sound->Play(target != nullptr ? SOUND_BUILD : SOUND_RECOVER); } void CRobotMain::CreateCodeBattleInterface() { if (m_phase == PHASE_SIMUL) { glm::vec2 pos, ddim; float offset = (ceil(m_viewpoints.size() / 2.0f) * 50); int numTeams = m_scoreboard ? GetAllTeams().size() : 0; assert(numTeams < EVENT_SCOREBOARD_MAX-EVENT_SCOREBOARD+1); float textHeight = m_engine->GetText()->GetHeight(Gfx::FONT_COMMON, Gfx::FONT_SIZE_SMALL); //window ddim.x = 100.0f/640.0f; ddim.y = (100.0f+offset)/480.0f + numTeams * textHeight; pos.x = 540.0f/640.0f; pos.y = 100.0f/480.0f; Ui::CWindow* pw = m_interface->CreateWindows(pos, ddim, 3, EVENT_WINDOW6); //label text ddim.x = 100.0f/640.0f; ddim.y = 16.0f/480.0f; pos.x = 540.0f/640.0f; pos.y = (178.0f+offset)/480.0f + numTeams * textHeight; std::string text; GetResource(RES_EVENT, EVENT_LABEL_CODE_BATTLE, text); pw->CreateLabel(pos, ddim, 0, EVENT_LABEL_CODE_BATTLE, text); //viewpoint selection section ddim.x = 40.0f/640.0f; ddim.y = 50.0f/640.0f; for(unsigned int i = 0; i < m_viewpoints.size(); i++) { //create button pos.x = (550.0f+40.0f*(i%2))/640.0f; pos.y = (130.0f+offset)/480.0f + numTeams * textHeight - 45.0f*(i/2)/480.0f; pw->CreateButton(pos, ddim, m_viewpoints[i].button, EventType(EVENT_VIEWPOINT0 + i)); } //start/camera button float titleBarSize = (11.0f/64.0f); // this is from the texture ddim.x = 80.0f/640.0f; ddim.y = ((1-titleBarSize)*100.0f-20.0f)/480.0f; pos.x = 550.0f/640.0f; pos.y = 110.0f/480.0f; if (!m_codeBattleStarted) { pw->CreateButton(pos, ddim, 21, EVENT_CODE_BATTLE_START); } else { pw->CreateButton(pos, ddim, 13, EVENT_CODE_BATTLE_SPECTATOR); } if (!m_scoreboard) return; pos.y += ddim.y; ddim.y = textHeight; int i = 0; auto teams = GetAllTeams(); for (auto it = teams.rbegin(); it != teams.rend(); ++it) { int team = *it; Ui::CControl* pl; ddim.x = 55.0f/640.0f; pl = m_codeBattleStarted ? static_cast(pw->CreateLabel(pos, ddim, 0, static_cast(EVENT_SCOREBOARD+2*(numTeams-i-1)+0), "XXXXX")) : static_cast(pw->CreateEdit( pos, ddim, 0, static_cast(EVENT_SCOREBOARD+2*(numTeams-i-1)+0))); pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); pl->SetFontSize(m_codeBattleStarted ? Gfx::FONT_SIZE_SMALL : Gfx::FONT_SIZE_SMALL*0.75f); m_codeBattleStarted ? pl->SetName(GetTeamName(team)) : static_cast(pl)->SetText(GetTeamName(team)); pos.x += 57.5f/640.0f; ddim.x = 22.5f/640.0f; pl = m_codeBattleStarted ? static_cast(pw->CreateLabel(pos, ddim, 0, static_cast(EVENT_SCOREBOARD+2*(numTeams-i-1)+1), "???")) : static_cast(pw->CreateEdit( pos, ddim, 0, static_cast(EVENT_SCOREBOARD+2*(numTeams-i-1)+1))); pl->SetTextAlign(Gfx::TEXT_ALIGN_RIGHT); pl->SetFontSize(m_codeBattleStarted ? Gfx::FONT_SIZE_SMALL : Gfx::FONT_SIZE_SMALL*0.75f); m_codeBattleStarted ? pl->SetName(StrUtils::ToString(m_scoreboard->GetScore(team).points)) : static_cast(pl)->SetText(StrUtils::ToString(m_scoreboard->GetScore(team).points)); pos.x -= 57.5f/640.0f; pos.y += ddim.y; i++; } } } void CRobotMain::ApplyCodeBattleInterface() { assert(GetMissionType() == MISSION_CODE_BATTLE); if (!m_scoreboard) return; Ui::CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW6)); assert(pw != nullptr); int i = 0; for (int team : GetAllTeams()) { Ui::CEdit* pl; pl = static_cast(pw->SearchControl(static_cast(EVENT_SCOREBOARD+2*i+0))); assert(pl != nullptr); m_teamNames[team] = pl->GetText(pl->GetTextLength()); pl = static_cast(pw->SearchControl(static_cast(EVENT_SCOREBOARD+2*i+1))); assert(pl != nullptr); m_scoreboard->SetScore(team, StrUtils::FromString(pl->GetText(pl->GetTextLength()))); i++; } } void CRobotMain::UpdateCodeBattleInterface() { assert(GetMissionType() == MISSION_CODE_BATTLE); if (!m_scoreboard) return; Ui::CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW6)); assert(pw != nullptr); int i = 0; for (std::pair team : m_scoreboard->GetSortedScores()) { Ui::CControl* pl; pl = pw->SearchControl(static_cast(EVENT_SCOREBOARD+2*i+0)); assert(pl != nullptr); pl->SetName(GetTeamName(team.first)); pl = pw->SearchControl(static_cast(EVENT_SCOREBOARD+2*i+1)); assert(pl != nullptr); pl->SetName(StrUtils::ToString(team.second.points)); i++; } } void CRobotMain::DestroyCodeBattleInterface() { m_viewpoints.clear(); m_interface->DeleteControl(EVENT_WINDOW6); } void CRobotMain::SetCodeBattleSpectatorMode(bool mode) { // Deselect object, but keep camera attached to it CObject* obj = DeselectAll(); if (m_codeBattleSpectator) obj = m_camera->GetControllingObject(); m_codeBattleSpectator = mode; SelectObject(obj, false); // this uses code battle selection mode already } void CRobotMain::UpdateDebugCrashSpheres() { if (m_debugCrashSpheres) { for (CObject* obj : m_objMan->GetAllObjects()) { for (const auto& crashSphere : obj->GetAllCrashSpheres()) { m_engine->RenderDebugSphere(crashSphere.sphere, glm::mat4(1.0f), Gfx::Color{0.0f, 0.0f, 1.0f, 1.0f}); } } } } void CRobotMain::SetDebugCrashSpheres(bool draw) { m_debugCrashSpheres = draw; } bool CRobotMain::GetDebugCrashSpheres() { return m_debugCrashSpheres; } void CRobotMain::PushToCommandHistory(std::string cmd) { if (!m_commandHistory.empty() && m_commandHistory.front() == cmd) // already in history return; m_commandHistory.push_front(cmd); if (m_commandHistory.size() > 50) // to avoid infinite growth m_commandHistory.pop_back(); } std::string CRobotMain::GetNextFromCommandHistory() { if (m_commandHistory.empty() || static_cast(m_commandHistory.size()) <= m_commandHistoryIndex + 1) // no next element return ""; return m_commandHistory[++m_commandHistoryIndex]; } std::string CRobotMain::GetPreviousFromCommandHistory() { if (m_commandHistory.empty() || m_commandHistoryIndex < 1) // first or none element selected return ""; return m_commandHistory[--m_commandHistoryIndex]; } CScoreboard* CRobotMain::GetScoreboard() { return m_scoreboard.get(); } std::set CRobotMain::GetAllTeams() { std::set teams = GetAllActiveTeams(); for(auto& it : m_teamFinished) { teams.insert(it.first); } return teams; } std::set CRobotMain::GetAllActiveTeams() { std::set teams; for (CObject* obj : m_objMan->GetAllObjects()) { int team = obj->GetTeam(); if (team == 0) continue; teams.insert(team); } return teams; }