fix(cab): the line contactors lamp goes out with the pressure switch (eu07/maszyna#470)
The lamp of the open line contactors, which tells the start can be made, went out at 1 bar in the brake
cylinders, while the pressure switch cuts the start off only at 2 bar (or below 3.6 bar in the main pipe), so
with the brakes partly applied the start could be made with the lamp out. With a pressure switch fitted the lamp
now follows it; without one it goes out at 1 bar as before. The same holds for the lamp of the second unit, which
used to keep its last state between 1 bar and the cut-off. The condition is
TMoverParameters::LineContactorsLampLit().
Tests: tests/unit/test_line_contactors.cpp.
refactor: Sonar findings of PR 217
- horn_supply::pneumatic shadowed the pneumatic enumerator of the sound flags (cpp:S1117), it's horn_supply::air;
the supplies read with using enum (S6177),
- CouplerForce(): the automatic coupling refused or caught without another level of nested ifs (S134), the
same outcome: too fast, the coupling is locked; caught, coupled and locked; otherwise left to try again,
- CompressorCheck(): the power the compressor doesn't draw yet noted without a TODO (S1135),
- tests: the previous crash damage setting in an in-class initializer (S3230), the loops which stop early with
break rather than in their conditions (S886), the stand-ins of test::pair in a std::vector rather than an array
(S5945), units in the comments of the example without /* */ (S1103).
test: the cases the changes of the branch left without a test
- Cntrl.: BDelay1 to BDelay4 reach the brake delays (now through std::format), the default start types of the
ground and converter overload relays of a multiple unit and of other vehicles (out of the nested conditionals),
- the compressor: fed from the low voltage it waits from the low voltage coming on; the pressure switch stops it
above the cut-out pressure, without one the excess air is vented (the vented fraction rewritten),
- the horns: a vehicle without any air sounds as before, the threshold pressure, an electric horn on 110 V,
- the positions of a cab control below zero, and a control without position sounds.
refactor(cab): the kind of the train heating switch as a flag
Read once with the vehicle definition, rather than compared as text in each of the switch commands.
feat(cab): train heating switch springing back (MaSzyna-EU07/maszyna#85)
TrainHeating=[toggle]|impulse in the Switches: section of the vehicle definition makes the train heating switch
an unstable one: it goes up to switch the heating on, down to switch it off, and back to the middle once let go,
instead of staying where it was put. The position of such a switch isn't set from the heating state when the cab
is entered.
Tests: tests/unit/test_switches.cpp.
feat(sound): mirror sounds (MaSzyna-EU07/maszyna#84, eu07/maszyna#322)
The sound definitions of a vehicle (.mmd) take mirroropen: and mirrorclose:, played as a mirror sets off
unfolding or folding, and the locations section mirrors: <offset>... end, the offsets along the vehicle of the
mirror pairs, as for the doors (negative ahead of the centre), each pair at the sides of the vehicle. As the
animation, only the mirrors at the occupied cab sound. The start of a move is mirror_moves::motion in
vehicle/mirrors.h.
Tests: tests/unit/test_mirrors.cpp.
feat(cab): joint oil and fuel pump switch (MaSzyna-EU07/maszyna#81)
A cab control oilfuelpump_sw: switches both the oil and the fuel pump at once, with the new command
oilfuelpumptoggle (no key by default). As a two-state switch it turns both pumps off when both are on and both on
otherwise; as an impulse switch it's held to start them, like the separate pump switches. Its position follows
the pumps when the cab is entered.
refactor(editor): text fields filled without strncpy (MaSzyna-EU07/maszyna#112)
The last three strncpy calls Sonar flags (cpp:S5816), in the road and track editors, copy the text into the
ImGui field buffers with std::string::copy and terminate it right after; the text gets cut to the buffer as
before.
fix: automatic coupler crashes, horns, compressor start delay, radio and cab lever sounds
- automatic couplers don't catch above the safe collision speed (#86): with crash damage on they couple at a
closing speed of 15 km/h at most, a faster hit locks the coupling until the vehicles part and the collision
runs its course. On the way: CollisionVelocity() read a head-on 10 + 10 km/h as 14.1 km/h (quaternions turn
by half the angle), it's now taken from the touching ends; CollisionDetect() took the other vehicle's speed
the wrong way round for vehicles facing each other. Head-on hits now derail sooner, at the intended limit,
- a single horn lever can't sound both tones at once (eu07/maszyna#392),
- a skid ends once the vehicle stops on locked wheels (eu07/maszyna#479),
- electronic destination signs are blanked at the end of the timetable (eu07/maszyna#446),
- horns need the air or the voltage to sound (eu07/maszyna#290, #76): HornType=[Pneumatic]|Electric and
Horn3Type in Cntrl.; a pneumatic horn needs 0.2 MPa in the main reservoir (or the main pipe without one),
an electric one the low voltage; the lever stays where it was put,
- compressor start delay (#67): CompressorStartDelay [s] in Brake., counted from the compressor getting its
power, after the engine starts say; none by default,
- the channel and volume buttons of a radio without power do nothing (eu07/maszyna#429); the power check of
the radio is TMoverParameters::RadioPowered(),
- a lever moved smoothly sounds the positions it passes (#83).
Sonar findings in the functions changed: nesting, nested conditionals, commented-out code, std::format,
references to const. Unit tests: test::pair for two vehicles running into each other, test_couplers,
test_horn, test_wheelslip, test_compressor, test_radio, test_gauge_positions.
* refactor(sonar): use override instead of redundant virtual (cpp:S3471)
* refactor(sonar): remove empty statements, one declaration per statement (cpp:S1116, cpp:S1659)
* refactor(sonar): drop redundant lambda return types (cpp:S3574)
* refactor(sonar): prefer using over typedef (cpp:S5416)
* refactor(sonar): use =default for trivial special members (cpp:S3490)
* refactor(sonar): use nullptr literal (cpp:S4962)
* refactor(sonar): remove redundant access specifiers (cpp:S3539)
* refactor(sonar): remove redundant casts (cpp:S1905)
* refactor(sonar): use std::scoped_lock instead of std::lock_guard (cpp:S5997)
* refactor(sonar): construct elements in place with emplace_back (cpp:S6003)
* refactor(sonar): make narrowing conversions explicit (cpp:S5276)
* refactor(sonar): mark unused parameters (cpp:S1172)
Unused names are commented out; parameters used only on some platforms
are marked [[maybe_unused]].
* refactor(sonar): declare non-mutating member functions const (cpp:S5817)
Virtual functions are left untouched so overrides keep matching.
* refactor(sonar): merge collapsible nested if statements (cpp:S1066)
Comments that preceded the inner if are kept above the merged statement.
* refactor(sonar): scope single-use variables in if init-statements (cpp:S6004)
* fix(sonar): give polymorphic base classes virtual destructors (cpp:S1235)
* refactor(sonar): use try_emplace for map insertion (cpp:S6030)
* refactor(sonar): use auto where the type is spelled out on the right (cpp:S5827)
* refactor(sonar): remove redundant parentheses (cpp:S1110)
* refactor(sonar): document intentionally empty functions (cpp:S1186)
* refactor(sonar): remove dead stores (cpp:S1854)
Side effects (e.g. LISTLINE++) are kept.
* refactor(sonar): remove unused variables, captures and private fields (cpp:S1481, cpp:S1068)
* refactor(sonar): use structured bindings for pair access (cpp:S6005)
* refactor(sonar): use range-based loops and ranges algorithms (cpp:S5566)
* refactor(sonar): split chained assignments (cpp:S1121)
* refactor(sonar): add default cases to switch statements (cpp:S3562)
* refactor(sonar): use std::size for array lengths (cpp:S7127)
* refactor(sonar): use std::ranges algorithms and std::erase(_if) (cpp:S6197)
* refactor(sonar): access static members through the class (cpp:S2209)
* refactor(sonar): add braces around nested if-else (cpp:S5261)
* refactor(sonar): avoid side effects inside typeid operands (cpp:S5279)
* refactor(sonar): drop top-level const from by-value return types (cpp:S5951)
* refactor(sonar): declare variables separately from their type definitions (cpp:S3646)
* refactor(sonar): declare loop counters in the for statement (cpp:S5955)
* refactor(sonar): rely on class template argument deduction (cpp:S6012)
* refactor(sonar): inherit base constructors instead of forwarding them (cpp:S5952)
* refactor(sonar): rename locals that shadow outer declarations (cpp:S1117)
Only local variables and definition parameters are renamed; shadowing
struct fields and enumerators in headers are left for a separate change.
* refactor(sonar): reduce enum qualification with using enum (cpp:S6177)
Applied only where no unqualified name in the block could be captured by
the introduced enumerators.
* refactor(sonar): mark converting constructors explicit (cpp:S1709)
sound_source and the gl::object conversion operators stay implicit; they
are relied upon by copy-list-initialisation and GL calls.
* refactor(sonar): initialise members in initialiser lists or declarations (cpp:S3230)
* refactor(sonar): make read-only local references and pointers const (cpp:S5350)
* refactor(sonar): take read-only pointer and reference parameters as const (cpp:S995)
TTrain command handlers keep their signature, it is fixed by the handler table.
* refactor(sonar): pass expensive-to-copy parameters by const reference (cpp:S1238)
Signatures declared in files touched by PR 206 (Driver.h, EU07.cpp) and
virtual overrides are left unchanged.
* refactor(sonar): use transparent comparators for string-keyed maps (cpp:S6045)
std::unordered_map instances and map types exposed in headers used by
other modules are left as they are.
* fix(sonar): do not use UI text as a printf format string (cpp:S5281)
Text lines containing '%' would otherwise be interpreted as format
specifiers by ImGui::TextColored.
* refactor(sonar): use std::bind_front for callbacks (cpp:S5995)
* refactor(sonar): drop const_cast on string literals passed to the Python C API (cpp:S859)
* refactor(sonar): take read-only string parameters as std::string_view (cpp:S6009)
The Windows messaging helpers keep std::string, they need c_str().
* refactor(sonar): make read-only global tables const (cpp:S5421)
Mutable global state (flags, timers, singletons) is left as is.
* refactor(sonar): replace single-case switch statements with if (cpp:S1301)
* refactor(sonar): format UUIDs and log timestamps with std::format (cpp:S6494, cpp:S5945)
* refactor(sonar): use std::array for local fixed-size arrays (cpp:S5945)
Arrays that are class members or part of headers are left unchanged.
* fix(sonar): test the bearing damage bit, not bit 0 (cpp:S872)
'DamageFlag & dtrain_bearing > 0' parsed as 'DamageFlag & (dtrain_bearing > 0)',
i.e. DamageFlag & 1, so bearing friction reacted to the wrong damage flag.
* refactor(sonar): assorted small fixes
- empty() instead of size() comparisons (cpp:S1155)
- member initialisers in declaration order (cpp:S3229)
- no redundant c_str() when a std::string is accepted (cpp:S7121)
- contains() on associative containers (cpp:S6171)
- const reference instead of a copying range-for (cpp:S6032)
- starts_with() for prefix checks (cpp:S6178)
- no std::move on temporaries (cpp:S5274)
- has_value() on std::optional (cpp:S7172)
- bool literals for bool members (cpp:S5820)
- inline only on the definition (cpp:S3548)
- explicit base access specifier (cpp:S5965)
- explicit element access instead of dereferencing an array (cpp:S7116)
- rely on the C++20 rewritten operator!= (cpp:S6186)
* refactor(sonar): more small fixes
- std::format instead of iostream manipulators in to_string/to_hex_str (cpp:S6495);
output verified identical, negative hex values keep the unsigned form
- remove an empty compound statement (cpp:S108)
- drop an always-false unsigned < 0 branch (cpp:S1768)
- explicit lambda capture (cpp:S3608)
- std::make_unique (cpp:S5950)
* chore: strip stray carriage returns left in TTrain constructor comments
* fix: keep quantize() in its own value type
The S5276 change cast the result of the generic quantize<Type_>() to
unsigned int, which truncated float/double callers (shadow angle slider,
editor rotation snapping, AI speed control steps) and broke negative values.
* fix(sonar): use integer loop counters in segment sampling (cpp:S2193)
* refactor(sonar): clean up issues reported on PR 209
- remove locals left unused after the dead-store cleanup (cpp:S1481, cpp:S1854)
- unnamed unused parameter (cpp:S1172)
- single-use declarations moved into if init-statements (cpp:S6004)
- auto for casted initialisers (cpp:S5827)
- const references and pointers where nothing is modified (cpp:S5350)
- rename structured bindings shadowed by inner declarations (cpp:S1117)
- Terrain work mode shows a browser of the textures folder in place of the
node bank; textures/materials can be dragged onto the palette entries
(replacing the material), the material field and the water material field
- Ctrl+Z/Ctrl+Y for heightmap terrain changes: a brush stroke or an
operation is one step of the editor history
- Ground conversion reads only the files of the terrain directives
(.txtf with origin/rotate, or .btf without text), paints the chunks with
the materials of the triangles, drops only the shapes of those files
- Fix: ground grids converted out-of-range floats to int, so whole-scene
queries returned the triangles of a single cell (shifted chunks)
- Fix: terrain layers copied from textures already uploaded without their
data stayed grey
- Fix: ImGui ID conflict between terrain tool radios and section headers
- Fix: layers of terrain .txtf files named with the scenery/ prefix and
could not be saved
- Editor selftest commands for the terrain
* Text displays: accept characters above 127, in windows-1250 or utf-8
A text display (sub-model of type "text") looks up the sub-model of each character
in a 256 entry table, and indexed it with a plain char. With char being signed, any
character above 127 gave a negative index: reading the text went outside the table,
and so did filling the table if a character sub-model had such a name. In effect the
displays could only show ASCII, and a text with national characters could crash.
The lookup is now keyed by unicode code point. Both the displayed text and the names
of the character sub-models are decoded as utf-8 if they are valid utf-8, and as
windows-1250 otherwise, so legacy (ansi) sceneries and models work next to utf-8
ones, and the two can be mixed. A short ansi text can be valid utf-8 by accident
(two upper case accented letters in a row will do); when both readings are possible
the one which finds more of its characters in the model is used.
The rest is unchanged: the first character of the text still isn't displayed, the
characters are still drawn in the translucent pass only. A missing text pointer no
longer gets dereferenced.
* Text displays: prepare the characters once for each text
The sequence of character sub-models was cached for the last text only, so
displays of several instances drawn in turn decoded their texts again on
every draw. It is now kept for each text the display shows; the texts of the
instances don't change, so a draw costs a single lookup.
Also the comments added in the renderers are now in English.
* Text displays: move the text decoding to utilities
utf8_to_utf32() and win1250_to_utf32(), with the windows-1250 table, now sit
next to win1250_to_utf8(). The existing function maps only the Polish letters
and passes other bytes above 127 through, so it can't serve the displays.
* Text displays: address the SonarCloud findings
std::array instead of the C-style arrays, the utf-8 sequence length worked
out in a function of its own instead of nested conditional operators,
std::byte for the bit operations, std::ranges::count_if and contains().
The character loop of the renderers moved to Render_Alpha_text(), which
takes the nesting back to the limit, and the static pasText is accessed
through the class.
std::uniform_real/int_distribution has UB when min>max. The old "fancy Random"
used interpolate(a,b,t) which tolerated a>b; the migration to
std::uniform_real_distribution did not, and only one inverted call site was
fixed by hand. Swap the bounds inside Random()/Random(int)/LocalRandom() so
every call site is safe, and reorder the two inverted literal calls in
Driver.cpp (fActionTime buzzer / departure delay) for readability.
The dumb3d->glm / utilities-simplification refactor replaced the old custom
clamp() (which tolerated Lo>Hi, returning the upper bound) with std::clamp,
where inverted bounds are undefined behaviour. Only a few sites were patched
afterwards, by hand, with std::minmax plumbing (incl. a stray `static` inside
a per-axle loop). Others silently produced wrong results in the AI braking /
acceleration path.
Add a safe_clamp() helper (normalizes inverted bounds) and use it ONLY where
Lo<=Hi cannot be proven at compile time (config min/max pairs, sign-dependent
expressions, container underflow). Sites with constant bounds or [0, x>=0]
keep std::clamp. Remove the manual std::minmax workarounds and rewrite the
damaged-track jolt code value-first.
Consolidates platform-specific config-directory logic (APPDATA on Windows,
~/Library/Application Support on macOS, ~/.config on Linux) into a single
user_config_path(filename) function in utilities/utilities.{cpp,h}, removing
copy-pasted #ifdef blocks from application.cpp, editor/editorSettings.cpp,
input/{keyboard,gamepad,drivermouse,uart}input.cpp.
Three small source tweaks that let the OpenGL renderer build and run
on Apple Silicon without affecting the Windows/Linux paths:
- stdafx.h: use GLM_FORCE_NEON on ARM64 instead of GLM_FORCE_AVX2.
AVX2 pulls <immintrin.h>, which clang on ARM64 rejects because
the x86 intrinsics are unimplemented.
- utilities/utilities.cpp: provide an Apple libc++ fallback for
Now() using localtime_r + strftime, since
std::chrono::current_zone() is not yet implemented there.
- audio/audio.h: prefer Khronos-style <AL/al.h> headers when they
are on the include path (brew openal-soft) and fall back to the
deprecated Apple OpenAL.framework's <OpenAL/al.h> only otherwise.