17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-06 02:18:35 +01:00

feat(editor): the vehicle tool places trainsets: what is placed on a named track is written to the scenery on save as trainset <timetable> <track> <offset> <speed>, 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

This commit is contained in:
maj00r
2026-10-12 02:00:50 +02:00
parent c9f381a9c9
commit 653d206a54
8 changed files with 153 additions and 19 deletions

View File

@@ -2937,11 +2937,24 @@ bool editor_mode::save()
auto const terrainadded{false == rootstatements.empty()}; auto const terrainadded{false == rootstatements.empty()};
auto const controls{turntable_controls(rootstatements)}; auto const controls{turntable_controls(rootstatements)};
std::vector<std::string> trailing; std::vector<std::string> 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); auto const result = scene::Layers.save(rootstatements, trailing);
if (result.success && controls) if (result.success && controls)
m_turntable_included = true; 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<int>(placed));
}
if (result.success && placed == 0 && trailing.size() == 3)
{ {
std::istringstream header{trailing.front()}; std::istringstream header{trailing.front()};
std::string keyword; 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", "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+Z takes a step back, Backspace while laying takes back the last piece",
"Ctrl+S saves the scenery", "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::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::SetNextWindowPos(ImVec2(8.0f, ImGui::GetIO().DisplaySize.y - 8.0f), ImGuiCond_FirstUseEver, ImVec2(0.0f, 1.0f));
ImGui::SetNextWindowBgAlpha(1.0f); ImGui::SetNextWindowBgAlpha(1.0f);

View File

@@ -1581,7 +1581,7 @@ class editor_mode : public application_mode, private editor_track::observer
void render_fouling_ui(); void render_fouling_ui();
void draw_fouling_overlay() const; 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 struct vehicle_tool
{ {
bool open{false}; bool open{false};
@@ -1605,6 +1605,16 @@ class editor_mode : public application_mode, private editor_track::observer
}; };
std::vector<consist_vehicle> consist; std::vector<consist_vehicle> consist;
int facing{1}; int facing{1};
bool write{true};
char timetable[64]{"none"};
double velocity{0.0};
struct trainset
{
std::string head;
std::string track;
std::vector<std::string> lines;
};
std::vector<trainset> trainsets;
}; };
vehicle_tool m_vehicle; vehicle_tool m_vehicle;
void vehicle_start(TTrack &Track, glm::dvec3 const &Point); void vehicle_start(TTrack &Track, glm::dvec3 const &Point);

View File

@@ -29,6 +29,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/renderer.h" #include "rendering/renderer.h"
#include "world/Sweep.h" #include "world/Sweep.h"
#include "input/keyboardinput.h" #include "input/keyboardinput.h"
#include "launcher/textures_scanner.h"
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include <algorithm> #include <algorithm>
@@ -148,6 +149,29 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm
save(); save();
return selftest_result::handled; 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<ui::vehicle_desc>()};
desc->path = vehicle;
auto set{std::make_shared<ui::skin_set>()};
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<int>(m_vehicle.trainsets.size()), m_vehicle.status.c_str()));
return selftest_result::handled;
}
if (command == "selectmodel") if (command == "selectmodel")
{ {
glm::dvec3 point{0.0}; 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 // checkspans logs whether the text the scenery files hold at each node's place starts and ends it
// spread <track> <stop 0..2> <reach> logs the paths the spread from the track takes // spread <track> <stop 0..2> <reach> logs the paths the spread from the track takes
// newscenery the editor starts again with the wizard of a new scenery // newscenery the editor starts again with the wizard of a new scenery
// trainset <track> <vehicle path> <skin> <facing> [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 // vehicles logs each vehicle: its head, facing, and what its couplers hold
// terrainmode the terrain field of work, with its texture browser // terrainmode the terrain field of work, with its texture browser
// terrainsculpt <x> <z> <radius> <metres>, terrainpaint <x> <z> <radius> <palette entry>, // terrainsculpt <x> <z> <radius> <metres>, terrainpaint <x> <z> <radius> <palette entry>,

View File

@@ -533,7 +533,7 @@ std::array<editor_mode::track_mode, 13> 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"}, {"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"}, {"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"}, {"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"}, {"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"}, {"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"}, {"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"},

View File

@@ -1428,7 +1428,7 @@ void editor_mode::render_lineside_ui()
std::array<tab, 3> const tabs{{ std::array<tab, 3> const tabs{{
{"Hectometre posts", &m_hekto.open, &m_hekto.expand, [this]() { render_hekto_ui(); }}, {"Hectometre posts", &m_hekto.open, &m_hekto.expand, [this]() { render_hekto_ui(); }},
{"Fouling points", &m_fouling.open, &m_fouling.expand, [this]() { render_fouling_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) 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")); ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", STR_C("LMB on a track: the line the objects go along"));

View File

@@ -11,6 +11,7 @@ http://mozilla.org/MPL/2.0/.
#include "application/editormode.h" #include "application/editormode.h"
#include "application/editoruilayer.h" #include "application/editoruilayer.h"
#include "application/editorprojection.h" #include "application/editorprojection.h"
#include "application/editormodeshared.h"
#include "application/application.h" #include "application/application.h"
#include "editor/editorFormat.hpp" #include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp" #include "editor/editorGeometry.hpp"
@@ -175,7 +176,28 @@ bool editor_mode::vehicle_place()
} }
tool.placed = head; tool.placed = head;
first_step_done(first_step::drive); 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<int>(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<int>(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); WriteLog("Editor: " + tool.status, logtype::generic);
return true; return true;
} }
@@ -198,7 +220,7 @@ void editor_mode::render_vehicle_ui()
tool.bank = std::make_shared<ui::vehicles_bank>(); tool.bank = std::make_shared<ui::vehicles_bank>();
tool.bank->scan_textures(); 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::SetNextItemWidth(-1.0f);
ImGui::InputTextWithHint("##vehiclesearch", STR_C("search: name, series, skin"), tool.search, sizeof(tool.search)); 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); 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::RadioButton(STR_C("With a driver, to drive"), &tool.driver, 0);
ImGui::SameLine(); ImGui::SameLine();
ImGui::RadioButton(STR_C("Nobody, parked"), &tool.driver, 1); 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)) if (tool.consist.empty() && (tool.vehicle == nullptr || tool.skin == nullptr))
ImGui::TextDisabled("%s", STR_C("Pick a vehicle from the list")); ImGui::TextDisabled("%s", STR_C("Pick a vehicle from the list"));
else else
@@ -288,6 +321,23 @@ void editor_mode::render_vehicle_ui()
} }
if (false == tool.status.empty()) if (false == tool.status.empty())
ImGui::TextWrapped("%s", tool.status.c_str()); 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<int>(tool.trainsets.size()));
std::optional<std::size_t> drop;
for (std::size_t i = 0; i < tool.trainsets.size(); ++i)
{
ImGui::PushID(static_cast<int>(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<std::ptrdiff_t>(*drop));
} }
void editor_mode::draw_vehicle_marker() const void editor_mode::draw_vehicle_marker() const

View File

@@ -173,7 +173,7 @@ void editor_mode::work_area_tools(std::vector<tool_button> &Buttons)
add(track_tab::turntable, icon::turntable, false); add(track_tab::turntable, icon::turntable, false);
add(track_tab::signals, icon::signal, true); add(track_tab::signals, icon::signal, true);
add(track_tab::lineside, icon::objects, false); 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); show_track_tab(vehicle ? track_tab::path : track_tab::lineside);
m_vehicle.expand = false == vehicle; m_vehicle.expand = false == vehicle;
}}); }});

View File

@@ -2506,8 +2506,8 @@ msgstr "co można z nim zrobić"
msgid "Vehicle on the track" msgid "Vehicle on the track"
msgstr "Pojazd na torze" 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" msgid "A trainset on the track, placed where the track was clicked, or in the middle of the path"
msgstr "Pojazd do jazdy, stawiany tam, gdzie kliknięto PPM na torze, albo w połowie odcinka.\nNie jest zapisywany do plików scenerii" msgstr "Skład na torze, stawiany tam, gdzie kliknięto tor, albo w połowie odcinka"
msgid "search: name, series, skin" msgid "search: name, series, skin"
msgstr "szukaj: nazwa, seria, malowanie" msgstr "szukaj: nazwa, seria, malowanie"
@@ -2755,8 +2755,8 @@ msgstr "Parzyste hektometry po prawej, nieparzyste po lewej"
msgid "Objects" msgid "Objects"
msgstr "Obiekty" msgstr "Obiekty"
msgid "Along the track: hectometre posts, fouling point markers, a vehicle to drive" msgid "Along the track: hectometre posts, fouling point markers, trainsets"
msgstr "Przy torze: słupki hektometrowe, wskaźniki ukresu, pojazd do jazdy" msgstr "Przy torze: słupki hektometrowe, wskaźniki ukresu, składy"
msgid "Model along the track: release where it ends" msgid "Model along the track: release where it ends"
msgstr "Model wzdłuż toru: puść tam, gdzie się kończy" 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" 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" 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" msgid "%s and %d vehicle(s) behind it stand on %s"
msgstr "%s i %d pojazd(ów) za nim stoją na %s. Nie zapisuje się ze scenerią" msgstr "%s i %d pojazd(ów) za nim stoją na %s"
msgid "Add to the train" msgid "Add to the train"
msgstr "Dodaj do składu" 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" 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" 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" msgid "A trainset on the selected track: written to the scenery on save, and to drive at once"
msgstr "Pojazd do jazdy, stawiany na zaznaczonym torze; nie trafia do plików scenerii" msgstr "Skład na zaznaczonym torze: zapisywany do scenerii przy zapisie, od razu do jazdy"
msgid "%zu groups taken apart" msgid "%zu groups taken apart"
msgstr "Rozgrupowano grup: %zu" 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" 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" 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" msgid "Drive it: Trainset 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ź" 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" msgid "the Parallel tool (N) with it"
msgstr "narzędzie Równoległy (N) z nią" 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" msgid "Click: the Parallel tool (N) with this track"
msgstr "Klik: narzędzie Równoległy (N) z tym torem" 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"