17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-31 22:49:05 +01:00

fix(roads): an axle getting on a crossroads takes the way the other axle took, one starting where it gets on

This commit is contained in:
2026-10-22 16:22:49 +02:00
parent c7f4790aa9
commit 1cc1ba27d2
11 changed files with 543 additions and 5 deletions

View File

@@ -534,6 +534,7 @@ editor_mode::editor_mode() {
ui()->set_file_actions([this]() { save(); }, [this]() { export_scenery(); });
ui()->set_new_scenery([this]() { m_newscenery_asked = true; });
ui()->set_open_scenery([this]() { m_openscenery_asked = true; });
ui()->set_memcell_focus([this](scene::basic_node *Node) { start_focus(Node); });
ui()->set_edit_menu([this]() { render_edit_menu(); });
ui()->set_tools_menu([this]() { render_track_presets_menu(); });
ui()->set_menu_options([this]() {

View File

@@ -20,6 +20,8 @@ http://mozilla.org/MPL/2.0/.
#include "world/Track.h"
#include "world/Road.h"
#include "world/RoadPoint.h"
#include "world/MemCell.h"
#include "world/Event.h"
#include "simulation/simulation.h"
#include "imgui/imgui.h"
@@ -537,6 +539,51 @@ bool editor_mode::render_roadpoint_layout(roadpoint_node::state &State, roadpoin
"It opens a few seconds after the last one is gone");
if (ImGui::Checkbox(STR_C("Paint the stop lines"), &State.stoplines))
changed = true;
// what the scenery closes the entry onto the crossing with
char memcell[128];
copy_text(memcell, sizeof(memcell), State.memcell);
ImGui::SetNextItemWidth(200.0f);
if (ImGui::InputTextWithHint(STR_C("Memory cell"), STR_C("(named after the crossing)"), memcell, sizeof(memcell), ImGuiInputTextFlags_EnterReturnsTrue))
{
auto name{lower_case(memcell)};
if (name == "none")
name.clear();
if (name.find(' ') == std::string::npos && name != State.memcell)
{
State.memcell = name;
changed = true;
}
}
item_tooltip("Events of the scenery close the entry onto the crossing with the first value of this memory cell:\n"
"above 0 the vehicles heading for the crossing are stopped, below 0 it's kept open whatever comes by the rails,\n"
"0 leaves it to the trains. The vehicles past the stops, on the crossing or leaving it, drive on.\n"
"The crossing sets the second value: 1 while the entry is closed, 0 while it's open.\n"
"Left empty, the cell is the one named after the crossing with \":block\" added, made if the scenery doesn't have it");
if (Point != nullptr)
{
auto *cell{Point->memcell()};
if (cell == nullptr)
{
ImGui::TextColored(warning, "%s", STR_C("The crossing has no name, so there's no memory cell to tell it by"));
}
else
{
ImGui::Text("\"%s\": %g %g", cell->name().c_str(), cell->Value1(), cell->Value2());
ImGui::SameLine();
ImGui::TextUnformatted(Point->closed() ? STR_C("The entry is closed") : STR_C("The entry is open"));
// the value can be tried out right here; the scenery isn't changed by it
int control{cell->Value1() > 0.0 ? 1 : cell->Value1() < 0.0 ? 2 : 0};
auto const before{control};
ImGui::RadioButton(STR_C("By the trains"), &control, 0);
ImGui::SameLine();
ImGui::RadioButton(STR_C("Closed"), &control, 1);
ImGui::SameLine();
ImGui::RadioButton(STR_C("Kept open"), &control, 2);
item_tooltip("Sets the first value of the memory cell, the way an event of the scenery would. It isn't saved with the scenery");
if (control != before)
cell->UpdateValues("", control == 1 ? 1.0 : control == 2 ? -1.0 : 0.0, 0.0, basic_event::flags::value1);
}
}
break;
}
case roadpoint_node::kind_type::spawn:

View File

@@ -36,6 +36,7 @@ editor_ui::editor_ui()
add_external_panel(&m_nodebankpanel);
add_external_panel(&m_layerspanel);
add_external_panel(&m_includespanel);
add_external_panel(&m_memcellspanel);
// the menu is a part of the layout of the editor, not a pop-up as in the driver mode
m_menu_always = true;
@@ -985,6 +986,14 @@ void editor_ui::render_menu_contents()
ui_layer::render_menu_contents();
if (ImGui::BeginMenu(STR_C("Debug")))
{
ImGui::MenuItem(STR_C("Memory cells"), nullptr, &m_memcellspanel.is_open);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Looks up the memory cells of the scenery, shows what's in them and sets it by hand"));
ImGui::EndMenu();
}
if (m_menuoptions)
m_menuoptions();
}

View File

@@ -108,6 +108,7 @@ class editor_ui : public ui_layer
void set_file_actions(std::function<void()> Save, std::function<void()> Export);
void set_new_scenery(std::function<void()> New) { m_newscenery = std::move(New); }
void set_open_scenery(std::function<void()> Open) { m_openscenery = std::move(Open); }
void set_memcell_focus(std::function<void(scene::basic_node *)> Focus) { m_memcellspanel.focus = std::move(Focus); }
// part of the main window between the docked windows, where the 3d view is seen; for the overlays placed in it
static glm::vec2 view_min()
{
@@ -197,6 +198,7 @@ class editor_ui : public ui_layer
nodebank_panel m_nodebankpanel{"Node bank", true}; // templates to insert, paint or fill with
layers_panel m_layerspanel{"Layers", true};
includes_panel m_includespanel{"Include database", false};
memcells_panel m_memcellspanel{"Memory cells", false};
std::function<void()> m_save;
std::function<void()> m_export;
std::function<void()> m_newscenery;

View File

@@ -21,11 +21,13 @@ http://mozilla.org/MPL/2.0/.
#include "application/editoruilayer.h"
#include "rendering/renderer.h"
#include "simulation/simulation.h"
#include "utilities/Logs.h"
#include "editor/editorModelSets.hpp"
#include "editor/editorFormat.hpp"
#include "editor/editorSettings.hpp"
#include "imgui/imgui_internal.h"
#include <cstring>
#include <filesystem>
#include <set>
@@ -2453,3 +2455,197 @@ void includes_panel::render()
}
ImGui::End();
}
// memory cells of the scenery
void memcells_panel::render()
{
if (false == is_open)
{
return;
}
ImGui::SetNextWindowPos(ImVec2S(560, 110), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2S(620, 560), ImGuiCond_FirstUseEver);
auto const panelname{(title.empty() ? std::string{STR_C(m_name.c_str())} : title) + "###" + m_name};
if (ImGui::Begin(panelname.c_str(), &is_open, ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse))
{
render_list();
render_cell();
}
ImGui::End();
}
void memcells_panel::render_list()
{
auto const &cells{simulation::Memory.sequence()};
// the cells are never taken away, only added, so a change of the count is a change of the set
if (m_sorted.size() != static_cast<std::size_t>(std::count_if(cells.begin(), cells.end(), [](TMemCell const *Cell) { return Cell != nullptr; })))
{
m_sorted.clear();
for (auto *cell : cells)
{
if (cell != nullptr)
{
m_sorted.emplace_back(cell);
}
}
std::stable_sort(m_sorted.begin(), m_sorted.end(), [](TMemCell const *Left, TMemCell const *Right) { return Left->name() < Right->name(); });
m_listedtime = -1.0;
}
ImGui::SetNextItemWidth(260.0f);
ImGui::InputTextWithHint("##filter", STR_C("Search"), m_filter, sizeof(m_filter));
ImGui::SameLine();
ImGui::Checkbox(STR_C("In the texts too"), &m_searchtext);
ImGui::SameLine();
if (ImGui::Button(STR_C("Log all")))
{
simulation::Memory.log_all();
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", STR_C("Writes the content of all memory cells to the log"));
}
std::string filter{m_filter};
std::transform(filter.begin(), filter.end(), filter.begin(), [](unsigned char Character) { return static_cast<char>(std::tolower(Character)); });
auto const now{ImGui::GetTime()};
auto const searchtext{m_searchtext && false == filter.empty()};
if (filter != m_listedfilter || m_listedtime < 0.0 || (searchtext && now - m_listedtime > 0.5))
{
m_listedfilter = filter;
m_listedtime = now;
m_listed.clear();
auto const matches = [&filter](std::string const &Text) {
if (filter.empty())
{
return true;
}
std::string text{Text};
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char Character) { return static_cast<char>(std::tolower(Character)); });
return text.find(filter) != std::string::npos;
};
for (auto *cell : m_sorted)
{
if (matches(cell->name()) || (searchtext && matches(cell->Text())))
{
m_listed.emplace_back(cell);
}
}
}
ImGui::TextDisabled(STR_C("Memory cells: %d, shown: %d"), static_cast<int>(m_sorted.size()), static_cast<int>(m_listed.size()));
auto const listheight{std::max(120.0f, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() * 7.0f)};
if (ImGui::BeginTable("##memcells", 5, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(-1.0f, listheight)))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn(STR_C("Name"), ImGuiTableColumnFlags_WidthStretch, 2.0f);
ImGui::TableSetupColumn(STR_C("Text"), ImGuiTableColumnFlags_WidthStretch, 1.5f);
ImGui::TableSetupColumn(STR_C("Value 1"), ImGuiTableColumnFlags_WidthStretch, 0.7f);
ImGui::TableSetupColumn(STR_C("Value 2"), ImGuiTableColumnFlags_WidthStretch, 0.7f);
ImGui::TableSetupColumn(STR_C("Track"), ImGuiTableColumnFlags_WidthStretch, 1.0f);
ImGui::TableHeadersRow();
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(m_listed.size()));
while (clipper.Step())
{
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
{
auto *cell{m_listed[row]};
ImGui::PushID(cell);
ImGui::TableNextRow();
ImGui::TableNextColumn();
auto const name{cell->name().empty() ? std::string{STR_C("(no name)")} : cell->name()};
if (ImGui::Selectable(name.c_str(), cell == m_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick))
{
select(cell);
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && focus && cell->location() != glm::dvec3{0.0})
{
focus(cell);
}
}
if (false == cell->is_exportable && ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", STR_C("Made by the simulator, it isn't saved with the scenery"));
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(cell->Text().c_str());
ImGui::TableNextColumn();
ImGui::Text("%g", cell->Value1());
ImGui::TableNextColumn();
ImGui::Text("%g", cell->Value2());
ImGui::TableNextColumn();
ImGui::TextUnformatted(cell->Track != nullptr ? cell->Track->name().c_str() : cell->asTrackName.c_str());
ImGui::PopID();
}
}
ImGui::EndTable();
}
}
void memcells_panel::select(TMemCell *Cell)
{
m_selected = Cell;
if (Cell == nullptr)
{
return;
}
auto const &text{Cell->Text()};
auto const length{std::min(text.size(), sizeof(m_text) - 1)};
std::memcpy(m_text, text.data(), length);
m_text[length] = 0;
m_value1 = Cell->Value1();
m_value2 = Cell->Value2();
}
void memcells_panel::render_cell()
{
ImGui::Separator();
if (m_selected == nullptr)
{
ImGui::TextDisabled("%s", STR_C("Pick a memory cell from the list to set what's in it. A double click shows where it is"));
return;
}
auto *cell{m_selected};
auto const location{cell->location()};
ImGui::Text(STR_C("\"%s\": %s %g %g"), cell->name().c_str(), cell->Text().c_str(), cell->Value1(), cell->Value2());
ImGui::SameLine();
ImGui::BeginDisabled(false == static_cast<bool>(focus) || location == glm::dvec3{0.0});
if (ImGui::SmallButton(STR_C("Show")))
{
focus(cell);
}
ImGui::EndDisabled();
ImGui::TextDisabled(STR_C("At %.1f %.1f %.1f"), location.x, location.y, location.z);
ImGui::Checkbox("##settext", &m_settext);
ImGui::SameLine();
ImGui::SetNextItemWidth(260.0f);
ImGui::InputText(STR_C("Text"), m_text, sizeof(m_text));
ImGui::Checkbox("##setvalue1", &m_setvalue1);
ImGui::SameLine();
ImGui::SetNextItemWidth(160.0f);
ImGui::InputDouble(STR_C("Value 1"), &m_value1, 1.0, 10.0, "%g");
ImGui::Checkbox("##setvalue2", &m_setvalue2);
ImGui::SameLine();
ImGui::SetNextItemWidth(160.0f);
ImGui::InputDouble(STR_C("Value 2"), &m_value2, 1.0, 10.0, "%g");
ImGui::BeginDisabled(false == (m_settext || m_setvalue1 || m_setvalue2));
if (ImGui::Button(STR_C("Set")))
{
auto const mask{(m_settext ? basic_event::flags::text : 0) | (m_setvalue1 ? basic_event::flags::value1 : 0) | (m_setvalue2 ? basic_event::flags::value2 : 0) |
(m_add ? basic_event::flags::mode_add : 0)};
cell->UpdateValues(m_text, m_value1, m_value2, mask);
WriteLog("Memory cell \"" + cell->name() + "\" set by hand to: " + cell->Text() + " " + std::to_string(cell->Value1()) + " " + std::to_string(cell->Value2()));
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::SetTooltip("%s", STR_C("Puts the ticked fields in the cell, the way an UpdateValues event does. The scenery files aren't changed"));
}
ImGui::SameLine();
ImGui::Checkbox(STR_C("Add to the values"), &m_add);
ImGui::SameLine();
if (ImGui::Button(STR_C("Read again")))
{
select(cell);
}
}

View File

@@ -275,6 +275,40 @@ class includes_panel : public ui_panel
bool m_statuserror{false};
};
// memory cells of the scenery: looked up by name or text, with what's in them, which can be set by hand while the scenery runs
class memcells_panel : public ui_panel
{
public:
memcells_panel(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen) {}
void render() override;
// moves the camera to specified node; provided by the editor mode
std::function<void(scene::basic_node *)> focus;
private:
// methods
void render_list();
void render_cell();
void select(TMemCell *Cell);
// members
char m_filter[128]{};
bool m_searchtext{true}; // the filter is looked for in the texts of the cells as well as in their names
std::vector<TMemCell *> m_sorted; // all cells, in the order of their names
std::vector<TMemCell *> m_listed; // the ones matching the filter
std::string m_listedfilter; // filter the list was made for
double m_listedtime{-1.0}; // when it was made; the texts change, so it's made again now and then
TMemCell *m_selected{nullptr};
char m_text[256]{}; // what's going to be put in the selected cell
double m_value1{0.0};
double m_value2{0.0};
bool m_settext{true};
bool m_setvalue1{true};
bool m_setvalue2{true};
bool m_add{false}; // the values are added to what the cell holds, the way an event with the add mode does
};
class functions_panel : public ui_panel
{