Rigs of Rods 2026.01
Soft-body Physics Simulation
Loading...
Searching...
No Matches
GUI_TopMenubar.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2005-2012 Pierre-Michel Ricordel
4 Copyright 2007-2012 Thomas Fischer
5 Copyright 2016-2020 Petr Ohlidal
6
7 For more information, see http://www.rigsofrods.org/
8
9 Rigs of Rods is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 3, as
11 published by the Free Software Foundation.
12
13 Rigs of Rods is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20*/
21
25
26#include "GUI_TopMenubar.h"
27
28#include "Application.h"
29#include "Actor.h"
30#include "ActorManager.h"
31#include "CameraManager.h"
32#include "DashBoardManager.h"
33#include "FlexBody.h"
34#include "GameContext.h"
35#include "GfxScene.h"
36#include "GUIManager.h"
37#include "GUIUtils.h"
38#include "GUI_MainSelector.h"
39#include "InputEngine.h"
40#include "Language.h"
41#include "Network.h"
42#include "PlatformUtils.h"
43#include "Replay.h"
44#include "SkyManager.h"
45#include "Terrain.h"
46#include "Terrn2FileFormat.h"
47#include "TuneupFileFormat.h"
48#include "GfxWater.h"
49#include "ScriptEngine.h"
50#include "Console.h"
51#include "ContentManager.h"
52
53#include <algorithm>
54#include <fmt/format.h>
55
56
57#ifdef USE_CURL
58# include <curl/curl.h>
59# include <curl/easy.h>
60#endif //USE_CURL
61
62#if defined(_MSC_VER) && defined(GetObject) // This MS Windows macro from <wingdi.h> (Windows Kit 8.1) clashes with RapidJSON
63# undef GetObject
64#endif
65
66using namespace RoR;
67using namespace GUI;
68
69#if defined(USE_CURL)
70
71static size_t CurlWriteFunc(void *ptr, size_t size, size_t nmemb, std::string* data)
72{
73 data->append((char*)ptr, size * nmemb);
74 return size * nmemb;
75}
76
78{
79 // If local file 'savegames/waypoints.json' exists, load it; otherwise download from GitHub.
80 // -----------------------------------------------------------------------------------------
81
82 if (FileExists(PathCombine(App::sys_savegames_dir->getStr(), "waypoints.json")))
83 {
84 try
85 {
86 Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource("waypoints.json", RGN_SAVEGAMES);
88 m.description = stream->getAsString();
90 }
91 catch (...)
92 {
93 RoR::HandleGenericException("Top menubar / AI presets");
95 m.description = "Failed to load local AI presets.";
97 }
98
99 return; // DONE
100 }
101
102 std::string url = "https://raw.githubusercontent.com/RigsOfRods-Community/ai-waypoints/main/waypoints.json";
103 std::string response_payload;
104 long response_code = 0;
105
106 CURL *curl = curl_easy_init();
107 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
108 curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
109#ifdef _WIN32
110 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
111#endif // _WIN32
112 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteFunc);
113 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_payload);
114
115 CURLcode curl_result = curl_easy_perform(curl);
116 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
117
118 curl_easy_cleanup(curl);
119 curl = nullptr;
120
121 if (curl_result != CURLE_OK || response_code != 200)
122 {
123 Ogre::LogManager::getSingleton().stream()
124 << "[RoR|Repository] Failed to download AI presets;"
125 << " Error: '" << curl_easy_strerror(curl_result) << "'; HTTP status code: " << response_code;
127 m.description = "Failed to download AI presets.";
129 }
130 else
131 {
133 m.description = response_payload;
135 }
136}
137
138#endif // defined(USE_CURL)
139
141{
142 // Constructs `ActorPtr` - doesn't compile without `#include Actor.h` - not pretty if in header (even if auto-generated by C++).
143
144 ai_presets_all.SetArray();
145 ai_presets_bundled.SetArray();
146 ai_presets_extern.SetArray();
147}
148
150{
151 // Destructs `ActorPtr` - doesn't compile without `#include Actor.h` - not pretty if in header (even if auto-generated by C++).
152}
153
154void TopMenubar::Draw(float dt)
155{
156 // ## ImGui's 'menubar' and 'menuitem' features won't quite cut it...
157 // ## Let's do our own menus and menuitems using buttons and coloring tricks.
158
160
161 int num_playable_actors = 0;
163 {
164 if (!actor->ar_hide_in_actor_list)
165 {
166 num_playable_actors++;
167 }
168 }
169
170 std::string sim_title = _LC("TopMenubar", "Simulation");
171 std::string actors_title = fmt::format("{} ({})", _LC("TopMenubar", "Vehicles"), num_playable_actors);
172 std::string savegames_title = _LC("TopMenubar", "Saves");
173 std::string settings_title = _LC("TopMenubar", "Settings");
174 std::string tools_title = _LC("TopMenubar", "Tools");
175 std::string ai_title = _LC("TopMenubar", "Vehicle AI");
176 std::string tuning_title = _LC("TopMenubar", "Tuning");
177
178 int menubar_num_buttons = 5;
179 float menubar_content_width =
180 ImGui::CalcTextSize(sim_title.c_str()).x +
181 ImGui::CalcTextSize(actors_title.c_str()).x +
182 ImGui::CalcTextSize(savegames_title.c_str()).x +
183 ImGui::CalcTextSize(settings_title.c_str()).x +
184 ImGui::CalcTextSize(tools_title.c_str()).x;
185
187 {
188 menubar_num_buttons += 1;
189 menubar_content_width += ImGui::CalcTextSize(ai_title.c_str()).x;
190 }
191
193 {
194 menubar_num_buttons += 1;
195 menubar_content_width += ImGui::CalcTextSize(tuning_title.c_str()).x;
196 }
197
198 menubar_content_width +=
199 (ImGui::GetStyle().ItemSpacing.x * (menubar_num_buttons - 1)) +
200 (ImGui::GetStyle().FramePadding.x * (menubar_num_buttons * 2));
201
202 ImVec2 window_target_pos = ImVec2((ImGui::GetIO().DisplaySize.x/2.f) - (menubar_content_width / 2.f), theme.screen_edge_padding.y);
203 if (!this->ShouldDisplay(window_target_pos))
204 {
206 m_confirm_remove_all = false;
207 this->DrawSpecialStateBox(ImGui::GetFontSize() * 0.625f);
208 return;
209 }
210
211 ImGui::PushStyleColor(ImGuiCol_WindowBg, theme.semitransparent_window_bg);
212 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); // Fully transparent
213
214 // The panel
215 int flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove
216 | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize;
217 ImGui::SetNextWindowContentSize(ImVec2(menubar_content_width, 0.f));
218 ImGui::SetNextWindowPos(window_target_pos);
219 ImGui::Begin("Top menubar", nullptr, flags);
220
221 if (ImGui::IsWindowHovered())
222 {
223 ai_menu = false;
224 }
225
226 // The 'simulation' button
227 ImVec2 window_pos = ImGui::GetWindowPos();
228 ImVec2 sim_cursor = ImGui::GetCursorPos();
229 ImGui::Button(sim_title.c_str());
230 if ((m_open_menu != TopMenu::TOPMENU_SIM) && ImGui::IsItemHovered())
231 {
233 }
234
235 // The 'Tuning' button - only shown if enabled
236 ImVec2 tuning_cursor = ImVec2(0, 0);
238 {
239 ImGui::SameLine();
240 tuning_cursor = ImGui::GetCursorPos();
241 ImGui::Button(tuning_title.c_str());
242 if ((m_open_menu != TopMenu::TOPMENU_TUNING) && ImGui::IsItemHovered())
243 {
245 }
246 }
247
248 // The 'AI' button - only shown in singleplayer
249 ImVec2 ai_cursor = ImVec2(0, 0);
251 {
252 ImGui::SameLine();
253 ai_cursor = ImGui::GetCursorPos();
254 ImGui::Button(ai_title.c_str());
255 if ((m_open_menu != TopMenu::TOPMENU_AI) && ImGui::IsItemHovered())
256 {
258 }
259 }
260
261 ImGui::SameLine();
262
263 // The 'vehicles' button
264 ImVec2 actors_cursor = ImGui::GetCursorPos();
265 ImGui::Button(actors_title.c_str());
266 if ((m_open_menu != TopMenu::TOPMENU_ACTORS) && ImGui::IsItemHovered())
267 {
269 }
270
271 ImGui::SameLine();
272
273 // The 'savegames' button
274 ImVec2 savegames_cursor = ImGui::GetCursorPos();
275 ImGui::Button(savegames_title.c_str());
276 if ((m_open_menu != TopMenu::TOPMENU_SAVEGAMES) && ImGui::IsItemHovered())
277 {
281 m_savegame_names.clear();
282 for (int i = 0; i <= 9; i++)
283 {
284 Ogre::String filename = Ogre::StringUtil::format("quicksave-%d.sav", i);
285 m_savegame_names.push_back(App::GetGameContext()->ExtractSceneName(filename));
286 }
287 }
288
289 ImGui::SameLine();
290
291 // The 'settings' button
292 ImVec2 settings_cursor = ImGui::GetCursorPos();
293 ImGui::Button(settings_title.c_str());
294 if ((m_open_menu != TopMenu::TOPMENU_SETTINGS) && ImGui::IsItemHovered())
295 {
297#ifdef USE_CAELUM
298 if (App::gfx_sky_mode->getEnum<GfxSkyMode>() == GfxSkyMode::CAELUM)
300#endif // USE_CAELUM
301 }
302
303 ImGui::SameLine();
304
305 // The 'tools' button
306 ImVec2 tools_cursor = ImGui::GetCursorPos();
307 ImGui::Button(tools_title.c_str());
308 if ((m_open_menu != TopMenu::TOPMENU_TOOLS) && ImGui::IsItemHovered())
309 {
311 }
312
313 ImVec2 topmenu_final_size = ImGui::GetWindowSize();
314 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
315 ImGui::End();
316
317 this->DrawSpecialStateBox(window_target_pos.y + topmenu_final_size.y + ImGui::GetFontSize() * 0.625f);
318
319 ImVec2 menu_pos;
320 ActorPtr current_actor = App::GetGameContext()->GetPlayerActor();
321 switch (m_open_menu)
322 {
324 menu_pos.y = window_pos.y + sim_cursor.y + MENU_Y_OFFSET * ImGui::GetFontSize();
325 menu_pos.x = sim_cursor.x + window_pos.x - ImGui::GetStyle().WindowPadding.x;
326 ImGui::SetNextWindowPos(menu_pos);
327 if (ImGui::Begin(_LC("TopMenubar", "Sim menu"), nullptr, static_cast<ImGuiWindowFlags_>(flags)))
328 {
329 // TODO: Display hotkeys on the right side of the menu (with different text color)
330
331 if (ImGui::Button(_LC("TopMenubar", "Get new vehicle")))
332 {
334
336 m.payload = reinterpret_cast<void*>(new LoaderType(LT_AllBeam));
338 }
339
340 if (current_actor != nullptr)
341 {
342 if (ImGui::Button(_LC("TopMenubar", "Show vehicle description")))
343 {
345 }
346
347 if (current_actor->ar_state != ActorState::NETWORKED_OK)
348 {
349 if (ImGui::Button(_LC("TopMenubar", "Reload current vehicle")))
350 {
353 rq->amr_actor = current_actor->ar_instance_id;
355 }
356
357 if (ImGui::Button(_LC("TopMenubar", "Remove current vehicle")))
358 {
359 App::GetGameContext()->PushMessage(Message(MSG_SIM_DELETE_ACTOR_REQUESTED, static_cast<void*>(new ActorPtr(current_actor))));
360 }
361 }
362 }
363 else if (App::GetGameContext()->GetLastSpawnedActor())
364 {
365 if (ImGui::Button(_LC("TopMenubar", "Activate last spawned vehicle")))
366 {
371 }
372
373 if (ImGui::Button(_LC("TopMenubar", "Reload last spawned vehicle")))
374 {
379 }
380
381 if (ImGui::Button(_LC("TopMenubar", "Remove last spawned vehicle")))
382 {
385 }
386 }
387
388 if (!App::GetGameContext()->GetActorManager()->GetLocalActors().empty())
389 {
390 if (ImGui::Button(_LC("TopMenubar", "Remove all vehicles")))
391 {
393 }
395 {
396 ImGui::PushStyleColor(ImGuiCol_Text, ORANGE_TEXT);
397 if (ImGui::Button(_LC("TopMenubar", " [!] Confirm removal")))
398 {
400 {
401 if (!actor->ar_hide_in_actor_list && !actor->isPreloadedWithTerrain() &&
402 actor->ar_state != ActorState::NETWORKED_OK)
403 {
405 }
406 }
407 m_confirm_remove_all = false;
408 }
409 ImGui::PopStyleColor();
410 }
411
412 if (ImGui::Button(_LC("TopMenubar", "Activate all vehicles")))
413 {
415 }
416
417 bool force_trucks_active = App::GetGameContext()->GetActorManager()->AreTrucksForcedAwake();
418 if (ImGui::Checkbox(_LC("TopMenubar", "Activated vehicles never sleep"), &force_trucks_active))
419 {
421 }
422
423 if (ImGui::Button(_LC("TopMenubar", "Send all vehicles to sleep")))
424 {
426 }
427 }
428
429 if (App::mp_state->getEnum<MpState>() != MpState::CONNECTED)
430 {
431 if (ImGui::Button(_LC("TopMenubar", "Reload current terrain")))
432 {
434 // Order is required - create chain.
436 new CacheEntryPtr(App::GetGameContext()->GetTerrain()->getCacheEntry())));
438 App::GetGameContext()->GetTerrain()->getCacheEntry()->fname));
439 }
440
441 if (App::sim_state->getEnum<SimState>() == SimState::EDITOR_MODE
442 && App::GetGameContext()->GetTerrain()->getCacheEntry()->resource_bundle_type == "FileSystem"
443 && ImGui::Button(_LC("TopMenubar", "Save changes to terrain")))
444 {
445 // This is a project (unzipped mod) - update TOBJ files in place
447 }
448 }
449
450 ImGui::Separator();
451
452 if (ImGui::Button(_LC("TopMenubar", "Back to menu")))
453 {
454 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
455 {
457 }
460 }
461
462 if (ImGui::Button(_LC("TopMenubar", "Exit")))
463 {
465 }
466
467 m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING * ImGui::GetFontSize();
468 m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth() + MENU_HOVERBOX_PADDING.x * ImGui::GetFontSize();
469 m_open_menu_hoverbox_max.y = menu_pos.y + ImGui::GetWindowHeight() + MENU_HOVERBOX_PADDING.y * ImGui::GetFontSize();
470 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
471 ImGui::End();
472 }
473 break;
474
476 menu_pos.y = window_pos.y + actors_cursor.y + MENU_Y_OFFSET * ImGui::GetFontSize();
477 menu_pos.x = actors_cursor.x + window_pos.x - ImGui::GetStyle().WindowPadding.x;
478 ImGui::SetNextWindowPos(menu_pos);
479 if (ImGui::Begin(_LC("TopMenubar", "Actors menu"), nullptr, static_cast<ImGuiWindowFlags_>(flags)))
480 {
481 if (App::mp_state->getEnum<MpState>() == MpState::DISABLED)
482 {
484 }
485 else
486 {
487#ifdef USE_SOCKETW
489 this->DrawMpUserToActorList(net_user_info);
490
491 std::vector<RoRnet::UserInfo> remote_users = App::GetNetwork()->GetUserInfos();
492 for (auto& user: remote_users)
493 {
494 this->DrawMpUserToActorList(user);
495 }
496#endif // USE_SOCKETW
497 }
498 m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING * ImGui::GetFontSize();
499 m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth() + MENU_HOVERBOX_PADDING.x * ImGui::GetFontSize();
500 m_open_menu_hoverbox_max.y = menu_pos.y + ImGui::GetWindowHeight() + MENU_HOVERBOX_PADDING.y * ImGui::GetFontSize();
501 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
502 ImGui::End();
503 }
504 break;
505
507 menu_pos.y = window_pos.y + savegames_cursor.y + MENU_Y_OFFSET * ImGui::GetFontSize();
508 menu_pos.x = savegames_cursor.x + window_pos.x - ImGui::GetStyle().WindowPadding.x;
509 ImGui::SetNextWindowPos(menu_pos);
510 if (ImGui::Begin(_LC("TopMenubar", "Savegames"), nullptr, static_cast<ImGuiWindowFlags_>(flags)))
511 {
512 if (ImGui::Button(_LC("TopMenubar", "Quicksave")))
513 {
516 }
517 ImGui::SameLine();
518 ImGui::TextColored(GRAY_HINT_TEXT, "(NUMPAD: /)");
519
520 if (m_quickload)
521 {
522 if (ImGui::Button(_LC("TopMenubar", "Quickload")))
523 {
526 }
527 ImGui::SameLine();
528 ImGui::TextColored(GRAY_HINT_TEXT, "(NUMPAD: *)");
529 }
530
531 ImGui::Separator();
532
533 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "(Save with CTRL+ALT+1..5)"));
534 for (int i = 1; i <= 5; i++)
535 {
536 Ogre::String name = _LC("TopMenubar", "Empty Slot");
537 if (!m_savegame_names[i].empty())
538 {
539 name = m_savegame_names[i];
540 }
541 Ogre::String caption = Ogre::StringUtil::format("%d. %s##Save", i, name.c_str());
542 if (ImGui::Button(caption.c_str()))
543 {
544 Ogre::String filename = Ogre::StringUtil::format("quicksave-%d.sav", i);
547 }
548 }
549
550 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "(Load with ALT+1..5)"));
551 for (int i = 1; i <= 5; i++)
552 {
553 if (!m_savegame_names[i].empty())
554 {
555 Ogre::String name = m_savegame_names[i];
556 Ogre::String caption = Ogre::StringUtil::format("%d. %s##Load", i, name.c_str());
557 if (ImGui::Button(caption.c_str()))
558 {
559 Ogre::String filename = Ogre::StringUtil::format("quicksave-%d.sav", i);
562 }
563 }
564 }
565
566 m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING * ImGui::GetFontSize();
567 m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth() + MENU_HOVERBOX_PADDING.x * ImGui::GetFontSize();
568 m_open_menu_hoverbox_max.y = menu_pos.y + ImGui::GetWindowHeight() + MENU_HOVERBOX_PADDING.y * ImGui::GetFontSize();
569 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
570 ImGui::End();
571 }
572 break;
573
575 menu_pos.y = window_pos.y + settings_cursor.y + MENU_Y_OFFSET * ImGui::GetFontSize();
576 menu_pos.x = settings_cursor.x + window_pos.x - ImGui::GetStyle().WindowPadding.x;
577 ImGui::SetNextWindowPos(menu_pos);
578 if (ImGui::Begin(_LC("TopMenubar", "Settings menu"), nullptr, static_cast<ImGuiWindowFlags_>(flags)))
579 {
580 // AUDIO SETTINGS
581 ImGui::Separator();
582 ImGui::PushItemWidth(ImGui::GetFontSize() * 7.8125f); // Width includes [+/-] buttons
583 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Audio:"));
584 DrawGFloatSlider(App::audio_master_volume, _LC("TopMenubar", "Volume"), 0, 1);
585
586 // RENDER SETTINGS
587 ImGui::Separator();
588 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Frames per second:"));
589 if (App::gfx_envmap_enabled->getBool())
590 {
591 DrawGIntSlider(App::gfx_envmap_rate, _LC("TopMenubar", "Reflections"), 0, 2);
592 }
593 DrawGIntSlider(App::gfx_fps_limit, _LC("TopMenubar", "Game"), 0, 240);
594
595 // SIM SETTINGS
596 ImGui::Separator();
597 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Simulation:"));
598 float slowmotion = std::min(App::GetGameContext()->GetActorManager()->GetSimulationSpeed(), 1.0f);
599 if (ImGui::SliderFloat(_LC("TopMenubar", "Slow motion"), &slowmotion, 0.01f, 1.0f))
600 {
602 }
603 float timelapse = std::max(App::GetGameContext()->GetActorManager()->GetSimulationSpeed(), 1.0f);
604 if (ImGui::SliderFloat(_LC("TopMenubar", "Time lapse"), &timelapse, 1.0f, 10.0f))
605 {
607 }
608
609 // CAMERA SETTINGS
611 {
612 ImGui::Separator();
613 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Camera:"));
614 DrawGFloatSlider(App::gfx_static_cam_fov_exp, _LC("TopMenubar", "FOV"), 0.8f, 1.5f);
615 DrawGIntSlider(App::gfx_camera_height, _LC("TopMenubar", "Height"), 1, 50);
616 }
617 else
618 {
619 ImGui::Separator();
620 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Camera:"));
622 {
623 int fov = App::gfx_fov_internal->getInt();
624 if (ImGui::SliderInt(_LC("TopMenubar", "FOV"), &fov, 10, 120))
625 {
627 }
628 }
629 else
630 {
631 int fov = App::gfx_fov_external->getInt();
632 if (ImGui::SliderInt(_LC("TopMenubar", "FOV"), &fov, 10, 120))
633 {
635 }
636 }
637 if (App::GetCameraManager()->GetCurrentBehavior() == CameraManager::CAMERA_BEHAVIOR_FIXED)
638 {
639 DrawGCheckbox(App::gfx_fixed_cam_tracking, _LC("TopMenubar", "Tracking"));
640 }
641 }
642
643 // SKY SETTINGS
644#ifdef USE_CAELUM
645 if (App::gfx_sky_mode->getEnum<GfxSkyMode>() == GfxSkyMode::CAELUM)
646 {
647 ImGui::Separator();
648 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Time of day:"));
649 float time = App::GetGameContext()->GetTerrain()->getSkyManager()->GetTime();
650 if (ImGui::SliderFloat("", &time, m_daytime - 0.5f, m_daytime + 0.5f, ""))
651 {
652 App::GetGameContext()->GetTerrain()->getSkyManager()->SetTime(time);
653 }
654 ImGui::SameLine();
655 DrawGCheckbox(App::gfx_sky_time_cycle, _LC("TopMenubar", "Cycle"));
656 if (App::gfx_sky_time_cycle->getBool())
657 {
658 DrawGIntSlider(App::gfx_sky_time_speed, _LC("TopMenubar", "Speed"), 10, 2000);
659 }
660 }
661#endif // USE_CAELUM
662
663 // WATER SETTINGS
664 if (RoR::App::gfx_water_waves->getBool() && App::mp_state->getEnum<MpState>() != MpState::CONNECTED && App::GetGameContext()->GetTerrain()->getWater())
665 {
666 if (App::gfx_water_mode->getEnum<GfxWaterMode>() != GfxWaterMode::HYDRAX && App::gfx_water_mode->getEnum<GfxWaterMode>() != GfxWaterMode::NONE)
667 {
668 ImGui::PushID("waves");
669 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Waves Height:"));
670 if(ImGui::SliderFloat("", &m_waves_height, 0.f, 4.f, ""))
671 {
673 }
674 ImGui::PopID();
675 }
676 }
677
678 // VEHICLE CONTROL SETTINGS
679 if (current_actor != nullptr)
680 {
681 ImGui::Separator();
682 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Vehicle control options:"));
683 DrawGCheckbox(App::io_hydro_coupling, _LC("TopMenubar", "Keyboard steering speed coupling"));
684 }
685
686 // MULTIPLAYER SETTINGS
687 if (App::mp_state->getEnum<MpState>() == MpState::CONNECTED)
688 {
689 ImGui::Separator();
690 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Multiplayer:"));
691 DrawGCheckbox(App::mp_pseudo_collisions, _LC("TopMenubar", "Collisions"));
692 DrawGCheckbox(App::mp_hide_net_labels, _LC("TopMenubar", "Hide labels"));
693 }
694 ImGui::PopItemWidth();
695 m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING * ImGui::GetFontSize();
696 m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth() + MENU_HOVERBOX_PADDING.x * ImGui::GetFontSize();
697 m_open_menu_hoverbox_max.y = menu_pos.y + ImGui::GetWindowHeight() + MENU_HOVERBOX_PADDING.y * ImGui::GetFontSize();
698 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
699 ImGui::End();
700 }
701 break;
702
704 menu_pos.y = window_pos.y + tools_cursor.y + MENU_Y_OFFSET * ImGui::GetFontSize();
705 menu_pos.x = tools_cursor.x + window_pos.x - ImGui::GetStyle().WindowPadding.x;
706 ImGui::SetNextWindowPos(menu_pos);
707 if (ImGui::Begin(_LC("TopMenubar", "Tools menu"), nullptr, static_cast<ImGuiWindowFlags_>(flags)))
708 {
709 if (ImGui::Button(_LC("TopMenubar", "Friction settings")))
710 {
713 }
714
715 if (ImGui::Button(_LC("TopMenubar", "Show console")))
716 {
719 }
720
721 if (ImGui::Button(_LC("TopMenubar", "Texture tool")))
722 {
725 }
726
727 if (ImGui::Button(_LC("TopMenubar", "Collisions debug")))
728 {
731 }
732
733 if (current_actor != nullptr)
734 {
735 if (ImGui::Button(_LC("TopMenubar", "Node / Beam utility")))
736 {
739 }
740
741 if (ImGui::Button(_LC("TopMenubar", "FlexBody debug")))
742 {
745 }
746 }
747
748 if (ImGui::Button(_LC("TopMenubar", "Browse gadgets ...")))
749 {
752 }
753
754 if (ImGui::Button(_LC("TopMenubar", "Browse repository ...")))
755 {
758 }
759
760 ImGui::Separator();
761 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Pre-spawn diag. options:"));
762
763 bool diag_mass = App::diag_truck_mass->getBool();
764 if (ImGui::Checkbox(_LC("TopMenubar", "Node mass recalc. logging"), &diag_mass))
765 {
766 App::diag_truck_mass->setVal(diag_mass);
767 }
768 if (ImGui::IsItemHovered())
769 {
770 ImGui::BeginTooltip();
771 ImGui::Text("%s", _LC("TopMenubar", "Extra logging on runtime - mass recalculation"));
772 ImGui::EndTooltip();
773 }
774
775 bool diag_break = App::diag_log_beam_break->getBool();
776 if (ImGui::Checkbox(_LC("TopMenubar", "Beam break logging"), &diag_break))
777 {
778 App::diag_log_beam_break->setVal(diag_break);
779 }
780 if (ImGui::IsItemHovered())
781 {
782 ImGui::BeginTooltip();
783 ImGui::Text("%s", _LC("TopMenubar", "Extra logging on runtime"));
784 ImGui::EndTooltip();
785 }
786
787 bool diag_deform = App::diag_log_beam_deform->getBool();
788 if (ImGui::Checkbox(_LC("TopMenubar", "Beam deform. logging"), &diag_deform))
789 {
790 App::diag_log_beam_deform->setVal(diag_deform);
791 }
792 if (ImGui::IsItemHovered())
793 {
794 ImGui::BeginTooltip();
795 ImGui::Text("%s", _LC("TopMenubar", "Extra logging on runtime"));
796 ImGui::EndTooltip();
797 }
798
799 bool diag_trig = App::diag_log_beam_trigger->getBool();
800 if (ImGui::Checkbox(_LC("TopMenubar", "Trigger logging"), &diag_trig))
801 {
803 }
804 if (ImGui::IsItemHovered())
805 {
806 ImGui::BeginTooltip();
807 ImGui::Text("%s", _LC("TopMenubar", "Extra logging on runtime - trigger beams activity"));
808 ImGui::EndTooltip();
809 }
810
811 bool diag_vcam = App::diag_videocameras->getBool();
812 if (ImGui::Checkbox(_LC("TopMenubar", "VideoCamera direction marker"), &diag_vcam))
813 {
814 App::diag_videocameras->setVal(diag_vcam);
815 }
816 if (ImGui::IsItemHovered())
817 {
818 ImGui::BeginTooltip();
819 ImGui::Text("%s", _LC("TopMenubar", "Visual marker of VideoCameras direction"));
820 ImGui::EndTooltip();
821 }
822
823 ImGui::PushItemWidth(ImGui::GetFontSize() * 7.8125f); // Width includes [+/-] buttons
824 ImGui::Separator();
825 ImGui::TextColored(GRAY_HINT_TEXT, _LC("TopMenubar", "Visual options:"));
826 DrawGIntSlider(App::gfx_polygon_mode, _LC("TopMenubar", "Polygon mode"), 1, 3);
827 if (ImGui::IsItemHovered())
828 {
829 ImGui::BeginTooltip();
830 ImGui::Text("%s", _LC("TopMenubar", "1 = Solid"));
831 ImGui::Text("%s", _LC("TopMenubar", "2 = Wireframe"));
832 ImGui::Text("%s", _LC("TopMenubar", "3 = Points"));
833 ImGui::EndTooltip();
834 }
835
836 m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING * ImGui::GetFontSize();
837 m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth() + MENU_HOVERBOX_PADDING.x * ImGui::GetFontSize();
838 m_open_menu_hoverbox_max.y = menu_pos.y + ImGui::GetWindowHeight() + MENU_HOVERBOX_PADDING.y * ImGui::GetFontSize();
839 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
840 ImGui::End();
841 }
842 break;
843
845 menu_pos.y = window_pos.y + ai_cursor.y + MENU_Y_OFFSET * ImGui::GetFontSize();
846 menu_pos.x = ai_cursor.x + window_pos.x - ImGui::GetStyle().WindowPadding.x;
847 ImGui::SetNextWindowPos(menu_pos);
848 if (ImGui::Begin(_LC("TopMenubar", "AI menu"), nullptr, static_cast<ImGuiWindowFlags_>(flags)))
849 {
850 if (ImGui::IsWindowHovered())
851 {
852 ai_menu = false;
853 }
854
855 ImGui::PushItemWidth(ImGui::GetFontSize() * 7.8125f); // Width includes [+/-] buttons
856 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "General options:"));
857
858 if (ai_num < 1)
859 ai_num = 1;
860
861
862 if (ai_mode == 2 || ai_mode == 3) // Drag Race or Crash driving mode
863 {
864 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
865 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
866 }
867
868 ImGui::InputInt(_LC("TopMenubar", "Vehicle count"), &ai_num, 1, 100);
869 if (ImGui::IsItemHovered())
870 {
871 ImGui::BeginTooltip();
872 ImGui::Text("%s", _LC("TopMenubar", "Number of vehicles"));
873 ImGui::EndTooltip();
874 }
875
876 if (ai_mode == 2 || ai_mode == 3) // Drag Race or Crash driving mode
877 {
878 ImGui::PopItemFlag();
879 ImGui::PopStyleVar();
880 }
881
882 if (ai_num < 2)
883 {
884 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
885 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
886 }
887
888 if (ai_mode == 3) // Crash driving mode
889 {
890 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
891 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
892 }
893
894 ImGui::InputInt(_LC("TopMenubar", "Distance"), &ai_distance, 1, 100);
895 if (ImGui::IsItemHovered())
896 {
897 ImGui::BeginTooltip();
898 ImGui::Text("%s", _LC("TopMenubar", "Following distance in meters"));
899 ImGui::EndTooltip();
900 }
901
902 if (ai_mode == 3) // Crash driving mode
903 {
904 ImGui::PopItemFlag();
905 ImGui::PopStyleVar();
906 }
907
908 std::string label1 = "Behind";
909 if (ai_position_scheme == 1)
910 {
911 label1 = "Parallel";
912 }
913 else if (ai_position_scheme == 2)
914 {
915 label1 = "Opposite";
916 }
917
918 if (ai_mode == 2 || ai_mode == 3) // Drag Race or Crash driving mode
919 {
920 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
921 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
922 }
923
924 if (ImGui::BeginCombo("Position", label1.c_str()))
925 {
926 if (ImGui::Selectable("Behind"))
927 {
929 }
930 if (ImGui::Selectable("Parallel"))
931 {
933 }
934 ImGui::EndCombo();
935 }
936 if (ImGui::IsItemHovered())
937 {
938 ImGui::BeginTooltip();
939 ImGui::Text("%s", _LC("TopMenubar", "Positioning scheme"));
940 ImGui::Separator();
941 ImGui::Text("%s", _LC("TopMenubar", "Behind: Set vehicle behind vehicle, in line"));
942 ImGui::Text("%s", _LC("TopMenubar", "Parallel: Set vehicles in parallel, useful for certain scenarios like drag races"));
943 ImGui::EndTooltip();
944 }
945
946 if (ai_num < 2)
947 {
948 ImGui::PopItemFlag();
949 ImGui::PopStyleVar();
950 }
951
952 if (ai_times < 1)
953 ai_times = 1;
954
955 if (ai_mode == 4) // Chase driving mode
956 {
957 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
958 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
959 }
960
961 ImGui::InputInt(_LC("TopMenubar", "Repeat times"), &ai_times, 1, 100);
962 if (ImGui::IsItemHovered())
963 {
964 ImGui::BeginTooltip();
965 ImGui::Text("%s", _LC("TopMenubar", "How many times to loop the path"));
966 ImGui::EndTooltip();
967 }
968
969 if (ai_mode == 4) // Chase driving mode
970 {
971 ImGui::PopItemFlag();
972 ImGui::PopStyleVar();
973 }
974
975 if (ai_mode == 2 || ai_mode == 3) // Drag Race or Crash driving mode
976 {
977 ImGui::PopItemFlag();
978 ImGui::PopStyleVar();
979 }
980
981 ImGui::Separator();
982 ImGui::TextColored(GRAY_HINT_TEXT, "%s", _LC("TopMenubar", "Vehicle options:"));
983
984 std::string label2 = "Normal";
985 if (ai_mode == 1)
986 {
987 label2 = "Race";
988 }
989 else if (ai_mode == 2)
990 {
991 label2 = "Drag Race";
992 }
993 else if (ai_mode == 3)
994 {
995 label2 = "Crash";
996 }
997 else if (ai_mode == 4)
998 {
999 label2 = "Chase";
1000 }
1001
1002 for (auto actor : App::GetGameContext()->GetActorManager()->GetLocalActors())
1003 {
1004 if (actor->ar_driveable == AI)
1005 {
1006 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
1007 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
1008 break;
1009 }
1010 }
1011
1012 if (ImGui::BeginCombo("Mode", label2.c_str()))
1013 {
1014 if (ImGui::Selectable("Normal"))
1015 {
1016 ai_mode = 0;
1017
1018 if (ai_mode_prev == 2 || ai_mode_prev == 3)
1019 {
1024 }
1026 }
1027 if (ImGui::Selectable("Race"))
1028 {
1029 ai_mode = 1;
1030
1031 if (ai_mode_prev == 2 || ai_mode_prev == 3)
1032 {
1037 }
1039 }
1040 if (ImGui::Selectable("Drag Race"))
1041 {
1042 ai_mode = 2;
1043
1044 if (ai_mode_prev != 3)
1045 {
1050 }
1052 ai_num = 2;
1053 ai_speed = 1000;
1055 ai_times = 1;
1056 }
1057 if (ImGui::Selectable("Crash"))
1058 {
1059 ai_mode = 3;
1060 if (ai_mode_prev != 2)
1061 {
1066 }
1068 ai_num = 2;
1069 ai_speed = 100;
1071 ai_times = 1;
1072 }
1073 if (ImGui::Selectable("Chase"))
1074 {
1075 ai_mode = 4;
1076
1077 if (ai_mode_prev == 2 || ai_mode_prev == 3)
1078 {
1083 }
1085 }
1086 ImGui::EndCombo();
1087 }
1088 if (ImGui::IsItemHovered())
1089 {
1090 ImGui::BeginTooltip();
1091 ImGui::Text("%s", _LC("TopMenubar", "Land vehicle driving mode"));
1092 ImGui::Separator();
1093 ImGui::Text("%s", _LC("TopMenubar", "Normal: Modify speed according to turns, other vehicles and character"));
1094 ImGui::Text("%s", _LC("TopMenubar", "Race: Always keep defined speed"));
1095 ImGui::Text("%s", _LC("TopMenubar", "Drag Race: Two vehicles performing a drag race"));
1096 ImGui::Text("%s", _LC("TopMenubar", "Crash: Two vehicles driving in opposite direction"));
1097 ImGui::Text("%s", _LC("TopMenubar", "Chase: Follow character and player vehicle"));
1098 ImGui::EndTooltip();
1099 }
1100
1101 for (auto actor : App::GetGameContext()->GetActorManager()->GetLocalActors())
1102 {
1103 if (actor->ar_driveable == AI)
1104 {
1105 ImGui::PopItemFlag();
1106 ImGui::PopStyleVar();
1107 break;
1108 }
1109 }
1110
1111 if (ai_speed < 1)
1112 ai_speed = 1;
1113
1114 ImGui::InputInt(_LC("TopMenubar", "Speed"), &ai_speed, 1, 100);
1115 if (ImGui::IsItemHovered())
1116 {
1117 ImGui::BeginTooltip();
1118 ImGui::Text("%s", _LC("TopMenubar", "Speed in km/h for land vehicles or knots/s for boats"));
1119 ImGui::EndTooltip();
1120 }
1121
1122 if (ai_altitude < 1)
1123 ai_altitude = 1;
1124
1125 ImGui::InputInt(_LC("TopMenubar", "Altitude"), &ai_altitude, 1, 100);
1126 if (ImGui::IsItemHovered())
1127 {
1128 ImGui::BeginTooltip();
1129 ImGui::Text("%s", _LC("TopMenubar", "Airplane maximum altitude in feet"));
1130 ImGui::EndTooltip();
1131 }
1132
1133 ImGui::Separator();
1134
1135 if (ImGui::Button(StripColorMarksFromText(ai_dname).c_str(), ImVec2(ImGui::GetFontSize() * 15.625f, 0)))
1136 {
1137 ai_select = true;
1138
1140 m.payload = reinterpret_cast<void*>(new LoaderType(LT_AllBeam));
1142 }
1143 if (ImGui::IsItemHovered())
1144 {
1145 ImGui::BeginTooltip();
1146 ImGui::Text("%s", _LC("TopMenubar", "Land vehicles, boats and airplanes"));
1147 ImGui::EndTooltip();
1148 }
1149
1150 if (ai_mode == 2 || ai_mode == 3) // Drag Race or Crash driving mode
1151 {
1152 ImGui::PushID("vehicle2");
1153 if (ImGui::Button(StripColorMarksFromText(ai_dname2).c_str(), ImVec2(ImGui::GetFontSize() * 15.625f, 0)))
1154 {
1155 ai_select2 = true;
1156
1158 m.payload = reinterpret_cast<void*>(new LoaderType(LT_AllBeam));
1160 }
1161 if (ImGui::IsItemHovered())
1162 {
1163 ImGui::BeginTooltip();
1164 ImGui::Text("%s", _LC("TopMenubar", "Land vehicles, boats and airplanes"));
1165 ImGui::EndTooltip();
1166 }
1167 ImGui::PopID();
1168 }
1169
1170 ImGui::Separator();
1171
1172 if (ai_rec)
1173 {
1174 ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
1175 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
1176 }
1177
1178 if (!ai_waypoints.empty() || ai_mode == 4) // Waypoints provided or Chase driving mode
1179 {
1180 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]);
1181 }
1182
1183 if (ImGui::Button(_LC("TopMenubar", "Start"), ImVec2(ImGui::GetFontSize() * 5.f, 0)))
1184 {
1185 if (ai_mode == 4) // Chase driving mode uses special waypoint setup
1186 {
1187 ai_waypoints.clear();
1188 if (App::GetGameContext()->GetPlayerActor()) // We are in vehicle
1189 {
1190 ai_events waypoint;
1191 waypoint.position = App::GetGameContext()->GetPlayerActor()->getPosition() + Ogre::Vector3(20, 0, 0);
1192 ai_waypoints.push_back(waypoint);
1193 }
1194 else // We are in feet
1195 {
1196 ai_events waypoint;
1197 waypoint.position = App::GetGameContext()->GetPlayerCharacter()->getPosition() + Ogre::Vector3(20, 0, 0);
1198 ai_waypoints.push_back(waypoint);
1199 }
1200 }
1201
1202 if (ai_waypoints.empty())
1203 {
1205 fmt::format(_LC("TopMenubar", "Select a preset, record or open survey map ({}) to set waypoints."),
1206 App::GetInputEngine()->getEventCommandTrimmed(EV_SURVEY_MAP_CYCLE)), "lightbulb.png");
1207 }
1208 else
1209 {
1212 rq->lsr_filename = "AI.as";
1214 }
1215 }
1216
1217 if (!ai_waypoints.empty() || ai_mode == 4) // Waypoints provided or Chase driving mode
1218 {
1219 ImGui::PopStyleColor();
1220 }
1221
1222 ImGui::SameLine();
1223
1224 if (ImGui::Button(_LC("TopMenubar", "Stop All"), ImVec2(ImGui::GetFontSize() * 5.f, 0)))
1225 {
1226 if (ai_mode == 4) // Chase driving mode
1227 {
1228 ai_waypoints.clear();
1229 }
1230
1231 if (App::mp_state->getEnum<MpState>() == MpState::LOCAL_SCRIPT)
1232 {
1233 for (const auto& pair: App::GetScriptEngine()->getScriptUnits())
1234 {
1235 if (pair.second.scriptCategory == ScriptCategory::AI_BOT)
1236 {
1238 }
1239 }
1240 }
1241 else
1242 {
1244 {
1245 if (actor->ar_driveable == AI)
1246 {
1248 }
1249 }
1250 }
1251 }
1252
1253 if (ai_rec)
1254 {
1255 ImGui::PopItemFlag();
1256 ImGui::PopStyleVar();
1257 }
1258
1259 ImGui::SameLine();
1260
1261 ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_Button]);
1262 std::string label = "Record";
1263 if (ai_rec)
1264 {
1265 label = "Recording";
1266 ImGui::PushStyleColor(ImGuiCol_Button, RED_TEXT);
1267 }
1268
1269 if (ImGui::Button(label.c_str(), ImVec2(ImGui::GetFontSize() * 5.f, 0)))
1270 {
1271 if (!ai_rec)
1272 {
1273 ai_waypoints.clear();
1274 ai_rec = true;
1275 }
1276 else
1277 {
1278 ai_rec = false;
1279 }
1280 }
1281
1282 ImGui::PopStyleColor();
1283 ImGui::Separator();
1284
1285 if (ImGui::CollapsingHeader(_LC("TopMenubar", "Presets")))
1286 {
1287 // Draw whatever we already have (i.e. presets bundled with terrain, see '[AI Presets]' in terrn2 format).
1288 size_t num_rows = ai_presets_all.GetArray().Size();
1289 int display_count = 0;
1290 for (size_t i = 0; i < num_rows; i++)
1291 {
1292 rapidjson::Value& j_row = ai_presets_all[static_cast<rapidjson::SizeType>(i)];
1293
1294 if (j_row.HasMember("terrain") && App::sim_terrain_name->getStr() == j_row["terrain"].GetString())
1295 {
1296 display_count++;
1297 if (ImGui::Button(j_row["preset"].GetString(), ImVec2(ImGui::GetFontSize() * 15.625f, 0)))
1298 {
1299 ai_waypoints.clear();
1300
1301 for (size_t i = 0; i < j_row["waypoints"].Size(); i++)
1302 {
1303 float x = j_row["waypoints"][i][0].GetFloat();
1304 float y = j_row["waypoints"][i][1].GetFloat();
1305 float z = j_row["waypoints"][i][2].GetFloat();
1306
1307 ai_events waypoint;
1308 waypoint.position = Ogre::Vector3(x, y, z);
1309
1310 int speed = -1;
1311 if (j_row["waypoints"][i].Size() == 4) // Custom speed defined
1312 {
1313 speed = j_row["waypoints"][i][3].GetInt();
1314 if (speed < 5)
1315 {
1316 speed = -1;
1317 }
1318 }
1319 waypoint.speed = speed;
1320 ai_waypoints.push_back(waypoint);
1321 }
1322 }
1323 }
1324 }
1325
1326 // Fetch additional presets, or display error if failed
1327 if (ai_presets_extern.Empty())
1328 {
1330 {
1331 float spinner_size = ImGui::GetFontSize() * 0.5f;
1332 ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.f) - spinner_size);
1333 LoadingIndicatorCircle("spinner", spinner_size, theme.value_blue_text_color, theme.value_blue_text_color, 10, 10);
1334 }
1335 else if (ai_presets_extern_error != "")
1336 {
1337 ImGui::TextColored(RED_TEXT, "%s", _LC("TopMenubar", "Failed to fetch external presets."));
1338 if (ImGui::Button(_LC("TopMenubar", "Retry")))
1339 {
1340 this->FetchExternAiPresetsOnBackground(); // Will post `MSG_NET_REFRESH_AI_PRESETS` when done.
1341 }
1342 }
1343 else
1344 {
1345 this->FetchExternAiPresetsOnBackground(); // Will post `MSG_NET_REFRESH_AI_PRESETS` when done.
1346 }
1347 }
1348
1349 // If no presets found, display message
1350 if (display_count == 0 && !ai_presets_extern_fetching && ai_presets_extern_error == "")
1351 {
1352 ImGui::Text("%s", _LC("TopMenubar", "No presets found for this terrain :("));
1353 ImGui::Text("%s", _LC("TopMenubar", "Supported terrains:"));
1354 ImGui::Separator();
1355
1356 ImGui::BeginChild("terrains-scrolling", ImVec2(0.f, ImGui::GetFontSize() * 12.5f), false);
1357
1358 for (size_t i = 0; i < num_rows; i++)
1359 {
1360 rapidjson::Value& j_row_terrains = ai_presets_all[static_cast<rapidjson::SizeType>(i)];
1361 if (j_row_terrains.HasMember("terrains"))
1362 {
1363 for (size_t i = 0; i < j_row_terrains["terrains"].Size(); i++)
1364 {
1365 ImGui::Text("%s", j_row_terrains["terrains"][i].GetString());
1366 }
1367 }
1368 }
1369
1370 ImGui::EndChild();
1371 }
1372 }
1373
1374 if (ImGui::CollapsingHeader(_LC("TopMenubar", "Waypoints")))
1375 {
1376 if (ai_waypoints.empty())
1377 {
1378 ImGui::Text("%s", _LC("TopMenubar", "No waypoints defined."));
1379 }
1380 else
1381 {
1382 if (ImGui::Button(_LC("TopMenubar", "Export"), ImVec2(ImGui::GetFontSize() * 15.625f, 0)))
1383 {
1384 std::string s;
1385
1386 for (int i = 0; i < ai_waypoints.size(); i++)
1387 {
1388 // Write position
1389 s += "\n [" + std::to_string(ai_waypoints[i].position.x) + ", " + std::to_string(ai_waypoints[i].position.y) + ", " + std::to_string(ai_waypoints[i].position.z);
1390
1391 // Write custom speed
1392 if (ai_waypoints[i].speed >= 5)
1393 {
1394 s += ", " + std::to_string(ai_waypoints[i].speed);
1395 }
1396
1397 // Close
1398 s += "]";
1399 if (i != ai_waypoints.size() - 1)
1400 {
1401 s += ",";
1402 }
1403 }
1404
1405 std::string json = fmt::format("\n {{\n \"terrain\":\"{}\",\n \"preset\":\"Preset name\",\n \"waypoints\":\n [{}\n ]\n }}", App::sim_terrain_name->getStr(), s);
1406 RoR::Log(json.c_str());
1407
1409 fmt::format(_LC("TopMenubar", "{} waypoints exported to RoR.log"),
1410 ai_waypoints.size()), "lightbulb.png");
1411 }
1412
1413 ImGui::BeginChild("waypoints-scrolling", ImVec2(0.f, ImGui::GetFontSize() * 12.5f), false);
1414
1415 for (int i = 0; i < ai_waypoints.size(); i++)
1416 {
1417 ImGui::PushID(i);
1418 ImGui::AlignTextToFramePadding();
1419 ImGui::Text("%d", i);
1420 ImGui::SameLine();
1421 if (ImGui::Button("teleport", ImVec2(ImGui::GetFontSize() * 3.75f, 0)))
1422 {
1423 Ogre::Vector3* payload = new Ogre::Vector3(ai_waypoints[i].position);
1425 }
1426 if (ImGui::IsItemHovered())
1427 {
1428 ImGui::BeginTooltip();
1429 std::string w = "x:" + std::to_string(ai_waypoints[i].position.x) + " y:" + std::to_string(ai_waypoints[i].position.y) + " z:" + std::to_string(ai_waypoints[i].position.z);
1430 ImGui::Text(w.c_str());
1431 ImGui::EndTooltip();
1432 }
1433 ImGui::SameLine();
1434 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 5.625f);
1435
1436 if (ai_waypoints[i].speed < -1)
1437 {
1438 ai_waypoints[i].speed = -1;
1439 }
1440 ImGui::InputInt(_LC("TopMenubar", "speed"), &ai_waypoints[i].speed, 1, 100);
1441 if (ImGui::IsItemHovered())
1442 {
1443 ImGui::BeginTooltip();
1444 ImGui::Text(_LC("TopMenubar", "Set waypoint speed in km/h for land vehicles"));
1445 ImGui::Separator();
1446 ImGui::Text(_LC("TopMenubar", "Value -1: Ignore, vehicle will use default speed"));
1447 ImGui::Text(_LC("TopMenubar", "Value >= 5: Override default speed"));
1448 ImGui::EndTooltip();
1449 }
1450 ImGui::PopID();
1451 }
1452
1453 ImGui::EndChild();
1454 }
1455 }
1456
1457 ImGui::PopItemWidth();
1458 m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING * ImGui::GetFontSize();
1459 m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth() + MENU_HOVERBOX_PADDING.x * ImGui::GetFontSize();
1460 m_open_menu_hoverbox_max.y = menu_pos.y + ImGui::GetWindowHeight() + MENU_HOVERBOX_PADDING.y * ImGui::GetFontSize();
1461 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
1462 ImGui::End();
1463 }
1464 break;
1465
1467 menu_pos.y = window_pos.y + tuning_cursor.y + MENU_Y_OFFSET * ImGui::GetFontSize();
1468 menu_pos.x = tuning_cursor.x + window_pos.x - ImGui::GetStyle().WindowPadding.x;
1469 ImGui::SetNextWindowPos(menu_pos);
1470 if (ImGui::Begin(_LC("TopMenubar", "Tuning menu"), nullptr, static_cast<ImGuiWindowFlags_>(flags)))
1471 {
1472 this->RefreshTuningMenu(); // make sure our local context is valid
1473 if (!tuning_actor)
1474 {
1475 ImGui::PushStyleColor(ImGuiCol_Text, GRAY_HINT_TEXT);
1476 ImGui::Text("%s", _LC("Tuning", "You are on foot."));
1477 ImGui::Text("%s", _LC("Tuning", "Enter a vehicle to tune it."));
1478 ImGui::PopStyleColor();
1479 }
1480 else
1481 {
1484
1485 // SAVED TUNEUPS
1486 ImGui::TextDisabled(fmt::format(_LC("Tuning", "Saved tuneups ({})"), tuning_saves.cqy_results.size()).c_str());
1487 for (CacheQueryResult& tuneup_result: tuning_saves.cqy_results)
1488 {
1489 ImGui::PushID(tuneup_result.cqr_entry->fname.c_str());
1490
1491 ImGui::AlignTextToFramePadding();
1492 ImGui::Bullet();
1493
1494 // Load button (with tuneup name)
1495 ImGui::SameLine();
1496 if (ImGui::Button(tuneup_result.cqr_entry->dname.c_str()))
1497 {
1500 req->mpr_subject = tuneup_result.cqr_entry->fname;
1503 // Why 'MODIFY_PROJECT_REQUESTED' for loading?
1504 // Instead of loading with the saved tuneup directly, we keep the autogenerated and sync it with the save.
1505 // That way, subsequent editing doesn't modify the save until user saves again.
1506 }
1507
1508 // Delete button (right-aligned)
1509 ImGui::SameLine();
1510 if (tuning_rwidget_cursorx_min < ImGui::GetCursorPosX()) // Make sure button won't draw over item name
1511 tuning_rwidget_cursorx_min = ImGui::GetCursorPosX();
1512 std::string delbtn_text = _LC("Tuning", "Delete");
1513 float delbtn_w = ImGui::CalcTextSize(delbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
1514 float delbtn_cursorx = ImGui::GetWindowContentRegionWidth() - delbtn_w;
1515 if (delbtn_cursorx < tuning_rwidget_cursorx_min)
1516 delbtn_cursorx = tuning_rwidget_cursorx_min;
1517 ImGui::SetCursorPosX(delbtn_cursorx);
1518 ImGui::PushStyleColor(ImGuiCol_Button, TUNING_HOLDTOCONFIRM_COLOR);
1519 bool delbtn_pressed = RoR::ImButtonHoldToConfirm(delbtn_text, /*small:*/true, TUNING_HOLDTOCONFIRM_TIMELIMIT);
1520 ImGui::PopStyleColor(); //ImGuiCol_Button
1521 if (delbtn_pressed)
1522 {
1524 }
1525
1526 ImGui::PopID(); // tuneup_result.cqr_entry->fname.c_str()
1527 }
1528
1529 // WORKING TUNEUP
1530 ImGui::Separator();
1531 ImGui::AlignTextToFramePadding();
1532 ImGui::TextDisabled(_LC("Tuning", "Working tuneup"));
1534 {
1535 ImGui::InputText(_LC("Tuning", "Name"), tuning_savebox_buf.GetBuffer(), tuning_savebox_buf.GetCapacity());
1536
1537 if (ImGui::Button(_LC("Tuning","Save")))
1538 {
1546 }
1547 ImGui::SameLine();
1548 ImGui::Checkbox(_LC("Tuning", "Overwrite"), &tuning_savebox_overwrite);
1549
1550 // Cancel button (right-aligned)
1551 if (tuning_rwidget_cursorx_min < ImGui::GetCursorPosX()) // Make sure button won't draw over save button
1552 tuning_rwidget_cursorx_min = ImGui::GetCursorPosX();
1553 std::string cancelbtn_text = _LC("Tuning", "Cancel");
1554 float cancelbtn_w = ImGui::CalcTextSize(cancelbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
1555 float cancelbtn_cursorx = ImGui::GetWindowContentRegionWidth() - cancelbtn_w;
1556 if (cancelbtn_cursorx < tuning_rwidget_cursorx_min)
1557 cancelbtn_cursorx = tuning_rwidget_cursorx_min;
1558 ImGui::SetCursorPosX(cancelbtn_cursorx);
1559 if (ImGui::SmallButton(_LC("Tuning", "Cancel")))
1560 {
1561 tuning_savebox_visible = false;
1562 }
1563 ImGui::Separator();
1564 }
1565 else if (tuneup_def)
1566 {
1567 ImGui::SameLine();
1568 if (ImGui::Button(_LC("Tuning", "Save as...")))
1569 {
1571 }
1572
1573 // Reset button (right-aligned)
1574 ImGui::SameLine();
1575 if (tuning_rwidget_cursorx_min < ImGui::GetCursorPosX()) // Make sure button won't draw over save button
1576 tuning_rwidget_cursorx_min = ImGui::GetCursorPosX();
1577 ImGui::AlignTextToFramePadding();
1578 std::string resetbtn_text = _LC("Tuning", "Reset");
1579 float delbtn_w = ImGui::CalcTextSize(resetbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
1580 float delbtn_cursorx = ImGui::GetWindowContentRegionWidth() - delbtn_w;
1581 if (delbtn_cursorx < tuning_rwidget_cursorx_min)
1582 delbtn_cursorx = tuning_rwidget_cursorx_min;
1583 ImGui::SetCursorPosX(delbtn_cursorx);
1584 ImGui::PushStyleColor(ImGuiCol_Button, TUNING_HOLDTOCONFIRM_COLOR);
1585 bool resetbtn_pressed = ImButtonHoldToConfirm(resetbtn_text, /*small:*/false, TUNING_HOLDTOCONFIRM_TIMELIMIT);
1586 ImGui::PopStyleColor(); //ImGuiCol_Button
1587 if (resetbtn_pressed)
1588 {
1590 request->mpr_target_actor = tuning_actor;
1593 }
1594 }
1595
1596 // ADDONPARTS
1597
1598 ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
1599 std::string addonparts_title = fmt::format(_LC("TopMenubar", "Addon parts ({})"), tuning_addonparts.size());
1600 if (ImGui::CollapsingHeader(addonparts_title.c_str()))
1601 {
1602 for (size_t i = 0; i < tuning_addonparts.size(); i++)
1603 {
1604 const CacheEntryPtr& addonpart_entry = tuning_addonparts[i];
1605
1606 ImGui::PushID(addonpart_entry->fname.c_str());
1607 const bool conflict_w_hovered = tuning_hovered_addonpart
1608 && (addonpart_entry != tuning_hovered_addonpart)
1610 bool used = TuneupUtil::isAddonPartUsed(tuneup_def, addonpart_entry->fname);
1611 const ImVec2 checkbox_cursor = ImGui::GetCursorScreenPos();
1612 if (ImGui::Checkbox(addonpart_entry->dname.c_str(), &used)
1613 && !conflict_w_hovered
1615 {
1617 req->mpr_type = (used)
1620 req->mpr_subject = addonpart_entry->fname;
1623 }
1624 // Draw conflict markers
1626 {
1627 // Gray-ish X inside the checkbox
1628 const float square_sz = ImGui::GetFrameHeight();
1629 const ImVec2 min = checkbox_cursor + ImGui::GetStyle().FramePadding*1.4f;
1630 const ImVec2 max = checkbox_cursor + (ImVec2(square_sz, square_sz) - ImGui::GetStyle().FramePadding*1.5f);
1631 const ImColor X_COLOR(0.5f, 0.48f, 0.45f);
1632 ImGui::GetWindowDrawList()->AddLine(min, max, X_COLOR, ImGui::GetFontSize() * 0.25f);
1633 ImGui::GetWindowDrawList()->AddLine(ImVec2(min.x, max.y), ImVec2(max.x, min.y), X_COLOR, ImGui::GetFontSize() * 0.25f);
1634 }
1635 if (conflict_w_hovered)
1636 {
1637 // Red unrounded square around the checkbox
1638 const float square_sz = ImGui::GetFrameHeight();
1639 const ImVec2 min = checkbox_cursor;
1640 const ImVec2 max = checkbox_cursor + ImVec2(square_sz + 0.5f, square_sz);
1641 const ImColor SQ_COLOR(0.7f, 0.1f, 0.f);
1642 ImGui::GetWindowDrawList()->AddRect(min, max, SQ_COLOR, 0.f, ImDrawCornerFlags_None, ImGui::GetFontSize() * 0.1875f);
1643 }
1644 // Record when checkbox is hovered - for drawing conflict markers
1645 if (ImGui::IsItemHovered())
1646 {
1647 tuning_hovered_addonpart = addonpart_entry;
1648 }
1649 else if (tuning_hovered_addonpart == addonpart_entry)
1650 {
1651 tuning_hovered_addonpart = nullptr;
1652 }
1653 // Reload button (right-aligned)
1654 ImGui::SameLine();
1655 if (tuning_rwidget_cursorx_min < ImGui::GetCursorPosX()) // Make sure button won't draw over save button
1656 tuning_rwidget_cursorx_min = ImGui::GetCursorPosX();
1657 ImGui::AlignTextToFramePadding();
1658 std::string reloadbtn_text = _LC("Tuning", "Reload");
1659 const float reloadbtn_w = ImGui::CalcTextSize(reloadbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
1660 const float reloadbtn_cursorx = std::max(ImGui::GetWindowContentRegionWidth() - reloadbtn_w, tuning_rwidget_cursorx_min);
1661 ImGui::SetCursorPosX(reloadbtn_cursorx);
1662 const bool reloadbtn_pressed = ImGui::SmallButton(reloadbtn_text.c_str());
1663 if (reloadbtn_pressed)
1664 {
1665 // Create spawn request while actor still exists
1666 // Note we don't use `ActorModifyRequest::Type::RELOAD` because we don't need the bundle reloaded.
1669 srq->asr_rotation = Ogre::Quaternion(Ogre::Degree(270) - Ogre::Radian(tuning_actor->getRotation()), Ogre::Vector3::UNIT_Y);
1676
1677 // Request bundle reloading and chain the actor delete/spawn messages to it.
1681 }
1682
1683 ImGui::PopID(); //(addonpart_entry->fname.c_str());
1684 }
1685
1686 if (ImGui::Button(_LC("Tuning", "Browse all parts")))
1687 {
1689 }
1690
1691 ImGui::Separator();
1692 }
1693
1694 // Draw props
1695 size_t total_props = tuning_actor->GetGfxActor()->getProps().size();
1696 std::string props_title = fmt::format(_LC("Tuning", "Props ({})"), total_props);
1697 if (ImGui::CollapsingHeader(props_title.c_str()))
1698 {
1699 // Draw all props (those removed by addonparts are also present as placeholders)
1700 for (Prop const& p: tuning_actor->GetGfxActor()->getProps())
1701 {
1702 ImGui::PushID(p.pp_id);
1703 ImGui::AlignTextToFramePadding();
1704
1705 this->DrawTuningBoxedSubjectIdInline(p.pp_id);
1706
1708 p.pp_id,
1709 p.pp_media[0],
1710 tuneup_def && tuneup_def->isPropUnwanted(p.pp_id),
1711 tuneup_def && tuneup_def->isPropForceRemoved(p.pp_id),
1714
1715 // Draw special prop tooltip
1716 if (p.pp_beacon_type == 'L' || p.pp_beacon_type == 'R' || p.pp_beacon_type == 'w')
1717 {
1718 ImGui::SameLine();
1719 ImGui::TextDisabled("(special!)");
1720 if (ImGui::IsItemHovered())
1721 {
1722 ImGui::BeginTooltip();
1723 ImGui::Text("special prop - aerial nav light");
1724 ImGui::EndTooltip();
1725 }
1726 }
1727 else if (p.pp_wheel_mesh_obj)
1728 {
1729 ImGui::SameLine();
1730 ImGui::TextDisabled("(special!)");
1731 if (ImGui::IsItemHovered())
1732 {
1733 ImGui::BeginTooltip();
1734 ImGui::Text("special prop - dashboard + dirwheel");
1735 ImGui::EndTooltip();
1736 }
1737
1738 }
1739
1741 p.pp_id,
1742 tuneup_def && tuneup_def->isPropProtected(p.pp_id),
1745
1746 ImGui::PopID(); // p.pp_id
1747 }
1748
1749 ImGui::Separator();
1750 }
1751
1752 // Ditto for flexbodies
1753 size_t total_flexbodies = tuning_actor->GetGfxActor()->GetFlexbodies().size();
1754 std::string flexbodies_title = fmt::format(_LC("Tuning", "Flexbodies ({})"), total_flexbodies);
1755 if (ImGui::CollapsingHeader(flexbodies_title.c_str()))
1756 {
1757 // Draw all flexbodies (those removed by addonparts are also present as placeholders)
1758 for (FlexBody* flexbody: tuning_actor->GetGfxActor()->GetFlexbodies())
1759 {
1760 ImGui::PushID(flexbody->getID());
1761 ImGui::AlignTextToFramePadding();
1762
1763 this->DrawTuningBoxedSubjectIdInline(flexbody->getID());
1764
1766 flexbody->getID(),
1767 flexbody->getOrigMeshName(),
1768 tuneup_def && tuneup_def->isFlexbodyUnwanted(flexbody->getID()),
1769 tuneup_def && tuneup_def->isFlexbodyForceRemoved(flexbody->getID()),
1772
1774 flexbody->getID(),
1775 tuneup_def && tuneup_def->isFlexbodyProtected(flexbody->getID()),
1778
1779 ImGui::PopID(); // flexbody->getID()
1780 }
1781 }
1782
1783 // Draw wheels
1784 const int total_wheels = tuning_actor->ar_num_wheels;
1785 std::string wheels_title = fmt::format(_LC("TopMenubar", "Wheels ({})"), total_wheels);
1786 if (ImGui::CollapsingHeader(wheels_title.c_str()))
1787 {
1788 for (WheelID_t i = 0; i < total_wheels; i++)
1789 {
1790 ImGui::PushID(i);
1791 ImGui::AlignTextToFramePadding();
1792
1794
1795 // Draw R/L radio buttons
1796 WheelSide forced_side = WheelSide::INVALID;
1797 if (tuneup_def && tuneup_def->isWheelSideForced(i, /*[out]*/forced_side))
1798 {
1799 ImGui::PushStyleColor(ImGuiCol_Border, ORANGE_TEXT);
1800 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.f);
1801 }
1802 const RoR::WheelSide active_side = TuneupUtil::getTweakedWheelSide(tuneup_def, i, tuning_actor->GetGfxActor()->getWheelSide(i));
1803 RoR::WheelSide selected_side = active_side;
1804 if (ImGui::RadioButton("##L", active_side == WheelSide::LEFT))
1805 selected_side = WheelSide::LEFT;
1806 ImGui::SameLine();
1807 ImGui::TextDisabled("|");
1808 ImGui::SameLine();
1809 if (ImGui::RadioButton("##R", active_side == WheelSide::RIGHT))
1810 selected_side = WheelSide::RIGHT;
1811
1812 // Draw rim mesh name
1813 ImGui::SameLine();
1814 ImGui::Text("%s", tuning_actor->GetGfxActor()->getWheelRimMeshName(i).c_str());
1815
1816 // Draw reset button
1817
1818 bool resetPressed = false;
1819 if (tuneup_def && tuneup_def->isWheelSideForced(i, forced_side))
1820 {
1821 ImGui::SameLine();
1822 ImGui::SameLine();
1823 ImGui::PushStyleColor(ImGuiCol_Text, GRAY_HINT_TEXT);
1824 resetPressed = ImGui::SmallButton(_LC("Tuning", "Reset"));
1825 ImGui::PopStyleColor(); //ImGuiCol_Text, GRAY_HINT_TEXT
1826 ImGui::PopStyleVar(); //ImGuiStyleVar_FrameBorderSize, 1.f
1827 ImGui::PopStyleColor(); //ImGuiCol_Border, ORANGE_TEXT
1828 }
1829
1830 // modify project if needed
1831 if (selected_side != active_side)
1832 {
1835 req->mpr_subject_id = i;
1836 req->mpr_value_int = (int)selected_side;
1839 }
1840 else if (resetPressed)
1841 {
1844 req->mpr_subject_id = i;
1847 }
1848
1850 i,
1851 tuneup_def && tuneup_def->isWheelProtected(i),
1854
1855 ImGui::PopID(); // i
1856 }
1857 }
1858
1859 // Draw flares
1860 size_t total_flares = tuning_actor->ar_flares.size();
1861 std::string flares_title = fmt::format(_LC("Tuning", "Flares ({})"), total_flares);
1862 if (ImGui::CollapsingHeader(flares_title.c_str()))
1863 {
1864 // Draw all flares (those removed by addonparts are also present as placeholders)
1865 for (FlareID_t flareid = 0; flareid < (int)tuning_actor->ar_flares.size(); flareid++)
1866 {
1867 ImGui::PushID(flareid);
1868 ImGui::AlignTextToFramePadding();
1869
1870 this->DrawTuningBoxedSubjectIdInline(flareid);
1871
1872 // Compose flare description string
1873 const FlareType flaretype = tuning_actor->ar_flares[flareid].fl_type;
1874 std::string flarename;
1875 if (flaretype == FlareType::USER)
1876 {
1877 int controlnumber = tuning_actor->ar_flares[flareid].controlnumber + 1; // Convert range 0-9 to 1-10
1878 flarename = fmt::format("{} {}", (char)flaretype, controlnumber);
1879 }
1880 else if (flaretype == FlareType::DASHBOARD)
1881 {
1882 std::string linkname = tuning_actor->ar_dashboard->getLinkNameForID((DashData)tuning_actor->ar_flares[flareid].dashboard_link);
1883 flarename = fmt::format("{} {}", (char)flaretype, linkname);
1884 }
1885 else
1886 {
1887 flarename = fmt::format("{}", (char)flaretype);
1888 }
1889
1891 flareid,
1892 flarename,
1893 tuneup_def && tuneup_def->isFlareUnwanted(flareid),
1894 tuneup_def && tuneup_def->isFlareForceRemoved(flareid),
1897
1899 flareid,
1900 tuneup_def && tuneup_def->isFlareProtected(flareid),
1903
1904 ImGui::PopID(); // flareid
1905 }
1906 }
1907
1908 // Draw exhausts
1909 size_t total_exhausts = tuning_actor->GetGfxActor()->getExhausts().size();
1910 std::string exhausts_title = fmt::format(_LC("Tuning", "Exhausts ({})"), total_exhausts);
1911 if (ImGui::CollapsingHeader(exhausts_title.c_str()))
1912 {
1913 // Draw all exhausts (those removed by addonparts are also present as placeholders)
1914 for (ExhaustID_t exhaustid = 0; exhaustid < (int)total_exhausts; exhaustid++)
1915 {
1916 ImGui::PushID(exhaustid);
1917 ImGui::AlignTextToFramePadding();
1918
1919 this->DrawTuningBoxedSubjectIdInline(exhaustid);
1920
1922 exhaustid,
1923 tuning_actor->GetGfxActor()->getExhausts()[exhaustid].particleSystemName,
1924 tuneup_def && tuneup_def->isExhaustUnwanted(exhaustid),
1925 tuneup_def && tuneup_def->isExhaustForceRemoved(exhaustid),
1928
1930 exhaustid,
1931 tuneup_def && tuneup_def->isExhaustProtected(exhaustid),
1934
1935 ImGui::PopID(); // exhaustid
1936 }
1937 }
1938
1939 // Draw managed materials
1940 size_t total_materials = tuning_actor->ar_managed_materials.size();
1941 std::string materials_title = fmt::format(_LC("Tuning", "Managed Materials ({})"), total_materials);
1942 if (ImGui::CollapsingHeader(materials_title.c_str()))
1943 {
1944 // Draw all materials (those removed by addonparts are also present as placeholders)
1945 for (auto mm_pair: tuning_actor->ar_managed_materials)
1946 {
1947 const std::string& material_name = mm_pair.first;
1948 ImGui::PushID(material_name.c_str());
1949 ImGui::AlignTextToFramePadding();
1950
1953 material_name,
1954 tuneup_def && tuneup_def->isManagedMatUnwanted(material_name),
1955 tuneup_def && tuneup_def->isManagedMatForceRemoved(material_name),
1958
1961 tuneup_def && tuneup_def->isManagedMatProtected(material_name),
1964 material_name);
1965
1966 ImGui::PopID(); // material_name.c_str()
1967 }
1968 }
1969
1970 // Draw videocameras
1971 size_t total_videocameras = tuning_actor->GetGfxActor()->getVideoCameras().size();
1972 std::string videocameras_title = fmt::format(_LC("Tuning", "Videocameras ({})"), total_videocameras);
1973 if (ImGui::CollapsingHeader(videocameras_title.c_str()))
1974 {
1975 // Draw all videocameras (those removed by addonparts are also present as placeholders)
1976 for (VideoCameraID_t videocameraid = 0; videocameraid < (int)total_videocameras; videocameraid++)
1977 {
1978 ImGui::PushID(videocameraid);
1979 ImGui::AlignTextToFramePadding();
1980
1981 this->DrawTuningBoxedSubjectIdInline(videocameraid);
1982
1983 const VideoCamera& vcam = tuning_actor->GetGfxActor()->getVideoCameras()[videocameraid];
1984
1985 // Draw RTT material name
1986 ImGui::SameLine();
1987 ImGui::Dummy(ImVec2(ImGui::GetFontSize() * 0.1875f, ImGui::GetFontSize() * 0.1875f));
1988 ImGui::SameLine();
1989 ImGui::Text("%s", tuning_actor->GetGfxActor()->getVideoCameras()[videocameraid].vcam_mat_name_orig.c_str());
1990
1991 // Setup the 'forced' state orange styling
1992
1993 VideoCamRole forced_role_def = VCAM_ROLE_INVALID;
1994 if (tuneup_def && tuneup_def->isVideoCameraRoleForced(videocameraid, /*[out]*/forced_role_def))
1995 {
1996 ImGui::PushStyleColor(ImGuiCol_Border, ORANGE_TEXT);
1997 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.f);
1998 }
1999
2000 // Draw the 'flipped' checkbox where applicable
2001 const bool is_mirror_flip = vcam.vcam_role == VCAM_ROLE_MIRROR || vcam.vcam_role == VCAM_ROLE_TRACKING_MIRROR;
2002 const bool is_mirror_noflip = vcam.vcam_role == VCAM_ROLE_MIRROR_NOFLIP || vcam.vcam_role == VCAM_ROLE_TRACKING_MIRROR_NOFLIP;
2003 bool checkbox_pressed = false;
2004 VideoCamRole desired_role = VCAM_ROLE_INVALID;
2005 if ((is_mirror_flip || is_mirror_noflip))
2006 {
2007 ImGui::SameLine();
2008 bool checkbox_checked = is_mirror_flip;
2009 checkbox_pressed = ImGui::Checkbox(_LC("Tuning", "Flipped"), &checkbox_checked);
2010 switch (vcam.vcam_role)
2011 {
2012 case VCAM_ROLE_MIRROR: desired_role = VCAM_ROLE_MIRROR_NOFLIP; break;
2013 case VCAM_ROLE_MIRROR_NOFLIP: desired_role = VCAM_ROLE_MIRROR; break;
2016 default: break;
2017 }
2018 }
2019 else
2020 {
2021 // Inform user why this videocamera isn't flippable
2022 ImGui::SameLine();
2023 switch (vcam.vcam_role)
2024 {
2025 case VCAM_ROLE_MIRROR_PROP_LEFT: ImGui::TextDisabled(_LC("Tuning", "(Mirror prop - Left)")); break;
2026 case VCAM_ROLE_MIRROR_PROP_RIGHT: ImGui::TextDisabled(_LC("Tuning", "(Mirror prop - Right)")); break;
2027 default: ImGui::TextDisabled(_LC("Tuning", "(Videocamera)")); break;
2028 }
2029 }
2030
2031 // Draw reset button and reset the orange styling
2032
2033 bool reset_pressed = false;
2034 if (tuneup_def && tuneup_def->isVideoCameraRoleForced(videocameraid, /*[out]*/forced_role_def))
2035 {
2036 ImGui::SameLine();
2037 ImGui::SameLine();
2038 ImGui::PushStyleColor(ImGuiCol_Text, GRAY_HINT_TEXT);
2039 reset_pressed = ImGui::SmallButton(_LC("Tuning", "Reset"));
2040 ImGui::PopStyleColor(); //ImGuiCol_Text, GRAY_HINT_TEXT
2041 ImGui::PopStyleVar(); //ImGuiStyleVar_FrameBorderSize, 1.f
2042 ImGui::PopStyleColor(); //ImGuiCol_Border, ORANGE_TEXT
2043 }
2044
2045 // modify project if needed
2046 if (checkbox_pressed)
2047 {
2050 req->mpr_subject_id = videocameraid;
2051 req->mpr_value_int = (int)desired_role;
2054 }
2055 else if (reset_pressed)
2056 {
2059 req->mpr_subject_id = videocameraid;
2062 }
2063
2064
2065 ImGui::PopID(); // videocameraid
2066 }
2067 }
2068 }
2069
2070 m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING * ImGui::GetFontSize();
2071 m_open_menu_hoverbox_max.x = menu_pos.x + ImGui::GetWindowWidth() + MENU_HOVERBOX_PADDING.x * ImGui::GetFontSize();
2072 m_open_menu_hoverbox_max.y = menu_pos.y + ImGui::GetWindowHeight() + MENU_HOVERBOX_PADDING.y * ImGui::GetFontSize();
2073 App::GetGuiManager()->RequestGuiCaptureKeyboard(ImGui::IsWindowHovered());
2074 ImGui::End();
2075 }
2076 break;
2077
2078 default:
2079 m_open_menu_hoverbox_min = ImVec2(0,0);
2080 m_open_menu_hoverbox_max = ImVec2(0,0);
2081 }
2082
2083 ImGui::PopStyleColor(2); // WindowBg, Button
2084}
2085
2086bool TopMenubar::ShouldDisplay(ImVec2 window_pos)
2087{
2088 if (!App::GetGuiManager()->AreStaticMenusAllowed())
2089 {
2090 return false;
2091 }
2092
2093 if (ImGui::IsMouseDown(1))
2094 {
2095 return false;
2096 }
2097
2098 if (ai_menu)
2099 {
2101 return true;
2102 }
2103
2104 ImVec2 box_min(0,0);
2105 ImVec2 box_max(ImGui::GetIO().DisplaySize.x, ImGui::GetStyle().WindowPadding.y + PANEL_HOVERBOX_HEIGHT * ImGui::GetFontSize());
2106 ImVec2 mouse_pos = ImGui::GetIO().MousePos;
2107 const bool window_hovered ((mouse_pos.x >= box_min.x) && (mouse_pos.x <= box_max.x) &&
2108 (mouse_pos.y >= box_min.y) && (mouse_pos.y <= box_max.y));
2109 bool result = window_hovered;
2110
2111 bool menu_hovered = false;
2113 {
2114 menu_hovered = ((mouse_pos.x >= m_open_menu_hoverbox_min.x) && (mouse_pos.x <= m_open_menu_hoverbox_max.x) &&
2115 (mouse_pos.y >= m_open_menu_hoverbox_min.y) && (mouse_pos.y <= m_open_menu_hoverbox_max.y));
2116 }
2117 result |= menu_hovered;
2118
2119 bool box_hovered = false;
2121 {
2122 box_hovered = ((mouse_pos.x >= m_state_box_hoverbox_min.x) && (mouse_pos.x <= m_state_box_hoverbox_max.x) &&
2123 (mouse_pos.y >= m_state_box_hoverbox_min.y) && (mouse_pos.y <= m_state_box_hoverbox_max.y));
2124 result |= box_hovered;
2125 }
2126
2127 if (box_hovered && !menu_hovered)
2128 {
2130 }
2131
2132 return result;
2133}
2134
2136{
2137 // Count actors owned by the player
2138 unsigned int num_actors_player = 0;
2140 {
2141 if (actor->ar_net_source_id == user.uniqueid)
2142 {
2143 ++num_actors_player;
2144 }
2145 }
2146
2147 // Display user in list
2148#ifdef USE_SOCKETW
2149 const Ogre::ColourValue player_color = App::GetNetwork()->GetPlayerColor(user.colournum);
2150 ImVec4 player_gui_color(player_color.r, player_color.g, player_color.b, 1.f);
2151 ImGui::PushStyleColor(ImGuiCol_Text, player_gui_color);
2152 ImGui::Text("%s: %u (%s, Ver: %s, Lang: %s)",
2153 user.username, num_actors_player,
2154 App::GetNetwork()->UserAuthToStringShort(user).c_str(),
2155 user.clientversion, user.language);
2156 ImGui::PopStyleColor();
2157#endif // USE_SOCKETW
2158
2159 // Display actor list
2160 Ogre::TexturePtr tex1 = FetchIcon("control_pause.png");
2161 Ogre::TexturePtr tex2 = FetchIcon("control_play.png");
2162 int i = 0;
2164 {
2165 if ((!actor->ar_hide_in_actor_list) && (actor->ar_net_source_id == user.uniqueid))
2166 {
2167 std::string id = fmt::format("{}:{}", i++, user.uniqueid);
2168 ImGui::PushID(id.c_str());
2169 if (actor->ar_state == ActorState::NETWORKED_OK)
2170 {
2171 if (ImGui::ImageButton(reinterpret_cast<ImTextureID>(tex1->getHandle()), ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize())))
2172 {
2174 }
2175 }
2176 else if (actor->ar_state == ActorState::NETWORKED_HIDDEN)
2177 {
2178 if (ImGui::ImageButton(reinterpret_cast<ImTextureID>(tex2->getHandle()), ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize())))
2179 {
2181 }
2182 }
2183 else // Our actor(s)
2184 {
2185 std::string text_buf_rem = fmt::format(" X ##[{}]", i);
2186 ImGui::PushStyleColor(ImGuiCol_Text, RED_TEXT);
2187 if (ImGui::Button(text_buf_rem.c_str()))
2188 {
2190 }
2191 ImGui::PopStyleColor();
2192 }
2193 ImGui::PopID();
2194 ImGui::SameLine();
2195
2196 std::string actortext_buf = fmt::format("{} ({}) ##[{}:{}]", StripColorMarksFromText(actor->ar_design_name).c_str(), actor->ar_filename.c_str(), i++, user.uniqueid);
2197 if (ImGui::Button(actortext_buf.c_str())) // Button clicked?
2198 {
2200 }
2201 }
2202 }
2203}
2204
2206{
2207 std::vector<ActorPtr> actor_list;
2209 {
2210 if (!actor->ar_hide_in_actor_list)
2211 {
2212 actor_list.emplace_back(actor);
2213 }
2214 }
2215 if (actor_list.empty())
2216 {
2217 ImGui::PushStyleColor(ImGuiCol_Text, GRAY_HINT_TEXT);
2218 ImGui::Text("%s", _LC("TopMenubar", "None spawned yet"));
2219 ImGui::Text("%s", _LC("TopMenubar", "Use [Simulation] menu"));
2220 ImGui::PopStyleColor();
2221 }
2222 else
2223 {
2224 ActorPtr player_actor = App::GetGameContext()->GetPlayerActor();
2225 int i = 0;
2226 for (ActorPtr& actor : actor_list)
2227 {
2228 std::string text_buf_rem = fmt::format("X ##[{}]", i);
2229 ImGui::PushStyleColor(ImGuiCol_Text, RED_TEXT);
2230 if (ImGui::Button(text_buf_rem.c_str()))
2231 {
2233 }
2234 ImGui::PopStyleColor();
2235 ImGui::SameLine();
2236
2237 std::string text_buf = fmt::format( "[{}] {}", i++, StripColorMarksFromText(actor->ar_design_name).c_str());
2238 if (actor == player_actor)
2239 {
2240 ImGui::PushStyleColor(ImGuiCol_Text, GREEN_TEXT);
2241 }
2242 else if (std::find(actor->ar_linked_actors.begin(), actor->ar_linked_actors.end(), player_actor) != actor->ar_linked_actors.end())
2243 {
2244 ImGui::PushStyleColor(ImGuiCol_Text, ORANGE_TEXT);
2245 }
2246 else if (actor->ar_state == ActorState::LOCAL_SIMULATED)
2247 {
2248 ImGui::PushStyleColor(ImGuiCol_Text, WHITE_TEXT);
2249 }
2250 else
2251 {
2252 ImGui::PushStyleColor(ImGuiCol_Text, GRAY_HINT_TEXT);
2253 }
2254 if (ImGui::Button(text_buf.c_str())) // Button clicked?
2255 {
2257 }
2258 ImGui::PopStyleColor();
2259 }
2260 }
2261}
2262
2263void DrawRepairBoxEvent(events ev, std::string const& desc)
2264{
2265 ImDrawEventHighlighted(ev); ImGui::SameLine(); ImGui::TextDisabled(desc.c_str()); ImGui::NextColumn();
2266}
2267
2268void DrawRepairBoxModkey(OIS::KeyCode modkey, std::string const& desc)
2269{
2270 ImDrawModifierKeyHighlighted(modkey); ImGui::SameLine(); ImGui::TextDisabled(desc.c_str()); ImGui::NextColumn();
2271}
2272
2274{
2275 float content_width = 0.f;
2276 // Always drawn on top:
2277 std::string special_text;
2278 ImVec4 special_color = ImGui::GetStyle().Colors[ImGuiCol_Text]; // Regular color
2279 float special_text_centering_weight = 1.f; // 0 = no centering
2280 // Only for race_box:
2281 std::string special_text_b;
2282 std::string special_text_c;
2283 std::string special_text_d;
2284 ImVec4 special_color_c = ImVec4(0,0,0,0);
2286
2287 // Gather state info
2288 if (App::GetGameContext()->GetActorManager()->IsSimulationPaused() && !App::GetGuiManager()->IsGuiHidden())
2289 {
2290 special_color = ORANGE_TEXT;
2291 special_text = fmt::format(_LC("TopMenubar", "All physics paused, press {} to resume"),
2292 App::GetInputEngine()->getEventCommandTrimmed(EV_COMMON_TOGGLE_PHYSICS));
2293 content_width = ImGui::CalcTextSize(special_text.c_str()).x;
2294 }
2295 else if (App::GetGameContext()->GetPlayerActor() &&
2296 App::GetGameContext()->GetPlayerActor()->ar_physics_paused &&
2297 !App::GetGuiManager()->IsGuiHidden())
2298 {
2299 special_color = GREEN_TEXT;
2300 special_text = fmt::format(_LC("TopMenubar", "Vehicle physics paused, press {} to resume"),
2301 App::GetInputEngine()->getEventCommandTrimmed(EV_TRUCK_TOGGLE_PHYSICS));
2302 content_width = ImGui::CalcTextSize(special_text.c_str()).x;
2303 }
2304 else if (App::GetGameContext()->GetPlayerActor() &&
2305 App::GetGameContext()->GetPlayerActor()->ar_state == ActorState::LOCAL_REPLAY)
2306 {
2307 content_width = ImGui::GetFontSize() * 18.75f;
2309 special_text = _LC("TopMenubar", "Replay");
2310 }
2311 else if (App::GetGameContext()->GetRepairMode().IsLiveRepairActive())
2312 {
2313 special_text = fmt::format(_LC("TopMenubar", "Live repair mode, hit '{}' to stop"),
2314 App::GetInputEngine()->getEventCommandTrimmed(EV_COMMON_REPAIR_TRUCK));
2315 content_width = ImGui::GetFontSize() * 28.125f;
2317 special_color = GREEN_TEXT;
2318 special_text_centering_weight = 0.7f;
2319 }
2320 else if (App::GetGameContext()->GetRepairMode().IsQuickRepairActive())
2321 {
2322 special_text = fmt::format(_LC("TopMenubar", "Quick repair ('{}' for Live repair)"),
2323 App::GetInputEngine()->getEventCommandTrimmed(EV_COMMON_LIVE_REPAIR_MODE));
2324 content_width = ImGui::GetFontSize() * 28.125f;
2326 special_color = ORANGE_TEXT;
2327 special_text_centering_weight = 0.7f;
2328 }
2329 else if (App::GetGfxScene()->GetSimDataBuffer().simbuf_dir_arrow_visible)
2330 {
2332
2333 // Calculate distance
2336 float distance = 0.0f;
2338 if (player_actor != nullptr && App::GetGameContext()->GetPlayerActor() &&
2340 {
2341 distance = player_actor->GetGfxActor()->GetSimDataBuffer().simbuf_pos.distance(data.simbuf_dir_arrow_target);
2342 }
2343 else
2344 {
2345 distance = data.simbuf_character_pos.distance(data.simbuf_dir_arrow_target);
2346 }
2347
2348 // format text
2350 special_text_b = fmt::format("{:.1f} {}", distance, _LC("DirectionArrow", "meter"));
2351 content_width = ImGui::CalcTextSize(special_text.c_str()).x + ImGui::CalcTextSize(special_text_b.c_str()).x;
2352
2354 special_text_c = fmt::format("{:02d}.{:02d}.{:02d}", (int)(time) / 60, (int)(time) % 60, (int)(time * 100.0) % 100);
2356 special_color_c = (time_diff > 0.0f)
2357 ? theme.value_red_text_color
2358 : ((time_diff < 0.0f) ? theme.success_text_color : theme.value_blue_text_color);
2359
2361 {
2363 special_text_d = fmt::format("{:02d}.{:02d}.{:02d}", (int)(best_time) / 60, (int)(best_time) % 60, (int)(best_time * 100.0) % 100);
2364 }
2365 }
2366 else if (App::sim_state->getEnum<SimState>() == SimState::EDITOR_MODE)
2367 {
2368 special_color = GREEN_TEXT;
2369 special_text = fmt::format(_LC("TopMenubar", "Terrain editing mode, press {} to exit"),
2370 App::GetInputEngine()->getEventCommandTrimmed(EV_COMMON_TOGGLE_TERRAIN_EDITOR));
2371
2372 if (App::GetGameContext()->GetTerrain()->getCacheEntry()->resource_bundle_type == "Zip")
2373 {
2374 // This is a read-only (ZIPped) terrain; offer the importer script.
2375 content_width = ImGui::CalcTextSize(special_text.c_str()).x + ImGui::GetFontSize() * 1.5625f;
2377 }
2378 else
2379 {
2380 special_text = fmt::format(_LC("TopMenubar", "Terrain editing mode, press {} to exit"),
2381 App::GetInputEngine()->getEventCommandTrimmed(EV_COMMON_TOGGLE_TERRAIN_EDITOR));
2382 content_width = ImGui::CalcTextSize(special_text.c_str()).x;
2384 }
2385 }
2386
2387 // Draw box if needed
2388 if (!special_text.empty())
2389 {
2390 ImVec2 box_pos;
2391 box_pos.y = top_offset;
2392 box_pos.x = (ImGui::GetIO().DisplaySize.x / 2) - ((content_width / 2) + ImGui::GetStyle().FramePadding.x);
2393 ImGui::SetNextWindowPos(box_pos);
2394 ImGui::SetNextWindowSize(ImVec2(0.f, 0.f));
2395 ImGui::SetNextWindowContentWidth(content_width);
2396 ImGuiWindowFlags flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
2397 ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse;
2398 ImGui::PushStyleColor(ImGuiCol_WindowBg, App::GetGuiManager()->GetTheme().semitransparent_window_bg);
2399 if (ImGui::Begin(special_text.c_str(), nullptr, flags))
2400 {
2402 {
2403 // Center the text, the box may be wider
2404 float text_w = ImGui::CalcTextSize(special_text.c_str()).x;
2405 ImGui::SetCursorPosX(((content_width / 2) - (text_w / 2)) * special_text_centering_weight);
2406 }
2407 ImGui::TextColored(special_color, "%s", special_text.c_str());
2408
2410 {
2411 ImGui::SameLine();
2412
2413 // Progress bar with frame index/count
2415 float fraction = (float)std::abs(replay->getCurrentFrame())/(float)replay->getNumFrames();
2416 Str<100> pbar_text; pbar_text << replay->getCurrentFrame() << "/" << replay->getNumFrames();
2417 float pbar_width = content_width - (ImGui::GetStyle().ItemSpacing.x + ImGui::CalcTextSize(special_text.c_str()).x);
2418 ImGui::ProgressBar(fraction, ImVec2(pbar_width, ImGui::GetTextLineHeight()), pbar_text.ToCStr());
2419
2420 // Game time text
2421 float time_sec = replay->getLastReadTime() / 1000000.0;
2422 char str[200];
2423 int str_pos = 0;
2424 if (time_sec > 60)
2425 {
2426 int min = (int)time_sec / 60;
2427 str_pos = snprintf(str, 200, "%dmin ", min);
2428 time_sec -= (float)min * 60.f;
2429 }
2430 snprintf(str+str_pos, 200-str_pos, "%.2fsec", time_sec);
2431 ImGui::TextDisabled("%s: %s", _LC("TopMenubar", "Time"), str);
2432
2433 }
2435 {
2436 ImGui::SameLine();
2437 ImGui::Text(special_text_b.c_str());
2438 ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2) - (ImGui::CalcTextSize(special_text_c.c_str()).x / 2));
2439 ImGui::TextColored(special_color_c,"%s", special_text_c.c_str());
2440
2441 Str<300> text;
2442 text << "Best Time: " << special_text_d.c_str();
2443 ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2) - (ImGui::CalcTextSize(text).x / 2));
2444
2445 if (!special_text_d.empty())
2446 {
2447 ImGui::TextDisabled(text);
2448 }
2449 }
2451 {
2452 // Draw special element on the right
2453 ImGui::SameLine();
2454 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetFontSize() * 0.125f, 0.f));
2456 {
2458 ImGui::ProgressBar(fraction, ImVec2(ImGui::GetFontSize() * 0.9375f, ImGui::GetTextLineHeight() / 2.f), "");
2459 ImGui::SameLine();
2460 }
2461 DrawGCheckbox(App::ui_show_live_repair_controls, _LC("LiveRepair", "Show controls"));
2462 ImGui::PopStyleVar(); // FramePadding
2463
2464 const ImVec2 MINI_SPACING = ImVec2(ImGui::GetFontSize() * 0.125f, 0.f);
2465 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, MINI_SPACING);
2466
2467 if (App::ui_show_live_repair_controls->getBool())
2468 {
2469 const float INDENT = ImGui::GetFontSize() * 0.9375f;
2470 ImGui::Separator();
2471 ImGui::TextDisabled("%s:", _LC("LiveRepair", "Movement"));
2472 ImGui::Columns(3);
2473 ImGui::SetColumnWidth(0, INDENT);
2474 ImGui::NextColumn();
2475 DrawRepairBoxEvent(EV_CHARACTER_FORWARD,_LC("LiveRepair", "Forward"));
2476 DrawRepairBoxEvent(EV_CHARACTER_BACKWARDS,_LC("LiveRepair", "Backward"));
2477 ImGui::NextColumn();
2478 DrawRepairBoxEvent(EV_CHARACTER_SIDESTEP_LEFT,_LC("LiveRepair", "Left"));
2479 DrawRepairBoxEvent(EV_CHARACTER_SIDESTEP_RIGHT,_LC("LiveRepair", "Right"));
2480 ImGui::NextColumn();
2481 DrawRepairBoxEvent(EV_TRUCK_ACCELERATE,_LC("LiveRepair", "Up"));
2482 DrawRepairBoxEvent(EV_TRUCK_BRAKE,_LC("LiveRepair", "Down"));
2483 ImGui::Columns(1);
2484
2485 ImGui::TextDisabled("%s:", _LC("LiveRepair", "Rotation"));
2486 ImGui::Columns(3);
2487 ImGui::SetColumnWidth(0, INDENT);
2488 ImGui::NextColumn();
2489 DrawRepairBoxEvent(EV_TRUCK_STEER_LEFT,_LC("LiveRepair", "Rot. left"));
2490 DrawRepairBoxEvent(EV_TRUCK_STEER_RIGHT,_LC("LiveRepair", "Rot. right"));
2491 ImGui::Columns(1);
2492
2493 ImGui::TextDisabled("%s:", _LC("LiveRepair", "Modifiers"));
2494 ImGui::Columns(4);
2495 ImGui::SetColumnWidth(0, INDENT);
2496 ImGui::SetColumnWidth(1, ImGui::GetFontSize() * 7.8125f);
2497 ImGui::SetColumnWidth(2, ImGui::GetFontSize() * 7.8125f);
2498 ImGui::NextColumn();
2499 DrawRepairBoxModkey(OIS::KC_LMENU,_LC("LiveRepair", "Slow step")); // Left alt
2500 DrawRepairBoxModkey(OIS::KC_LSHIFT,_LC("LiveRepair", "Fast step"));
2501 DrawRepairBoxModkey(OIS::KC_LCONTROL,_LC("LiveRepair", "10x step")); // Left ctrl
2502 ImGui::Columns(1);
2503
2505 ImGui::TextDisabled("%s (%s):", _LC("LiveRepair", "Reset mode"), ToLocalizedString(resetmode).c_str());
2506 ImGui::Dummy(ImVec2(INDENT, 1.f));
2507 ImGui::SameLine();
2508 DrawRepairBoxEvent(EV_COMMON_TOGGLE_RESET_MODE,_LC("LiveRepair", "Switch reset mode"));
2509 }
2510 ImGui::PopStyleVar(); // ItemSpacing
2511 }
2513 {
2514 ImGui::Separator();
2515 // notice text
2516 std::string lbl_readonly = _LC("TopMenubar", "This terrain is read only.");
2517 ImGui::SetCursorPosX(ImGui::GetCursorPosX()
2518 + (ImGui::GetWindowContentRegionWidth() / 2 - ImGui::CalcTextSize(lbl_readonly.c_str()).x/2));
2519 ImGui::TextDisabled("%s", lbl_readonly.c_str());
2520 // import button
2521 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2, 0.2, 0.2, 1.0));
2522 std::string btn_import = _LC("TopMenubar", "Import as editable project.");
2523 ImGui::SetCursorPosX(ImGui::GetCursorPosX()
2524 + ((ImGui::GetWindowContentRegionWidth() / 2 - ImGui::CalcTextSize(btn_import.c_str()).x / 2) - ImGui::GetStyle().FramePadding.x));
2525 if (!m_terrn_import_started && ImGui::Button(btn_import.c_str()))
2526 {
2528 rq->lsr_filename = "terrain_project_importer.as";
2532 }
2533 ImGui::PopStyleColor(); // ImGuiCol_Button
2534 }
2536 {
2537 // Info text
2538 std::string lbl_usemenu = _LC("TopMenubar", "Use 'Simulation' menu to save changes.");
2539 ImGui::SetCursorPosX(ImGui::GetCursorPosX()
2540 + (ImGui::GetWindowContentRegionWidth() / 2 - ImGui::CalcTextSize(lbl_usemenu.c_str()).x / 2));
2541 ImGui::TextDisabled("%s", lbl_usemenu.c_str());
2542 }
2543 const ImVec2 PAD = ImVec2(ImGui::GetFontSize() * 0.3125f, ImGui::GetFontSize() * 0.3125f); // To bridge top menubar hoverbox and statebox hoverbox
2544 m_state_box_hoverbox_min = box_pos - PAD;
2545 m_state_box_hoverbox_max.x = box_pos.x + ImGui::GetWindowWidth();
2546 m_state_box_hoverbox_max.y = box_pos.y + ImGui::GetWindowHeight();
2548 // DO NOT `RequestGuiCaptureKeyboard()` - we want to use the hotkeys through it.
2549 ImGui::End();
2550 }
2551 ImGui::PopStyleColor(1); // WindowBg
2552 }
2553
2554 if (App::sim_state->getEnum<SimState>() != SimState::EDITOR_MODE)
2555 {
2556 m_terrn_import_started = false;
2557 }
2558}
2559
2561{
2562 // Load 'bundled' AI presets - see section `[AI Presets]` in terrn2 file format
2563 // ----------------------------------------------------------------------------
2564
2566
2567 for (const std::string& filename: terrain->GetDef()->ai_presets_files)
2568 {
2569 rapidjson::Document j_doc;
2570 if (Ogre::ResourceGroupManager::getSingleton().resourceExists(terrain->getTerrainFileResourceGroup(), filename))
2571 {
2573 }
2574 else
2575 {
2576 LOG(fmt::format("[RoR|Terrain] AI presets file '{}' declared in '{}' not found!", filename, terrain->getTerrainFileName()));
2577 }
2578
2579 // Ensure the format is about right
2580 if (!j_doc.IsArray())
2581 {
2582 LOG(fmt::format("[RoR|Terrain] AI presets file '{}' declared in '{}' has wrong format - the root element is not an array!",
2583 filename, terrain->getTerrainFileName()));
2584 }
2585 else
2586 {
2587 // Finally add the presets to the list
2588 for (const rapidjson::Value& j_bundled_preset: j_doc.GetArray())
2589 {
2590 rapidjson::Value preset_copy(j_bundled_preset, App::GetGuiManager()->TopMenubar.ai_presets_bundled.GetAllocator());
2592 }
2593 }
2594 }
2595
2597}
2598
2600{
2601#if defined(USE_CURL)
2602 std::packaged_task<void()> task(FetchAiPresetsThreadFunc);
2603 std::thread(std::move(task)).detach();
2605#endif // defined(USE_CURL)
2606}
2607
2609{
2610 // Combine external and bundled presets into one JSON doc
2611 // -------------------------------------------------------
2612
2613 ai_presets_all.Clear();
2614 ai_presets_all.SetArray();
2615
2616 for (rapidjson::Value& bundled_preset: ai_presets_bundled.GetArray())
2617 {
2618 rapidjson::Value preset_copy(bundled_preset, ai_presets_all.GetAllocator());
2619 ai_presets_all.PushBack(preset_copy, ai_presets_all.GetAllocator());
2620 }
2621
2622 for (const rapidjson::Value& extern_preset: ai_presets_extern.GetArray())
2623 {
2624 rapidjson::Value preset_copy(extern_preset, ai_presets_all.GetAllocator());
2625 ai_presets_all.PushBack(preset_copy, ai_presets_all.GetAllocator());
2626 }
2627}
2628
2630{
2631 // Updates/resets the tuning menu for the current vehicle driven by player (if any).
2632 // -------------------------------------------------------------------------------
2633
2634 if (App::sim_tuning_enabled->getBool()
2635 && (App::mp_state->getEnum<MpState>() != MpState::CONNECTED)
2636 && App::GetGameContext()->GetPlayerActor()
2637 && (tuning_actor != App::GetGameContext()->GetPlayerActor()))
2638 {
2641
2642 tuning_addonparts.clear();
2644
2645 // Addonparts matched by GUID
2646 if (tuning_actor->getUsedActorEntry()->guid != "")
2647 {
2648 CacheQuery query_addonparts;
2649 query_addonparts.cqy_filter_type = LT_AddonPart;
2650 query_addonparts.cqy_filter_guid = tuning_actor->getUsedActorEntry()->guid;
2651 query_addonparts.cqy_filter_target_filename = tuning_actor->getTruckFileName(); // Addonparts without any filenames listed will just pass.
2652 App::GetCacheSystem()->Query(query_addonparts);
2653 for (CacheQueryResult& res: query_addonparts.cqy_results)
2654 {
2655 tuning_addonparts.push_back(res.cqr_entry);
2656 }
2657 }
2658
2659 // Addonparts force-installed via [browse all] button; watch for duplicates.
2661 {
2662 for (std::string const& use_addonpart_fname: tuning_actor->getWorkingTuneupDef()->use_addonparts)
2663 {
2664 CacheEntryPtr entry = App::GetCacheSystem()->FindEntryByFilename(LT_AddonPart, /*partial:*/false, use_addonpart_fname);
2665 if (entry)
2666 {
2667 if (std::find(tuning_addonparts.begin(), tuning_addonparts.end(), entry) == tuning_addonparts.end())
2668 {
2669 tuning_addonparts.push_back(entry);
2670 }
2671 }
2672 }
2673 }
2674
2678 tuning_saves.cqy_filter_category_id = CID_Tuneups; // Exclude auto-generated entries
2681
2682 // Refresh `tuning_conflicts` database ~ test eligible addonparts each with each once.
2683 tuning_conflicts.clear();
2684 for (size_t i1 = 0; i1 < tuning_addonparts.size(); i1++)
2685 {
2686 for (size_t i2 = i1; i2 < tuning_addonparts.size(); i2++)
2687 {
2688 if (i1 != i2)
2689 {
2691 }
2692 }
2693 }
2694
2695 // Refresh `tuning_addonparts_conflicting` listing ~ test used addonparts against unused.
2699 {
2700 for (const std::string& use_addonpart_fname: tuning_actor->getWorkingTuneupDef()->use_addonparts)
2701 {
2702 CacheEntryPtr use_addonpart_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AddonPart, /*partial:*/false, use_addonpart_fname);
2703 for (size_t i = 0; i < tuning_addonparts.size(); i++)
2704 {
2705 if (tuning_addonparts[i] != use_addonpart_entry)
2706 {
2709 }
2710 }
2711 }
2712 }
2713
2715 }
2716 else if (!App::sim_tuning_enabled->getBool() || !App::GetGameContext()->GetPlayerActor())
2717 {
2718 tuning_addonparts.clear();
2720 tuning_actor = nullptr;
2721 }
2722}
2723
2724void TopMenubar::DrawTuningProtectedChkRightAligned(const int subject_id, bool protectchk_value, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset, const std::string& subject /* ="" */)
2725{
2726 // > resolve the alignment
2727 ImGui::SameLine();
2728 if (tuning_rwidget_cursorx_min < ImGui::GetCursorPosX()) // Make sure button won't draw over item name
2729 tuning_rwidget_cursorx_min = ImGui::GetCursorPosX();
2730 std::string protectchk_text = _LC("Tuning", "Protected");
2731 float protectchk_w = ImGui::CalcTextSize(protectchk_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
2732 float protectchk_cursorx = (ImGui::GetWindowContentRegionWidth() - protectchk_w) - ImGui::GetFontSize() * 1.25f;
2733 if (protectchk_cursorx < tuning_rwidget_cursorx_min)
2734 protectchk_cursorx = tuning_rwidget_cursorx_min;
2735 ImGui::SetCursorPosX(protectchk_cursorx);
2736
2737 // > set styling and draw
2738 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
2739 bool chk_pressed = ImGui::Checkbox(protectchk_text.c_str(), &protectchk_value);
2740 ImGui::PopStyleVar(1); // ImGuiStyleVar_FramePadding
2741
2742 // > handle user action
2743 if (chk_pressed)
2744 {
2746 request->mpr_target_actor = tuning_actor;
2747 if (subject_id == TUNING_SUBJECTID_USE_NAME)
2748 {
2749 request->mpr_subject = subject;
2750 }
2751 else
2752 {
2753 request->mpr_subject_id = subject_id;
2754 }
2755 request->mpr_type = (protectchk_value) ? request_type_set : request_type_reset;
2757 }
2758}
2759
2761{
2762 // Draw subject ID in outlined box
2763 // -------------------------------
2764 ImGui::GetWindowDrawList()->AddRect(
2765 ImGui::GetCursorScreenPos(),
2766 ImGui::GetCursorScreenPos() + ImGui::CalcTextSize("00") + ImGui::GetStyle().FramePadding*2,
2767 ImColor(ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]),
2768 ImGui::GetStyle().FrameRounding);
2769 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetStyle().FramePadding.x);
2770 ImGui::Text("%02d", subject_id);
2771 ImGui::SameLine();
2772 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetStyle().FramePadding.x);
2773}
2774
2775void TopMenubar::DrawTuningForceRemoveControls(const int subject_id, const std::string& name, const bool is_unwanted, const bool is_force_removed, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset)
2776{
2777 // Common for props and flexbodies: draws the force-remove checkbox and the reset button
2778 // ------------------------------------------------------------------------------------
2779
2780 // Draw the checkbox for force-removing.
2781 bool isEnabled = !is_unwanted && !is_force_removed;
2782 if (is_force_removed)
2783 {
2784 ImGui::PushStyleColor(ImGuiCol_Border, ORANGE_TEXT);
2785 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.f);
2786 }
2787 bool chkPressed = ImGui::Checkbox(name.c_str(), &isEnabled);
2788 bool resetPressed = false;
2789 if (is_force_removed)
2790 {
2791 ImGui::SameLine();
2792 ImGui::PushStyleColor(ImGuiCol_Text, GRAY_HINT_TEXT);
2793 resetPressed = ImGui::SmallButton(_LC("Tuning", "Reset"));
2794 ImGui::PopStyleColor(); //ImGuiCol_Text, GRAY_HINT_TEXT
2795 ImGui::PopStyleVar(); //ImGuiStyleVar_FrameBorderSize, 1.f
2796 ImGui::PopStyleColor(); //ImGuiCol_Border, ORANGE_TEXT
2797 }
2798
2799 // perform project modification if needed
2800 if (chkPressed && !isEnabled)
2801 {
2803 req->mpr_type = request_type_set;
2804 if (subject_id == TUNING_SUBJECTID_USE_NAME)
2805 {
2806 req->mpr_subject = name;
2807 }
2808 else
2809 {
2810 req->mpr_subject_id = subject_id;
2811 }
2814 }
2815 else if ((chkPressed && isEnabled) || resetPressed)
2816 {
2818 req->mpr_type = request_type_reset;
2819 if (subject_id == TUNING_SUBJECTID_USE_NAME)
2820 {
2821 req->mpr_subject = name;
2822 }
2823 else
2824 {
2825 req->mpr_subject_id = subject_id;
2826 }
2829 }
2830
2831}
2832
2834{
2835 switch (which)
2836 {
2841 default:
2842 return true;
2843 }
2844}
Central state/object manager and communications hub.
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
void LOG(const char *msg)
Legacy alias - formerly a macro.
#define RGN_SAVEGAMES
Definition Application.h:51
static size_t CurlWriteFunc(void *ptr, size_t size, size_t nmemb, std::string *data)
void DrawRepairBoxModkey(OIS::KeyCode modkey, std::string const &desc)
void FetchAiPresetsThreadFunc()
void DrawRepairBoxEvent(events ev, std::string const &desc)
Game state manager and message-queue provider.
Handles controller inputs from player.
#define _LC(ctx, str)
Definition Language.h:38
Platform-specific utilities. We use narrow UTF-8 encoded strings as paths. Inspired by http://utf8eve...
The vehicle tuning system; applies addonparts and user overrides to vehicles.
CacheEntryPtr & getUsedSkinEntry()
Definition Actor.cpp:5030
DashBoardManagerPtr ar_dashboard
Gfx state; Dashboard manager (HUD element manager)
Definition Actor.h:563
GfxActor * GetGfxActor()
Definition Actor.h:344
float getRotation()
Definition Actor.cpp:355
Replay * getReplay()
Definition Actor.cpp:4815
float getMinHeight(bool skip_virtual_nodes=true)
Definition Actor.cpp:1568
Ogre::Vector3 getPosition()
Definition Actor.cpp:370
std::vector< flare_t > ar_flares
Definition Actor.h:402
ActorState ar_state
Definition Actor.h:567
std::string getTruckFileName()
Definition Actor.h:302
std::map< std::string, Ogre::MaterialPtr > ar_managed_materials
Definition Actor.h:414
ActorInstanceID_t ar_instance_id
Static attr; session-unique ID.
Definition Actor.h:464
Ogre::String getSectionConfig()
Definition Actor.h:305
int ar_num_wheels
Definition Actor.h:420
CacheEntryPtr & getUsedActorEntry()
The actor entry itself.
Definition Actor.cpp:5025
TuneupDefPtr & getWorkingTuneupDef()
Definition Actor.cpp:5045
ActorPtrVec & GetActors()
bool LoadScene(Ogre::String filename)
Definition Savegame.cpp:240
void SetSimulationSpeed(float speed)
bool SaveScene(Ogre::String filename)
Definition Savegame.cpp:418
void SetTrucksForcedAwake(bool forced)
bool AreTrucksForcedAwake() const
std::vector< ActorPtr > GetLocalActors()
static bool CheckForAddonpartConflict(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec &conflicts)
static void RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec &conflicts)
T getEnum() const
Definition CVar.h:99
float getFloat() const
Definition CVar.h:96
std::string const & getStr() const
Definition CVar.h:95
bool getBool() const
Definition CVar.h:98
void setVal(T val)
Definition CVar.h:72
int getInt() const
Definition CVar.h:97
Ogre::String fname
filename
Definition CacheSystem.h:67
Ogre::String dname
name parsed from the file
Definition CacheSystem.h:70
Ogre::String guid
global unique id; Type "addonpart" leaves this empty and uses addonpart_guids; Always lowercase.
Definition CacheSystem.h:77
CacheEntryPtr FindEntryByFilename(RoR::LoaderType type, bool partial, const std::string &_filename_maybe_bundlequalified)
Returns NULL if none found; "Bundle-qualified" format also specifies the ZIP/directory in modcache,...
size_t Query(CacheQuery &query)
Ogre::Vector3 getPosition()
Definition Character.cpp:92
@ CONSOLE_MSGTYPE_INFO
Generic message.
Definition Console.h:60
void putMessage(MessageArea area, MessageType type, std::string const &msg, std::string icon="")
Definition Console.cpp:103
@ CONSOLE_SYSTEM_NOTICE
Definition Console.h:51
bool LoadAndParseJson(std::string const &filename, std::string const &rg_name, rapidjson::Document &j_doc)
std::string getLinkNameForID(DashData id)
Flexbody = A deformable mesh; updated on CPU every frame, then uploaded to video memory.
Definition FlexBody.h:44
void SetVisible(bool visible)
void SetVisible(bool value)
void SetVisible(bool visible)
const ImVec4 WHITE_TEXT
std::vector< std::string > m_savegame_names
CacheQuery tuning_saves
Tuneups saved by user, with category ID RoR::CID_AddonpartUser
std::vector< bool > tuning_addonparts_conflict_w_used
1:1 with tuning_addonparts; True means the eligible (not used) addonpart conflicts with an used addon...
void DrawTuningProtectedChkRightAligned(const int subject_id, bool is_protected, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset, const std::string &subject="")
const ImVec4 ORANGE_TEXT
bool ShouldDisplay(ImVec2 window_pos)
const ImVec4 TUNING_HOLDTOCONFIRM_COLOR
Str< 200 > tuning_savebox_buf
Buffer for tuneup name to be saved.
const ImVec4 GRAY_HINT_TEXT
rapidjson::Document ai_presets_bundled
Presets bundled with the terrain, see [AI Presets] section in .terrn2 file format.
const float PANEL_HOVERBOX_HEIGHT
Relative to font size.
bool tuning_savebox_overwrite
Status of "Overwrite?" checkbox.
bool ai_presets_extern_fetching
True if the (down)load of 'extern' waypoints is in progress.
const int TUNING_SUBJECTID_USE_NAME
void DrawMpUserToActorList(RoRnet::UserInfo &user)
AddonPartConflictVec tuning_conflicts
Conflicts between eligible addonparts tweaking the same element.
const float TUNING_HOLDTOCONFIRM_TIMELIMIT
Delete button must be held for several sec to confirm.
rapidjson::Document ai_presets_extern
Externally provided presets (GitHub repo or local 'savegames/waypoints.json' file).
const ImVec4 GREEN_TEXT
std::string m_quicksave_name
rapidjson::Document ai_presets_all
The full list of presets, used for display. Needs to be refreshed when terrain is loaded.
std::vector< CacheEntryPtr > tuning_addonparts
Addonparts eligible for current actor, both matched by GUID and force-installed by user via [browse a...
const float MENU_Y_OFFSET
Relative to font size.
std::vector< ai_events > ai_waypoints
void DrawTuningBoxedSubjectIdInline(int subject_id)
void RefreshAiPresets()
Refresh the list of presets, used for display. Needs to be called when terrain is loaded.
float tuning_rwidget_cursorx_min
Avoid drawing right-side widgets ('Delete' button or 'Protected' chk) over saved tuneup names.
std::string ai_presets_extern_error
Error message from the (down)load of 'extern' waypoints.
ActorPtr tuning_actor
Detecting actor change to update cached values.
void DrawSpecialStateBox(float top_offset)
bool IsMenuEnabled(TopMenu which)
bool tuning_savebox_visible
User pressed 'save active' to open savebox.
void LoadBundledAiPresets(TerrainPtr terrain)
Loads JSON files from [AI Presets] section in .terrn2 file format.
void FetchExternAiPresetsOnBackground()
Initiate threaded (down)load of 'extern' waypoints from GitHub repo.
void DrawTuningForceRemoveControls(const int subject_id, const std::string &name, const bool is_unwanted, const bool is_force_removed, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset)
const ImVec2 MENU_HOVERBOX_PADDING
Relative to font size.
CacheEntryPtr tuning_hovered_addonpart
void SetVisible(TPanelMode mode, TPanelFocus focus=TPANELFOCUS_NONE)
GUI::RepositorySelector RepositorySelector
Definition GUIManager.h:131
GUI::TextureToolWindow TextureToolWindow
Definition GUIManager.h:129
GUI::VehicleInfoTPanel VehicleInfoTPanel
Definition GUIManager.h:121
GUI::TopMenubar TopMenubar
Definition GUIManager.h:134
GUI::FrictionSettings FrictionSettings
Definition GUIManager.h:128
GUI::CollisionsDebug CollisionsDebug
Definition GUIManager.h:117
void RequestGuiCaptureKeyboard(bool val)
Pass true during frame to prevent input passing to application.
GuiTheme & GetTheme()
Definition GUIManager.h:171
GUI::NodeBeamUtils NodeBeamUtils
Definition GUIManager.h:132
GUI::ConsoleWindow ConsoleWindow
Definition GUIManager.h:135
GUI::FlexbodyDebug FlexbodyDebug
Definition GUIManager.h:138
Character * GetPlayerCharacter()
const ActorPtr & GetPlayerActor()
const TerrainPtr & GetTerrain()
void PushMessage(Message m)
Doesn't guarantee order! Use ChainMessage() if order matters.
const ActorPtr & GetLastSpawnedActor()
Last actor spawned by user and still alive.
RepairMode & GetRepairMode()
void ChainMessage(Message m)
Add to last pushed message's chain.
std::string GetQuicksaveFilename()
For currently loaded terrain (cvar 'sim_terrain_name')
Definition Savegame.cpp:56
ActorManager * GetActorManager()
std::vector< FlexBody * > & GetFlexbodies()
Definition GfxActor.h:70
ActorSB & GetSimDataBuffer()
Definition GfxActor.h:125
std::string getWheelRimMeshName(WheelID_t wheel_id)
Definition GfxActor.h:152
std::vector< VideoCamera > & getVideoCameras()
Definition GfxActor.h:74
std::vector< Prop > & getProps()
Definition GfxActor.h:71
WheelSide getWheelSide(WheelID_t wheel_id)
Definition GfxActor.h:151
std::vector< Exhaust > & getExhausts()
Definition GfxActor.h:73
DebugViewType GetDebugView() const
Definition GfxActor.h:142
GameContextSB & GetSimDataBuffer()
Definition GfxScene.h:80
Ogre::ColourValue GetPlayerColor(int color_num)
Definition Network.cpp:95
std::string UserAuthToStringShort(RoRnet::UserInfo const &user)
Definition Network.cpp:869
RoRnet::UserInfo GetLocalUserData()
Definition Network.cpp:708
std::vector< RoRnet::UserInfo > GetUserInfos()
Definition Network.cpp:714
float GetLiveRepairTimer() const
Definition RepairMode.h:44
int getCurrentFrame() const
Definition Replay.h:54
int getNumFrames() const
Definition Replay.h:53
unsigned long getLastReadTime()
Definition Replay.cpp:178
ScriptUnitMap const & getScriptUnits() const
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition Str.h:36
const char * ToCStr() const
Definition Str.h:46
char * GetBuffer()
Definition Str.h:48
size_t GetCapacity() const
Definition Str.h:49
std::string getTerrainFileResourceGroup()
Definition Terrain.cpp:563
SkyManager * getSkyManager()
Definition Terrain.cpp:521
std::string getTerrainFileName()
Definition Terrain.cpp:558
Wavefield * getWater()
Definition Terrain.h:87
Terrn2DocumentPtr GetDef()
Definition Terrain.cpp:583
static bool isAddonPartUsed(TuneupDefPtr &tuneup_entry, const std::string &filename)
static WheelSide getTweakedWheelSide(TuneupDefPtr &tuneup_entry, WheelID_t wheel_id, WheelSide orig_val)
void SetWavesHeight(float)
Definition Wavefield.cpp:85
std::string PathCombine(std::string a, std::string b)
bool FileExists(const char *path)
Path must be UTF-8 encoded.
@ DISABLED
Not connected for whatever reason.
@ LOCAL_SCRIPT
Not actually connected to network, but running server script locally.
@ EDITOR_MODE
Hacky, but whatever... added by Ulteq, 2016.
@ AI
machine controlled by an Artificial Intelligence
Definition SimData.h:89
@ EV_TRUCK_ACCELERATE
accelerate the truck
@ EV_COMMON_TOGGLE_RESET_MODE
toggle truck reset truck mode (soft vs. hard)
@ EV_TRUCK_TOGGLE_PHYSICS
toggle physics simulation
@ EV_SURVEY_MAP_CYCLE
cycle overview-map mode
@ EV_CHARACTER_SIDESTEP_LEFT
sidestep to the left
@ EV_COMMON_TOGGLE_PHYSICS
toggle physics on/off
@ EV_CHARACTER_FORWARD
step forward with the character
@ EV_TRUCK_STEER_LEFT
steer left
@ EV_COMMON_REPAIR_TRUCK
repair truck to original condition
@ EV_TRUCK_BRAKE
brake
@ EV_TRUCK_STEER_RIGHT
steer right
@ EV_CHARACTER_SIDESTEP_RIGHT
sidestep to the right
@ EV_COMMON_LIVE_REPAIR_MODE
toggles live repair and recovery mode, controlled by keyboard
@ EV_CHARACTER_BACKWARDS
step backwards with the character
@ EV_COMMON_TOGGLE_TERRAIN_EDITOR
toggle terrain editor
@ MSG_GUI_OPEN_MENU_REQUESTED
@ MSG_SIM_TELEPORT_PLAYER_REQUESTED
Payload = Ogre::Vector3* (owner)
@ MSG_NET_FETCH_AI_PRESETS_SUCCESS
Description = JSON string.
@ MSG_SIM_UNHIDE_NET_ACTOR_REQUESTED
Payload = ActorPtr* (owner)
@ MSG_SIM_HIDE_NET_ACTOR_REQUESTED
Payload = ActorPtr* (owner)
@ MSG_SIM_UNLOAD_TERRN_REQUESTED
@ MSG_NET_DISCONNECT_REQUESTED
@ MSG_NET_FETCH_AI_PRESETS_FAILURE
Description = message.
@ MSG_SIM_SPAWN_ACTOR_REQUESTED
Payload = RoR::ActorSpawnRequest* (owner)
@ MSG_GUI_OPEN_SELECTOR_REQUESTED
Payload = LoaderType* (owner), Description = GUID | empty.
@ MSG_APP_UNLOAD_SCRIPT_REQUESTED
Payload = RoR::ScriptUnitId_t* (owner)
Definition Application.h:95
@ MSG_SIM_SEAT_PLAYER_REQUESTED
Payload = RoR::ActorPtr (owner) | nullptr.
@ MSG_EDI_SAVE_TERRN_CHANGES_REQUESTED
@ MSG_APP_LOAD_SCRIPT_REQUESTED
Payload = RoR::LoadScriptRequest* (owner)
Definition Application.h:94
@ MSG_SIM_LOAD_SAVEGAME_REQUESTED
@ MSG_APP_SHUTDOWN_REQUESTED
Definition Application.h:87
@ MSG_EDI_CREATE_PROJECT_REQUESTED
Payload = RoR::CreateProjectRequest* (owner)
@ MSG_EDI_DELETE_PROJECT_REQUESTED
Payload = RoR::CacheEntryPtr* (owner)
@ MSG_EDI_MODIFY_PROJECT_REQUESTED
Payload = RoR::UpdateProjectRequest* (owner)
@ MSG_SIM_LOAD_TERRN_REQUESTED
@ MSG_SIM_DELETE_ACTOR_REQUESTED
Payload = RoR::ActorPtr* (owner)
@ MSG_SIM_MODIFY_ACTOR_REQUESTED
Payload = RoR::ActorModifyRequest* (owner)
@ MSG_EDI_RELOAD_BUNDLE_REQUESTED
Payload = RoR::CacheEntryPtr* (owner)
@ LOCAL_SIMULATED
simulated (local) actor
@ NETWORKED_OK
not simulated (remote) actor
@ NETWORKED_HIDDEN
not simulated, not updated (remote)
@ CUSTOM
Loaded by user via either: A) ingame console 'loadscript'; B) RoR.cfg 'app_custom_scripts'; C) comman...
@ AI_BOT
Only valid under MpState::LOCAL_SCRIPT; registers as RoRnet user in the server script (i....
std::string ToLocalizedString(SimGearboxMode e)
CVar * gfx_sky_mode
CVar * gfx_polygon_mode
ContentManager * GetContentManager()
CVar * gfx_static_cam_fov_exp
CVar * ui_show_live_repair_controls
bool
CVar * gfx_sky_time_cycle
CVar * gfx_water_mode
CVar * sim_state
CVar * mp_hide_net_labels
InputEngine * GetInputEngine()
CVar * diag_log_beam_trigger
CVar * gfx_envmap_rate
CVar * sim_terrain_name
CVar * gfx_sky_time_speed
CameraManager * GetCameraManager()
CVar * diag_truck_mass
CVar * audio_master_volume
GUIManager * GetGuiManager()
GameContext * GetGameContext()
CVar * gfx_camera_height
CVar * sim_tuning_enabled
CVar * sys_savegames_dir
CVar * sim_soft_reset_mode
GfxScene * GetGfxScene()
CVar * gfx_water_waves
CVar * diag_log_beam_deform
CVar * gfx_fov_external
CVar * mp_state
CVar * gfx_fps_limit
CVar * sim_live_repair_interval
Hold EV_COMMON_REPAIR_TRUCK to enter LiveRepair mode. 0 or negative interval disables.
Console * GetConsole()
CVar * gfx_fov_internal
CVar * mp_pseudo_collisions
CVar * io_hydro_coupling
ScriptEngine * GetScriptEngine()
CacheSystem * GetCacheSystem()
CVar * gfx_envmap_enabled
CVar * diag_videocameras
CVar * diag_log_beam_break
Network * GetNetwork()
CVar * gfx_fixed_cam_tracking
void ImDrawEventHighlighted(events input_event)
Definition GUIUtils.cpp:446
void Log(const char *msg)
The ultimate, application-wide logging function. Adds a line (any length) in 'RoR....
LoaderType
< Search mode for ModCache::Query() & Operation mode for GUI::MainSelector
@ LT_Tuneup
@ LT_AddonPart
@ LT_Gadget
@ LT_AllBeam
@ SAVE_TUNEUP
Dumps .tuneup file with CID_Tuneup from source actor, will not overwrite existing unless explicitly i...
ModifyProjectRequestType
@ TUNEUP_PROTECTED_WHEEL_RESET
'subject_id' is wheel ID.
@ TUNEUP_FORCED_VCAM_ROLE_RESET
'subject_id' is video camera ID.
@ TUNEUP_FORCEREMOVE_MANAGEDMAT_RESET
'subject' is managed material name.
@ TUNEUP_FORCEREMOVE_FLEXBODY_SET
'subject_id' is flexbody ID.
@ TUNEUP_PROTECTED_WHEEL_SET
'subject_id' is wheel ID.
@ TUNEUP_PROTECTED_MANAGEDMAT_SET
'subject' is managed material name.
@ TUNEUP_FORCEREMOVE_EXHAUST_SET
'subject_id' is exhaust ID.
@ TUNEUP_USE_ADDONPART_RESET
'subject' is addonpart filename.
@ TUNEUP_PROTECTED_PROP_SET
'subject_id' is prop ID.
@ TUNEUP_USE_ADDONPART_SET
'subject' is addonpart filename.
@ TUNEUP_FORCEREMOVE_PROP_SET
'subject_id' is prop ID.
@ TUNEUP_PROTECTED_EXHAUST_SET
'subject_id' is exhaust ID.
@ TUNEUP_PROTECTED_FLARE_SET
'subject_id' is flare ID.
@ TUNEUP_FORCED_WHEEL_SIDE_RESET
'subject_id' is wheel ID.
@ TUNEUP_FORCEREMOVE_MANAGEDMAT_SET
'subject' is managed material name.
@ TUNEUP_PROTECTED_FLEXBODY_RESET
'subject_id' is flexbody ID.
@ TUNEUP_PROTECTED_FLEXBODY_SET
'subject_id' is flexbody ID.
@ TUNEUP_FORCED_WHEEL_SIDE_SET
'subject_id' is wheel ID, 'value_int' is RoR::WheelSide
@ TUNEUP_PROTECTED_EXHAUST_RESET
'subject_id' is exhaust ID.
@ TUNEUP_PROTECTED_MANAGEDMAT_RESET
'subject' is managed material name.
@ TUNEUP_FORCED_VCAM_ROLE_SET
'subject_id' is video camera ID, 'value_int' is RoR::VideoCamRole
@ PROJECT_RESET_TUNEUP
'subject' is empty. This resets the auto-generated tuneup to orig. values.
@ TUNEUP_FORCEREMOVE_FLARE_RESET
'subject_id' is flare ID.
@ TUNEUP_PROTECTED_PROP_RESET
'subject_id' is prop ID.
@ TUNEUP_FORCEREMOVE_FLEXBODY_RESET
'subject_id' is flexbody ID.
@ TUNEUP_FORCEREMOVE_PROP_RESET
'subject_id' is prop ID.
@ TUNEUP_PROTECTED_FLARE_RESET
'subject_id' is flare ID.
@ TUNEUP_FORCEREMOVE_EXHAUST_RESET
'subject_id' is exhaust ID.
@ PROJECT_LOAD_TUNEUP
'subject' is tuneup filename. This overwrites the auto-generated tuneup with the save.
@ TUNEUP_FORCEREMOVE_FLARE_SET
'subject_id' is flare ID.
void HandleGenericException(const std::string &from, BitMask_t flags)
WheelSide
Used by rig-def/addonpart/tuneup formats to specify wheel rim mesh orientation.
VideoCamRole
@ VCAM_ROLE_MIRROR
Flips the video output and when not in driver cam, acts like a natural mirror, not a screen.
@ VCAM_ROLE_TRACKING_MIRROR_NOFLIP
A MIRROR_NOFLIP(2) with tracking node set.
@ VCAM_ROLE_TRACKING_MIRROR
A MIRROR(1) with tracking node set.
@ VCAM_ROLE_INVALID
@ VCAM_ROLE_MIRROR_PROP_LEFT
The classic 'special prop/rear view mirror'.
@ VCAM_ROLE_MIRROR_PROP_RIGHT
The classic 'special prop/rear view mirror'.
@ VCAM_ROLE_MIRROR_NOFLIP
Same as VCAM_ROLE_MIRROR, but without flipping the texture horizontally (expects texcoords to be alre...
void DrawGIntSlider(CVar *cvar, const char *label, int v_min, int v_max)
Definition GUIUtils.cpp:316
int WheelID_t
Index to Actor::ar_wheels, use RoR::WHEELID_INVALID as empty value.
int ScriptUnitID_t
Unique sequentially generated ID of a loaded and running scriptin session. Use ScriptEngine::getScrip...
RefCountingObjectPtr< Actor > ActorPtr
int VideoCameraID_t
Index into GfxActor::m_videocameras, use RoR::VIDEOCAMERAID_INVALID as empty value.
@ CAELUM
Caelum (best looking, slower)
int FlareID_t
Index into Actor::ar_flares, use RoR::FLAREID_INVALID as empty value.
int ExhaustID_t
Index into GfxActor::m_exhausts, use RoR::EXHAUSTID_INVALID as empty value.
void ImDrawModifierKeyHighlighted(OIS::KeyCode key)
Definition GUIUtils.cpp:489
void DrawGFloatSlider(CVar *cvar, const char *label, float v_min, float v_max)
Definition GUIUtils.cpp:326
SimResetMode
bool DrawGCheckbox(CVar *cvar, const char *label)
Definition GUIUtils.cpp:287
@ CID_Tuneups
For unsorted tuneup files.
std::string StripColorMarksFromText(std::string const &text)
Definition GUIUtils.cpp:225
RefCountingObjectPtr< CacheEntry > CacheEntryPtr
Ogre::TexturePtr FetchIcon(const char *name)
Definition GUIUtils.cpp:372
void LoadingIndicatorCircle(const char *label, const float indicator_radius, const ImVec4 &main_color, const ImVec4 &backdrop_color, const int circle_count, const float speed)
Draws animated loading spinner.
Definition GUIUtils.cpp:158
bool ImButtonHoldToConfirm(const std::string &btn_idstr, const bool smallbutton, const float time_limit)
Definition GUIUtils.cpp:512
ActorInstanceID_t amr_actor
Definition SimData.h:875
@ RELOAD
Full reload from filesystem, requested by user.
ActorState simbuf_actor_state
Definition SimBuffers.h:115
Ogre::Vector3 simbuf_pos
Definition SimBuffers.h:123
Ogre::String asr_config
Definition SimData.h:836
CacheEntryPtr asr_cache_entry
Optional, overrides 'asr_filename' and 'asr_cache_entry_num'.
Definition SimData.h:834
TuneupDefPtr asr_working_tuneup
Only filled when editing tuneup via Tuning menu.
Definition SimData.h:842
Ogre::Vector3 asr_position
Definition SimData.h:837
CacheEntryPtr asr_skin_entry
Definition SimData.h:840
Ogre::Quaternion asr_rotation
Definition SimData.h:838
@ USER
Direct selection by user via GUI.
std::string cqy_filter_target_filename
Exact match (case-insensitive); leave empty to disable (currently only used with addonparts)
std::string cqy_filter_guid
Exact match (case-insensitive); leave empty to disable.
std::vector< CacheQueryResult > cqy_results
RoR::LoaderType cqy_filter_type
Creates subdirectory in 'My Games\Rigs of Rods\projects', pre-populates it with files and adds modcac...
CreateProjectRequestType cpr_type
CacheEntryPtr cpr_source_entry
The original mod to copy files from.
std::string cpr_name
Directory and also the mod file (without extension).
ActorPtr cpr_source_actor
Only for type SAVE_TUNEUP
ImVec2 screen_edge_padding
Set by ApplyImGuiStyle() relative to font size.
Definition GUIManager.h:97
Ogre::Vector3 simbuf_character_pos
Definition SimBuffers.h:205
float simbuf_race_time_diff
Definition SimBuffers.h:214
ActorPtr simbuf_player_actor
Definition SimBuffers.h:204
Ogre::Vector3 simbuf_dir_arrow_target
Definition SimBuffers.h:217
float simbuf_race_best_time
Definition SimBuffers.h:213
std::string simbuf_dir_arrow_text
Definition SimBuffers.h:218
std::string lsr_filename
Load from resource ('.as' file or '.gadget' file); If buffer is supplied, use this as display name on...
ScriptCategory lsr_category
Unified game event system - all requests and state changes are reported using a message.
Definition GameContext.h:52
std::string description
Definition GameContext.h:58
void * payload
Definition GameContext.h:59
ModifyProjectRequestType mpr_type
A mesh attached to vehicle frame via 3 nodes.
Definition GfxData.h:157
bool isFlexbodyUnwanted(FlexbodyID_t flexbodyid)
bool isManagedMatUnwanted(const std::string &matname)
bool isVideoCameraRoleForced(VideoCameraID_t camera_id, VideoCamRole &out_val) const
bool isFlexbodyProtected(FlexbodyID_t flexbodyid) const
bool isPropProtected(PropID_t propid) const
bool isFlareUnwanted(FlareID_t flareid)
bool isPropForceRemoved(PropID_t propid)
bool isPropUnwanted(PropID_t propid)
bool isWheelSideForced(WheelID_t wheelid, WheelSide &out_val) const
std::set< std::string > use_addonparts
Addonpart filenames.
bool isFlexbodyForceRemoved(FlexbodyID_t flexbodyid)
bool isFlareForceRemoved(FlareID_t flareid)
bool isManagedMatForceRemoved(const std::string &matname)
bool isWheelProtected(WheelID_t wheelid) const
bool isManagedMatProtected(const std::string &matname) const
bool isExhaustUnwanted(ExhaustID_t exhaustid)
bool isExhaustProtected(ExhaustID_t exhaustid) const
bool isExhaustForceRemoved(ExhaustID_t exhaustid)
bool isFlareProtected(FlareID_t flareid) const
An Ogre::Camera mounted on the actor and rendering into either in-scene texture or external window.
Definition GfxData.h:216
VideoCamRole vcam_role
Definition GfxData.h:217
char language[10]
user's language. For example "de-DE" or "en-US"
Definition RoRnet.h:199
char username[RORNET_MAX_USERNAME_LEN]
the nickname of the user (UTF-8)
Definition RoRnet.h:196
int32_t colournum
colour set by server
Definition RoRnet.h:194
uint32_t uniqueid
user unique id
Definition RoRnet.h:191
char clientversion[25]
a version number of the client. For example 1 for RoR 0.35
Definition RoRnet.h:201
Ogre::Vector3 position