From dae97f71694439d5b15626ee782a09244b54ee3e Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 11 Oct 2026 19:06:44 +0200 Subject: [PATCH] feat(editor): every switch laid gets its drive at once, also the one made by pulling a free end into a straight (named z1, z2... like the others): the drive marked default: true in its //$e description (mech_komin) until another is picked, on the diverging side or the other; the drive goes into the same undo step as the switch; a switch with a drive puts it on the other side with one button, as one undo step; drive_headers.py marks mech_komin as the default; self-test commands drives and driveflip; Polish translation --- application/editormode.h | 4 ++ application/editormodeselftest.cpp | 22 +++++++ application/editormodetrack.cpp | 94 ++++++++++++++++++++++++++++-- editor/editorIncludeInfo.cpp | 1 + editor/editorIncludeInfo.hpp | 1 + lang/editor_pl.po | 21 +++++++ tools/editor/drive_headers.py | 3 +- 7 files changed, 141 insertions(+), 5 deletions(-) diff --git a/application/editormode.h b/application/editormode.h index a888e5ce..7e18f2f5 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -933,6 +933,7 @@ class editor_mode : public application_mode, private editor_track::observer { std::string name; std::array files; + bool preferred{false}; }; std::vector drives; bool drives_scanned{false}; @@ -964,6 +965,9 @@ class editor_mode : public application_mode, private editor_track::observer void scan_switch_drives(); std::string switch_name_for_new() const; bool place_switch_drive(TTrack &Switch); + bool flip_switch_drive(TTrack &Switch); + void advance_switch_name(); + void join_last_steps(); bool render_switch_drive_choice(); int m_turnout_template{0}; struct turntable_template diff --git a/application/editormodeselftest.cpp b/application/editormodeselftest.cpp index 58d888a3..38092bf2 100644 --- a/application/editormodeselftest.cpp +++ b/application/editormodeselftest.cpp @@ -405,6 +405,28 @@ void editor_mode::selftest_step() m_switch.drive_side = side; WriteLog("SELFTEST switchdrive: " + (index >= 0 && index < static_cast(m_switch.drives.size()) ? m_switch.drives[index].name : std::string{"none"})); } + else if (command == "drives") + { + std::string name; + words >> name; + scan_switch_drives(); + std::string files; + for (auto const &standing : standing_templates([](std::string const &File) { return File.find("napedy/") != std::string::npos; })) + if (false == standing.values.empty() && standing.values[0] == name) + files += " " + standing.file; + for (scene::instance_handle handle = 1; handle <= scene::Layers.instance_count(); ++handle) + if (scene::Layers.tracked(handle) && scene::Layers.instance(handle).file != nullptr && scene::Layers.instance(handle).file->find("napedy") != std::string::npos) + files += format(" [%d %s removed %d dead %d]", static_cast(handle), scene::Layers.instance(handle).file->c_str(), scene::Layers.instance(handle).removed ? 1 : 0, scene::Layers.instance(handle).dead ? 1 : 0); + WriteLog("SELFTEST drives " + name + ": chosen " + (m_switch.drive >= 0 ? m_switch.drives[m_switch.drive].name : std::string{"none"}) + ", side " + std::to_string(m_switch.drive_side) + ", standing" + files + ", steps " + std::to_string(m_history.size())); + } + else if (command == "driveflip") + { + std::string name; + words >> name; + auto *track{simulation::Paths.find(name)}; + auto const done{track != nullptr && flip_switch_drive(*track)}; + WriteLog("SELFTEST driveflip " + name + ": " + (done ? "done " : "failed ") + m_switch.status); + } else if (command == "drivetilt") { std::string name; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index 09137f45..fed57e3a 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1927,7 +1927,12 @@ void editor_mode::render_turnout_ui() render_track_name(*track); render_switch_record(*track); if ((simulation::Events.FindEvent(track->name() + "+") != nullptr && simulation::Events.FindEvent(track->name() + "-") != nullptr) || (m_track_name.track == track && m_track_name.uses.includes > 0)) + { ImGui::TextDisabled(STR_C("Drive: events %s+ and %s- switch it, also in the map"), track->name().c_str(), track->name().c_str()); + if (ImGui::Button(STR_C("Drive to the other side"))) + flip_switch_drive(*track); + item_tooltip("Puts the drive of the switch on the other side of the track: the same drive, its variant for the other side"); + } else { ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), "%s", STR_C("No drive: the switch can't be switched during the game")); @@ -5769,8 +5774,9 @@ void editor_mode::finish_extend() std::vector> states; std::vector created; std::vector removed; - if (false == place_switch_on_straight(plan.target, plan.shape, origin, plan.along, plan.direction, plan.side, false, states, created, removed)) + if (false == place_switch_on_straight(plan.target, plan.shape, origin, plan.along, plan.direction, plan.side, false, states, created, removed, switch_name_for_new())) return; + auto *placed{*std::find_if(created.begin(), created.end(), [](TTrack const *Track) { return Track != nullptr && Track->eType == tt_Switch; })}; if (glm::distance(plan.insert.points[segment_data::point::start], plan.insert.points[segment_data::point::end]) > 0.01) { auto *insert{editor_track::create_path(*origin, plan.insert)}; @@ -5779,6 +5785,9 @@ void editor_mode::finish_extend() } push_track_snapshot(std::move(states), std::move(created), std::move(removed)); straight_refresh(); + if (place_switch_drive(*placed)) + join_last_steps(); + advance_switch_name(); return; } auto const pieces{extend_pieces()}; @@ -6552,8 +6561,13 @@ void editor_mode::switch_placed(std::vector const &Created) ui()->set_node(m_node); if (slip) return; - if (m_switch.drive >= 0) - place_switch_drive(**found); + if (place_switch_drive(**found)) + join_last_steps(); + advance_switch_name(); +} + +void editor_mode::advance_switch_name() +{ std::string name{m_switch.name}; auto digits{name.size()}; while (digits > 0 && std::isdigit(static_cast(name[digits - 1]))) @@ -6595,9 +6609,11 @@ void editor_mode::scan_switch_drives() family = std::prev(tool.drives.end()); } family->files[hand * 2 + drive] = entry.file; + family->preferred |= info.switch_default; } std::sort(tool.drives.begin(), tool.drives.end(), [](switch_tool::drive_family const &A, switch_tool::drive_family const &B) { return A.name < B.name; }); - tool.drive = tool.drives.empty() ? -1 : 0; + auto const preferred{std::find_if(tool.drives.begin(), tool.drives.end(), [](switch_tool::drive_family const &Family) { return Family.preferred; })}; + tool.drive = tool.drives.empty() ? -1 : preferred != tool.drives.end() ? static_cast(preferred - tool.drives.begin()) : 0; } std::string editor_mode::switch_name_for_new() const @@ -6615,6 +6631,7 @@ std::string editor_mode::switch_name_for_new() const bool editor_mode::place_switch_drive(TTrack &Switch) { auto &tool{m_switch}; + scan_switch_drives(); if (tool.drive < 0 || tool.drive >= static_cast(tool.drives.size()) || Switch.eType != tt_Switch || Switch.m_paths.size() < 2) return false; auto const &main{Switch.m_paths[0]}; @@ -6659,6 +6676,75 @@ bool editor_mode::place_switch_drive(TTrack &Switch) return true; } +bool editor_mode::flip_switch_drive(TTrack &Switch) +{ + auto &tool{m_switch}; + scan_switch_drives(); + auto const plain = [](std::string Text) { + std::replace(Text.begin(), Text.end(), '\\', '/'); + std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char Character) { return static_cast(std::tolower(Character)); }); + return Text; + }; + std::map> variants; + for (int family = 0; family < static_cast(tool.drives.size()); ++family) + for (int index = 0; index < 4; ++index) + if (false == tool.drives[family].files[index].empty()) + variants[plain(tool.drives[family].files[index])] = {family, index}; + auto const name{plain(Switch.name())}; + auto const standing{standing_templates([&](std::string const &File) { return variants.count(File) > 0; })}; + auto const found{std::find_if(standing.begin(), standing.end(), [&](standing_template const &Drive) { return plain(Drive.values[0]) == name; })}; + if (found == standing.end()) + { + tool.status = format(STR_C("No drive of the switch %s among the described drives"), Switch.name().c_str()); + return false; + } + auto const [family, index]{variants[found->file]}; + template_item item; + item.file = tool.drives[family].files[index ^ 1]; + if (item.file.empty()) + { + tool.status = format(STR_C("The drive %s has no variant with the drive on the other side"), tool.drives[family].name.c_str()); + return false; + } + item.location = found->location; + item.yaw = found->yaw; + item.described = true; + item.track = Switch.name(); + auto const angle = [&](std::size_t const Index) { + double value{0.0}; + if (Index < found->values.size()) + std::sscanf(found->values[Index].c_str(), "%lf", &value); + return value; + }; + item.tilt = {angle(5), angle(6)}; + if (remove_templates({found->instance}) == 0) + { + tool.status = STR_C("The drive of this switch can't be changed in its file"); + return false; + } + std::string error; + if (place_templates({item}, error) == 0) + { + scene::Layers.removed(found->instance, false); + m_history.pop_back(); + tool.status = STR_C("The drive couldn't be placed: ") + error; + return false; + } + join_last_steps(); + tool.status = format((index ^ 1) & 1 ? STR_C("Switch %s: the drive %s, on the right now") : STR_C("Switch %s: the drive %s, on the left now"), Switch.name().c_str(), item.file.c_str()); + return true; +} + +void editor_mode::join_last_steps() +{ + if (m_history.size() < 2) + return; + auto last{std::move(m_history.back())}; + m_history.pop_back(); + auto &previous{m_history.back()}; + previous.instances.insert(previous.instances.end(), last.instances.begin(), last.instances.end()); +} + bool editor_mode::render_switch_drive_choice() { auto &tool{m_switch}; diff --git a/editor/editorIncludeInfo.cpp b/editor/editorIncludeInfo.cpp index 1a3ea1e6..f0dbdf9d 100644 --- a/editor/editorIncludeInfo.cpp +++ b/editor/editorIncludeInfo.cpp @@ -250,6 +250,7 @@ bool parse(template_text const &Text, include_info &Info, std::string &Error) { Info.switch_hand = drive["hand"].as(""); Info.switch_drive = drive["drive"].as(""); + Info.switch_default = drive["default"].as(false); } if (auto const signal{document["signal"]}; signal.IsDefined() && signal.IsMap()) { diff --git a/editor/editorIncludeInfo.hpp b/editor/editorIncludeInfo.hpp index d2565a2f..67059bb7 100644 --- a/editor/editorIncludeInfo.hpp +++ b/editor/editorIncludeInfo.hpp @@ -37,6 +37,7 @@ struct include_info std::string description; std::string switch_hand; // a switch drive: side of the diverging track, left or right, looking from the start of the switch std::string switch_drive; // and the side the drive stands on + bool switch_default{false}; // the drive put with a new switch until another is picked double turntable_length{0.0}; int turntable_angles{0}; int turntable_positions{0}; diff --git a/lang/editor_pl.po b/lang/editor_pl.po index d98cdcb1..05d5bfca 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -5421,3 +5421,24 @@ msgstr "Miejsce na prostej: %.2f m do tyłu, %.2f m do przodu" msgid "Click: edit this switch, drag: slide it along the straight" msgstr "Klik: edytuj ten rozjazd, przeciągnij: przesuń go wzdłuż prostej" + +msgid "Drive to the other side" +msgstr "Napęd na drugą stronę" + +msgid "Puts the drive of the switch on the other side of the track: the same drive, its variant for the other side" +msgstr "Stawia napęd zwrotnicy po drugiej stronie toru: ten sam napęd, jego wariant na drugą stronę" + +msgid "No drive of the switch %s among the described drives" +msgstr "Wśród opisanych napędów nie ma napędu zwrotnicy %s" + +msgid "The drive %s has no variant with the drive on the other side" +msgstr "Napęd %s nie ma wariantu z napędem po drugiej stronie" + +msgid "The drive of this switch can't be changed in its file" +msgstr "Napędu tej zwrotnicy nie da się zmienić w jego pliku" + +msgid "Switch %s: the drive %s, on the right now" +msgstr "Zwrotnica %s: napęd %s, teraz po prawej" + +msgid "Switch %s: the drive %s, on the left now" +msgstr "Zwrotnica %s: napęd %s, teraz po lewej" diff --git a/tools/editor/drive_headers.py b/tools/editor/drive_headers.py index cb620814..2f550917 100644 --- a/tools/editor/drive_headers.py +++ b/tools/editor/drive_headers.py @@ -16,6 +16,7 @@ LINEMARK = '//$e' FOLDER = 'scenery/tory/napedy' PATTERN = re.compile(r'^(ele1?|mech[12]?)-rz([lp])-([lp])((?:_[a-z]+)*)\.inc$') KINDS = {'ele': 'elektryczny', 'mech': 'mechaniczny'} +DEFAULT = 'mech_komin' def examine(path, file): @@ -48,7 +49,7 @@ def examine(path, file): 'name: ' + family, 'category: switch drive', 'description: "' + description + '"', - 'switch: {hand: %s, drive: %s}' % (hand, side), + 'switch: {hand: %s, drive: %s%s}' % (hand, side, ', default: true' if family == DEFAULT else ''), 'params:', ' - {id: 1, role: track, label: Switch}', ' - {id: 2, role: pos.x}',