diff --git a/resources/icons/walkie_talkie.png b/resources/icons/walkie_talkie.png new file mode 100644 index 0000000000..85c96a61d9 Binary files /dev/null and b/resources/icons/walkie_talkie.png differ diff --git a/source/main/AppContext.cpp b/source/main/AppContext.cpp index ed434d6fc1..d9dddb1bf4 100644 --- a/source/main/AppContext.cpp +++ b/source/main/AppContext.cpp @@ -253,21 +253,30 @@ bool AppContext::SetUpRendering() } catch (Ogre::Exception&) {} // Logged by OGRE } + } catch (Ogre::Exception& e) { - ErrorUtils::ShowError (_L("Startup error"), e.getFullDescription()); + ErrorUtils::ShowError (_L("Startup error"), e.getDescription()); return false; } - // Load renderer configuration - if (!m_ogre_root->restoreConfig()) + try { - const auto render_systems = App::GetAppContext()->GetOgreRoot()->getAvailableRenderers(); - if (!render_systems.empty()) - m_ogre_root->setRenderSystem(render_systems.front()); - else - ErrorUtils::ShowError (_L("Startup error"), _L("No render system plugin available. Check your plugins.cfg")); + // Load renderer configuration + if (!m_ogre_root->restoreConfig()) + { + const auto render_systems = App::GetAppContext()->GetOgreRoot()->getAvailableRenderers(); + if (!render_systems.empty()) + m_ogre_root->setRenderSystem(render_systems.front()); + else + ErrorUtils::ShowError (_L("Startup error"), _L("No render system plugin available. Check your plugins.cfg")); + } + } + catch (Ogre::Exception& e) + { + ErrorUtils::ShowError (_L("Error restoring settings from 'ogre.cfg'"), e.getDescription()); + return false; } const auto rs = m_ogre_root->getRenderSystemByName(App::app_rendersys_override->getStr()); diff --git a/source/main/CMakeLists.txt b/source/main/CMakeLists.txt index 4c41080f9f..8f56bc699d 100644 --- a/source/main/CMakeLists.txt +++ b/source/main/CMakeLists.txt @@ -134,6 +134,7 @@ set(SOURCE_FILES gui/panels/GUI_MultiplayerSelector.{h,cpp} gui/panels/GUI_MultiplayerClientList.{h,cpp} gui/panels/GUI_NodeBeamUtils.{h,cpp} + gui/panels/GUI_SceneLabels.{h,cpp} gui/panels/GUI_SimActorStats.{h,cpp} gui/panels/GUI_ScriptMonitor.{h,cpp} gui/panels/GUI_SimPerfStats.{h,cpp} diff --git a/source/main/GameContext.cpp b/source/main/GameContext.cpp index 946e7be90c..f20abf18a1 100644 --- a/source/main/GameContext.cpp +++ b/source/main/GameContext.cpp @@ -1078,7 +1078,7 @@ void GameContext::UpdateSimInputEvents(float dt) const Ogre::Vector3 position = App::GetGameContext()->GetPlayerCharacter()->getPosition(); ActorPtr nearest_actor = nullptr; float min_squared_distance = std::numeric_limits::max(); - for (ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors()) + for (const ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors()) { float squared_distance = position.squaredDistance(actor->ar_nodes[0].AbsPosition); if (squared_distance < min_squared_distance) @@ -1093,15 +1093,38 @@ void GameContext::UpdateSimInputEvents(float dt) nearest_actor->ar_import_commands && min_squared_distance < (nearest_actor->getMinCameraRadius()*nearest_actor->getMinCameraRadius())) { + bool asleep = nearest_actor->ar_state == ActorState::LOCAL_SLEEPING; // get commands for (int i = 1; i <= MAX_COMMANDS; i++) // BEWARE: commandkeys are indexed 1-MAX_COMMANDS! { int eventID = EV_COMMANDS_01 + (i - 1); - nearest_actor->ar_command_key[i].playerInputValue = RoR::App::GetInputEngine()->getEventValue(eventID); + const float eventVal = RoR::App::GetInputEngine()->getEventValue(eventID); + if (asleep && (eventVal != nearest_actor->ar_command_key[i].playerInputValue)) + { + // Wake up + nearest_actor->ar_state = ActorState::LOCAL_SIMULATED; + nearest_actor->ar_sleep_counter = 0.0f; + asleep = false; + } + + nearest_actor->ar_command_key[i].playerInputValue = eventVal; } + nearest_actor->ar_walkie_talkie = true; + App::GetGameContext()->GetPlayerCharacter()->cr_walkie_talkie = true; + } + else + { + if (nearest_actor) + nearest_actor->ar_walkie_talkie = false; + App::GetGameContext()->GetPlayerCharacter()->cr_walkie_talkie = false; } } + else + { + for (const ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors()) + actor->ar_walkie_talkie = false; + } // AI waypoint recording if (App::GetGuiManager()->TopMenubar.ai_rec) diff --git a/source/main/gameplay/Character.cpp b/source/main/gameplay/Character.cpp index 166599230d..09f4343d93 100644 --- a/source/main/gameplay/Character.cpp +++ b/source/main/gameplay/Character.cpp @@ -28,6 +28,7 @@ #include "Collisions.h" #include "GameContext.h" #include "GfxScene.h" +#include "GUIManager.h" #include "InputEngine.h" #include "MovableText.h" #include "Network.h" @@ -597,6 +598,7 @@ void RoR::GfxCharacter::BufferSimulationData() xc_simbuf.simbuf_actor_coupling = xc_character->GetActorCoupling(); xc_simbuf.simbuf_anim_name = xc_character->GetAnimName(); xc_simbuf.simbuf_anim_time = xc_character->GetAnimTime(); + xc_simbuf.simbuf_character_walkie_talkie = xc_character->cr_walkie_talkie; } void RoR::GfxCharacter::UpdateCharacterInScene() @@ -686,11 +688,10 @@ void RoR::GfxCharacter::UpdateCharacterInScene() as_cur->setTimePosition(xc_simbuf.simbuf_anim_time); } - // Multiplayer label #ifdef USE_SOCKETW if (App::mp_state->getEnum() == MpState::CONNECTED && !xc_simbuf.simbuf_actor_coupling) { - // From 'updateCharacterNetworkColor()' + // Update network color const String materialName = "tracks/" + xc_instance_name; MaterialPtr mat = MaterialManager::getSingleton().getByName(materialName); @@ -702,10 +703,11 @@ void RoR::GfxCharacter::UpdateCharacterInScene() state->setColourOperationEx(LBX_BLEND_CURRENT_ALPHA, LBS_MANUAL, LBS_CURRENT, color); } + // Draw multiplayer label if ((!xc_simbuf.simbuf_is_remote && !App::mp_hide_own_net_label->getBool()) || (xc_simbuf.simbuf_is_remote && !App::mp_hide_net_labels->getBool())) { - float camDist = (xc_scenenode->getPosition() - App::GetCameraManager()->GetCameraNode()->getPosition()).length(); + const float camDist = (xc_scenenode->getPosition() - App::GetCameraManager()->GetCameraNode()->getPosition()).length(); Ogre::Vector3 scene_pos = xc_scenenode->getPosition(); scene_pos.y += (1.9f + camDist / 100.0f); @@ -713,4 +715,15 @@ void RoR::GfxCharacter::UpdateCharacterInScene() } } #endif // USE_SOCKETW + + // Walkie talkie label (forwardcommands / importcommands) + if (xc_simbuf.simbuf_character_walkie_talkie) + { + const float camDist = (xc_scenenode->getPosition() - App::GetCameraManager()->GetCameraNode()->getPosition()).length(); + Ogre::Vector3 scene_pos = xc_scenenode->getPosition(); + scene_pos.y += (1.9f + camDist / 100.0f); + + App::GetGuiManager()->SceneLabels.DrawInstance(scene_pos, camDist, nullptr); + + } } diff --git a/source/main/gameplay/Character.h b/source/main/gameplay/Character.h index 21c6071367..26423a2962 100644 --- a/source/main/gameplay/Character.h +++ b/source/main/gameplay/Character.h @@ -64,6 +64,8 @@ class Character void SetActorCoupling(bool enabled, ActorPtr actor); GfxCharacter* SetupGfx(); + bool cr_walkie_talkie = false; + private: void ReportError(const char* detail); @@ -108,6 +110,7 @@ struct GfxCharacter ActorPtr simbuf_actor_coupling; std::string simbuf_anim_name; float simbuf_anim_time; // Intentionally left empty = forces initial update. + bool simbuf_character_walkie_talkie = false; }; ~GfxCharacter(); diff --git a/source/main/gfx/GfxActor.cpp b/source/main/gfx/GfxActor.cpp index ffd272ae71..8479815bb2 100644 --- a/source/main/gfx/GfxActor.cpp +++ b/source/main/gfx/GfxActor.cpp @@ -54,6 +54,8 @@ #include +#include + using namespace RoR; RoR::GfxActor::GfxActor(ActorPtr actor, ActorSpawner* spawner, std::string ogre_resource_group, @@ -1793,6 +1795,7 @@ void RoR::GfxActor::UpdateSimDataBuffer() m_simbuf.simbuf_speedo_highest_kph = m_actor->ar_speedo_max_kph; m_simbuf.simbuf_speedo_use_engine_max_rpm = m_actor->ar_gui_use_engine_max_rpm; + m_simbuf.simbuf_wakie_talkie = m_actor->ar_walkie_talkie; // Linked Actors m_linked_gfx_actors.clear(); @@ -1935,21 +1938,35 @@ void RoR::GfxActor::UpdateAeroEngines() void RoR::GfxActor::UpdateNetLabels(float dt) { - const bool is_remote = - m_simbuf.simbuf_actor_state == ActorState::NETWORKED_OK || - m_simbuf.simbuf_actor_state == ActorState::NETWORKED_HIDDEN; + const bool is_remote = + m_simbuf.simbuf_actor_state == ActorState::NETWORKED_OK || + m_simbuf.simbuf_actor_state == ActorState::NETWORKED_HIDDEN; - if (App::mp_hide_net_labels->getBool() || (!is_remote && App::mp_hide_own_net_label->getBool()) || App::mp_state->getEnum() != MpState::CONNECTED) - { - return; - } + if (App::mp_hide_net_labels->getBool() || (!is_remote && App::mp_hide_own_net_label->getBool()) || App::mp_state->getEnum() != MpState::CONNECTED) + { + return; + } + + float vlen = m_simbuf.simbuf_pos.distance(App::GetCameraManager()->GetCameraNode()->getPosition()); + + float y_offset = (m_simbuf.simbuf_aabb.getMaximum().y - m_simbuf.simbuf_pos.y) + (vlen / 100.0); + Ogre::Vector3 scene_pos = m_simbuf.simbuf_pos + Ogre::Vector3::UNIT_Y * y_offset; - float vlen = m_simbuf.simbuf_pos.distance(App::GetCameraManager()->GetCameraNode()->getPosition()); + App::GetGfxScene()->DrawNetLabel(scene_pos, vlen, m_simbuf.simbuf_net_username, m_simbuf.simbuf_net_colornum); - float y_offset = (m_simbuf.simbuf_aabb.getMaximum().y - m_simbuf.simbuf_pos.y) + (vlen / 100.0); - Ogre::Vector3 scene_pos = m_simbuf.simbuf_pos + Ogre::Vector3::UNIT_Y * y_offset; +} + +void RoR::GfxActor::UpdateWalkieTalkieLabels(float dt) +{ + if (!m_simbuf.simbuf_wakie_talkie) + return; + + float vlen = m_simbuf.simbuf_pos.distance(App::GetCameraManager()->GetCameraNode()->getPosition()); - App::GetGfxScene()->DrawNetLabel(scene_pos, vlen, m_simbuf.simbuf_net_username, m_simbuf.simbuf_net_colornum); + float y_offset = (m_simbuf.simbuf_aabb.getMaximum().y - m_simbuf.simbuf_pos.y) + (vlen / 100.0); + Ogre::Vector3 scene_pos = m_simbuf.simbuf_pos + Ogre::Vector3::UNIT_Y * y_offset; + + App::GetGuiManager()->SceneLabels.DrawInstance(scene_pos, vlen, m_actor); } @@ -3206,3 +3223,8 @@ void RoR::GfxActor::RemoveBeam(int beam_index) itor++; } } + +int RoR::GfxActor::getNumBeacons() const +{ + return std::count_if(m_props.begin(), m_props.end(), [](const Prop& p) { return p.pp_beacon_type != 0; }); +} diff --git a/source/main/gfx/GfxActor.h b/source/main/gfx/GfxActor.h index 2083a9240e..79f36e947d 100644 --- a/source/main/gfx/GfxActor.h +++ b/source/main/gfx/GfxActor.h @@ -110,6 +110,7 @@ class GfxActor void UpdateCParticles(); void UpdateAeroEngines(); void UpdateNetLabels(float dt); + void UpdateWalkieTalkieLabels(float dt); void UpdateFlares(float dt_sec, bool is_player); void UpdateRenderdashRTT (); @@ -149,6 +150,7 @@ class GfxActor void CalcPropAnimation(const int flag_state, float& cstate, int& div, float timer, const float lower_limit, const float upper_limit, const float option3); std::vector& getProps() { return m_props; } + int getNumBeacons() const; bool hasCamera() { return m_videocameras.size() > 0; } private: diff --git a/source/main/gfx/GfxScene.cpp b/source/main/gfx/GfxScene.cpp index 9f514cbbb7..eba332e860 100644 --- a/source/main/gfx/GfxScene.cpp +++ b/source/main/gfx/GfxScene.cpp @@ -194,10 +194,11 @@ void GfxScene::UpdateScene(float dt_sec) App::GetOverlayWrapper()->UpdatePressureOverlay(m_simbuf.simbuf_player_actor->GetGfxActor()); } - // HUD - network labels (always update) + // HUD - labels (always update) for (GfxActor* gfx_actor: m_all_gfx_actors) { gfx_actor->UpdateNetLabels(m_simbuf.simbuf_sim_speed * dt_sec); + gfx_actor->UpdateWalkieTalkieLabels(m_simbuf.simbuf_sim_speed * dt_sec); } // Player avatars @@ -340,26 +341,6 @@ void GfxScene::DrawNetLabel(Ogre::Vector3 scene_pos, float cam_dist, std::string { #if USE_SOCKETW - // this ensures that the nickname is always in a readable size - float font_size = std::max(0.6, cam_dist / 40.0); - std::string caption; - if (cam_dist > 1000) // 1000 ... vlen - { - caption = - nick + " (" + TOSTRING((float)(ceil(cam_dist / 100) / 10.0) ) + " km)"; - } - else if (cam_dist > 20) // 20 ... vlen ... 1000 - { - caption = - nick + " (" + TOSTRING((int)cam_dist) + " m)"; - } - else // 0 ... vlen ... 20 - { - caption = nick; - } - - // draw with DearIMGUI - ImVec2 screen_size = ImGui::GetIO().DisplaySize; World2ScreenConverter world2screen( App::GetCameraManager()->GetCamera()->getViewMatrix(true), App::GetCameraManager()->GetCamera()->getProjectionMatrix(), Ogre::Vector2(screen_size.x, screen_size.y)); @@ -372,6 +353,7 @@ void GfxScene::DrawNetLabel(Ogre::Vector3 scene_pos, float cam_dist, std::string // Align position to whole pixels, to minimize jitter. ImVec2 pos((int)pos_xyz.x+0.5, (int)pos_xyz.y+0.5); + std::string caption = FormatLabelWithDistance(nick, cam_dist); ImVec2 text_size = ImGui::CalcTextSize(caption.c_str()); GUIManager::GuiTheme const& theme = App::GetGuiManager()->GetTheme(); @@ -396,4 +378,3 @@ void GfxScene::DrawNetLabel(Ogre::Vector3 scene_pos, float cam_dist, std::string #endif // USE_SOCKETW } - diff --git a/source/main/gfx/SimBuffers.h b/source/main/gfx/SimBuffers.h index 2bd5fa46bb..a45d6879f1 100644 --- a/source/main/gfx/SimBuffers.h +++ b/source/main/gfx/SimBuffers.h @@ -190,6 +190,7 @@ struct ActorSB // GUI float simbuf_speedo_highest_kph = 0; bool simbuf_speedo_use_engine_max_rpm = false; + bool simbuf_wakie_talkie = false; }; struct GameContextSB diff --git a/source/main/gui/GUIManager.cpp b/source/main/gui/GUIManager.cpp index 0981f84e66..ebb98b97bf 100644 --- a/source/main/gui/GUIManager.cpp +++ b/source/main/gui/GUIManager.cpp @@ -105,14 +105,16 @@ void GUIManager::ShutdownMyGUI() } } -void GUIManager::ApplyGuiCaptureKeyboard() +void GUIManager::ApplyQueuedGuiRequests() { m_gui_kb_capture_requested = m_gui_kb_capture_queued; + m_staticmenus_blocking_requested = m_staticmenus_blocking_queued; }; bool GUIManager::AreStaticMenusAllowed() //!< i.e. top menubar / vehicle UI buttons { return (App::GetCameraManager()->GetCurrentBehavior() != CameraManager::CAMERA_BEHAVIOR_FREE && + !m_staticmenus_blocking_requested && // For ad-hoc windows like walkie-talkie label !this->ConsoleWindow.IsHovered() && !this->GameControls.IsHovered() && !this->FrictionSettings.IsHovered() && @@ -129,6 +131,7 @@ void GUIManager::DrawSimulationGui(float dt) if (App::app_state->getEnum() == AppState::SIMULATION) { this->TopMenubar.Update(); + this->SceneLabels.Update(); if (this->GameMainMenu.IsVisible()) { @@ -317,6 +320,7 @@ void GUIManager::NewImGuiFrame(float dt) // Reset state m_gui_kb_capture_queued = false; + m_staticmenus_blocking_queued = false; } void GUIManager::SetupImGui() @@ -450,6 +454,11 @@ void GUIManager::RequestGuiCaptureKeyboard(bool val) m_gui_kb_capture_queued = m_gui_kb_capture_queued || val; } +void GUIManager::RequestStaticMenusBlocking(bool val) +{ + m_staticmenus_blocking_queued = m_staticmenus_blocking_queued || val; +} + void GUIManager::WakeUpGUI() { m_last_mousemove_time.reset(); diff --git a/source/main/gui/GUIManager.h b/source/main/gui/GUIManager.h index 90f82ae0a7..06e6cf9b57 100644 --- a/source/main/gui/GUIManager.h +++ b/source/main/gui/GUIManager.h @@ -48,6 +48,7 @@ #include "GUI_SimActorStats.h" #include "GUI_SimPerfStats.h" #include "GUI_SurveyMap.h" +#include "GUI_SceneLabels.h" #include "GUI_TextureToolWindow.h" #include "GUI_GameControls.h" #include "GUI_TopMenubar.h" @@ -126,16 +127,17 @@ class GUIManager GUI::DirectionArrow DirectionArrow; GUI::VehicleButtons VehicleButtons; GUI::FlexbodyDebug FlexbodyDebug; + GUI::SceneLabels SceneLabels; Ogre::Overlay* MenuWallpaper = nullptr; // GUI manipulation void ShowMessageBox(const char* title, const char* text, bool allow_close = true, const char* btn1_text = "OK", const char* btn2_text = nullptr); void ShowMessageBox(GUI::MessageBoxConfig const& conf); void RequestGuiCaptureKeyboard(bool val); //!< Pass true during frame to prevent input passing to application + void RequestStaticMenusBlocking(bool val); //!< Pass true during frame to prevent static menus (i.e. top menubar) from displaying bool IsGuiCaptureKeyboardRequested() const { return m_gui_kb_capture_requested; } - void ApplyGuiCaptureKeyboard(); //!< Call after rendered frame to apply queued value + void ApplyQueuedGuiRequests(); //!< Call after rendered frame to apply queued value bool AreStaticMenusAllowed(); //!< i.e. top menubar / vehicle UI buttons - void NewImGuiFrame(float dt); void DrawMainMenuGui(); void DrawSimulationGui(float dt); //!< Touches live data; must be called in sync with sim. thread @@ -173,6 +175,8 @@ class GUIManager GuiTheme m_theme; bool m_gui_kb_capture_queued = false; //!< Resets and accumulates every frame bool m_gui_kb_capture_requested = false; //!< Effective value, persistent + bool m_staticmenus_blocking_queued = false; //!< Resets and accumulates every frame + bool m_staticmenus_blocking_requested = false; //!< Effective value, persistent Ogre::Timer m_last_mousemove_time; bool m_is_cursor_supressed = false; //!< True if cursor was manually hidden. }; diff --git a/source/main/gui/GUIUtils.cpp b/source/main/gui/GUIUtils.cpp index bf64105ae4..9b266a9696 100644 --- a/source/main/gui/GUIUtils.cpp +++ b/source/main/gui/GUIUtils.cpp @@ -338,19 +338,19 @@ void RoR::DrawGCombo(CVar* cvar, const char* label, const char* values) } } -Ogre::TexturePtr RoR::FetchIcon(const char* name) +Ogre::TexturePtr RoR::FetchIcon(const char* name, const char* resource_group/* = "FlagsRG"*/) { try { return Ogre::static_pointer_cast( - Ogre::TextureManager::getSingleton().createOrRetrieve(name, "FlagsRG").first); + Ogre::TextureManager::getSingleton().createOrRetrieve(name, resource_group).first); } catch (...) {} return Ogre::TexturePtr(); // null } -ImDrawList* RoR::GetImDummyFullscreenWindow() +ImDrawList* RoR::GetImDummyFullscreenWindow(const char* id /*= "rigsofrods/DummyWindow"*/) { ImVec2 screen_size = ImGui::GetIO().DisplaySize; @@ -360,7 +360,7 @@ ImDrawList* RoR::GetImDummyFullscreenWindow() ImGui::SetNextWindowPos(ImVec2(0,0)); ImGui::SetNextWindowSize(screen_size); ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0,0,0,0)); // Fully transparent background! - ImGui::Begin("RoR_TransparentFullscreenWindow", NULL, window_flags); + ImGui::Begin(id, NULL, window_flags); ImDrawList* drawlist = ImGui::GetWindowDrawList(); ImGui::End(); ImGui::PopStyleColor(1); // WindowBg @@ -402,21 +402,24 @@ void RoR::ImTerminateComboboxString(std::string& target) target.resize(prev_size + 2, '\0'); } -void RoR::ImDrawEventHighlighted(events input_event) +void RoR::ImDrawEventHighlighted(events input_event, bool force_active /*=false*/) { ImVec4 col = ImGui::GetStyle().Colors[ImGuiCol_Text]; - if (App::GetInputEngine()->getEventValue(input_event)) + if (force_active || App::GetInputEngine()->getEventValue(input_event)) { col = App::GetGuiManager()->GetTheme().highlight_text_color; } std::string text = App::GetInputEngine()->getKeyForCommand(input_event); const ImVec2 PAD = ImVec2(2.f, 0.f); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, PAD); - ImGui::BeginChildFrame(ImGuiID(input_event), ImGui::CalcTextSize(text.c_str()) + PAD*2); - ImGui::TextColored(col, "%s", text.c_str()); - ImGui::EndChildFrame(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, PAD); + ImGui::PushStyleColor(ImGuiCol_Text, col); + ImGui::PushID(ImGuiID(input_event)); + ImGui::Button(text.c_str()); + // `Button()` only returns `true` the moment when pressed, we need continuous input from `IsItemActive()` + App::GetInputEngine()->setEventSimulatedValue(input_event, static_cast(ImGui::IsItemActive())); + ImGui::PopID(); //ImGuiID(input_event) + ImGui::PopStyleColor(); // Text ImGui::PopStyleVar(); // FramePadding - } void RoR::ImDrawModifierKeyHighlighted(OIS::KeyCode key) @@ -434,3 +437,20 @@ void RoR::ImDrawModifierKeyHighlighted(OIS::KeyCode key) ImGui::EndChildFrame(); ImGui::PopStyleVar(); // FramePadding } + +std::string RoR::FormatLabelWithDistance(const std::string& nick, float cam_dist) +{ + std::string caption; + if (cam_dist > 1000) // 1000 ... vlen + { + return nick + " (" + TOSTRING((float)(ceil(cam_dist / 100) / 10.0) ) + " km)"; + } + else if (cam_dist > 20) // 20 ... vlen ... 1000 + { + return nick + " (" + TOSTRING((int)cam_dist) + " m)"; + } + else // 0 ... vlen ... 20 + { + return nick; + } +} \ No newline at end of file diff --git a/source/main/gui/GUIUtils.h b/source/main/gui/GUIUtils.h index e3fde7838d..626af70300 100644 --- a/source/main/gui/GUIUtils.h +++ b/source/main/gui/GUIUtils.h @@ -72,16 +72,18 @@ void DrawGTextEdit(CVar* cvar, const char* label, Str<1000>& buf); void DrawGCombo(CVar* cvar, const char* label, const char* values); -Ogre::TexturePtr FetchIcon(const char* name); +Ogre::TexturePtr FetchIcon(const char* name, const char* resource_group = "FlagsRG"); -ImDrawList* GetImDummyFullscreenWindow(); +ImDrawList* GetImDummyFullscreenWindow(const char* id = "rigsofrods/DummyWindow"); // Helpers for coposing combobox item strings. void ImAddItemToComboboxString(std::string& target, std::string const& item); void ImTerminateComboboxString(std::string& target); // Input engine helpers -void ImDrawEventHighlighted(events input_event); +void ImDrawEventHighlighted(events input_event, bool force_active=false); ///!< Draws button displaying configured key combo and simulating the event when pressed. void ImDrawModifierKeyHighlighted(OIS::KeyCode key); +std::string FormatLabelWithDistance(const std::string& nick, float cam_dist); + } // namespace RoR diff --git a/source/main/gui/panels/GUI_SceneLabels.cpp b/source/main/gui/panels/GUI_SceneLabels.cpp new file mode 100644 index 0000000000..3bd3996af4 --- /dev/null +++ b/source/main/gui/panels/GUI_SceneLabels.cpp @@ -0,0 +1,292 @@ +/* + This source file is part of Rigs of Rods + Copyright 2005-2012 Pierre-Michel Ricordel + Copyright 2007-2012 Thomas Fischer + Copyright 2013-2023 Petr Ohlidal + + For more information, see http://www.rigsofrods.org/ + + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. + + Rigs of Rods 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 Rigs of Rods. If not, see . +*/ + +#include "GUI_SceneLabels.h" + +#include "Application.h" +#include "Actor.h" +#include "Console.h" +#include "EngineSim.h" +#include "GameContext.h" +#include "GfxActor.h" +#include "GUIManager.h" +#include "GUIUtils.h" +#include "InputEngine.h" +#include "SoundScriptManager.h" +#include "Utils.h" + +#include +#include + +using namespace RoR; +using namespace GUI; +using namespace Ogre; + +void SceneLabels::Update() +{ + if (!m_icons_cached) + this->CacheIcons(); + + // Evaluate mouse hover + m_hovered_actor = this->FindHoveredActor(); + + // Draw hovered actor box + if (m_hovered_actor) + { + float vlen = m_hovered_actor->getPosition().distance(App::GetCameraManager()->GetCameraNode()->getPosition()); + this->DrawInstance(m_hovered_actor->getPosition() + Vector3(0.f, 1.4f, 0.f), vlen, nullptr); + } + + // NOTE: walkietalkie labels are drawn from `GfxScene::UpdateWalkieTalkieLabels()` +} + +ActorPtr SceneLabels::FindHoveredActor() +{ + // get mouse ray + Ogre::Viewport* vp = App::GetCameraManager()->GetCamera()->getViewport(); + Ogre::Ray mouseRay = App::GetCameraManager()->GetCamera()->getCameraToViewportRay( + (float)ImGui::GetIO().MousePos.x / (float)vp->getActualWidth(), + (float)ImGui::GetIO().MousePos.y / (float)vp->getActualHeight()); + + // walk all trucks + ActorPtr grab_truck; + float mindist = 99999; + for (const ActorPtr& actor : App::GetGameContext()->GetActorManager()->GetActors()) + { + if (actor->ar_state == ActorState::LOCAL_SIMULATED) + { + // check if our ray intersects with the bounding box of the truck + std::pair pair = mouseRay.intersects(actor->ar_bounding_box); + if (pair.first) + { + // we hit it, check if its the nearest sphere + if (pair.second < mindist) + { + mindist = pair.second; + grab_truck = actor; + } + } + + } + } + + return grab_truck; +} + +void DrawRow(events ev, const Ogre::TexturePtr& icon) +{ + ImDrawEventHighlighted(ev); + ImGui::SameLine(); + if (icon) + { + ImGui::ImageButton(reinterpret_cast(icon->getHandle()), ImVec2(24, 24)); + ImGui::SameLine(); + } + ImGui::TextDisabled(App::GetInputEngine()->eventIDToDescription(ev).c_str()); + ImGui::NextColumn(); +} + +void SceneLabels::DrawInstance(Ogre::Vector3 scene_pos, float cam_dist, const ActorPtr& actor) +{ + // Draw a context-sensitive label: + // * Character: walkietalkie icon + // * Actor: icon and usable light+command keys + // * Actor (mouse hover): name and mouse hints + // ---------------------------------------------- + + if (!m_icons_cached) + this->CacheIcons(); + + ImVec2 screen_size = ImGui::GetIO().DisplaySize; + World2ScreenConverter world2screen( + App::GetCameraManager()->GetCamera()->getViewMatrix(true), App::GetCameraManager()->GetCamera()->getProjectionMatrix(), Ogre::Vector2(screen_size.x, screen_size.y)); + + Ogre::Vector3 pos_xyz = world2screen.Convert(scene_pos); + + // Do not draw when behind camera + if (pos_xyz.z > 0.f) + return; + + // Align position to whole pixels, to minimize jitter. + ImVec2 pos((int)pos_xyz.x+0.5, (int)pos_xyz.y+0.5); + + Ogre::TexturePtr icon = FetchIcon("walkie_talkie.png", "IconsRG"); + ImVec2 icon_size(24.f, 24.f);//(icon->getWidth(), icon->getHeight()); + + const float ICON_PAD_RIGHT = 5.f; + ImVec2 total_size = icon_size; + + std::string caption; + std::string id_str = "walkie talkie for character"; + if (actor) + { + caption = FormatLabelWithDistance(actor->ar_design_name, cam_dist); + + // Place name+commands next to the icon + ImVec2 namesize = ImGui::CalcTextSize(caption.c_str()); + total_size.x += ICON_PAD_RIGHT + std::max(namesize.x, actor->m_walkietalkie_commandkeys_screensize.x); + total_size.y = std::max(total_size.y, actor->m_walkietalkie_commandkeys_screensize.y); + + // Account for space between event-button and description + total_size += ImGui::GetStyle().ItemSpacing * ImVec2(1.f, 0.f) + + ImVec2(ImGui::GetStyle().FramePadding.x*2, 0.f); + + id_str = fmt::format("walkie talkie for actor {}", actor->ar_instance_id).c_str(); + } + else if (m_hovered_actor) + { + caption = FormatLabelWithDistance(m_hovered_actor->ar_design_name, cam_dist); + id_str = fmt::format("mouse hover box for actor {}", m_hovered_actor->ar_instance_id).c_str(); + icon.reset(); // omit icon + + ImVec2 namesize = ImGui::CalcTextSize(caption.c_str()); + total_size = ImVec2(std::max(namesize.x, 250.f), 55); + } + + // Because we want buttons in the label, it must be a window, not a fullscreen dummy for drawing + const float PADDING = 4.f; + const ImVec2 BOXPAD2(PADDING, PADDING); + const ImVec2 BTNPAD2(2.f, 0.f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, BOXPAD2); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, BTNPAD2); + ImVec2 rect_min = (pos - total_size/2) - BOXPAD2; + ImGui::SetNextWindowPos(rect_min); + ImGui::SetNextWindowSize(total_size + BOXPAD2*2); + int window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar + | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus; + if (ImGui::Begin(id_str.c_str(), nullptr, window_flags)) + { + GUIManager::GuiTheme const& theme = App::GetGuiManager()->GetTheme(); + + if (actor) + { + // Draw actor name (dark text) + ImGui::SetCursorPosX(icon_size.x + ICON_PAD_RIGHT); + ImGui::TextDisabled(caption.c_str()); + ImGui::Separator(); + + // Draw light controls (with activity highlight) + for (auto& pair: actor->m_walkietalkie_lights_cache) + { + ImGui::SetCursorPosX(icon_size.x + ICON_PAD_RIGHT); + DrawRow(pair.first, pair.second); + } + + // Draw the commands (with activity highlight) + for (auto& pair: actor->m_walkietalkie_commandkeys_cache) + { + const bool force_active = (actor->ar_command_key[pair.first].playerInputValue != 0); + ImGui::SetCursorPosX(icon_size.x + ICON_PAD_RIGHT); + ImDrawEventHighlighted(events(pair.first), force_active); + ImGui::SameLine(); + ImGui::Text(pair.second.c_str()); + } + } + else if (m_hovered_actor) + { + // Draw actor name (dark text) + ImGui::TextDisabled(caption.c_str()); + ImGui::Separator(); + + // Bottom area - Draw mouse hints + float orig_y = ImGui::GetCursorPosY(); + + // Left mouse button = grab + ImGui::Image(reinterpret_cast(rc_left_mouse_button->getHandle()), ImVec2(28, 24)); + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().ItemSpacing.y); + ImGui::Text("%s", _LC("ActorMouseHoverBox", "Grab")); + + // Middle mouse button = enter + ImGui::SameLine(); + ImGui::SetCursorPosY(orig_y); + ImGui::Image(reinterpret_cast(rc_middle_mouse_button->getHandle()), ImVec2(28, 24)); + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().ItemSpacing.y); + ImGui::Text("%s", _LC("ActorMouseHoverBox", "Enter")); + + // Right mouse button = menu + ImGui::SameLine(); + ImGui::SetCursorPosY(orig_y); + ImGui::Image(reinterpret_cast(rc_middle_mouse_button->getHandle()), ImVec2(28, 24)); + ImGui::SameLine(); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetStyle().ItemSpacing.y); + + ImGui::Text("%s", _LC("ActorMouseHoverBox", "Open menu")); + } + + // Draw icon last + if (icon) + { + ImGui::SetCursorPos(BOXPAD2); + ImGui::Image(reinterpret_cast(icon->getHandle()), icon_size); + } + + App::GetGuiManager()->RequestStaticMenusBlocking(ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows)); + ImGui::End(); + ImGui::PopStyleVar(); //ImGuiStyleVar_WindowPadding, BOXPAD2); + ImGui::PopStyleVar(); //ImGuiStyleVar_FramePadding, BTNPAD2); + } +} + + + + + +void SceneLabels::CacheIcons() +{ + // Icons used https://materialdesignicons.com/ + // Licence https://github.com/Templarian/MaterialDesign/blob/master/LICENSE + + rc_headlight_icon = FetchIcon("car-light-high.png"); + rc_left_blinker_icon = FetchIcon("arrow-left-bold.png"); + rc_right_blinker_icon = FetchIcon("arrow-right-bold.png"); + rc_warning_light_icon = FetchIcon("hazard-lights.png"); + rc_horn_icon = FetchIcon("bugle.png"); + rc_mirror_icon = FetchIcon("mirror-rectangle.png"); + rc_repair_icon = FetchIcon("car-wrench.png"); + rc_parking_brake_icon = FetchIcon("car-brake-alert.png"); + rc_traction_control_icon = FetchIcon("car-traction-control.png"); + rc_abs_icon = FetchIcon("car-brake-abs.png"); + rc_physics_icon = FetchIcon("pause.png"); + rc_actor_physics_icon = FetchIcon("pause-circle-outline.png"); + rc_a_icon = FetchIcon("alpha-a-circle.png"); + rc_w_icon = FetchIcon("alpha-w-circle.png"); + rc_m_icon = FetchIcon("alpha-m-circle.png"); + rc_g_icon = FetchIcon("alpha-g-circle.png"); + rc_particle_icon = FetchIcon("water.png"); + rc_shift_icon = FetchIcon("car-shift-pattern.png"); + rc_engine_icon = FetchIcon("engine.png"); + rc_beacons_icon = FetchIcon("alarm-light-outline.png"); + rc_camera_icon = FetchIcon("camera-switch-outline.png"); + rc_lock_icon = FetchIcon("alpha-l-circle.png"); + rc_secure_icon = FetchIcon("lock-outline.png"); + rc_cruise_control_icon = FetchIcon("car-cruise-control.png"); + + // Thanks WillM for the icons! + + rc_left_mouse_button = FetchIcon("left-mouse-button.png"); + rc_middle_mouse_button = FetchIcon("middle-mouse-button.png"); + rc_middle_mouse_scroll_button = FetchIcon("middle-mouse-scroll-button.png"); + rc_right_mouse_button = FetchIcon("right-mouse-button.png"); + + m_icons_cached = true; +} \ No newline at end of file diff --git a/source/main/gui/panels/GUI_SceneLabels.h b/source/main/gui/panels/GUI_SceneLabels.h new file mode 100644 index 0000000000..5116b93f77 --- /dev/null +++ b/source/main/gui/panels/GUI_SceneLabels.h @@ -0,0 +1,88 @@ +/* + This source file is part of Rigs of Rods + Copyright 2023 Petr Ohlidal + + For more information, see http://www.rigsofrods.org/ + + Rigs of Rods is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 3, as + published by the Free Software Foundation. + + Rigs of Rods 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 Rigs of Rods. If not, see . +*/ + +/// @author Petr Ohlidal; Derived from VehicleButtonsUI by tritonas00 +/// @date 07/2023 + +#pragma once + +#include "ForwardDeclarations.h" + +#include +#include + +namespace RoR { +namespace GUI { + +/// Common base for various mini-panels in the scene. +class SceneLabels +{ +public: + void Update(); + void DrawInstance(Ogre::Vector3 scene_pos, float cam_dist, const ActorPtr& actor); + + bool GetHornButtonState() { return m_horn; } + std::vector GetCommandEventID() { return m_id; } + +private: + ActorPtr FindHoveredActor(); + ActorPtr m_hovered_actor; + + bool m_horn = false; + std::vector m_id; + Ogre::Timer m_timer; + bool m_init = false; + + // Icon cache + void CacheIcons(); + bool m_icons_cached = false; +public: + Ogre::TexturePtr rc_headlight_icon; + Ogre::TexturePtr rc_left_blinker_icon; + Ogre::TexturePtr rc_right_blinker_icon; + Ogre::TexturePtr rc_warning_light_icon; + Ogre::TexturePtr rc_horn_icon; + Ogre::TexturePtr rc_mirror_icon; + Ogre::TexturePtr rc_repair_icon; + Ogre::TexturePtr rc_parking_brake_icon; + Ogre::TexturePtr rc_traction_control_icon; + Ogre::TexturePtr rc_abs_icon; + Ogre::TexturePtr rc_physics_icon; + Ogre::TexturePtr rc_actor_physics_icon; + Ogre::TexturePtr rc_a_icon; + Ogre::TexturePtr rc_w_icon; + Ogre::TexturePtr rc_m_icon; + Ogre::TexturePtr rc_g_icon; + Ogre::TexturePtr rc_particle_icon; + Ogre::TexturePtr rc_shift_icon; + Ogre::TexturePtr rc_engine_icon; + Ogre::TexturePtr rc_beacons_icon; + Ogre::TexturePtr rc_camera_icon; + Ogre::TexturePtr rc_lock_icon; + Ogre::TexturePtr rc_secure_icon; + Ogre::TexturePtr rc_cruise_control_icon; + // Mouse hints + Ogre::TexturePtr rc_left_mouse_button; + Ogre::TexturePtr rc_middle_mouse_button; + Ogre::TexturePtr rc_middle_mouse_scroll_button; + Ogre::TexturePtr rc_right_mouse_button; +}; + +} // namespace GUI +} // namespace RoR \ No newline at end of file diff --git a/source/main/main.cpp b/source/main/main.cpp index bbc668a9a1..c0257dc0ea 100644 --- a/source/main/main.cpp +++ b/source/main/main.cpp @@ -1151,7 +1151,7 @@ int main(int argc, char *argv[]) } } // Render block - App::GetGuiManager()->ApplyGuiCaptureKeyboard(); + App::GetGuiManager()->ApplyQueuedGuiRequests(); } // End of main rendering/input loop diff --git a/source/main/physics/Actor.cpp b/source/main/physics/Actor.cpp index 8134d9093d..4b69a3c13a 100644 --- a/source/main/physics/Actor.cpp +++ b/source/main/physics/Actor.cpp @@ -4423,6 +4423,7 @@ Actor::Actor( , ar_toggle_ropes(false) , ar_toggle_ties(false) , ar_physics_paused(false) + , ar_walkie_talkie(false) // Private bit flags , m_hud_features_ok(false) @@ -4657,3 +4658,105 @@ std::string Actor::getTruckFileResourceGroup() { return m_gfx_actor->GetResourceGroup(); } + +void Actor::CacheWalkietalkieLightControl(events ev, const Ogre::TexturePtr& icon) +{ + auto inserted_pair = m_walkietalkie_lights_cache.insert( + std::make_pair(ev, icon)); + // Only increase the size if we added a new commandkey - there may be multiple commandbeams for the same key. + if (inserted_pair.second) + { + std::string text = App::GetInputEngine()->eventIDToDescription(ev); + ImVec2 text_size = ImGui::CalcTextSize(text.c_str()); + m_walkietalkie_commandkeys_screensize.x = std::max(m_walkietalkie_commandkeys_screensize.x, text_size.x); + m_walkietalkie_commandkeys_screensize.y += text_size.y; + if (icon) + { + m_walkietalkie_commandkeys_screensize.x += 24.f + ImGui::GetStyle().ItemSpacing.x; + m_walkietalkie_commandkeys_screensize.y = std::max(24.f, text_size.y); + } + } +} + +void Actor::CacheWalkietalkieCommandButtons() +{ + m_walkietalkie_commandkeys_cache.clear(); + m_walkietalkie_commandkeys_screensize = ImVec2(0,0); + + // BEWARE: commandkeys are indexed 1-MAX_COMMANDS! + for (int i = 1; i <= MAX_COMMANDS; i++) + { + if (this->ar_command_key[i].description == "hide") + continue; + if (this->ar_command_key[i].beams.empty() && this->ar_command_key[i].rotators.empty()) + continue; + + + int eventID = RoR::InputEngine::resolveEventName(fmt::format("COMMANDS_{:02d}", i)); + + for (commandbeam_t& cmdbeam: this->ar_command_key[i].beams) + { + std::string desc = "(unknown function)"; + if (!this->ar_command_key[i].description.empty()) + { + desc = this->ar_command_key[i].description.c_str(); + } + std::string text = fmt::format("{} {}", + cmdbeam.cmb_is_contraction ? "Retract" : "Extend", + desc); + + auto inserted_pair = m_walkietalkie_commandkeys_cache.insert( + std::make_pair(eventID, text)); + // Only increase the size if we added a new commandkey - there may be multiple commandbeams for the same key. + if (inserted_pair.second) + { + ImVec2 text_size = ImGui::CalcTextSize(text.c_str()); + m_walkietalkie_commandkeys_screensize.x = std::max(m_walkietalkie_commandkeys_screensize.x, text_size.x); + m_walkietalkie_commandkeys_screensize.y += text_size.y; + } + } + } + + // Now also cache light controls + // > Low beams + if (this->countFlaresByType(FlareType::HEADLIGHT) + this->countFlaresByType(FlareType::TAIL_LIGHT)) + { + this->CacheWalkietalkieLightControl(EV_COMMON_TOGGLE_TRUCK_LOW_BEAMS, App::GetGuiManager()->SceneLabels.rc_headlight_icon); + } + + // > High beams + if (this->countFlaresByType(FlareType::HIGH_BEAM)) + { + this->CacheWalkietalkieLightControl(EV_COMMON_TOGGLE_TRUCK_HIGH_BEAMS, App::GetGuiManager()->SceneLabels.rc_headlight_icon); + } + + // > Fog lights + if (this->countFlaresByType(FlareType::FOG_LIGHT)) + { + this->CacheWalkietalkieLightControl(EV_COMMON_TOGGLE_TRUCK_FOG_LIGHTS, App::GetGuiManager()->SceneLabels.rc_headlight_icon); + } + + // > Left blinker + if (this->countFlaresByType(FlareType::BLINKER_LEFT)) + { + this->CacheWalkietalkieLightControl(EV_TRUCK_BLINK_LEFT, App::GetGuiManager()->SceneLabels.rc_left_blinker_icon); + } + + // > Left blinker + if (this->countFlaresByType(FlareType::BLINKER_LEFT)) + { + this->CacheWalkietalkieLightControl(EV_TRUCK_BLINK_LEFT, App::GetGuiManager()->SceneLabels.rc_left_blinker_icon); + } + + // > Warn signal + if (this->countFlaresByType(FlareType::BLINKER_LEFT) + this->countFlaresByType(FlareType::BLINKER_RIGHT)) + { + CacheWalkietalkieLightControl(EV_TRUCK_BLINK_WARN, App::GetGuiManager()->SceneLabels.rc_warning_light_icon); + } + + // > Beacon toggle + if (this->GetGfxActor()->getNumBeacons()) + { + CacheWalkietalkieLightControl(EV_COMMON_TOGGLE_TRUCK_BEACONS, App::GetGuiManager()->SceneLabels.rc_beacons_icon); + } +} \ No newline at end of file diff --git a/source/main/physics/Actor.h b/source/main/physics/Actor.h index 3cb2df6636..2ea4ed5069 100644 --- a/source/main/physics/Actor.h +++ b/source/main/physics/Actor.h @@ -440,6 +440,13 @@ ActorPtrVec ar_linked_actors; //!< Sim state std::pair ar_nb_wheels_d_interval; //!< Search interval for springiness & damping of wheel / rim beams std::pair ar_nb_wheels_k_interval; //!< Search interval for springiness & damping of wheel / rim beams + // SceneLabels UI (commands, lights, etc...) + void CacheWalkietalkieCommandButtons(); + void CacheWalkietalkieLightControl(events ev, const Ogre::TexturePtr& icon); + std::unordered_map m_walkietalkie_commandkeys_cache; // eventID -> description + std::unordered_map m_walkietalkie_lights_cache; // eventID -> icon (descriptions are provided by InputEngine) + ImVec2 m_walkietalkie_commandkeys_screensize; // total screensize + // Bit flags bool ar_update_physics:1; //!< Physics state; Should this actor be updated (locally) in the next physics step? bool ar_disable_aerodyn_turbulent_drag:1; //!< Physics state @@ -454,6 +461,7 @@ ActorPtrVec ar_linked_actors; //!< Sim state bool ar_toggle_ropes:1; //!< Sim state bool ar_toggle_ties:1; //!< Sim state bool ar_physics_paused:1; //!< Sim state + bool ar_walkie_talkie:1; private: diff --git a/source/main/physics/ActorManager.cpp b/source/main/physics/ActorManager.cpp index 2c8c7e7f02..c9a8ec9864 100644 --- a/source/main/physics/ActorManager.cpp +++ b/source/main/physics/ActorManager.cpp @@ -324,6 +324,8 @@ ActorPtr ActorManager::CreateNewActor(ActorSpawnRequest rq, RigDef::DocumentPtr App::GetScriptEngine()->loadScript(script_def.filename, ScriptCategory::ACTOR, actor); } + actor->CacheWalkietalkieCommandButtons(); + LOG(" ===== DONE LOADING VEHICLE"); if (App::diag_actor_dump->getBool()) diff --git a/source/main/utils/ErrorUtils.cpp b/source/main/utils/ErrorUtils.cpp index ac1db900dc..793b681114 100644 --- a/source/main/utils/ErrorUtils.cpp +++ b/source/main/utils/ErrorUtils.cpp @@ -43,7 +43,7 @@ using namespace Ogre; int ErrorUtils::ShowError(Ogre::UTFString title, Ogre::UTFString err) { Ogre::UTFString infoText = _L("An internal error occured in Rigs of Rods.\n\nTechnical details below: \n\n"); - return ErrorUtils::ShowMsgBox(_L("FATAL ERROR"), infoText + err, 0); + return ErrorUtils::ShowMsgBox(_L("FATAL ERROR"), infoText + title + "\n\n" + err, 0); } int ErrorUtils::ShowInfo(Ogre::UTFString title, Ogre::UTFString err) diff --git a/source/main/utils/InputEngine.cpp b/source/main/utils/InputEngine.cpp index 413769e493..b75285df05 100644 --- a/source/main/utils/InputEngine.cpp +++ b/source/main/utils/InputEngine.cpp @@ -697,6 +697,11 @@ void InputEngine::resetKeys() } } +void InputEngine::setEventSimulatedValue(RoR::events eventID, float value) +{ + event_values_simulated[eventID] = value; +} + bool InputEngine::getEventBoolValue(int eventID) { return (getEventValue(eventID) > 0.5f); @@ -900,6 +905,10 @@ bool InputEngine::isEventAnalog(int eventID) float InputEngine::getEventValue(int eventID, bool pure, InputSourceType valueSource /*= InputSourceType::IST_ANY*/) { + const float simulatedValue = event_values_simulated[eventID]; + if (simulatedValue != 0.f) + return simulatedValue; + float returnValue = 0; std::vector t_vec = events[eventID]; float value = 0; @@ -1174,6 +1183,12 @@ const char* InputEngine::getEventTypeName(eventtypes type) } void InputEngine::addEvent(int eventID, event_trigger_t& t) +{ + this->addEvent(eventID); + events[eventID].push_back(t); +} + +void InputEngine::addEvent(int eventID) { uniqueCounter++; @@ -1183,9 +1198,8 @@ void InputEngine::addEvent(int eventID, event_trigger_t& t) if (events.find(eventID) == events.end()) { events[eventID] = std::vector(); - events[eventID].clear(); + event_values_simulated[eventID] = false; } - events[eventID].push_back(t); } void InputEngine::addEventDefault(int eventID, int deviceID /*= -1*/) @@ -1495,7 +1509,7 @@ bool InputEngine::processLine(const char* line, int deviceID) if (eventID == -1) return false; // Insert event with no trigger - events.insert(std::make_pair(eventID, std::vector())); + addEvent(eventID); return true; } case ET_MouseButton: diff --git a/source/main/utils/InputEngine.h b/source/main/utils/InputEngine.h index 7ec14c463a..61b2a68a66 100644 --- a/source/main/utils/InputEngine.h +++ b/source/main/utils/InputEngine.h @@ -484,6 +484,7 @@ class InputEngine void ProcessKeyRelease(const OIS::KeyEvent& arg); void ProcessJoystickEvent(const OIS::JoyStickEvent& arg); void resetKeys(); + void setEventSimulatedValue(events eventID, float value); // Event info @@ -513,6 +514,7 @@ class InputEngine // Event management void addEvent(int eventID, event_trigger_t& t); //!< Registers new trigger for this event. + void addEvent(int eventID); //!< Registers new event without trigger. void addEventDefault(int eventID, int deviceID = -1); //!< Adds a new trigger with builtin value for this event. void updateEvent(int eventID, const event_trigger_t& t); void eraseEvent(int eventID, const event_trigger_t* t); @@ -573,6 +575,7 @@ class InputEngine // define event aliases std::map> events; + std::map event_values_simulated; std::map event_times; std::string m_loaded_configs[MAX_JOYSTICKS]; bool loadMapping(Ogre::String fileName, int deviceID);