17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 12:09:19 +02:00
Files
maszyna/world/Event.cpp
maj00r 2660d14565 Stream the scenery: a baked terrain heightfield, a logic container, and prefabs
Today's work on the binary scene format plan (binary-scene-format-plan.md), from the
first measurement to a terrain that bakes itself on first run and streams from a
thread. The step-by-step history is kept on backup/scene-streaming-format-presquash-0916.

Measuring first. -loadprofile times a scenery load by stage and by node kind and reports
node counts and resident memory. On l204 terrain is 95% of the bytes and 99.8% of the
nodes; on quarkmce2007 vehicles, not models, dominate what is left. The terrain turned
out to be a heightfield in all but name, a ribbon covering 12% of its bounding box.

The heightfield. scene/heightfieldformat.h is the .ehf layout: Morton-ordered tiles
of 128 samples with one shared edge sample, a point-sampled mip chain, zstd per tile,
a nodata sentinel, a material palette with a measured tiling per material, and a stamp
of the rules that decided what counted as terrain. scene/heightfieldreader.h reads it
for the tool, the renderer and the loader alike, and scene/terraincooker.h writes it,
shared by the terraincook tool and the engine so a first-run bake is the file the tool
would have made. The cooker rasterises every triangle where it is drawn (through
scene::place_in_world, the placement the region applies too), keeps an upper surface
laid within half a metre over another and the lower of two further apart, and then
asks each triangle whether the heightfield shows it: laid, reaching a sample, keeping
nearly all it claimed, and with the ground around it drawn at every level out to 2 km.

Baking on first run. simulation/terrainbake bakes <scenario>.ehf the first time a
scenery is opened, choosing the grid step from the first 200,000 vertices, and on later
loads leaves out the triangles its node table says the heightfield shows. Everything
else - trees, grass tufts, gantries, bridge decks - is drawn as written, so a bad bake
can at worst draw something twice, and the heightfield sits 2 cm under the triangles so
the original wins there. Nodes are keyed by file, line, include parameters and
placement; an edited file changes its keys and those nodes are drawn again. A bake under
older rules, or without its table, is thrown away and redone. A launcher copy named
$scenario shares the scenario's files. -nobaketerrain shows the triangles as written.
A scenery can still name hand-cooked fields, several at different steps.

Drawing and streaming. rendering/terrainclipmap draws resident tiles at the mip level
their distance earns, deriving the grid from gl_VertexID and texturing in world space
with the scenery's own materials. rendering/terraintileloader reads and decodes tiles on
a thread of its own; the renderer rescans what is in range only after moving a quarter
of a tile, asks for what is missing nearest first, uploads at most 32 tiles a frame,
drops arrivals the camera has moved past, and keeps drawing a tile at the level it has
until the right one arrives. The terrain has its own range, terrainrange in the ini and
a slider in the debug panel, 20 km by default and independent of basedrawrange; the far
plane of an ordinary view is pushed out to it. The debug panel shows the range, tiles in
view, on the gpu, wanted and queued, and how many sit at each level.

Events. The event queue moved out of the events into a binary heap with a deterministic
tie-break, and an event is handed its activator instead of storing it, so basic_event
is a definition and nothing else.

The logic container. scene/logicformat.h and scene/logiccontainer write a scenery's
events, their targets and sibling chains as flat, deterministic arrays with a schema
hash that refuses a mismatched build. simulation/logiccook cooks it from the loaded
state whenever it is missing, stale or unreadable, reads it straight back to check it,
and resolves each target to a position in the registry its event kind searches.
-cooklogic forces a cook, -verifylogic only checks. Nothing reads it at load yet.

Prefabs. scene/prefab describes a signal by the aspects it can show - lights, speeds,
animation channels, a sound - instead of pasting an .inc per variant, and expands an
instance to ordinary scenery text. Tried on a light signal and a shape signal.

Screenshots without a person present: -shot x,y,z[,yaw[,pitch]], -shotsettle, -shotstay.
2026-09-16 23:18:09 +02:00

2645 lines
84 KiB
C++

module;
#include <utility>
#include <chrono>
#include <mutex>
#include <sstream>
#include <type_traits>
#include <array>
#include <cstddef>
#include <tuple>
#include <deque>
#include <list>
#include <unordered_map>
#include <GLFW/glfw3.h>
#include <lua.hpp>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <ios>
#include <limits>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include "global_include/interfaces/ITexture_macros.h"
#include "utilities/Globals_macros.h"
#include "vehicle/DynObj_macros.h"
module eu07.simcore;
import eu07.input.messaging;
import eu07.simulation.simulation;
import eu07.rendering.renderer;
import eu07.scripting.lua;
import eu07.utilities.utilities;
import :evlaunch;
import eu07.utilities.globals;
import eu07.simulation.simulationtime;
import eu07.simulation.simulationsounds;
import eu07.utilities.timer;
import eu07.widgets.map_objects;
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
/*
MaSzyna EU07 locomotive simulator
Copyright (C) 2001-2004 Marcin Wozniak and others
*/
void
basic_event::event_conditions::bind( basic_event::node_sequence *Nodes ) {
memcompare_cells = Nodes;
}
void
basic_event::event_conditions::init() {
tracks.clear();
if( flags & ( flags::track_busy | flags::track_free ) ) {
for( auto &target : *memcompare_cells ) {
tracks.emplace_back( simulation::Paths.find( std::get<std::string>( target ) ) );
if( tracks.back() == nullptr ) {
// legacy compatibility behaviour, instead of disabling the event we disable the memory cell comparison test
// m_ignored = true; // deaktywacja
// ErrorLog( "Bad event: track \"" + std::get<std::string>( target ) + "\" referenced in event \"" + asName + "\" doesn't exist" );
flags &= ~( flags::track_busy | flags::track_free ); // zerowanie flag
}
}
}
}
bool
basic_event::event_conditions::test() const {
if( flags == 0 ) {
return true; // bezwarunkowo
}
// if there's conditions, check them
if( flags & flags::probability ) {
auto const randomroll { static_cast<float>( Random() ) };
WriteLog(
"Test: Random integer - ["
+ std::to_string( randomroll ) + "] / [" + std::to_string( probability )
+ "] - "
+ ( randomroll > probability ? "Pass" : "Fail" ) );
if( randomroll > probability ) {
return false;
}
}
if( flags & flags::track_busy ) {
auto trackbusyresult { true };
std::string trackbusylog { "Test: Track busy - " };
for( auto *track : tracks ) {
if( true == track->IsEmpty() ) {
trackbusylog += "[" + track->name() + "]";
trackbusyresult = false;
break;
}
else {
auto const &vehicles { track->Dynamics };
if( trackbusylog.back() == ']' ) {
trackbusylog += ", ";
}
trackbusylog += "[" + vehicles.front()->asName + "] @ [" + track->name() + "]";
}
}
WriteLog(
trackbusylog
+ " - "
+ ( trackbusyresult ? "Pass" : "Fail" ) );
if( false == trackbusyresult ) {
return false;
}
}
if( flags & flags::track_free ) {
auto trackfreeresult { true };
std::string trackfreelog{ "Test: Track free - " };
for( auto *track : tracks ) {
if( false == track->IsEmpty() ) {
auto const &vehicles { track->Dynamics };
trackfreelog += "[" + vehicles.front()->asName + "] @ [" + track->name() + "] - ";
trackfreeresult = false;
break;
}
}
WriteLog(
trackfreelog
+ ( trackfreeresult ? "Pass" : "Fail" ) );
if( false == trackfreeresult ) {
return false;
}
}
if( flags & ( flags::text | flags::value1 | flags::value2 ) ) {
// porównanie wartości
for( auto &cellwrapper : *memcompare_cells ) {
auto *cell { static_cast<TMemCell *>( std::get<scene::basic_node *>( cellwrapper ) ) };
if( cell == nullptr ) {
// ErrorLog( "Event " + asName + " trying conditional_memcompare with nonexistent memcell" );
continue; // though this is technically error, we treat it as a success to maintain backward compatibility
}
auto const comparisonresult =
cell->Compare(
memcompare_text, memcompare_value1, memcompare_value2,
flags,
memcompare_text_operator, memcompare_value1_operator, memcompare_value2_operator,
memcompare_pass );
auto const combiner { (
memcompare_pass == comparison_pass::all ? " && " :
memcompare_pass == comparison_pass::any ? " || " :
memcompare_pass == comparison_pass::none ? " !! " :
" ?? " ) };
std::string comparisonlog = "Test: MemCompare - " + cell->name() + " - ";
comparisonlog +=
( TestFlag( flags, flags::text ) ?
"[" + cell->Text() + "] " + to_string( memcompare_text_operator ) + " [" + memcompare_text + "]" :
"[*]" )
+ combiner;
comparisonlog +=
( TestFlag( flags, flags::value1 ) ?
"[" + to_string( cell->Value1(), 2 ) + "] " + to_string( memcompare_value1_operator ) + " [" + to_string( memcompare_value1, 2 ) + "]" :
"[*]" )
+ combiner;
comparisonlog +=
( TestFlag( flags, flags::value2 ) ?
"[" + to_string( cell->Value2(), 2 ) + "] " + to_string( memcompare_value2_operator ) + " [" + to_string( memcompare_value2, 2 ) + "]" :
"[*]" )
+ " - ";
comparisonlog += comparisonresult ? "Pass" : "Fail";
WriteLog( comparisonlog );
if( false == comparisonresult ) {
return false;
}
}
}
// if we made it here it means we passed all checks
return true;
}
void
basic_event::event_conditions::deserialize( cParser &Input ) {
// przetwarzanie warunków, wspólne dla Multiple i UpdateValues
std::string token;
while( true == Input.getTokens()
&& false == (token = Input.peek()).empty()
&& false == basic_event::is_keyword(token) ) {
if( token == "trackoccupied" ) {
flags |= flags::track_busy;
}
else if( token == "trackfree" ) {
flags |= flags::track_free;
}
else if( token == "propability" || token == "probability") { //remove propability in few years after changing old scenery scripts 01.2021
flags |= flags::probability;
Input.getTokens();
Input >> probability;
}
else if( token == "memcompare" ) {
Input.getTokens( 1, false ); // case sensitive
if( Input.peek() != "*" ) //"*" - nie brac command pod uwage
{ // zapamiętanie łańcucha do porównania
Input >> memcompare_text;
flags |= flags::text;
}
Input.getTokens();
if( Input.peek() != "*" ) //"*" - nie brac val1 pod uwage
{
Input >> memcompare_value1;
flags |= flags::value1;
}
Input.getTokens();
if( Input.peek() != "*" ) //"*" - nie brac val2 pod uwage
{
Input >> memcompare_value2;
flags |= flags::value2;
}
}
else if( token == "memcompareex" ) {
memcompare_pass = comparison_pass_from_string( Input.getToken<std::string>() );
Input.getTokens();
if( Input.peek() != "*" ) //"*" - nie brac pod uwage
{ // two tokens, operator followed by comparison value
std::string operatorstring;
Input >> operatorstring;
memcompare_text_operator = comparison_operator_from_string( operatorstring );
Input.getTokens( 1, false ); // case sensitive
Input >> memcompare_text;
flags |= flags::text;
}
Input.getTokens();
if( Input.peek() != "*" ) //"*" - nie brac pod uwage
{ // two tokens, operator followed by comparison value
std::string operatorstring;
Input >> operatorstring;
memcompare_value1_operator = comparison_operator_from_string( operatorstring );
Input.getTokens();
Input >> memcompare_value1;
flags |= flags::value1;
}
Input.getTokens();
if( Input.peek() != "*" ) //"*" - nie brac pod uwage
{ // two tokens, operator followed by comparison value
std::string operatorstring;
Input >> operatorstring;
memcompare_value2_operator = comparison_operator_from_string( operatorstring );
Input.getTokens();
Input >> memcompare_value2;
flags |= flags::value2;
}
}
}
}
// sends basic content of the class in legacy (text) format to provided stream
void
basic_event::event_conditions::export_as_text( std::ostream &Output ) const {
if( flags != 0 ) {
Output << "condition ";
if( ( flags & flags::track_busy ) != 0 ) {
Output << "trackoccupied ";
}
if( ( flags & flags::track_free ) != 0 ) {
Output << "trackfree ";
}
if( ( flags & flags::probability ) != 0 ) {
Output
<< "probability "
<< probability << ' ';
}
if( ( flags & ( flags::text | flags::value1 | flags::value2 ) ) != 0 ) {
// NOTE: export doesn't preserve original memcompare condition, these are all upgraded to memcompareex format for simplicity
Output
<< "memcompareex "
<< ( ( flags & flags::text ) == 0 ? "*" : memcompare_text + ' ' + to_string( memcompare_text_operator ) ) << ' '
<< ( ( flags & flags::value1 ) == 0 ? "*" : std::to_string( memcompare_value1 ) + ' ' + to_string( memcompare_value1_operator ) ) << ' '
<< ( ( flags & flags::value2 ) == 0 ? "*" : std::to_string( memcompare_value2 ) + ' ' + to_string( memcompare_value2_operator ) ) << ' ';
}
}
}
basic_event::~basic_event() {
m_sibling = nullptr; // nie usuwać podczepionych tutaj
}
std::vector<std::string>
basic_event::target_names() const {
std::vector<std::string> names;
names.reserve( m_targets.size() );
for( auto const &target : m_targets ) {
names.emplace_back( std::get<std::string>( target ) );
}
return names;
}
void
basic_event::deserialize( cParser &Input, scene::scratch_data &Scratchpad ) {
std::string token;
Input.getTokens();
Input >> m_delay;
Input.getTokens();
Input >> token;
deserialize_targets( token );
if( m_name.starts_with("none_") ) {
m_ignored = true; // Ra: takie są ignorowane
}
deserialize_( Input, Scratchpad );
// subclass method is expected to leave next token past its own data preloaded on its exit
while( false == (token = Input.peek()).empty()
&& token != "endevent" ) {
if( token == "randomdelay" ) { // losowe opóźnienie
Input.getTokens();
Input >> m_delayrandom; // Ra 2014-03-11
}
if( token == "departuredelay" ) { // timetable-based delay
Input.getTokens();
Input >> m_delaydeparture;
}
Input.getTokens();
}
}
void
basic_event::deserialize_targets( std::string const &Input ) {
cParser targetparser { Input };
std::string target;
while( false == ( target = targetparser.getToken<std::string>( true, "|," ) ).empty() ) {
// actual bindings to targets of proper type are created during scenario initialization
if( target != "none" ) {
m_targets.emplace_back( target, nullptr );
}
}
}
void
basic_event::run( TDynamicObject const *Activator ) {
WriteLog( "EVENT LAUNCHED" + ( Activator ? " by " + Activator->asName : "" ) + ": " + m_name );
run_( Activator );
}
// sends basic content of the class in legacy (text) format to provided stream
void
basic_event::export_as_text( std::ostream &Output ) const {
// header
Output << "event ";
// name
Output << m_name << ' ';
// type
Output << type() << ' ';
// delay
Output << m_delay << ' ';
// target node(s)
if( m_targets.empty() ) {
Output << "none";
}
else {
auto targetidx { 0 };
for( auto &target : m_targets ) {
auto *targetnode { std::get<scene::basic_node *>( target ) };
Output
<< ( targetnode != nullptr ? targetnode->name() : std::get<std::string>( target ) )
<< ( ++targetidx < m_targets.size() ? '|' : ' ' );
}
}
// type-specific attributes
export_as_text_( Output );
if( m_delayrandom != 0.0 ) {
Output
<< "randomdelay "
<< m_delayrandom << ' ';
}
if( false == std::isnan( m_delayrandom ) ) {
Output
<< "departuredelay "
<< m_delaydeparture << ' ';
}
// footer
Output
<< "endevent"
<< "\n";
}
void
basic_event::append( basic_event *Event ) {
// doczepienie kolejnych z tą samą nazwą
// TODO: remove recursion
if( m_sibling )
m_sibling->append( Event ); // rekurencja! - góra kilkanaście eventów będzie potrzebne
else {
m_sibling = Event;
Event->m_passive = false; // ten doczepiony może być tylko kolejkowany
}
};
// returns: true if the event should be executed immediately
bool
basic_event::is_instant() const {
return false;
}
// sends content of associated data cell to specified vehicle controller
void
basic_event::send_command( TController &Controller ) {
return;
}
// returns: true if associated data cell contains a command for vehicle controller
bool
basic_event::is_command() const {
return false;
};
std::string
basic_event::input_text() const {
// odczytanie komendy z eventu
return "";
};
TCommandType
basic_event::input_command() const {
// odczytanie komendy z eventu
return TCommandType::cm_Unknown;
};
double
basic_event::input_value( int Index ) const {
// odczytanie komendy z eventu
return 0.0;
};
glm::dvec3
basic_event::input_location() const {
// pobranie współrzędnych eventu
return glm::dvec3( 0, 0, 0 );
};
bool
basic_event::is_keyword( std::string const &Token ) {
// TODO: convert to array lookup if keyword list gets longer
return Token == "endevent"
|| Token == "randomdelay"
|| Token == "departuredelay";
}
TMemCell const *
input_event::input_data::data_cell() const {
return static_cast<TMemCell const *>( std::get<scene::basic_node *>( data_source ) );
}
TMemCell *
input_event::input_data::data_cell() {
return static_cast<TMemCell *>( std::get<scene::basic_node *>( data_source ) );
}
// prepares event for use
void
updatevalues_event::init() {
// sumowanie wartości // zmiana wartości
init_targets( simulation::Memory, "memory cell" );
// conditional data
m_conditions.bind( &m_targets );
m_conditions.init();
}
// event type string
std::string
updatevalues_event::type() const {
return (m_input.flags & flags::mode_add) == 0 ? "updatevalues" : "addvalues";
}
// deserialize() subclass details
void
updatevalues_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
Input.getTokens( 1, false ); // case sensitive
Input >> m_input.data_text;
if( m_input.data_text != "*" ) { //"*" - nie brac command pod uwage
m_input.flags |= flags::text;
}
Input.getTokens();
if( Input.peek() != "*" ) { //"*" - nie brac val1 pod uwage
Input >> m_input.data_value_1;
m_input.flags |= flags::value1;
}
Input.getTokens();
if( Input.peek() != "*" ) { //"*" - nie brac val2 pod uwage
Input >> m_input.data_value_2;
m_input.flags |= flags::value2;
}
Input.getTokens();
// optional blocks
std::string token;
while( false == (token = Input.peek()).empty()
&& false == is_keyword(token) ) {
if( token == "condition" ) {
m_conditions.deserialize( Input );
// NOTE: condition deserialization leaves preloaded next token
}
}
}
// run() subclass details
// TODO: update and copy values run_ methods are largely identical, refactor to a single helper
void
updatevalues_event::run_( TDynamicObject const *Activator ) {
if( false == m_conditions.test() ) { return; }
WriteLog( "Type: " + std::string( m_input.flags & flags::mode_add ? "AddValues" : "UpdateValues" ) + " & Track command - ["
+ ( m_input.flags & flags::text ? m_input.data_text : "X" ) + "] ["
+ ( m_input.flags & flags::value1 ? to_string( m_input.data_value_1, 2 ) : "X" ) + "] ["
+ ( m_input.flags & flags::value2 ? to_string( m_input.data_value_2, 2 ) : "X" ) + "]" );
for( auto &target : m_targets ) {
auto *targetcell { static_cast<TMemCell *>( std::get<scene::basic_node *>( target ) ) };
if( targetcell == nullptr ) { continue; }
targetcell->UpdateValues(
m_input.data_text,
m_input.data_value_1,
m_input.data_value_2,
m_input.flags );
WriteLog( " Memcell: " + targetcell->name() + " - " + targetcell->Values() );
// targetcell->LogValues();
if( targetcell->Track == nullptr ) { continue; }
// McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla wszystkich 'dynamic' na danym torze
for( auto vehicle : targetcell->Track->Dynamics ) {
if( vehicle->Mechanik ) {
WriteLog( " Vehicle: [" + vehicle->name() + "]" );
targetcell->PutCommand(
vehicle->Mechanik,
&targetcell->location() );
}
}
}
map::Objects.poi_dirty = true; // it could potentially change map icons
}
// export_as_text() subclass details
void
updatevalues_event::export_as_text_( std::ostream &Output ) const {
Output
<< ( ( m_input.flags & flags::text ) == 0 ? "*" : m_input.data_text ) << ' '
<< ( ( m_input.flags & flags::value1 ) == 0 ? "*" : std::to_string( m_input.data_value_1 ) ) << ' '
<< ( ( m_input.flags & flags::value2 ) == 0 ? "*" : std::to_string( m_input.data_value_2 ) ) << ' ';
m_conditions.export_as_text( Output );
}
// returns: true if the event should be executed immediately
bool
updatevalues_event::is_instant() const {
return (m_input.flags & flags::mode_add) != 0 && m_delay == 0.0;
}
// prepares event for use
void
getvalues_event::init() {
init_targets( simulation::Memory, "memory cell" );
// custom target initialization code
for( auto &target : m_targets ) {
auto *targetcell { static_cast<TMemCell *>( std::get<scene::basic_node *>( target ) ) };
if( targetcell == nullptr ) { continue; }
if( targetcell->IsVelocity() ) {
// jeśli odczyt komórki a komórka zawiera komendę SetVelocity albo ShuntVelocity
// to event nie będzie dodawany do kolejki
m_passive = true;
}
}
if( m_targets.empty() ) {
m_ignored = true;
return;
}
// NOTE: GetValues retrieves data only from first specified memory cell
// TBD, TODO: allow retrieval from more than one cell?
m_input.data_source = m_targets.front();
}
// event type string
std::string
getvalues_event::type() const {
return "getvalues";
}
// deserialize() subclass details
void
getvalues_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// nothing to do here, just preload next token
Input.getTokens();
}
// run() subclass details
void
getvalues_event::run_( TDynamicObject const *Activator ) {
auto const *cell { m_input.data_cell() };
WriteLog( "Type: GetValues - "
+ cell->name() + " - ["
+ cell->Text() + "] ["
+ to_string( cell->Value1(), 2 ) + "] ["
+ to_string( cell->Value2(), 2 ) + "]" );
if( Activator == nullptr ) { return; }
cell->PutCommand(
Activator->Mechanik,
&cell->location() );
// potwierdzenie wykonania dla serwera (odczyt semafora już tak nie działa)
if( Global.iMultiplayer ) {
multiplayer::WyslijEvent( m_name, Activator->name() );
}
}
// export_as_text() subclass details
void
getvalues_event::export_as_text_( std::ostream &Output ) const {
// nothing to do here
}
// sends content of associated data cell to specified vehicle controller
void
getvalues_event::send_command( TController &Controller ) {
Controller.PutCommand( input_text(), input_value( 1 ), input_value( 2 ), nullptr );
m_input.data_cell()->StopCommandSent(); // komenda z komórki została wysłana
}
// returns: true if associated data cell contains a command for vehicle controller
bool
getvalues_event::is_command() const {
// info o komendzie z komórki
return m_input.data_cell()->StopCommand();
}
// input data access
std::string
getvalues_event::input_text() const {
return m_input.data_cell()->Text();
}
TCommandType
getvalues_event::input_command() const {
return m_input.data_cell()->Command();
}
double
getvalues_event::input_value( int Index ) const {
Index &= 1; // tylko 1 albo 2 jest prawidłowy
return Index == 1 ? m_input.data_cell()->Value1() : m_input.data_cell()->Value2();
}
glm::dvec3
getvalues_event::input_location() const {
return m_input.data_cell()->location(); // współrzędne podłączonej komórki pamięci
}
// prepares event for use
void
putvalues_event::init() {
// nothing to do here
}
// event type string
std::string
putvalues_event::type() const {
return "putvalues";
}
// deserialize() subclass details
void
putvalues_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
Input.getTokens( 3 );
// location, previously held in param 3, 4, 5
Input
>> m_input.location.x
>> m_input.location.y
>> m_input.location.z;
// przesunięcie
// tmp->pCenter.RotateY(aRotate.y/180.0*M_PI); //Ra 2014-11: uwzględnienie rotacji
// TBD, TODO: take into account rotation as well?
if( false == Scratchpad.location.offset.empty() ) {
m_input.location += Scratchpad.location.offset.top();
}
std::string token;
Input.getTokens( 1, false ); // komendy 'case sensitive'
Input >> token;
// command type, previously held in param 6
if( token.starts_with("PassengerStopPoint:") ) {
if( contains( token, '#' ) ) {
token.erase( token.find( '#' ) ); // obcięcie unikatowości
}
win1250_to_ascii( token ); // get rid of non-ascii chars
m_input.command_type = TCommandType::cm_PassengerStopPoint;
// nie do kolejki (dla SetVelocity też, ale jak jest do toru dowiązany)
m_passive = true;
}
else if( token == "SetVelocity" ) {
m_input.command_type = TCommandType::cm_SetVelocity;
m_passive = true;
}
else if( token == "RoadVelocity" ) {
m_input.command_type = TCommandType::cm_RoadVelocity;
m_passive = true;
}
else if( token == "SectionVelocity" ) {
m_input.command_type = TCommandType::cm_SectionVelocity;
m_passive = true;
}
else if( token == "ShuntVelocity" ) {
m_input.command_type = TCommandType::cm_ShuntVelocity;
m_passive = true;
}
else if( token == "OutsideStation" ) {
m_input.command_type = TCommandType::cm_OutsideStation;
m_passive = true; // ma być skanowny, aby AI nie przekraczało W5
}
else if( token == "CabSignal" ) {
m_input.command_type = TCommandType::cm_SecuritySystemMagnet;
m_passive = true;
}
else {
m_input.command_type = TCommandType::cm_Unknown;
}
// update data, previously stored in params 0, 1, 2
m_input.data_text = token;
Input.getTokens();
if( Input.peek() != "none" ) {
Input >> m_input.data_value_1;
}
Input.getTokens();
if( Input.peek() != "none" ) {
Input >> m_input.data_value_2;
}
// preload next token
Input.getTokens();
}
// run() subclass details
void
putvalues_event::run_( TDynamicObject const *Activator ) {
WriteLog(
"Type: PutValues - ["
+ m_input.data_text + "] ["
+ to_string( m_input.data_value_1, 2 ) + "] ["
+ to_string( m_input.data_value_2, 2 ) + "]" );
if( Activator == nullptr ) { return; }
// zamiana, bo fizyka ma inaczej niż sceneria
// NOTE: y & z swap, negative x
TLocation const loc {
-m_input.location.x,
m_input.location.z,
m_input.location.y };
if( Activator->Mechanik ) {
// przekazanie rozkazu do AI
WriteLog( " Vehicle: [" + Activator->Mechanik->Vehicle()->name() + "]" );
Activator->Mechanik->PutCommand(
m_input.data_text,
m_input.data_value_1,
m_input.data_value_2,
loc );
}
else if( Activator->ctOwner
&& is_command_for_owner(m_input) ) {
// send the command to consist owner,
// we're acting on presumption there's hardly ever need to issue command to unmanned vehicle
// and the intended recipient moved between vehicles after the event was queued
WriteLog( " Vehicle: [" + Activator->ctOwner->Vehicle()->name() + "]" );
Activator->ctOwner->PutCommand(
m_input.data_text,
m_input.data_value_1,
m_input.data_value_2,
loc );
}
else {
// przekazanie do pojazdu
WriteLog( " Vehicle: [" + Activator->name() +"]" );
Activator->MoverParameters->PutCommand(
m_input.data_text,
m_input.data_value_1,
m_input.data_value_2,
loc );
}
}
// export_as_text() subclass details
void
putvalues_event::export_as_text_( std::ostream &Output ) const {
Output
// location
<< m_input.location.x << ' '
<< m_input.location.y << ' '
<< m_input.location.z << ' '
// command
<< m_input.data_text << ' '
<< m_input.data_value_1 << ' '
<< m_input.data_value_2 << ' ';
}
//determines whether provided input should be passed to consist owner
bool
putvalues_event::is_command_for_owner( input_data const &Input ) const {
if (Input.data_text.starts_with("Load=") || Input.data_text.starts_with("UnLoad=")) {
return false;
}
// TBD, TODO: add other exceptions
return true;
}
// input data access
std::string
putvalues_event::input_text() const {
return m_input.data_text;
}
TCommandType
putvalues_event::input_command() const {
return m_input.command_type; // komenda zakodowana binarnie
}
double
putvalues_event::input_value( int Index ) const {
Index &= 1; // tylko 1 albo 2 jest prawidłowy
return Index == 1 ? m_input.data_value_1 : m_input.data_value_2;
}
glm::dvec3
putvalues_event::input_location() const {
return m_input.location;
}
// prepares event for use
void
copyvalues_event::init() {
// skopiowanie komórki do innej
init_targets( simulation::Memory, "memory cell" );
// source cell
std::get<scene::basic_node *>( m_input.data_source ) = simulation::Memory.find( std::get<std::string>( m_input.data_source ) );
if( std::get<scene::basic_node *>( m_input.data_source ) == nullptr ) {
m_ignored = true; // deaktywacja
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") can't find memory cell \"" + std::get<std::string>( m_input.data_source ) + "\"" );
}
}
// event type string
std::string
copyvalues_event::type() const {
return "copyvalues";
}
// deserialize() subclass details
void
copyvalues_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
m_input.flags = flags::text | flags::value1 | flags::value2; // normalnie trzy
std::string token;
int paramidx { 0 };
while( true == Input.getTokens()
&& false == (token = Input.peek()).empty()
&& false == is_keyword(token) ) {
Input >> token;
switch( ++paramidx ) {
case 1: { // nazwa drugiej komórki (źródłowej) // previously stored in param 9
std::get<std::string>( m_input.data_source ) = token;
break;
}
case 2: { // maska wartości
m_input.flags = stol_def( token, flags::text | flags::value1 | flags::value2 );
break;
}
default: {
break;
}
}
}
}
// run() subclass details
// TODO: update and copy values run_ methods are largely identical, refactor to a single helper
void
copyvalues_event::run_( TDynamicObject const *Activator ) {
// skopiowanie wartości z innej komórki
auto const *datasource { static_cast<TMemCell *>( std::get<scene::basic_node *>( m_input.data_source ) ) };
m_input.data_text = datasource->Text();
m_input.data_value_1 = datasource->Value1();
m_input.data_value_2 = datasource->Value2();
WriteLog( "Type: CopyValues - ["
+ ( m_input.flags & flags::text ? m_input.data_text : "X" ) + "] ["
+ ( m_input.flags & flags::value1 ? to_string( m_input.data_value_1, 2 ) : "X" ) + "] ["
+ ( m_input.flags & flags::value2 ? to_string( m_input.data_value_2, 2 ) : "X" ) + "]" );
// TODO: dump status of target cells after the operation
for( auto &target : m_targets ) {
auto *targetcell { static_cast<TMemCell *>( std::get<scene::basic_node *>( target ) ) };
if( targetcell == nullptr ) { continue; }
targetcell->UpdateValues(
m_input.data_text,
m_input.data_value_1,
m_input.data_value_2,
m_input.flags );
WriteLog( " Memcell: " + targetcell->name() + " - " + targetcell->Values() );
// targetcell->LogValues();
if( targetcell->Track == nullptr ) { continue; }
// McZapkie-100302 - updatevalues oprocz zmiany wartosci robi putcommand dla wszystkich 'dynamic' na danym torze
auto const location { targetcell->location() };
for( auto vehicle : targetcell->Track->Dynamics ) {
if( vehicle->Mechanik ) {
WriteLog( " Vehicle: [" + vehicle->name() + "]" );
targetcell->PutCommand(
vehicle->Mechanik,
&location );
}
}
}
}
// export_as_text() subclass details
void
copyvalues_event::export_as_text_( std::ostream &Output ) const {
auto const *datasource { static_cast<TMemCell *>( std::get<scene::basic_node *>( m_input.data_source ) ) };
Output
<< ( datasource != nullptr ?
datasource->name() :
std::get<std::string>( m_input.data_source ) )
<< ' ' << ( m_input.flags & ( flags::text | flags::value1 | flags::value2 ) ) << ' ';
}
// prepares event for use
void
whois_event::init() {
init_targets( simulation::Memory, "memory cell" );
}
// event type string
std::string
whois_event::type() const {
return "whois";
}
// deserialize() subclass details
void
whois_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
m_input.flags = flags::text | flags::value1 | flags::value2; // normalnie trzy
std::string token;
int paramidx { 0 };
while( true == Input.getTokens()
&& false == (token = Input.peek()).empty()
&& false == is_keyword(token) ) {
Input >> token;
switch( ++paramidx ) {
case 1: { // maska wartości
m_input.flags = stol_def( token, flags::text | flags::value1 | flags::value2 );
break;
}
default: {
break;
}
}
}
}
// run() subclass details
void
whois_event::run_( TDynamicObject const *Activator ) {
if( Activator == nullptr ) {
// no triggering vehicle to report on - most likely whois was launched from a
// scenery event chain rather than a track/isolation crossing
WriteLog( "Type: WhoIs (" + std::to_string( m_input.flags ) + ") - no activator, event ignored" );
return;
}
for( auto &target : m_targets ) {
auto *targetcell { static_cast<TMemCell *>( std::get<scene::basic_node *>( target ) ) };
if( targetcell == nullptr ) { continue; }
// event effect code
// +40: next station name, unused, stop at next station (duplicate of +0 2nd numeric value)
// +32: vehicle name
// +24: vehicle type, consist brake level, obstacle distance
// +16: load type, load amount, max load amount
// +8: destination, direction, engine power
// +0: train name, station count, stop on next station
if( m_input.flags & flags::whois_name ) {
// +32 or +40
// next station name
if( m_input.flags & flags::mode_alt ) {
auto const *owner { (
Activator->Mechanik != nullptr && Activator->Mechanik->primary() ?
Activator->Mechanik :
Activator->ctOwner ) };
auto const nextstop { (
owner != nullptr ?
owner->TrainTimetable().NextStop() :
"none" ) };
auto const isstop { (
owner != nullptr && owner->IsStop() ?
1 :
0 ) }; // 1, gdy ma tu zatrzymanie
targetcell->UpdateValues(
nextstop, // next station name
0, // unused
isstop, // stop at next station or passthrough
m_input.flags & ( flags::text | flags::value1 | flags::value2 ) );
WriteLog(
"Type: WhoIs (" + std::to_string( m_input.flags ) + ") - "
+ "[next station: " + nextstop + "], "
+ "[X], "
+ "[stop at next station: " + ( isstop != 0 ? "yes" : "no" ) + "]" );
}
// vehicle name
else {
targetcell->UpdateValues(
Activator->asName, // vehicle name
0, // unused
0, // unused
m_input.flags & ( flags::text | flags::value1 | flags::value2 ) );
WriteLog(
"Type: WhoIs (" + std::to_string( m_input.flags ) + ") - "
+ "[name: " + Activator->asName + "], "
+ "[X], "
+ "[X]" );
}
}
else if( m_input.flags & flags::whois_load ) {
// +16 or +24
// jeśli pytanie o ładunek
if( m_input.flags & flags::mode_alt ) {
// jeśli typ pojazdu
// TODO: define and recognize individual request types
auto const owner { (
Activator->Mechanik != nullptr && Activator->Mechanik->primary() ?
Activator->Mechanik :
Activator->ctOwner ) };
auto const consistbrakelevel { (
owner != nullptr ?
owner->fReady :
-1.0 ) };
auto const collisiondistance { (
owner != nullptr ?
owner->TrackObstacle() :
-1.0 ) };
targetcell->UpdateValues(
Activator->MoverParameters->TypeName, // typ pojazdu
consistbrakelevel,
collisiondistance,
m_input.flags & ( flags::text | flags::value1 | flags::value2 ) );
WriteLog(
"Type: WhoIs (" + std::to_string( m_input.flags ) + ") - "
+ "[type: " + Activator->MoverParameters->TypeName + "], "
+ "[consist brake level: " + to_string( consistbrakelevel, 2 ) + "], "
+ "[obstacle distance: " + to_string( collisiondistance, 2 ) + " m]" );
}
else {
// jeśli parametry ładunku
targetcell->UpdateValues(
Activator->MoverParameters->LoadType.name, // nazwa ładunku
Activator->MoverParameters->LoadAmount, // aktualna ilość
Activator->MoverParameters->MaxLoad, // maksymalna ilość
m_input.flags & ( flags::text | flags::value1 | flags::value2 ) );
WriteLog(
"Type: WhoIs (" + std::to_string( m_input.flags ) + ") - "
+ "[load type: " + Activator->MoverParameters->LoadType.name + "], "
+ "[current load: " + to_string( Activator->MoverParameters->LoadAmount, 2 ) + "], "
+ "[max load: " + to_string( Activator->MoverParameters->MaxLoad, 2 ) + "]" );
}
}
// +8
else if( m_input.flags & flags::mode_alt ) { // jeśli miejsce docelowe pojazdu
targetcell->UpdateValues(
Activator->asDestination, // adres docelowy
Activator->DirectionGet(), // kierunek pojazdu względem czoła składu (1=zgodny,-1=przeciwny)
Activator->MoverParameters->Power, // moc pojazdu silnikowego: 0 dla wagonu
m_input.flags & ( flags::text | flags::value1 | flags::value2 ) );
WriteLog(
"Type: WhoIs (" + std::to_string( m_input.flags ) + ") - "
+ "[destination: " + Activator->asDestination + "], "
+ "[direction: " + std::to_string( Activator->DirectionGet() ) + "], "
+ "[engine power: " + to_string( Activator->MoverParameters->Power, 2 ) + "]" );
}
// +0
else {
// same owner selection as the +8/+16/+24/+32/+40/+48 branches above: fall back to
// the consist's controlling vehicle if the activator itself isn't one (e.g. a wagon
// triggered the event ahead of its locomotive) - +0 used to require the activator's
// own Mechanik and silently produce nothing for a wagon-triggered whois
auto const *owner { (
Activator->Mechanik != nullptr && Activator->Mechanik->primary() ?
Activator->Mechanik :
Activator->ctOwner ) };
if( owner != nullptr ) {
targetcell->UpdateValues(
owner->TrainName(),
owner->StationCount() - owner->StationIndex(), // ile przystanków do końca
owner->IsStop() ?
1 :
0, // 1, gdy ma tu zatrzymanie
m_input.flags & ( flags::text | flags::value1 | flags::value2 ) );
WriteLog(
"Type: WhoIs (" + std::to_string( m_input.flags ) + ") - "
+ "[train: " + owner->TrainName() + "], "
+ "[stations left: " + std::to_string( owner->StationCount() - owner->StationIndex() ) + "], "
+ "[stop at next station: " + ( owner->IsStop() ? "yes" : "no") + "]" );
}
}
}
}
// export_as_text() subclass details
void
whois_event::export_as_text_( std::ostream &Output ) const {
Output << ( m_input.flags & ( flags::text | flags::value1 | flags::value2 ) ) << ' ';
}
// prepares event for use
void
logvalues_event::init() {
init_targets( simulation::Memory, "memory cell" );
}
// event type string
std::string
logvalues_event::type() const {
return "logvalues";
}
// deserialize() subclass details
void
logvalues_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// nothing to do here, just preload next token
Input.getTokens();
}
// run() subclass details
void
logvalues_event::run_( TDynamicObject const *Activator ) {
// zapisanie zawartości komórki pamięci do logu
if( m_targets.empty() ) {
// lista wszystkich
simulation::Memory.log_all();
}
else {
// jeśli była podana nazwa komórki
for( auto &target : m_targets ) {
auto *targetcell { static_cast<TMemCell *>( std::get<scene::basic_node *>( target ) ) };
if( targetcell == nullptr ) { continue; }
targetcell->LogValues();
}
}
}
// export_as_text() subclass details
void
logvalues_event::export_as_text_( std::ostream &Output ) const {
}
// prepares event for use
void
multi_event::init() {
auto const conditiontchecksmemcell { ( m_conditions.flags & ( flags::text | flags::value1 | flags::value2 ) ) != 0 };
// not all multi-events have memory cell checks, for the ones which don't we can keep quiet about it
init_targets( simulation::Memory, "memory cell", conditiontchecksmemcell );
if( m_ignored ) {
// legacy compatibility behaviour, instead of disabling the event we disable the memory cell comparison test
m_conditions.flags &= ~( flags::text | flags::value1 | flags::value2 );
m_ignored = false;
}
// conditional data
m_conditions.bind( &m_targets );
m_conditions.init();
// child events bindings
for( auto &childevent : m_children ) {
std::get<basic_event *>( childevent ) = simulation::Events.FindEvent( std::get<std::string>( childevent ) );
if( std::get<basic_event *>( childevent ) == nullptr ) {
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") can't find event \"" + std::get<std::string>( childevent ) + "\"" );
}
}
}
std::vector<std::string> multi_event::dump_children_names() const {
std::vector<std::string> result;
for (auto const &childevent : m_children) {
result.push_back(std::get<std::string>(childevent));
}
return result;
}
// event type string
std::string
multi_event::type() const {
return "multiple";
}
// deserialize() subclass details
void
multi_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
m_conditions.has_else = false;
std::string token;
while( true == Input.getTokens()
&& false == (token = Input.peek()).empty()
&& false == is_keyword(token) ) {
if( token == "condition" ) {
m_conditions.deserialize( Input );
// NOTE: condition block comes last so we can bail out afterwards
break;
}
else if( token == "else" ) {
// zmiana flagi dla słowa "else"
m_conditions.has_else = !m_conditions.has_else;
}
else {
// potentially valid event name
if( token.starts_with("none_") ) {
// eventy rozpoczynające się od "none_" są ignorowane
WriteLog( "Multi-event \"" + m_name + "\" ignored link to event \"" + token + "\"" );
}
else {
m_children.emplace_back( token, nullptr, m_conditions.has_else == false );
}
}
}
}
// run() subclass details
void
multi_event::run_( TDynamicObject const *Activator ) {
auto const conditiontest { m_conditions.test() };
if( conditiontest
|| m_conditions.has_else ) {
// warunek spelniony albo było użyte else
WriteLog( "Type: Multi-event" );
for( auto &childwrapper : m_children ) {
auto *childevent { std::get<basic_event*>( childwrapper ) };
if( childevent == nullptr ) { continue; }
if( std::get<bool>( childwrapper ) != conditiontest ) { continue; }
if( childevent != this ) {
// normalnie dodać
simulation::Events.AddToQuery( childevent, Activator );
}
else {
// jeśli ma być rekurencja to musi mieć sensowny okres powtarzania
if( m_delay >= 5.0 ) {
simulation::Events.AddToQuery( this, Activator );
}
}
}
if( Global.iMultiplayer ) {
// dajemy znać do serwera o wykonaniu
if( false == m_conditions.has_else ) {
// jednoznaczne tylko, gdy nie było else
if( Activator ) {
multiplayer::WyslijEvent( m_name, Activator->name() );
}
else {
multiplayer::WyslijEvent( m_name, "" );
}
}
}
}
}
// export_as_text() subclass details
void
multi_event::export_as_text_( std::ostream &Output ) const {
for( auto const &childevent : m_children ) {
if( true == std::get<bool>( childevent ) ) {
auto *childeventdata { std::get<basic_event *>( childevent ) };
Output
<< ( childeventdata != nullptr ?
childeventdata->m_name :
std::get<std::string>( childevent ) )
<< ' ';
}
}
// optional 'else' block
if( true == m_conditions.has_else ) {
Output << "else ";
for( auto const &childevent : m_children ) {
if( false == std::get<bool>( childevent ) ) {
auto *childeventdata{ std::get<basic_event *>( childevent ) };
Output
<< ( childeventdata != nullptr ?
childeventdata->m_name :
std::get<std::string>( childevent ) )
<< ' ';
}
}
}
m_conditions.export_as_text( Output );
}
// prepares event for use
void
sound_event::init() {
// odtworzenie dźwięku
for( auto &target : m_sounds ) {
std::get<sound_source *>( target ) = simulation::Sounds.find( std::get<std::string>( target ) );
if( std::get<sound_source *>( target ) == nullptr ) {
m_ignored = true; // deaktywacja
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") can't find static sound \"" + std::get<std::string>( target ) + "\"" );
}
}
}
// event type string
std::string
sound_event::type() const {
return "sound";
}
// deserialize() subclass details
void
sound_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
Input.getTokens();
// playback mode, previously held in param 0 // 0: wylaczyc, 1: wlaczyc; -1: wlaczyc zapetlone
Input >> m_soundmode;
Input.getTokens();
if( false == is_keyword( Input.peek() ) ) {
// optional parameter, radio channel to receive/broadcast the sound, previously held in param 1
Input >> m_soundradiochannel;
Input.getTokens(); // preload next token
}
}
// run() subclass details
void
sound_event::run_( TDynamicObject const *Activator ) {
WriteLog(
"Type: Sound - [" + std::string( m_soundmode == 1 ? "play" : m_soundmode == -1 ? "loop" : "stop" ) + "]"
+ ( m_soundradiochannel > 0 ? " [channel " + std::to_string( m_soundradiochannel ) + "]" : "" ) );
for( auto &target : m_sounds ) {
auto *targetsound = std::get<sound_source *>( target );
if( targetsound == nullptr ) { continue; }
// event effect code
switch( m_soundmode ) {
// trzy możliwe przypadki:
case 0: {
targetsound->stop();
break;
}
case 1: {
if( m_soundradiochannel > 0 ) {
simulation::radio_message(
targetsound,
m_soundradiochannel );
}
else {
targetsound->play( sound_flags::exclusive | sound_flags::event );
}
break;
}
case -1: {
targetsound->play( sound_flags::exclusive | sound_flags::looping | sound_flags::event );
break;
}
default: {
break;
}
}
}
}
// export_as_text() subclass details
void
sound_event::export_as_text_( std::ostream &Output ) const {
// playback mode
Output << m_soundmode << ' ';
// optional radio channel
if( m_soundradiochannel > 0 ) {
Output << m_soundradiochannel << ' ';
}
}
void
sound_event::deserialize_targets( std::string const &Input ) {
// sound objects don't inherit from scene node, so we can't use the same container
// TODO: fix this, having sounds as scene nodes (or a node wrapper for one) might have benefits elsewhere
cParser targetparser{ Input };
std::string target;
while( false == ( target = targetparser.getToken<std::string>( true, "|," ) ).empty() ) {
// actual bindings to targets of proper type are created during scenario initialization
if( target != "none" ) {
m_sounds.emplace_back( target, nullptr );
}
}
}
TMemCell const *
texture_event::input_data::data_cell() const {
return static_cast<TMemCell const *>( std::get<scene::basic_node *>( data_source ) );
}
TMemCell *
texture_event::input_data::data_cell() {
return static_cast<TMemCell *>( std::get<scene::basic_node *>( data_source ) );
}
// prepares event for use
void
texture_event::init() {
// target models
init_targets( simulation::Instances, "model instance" );
// optional input data memory cell
TMemCell *inputcell { nullptr };
auto const inputcellname { std::get<std::string>( m_input.data_source ) };
if( inputcellname != "none" ) {
inputcell = simulation::Memory.find( inputcellname );
if( inputcell == nullptr ) {
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") can't find memory cell \"" + inputcellname + "\"" );
}
}
std::get<scene::basic_node *>( m_input.data_source ) = inputcell;
}
// event type string
std::string
texture_event::type() const {
return "texture";
}
// deserialize() subclass details
void
texture_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
Input.getTokens( 3 );
Input
>> m_skinindex
>> m_skin
>> std::get<std::string>( m_input.data_source );
// validate input
if( m_skinindex < 0 ) {
// intercept potential error, index specified using .t3d format convention
m_skinindex *= -1;
}
m_skinindex = std::clamp( m_skinindex, 1, 4 ); // TODO: define-based upper bound in case of future extension
Input.getTokens(); // preload next token
}
// run() subclass details
void
texture_event::run_( TDynamicObject const *Activator ) {
material_handle material { null_handle };
if( m_input.data_cell() == nullptr ) {
// straightforward variant without parameters, we can pass expression directly
material = GfxRenderer->Fetch_Material( m_skin );
}
else {
// variant with memory cell: pass the cell content as parameters and generate filename
cParser expression(
m_skin, cParser::buffer_TEXT,
"", true,
{ m_input.data_cell()->Text(),
to_string( m_input.data_cell()->Value1(), 0 ), // memory cell values are passed as ints
to_string( m_input.data_cell()->Value2(), 0 ) } );
material = GfxRenderer->Fetch_Material( expression.getToken<std::string>( false, "\n\r" ) );
}
// assign received material to target models
for( auto &target : m_targets ) {
auto *targetmodel = static_cast<TAnimModel *>( std::get<scene::basic_node *>( target ) );
if( targetmodel == nullptr ) { continue; }
// event effect code
targetmodel->SkinSet( m_skinindex, material );
}
}
// export_as_text() subclass details
void
texture_event::export_as_text_( std::ostream &Output ) const {
// playback mode
Output
<< m_skinindex << ' '
<< m_skin << ' '
<< std::get<std::string>( m_input.data_source ) << ' ';
}
// destructor
animation_event::~animation_event() {
if( m_animationtype == 4 ) {
// jeśli z pliku VMD
SafeDeleteArray( m_animationfiledata ); // zwolnić obszar
}
}
// prepares event for use
void
animation_event::init() {
// animacja modelu
init_targets( simulation::Instances, "model instance" );
// custom target initialization code
if( m_animationtype == 4 ) {
// vmd animations target whole model
return;
}
// locate and set up animated submodels
m_animationcontainers.clear();
for( auto &target : m_targets ) {
auto *targetmodel { static_cast<TAnimModel *>( std::get<scene::basic_node *>( target ) ) };
if( targetmodel == nullptr ) { continue; }
auto targetcontainer{ targetmodel->GetContainer( m_animationsubmodel ) };
if( !targetcontainer ) {
m_ignored = true;
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") can't find submodel " + m_animationsubmodel + " in model instance \"" + targetmodel->name() + "\"" );
break;
}
targetcontainer->WillBeAnimated(); // oflagowanie animacji
if( targetcontainer->Event() == nullptr ) {
// nie szukać, gdy znaleziony
targetcontainer->EventAssign( simulation::Events.FindEvent( targetmodel->name() + '.' + m_animationsubmodel + ":done" ) );
}
m_animationcontainers.emplace_back( targetcontainer );
}
}
// event type string
std::string
animation_event::type() const {
return "animation";
}
// deserialize() subclass details
void
animation_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
std::string token;
Input.getTokens();
Input >> token;
if( token == "rotate" ) { // obrót względem osi
Input.getTokens();
// animation submodel, previously held in param 9
Input >> m_animationsubmodel;
// animation type, previously held in param 0
m_animationtype = 1;
Input.getTokens( 4 );
// animation params, previously held in param 1, 2, 3, 4
Input
>> m_animationparams[ 0 ]
>> m_animationparams[ 1 ]
>> m_animationparams[ 2 ]
>> m_animationparams[ 3 ];
}
else if( token == "translate" ) { // przesuw o wektor
Input.getTokens();
// animation submodel, previously held in param 9
Input >> m_animationsubmodel;
// animation type, previously held in param 0
m_animationtype = 2;
Input.getTokens( 4 );
// animation params, previously held in param 1, 2, 3, 4
Input
>> m_animationparams[ 0 ]
>> m_animationparams[ 1 ]
>> m_animationparams[ 2 ]
>> m_animationparams[ 3 ];
}
else if( token == "digital" ) { // licznik cyfrowy
Input.getTokens();
// animation submodel, previously held in param 9
Input >> m_animationsubmodel;
// animation type, previously held in param 0
m_animationtype = 8;
Input.getTokens( 4 );
// animation params, previously held in param 1, 2, 3, 4
Input
>> m_animationparams[ 0 ]
>> m_animationparams[ 1 ]
>> m_animationparams[ 2 ]
>> m_animationparams[ 3 ];
}
else if( token.ends_with(".vmd") ) // na razie tu, może będzie inaczej
{ // animacja z pliku VMD
{
m_animationfilename = token;
std::ifstream file( paths::models + m_animationfilename, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
auto size = file.tellg(); // ios::ate already positioned us at the end of the file
file.seekg( 0, std::ios::beg ); // rewind the caret afterwards
// animation size, previously held in param 7
m_animationfilesize = size;
// animation data, previously held in param 8
m_animationfiledata = new char[ size ];
file.read( m_animationfiledata, size ); // wczytanie pliku
}
Input.getTokens();
// animation submodel, previously held in param 9
Input >> m_animationsubmodel;
// animation type, previously held in param 0
m_animationtype = 4;
Input.getTokens( 4 );
// animation params, previously held in param 1, 2, 3, 4
Input
>> m_animationparams[ 0 ]
>> m_animationparams[ 1 ]
>> m_animationparams[ 2 ]
>> m_animationparams[ 3 ];
}
// preload next token
Input.getTokens();
}
// run() subclass details
void
animation_event::run_( TDynamicObject const *Activator ) {
WriteLog( "Type: Animation" );
// animation modes target specific submodels
m_animationcontainers.remove_if([this](std::weak_ptr<TAnimContainer> ptr)
{
auto targetcontainer = ptr.lock();
if (!targetcontainer)
return true;
switch( m_animationtype ) {
case 1: { // rotate
targetcontainer->SetRotateAnim(
glm::make_vec3( m_animationparams.data() ),
m_animationparams[ 3 ] );
break;
}
case 2: { // translate
targetcontainer->SetTranslateAnim(
glm::make_vec3( m_animationparams.data() ),
m_animationparams[ 3 ] );
break;
}
// TODO: implement digital mode
default: {
break;
}
}
return false;
});
}
// export_as_text() subclass details
void
animation_event::export_as_text_( std::ostream &Output ) const {
// animation type
Output << (
m_animationtype == 1 ? "rotate" :
m_animationtype == 2 ? "translate" :
m_animationtype == 4 ? m_animationfilename :
m_animationtype == 8 ? "digital" :
"none" )
<< ' ';
// submodel
Output << m_animationsubmodel << ' ';
// animation parameters
Output
<< m_animationparams[ 0 ] << ' '
<< m_animationparams[ 1 ] << ' '
<< m_animationparams[ 2 ] << ' '
<< m_animationparams[ 3 ] << ' ';
}
// prepares event for use
void
lights_event::init() {
// zmiana świeteł modelu
init_targets( simulation::Instances, "model instance" );
}
// event type string
std::string
lights_event::type() const {
return "lights";
}
// deserialize() subclass details
void
lights_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// TBD, TODO: remove light count limit?
auto const lightcountlimit { 8 };
m_lights.resize( lightcountlimit );
int lightidx { 0 };
std::string token;
while( true == Input.getTokens()
&& false == (token = Input.peek()).empty()
&& false == is_keyword(token) ) {
if( lightidx < lightcountlimit ) {
Input >> m_lights[ lightidx++ ];
}
else {
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") with more than " + std::to_string( lightcountlimit ) + " parameters" );
}
}
while( lightidx < lightcountlimit ) {
// HACK: mark unspecified lights with magic value
m_lights[ lightidx++ ] = std::numeric_limits<float>::quiet_NaN();
}
}
// run() subclass details
void
lights_event::run_( TDynamicObject const *Activator ) {
for( auto &target : m_targets ) {
auto *targetmodel = static_cast<TAnimModel *>( std::get<scene::basic_node *>( target ) );
if( targetmodel == nullptr ) { continue; }
// event effect code
for( auto lightidx = 0; lightidx < iMaxNumLights; ++lightidx ) {
if( std::isnan( m_lights[ lightidx ] ) ) {
// processed all supplied values, bail out
break;
}
if( m_lights[ lightidx ] != -1.f ) {
// -1 zostawia bez zmiany
targetmodel->LightSet(
lightidx,
m_lights[ lightidx ] );
}
}
}
}
// export_as_text() subclass details
void
lights_event::export_as_text_( std::ostream &Output ) const {
auto lightidx{ 0 };
while( lightidx < iMaxNumLights
&& false == std::isnan(m_lights[lightidx]) ) {
Output << m_lights[ lightidx ] << ' ';
++lightidx;
}
}
// prepares event for use
void
switch_event::init() {
// przełożenie zwrotnicy albo zmiana stanu obrotnicy
init_targets( simulation::Paths, "track" );
// custom target initialization code
for( auto &target : m_targets ) {
auto *targettrack = static_cast<TTrack *>( std::get<scene::basic_node *>( target ) );
if( targettrack == nullptr ) { continue; }
// dowiązanie toru
if( targettrack->iAction == 0 ) {
// jeśli nie jest zwrotnicą ani obrotnicą to będzie się zmieniał stan uszkodzenia
targettrack->iAction |= 0x100;
}
if( m_switchstate == 0
&& m_switchmovedelay >= 0.0 ) {
// jeśli przełącza do stanu 0 & jeśli jest zdefiniowany dodatkowy ruch iglic
// przesłanie parametrów
targettrack->Switch(
m_switchstate,
m_switchmoverate,
m_switchmovedelay );
}
}
}
// event type string
std::string
switch_event::type() const {
return "switch";
}
// deserialize() subclass details
void
switch_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
Input.getTokens();
// switch state, previously held in param 0
Input >> m_switchstate;
Input.getTokens();
if( false == is_keyword( Input.peek() ) ) {
// prędkość liniowa ruchu iglic
// previously held in param 1
Input >> m_switchmoverate;
Input.getTokens();
}
if( false == is_keyword( Input.peek() ) ) {
// dodatkowy ruch drugiej iglicy (zamknięcie nastawnicze)
// previously held in param 2
Input >> m_switchmovedelay;
Input.getTokens();
}
}
// run() subclass details
void
switch_event::run_( TDynamicObject const *Activator ) {
for( auto &target : m_targets ) {
auto *targettrack { static_cast<TTrack *>( std::get<scene::basic_node *>( target ) ) };
if( targettrack == nullptr ) { continue; }
// event effect code
targettrack->Switch(
m_switchstate,
m_switchmoverate,
m_switchmovedelay );
}
if( Global.iMultiplayer ) {
// dajemy znać do serwera o przełożeniu
multiplayer::WyslijEvent( m_name, "" ); // wysłanie nazwy eventu przełączajacego
}
// Ra: bardziej by się przydała nazwa toru, ale nie ma do niej stąd dostępu
}
// export_as_text() subclass details
void
switch_event::export_as_text_( std::ostream &Output ) const {
Output << m_switchstate << ' ';
if( m_switchmoverate < 0.f
|| m_switchmovedelay < 0.f ) {
Output << m_switchmoverate << ' ';
}
if( m_switchmovedelay < 0.f ) {
Output << m_switchmovedelay << ' ';
}
}
// prepares event for use
void
track_event::init() {
// ustawienie prędkości na torze
init_targets( simulation::Paths, "track" );
// custom target initialization code
for( auto &target : m_targets ) {
auto *targettrack = static_cast<TTrack *>( std::get<scene::basic_node *>( target ) );
if( targettrack == nullptr ) { continue; }
// flaga zmiany prędkości toru jest istotna dla skanowania
targettrack->iAction |= 0x200;
}
}
// event type string
std::string
track_event::type() const {
return "trackvel";
}
// deserialize() subclass details
void
track_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
Input.getTokens();
Input >> m_velocity;
// preload next token
Input.getTokens();
}
// run() subclass details
void
track_event::run_( TDynamicObject const *Activator ) {
WriteLog( "Type: TrackVel - [" + to_string( m_velocity, 2 ) + "]" );
for( auto &target : m_targets ) {
auto *targettrack = static_cast<TTrack *>( std::get<scene::basic_node *>( target ) );
if( targettrack == nullptr ) { continue; }
// event effect code
targettrack->VelocitySet( m_velocity );
if( DebugModeFlag ) {
WriteLog( "actual track velocity for " + targettrack->name() + " [" + to_string( targettrack->VelocityGet(), 2 ) + "]" );
}
}
}
// export_as_text() subclass details
void
track_event::export_as_text_( std::ostream &Output ) const {
Output << m_velocity << ' ';
}
// prepares event for use
void
voltage_event::init() {
// zmiana napięcia w zasilaczu (TractionPowerSource)
init_targets( simulation::Powergrid, "power source" );
}
// event type string
std::string
voltage_event::type() const {
return "voltage";
}
// deserialize() subclass details
void
voltage_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// zmiana napięcia w zasilaczu (TractionPowerSource)
Input.getTokens();
Input >> m_voltage;
// preload next token
Input.getTokens();
}
// run() subclass details
void
voltage_event::run_( TDynamicObject const *Activator ) {
// zmiana napięcia w zasilaczu (TractionPowerSource)
WriteLog( "Type: Voltage [" + to_string( m_voltage, 2 ) + "]" );
for( auto &target : m_targets ) {
auto *targetpowersource = static_cast<TTractionPowerSource *>( std::get<scene::basic_node *>( target ) );
if( targetpowersource == nullptr ) { continue; }
// na razie takie chamskie ustawienie napięcia zasilania
targetpowersource->VoltageSet( m_voltage );
}
}
// export_as_text() subclass details
void
voltage_event::export_as_text_( std::ostream &Output ) const {
Output << m_voltage << ' ';
}
// prepares event for use
void
visible_event::init() {
// ukrycie albo przywrócenie obiektu
for( auto &target : m_targets ) {
auto &targetnode{ std::get<scene::basic_node *>( target ) };
auto &targetname{ std::get<std::string>( target ) };
// najpierw model
targetnode = simulation::Instances.find( targetname );
if( targetnode == nullptr ) {
// albo tory?
targetnode = simulation::Paths.find( targetname );
}
if( targetnode == nullptr ) {
// może druty?
targetnode = simulation::Traction.find( targetname );
}
if( targetnode == nullptr ) {
m_ignored = true; // deaktywacja
ErrorLog( "Bad event: \"" + m_name + "\" (type: " + type() + ") can't find item \"" + std::get<std::string>( target ) + "\"" );
}
}
}
// event type string
std::string
visible_event::type() const {
return "visible";
}
// deserialize() subclass details
void
visible_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// zmiana wyświetlania obiektu
Input.getTokens();
Input >> m_visible;
// preload next token
Input.getTokens();
}
// run() subclass details
void
visible_event::run_( TDynamicObject const *Activator ) {
for( auto &target : m_targets ) {
auto *targetnode = std::get<scene::basic_node *>( target );
if( targetnode == nullptr ) { continue; }
// event effect code
targetnode->visible( m_visible );
}
}
// export_as_text() subclass details
void
visible_event::export_as_text_( std::ostream &Output ) const {
Output << ( m_visible ? 1 : 0 ) << ' ';
}
// prepares event for use
void
friction_event::init() {
// nothing to do here
}
// event type string
std::string
friction_event::type() const {
return "friction";
}
// deserialize() subclass details
void
friction_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// zmiana przyczepnosci na scenerii
Input.getTokens();
Input >> m_friction;
// preload next token
Input.getTokens();
}
// run() subclass details
void
friction_event::run_( TDynamicObject const *Activator ) {
// zmiana tarcia na scenerii
WriteLog( "Type: Friction" );
Global.fFriction = m_friction;
}
// export_as_text() subclass details
void
friction_event::export_as_text_( std::ostream &Output ) const {
Output << m_friction << ' ';
}
#ifdef WITH_LUA
lua_event::lua_event(lua_State *L, const int ref) {
lua_state = L;
lua_func = ref;
}
lua_event::~lua_event() {
lua::unref(lua_state, lua_func);
}
// prepares event for use
void lua_event::init() {
// nothing to do here
}
// event type string
std::string lua_event::type() const {
return "lua";
}
// deserialize() subclass details
void lua_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// preload next token
Input.getTokens();
}
// run() subclass details
void lua_event::run_( TDynamicObject const *Activator ) {
try {
if (lua_func != LUA_NOREF)
lua::dispatch_event(lua_state, lua_func, this, Activator);
} catch (...) {
ErrorLog("lua: Runtime error: " + simulation::Lua.get_error());
}
}
// export_as_text() subclass details
void lua_event::export_as_text_( std::ostream &Output ) const {
// nothing to do here
}
bool lua_event::is_instant() const {
return m_delay == 0.0 && m_delayrandom == 0.0;
}
#endif
// prepares event for use
void
message_event::init() {
// nothing to do here
}
// event type string
std::string
message_event::type() const {
return "message";
}
// deserialize() subclass details
void
message_event::deserialize_( cParser &Input, scene::scratch_data &Scratchpad ) {
// wyświetlenie komunikatu
std::string token;
while( true == Input.getTokens()
&& false == (token = Input.peek()).empty()
&& false == is_keyword(token) ) {
m_message += ( m_message.empty() ? "" : " " ) + token;
}
}
// run() subclass details
void
message_event::run_( TDynamicObject const *Activator ) {
// TODO: implement
}
// export_as_text() subclass details
void
message_event::export_as_text_( std::ostream &Output ) const {
Output << '\"' << m_message << '\"' << ' ';
}
//---------------------------------------------------------------------------
basic_event *
make_event( cParser &Input, scene::scratch_data &Scratchpad ) {
// getToken() already lowercases ASCII letters via the parser's toLowerChar(),
// which deliberately leaves bytes >= 0x80 untouched. The extra ToLower() here
// ran std::tolower() over every byte, so under a non-"C" global locale it
// remapped cp1250 uppercase accented letters (e.g. 0xAF -> 0xBF) - producing a
// registered name that no lookup site (Track::AssignEvents, child-event and
// launcher resolution) could match, since those normalize with the parser only.
auto const name = Input.getToken<std::string>();
auto const type = Input.getToken<std::string>();
basic_event *event { nullptr };
if( type == "addvalues" ) { event = new updatevalues_event(); }
else if( type == "updatevalues" ) { event = new updatevalues_event(); }
else if( type == "copyvalues" ) { event = new copyvalues_event(); }
else if( type == "getvalues" ) { event = new getvalues_event(); }
else if( type == "putvalues" ) { event = new putvalues_event(); }
else if( type == "whois" ) { event = new whois_event(); }
else if( type == "logvalues" ) { event = new logvalues_event(); }
else if( type == "multiple" ) { event = new multi_event(); }
else if( type == "switch" ) { event = new switch_event(); }
else if( type == "trackvel" ) { event = new track_event(); }
else if( type == "sound" ) { event = new sound_event(); }
else if( type == "texture" ) { event = new texture_event(); }
else if( type == "animation" ) { event = new animation_event(); }
else if( type == "lights" ) { event = new lights_event(); }
else if( type == "voltage" ) { event = new voltage_event(); }
else if( type == "visible" ) { event = new visible_event(); }
else if( type == "friction" ) { event = new friction_event(); }
else if( type == "message" ) { event = new message_event(); }
if( event == nullptr ) {
ErrorLog( "Bad event: unrecognized type \"" + type + "\" specified for event \"" + name + "\"." );
return event;
}
event->m_name = name;
if( type == "addvalues" ) {
static_cast<updatevalues_event*>( event )->m_input.flags = basic_event::flags::mode_add;
}
return event;
}
//---------------------------------------------------------------------------
event_manager::~event_manager() {
for( auto *event : m_events ) {
delete event;
}
}
// adds specified event launcher to the list of global launchers
void
event_manager::queue( TEventLauncher *Launcher ) {
m_launcherqueue.emplace_back( Launcher );
}
// inserts in the event query events assigned to event launchers capable of receiving specified radio message sent from specified location
void
event_manager::queue_receivers( radio_message const Message, glm::dvec3 const &Location ) {
for( auto *launcher : m_radiodrivenlaunchers.sequence() ) {
if( launcher->key() == Message
&& ( launcher->dRadius < 0
|| glm::length2(launcher->location() - Location) < launcher->dRadius )
&& true == launcher->check_conditions() ) {
// NOTE: only execution of event1 is supported for radio messages
// TBD, TODO: consider ability/way to execute event2
simulation::Events.AddToQuery( launcher->Event1, nullptr );
}
}
}
// legacy method, updates event queues
void
event_manager::update() {
// process currently queued events
CheckQuery();
// test list of global events for possible new additions to the queue
for( auto *launcher : m_launcherqueue ) {
if (launcher->check_conditions() && launcher->Event1) {
// NOTE: we're presuming global events aren't going to use event2
if (launcher->check_activation()) {
WriteLog( "Eventlauncher: " + launcher->name() );
AddToQuery( launcher->Event1, nullptr );
}
if (launcher->check_activation_key()) {
WriteLog( "Eventlauncher: " + launcher->name() );
m_relay.post(user_command::queueevent, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &launcher->Event1->name());
}
}
}
}
// adds provided event to the collection. returns: true on success
// TBD, TODO: return handle instead of pointer
bool
event_manager::insert( basic_event *Event ) {
// najpierw sprawdzamy, czy nie ma, a potem dopisujemy
auto lookup = m_eventmap.find( Event->m_name );
if( lookup != m_eventmap.end() ) {
// duplicate of already existing event
auto const size = Event->m_name.size();
// zawsze jeden znak co najmniej jest
if( Event->m_name[ 0 ] == '#' ) {
// utylizacja duplikatu z krzyżykiem
return false;
}
// tymczasowo wyjątki:
else if( Event->m_name.ends_with("lineinfo:") ) {
// tymczasowa utylizacja duplikatów W5
return false;
}
else if( Event->m_name.ends_with("_warning") ) {
// tymczasowa utylizacja duplikatu z trąbieniem
return false;
}
else if( Event->m_name.ends_with("_shp") ) {
// nie podlegają logowaniu
// tymczasowa utylizacja duplikatu SHP
return false;
}
auto *duplicate = m_events[ lookup->second ];
if( Global.bJoinEvents ) {
// doczepka (taki wirtualny multiple bez warunków)
duplicate->append( Event );
}
else {
// NOTE: somewhat convoluted way to deal with 'replacing' events without leaving dangling pointers
// can be cleaned up if pointers to events were replaced with handles
ErrorLog( "Bad scenario: duplicate event name \"" + Event->m_name + "\"" );
duplicate->append( Event ); // doczepka (taki wirtualny multiple bez warunków)
duplicate->m_ignored = true; // dezaktywacja pierwotnego - taka proteza na wsteczną zgodność
}
}
// the event's identity, fixed for its lifetime; the queue refers to it by this
Event->m_index = static_cast<std::uint32_t>( m_events.size() );
m_events.emplace_back( Event );
if( lookup == m_eventmap.end() ) {
// if it's first event with such name, it's potential candidate for the execution queue
m_eventmap.emplace( Event->m_name, m_events.size() - 1 );
if( Event->m_ignored != true
&& contains(Event->m_name, "onstart") ) {
// event uruchamiany automatycznie po starcie
AddToQuery( Event, nullptr );
}
}
return true;
}
basic_event * event_manager::FindEventById(uint32_t id)
{
if (id < m_events.size())
return m_events[id];
else
return nullptr;
}
uint32_t event_manager::GetEventId(const basic_event *ev) {
return GetEventId(ev->m_name);
}
uint32_t event_manager::GetEventId(const std::string &Name)
{
if (Name.empty())
return -1;
auto const lookup = m_eventmap.find(Name);
return lookup != m_eventmap.end() ? lookup->second : -1;
}
// legacy method, returns pointer to specified event, or null
basic_event *
event_manager::FindEvent( std::string const &Name )
{
return FindEventById(GetEventId(Name));
}
// legacy method, inserts specified event in the event query
bool
event_manager::AddToQuery( basic_event *Event, TDynamicObject const *Owner, double delay ) {
if( Event->m_passive ) { return false; } // not queued at all: scanning uses it in place
if( true == pending( Event ) ) { return false; } // already waiting its turn
if( Event->is_instant() ) {
// AddValues and its kind have to run on the spot, or the queue loses additions
if( false == Event->m_ignored ) {
Event->run( Owner );
}
// an event sharing its name goes to the queue instead; skip the ones that cannot
do {
Event = Event->m_sibling;
} while( ( Event != nullptr )
&& ( Event->m_passive || pending( Event ) ) );
}
if( ( Event == nullptr ) || ( true == Event->m_ignored ) ) { return true; }
WriteLog( "EVENT ADDED TO QUEUE" + ( Owner ? " by " + Owner->asName : "" ) + ": " + Event->m_name );
auto launchtime { delay + std::abs( Event->m_delay ) + Timer::GetTime() };
if( Event->m_delayrandom > 0.0 ) {
launchtime += Event->m_delayrandom * Random();
}
if( ( Owner != nullptr ) && ( false == std::isnan( Event->m_delaydeparture ) ) ) {
auto const *timetableowner { (
Owner->Mechanik != nullptr && Owner->Mechanik->primary() ?
Owner->Mechanik :
Owner->ctOwner ) };
if( timetableowner != nullptr ) {
auto const &timetable { timetableowner->TrainTimetable() };
auto const &time { simulation::Time.data() };
launchtime +=
timetable.seconds_until_departure( time.wHour, time.wMinute + time.wSecond * 0.0167 )
+ Event->m_delaydeparture;
}
}
// departure-based delays can work out negative overall
launchtime = std::max( launchtime, 0.0 );
push( { launchtime, Event->m_index, Owner } );
return true;
}
// orders the queue: soonest first, and among events due at the same instant the one that
// appears first in the scenery. ties broken by index rather than by insertion order, so
// the same scenario runs the same way twice
bool
event_manager::later( queued_event const &Left, queued_event const &Right ) {
if( Left.launchtime != Right.launchtime ) { return Left.launchtime > Right.launchtime; }
return Left.index > Right.index;
}
bool
event_manager::pending( basic_event const *Event ) const {
return ( Event->m_index < m_queued.size() ) && ( m_queued[ Event->m_index ] != 0 );
}
void
event_manager::push( queued_event const &Entry ) {
if( Entry.index >= m_queued.size() ) { m_queued.resize( Entry.index + 1, 0 ); }
m_queued[ Entry.index ] = 1;
m_queue.emplace_back( Entry );
std::push_heap( std::begin( m_queue ), std::end( m_queue ), later );
}
std::uint32_t
event_manager::sibling_of( std::uint32_t const Index ) const {
// every event knows its own position, so following the sibling chain costs a pointer
// hop rather than a search
auto const *event { event_at( Index ) };
if( ( event == nullptr ) || ( event->m_sibling == nullptr ) ) {
return static_cast<std::uint32_t>( m_events.size() );
}
return event->m_sibling->m_index;
}
std::vector<event_manager::queued_event>
event_manager::queue_snapshot() const {
auto ordered { m_queue };
std::sort( std::begin( ordered ), std::end( ordered ),
[]( queued_event const &Left, queued_event const &Right ) { return later( Right, Left ); } );
return ordered;
}
bool
event_manager::CheckQuery() {
while( ( false == m_queue.empty() )
&& ( m_queue.front().launchtime < Timer::GetTime() ) ) {
std::pop_heap( std::begin( m_queue ), std::end( m_queue ), later );
auto const entry { m_queue.back() };
m_queue.pop_back();
m_queued[ entry.index ] = 0;
m_workevent = event_at( entry.index );
if( m_workevent == nullptr ) { continue; }
// an event sharing this one's name follows it at the same instant. it goes
// through the queue like any other, rather than being spliced into the list head
auto const *sibling { m_workevent->m_sibling };
if( ( sibling != nullptr ) && ( false == sibling->m_passive ) && ( false == pending( sibling ) ) ) {
push( { entry.launchtime, sibling->m_index, entry.activator } );
}
if( ( false == m_workevent->m_ignored ) && ( false == m_workevent->m_passive ) ) {
m_workevent->run( entry.activator );
}
}
return true;
}
// legacy method, initializes events after deserialization from scenario file
void
event_manager::InitEvents() {
// how many events a scenery carries, which is worth knowing: they are not written by
// hand but expanded from parameterised includes, a dozen or more per signal
WriteLog( "Events: " + std::to_string( m_events.size() ) + " defined" );
//łączenie eventów z pozostałymi obiektami
for( auto *event : m_events ) {
event->init();
if( event->m_delay < 0 ) { AddToQuery( event, nullptr ); }
}
}
// legacy method, initializes event launchers after deserialization from scenario file
void
event_manager::InitLaunchers() {
std::vector<basic_table<TEventLauncher> *> launchertables {
&m_inputdrivenlaunchers,
&m_radiodrivenlaunchers
};
for( auto *launchertable : launchertables ) {
for( auto *launcher : launchertable->sequence() ) {
if( launcher->iCheckMask != 0 ) {
if( launcher->asMemCellName != "none" ) {
// jeśli jest powiązana komórka pamięci
launcher->MemCell = simulation::Memory.find( launcher->asMemCellName );
if( launcher->MemCell == nullptr ) {
ErrorLog( "Bad scenario: event launcher \"" + launcher->name() + "\" can't find memcell \"" + launcher->asMemCellName + "\"" );
}
}
else {
launcher->MemCell = nullptr;
}
}
if( launcher->asEvent1Name != "none" ) {
launcher->Event1 = simulation::Events.FindEvent( launcher->asEvent1Name );
if( launcher->Event1 == nullptr ) {
ErrorLog( "Bad scenario: event launcher \"" + launcher->name() + "\" can't find event \"" + launcher->asEvent1Name + "\"" );
}
}
if( launcher->asEvent2Name != "none" ) {
launcher->Event2 = simulation::Events.FindEvent( launcher->asEvent2Name );
if( launcher->Event2 == nullptr ) {
ErrorLog( "Bad scenario: event launcher \"" + launcher->name() + "\" can't find event \"" + launcher->asEvent2Name + "\"" );
}
}
}
}
}
// sends basic content of the class in legacy (text) format to provided stream
void
event_manager::export_as_text( std::ostream &Output ) const {
Output << "// events\n";
for( auto const *event : m_events ) {
if( event->group() == null_handle ) {
event->export_as_text( Output );
}
}
Output << "// event launchers, basic\n";
for( auto const *launcher : m_inputdrivenlaunchers.sequence() ) {
if( launcher->group() == null_handle ) {
launcher->export_as_text( Output );
}
}
Output << "// event launchers, radio driven\n";
for( auto const *launcher : m_radiodrivenlaunchers.sequence() ) {
if( launcher->group() == null_handle ) {
launcher->export_as_text( Output );
}
}
}
std::vector<TEventLauncher*> event_manager::find_eventlaunchers(glm::vec2 center, float radius) const {
std::vector<TEventLauncher *> results;
for (auto &launcher : m_inputdrivenlaunchers.sequence()) {
glm::dvec3 location = launcher->location();
if (glm::distance2(glm::vec2(location.x, location.z), center) < radius)
results.push_back(launcher);
}
return results;
}