mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 12:09:19 +02:00
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.
2645 lines
84 KiB
C++
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;
|
|
}
|