16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-20 11:29:18 +02:00

build 200126. memory cell-based simulation state interface , time of day slider, minor bug fixes

This commit is contained in:
tmj-fstate
2020-01-26 16:47:33 +01:00
parent 0526833e42
commit a1e8f6e24d
20 changed files with 256 additions and 53 deletions

View File

@@ -81,6 +81,7 @@ struct global_settings {
float fFogEnd{ 7500 };
std::string Season{}; // season of the year, based on simulation date
std::string Weather{ "cloudy:" }; // current weather
std::string Period{}; // time of the day, based on sun position
bool FullPhysics{ true }; // full calculations performed for each simulation step
bool bnewAirCouplers{ true };
float fMoveLight{ -1.f }; // numer dnia w roku albo -1

View File

@@ -1381,6 +1381,7 @@ public:
double MainsInitTime{ 0.0 }; // config, initialization time (in seconds) of the main circuit after it receives power, before it can be closed
double MainsInitTimeCountdown{ 0.0 }; // current state of main circuit initialization, remaining time (in seconds) until it's ready
bool LineBreakerClosesAtNoPowerPosOnly{ false };
bool HasPressureSwitch{ true };
int MainCtrlPos = 0; /*polozenie glownego nastawnika*/
int ScndCtrlPos = 0; /*polozenie dodatkowego nastawnika*/
int LightsPos = 0; /*polozenie przelacznika wielopozycyjnego swiatel*/

View File

@@ -6222,9 +6222,8 @@ bool TMoverParameters::AutoRelayCheck(void)
// main bez samoczynnego rozruchu
if( ( MainCtrlActualPos < ( sizeof( RList ) / sizeof( TScheme ) - 1 ) ) // crude guard against running out of current fixed table
&& ( ( RList[ MainCtrlActualPos ].Relay < MainCtrlPos )
|| ( RList[ MainCtrlActualPos + 1 ].Relay == MainCtrlPos )
|| ( ( TrainType == dt_ET22 )
&& ( DelayCtrlFlag ) ) ) ) {
|| ( ( RList[ MainCtrlActualPos + 1 ].Relay == MainCtrlPos ) && ( MainCtrlActualPos < RlistSize ) )
|| ( ( TrainType == dt_ET22 ) && ( DelayCtrlFlag ) ) ) ) {
if( ( RList[MainCtrlPos].R == 0 )
&& ( MainCtrlPos > 0 )
@@ -6359,7 +6358,7 @@ bool TMoverParameters::AutoRelayCheck(void)
&& ( ( MainCtrlActualPos > 0 )
|| ( ScndCtrlActualPos > 0 ) ) ) {
if( CoupledCtrl || HasCamshaft ) {
if( CoupledCtrl ) {
if( TrainType == dt_EZT ) {
// EN57 wal jednokierunkowy calosciowy
@@ -6393,8 +6392,7 @@ bool TMoverParameters::AutoRelayCheck(void)
else {
// wal kulakowy dwukierunkowy
if( LastRelayTime > CtrlDownDelay ) {
if( ( CoupledCtrl )
&& ( ScndCtrlActualPos > 0 ) ) {
if( ScndCtrlActualPos > 0 ) {
--ScndCtrlActualPos;
SetFlag( SoundFlag, sound::shuntfield );
}
@@ -6405,6 +6403,16 @@ bool TMoverParameters::AutoRelayCheck(void)
}
}
}
else if( HasCamshaft ) {
// wal kulakowy dwukierunkowy
if( LastRelayTime > CtrlDownDelay ) {
if( MainCtrlActualPos > 0 ) {
--MainCtrlActualPos;
}
ScndCtrlActualPos = 0;
OK = true;
}
}
else {
MainCtrlActualPos = 0;
ScndCtrlActualPos = 0;
@@ -6424,6 +6432,7 @@ bool TMoverParameters::MotorConnectorsCheck() {
// hunter-111211: wylacznik cisnieniowy
auto const pressureswitch {
( TrainType != dt_EZT )
&& ( HasPressureSwitch )
&& ( ( BrakePress > 2.0 )
|| ( PipePress < 3.6 ) ) };
@@ -10090,6 +10099,8 @@ void TMoverParameters::LoadFIZ_Engine( std::string const &Input ) {
// traction motors
extract_value( MotorBlowers[ end::front ].speed, "MotorBlowersSpeed", Input, "" );
MotorBlowers[ end::rear ] = MotorBlowers[ end::front ];
// pressure switch
extract_value( HasPressureSwitch, "PressureSwitch", Input, "" );
}
void TMoverParameters::LoadFIZ_Switches( std::string const &Input ) {

View File

@@ -249,6 +249,7 @@ TTrain::commandhandler_map const TTrain::m_commandhandlers = {
{ user_command::pantographlowerall, &TTrain::OnCommand_pantographlowerall },
{ user_command::pantographselectnext, &TTrain::OnCommand_pantographselectnext },
{ user_command::pantographselectprevious, &TTrain::OnCommand_pantographselectprevious },
{ user_command::pantographtoggleselected, &TTrain::OnCommand_pantographtoggleselected },
{ user_command::pantographraiseselected, &TTrain::OnCommand_pantographraiseselected },
{ user_command::pantographlowerselected, &TTrain::OnCommand_pantographlowerselected },
{ user_command::linebreakertoggle, &TTrain::OnCommand_linebreakertoggle },
@@ -2224,6 +2225,37 @@ void TTrain::OnCommand_pantographselectprevious( TTrain *Train, command_data con
Train->change_pantograph_selection( -1 );
}
void TTrain::OnCommand_pantographtoggleselected( TTrain *Train, command_data const &Command ) {
if( Command.action == GLFW_REPEAT ) { return; }
if( Command.action == GLFW_PRESS ) {
// only reacting to press, so the switch doesn't flip back and forth if key is held down
auto const state {
Train->mvControlled->PantsValve.is_enabled
| Train->mvControlled->PantsValve.is_active }; // fallback for impulse switches
if( state ) {
OnCommand_pantographlowerselected( Train, Command );
}
else {
OnCommand_pantographraiseselected( Train, Command );
}
}
else if( Command.action == GLFW_RELEASE ) {
// impulse switches return automatically to neutral position
if( Train->ggPantSelectedButton.type() != TGaugeType::toggle ) {
Train->mvControlled->OperatePantographsValve( operation_t::enable_off );
// visual feedback
Train->ggPantSelectedButton.UpdateValue( 0.0, Train->dsbSwitch );
}
if( Train->ggPantSelectedDownButton.type() != TGaugeType::toggle ) {
Train->mvControlled->OperatePantographsValve( operation_t::disable_off );
// visual feedback
Train->ggPantSelectedDownButton.UpdateValue( 0.0, Train->dsbSwitch );
}
}
}
void TTrain::OnCommand_pantographraiseselected( TTrain *Train, command_data const &Command ) {
if( Command.action == GLFW_REPEAT ) { return; }
@@ -2231,18 +2263,15 @@ void TTrain::OnCommand_pantographraiseselected( TTrain *Train, command_data cons
if( Command.action == GLFW_PRESS ) {
// raise selected
Train->mvControlled->OperatePantographsValve(
Train->ggPantSelectedButton.type() == TGaugeType::toggle ?
operation_t::enable :
operation_t::enable_on );
Train->ggPantSelectedButton.type() != TGaugeType::toggle ?
operation_t::enable_on :
operation_t::enable );
// visual feedback
Train->ggPantSelectedButton.UpdateValue( 1.0, Train->dsbSwitch );
}
else if( Command.action == GLFW_RELEASE ) {
if( Train->ggPantSelectedButton.type() != TGaugeType::toggle ) {
Train->mvControlled->OperatePantographsValve( operation_t::enable_off );
// visual feedback
Train->ggPantSelectedButton.UpdateValue( 0.0, Train->dsbSwitch );
}
// NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
OnCommand_pantographtoggleselected( Train, Command );
}
}
@@ -2253,18 +2282,18 @@ void TTrain::OnCommand_pantographlowerselected( TTrain *Train, command_data cons
if( Command.action == GLFW_PRESS ) {
// lower selected
Train->mvControlled->OperatePantographsValve(
Train->ggPantSelectedDownButton.type() == TGaugeType::toggle ?
operation_t::disable :
operation_t::disable_on );
Train->ggPantSelectedDownButton.type() != TGaugeType::toggle ?
operation_t::disable_on :
operation_t::disable );
// visual feedback
Train->ggPantSelectedDownButton.UpdateValue( 1.0, Train->dsbSwitch );
if( Train->ggPantSelectedButton.type() == TGaugeType::toggle ) {
Train->ggPantSelectedButton.UpdateValue( 0.0, Train->dsbSwitch );
}
}
else if( Command.action == GLFW_RELEASE ) {
if( Train->ggPantSelectedDownButton.type() != TGaugeType::toggle ) {
Train->mvControlled->OperatePantographsValve( operation_t::disable_off );
// visual feedback
Train->ggPantSelectedDownButton.UpdateValue( 0.0, Train->dsbSwitch );
}
// NOTE: bit of a hax here, we're reusing button reset routine so we don't need a copy in every branch
OnCommand_pantographtoggleselected( Train, Command );
}
}
@@ -5682,7 +5711,8 @@ bool TTrain::Update( double const Deltatime )
&& ( false == DynamicObject->Mechanik->AIControllFlag ) ) {
// nie blokujemy AI
if( ( mvOccupied->TrainType == dt_ET40 )
|| ( mvOccupied->TrainType == dt_EP05 ) ) {
|| ( mvOccupied->TrainType == dt_EP05 )
|| ( mvOccupied->HasCamshaft ) ) {
// dla ET40 i EU05 automatyczne cofanie nastawnika - i tak nie będzie to działać dobrze...
// TODO: use deltatime to stabilize speed
/*
@@ -5691,7 +5721,7 @@ bool TTrain::Update( double const Deltatime )
|| ( input::command == user_command::mastercontrollerincrease )
|| ( input::command == user_command::mastercontrollerdecrease ) ) ) {
*/
if( false == m_mastercontrollerinuse ) {
if( false == ( m_mastercontrollerinuse || Global.ctrlState ) ) {
m_mastercontrollerreturndelay -= Deltatime;
if( m_mastercontrollerreturndelay < 0.f ) {
m_mastercontrollerreturndelay = EU07_CONTROLLER_BASERETURNDELAY;

View File

@@ -257,6 +257,7 @@ class TTrain {
static void OnCommand_pantographlowerall( TTrain *Train, command_data const &Command );
static void OnCommand_pantographselectnext( TTrain *Train, command_data const &Command );
static void OnCommand_pantographselectprevious( TTrain *Train, command_data const &Command );
static void OnCommand_pantographtoggleselected( TTrain *Train, command_data const &Command );
static void OnCommand_pantographraiseselected( TTrain *Train, command_data const &Command );
static void OnCommand_pantographlowerselected( TTrain *Train, command_data const &Command );
static void OnCommand_linebreakertoggle( TTrain *Train, command_data const &Command );

View File

@@ -255,6 +255,14 @@ eu07_application::set_progress( float const Progress, float const Subtaskprogres
m_modes[ m_modestack.top() ]->set_progress( Progress, Subtaskprogress );
}
void
eu07_application::set_tooltip( std::string const &Tooltip ) {
if( m_modestack.empty() ) { return; }
m_modes[ m_modestack.top() ]->set_tooltip( Tooltip );
}
void
eu07_application::set_cursor( int const Mode ) {
@@ -288,7 +296,16 @@ eu07_application::get_mouse_button( int const Button ) const {
return glfwGetMouseButton( m_windows.front(), Button );
}
/*
// provides keyboard mapping associated with specified control item
std::string
eu07_application::get_input_hint( user_command const Command ) const {
if( m_modestack.empty() ) { return ""; }
return m_modes[ m_modestack.top() ]->get_input_hint( Command );
}
*/
void
eu07_application::on_key( int const Key, int const Scancode, int const Action, int const Mods ) {

View File

@@ -52,6 +52,8 @@ public:
set_title( std::string const &Title );
void
set_progress( float const Progress = 0.f, float const Subtaskprogress = 0.f );
void
set_tooltip( std::string const &Tooltip );
void
set_cursor( int const Mode );
void
@@ -62,6 +64,11 @@ public:
get_cursor_pos( double &Horizontal, double &Vertical ) const;
int
get_mouse_button( int const Button ) const;
/*
// provides inputs associated with specified command
std::string
get_input_hint( user_command const Command ) const;
*/
// input handlers
void
on_key( int const Key, int const Scancode, int const Action, int const Mods );

View File

@@ -180,6 +180,7 @@ commanddescription_sequence Commands_descriptions = {
{ "pantographlowerall", command_target::vehicle },
{ "pantographselectnext", command_target::vehicle },
{ "pantographselectprevious", command_target::vehicle },
{ "pantographtoggleselected", command_target::vehicle },
{ "pantographraiseselected", command_target::vehicle },
{ "pantographlowerselected", command_target::vehicle },
{ "heatingtoggle", command_target::vehicle },

View File

@@ -173,6 +173,7 @@ enum class user_command {
pantographlowerall,
pantographselectnext,
pantographselectprevious,
pantographtoggleselected,
pantographraiseselected,
pantographlowerselected,
heatingtoggle,

View File

@@ -182,8 +182,9 @@ driverkeyboard_input::default_bindings() {
{ user_command::pantographlowerall, GLFW_KEY_P | keymodifier::control },
{ user_command::pantographselectnext, GLFW_KEY_P | keymodifier::shift },
{ user_command::pantographselectprevious, GLFW_KEY_O | keymodifier::shift },
{ user_command::pantographraiseselected, GLFW_KEY_O | keymodifier::shift | keymodifier::control },
{ user_command::pantographlowerselected, GLFW_KEY_O | keymodifier::control },
{ user_command::pantographtoggleselected, GLFW_KEY_O | keymodifier::shift | keymodifier::control },
// pantographraiseselected,
// pantographlowerselected,
{ user_command::heatingtoggle, GLFW_KEY_H },
// heatingenable,
// heatingdisable,

View File

@@ -151,6 +151,7 @@ driver_mode::update() {
simulation::Time.update( deltatime );
}
simulation::State.update_clocks();
simulation::State.update_scripting_interface();
simulation::Environment.update();
// fixed step, simulation time based updates

View File

@@ -793,7 +793,7 @@ drivermouse_input::default_bindings() {
user_command::pantographlowerall,
user_command::none } },
{ "pantselected_sw:", {
user_command::pantographraiseselected,
user_command::pantographtoggleselected,
user_command::none } }, // TBD: bind lowerselected in case of toggle switch
{ "pantselectedoff_sw:", {
user_command::pantographlowerselected,

View File

@@ -542,31 +542,7 @@ debug_panel::render() {
render_section( "Vehicle Engine", m_enginelines );
render_section( "Vehicle AI", m_ailines );
render_section( "Vehicle Scan Table", m_scantablelines );
if( true == render_section( "Scenario", m_scenariolines ) ) {
// fog slider
auto fogrange = std::log( Global.fFogEnd );
if( ImGui::SliderFloat(
( to_string( std::exp( fogrange ), 0 ) + " m###fogend" ).c_str(), &fogrange, std::log( 10.0f ), std::log( 25000.0f ), "Fog distance" ) ) {
Global.fFogEnd = clamp( std::exp( fogrange ), 10.0f, 25000.0f );
}
// cloud cover slider
if( ImGui::SliderFloat(
( to_string(Global.Overcast, 2 ) + " (" + Global.Weather + ")###overcast" ).c_str(), &Global.Overcast, 0.0f, 2.0f, "Cloud cover" ) ) {
Global.Overcast = clamp( Global.Overcast, 0.0f, 2.0f );
simulation::Environment.compute_weather();
}
// day of year slider
if( ImGui::SliderFloat( ( to_string( Global.fMoveLight, 0, 4 ) + " (" + Global.Season + ")###movelight" ).c_str(), &Global.fMoveLight, 0.0f, 355.0f, "Day of year" ) ) {
Global.fMoveLight = clamp( Global.fMoveLight, 0.0f, 355.0f );
auto const weather { Global.Weather };
simulation::Environment.compute_season( Global.fMoveLight );
simulation::Time.init();
if( weather != Global.Weather ) {
// HACK: force re-calculation of precipitation
Global.Overcast = clamp( Global.Overcast - 0.0001f, 0.0f, 2.0f );
}
}
}
render_section_scenario();
if( true == render_section( "Scenario Event Queue", m_eventqueuelines ) ) {
// event queue filter
ImGui::Checkbox( "By This Vehicle Only", &m_eventqueueactivevehicleonly );
@@ -584,6 +560,63 @@ debug_panel::render() {
ImGui::End();
}
bool
debug_panel::render_section_scenario() {
if( false == render_section( "Scenario", m_scenariolines ) ) { return false; }
// fog slider
{
auto fogrange = std::log( Global.fFogEnd );
if( ImGui::SliderFloat(
( to_string( std::exp( fogrange ), 0, 5 ) + " m###fogend" ).c_str(), &fogrange, std::log( 10.0f ), std::log( 25000.0f ), "Fog distance" ) ) {
Global.fFogEnd = clamp( std::exp( fogrange ), 10.0f, 25000.0f );
}
}
// cloud cover slider
{
if( ImGui::SliderFloat(
( to_string( Global.Overcast, 2, 5 ) + " (" + Global.Weather + ")###overcast" ).c_str(), &Global.Overcast, 0.0f, 2.0f, "Cloud cover" ) ) {
Global.Overcast = clamp( Global.Overcast, 0.0f, 2.0f );
simulation::Environment.compute_weather();
}
}
// day of year slider
{
if( ImGui::SliderFloat( ( to_string( Global.fMoveLight, 0, 5 ) + " (" + Global.Season + ")###movelight" ).c_str(), &Global.fMoveLight, 0.0f, 364.0f, "Day of year" ) ) {
Global.fMoveLight = clamp( Global.fMoveLight, 0.0f, 365.0f );
auto const weather{ Global.Weather };
simulation::Environment.compute_season( Global.fMoveLight );
simulation::Time.init();
if( weather != Global.Weather ) {
// HACK: force re-calculation of precipitation
Global.Overcast = clamp( Global.Overcast - 0.0001f, 0.0f, 2.0f );
}
}
}
// time of day slider
{
ImGui::PushStyleColor( ImGuiCol_Text, { Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a } );
ImGui::TextUnformatted( "CAUTION: time change will affect simulation state" );
ImGui::PopStyleColor();
auto time = simulation::Time.data().wHour * 60 + simulation::Time.data().wMinute;
auto const timestring{
std::string( to_string( int( 100 + simulation::Time.data().wHour ) ).substr( 1, 2 )
+ ":"
+ std::string( to_string( int( 100 + simulation::Time.data().wMinute ) ).substr( 1, 2 ) ) ) };
if( ImGui::SliderInt( ( timestring + " (" + Global.Period + ")###simulationtime" ).c_str(), &time, 0, 1439, "Time of day" ) ) {
time = clamp( time, 0, 1439 );
auto const hour{ std::floor( time / 60 ) };
auto const minute{ time % 60 };
simulation::Time.data().wHour = hour;
simulation::Time.data().wMinute = minute;
}
}
return true;
}
void
debug_panel::update_section_vehicle( std::vector<text_line> &Output ) {

View File

@@ -95,6 +95,7 @@ private:
std::string update_vehicle_brake() const;
// renders provided lines, under specified collapsing header
bool render_section( std::string const &Header, std::vector<text_line> const &Lines );
bool render_section_scenario();
// members
std::array<char, 1024> m_buffer;
input_data m_input;

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include "Globals.h"
#include "AnimModel.h"
#include "simulationenvironment.h"
#include "Logs.h"
void
@@ -429,6 +430,9 @@ particle_manager::find( std::string const &Template ) {
// should be 'safe enough' to return lookup result directly afterwards
return &( m_sourcetemplates.find( templatename )->second );
}
else {
ErrorLog( "Bad file: failed do locate particle source configuration file \"" + std::string( templatepath + templatename + ".txt" ) + "\"", logtype::file );
}
// if fetching data from the file fails too, give up
return nullptr;
}

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "simulation.h"
#include "simulationtime.h"
#include "simulationenvironment.h"
#include "Globals.h"
#include "Event.h"
@@ -57,6 +58,36 @@ state_manager::export_as_text( std::string const &Scenariofile ) const {
return m_serializer.export_as_text( Scenariofile );
}
void
state_manager::init_scripting_interface() {
// create scenario data memory cells
{
auto *memorycell = new TMemCell( {
0, -1,
"__simulation.weather",
"memcell" } );
simulation::Memory.insert( memorycell );
simulation::Region->insert( memorycell );
}
{
auto *memorycell = new TMemCell( {
0, -1,
"__simulation.time",
"memcell" } );
simulation::Memory.insert( memorycell );
simulation::Region->insert( memorycell );
}
{
auto *memorycell = new TMemCell( {
0, -1,
"__simulation.date",
"memcell" } );
simulation::Memory.insert( memorycell );
simulation::Region->insert( memorycell );
}
}
// legacy method, calculates changes in simulation state over specified time
void
state_manager::update( double const Deltatime, int Iterationcount ) {
@@ -87,6 +118,54 @@ state_manager::update_clocks() {
Global.fClockAngleDeg[ 5 ] = 36.0 * ( time.wHour / 10 ); // dziesiątki godzin
}
void
state_manager::update_scripting_interface() {
auto *weather{ Memory.find( "__simulation.weather" ) };
auto *time{ Memory.find( "__simulation.time" ) };
auto *date{ Memory.find( "__simulation.date" ) };
if( simulation::is_ready ) {
// potentially adjust weather
if( weather->Value1() != m_scriptinginterface.weather->Value1() ) {
Global.Overcast = clamp<float>( weather->Value1(), 0, 2 );
simulation::Environment.compute_weather();
}
if( weather->Value2() != m_scriptinginterface.weather->Value2() ) {
Global.fFogEnd = clamp<float>( weather->Value2(), 10, 25000 );
}
}
else {
m_scriptinginterface.weather = std::make_shared<TMemCell>( scene::node_data() );
m_scriptinginterface.date = std::make_shared<TMemCell>( scene::node_data() );
m_scriptinginterface.time = std::make_shared<TMemCell>( scene::node_data() );
}
// update scripting interface
weather->UpdateValues(
Global.Weather,
Global.Overcast,
Global.fFogEnd,
basic_event::flags::text | basic_event::flags::value_1 | basic_event::flags::value_2 );
time->UpdateValues(
Global.Period,
Time.data().wHour,
Time.data().wMinute,
basic_event::flags::text | basic_event::flags::value_1 | basic_event::flags::value_2 );
date->UpdateValues(
Global.Season,
Time.year_day(),
0,
basic_event::flags::text | basic_event::flags::value_1 );
// cache cell state to detect potential script-issued changes on next cycle
*m_scriptinginterface.weather = *weather;
*m_scriptinginterface.time = *time;
*m_scriptinginterface.date = *date;
}
// passes specified sound to all vehicles within range as a radio message broadcasted on specified channel
void
radio_message( sound_source *Message, int const Channel ) {

View File

@@ -18,11 +18,15 @@ class state_manager {
public:
// methods
void
init_scripting_interface();
// legacy method, calculates changes in simulation state over specified time
void
update( double Deltatime, int Iterationcount );
void
update_clocks();
void
update_scripting_interface();
// restores simulation data from specified file. returns: true on success, false otherwise
bool
deserialize( std::string const &Scenariofile );
@@ -33,6 +37,9 @@ public:
private:
// members
state_serializer m_serializer;
struct {
std::shared_ptr<TMemCell> weather, time, date;
} m_scriptinginterface;
};
// passes specified sound to all vehicles within range as a radio message broadcasted on specified channel

View File

@@ -174,6 +174,11 @@ world_environment::update() {
Global.Weather == "snow:" ? 0.75f :
1.0f );
Global.Period = (
m_sun.getAngle() > -12.0f ?
"day:" :
"night:" );
if( Global.Weather == "rain:" ) {
m_precipitationsound.play( sound_flags::exclusive | sound_flags::looping );
}

View File

@@ -38,6 +38,8 @@ state_serializer::deserialize( std::string const &Scenariofile ) {
SafeDelete( Region );
Region = new scene::basic_region();
simulation::State.init_scripting_interface();
// TODO: check first for presence of serialized binary files
// if this fails, fall back on the legacy text format
scene::scratch_data importscratchpad;

View File

@@ -1,5 +1,5 @@
#pragma once
#define VERSION_MAJOR 20
#define VERSION_MINOR 124
#define VERSION_MINOR 126
#define VERSION_REVISION 0