From 653d206a54712c259a4eb7d894edbc510228cabe Mon Sep 17 00:00:00 2001 From: maj00r Date: Mon, 12 Oct 2026 02:00:50 +0200 Subject: [PATCH] feat(editor): the vehicle tool places trainsets: what is placed on a named track is written to the scenery on save as trainset , with reversed when it faces against the track, listed until the save with x to leave one out; the timetable and the starting speed are set in the panel, a tick turns writing off for a vehicle only to drive; the starter WMB10 is only added when nothing was placed; self-test command trainset --- application/editormode.cpp | 19 ++++++++-- application/editormode.h | 12 ++++++- application/editormodeselftest.cpp | 25 +++++++++++++ application/editormodetrack.cpp | 2 +- application/editormodetrackux.cpp | 2 +- application/editormodevehicle.cpp | 54 ++++++++++++++++++++++++++-- application/editormodeviewport.cpp | 2 +- lang/editor_pl.po | 56 ++++++++++++++++++++++++------ 8 files changed, 153 insertions(+), 19 deletions(-) diff --git a/application/editormode.cpp b/application/editormode.cpp index 8d8ba960..728c2c59 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -2937,11 +2937,24 @@ bool editor_mode::save() auto const terrainadded{false == rootstatements.empty()}; auto const controls{turntable_controls(rootstatements)}; std::vector trailing; - starter_trainset(trailing); + std::erase_if(m_vehicle.trainsets, [](auto const &Trainset) { + auto const *track{simulation::Paths.find(Trainset.track)}; + return track == nullptr || track->m_editorremoved; + }); + auto const placed{m_vehicle.trainsets.size()}; + for (auto const &trainset : m_vehicle.trainsets) + trailing.insert(trailing.end(), trainset.lines.begin(), trainset.lines.end()); + if (placed == 0) + starter_trainset(trailing); auto const result = scene::Layers.save(rootstatements, trailing); if (result.success && controls) m_turntable_included = true; - if (result.success && trailing.size() == 3) + if (result.success && placed > 0) + { + m_vehicle.trainsets.clear(); + m_vehicle.status = format(STR_C("%d trainset(s) written to the scenery"), static_cast(placed)); + } + if (result.success && placed == 0 && trailing.size() == 3) { std::istringstream header{trailing.front()}; std::string keyword; @@ -4613,7 +4626,7 @@ void editor_mode::render_first_steps() "T: switch. Click a straight, then press on it and drag: along sets the direction, sideways the side", "Ctrl+Z takes a step back, Backspace while laying takes back the last piece", "Ctrl+S saves the scenery", - "Drive it: Vehicle in the toolbar, pick a locomotive from the list, LMB on the track, then Place and drive"}; + "Drive it: Trainset in the toolbar, pick a locomotive from the list, LMB on the track, then Place and drive"}; ImGui::SetNextWindowSize(ImVec2(440.0f * Global.ui_scale, 0.0f), ImGuiCond_FirstUseEver); ImGui::SetNextWindowPos(ImVec2(8.0f, ImGui::GetIO().DisplaySize.y - 8.0f), ImGuiCond_FirstUseEver, ImVec2(0.0f, 1.0f)); ImGui::SetNextWindowBgAlpha(1.0f); diff --git a/application/editormode.h b/application/editormode.h index 6b056944..4a6dc906 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1581,7 +1581,7 @@ class editor_mode : public application_mode, private editor_track::observer void render_fouling_ui(); void draw_fouling_overlay() const; - // a vehicle put on the selected path, to be driven: it isn't written to the scenery files + // a trainset put on the selected path, written to the scenery on save, or only to be driven struct vehicle_tool { bool open{false}; @@ -1605,6 +1605,16 @@ class editor_mode : public application_mode, private editor_track::observer }; std::vector consist; int facing{1}; + bool write{true}; + char timetable[64]{"none"}; + double velocity{0.0}; + struct trainset + { + std::string head; + std::string track; + std::vector lines; + }; + std::vector trainsets; }; vehicle_tool m_vehicle; void vehicle_start(TTrack &Track, glm::dvec3 const &Point); diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index 2aa89c72..fef32cf6 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -29,6 +29,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/renderer.h" #include "world/Sweep.h" #include "input/keyboardinput.h" +#include "launcher/textures_scanner.h" #include #include #include @@ -148,6 +149,29 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm save(); return selftest_result::handled; } + if (command == "trainset") + { + std::string name; + std::string vehicle; + std::string skin; + int facing{1}; + words >> name >> vehicle >> skin >> facing >> m_vehicle.velocity; + auto *track{simulation::Paths.find(name)}; + if (track == nullptr) + return selftest_result::handled; + auto desc{std::make_shared()}; + desc->path = vehicle; + auto set{std::make_shared()}; + set->skin = skin; + m_vehicle.consist.clear(); + m_vehicle.vehicle = desc; + m_vehicle.skin = set; + vehicle_start(*track, geometry::bezier{track->m_paths.front()}.point(0.5)); + m_vehicle.facing = facing; + auto const placed{vehicle_place()}; + WriteLog(format("SELFTEST trainset %s placed %d pending %d: %s", name.c_str(), placed ? 1 : 0, static_cast(m_vehicle.trainsets.size()), m_vehicle.status.c_str())); + return selftest_result::handled; + } if (command == "selectmodel") { glm::dvec3 point{0.0}; @@ -2402,6 +2426,7 @@ editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &co // checkspans logs whether the text the scenery files hold at each node's place starts and ends it // spread logs the paths the spread from the track takes // newscenery the editor starts again with the wizard of a new scenery +// trainset [km/h] the trainset tool puts the vehicle in the middle of the track // vehicles logs each vehicle: its head, facing, and what its couplers hold // terrainmode the terrain field of work, with its texture browser // terrainsculpt , terrainpaint , diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index cdce2f3f..b79f2b3f 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -533,7 +533,7 @@ std::array const &editor_mode::track_modes() {"Straight", "G", track_tab::straights, "The whole straight through the selected path: drag its ends or the middle, break it, shift it"}, {"Curve", "C", track_tab::route, "The line through the path, switch to switch: vertices with the radii, transitions and cant of the curves"}, {"Signals", "H", track_tab::signals, "Signals by the track, from the templates of the scenery, with the event the train reads them by"}, - {"Objects", "B", track_tab::lineside, "Along the track: hectometre posts, fouling point markers, a vehicle to drive"}, + {"Objects", "B", track_tab::lineside, "Along the track: hectometre posts, fouling point markers, trainsets"}, {"Profile", "P", track_tab::profile, "Vertical profile (grade line) along the line"}, {"Speed", "V", track_tab::speed, "Speed limits of the paths against the speed their geometry allows"}, {"Joints", "J", track_tab::joints, "Ends of the paths which almost meet, steps, kinks, jumps of the cant and of the grade at the joints"}, diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index 459f8812..02cd097e 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -1428,7 +1428,7 @@ void editor_mode::render_lineside_ui() std::array const tabs{{ {"Hectometre posts", &m_hekto.open, &m_hekto.expand, [this]() { render_hekto_ui(); }}, {"Fouling points", &m_fouling.open, &m_fouling.expand, [this]() { render_fouling_ui(); }}, - {"Vehicle", &m_vehicle.open, &m_vehicle.expand, [this]() { render_vehicle_ui(); }}, + {"Trainset", &m_vehicle.open, &m_vehicle.expand, [this]() { render_vehicle_ui(); }}, }}; if (selected_track() == nullptr) ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", STR_C("LMB on a track: the line the objects go along")); diff --git a/application/editormodevehicle.cpp b/application/editormodevehicle.cpp index bcd1f1ad..431f74be 100644 --- a/application/editormodevehicle.cpp +++ b/application/editormodevehicle.cpp @@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/. #include "application/editormode.h" #include "application/editoruilayer.h" #include "application/editorprojection.h" +#include "application/editormodeshared.h" #include "application/application.h" #include "editor/editorFormat.hpp" #include "editor/editorGeometry.hpp" @@ -175,7 +176,28 @@ bool editor_mode::vehicle_place() } tool.placed = head; first_step_done(first_step::drive); - tool.status = format(STR_C("%s and %d vehicle(s) behind it stand on %s. It isn't saved with the scenery"), head.c_str(), static_cast(vehicles.size()) - 1, tool.track->name().empty() ? "(noname)" : tool.track->name().c_str()); + auto const trackname{tool.track->name().empty() ? std::string{"(noname)"} : tool.track->name()}; + tool.status = format(STR_C("%s and %d vehicle(s) behind it stand on %s"), head.c_str(), static_cast(vehicles.size()) - 1, trackname.c_str()); + if (tool.write && tool.track->name().empty()) + tool.status += ". " + std::string{STR_C("The track has no name, the trainset can't be written to the scenery")}; + else if (tool.write) + { + std::string timetable{tool.timetable}; + timetable.erase(std::remove_if(timetable.begin(), timetable.end(), [](unsigned char const Character) { return std::isspace(Character) != 0; }), timetable.end()); + vehicle_tool::trainset entry{head, tool.track->name(), {}}; + entry.lines.push_back("trainset " + (timetable.empty() ? std::string{"none"} : timetable) + " " + tool.track->name() + " " + to_string(offset, 2) + " " + to_string(tool.velocity, 0)); + if (against) + entry.lines.push_back("reversed"); + std::istringstream lines{nodes}; + for (std::string line; std::getline(lines, line);) + if (false == line.empty()) + entry.lines.push_back(line); + entry.lines.push_back("endtrainset"); + tool.trainsets.push_back(std::move(entry)); + tool.status += ". " + std::string{STR_C("Written to the scenery as a trainset on save (Ctrl+S)")}; + } + else + tool.status += ". " + std::string{STR_C("Only to drive, it isn't written to the scenery")}; WriteLog("Editor: " + tool.status, logtype::generic); return true; } @@ -198,7 +220,7 @@ void editor_mode::render_vehicle_ui() tool.bank = std::make_shared(); tool.bank->scan_textures(); } - ImGui::TextDisabled("%s", STR_C("A vehicle to drive, placed where RMB on the track was clicked, or in the middle of the path.\nIt isn't written to the scenery files")); + ImGui::TextDisabled("%s", STR_C("A trainset on the track, placed where the track was clicked, or in the middle of the path")); ImGui::SetNextItemWidth(-1.0f); ImGui::InputTextWithHint("##vehiclesearch", STR_C("search: name, series, skin"), tool.search, sizeof(tool.search)); ImGui::Checkbox(STR_C("Only the ones with a cab"), &tool.controllable_only); @@ -273,6 +295,17 @@ void editor_mode::render_vehicle_ui() ImGui::RadioButton(STR_C("With a driver, to drive"), &tool.driver, 0); ImGui::SameLine(); ImGui::RadioButton(STR_C("Nobody, parked"), &tool.driver, 1); + ImGui::Checkbox(STR_C("Write to the scenery as a trainset"), &tool.write); + item_tooltip("On: the trainset is written to the scenery on save (Ctrl+S). Off: only to drive now, gone when the scenery is loaded again"); + if (tool.write) + { + ImGui::PushItemWidth(120.0f); + ImGui::InputText(STR_C("Timetable"), tool.timetable, sizeof(tool.timetable)); + item_tooltip("The timetable the train runs by; none: no timetable, it waits for orders"); + ImGui::DragScalar(STR_C("Speed at the start (km/h)"), ImGuiDataType_Double, &tool.velocity, 1.0f, nullptr, nullptr, "%.0f"); + tool.velocity = std::clamp(tool.velocity, 0.0, 400.0); + ImGui::PopItemWidth(); + } if (tool.consist.empty() && (tool.vehicle == nullptr || tool.skin == nullptr)) ImGui::TextDisabled("%s", STR_C("Pick a vehicle from the list")); else @@ -288,6 +321,23 @@ void editor_mode::render_vehicle_ui() } if (false == tool.status.empty()) ImGui::TextWrapped("%s", tool.status.c_str()); + if (tool.trainsets.empty()) + return; + ImGui::Separator(); + ImGui::TextDisabled(STR_C("%d trainset(s) to write on save:"), static_cast(tool.trainsets.size())); + std::optional drop; + for (std::size_t i = 0; i < tool.trainsets.size(); ++i) + { + ImGui::PushID(static_cast(i)); + if (ImGui::SmallButton("x")) + drop = i; + item_tooltip("Not written to the scenery; the vehicles stay until the scenery is loaded again"); + ImGui::SameLine(); + ImGui::Text("%s (%s)", tool.trainsets[i].head.c_str(), tool.trainsets[i].track.c_str()); + ImGui::PopID(); + } + if (drop) + tool.trainsets.erase(tool.trainsets.begin() + static_cast(*drop)); } void editor_mode::draw_vehicle_marker() const diff --git a/application/editormodeviewport.cpp b/application/editormodeviewport.cpp index 8ea888d2..d3d4aeb2 100644 --- a/application/editormodeviewport.cpp +++ b/application/editormodeviewport.cpp @@ -173,7 +173,7 @@ void editor_mode::work_area_tools(std::vector &Buttons) add(track_tab::turntable, icon::turntable, false); add(track_tab::signals, icon::signal, true); add(track_tab::lineside, icon::objects, false); - Buttons.push_back({icon::vehicle, STR_C("Vehicle"), nullptr, STR_C("A vehicle to drive, put on the selected track; it isn't written to the scenery files"), vehicle, [this, vehicle]() { + Buttons.push_back({icon::vehicle, STR_C("Trainset"), nullptr, STR_C("A trainset on the selected track: written to the scenery on save, and to drive at once"), vehicle, [this, vehicle]() { show_track_tab(vehicle ? track_tab::path : track_tab::lineside); m_vehicle.expand = false == vehicle; }}); diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 841b91cd..1e6321d5 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -2506,8 +2506,8 @@ msgstr "co można z nim zrobić" msgid "Vehicle on the track" msgstr "Pojazd na torze" -msgid "A vehicle to drive, placed where RMB on the track was clicked, or in the middle of the path.\nIt isn't written to the scenery files" -msgstr "Pojazd do jazdy, stawiany tam, gdzie kliknięto PPM na torze, albo w połowie odcinka.\nNie jest zapisywany do plików scenerii" +msgid "A trainset on the track, placed where the track was clicked, or in the middle of the path" +msgstr "Skład na torze, stawiany tam, gdzie kliknięto tor, albo w połowie odcinka" msgid "search: name, series, skin" msgstr "szukaj: nazwa, seria, malowanie" @@ -2755,8 +2755,8 @@ msgstr "Parzyste hektometry po prawej, nieparzyste po lewej" msgid "Objects" msgstr "Obiekty" -msgid "Along the track: hectometre posts, fouling point markers, a vehicle to drive" -msgstr "Przy torze: słupki hektometrowe, wskaźniki ukresu, pojazd do jazdy" +msgid "Along the track: hectometre posts, fouling point markers, trainsets" +msgstr "Przy torze: słupki hektometrowe, wskaźniki ukresu, składy" msgid "Model along the track: release where it ends" msgstr "Model wzdłuż toru: puść tam, gdzie się kończy" @@ -3357,8 +3357,8 @@ msgstr "Przywraca teren sprzed ostatniego kształtowania i usuwa teren, który p msgid "Click: start from this free end, drag: the curve of the switch goes on" msgstr "Kliknij: zacznij od tego wolnego końca, przeciągnij: łuk rozjazdu biegnie dalej" -msgid "%s and %d vehicle(s) behind it stand on %s. It isn't saved with the scenery" -msgstr "%s i %d pojazd(ów) za nim stoją na %s. Nie zapisuje się ze scenerią" +msgid "%s and %d vehicle(s) behind it stand on %s" +msgstr "%s i %d pojazd(ów) za nim stoją na %s" msgid "Add to the train" msgstr "Dodaj do składu" @@ -4837,8 +4837,8 @@ msgstr "LPM wyrównuje teren pod pędzlem" msgid "LMB adds a chunk next to the clicked one, Shift+LMB deletes it" msgstr "LPM dodaje kafel obok klikniętego, Shift+LPM go usuwa" -msgid "A vehicle to drive, put on the selected track; it isn't written to the scenery files" -msgstr "Pojazd do jazdy, stawiany na zaznaczonym torze; nie trafia do plików scenerii" +msgid "A trainset on the selected track: written to the scenery on save, and to drive at once" +msgstr "Skład na zaznaczonym torze: zapisywany do scenerii przy zapisie, od razu do jazdy" msgid "%zu groups taken apart" msgstr "Rozgrupowano grup: %zu" @@ -5677,8 +5677,8 @@ msgstr "Przeciągnij, by zmienić, Ctrl+klik, by wpisać. W trakcie układania: msgid "Speed limit of the new paths; the speed of the track joined comes first" msgstr "Ograniczenie prędkości nowych odcinków; pierwszeństwo ma prędkość dołączanego toru" -msgid "Drive it: Vehicle in the toolbar, pick a locomotive from the list, LMB on the track, then Place and drive" -msgstr "Przejedź się: Pojazd na pasku narzędzi, wybierz lokomotywę z listy, LPM na torze, potem Postaw i jedź" +msgid "Drive it: Trainset in the toolbar, pick a locomotive from the list, LMB on the track, then Place and drive" +msgstr "Przejedź się: Skład na pasku narzędzi, wybierz lokomotywę z listy, LPM na torze, potem Postaw i jedź" msgid "the Parallel tool (N) with it" msgstr "narzędzie Równoległy (N) z nią" @@ -5691,3 +5691,39 @@ msgstr "Narzędzie Równoległy (N) z tym torem" msgid "Click: the Parallel tool (N) with this track" msgstr "Klik: narzędzie Równoległy (N) z tym torem" + +msgid "Trainset" +msgstr "Skład" + +msgid "The track has no name, the trainset can't be written to the scenery" +msgstr "Tor nie ma nazwy, składu nie da się zapisać do scenerii" + +msgid "Written to the scenery as a trainset on save (Ctrl+S)" +msgstr "Trafi do scenerii jako trainset przy zapisie (Ctrl+S)" + +msgid "Only to drive, it isn't written to the scenery" +msgstr "Tylko do jazdy, nie trafia do scenerii" + +msgid "Write to the scenery as a trainset" +msgstr "Zapisz do scenerii jako trainset" + +msgid "On: the trainset is written to the scenery on save (Ctrl+S). Off: only to drive now, gone when the scenery is loaded again" +msgstr "Włączone: skład trafia do scenerii przy zapisie (Ctrl+S). Wyłączone: tylko do jazdy teraz, znika po ponownym wczytaniu scenerii" + +msgid "Timetable" +msgstr "Rozkład" + +msgid "The timetable the train runs by; none: no timetable, it waits for orders" +msgstr "Rozkład jazdy pociągu; none: bez rozkładu, czeka na polecenia" + +msgid "Speed at the start (km/h)" +msgstr "Prędkość na starcie (km/h)" + +msgid "%d trainset(s) to write on save:" +msgstr "%d skład(ów) do zapisania:" + +msgid "Not written to the scenery; the vehicles stay until the scenery is loaded again" +msgstr "Nie trafi do scenerii; pojazdy zostają do ponownego wczytania scenerii" + +msgid "%d trainset(s) written to the scenery" +msgstr "Zapisano %d skład(ów) do scenerii"